package wat import ( "crypto/tls" "fmt" "strconv" "strings" "github.com/go-irc/irc" ) type WatBot struct { client *irc.Client conn *tls.Conn c *WatConfig game *WatGame Db *WatDb Nick string } type BotGame struct { Games []string Name string } type BotGameConfig map[string][]string type WatConfig struct { BotHosts []string BotGames BotGameConfig AdminHosts []string IgnoredHosts []string AutoJoinChannels []string PermittedChannels []string } func NewWatBot(config *irc.ClientConfig, watConfig *WatConfig, serverConn *tls.Conn) *WatBot { wat := WatBot{conn: serverConn, Nick: config.Nick, c: watConfig} wat.Db = NewWatDb() wat.game = NewWatGame(&wat, wat.Db) config.Handler = irc.HandlerFunc(wat.HandleIrcMsg) wat.client = irc.NewClient(wat.conn, *config) return &wat } func CleanNick(nick string) string { return string(nick[0]) + "\u200c" + nick[1:] } func PrefixChannel(channel string) string { // there could theoretically be other channel prefixes .. if channel[0] != '#' && channel[0] != '!' { channel = "#" + channel } return channel } func (w *WatBot) HandleIrcMsg(c *irc.Client, m *irc.Message) { switch cmd := m.Command; cmd { case "PING": w.write("PONG", m.Params[0]) case "PRIVMSG": w.Msg(m) case "001": for _, channel := range w.c.AutoJoinChannels { w.write("JOIN", PrefixChannel(channel)) } } } func (w *WatBot) Admin(m *irc.Message) bool { return w.Allowed(m.Prefix.Host, w.c.AdminHosts) } func (w *WatBot) Bot(m *irc.Message) (bool, []string) { isBot := w.Allowed(m.Prefix.Host, w.c.BotHosts) var games []string if isBot { for b, g := range w.c.BotGames { if b == m.Prefix.Name { games = g break } } } return isBot, games } func (w *WatBot) Allowed(c string, r []string) bool { for _, allowed := range r { if c == allowed { return true } } return false } func (w *WatBot) CanRespond(m *irc.Message) bool { if w.Admin(m) { return true } if w.Allowed(m.Prefix.Host, w.c.IgnoredHosts) { return false } // if !strings.Contains(m.Prefix.Host, "") { // return false // } if !w.Allowed(PrefixChannel(m.Params[0]), w.c.PermittedChannels) { return false } return true } func (w *WatBot) Msg(m *irc.Message) { // bail out if you're not yves, if you're not tripsit or if you're not in an allowed channel // but if you're an admin you can do whatever if !w.CanRespond(m) { return } // make sure there's actually some text to process if len(m.Params[1]) == 0 { return } // fieldsfunc allows you to obtain rune separated fields/args args := strings.FieldsFunc(m.Params[1], func(c rune) bool { return c == ' ' }) if len(args) == 0 { return } if w.Admin(m) { // allow impersonation of the robot from anywhere if (args[0] == "imp" || args[0] == "imps") && len(args) > 2 { if args[0] == "imps" { w.write(args[1], args[2], strings.Join(args[3:], " ")) } else { w.write(args[1], args[2:]...) } return } } // strip offline message prefix from znc for handling offline buffer if args[0][0] == '[' && len(args) > 1 { args = args[1:] } // integration with games in other bots isBot, games := w.Bot(m) if isBot { // Jeopardy // parses a message "Top finishers: (nick1: 1300) (nick2: 1200)" from an authorized Jeopardy game bot if args[0] == "Top" && args[1] == "finishers:" && w.Allowed("jeopardy", games) { // hey, I avoided regex! finisherPrizes := strings.Split(strings.Replace(strings.Replace(strings.Replace(strings.Replace(strings.Join(args[2:], " "), ") (", ";", -1), ": ", ":", -1), "(", "", 1), ")", "", 1), ";") fmt.Printf("Processing Jeopardy: %s\n", finisherPrizes) for _, pair := range finisherPrizes { nameCoinPair := strings.Split(pair, ":") coins, err := strconv.ParseUint(nameCoinPair[1], 10, 64) if err != nil { fmt.Printf("Invalid coins, cannot process pair for cashout: %s.\n", nameCoinPair) continue } name := nameCoinPair[0] // Jeopardy prizes are quite a lot of $$$, make it a bit more sane coins = coins / 40 // name = we assume the Jeopardy player name to match a Watbot player name // host = we could use some WHO logic to find the host, but assuming nickname lookup to be sufficient here // create = based on the above, maybe rather not create Watbot players based on only a nick? // but it expects someone to have played with Watbot before to be eligible for Jeopardy cashout .. player := w.Db.User(name, "", false) if player.Nick == "" { fmt.Printf("Player %s does not exist in Watbot, skipping cashout.\n", name) continue } else { w.reply(m, fmt.Sprintf("smartass %s, gave u %d :)", player.Nick, coins)) player.Coins += coins w.Db.Update(player) } } return } } // check if command char (or something weird) is present if args[0] != "wat" && args[0][0] != '#' { return } // clean input if args[0][0] == '#' { args[0] = args[0][1:] } user := strings.ToLower(m.Prefix.Name) player := w.Db.User(user, m.Prefix.Host, true) w.game.Msg(m, &player, args) } func (w *WatBot) Run() { defer w.conn.Close() err := w.client.Run() if err != nil { fmt.Println("Error returned while running client: " + err.Error()) } } func (w *WatBot) say(dest, msg string) { if len(msg) == 0 { return } //fmt.Printf("MSG %s: %s\n", dest, msg) w.write("PRIVMSG", dest, msg) } func (w *WatBot) reply(s *irc.Message, r string) { sender := s.Params[0] if sender == w.Nick { sender = s.Prefix.Name } w.say(sender, r) } func (w *WatBot) write(cmd string, params ...string) { w.client.WriteMessage(&irc.Message{ Command: cmd, Params: params, }) }