watbot/wat/bot.go

233 lines
5.6 KiB
Go
Raw Normal View History

package wat
import (
"crypto/tls"
"fmt"
"strconv"
"strings"
"github.com/go-irc/irc"
)
type WatBot struct {
client *irc.Client
conn *tls.Conn
2019-01-04 10:55:40 +01:00
c *WatConfig
game *WatGame
Db *WatDb
Nick string
}
type BotGame struct {
Games []string
Name string
}
type BotGameConfig map[string][]string
2019-01-04 10:55:40 +01:00
type WatConfig struct {
BotHosts []string
BotGames BotGameConfig
AdminHosts []string
2019-01-04 10:55:40 +01:00
IgnoredHosts []string
AutoJoinChannels []string
PermittedChannels []string
}
2019-01-04 10:55:40 +01:00
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
}
2019-01-04 10:55:40 +01:00
func (w *WatBot) Allowed(c string, r []string) bool {
for _, allowed := range r {
if c == allowed {
return true
}
}
return false
}
2019-01-04 10:55:40 +01:00
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
}
2021-03-10 15:03:48 +01:00
// if !strings.Contains(m.Prefix.Host, "") {
// return false
// }
if !w.Allowed(PrefixChannel(m.Params[0]), w.c.PermittedChannels) {
2019-01-04 10:55:40 +01:00
return false
}
return true
}
func (w *WatBot) Msg(m *irc.Message) {
2018-10-23 03:36:02 +02:00
// 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
2019-01-04 10:55:40 +01:00
if !w.CanRespond(m) {
return
}
// make sure there's actually some text to process
2018-10-23 03:36:02 +02:00
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 == ' ' })
2018-10-23 03:36:02 +02:00
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:]...)
}
2018-10-23 03:36:02 +02:00
return
}
}
2018-10-23 03:36:02 +02:00
// 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
}
}
2018-10-23 03:36:02 +02:00
// check if command char (or something weird) is present
if args[0] != "wat" && args[0][0] != '#' {
return
}
2018-10-23 03:36:02 +02:00
// clean input
if args[0][0] == '#' {
args[0] = args[0][1:]
}
2018-10-23 03:36:02 +02:00
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,
})
}