irc-client.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704
  1. // An IRC-Client library.
  2. /*
  3. An IRC client library.
  4. This handles connecting in, autojoining channels,
  5. and SASL authentication.
  6. Example:
  7. package main
  8. import (
  9. "fmt"
  10. ircclient "git.red-green.com/RedGreen/irc-client"
  11. )
  12. func main() {
  13. config := ircclient.IRCConfig{Port: 6697,
  14. Hostname: "irc.libera.chat",
  15. UseTLS: true,
  16. Nick: "go-bot",
  17. Username: "go-bot",
  18. Realname: "Example IRC bot",
  19. AutoJoin: []string{"#go-nuts"},
  20. }
  21. FromIRC := make(chan ircclient.IRCMsg)
  22. irc := ircclient.IRCClient{IRCConfig: config, ReadChannel: FromIRC}
  23. irc.Connect()
  24. var Msg ircclient.IRCMsg
  25. for Msg = range FromIRC {
  26. fmt.Println(Msg)
  27. if Msg.Cmd == "PRIVMSG" {
  28. if ircclient.Match(Msg.To, irc.MyNick) {
  29. // Private message to bot
  30. } else {
  31. // Message to channel bot is on.
  32. }
  33. }
  34. }
  35. }
  36. */
  37. package ircclient
  38. import (
  39. "bufio"
  40. "crypto/tls"
  41. "crypto/x509"
  42. "encoding/base64"
  43. "fmt"
  44. "io"
  45. "log"
  46. "math/rand"
  47. "net"
  48. "os"
  49. "os/signal"
  50. "strconv"
  51. "strings"
  52. "sync"
  53. "syscall"
  54. "time"
  55. )
  56. // get client version information from runtime package.
  57. var VERSION string = GetModuleVersion()
  58. // Get irc-client version information from runtime.
  59. func GetModuleVersion() string {
  60. modules := GetModules()
  61. version, has := modules["git.red-green.com/RedGreen/irc-client"]
  62. if has {
  63. return version
  64. }
  65. return "git.red-green.com/irc-client"
  66. }
  67. // Is string in slice?
  68. func StrInArray(strings []string, str string) bool {
  69. for _, s := range strings {
  70. if s == str {
  71. return true
  72. }
  73. }
  74. return false
  75. }
  76. // IRC Message
  77. type IRCMsg struct {
  78. MsgParts []string // Message parts
  79. From string // Message From
  80. To string // Message To
  81. Cmd string // Command
  82. Msg string // Message
  83. }
  84. // Convert nick to lowercase following IRC capitalization rules.
  85. func NameLower(name string) string {
  86. // uppercase: []\-
  87. // lowercase: {}|^
  88. var result string = strings.ToLower(name)
  89. result = strings.ReplaceAll(result, "[", "{")
  90. result = strings.ReplaceAll(result, "]", "}")
  91. result = strings.ReplaceAll(result, "\\", "|")
  92. result = strings.ReplaceAll(result, "-", "^")
  93. return result
  94. }
  95. // Check to see if nicks or channels match according to IRC rules.
  96. func Match(name1 string, name2 string) bool {
  97. return NameLower(name1) == NameLower(name2)
  98. }
  99. // Strip out the NICK part of the From message.
  100. // :[email protected] => NickServ
  101. // IRCNick return just the NICK from :Nick!name@host
  102. func IRCNick(from string) string {
  103. if from[0] == ':' {
  104. from = from[1:]
  105. }
  106. var pos int = strings.Index(from, "!")
  107. if pos != -1 {
  108. from = from[:pos]
  109. }
  110. return from
  111. }
  112. /*
  113. IRCParse - split line into IRCMsg
  114. Everything after " :" is the Msg.
  115. Everything before " :" is split into MsgParts[].
  116. If >= 3 MsgParts {
  117. To = MsgParts[2]
  118. }
  119. if >= 2 MsgParts {
  120. From = IrcNic(MsgParts[0])
  121. Cmd = MsgParts[1]
  122. } else {
  123. Cmd = MsgParts[0]
  124. }
  125. Example Messages:
  126. :irc.red-green.com 001 test :Welcome to IRC
  127. ^From ^Cmd ^Msg
  128. ^To
  129. ^0 ^1 ^2 MsgParts[]
  130. PING :1234567890
  131. ^Cmd ^Msg
  132. ^0 MsgParts[]
  133. */
  134. // IRCParse Parse an IRC line into the IRCMsg struct.
  135. func IRCParse(line string) IRCMsg {
  136. var pos int = strings.Index(line, " :")
  137. var results IRCMsg
  138. if pos != -1 {
  139. // Message is everything after " :"
  140. results.Msg = line[pos+2:]
  141. line = line[:pos]
  142. }
  143. results.MsgParts = strings.Split(line, " ")
  144. if len(results.MsgParts) >= 2 {
  145. results.From = IRCNick(results.MsgParts[0])
  146. results.Cmd = results.MsgParts[1]
  147. } else {
  148. results.Cmd = results.MsgParts[0]
  149. }
  150. if len(results.MsgParts) >= 3 {
  151. results.To = results.MsgParts[2]
  152. }
  153. // Translate CTCP and ACTION
  154. if results.Cmd == "PRIVMSG" {
  155. if (results.Msg[0] == '\x01') && (results.Msg[len(results.Msg)-1] == '\x01') {
  156. results.Cmd = "CTCP"
  157. results.Msg = results.Msg[1 : len(results.Msg)-1]
  158. if strings.HasPrefix(results.Msg, "ACTION ") {
  159. results.Cmd = "ACTION"
  160. results.Msg = results.Msg[7:]
  161. }
  162. }
  163. }
  164. return results
  165. }
  166. // Sent to writer
  167. //
  168. // The To part allows the writer to throttle messages to a specific target.
  169. // When throttled, it delays and alternates between the targets.
  170. type IRCWrite struct {
  171. To string // Write To
  172. Output string // Output
  173. }
  174. // Configuration
  175. type IRCConfig struct {
  176. Port int `json:"Port"` // IRC Connection port
  177. Hostname string `json:"Hostname"` // Hostname for connection
  178. UseTLS bool `json:"UseTLS"` // Use TLS Secure connection
  179. UseSASL bool `json:"UseSASL"` // Authenticate via SASL
  180. Insecure bool `json:"Insecure"` // Allow self-signed certificates
  181. Nick string `json:"Nick"` // Client's nick
  182. Username string `json:"Username"` // Client's username
  183. Realname string `json:"Realname"` // Client's realname
  184. Password string `json:"Password"` // Password for nickserv
  185. ServerPassword string `json:"ServerPassword"` // Password for server
  186. AutoJoin []string `json:"AutoJoin"` // Channels to auto-join
  187. RejoinDelay int `json:"RejoinDelay"` // ms to rejoin
  188. Version string `json:"Version"` // Version displayed
  189. Flood_Num int `json:"FloodNum"` // Number of lines sent before considered a flood
  190. Flood_Time int `json:"FloodTime"` // Number of Seconds to track previous messages
  191. Flood_Delay int `json:"FloodDelay"` // Delay between sending when flood protection on (Milliseconds)
  192. Debug_Output bool `json:"Debug"` // Enable debug output
  193. }
  194. type IRCClient struct {
  195. IRCConfig
  196. MyNick string // Client's current nick
  197. OnExit func() // Called on exit
  198. Socket net.Conn // Connection to IRCD
  199. Reader *bufio.Reader // For reading a line at a time from the IRCD
  200. ReadChannel chan IRCMsg // Channel of parsed IRC Messages
  201. ReadEvents []string //
  202. WriteChannel chan IRCWrite // Channel for writing messages to IRCD
  203. DelChannel chan string // For deleting channel or nicks that are missing.
  204. Registered bool // Registered with services?
  205. ISupport map[string]string // IRCD capabilities (005)
  206. wg sync.WaitGroup // Safe shutdown of goroutines
  207. Mutex sync.Mutex // Guards MyNick
  208. }
  209. // Get the current nick of the client
  210. func (Config *IRCClient) GetNick() string {
  211. Config.Mutex.Lock()
  212. defer Config.Mutex.Unlock()
  213. return Config.MyNick
  214. }
  215. // Sets the current nick of the client
  216. func (Config *IRCClient) SetNick(nick string) {
  217. Config.Mutex.Lock()
  218. defer Config.Mutex.Unlock()
  219. Config.MyNick = nick
  220. }
  221. // Is channel in the AutoJoin list?
  222. func (Config *IRCClient) IsAuto(channel string) bool {
  223. return StrInArray(Config.AutoJoin, channel)
  224. }
  225. // Connect to IRCD and authenticate.
  226. //
  227. // This starts the ReaderRoutine to handle processing the lines from the IRCD.
  228. // You must setup ReadChannel if you want to process any messages.
  229. func (Config *IRCClient) Connect() bool {
  230. var err error
  231. // Set sensible defaults (if not provided),
  232. if Config.Version == "" {
  233. Config.Version = VERSION
  234. }
  235. if Config.Flood_Num == 0 {
  236. Config.Flood_Num = 5
  237. }
  238. if Config.Flood_Time == 0 {
  239. Config.Flood_Time = 10
  240. }
  241. if Config.Flood_Delay == 0 {
  242. Config.Flood_Delay = 2000
  243. }
  244. if Config.UseSASL {
  245. if !Config.UseTLS {
  246. log.Println("Can't UseSASL if not using UseTLS")
  247. Config.UseSASL = false
  248. }
  249. }
  250. Config.Registered = false
  251. if Config.ReadChannel == nil {
  252. log.Println("Warning: ReadChannel is nil. You can't received IRCMsg messages.")
  253. }
  254. if Config.UseTLS {
  255. var tlsConfig tls.Config
  256. if !Config.Insecure {
  257. // Use system default CA Certificates
  258. certPool := x509.NewCertPool()
  259. tlsConfig.ClientCAs = certPool
  260. tlsConfig.InsecureSkipVerify = false
  261. } else {
  262. tlsConfig.InsecureSkipVerify = true
  263. }
  264. Config.Socket, err = tls.Dial("tcp", Config.Hostname+":"+strconv.Itoa(Config.Port), &tlsConfig)
  265. if err != nil {
  266. log.Fatal("tls.Dial:", err)
  267. }
  268. Config.Reader = bufio.NewReader(Config.Socket)
  269. } else {
  270. Config.Socket, err = net.Dial("tcp", Config.Hostname+":"+strconv.Itoa(Config.Port))
  271. if err != nil {
  272. log.Fatal("net.Dial:", err)
  273. }
  274. Config.Reader = bufio.NewReader(Config.Socket)
  275. }
  276. // WriteChannel may contain a message when we're trying to PriorityWrite from sigChannel.
  277. Config.WriteChannel = make(chan IRCWrite, 3)
  278. Config.DelChannel = make(chan string)
  279. Config.ISupport = make(map[string]string)
  280. // We are connected.
  281. go Config.WriterRoutine()
  282. Config.wg.Add(1)
  283. // Registration
  284. if Config.UseTLS && Config.UseSASL {
  285. Config.PriorityWrite("CAP REQ :sasl")
  286. }
  287. if Config.ServerPassword != "" {
  288. Config.PriorityWrite(fmt.Sprintf("PASS %s", Config.ServerPassword))
  289. }
  290. // Register nick
  291. Config.MyNick = Config.Nick
  292. Config.PriorityWrite(fmt.Sprintf("NICK %s", Config.Nick))
  293. Config.PriorityWrite(fmt.Sprintf("USER %s 0 * :%s", Config.Username, Config.Realname))
  294. go Config.ReaderRoutine()
  295. Config.wg.Add(1)
  296. return true
  297. }
  298. // Write to IRCD
  299. func (Config *IRCClient) write(output string) error {
  300. var err error
  301. if Config.Debug_Output {
  302. log.Println(">>", output)
  303. }
  304. output += "\r\n"
  305. _, err = Config.Socket.Write([]byte(output))
  306. return err
  307. }
  308. // WriterRoutine a goroutine that handles flood control
  309. func (Config *IRCClient) WriterRoutine() {
  310. var err error
  311. var throttle ThrottleBuffer
  312. var Flood FloodTrack
  313. Flood.Init(Config.Flood_Num, Config.Flood_Time)
  314. defer Config.wg.Done()
  315. throttle.Init()
  316. // signal handler
  317. var sigChannel chan os.Signal = make(chan os.Signal, 1)
  318. signal.Notify(sigChannel, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
  319. // Change this into a select with timeout.
  320. // Timeout, if there's something to be buffered.
  321. var gotSignal bool
  322. for {
  323. if throttle.Life_sucks {
  324. select {
  325. case output := <-Config.WriteChannel:
  326. if output.To == "" {
  327. err = Config.write(output.Output)
  328. if err != nil {
  329. log.Println("Writer:", err)
  330. return
  331. }
  332. continue
  333. }
  334. throttle.push(output.To, output.Output)
  335. case <-sigChannel:
  336. if !gotSignal {
  337. gotSignal = true
  338. log.Println("SIGNAL")
  339. Config.PriorityWrite("QUIT :Received SIGINT")
  340. }
  341. continue
  342. case remove := <-Config.DelChannel:
  343. if Config.Debug_Output {
  344. log.Printf("Remove: [%s]\n", remove)
  345. }
  346. throttle.delete(remove)
  347. case <-time.After(time.Millisecond * time.Duration(Config.Flood_Delay)):
  348. var msg string = throttle.pop()
  349. err = Config.write(msg)
  350. if err != nil {
  351. log.Println("Writer:", err)
  352. }
  353. }
  354. } else {
  355. // Life is good.
  356. select {
  357. case <-sigChannel:
  358. if !gotSignal {
  359. gotSignal = true
  360. log.Println("SIGNAL")
  361. Config.PriorityWrite("QUIT :Received SIGINT")
  362. }
  363. continue
  364. case remove := <-Config.DelChannel:
  365. if Config.Debug_Output {
  366. log.Printf("Remove: [%s]\n", remove)
  367. }
  368. throttle.delete(remove)
  369. case output := <-Config.WriteChannel:
  370. if output.To == "" {
  371. err = Config.write(output.Output)
  372. if err != nil {
  373. log.Println("Writer:", err)
  374. return
  375. }
  376. continue
  377. }
  378. if Flood.Full() {
  379. throttle.push(output.To, output.Output)
  380. } else {
  381. // Flood limits not reached
  382. Flood.Save()
  383. err = Config.write(output.Output)
  384. if err != nil {
  385. log.Println("Writer:", err)
  386. }
  387. }
  388. }
  389. }
  390. }
  391. }
  392. // Shutdown the client.
  393. func (Config *IRCClient) Close() {
  394. Config.Socket.Close()
  395. Config.PriorityWrite("")
  396. if Config.OnExit != nil {
  397. Config.OnExit()
  398. }
  399. Config.wg.Wait()
  400. }
  401. // Generate random nick to change to (upon collision/433)
  402. func RandomNick(nick string) string {
  403. var result string = nick + "-"
  404. result += strconv.Itoa(rand.Intn(1000))
  405. return result
  406. }
  407. // PriorityWrite: Send output command to server immediately.
  408. //
  409. // This is never throttled.
  410. func (Config *IRCClient) PriorityWrite(output string) {
  411. Config.WriteChannel <- IRCWrite{To: "", Output: output}
  412. }
  413. // WriteTo: Send throttled output command using "to" to throttle.
  414. func (Config *IRCClient) WriteTo(to string, output string) {
  415. Config.WriteChannel <- IRCWrite{To: to, Output: output}
  416. }
  417. // Msg: Send PRIVMSG, uses WriteTo to throttle.
  418. func (Config *IRCClient) Msg(to string, message string) {
  419. Config.WriteTo(to, fmt.Sprintf("PRIVMSG %s :%s", to, message))
  420. }
  421. // Notice: Send NOTICE, uses WriteTo to throttle.
  422. func (Config *IRCClient) Notice(to string, message string) {
  423. Config.WriteTo(to, fmt.Sprintf("NOTICE %s :%s", to, message))
  424. }
  425. // Action: Send PRIVMSG CTCP ACTION, uses WriteTo to throttle.
  426. func (Config *IRCClient) Action(to string, message string) {
  427. Config.WriteTo(to, fmt.Sprintf("PRIVMSG %s :\x01ACTION %s\x01", to, message))
  428. }
  429. // Goroutine reader routine
  430. //
  431. // This reads a line at a time, delimited by '\n'.
  432. //
  433. // Auto-replies to server PING.
  434. // Converts CTCP & ACTION messages to type CTCP/ACTION.
  435. // Automatically answers /VERSION and /TIME.
  436. // Handles SASL authentication.
  437. // Rejoins AutoJoin channels kicked from.
  438. func (Config *IRCClient) ReaderRoutine() {
  439. defer Config.wg.Done()
  440. var registering bool
  441. for {
  442. var line string
  443. var err error
  444. var results IRCMsg
  445. line, err = Config.Reader.ReadString('\n')
  446. if err == nil {
  447. line = strings.Trim(line, "\r\n")
  448. if Config.Debug_Output {
  449. log.Println("<<", line)
  450. }
  451. results = IRCParse(line)
  452. switch results.Cmd {
  453. case "PING":
  454. // Ping from Server
  455. Config.PriorityWrite("PONG " + results.Msg)
  456. case "005":
  457. var support string
  458. for _, support = range results.MsgParts[3:] {
  459. if strings.Contains(support, "=") {
  460. var suppart []string = strings.Split(support, "=")
  461. Config.ISupport[suppart[0]] = suppart[1]
  462. } else {
  463. Config.ISupport[support] = ""
  464. }
  465. }
  466. case "433":
  467. if !registering {
  468. // Nick already in use!
  469. var newNick string = RandomNick(Config.Nick)
  470. Config.MyNick = newNick
  471. Config.PriorityWrite("NICK " + newNick)
  472. }
  473. case "CTCP":
  474. if strings.HasPrefix(results.Msg, "PING ") {
  475. Config.WriteTo(IRCNick(results.From),
  476. fmt.Sprintf("NOTICE %s :\x01%s\x01",
  477. IRCNick(results.From),
  478. results.Msg))
  479. } else if strings.HasPrefix(results.Msg, "VERSION") {
  480. var version string
  481. if Config.Version != "" {
  482. version = Config.Version + " " + VERSION
  483. } else {
  484. version = VERSION
  485. }
  486. Config.WriteTo(IRCNick(results.From),
  487. fmt.Sprintf("NOTICE %s :\x01VERSION %s\x01",
  488. IRCNick(results.From), version))
  489. } else if strings.HasPrefix(results.Msg, "TIME") {
  490. var now time.Time = time.Now()
  491. Config.WriteTo(IRCNick(results.From),
  492. fmt.Sprintf("NOTICE %s :\x01TIME %s\x01",
  493. IRCNick(results.From),
  494. now.Format(time.ANSIC)))
  495. }
  496. }
  497. } else {
  498. // This is likely, 2022/04/05 10:11:41 ReadString: EOF
  499. if err != io.EOF {
  500. log.Println("ReadString:", err)
  501. }
  502. close(Config.ReadChannel)
  503. return
  504. }
  505. var msg IRCMsg = results
  506. if !Config.Debug_Output {
  507. if msg.Cmd == "ERROR" || msg.Cmd[0] == '4' || msg.Cmd[0] == '5' {
  508. // Always log errors.
  509. log.Println("<<", line)
  510. }
  511. }
  512. if msg.Cmd == "401" || msg.Cmd == "404" {
  513. // No such nick/channel
  514. log.Printf("Remove %s from buffer.", msg.MsgParts[3])
  515. Config.DelChannel <- msg.MsgParts[3]
  516. }
  517. if !Config.Registered {
  518. // We're not registered yet
  519. // Answer the queries for SASL authentication
  520. if msg.Cmd == "CAP" && msg.MsgParts[3] == "ACK" {
  521. Config.PriorityWrite("AUTHENTICATE PLAIN")
  522. }
  523. if msg.Cmd == "CAP" && msg.MsgParts[3] == "NAK" {
  524. // SASL Authentication failed/not available.
  525. Config.PriorityWrite("CAP END")
  526. Config.UseSASL = false
  527. }
  528. // msg.Cmd == "AUTH..."
  529. if msg.MsgParts[0] == "AUTHENTICATE" && msg.MsgParts[1] == "+" {
  530. var userpass string = fmt.Sprintf("\x00%s\x00%s", Config.Nick, Config.Password)
  531. var b64 string = base64.StdEncoding.EncodeToString([]byte(userpass))
  532. Config.PriorityWrite("AUTHENTICATE " + b64)
  533. }
  534. if msg.Cmd == "903" {
  535. // Success SASL
  536. Config.PriorityWrite("CAP END")
  537. Config.Registered = true
  538. }
  539. if msg.Cmd == "904" {
  540. // Failed SASL
  541. Config.PriorityWrite("CAP END")
  542. // Should we exit here?
  543. Config.UseSASL = false
  544. }
  545. /*
  546. 2022/04/06 19:12:11 << :[email protected] NOTICE meow :nick, type /msg NickServ IDENTIFY password. Otherwise,
  547. */
  548. if (msg.From == "NickServ") && (msg.Cmd == "NOTICE") {
  549. if strings.Contains(msg.Msg, "IDENTIFY") && Config.Password != "" {
  550. Config.PriorityWrite(fmt.Sprintf("NS IDENTIFY %s", Config.Password))
  551. }
  552. }
  553. if !Config.UseSASL && (msg.Cmd == "900") {
  554. Config.Registered = true
  555. }
  556. }
  557. // This is a better way of knowing when we've identified for services
  558. if (msg.Cmd == "MODE") && (msg.To == Config.MyNick) {
  559. // This should probably be look for + and contains "r"
  560. if (msg.Msg[0] == '+') && (strings.Contains(msg.Msg, "r")) {
  561. Config.ReadChannel <- IRCMsg{Cmd: "Identified"}
  562. if len(Config.AutoJoin) > 0 {
  563. Config.PriorityWrite("JOIN " + strings.Join(Config.AutoJoin, ","))
  564. }
  565. }
  566. }
  567. if msg.Cmd == "KICK" {
  568. // We were kicked, is channel in AutoJoin?
  569. if msg.MsgParts[3] == Config.MyNick {
  570. if Config.IsAuto(msg.To) {
  571. // Yes, we were kicked from AutoJoin channel
  572. time.AfterFunc(time.Duration(Config.RejoinDelay)*time.Millisecond, func() { Config.WriteTo(msg.To, "JOIN "+msg.To) })
  573. }
  574. }
  575. }
  576. if Config.ReadChannel != nil {
  577. Config.ReadChannel <- msg
  578. }
  579. if (msg.Cmd == "376") || (msg.Cmd == "422") {
  580. // End MOTD, or MOTD Missing
  581. var reg IRCMsg = IRCMsg{Cmd: "EndMOTD"}
  582. Config.ReadChannel <- reg
  583. registering = true
  584. if Config.Password == "" {
  585. // We can't register with services, so join the AutJoin channels.
  586. if len(Config.AutoJoin) > 0 {
  587. Config.PriorityWrite("JOIN " + strings.Join(Config.AutoJoin, ","))
  588. }
  589. }
  590. }
  591. if msg.Cmd == "NICK" {
  592. // Handle nick changes from the server
  593. if Match(msg.From, Config.MyNick) {
  594. Config.SetNick(msg.Msg)
  595. if Config.Debug_Output {
  596. log.Println("Nick is now:", Config.MyNick)
  597. }
  598. }
  599. }
  600. }
  601. }