2016-01-17 18:00:44 +00:00
|
|
|
// © 2013 the CatBase Authors under the WTFPL. See AUTHORS for the list of authors.
|
2013-12-10 23:37:07 +00:00
|
|
|
|
2016-03-24 17:32:40 +00:00
|
|
|
package first
|
2013-01-25 21:33:41 +00:00
|
|
|
|
|
|
|
import (
|
2016-01-15 16:54:09 +00:00
|
|
|
"database/sql"
|
2013-01-25 21:33:41 +00:00
|
|
|
"fmt"
|
2016-01-15 06:12:26 +00:00
|
|
|
"regexp"
|
2013-01-25 21:33:41 +00:00
|
|
|
"strings"
|
|
|
|
"time"
|
2016-01-15 06:12:26 +00:00
|
|
|
|
2016-03-19 18:02:46 +00:00
|
|
|
"github.com/jmoiron/sqlx"
|
2019-03-07 16:35:42 +00:00
|
|
|
"github.com/rs/zerolog/log"
|
2021-07-03 17:07:03 +00:00
|
|
|
"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"
|
2016-04-01 14:20:03 +00:00
|
|
|
"github.com/velour/catbase/bot/msg"
|
2013-01-25 21:33:41 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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
|
2021-07-03 17:07:03 +00:00
|
|
|
config *config.Config
|
2021-02-04 01:35:56 +00:00
|
|
|
db *sqlx.DB
|
|
|
|
handlers bot.HandlerTable
|
|
|
|
enabled bool
|
2013-01-25 21:33:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type FirstEntry struct {
|
2019-06-06 14:33:50 +00:00
|
|
|
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
|
2016-03-19 18:02:46 +00:00
|
|
|
func (fe *FirstEntry) save(db *sqlx.DB) error {
|
2019-06-06 14:33:50 +00:00
|
|
|
if _, err := db.Exec(`insert into first (day, time, channel, body, nick)
|
|
|
|
values (?, ?, ?, ?, ?)`,
|
2016-01-15 18:37:54 +00:00
|
|
|
fe.day.Unix(),
|
|
|
|
fe.time.Unix(),
|
2019-06-06 14:33:50 +00:00
|
|
|
fe.channel,
|
2016-01-15 16:54:09 +00:00
|
|
|
fe.body,
|
|
|
|
fe.nick,
|
|
|
|
); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
2013-01-25 21:33:41 +00:00
|
|
|
}
|
|
|
|
|
2021-02-04 01:35:56 +00:00
|
|
|
func (fe *FirstEntry) delete(db *sqlx.DB) error {
|
|
|
|
tx, err := db.Beginx()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
_, err = tx.Exec(`delete from first where id=?`, fe.id)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = tx.Commit()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-01-25 21:33:41 +00:00
|
|
|
// NewFirstPlugin creates a new FirstPlugin with the Plugin interface
|
2016-03-30 14:00:20 +00:00
|
|
|
func New(b bot.Bot) *FirstPlugin {
|
2019-01-20 20:21:26 +00:00
|
|
|
_, err := b.DB().Exec(`create table if not exists first (
|
2016-01-15 16:54:09 +00:00
|
|
|
id integer primary key,
|
2016-01-15 18:37:54 +00:00
|
|
|
day integer,
|
|
|
|
time integer,
|
2019-06-06 14:33:50 +00:00
|
|
|
channel string,
|
2016-01-15 16:54:09 +00:00
|
|
|
body string,
|
|
|
|
nick string
|
|
|
|
);`)
|
2019-01-20 20:21:26 +00:00
|
|
|
if err != nil {
|
2019-03-07 16:35:42 +00:00
|
|
|
log.Fatal().
|
|
|
|
Err(err).
|
|
|
|
Msg("Could not create first table")
|
2016-01-15 16:54:09 +00:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
2019-02-05 19:41:38 +00:00
|
|
|
fp := &FirstPlugin{
|
2021-02-04 01:35:56 +00:00
|
|
|
bot: b,
|
2021-07-03 17:07:03 +00:00
|
|
|
config: b.Config(),
|
2021-02-04 01:35:56 +00:00
|
|
|
db: b.DB(),
|
|
|
|
enabled: true,
|
2013-01-25 21:33:41 +00:00
|
|
|
}
|
2021-02-04 01:35:56 +00:00
|
|
|
fp.register()
|
2019-02-05 19:41:38 +00:00
|
|
|
b.Register(fp, bot.Help, fp.help)
|
|
|
|
return fp
|
2013-01-25 21:33:41 +00:00
|
|
|
}
|
|
|
|
|
2019-06-06 14:33:50 +00:00
|
|
|
func getLastFirst(db *sqlx.DB, channel string) (*FirstEntry, error) {
|
2016-01-15 16:54:09 +00:00
|
|
|
// Get last first entry
|
|
|
|
var id sql.NullInt64
|
|
|
|
var day sql.NullInt64
|
|
|
|
var timeEntered sql.NullInt64
|
|
|
|
var body sql.NullString
|
|
|
|
var nick sql.NullString
|
|
|
|
|
|
|
|
err := db.QueryRow(`select
|
|
|
|
id, max(day), time, body, nick from first
|
2019-06-06 14:33:50 +00:00
|
|
|
where channel = ?
|
2016-01-15 16:54:09 +00:00
|
|
|
limit 1;
|
2019-06-06 14:33:50 +00:00
|
|
|
`, channel).Scan(
|
2016-01-15 16:54:09 +00:00
|
|
|
&id,
|
|
|
|
&day,
|
|
|
|
&timeEntered,
|
|
|
|
&body,
|
|
|
|
&nick,
|
|
|
|
)
|
|
|
|
switch {
|
|
|
|
case err == sql.ErrNoRows || !id.Valid:
|
2019-03-07 16:35:42 +00:00
|
|
|
log.Info().Msg("No previous first entries")
|
2018-06-22 18:31:33 +00:00
|
|
|
return nil, nil
|
2016-01-15 16:54:09 +00:00
|
|
|
case err != nil:
|
2019-03-07 16:35:42 +00:00
|
|
|
log.Warn().Err(err).Msg("Error on first query row")
|
2018-06-22 18:31:33 +00:00
|
|
|
return nil, err
|
2016-01-15 16:54:09 +00:00
|
|
|
}
|
2019-03-07 16:35:42 +00:00
|
|
|
log.Debug().Msgf("id: %v day %v time %v body %v nick %v",
|
|
|
|
id, day, timeEntered, body, nick)
|
2018-06-22 18:31:33 +00:00
|
|
|
return &FirstEntry{
|
2019-06-06 14:33:50 +00:00
|
|
|
id: id.Int64,
|
|
|
|
day: time.Unix(day.Int64, 0),
|
|
|
|
time: time.Unix(timeEntered.Int64, 0),
|
|
|
|
channel: channel,
|
|
|
|
body: body.String,
|
|
|
|
nick: nick.String,
|
|
|
|
saved: true,
|
2018-06-22 18:31:33 +00:00
|
|
|
}, nil
|
2016-01-15 16:54:09 +00:00
|
|
|
}
|
|
|
|
|
2021-04-27 16:36:34 +00:00
|
|
|
func Midnight(t time.Time) time.Time {
|
2013-01-25 21:33:41 +00:00
|
|
|
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)
|
2013-01-25 21:33:41 +00:00
|
|
|
}
|
|
|
|
|
2021-07-03 17:07:03 +00:00
|
|
|
func (p *FirstPlugin) isNotToday(f *FirstEntry) bool {
|
2019-06-06 14:33:50 +00:00
|
|
|
if f == nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
t := f.time
|
2021-04-27 16:36:34 +00:00
|
|
|
t0 := Midnight(t)
|
2021-07-03 17:07:03 +00:00
|
|
|
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()))
|
2013-01-25 21:33:41 +00:00
|
|
|
}
|
|
|
|
|
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 {
|
|
|
|
first, err := getLastFirst(p.db, r.Msg.Channel)
|
|
|
|
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 {
|
|
|
|
first, err := getLastFirst(p.db, r.Msg.Channel)
|
|
|
|
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
|
|
|
|
}
|
|
|
|
fe, err := getLastFirst(p.db, r.Msg.Channel)
|
|
|
|
if err != nil {
|
|
|
|
p.bot.Send(r.Conn, bot.Message, r.Msg.Channel, "Could not find a first entry.")
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
p.enabled = false
|
|
|
|
err = fe.delete(p.db)
|
|
|
|
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
|
|
|
|
}
|
2019-06-06 14:33:50 +00:00
|
|
|
|
2021-02-04 01:35:56 +00:00
|
|
|
first, err := getLastFirst(p.db, r.Msg.Channel)
|
|
|
|
if err != nil {
|
|
|
|
log.Error().
|
|
|
|
Err(err).
|
|
|
|
Msg("Error getting last first")
|
|
|
|
}
|
2019-06-06 14:33:50 +00:00
|
|
|
|
2021-02-04 01:35:56 +00:00
|
|
|
log.Debug().Bool("first == nil", first == nil).Msg("Is first nil?")
|
2021-07-03 17:07:03 +00:00
|
|
|
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?")
|
2013-01-25 21:33:41 +00:00
|
|
|
|
2021-07-03 17:07:03 +00:00
|
|
|
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)
|
2013-01-25 21:33:41 +00:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2016-04-01 14:20:03 +00:00
|
|
|
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{}) {
|
2019-06-06 14:33:50 +00:00
|
|
|
match, err := regexp.MatchString(m, strings.ToLower(message.Body))
|
2016-01-15 06:12:26 +00:00
|
|
|
if err != nil {
|
2019-03-07 16:35:42 +00:00
|
|
|
log.Error().Err(err).Msg("Bad regexp")
|
2016-01-15 06:12:26 +00:00
|
|
|
}
|
|
|
|
if match {
|
2019-03-07 16:35:42 +00:00
|
|
|
log.Info().
|
|
|
|
Str("user", message.User.Name).
|
|
|
|
Str("body", message.Body).
|
|
|
|
Msg("Disallowing first")
|
2013-03-30 11:12:27 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2013-03-30 11:12:27 +00:00
|
|
|
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().
|
2019-06-06 14:33:50 +00:00
|
|
|
Str("channel", message.Channel).
|
2019-03-07 16:35:42 +00:00
|
|
|
Str("user", message.User.Name).
|
|
|
|
Str("body", message.Body).
|
|
|
|
Msg("Recording first")
|
2019-06-06 14:33:50 +00:00
|
|
|
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(),
|
2019-06-06 14:33:50 +00:00
|
|
|
channel: message.Channel,
|
|
|
|
body: message.Body,
|
|
|
|
nick: message.User.Name,
|
2016-01-15 16:54:09 +00:00
|
|
|
}
|
2019-06-06 14:33:50 +00:00
|
|
|
log.Info().Msgf("recordFirst: %+v", first.day)
|
|
|
|
err := first.save(p.db)
|
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
|
2013-01-25 21:33:41 +00:00
|
|
|
}
|
2019-06-06 14:33:50 +00:00
|
|
|
p.announceFirst(c, first)
|
2013-01-25 21:33:41 +00:00
|
|
|
}
|
|
|
|
|
2019-10-26 15:26:31 +00:00
|
|
|
func (p *FirstPlugin) leaderboard(c bot.Connector, ch string) error {
|
|
|
|
q := `select max(channel) channel, max(nick) nick, count(id) count
|
|
|
|
from first
|
|
|
|
group by channel, nick
|
|
|
|
having channel = ?
|
2019-10-26 15:44:01 +00:00
|
|
|
order by count desc
|
2019-10-26 15:26:31 +00:00
|
|
|
limit 3`
|
|
|
|
res := []struct {
|
|
|
|
Channel string
|
|
|
|
Nick string
|
|
|
|
Count int
|
|
|
|
}{}
|
|
|
|
err := p.db.Select(&res, q, ch)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
talismans := []string{":gold-trophy:", ":silver-trophy:", ":bronze-trophy:"}
|
|
|
|
msg := "First leaderboard:\n"
|
|
|
|
for i, e := range res {
|
|
|
|
msg += fmt.Sprintf("%s %d %s\n", talismans[i], e.Count, e.Nick)
|
|
|
|
}
|
2021-02-04 01:35:56 +00:00
|
|
|
p.bot.Send(c, bot.Message, ch, msg)
|
2019-10-26 15:26:31 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-06-06 14:33:50 +00:00
|
|
|
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\"",
|
2019-06-06 14:33:50 +00:00
|
|
|
first.nick, first.time.Format("15:04"), first.body))
|
2013-01-25 21:33:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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.")
|
2019-02-05 19:41:38 +00:00
|
|
|
return true
|
2013-05-08 00:08:18 +00:00
|
|
|
}
|