parent
449f99cf69
commit
2baf3491b3
|
@ -7,10 +7,12 @@ import (
|
|||
var (
|
||||
firedBucket = []byte("fired")
|
||||
unbanBucket = []byte("unban")
|
||||
trackBucket = []byte("track")
|
||||
|
||||
buckets = [][]byte{
|
||||
firedBucket,
|
||||
unbanBucket,
|
||||
trackBucket,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"git.jojodev.com/Minecraft/canopeas/discord/track"
|
||||
"go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
// LoadTracker loads the tracks from the database
|
||||
func (db *Database) LoadTracker() (track.Tracker, error) {
|
||||
t := track.New()
|
||||
return t, db.db.View(func(tx *bbolt.Tx) error {
|
||||
return tx.Bucket(trackBucket).ForEach(func(k, v []byte) error {
|
||||
var users map[string]struct{}
|
||||
if err := json.Unmarshal(v, &users); err != nil {
|
||||
return err
|
||||
}
|
||||
t[string(k)] = users
|
||||
return nil
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// SetTrack sets a list of trackers for a word
|
||||
func (db *Database) SetTrack(word string, trackers map[string]struct{}) error {
|
||||
return db.db.Update(func(tx *bbolt.Tx) error {
|
||||
v, err := json.Marshal(trackers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Bucket(trackBucket).Put([]byte(word), v)
|
||||
})
|
||||
}
|
|
@ -2,6 +2,7 @@ package discord
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"git.jojodev.com/Minecraft/canopeas/discord/track"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -67,6 +68,12 @@ func Bot(cfg *config.Config, db *database.Database) (*discordgo.Session, error)
|
|||
twitterHttpClient := twitterConfig.Client(oauth1.NoContext, twitterToken)
|
||||
twitterClient := twitter.NewClient(twitterHttpClient)
|
||||
|
||||
// Init tracker
|
||||
t, err := db.LoadTracker()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Init Unban Schedule
|
||||
sched := &unbanSchedule{
|
||||
db: db,
|
||||
|
@ -104,7 +111,9 @@ func Bot(cfg *config.Config, db *database.Database) (*discordgo.Session, error)
|
|||
bot.AddHandler(joinHandler(cfg))
|
||||
bot.AddHandler(leaveHandler(cfg))
|
||||
bot.AddHandler(commandHandler(cfg, db, sapi, twitterClient))
|
||||
bot.AddHandler(messageHandler(cfg, db))
|
||||
bot.AddHandler(pingHandler(cfg, db))
|
||||
bot.AddHandler(trackHandler(t))
|
||||
bot.AddHandler(trackCommandHandler(t, db))
|
||||
bot.AddHandler(reactionAddHandler())
|
||||
bot.AddHandler(reactionRemoveHandler())
|
||||
|
||||
|
@ -118,6 +127,13 @@ func Bot(cfg *config.Config, db *database.Database) (*discordgo.Session, error)
|
|||
// Intents
|
||||
bot.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsAll)
|
||||
|
||||
// Create application commands
|
||||
if _, err := bot.ApplicationCommandBulkOverwrite(bot.State.User.ID, "", []*discordgo.ApplicationCommand{
|
||||
trackCommand,
|
||||
}); err != nil {
|
||||
log.Err(err).Msg("could not create application commands")
|
||||
}
|
||||
|
||||
return bot, nil
|
||||
}
|
||||
|
||||
|
@ -232,7 +248,7 @@ func commandHandler(cfg *config.Config, db *database.Database, sapi *serverapi.C
|
|||
}
|
||||
}
|
||||
|
||||
func messageHandler(cfg *config.Config, db *database.Database) func(s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||
func pingHandler(cfg *config.Config, db *database.Database) func(s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||
return func(s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||
// Ignore bots
|
||||
if m.Author.Bot {
|
||||
|
@ -250,6 +266,25 @@ func messageHandler(cfg *config.Config, db *database.Database) func(s *discordgo
|
|||
}
|
||||
}
|
||||
|
||||
func trackHandler(t track.Tracker) func(s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||
return func(s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||
// Ignore bots
|
||||
if m.Author.Bot {
|
||||
return
|
||||
}
|
||||
|
||||
trackers := t.Search(strings.ToLower(m.Content))
|
||||
for _, tracker := range trackers {
|
||||
// Ignore self-trackers
|
||||
if tracker == m.Author.ID {
|
||||
continue
|
||||
}
|
||||
sendMessage(s, tracker, fmt.Sprintf("%s\n>>>%s", m.Author.Mention(), m.Content), true)
|
||||
sendMessage(s, tracker, fmt.Sprintf("https://discord.com/channels/%s/%s/%s", m.GuildID, m.ChannelID, m.ID), true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func reactionAddHandler() func(s *discordgo.Session, m *discordgo.MessageReactionAdd) {
|
||||
return func(s *discordgo.Session, m *discordgo.MessageReactionAdd) {
|
||||
reactionHandler(true, s, m.MessageReaction)
|
||||
|
@ -290,7 +325,7 @@ func updateStatus(s *discordgo.Session) {
|
|||
dj, err := newDadJoke()
|
||||
if err != nil {
|
||||
log.Warn().Msgf("could not get new dad joke: %v", err)
|
||||
} else if err := s.UpdateStatus(1, dj.Joke); err != nil {
|
||||
} else if err := s.UpdateGameStatus(1, dj.Joke); err != nil {
|
||||
log.Warn().Msgf("could not update status: %v", err)
|
||||
}
|
||||
<-ticker.C
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
package discord
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.jojodev.com/Minecraft/canopeas/database"
|
||||
"git.jojodev.com/Minecraft/canopeas/discord/track"
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"github.com/rs/zerolog/log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var trackCommand = &discordgo.ApplicationCommand{
|
||||
Name: "track",
|
||||
Description: "Track words",
|
||||
Options: []*discordgo.ApplicationCommandOption{
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||
Name: "add",
|
||||
Description: "Add a tracked word",
|
||||
Options: []*discordgo.ApplicationCommandOption{
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionString,
|
||||
Name: "word",
|
||||
Description: "The word to track",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionSubCommand,
|
||||
Name: "remove",
|
||||
Description: "Remove a tracked word",
|
||||
Options: []*discordgo.ApplicationCommandOption{
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionString,
|
||||
Name: "word",
|
||||
Description: "The tracked word to remove",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
trackCommandHandler = func(t track.Tracker, d *database.Database) func(*discordgo.Session, *discordgo.InteractionCreate) {
|
||||
return func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
var action string
|
||||
word := strings.ToLower(i.ApplicationCommandData().Options[0].Options[0].StringValue())
|
||||
switch i.ApplicationCommandData().Options[0].Name {
|
||||
case "add":
|
||||
action = "added"
|
||||
t.Add(word, i.User.ID)
|
||||
case "remove":
|
||||
action = "removed"
|
||||
t.Remove(word, i.User.ID)
|
||||
default:
|
||||
log.Error().Msg("Track command did a woopsie")
|
||||
}
|
||||
if err := d.SetTrack(word, t[word]); err != nil {
|
||||
log.Err(err).Msg("")
|
||||
}
|
||||
if err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: fmt.Sprintf("Successfully %s tracking for %q", action, word),
|
||||
Flags: uint64(discordgo.MessageFlagsEphemeral),
|
||||
},
|
||||
}); err != nil {
|
||||
log.Err(err).Msg("")
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
|
@ -0,0 +1,52 @@
|
|||
package track
|
||||
|
||||
import "strings"
|
||||
|
||||
// Tracker is a mapping of tracked words to a set of user IDs
|
||||
type Tracker map[string]map[string]struct{}
|
||||
|
||||
// New returns an initialized Tracker
|
||||
func New() Tracker {
|
||||
return make(Tracker)
|
||||
}
|
||||
|
||||
// Add a user ID tracking a word
|
||||
func (t Tracker) Add(word, userID string) {
|
||||
if _, ok := t[word]; !ok {
|
||||
t[word] = make(map[string]struct{})
|
||||
}
|
||||
t[word][userID] = struct{}{}
|
||||
}
|
||||
|
||||
// Remove a user ID tracking a word
|
||||
func (t Tracker) Remove(word, userID string) bool {
|
||||
if _, ok := t[word][userID]; ok {
|
||||
delete(t[word], userID)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Search for a tracked word and return the list of unique user IDs
|
||||
func (t Tracker) Search(msg string) []string {
|
||||
tokens := make(map[string]struct{})
|
||||
fields := strings.Fields(msg)
|
||||
for _, field := range fields {
|
||||
field = strings.Trim(field, `.,;:'"!?`)
|
||||
tokens[field] = struct{}{}
|
||||
}
|
||||
|
||||
set := make(map[string]struct{}, 0)
|
||||
for word, users := range t {
|
||||
if _, ok := tokens[word]; ok {
|
||||
for user := range users {
|
||||
set[user] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
users := make([]string, 0, len(set))
|
||||
for user := range set {
|
||||
users = append(users, user)
|
||||
}
|
||||
return users
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package track
|
||||
|
||||
import (
|
||||
"github.com/matryer/is"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTracker(t *testing.T) {
|
||||
assert := is.New(t)
|
||||
|
||||
track := New()
|
||||
|
||||
track.Add("admin", "foo")
|
||||
track.Add("mod", "foo")
|
||||
track.Add("mod", "bar")
|
||||
track.Add("mode", "bar")
|
||||
|
||||
admin := track.Search("I need an admin.")
|
||||
assert.Equal(len(admin), 1) // One tracker for admin
|
||||
|
||||
mod := track.Search("I need a mod. Please send me a mod.")
|
||||
assert.Equal(len(mod), 2) // Two trackers for mod
|
||||
|
||||
mode := track.Search("I'm in gaminga mode!")
|
||||
assert.Equal(len(mode), 1) // One tracker for mode
|
||||
|
||||
}
|
7
go.mod
7
go.mod
|
@ -7,11 +7,14 @@ require (
|
|||
git.jojodev.com/Minecraft/go-serverapi v0.0.1
|
||||
gitea.com/jolheiser/gojang v0.0.7
|
||||
gitea.com/jolheiser/xkcd v0.0.2
|
||||
github.com/bwmarrin/discordgo v0.22.0
|
||||
github.com/bwmarrin/discordgo v0.23.3-0.20220223175904-4cc53b7ed45c
|
||||
github.com/dghubble/go-twitter v0.0.0-20201011215211-4b180d0cc78d
|
||||
github.com/dghubble/oauth1 v0.7.0
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/matryer/is v1.4.0 // indirect
|
||||
github.com/pelletier/go-toml v1.8.1
|
||||
github.com/rs/zerolog v1.26.1
|
||||
go.etcd.io/bbolt v1.3.4
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect
|
||||
golang.org/x/sys v0.0.0-20220224003255-dbe011f71a99 // indirect
|
||||
)
|
||||
|
|
13
go.sum
13
go.sum
|
@ -8,6 +8,8 @@ gitea.com/jolheiser/xkcd v0.0.2 h1:HJP83YwSKxSYcoNfpb1ZpAfBvkUAnN+YgeukraXtfrc=
|
|||
gitea.com/jolheiser/xkcd v0.0.2/go.mod h1:aDa2vX54wLaX8Ra5CGN2GWBX13UWAGJKGGddzHl/hks=
|
||||
github.com/bwmarrin/discordgo v0.22.0 h1:uBxY1HmlVCsW1IuaPjpCGT6A2DBwRn0nvOguQIxDdFM=
|
||||
github.com/bwmarrin/discordgo v0.22.0/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M=
|
||||
github.com/bwmarrin/discordgo v0.23.3-0.20220223175904-4cc53b7ed45c h1:efrWWIJhwYDtEhZsduYcJVtMD0m5qb/1X/5TExrsWXg=
|
||||
github.com/bwmarrin/discordgo v0.23.3-0.20220223175904-4cc53b7ed45c/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
||||
github.com/cenkalti/backoff v2.1.1+incompatible h1:tKJnvO2kl0zmb/jA5UKAt4VoEVw1qxKWjE/Bpp46npY=
|
||||
github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
|
@ -26,6 +28,10 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO
|
|||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
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/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
|
||||
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
|
@ -45,13 +51,17 @@ go.jolheiser.com/gql v0.0.1/go.mod h1:74eYqVRIxsOFxtVl0RYGKNyYQgJYQaxOCgar7LP71H
|
|||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
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-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e h1:1SzTfNOXwIS2oWiMF+6qu0OUDKb0dauo6MoDUQyu+yU=
|
||||
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -59,8 +69,11 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220224003255-dbe011f71a99 h1:Us899Z5PCfOrSgeCYWobI1/bSigAz9Rhf8+fz5Grkzc=
|
||||
golang.org/x/sys v0.0.0-20220224003255-dbe011f71a99/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
|
|
Loading…
Reference in New Issue