2017-11-22 06:11:40 +00:00
package libnmdc
2017-11-23 05:23:39 +00:00
import (
2017-11-26 00:30:19 +00:00
"encoding/base32"
"fmt"
"regexp"
"strconv"
2017-11-23 05:23:39 +00:00
"strings"
)
type adcState int
const (
adcStateProtocol adcState = 0
adcStateIdentify adcState = 1
adcStateVerify adcState = 2
adcStateNormal adcState = 3
adcStateData adcState = 4
)
2017-11-22 06:11:40 +00:00
type AdcProtocol struct {
2017-11-26 00:30:19 +00:00
hc * HubConnection
state adcState
sid , pid , cid string // all in base32 encoding
supports map [ string ] struct { }
2017-11-22 06:11:40 +00:00
}
2017-11-26 00:30:19 +00:00
const (
// extra extensions that aren't flagged in SUPPORTS
adcSeparateApVe string = "SEPARATE_AP_VE" // we invented this string
)
2017-11-22 07:02:11 +00:00
func NewAdcProtocol ( hc * HubConnection ) Protocol {
2017-11-23 05:23:39 +00:00
proto := AdcProtocol {
hc : hc ,
state : adcStateProtocol ,
supports : make ( map [ string ] struct { } ) ,
}
2017-11-22 06:11:40 +00:00
2017-11-26 00:30:19 +00:00
rxPid := regexp . MustCompile ( "^[A-Z2-7]{39}$" )
if ! rxPid . MatchString ( hc . Hco . AdcPID ) {
hc . processEvent ( HubEvent { EventType : EVENT_SYSTEM_MESSAGE_FROM_CONN , Message : "Invalid custom PID, regenerating" } )
hc . Hco . AdcPID = NewPID ( )
}
pid_base32 := hc . Hco . AdcPID
cid_base32 , err := proto . pid2cid ( pid_base32 )
if err != nil {
panic ( err )
}
proto . cid = cid_base32
proto . pid = pid_base32
2017-11-22 07:02:21 +00:00
// Start logging in
2017-11-26 06:19:06 +00:00
hc . SayRaw ( "HSUP ADBASE ADTIGR ADUCMD\n" )
2017-11-22 06:22:13 +00:00
2017-11-22 06:11:40 +00:00
return & proto
}
2017-11-26 00:30:19 +00:00
func ( this * AdcProtocol ) pid2cid ( pid_base32 string ) ( string , error ) {
pid_raw , err := base32 . StdEncoding . DecodeString ( pid_base32 + "=" )
if err != nil {
return "" , err
}
2017-11-26 01:01:46 +00:00
cid_raw := Tiger ( string ( pid_raw ) )
2017-11-26 00:30:19 +00:00
cid_base32 := Base32 ( cid_raw )
return cid_base32 , nil
}
2017-11-26 05:11:25 +00:00
func ( this * AdcProtocol ) SID2Nick ( sid string ) ( string , bool ) {
this . hc . usersMut . Lock ( )
defer this . hc . usersMut . Unlock ( )
nick , ok := this . hc . userSIDs [ sid ]
return nick , ok
}
func ( this * AdcProtocol ) Nick2SID ( targetNick string ) ( string , bool ) {
this . hc . usersMut . Lock ( )
defer this . hc . usersMut . Unlock ( )
for sid , nick := range this . hc . userSIDs {
if nick == targetNick {
return sid , true
}
}
return "" , false
}
2017-11-22 06:11:40 +00:00
func ( this * AdcProtocol ) ProcessCommand ( msg string ) {
2017-11-23 05:23:39 +00:00
if len ( msg ) == 0 {
return
}
2017-11-26 01:55:30 +00:00
this . hc . processEvent ( HubEvent { EventType : EVENT_DEBUG_MESSAGE , Message : msg } )
2017-11-26 04:53:24 +00:00
2017-11-23 05:23:39 +00:00
parts := strings . Split ( msg , " " )
switch parts [ 0 ] {
case "ISUP" :
if ! ( this . state == adcStateProtocol || this . state == adcStateNormal ) {
this . malformed ( parts )
return
}
for _ , supportflag := range parts [ 1 : ] {
if len ( supportflag ) < 2 {
this . malformed ( parts )
return
}
if supportflag [ 0 : 2 ] == "AD" {
this . supports [ supportflag [ 2 : ] ] = struct { } { }
} else if supportflag [ 0 : 2 ] == "RM" {
delete ( this . supports , supportflag [ 2 : ] )
} else {
this . malformed ( parts )
return
}
}
if this . state == adcStateProtocol {
this . state = adcStateIdentify
}
case "ISID" :
if this . state != adcStateIdentify {
this . malformed ( parts )
return
}
this . sid = parts [ 1 ]
2017-11-26 04:24:08 +00:00
// State transition IDENTIFY --> VERIFY and send our own info
2018-03-24 00:21:49 +00:00
this . SayInfo ( )
2017-11-26 04:24:08 +00:00
this . state = adcStateVerify
2017-11-23 05:23:39 +00:00
case "IINF" :
2017-11-26 01:55:30 +00:00
// Hub telling information about itself
2017-11-26 04:24:08 +00:00
// ADCH++ sends this once we are successfully logged in
2017-11-26 01:55:30 +00:00
flags , err := this . parts2flags ( parts [ 1 : ] )
if err != nil {
this . logError ( err )
return
}
if flags [ "CT" ] != "32" {
this . malformed ( parts )
return
2017-11-23 05:23:39 +00:00
}
2017-11-26 01:55:30 +00:00
err = this . handleHubInfo ( flags )
if err != nil {
this . logError ( err )
return
}
2017-11-23 05:23:39 +00:00
2017-11-26 04:24:08 +00:00
if this . state != adcStateNormal {
this . enterNormalState ( ) // successful login
2017-11-23 05:23:39 +00:00
}
2017-11-26 01:55:30 +00:00
case "BINF" :
if this . state != adcStateNormal {
this . enterNormalState ( ) // successful login
}
sid := parts [ 1 ]
flags , err := this . parts2flags ( parts [ 2 : ] )
if err != nil {
this . logError ( err )
return
}
// Log this user in, and associate this SID with this user
this . hc . usersMut . Lock ( )
defer this . hc . usersMut . Unlock ( )
2017-11-26 05:38:03 +00:00
oldNick , sidExists := this . hc . userSIDs [ sid ]
uinfo := UserInfo { }
if sidExists {
uinfo_lookup , ok := this . hc . users [ oldNick ]
if ! ok {
// Shouldn't happen
this . hc . processEvent ( HubEvent {
EventType : EVENT_SYSTEM_MESSAGE_FROM_CONN ,
Message : fmt . Sprintf ( "Hub connection corrupted (missing info for SID='%s' nick='%s'), disconnecting" , sid , oldNick ) ,
} )
this . hc . Disconnect ( )
return
}
uinfo = uinfo_lookup
}
this . updateUserInfo ( & uinfo , flags )
2017-11-26 01:55:30 +00:00
newNick := uinfo . Nick
2017-11-26 05:38:03 +00:00
if len ( newNick ) == 0 {
this . logError ( fmt . Errorf ( "Zero-length nick for user (SID='%s')" , sid ) )
}
2017-11-26 01:55:30 +00:00
2017-11-26 05:38:03 +00:00
shouldHandleNewUser := false
if sidExists && oldNick != newNick {
// Nick change = delete all trace of this user first, treat as new
delete ( this . hc . users , oldNick )
delete ( this . hc . userSIDs , sid )
this . hc . processEvent ( HubEvent { EventType : EVENT_USER_PART , Nick : oldNick } )
shouldHandleNewUser = true
} else if sidExists && oldNick == newNick {
// Updating existing user
this . hc . users [ newNick ] = uinfo
this . hc . processEvent ( HubEvent { EventType : EVENT_USER_UPDATED_INFO , Nick : newNick } )
2017-11-26 01:55:30 +00:00
2017-11-26 05:38:03 +00:00
} else if ! sidExists {
// User joined
shouldHandleNewUser = true
}
//
if shouldHandleNewUser {
2017-11-26 01:55:30 +00:00
// Install this SID as pointing to this nick
this . hc . userSIDs [ sid ] = uinfo . Nick
// Check if this nick was in use by any other SID already
for otherSid , otherSidNick := range this . hc . userSIDs {
if otherSidNick == newNick && otherSid != sid {
this . hc . processEvent ( HubEvent {
EventType : EVENT_SYSTEM_MESSAGE_FROM_CONN ,
Message : fmt . Sprintf ( "Hub connection corrupted (duplicate SIDs '%s' and '%s' for nick '%s'), disconnecting" , sid , otherSid , newNick ) ,
} )
this . hc . Disconnect ( )
return
}
}
// Notifications
2017-11-26 05:38:03 +00:00
this . hc . users [ newNick ] = uinfo
2017-11-26 01:55:30 +00:00
this . hc . processEvent ( HubEvent { EventType : EVENT_USER_JOINED , Nick : newNick } )
}
2017-11-26 04:24:08 +00:00
case "IMSG" :
// General message from the hub
if len ( parts ) < 2 {
this . malformed ( parts )
return
}
this . hc . processEvent ( HubEvent { EventType : EVENT_SYSTEM_MESSAGE_FROM_HUB , Message : this . unescape ( parts [ 1 ] ) } )
2017-11-26 00:30:19 +00:00
case "ISTA" :
2017-11-26 04:24:08 +00:00
// Error message from the hub
2017-11-26 00:30:19 +00:00
if len ( parts ) < 3 {
this . malformed ( parts )
return
}
code , _ := strconv . Atoi ( parts [ 1 ] )
msg := this . unescape ( parts [ 2 ] )
this . hc . processEvent ( HubEvent { EventType : EVENT_SYSTEM_MESSAGE_FROM_HUB , Message : this . ErrorMessage ( code , msg ) } )
2017-11-26 04:24:08 +00:00
case "IQUI" :
// Error message from the hub
// IQUI V3M6 DI1 MSNick\staken,\splease\spick\sanother\sone TL-1
2017-11-26 04:53:14 +00:00
if len ( parts ) < 2 {
this . malformed ( parts )
return
}
sid := parts [ 1 ]
flags , err := this . parts2flags ( parts [ 2 : ] )
2017-11-26 04:24:08 +00:00
if err != nil {
return
}
2017-11-26 04:53:14 +00:00
if sid == this . sid {
if msg , ok := flags [ "MS" ] ; ok {
this . hc . processEvent ( HubEvent { EventType : EVENT_SYSTEM_MESSAGE_FROM_HUB , Message : "The hub is closing our connection because: " + this . unescape ( msg ) } )
} else {
this . hc . processEvent ( HubEvent { EventType : EVENT_SYSTEM_MESSAGE_FROM_HUB , Message : "The hub is closing our connection" } )
}
2017-11-26 04:24:08 +00:00
} else {
2017-11-26 04:53:14 +00:00
this . hc . usersMut . Lock ( )
defer this . hc . usersMut . Unlock ( )
otherSidNick , ok := this . hc . userSIDs [ sid ]
if ok {
delete ( this . hc . userSIDs , sid )
delete ( this . hc . users , otherSidNick )
this . hc . processEvent ( HubEvent { EventType : EVENT_USER_PART , Nick : otherSidNick } )
} else {
// ??
this . logError ( fmt . Errorf ( "An unknown user quit the hub (SID=%s)" , sid ) )
}
2017-11-26 04:24:08 +00:00
}
case "BMSG" :
// Message from a user
2017-11-26 05:11:25 +00:00
// BMSG ZVF4 hi
2017-11-26 04:24:08 +00:00
if len ( parts ) < 3 {
this . malformed ( parts )
return
}
sid := this . unescape ( parts [ 1 ] )
msg := this . unescape ( parts [ 2 ] )
this . hc . usersMut . Lock ( )
defer this . hc . usersMut . Unlock ( )
nick , ok := this . hc . userSIDs [ sid ]
if ! ok {
this . logError ( fmt . Errorf ( "Recieved message from unknown SID '%s'" , sid ) )
return
}
this . hc . processEvent ( HubEvent { EventType : EVENT_PUBLIC , Nick : nick , Message : msg } )
2017-11-23 05:23:39 +00:00
case "IGPA" :
2017-11-26 04:24:08 +00:00
// Password is needed
// IGPA 7EIAAAECLMAAAPJQAAADQQYAAAWAYAAAKVFQAAF6EAAAAAYFAAAA
// HPAS LZDIJOTZDPWHINHGPT5RHT6WLU7DRME7DQO2O3Q
if len ( parts ) < 2 {
this . malformed ( parts )
return
}
/ *
For GPA / PAS , assuming that ' 12345 ' is the random data supplied in GPA , then ;
PAS = Base32 ( Hash ( password + ' 12345 ' ) )
GPA : The data parameter is at least 24 random bytes ( base32 encoded ) .
* /
data_base32 := parts [ 1 ]
2017-11-26 04:53:24 +00:00
if len ( data_base32 ) % 8 != 0 {
data_base32 += strings . Repeat ( "=" , 8 - ( len ( data_base32 ) % 8 ) )
2017-11-26 04:24:08 +00:00
}
data_raw , err := base32 . StdEncoding . DecodeString ( data_base32 )
if err != nil {
this . logError ( err )
return
}
resp := Base32 ( Tiger ( this . hc . Hco . NickPassword + string ( data_raw ) ) )
this . hc . SayRaw ( "HPAS " + resp + "\n" )
case "EMSG" :
// Private message from other user
// EMSG I5RO FMWH test\spm PMI5RO
2017-11-26 05:11:25 +00:00
// EMSG sender recip==us message [flags...]
if len ( parts ) < 4 {
this . malformed ( parts )
return
}
if parts [ 2 ] != this . sid {
this . logError ( fmt . Errorf ( "Recieved a PM intended for someone else (got SID=%s expected SID=%s)" , parts [ 2 ] , this . sid ) )
return
}
senderSid := parts [ 1 ]
senderNick , ok := this . SID2Nick ( parts [ 1 ] )
if ! ok {
this . logError ( fmt . Errorf ( "Recieved a PM from an unknown user (SID=%s)" , senderSid ) )
return
}
msg := this . unescape ( parts [ 3 ] )
this . hc . processEvent ( HubEvent { EventType : EVENT_PRIVATE , Nick : senderNick , Message : msg } )
2017-11-26 04:24:08 +00:00
2017-11-26 06:19:06 +00:00
case "ICMD" :
// Usercommand
// ICMD ADCH++/About\sthis\shub TTHMSG\s+about\n CT3
if len ( parts ) < 2 {
this . malformed ( parts )
return
}
uc := UserCommand {
Message : this . unescape ( parts [ 1 ] ) ,
Type : USERCOMMAND_TYPE_RAW , // default
}
flags , err := this . parts2flags ( parts [ 2 : ] )
if err != nil {
this . malformed ( parts )
return
}
if ct , ok := flags [ "CT" ] ; ok {
ct64 , _ := strconv . ParseUint ( ct , 10 , 64 )
uc . Context = UserCommandContext ( ct64 )
}
if tt , ok := flags [ "TT" ] ; ok {
uc . Command = tt
}
if sp , ok := flags [ "SP" ] ; ok && sp == "1" {
uc . Type = USERCOMMAND_TYPE_SEPARATOR
}
if co , ok := flags [ "CO" ] ; ok && co == "1" {
uc . Type = USERCOMMAND_TYPE_NICKLIMITED // "Constrained" in ADC parlance
}
if rm , ok := flags [ "RM" ] ; ok && rm == "1" {
uc . RemoveThis = true
}
this . hc . processEvent ( HubEvent { EventType : EVENT_USERCOMMAND , UserCommand : & uc } )
2017-11-26 04:54:56 +00:00
// Ignored messages
// ````````````````
2017-11-26 04:24:08 +00:00
case "DCTM" : // Client-client ConnectToMe
2017-11-26 04:54:56 +00:00
case "BSCH" : // Search
2017-11-23 05:23:39 +00:00
default :
this . malformed ( parts )
}
}
2017-11-26 00:30:19 +00:00
func ( this * AdcProtocol ) infoFlagsFor ( u * UserInfo ) map [ string ] string {
parts := map [ string ] string {
"NI" : u . Nick ,
"SS" : fmt . Sprintf ( "%d" , u . ShareSize ) ,
"SF" : fmt . Sprintf ( "%d" , u . SharedFiles ) ,
"US" : fmt . Sprintf ( "%d" , u . UploadSpeedBps ) ,
"DS" : fmt . Sprintf ( "%d" , u . DownloadSpeedBps ) ,
"SL" : fmt . Sprintf ( "%d" , u . Slots ) ,
"HN" : fmt . Sprintf ( "%d" , u . HubsUnregistered ) ,
"HR" : fmt . Sprintf ( "%d" , u . HubsRegistered ) ,
"HO" : fmt . Sprintf ( "%d" , u . HubsOperator ) ,
}
if _ , ok := this . supports [ adcSeparateApVe ] ; ok {
parts [ "AP" ] = u . ClientTag
parts [ "VE" ] = u . ClientVersion
} else {
2017-11-26 05:11:32 +00:00
parts [ "VE" ] = fmt . Sprintf ( "%s %s" , u . ClientTag , u . ClientVersion )
2017-11-26 00:30:19 +00:00
}
2017-11-26 04:24:08 +00:00
// Do not send the hub a CT (it decides what type we are)
2017-11-26 00:30:19 +00:00
return parts
}
func ( this * AdcProtocol ) ourINFO ( includePid bool ) string {
parts := this . infoFlagsFor ( this . hc . Hco . Self )
parts [ "ID" ] = this . cid
if includePid {
parts [ "PD" ] = this . pid
}
ret := ""
for k , v := range parts {
ret += " " + k + this . escape ( v )
}
return ret [ 1 : ]
}
2018-06-04 04:27:34 +00:00
func ( this * AdcProtocol ) SayInfo ( ) error {
return this . hc . SayRaw ( "BINF " + this . escape ( this . sid ) + " " + this . ourINFO ( true ) + "\n" )
2018-03-24 00:21:49 +00:00
}
2017-11-26 01:55:30 +00:00
func ( this * AdcProtocol ) parts2flags ( parts [ ] string ) ( map [ string ] string , error ) {
flags := make ( map [ string ] string , len ( parts ) )
for _ , flag := range parts {
if len ( flag ) < 2 {
return nil , fmt . Errorf ( "Malformed flag '%s'" , flag )
}
flags [ flag [ 0 : 2 ] ] = this . unescape ( flag [ 2 : ] )
}
return flags , nil
}
func ( this * AdcProtocol ) handleHubInfo ( flags map [ string ] string ) error {
if flags [ "CT" ] != "32" {
return fmt . Errorf ( "Expected CT==32" )
}
2017-11-23 05:23:39 +00:00
2017-11-26 01:55:30 +00:00
// IINF DEADCH++\sTest\shub VE2.12.1\s(r"[unknown]")\sRelease HI1 NIADCH++ APADCH++ CT32
// AP: extension 3.24 "Application and version separation in INF"
// HI:
2017-11-23 05:23:39 +00:00
2017-11-26 01:55:30 +00:00
// Hub properties updated
2017-11-26 00:30:19 +00:00
2017-11-26 01:55:30 +00:00
// Special SUPPORT that is only indicated in IINF
if _ , ok := flags [ "AP" ] ; ok {
this . supports [ adcSeparateApVe ] = struct { } { }
}
// Hub's name is in "NI", hub description in "DE"
hubName , ok := flags [ "NI" ]
if ok {
if hubDesc , ok := flags [ "DE" ] ; ok && len ( hubDesc ) > 0 {
hubName += " - " + hubDesc
2017-11-26 00:30:19 +00:00
}
2017-11-26 01:55:30 +00:00
this . hc . HubName = hubName
this . hc . processEvent ( HubEvent { EventType : EVENT_HUBNAME_CHANGED , Nick : this . hc . HubName } )
}
return nil
}
2017-11-26 05:38:03 +00:00
func ( this * AdcProtocol ) updateUserInfo ( u * UserInfo , flags map [ string ] string ) {
2017-11-26 01:55:30 +00:00
// User MyINFO
// BINF GUPR IDFEARIFD33NTGC4YBEZ3UFQS5R4ZXXTFL2QN2GRY PDZMIFLG5EKZG3BDRRMIJPG7ARNA6KW3JVIH3DF7Q NIivysaur5 SL3 FS3 SS0 SF0 HN1 HR0 HO0 VEEiskaltDC++\s2.2.9 US2621440 KPSHA256/3UPRORG4BLJ4CG6TO6R3G75A67LXOGD437NALQALRWJF6XBOECTA I40.0.0.0 U418301
// BINF GUPR I4172.17.0.1 U418301 IDFEARIFD33NTGC4YBEZ3UFQS5R4ZXXTFL2QN2GRY VEEiskaltDC++\s2.2.9 SF0 NIivysaur5 SL3 HN1 HO0 KPSHA256/3UPRORG4BLJ4CG6TO6R3G75A67LXOGD437NALQALRWJF6XBOECTA HR0 FS3 SS0 US2621440 SUSEGA,ADC0,TCP4,UDP4
2017-11-26 05:38:03 +00:00
// Or maybe only incremental:
// BINF Z3BA HO1
2017-11-26 01:55:30 +00:00
// TODO
for prop , val := range flags {
switch prop {
case "ID" :
u . CID = val
case "PD" :
// ignore PID - it will only appear if we're talking about our own user
case "NI" :
u . Nick = val
case "SL" :
u . Slots , _ = strconv . ParseUint ( val , 10 , 64 )
case "SS" :
u . ShareSize , _ = strconv . ParseUint ( val , 10 , 64 )
case "SF" :
u . SharedFiles , _ = strconv . ParseUint ( val , 10 , 64 )
case "HN" :
u . HubsUnregistered , _ = strconv . ParseUint ( val , 10 , 64 )
case "HR" :
u . HubsRegistered , _ = strconv . ParseUint ( val , 10 , 64 )
case "HO" :
u . HubsOperator , _ = strconv . ParseUint ( val , 10 , 64 )
case "US" :
u . UploadSpeedBps , _ = strconv . ParseUint ( val , 10 , 64 )
case "DS" :
u . DownloadSpeedBps , _ = strconv . ParseUint ( val , 10 , 64 )
case "KP" :
u . Keyprint = val
case "I4" :
u . IPv4Address = val
case "I6" :
u . IPv6Address = val
case "U4" :
u . IPv4UDPPort , _ = strconv . ParseUint ( val , 10 , 64 )
case "U6" :
u . IPv6UDPPort , _ = strconv . ParseUint ( val , 10 , 64 )
case "SU" :
u . SupportFlags = make ( map [ string ] struct { } )
for _ , supportFlag := range strings . Split ( val , "," ) {
u . SupportFlags [ supportFlag ] = struct { } { }
}
2017-11-23 05:23:39 +00:00
}
2017-11-26 01:55:30 +00:00
}
2017-11-23 05:23:39 +00:00
2017-11-26 01:55:30 +00:00
// VE / AP
AP , hasAP := flags [ "AP" ]
VE , hasVE := flags [ "VE" ]
if hasAP && hasVE {
u . ClientTag = AP
u . ClientVersion = VE
} else if hasAP && ! hasVE {
u . ClientTag , u . ClientVersion = this . getAPVEFromSingle ( AP )
} else if ! hasAP && hasVE {
u . ClientTag , u . ClientVersion = this . getAPVEFromSingle ( VE )
}
}
func ( this * AdcProtocol ) getAPVEFromSingle ( term string ) ( string , string ) {
words := strings . Split ( term , " " )
if len ( words ) > 1 {
return strings . Join ( words [ 0 : len ( words ) - 1 ] , " " ) , words [ len ( words ) - 1 ]
} else {
return term , "0"
2017-11-23 05:23:39 +00:00
}
}
func ( this * AdcProtocol ) enterNormalState ( ) {
this . state = adcStateNormal
this . hc . processEvent ( HubEvent { EventType : EVENT_CONNECTION_STATE_CHANGED , StateChange : CONNECTIONSTATE_CONNECTED } )
this . hc . State = CONNECTIONSTATE_CONNECTED
}
func ( this * AdcProtocol ) malformed ( parts [ ] string ) {
2017-11-26 01:55:30 +00:00
this . logError ( fmt . Errorf ( "Ignoring malformed, unhandled, or out-of-state protocol command %v" , parts ) )
}
func ( this * AdcProtocol ) logError ( e error ) {
this . hc . processEvent ( HubEvent { EventType : EVENT_DEBUG_MESSAGE , Message : "Protocol error: " + e . Error ( ) } )
2017-11-23 05:23:39 +00:00
}
func ( this * AdcProtocol ) escape ( plaintext string ) string {
// The string "\s" escapes space, "\n" newline and "\\" backslash. This version of the protocol reserves all other escapes for future use; any message containing unknown escapes must be discarded.
v1 := strings . Replace ( plaintext , ` \ ` , ` \\ ` , - 1 )
v2 := strings . Replace ( v1 , "\n" , ` \n ` , - 1 )
return strings . Replace ( v2 , " " , ` \s ` , - 1 )
}
func ( this * AdcProtocol ) unescape ( encoded string ) string {
v1 := strings . Replace ( encoded , ` \s ` , " " , - 1 )
v2 := strings . Replace ( v1 , ` \n ` , "\n" , - 1 )
return strings . Replace ( v2 , ` \\ ` , ` \ ` , - 1 )
2017-11-22 06:11:40 +00:00
}
2018-06-04 04:27:34 +00:00
func ( this * AdcProtocol ) SayPublic ( msg string ) error {
return this . hc . SayRaw ( "BMSG " + this . sid + " " + this . escape ( msg ) + "\n" )
2017-11-22 06:11:40 +00:00
}
2018-06-04 04:27:34 +00:00
func ( this * AdcProtocol ) SayPrivate ( user , message string ) error {
sid , ok := this . Nick2SID ( user )
if ! ok {
return fmt . Errorf ( "Unknown user '%s'" , user )
2017-11-26 05:11:25 +00:00
}
2018-06-04 04:27:34 +00:00
return this . hc . SayRaw ( "DMSG " + this . sid + " " + sid + " " + this . escape ( message ) + "\n" )
2017-11-22 06:11:40 +00:00
}
func ( this * AdcProtocol ) ProtoMessageSeparator ( ) string {
return "\n"
}
2017-11-26 00:30:19 +00:00
func ( this * AdcProtocol ) ErrorMessage ( code int , msg string ) string {
severity := code / 100
category := ( code % 100 ) / 10
cat_sub := ( code % 100 )
formatSeverity := func ( severity int ) string {
switch severity {
case 0 :
return "OK"
case 1 :
return "Warning"
case 2 :
return "Error"
default :
return ""
}
}
formatCategory := func ( category int ) string {
switch category {
case 0 :
return ""
case 1 :
return "Hub not accepting users"
case 2 :
return "Login failed"
case 3 :
return "Access denied"
case 4 :
return "Protocol error"
case 5 :
return "Transfer error"
default :
return ""
}
}
formatCatSub := func ( cat_sub int ) string {
switch cat_sub {
case 11 :
return "Hub is full"
case 12 :
return "Hub is disabled"
case 21 :
return "Invalid nick"
case 22 :
return "Nick is already in use"
case 23 :
return "Invalid password"
case 24 :
return "CID already connected"
case 25 :
return "Access denied"
case 26 :
return "Registered users only"
case 27 :
return "Invalid PID"
case 31 :
return "Permanently banned"
case 32 :
return "Temporarily banned"
default :
return ""
}
}
parts := make ( [ ] string , 0 , 4 )
if fs := formatSeverity ( severity ) ; len ( fs ) > 0 {
parts = append ( parts , fs )
}
if fc := formatCategory ( category ) ; len ( fc ) > 0 {
parts = append ( parts , fc )
}
if fcs := formatCatSub ( cat_sub ) ; len ( fcs ) > 0 {
parts = append ( parts , fcs )
}
if len ( msg ) > 0 {
parts = append ( parts , msg )
}
return strings . Join ( parts , ": " ) + fmt . Sprintf ( " (code %d)" , code )
}