mirror of
https://git.sr.ht/~ashkeel/strimertul
synced 2024-09-18 01:50:50 +00:00
Add callable functions to custom commands
This commit is contained in:
parent
de7c23f14b
commit
8596ce3f6d
8 changed files with 151 additions and 35 deletions
|
@ -225,11 +225,11 @@ export default function TwitchBotCommandsPage(
|
|||
dispatch(
|
||||
setCommands({
|
||||
...commands,
|
||||
[oldName]: undefined,
|
||||
[newName]: {
|
||||
...commands[oldName],
|
||||
...data,
|
||||
},
|
||||
[oldName]: undefined,
|
||||
}),
|
||||
);
|
||||
setShowModifyCommand(null);
|
||||
|
|
7
go.mod
7
go.mod
|
@ -3,10 +3,17 @@ module github.com/strimertul/strimertul
|
|||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
|
||||
github.com/dgraph-io/badger/v3 v3.2011.1
|
||||
github.com/gempir/go-twitch-irc/v2 v2.5.0
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/huandu/xstrings v1.3.2 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/json-iterator/go v1.1.11
|
||||
github.com/mattn/go-colorable v0.1.8
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/nicklaw5/helix v1.15.0
|
||||
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
|
|
18
go.sum
18
go.sum
|
@ -2,6 +2,12 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
|||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM=
|
||||
github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
|
@ -44,11 +50,17 @@ github.com/google/flatbuffers v1.12.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv
|
|||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-dap v0.2.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
|
||||
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
|
@ -63,8 +75,12 @@ github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope
|
|||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mmcloughlin/avo v0.0.0-20201105074841-5d2f697d268f/go.mod h1:6aKT4zZIrpGqB3RpFU14ByCSSyKY6LfJz4J/JJChHfI=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
|
@ -131,6 +147,7 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf
|
|||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210505212654-3497b51f5e64 h1:QuAh/1Gwc0d+u9walMU1NqzhRemNegsv5esp2ALQIY4=
|
||||
golang.org/x/crypto v0.0.0-20210505212654-3497b51f5e64/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
@ -190,5 +207,6 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
|
|||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
|
3
main.go
3
main.go
|
@ -5,6 +5,7 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"time"
|
||||
|
@ -69,6 +70,8 @@ func main() {
|
|||
loglevel := flag.String("loglevel", "info", "Logging level (debug, info, warn, error)")
|
||||
flag.Parse()
|
||||
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
log.SetLevel(parseLogLevel(*loglevel))
|
||||
|
||||
// Ok this is dumb but listen, I like colors.
|
||||
|
|
|
@ -110,21 +110,29 @@ func (m *Manager) update(kvs []database.ModifiedKV) error {
|
|||
// Check for config changes/RPC
|
||||
switch key {
|
||||
case ConfigKey:
|
||||
m.mu.Lock()
|
||||
err = jsoniter.ConfigFastest.Unmarshal(kv.Data, &m.config)
|
||||
m.mu.Unlock()
|
||||
err = func() error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
return jsoniter.ConfigFastest.Unmarshal(kv.Data, &m.config)
|
||||
}()
|
||||
case GoalsKey:
|
||||
m.mu.Lock()
|
||||
err = jsoniter.ConfigFastest.Unmarshal(kv.Data, &m.goals)
|
||||
m.mu.Unlock()
|
||||
err = func() error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
return jsoniter.ConfigFastest.Unmarshal(kv.Data, &m.goals)
|
||||
}()
|
||||
case RewardsKey:
|
||||
m.mu.Lock()
|
||||
err = jsoniter.ConfigFastest.Unmarshal(kv.Data, &m.rewards)
|
||||
m.mu.Unlock()
|
||||
err = func() error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
return jsoniter.ConfigFastest.Unmarshal(kv.Data, &m.rewards)
|
||||
}()
|
||||
case QueueKey:
|
||||
m.mu.Lock()
|
||||
err = jsoniter.ConfigFastest.Unmarshal(kv.Data, &m.queue)
|
||||
m.mu.Unlock()
|
||||
err = func() error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
return jsoniter.ConfigFastest.Unmarshal(kv.Data, &m.queue)
|
||||
}()
|
||||
case CreateRedeemRPC:
|
||||
var redeem Redeem
|
||||
err = jsoniter.ConfigFastest.Unmarshal(kv.Data, &redeem)
|
||||
|
@ -145,9 +153,11 @@ func (m *Manager) update(kvs []database.ModifiedKV) error {
|
|||
var entry PointsEntry
|
||||
err = jsoniter.ConfigFastest.Unmarshal(kv.Data, &entry)
|
||||
user := kv.Key[len(PointsPrefix):]
|
||||
m.mu.Lock()
|
||||
m.points[user] = entry
|
||||
m.mu.Unlock()
|
||||
func() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.points[user] = entry
|
||||
}()
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
|
|
|
@ -4,8 +4,10 @@ import (
|
|||
"context"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/sprig"
|
||||
irc "github.com/gempir/go-twitch-irc/v2"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
@ -25,8 +27,10 @@ type Bot struct {
|
|||
banlist map[string]bool
|
||||
chatHistory []irc.PrivateMessage
|
||||
|
||||
commands map[string]BotCommand
|
||||
customCommands map[string]BotCustomCommand
|
||||
commands map[string]BotCommand
|
||||
customCommands map[string]BotCustomCommand
|
||||
customTemplates map[string]*template.Template
|
||||
customFunctions template.FuncMap
|
||||
|
||||
mu sync.Mutex
|
||||
|
||||
|
@ -39,17 +43,18 @@ func NewBot(api *Client, config BotConfig) *Bot {
|
|||
client := irc.NewClient(config.Username, config.Token)
|
||||
|
||||
bot := &Bot{
|
||||
Client: client,
|
||||
username: strings.ToLower(config.Username), // Normalize username
|
||||
config: config,
|
||||
logger: api.logger,
|
||||
api: api,
|
||||
lastMessage: time.Now(),
|
||||
activeUsers: make(map[string]bool),
|
||||
banlist: make(map[string]bool),
|
||||
mu: sync.Mutex{},
|
||||
commands: make(map[string]BotCommand),
|
||||
customCommands: make(map[string]BotCustomCommand),
|
||||
Client: client,
|
||||
username: strings.ToLower(config.Username), // Normalize username
|
||||
config: config,
|
||||
logger: api.logger,
|
||||
api: api,
|
||||
lastMessage: time.Now(),
|
||||
activeUsers: make(map[string]bool),
|
||||
banlist: make(map[string]bool),
|
||||
mu: sync.Mutex{},
|
||||
commands: make(map[string]BotCommand),
|
||||
customCommands: make(map[string]BotCustomCommand),
|
||||
customTemplates: make(map[string]*template.Template),
|
||||
}
|
||||
|
||||
client.OnPrivateMessage(func(message irc.PrivateMessage) {
|
||||
|
@ -81,7 +86,7 @@ func NewBot(api *Client, config BotConfig) *Bot {
|
|||
continue
|
||||
}
|
||||
if strings.HasPrefix(message.Message, cmd) {
|
||||
go cmdCustom(bot, data, message)
|
||||
go cmdCustom(bot, cmd, data, message)
|
||||
bot.lastMessage = time.Now()
|
||||
}
|
||||
}
|
||||
|
@ -124,7 +129,12 @@ func NewBot(api *Client, config BotConfig) *Bot {
|
|||
bot.Client.Join(config.Channel)
|
||||
|
||||
// Load custom commands
|
||||
bot.setupFunctions()
|
||||
api.db.GetJSON(CustomCommandsKey, &bot.customCommands)
|
||||
err := bot.updateTemplates()
|
||||
if err != nil {
|
||||
bot.logger.WithError(err).Error("failed to load custom commands")
|
||||
}
|
||||
go api.db.Subscribe(context.Background(), bot.updateCommands, CustomCommandsKey)
|
||||
|
||||
return bot
|
||||
|
@ -135,9 +145,28 @@ func (b *Bot) updateCommands(kvs []database.ModifiedKV) error {
|
|||
key := string(kv.Key)
|
||||
switch key {
|
||||
case CustomCommandsKey:
|
||||
b.mu.Lock()
|
||||
err := jsoniter.ConfigFastest.Unmarshal(kv.Data, &b.customCommands)
|
||||
b.mu.Unlock()
|
||||
err := func() error {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
return jsoniter.ConfigFastest.Unmarshal(kv.Data, &b.customCommands)
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Recreate templates
|
||||
if err := b.updateTemplates(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bot) updateTemplates() error {
|
||||
for cmd, tmpl := range b.customCommands {
|
||||
var err error
|
||||
b.customTemplates[cmd], err = template.New("").Funcs(sprig.TxtFuncMap()).Funcs(b.customFunctions).Parse(tmpl.Response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
package twitch
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
irc "github.com/gempir/go-twitch-irc/v2"
|
||||
"github.com/nicklaw5/helix"
|
||||
)
|
||||
|
||||
type AccessLevelType string
|
||||
|
@ -23,7 +30,47 @@ type BotCommand struct {
|
|||
Enabled bool
|
||||
}
|
||||
|
||||
func cmdCustom(bot *Bot, cmd BotCustomCommand, message irc.PrivateMessage) {
|
||||
func cmdCustom(bot *Bot, cmd string, data BotCustomCommand, message irc.PrivateMessage) {
|
||||
// Add future logic (like counters etc) here, for now it's just fixed messages
|
||||
bot.Client.Say(message.Channel, cmd.Response)
|
||||
var buf bytes.Buffer
|
||||
err := bot.customTemplates[cmd].Execute(&buf, message)
|
||||
if err != nil {
|
||||
bot.logger.WithError(err).Error("Failed to execute custom command template")
|
||||
return
|
||||
}
|
||||
bot.Client.Say(message.Channel, buf.String())
|
||||
}
|
||||
|
||||
func (b *Bot) setupFunctions() {
|
||||
b.customFunctions = template.FuncMap{
|
||||
"user": func(message irc.PrivateMessage) string {
|
||||
return message.User.DisplayName
|
||||
},
|
||||
"param": func(num int, message irc.PrivateMessage) string {
|
||||
parts := strings.Split(message.Message, " ")
|
||||
if num >= len(parts) {
|
||||
return parts[len(parts)-1]
|
||||
}
|
||||
return parts[num]
|
||||
},
|
||||
"randomInt": func(min int, max int) int {
|
||||
return rand.Intn(max-min) + min
|
||||
},
|
||||
"game": func(channel string) string {
|
||||
info, err := b.api.API.SearchChannels(&helix.SearchChannelsParams{Channel: channel, First: 1, LiveOnly: false})
|
||||
if err != nil {
|
||||
return "unknown"
|
||||
}
|
||||
return info.Data.Channels[0].GameName
|
||||
},
|
||||
"count": func(name string) int {
|
||||
counter := 0
|
||||
if byt, err := b.api.db.GetKey(BotCounterPrefix + name); err == nil {
|
||||
counter, _ = strconv.Atoi(string(byt))
|
||||
}
|
||||
counter += 1
|
||||
b.api.db.PutKey(BotCounterPrefix+name, []byte(strconv.Itoa(counter)))
|
||||
return counter
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,3 +31,5 @@ type BotCustomCommand struct {
|
|||
const CustomCommandsKey = "twitch/bot-custom-commands"
|
||||
|
||||
const WriteMessageRPC = "twitch/@send-chat-message"
|
||||
|
||||
const BotCounterPrefix = "twitch/bot-counters/"
|
||||
|
|
Loading…
Reference in a new issue