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 }