watbot/wat/bot.go
Georg Pfuetzenreuter 97e9d7d0c2
Implement Jeopardy cashout
This adds integration between Watbot and the Limnoria Jeopardy plugin.
If a game of Jeopardy ends, Watbot will parse the finishers message and
pay a small share of the Jeopardy price money in the form of Watcoins.
To avoid abuse, only Jeopardy finishing messages from authorized bots
are considered. An IRC user is considered an authorized bot if the
hostmask matches one of the configured bot hostmasks, and if the
nickname is configured for "jeopardy" in the newly introduced bot games
configuration.

Signed-off-by: Georg Pfuetzenreuter <mail@georg-pfuetzenreuter.net>

Add sample message to Jeopardy logic

Signed-off-by: Georg Pfuetzenreuter <mail@georg-pfuetzenreuter.net>
2024-09-29 14:36:23 +02:00

233 lines
5.6 KiB
Go

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,
})
}