skeleton- bot skeleton for irc |
git clone git://git.acid.vegas/skeleton.git |
Log | Files | Refs | Archive | README | LICENSE |
skelly.go (7409B)
1 // irc bot skeleton - developed by acidvegas in golang (https://git.acid.vegas/skeleton) 2 3 package main 4 5 import ( 6 "bufio" 7 "crypto/tls" 8 "flag" 9 "fmt" 10 "log" 11 "net" 12 "strings" 13 "time" 14 ) 15 16 // IRC color & control codes 17 const ( 18 bold = "\x02" 19 italic = "\x1D" 20 underline = "\x1F" 21 reverse = "\x16" 22 reset = "\x0f" 23 white = "00" 24 black = "01" 25 blue = "02" 26 green = "03" 27 red = "04" 28 brown = "05" 29 purple = "06" 30 orange = "07" 31 yellow = "08" 32 lightGreen = "09" 33 cyan = "10" 34 lightCyan = "11" 35 lightBlue = "12" 36 pink = "13" 37 grey = "14" 38 lightGrey = "15" 39 ) 40 41 var ( 42 // Connection settings 43 server string 44 port int 45 channel string 46 key string 47 password string 48 ipv4 bool 49 ipv6 bool 50 vhost string 51 52 // SSL settings 53 useSSL bool 54 sslVerify bool 55 sslCert string 56 sslPass string 57 58 // Bot settings 59 nick string 60 user string 61 real string 62 nickserv string 63 operserv string 64 mode string 65 flood int 66 ) 67 68 func init() { 69 flag.StringVar(&server, "server", "", "The IRC server address.") 70 flag.IntVar(&port, "port", 6667, "The port number for the IRC server.") 71 flag.StringVar(&channel, "channel", "", "The IRC channel to join.") 72 flag.StringVar(&key, "key", "", "The key (password) for the IRC channel, if required.") 73 flag.StringVar(&password, "password", "", "The password for the IRC server.") 74 flag.BoolVar(&ipv4, "v4", false, "Use IPv4 for the connection.") 75 flag.BoolVar(&ipv6, "v6", false, "Use IPv6 for the connection.") 76 flag.StringVar(&vhost, "vhost", "", "The VHOST to use for connection.") 77 flag.BoolVar(&useSSL, "ssl", false, "Use SSL for the connection.") 78 flag.BoolVar(&sslVerify, "ssl-verify", false, "Verify SSL certificates.") 79 flag.StringVar(&sslCert, "ssl-cert", "", "The SSL certificate to use for the connection.") 80 flag.StringVar(&sslPass, "ssl-pass", "", "The SSL certificate password.") 81 flag.StringVar(&nick, "nick", "skelly", "The nickname to use for the bot.") 82 flag.StringVar(&user, "user", "skelly", "The username to use for the bot.") 83 flag.StringVar(&real, "real", "Development Bot", "The realname to use for the bot.") 84 flag.StringVar(&mode, "mode", "+B", "The mode to set on the bot's nickname.") 85 flag.StringVar(&nickserv, "nickserv", "", "The password for the bot's nickname to be identified with NickServ.") 86 flag.StringVar(&operserv, "operserv", "", "The password for the bot's nickname to be identified with OperServ.") 87 flag.IntVar(&flood, "flood", 3, "Delay between command usage.") 88 flag.Parse() 89 } 90 91 func logfmt(option string, message string) string { 92 switch option { 93 case "DEBUG": 94 return fmt.Sprintf("\033[95m%s\033[0m [\033[95mDEBUG\033[0m] %s", getnow(), message) 95 case "ERROR": 96 return fmt.Sprintf("\033[95m%s\033[0m [\033[31mERROR\033[0m] %s", getnow(), message) 97 case "SEND": 98 return fmt.Sprintf("\033[95m%s\033[0m [\033[92mSEND\033[0m] %s", getnow(), message) 99 case "RECV": 100 return fmt.Sprintf("\033[95m%s\033[0m [\033[96mRECV\033[0m] %s", getnow(), message) 101 } 102 return fmt.Sprintf("\033[95m%s\033[0m [\033[95mDEBUG\033[0m] %s", getnow(), message) // This should never happen 103 } 104 105 func color(msg string, foreground string, background string) string { 106 if background != "" { 107 return fmt.Sprintf("\x03%s,%s%s%s", foreground, background, msg, reset) 108 } 109 return fmt.Sprintf("\x03%s%s%s", foreground, msg, reset) 110 } 111 112 type Bot struct { 113 nickname string 114 username string 115 realname string 116 conn net.Conn 117 reader *bufio.Reader 118 writer *bufio.Writer 119 last time.Time 120 slow bool 121 } 122 123 func Skeleton() *Bot { 124 return &Bot{ 125 nickname: "skeleton", 126 username: "skelly", 127 realname: "Development Bot", 128 } 129 } 130 131 func (bot *Bot) Connect() error { 132 address := fmt.Sprintf("%s:%d", server, port) 133 134 var networkType string 135 switch { 136 case ipv4: 137 networkType = "tcp4" 138 case ipv6: 139 networkType = "tcp6" 140 default: 141 networkType = "tcp" 142 } 143 144 var dialer net.Dialer 145 146 if vhost != "" { 147 localAddr, err := net.ResolveTCPAddr(networkType, vhost+":0") 148 if err != nil { 149 return fmt.Errorf("failed to resolve local address: %w", err) 150 } 151 dialer.LocalAddr = localAddr 152 } 153 154 var err error 155 if useSSL { 156 tlsConfig := &tls.Config{ 157 InsecureSkipVerify: !sslVerify, 158 } 159 160 if sslCert != "" { 161 var cert tls.Certificate 162 cert, err = tls.LoadX509KeyPair(sslCert, sslPass) 163 if err != nil { 164 return fmt.Errorf("failed to load SSL certificate: %w", err) 165 } 166 tlsConfig.Certificates = []tls.Certificate{cert} 167 } 168 169 bot.conn, err = tls.DialWithDialer(&dialer, networkType, address, tlsConfig) 170 } else { 171 bot.conn, err = dialer.Dial(networkType, address) 172 } 173 174 if err != nil { 175 return fmt.Errorf("failed to dial: %w", err) 176 } 177 178 bot.reader = bufio.NewReader(bot.conn) 179 bot.writer = bufio.NewWriter(bot.conn) 180 181 if password != "" { 182 bot.raw("PASS " + password) 183 } 184 bot.raw(fmt.Sprintf("USER %s 0 * :%s", user, real)) 185 bot.raw("NICK " + nick) 186 187 return nil 188 } 189 190 func (bot *Bot) raw(data string) { 191 if bot.writer != nil { 192 bot.writer.WriteString(data + "\r\n") 193 bot.writer.Flush() 194 if strings.Split(data, " ")[0] == "PONG" { 195 fmt.Println(logfmt("SEND", "\033[93m"+data+"\033[0m")) 196 } else { 197 fmt.Println(logfmt("SEND", data)) 198 } 199 } 200 } 201 202 func (bot *Bot) sendMsg(target string, msg string) { 203 bot.raw(fmt.Sprintf("PRIVMSG %s :%s", target, msg)) 204 } 205 206 func (bot *Bot) handle(data string) { 207 parts := strings.Fields(data) 208 209 if len(parts) < 2 { 210 return 211 } 212 213 if parts[0] != "PING" { 214 parts[1] = "\033[38;5;141m" + parts[1] + "\033[0m" 215 } 216 coloredData := strings.Join(parts, " ") 217 fmt.Println(logfmt("RECV", coloredData)) 218 219 parts = strings.Fields(data) 220 if parts[0] == "PING" { 221 bot.raw("PONG " + parts[1]) 222 return 223 } else { 224 command := parts[1] 225 switch command { 226 case "001": // RPL_WELCOME 227 bot.raw("MODE " + nick + " " + mode) 228 229 if nickserv != "" { 230 bot.raw("PRIVMSG NickServ :IDENTIFY " + nickserv) 231 } 232 233 if operserv != "" { 234 bot.raw("OPER " + nick + " " + operserv) 235 } 236 237 go func() { 238 time.Sleep(15 * time.Second) 239 if key != "" { 240 bot.raw("JOIN " + channel + " " + key) 241 } else { 242 bot.raw("JOIN " + channel) 243 } 244 }() 245 case "PRIVMSG": 246 bot.eventPrivMsg(data) 247 } 248 } 249 } 250 251 func getnow() string { 252 return time.Now().Format("03:04:05") 253 } 254 255 func (bot *Bot) eventPrivMsg(data string) { 256 parts := strings.Split(data, " ") 257 ident := strings.TrimPrefix(parts[0], ":") 258 nick := strings.Split(ident, "!")[0] 259 target := parts[2] 260 msg := strings.Join(parts[3:], " ")[1:] 261 262 if target == bot.nickname { 263 // Private message handling 264 } else if strings.HasPrefix(target, "#") { 265 if target == channel { 266 if msg == "!test" { 267 bot.sendMsg(channel, nick+": Test successful!") 268 } 269 } 270 } 271 } 272 func main() { 273 for { 274 fmt.Printf("\033[90m%s\033[0m [\033[95mDEBUG\033[0m] Connecting to %s:%d and joining %s\n", getnow(), server, port, channel) 275 276 bot := Skeleton() 277 err := bot.Connect() 278 279 if err != nil { 280 log.Printf("\033[90m%s\033[0m [\033[31mERROR\033[0m]\033[93m Failed to connect to server! Retrying in 15 seconds... \033[90m(%v)\033[0m", getnow(), err) 281 } else { 282 for { 283 line, _, err := bot.reader.ReadLine() 284 if err != nil { 285 log.Printf("\033[90m%s\033[0m [\033[31mERROR\033[0m]\033[93m Lost connection to server! Retrying in 15 seconds... \033[90m(%v)\033[0m", getnow(), err) 286 break 287 } 288 bot.handle(string(line)) 289 } 290 } 291 292 if bot.conn != nil { 293 bot.conn.Close() 294 } 295 296 time.Sleep(15 * time.Second) 297 } 298 }