| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484 | 
							- package ircclient
 
- import (
 
- 	"bufio"
 
- 	"crypto/tls"
 
- 	"crypto/x509"
 
- 	"encoding/base64"
 
- 	"fmt"
 
- 	"io"
 
- 	"log"
 
- 	"math/rand"
 
- 	"net"
 
- 	"os"
 
- 	"os/signal"
 
- 	"strconv"
 
- 	"strings"
 
- 	"syscall"
 
- 	"time"
 
- )
 
- func StrInArray(strings []string, str string) bool {
 
- 	for _, s := range strings {
 
- 		if s == str {
 
- 			return true
 
- 		}
 
- 	}
 
- 	return false
 
- }
 
- type IRCMsg struct {
 
- 	MsgParts []string
 
- 	From     string
 
- 	To       string
 
- 	Cmd      string
 
- 	Msg      string
 
- }
 
- type IRCWrite struct {
 
- 	To     string
 
- 	Output string
 
- }
 
- type IRCConfig struct {
 
- 	Port           int      `json: Port`
 
- 	Hostname       string   `json: Hostname`
 
- 	UseTLS         bool     `json: UseTLS`   // Use TLS Secure connection
 
- 	UseSASL        bool     `json: UseSASL`  // Authenticate via SASL
 
- 	Insecure       bool     `json: Insecure` // Allow self-signed certificates
 
- 	Nick           string   `json: Nick`
 
- 	Username       string   `json: Username`
 
- 	Realname       string   `json: Realname`
 
- 	Password       string   `json: Password`       // Password for nickserv
 
- 	ServerPassword string   `json: ServerPassword` // Password for server
 
- 	AutoJoin       []string `json: AutoJoin`       // Channels to auto-join
 
- 	RejoinDelay    int      `json: RejoinDelay`    // ms to rejoin
 
- 	MyNick         string
 
- 	Socket         net.Conn
 
- 	TLSSocket      *tls.Conn
 
- 	Reader         *bufio.Reader
 
- 	ReadChannel    chan IRCMsg
 
- 	ReadEvents     []string
 
- 	WriteChannel   chan IRCWrite
 
- 	DelChannel     chan string
 
- 	Registered     bool
 
- 	Flood_Num      int // Number of lines sent before considered a flood
 
- 	Flood_Time     int // Number of Seconds to track previous messages
 
- 	Flood_Delay    int // Delay between sending when flood protection on (Milliseconds)
 
- 	OnExit         func()
 
- }
 
- // Writer       *bufio.Writer
 
- func (Config *IRCConfig) Connect() bool {
 
- 	var err error
 
- 	if Config.Flood_Num == 0 {
 
- 		Config.Flood_Num = 5
 
- 	}
 
- 	if Config.Flood_Time == 0 {
 
- 		Config.Flood_Time = 10
 
- 	}
 
- 	if Config.Flood_Delay == 0 {
 
- 		Config.Flood_Delay = 1000
 
- 	}
 
- 	Config.Registered = false
 
- 	if Config.ReadChannel == nil {
 
- 		log.Println("Warning: ReadChannel is nil.")
 
- 	}
 
- 	if Config.UseTLS {
 
- 		var tlsConfig tls.Config
 
- 		if !Config.Insecure {
 
- 			certPool := x509.NewCertPool()
 
- 			tlsConfig.ClientCAs = certPool
 
- 			tlsConfig.InsecureSkipVerify = false
 
- 		} else {
 
- 			tlsConfig.InsecureSkipVerify = true
 
- 		}
 
- 		Config.TLSSocket, err = tls.Dial("tcp", Config.Hostname+":"+strconv.Itoa(Config.Port), &tlsConfig)
 
- 		if err != nil {
 
- 			log.Fatal("tls.Dial:", err)
 
- 		}
 
- 		// Config.Writer = bufio.NewWriter(Config.TLSSocket)
 
- 		Config.Reader = bufio.NewReader(Config.TLSSocket)
 
- 	} else {
 
- 		Config.Socket, err = net.Dial("tcp", Config.Hostname+":"+strconv.Itoa(Config.Port))
 
- 		if err != nil {
 
- 			log.Fatal("net.Dial:", err)
 
- 		}
 
- 		// Config.Writer = bufio.NewWriter(Config.Socket)
 
- 		Config.Reader = bufio.NewReader(Config.Socket)
 
- 	}
 
- 	Config.WriteChannel = make(chan IRCWrite)
 
- 	Config.DelChannel = make(chan string)
 
- 	// We are connected.
 
- 	go Config.WriterRoutine()
 
- 	// Registration
 
- 	if Config.UseTLS && Config.UseSASL {
 
- 		Config.PriorityWrite("CAP REQ :sasl")
 
- 	}
 
- 	if Config.ServerPassword != "" {
 
- 		Config.PriorityWrite(fmt.Sprintf("PASS %s", Config.ServerPassword))
 
- 	}
 
- 	// Register nick
 
- 	Config.MyNick = Config.Nick
 
- 	Config.PriorityWrite(fmt.Sprintf("NICK %s", Config.Nick))
 
- 	Config.PriorityWrite(fmt.Sprintf("USER %s 0 * :%s", Config.Username, Config.Realname))
 
- 	// Config.Writer.Flush()
 
- 	go Config.ReaderRoutine()
 
- 	return true
 
- }
 
- // Low level write to [TLS]Socket routine.
 
- func (Config *IRCConfig) write(output string) error {
 
- 	var err error
 
- 	log.Println(">>", output)
 
- 	output += "\r\n"
 
- 	if Config.UseTLS {
 
- 		_, err = Config.TLSSocket.Write([]byte(output))
 
- 	} else {
 
- 		_, err = Config.Socket.Write([]byte(output))
 
- 	}
 
- 	return err
 
- }
 
- func (Config *IRCConfig) WriterRoutine() {
 
- 	var err error
 
- 	var throttle ThrottleBuffer
 
- 	var Flood FloodTrack
 
- 	throttle.init()
 
- 	Flood.Init(Config.Flood_Num, Config.Flood_Time)
 
- 	// signal handler
 
- 	var sigChannel chan os.Signal = make(chan os.Signal, 1)
 
- 	signal.Notify(sigChannel, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
 
- 	// Change this into a select with timeout.
 
- 	// Timeout, if there's something to be buffered.
 
- 	for {
 
- 		if throttle.Life_sucks {
 
- 			select {
 
- 			case output := <-Config.WriteChannel:
 
- 				if output.To == "" {
 
- 					err = Config.write(output.Output)
 
- 					if err != nil {
 
- 						log.Println("Writer:", err)
 
- 					}
 
- 					continue
 
- 				}
 
- 				throttle.push(output.To, output.Output)
 
- 			case <-sigChannel:
 
- 				if Config.OnExit != nil {
 
- 					Config.OnExit()
 
- 				}
 
- 				os.Exit(2)
 
- 			case remove := <-Config.DelChannel:
 
- 				log.Printf("Remove: [%s]\n", remove)
 
- 				throttle.delete(remove)
 
- 			case <-time.After(time.Millisecond * time.Duration(Config.Flood_Delay)):
 
- 				// Send from the buffer
 
- 				// debugging the flood buffer
 
- 				// log.Printf("targets: %#v\n", targets)
 
- 				// log.Printf("last: %d\n", last)
 
- 				// log.Printf("buffer: %#v\n", buffer)
 
- 				var msg string = throttle.pop()
 
- 				err = Config.write(msg)
 
- 				if err != nil {
 
- 					log.Println("Writer:", err)
 
- 				}
 
- 			}
 
- 		} else {
 
- 			// Life is good.
 
- 			select {
 
- 			case <-sigChannel:
 
- 				if Config.OnExit != nil {
 
- 					Config.OnExit()
 
- 				}
 
- 				os.Exit(2)
 
- 			case output := <-Config.WriteChannel:
 
- 				if output.To == "" {
 
- 					err = Config.write(output.Output)
 
- 					if err != nil {
 
- 						log.Println("Writer:", err)
 
- 					}
 
- 					continue
 
- 				}
 
- 				if Flood.Full() {
 
- 					throttle.push(output.To, output.Output)
 
- 				} else {
 
- 					// Flood limits not reached
 
- 					Flood.Save()
 
- 					err = Config.write(output.Output)
 
- 					if err != nil {
 
- 						log.Println("Writer:", err)
 
- 					}
 
- 				}
 
- 			}
 
- 		}
 
- 	}
 
- 	log.Println("~WriterRoutine")
 
- }
 
- func (Config *IRCConfig) Close() {
 
- 	if Config.UseTLS {
 
- 		Config.TLSSocket.Close()
 
- 	} else {
 
- 		Config.Socket.Close()
 
- 	}
 
- }
 
- func IRCParse(line string) []string {
 
- 	var pos int = strings.Index(line, " :")
 
- 	var message string
 
- 	if pos != -1 {
 
- 		message = line[pos+2:]
 
- 		line = line[:pos]
 
- 	}
 
- 	var results []string
 
- 	results = strings.Split(line, " ")
 
- 	if message != "" {
 
- 		results = append(results, message)
 
- 	}
 
- 	return results
 
- }
 
- func IRCNick(from string) string {
 
- 	if from[0] == ':' {
 
- 		from = from[1:]
 
- 	}
 
- 	var pos int = strings.Index(from, "!")
 
- 	if pos != -1 {
 
- 		from = from[:pos]
 
- 	}
 
- 	return from
 
- }
 
- func RandomNick(nick string) string {
 
- 	var result string = nick + "-"
 
- 	result += strconv.Itoa(rand.Intn(1000))
 
- 	return result
 
- }
 
- func (Config *IRCConfig) PriorityWrite(output string) {
 
- 	Config.WriteChannel <- IRCWrite{To: "", Output: output}
 
- }
 
- func (Config *IRCConfig) WriteTo(to string, output string) {
 
- 	Config.WriteChannel <- IRCWrite{To: to, Output: output}
 
- }
 
- func (Config *IRCConfig) Msg(to string, message string) {
 
- 	Config.WriteTo(to, fmt.Sprintf("PRIVMSG %s :%s", to, message))
 
- }
 
- func (Config *IRCConfig) Notice(to string, message string) {
 
- 	Config.WriteTo(to, fmt.Sprintf("NOTICE %s :%s", to, message))
 
- }
 
- func (Config *IRCConfig) Action(to string, message string) {
 
- 	Config.WriteTo(to, fmt.Sprintf("PRIVMSG %s :\x01ACTION %s\x01", to, message))
 
- }
 
- func (Config *IRCConfig) ReaderRoutine() {
 
- 	for {
 
- 		var line string
 
- 		var err error
 
- 		var results []string
 
- 		line, err = Config.Reader.ReadString('\n')
 
- 		if err == nil {
 
- 			line = strings.Trim(line, "\r\n")
 
- 			log.Println("<<", line)
 
- 			results = IRCParse(line)
 
- 			if results[1] == "433" {
 
- 				// Nick already in use!
 
- 				var newNick string = RandomNick(Config.Nick)
 
- 				Config.MyNick = newNick
 
- 				Config.PriorityWrite("NICK " + newNick)
 
- 			}
 
- 			if results[1] == "PRIVMSG" {
 
- 				// Is this an action?
 
- 				if len(results) >= 3 {
 
- 					if (results[3][0] == '\x01') && (results[3][len(results[3])-1] == '\x01') {
 
- 						// ACTION
 
- 						results[1] = "CTCP"
 
- 						results[3] = results[3][1 : len(results[3])-1]
 
- 						log.Println("CTCP:", results[3])
 
- 						// Process CTCP commands
 
- 						if strings.HasPrefix(results[3], "ACTION ") {
 
- 							results[1] = "ACTION"
 
- 							results[3] = results[3][7:]
 
- 						}
 
- 						if strings.HasPrefix(results[3], "PING ") {
 
- 							Config.WriteTo(IRCNick(results[0]),
 
- 								fmt.Sprintf("NOTICE %s :\x01PING %s\x01",
 
- 									IRCNick(results[0]),
 
- 									results[3][5:]))
 
- 						}
 
- 						if results[3] == "VERSION" {
 
- 							// Send version reply
 
- 							Config.WriteTo(IRCNick(results[0]),
 
- 								fmt.Sprintf("NOTICE %s :\x01VERSION red-green.com/irc-client 0.1\x01",
 
- 									IRCNick(results[0])))
 
- 						}
 
- 						if results[3] == "TIME" {
 
- 							// Send time reply
 
- 							var now time.Time = time.Now()
 
- 							Config.WriteTo(IRCNick(results[0]),
 
- 								fmt.Sprintf("NOTICE %s :\x01TIME %s\x01",
 
- 									IRCNick(results[0]),
 
- 									now.Format(time.ANSIC)))
 
- 						}
 
- 					}
 
- 				}
 
- 			}
 
- 		} else {
 
- 			// This is likely, 2022/04/05 10:11:41 ReadString: EOF
 
- 			if err != io.EOF {
 
- 				log.Println("ReadString:", err)
 
- 			}
 
- 			close(Config.ReadChannel)
 
- 			return
 
- 		}
 
- 		var msg IRCMsg = IRCMsg{MsgParts: results}
 
- 		if len(results) >= 3 {
 
- 			msg.From = IRCNick(results[0])
 
- 			msg.Cmd = results[1]
 
- 			msg.To = results[2]
 
- 			if len(results) >= 4 {
 
- 				msg.Msg = results[3]
 
- 			}
 
- 		} else {
 
- 			msg.Cmd = results[0]
 
- 		}
 
- 		// Answer PING/PONG immediately.
 
- 		if results[0] == "PING" {
 
- 			Config.PriorityWrite("PONG " + results[1])
 
- 		}
 
- 		if (msg.Cmd == "401") || (msg.Cmd == "404") {
 
- 			// No such nick/channel
 
- 			log.Printf("Remove %s from buffer.", msg.MsgParts[3])
 
- 			Config.DelChannel <- msg.MsgParts[3]
 
- 		}
 
- 		if !Config.Registered {
 
- 			// We're not registered yet
 
- 			// Answer the queries for SASL authentication
 
- 			if (msg.Cmd == "CAP") && (msg.Msg == "ACK") {
 
- 				Config.PriorityWrite("AUTHENTICATE PLAIN")
 
- 			}
 
- 			if (msg.MsgParts[0] == "AUTHENTICATE") && (msg.MsgParts[1] == "+") {
 
- 				var userpass string = fmt.Sprintf("\x00%s\x00%s", Config.Nick, Config.Password)
 
- 				var b64 string = base64.StdEncoding.EncodeToString([]byte(userpass))
 
- 				Config.PriorityWrite("AUTHENTICATE " + b64)
 
- 			}
 
- 			if msg.Cmd == "903" {
 
- 				// Success SASL
 
- 				Config.PriorityWrite("CAP END")
 
- 				Config.Registered = true
 
- 			}
 
- 			if msg.Cmd == "904" {
 
- 				// Failed SASL
 
- 				Config.PriorityWrite("CAP END")
 
- 				// Should we exit here?
 
- 			}
 
- 			/*
 
- 				2022/04/06 19:12:11 << :[email protected] NOTICE meow :This nickname is registered and protected.  If it is your
 
- 				2022/04/06 19:12:11 << :[email protected] NOTICE meow :nick, type /msg NickServ IDENTIFY password.  Otherwise,
 
- 				2022/04/06 19:12:11 << :[email protected] NOTICE meow :please choose a different nick.
 
- 			*/
 
- 			if (msg.From == "NickServ") && (msg.Cmd == "NOTICE") {
 
- 				if strings.Contains(msg.Msg, "IDENTIFY") {
 
- 					Config.PriorityWrite(fmt.Sprintf("NS IDENTIFY %s", Config.Password))
 
- 				}
 
- 				// :[email protected] NOTICE meow :Password accepted - you are now recognized.
 
- 			}
 
- 			if !Config.UseSASL && (msg.Cmd == "900") {
 
- 				Config.Registered = true
 
- 			}
 
- 		}
 
- 		// This is a better way of knowing when we've identified for services
 
- 		if (msg.Cmd == "MODE") && (msg.To == Config.MyNick) {
 
- 			// This should probably be look for + and contains "r"
 
- 			if (msg.Msg[0] == '+') && (strings.Contains(msg.Msg, "r")) {
 
- 				Config.ReadChannel <- IRCMsg{Cmd: "Identified"}
 
- 			}
 
- 			if len(Config.AutoJoin) > 0 {
 
- 				Config.PriorityWrite("JOIN " + strings.Join(Config.AutoJoin, ","))
 
- 			}
 
- 		}
 
- 		if msg.Cmd == "KICK" {
 
- 			// Were we kicked, is channel in AutoJoin?
 
- 			// 2022/04/13 20:02:52 << :[email protected] KICK #bugz meow-bot :bugz
 
- 			// Msg: ircclient.IRCMsg{MsgParts:[]string{":[email protected]", "KICK", "#bugz", "meow-bot", "bugz"}, From:"bugz", To:"#bugz", Cmd:"KICK", Msg:"meow-bot"}
 
- 			if strings.Contains(msg.Msg, Config.MyNick) {
 
- 				if StrInArray(Config.AutoJoin, msg.To) {
 
- 					// Yes, we were kicked from AutoJoin channel
 
- 					time.AfterFunc(time.Duration(Config.RejoinDelay)*time.Millisecond, func() { Config.WriteTo(msg.To, "JOIN "+msg.To) })
 
- 				}
 
- 			}
 
- 		}
 
- 		if Config.ReadChannel != nil {
 
- 			Config.ReadChannel <- msg
 
- 		}
 
- 		if (msg.Cmd == "376") || (msg.Cmd == "422") {
 
- 			// End MOTD, or MOTD Missing
 
- 			var reg IRCMsg = IRCMsg{Cmd: "EndMOTD"}
 
- 			Config.ReadChannel <- reg
 
- 		}
 
- 		if msg.Cmd == "NICK" {
 
- 			// :meow NICK :meow-bot
 
- 			if msg.From == Config.MyNick {
 
- 				Config.MyNick = msg.To
 
- 				log.Println("Nick is now:", Config.MyNick)
 
- 			}
 
- 		}
 
- 	}
 
- }
 
 
  |