catbase/plugins/first/first.go

287 lines
7.6 KiB
Go
Raw Normal View History

2016-01-17 18:00:44 +00:00
// © 2013 the CatBase Authors under the WTFPL. See AUTHORS for the list of authors.
package first
import (
"fmt"
2021-12-20 17:40:10 +00:00
bh "github.com/timshannon/bolthold"
"regexp"
"strings"
"time"
2019-03-07 16:35:42 +00:00
"github.com/rs/zerolog/log"
"github.com/velour/catbase/config"
2021-02-04 01:35:56 +00:00
2016-01-17 18:00:44 +00:00
"github.com/velour/catbase/bot"
"github.com/velour/catbase/bot/msg"
)
// This is a first plugin to serve as an example and quick copy/paste for new plugins.
type FirstPlugin struct {
2021-02-04 01:35:56 +00:00
bot bot.Bot
config *config.Config
2021-12-20 17:40:10 +00:00
store *bh.Store
2021-02-04 01:35:56 +00:00
handlers bot.HandlerTable
enabled bool
}
type FirstEntry struct {
id int64
day time.Time
time time.Time
channel string
body string
nick string
saved bool
2016-01-15 16:54:09 +00:00
}
// Insert or update the first entry
2021-12-20 17:40:10 +00:00
func (fe *FirstEntry) save(store *bh.Store) error {
return store.Insert(bh.NextSequence(), fe)
}
2021-12-20 17:40:10 +00:00
func (fe *FirstEntry) delete(store *bh.Store) error {
return store.Delete(fe.id, FirstEntry{})
2021-02-04 01:35:56 +00:00
}
// NewFirstPlugin creates a new FirstPlugin with the Plugin interface
func New(b bot.Bot) *FirstPlugin {
2019-03-07 16:35:42 +00:00
log.Info().Msgf("First plugin initialized with day: %s",
2021-04-27 16:36:34 +00:00
Midnight(time.Now()))
2016-01-15 16:54:09 +00:00
fp := &FirstPlugin{
2021-02-04 01:35:56 +00:00
bot: b,
config: b.Config(),
2021-12-20 17:40:10 +00:00
store: b.Store(),
2021-02-04 01:35:56 +00:00
enabled: true,
}
2021-02-04 01:35:56 +00:00
fp.register()
b.Register(fp, bot.Help, fp.help)
return fp
}
2021-12-20 17:40:10 +00:00
func getLastFirst(store *bh.Store, channel string) (*FirstEntry, error) {
fe := &FirstEntry{}
err := store.FindOne(fe, bh.Where("channel").Eq(channel))
if err != nil {
2018-06-22 18:31:33 +00:00
return nil, err
2016-01-15 16:54:09 +00:00
}
2021-12-20 17:40:10 +00:00
log.Debug().Msgf("id: %v day %v time %v body %v nick %v", fe)
return fe, nil
2016-01-15 16:54:09 +00:00
}
2021-04-27 16:36:34 +00:00
func Midnight(t time.Time) time.Time {
y, m, d := t.Date()
2021-04-27 16:36:34 +00:00
return time.Date(y, m, d, 0, 0, 0, 0, time.Local)
}
func (p *FirstPlugin) isNotToday(f *FirstEntry) bool {
if f == nil {
return true
}
t := f.time
2021-04-27 16:36:34 +00:00
t0 := Midnight(t)
jitter := time.Duration(p.config.GetInt("first.jitter", 0))
2021-07-21 13:54:29 +00:00
t0 = t0.Add(jitter * time.Millisecond)
2021-04-27 16:36:34 +00:00
return t0.Before(Midnight(time.Now()))
}
2021-02-04 01:35:56 +00:00
func (p *FirstPlugin) register() {
p.handlers = []bot.HandlerSpec{
{Kind: bot.Message, IsCmd: false,
Regex: regexp.MustCompile(`(?i)^who'?s on first the most.?$`),
Handler: func(r bot.Request) bool {
2021-12-20 17:40:10 +00:00
first, err := getLastFirst(p.store, r.Msg.Channel)
2021-02-04 01:35:56 +00:00
if first != nil && err == nil {
p.leaderboard(r.Conn, r.Msg.Channel)
return true
}
return false
}},
{Kind: bot.Message, IsCmd: false,
Regex: regexp.MustCompile(`(?i)^who'?s on first.?$`),
Handler: func(r bot.Request) bool {
2021-12-20 17:40:10 +00:00
first, err := getLastFirst(p.store, r.Msg.Channel)
2021-02-04 01:35:56 +00:00
if first != nil && err == nil {
p.announceFirst(r.Conn, first)
return true
}
return false
}},
{Kind: bot.Message, IsCmd: true,
Regex: regexp.MustCompile(`(?i)^clear first$`),
Handler: func(r bot.Request) bool {
if !p.bot.CheckAdmin(r.Msg.User.Name) {
p.bot.Send(r.Conn, bot.Message, r.Msg.Channel, "You are not authorized to do that.")
return true
}
2021-12-20 17:40:10 +00:00
fe, err := getLastFirst(p.store, r.Msg.Channel)
2021-02-04 01:35:56 +00:00
if err != nil {
p.bot.Send(r.Conn, bot.Message, r.Msg.Channel, "Could not find a first entry.")
return true
}
p.enabled = false
2021-12-20 17:40:10 +00:00
err = fe.delete(p.store)
2021-02-04 01:35:56 +00:00
if err != nil {
p.bot.Send(r.Conn, bot.Message, r.Msg.Channel,
fmt.Sprintf("Could not delete first entry: %s", err))
p.enabled = true
return true
}
d := p.bot.Config().GetInt("first.maxregen", 300)
log.Debug().Msgf("Setting first timer for %d seconds", d)
timer := time.NewTimer(time.Duration(d) * time.Second)
go func() {
<-timer.C
p.enabled = true
log.Debug().Msgf("Re-enabled first")
}()
p.bot.Send(r.Conn, bot.Message, r.Msg.Channel,
fmt.Sprintf("Deleted first entry: '%s' and set a random timer for when first will happen next.", fe.body))
return true
}},
{Kind: bot.Message, IsCmd: false,
Regex: regexp.MustCompile(`.*`),
Handler: func(r bot.Request) bool {
2021-08-26 14:03:22 +00:00
if r.Msg.IsIM || !p.enabled || !p.enabled_channel(r) {
2021-02-04 01:35:56 +00:00
return false
}
2021-12-20 17:40:10 +00:00
first, err := getLastFirst(p.store, r.Msg.Channel)
2021-02-04 01:35:56 +00:00
if err != nil {
log.Error().
Err(err).
Msg("Error getting last first")
}
2021-02-04 01:35:56 +00:00
log.Debug().Bool("first == nil", first == nil).Msg("Is first nil?")
log.Debug().Bool("first == nil || isNotToday()", p.isNotToday(first)).Msg("Is it today?")
2021-02-04 01:35:56 +00:00
log.Debug().Bool("p.allowed", p.allowed(r.Msg)).Msg("Allowed?")
if (first == nil || p.isNotToday(first)) && p.allowed(r.Msg) {
2021-02-04 01:35:56 +00:00
log.Debug().
Str("body", r.Msg.Body).
Interface("t0", first).
Time("t1", time.Now()).
Msg("Recording first")
p.recordFirst(r.Conn, r.Msg)
return false
}
return false
}},
2019-10-26 15:26:31 +00:00
}
2021-02-04 01:35:56 +00:00
p.bot.RegisterTable(p, p.handlers)
}
2021-08-26 14:03:22 +00:00
func (p *FirstPlugin) enabled_channel(r bot.Request) bool {
chs := p.config.GetArray("first.channels", []string{})
for _, ch := range chs {
if r.Msg.Channel == ch {
return true
}
}
return false
}
func (p *FirstPlugin) allowed(message msg.Message) bool {
2020-10-20 01:28:13 +00:00
if message.Body == "" {
return false
}
2021-02-04 01:35:56 +00:00
for _, m := range p.bot.Config().GetArray("Bad.Msgs", []string{}) {
match, err := regexp.MatchString(m, strings.ToLower(message.Body))
if err != nil {
2019-03-07 16:35:42 +00:00
log.Error().Err(err).Msg("Bad regexp")
}
if match {
2019-03-07 16:35:42 +00:00
log.Info().
Str("user", message.User.Name).
Str("body", message.Body).
Msg("Disallowing first")
return false
}
}
2021-02-04 01:35:56 +00:00
for _, host := range p.bot.Config().GetArray("Bad.Hosts", []string{}) {
2013-04-21 20:49:00 +00:00
if host == message.Host {
2019-03-07 16:35:42 +00:00
log.Info().
Str("user", message.User.Name).
Str("body", message.Body).
Msg("Disallowing first")
2013-04-21 20:49:00 +00:00
return false
}
}
2021-02-04 01:35:56 +00:00
for _, nick := range p.bot.Config().GetArray("Bad.Nicks", []string{}) {
2013-04-21 20:49:00 +00:00
if nick == message.User.Name {
2019-03-07 16:35:42 +00:00
log.Info().
Str("user", message.User.Name).
Str("body", message.Body).
Msg("Disallowing first")
2013-04-21 20:49:00 +00:00
return false
}
}
return true
}
2019-05-27 23:21:53 +00:00
func (p *FirstPlugin) recordFirst(c bot.Connector, message msg.Message) {
2019-03-07 16:35:42 +00:00
log.Info().
Str("channel", message.Channel).
2019-03-07 16:35:42 +00:00
Str("user", message.User.Name).
Str("body", message.Body).
Msg("Recording first")
first := &FirstEntry{
2021-04-27 16:36:34 +00:00
day: Midnight(time.Now()),
2021-08-21 11:24:15 +00:00
time: time.Now(),
channel: message.Channel,
body: message.Body,
nick: message.User.Name,
2016-01-15 16:54:09 +00:00
}
log.Info().Msgf("recordFirst: %+v", first.day)
2021-12-20 17:40:10 +00:00
err := first.save(p.store)
2016-01-15 16:54:09 +00:00
if err != nil {
2019-03-07 16:35:42 +00:00
log.Error().Err(err).Msg("Error saving first entry")
2016-01-15 16:54:09 +00:00
return
}
p.announceFirst(c, first)
}
2019-10-26 15:26:31 +00:00
func (p *FirstPlugin) leaderboard(c bot.Connector, ch string) error {
2021-12-20 17:40:10 +00:00
// todo: remove this once we verify stuff
//q := `select max(channel) channel, max(nick) nick, count(id) count
// from first
// group by channel, nick
// having channel = ?
// order by count desc
// limit 3`
groups, err := p.store.FindAggregate(FirstEntry{}, bh.Where("channel").Eq(ch), "channel", "nick")
2019-10-26 15:26:31 +00:00
if err != nil {
return err
}
2021-12-20 17:40:10 +00:00
if len(groups) != 1 {
return fmt.Errorf("found %d groups but expected 1", len(groups))
2019-10-26 15:26:31 +00:00
}
2021-12-20 17:40:10 +00:00
//res := groups[0]
//talismans := []string{":gold-trophy:", ":silver-trophy:", ":bronze-trophy:"}
//msg := "First leaderboard:\n"
//n := res.Count()
//fe := FirstEntry{}
//
//for i, e := range res {
// msg += fmt.Sprintf("%s %d %s\n", talismans[i], e.Count, e.Nick)
//}
//p.bot.Send(c, bot.Message, ch, msg)
// todo: care about this
2019-10-26 15:26:31 +00:00
return nil
}
func (p *FirstPlugin) announceFirst(c bot.Connector, first *FirstEntry) {
ch := first.channel
2021-02-04 01:35:56 +00:00
p.bot.Send(c, bot.Message, ch, fmt.Sprintf("%s had first at %s with the message: \"%s\"",
first.nick, first.time.Format("15:04"), first.body))
}
// Help responds to help requests. Every plugin must implement a help function.
2019-05-27 23:21:53 +00:00
func (p *FirstPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
2021-02-04 01:35:56 +00:00
p.bot.Send(c, bot.Message, message.Channel, "You can ask 'who's on first?' to find out.")
return true
}