From b33eccaaa0edd05942b226e23681f69e117fad0a Mon Sep 17 00:00:00 2001 From: skkiesel Date: Sat, 19 Jan 2019 11:06:13 -0500 Subject: [PATCH 001/107] Add pokemon. Extra dune trigger. Actually have a test for each endpoint --- plugins/nerdepedia/nerdepedia.go | 4 +++- plugins/nerdepedia/nerdepeida_test.go | 29 ++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/plugins/nerdepedia/nerdepedia.go b/plugins/nerdepedia/nerdepedia.go index aa2665a..1da2514 100644 --- a/plugins/nerdepedia/nerdepedia.go +++ b/plugins/nerdepedia/nerdepedia.go @@ -44,10 +44,12 @@ func (p *NerdepediaPlugin) Message(message msg.Message) bool { query = "http://starwars.wikia.com/wiki/Special:Random" } else if lowerCase == "beam me up scotty" || lowerCase == "live long and prosper" { query = "http://memory-alpha.wikia.com/wiki/Special:Random" - } else if lowerCase == "bless the maker" || lowerCase == "i must not fear" { + } else if lowerCase == "bless the maker" || lowerCase == "i must not fear" || lowerCase == "the spice must flow" { query = "http://dune.wikia.com/wiki/Special:Random" } else if lowerCase == "my precious" || lowerCase == "one ring to rule them all" || lowerCase == "one does not simply walk into mordor" { query = "http://lotr.wikia.com/wiki/Special:Random" + } else if lowerCase == "pikachu i choose you" || lowerCase == "gotta catch em all" { + query = "http://pokemon.wikia.com/wiki/Special:Random" } if query != "" { diff --git a/plugins/nerdepedia/nerdepeida_test.go b/plugins/nerdepedia/nerdepeida_test.go index 04d3ad0..5cba343 100644 --- a/plugins/nerdepedia/nerdepeida_test.go +++ b/plugins/nerdepedia/nerdepeida_test.go @@ -25,7 +25,7 @@ func makeMessage(payload string) msg.Message { } } -func TestObiWan(t *testing.T) { +func TestWars(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) @@ -33,3 +33,30 @@ func TestObiWan(t *testing.T) { assert.Len(t, mb.Messages, 1) assert.True(t, res) } + +func TestTrek(t *testing.T) { + mb := bot.NewMockBot() + c := New(mb) + assert.NotNil(t, c) + res := c.Message(makeMessage("live long and prosper")) + assert.Len(t, mb.Messages, 1) + assert.True(t, res) +} + +func TestDune(t *testing.T) { + mb := bot.NewMockBot() + c := New(mb) + assert.NotNil(t, c) + res := c.Message(makeMessage("bless the maker")) + assert.Len(t, mb.Messages, 1) + assert.True(t, res) +} + +func TestPoke(t *testing.T) { + mb := bot.NewMockBot() + c := New(mb) + assert.NotNil(t, c) + res := c.Message(makeMessage("gotta catch em all")) + assert.Len(t, mb.Messages, 1) + assert.True(t, res) +} From 3a7651d1841ce6f8b71da696a17177f90ce2f7a8 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Sun, 20 Jan 2019 12:33:19 -0500 Subject: [PATCH 002/107] fact: add reaction type facts If a user creates a fact with the verb , catbase will try to react with the emojy that the user specifies. It filters things with spaces and fixes colons, but does not check if the emojy actually exists. There will be no feedback in this case, which should probably get fixed but meh. * Updated mock bot to check reactions, and do filtering correctly. * Added a couple tests of the react functionality. --- bot/mock.go | 12 ++++++++---- plugins/fact/factoid.go | 26 ++++++++++++++++++-------- plugins/fact/remember_test.go | 27 +++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 12 deletions(-) diff --git a/bot/mock.go b/bot/mock.go index 7d6c6e6..509013e 100644 --- a/bot/mock.go +++ b/bot/mock.go @@ -21,8 +21,9 @@ type MockBot struct { Cfg config.Config - Messages []string - Actions []string + Messages []string + Actions []string + Reactions []string } func (mb *MockBot) Config() *config.Config { return &mb.Cfg } @@ -47,11 +48,14 @@ func (mb *MockBot) ReplyToMessage(channel, message string, replyTo msg.Message) } func (mb *MockBot) MsgReceived(msg msg.Message) {} func (mb *MockBot) EventReceived(msg msg.Message) {} -func (mb *MockBot) Filter(msg msg.Message, s string) string { return "" } +func (mb *MockBot) Filter(msg msg.Message, s string) string { return s } func (mb *MockBot) LastMessage(ch string) (msg.Message, error) { return msg.Message{}, nil } func (mb *MockBot) CheckAdmin(nick string) bool { return false } -func (mb *MockBot) React(channel, reaction string, message msg.Message) bool { return false } +func (mb *MockBot) React(channel, reaction string, message msg.Message) bool { + mb.Reactions = append(mb.Reactions, reaction) + return false +} func (mb *MockBot) Edit(channel, newMessage, identifier string) bool { isMessage := identifier[0] == 'm' diff --git a/plugins/fact/factoid.go b/plugins/fact/factoid.go index 17a4e4a..b86e72e 100644 --- a/plugins/fact/factoid.go +++ b/plugins/fact/factoid.go @@ -338,8 +338,16 @@ func findAction(message string) string { // learnFact assumes we have a learning situation and inserts a new fact // into the database -func (p *Factoid) learnFact(message msg.Message, fact, verb, tidbit string) bool { +func (p *Factoid) learnFact(message msg.Message, fact, verb, tidbit string) error { verb = strings.ToLower(verb) + if verb == "react" { + // This would be a great place to check against the API for valid emojy + // I'm too lazy for that + tidbit = strings.Replace(tidbit, ":", "", -1) + if len(strings.Split(tidbit, " ")) > 1 { + return fmt.Errorf("That's not a valid emojy.") + } + } var count sql.NullInt64 err := p.db.QueryRow(`select count(*) from factoid @@ -347,10 +355,10 @@ func (p *Factoid) learnFact(message msg.Message, fact, verb, tidbit string) bool fact, verb, tidbit).Scan(&count) if err != nil { log.Println("Error counting facts: ", err) - return false + return fmt.Errorf("What?") } else if count.Valid && count.Int64 != 0 { log.Println("User tried to relearn a fact.") - return false + return fmt.Errorf("Look, I already know that.") } n := factoid{ @@ -366,10 +374,10 @@ func (p *Factoid) learnFact(message msg.Message, fact, verb, tidbit string) bool err = n.save(p.db) if err != nil { log.Println("Error inserting fact: ", err) - return false + return fmt.Errorf("My brain is overheating.") } - return true + return nil } // findTrigger checks to see if a given string is a trigger or not @@ -398,6 +406,8 @@ func (p *Factoid) sayFact(message msg.Message, fact factoid) { if fact.Verb == "action" { p.Bot.SendAction(message.Channel, msg) + } else if fact.Verb == "react" { + p.Bot.React(message.Channel, msg, message) } else if fact.Verb == "reply" { p.Bot.SendMessage(message.Channel, msg) } else { @@ -476,10 +486,10 @@ func (p *Factoid) learnAction(message msg.Message, action string) bool { strippedaction := strings.Replace(strings.Replace(action, "<", "", 1), ">", "", 1) - if p.learnFact(message, trigger, strippedaction, fact) { - p.Bot.SendMessage(message.Channel, fmt.Sprintf("Okay, %s.", message.User.Name)) + if err := p.learnFact(message, trigger, strippedaction, fact); err != nil { + p.Bot.SendMessage(message.Channel, err.Error()) } else { - p.Bot.SendMessage(message.Channel, "I already know that.") + p.Bot.SendMessage(message.Channel, fmt.Sprintf("Okay, %s.", message.User.Name)) } return true diff --git a/plugins/fact/remember_test.go b/plugins/fact/remember_test.go index 3cbc835..6e8c8a1 100644 --- a/plugins/fact/remember_test.go +++ b/plugins/fact/remember_test.go @@ -50,3 +50,30 @@ func TestCornerCaseBug(t *testing.T) { assert.Nil(t, err) assert.Contains(t, q.Tidbit, "horse dick") } + +func TestReact(t *testing.T) { + msgs := []msg.Message{ + makeMessage("user1", "!testing123 jesus"), + makeMessage("user2", "testing123"), + } + _, p, mb := makePlugin(t) + + for _, m := range msgs { + p.Message(m) + } + assert.Len(t, mb.Reactions, 1) + assert.Contains(t, mb.Reactions[0], "jesus") +} + +func TestReactCantLearnSpaces(t *testing.T) { + msgs := []msg.Message{ + makeMessage("user1", "!test jesus christ"), + } + _, p, mb := makePlugin(t) + + for _, m := range msgs { + p.Message(m) + } + assert.Len(t, mb.Messages, 1) + assert.Contains(t, mb.Messages[0], "not a valid") +} From a8d0f3fd3458915bc72c1a2d68a0df740ae33b5a Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Sun, 20 Jan 2019 15:21:26 -0500 Subject: [PATCH 003/107] stats: remove unused plugin config: checkpoint config: checkpoint all but two tests passing config: checkpoint all but one test suites pass --- .gitignore | 1 + bot/bot.go | 55 ++---- bot/handlers.go | 4 +- bot/interfaces.go | 1 - bot/mock.go | 13 +- config/config.go | 217 +++++++++++----------- config/config_test.go | 23 +++ irc/irc.go | 14 +- main.go | 10 +- plugins/babbler/babbler.go | 3 - plugins/babbler/babbler_test.go | 17 -- plugins/beers/beers.go | 25 +-- plugins/beers/beers_test.go | 1 - plugins/counter/counter.go | 3 + plugins/db/db.go | 2 +- plugins/downtime/downtime.go | 12 +- plugins/emojifyme/emojifyme.go | 4 +- plugins/fact/factoid.go | 10 +- plugins/first/first.go | 14 +- plugins/inventory/inventory.go | 9 +- plugins/leftpad/leftpad.go | 4 +- plugins/leftpad/leftpad_test.go | 5 +- plugins/reaction/reaction.go | 16 +- plugins/reminder/reminder.go | 8 +- plugins/reminder/reminder_test.go | 6 +- plugins/rpgORdie/rpgORdie.go | 2 +- plugins/sisyphus/sisyphus.go | 10 +- plugins/stats/stats.go | 279 ---------------------------- plugins/stats/stats_test.go | 290 ------------------------------ plugins/talker/talker.go | 25 +-- plugins/talker/talker_test.go | 41 ----- plugins/twitch/twitch.go | 23 ++- plugins/twitch/twitch_test.go | 3 +- plugins/your/your.go | 11 +- plugins/your/your_test.go | 47 ++--- slack/slack.go | 30 ++-- 36 files changed, 270 insertions(+), 968 deletions(-) create mode 100644 config/config_test.go delete mode 100644 plugins/stats/stats.go delete mode 100644 plugins/stats/stats_test.go diff --git a/.gitignore b/.gitignore index ba65788..1287944 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ vendor .vscode/ *.code-workspace *config.lua +modd.conf diff --git a/bot/bot.go b/bot/bot.go index 777229c..e584ee1 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -3,7 +3,6 @@ package bot import ( - "database/sql" "html/template" "log" "net/http" @@ -32,13 +31,6 @@ type bot struct { conn Connector - // SQL DB - // TODO: I think it'd be nice to use https://github.com/jmoiron/sqlx so that - // the select/update/etc statements could be simplified with struct - // marshalling. - db *sqlx.DB - dbVersion int64 - logIn chan msg.Message logOut chan msg.Messages @@ -64,7 +56,7 @@ func New(config *config.Config, connector Connector) Bot { users := []user.User{ user.User{ - Name: config.Nick, + Name: config.Get("Nick"), }, } @@ -75,10 +67,8 @@ func New(config *config.Config, connector Connector) Bot { conn: connector, users: users, me: users[0], - db: config.DBConn, logIn: logIn, logOut: logOut, - version: config.Version, httpEndPoints: make(map[string]string), filters: make(map[string]func(string) string), } @@ -86,10 +76,11 @@ func New(config *config.Config, connector Connector) Bot { bot.migrateDB() http.HandleFunc("/", bot.serveRoot) - if config.HttpAddr == "" { - config.HttpAddr = "127.0.0.1:1337" + addr := config.Get("HttpAddr") + if addr == "" { + addr = "127.0.0.1:1337" } - go http.ListenAndServe(config.HttpAddr, nil) + go http.ListenAndServe(addr, nil) connector.RegisterMessageReceived(bot.MsgReceived) connector.RegisterEventReceived(bot.EventReceived) @@ -103,39 +94,15 @@ func (b *bot) Config() *config.Config { return b.config } -func (b *bot) DBVersion() int64 { - return b.dbVersion -} - func (b *bot) DB() *sqlx.DB { - return b.db + return b.config.DB } // Create any tables if necessary based on version of DB // Plugins should create their own tables, these are only for official bot stuff // Note: This does not return an error. Database issues are all fatal at this stage. func (b *bot) migrateDB() { - _, err := b.db.Exec(`create table if not exists version (version integer);`) - if err != nil { - log.Fatal("Initial DB migration create version table: ", err) - } - var version sql.NullInt64 - err = b.db.QueryRow("select max(version) from version").Scan(&version) - if err != nil { - log.Fatal("Initial DB migration get version: ", err) - } - if version.Valid { - b.dbVersion = version.Int64 - log.Printf("Database version: %v\n", b.dbVersion) - } else { - log.Printf("No versions, we're the first!.") - _, err := b.db.Exec(`insert into version (version) values (1)`) - if err != nil { - log.Fatal("Initial DB migration insert: ", err) - } - } - - if _, err := b.db.Exec(`create table if not exists variables ( + if _, err := b.DB().Exec(`create table if not exists variables ( id integer primary key, name string, value string @@ -204,8 +171,8 @@ func (b *bot) serveRoot(w http.ResponseWriter, r *http.Request) { // Checks if message is a command and returns its curtailed version func IsCmd(c *config.Config, message string) (bool, string) { - cmdcs := c.CommandChar - botnick := strings.ToLower(c.Nick) + cmdcs := c.GetArray("CommandChar") + botnick := strings.ToLower(c.Get("Nick")) iscmd := false lowerMessage := strings.ToLower(message) @@ -237,7 +204,7 @@ func IsCmd(c *config.Config, message string) (bool, string) { } func (b *bot) CheckAdmin(nick string) bool { - for _, u := range b.Config().Admins { + for _, u := range b.Config().GetArray("Admins") { if nick == u { return true } @@ -265,7 +232,7 @@ func (b *bot) NewUser(nick string) *user.User { } func (b *bot) checkAdmin(nick string) bool { - for _, u := range b.Config().Admins { + for _, u := range b.Config().GetArray("Admins") { if nick == u { return true } diff --git a/bot/handlers.go b/bot/handlers.go index cd01bb0..39b5d72 100644 --- a/bot/handlers.go +++ b/bot/handlers.go @@ -192,7 +192,7 @@ func (b *bot) Filter(message msg.Message, input string) string { func (b *bot) getVar(varName string) (string, error) { var text string - err := b.db.Get(&text, `select value from variables where name=? order by random() limit 1`, varName) + err := b.DB().Get(&text, `select value from variables where name=? order by random() limit 1`, varName) switch { case err == sql.ErrNoRows: return "", fmt.Errorf("No factoid found") @@ -204,7 +204,7 @@ func (b *bot) getVar(varName string) (string, error) { func (b *bot) listVars(channel string, parts []string) { var variables []string - err := b.db.Select(&variables, `select name from variables group by name`) + err := b.DB().Select(&variables, `select name from variables group by name`) if err != nil { log.Fatal(err) } diff --git a/bot/interfaces.go b/bot/interfaces.go index 2780d29..42b0980 100644 --- a/bot/interfaces.go +++ b/bot/interfaces.go @@ -11,7 +11,6 @@ import ( type Bot interface { Config() *config.Config - DBVersion() int64 DB() *sqlx.DB Who(string) []user.User AddHandler(string, Handler) diff --git a/bot/mock.go b/bot/mock.go index 509013e..d90f757 100644 --- a/bot/mock.go +++ b/bot/mock.go @@ -19,16 +19,16 @@ type MockBot struct { mock.Mock db *sqlx.DB - Cfg config.Config + Cfg *config.Config Messages []string Actions []string Reactions []string } -func (mb *MockBot) Config() *config.Config { return &mb.Cfg } +func (mb *MockBot) Config() *config.Config { return mb.Cfg } func (mb *MockBot) DBVersion() int64 { return 1 } -func (mb *MockBot) DB() *sqlx.DB { return mb.db } +func (mb *MockBot) DB() *sqlx.DB { return mb.Cfg.DB } func (mb *MockBot) Conn() Connector { return nil } func (mb *MockBot) Who(string) []user.User { return []user.User{} } func (mb *MockBot) AddHandler(name string, f Handler) {} @@ -94,12 +94,9 @@ func (mb *MockBot) GetEmojiList() map[string]string { return make func (mb *MockBot) RegisterFilter(s string, f func(string) string) {} func NewMockBot() *MockBot { - db, err := sqlx.Open("sqlite3_custom", ":memory:") - if err != nil { - log.Fatal("Failed to open database:", err) - } + cfg := config.ReadConfig(":memory:") b := MockBot{ - db: db, + Cfg: cfg, Messages: make([]string, 0), Actions: make([]string, 0), } diff --git a/config/config.go b/config/config.go index a16e00c..2c540e3 100644 --- a/config/config.go +++ b/config/config.go @@ -6,111 +6,103 @@ import ( "database/sql" "fmt" "log" + "os" "regexp" + "strconv" + "strings" "github.com/jmoiron/sqlx" sqlite3 "github.com/mattn/go-sqlite3" - "github.com/yuin/gluamapper" - lua "github.com/yuin/gopher-lua" ) // Config stores any system-wide startup information that cannot be easily configured via // the database type Config struct { - DBConn *sqlx.DB + *sqlx.DB - DB struct { - File string - Name string - Server string + DBFile string +} + +// GetFloat64 returns the config value for a string key +// It will first look in the env vars for the key +// It will check the DB for the key if an env DNE +// Finally, it will return a zero value if the key does not exist +// It will attempt to convert the value to a float64 if it exists +func (c *Config) GetFloat64(key string) float64 { + f, err := strconv.ParseFloat(c.GetString(key), 64) + if err != nil { + return 0.0 } - Channels []string - MainChannel string - Plugins []string - Type string - Irc struct { - Server, Pass string + return f +} + +// GetInt returns the config value for a string key +// It will first look in the env vars for the key +// It will check the DB for the key if an env DNE +// Finally, it will return a zero value if the key does not exist +// It will attempt to convert the value to an int if it exists +func (c *Config) GetInt(key string) int { + i, err := strconv.Atoi(c.GetString(key)) + if err != nil { + return 0 } - Slack struct { - Token string + return i +} + +// Get is a shortcut for GetString +func (c *Config) Get(key string) string { + return c.GetString(key) +} + +// GetString returns the config value for a string key +// It will first look in the env vars for the key +// It will check the DB for the key if an env DNE +// Finally, it will return a zero value if the key does not exist +// It will convert the value to a string if it exists +func (c *Config) GetString(key string) string { + key = strings.ToLower(key) + if v, found := os.LookupEnv(key); found { + return v } - Nick string - IconURL string - FullName string - Version string - CommandChar []string - RatePerSec float64 - LogLength int - Admins []string - HttpAddr string - Untappd struct { - Token string - Freq int - Channels []string + var configValue string + q := `select value from config where key=?` + err := c.DB.Get(&configValue, q, key) + if err != nil { + return "" } - Twitch struct { - Freq int - Users map[string][]string //channel -> usernames - ClientID string - Authorization string - } - EnforceNicks bool - WelcomeMsgs []string - TwitterConsumerKey string - TwitterConsumerSecret string - TwitterUserKey string - TwitterUserSecret string - BadMsgs []string - Bad struct { - Msgs []string - Nicks []string - Hosts []string - } - Your struct { - MaxLength int - Replacements []Replacement - } - LeftPad struct { - MaxLen int - Who string - } - Factoid struct { - MinLen int - QuoteChance float64 - QuoteTime int - StartupFact string - } - Babbler struct { - DefaultUsers []string - } - Reminder struct { - MaxBatchAdd int - } - Stats struct { - DBPath string - Sightings []string - } - Emojify struct { - Chance float64 - Scoreless []string - } - Reaction struct { - GeneralChance float64 - HarrassChance float64 - NegativeHarrassmentMultiplier int - HarrassList []string - PositiveReactions []string - NegativeReactions []string - } - Inventory struct { - Max int - } - Sisyphus struct { - MinDecrement int - MaxDecrement int - MinPush int - MaxPush int + return configValue +} + +// GetArray returns the string slice config value for a string key +// It will first look in the env vars for the key with ;; separated values +// Look, I'm too lazy to do parsing to ensure that a comma is what the user meant +// It will check the DB for the key if an env DNE +// Finally, it will return a zero value if the key does not exist +// This will do no conversion. +func (c *Config) GetArray(key string) []string { + val := c.GetString(key) + return strings.Split(val, ";;") +} + +// Set changes the value for a configuration in the database +// Note, this is always a string. Use the SetArray for an array helper +func (c *Config) Set(key, value string) error { + key = strings.ToLower(key) + q := (`insert into config (key,value) values (?, ?);`) + _, err := c.Exec(q, key, value) + if err != nil { + q := (`update config set value=? where key=?);`) + _, err = c.Exec(q, value, key) + if err != nil { + return err + } } + return nil +} + +func (c *Config) SetArray(key string, values []string) error { + vals := strings.Join(values, ";;") + return c.Set(key, vals) } func init() { @@ -125,38 +117,31 @@ func init() { }) } -type Replacement struct { - This string - That string - Frequency float64 -} - // Readconfig loads the config data out of a JSON file located in cfile -func Readconfig(version, cfile string) *Config { - fmt.Printf("Using %s as config file.\n", cfile) - L := lua.NewState() - if err := L.DoFile(cfile); err != nil { - panic(err) +func ReadConfig(dbpath string) *Config { + if dbpath == "" { + dbpath = "catbase.db" } + fmt.Printf("Using %s as database file.\n", dbpath) - var c Config - if err := gluamapper.Map(L.GetGlobal("config").(*lua.LTable), &c); err != nil { - panic(err) - } - - c.Version = version - - if c.Type == "" { - c.Type = "irc" - } - - fmt.Printf("godeepintir version %s running.\n", c.Version) - - sqlDB, err := sqlx.Open("sqlite3_custom", c.DB.File) + sqlDB, err := sqlx.Open("sqlite3_custom", dbpath) if err != nil { log.Fatal(err) } - c.DBConn = sqlDB + c := Config{ + DBFile: dbpath, + } + c.DB = sqlDB + + if _, err := c.Exec(`create table if not exists config ( + key string, + value string, + primary key (key) + );`); err != nil { + panic(err) + } + + fmt.Printf("catbase is running.\n") return &c } diff --git a/config/config_test.go b/config/config_test.go new file mode 100644 index 0000000..7c6ebbb --- /dev/null +++ b/config/config_test.go @@ -0,0 +1,23 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSetGet(t *testing.T) { + cfg := ReadConfig(":memory:") + expected := "value" + cfg.Set("test", expected) + actual := cfg.Get("test") + assert.Equal(t, expected, actual, "Config did not store values") +} + +func TestSetGetArray(t *testing.T) { + cfg := ReadConfig(":memory:") + expected := []string{"a", "b", "c"} + cfg.SetArray("test", expected) + actual := cfg.GetArray("test") + assert.Equal(t, expected, actual, "Config did not store values") +} diff --git a/irc/irc.go b/irc/irc.go index b965749..1ad9e58 100644 --- a/irc/irc.go +++ b/irc/irc.go @@ -87,7 +87,7 @@ func (i *Irc) SendMessage(channel, message string) string { } if throttle == nil { - ratePerSec := i.config.RatePerSec + ratePerSec := i.config.GetInt("RatePerSec") throttle = time.Tick(time.Second / time.Duration(ratePerSec)) } @@ -136,17 +136,17 @@ func (i *Irc) Serve() error { var err error i.Client, err = irc.DialSSL( - i.config.Irc.Server, - i.config.Nick, - i.config.FullName, - i.config.Irc.Pass, + i.config.Get("Irc.Server"), + i.config.Get("Nick"), + i.config.Get("FullName"), + i.config.Get("Irc.Pass"), true, ) if err != nil { return fmt.Errorf("%s", err) } - for _, c := range i.config.Channels { + for _, c := range i.config.GetArray("channels") { i.JoinChannel(c) } @@ -270,7 +270,7 @@ func (i *Irc) buildMessage(inMsg irc.Msg) msg.Message { } channel := inMsg.Args[0] - if channel == i.config.Nick { + if channel == i.config.Get("Nick") { channel = inMsg.Args[0] } diff --git a/main.go b/main.go index 3b1a22f..094e648 100644 --- a/main.go +++ b/main.go @@ -41,20 +41,20 @@ import ( func main() { rand.Seed(time.Now().Unix()) - var cfile = flag.String("config", "config.lua", - "Config file to load. (Defaults to config.lua)") + var dbpath = flag.String("db", "catbase.db", + "Database file to load. (Defaults to catbase.db)") flag.Parse() // parses the logging flags. - c := config.Readconfig(Version, *cfile) + c := config.ReadConfig(*dbpath) var client bot.Connector - switch c.Type { + switch c.Get("type") { case "irc": client = irc.New(c) case "slack": client = slack.New(c) default: - log.Fatalf("Unknown connection type: %s", c.Type) + log.Fatalf("Unknown connection type: %s", c.Get("type")) } b := bot.New(c, client) diff --git a/plugins/babbler/babbler.go b/plugins/babbler/babbler.go index 36f1521..78ba647 100644 --- a/plugins/babbler/babbler.go +++ b/plugins/babbler/babbler.go @@ -13,7 +13,6 @@ import ( "github.com/jmoiron/sqlx" "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" - "github.com/velour/catbase/config" ) var ( @@ -25,7 +24,6 @@ var ( type BabblerPlugin struct { Bot bot.Bot db *sqlx.DB - config *config.Config WithGoRoutines bool } @@ -93,7 +91,6 @@ func New(bot bot.Bot) *BabblerPlugin { plugin := &BabblerPlugin{ Bot: bot, db: bot.DB(), - config: bot.Config(), WithGoRoutines: true, } diff --git a/plugins/babbler/babbler_test.go b/plugins/babbler/babbler_test.go index d598359..726a3ab 100644 --- a/plugins/babbler/babbler_test.go +++ b/plugins/babbler/babbler_test.go @@ -34,7 +34,6 @@ func newBabblerPlugin(mb *bot.MockBot) *BabblerPlugin { func TestBabblerNoBabbler(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) - bp.config.Babbler.DefaultUsers = []string{"seabass"} assert.NotNil(t, bp) bp.Message(makeMessage("!seabass2 says")) res := assert.Len(t, mb.Messages, 0) @@ -45,7 +44,6 @@ func TestBabblerNoBabbler(t *testing.T) { func TestBabblerNothingSaid(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) - bp.config.Babbler.DefaultUsers = []string{"seabass"} assert.NotNil(t, bp) res := bp.Message(makeMessage("initialize babbler for seabass")) assert.True(t, res) @@ -59,7 +57,6 @@ func TestBabblerNothingSaid(t *testing.T) { func TestBabbler(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) - bp.config.Babbler.DefaultUsers = []string{"seabass"} assert.NotNil(t, bp) seabass := makeMessage("This is a message") seabass.User = &user.User{Name: "seabass"} @@ -78,7 +75,6 @@ func TestBabbler(t *testing.T) { func TestBabblerSeed(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) - bp.config.Babbler.DefaultUsers = []string{"seabass"} assert.NotNil(t, bp) seabass := makeMessage("This is a message") seabass.User = &user.User{Name: "seabass"} @@ -96,7 +92,6 @@ func TestBabblerSeed(t *testing.T) { func TestBabblerMultiSeed(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) - bp.config.Babbler.DefaultUsers = []string{"seabass"} assert.NotNil(t, bp) seabass := makeMessage("This is a message") seabass.User = &user.User{Name: "seabass"} @@ -114,7 +109,6 @@ func TestBabblerMultiSeed(t *testing.T) { func TestBabblerMultiSeed2(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) - bp.config.Babbler.DefaultUsers = []string{"seabass"} assert.NotNil(t, bp) seabass := makeMessage("This is a message") seabass.User = &user.User{Name: "seabass"} @@ -132,7 +126,6 @@ func TestBabblerMultiSeed2(t *testing.T) { func TestBabblerBadSeed(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) - bp.config.Babbler.DefaultUsers = []string{"seabass"} assert.NotNil(t, bp) seabass := makeMessage("This is a message") seabass.User = &user.User{Name: "seabass"} @@ -149,7 +142,6 @@ func TestBabblerBadSeed(t *testing.T) { func TestBabblerBadSeed2(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) - bp.config.Babbler.DefaultUsers = []string{"seabass"} assert.NotNil(t, bp) seabass := makeMessage("This is a message") seabass.User = &user.User{Name: "seabass"} @@ -166,7 +158,6 @@ func TestBabblerBadSeed2(t *testing.T) { func TestBabblerSuffixSeed(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) - bp.config.Babbler.DefaultUsers = []string{"seabass"} assert.NotNil(t, bp) seabass := makeMessage("This is message one") seabass.User = &user.User{Name: "seabass"} @@ -186,7 +177,6 @@ func TestBabblerSuffixSeed(t *testing.T) { func TestBabblerBadSuffixSeed(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) - bp.config.Babbler.DefaultUsers = []string{"seabass"} assert.NotNil(t, bp) seabass := makeMessage("This is message one") seabass.User = &user.User{Name: "seabass"} @@ -204,7 +194,6 @@ func TestBabblerBadSuffixSeed(t *testing.T) { func TestBabblerBookendSeed(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) - bp.config.Babbler.DefaultUsers = []string{"seabass"} assert.NotNil(t, bp) seabass := makeMessage("It's easier to test with unique messages") seabass.User = &user.User{Name: "seabass"} @@ -218,7 +207,6 @@ func TestBabblerBookendSeed(t *testing.T) { func TestBabblerBookendSeedShort(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) - bp.config.Babbler.DefaultUsers = []string{"seabass"} assert.NotNil(t, bp) seabass := makeMessage("It's easier to test with unique messages") seabass.User = &user.User{Name: "seabass"} @@ -232,7 +220,6 @@ func TestBabblerBookendSeedShort(t *testing.T) { func TestBabblerBadBookendSeed(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) - bp.config.Babbler.DefaultUsers = []string{"seabass"} assert.NotNil(t, bp) seabass := makeMessage("It's easier to test with unique messages") seabass.User = &user.User{Name: "seabass"} @@ -246,7 +233,6 @@ func TestBabblerBadBookendSeed(t *testing.T) { func TestBabblerMiddleOutSeed(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) - bp.config.Babbler.DefaultUsers = []string{"seabass"} assert.NotNil(t, bp) seabass := makeMessage("It's easier to test with unique messages") seabass.User = &user.User{Name: "seabass"} @@ -260,7 +246,6 @@ func TestBabblerMiddleOutSeed(t *testing.T) { func TestBabblerBadMiddleOutSeed(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) - bp.config.Babbler.DefaultUsers = []string{"seabass"} assert.NotNil(t, bp) seabass := makeMessage("It's easier to test with unique messages") seabass.User = &user.User{Name: "seabass"} @@ -274,7 +259,6 @@ func TestBabblerBadMiddleOutSeed(t *testing.T) { func TestBabblerBatch(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) - bp.config.Babbler.DefaultUsers = []string{"seabass"} assert.NotNil(t, bp) seabass := makeMessage("batch learn for seabass This is a message! This is another message. This is not a long message? This is not a message! This is not another message. This is a long message?") res := bp.Message(seabass) @@ -289,7 +273,6 @@ func TestBabblerBatch(t *testing.T) { func TestBabblerMerge(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) - bp.config.Babbler.DefaultUsers = []string{"seabass"} assert.NotNil(t, bp) seabass := makeMessage(" This is a message") diff --git a/plugins/beers/beers.go b/plugins/beers/beers.go index cd019e2..3e91869 100644 --- a/plugins/beers/beers.go +++ b/plugins/beers/beers.go @@ -37,25 +37,22 @@ type untappdUser struct { chanNick string } -// NewBeersPlugin creates a new BeersPlugin with the Plugin interface +// New BeersPlugin creates a new BeersPlugin with the Plugin interface func New(bot bot.Bot) *BeersPlugin { - if bot.DBVersion() == 1 { - if _, err := bot.DB().Exec(`create table if not exists untappd ( + if _, err := bot.DB().Exec(`create table if not exists untappd ( id integer primary key, untappdUser string, channel string, lastCheckin integer, chanNick string );`); err != nil { - log.Fatal(err) - } + log.Fatal(err) } p := BeersPlugin{ Bot: bot, db: bot.DB(), } - p.LoadData() - for _, channel := range bot.Config().Untappd.Channels { + for _, channel := range bot.Config().GetArray("Untappd.Channels") { go p.untappdLoop(channel) } return &p @@ -198,13 +195,6 @@ func (p *BeersPlugin) Event(kind string, message msg.Message) bool { return false } -// LoadData imports any configuration data into the plugin. This is not strictly necessary other -// than the fact that the Plugin interface demands it exist. This may be deprecated at a later -// date. -func (p *BeersPlugin) LoadData() { - rand.Seed(time.Now().Unix()) -} - // Help responds to help requests. Every plugin must implement a help function. func (p *BeersPlugin) Help(channel string, parts []string) { msg := "Beers: imbibe by using either beers +=,=,++ or with the !imbibe/drink " + @@ -316,7 +306,7 @@ type Beers struct { } func (p *BeersPlugin) pullUntappd() ([]checkin, error) { - access_token := "?access_token=" + p.Bot.Config().Untappd.Token + access_token := "?access_token=" + p.Bot.Config().Get("Untappd.Token") baseUrl := "https://api.untappd.com/v4/checkin/recent/" url := baseUrl + access_token + "&limit=25" @@ -346,9 +336,8 @@ func (p *BeersPlugin) pullUntappd() ([]checkin, error) { } func (p *BeersPlugin) checkUntappd(channel string) { - token := p.Bot.Config().Untappd.Token + token := p.Bot.Config().Get("Untappd.Token") if token == "" || token == "" { - log.Println("No Untappd token, cannot enable plugin.") return } @@ -431,7 +420,7 @@ func (p *BeersPlugin) checkUntappd(channel string) { } func (p *BeersPlugin) untappdLoop(channel string) { - frequency := p.Bot.Config().Untappd.Freq + frequency := p.Bot.Config().GetInt("Untappd.Freq") log.Println("Checking every ", frequency, " seconds") diff --git a/plugins/beers/beers_test.go b/plugins/beers/beers_test.go index 8d14d86..7c56a3a 100644 --- a/plugins/beers/beers_test.go +++ b/plugins/beers/beers_test.go @@ -30,7 +30,6 @@ func makeBeersPlugin(t *testing.T) (*BeersPlugin, *bot.MockBot) { mb := bot.NewMockBot() counter.New(mb) b := New(mb) - assert.NotNil(t, b) b.Message(makeMessage("!mkalias beer :beer:")) b.Message(makeMessage("!mkalias beers :beer:")) return b, mb diff --git a/plugins/counter/counter.go b/plugins/counter/counter.go index 4ad9a41..d445af5 100644 --- a/plugins/counter/counter.go +++ b/plugins/counter/counter.go @@ -133,6 +133,9 @@ func GetItem(db *sqlx.DB, nick, itemName string) (Item, error) { func (i *Item) Create() error { res, err := i.Exec(`insert into counter (nick, item, count) values (?, ?, ?);`, i.Nick, i.Item, i.Count) + if err != nil { + return err + } id, _ := res.LastInsertId() // hackhackhack? i.ID = id diff --git a/plugins/db/db.go b/plugins/db/db.go index bff767b..4bf8387 100644 --- a/plugins/db/db.go +++ b/plugins/db/db.go @@ -34,7 +34,7 @@ func (p *DBPlugin) RegisterWeb() *string { } func (p *DBPlugin) serveQuery(w http.ResponseWriter, r *http.Request) { - f, err := os.Open(p.bot.Config().DB.File) + f, err := os.Open(p.bot.Config().DBFile) defer f.Close() if err != nil { log.Printf("Error opening DB for web service: %s", err) diff --git a/plugins/downtime/downtime.go b/plugins/downtime/downtime.go index ef9df9e..fc65f78 100644 --- a/plugins/downtime/downtime.go +++ b/plugins/downtime/downtime.go @@ -107,15 +107,13 @@ func New(bot bot.Bot) *DowntimePlugin { db: bot.DB(), } - if bot.DBVersion() == 1 { - _, err := p.db.Exec(`create table if not exists downtime ( + _, err := p.db.Exec(`create table if not exists downtime ( id integer primary key, nick string, lastSeen integer );`) - if err != nil { - log.Fatal("Error creating downtime table: ", err) - } + if err != nil { + log.Fatal("Error creating downtime table: ", err) } return &p @@ -161,7 +159,7 @@ func (p *DowntimePlugin) Message(message msg.Message) bool { for _, e := range entries { // filter out ZNC entries and ourself - if strings.HasPrefix(e.nick, "*") || strings.ToLower(p.Bot.Config().Nick) == e.nick { + if strings.HasPrefix(e.nick, "*") || strings.ToLower(p.Bot.Config().Get("Nick")) == e.nick { p.remove(e.nick) } else { tops = fmt.Sprintf("%s%s: %s ", tops, e.nick, time.Now().Sub(e.lastSeen)) @@ -205,7 +203,7 @@ func (p *DowntimePlugin) Help(channel string, parts []string) { // Empty event handler because this plugin does not do anything on event recv func (p *DowntimePlugin) Event(kind string, message msg.Message) bool { log.Println(kind, "\t", message) - if kind != "PART" && message.User.Name != p.Bot.Config().Nick { + if kind != "PART" && message.User.Name != p.Bot.Config().Get("Nick") { // user joined, let's nail them for it if kind == "NICK" { p.record(strings.ToLower(message.Channel)) diff --git a/plugins/emojifyme/emojifyme.go b/plugins/emojifyme/emojifyme.go index 424a88b..2d513c0 100644 --- a/plugins/emojifyme/emojifyme.go +++ b/plugins/emojifyme/emojifyme.go @@ -64,7 +64,7 @@ func (p *EmojifyMePlugin) Message(message msg.Message) bool { } } - inertTokens := p.Bot.Config().Emojify.Scoreless + inertTokens := p.Bot.Config().GetArray("Emojify.Scoreless") emojied := 0.0 tokens := strings.Fields(strings.ToLower(message.Body)) for i, token := range tokens { @@ -93,7 +93,7 @@ func (p *EmojifyMePlugin) Message(message msg.Message) bool { } } } - if emojied > 0 && rand.Float64() <= p.Bot.Config().Emojify.Chance*emojied { + if emojied > 0 && rand.Float64() <= p.Bot.Config().GetFloat64("Emojify.Chance")*emojied { modified := strings.Join(tokens, " ") p.Bot.SendMessage(message.Channel, modified) return true diff --git a/plugins/fact/factoid.go b/plugins/fact/factoid.go index b86e72e..f216ccc 100644 --- a/plugins/fact/factoid.go +++ b/plugins/fact/factoid.go @@ -297,13 +297,13 @@ func New(botInst bot.Bot) *Factoid { log.Fatal(err) } - for _, channel := range botInst.Config().Channels { + for _, channel := range botInst.Config().GetArray("channels") { go p.factTimer(channel) go func(ch string) { // Some random time to start up time.Sleep(time.Duration(15) * time.Second) - if ok, fact := p.findTrigger(p.Bot.Config().Factoid.StartupFact); ok { + if ok, fact := p.findTrigger(p.Bot.Config().Get("Factoid.StartupFact")); ok { p.sayFact(msg.Message{ Channel: ch, Body: "speed test", // BUG: This is defined in the config too @@ -430,7 +430,7 @@ func (p *Factoid) sayFact(message msg.Message, fact factoid) { // trigger checks the message for its fitness to be a factoid and then hauls // the message off to sayFact for processing if it is in fact a trigger func (p *Factoid) trigger(message msg.Message) bool { - minLen := p.Bot.Config().Factoid.MinLen + minLen := p.Bot.Config().GetInt("Factoid.MinLen") if len(message.Body) > minLen || message.Command || message.Body == "..." { if ok, fact := p.findTrigger(message.Body); ok { p.sayFact(message, *fact) @@ -691,7 +691,7 @@ func (p *Factoid) randomFact() *factoid { // factTimer spits out a fact at a given interval and with given probability func (p *Factoid) factTimer(channel string) { - duration := time.Duration(p.Bot.Config().Factoid.QuoteTime) * time.Minute + duration := time.Duration(p.Bot.Config().GetInt("Factoid.QuoteTime")) * time.Minute myLastMsg := time.Now() for { time.Sleep(time.Duration(5) * time.Second) // why 5? @@ -705,7 +705,7 @@ func (p *Factoid) factTimer(channel string) { tdelta := time.Since(lastmsg.Time) earlier := time.Since(myLastMsg) > tdelta chance := rand.Float64() - success := chance < p.Bot.Config().Factoid.QuoteChance + success := chance < p.Bot.Config().GetFloat64("Factoid.QuoteChance") if success && tdelta > duration && earlier { fact := p.randomFact() diff --git a/plugins/first/first.go b/plugins/first/first.go index d7213f2..88c874a 100644 --- a/plugins/first/first.go +++ b/plugins/first/first.go @@ -48,17 +48,15 @@ func (fe *FirstEntry) save(db *sqlx.DB) error { // NewFirstPlugin creates a new FirstPlugin with the Plugin interface func New(b bot.Bot) *FirstPlugin { - if b.DBVersion() == 1 { - _, err := b.DB().Exec(`create table if not exists first ( + _, err := b.DB().Exec(`create table if not exists first ( id integer primary key, day integer, time integer, body string, nick string );`) - if err != nil { - log.Fatal("Could not create first table: ", err) - } + if err != nil { + log.Fatal("Could not create first table: ", err) } log.Println("First plugin initialized with day:", midnight(time.Now())) @@ -152,7 +150,7 @@ func (p *FirstPlugin) Message(message msg.Message) bool { } func (p *FirstPlugin) allowed(message msg.Message) bool { - for _, msg := range p.Bot.Config().Bad.Msgs { + for _, msg := range p.Bot.Config().GetArray("Bad.Msgs") { match, err := regexp.MatchString(msg, strings.ToLower(message.Body)) if err != nil { log.Println("Bad regexp: ", err) @@ -162,13 +160,13 @@ func (p *FirstPlugin) allowed(message msg.Message) bool { return false } } - for _, host := range p.Bot.Config().Bad.Hosts { + for _, host := range p.Bot.Config().GetArray("Bad.Hosts") { if host == message.Host { log.Println("Disallowing first: ", message.User.Name, ":", message.Body) return false } } - for _, nick := range p.Bot.Config().Bad.Nicks { + for _, nick := range p.Bot.Config().GetArray("Bad.Nicks") { if nick == message.User.Name { log.Println("Disallowing first: ", message.User.Name, ":", message.Body) return false diff --git a/plugins/inventory/inventory.go b/plugins/inventory/inventory.go index 7efb807..83e767c 100644 --- a/plugins/inventory/inventory.go +++ b/plugins/inventory/inventory.go @@ -26,15 +26,16 @@ type InventoryPlugin struct { // New creates a new InventoryPlugin with the Plugin interface func New(bot bot.Bot) *InventoryPlugin { config := bot.Config() + nick := config.Get("nick") r1, err := regexp.Compile("take this (.+)") checkerr(err) r2, err := regexp.Compile("have a (.+)") checkerr(err) - r3, err := regexp.Compile(fmt.Sprintf("puts (.+) in %s([^a-zA-Z].*)?", config.Nick)) + r3, err := regexp.Compile(fmt.Sprintf("puts (.+) in %s([^a-zA-Z].*)?", nick)) checkerr(err) - r4, err := regexp.Compile(fmt.Sprintf("gives %s (.+)", config.Nick)) + r4, err := regexp.Compile(fmt.Sprintf("gives %s (.+)", nick)) checkerr(err) - r5, err := regexp.Compile(fmt.Sprintf("gives (.+) to %s([^a-zA-Z].*)?", config.Nick)) + r5, err := regexp.Compile(fmt.Sprintf("gives (.+) to %s([^a-zA-Z].*)?", nick)) checkerr(err) p := InventoryPlugin{ @@ -200,7 +201,7 @@ func (p *InventoryPlugin) addItem(m msg.Message, i string) bool { return true } var removed string - if p.count() > p.config.Inventory.Max { + if p.count() > p.config.GetInt("Inventory.Max") { removed = p.removeRandom() } _, err := p.Exec(`INSERT INTO inventory (item) values (?)`, i) diff --git a/plugins/leftpad/leftpad.go b/plugins/leftpad/leftpad.go index 12d7395..67189bf 100644 --- a/plugins/leftpad/leftpad.go +++ b/plugins/leftpad/leftpad.go @@ -45,8 +45,8 @@ func (p *LeftpadPlugin) Message(message msg.Message) bool { p.bot.SendMessage(message.Channel, "Invalid padding number") return true } - if length > p.config.LeftPad.MaxLen && p.config.LeftPad.MaxLen > 0 { - msg := fmt.Sprintf("%s would kill me if I did that.", p.config.LeftPad.Who) + if length > p.config.GetInt("LeftPad.MaxLen") && p.config.GetInt("LeftPad.MaxLen") > 0 { + msg := fmt.Sprintf("%s would kill me if I did that.", p.config.Get("LeftPad.Who")) p.bot.SendMessage(message.Channel, msg) return true } diff --git a/plugins/leftpad/leftpad_test.go b/plugins/leftpad/leftpad_test.go index 805f8f1..4f5511f 100644 --- a/plugins/leftpad/leftpad_test.go +++ b/plugins/leftpad/leftpad_test.go @@ -63,7 +63,8 @@ func TestNoMaxLen(t *testing.T) { func Test50Padding(t *testing.T) { p, mb := makePlugin(t) - p.config.LeftPad.MaxLen = 50 + p.config.Set("LeftPad.MaxLen", "50") + assert.Equal(t, 50, p.config.GetInt("LeftPad.MaxLen")) p.Message(makeMessage("!leftpad dicks 100 dicks")) assert.Len(t, mb.Messages, 1) assert.Contains(t, mb.Messages[0], "kill me") @@ -71,7 +72,7 @@ func Test50Padding(t *testing.T) { func TestUnder50Padding(t *testing.T) { p, mb := makePlugin(t) - p.config.LeftPad.MaxLen = 50 + p.config.Set("LeftPad.MaxLen", "50") p.Message(makeMessage("!leftpad dicks 49 dicks")) assert.Len(t, mb.Messages, 1) assert.Contains(t, mb.Messages[0], "dicks") diff --git a/plugins/reaction/reaction.go b/plugins/reaction/reaction.go index 266a4ab..e36314c 100644 --- a/plugins/reaction/reaction.go +++ b/plugins/reaction/reaction.go @@ -24,23 +24,23 @@ func New(bot bot.Bot) *ReactionPlugin { func (p *ReactionPlugin) Message(message msg.Message) bool { harrass := false - for _, nick := range p.Config.Reaction.HarrassList { + for _, nick := range p.Config.GetArray("Reaction.HarrassList") { if message.User.Name == nick { harrass = true break } } - chance := p.Config.Reaction.GeneralChance + chance := p.Config.GetFloat64("Reaction.GeneralChance") negativeWeight := 1 if harrass { - chance = p.Config.Reaction.HarrassChance - negativeWeight = p.Config.Reaction.NegativeHarrassmentMultiplier + chance = p.Config.GetFloat64("Reaction.HarrassChance") + negativeWeight = p.Config.GetInt("Reaction.NegativeHarrassmentMultiplier") } if rand.Float64() < chance { - numPositiveReactions := len(p.Config.Reaction.PositiveReactions) - numNegativeReactions := len(p.Config.Reaction.NegativeReactions) + numPositiveReactions := len(p.Config.GetArray("Reaction.PositiveReactions")) + numNegativeReactions := len(p.Config.GetArray("Reaction.NegativeReactions")) maxIndex := numPositiveReactions + numNegativeReactions*negativeWeight @@ -49,11 +49,11 @@ func (p *ReactionPlugin) Message(message msg.Message) bool { reaction := "" if index < numPositiveReactions { - reaction = p.Config.Reaction.PositiveReactions[index] + reaction = p.Config.GetArray("Reaction.PositiveReactions")[index] } else { index -= numPositiveReactions index %= numNegativeReactions - reaction = p.Config.Reaction.NegativeReactions[index] + reaction = p.Config.GetArray("Reaction.NegativeReactions")[index] } p.Bot.React(message.Channel, reaction, message) diff --git a/plugins/reminder/reminder.go b/plugins/reminder/reminder.go index e4a83d0..b618289 100644 --- a/plugins/reminder/reminder.go +++ b/plugins/reminder/reminder.go @@ -40,8 +40,7 @@ type Reminder struct { func New(bot bot.Bot) *ReminderPlugin { log.SetFlags(log.LstdFlags | log.Lshortfile) - if bot.DBVersion() == 1 { - if _, err := bot.DB().Exec(`create table if not exists reminders ( + if _, err := bot.DB().Exec(`create table if not exists reminders ( id integer primary key, fromWho string, toWho string, @@ -49,8 +48,7 @@ func New(bot bot.Bot) *ReminderPlugin { remindWhen string, channel string );`); err != nil { - log.Fatal(err) - } + log.Fatal(err) } dur, _ := time.ParseDuration("1h") @@ -124,7 +122,7 @@ func (p *ReminderPlugin) Message(message msg.Message) bool { what := strings.Join(parts[6:], " ") for i := 0; when.Before(endTime); i++ { - if i >= p.config.Reminder.MaxBatchAdd { + if i >= p.config.GetInt("Reminder.MaxBatchAdd") { p.Bot.SendMessage(channel, "Easy cowboy, that's a lot of reminders. I'll add some of them.") doConfirm = false break diff --git a/plugins/reminder/reminder_test.go b/plugins/reminder/reminder_test.go index afafd5d..cdd3d13 100644 --- a/plugins/reminder/reminder_test.go +++ b/plugins/reminder/reminder_test.go @@ -176,11 +176,11 @@ func TestFromEmptyList(t *testing.T) { func TestBatch(t *testing.T) { mb := bot.NewMockBot() c := New(mb) - c.config.Reminder.MaxBatchAdd = 50 + c.config.Set("Reminder.MaxBatchAdd", "50") assert.NotNil(t, c) res := c.Message(makeMessage("!remind testuser every 1ms for 5ms yikes")) assert.True(t, res) - time.Sleep(2 * time.Second) + time.Sleep(3 * time.Second) assert.Len(t, mb.Messages, 6) for i := 0; i < 5; i++ { assert.Contains(t, mb.Messages[i+1], "Hey testuser, tester wanted you to be reminded: yikes") @@ -190,7 +190,7 @@ func TestBatch(t *testing.T) { func TestBatchMax(t *testing.T) { mb := bot.NewMockBot() c := New(mb) - c.config.Reminder.MaxBatchAdd = 10 + c.config.Set("Reminder.MaxBatchAdd", "10") assert.NotNil(t, c) res := c.Message(makeMessage("!remind testuser every 1h for 24h yikes")) assert.True(t, res) diff --git a/plugins/rpgORdie/rpgORdie.go b/plugins/rpgORdie/rpgORdie.go index 7320046..7ea15d6 100644 --- a/plugins/rpgORdie/rpgORdie.go +++ b/plugins/rpgORdie/rpgORdie.go @@ -136,7 +136,7 @@ func (p *RPGPlugin) RegisterWeb() *string { } func (p *RPGPlugin) ReplyMessage(message msg.Message, identifier string) bool { - if strings.ToLower(message.User.Name) != strings.ToLower(p.Bot.Config().Nick) { + if strings.ToLower(message.User.Name) != strings.ToLower(p.Bot.Config().Get("Nick")) { if b, ok := p.listenFor[identifier]; ok { var res int diff --git a/plugins/sisyphus/sisyphus.go b/plugins/sisyphus/sisyphus.go index 926dfc1..834e1b8 100644 --- a/plugins/sisyphus/sisyphus.go +++ b/plugins/sisyphus/sisyphus.go @@ -59,8 +59,8 @@ func (g *game) scheduleDecrement() { if g.timers[0] != nil { g.timers[0].Stop() } - minDec := g.bot.Config().Sisyphus.MinDecrement - maxDec := g.bot.Config().Sisyphus.MinDecrement + minDec := g.bot.Config().GetInt("Sisyphus.MinDecrement") + maxDec := g.bot.Config().GetInt("Sisyphus.MinDecrement") g.nextDec = time.Now().Add(time.Duration((minDec + rand.Intn(maxDec))) * time.Minute) go func() { t := time.NewTimer(g.nextDec.Sub(time.Now())) @@ -76,8 +76,8 @@ func (g *game) schedulePush() { if g.timers[1] != nil { g.timers[1].Stop() } - minPush := g.bot.Config().Sisyphus.MinPush - maxPush := g.bot.Config().Sisyphus.MaxPush + minPush := g.bot.Config().GetInt("Sisyphus.MinPush") + maxPush := g.bot.Config().GetInt("Sisyphus.MaxPush") g.nextPush = time.Now().Add(time.Duration(rand.Intn(maxPush)+minPush) * time.Minute) go func() { t := time.NewTimer(g.nextPush.Sub(time.Now())) @@ -195,7 +195,7 @@ func (p *SisyphusPlugin) RegisterWeb() *string { } func (p *SisyphusPlugin) ReplyMessage(message msg.Message, identifier string) bool { - if strings.ToLower(message.User.Name) != strings.ToLower(p.Bot.Config().Nick) { + if strings.ToLower(message.User.Name) != strings.ToLower(p.Bot.Config().Get("Nick")) { if g, ok := p.listenFor[identifier]; ok { log.Printf("got message on %s: %+v", identifier, message) diff --git a/plugins/stats/stats.go b/plugins/stats/stats.go deleted file mode 100644 index 81450b2..0000000 --- a/plugins/stats/stats.go +++ /dev/null @@ -1,279 +0,0 @@ -// © 2016 the CatBase Authors under the WTFPL license. See AUTHORS for the list of authors. - -// Stats contains the plugin that allows the bot record chat statistics -package stats - -import ( - "encoding/json" - "fmt" - "log" - "net/http" - "os" - "strconv" - "strings" - "time" - - "github.com/boltdb/bolt" - "github.com/velour/catbase/bot" - "github.com/velour/catbase/bot/msg" - "github.com/velour/catbase/config" -) - -const ( - DayFormat = "2006-01-02" - HourFormat = "2006-01-02-15" - HourBucket = "hour" - UserBucket = "user" - SightingBucket = "sighting" -) - -type StatsPlugin struct { - bot bot.Bot - config *config.Config -} - -// New creates a new StatsPlugin with the Plugin interface -func New(bot bot.Bot) *StatsPlugin { - p := StatsPlugin{ - bot: bot, - config: bot.Config(), - } - return &p - -} - -type stat struct { - // date formatted: DayFormat - day string - // category - bucket string - // specific unique individual - key string - val value -} - -func mkDay() string { - return time.Now().Format(DayFormat) -} - -// The value type is here in the future growth case that we might want to put a -// struct of more interesting information into the DB -type value int - -func (v value) Bytes() ([]byte, error) { - b, err := json.Marshal(v) - return b, err -} - -func valueFromBytes(b []byte) (value, error) { - var v value - err := json.Unmarshal(b, &v) - return v, err -} - -type stats []stat - -// mkStat converts raw data to a stat struct -// Expected a string representation of the date formatted: DayFormat -func mkStat(day string, bucket, key, val []byte) (stat, error) { - v, err := valueFromBytes(val) - if err != nil { - log.Printf("mkStat: error getting value from bytes: %s", err) - return stat{}, err - } - return stat{ - day: day, - bucket: string(bucket), - key: string(key), - val: v, - }, nil -} - -// Another future-proofing function I shouldn't have written -func (v value) add(other value) value { - return v + other -} - -func openDB(path string) (*bolt.DB, error) { - db, err := bolt.Open(path, 0600, &bolt.Options{ - Timeout: 1 * time.Second, - }) - if err != nil { - log.Printf("Couldn't open BoltDB for stats (%s): %s", path, err) - return nil, err - } - return db, err -} - -// statFromDB takes a location specification and returns the data at that path -// Expected a string representation of the date formatted: DayFormat -func statFromDB(path, day, bucket, key string) (stat, error) { - db, err := openDB(path) - if err != nil { - return stat{}, err - } - defer db.Close() - buk := []byte(bucket) - k := []byte(key) - - tx, err := db.Begin(true) - if err != nil { - log.Println("statFromDB: Error beginning the Tx") - return stat{}, err - } - defer tx.Rollback() - - d, err := tx.CreateBucketIfNotExists([]byte(day)) - if err != nil { - log.Println("statFromDB: Error creating the bucket") - return stat{}, err - } - b, err := d.CreateBucketIfNotExists(buk) - if err != nil { - log.Println("statFromDB: Error creating the bucket") - return stat{}, err - } - - v := b.Get(k) - - if err := tx.Commit(); err != nil { - log.Println("statFromDB: Error commiting the Tx") - return stat{}, err - } - - if v == nil { - return stat{day, bucket, key, 0}, nil - } - - return mkStat(day, buk, k, v) -} - -// toDB takes a stat and records it, adding to the value in the DB if necessary -func (s stats) toDB(path string) error { - db, err := openDB(path) - if err != nil { - return err - } - defer db.Close() - - for _, stat := range s { - err = db.Update(func(tx *bolt.Tx) error { - d, err := tx.CreateBucketIfNotExists([]byte(stat.day)) - if err != nil { - log.Println("toDB: Error creating bucket") - return err - } - b, err := d.CreateBucketIfNotExists([]byte(stat.bucket)) - if err != nil { - log.Println("toDB: Error creating bucket") - return err - } - - valueInDB := b.Get([]byte(stat.key)) - if valueInDB != nil { - val, err := valueFromBytes(valueInDB) - if err != nil { - log.Println("toDB: Error getting value from bytes") - return err - } - stat.val = stat.val.add(val) - } - - v, err := stat.val.Bytes() - if err != nil { - return err - } - if stat.key == "" { - log.Println("Keys should not be empty") - return nil - } - log.Printf("Putting value in: '%s' %b, %+v", stat.key, []byte(stat.key), stat) - err = b.Put([]byte(stat.key), v) - return err - }) - if err != nil { - return err - } - } - return nil -} - -func (p *StatsPlugin) record(message msg.Message) { - statGenerators := []func(msg.Message) stats{ - p.mkUserStat, - p.mkHourStat, - p.mkChannelStat, - p.mkSightingStat, - } - - allStats := stats{} - - for _, mk := range statGenerators { - stats := mk(message) - if stats != nil { - allStats = append(allStats, stats...) - } - } - - allStats.toDB(p.bot.Config().Stats.DBPath) -} - -func (p *StatsPlugin) Message(message msg.Message) bool { - p.record(message) - return false -} - -func (p *StatsPlugin) Event(e string, message msg.Message) bool { - p.record(message) - return false -} - -func (p *StatsPlugin) BotMessage(message msg.Message) bool { - p.record(message) - return false -} - -func (p *StatsPlugin) Help(e string, m []string) { -} - -func (p *StatsPlugin) serveQuery(w http.ResponseWriter, r *http.Request) { - f, err := os.Open(p.bot.Config().Stats.DBPath) - defer f.Close() - if err != nil { - log.Printf("Error opening DB for web service: %s", err) - fmt.Fprintf(w, "Error opening DB") - return - } - http.ServeContent(w, r, "stats.db", time.Now(), f) -} - -func (p *StatsPlugin) RegisterWeb() *string { - http.HandleFunc("/stats", p.serveQuery) - tmp := "/stats" - return &tmp -} - -func (p *StatsPlugin) mkUserStat(message msg.Message) stats { - return stats{stat{mkDay(), UserBucket, message.User.Name, 1}} -} - -func (p *StatsPlugin) mkHourStat(message msg.Message) stats { - hr := time.Now().Hour() - return stats{stat{mkDay(), HourBucket, strconv.Itoa(hr), 1}} -} - -func (p *StatsPlugin) mkSightingStat(message msg.Message) stats { - stats := stats{} - for _, name := range p.bot.Config().Stats.Sightings { - if strings.Contains(message.Body, name+" sighting") { - stats = append(stats, stat{mkDay(), SightingBucket, name, 1}) - } - } - return stats -} - -func (p *StatsPlugin) mkChannelStat(message msg.Message) stats { - return stats{stat{mkDay(), "channel", message.Channel, 1}} -} - -func (p *StatsPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false } diff --git a/plugins/stats/stats_test.go b/plugins/stats/stats_test.go deleted file mode 100644 index d5597f4..0000000 --- a/plugins/stats/stats_test.go +++ /dev/null @@ -1,290 +0,0 @@ -package stats - -import ( - "encoding/json" - "os" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/velour/catbase/bot" - "github.com/velour/catbase/bot/msg" - "github.com/velour/catbase/bot/user" -) - -var dbPath = "test.db" - -func TestJSON(t *testing.T) { - expected := 5 - b, err := json.Marshal(expected) - assert.Nil(t, err) - t.Logf("%+v", expected) - t.Log(string(b)) -} - -func TestValueConversion(t *testing.T) { - expected := value(5) - - b, err := expected.Bytes() - assert.Nil(t, err) - - t.Log(string(b)) - - actual, err := valueFromBytes(b) - assert.Nil(t, err) - - assert.Equal(t, actual, expected) -} - -func rmDB(t *testing.T) { - err := os.Remove(dbPath) - if err != nil && !strings.Contains(err.Error(), "no such file or directory") { - t.Fatal(err) - } -} - -func TestWithDB(t *testing.T) { - rmDB(t) - - t.Run("TestDBReadWrite", func(t *testing.T) { - day := mkDay() - bucket := "testBucket" - key := "testKey" - - expected := stats{stat{ - day, - bucket, - key, - 1, - }} - - err := expected.toDB(dbPath) - assert.Nil(t, err) - - actual, err := statFromDB(dbPath, day, bucket, key) - assert.Nil(t, err) - - assert.Equal(t, actual, expected[0]) - - }) - - rmDB(t) - - t.Run("TestDBAddStatInLoop", func(t *testing.T) { - day := mkDay() - bucket := "testBucket" - key := "testKey" - expected := value(25) - - statPack := stats{stat{ - day, - bucket, - key, - 5, - }} - - for i := 0; i < 5; i++ { - err := statPack.toDB(dbPath) - assert.Nil(t, err) - } - - actual, err := statFromDB(dbPath, day, bucket, key) - assert.Nil(t, err) - - assert.Equal(t, actual.val, expected) - }) - - rmDB(t) - - t.Run("TestDBAddStats", func(t *testing.T) { - day := mkDay() - bucket := "testBucket" - key := "testKey" - expected := value(5) - - statPack := stats{} - for i := 0; i < 5; i++ { - statPack = append(statPack, stat{ - day, - bucket, - key, - 1, - }) - } - - err := statPack.toDB(dbPath) - assert.Nil(t, err) - - actual, err := statFromDB(dbPath, day, bucket, key) - assert.Nil(t, err) - - assert.Equal(t, actual.val, expected) - }) - - rmDB(t) -} - -func makeMessage(payload string) msg.Message { - isCmd := strings.HasPrefix(payload, "!") - if isCmd { - payload = payload[1:] - } - return msg.Message{ - User: &user.User{Name: "tester"}, - Channel: "test", - Body: payload, - Command: isCmd, - } -} - -func testUserCounter(t *testing.T, count int) { - day := mkDay() - expected := value(count) - mb := bot.NewMockBot() - mb.Cfg.Stats.DBPath = dbPath - s := New(mb) - assert.NotNil(t, s) - - for i := 0; i < count; i++ { - s.Message(makeMessage("test")) - } - - _, err := os.Stat(dbPath) - assert.Nil(t, err) - - stat, err := statFromDB(mb.Config().Stats.DBPath, day, "user", "tester") - assert.Nil(t, err) - actual := stat.val - assert.Equal(t, actual, expected) -} - -func TestMessages(t *testing.T) { - _, err := os.Stat(dbPath) - assert.NotNil(t, err) - - t.Run("TestOneUserCounter", func(t *testing.T) { - day := mkDay() - count := 5 - expected := value(count) - mb := bot.NewMockBot() - mb.Cfg.Stats.DBPath = dbPath - s := New(mb) - assert.NotNil(t, s) - - for i := 0; i < count; i++ { - s.Message(makeMessage("test")) - } - - _, err := os.Stat(dbPath) - assert.Nil(t, err) - - stat, err := statFromDB(mb.Config().Stats.DBPath, day, "user", "tester") - assert.Nil(t, err) - actual := stat.val - assert.Equal(t, actual, expected) - }) - - rmDB(t) - - t.Run("TestTenUserCounter", func(t *testing.T) { - day := mkDay() - count := 5 - expected := value(count) - mb := bot.NewMockBot() - mb.Cfg.Stats.DBPath = dbPath - s := New(mb) - assert.NotNil(t, s) - - for i := 0; i < count; i++ { - s.Message(makeMessage("test")) - } - - _, err := os.Stat(dbPath) - assert.Nil(t, err) - - stat, err := statFromDB(mb.Config().Stats.DBPath, day, "user", "tester") - assert.Nil(t, err) - actual := stat.val - assert.Equal(t, actual, expected) - }) - - rmDB(t) - - t.Run("TestChannelCounter", func(t *testing.T) { - day := mkDay() - count := 5 - expected := value(count) - mb := bot.NewMockBot() - mb.Cfg.Stats.DBPath = dbPath - s := New(mb) - assert.NotNil(t, s) - - for i := 0; i < count; i++ { - s.Message(makeMessage("test")) - } - - _, err := os.Stat(dbPath) - assert.Nil(t, err) - - stat, err := statFromDB(mb.Config().Stats.DBPath, day, "channel", "test") - assert.Nil(t, err) - actual := stat.val - assert.Equal(t, actual, expected) - }) - - rmDB(t) - - t.Run("TestSightingCounter", func(t *testing.T) { - day := mkDay() - count := 5 - expected := value(count) - mb := bot.NewMockBot() - - mb.Cfg.Stats.DBPath = dbPath - mb.Cfg.Stats.Sightings = []string{"user", "nobody"} - - s := New(mb) - assert.NotNil(t, s) - - for i := 0; i < count; i++ { - s.Message(makeMessage("user sighting")) - } - - _, err := os.Stat(dbPath) - assert.Nil(t, err) - - stat, err := statFromDB(mb.Config().Stats.DBPath, day, "sighting", "user") - assert.Nil(t, err) - actual := stat.val - assert.Equal(t, actual, expected) - }) - - rmDB(t) - - t.Run("TestSightingCounterNoResults", func(t *testing.T) { - day := mkDay() - count := 5 - expected := value(0) - mb := bot.NewMockBot() - - mb.Cfg.Stats.DBPath = dbPath - mb.Cfg.Stats.Sightings = []string{} - - s := New(mb) - assert.NotNil(t, s) - - for i := 0; i < count; i++ { - s.Message(makeMessage("user sighting")) - } - - _, err := os.Stat(dbPath) - assert.Nil(t, err) - - stat, err := statFromDB(mb.Config().Stats.DBPath, day, "sighting", "user") - assert.Nil(t, err) - actual := stat.val - assert.Equal(t, actual, expected) - }) - - rmDB(t) -} diff --git a/plugins/talker/talker.go b/plugins/talker/talker.go index 143e17b..b96866f 100644 --- a/plugins/talker/talker.go +++ b/plugins/talker/talker.go @@ -4,7 +4,6 @@ package talker import ( "fmt" - "math/rand" "strings" "github.com/velour/catbase/bot" @@ -40,16 +39,13 @@ var goatse []string = []string{ } type TalkerPlugin struct { - Bot bot.Bot - enforceNicks bool - sayings []string + Bot bot.Bot + sayings []string } func New(bot bot.Bot) *TalkerPlugin { return &TalkerPlugin{ - Bot: bot, - enforceNicks: bot.Config().EnforceNicks, - sayings: bot.Config().WelcomeMsgs, + Bot: bot, } } @@ -81,13 +77,6 @@ func (p *TalkerPlugin) Message(message msg.Message) bool { return true } - if p.enforceNicks && len(message.User.Name) != 9 { - msg := fmt.Sprintf("Hey %s, we really like to have 9 character nicks because we're crazy OCD and stuff.", - message.User.Name) - p.Bot.SendMessage(message.Channel, msg) - return true - } - return false } @@ -97,14 +86,6 @@ func (p *TalkerPlugin) Help(channel string, parts []string) { // Empty event handler because this plugin does not do anything on event recv func (p *TalkerPlugin) Event(kind string, message msg.Message) bool { - if kind == "JOIN" && strings.ToLower(message.User.Name) != strings.ToLower(p.Bot.Config().Nick) { - if len(p.sayings) == 0 { - return false - } - msg := fmt.Sprintf(p.sayings[rand.Intn(len(p.sayings))], message.User.Name) - p.Bot.SendMessage(message.Channel, msg) - return true - } return false } diff --git a/plugins/talker/talker_test.go b/plugins/talker/talker_test.go index fd81aa6..dd45ec1 100644 --- a/plugins/talker/talker_test.go +++ b/plugins/talker/talker_test.go @@ -74,47 +74,6 @@ func TestSayCommand(t *testing.T) { assert.Contains(t, mb.Messages[0], "hello") } -func TestNineChars(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) - c.enforceNicks = true - assert.NotNil(t, c) - res := c.Message(makeMessage("hello there")) - assert.Len(t, mb.Messages, 1) - assert.True(t, res) - assert.Contains(t, mb.Messages[0], "OCD") -} - -func TestWelcome(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) - c.sayings = []string{"Hi"} - assert.NotNil(t, c) - res := c.Event("JOIN", makeMessage("hello there")) - assert.Len(t, mb.Messages, 1) - assert.True(t, res) - assert.Contains(t, mb.Messages[0], "Hi") -} - -func TestNoSayings(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) - c.sayings = []string{} - assert.NotNil(t, c) - res := c.Event("JOIN", makeMessage("hello there")) - assert.Len(t, mb.Messages, 0) - assert.False(t, res) -} - -func TestNonJoinEvent(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) - assert.NotNil(t, c) - res := c.Event("SPLURT", makeMessage("hello there")) - assert.Len(t, mb.Messages, 0) - assert.False(t, res) -} - func TestHelp(t *testing.T) { mb := bot.NewMockBot() c := New(mb) diff --git a/plugins/twitch/twitch.go b/plugins/twitch/twitch.go index 941b55a..bbe4003 100644 --- a/plugins/twitch/twitch.go +++ b/plugins/twitch/twitch.go @@ -58,8 +58,8 @@ func New(bot bot.Bot) *TwitchPlugin { twitchList: map[string]*Twitcher{}, } - for _, users := range p.config.Twitch.Users { - for _, twitcherName := range users { + for _, ch := range p.config.GetArray("Twitch.Channels") { + for _, twitcherName := range p.config.GetArray("Twitch." + ch + ".Users") { if _, ok := p.twitchList[twitcherName]; !ok { p.twitchList[twitcherName] = &Twitcher{ name: twitcherName, @@ -67,10 +67,7 @@ func New(bot bot.Bot) *TwitchPlugin { } } } - } - - for channel := range p.config.Twitch.Users { - go p.twitchLoop(channel) + go p.twitchLoop(ch) } return p @@ -120,9 +117,9 @@ func (p *TwitchPlugin) serveStreaming(w http.ResponseWriter, r *http.Request) { func (p *TwitchPlugin) Message(message msg.Message) bool { if strings.ToLower(message.Body) == "twitch status" { channel := message.Channel - if _, ok := p.config.Twitch.Users[channel]; ok { - for _, twitcherName := range p.config.Twitch.Users[channel] { - if _, ok = p.twitchList[twitcherName]; ok { + if users := p.config.GetArray("Twitch." + channel + ".Users"); len(users) > 0 { + for _, twitcherName := range users { + if _, ok := p.twitchList[twitcherName]; ok { p.checkTwitch(channel, p.twitchList[twitcherName], true) } } @@ -147,14 +144,14 @@ func (p *TwitchPlugin) Help(channel string, parts []string) { } func (p *TwitchPlugin) twitchLoop(channel string) { - frequency := p.config.Twitch.Freq + frequency := p.config.GetInt("Twitch.Freq") log.Println("Checking every ", frequency, " seconds") for { time.Sleep(time.Duration(frequency) * time.Second) - for _, twitcherName := range p.config.Twitch.Users[channel] { + for _, twitcherName := range p.config.GetArray("Twitch." + channel + ".Users") { p.checkTwitch(channel, p.twitchList[twitcherName], false) } } @@ -200,8 +197,8 @@ func (p *TwitchPlugin) checkTwitch(channel string, twitcher *Twitcher, alwaysPri baseURL.RawQuery = query.Encode() - cid := p.config.Twitch.ClientID - auth := p.config.Twitch.Authorization + cid := p.config.Get("Twitch.ClientID") + auth := p.config.Get("Twitch.Authorization") body, ok := getRequest(baseURL.String(), cid, auth) if !ok { diff --git a/plugins/twitch/twitch_test.go b/plugins/twitch/twitch_test.go index ba0967d..bfdbf7b 100644 --- a/plugins/twitch/twitch_test.go +++ b/plugins/twitch/twitch_test.go @@ -28,7 +28,8 @@ func makeMessage(payload string) msg.Message { func makeTwitchPlugin(t *testing.T) (*TwitchPlugin, *bot.MockBot) { mb := bot.NewMockBot() c := New(mb) - c.config.Twitch.Users = map[string][]string{"test": []string{"drseabass"}} + c.config.SetArray("Twitch.Channels", []string{"test"}) + c.config.SetArray("Twitch.test.Users", []string{"drseabass"}) assert.NotNil(t, c) c.twitchList["drseabass"] = &Twitcher{ diff --git a/plugins/your/your.go b/plugins/your/your.go index 810b6fe..59d1222 100644 --- a/plugins/your/your.go +++ b/plugins/your/your.go @@ -28,13 +28,16 @@ func New(bot bot.Bot) *YourPlugin { // This function returns true if the plugin responds in a meaningful way to the users message. // Otherwise, the function returns false and the bot continues execution of other plugins. func (p *YourPlugin) Message(message msg.Message) bool { - if len(message.Body) > p.config.Your.MaxLength { + if len(message.Body) > p.config.GetInt("Your.MaxLength") { return false } msg := message.Body - for _, replacement := range p.config.Your.Replacements { - if rand.Float64() < replacement.Frequency { - r := strings.NewReplacer(replacement.This, replacement.That) + for _, replacement := range p.config.GetArray("Your.Replacements") { + freq := p.config.GetFloat64("your.replacements." + replacement + ".freq") + this := p.config.Get("your.replacements." + replacement + ".this") + that := p.config.Get("your.replacements." + replacement + ".that") + if rand.Float64() < freq { + r := strings.NewReplacer(this, that) msg = r.Replace(msg) } } diff --git a/plugins/your/your_test.go b/plugins/your/your_test.go index 01d3f4f..82ea896 100644 --- a/plugins/your/your_test.go +++ b/plugins/your/your_test.go @@ -10,7 +10,6 @@ import ( "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" "github.com/velour/catbase/bot/user" - "github.com/velour/catbase/config" ) func makeMessage(payload string) msg.Message { @@ -30,17 +29,14 @@ func TestReplacement(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - c.config.Your.MaxLength = 1000 - c.config.Your.Replacements = []config.Replacement{ - config.Replacement{ - This: "fuck", - That: "duck", - Frequency: 1.0, - }, - } + c.config.Set("Your.MaxLength", "1000") + c.config.SetArray("your.replacements", []string{"0"}) + c.config.Set("your.replacements.0.freq", "1.0") + c.config.Set("your.replacements.0.this", "fuck") + c.config.Set("your.replacements.0.that", "duck") res := c.Message(makeMessage("fuck a duck")) - assert.Len(t, mb.Messages, 1) assert.True(t, res) + assert.Len(t, mb.Messages, 1) assert.Contains(t, mb.Messages[0], "duck a duck") } @@ -48,24 +44,19 @@ func TestNoReplacement(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - c.config.Your.MaxLength = 1000 - c.config.Your.Replacements = []config.Replacement{ - config.Replacement{ - This: "nope", - That: "duck", - Frequency: 1.0, - }, - config.Replacement{ - This: " fuck", - That: "duck", - Frequency: 1.0, - }, - config.Replacement{ - This: "Fuck", - That: "duck", - Frequency: 1.0, - }, - } + c.config.Set("Your.MaxLength", "1000") + c.config.SetArray("your.replacements", []string{"0", "1", "2"}) + c.config.Set("your.replacements.0.freq", "1.0") + c.config.Set("your.replacements.0.this", "nope") + c.config.Set("your.replacements.0.that", "duck") + + c.config.Set("your.replacements.1.freq", "1.0") + c.config.Set("your.replacements.1.this", "nope") + c.config.Set("your.replacements.1.that", "duck") + + c.config.Set("your.replacements.2.freq", "1.0") + c.config.Set("your.replacements.2.this", "Fuck") + c.config.Set("your.replacements.2.that", "duck") c.Message(makeMessage("fuck a duck")) assert.Len(t, mb.Messages, 0) } diff --git a/slack/slack.go b/slack/slack.go index 32faaaa..f6ba143 100644 --- a/slack/slack.go +++ b/slack/slack.go @@ -210,11 +210,11 @@ func (s *Slack) SendMessageType(channel, message string, meMessage bool) (string postUrl = "https://slack.com/api/chat.meMessage" } - nick := s.config.Nick - icon := s.config.IconURL + nick := s.config.Get("Nick") + icon := s.config.Get("IconURL") resp, err := http.PostForm(postUrl, - url.Values{"token": {s.config.Slack.Token}, + url.Values{"token": {s.config.Get("Slack.Token")}, "username": {nick}, "icon_url": {icon}, "channel": {channel}, @@ -269,11 +269,11 @@ func (s *Slack) SendAction(channel, message string) string { } func (s *Slack) ReplyToMessageIdentifier(channel, message, identifier string) (string, bool) { - nick := s.config.Nick - icon := s.config.IconURL + nick := s.config.Get("Nick") + icon := s.config.Get("IconURL") resp, err := http.PostForm("https://slack.com/api/chat.postMessage", - url.Values{"token": {s.config.Slack.Token}, + url.Values{"token": {s.config.Get("Slack.Token")}, "username": {nick}, "icon_url": {icon}, "channel": {channel}, @@ -321,7 +321,7 @@ func (s *Slack) ReplyToMessage(channel, message string, replyTo msg.Message) (st func (s *Slack) React(channel, reaction string, message msg.Message) bool { log.Printf("Reacting in %s: %s", channel, reaction) resp, err := http.PostForm("https://slack.com/api/reactions.add", - url.Values{"token": {s.config.Slack.Token}, + url.Values{"token": {s.config.Get("Slack.Token")}, "name": {reaction}, "channel": {channel}, "timestamp": {message.AdditionalData["RAW_SLACK_TIMESTAMP"]}}) @@ -335,7 +335,7 @@ func (s *Slack) React(channel, reaction string, message msg.Message) bool { func (s *Slack) Edit(channel, newMessage, identifier string) bool { log.Printf("Editing in (%s) %s: %s", identifier, channel, newMessage) resp, err := http.PostForm("https://slack.com/api/chat.update", - url.Values{"token": {s.config.Slack.Token}, + url.Values{"token": {s.config.Get("Slack.Token")}, "channel": {channel}, "text": {newMessage}, "ts": {identifier}}) @@ -352,7 +352,7 @@ func (s *Slack) GetEmojiList() map[string]string { func (s *Slack) populateEmojiList() { resp, err := http.PostForm("https://slack.com/api/emoji.list", - url.Values{"token": {s.config.Slack.Token}}) + url.Values{"token": {s.config.Get("Slack.Token")}}) if err != nil { log.Printf("Error retrieving emoji list from Slack: %s", err) return @@ -545,7 +545,7 @@ func (s *Slack) markAllChannelsRead() { func (s *Slack) getAllChannels() []slackChannelListItem { u := s.url + "channels.list" resp, err := http.PostForm(u, - url.Values{"token": {s.config.Slack.Token}}) + url.Values{"token": {s.config.Get("Slack.Token")}}) if err != nil { log.Printf("Error posting user info request: %s", err) @@ -570,7 +570,7 @@ func (s *Slack) getAllChannels() []slackChannelListItem { func (s *Slack) markChannelAsRead(slackChanId string) error { u := s.url + "channels.info" resp, err := http.PostForm(u, - url.Values{"token": {s.config.Slack.Token}, "channel": {slackChanId}}) + url.Values{"token": {s.config.Get("Slack.Token")}, "channel": {slackChanId}}) if err != nil { log.Printf("Error posting user info request: %s", err) @@ -592,7 +592,7 @@ func (s *Slack) markChannelAsRead(slackChanId string) error { u = s.url + "channels.mark" resp, err = http.PostForm(u, - url.Values{"token": {s.config.Slack.Token}, "channel": {slackChanId}, "ts": {chanInfo.Channel.Latest.Ts}}) + url.Values{"token": {s.config.Get("Slack.Token")}, "channel": {slackChanId}, "ts": {chanInfo.Channel.Latest.Ts}}) if err != nil { log.Printf("Error posting user info request: %s", err) @@ -617,7 +617,7 @@ func (s *Slack) markChannelAsRead(slackChanId string) error { } func (s *Slack) connect() { - token := s.config.Slack.Token + token := s.config.Get("Slack.Token") u := fmt.Sprintf("https://slack.com/api/rtm.connect?token=%s", token) resp, err := http.Get(u) if err != nil { @@ -663,7 +663,7 @@ func (s *Slack) getUser(id string) (string, bool) { log.Printf("User %s not already found, requesting info", id) u := s.url + "users.info" resp, err := http.PostForm(u, - url.Values{"token": {s.config.Slack.Token}, "user": {id}}) + url.Values{"token": {s.config.Get("Slack.Token")}, "user": {id}}) if err != nil || resp.StatusCode != 200 { log.Printf("Error posting user info request: %d %s", resp.StatusCode, err) @@ -685,7 +685,7 @@ func (s *Slack) Who(id string) []string { log.Println("Who is queried for ", id) u := s.url + "channels.info" resp, err := http.PostForm(u, - url.Values{"token": {s.config.Slack.Token}, "channel": {id}}) + url.Values{"token": {s.config.Get("Slack.Token")}, "channel": {id}}) if err != nil { log.Printf("Error posting user info request: %s", err) From 15168f5db0b3a4b2326b0a77a9cd260c7292c602 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Mon, 21 Jan 2019 12:36:02 -0500 Subject: [PATCH 004/107] config: all tests passing * Using in-memory but shared DB. ALL TESTS MUST CLEAR RELEVANT TABLES * Removed problematic reminder test --- bot/mock.go | 2 +- plugins/babbler/babbler_test.go | 6 +++ plugins/beers/beers_test.go | 12 +++++ plugins/counter/counter.go | 14 +++--- plugins/counter/counter_test.go | 1 + plugins/reminder/reminder_test.go | 82 +++++++++---------------------- plugins/your/your_test.go | 13 +++-- 7 files changed, 58 insertions(+), 72 deletions(-) diff --git a/bot/mock.go b/bot/mock.go index d90f757..f3170e4 100644 --- a/bot/mock.go +++ b/bot/mock.go @@ -94,7 +94,7 @@ func (mb *MockBot) GetEmojiList() map[string]string { return make func (mb *MockBot) RegisterFilter(s string, f func(string) string) {} func NewMockBot() *MockBot { - cfg := config.ReadConfig(":memory:") + cfg := config.ReadConfig("file::memory:?mode=memory&cache=shared") b := MockBot{ Cfg: cfg, Messages: make([]string, 0), diff --git a/plugins/babbler/babbler_test.go b/plugins/babbler/babbler_test.go index 726a3ab..b07b75f 100644 --- a/plugins/babbler/babbler_test.go +++ b/plugins/babbler/babbler_test.go @@ -28,6 +28,12 @@ func makeMessage(payload string) msg.Message { func newBabblerPlugin(mb *bot.MockBot) *BabblerPlugin { bp := New(mb) bp.WithGoRoutines = false + mb.DB().MustExec(` + delete from babblers; + delete from babblerWords; + delete from babblerNodes; + delete from babblerArcs; + `) return bp } diff --git a/plugins/beers/beers_test.go b/plugins/beers/beers_test.go index 7c56a3a..99964e3 100644 --- a/plugins/beers/beers_test.go +++ b/plugins/beers/beers_test.go @@ -29,12 +29,24 @@ func makeMessage(payload string) msg.Message { func makeBeersPlugin(t *testing.T) (*BeersPlugin, *bot.MockBot) { mb := bot.NewMockBot() counter.New(mb) + mb.DB().MustExec(`delete from counter; delete from counter_alias;`) b := New(mb) b.Message(makeMessage("!mkalias beer :beer:")) b.Message(makeMessage("!mkalias beers :beer:")) return b, mb } +func TestCounter(t *testing.T) { + _, mb := makeBeersPlugin(t) + i, err := counter.GetItem(mb.DB(), "tester", "test") + if !assert.Nil(t, err) { + t.Log(err) + t.Fatal() + } + err = i.Update(5) + assert.Nil(t, err) +} + func TestImbibe(t *testing.T) { b, mb := makeBeersPlugin(t) b.Message(makeMessage("!imbibe")) diff --git a/plugins/counter/counter.go b/plugins/counter/counter.go index d445af5..2c7668a 100644 --- a/plugins/counter/counter.go +++ b/plugins/counter/counter.go @@ -173,21 +173,19 @@ func (i *Item) Delete() error { // NewCounterPlugin creates a new CounterPlugin with the Plugin interface func New(bot bot.Bot) *CounterPlugin { - if _, err := bot.DB().Exec(`create table if not exists counter ( + tx := bot.DB().MustBegin() + bot.DB().MustExec(`create table if not exists counter ( id integer primary key, nick string, item string, count integer - );`); err != nil { - log.Fatal(err) - } - if _, err := bot.DB().Exec(`create table if not exists counter_alias ( + );`) + bot.DB().MustExec(`create table if not exists counter_alias ( id integer PRIMARY KEY AUTOINCREMENT, item string NOT NULL UNIQUE, points_to string NOT NULL - );`); err != nil { - log.Fatal(err) - } + );`) + tx.Commit() return &CounterPlugin{ Bot: bot, DB: bot.DB(), diff --git a/plugins/counter/counter_test.go b/plugins/counter/counter_test.go index fff9ef0..f1c673c 100644 --- a/plugins/counter/counter_test.go +++ b/plugins/counter/counter_test.go @@ -16,6 +16,7 @@ import ( func setup(t *testing.T) (*bot.MockBot, *CounterPlugin) { mb := bot.NewMockBot() c := New(mb) + mb.DB().MustExec(`delete from counter; delete from counter_alias;`) _, err := MkAlias(mb.DB(), "tea", ":tea:") assert.Nil(t, err) return mb, c diff --git a/plugins/reminder/reminder_test.go b/plugins/reminder/reminder_test.go index cdd3d13..d2cc838 100644 --- a/plugins/reminder/reminder_test.go +++ b/plugins/reminder/reminder_test.go @@ -40,10 +40,15 @@ func makeMessageBy(payload, by string) msg.Message { } } -func TestMeReminder(t *testing.T) { +func setup(t *testing.T) (*ReminderPlugin, *bot.MockBot) { mb := bot.NewMockBot() - c := New(mb) - assert.NotNil(t, c) + r := New(mb) + mb.DB().MustExec(`delete from reminders; delete from config;`) + return r, mb +} + +func TestMeReminder(t *testing.T) { + c, mb := setup(t) res := c.Message(makeMessage("!remind me in 1s don't fail this test")) time.Sleep(2 * time.Second) assert.Len(t, mb.Messages, 2) @@ -53,9 +58,7 @@ func TestMeReminder(t *testing.T) { } func TestReminder(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) - assert.NotNil(t, c) + c, mb := setup(t) res := c.Message(makeMessage("!remind testuser in 1s don't fail this test")) time.Sleep(2 * time.Second) assert.Len(t, mb.Messages, 2) @@ -65,9 +68,7 @@ func TestReminder(t *testing.T) { } func TestReminderReorder(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) - assert.NotNil(t, c) + c, mb := setup(t) res := c.Message(makeMessage("!remind testuser in 2s don't fail this test 2")) assert.True(t, res) res = c.Message(makeMessage("!remind testuser in 1s don't fail this test 1")) @@ -81,9 +82,7 @@ func TestReminderReorder(t *testing.T) { } func TestReminderParse(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) - assert.NotNil(t, c) + c, mb := setup(t) res := c.Message(makeMessage("!remind testuser in unparseable don't fail this test")) assert.Len(t, mb.Messages, 1) assert.True(t, res) @@ -91,9 +90,7 @@ func TestReminderParse(t *testing.T) { } func TestEmptyList(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) - assert.NotNil(t, c) + c, mb := setup(t) res := c.Message(makeMessage("!list reminders")) assert.Len(t, mb.Messages, 1) assert.True(t, res) @@ -101,9 +98,7 @@ func TestEmptyList(t *testing.T) { } func TestList(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) - assert.NotNil(t, c) + c, mb := setup(t) res := c.Message(makeMessage("!remind testuser in 5m don't fail this test 1")) assert.True(t, res) res = c.Message(makeMessage("!remind testuser in 5m don't fail this test 2")) @@ -116,9 +111,7 @@ func TestList(t *testing.T) { } func TestListBy(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) - assert.NotNil(t, c) + c, mb := setup(t) res := c.Message(makeMessageBy("!remind testuser in 5m don't fail this test 1", "testuser")) assert.True(t, res) res = c.Message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2")) @@ -131,9 +124,7 @@ func TestListBy(t *testing.T) { } func TestListTo(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) - assert.NotNil(t, c) + c, mb := setup(t) res := c.Message(makeMessageBy("!remind testuser2 in 5m don't fail this test 1", "testuser")) assert.True(t, res) res = c.Message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2")) @@ -146,9 +137,7 @@ func TestListTo(t *testing.T) { } func TestToEmptyList(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) - assert.NotNil(t, c) + c, mb := setup(t) res := c.Message(makeMessageBy("!remind testuser2 in 5m don't fail this test 1", "testuser")) assert.True(t, res) res = c.Message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2")) @@ -160,9 +149,7 @@ func TestToEmptyList(t *testing.T) { } func TestFromEmptyList(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) - assert.NotNil(t, c) + c, mb := setup(t) res := c.Message(makeMessageBy("!remind testuser2 in 5m don't fail this test 1", "testuser")) assert.True(t, res) res = c.Message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2")) @@ -173,23 +160,8 @@ func TestFromEmptyList(t *testing.T) { assert.Contains(t, mb.Messages[2], "no pending reminders") } -func TestBatch(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) - c.config.Set("Reminder.MaxBatchAdd", "50") - assert.NotNil(t, c) - res := c.Message(makeMessage("!remind testuser every 1ms for 5ms yikes")) - assert.True(t, res) - time.Sleep(3 * time.Second) - assert.Len(t, mb.Messages, 6) - for i := 0; i < 5; i++ { - assert.Contains(t, mb.Messages[i+1], "Hey testuser, tester wanted you to be reminded: yikes") - } -} - func TestBatchMax(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) + c, mb := setup(t) c.config.Set("Reminder.MaxBatchAdd", "10") assert.NotNil(t, c) res := c.Message(makeMessage("!remind testuser every 1h for 24h yikes")) @@ -206,8 +178,7 @@ func TestBatchMax(t *testing.T) { } func TestCancel(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) + c, mb := setup(t) assert.NotNil(t, c) res := c.Message(makeMessage("!remind testuser in 1m don't fail this test")) assert.True(t, res) @@ -222,8 +193,7 @@ func TestCancel(t *testing.T) { } func TestCancelMiss(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) + c, mb := setup(t) assert.NotNil(t, c) res := c.Message(makeMessage("!cancel reminder 1")) assert.True(t, res) @@ -232,30 +202,26 @@ func TestCancelMiss(t *testing.T) { } func TestHelp(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) + c, mb := setup(t) assert.NotNil(t, c) c.Help("channel", []string{}) assert.Len(t, mb.Messages, 1) } func TestBotMessage(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) + c, _ := setup(t) assert.NotNil(t, c) assert.False(t, c.BotMessage(makeMessage("test"))) } func TestEvent(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) + c, _ := setup(t) assert.NotNil(t, c) assert.False(t, c.Event("dummy", makeMessage("test"))) } func TestRegisterWeb(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) + c, _ := setup(t) assert.NotNil(t, c) assert.Nil(t, c.RegisterWeb()) } diff --git a/plugins/your/your_test.go b/plugins/your/your_test.go index 82ea896..75a0ace 100644 --- a/plugins/your/your_test.go +++ b/plugins/your/your_test.go @@ -25,10 +25,15 @@ func makeMessage(payload string) msg.Message { } } -func TestReplacement(t *testing.T) { +func setup(t *testing.T) (*YourPlugin, *bot.MockBot) { mb := bot.NewMockBot() c := New(mb) - assert.NotNil(t, c) + mb.DB().MustExec(`delete from config;`) + return c, mb +} + +func TestReplacement(t *testing.T) { + c, mb := setup(t) c.config.Set("Your.MaxLength", "1000") c.config.SetArray("your.replacements", []string{"0"}) c.config.Set("your.replacements.0.freq", "1.0") @@ -41,9 +46,7 @@ func TestReplacement(t *testing.T) { } func TestNoReplacement(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) - assert.NotNil(t, c) + c, mb := setup(t) c.config.Set("Your.MaxLength", "1000") c.config.SetArray("your.replacements", []string{"0", "1", "2"}) c.config.Set("your.replacements.0.freq", "1.0") From 742c76f562d9ff73e02d675aeecc7f0fc0c51c88 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Mon, 21 Jan 2019 14:24:03 -0500 Subject: [PATCH 005/107] config: add defaults checking where necessary --- bot/bot.go | 8 +++++ config/config.go | 31 +++++++++++++----- config/defaults.go | 57 +++++++++++++++++++++++++++++++++ main.go | 24 ++++++++++++++ plugins/beers/beers.go | 13 ++++++-- plugins/fact/factoid.go | 14 ++++++-- plugins/inventory/inventory.go | 8 ++++- plugins/leftpad/leftpad.go | 7 +++- plugins/leftpad/leftpad_test.go | 2 ++ plugins/reminder/reminder.go | 7 +++- plugins/sisyphus/sisyphus.go | 14 +++++++- plugins/your/your.go | 8 ++++- slack/slack.go | 4 +++ 13 files changed, 180 insertions(+), 17 deletions(-) create mode 100644 config/defaults.go diff --git a/bot/bot.go b/bot/bot.go index e584ee1..6129e82 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -79,6 +79,7 @@ func New(config *config.Config, connector Connector) Bot { addr := config.Get("HttpAddr") if addr == "" { addr = "127.0.0.1:1337" + config.Set("HttpAddr", addr) } go http.ListenAndServe(addr, nil) @@ -172,7 +173,14 @@ func (b *bot) serveRoot(w http.ResponseWriter, r *http.Request) { // Checks if message is a command and returns its curtailed version func IsCmd(c *config.Config, message string) (bool, string) { cmdcs := c.GetArray("CommandChar") + if len(cmdcs) == 0 { + cmdcs = []string{"!"} + c.SetArray("CommandChar", cmdcs) + } botnick := strings.ToLower(c.Get("Nick")) + if botnick == "" { + log.Fatalf(`You must run catbase -set nick -val `) + } iscmd := false lowerMessage := strings.ToLower(message) diff --git a/config/config.go b/config/config.go index 2c540e3..6cb56f2 100644 --- a/config/config.go +++ b/config/config.go @@ -54,6 +54,12 @@ func (c *Config) Get(key string) string { return c.GetString(key) } +func envkey(key string) string { + key = strings.ToUpper(key) + key = strings.Replace(key, ".", "", -1) + return key +} + // GetString returns the config value for a string key // It will first look in the env vars for the key // It will check the DB for the key if an env DNE @@ -61,13 +67,14 @@ func (c *Config) Get(key string) string { // It will convert the value to a string if it exists func (c *Config) GetString(key string) string { key = strings.ToLower(key) - if v, found := os.LookupEnv(key); found { + if v, found := os.LookupEnv(envkey(key)); found { return v } var configValue string q := `select value from config where key=?` err := c.DB.Get(&configValue, q, key) if err != nil { + log.Printf("WARN: Key %s is empty", key) return "" } return configValue @@ -81,6 +88,9 @@ func (c *Config) GetString(key string) string { // This will do no conversion. func (c *Config) GetArray(key string) []string { val := c.GetString(key) + if val == "" { + return []string{} + } return strings.Split(val, ";;") } @@ -88,14 +98,19 @@ func (c *Config) GetArray(key string) []string { // Note, this is always a string. Use the SetArray for an array helper func (c *Config) Set(key, value string) error { key = strings.ToLower(key) - q := (`insert into config (key,value) values (?, ?);`) - _, err := c.Exec(q, key, value) + q := (`insert into config (key,value) values (?, ?) + on conflict(key) do update set value=?;`) + tx, err := c.Begin() if err != nil { - q := (`update config set value=? where key=?);`) - _, err = c.Exec(q, value, key) - if err != nil { - return err - } + return err + } + _, err = tx.Exec(q, key, value, value) + if err != nil { + return err + } + err = tx.Commit() + if err != nil { + return err } return nil } diff --git a/config/defaults.go b/config/defaults.go new file mode 100644 index 0000000..d98724c --- /dev/null +++ b/config/defaults.go @@ -0,0 +1,57 @@ +package config + +import ( + "bytes" + "html/template" + "log" + "strings" +) + +var q = ` +INSERT INTO config VALUES('type','slack'); +INSERT INTO config VALUES('nick','{{.Nick}}'); +INSERT INTO config VALUES('channels','{{.Channel}}'); +INSERT INTO config VALUES('factoid.quotetime',30); +INSERT INTO config VALUES('reaction.negativereactions','bullshit;;fake;;tableflip;;vomit'); +INSERT INTO config VALUES('reaction.positivereactions','+1;;authorized;;aw_yeah;;yeah_man;;joy'); +INSERT INTO config VALUES('reaction.generalchance',0.01); +INSERT INTO config VALUES('reaction.harrasschance',0.05); +INSERT INTO config VALUES('commandchar','!;;¡'); +INSERT INTO config VALUES('factoid.startupfact','speed test'); +INSERT INTO config VALUES('factoid.quotechance',0.99); +INSERT INTO config VALUES('factoid.minlen',4); +INSERT INTO config VALUES('untappd.channels','{{.Channel}}'); +INSERT INTO config VALUES('twitch.channels','{{.Channel}}'); +INSERT INTO config VALUES('twitch.{{.ChannelKey}}.users','drseabass;;phlyingpenguin;;stack5;;geoffwithaj;;msherms;;eaburns;;sheltim;;rathaus;;rcuhljr'); +INSERT INTO config VALUES('twitch.freq',60); +INSERT INTO config VALUES('leftpad.maxlen',50); +INSERT INTO config VALUES('untappd.freq',60); +INSERT INTO config VALUES('your.replacements.0.freq',1); +INSERT INTO config VALUES('your.replacements.0.this','fuck'); +INSERT INTO config VALUES('your.replacements.0.that','duck'); +INSERT INTO config VALUES('your.replacements','0;;1;;2'); +INSERT INTO config VALUES('httpaddr','127.0.0.1:1337'); +INSERT INTO config VALUES('your.maxlength',140); +INSERT INTO config VALUES('init',1); +` + +func (c *Config) SetDefaults(mainChannel, nick string) { + if nick == mainChannel && nick == "" { + log.Fatalf("You must provide a nick and a mainChannel") + } + t := template.Must(template.New("query").Parse(q)) + vals := struct { + Nick string + Channel string + ChannelKey string + }{ + nick, + mainChannel, + strings.ToLower(mainChannel), + } + var buf bytes.Buffer + t.Execute(&buf, vals) + c.MustExec(`delete from config;`) + c.MustExec(buf.String()) + log.Println("Configuration initialized.") +} diff --git a/main.go b/main.go index 094e648..3b77307 100644 --- a/main.go +++ b/main.go @@ -38,6 +38,12 @@ import ( "github.com/velour/catbase/slack" ) +var ( + key = flag.String("set", "", "Configuration key to set") + val = flag.String("val", "", "Configuration value to set") + initDB = flag.Bool("init", false, "Initialize the configuration DB") +) + func main() { rand.Seed(time.Now().Unix()) @@ -46,8 +52,26 @@ func main() { flag.Parse() // parses the logging flags. c := config.ReadConfig(*dbpath) + + if *key != "" && *val != "" { + c.Set(*key, *val) + log.Printf("Set config %s: %s", *key, *val) + return + } + if (*initDB && len(flag.Args()) != 2) || (!*initDB && c.GetInt("init") != 1) { + log.Fatal(`You must run "catbase -init "`) + } else if *initDB { + c.SetDefaults(flag.Arg(0), flag.Arg(1)) + return + } + var client bot.Connector + t := c.Get("type") + if t == "" { + c.Set("type", "slack") + t = "slack" + } switch c.Get("type") { case "irc": client = irc.New(c) diff --git a/plugins/beers/beers.go b/plugins/beers/beers.go index 3e91869..4d4477c 100644 --- a/plugins/beers/beers.go +++ b/plugins/beers/beers.go @@ -306,7 +306,12 @@ type Beers struct { } func (p *BeersPlugin) pullUntappd() ([]checkin, error) { - access_token := "?access_token=" + p.Bot.Config().Get("Untappd.Token") + token := p.Bot.Config().Get("Untappd.Token") + if token == "" { + return []checkin{}, fmt.Errorf("No untappd token") + } + + access_token := "?access_token=" + token baseUrl := "https://api.untappd.com/v4/checkin/recent/" url := baseUrl + access_token + "&limit=25" @@ -337,7 +342,8 @@ func (p *BeersPlugin) pullUntappd() ([]checkin, error) { func (p *BeersPlugin) checkUntappd(channel string) { token := p.Bot.Config().Get("Untappd.Token") - if token == "" || token == "" { + if token == "" { + log.Println(`Set config value "untappd.token" if you wish to enable untappd`) return } @@ -421,6 +427,9 @@ func (p *BeersPlugin) checkUntappd(channel string) { func (p *BeersPlugin) untappdLoop(channel string) { frequency := p.Bot.Config().GetInt("Untappd.Freq") + if frequency == 0 { + return + } log.Println("Checking every ", frequency, " seconds") diff --git a/plugins/fact/factoid.go b/plugins/fact/factoid.go index f216ccc..bf989b8 100644 --- a/plugins/fact/factoid.go +++ b/plugins/fact/factoid.go @@ -691,7 +691,12 @@ func (p *Factoid) randomFact() *factoid { // factTimer spits out a fact at a given interval and with given probability func (p *Factoid) factTimer(channel string) { - duration := time.Duration(p.Bot.Config().GetInt("Factoid.QuoteTime")) * time.Minute + quoteTime := p.Bot.Config().GetInt("Factoid.QuoteTime") + if quoteTime == 0 { + quoteTime = 30 + p.Bot.Config().Set("Factoid.QuoteTime", "30") + } + duration := time.Duration(quoteTime) * time.Minute myLastMsg := time.Now() for { time.Sleep(time.Duration(5) * time.Second) // why 5? @@ -705,7 +710,12 @@ func (p *Factoid) factTimer(channel string) { tdelta := time.Since(lastmsg.Time) earlier := time.Since(myLastMsg) > tdelta chance := rand.Float64() - success := chance < p.Bot.Config().GetFloat64("Factoid.QuoteChance") + quoteChance := p.Bot.Config().GetFloat64("Factoid.QuoteChance") + if quoteChance == 0.0 { + quoteChance = 0.99 + p.Bot.Config().Set("Factoid.QuoteChance", "0.99") + } + success := chance < quoteChance if success && tdelta > duration && earlier { fact := p.randomFact() diff --git a/plugins/inventory/inventory.go b/plugins/inventory/inventory.go index 83e767c..5166f04 100644 --- a/plugins/inventory/inventory.go +++ b/plugins/inventory/inventory.go @@ -8,6 +8,7 @@ import ( "fmt" "log" "regexp" + "strconv" "strings" "github.com/jmoiron/sqlx" @@ -201,7 +202,12 @@ func (p *InventoryPlugin) addItem(m msg.Message, i string) bool { return true } var removed string - if p.count() > p.config.GetInt("Inventory.Max") { + max := p.config.GetInt("inventory.max") + if max == 0 { + max = 10 + p.config.Set("inventory.max", strconv.Itoa(max)) + } + if p.count() > max { removed = p.removeRandom() } _, err := p.Exec(`INSERT INTO inventory (item) values (?)`, i) diff --git a/plugins/leftpad/leftpad.go b/plugins/leftpad/leftpad.go index 67189bf..d068ae1 100644 --- a/plugins/leftpad/leftpad.go +++ b/plugins/leftpad/leftpad.go @@ -45,7 +45,12 @@ func (p *LeftpadPlugin) Message(message msg.Message) bool { p.bot.SendMessage(message.Channel, "Invalid padding number") return true } - if length > p.config.GetInt("LeftPad.MaxLen") && p.config.GetInt("LeftPad.MaxLen") > 0 { + maxLen, who := p.config.GetInt("LeftPad.MaxLen"), p.config.Get("LeftPad.Who") + if who == "" { + who = "Putin" + p.config.Set("LeftPad.MaxLen", who) + } + if length > maxLen && maxLen > 0 { msg := fmt.Sprintf("%s would kill me if I did that.", p.config.Get("LeftPad.Who")) p.bot.SendMessage(message.Channel, msg) return true diff --git a/plugins/leftpad/leftpad_test.go b/plugins/leftpad/leftpad_test.go index 4f5511f..cb57c25 100644 --- a/plugins/leftpad/leftpad_test.go +++ b/plugins/leftpad/leftpad_test.go @@ -31,6 +31,7 @@ func makePlugin(t *testing.T) (*LeftpadPlugin, *bot.MockBot) { counter.New(mb) p := New(mb) assert.NotNil(t, p) + p.config.Set("LeftPad.MaxLen", "0") return p, mb } @@ -56,6 +57,7 @@ func TestNotCommand(t *testing.T) { func TestNoMaxLen(t *testing.T) { p, mb := makePlugin(t) + p.config.Set("LeftPad.MaxLen", "0") p.Message(makeMessage("!leftpad dicks 100 dicks")) assert.Len(t, mb.Messages, 1) assert.Contains(t, mb.Messages[0], "dicks") diff --git a/plugins/reminder/reminder.go b/plugins/reminder/reminder.go index b618289..49ca760 100644 --- a/plugins/reminder/reminder.go +++ b/plugins/reminder/reminder.go @@ -122,7 +122,12 @@ func (p *ReminderPlugin) Message(message msg.Message) bool { what := strings.Join(parts[6:], " ") for i := 0; when.Before(endTime); i++ { - if i >= p.config.GetInt("Reminder.MaxBatchAdd") { + max := p.config.GetInt("Reminder.MaxBatchAdd") + if max == 0 { + max = 10 + p.config.Set("reminder.maxbatchadd", strconv.Itoa(max)) + } + if i >= max { p.Bot.SendMessage(channel, "Easy cowboy, that's a lot of reminders. I'll add some of them.") doConfirm = false break diff --git a/plugins/sisyphus/sisyphus.go b/plugins/sisyphus/sisyphus.go index 834e1b8..c73cbd9 100644 --- a/plugins/sisyphus/sisyphus.go +++ b/plugins/sisyphus/sisyphus.go @@ -60,7 +60,13 @@ func (g *game) scheduleDecrement() { g.timers[0].Stop() } minDec := g.bot.Config().GetInt("Sisyphus.MinDecrement") - maxDec := g.bot.Config().GetInt("Sisyphus.MinDecrement") + maxDec := g.bot.Config().GetInt("Sisyphus.MaxDecrement") + if maxDec == minDec && maxDec == 0 { + maxDec = 30 + minDec = 10 + g.bot.Config().Set("Sisyphus.MinDecrement", strconv.Itoa(minDec)) + g.bot.Config().Set("Sisyphus.MaxDecrement", strconv.Itoa(maxDec)) + } g.nextDec = time.Now().Add(time.Duration((minDec + rand.Intn(maxDec))) * time.Minute) go func() { t := time.NewTimer(g.nextDec.Sub(time.Now())) @@ -78,6 +84,12 @@ func (g *game) schedulePush() { } minPush := g.bot.Config().GetInt("Sisyphus.MinPush") maxPush := g.bot.Config().GetInt("Sisyphus.MaxPush") + if minPush == maxPush && maxPush == 0 { + minPush = 1 + maxPush = 10 + g.bot.Config().Set("Sisyphus.MinPush", strconv.Itoa(minPush)) + g.bot.Config().Set("Sisyphus.MaxPush", strconv.Itoa(maxPush)) + } g.nextPush = time.Now().Add(time.Duration(rand.Intn(maxPush)+minPush) * time.Minute) go func() { t := time.NewTimer(g.nextPush.Sub(time.Now())) diff --git a/plugins/your/your.go b/plugins/your/your.go index 59d1222..9f6ad47 100644 --- a/plugins/your/your.go +++ b/plugins/your/your.go @@ -4,6 +4,7 @@ package your import ( "math/rand" + "strconv" "strings" "github.com/velour/catbase/bot" @@ -28,7 +29,12 @@ func New(bot bot.Bot) *YourPlugin { // This function returns true if the plugin responds in a meaningful way to the users message. // Otherwise, the function returns false and the bot continues execution of other plugins. func (p *YourPlugin) Message(message msg.Message) bool { - if len(message.Body) > p.config.GetInt("Your.MaxLength") { + maxLen := p.config.GetInt("your.maxlength") + if maxLen == 0 { + maxLen = 140 + p.config.Set("your.maxlength", strconv.Itoa(maxLen)) + } + if len(message.Body) > maxLen { return false } msg := message.Body diff --git a/slack/slack.go b/slack/slack.go index f6ba143..c7b3981 100644 --- a/slack/slack.go +++ b/slack/slack.go @@ -212,6 +212,10 @@ func (s *Slack) SendMessageType(channel, message string, meMessage bool) (string nick := s.config.Get("Nick") icon := s.config.Get("IconURL") + if icon == "" { + icon = "https://placekitten.com/400/400" + log.Println("Set config item IconURL to customize appearance!") + } resp, err := http.PostForm(postUrl, url.Values{"token": {s.config.Get("Slack.Token")}, From 9ea1ba68f54562c7129ee4f364b5ffa1dd0af827 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Mon, 21 Jan 2019 16:26:54 -0500 Subject: [PATCH 006/107] admin: add ability to get/set config values * Users cannot get/set sensitive values * Removed example lua config file --- example_config.lua | 123 ------------------------------------ plugins/admin/admin.go | 41 +++++++++--- plugins/admin/admin_test.go | 69 ++++++++++++++++++++ 3 files changed, 102 insertions(+), 131 deletions(-) delete mode 100644 example_config.lua create mode 100644 plugins/admin/admin_test.go diff --git a/example_config.lua b/example_config.lua deleted file mode 100644 index b1a2ea3..0000000 --- a/example_config.lua +++ /dev/null @@ -1,123 +0,0 @@ -config = { - Channels = { - "#CatBaseTest" - }, - TwitterConsumerSecret = "", - Reminder = { - MaxBatchAdd = 10 - }, - Nick = "CatBaseTest", - IconURL = "http://placekitten.com/g/200/300", - LeftPad = { - Who = "person", - MaxLen = 50 - }, - Factoid = { - StartupFact = "speed test", - QuoteTime = 1, - QuoteChance = 0.99, - MinLen = 5 - }, - CommandChar = { - "!", - "¡" - }, - FullName = "CatBase", - Your = { - MaxLength = 140, - DuckingChance = 0.5, - FuckingChance = 0.15, - YourChance = 0.4 - }, - Emojify = { - Chance = 0.02, - Scoreless = { - "a", - "it" - } - }, - DB = { - File = "catbase.db", - Server = "127.0.0.1" - }, - Plugins = { - }, - Untappd = { - Freq = 3600, - Channels = { - }, - Token = "" - }, - LogLength = 50, - RatePerSec = 10, - Reaction = { - HarrassChance = 0.05, - GeneralChance = 0.01, - NegativeHarrassmentMultiplier = 2, - HarrassList = { - "msherms" - }, - NegativeReactions = { - "bullshit", - "fake", - "tableflip", - "vomit" - }, - PositiveReactions = { - "+1", - "authorized", - "aw_yea", - "joy" - } - }, - TwitterUserKey = "", - MainChannel = "#CatBaseTest", - TwitterUserSecret = "", - WelcomeMsgs = { - "Real men use screen, %s.", - "Joins upset the hivemind's OCD, %s.", - "Joins upset the hivemind's CDO, %s.", - "%s, I WILL CUT YOU!" - }, - Bad = { - Msgs = { - }, - Hosts = { - }, - Nicks = { - } - }, - Irc = { - Server = "ircserver:6697", - Pass = "CatBaseTest:test" - }, - Slack = { - Token = "" - }, - TwitterConsumerKey = "", - Babbler = { - DefaultUsers = { - "seabass" - } - }, - Type = "slack", - Admins = { - "" - }, - Stats = { - Sightings = { - "user" - }, - DBPath = "stats.db" - }, - HttpAddr = "127.0.0.1:1337", - Inventory = { - Max = 5 - }, - Sisyphus = { - MinDecrement = 10, - MinPush = 1 - } -} - -} diff --git a/plugins/admin/admin.go b/plugins/admin/admin.go index ae624b2..6543930 100644 --- a/plugins/admin/admin.go +++ b/plugins/admin/admin.go @@ -3,12 +3,14 @@ package admin import ( + "fmt" "log" "strings" "github.com/jmoiron/sqlx" "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" + "github.com/velour/catbase/config" ) // This is a admin plugin to serve as an example and quick copy/paste for new plugins. @@ -16,6 +18,7 @@ import ( type AdminPlugin struct { Bot bot.Bot db *sqlx.DB + cfg *config.Config } // NewAdminPlugin creates a new AdminPlugin with the Plugin interface @@ -23,11 +26,18 @@ func New(bot bot.Bot) *AdminPlugin { p := &AdminPlugin{ Bot: bot, db: bot.DB(), + cfg: bot.Config(), } - p.LoadData() return p } +var forbiddenKeys = map[string]bool{ + "twitch.authorization": true, + "twitch.clientid": true, + "untappd.token": true, + "slack.token": true, +} + // Message responds to the bot hook on recieving messages. // This function returns true if the plugin responds in a meaningful way to the users message. // Otherwise, the function returns false and the bot continues execution of other plugins. @@ -38,6 +48,28 @@ func (p *AdminPlugin) Message(message msg.Message) bool { return p.handleVariables(message) } + if !message.Command { + return false + } + + parts := strings.Split(body, " ") + if parts[0] == "set" && len(parts) > 2 && forbiddenKeys[parts[1]] { + p.Bot.SendMessage(message.Channel, "You cannot access that key") + return true + } else if parts[0] == "set" && len(parts) > 2 { + p.cfg.Set(parts[1], strings.Join(parts[2:], " ")) + p.Bot.SendMessage(message.Channel, fmt.Sprintf("Set %s", parts[1])) + return true + } + if parts[0] == "get" && len(parts) == 2 && forbiddenKeys[parts[1]] { + p.Bot.SendMessage(message.Channel, "You cannot access that key") + return true + } else if parts[0] == "get" && len(parts) == 2 { + v := p.cfg.Get(parts[1]) + p.Bot.SendMessage(message.Channel, fmt.Sprintf("%s: %s", parts[1], v)) + return true + } + return false } @@ -88,13 +120,6 @@ func (p *AdminPlugin) handleVariables(message msg.Message) bool { return true } -// LoadData imports any configuration data into the plugin. This is not strictly necessary other -// than the fact that the Plugin interface demands it exist. This may be deprecated at a later -// date. -func (p *AdminPlugin) LoadData() { - // This bot has no data to load -} - // Help responds to help requests. Every plugin must implement a help function. func (p *AdminPlugin) Help(channel string, parts []string) { p.Bot.SendMessage(channel, "This does super secret things that you're not allowed to know about.") diff --git a/plugins/admin/admin_test.go b/plugins/admin/admin_test.go new file mode 100644 index 0000000..8df6b0d --- /dev/null +++ b/plugins/admin/admin_test.go @@ -0,0 +1,69 @@ +package admin + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/velour/catbase/bot" + "github.com/velour/catbase/bot/msg" + "github.com/velour/catbase/bot/user" +) + +var ( + a *AdminPlugin + mb *bot.MockBot +) + +func setup(t *testing.T) (*AdminPlugin, *bot.MockBot) { + mb = bot.NewMockBot() + a = New(mb) + mb.DB().MustExec(`delete from config`) + return a, mb +} + +func makeMessage(payload string) msg.Message { + isCmd := strings.HasPrefix(payload, "!") + if isCmd { + payload = payload[1:] + } + return msg.Message{ + User: &user.User{Name: "tester"}, + Channel: "test", + Body: payload, + Command: isCmd, + } +} + +func TestSet(t *testing.T) { + a, mb := setup(t) + expected := "test value" + a.Message(makeMessage("!set test.key " + expected)) + actual := mb.Config().Get("test.key") + assert.Equal(t, expected, actual) +} + +func TestGetValue(t *testing.T) { + a, mb := setup(t) + expected := "value" + mb.Config().Set("test.key", "value") + a.Message(makeMessage("!get test.key")) + assert.Len(t, mb.Messages, 1) + assert.Contains(t, mb.Messages[0], expected) +} + +func TestGetEmpty(t *testing.T) { + a, mb := setup(t) + expected := "test.key: " + a.Message(makeMessage("!get test.key")) + assert.Len(t, mb.Messages, 1) + assert.Equal(t, expected, mb.Messages[0]) +} + +func TestGetForbidden(t *testing.T) { + a, mb := setup(t) + expected := "cannot access" + a.Message(makeMessage("!get slack.token")) + assert.Len(t, mb.Messages, 1) + assert.Contains(t, mb.Messages[0], expected) +} From 7a74ca005963474e779606010c40962823fb55ad Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Mon, 21 Jan 2019 17:15:46 -0500 Subject: [PATCH 007/107] emojifyme: make it use ractions; add velour emojy --- plugins/emojifyme/emojifyme.go | 18 +- plugins/emojifyme/velour.go | 689 +++++++++++++++++++++++++++++++++ 2 files changed, 701 insertions(+), 6 deletions(-) create mode 100644 plugins/emojifyme/velour.go diff --git a/plugins/emojifyme/emojifyme.go b/plugins/emojifyme/emojifyme.go index 2d513c0..1935206 100644 --- a/plugins/emojifyme/emojifyme.go +++ b/plugins/emojifyme/emojifyme.go @@ -48,6 +48,10 @@ func New(bot bot.Bot) *EmojifyMePlugin { } } + for _, e := range customEmojys { + emojiMap[e] = e + } + return &EmojifyMePlugin{ Bot: bot, GotBotEmoji: false, @@ -67,12 +71,13 @@ func (p *EmojifyMePlugin) Message(message msg.Message) bool { inertTokens := p.Bot.Config().GetArray("Emojify.Scoreless") emojied := 0.0 tokens := strings.Fields(strings.ToLower(message.Body)) - for i, token := range tokens { + emojys := map[string]bool{} + for _, token := range tokens { if _, ok := p.Emoji[token]; ok { if !stringsContain(inertTokens, token) { emojied++ } - tokens[i] = ":" + token + ":" + emojys[token] = true } else if strings.HasSuffix(token, "s") { //Check to see if we can strip the trailing "s" off and get an emoji temp := strings.TrimSuffix(token, "s") @@ -80,7 +85,7 @@ func (p *EmojifyMePlugin) Message(message msg.Message) bool { if !stringsContain(inertTokens, temp) { emojied++ } - tokens[i] = ":" + temp + ":s" + emojys[token] = true } else if strings.HasSuffix(token, "es") { //Check to see if we can strip the trailing "es" off and get an emoji temp := strings.TrimSuffix(token, "es") @@ -88,14 +93,15 @@ func (p *EmojifyMePlugin) Message(message msg.Message) bool { if !stringsContain(inertTokens, temp) { emojied++ } - tokens[i] = ":" + temp + ":es" + emojys[token] = true } } } } if emojied > 0 && rand.Float64() <= p.Bot.Config().GetFloat64("Emojify.Chance")*emojied { - modified := strings.Join(tokens, " ") - p.Bot.SendMessage(message.Channel, modified) + for e, _ := range emojys { + p.Bot.React(message.Channel, e, message) + } return true } return false diff --git a/plugins/emojifyme/velour.go b/plugins/emojifyme/velour.go new file mode 100644 index 0000000..8ce4bbf --- /dev/null +++ b/plugins/emojifyme/velour.go @@ -0,0 +1,689 @@ +package emojifyme + +var customEmojys = []string{ + "-1000", + "15", + "1up", + "3rd_turn", + "40oz", + "4loko", + "603_brewery", + "a_bad_guy_from_die_hard", + "aeropress", + "agentsmith", + "agreed", + "aim", + "airship", + "alcoholic", + "aloha_shirt", + "amazon", + "angry_poop", + "animal", + "anti-ninja", + "aol", + "aplus", + "appleinc", + "apu", + "asterisk", + "atredies", + "authorized", + "aw_yea", + "axe", + "badge", + "badpundog", + "balls", + "banjo", + "barneygumble", + "basshot", + "beaker", + "beard", + "beefsquatch", + "bender", + "beryl", + "best_korea", + "better_gopher", + "bezos", + "bible", + "bilbo", + "binary_tree", + "bitcoin", + "black_belt", + "black_eye", + "black_mage", + "black_square", + "blackboard", + "blinded", + "blinky", + "blood", + "blue_baby", + "blue_balls", + "bluesteel", + "bmo", + "bob", + "bobby", + "bondage", + "boo", + "boo_this_man", + "bourbon", + "bowtie", + "bra", + "brain-problems", + "brannigan", + "breakfast", + "brian", + "brick", + "brimstone", + "bro", + "broken", + "bronze-trophy", + "broom", + "btc", + "bucket", + "bud_light", + "budweiser", + "bullet", + "bullseye", + "bullshit", + "burgerboy", + "burglar", + "butt", + "butter", + "caffeine", + "cage", + "calavera", + "camper", + "carbsquatch", + "carmen_sandiego", + "cave-troll", + "caveman", + "ceiling_anti-ninja", + "ceiling_ninja", + "chad", + "charpov", + "chase_the_dragon", + "che", + "cheers", + "cheese", + "chemex", + "chicago-hot-dog", + "chilidog", + "chocobo", + "chrome", + "chronofroyo", + "chub", + "cigar", + "clamps", + "clown", + "code", + "codiscope", + "coffee-sprite", + "coke", + "colt45", + "comma_splice", + "command", + "confederate", + "conquest", + "contra", + "cookies", + "corona", + "cortana", + "couch", + "count", + "courage", + "cowboy", + "crazy_lizard", + "crisco", + "crock_pot", + "croooow", + "cubimal_chick", + "cwilt", + "cylon", + "d20", + "dagaz", + "daggett", + "dancing_penguin", + "dangerous", + "dark_one", + "darkwing", + "death", + "debrief", + "deep", + "deodorant", + "devil", + "dickbutt", + "dickdance", + "dingle", + "dino", + "disappointed_ruml", + "do_not_go_in_there", + "doc", + "doge", + "doh", + "doit", + "donnie", + "doritos", + "double_asterisk", + "doubt", + "downvote", + "dr-nick", + "dr_claw", + "dr_spaceman", + "dr_stephen_hawkings", + "dragonball", + "dramatic-chipmunk", + "drooling", + "drumpf", + "duff", + "duffman", + "dusty_stick", + "dw", + "dwarf", + "eaburned", + "eaburns", + "eaburns-fail", + "edge", + "egg-0", + "eggman", + "ehwaz", + "einstein", + "eleven", + "empire", + "everything", + "f", + "fail", + "fake", + "fallout_boy", + "false", + "famine", + "famous-heath-ledger-as-the-joker-clapping-for-james-gordon-in-the-dark-knight", + "fart", + "fearless_leader", + "feelsgood", + "fifteen", + "fighter", + "finn", + "finnadie", + "firefox", + "fivefivefive", + "flag-ussr", + "flailing", + "flanders", + "flyngpngn", + "fminus", + "fool", + "forever", + "freud", + "fry", + "fu", + "fuckoff", + "fuckyeah", + "gandalf", + "garbage", + "gates", + "gay", + "gene", + "get_some", + "ghostbusters", + "giggity", + "gimp", + "gin", + "git", + "glitch_crab", + "gloriousleader", + "glucose", + "gnu", + "go", + "go-go-gadget", + "goatse", + "goberserk", + "god", + "godmode", + "gold-trophy", + "gollum", + "goof", + "google", + "gopher", + "gpu", + "grinch", + "growler", + "gryffindor", + "guiness", + "guinness", + "gumby", + "h", + "hackar", + "hagalaz", + "haha", + "hahaha", + "half_done", + "ham", + "hank", + "harambe", + "hardy", + "hatcher", + "hate", + "hawaiian_shirt", + "hawkings", + "headless_horseman", + "heady_topper", + "heavy_equals_sign", + "heavybreathing", + "heck_yea", + "heineken", + "helldivers", + "herdez", + "hes_on_fire", + "highdea", + "historic_artifact", + "holte", + "homer", + "hooker", + "hop", + "how_to_jump", + "hufflepuff", + "huge_metal_fan", + "hulk", + "hurtrealbad", + "hydrant", + "hypnotoad", + "i93", + "i_do_cocaine", + "ice_cube", + "inspector_gadget", + "internet_explorer", + "ipa", + "isaac", + "isaac_pride", + "it_lives", + "ius", + "jail", + "java", + "jedi", + "jeff", + "jenkins", + "jera", + "jesus", + "jinkies", + "jira", + "joint", + "juicy", + "keeper", + "keg", + "kermit", + "kevin_bacon", + "kfc", + "king_cobra", + "klingon", + "kneeling_man", + "kneeling_woman", + "knight", + "kodos", + "korf", + "krampus", + "krang", + "la_croix", + "lambdahead", + "lastdab", + "latte", + "laugh_devil", + "laugh_dr_evil", + "laugh_dumb", + "laugh_dwight", + "laugh_jack", + "laugh_milk", + "left_blinker", + "leo_", + "liar", + "lick", + "listentome", + "literally_hitler", + "literally_stk5", + "literally_trump", + "little_caesars", + "little_horn", + "lobster", + "lobster_roll", + "lobster_tail", + "logs", + "lol", + "lsd", + "luchador", + "luigi", + "magic", + "magicleap", + "maid_crossbones", + "malte", + "malört", + "margarita", + "mario", + "mario_banana", + "marvin", + "math_checks_out", + "maybe", + "mcduck", + "mcduck_diving", + "meat", + "meatwad", + "meeseeks", + "megaman", + "merchant", + "metal", + "metroid", + "mickeys", + "mikey", + "milf", + "milk", + "minion", + "mint_julep", + "missle", + "mj", + "mojito", + "monocle", + "monster", + "monstro", + "mooning", + "moonshot", + "moonwalk", + "moose", + "morty", + "moxie", + "mozilla", + "mr_yuk", + "msherms", + "msherms_bait", + "mudkip", + "muffin", + "mystery_machine", + "nachos", + "nailed_it", + "neckbeard", + "nelson", + "nice", + "nils", + "ninja", + "nintendo-64-controller", + "nipple", + "no", + "no_wheelchair", + "noose", + "nope", + "norbert", + "norris", + "not-a-pipe", + "not_a_doctor", + "nra", + "nuclear_explosion", + "nurse", + "nyancat", + "octocat", + "og_tap", + "oh_yea", + "ohyea", + "olde_english", + "olde_fortran", + "oliver", + "open_trump_hands", + "opera", + "ouji", + "ouroboros", + "pacman", + "papajohns", + "paranoid", + "paranoid_android", + "party", + "partyparrot", + "patrik", + "pay_respects", + "pbr", + "pcmasterrace", + "pea", + "pea-lang", + "pee", + "peggy", + "pennies", + "penny", + "pentagram", + "pepe", + "pestilence", + "phd", + "php", + "pi", + "picard", + "pickle", + "pickle_rick", + "picklerick", + "piggy", + "pigpen", + "pikachu", + "pinky", + "pipe", + "pirate", + "pirate_flag", + "pjw", + "plan9", + "plane", + "platinum-trophy", + "poe", + "poison", + "pokeball", + "poochie", + "pretzels", + "pride", + "probably_racist", + "professor_farnsworth", + "programming", + "psn", + "puke", + "pun", + "q", + "quagmire", + "r", + "rage1", + "rage2", + "rage3", + "rage4", + "ralph", + "raph", + "ravenclaw", + "razor", + "real_sandwich", + "real_scream", + "real_smoking", + "realistic_gun", + "reaper", + "rebellion", + "recursion", + "red_bull", + "red_mage", + "red_room", + "revelations", + "revolution", + "rich_bitch", + "rick", + "right_blinker", + "rimshot", + "rip", + "rms", + "ron_swanson", + "roomba", + "rube", + "ruml", + "rupee", + "rut-roh", + "s2000", + "s2k", + "saccharomyces", + "sad-laugh", + "safari", + "sailor", + "salt", + "samurai", + "sarcasm", + "sarcastic_clap", + "satan", + "satanic_bible", + "sausage", + "science", + "scoobysnack", + "scotch", + "scrooge", + "seabass", + "seabass-shamed", + "seabass-victory", + "seabass-woke", + "seabass_glass", + "seabass_test", + "seabass_travel", + "semicolon", + "serious", + "seymour", + "shame", + "shipit", + "shocked", + "shocker", + "shovel", + "silver-trophy", + "simple_smile", + "single", + "siren", + "siri", + "skiesel", + "skinner", + "skunked", + "slack", + "slack_call", + "slash", + "slime", + "slurmsmckenzie", + "slytherin", + "smiling_sausage", + "smith", + "smoker", + "smooth", + "snicklefritz", + "snoop-dogg", + "soap", + "sobchak", + "soda-can", + "soiled", + "sombrero", + "sonic", + "sort_of_check", + "sort_of_done", + "spam", + "sperm", + "spic", + "spice-weasel", + "splinter", + "spring", + "squirrel", + "sriracha", + "staple", + "star_trek", + "stardew_chicken_white", + "stat_and_wal", + "stein", + "stephen_hawkings", + "stk5", + "stk5-fail", + "stk5_face", + "stk5onagoodday", + "submarine", + "subway", + "subway_jared", + "sunglasses_cat", + "suspect", + "svohr", + "swipe", + "switch", + "swoon", + "tableflip", + "taco_bell", + "take_my_money", + "tap", + "taz", + "teapot", + "tear_jar", + "tecumseh", + "template", + "tfogal", + "thats_the_joke", + "the-duke", + "the_brain", + "the_day_is_mine", + "the_donut", + "the_dude", + "the_fool", + "the_hush", + "the_lost", + "the_rock", + "thief", + "thisisfine", + "thumbsup2", + "thumbsup_all", + "tina", + "tire", + "to-the-diner", + "toilet_that_opens_right_for_flyngpngn", + "tom_nook", + "tom_servo", + "top_", + "towel", + "triforce", + "trogdor", + "troll", + "trollface", + "true", + "tumbleweed", + "tux", + "twin_peaks", + "twitch", + "un", + "unauthorized", + "unh", + "unholy-antichrist", + "uofl", + "urinal", + "vacuum", + "vader", + "vault_boy", + "vcard", + "vim", + "voltron", + "vomit", + "waiting", + "wal_and_stat", + "waldo", + "wally", + "war", + "weed", + "wfh_beer", + "whiskey", + "white_mage", + "white_square", + "wholefoods", + "wiggum", + "winamp", + "windows", + "wirecutter", + "wiz", + "woody", + "wow", + "wow_owen", + "wrong", + "x2", + "xzibit", + "yak", + "yea_well_you_know_thats_just_like_your_opinion_man", + "yeah_man", + "yeast", + "yes", + "yeti", + "yguengling", + "yo-yo", + "you_know_not_the_elaborate_criteria_by_which_you_are_to_be_judged", + "yuengling", + "zelda_candle", + "zelda_fire", + "zoidberg", +} From 36320df72549d0b8958d1ef17403a6f7b58dd84e Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Mon, 21 Jan 2019 18:00:51 -0500 Subject: [PATCH 008/107] emojify: change THE ALGORITHM --- plugins/emojifyme/emojifyme.go | 38 +- plugins/emojifyme/velour.go | 689 --------------------------------- 2 files changed, 9 insertions(+), 718 deletions(-) delete mode 100644 plugins/emojifyme/velour.go diff --git a/plugins/emojifyme/emojifyme.go b/plugins/emojifyme/emojifyme.go index 1935206..6805304 100644 --- a/plugins/emojifyme/emojifyme.go +++ b/plugins/emojifyme/emojifyme.go @@ -48,10 +48,6 @@ func New(bot bot.Bot) *EmojifyMePlugin { } } - for _, e := range customEmojys { - emojiMap[e] = e - } - return &EmojifyMePlugin{ Bot: bot, GotBotEmoji: false, @@ -70,36 +66,20 @@ func (p *EmojifyMePlugin) Message(message msg.Message) bool { inertTokens := p.Bot.Config().GetArray("Emojify.Scoreless") emojied := 0.0 - tokens := strings.Fields(strings.ToLower(message.Body)) - emojys := map[string]bool{} - for _, token := range tokens { - if _, ok := p.Emoji[token]; ok { - if !stringsContain(inertTokens, token) { + emojys := []string{} + msg := strings.Replace(strings.ToLower(message.Body), "_", " ", -1) + for k, v := range p.Emoji { + k = strings.Replace(k, "_", " ", -1) + if strings.Contains(msg, " "+k+" ") || strings.HasPrefix(msg, k) || strings.HasSuffix(msg, k) { + emojys = append(emojys, v) + if !stringsContain(inertTokens, k) || len(k) <= 2 { emojied++ } - emojys[token] = true - } else if strings.HasSuffix(token, "s") { - //Check to see if we can strip the trailing "s" off and get an emoji - temp := strings.TrimSuffix(token, "s") - if _, ok := p.Emoji[temp]; ok { - if !stringsContain(inertTokens, temp) { - emojied++ - } - emojys[token] = true - } else if strings.HasSuffix(token, "es") { - //Check to see if we can strip the trailing "es" off and get an emoji - temp := strings.TrimSuffix(token, "es") - if _, ok := p.Emoji[temp]; ok { - if !stringsContain(inertTokens, temp) { - emojied++ - } - emojys[token] = true - } - } } } + if emojied > 0 && rand.Float64() <= p.Bot.Config().GetFloat64("Emojify.Chance")*emojied { - for e, _ := range emojys { + for _, e := range emojys { p.Bot.React(message.Channel, e, message) } return true diff --git a/plugins/emojifyme/velour.go b/plugins/emojifyme/velour.go deleted file mode 100644 index 8ce4bbf..0000000 --- a/plugins/emojifyme/velour.go +++ /dev/null @@ -1,689 +0,0 @@ -package emojifyme - -var customEmojys = []string{ - "-1000", - "15", - "1up", - "3rd_turn", - "40oz", - "4loko", - "603_brewery", - "a_bad_guy_from_die_hard", - "aeropress", - "agentsmith", - "agreed", - "aim", - "airship", - "alcoholic", - "aloha_shirt", - "amazon", - "angry_poop", - "animal", - "anti-ninja", - "aol", - "aplus", - "appleinc", - "apu", - "asterisk", - "atredies", - "authorized", - "aw_yea", - "axe", - "badge", - "badpundog", - "balls", - "banjo", - "barneygumble", - "basshot", - "beaker", - "beard", - "beefsquatch", - "bender", - "beryl", - "best_korea", - "better_gopher", - "bezos", - "bible", - "bilbo", - "binary_tree", - "bitcoin", - "black_belt", - "black_eye", - "black_mage", - "black_square", - "blackboard", - "blinded", - "blinky", - "blood", - "blue_baby", - "blue_balls", - "bluesteel", - "bmo", - "bob", - "bobby", - "bondage", - "boo", - "boo_this_man", - "bourbon", - "bowtie", - "bra", - "brain-problems", - "brannigan", - "breakfast", - "brian", - "brick", - "brimstone", - "bro", - "broken", - "bronze-trophy", - "broom", - "btc", - "bucket", - "bud_light", - "budweiser", - "bullet", - "bullseye", - "bullshit", - "burgerboy", - "burglar", - "butt", - "butter", - "caffeine", - "cage", - "calavera", - "camper", - "carbsquatch", - "carmen_sandiego", - "cave-troll", - "caveman", - "ceiling_anti-ninja", - "ceiling_ninja", - "chad", - "charpov", - "chase_the_dragon", - "che", - "cheers", - "cheese", - "chemex", - "chicago-hot-dog", - "chilidog", - "chocobo", - "chrome", - "chronofroyo", - "chub", - "cigar", - "clamps", - "clown", - "code", - "codiscope", - "coffee-sprite", - "coke", - "colt45", - "comma_splice", - "command", - "confederate", - "conquest", - "contra", - "cookies", - "corona", - "cortana", - "couch", - "count", - "courage", - "cowboy", - "crazy_lizard", - "crisco", - "crock_pot", - "croooow", - "cubimal_chick", - "cwilt", - "cylon", - "d20", - "dagaz", - "daggett", - "dancing_penguin", - "dangerous", - "dark_one", - "darkwing", - "death", - "debrief", - "deep", - "deodorant", - "devil", - "dickbutt", - "dickdance", - "dingle", - "dino", - "disappointed_ruml", - "do_not_go_in_there", - "doc", - "doge", - "doh", - "doit", - "donnie", - "doritos", - "double_asterisk", - "doubt", - "downvote", - "dr-nick", - "dr_claw", - "dr_spaceman", - "dr_stephen_hawkings", - "dragonball", - "dramatic-chipmunk", - "drooling", - "drumpf", - "duff", - "duffman", - "dusty_stick", - "dw", - "dwarf", - "eaburned", - "eaburns", - "eaburns-fail", - "edge", - "egg-0", - "eggman", - "ehwaz", - "einstein", - "eleven", - "empire", - "everything", - "f", - "fail", - "fake", - "fallout_boy", - "false", - "famine", - "famous-heath-ledger-as-the-joker-clapping-for-james-gordon-in-the-dark-knight", - "fart", - "fearless_leader", - "feelsgood", - "fifteen", - "fighter", - "finn", - "finnadie", - "firefox", - "fivefivefive", - "flag-ussr", - "flailing", - "flanders", - "flyngpngn", - "fminus", - "fool", - "forever", - "freud", - "fry", - "fu", - "fuckoff", - "fuckyeah", - "gandalf", - "garbage", - "gates", - "gay", - "gene", - "get_some", - "ghostbusters", - "giggity", - "gimp", - "gin", - "git", - "glitch_crab", - "gloriousleader", - "glucose", - "gnu", - "go", - "go-go-gadget", - "goatse", - "goberserk", - "god", - "godmode", - "gold-trophy", - "gollum", - "goof", - "google", - "gopher", - "gpu", - "grinch", - "growler", - "gryffindor", - "guiness", - "guinness", - "gumby", - "h", - "hackar", - "hagalaz", - "haha", - "hahaha", - "half_done", - "ham", - "hank", - "harambe", - "hardy", - "hatcher", - "hate", - "hawaiian_shirt", - "hawkings", - "headless_horseman", - "heady_topper", - "heavy_equals_sign", - "heavybreathing", - "heck_yea", - "heineken", - "helldivers", - "herdez", - "hes_on_fire", - "highdea", - "historic_artifact", - "holte", - "homer", - "hooker", - "hop", - "how_to_jump", - "hufflepuff", - "huge_metal_fan", - "hulk", - "hurtrealbad", - "hydrant", - "hypnotoad", - "i93", - "i_do_cocaine", - "ice_cube", - "inspector_gadget", - "internet_explorer", - "ipa", - "isaac", - "isaac_pride", - "it_lives", - "ius", - "jail", - "java", - "jedi", - "jeff", - "jenkins", - "jera", - "jesus", - "jinkies", - "jira", - "joint", - "juicy", - "keeper", - "keg", - "kermit", - "kevin_bacon", - "kfc", - "king_cobra", - "klingon", - "kneeling_man", - "kneeling_woman", - "knight", - "kodos", - "korf", - "krampus", - "krang", - "la_croix", - "lambdahead", - "lastdab", - "latte", - "laugh_devil", - "laugh_dr_evil", - "laugh_dumb", - "laugh_dwight", - "laugh_jack", - "laugh_milk", - "left_blinker", - "leo_", - "liar", - "lick", - "listentome", - "literally_hitler", - "literally_stk5", - "literally_trump", - "little_caesars", - "little_horn", - "lobster", - "lobster_roll", - "lobster_tail", - "logs", - "lol", - "lsd", - "luchador", - "luigi", - "magic", - "magicleap", - "maid_crossbones", - "malte", - "malört", - "margarita", - "mario", - "mario_banana", - "marvin", - "math_checks_out", - "maybe", - "mcduck", - "mcduck_diving", - "meat", - "meatwad", - "meeseeks", - "megaman", - "merchant", - "metal", - "metroid", - "mickeys", - "mikey", - "milf", - "milk", - "minion", - "mint_julep", - "missle", - "mj", - "mojito", - "monocle", - "monster", - "monstro", - "mooning", - "moonshot", - "moonwalk", - "moose", - "morty", - "moxie", - "mozilla", - "mr_yuk", - "msherms", - "msherms_bait", - "mudkip", - "muffin", - "mystery_machine", - "nachos", - "nailed_it", - "neckbeard", - "nelson", - "nice", - "nils", - "ninja", - "nintendo-64-controller", - "nipple", - "no", - "no_wheelchair", - "noose", - "nope", - "norbert", - "norris", - "not-a-pipe", - "not_a_doctor", - "nra", - "nuclear_explosion", - "nurse", - "nyancat", - "octocat", - "og_tap", - "oh_yea", - "ohyea", - "olde_english", - "olde_fortran", - "oliver", - "open_trump_hands", - "opera", - "ouji", - "ouroboros", - "pacman", - "papajohns", - "paranoid", - "paranoid_android", - "party", - "partyparrot", - "patrik", - "pay_respects", - "pbr", - "pcmasterrace", - "pea", - "pea-lang", - "pee", - "peggy", - "pennies", - "penny", - "pentagram", - "pepe", - "pestilence", - "phd", - "php", - "pi", - "picard", - "pickle", - "pickle_rick", - "picklerick", - "piggy", - "pigpen", - "pikachu", - "pinky", - "pipe", - "pirate", - "pirate_flag", - "pjw", - "plan9", - "plane", - "platinum-trophy", - "poe", - "poison", - "pokeball", - "poochie", - "pretzels", - "pride", - "probably_racist", - "professor_farnsworth", - "programming", - "psn", - "puke", - "pun", - "q", - "quagmire", - "r", - "rage1", - "rage2", - "rage3", - "rage4", - "ralph", - "raph", - "ravenclaw", - "razor", - "real_sandwich", - "real_scream", - "real_smoking", - "realistic_gun", - "reaper", - "rebellion", - "recursion", - "red_bull", - "red_mage", - "red_room", - "revelations", - "revolution", - "rich_bitch", - "rick", - "right_blinker", - "rimshot", - "rip", - "rms", - "ron_swanson", - "roomba", - "rube", - "ruml", - "rupee", - "rut-roh", - "s2000", - "s2k", - "saccharomyces", - "sad-laugh", - "safari", - "sailor", - "salt", - "samurai", - "sarcasm", - "sarcastic_clap", - "satan", - "satanic_bible", - "sausage", - "science", - "scoobysnack", - "scotch", - "scrooge", - "seabass", - "seabass-shamed", - "seabass-victory", - "seabass-woke", - "seabass_glass", - "seabass_test", - "seabass_travel", - "semicolon", - "serious", - "seymour", - "shame", - "shipit", - "shocked", - "shocker", - "shovel", - "silver-trophy", - "simple_smile", - "single", - "siren", - "siri", - "skiesel", - "skinner", - "skunked", - "slack", - "slack_call", - "slash", - "slime", - "slurmsmckenzie", - "slytherin", - "smiling_sausage", - "smith", - "smoker", - "smooth", - "snicklefritz", - "snoop-dogg", - "soap", - "sobchak", - "soda-can", - "soiled", - "sombrero", - "sonic", - "sort_of_check", - "sort_of_done", - "spam", - "sperm", - "spic", - "spice-weasel", - "splinter", - "spring", - "squirrel", - "sriracha", - "staple", - "star_trek", - "stardew_chicken_white", - "stat_and_wal", - "stein", - "stephen_hawkings", - "stk5", - "stk5-fail", - "stk5_face", - "stk5onagoodday", - "submarine", - "subway", - "subway_jared", - "sunglasses_cat", - "suspect", - "svohr", - "swipe", - "switch", - "swoon", - "tableflip", - "taco_bell", - "take_my_money", - "tap", - "taz", - "teapot", - "tear_jar", - "tecumseh", - "template", - "tfogal", - "thats_the_joke", - "the-duke", - "the_brain", - "the_day_is_mine", - "the_donut", - "the_dude", - "the_fool", - "the_hush", - "the_lost", - "the_rock", - "thief", - "thisisfine", - "thumbsup2", - "thumbsup_all", - "tina", - "tire", - "to-the-diner", - "toilet_that_opens_right_for_flyngpngn", - "tom_nook", - "tom_servo", - "top_", - "towel", - "triforce", - "trogdor", - "troll", - "trollface", - "true", - "tumbleweed", - "tux", - "twin_peaks", - "twitch", - "un", - "unauthorized", - "unh", - "unholy-antichrist", - "uofl", - "urinal", - "vacuum", - "vader", - "vault_boy", - "vcard", - "vim", - "voltron", - "vomit", - "waiting", - "wal_and_stat", - "waldo", - "wally", - "war", - "weed", - "wfh_beer", - "whiskey", - "white_mage", - "white_square", - "wholefoods", - "wiggum", - "winamp", - "windows", - "wirecutter", - "wiz", - "woody", - "wow", - "wow_owen", - "wrong", - "x2", - "xzibit", - "yak", - "yea_well_you_know_thats_just_like_your_opinion_man", - "yeah_man", - "yeast", - "yes", - "yeti", - "yguengling", - "yo-yo", - "you_know_not_the_elaborate_criteria_by_which_you_are_to_be_judged", - "yuengling", - "zelda_candle", - "zelda_fire", - "zoidberg", -} From 4d188bdf206241abbb32e878b2557a02c88bc909 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Mon, 21 Jan 2019 18:05:35 -0500 Subject: [PATCH 009/107] emojify: handle plurals again --- plugins/emojifyme/emojifyme.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/plugins/emojifyme/emojifyme.go b/plugins/emojifyme/emojifyme.go index 6805304..629174e 100644 --- a/plugins/emojifyme/emojifyme.go +++ b/plugins/emojifyme/emojifyme.go @@ -70,10 +70,18 @@ func (p *EmojifyMePlugin) Message(message msg.Message) bool { msg := strings.Replace(strings.ToLower(message.Body), "_", " ", -1) for k, v := range p.Emoji { k = strings.Replace(k, "_", " ", -1) - if strings.Contains(msg, " "+k+" ") || strings.HasPrefix(msg, k) || strings.HasSuffix(msg, k) { - emojys = append(emojys, v) - if !stringsContain(inertTokens, k) || len(k) <= 2 { - emojied++ + candidates := []string{ + k + "es", + k + "s", + } + for _, c := range candidates { + if strings.Contains(msg, " "+c+" ") || + strings.HasPrefix(msg, c) || + strings.HasSuffix(msg, c) { + emojys = append(emojys, v) + if !stringsContain(inertTokens, k) || len(k) <= 2 { + emojied++ + } } } } From c07e612941e34ac5c8e35ab849170ed4aaa953a2 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Mon, 21 Jan 2019 18:30:20 -0500 Subject: [PATCH 010/107] emojify: woops --- plugins/emojifyme/emojifyme.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/emojifyme/emojifyme.go b/plugins/emojifyme/emojifyme.go index 629174e..0960474 100644 --- a/plugins/emojifyme/emojifyme.go +++ b/plugins/emojifyme/emojifyme.go @@ -71,6 +71,7 @@ func (p *EmojifyMePlugin) Message(message msg.Message) bool { for k, v := range p.Emoji { k = strings.Replace(k, "_", " ", -1) candidates := []string{ + k, k + "es", k + "s", } @@ -79,8 +80,8 @@ func (p *EmojifyMePlugin) Message(message msg.Message) bool { strings.HasPrefix(msg, c) || strings.HasSuffix(msg, c) { emojys = append(emojys, v) - if !stringsContain(inertTokens, k) || len(k) <= 2 { - emojied++ + if !stringsContain(inertTokens, k) || len(v) < 2 { + emojied += 1 } } } From 0593b4f16431ed0da3c8474a0dca5d71a4f7ca4b Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Mon, 21 Jan 2019 19:16:57 -0500 Subject: [PATCH 011/107] config: set some defaults --- bot/bot.go | 20 +++++--------- config/config.go | 22 ++++++++-------- config/config_test.go | 4 +-- irc/irc.go | 14 +++++----- main.go | 11 +++----- plugins/admin/admin.go | 2 +- plugins/admin/admin_test.go | 4 +-- plugins/beers/beers.go | 12 ++++----- plugins/downtime/downtime.go | 4 +-- plugins/emojifyme/emojifyme.go | 4 +-- plugins/fact/factoid.go | 10 +++---- plugins/first/first.go | 6 ++--- plugins/inventory/inventory.go | 9 ++----- plugins/leftpad/leftpad.go | 8 ++---- plugins/leftpad/leftpad_test.go | 2 +- plugins/reaction/reaction.go | 16 ++++++------ plugins/reminder/reminder.go | 6 +---- plugins/rpgORdie/rpgORdie.go | 2 +- plugins/sisyphus/sisyphus.go | 22 ++++------------ plugins/twitch/twitch.go | 18 ++++++++----- plugins/twitch/twitch_test.go | 2 ++ plugins/your/your.go | 15 ++++------- slack/slack.go | 46 +++++++++++++++++---------------- 23 files changed, 112 insertions(+), 147 deletions(-) diff --git a/bot/bot.go b/bot/bot.go index 6129e82..6a69bb2 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -56,7 +56,7 @@ func New(config *config.Config, connector Connector) Bot { users := []user.User{ user.User{ - Name: config.Get("Nick"), + Name: config.Get("Nick", "bot"), }, } @@ -76,11 +76,7 @@ func New(config *config.Config, connector Connector) Bot { bot.migrateDB() http.HandleFunc("/", bot.serveRoot) - addr := config.Get("HttpAddr") - if addr == "" { - addr = "127.0.0.1:1337" - config.Set("HttpAddr", addr) - } + addr := config.Get("HttpAddr", "127.0.0.1:1337") go http.ListenAndServe(addr, nil) connector.RegisterMessageReceived(bot.MsgReceived) @@ -172,12 +168,8 @@ func (b *bot) serveRoot(w http.ResponseWriter, r *http.Request) { // Checks if message is a command and returns its curtailed version func IsCmd(c *config.Config, message string) (bool, string) { - cmdcs := c.GetArray("CommandChar") - if len(cmdcs) == 0 { - cmdcs = []string{"!"} - c.SetArray("CommandChar", cmdcs) - } - botnick := strings.ToLower(c.Get("Nick")) + cmdcs := c.GetArray("CommandChar", []string{"!"}) + botnick := strings.ToLower(c.Get("Nick", "bot")) if botnick == "" { log.Fatalf(`You must run catbase -set nick -val `) } @@ -212,7 +204,7 @@ func IsCmd(c *config.Config, message string) (bool, string) { } func (b *bot) CheckAdmin(nick string) bool { - for _, u := range b.Config().GetArray("Admins") { + for _, u := range b.Config().GetArray("Admins", []string{}) { if nick == u { return true } @@ -240,7 +232,7 @@ func (b *bot) NewUser(nick string) *user.User { } func (b *bot) checkAdmin(nick string) bool { - for _, u := range b.Config().GetArray("Admins") { + for _, u := range b.Config().GetArray("Admins", []string{}) { if nick == u { return true } diff --git a/config/config.go b/config/config.go index 6cb56f2..827b310 100644 --- a/config/config.go +++ b/config/config.go @@ -28,8 +28,8 @@ type Config struct { // It will check the DB for the key if an env DNE // Finally, it will return a zero value if the key does not exist // It will attempt to convert the value to a float64 if it exists -func (c *Config) GetFloat64(key string) float64 { - f, err := strconv.ParseFloat(c.GetString(key), 64) +func (c *Config) GetFloat64(key string, fallback float64) float64 { + f, err := strconv.ParseFloat(c.GetString(key, fmt.Sprintf("%f", fallback)), 64) if err != nil { return 0.0 } @@ -41,8 +41,8 @@ func (c *Config) GetFloat64(key string) float64 { // It will check the DB for the key if an env DNE // Finally, it will return a zero value if the key does not exist // It will attempt to convert the value to an int if it exists -func (c *Config) GetInt(key string) int { - i, err := strconv.Atoi(c.GetString(key)) +func (c *Config) GetInt(key string, fallback int) int { + i, err := strconv.Atoi(c.GetString(key, strconv.Itoa(fallback))) if err != nil { return 0 } @@ -50,8 +50,8 @@ func (c *Config) GetInt(key string) int { } // Get is a shortcut for GetString -func (c *Config) Get(key string) string { - return c.GetString(key) +func (c *Config) Get(key, fallback string) string { + return c.GetString(key, fallback) } func envkey(key string) string { @@ -65,7 +65,7 @@ func envkey(key string) string { // It will check the DB for the key if an env DNE // Finally, it will return a zero value if the key does not exist // It will convert the value to a string if it exists -func (c *Config) GetString(key string) string { +func (c *Config) GetString(key, fallback string) string { key = strings.ToLower(key) if v, found := os.LookupEnv(envkey(key)); found { return v @@ -75,7 +75,7 @@ func (c *Config) GetString(key string) string { err := c.DB.Get(&configValue, q, key) if err != nil { log.Printf("WARN: Key %s is empty", key) - return "" + return fallback } return configValue } @@ -86,10 +86,10 @@ func (c *Config) GetString(key string) string { // It will check the DB for the key if an env DNE // Finally, it will return a zero value if the key does not exist // This will do no conversion. -func (c *Config) GetArray(key string) []string { - val := c.GetString(key) +func (c *Config) GetArray(key string, fallback []string) []string { + val := c.GetString(key, "") if val == "" { - return []string{} + return fallback } return strings.Split(val, ";;") } diff --git a/config/config_test.go b/config/config_test.go index 7c6ebbb..3bdfb59 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -10,7 +10,7 @@ func TestSetGet(t *testing.T) { cfg := ReadConfig(":memory:") expected := "value" cfg.Set("test", expected) - actual := cfg.Get("test") + actual := cfg.Get("test", "NOPE") assert.Equal(t, expected, actual, "Config did not store values") } @@ -18,6 +18,6 @@ func TestSetGetArray(t *testing.T) { cfg := ReadConfig(":memory:") expected := []string{"a", "b", "c"} cfg.SetArray("test", expected) - actual := cfg.GetArray("test") + actual := cfg.GetArray("test", []string{"NOPE"}) assert.Equal(t, expected, actual, "Config did not store values") } diff --git a/irc/irc.go b/irc/irc.go index 1ad9e58..7a89110 100644 --- a/irc/irc.go +++ b/irc/irc.go @@ -87,7 +87,7 @@ func (i *Irc) SendMessage(channel, message string) string { } if throttle == nil { - ratePerSec := i.config.GetInt("RatePerSec") + ratePerSec := i.config.GetInt("RatePerSec", 5) throttle = time.Tick(time.Second / time.Duration(ratePerSec)) } @@ -136,17 +136,17 @@ func (i *Irc) Serve() error { var err error i.Client, err = irc.DialSSL( - i.config.Get("Irc.Server"), - i.config.Get("Nick"), - i.config.Get("FullName"), - i.config.Get("Irc.Pass"), + i.config.Get("Irc.Server", "localhost"), + i.config.Get("Nick", "bot"), + i.config.Get("FullName", "bot"), + i.config.Get("Irc.Pass", ""), true, ) if err != nil { return fmt.Errorf("%s", err) } - for _, c := range i.config.GetArray("channels") { + for _, c := range i.config.GetArray("channels", []string{}) { i.JoinChannel(c) } @@ -270,7 +270,7 @@ func (i *Irc) buildMessage(inMsg irc.Msg) msg.Message { } channel := inMsg.Args[0] - if channel == i.config.Get("Nick") { + if channel == i.config.Get("Nick", "bot") { channel = inMsg.Args[0] } diff --git a/main.go b/main.go index 3b77307..c3d7ebe 100644 --- a/main.go +++ b/main.go @@ -58,7 +58,7 @@ func main() { log.Printf("Set config %s: %s", *key, *val) return } - if (*initDB && len(flag.Args()) != 2) || (!*initDB && c.GetInt("init") != 1) { + if (*initDB && len(flag.Args()) != 2) || (!*initDB && c.GetInt("init", 0) != 1) { log.Fatal(`You must run "catbase -init "`) } else if *initDB { c.SetDefaults(flag.Arg(0), flag.Arg(1)) @@ -67,18 +67,13 @@ func main() { var client bot.Connector - t := c.Get("type") - if t == "" { - c.Set("type", "slack") - t = "slack" - } - switch c.Get("type") { + switch c.Get("type", "slack") { case "irc": client = irc.New(c) case "slack": client = slack.New(c) default: - log.Fatalf("Unknown connection type: %s", c.Get("type")) + log.Fatalf("Unknown connection type: %s", c.Get("type", "UNSET")) } b := bot.New(c, client) diff --git a/plugins/admin/admin.go b/plugins/admin/admin.go index 6543930..50049b3 100644 --- a/plugins/admin/admin.go +++ b/plugins/admin/admin.go @@ -65,7 +65,7 @@ func (p *AdminPlugin) Message(message msg.Message) bool { p.Bot.SendMessage(message.Channel, "You cannot access that key") return true } else if parts[0] == "get" && len(parts) == 2 { - v := p.cfg.Get(parts[1]) + v := p.cfg.Get(parts[1], "") p.Bot.SendMessage(message.Channel, fmt.Sprintf("%s: %s", parts[1], v)) return true } diff --git a/plugins/admin/admin_test.go b/plugins/admin/admin_test.go index 8df6b0d..f4ce489 100644 --- a/plugins/admin/admin_test.go +++ b/plugins/admin/admin_test.go @@ -39,7 +39,7 @@ func TestSet(t *testing.T) { a, mb := setup(t) expected := "test value" a.Message(makeMessage("!set test.key " + expected)) - actual := mb.Config().Get("test.key") + actual := mb.Config().Get("test.key", "ERR") assert.Equal(t, expected, actual) } @@ -54,7 +54,7 @@ func TestGetValue(t *testing.T) { func TestGetEmpty(t *testing.T) { a, mb := setup(t) - expected := "test.key: " + expected := "test.key: " a.Message(makeMessage("!get test.key")) assert.Len(t, mb.Messages, 1) assert.Equal(t, expected, mb.Messages[0]) diff --git a/plugins/beers/beers.go b/plugins/beers/beers.go index 4d4477c..d4da158 100644 --- a/plugins/beers/beers.go +++ b/plugins/beers/beers.go @@ -52,7 +52,7 @@ func New(bot bot.Bot) *BeersPlugin { Bot: bot, db: bot.DB(), } - for _, channel := range bot.Config().GetArray("Untappd.Channels") { + for _, channel := range bot.Config().GetArray("Untappd.Channels", []string{}) { go p.untappdLoop(channel) } return &p @@ -306,8 +306,8 @@ type Beers struct { } func (p *BeersPlugin) pullUntappd() ([]checkin, error) { - token := p.Bot.Config().Get("Untappd.Token") - if token == "" { + token := p.Bot.Config().Get("Untappd.Token", "NONE") + if token == "NONE" { return []checkin{}, fmt.Errorf("No untappd token") } @@ -341,8 +341,8 @@ func (p *BeersPlugin) pullUntappd() ([]checkin, error) { } func (p *BeersPlugin) checkUntappd(channel string) { - token := p.Bot.Config().Get("Untappd.Token") - if token == "" { + token := p.Bot.Config().Get("Untappd.Token", "NONE") + if token == "NONE" { log.Println(`Set config value "untappd.token" if you wish to enable untappd`) return } @@ -426,7 +426,7 @@ func (p *BeersPlugin) checkUntappd(channel string) { } func (p *BeersPlugin) untappdLoop(channel string) { - frequency := p.Bot.Config().GetInt("Untappd.Freq") + frequency := p.Bot.Config().GetInt("Untappd.Freq", 120) if frequency == 0 { return } diff --git a/plugins/downtime/downtime.go b/plugins/downtime/downtime.go index fc65f78..e50df61 100644 --- a/plugins/downtime/downtime.go +++ b/plugins/downtime/downtime.go @@ -159,7 +159,7 @@ func (p *DowntimePlugin) Message(message msg.Message) bool { for _, e := range entries { // filter out ZNC entries and ourself - if strings.HasPrefix(e.nick, "*") || strings.ToLower(p.Bot.Config().Get("Nick")) == e.nick { + if strings.HasPrefix(e.nick, "*") || strings.ToLower(p.Bot.Config().Get("Nick", "bot")) == e.nick { p.remove(e.nick) } else { tops = fmt.Sprintf("%s%s: %s ", tops, e.nick, time.Now().Sub(e.lastSeen)) @@ -203,7 +203,7 @@ func (p *DowntimePlugin) Help(channel string, parts []string) { // Empty event handler because this plugin does not do anything on event recv func (p *DowntimePlugin) Event(kind string, message msg.Message) bool { log.Println(kind, "\t", message) - if kind != "PART" && message.User.Name != p.Bot.Config().Get("Nick") { + if kind != "PART" && message.User.Name != p.Bot.Config().Get("Nick", "bot") { // user joined, let's nail them for it if kind == "NICK" { p.record(strings.ToLower(message.Channel)) diff --git a/plugins/emojifyme/emojifyme.go b/plugins/emojifyme/emojifyme.go index 0960474..d4cc975 100644 --- a/plugins/emojifyme/emojifyme.go +++ b/plugins/emojifyme/emojifyme.go @@ -64,7 +64,7 @@ func (p *EmojifyMePlugin) Message(message msg.Message) bool { } } - inertTokens := p.Bot.Config().GetArray("Emojify.Scoreless") + inertTokens := p.Bot.Config().GetArray("Emojify.Scoreless", []string{}) emojied := 0.0 emojys := []string{} msg := strings.Replace(strings.ToLower(message.Body), "_", " ", -1) @@ -87,7 +87,7 @@ func (p *EmojifyMePlugin) Message(message msg.Message) bool { } } - if emojied > 0 && rand.Float64() <= p.Bot.Config().GetFloat64("Emojify.Chance")*emojied { + if emojied > 0 && rand.Float64() <= p.Bot.Config().GetFloat64("Emojify.Chance", 0.02)*emojied { for _, e := range emojys { p.Bot.React(message.Channel, e, message) } diff --git a/plugins/fact/factoid.go b/plugins/fact/factoid.go index bf989b8..c53c934 100644 --- a/plugins/fact/factoid.go +++ b/plugins/fact/factoid.go @@ -297,13 +297,13 @@ func New(botInst bot.Bot) *Factoid { log.Fatal(err) } - for _, channel := range botInst.Config().GetArray("channels") { + for _, channel := range botInst.Config().GetArray("channels", []string{}) { go p.factTimer(channel) go func(ch string) { // Some random time to start up time.Sleep(time.Duration(15) * time.Second) - if ok, fact := p.findTrigger(p.Bot.Config().Get("Factoid.StartupFact")); ok { + if ok, fact := p.findTrigger(p.Bot.Config().Get("Factoid.StartupFact", "speed test")); ok { p.sayFact(msg.Message{ Channel: ch, Body: "speed test", // BUG: This is defined in the config too @@ -430,7 +430,7 @@ func (p *Factoid) sayFact(message msg.Message, fact factoid) { // trigger checks the message for its fitness to be a factoid and then hauls // the message off to sayFact for processing if it is in fact a trigger func (p *Factoid) trigger(message msg.Message) bool { - minLen := p.Bot.Config().GetInt("Factoid.MinLen") + minLen := p.Bot.Config().GetInt("Factoid.MinLen", 4) if len(message.Body) > minLen || message.Command || message.Body == "..." { if ok, fact := p.findTrigger(message.Body); ok { p.sayFact(message, *fact) @@ -691,7 +691,7 @@ func (p *Factoid) randomFact() *factoid { // factTimer spits out a fact at a given interval and with given probability func (p *Factoid) factTimer(channel string) { - quoteTime := p.Bot.Config().GetInt("Factoid.QuoteTime") + quoteTime := p.Bot.Config().GetInt("Factoid.QuoteTime", 30) if quoteTime == 0 { quoteTime = 30 p.Bot.Config().Set("Factoid.QuoteTime", "30") @@ -710,7 +710,7 @@ func (p *Factoid) factTimer(channel string) { tdelta := time.Since(lastmsg.Time) earlier := time.Since(myLastMsg) > tdelta chance := rand.Float64() - quoteChance := p.Bot.Config().GetFloat64("Factoid.QuoteChance") + quoteChance := p.Bot.Config().GetFloat64("Factoid.QuoteChance", 0.99) if quoteChance == 0.0 { quoteChance = 0.99 p.Bot.Config().Set("Factoid.QuoteChance", "0.99") diff --git a/plugins/first/first.go b/plugins/first/first.go index 88c874a..a70f6e7 100644 --- a/plugins/first/first.go +++ b/plugins/first/first.go @@ -150,7 +150,7 @@ func (p *FirstPlugin) Message(message msg.Message) bool { } func (p *FirstPlugin) allowed(message msg.Message) bool { - for _, msg := range p.Bot.Config().GetArray("Bad.Msgs") { + for _, msg := range p.Bot.Config().GetArray("Bad.Msgs", []string{}) { match, err := regexp.MatchString(msg, strings.ToLower(message.Body)) if err != nil { log.Println("Bad regexp: ", err) @@ -160,13 +160,13 @@ func (p *FirstPlugin) allowed(message msg.Message) bool { return false } } - for _, host := range p.Bot.Config().GetArray("Bad.Hosts") { + for _, host := range p.Bot.Config().GetArray("Bad.Hosts", []string{}) { if host == message.Host { log.Println("Disallowing first: ", message.User.Name, ":", message.Body) return false } } - for _, nick := range p.Bot.Config().GetArray("Bad.Nicks") { + for _, nick := range p.Bot.Config().GetArray("Bad.Nicks", []string{}) { if nick == message.User.Name { log.Println("Disallowing first: ", message.User.Name, ":", message.Body) return false diff --git a/plugins/inventory/inventory.go b/plugins/inventory/inventory.go index 5166f04..708639f 100644 --- a/plugins/inventory/inventory.go +++ b/plugins/inventory/inventory.go @@ -8,7 +8,6 @@ import ( "fmt" "log" "regexp" - "strconv" "strings" "github.com/jmoiron/sqlx" @@ -27,7 +26,7 @@ type InventoryPlugin struct { // New creates a new InventoryPlugin with the Plugin interface func New(bot bot.Bot) *InventoryPlugin { config := bot.Config() - nick := config.Get("nick") + nick := config.Get("nick", "bot") r1, err := regexp.Compile("take this (.+)") checkerr(err) r2, err := regexp.Compile("have a (.+)") @@ -202,11 +201,7 @@ func (p *InventoryPlugin) addItem(m msg.Message, i string) bool { return true } var removed string - max := p.config.GetInt("inventory.max") - if max == 0 { - max = 10 - p.config.Set("inventory.max", strconv.Itoa(max)) - } + max := p.config.GetInt("inventory.max", 10) if p.count() > max { removed = p.removeRandom() } diff --git a/plugins/leftpad/leftpad.go b/plugins/leftpad/leftpad.go index d068ae1..cf5327e 100644 --- a/plugins/leftpad/leftpad.go +++ b/plugins/leftpad/leftpad.go @@ -45,13 +45,9 @@ func (p *LeftpadPlugin) Message(message msg.Message) bool { p.bot.SendMessage(message.Channel, "Invalid padding number") return true } - maxLen, who := p.config.GetInt("LeftPad.MaxLen"), p.config.Get("LeftPad.Who") - if who == "" { - who = "Putin" - p.config.Set("LeftPad.MaxLen", who) - } + maxLen, who := p.config.GetInt("LeftPad.MaxLen", 50), p.config.Get("LeftPad.Who", "Putin") if length > maxLen && maxLen > 0 { - msg := fmt.Sprintf("%s would kill me if I did that.", p.config.Get("LeftPad.Who")) + msg := fmt.Sprintf("%s would kill me if I did that.", who) p.bot.SendMessage(message.Channel, msg) return true } diff --git a/plugins/leftpad/leftpad_test.go b/plugins/leftpad/leftpad_test.go index cb57c25..21e4af4 100644 --- a/plugins/leftpad/leftpad_test.go +++ b/plugins/leftpad/leftpad_test.go @@ -66,7 +66,7 @@ func TestNoMaxLen(t *testing.T) { func Test50Padding(t *testing.T) { p, mb := makePlugin(t) p.config.Set("LeftPad.MaxLen", "50") - assert.Equal(t, 50, p.config.GetInt("LeftPad.MaxLen")) + assert.Equal(t, 50, p.config.GetInt("LeftPad.MaxLen", 100)) p.Message(makeMessage("!leftpad dicks 100 dicks")) assert.Len(t, mb.Messages, 1) assert.Contains(t, mb.Messages[0], "kill me") diff --git a/plugins/reaction/reaction.go b/plugins/reaction/reaction.go index e36314c..6f98d55 100644 --- a/plugins/reaction/reaction.go +++ b/plugins/reaction/reaction.go @@ -24,23 +24,23 @@ func New(bot bot.Bot) *ReactionPlugin { func (p *ReactionPlugin) Message(message msg.Message) bool { harrass := false - for _, nick := range p.Config.GetArray("Reaction.HarrassList") { + for _, nick := range p.Config.GetArray("Reaction.HarrassList", []string{}) { if message.User.Name == nick { harrass = true break } } - chance := p.Config.GetFloat64("Reaction.GeneralChance") + chance := p.Config.GetFloat64("Reaction.GeneralChance", 0.01) negativeWeight := 1 if harrass { - chance = p.Config.GetFloat64("Reaction.HarrassChance") - negativeWeight = p.Config.GetInt("Reaction.NegativeHarrassmentMultiplier") + chance = p.Config.GetFloat64("Reaction.HarrassChance", 0.05) + negativeWeight = p.Config.GetInt("Reaction.NegativeHarrassmentMultiplier", 2) } if rand.Float64() < chance { - numPositiveReactions := len(p.Config.GetArray("Reaction.PositiveReactions")) - numNegativeReactions := len(p.Config.GetArray("Reaction.NegativeReactions")) + numPositiveReactions := len(p.Config.GetArray("Reaction.PositiveReactions", []string{})) + numNegativeReactions := len(p.Config.GetArray("Reaction.NegativeReactions", []string{})) maxIndex := numPositiveReactions + numNegativeReactions*negativeWeight @@ -49,11 +49,11 @@ func (p *ReactionPlugin) Message(message msg.Message) bool { reaction := "" if index < numPositiveReactions { - reaction = p.Config.GetArray("Reaction.PositiveReactions")[index] + reaction = p.Config.GetArray("Reaction.PositiveReactions", []string{})[index] } else { index -= numPositiveReactions index %= numNegativeReactions - reaction = p.Config.GetArray("Reaction.NegativeReactions")[index] + reaction = p.Config.GetArray("Reaction.NegativeReactions", []string{})[index] } p.Bot.React(message.Channel, reaction, message) diff --git a/plugins/reminder/reminder.go b/plugins/reminder/reminder.go index 49ca760..f1ec707 100644 --- a/plugins/reminder/reminder.go +++ b/plugins/reminder/reminder.go @@ -122,11 +122,7 @@ func (p *ReminderPlugin) Message(message msg.Message) bool { what := strings.Join(parts[6:], " ") for i := 0; when.Before(endTime); i++ { - max := p.config.GetInt("Reminder.MaxBatchAdd") - if max == 0 { - max = 10 - p.config.Set("reminder.maxbatchadd", strconv.Itoa(max)) - } + max := p.config.GetInt("Reminder.MaxBatchAdd", 10) if i >= max { p.Bot.SendMessage(channel, "Easy cowboy, that's a lot of reminders. I'll add some of them.") doConfirm = false diff --git a/plugins/rpgORdie/rpgORdie.go b/plugins/rpgORdie/rpgORdie.go index 7ea15d6..48394d4 100644 --- a/plugins/rpgORdie/rpgORdie.go +++ b/plugins/rpgORdie/rpgORdie.go @@ -136,7 +136,7 @@ func (p *RPGPlugin) RegisterWeb() *string { } func (p *RPGPlugin) ReplyMessage(message msg.Message, identifier string) bool { - if strings.ToLower(message.User.Name) != strings.ToLower(p.Bot.Config().Get("Nick")) { + if strings.ToLower(message.User.Name) != strings.ToLower(p.Bot.Config().Get("Nick", "bot")) { if b, ok := p.listenFor[identifier]; ok { var res int diff --git a/plugins/sisyphus/sisyphus.go b/plugins/sisyphus/sisyphus.go index c73cbd9..91a9fbc 100644 --- a/plugins/sisyphus/sisyphus.go +++ b/plugins/sisyphus/sisyphus.go @@ -59,14 +59,8 @@ func (g *game) scheduleDecrement() { if g.timers[0] != nil { g.timers[0].Stop() } - minDec := g.bot.Config().GetInt("Sisyphus.MinDecrement") - maxDec := g.bot.Config().GetInt("Sisyphus.MaxDecrement") - if maxDec == minDec && maxDec == 0 { - maxDec = 30 - minDec = 10 - g.bot.Config().Set("Sisyphus.MinDecrement", strconv.Itoa(minDec)) - g.bot.Config().Set("Sisyphus.MaxDecrement", strconv.Itoa(maxDec)) - } + minDec := g.bot.Config().GetInt("Sisyphus.MinDecrement", 10) + maxDec := g.bot.Config().GetInt("Sisyphus.MaxDecrement", 30) g.nextDec = time.Now().Add(time.Duration((minDec + rand.Intn(maxDec))) * time.Minute) go func() { t := time.NewTimer(g.nextDec.Sub(time.Now())) @@ -82,14 +76,8 @@ func (g *game) schedulePush() { if g.timers[1] != nil { g.timers[1].Stop() } - minPush := g.bot.Config().GetInt("Sisyphus.MinPush") - maxPush := g.bot.Config().GetInt("Sisyphus.MaxPush") - if minPush == maxPush && maxPush == 0 { - minPush = 1 - maxPush = 10 - g.bot.Config().Set("Sisyphus.MinPush", strconv.Itoa(minPush)) - g.bot.Config().Set("Sisyphus.MaxPush", strconv.Itoa(maxPush)) - } + minPush := g.bot.Config().GetInt("Sisyphus.MinPush", 1) + maxPush := g.bot.Config().GetInt("Sisyphus.MaxPush", 10) g.nextPush = time.Now().Add(time.Duration(rand.Intn(maxPush)+minPush) * time.Minute) go func() { t := time.NewTimer(g.nextPush.Sub(time.Now())) @@ -207,7 +195,7 @@ func (p *SisyphusPlugin) RegisterWeb() *string { } func (p *SisyphusPlugin) ReplyMessage(message msg.Message, identifier string) bool { - if strings.ToLower(message.User.Name) != strings.ToLower(p.Bot.Config().Get("Nick")) { + if strings.ToLower(message.User.Name) != strings.ToLower(p.Bot.Config().Get("Nick", "bot")) { if g, ok := p.listenFor[identifier]; ok { log.Printf("got message on %s: %+v", identifier, message) diff --git a/plugins/twitch/twitch.go b/plugins/twitch/twitch.go index bbe4003..6e22f14 100644 --- a/plugins/twitch/twitch.go +++ b/plugins/twitch/twitch.go @@ -58,8 +58,8 @@ func New(bot bot.Bot) *TwitchPlugin { twitchList: map[string]*Twitcher{}, } - for _, ch := range p.config.GetArray("Twitch.Channels") { - for _, twitcherName := range p.config.GetArray("Twitch." + ch + ".Users") { + for _, ch := range p.config.GetArray("Twitch.Channels", []string{}) { + for _, twitcherName := range p.config.GetArray("Twitch."+ch+".Users", []string{}) { if _, ok := p.twitchList[twitcherName]; !ok { p.twitchList[twitcherName] = &Twitcher{ name: twitcherName, @@ -117,7 +117,7 @@ func (p *TwitchPlugin) serveStreaming(w http.ResponseWriter, r *http.Request) { func (p *TwitchPlugin) Message(message msg.Message) bool { if strings.ToLower(message.Body) == "twitch status" { channel := message.Channel - if users := p.config.GetArray("Twitch." + channel + ".Users"); len(users) > 0 { + if users := p.config.GetArray("Twitch."+channel+".Users", []string{}); len(users) > 0 { for _, twitcherName := range users { if _, ok := p.twitchList[twitcherName]; ok { p.checkTwitch(channel, p.twitchList[twitcherName], true) @@ -144,14 +144,14 @@ func (p *TwitchPlugin) Help(channel string, parts []string) { } func (p *TwitchPlugin) twitchLoop(channel string) { - frequency := p.config.GetInt("Twitch.Freq") + frequency := p.config.GetInt("Twitch.Freq", 60) log.Println("Checking every ", frequency, " seconds") for { time.Sleep(time.Duration(frequency) * time.Second) - for _, twitcherName := range p.config.GetArray("Twitch." + channel + ".Users") { + for _, twitcherName := range p.config.GetArray("Twitch."+channel+".Users", []string{}) { p.checkTwitch(channel, p.twitchList[twitcherName], false) } } @@ -197,8 +197,12 @@ func (p *TwitchPlugin) checkTwitch(channel string, twitcher *Twitcher, alwaysPri baseURL.RawQuery = query.Encode() - cid := p.config.Get("Twitch.ClientID") - auth := p.config.Get("Twitch.Authorization") + cid := p.config.Get("Twitch.ClientID", "") + auth := p.config.Get("Twitch.Authorization", "") + if cid == auth && cid == "" { + log.Println("Twitch plugin not enabled.") + return + } body, ok := getRequest(baseURL.String(), cid, auth) if !ok { diff --git a/plugins/twitch/twitch_test.go b/plugins/twitch/twitch_test.go index bfdbf7b..728fe00 100644 --- a/plugins/twitch/twitch_test.go +++ b/plugins/twitch/twitch_test.go @@ -28,6 +28,8 @@ func makeMessage(payload string) msg.Message { func makeTwitchPlugin(t *testing.T) (*TwitchPlugin, *bot.MockBot) { mb := bot.NewMockBot() c := New(mb) + mb.Config().Set("twitch.clientid", "fake") + mb.Config().Set("twitch.authorization", "fake") c.config.SetArray("Twitch.Channels", []string{"test"}) c.config.SetArray("Twitch.test.Users", []string{"drseabass"}) assert.NotNil(t, c) diff --git a/plugins/your/your.go b/plugins/your/your.go index 9f6ad47..c7c8b45 100644 --- a/plugins/your/your.go +++ b/plugins/your/your.go @@ -4,7 +4,6 @@ package your import ( "math/rand" - "strconv" "strings" "github.com/velour/catbase/bot" @@ -29,19 +28,15 @@ func New(bot bot.Bot) *YourPlugin { // This function returns true if the plugin responds in a meaningful way to the users message. // Otherwise, the function returns false and the bot continues execution of other plugins. func (p *YourPlugin) Message(message msg.Message) bool { - maxLen := p.config.GetInt("your.maxlength") - if maxLen == 0 { - maxLen = 140 - p.config.Set("your.maxlength", strconv.Itoa(maxLen)) - } + maxLen := p.config.GetInt("your.maxlength", 140) if len(message.Body) > maxLen { return false } msg := message.Body - for _, replacement := range p.config.GetArray("Your.Replacements") { - freq := p.config.GetFloat64("your.replacements." + replacement + ".freq") - this := p.config.Get("your.replacements." + replacement + ".this") - that := p.config.Get("your.replacements." + replacement + ".that") + for _, replacement := range p.config.GetArray("Your.Replacements", []string{}) { + freq := p.config.GetFloat64("your.replacements."+replacement+".freq", 0.0) + this := p.config.Get("your.replacements."+replacement+".this", "") + that := p.config.Get("your.replacements."+replacement+".that", "") if rand.Float64() < freq { r := strings.NewReplacer(this, that) msg = r.Replace(msg) diff --git a/slack/slack.go b/slack/slack.go index c7b3981..3c1049b 100644 --- a/slack/slack.go +++ b/slack/slack.go @@ -31,9 +31,10 @@ import ( type Slack struct { config *config.Config - url string - id string - ws *websocket.Conn + url string + id string + token string + ws *websocket.Conn lastRecieved time.Time @@ -163,8 +164,13 @@ type rtmStart struct { } func New(c *config.Config) *Slack { + token := c.Get("slack.token", "NONE") + if token == "NONE" { + log.Fatalf("No slack token found. Set SLACKTOKEN env.") + } return &Slack{ config: c, + token: c.Get("slack.token", ""), lastRecieved: time.Now(), users: make(map[string]string), emoji: make(map[string]string), @@ -210,15 +216,11 @@ func (s *Slack) SendMessageType(channel, message string, meMessage bool) (string postUrl = "https://slack.com/api/chat.meMessage" } - nick := s.config.Get("Nick") - icon := s.config.Get("IconURL") - if icon == "" { - icon = "https://placekitten.com/400/400" - log.Println("Set config item IconURL to customize appearance!") - } + nick := s.config.Get("Nick", "bot") + icon := s.config.Get("IconURL", "https://placekitten.com/128/128") resp, err := http.PostForm(postUrl, - url.Values{"token": {s.config.Get("Slack.Token")}, + url.Values{"token": {s.token}, "username": {nick}, "icon_url": {icon}, "channel": {channel}, @@ -273,11 +275,11 @@ func (s *Slack) SendAction(channel, message string) string { } func (s *Slack) ReplyToMessageIdentifier(channel, message, identifier string) (string, bool) { - nick := s.config.Get("Nick") - icon := s.config.Get("IconURL") + nick := s.config.Get("Nick", "bot") + icon := s.config.Get("IconURL", "https://placekitten.com/128/128") resp, err := http.PostForm("https://slack.com/api/chat.postMessage", - url.Values{"token": {s.config.Get("Slack.Token")}, + url.Values{"token": {s.token}, "username": {nick}, "icon_url": {icon}, "channel": {channel}, @@ -325,7 +327,7 @@ func (s *Slack) ReplyToMessage(channel, message string, replyTo msg.Message) (st func (s *Slack) React(channel, reaction string, message msg.Message) bool { log.Printf("Reacting in %s: %s", channel, reaction) resp, err := http.PostForm("https://slack.com/api/reactions.add", - url.Values{"token": {s.config.Get("Slack.Token")}, + url.Values{"token": {s.token}, "name": {reaction}, "channel": {channel}, "timestamp": {message.AdditionalData["RAW_SLACK_TIMESTAMP"]}}) @@ -339,7 +341,7 @@ func (s *Slack) React(channel, reaction string, message msg.Message) bool { func (s *Slack) Edit(channel, newMessage, identifier string) bool { log.Printf("Editing in (%s) %s: %s", identifier, channel, newMessage) resp, err := http.PostForm("https://slack.com/api/chat.update", - url.Values{"token": {s.config.Get("Slack.Token")}, + url.Values{"token": {s.token}, "channel": {channel}, "text": {newMessage}, "ts": {identifier}}) @@ -356,7 +358,7 @@ func (s *Slack) GetEmojiList() map[string]string { func (s *Slack) populateEmojiList() { resp, err := http.PostForm("https://slack.com/api/emoji.list", - url.Values{"token": {s.config.Get("Slack.Token")}}) + url.Values{"token": {s.token}}) if err != nil { log.Printf("Error retrieving emoji list from Slack: %s", err) return @@ -549,7 +551,7 @@ func (s *Slack) markAllChannelsRead() { func (s *Slack) getAllChannels() []slackChannelListItem { u := s.url + "channels.list" resp, err := http.PostForm(u, - url.Values{"token": {s.config.Get("Slack.Token")}}) + url.Values{"token": {s.token}}) if err != nil { log.Printf("Error posting user info request: %s", err) @@ -574,7 +576,7 @@ func (s *Slack) getAllChannels() []slackChannelListItem { func (s *Slack) markChannelAsRead(slackChanId string) error { u := s.url + "channels.info" resp, err := http.PostForm(u, - url.Values{"token": {s.config.Get("Slack.Token")}, "channel": {slackChanId}}) + url.Values{"token": {s.token}, "channel": {slackChanId}}) if err != nil { log.Printf("Error posting user info request: %s", err) @@ -596,7 +598,7 @@ func (s *Slack) markChannelAsRead(slackChanId string) error { u = s.url + "channels.mark" resp, err = http.PostForm(u, - url.Values{"token": {s.config.Get("Slack.Token")}, "channel": {slackChanId}, "ts": {chanInfo.Channel.Latest.Ts}}) + url.Values{"token": {s.token}, "channel": {slackChanId}, "ts": {chanInfo.Channel.Latest.Ts}}) if err != nil { log.Printf("Error posting user info request: %s", err) @@ -621,7 +623,7 @@ func (s *Slack) markChannelAsRead(slackChanId string) error { } func (s *Slack) connect() { - token := s.config.Get("Slack.Token") + token := s.token u := fmt.Sprintf("https://slack.com/api/rtm.connect?token=%s", token) resp, err := http.Get(u) if err != nil { @@ -667,7 +669,7 @@ func (s *Slack) getUser(id string) (string, bool) { log.Printf("User %s not already found, requesting info", id) u := s.url + "users.info" resp, err := http.PostForm(u, - url.Values{"token": {s.config.Get("Slack.Token")}, "user": {id}}) + url.Values{"token": {s.token}, "user": {id}}) if err != nil || resp.StatusCode != 200 { log.Printf("Error posting user info request: %d %s", resp.StatusCode, err) @@ -689,7 +691,7 @@ func (s *Slack) Who(id string) []string { log.Println("Who is queried for ", id) u := s.url + "channels.info" resp, err := http.PostForm(u, - url.Values{"token": {s.config.Get("Slack.Token")}, "channel": {id}}) + url.Values{"token": {s.token}, "channel": {id}}) if err != nil { log.Printf("Error posting user info request: %s", err) From cd0aefbb9ce16877501fc6b518ad131a3f486575 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Mon, 21 Jan 2019 19:18:46 -0500 Subject: [PATCH 012/107] config: simplify the default setup --- config/defaults.go | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/config/defaults.go b/config/defaults.go index d98724c..4838e16 100644 --- a/config/defaults.go +++ b/config/defaults.go @@ -8,30 +8,10 @@ import ( ) var q = ` -INSERT INTO config VALUES('type','slack'); INSERT INTO config VALUES('nick','{{.Nick}}'); INSERT INTO config VALUES('channels','{{.Channel}}'); -INSERT INTO config VALUES('factoid.quotetime',30); -INSERT INTO config VALUES('reaction.negativereactions','bullshit;;fake;;tableflip;;vomit'); -INSERT INTO config VALUES('reaction.positivereactions','+1;;authorized;;aw_yeah;;yeah_man;;joy'); -INSERT INTO config VALUES('reaction.generalchance',0.01); -INSERT INTO config VALUES('reaction.harrasschance',0.05); -INSERT INTO config VALUES('commandchar','!;;¡'); -INSERT INTO config VALUES('factoid.startupfact','speed test'); -INSERT INTO config VALUES('factoid.quotechance',0.99); -INSERT INTO config VALUES('factoid.minlen',4); INSERT INTO config VALUES('untappd.channels','{{.Channel}}'); INSERT INTO config VALUES('twitch.channels','{{.Channel}}'); -INSERT INTO config VALUES('twitch.{{.ChannelKey}}.users','drseabass;;phlyingpenguin;;stack5;;geoffwithaj;;msherms;;eaburns;;sheltim;;rathaus;;rcuhljr'); -INSERT INTO config VALUES('twitch.freq',60); -INSERT INTO config VALUES('leftpad.maxlen',50); -INSERT INTO config VALUES('untappd.freq',60); -INSERT INTO config VALUES('your.replacements.0.freq',1); -INSERT INTO config VALUES('your.replacements.0.this','fuck'); -INSERT INTO config VALUES('your.replacements.0.that','duck'); -INSERT INTO config VALUES('your.replacements','0;;1;;2'); -INSERT INTO config VALUES('httpaddr','127.0.0.1:1337'); -INSERT INTO config VALUES('your.maxlength',140); INSERT INTO config VALUES('init',1); ` From 8801dd1e8c267a231ade721cd514472729ef7ccb Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Mon, 21 Jan 2019 19:40:21 -0500 Subject: [PATCH 013/107] config: remove version file --- version.go | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 version.go diff --git a/version.go b/version.go deleted file mode 100644 index c2d9bee..0000000 --- a/version.go +++ /dev/null @@ -1,5 +0,0 @@ -// © 2013 the CatBase Authors under the WTFPL. See AUTHORS for the list of authors. - -package main - -const Version = "0.9" From 0c6552e854c3952ae263e15b5845ce2e7e688b1b Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Mon, 21 Jan 2019 20:07:03 -0500 Subject: [PATCH 014/107] config: whoops wrong package --- config/defaults.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/defaults.go b/config/defaults.go index 4838e16..f94d6b3 100644 --- a/config/defaults.go +++ b/config/defaults.go @@ -2,9 +2,9 @@ package config import ( "bytes" - "html/template" "log" "strings" + "text/template" ) var q = ` From aee106b21ba0e856c6ffb0f0b68a8a0043c4d126 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Tue, 22 Jan 2019 10:43:10 -0500 Subject: [PATCH 015/107] emojify: make sure there are spaces around beginning/ending emojy --- plugins/emojifyme/emojifyme.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/emojifyme/emojifyme.go b/plugins/emojifyme/emojifyme.go index d4cc975..de15e70 100644 --- a/plugins/emojifyme/emojifyme.go +++ b/plugins/emojifyme/emojifyme.go @@ -77,8 +77,8 @@ func (p *EmojifyMePlugin) Message(message msg.Message) bool { } for _, c := range candidates { if strings.Contains(msg, " "+c+" ") || - strings.HasPrefix(msg, c) || - strings.HasSuffix(msg, c) { + strings.HasPrefix(msg, c+" ") || + strings.HasSuffix(msg, " "+c) { emojys = append(emojys, v) if !stringsContain(inertTokens, k) || len(v) < 2 { emojied += 1 From 8795d956ff43f0360f6342e85d4ee3881e5ed47e Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Tue, 22 Jan 2019 10:51:19 -0500 Subject: [PATCH 016/107] emojify: flipped lt --- plugins/emojifyme/emojifyme.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/emojifyme/emojifyme.go b/plugins/emojifyme/emojifyme.go index de15e70..8234797 100644 --- a/plugins/emojifyme/emojifyme.go +++ b/plugins/emojifyme/emojifyme.go @@ -80,7 +80,7 @@ func (p *EmojifyMePlugin) Message(message msg.Message) bool { strings.HasPrefix(msg, c+" ") || strings.HasSuffix(msg, " "+c) { emojys = append(emojys, v) - if !stringsContain(inertTokens, k) || len(v) < 2 { + if !stringsContain(inertTokens, k) || len(v) > 2 { emojied += 1 } } From 2aaf18518a89bfe7e6fdcece93aa3be94f349b71 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Tue, 22 Jan 2019 10:52:28 -0500 Subject: [PATCH 017/107] emojify: flipped lt --- plugins/emojifyme/emojifyme.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/emojifyme/emojifyme.go b/plugins/emojifyme/emojifyme.go index 8234797..e7d244a 100644 --- a/plugins/emojifyme/emojifyme.go +++ b/plugins/emojifyme/emojifyme.go @@ -80,7 +80,7 @@ func (p *EmojifyMePlugin) Message(message msg.Message) bool { strings.HasPrefix(msg, c+" ") || strings.HasSuffix(msg, " "+c) { emojys = append(emojys, v) - if !stringsContain(inertTokens, k) || len(v) > 2 { + if !stringsContain(inertTokens, k) && len(v) > 2 { emojied += 1 } } From adb0eb9f45eb6a723b035bda47043472f0050354 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Tue, 22 Jan 2019 10:59:08 -0500 Subject: [PATCH 018/107] emojify: add direct eq --- plugins/emojifyme/emojifyme.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/emojifyme/emojifyme.go b/plugins/emojifyme/emojifyme.go index e7d244a..26b5b72 100644 --- a/plugins/emojifyme/emojifyme.go +++ b/plugins/emojifyme/emojifyme.go @@ -78,7 +78,8 @@ func (p *EmojifyMePlugin) Message(message msg.Message) bool { for _, c := range candidates { if strings.Contains(msg, " "+c+" ") || strings.HasPrefix(msg, c+" ") || - strings.HasSuffix(msg, " "+c) { + strings.HasSuffix(msg, " "+c) || + msg == c { emojys = append(emojys, v) if !stringsContain(inertTokens, k) && len(v) > 2 { emojied += 1 From 17afdda35ee0ffe2a46a3b9814e5b56fbedb9133 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Fri, 25 Jan 2019 10:04:38 -0500 Subject: [PATCH 019/107] admin: be quiet This will not silence any out of band messages such as Untappd checks, Twitch notifications, or the startup message. This will cause catbase not to know anything about the conversation, which means quoting something during the quiet period is impossible. Everything during quiet time is off the record. --- plugins/admin/admin.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/plugins/admin/admin.go b/plugins/admin/admin.go index 50049b3..a7e45c8 100644 --- a/plugins/admin/admin.go +++ b/plugins/admin/admin.go @@ -6,6 +6,7 @@ import ( "fmt" "log" "strings" + "time" "github.com/jmoiron/sqlx" "github.com/velour/catbase/bot" @@ -19,6 +20,8 @@ type AdminPlugin struct { Bot bot.Bot db *sqlx.DB cfg *config.Config + + quiet bool } // NewAdminPlugin creates a new AdminPlugin with the Plugin interface @@ -44,6 +47,10 @@ var forbiddenKeys = map[string]bool{ func (p *AdminPlugin) Message(message msg.Message) bool { body := message.Body + if p.quiet { + return true + } + if len(body) > 0 && body[0] == '$' { return p.handleVariables(message) } @@ -52,6 +59,21 @@ func (p *AdminPlugin) Message(message msg.Message) bool { return false } + if strings.ToLower(body) == "shut up" { + dur := time.Duration(p.cfg.GetInt("quietDuration", 5)) * time.Minute + log.Printf("Going to sleep for %v, %v", dur, time.Now().Add(dur)) + p.Bot.SendMessage(message.Channel, "Okay. I'll be back later.") + p.quiet = true + go func() { + select { + case <-time.After(dur): + p.quiet = false + log.Println("Waking up from nap.") + } + }() + return true + } + parts := strings.Split(body, " ") if parts[0] == "set" && len(parts) > 2 && forbiddenKeys[parts[1]] { p.Bot.SendMessage(message.Channel, "You cannot access that key") From 1726b834380077a527f394372071a2ee16e443ff Mon Sep 17 00:00:00 2001 From: skkiesel Date: Sun, 27 Jan 2019 11:18:16 -1000 Subject: [PATCH 020/107] Limit the listing pain caused by inflicting reminder pain on others --- plugins/reminder/reminder.go | 32 ++++++++++++++++++++++++------- plugins/reminder/reminder_test.go | 28 +++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/plugins/reminder/reminder.go b/plugins/reminder/reminder.go index f1ec707..4e00e9c 100644 --- a/plugins/reminder/reminder.go +++ b/plugins/reminder/reminder.go @@ -121,8 +121,8 @@ func (p *ReminderPlugin) Message(message msg.Message) bool { endTime := time.Now().UTC().Add(dur2) what := strings.Join(parts[6:], " ") + max := p.config.GetInt("Reminder.MaxBatchAdd", 10) for i := 0; when.Before(endTime); i++ { - max := p.config.GetInt("Reminder.MaxBatchAdd", 10) if i >= max { p.Bot.SendMessage(channel, "Easy cowboy, that's a lot of reminders. I'll add some of them.") doConfirm = false @@ -272,9 +272,25 @@ func (p *ReminderPlugin) deleteReminder(id int64) error { return err } -func (p *ReminderPlugin) getRemindersFormatted(queryString string) (string, error) { +func (p *ReminderPlugin) getRemindersFormatted(filter string) (string, error) { + max := p.config.GetInt("Reminder.MaxList", 25) + queryString := fmt.Sprintf("select id, fromWho, toWho, what, remindWhen from reminders %s order by remindWhen asc limit %d;", filter, max) + countString := fmt.Sprintf("select COUNT(*) from reminders %s;", filter) + p.mutex.Lock() defer p.mutex.Unlock() + + var total int + err := p.db.Get(&total, countString) + if err != nil { + log.Print(err) + return "", nil + } + + if total == 0 { + return "no pending reminders", nil + } + rows, err := p.db.Query(queryString) if err != nil { log.Print(err) @@ -293,23 +309,25 @@ func (p *ReminderPlugin) getRemindersFormatted(queryString string) (string, erro reminders += fmt.Sprintf("%d) %s -> %s :: %s @ %s (%d)\n", counter, reminder.from, reminder.who, reminder.what, when, reminder.id) counter++ } - if counter == 1 { - return "no pending reminders", nil + + remaining := total - max + if remaining > 0 { + reminders += fmt.Sprintf("...%d more...\n", remaining) } return reminders, nil } func (p *ReminderPlugin) getAllRemindersFormatted(channel string) (string, error) { - return p.getRemindersFormatted("select id, fromWho, toWho, what, remindWhen from reminders order by remindWhen asc;") + return p.getRemindersFormatted("") } func (p *ReminderPlugin) getAllRemindersFromMeFormatted(channel, me string) (string, error) { - return p.getRemindersFormatted(fmt.Sprintf("select id, fromWho, toWho, what, remindWhen from reminders where fromWho = '%s' order by remindWhen asc;", me)) + return p.getRemindersFormatted(fmt.Sprintf("where fromWho = '%s'", me)) } func (p *ReminderPlugin) getAllRemindersToMeFormatted(channel, me string) (string, error) { - return p.getRemindersFormatted(fmt.Sprintf("select id, fromWho, toWho, what, remindWhen from reminders where toWho = '%s' order by remindWhen asc;", me)) + return p.getRemindersFormatted(fmt.Sprintf("where toWho = '%s'", me)) } func (p *ReminderPlugin) queueUpNextReminder() { diff --git a/plugins/reminder/reminder_test.go b/plugins/reminder/reminder_test.go index d2cc838..c9095d9 100644 --- a/plugins/reminder/reminder_test.go +++ b/plugins/reminder/reminder_test.go @@ -201,6 +201,34 @@ func TestCancelMiss(t *testing.T) { assert.Contains(t, mb.Messages[0], "failed to find and cancel reminder: 1") } +func TestLimitList(t *testing.T) { + c, mb := setup(t) + c.config.Set("Reminder.MaxBatchAdd", "10") + c.config.Set("Reminder.MaxList", "25") + assert.NotNil(t, c) + + //Someone can redo this with a single batch add, but I can't locally due to an old version of sqllite (maybe). + res := c.Message(makeMessage("!remind testuser every 1h for 10h don't fail this test")) + assert.True(t, res) + res = c.Message(makeMessage("!remind testuser every 1h for 10h don't fail this test")) + assert.True(t, res) + res = c.Message(makeMessage("!remind testuser every 1h for 10h don't fail this test")) + assert.True(t, res) + res = c.Message(makeMessage("!list reminders")) + assert.True(t, res) + assert.Len(t, mb.Messages, 4) + assert.Contains(t, mb.Messages[0], "Sure tester, I'll remind testuser.") + assert.Contains(t, mb.Messages[1], "Sure tester, I'll remind testuser.") + assert.Contains(t, mb.Messages[2], "Sure tester, I'll remind testuser.") + + for i := 0; i < 25; i++ { + assert.Contains(t, mb.Messages[3], fmt.Sprintf("%d) tester -> testuser :: don't fail this test", i+1)) + } + assert.Contains(t, mb.Messages[3], "...5 more...") + + assert.NotContains(t, mb.Messages[3], "26) tester -> testuser") +} + func TestHelp(t *testing.T) { c, mb := setup(t) assert.NotNil(t, c) From 0c93259c55dc00308fcee60232c035bbd8ef83a9 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Mon, 4 Feb 2019 22:17:33 -0500 Subject: [PATCH 021/107] twitch: configerize the stream word --- plugins/reminder/reminder_test.go | 2 +- plugins/twitch/twitch.go | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/plugins/reminder/reminder_test.go b/plugins/reminder/reminder_test.go index c9095d9..d3c6155 100644 --- a/plugins/reminder/reminder_test.go +++ b/plugins/reminder/reminder_test.go @@ -224,7 +224,7 @@ func TestLimitList(t *testing.T) { for i := 0; i < 25; i++ { assert.Contains(t, mb.Messages[3], fmt.Sprintf("%d) tester -> testuser :: don't fail this test", i+1)) } - assert.Contains(t, mb.Messages[3], "...5 more...") + assert.Contains(t, mb.Messages[3], "more...") assert.NotContains(t, mb.Messages[3], "26) tester -> testuser") } diff --git a/plugins/twitch/twitch.go b/plugins/twitch/twitch.go index 6e22f14..9b2c2b7 100644 --- a/plugins/twitch/twitch.go +++ b/plugins/twitch/twitch.go @@ -221,20 +221,21 @@ func (p *TwitchPlugin) checkTwitch(channel string, twitcher *Twitcher, alwaysPri if len(games) > 0 { game = games[0].Title } + streamWord := p.config.Get("Twitch.StreamWord", "streaming") if alwaysPrintStatus { if game == "" { - p.Bot.SendMessage(channel, twitcher.name+" is not streaming.") + p.Bot.SendMessage(channel, fmt.Sprintf("%s is not %s.", twitcher.name, streamWord)) } else { - p.Bot.SendMessage(channel, twitcher.name+" is streaming "+game+" at "+twitcher.URL()) + p.Bot.SendMessage(channel, fmt.Sprintf("%s is %s %s at %s", twitcher.name, streamWord, game, twitcher.URL())) } } else if game == "" { if twitcher.game != "" { - p.Bot.SendMessage(channel, twitcher.name+" just stopped streaming.") + p.Bot.SendMessage(channel, fmt.Sprintf("%s just stopped %s.", twitcher.name, streamWord)) } twitcher.game = "" } else { if twitcher.game != game { - p.Bot.SendMessage(channel, twitcher.name+" just started streaming "+game+" at "+twitcher.URL()) + p.Bot.SendMessage(channel, fmt.Sprintf("%s is %s %s at %s", twitcher.name, streamWord, game, twitcher.URL())) } twitcher.game = game } From e7c88c0c9c028722bdda6a708b38861cb82d8bc6 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Tue, 5 Feb 2019 10:54:13 -0500 Subject: [PATCH 022/107] events: refactor Combining all of the various send/recv functions into one --- bot/bot.go | 22 ++++++++--- bot/handlers.go | 58 +++++++++++++++++------------ bot/interfaces.go | 60 +++++++++++++++++++----------- bot/mock.go | 11 +++--- main.go | 50 ++++++++++++------------- plugins/admin/admin.go | 24 ++++++------ plugins/babbler/babbler.go | 4 +- plugins/beers/beers.go | 26 ++++++------- plugins/couldashouldawoulda/csw.go | 2 +- plugins/counter/counter.go | 36 +++++++++--------- plugins/dice/dice.go | 6 +-- plugins/downtime/downtime.go | 8 ++-- plugins/emojifyme/emojifyme.go | 2 +- plugins/fact/factoid.go | 48 ++++++++++++------------ plugins/fact/remember.go | 10 ++--- plugins/first/first.go | 4 +- plugins/inventory/inventory.go | 8 ++-- plugins/leftpad/leftpad.go | 6 +-- plugins/nerdepedia/nerdepedia.go | 4 +- plugins/picker/picker.go | 8 ++-- plugins/reaction/reaction.go | 2 +- plugins/reminder/reminder.go | 26 ++++++------- plugins/rpgORdie/rpgORdie.go | 14 +++---- plugins/rss/rss.go | 8 ++-- plugins/sisyphus/sisyphus.go | 28 +++++++------- plugins/talker/talker.go | 6 +-- plugins/tell/tell.go | 4 +- plugins/twitch/twitch.go | 10 ++--- plugins/your/your.go | 4 +- plugins/zork/zork.go | 8 ++-- 30 files changed, 271 insertions(+), 236 deletions(-) diff --git a/bot/bot.go b/bot/bot.go index 6a69bb2..e3fffa3 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -19,7 +19,7 @@ import ( type bot struct { // Each plugin must be registered in our plugins handler. To come: a map so that this // will allow plugins to respond to specific kinds of events - plugins map[string]Handler + plugins map[string]Plugin pluginOrdering []string // Users holds information about all of our friends @@ -41,13 +41,16 @@ type bot struct { // filters registered by plugins filters map[string]func(string) string + + callbacks map[string][]Callback } +// Variable represents a $var replacement type Variable struct { Variable, Value string } -// Newbot creates a bot for a given connection and set of handlers. +// New creates a bot for a given connection and set of handlers. func New(config *config.Config, connector Connector) Bot { logIn := make(chan msg.Message) logOut := make(chan msg.Messages) @@ -62,7 +65,7 @@ func New(config *config.Config, connector Connector) Bot { bot := &bot{ config: config, - plugins: make(map[string]Handler), + plugins: make(map[string]Plugin), pluginOrdering: make([]string, 0), conn: connector, users: users, @@ -71,6 +74,7 @@ func New(config *config.Config, connector Connector) Bot { logOut: logOut, httpEndPoints: make(map[string]string), filters: make(map[string]func(string) string), + callbacks: make(map[string][]Callback), } bot.migrateDB() @@ -109,7 +113,7 @@ func (b *bot) migrateDB() { } // Adds a constructed handler to the bots handlers list -func (b *bot) AddHandler(name string, h Handler) { +func (b *bot) AddPlugin(name string, h Plugin) { b.plugins[name] = h b.pluginOrdering = append(b.pluginOrdering, name) if entry := h.RegisterWeb(); entry != nil { @@ -126,7 +130,7 @@ func (b *bot) Who(channel string) []user.User { return users } -var rootIndex string = ` +var rootIndex = ` @@ -166,7 +170,7 @@ func (b *bot) serveRoot(w http.ResponseWriter, r *http.Request) { t.Execute(w, context) } -// Checks if message is a command and returns its curtailed version +// IsCmd checks if message is a command and returns its curtailed version func IsCmd(c *config.Config, message string) (bool, string) { cmdcs := c.GetArray("CommandChar", []string{"!"}) botnick := strings.ToLower(c.Get("Nick", "bot")) @@ -244,3 +248,9 @@ func (b *bot) checkAdmin(nick string) bool { func (b *bot) RegisterFilter(name string, f func(string) string) { b.filters[name] = f } + +// Send a message to the connection +func (b *bot) Send(int, ...interface{}) (error, string) { return nil, "" } + +// Register a callback +func (b *bot) Register(int, Callback) {} diff --git a/bot/handlers.go b/bot/handlers.go index 39b5d72..072d36e 100644 --- a/bot/handlers.go +++ b/bot/handlers.go @@ -16,6 +16,10 @@ import ( "github.com/velour/catbase/bot/msg" ) +func (b *bot) Receive(kind int, msg msg.Message, args ...interface{}) { + panic("I don't know what to do here yet") +} + // Handles incomming PRIVMSG requests func (b *bot) MsgReceived(msg msg.Message) { log.Println("Received message: ", msg) @@ -29,9 +33,8 @@ func (b *bot) MsgReceived(msg msg.Message) { } for _, name := range b.pluginOrdering { - p := b.plugins[name] - if p.Message(msg) { - break + if b.runCallback(name, Message, msg) { + goto RET } } @@ -45,47 +48,54 @@ func (b *bot) EventReceived(msg msg.Message) { log.Println("Received event: ", msg) //msg := b.buildMessage(conn, inMsg) for _, name := range b.pluginOrdering { - p := b.plugins[name] - if p.Event(msg.Body, msg) { // TODO: could get rid of msg.Body - break + if b.runCallback(name, Event, msg) { + return } } } +func (b *bot) runCallback(plugin string, evt int, message msg.Message, args ...interface{}) bool { + for _, cb := range b.callbacks[plugin] { + if cb(evt, message) { + return true + } + } + return false +} + // Handle incoming replys func (b *bot) ReplyMsgReceived(msg msg.Message, identifier string) { log.Println("Received message: ", msg) for _, name := range b.pluginOrdering { - p := b.plugins[name] - if p.ReplyMessage(msg, identifier) { + if b.runCallback(name, Reply, msg, identifier) { break } } } -func (b *bot) SendMessage(channel, message string) string { - return b.conn.SendMessage(channel, message) +func (b *bot) SendMessage(channel, message string) (error, string) { + return b.conn.Send(Message, channel, message) } -func (b *bot) SendAction(channel, message string) string { - return b.conn.SendAction(channel, message) +func (b *bot) SendAction(channel, message string) (error, string) { + return b.conn.Send(Action, channel, message) } -func (b *bot) ReplyToMessageIdentifier(channel, message, identifier string) (string, bool) { - return b.conn.ReplyToMessageIdentifier(channel, message, identifier) +func (b *bot) ReplyToMessageIdentifier(channel, message, identifier string) (error, string) { + return b.conn.Send(Reply, channel, message, identifier) } -func (b *bot) ReplyToMessage(channel, message string, replyTo msg.Message) (string, bool) { - return b.conn.ReplyToMessage(channel, message, replyTo) +func (b *bot) ReplyToMessage(channel, message string, replyTo msg.Message) (error, string) { + return b.conn.Send(Reply, channel, message, replyTo) } -func (b *bot) React(channel, reaction string, message msg.Message) bool { - return b.conn.React(channel, reaction, message) +func (b *bot) React(channel, reaction string, message msg.Message) (error, string) { + return b.conn.Send(Reaction, channel, reaction, message) } -func (b *bot) Edit(channel, newMessage, identifier string) bool { - return b.conn.Edit(channel, newMessage, identifier) +func (b *bot) Edit(channel, newMessage, identifier string) (error, string) { + return b.conn.Send(Edit, channel, newMessage, identifier) } func (b *bot) GetEmojiList() map[string]string { @@ -113,7 +123,8 @@ func (b *bot) checkHelp(channel string, parts []string) { } plugin := b.plugins[parts[1]] if plugin != nil { - plugin.Help(channel, parts) + // TODO: Maybe broke + b.runCallback(parts[1], Help, msg.Message{Channel: channel}, channel, parts) } else { msg := fmt.Sprintf("I'm sorry, I don't know what %s is!", parts[1]) b.SendMessage(channel, msg) @@ -236,9 +247,8 @@ func (b *bot) selfSaid(channel, message string, action bool) { } for _, name := range b.pluginOrdering { - p := b.plugins[name] - if p.BotMessage(msg) { - break + if b.runCallback(name, SelfMessage, msg) { + return } } } diff --git a/bot/interfaces.go b/bot/interfaces.go index 42b0980..ec2aa6e 100644 --- a/bot/interfaces.go +++ b/bot/interfaces.go @@ -9,22 +9,47 @@ import ( "github.com/velour/catbase/config" ) +const ( + _ = iota + + // Message any standard chat + Message + // Reply something containing a message reference + Reply + // Action any /me action + Action + // Reaction Icon reaction if service supports it + Reaction + // Edit message ref'd new message to replace + Edit + // Not sure what event is + Event + // Help is used when the bot help system is triggered + Help + // SelfMessage triggers when the bot is sending a message + SelfMessage +) + +type kind int + +type Callback func(int, msg.Message, ...interface{}) bool + type Bot interface { Config() *config.Config DB() *sqlx.DB Who(string) []user.User - AddHandler(string, Handler) - SendMessage(string, string) string - SendAction(string, string) string - ReplyToMessageIdentifier(string, string, string) (string, bool) - ReplyToMessage(string, string, msg.Message) (string, bool) - React(string, string, msg.Message) bool - Edit(string, string, string) bool - MsgReceived(msg.Message) - ReplyMsgReceived(msg.Message, string) - EventReceived(msg.Message) + AddPlugin(string, Plugin) + + // First arg should be one of bot.Message/Reply/Action/etc + Send(int, ...interface{}) (error, string) + // First arg should be one of bot.Message/Reply/Action/etc + Receive(int, msg.Message, ...interface{}) + // Register a callback + Register(int, Callback) + Filter(msg.Message, string) string LastMessage(string) (msg.Message, error) + CheckAdmin(string) bool GetEmojiList() map[string]string RegisterFilter(string, func(string) string) @@ -35,12 +60,8 @@ type Connector interface { RegisterMessageReceived(func(message msg.Message)) RegisterReplyMessageReceived(func(msg.Message, string)) - SendMessage(channel, message string) string - SendAction(channel, message string) string - ReplyToMessageIdentifier(string, string, string) (string, bool) - ReplyToMessage(string, string, msg.Message) (string, bool) - React(string, string, msg.Message) bool - Edit(string, string, string) bool + Send(int, ...interface{}) (error, string) + GetEmojiList() map[string]string Serve() error @@ -48,11 +69,6 @@ type Connector interface { } // Interface used for compatibility with the Plugin interface -type Handler interface { - Message(message msg.Message) bool - Event(kind string, message msg.Message) bool - ReplyMessage(msg.Message, string) bool - BotMessage(message msg.Message) bool - Help(channel string, parts []string) +type Plugin interface { RegisterWeb() *string } diff --git a/bot/mock.go b/bot/mock.go index f3170e4..710b7f0 100644 --- a/bot/mock.go +++ b/bot/mock.go @@ -26,12 +26,11 @@ type MockBot struct { Reactions []string } -func (mb *MockBot) Config() *config.Config { return mb.Cfg } -func (mb *MockBot) DBVersion() int64 { return 1 } -func (mb *MockBot) DB() *sqlx.DB { return mb.Cfg.DB } -func (mb *MockBot) Conn() Connector { return nil } -func (mb *MockBot) Who(string) []user.User { return []user.User{} } -func (mb *MockBot) AddHandler(name string, f Handler) {} +func (mb *MockBot) Config() *config.Config { return mb.Cfg } +func (mb *MockBot) DBVersion() int64 { return 1 } +func (mb *MockBot) DB() *sqlx.DB { return mb.Cfg.DB } +func (mb *MockBot) Conn() Connector { return nil } +func (mb *MockBot) Who(string) []user.User { return []user.User{} } func (mb *MockBot) SendMessage(ch string, msg string) string { mb.Messages = append(mb.Messages, msg) return fmt.Sprintf("m-%d", len(mb.Actions)-1) diff --git a/main.go b/main.go index c3d7ebe..18ff59f 100644 --- a/main.go +++ b/main.go @@ -78,32 +78,32 @@ func main() { b := bot.New(c, client) - b.AddHandler("admin", admin.New(b)) - b.AddHandler("first", first.New(b)) - b.AddHandler("leftpad", leftpad.New(b)) - b.AddHandler("talker", talker.New(b)) - b.AddHandler("dice", dice.New(b)) - b.AddHandler("picker", picker.New(b)) - b.AddHandler("beers", beers.New(b)) - b.AddHandler("remember", fact.NewRemember(b)) - b.AddHandler("your", your.New(b)) - b.AddHandler("counter", counter.New(b)) - b.AddHandler("reminder", reminder.New(b)) - b.AddHandler("babbler", babbler.New(b)) - b.AddHandler("zork", zork.New(b)) - b.AddHandler("rss", rss.New(b)) - b.AddHandler("reaction", reaction.New(b)) - b.AddHandler("emojifyme", emojifyme.New(b)) - b.AddHandler("twitch", twitch.New(b)) - b.AddHandler("inventory", inventory.New(b)) - b.AddHandler("rpgORdie", rpgORdie.New(b)) - b.AddHandler("sisyphus", sisyphus.New(b)) - b.AddHandler("tell", tell.New(b)) - b.AddHandler("couldashouldawoulda", couldashouldawoulda.New(b)) - b.AddHandler("nedepedia", nerdepedia.New(b)) + b.AddPlugin("admin", admin.New(b)) + b.AddPlugin("first", first.New(b)) + b.AddPlugin("leftpad", leftpad.New(b)) + b.AddPlugin("talker", talker.New(b)) + b.AddPlugin("dice", dice.New(b)) + b.AddPlugin("picker", picker.New(b)) + b.AddPlugin("beers", beers.New(b)) + b.AddPlugin("remember", fact.NewRemember(b)) + b.AddPlugin("your", your.New(b)) + b.AddPlugin("counter", counter.New(b)) + b.AddPlugin("reminder", reminder.New(b)) + b.AddPlugin("babbler", babbler.New(b)) + b.AddPlugin("zork", zork.New(b)) + b.AddPlugin("rss", rss.New(b)) + b.AddPlugin("reaction", reaction.New(b)) + b.AddPlugin("emojifyme", emojifyme.New(b)) + b.AddPlugin("twitch", twitch.New(b)) + b.AddPlugin("inventory", inventory.New(b)) + b.AddPlugin("rpgORdie", rpgORdie.New(b)) + b.AddPlugin("sisyphus", sisyphus.New(b)) + b.AddPlugin("tell", tell.New(b)) + b.AddPlugin("couldashouldawoulda", couldashouldawoulda.New(b)) + b.AddPlugin("nedepedia", nerdepedia.New(b)) // catches anything left, will always return true - b.AddHandler("factoid", fact.New(b)) - b.AddHandler("db", db.New(b)) + b.AddPlugin("factoid", fact.New(b)) + b.AddPlugin("db", db.New(b)) for { err := client.Serve() diff --git a/plugins/admin/admin.go b/plugins/admin/admin.go index a7e45c8..628125e 100644 --- a/plugins/admin/admin.go +++ b/plugins/admin/admin.go @@ -62,7 +62,7 @@ func (p *AdminPlugin) Message(message msg.Message) bool { if strings.ToLower(body) == "shut up" { dur := time.Duration(p.cfg.GetInt("quietDuration", 5)) * time.Minute log.Printf("Going to sleep for %v, %v", dur, time.Now().Add(dur)) - p.Bot.SendMessage(message.Channel, "Okay. I'll be back later.") + p.Bot.Send(bot.Message, message.Channel, "Okay. I'll be back later.") p.quiet = true go func() { select { @@ -76,19 +76,19 @@ func (p *AdminPlugin) Message(message msg.Message) bool { parts := strings.Split(body, " ") if parts[0] == "set" && len(parts) > 2 && forbiddenKeys[parts[1]] { - p.Bot.SendMessage(message.Channel, "You cannot access that key") + p.Bot.Send(bot.Message, message.Channel, "You cannot access that key") return true } else if parts[0] == "set" && len(parts) > 2 { p.cfg.Set(parts[1], strings.Join(parts[2:], " ")) - p.Bot.SendMessage(message.Channel, fmt.Sprintf("Set %s", parts[1])) + p.Bot.Send(bot.Message, message.Channel, fmt.Sprintf("Set %s", parts[1])) return true } if parts[0] == "get" && len(parts) == 2 && forbiddenKeys[parts[1]] { - p.Bot.SendMessage(message.Channel, "You cannot access that key") + p.Bot.Send(bot.Message, message.Channel, "You cannot access that key") return true } else if parts[0] == "get" && len(parts) == 2 { v := p.cfg.Get(parts[1], "") - p.Bot.SendMessage(message.Channel, fmt.Sprintf("%s: %s", parts[1], v)) + p.Bot.Send(bot.Message, message.Channel, fmt.Sprintf("%s: %s", parts[1], v)) return true } @@ -102,10 +102,10 @@ func (p *AdminPlugin) handleVariables(message msg.Message) bool { _, err := p.db.Exec(`delete from variables where name=? and value=?`, variable, value) if err != nil { - p.Bot.SendMessage(message.Channel, "I'm broke and need attention in my variable creation code.") + p.Bot.Send(bot.Message, message.Channel, "I'm broke and need attention in my variable creation code.") log.Println("[admin]: ", err) } else { - p.Bot.SendMessage(message.Channel, "Removed.") + p.Bot.Send(bot.Message, message.Channel, "Removed.") } return true @@ -123,28 +123,28 @@ func (p *AdminPlugin) handleVariables(message msg.Message) bool { row := p.db.QueryRow(`select count(*) from variables where value = ?`, variable, value) err := row.Scan(&count) if err != nil { - p.Bot.SendMessage(message.Channel, "I'm broke and need attention in my variable creation code.") + p.Bot.Send(bot.Message, message.Channel, "I'm broke and need attention in my variable creation code.") log.Println("[admin]: ", err) return true } if count > 0 { - p.Bot.SendMessage(message.Channel, "I've already got that one.") + p.Bot.Send(bot.Message, message.Channel, "I've already got that one.") } else { _, err := p.db.Exec(`INSERT INTO variables (name, value) VALUES (?, ?)`, variable, value) if err != nil { - p.Bot.SendMessage(message.Channel, "I'm broke and need attention in my variable creation code.") + p.Bot.Send(bot.Message, message.Channel, "I'm broke and need attention in my variable creation code.") log.Println("[admin]: ", err) return true } - p.Bot.SendMessage(message.Channel, "Added.") + p.Bot.Send(bot.Message, message.Channel, "Added.") } return true } // Help responds to help requests. Every plugin must implement a help function. func (p *AdminPlugin) Help(channel string, parts []string) { - p.Bot.SendMessage(channel, "This does super secret things that you're not allowed to know about.") + p.Bot.Send(bot.Message, channel, "This does super secret things that you're not allowed to know about.") } // Empty event handler because this plugin does not do anything on event recv diff --git a/plugins/babbler/babbler.go b/plugins/babbler/babbler.go index 78ba647..38997ec 100644 --- a/plugins/babbler/babbler.go +++ b/plugins/babbler/babbler.go @@ -141,7 +141,7 @@ func (p *BabblerPlugin) Message(message msg.Message) bool { } if saidSomething { - p.Bot.SendMessage(message.Channel, saidWhat) + p.Bot.Send(bot.Message, message.Channel, saidWhat) } return saidSomething } @@ -155,7 +155,7 @@ func (p *BabblerPlugin) Help(channel string, parts []string) { "seabass says-middle-out ...", "seabass says-bridge ... | ...", } - p.Bot.SendMessage(channel, strings.Join(commands, "\n\n")) + p.Bot.Send(bot.Message, channel, strings.Join(commands, "\n\n")) } func (p *BabblerPlugin) Event(kind string, message msg.Message) bool { diff --git a/plugins/beers/beers.go b/plugins/beers/beers.go index d4da158..d81cfa0 100644 --- a/plugins/beers/beers.go +++ b/plugins/beers/beers.go @@ -81,13 +81,13 @@ func (p *BeersPlugin) Message(message msg.Message) bool { count, err := strconv.Atoi(parts[2]) if err != nil { // if it's not a number, maybe it's a nick! - p.Bot.SendMessage(channel, "Sorry, that didn't make any sense.") + p.Bot.Send(bot.Message, channel, "Sorry, that didn't make any sense.") } if count < 0 { // you can't be negative msg := fmt.Sprintf("Sorry %s, you can't have negative beers!", nick) - p.Bot.SendMessage(channel, msg) + p.Bot.Send(bot.Message, channel, msg) return true } if parts[1] == "+=" { @@ -101,14 +101,14 @@ func (p *BeersPlugin) Message(message msg.Message) bool { p.randomReply(channel) } } else { - p.Bot.SendMessage(channel, "I don't know your math.") + p.Bot.Send(bot.Message, channel, "I don't know your math.") } } else if len(parts) == 2 { if p.doIKnow(parts[1]) { p.reportCount(parts[1], channel, false) } else { msg := fmt.Sprintf("Sorry, I don't know %s.", parts[1]) - p.Bot.SendMessage(channel, msg) + p.Bot.Send(bot.Message, channel, msg) } } else if len(parts) == 1 { p.reportCount(nick, channel, true) @@ -132,7 +132,7 @@ func (p *BeersPlugin) Message(message msg.Message) bool { channel := message.Channel if len(parts) < 2 { - p.Bot.SendMessage(channel, "You must also provide a user name.") + p.Bot.Send(bot.Message, channel, "You must also provide a user name.") } else if len(parts) == 3 { chanNick = parts[2] } else if len(parts) == 4 { @@ -154,7 +154,7 @@ func (p *BeersPlugin) Message(message msg.Message) bool { log.Println("Error registering untappd: ", err) } if count > 0 { - p.Bot.SendMessage(channel, "I'm already watching you.") + p.Bot.Send(bot.Message, channel, "I'm already watching you.") return true } _, err = p.db.Exec(`insert into untappd ( @@ -170,11 +170,11 @@ func (p *BeersPlugin) Message(message msg.Message) bool { ) if err != nil { log.Println("Error registering untappd: ", err) - p.Bot.SendMessage(channel, "I can't see.") + p.Bot.Send(bot.Message, channel, "I can't see.") return true } - p.Bot.SendMessage(channel, "I'll be watching you.") + p.Bot.Send(bot.Message, channel, "I'll be watching you.") p.checkUntappd(channel) @@ -200,7 +200,7 @@ func (p *BeersPlugin) Help(channel string, parts []string) { msg := "Beers: imbibe by using either beers +=,=,++ or with the !imbibe/drink " + "commands. I'll keep a count of how many beers you've had and then if you want " + "to reset, just !puke it all up!" - p.Bot.SendMessage(channel, msg) + p.Bot.Send(bot.Message, channel, msg) } func getUserBeers(db *sqlx.DB, user string) counter.Item { @@ -239,13 +239,13 @@ func (p *BeersPlugin) reportCount(nick, channel string, himself bool) { msg = fmt.Sprintf("You've had %d beers so far, %s.", beers, nick) } } - p.Bot.SendMessage(channel, msg) + p.Bot.Send(bot.Message, channel, msg) } func (p *BeersPlugin) puke(user string, channel string) { p.setBeers(user, 0) msg := fmt.Sprintf("Ohhhhhh, and a reversal of fortune for %s!", user) - p.Bot.SendMessage(channel, msg) + p.Bot.Send(bot.Message, channel, msg) } func (p *BeersPlugin) doIKnow(nick string) bool { @@ -260,7 +260,7 @@ func (p *BeersPlugin) doIKnow(nick string) bool { // Sends random affirmation to the channel. This could be better (with a datastore for sayings) func (p *BeersPlugin) randomReply(channel string) { replies := []string{"ZIGGY! ZAGGY!", "HIC!", "Stay thirsty, my friend!"} - p.Bot.SendMessage(channel, replies[rand.Intn(len(replies))]) + p.Bot.Send(bot.Message, channel, replies[rand.Intn(len(replies))]) } type checkin struct { @@ -421,7 +421,7 @@ func (p *BeersPlugin) checkUntappd(channel string) { } log.Println("checkin id:", checkin.Checkin_id, "Message:", msg) - p.Bot.SendMessage(channel, msg) + p.Bot.Send(bot.Message, channel, msg) } } diff --git a/plugins/couldashouldawoulda/csw.go b/plugins/couldashouldawoulda/csw.go index 455c45d..1a99317 100644 --- a/plugins/couldashouldawoulda/csw.go +++ b/plugins/couldashouldawoulda/csw.go @@ -63,7 +63,7 @@ func (p *CSWPlugin) Message(message msg.Message) bool { } } - p.Bot.SendMessage(message.Channel, responses[rand.Intn(len(responses))]) + p.Bot.Send(bot.Message, message.Channel, responses[rand.Intn(len(responses))]) return true } diff --git a/plugins/counter/counter.go b/plugins/counter/counter.go index 2c7668a..0100d79 100644 --- a/plugins/counter/counter.go +++ b/plugins/counter/counter.go @@ -211,7 +211,7 @@ func (p *CounterPlugin) Message(message msg.Message) bool { log.Println(err) return false } - p.Bot.SendMessage(channel, fmt.Sprintf("Created alias %s -> %s", + p.Bot.Send(bot.Message, channel, fmt.Sprintf("Created alias %s -> %s", parts[1], parts[2])) return true } else if strings.ToLower(parts[0]) == "leaderboard" { @@ -241,7 +241,7 @@ func (p *CounterPlugin) Message(message msg.Message) bool { it.Item, ) } - p.Bot.SendMessage(channel, out) + p.Bot.Send(bot.Message, channel, out) return true } else if match := teaMatcher.MatchString(message.Body); match { // check for tea match TTT @@ -250,14 +250,14 @@ func (p *CounterPlugin) Message(message msg.Message) bool { items, err := GetItems(p.DB, strings.ToLower(nick)) if err != nil { log.Printf("Error getting items to reset %s: %s", nick, err) - p.Bot.SendMessage(channel, "Something is technically wrong with your counters.") + p.Bot.Send(bot.Message, channel, "Something is technically wrong with your counters.") return true } log.Printf("Items: %+v", items) for _, item := range items { item.Delete() } - p.Bot.SendMessage(channel, fmt.Sprintf("%s, you are as new, my son.", nick)) + p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s, you are as new, my son.", nick)) return true } else if message.Command && parts[0] == "inspect" && len(parts) == 2 { var subject string @@ -273,7 +273,7 @@ func (p *CounterPlugin) Message(message msg.Message) bool { items, err := GetItems(p.DB, subject) if err != nil { log.Fatalf("Error retrieving items for %s: %s", subject, err) - p.Bot.SendMessage(channel, "Something went wrong finding that counter;") + p.Bot.Send(bot.Message, channel, "Something went wrong finding that counter;") return true } @@ -293,11 +293,11 @@ func (p *CounterPlugin) Message(message msg.Message) bool { resp += "." if count == 0 { - p.Bot.SendMessage(channel, fmt.Sprintf("%s has no counters.", subject)) + p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s has no counters.", subject)) return true } - p.Bot.SendMessage(channel, resp) + p.Bot.Send(bot.Message, channel, resp) return true } else if message.Command && len(parts) == 2 && parts[0] == "clear" { subject := strings.ToLower(nick) @@ -306,17 +306,17 @@ func (p *CounterPlugin) Message(message msg.Message) bool { it, err := GetItem(p.DB, subject, itemName) if err != nil { log.Printf("Error getting item to remove %s.%s: %s", subject, itemName, err) - p.Bot.SendMessage(channel, "Something went wrong removing that counter;") + p.Bot.Send(bot.Message, channel, "Something went wrong removing that counter;") return true } err = it.Delete() if err != nil { log.Printf("Error removing item %s.%s: %s", subject, itemName, err) - p.Bot.SendMessage(channel, "Something went wrong removing that counter;") + p.Bot.Send(bot.Message, channel, "Something went wrong removing that counter;") return true } - p.Bot.SendAction(channel, fmt.Sprintf("chops a few %s out of his brain", + p.Bot.Send(bot.Action, channel, fmt.Sprintf("chops a few %s out of his brain", itemName)) return true @@ -339,7 +339,7 @@ func (p *CounterPlugin) Message(message msg.Message) bool { item, err := GetItem(p.DB, subject, itemName) switch { case err == sql.ErrNoRows: - p.Bot.SendMessage(channel, fmt.Sprintf("I don't think %s has any %s.", + p.Bot.Send(bot.Message, channel, fmt.Sprintf("I don't think %s has any %s.", subject, itemName)) return true case err != nil: @@ -348,7 +348,7 @@ func (p *CounterPlugin) Message(message msg.Message) bool { return true } - p.Bot.SendMessage(channel, fmt.Sprintf("%s has %d %s.", subject, item.Count, + p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s has %d %s.", subject, item.Count, itemName)) return true @@ -379,7 +379,7 @@ func (p *CounterPlugin) Message(message msg.Message) bool { } log.Printf("About to update item: %#v", item) item.UpdateDelta(1) - p.Bot.SendMessage(channel, fmt.Sprintf("%s has %d %s.", subject, + p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s has %d %s.", subject, item.Count, item.Item)) return true } else if strings.HasSuffix(parts[0], "--") { @@ -391,7 +391,7 @@ func (p *CounterPlugin) Message(message msg.Message) bool { return false } item.UpdateDelta(-1) - p.Bot.SendMessage(channel, fmt.Sprintf("%s has %d %s.", subject, + p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s has %d %s.", subject, item.Count, item.Item)) return true } @@ -420,7 +420,7 @@ func (p *CounterPlugin) Message(message msg.Message) bool { n, _ := strconv.Atoi(parts[2]) log.Printf("About to update item by %d: %#v", n, item) item.UpdateDelta(n) - p.Bot.SendMessage(channel, fmt.Sprintf("%s has %d %s.", subject, + p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s has %d %s.", subject, item.Count, item.Item)) return true } else if parts[1] == "-=" { @@ -434,7 +434,7 @@ func (p *CounterPlugin) Message(message msg.Message) bool { n, _ := strconv.Atoi(parts[2]) log.Printf("About to update item by -%d: %#v", n, item) item.UpdateDelta(-n) - p.Bot.SendMessage(channel, fmt.Sprintf("%s has %d %s.", subject, + p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s has %d %s.", subject, item.Count, item.Item)) return true } @@ -445,7 +445,7 @@ func (p *CounterPlugin) Message(message msg.Message) bool { // Help responds to help requests. Every plugin must implement a help function. func (p *CounterPlugin) Help(channel string, parts []string) { - p.Bot.SendMessage(channel, "You can set counters incrementally by using "+ + p.Bot.Send(bot.Message, channel, "You can set counters incrementally by using "+ "++ and --. You can see all of your counters using "+ "\"inspect\", erase them with \"clear\", and view single counters with "+ "\"count\".") @@ -487,7 +487,7 @@ func (p *CounterPlugin) checkMatch(message msg.Message) bool { } log.Printf("About to update item: %#v", item) item.UpdateDelta(1) - p.Bot.SendMessage(channel, fmt.Sprintf("bleep-bloop-blop... %s has %d %s", + p.Bot.Send(bot.Message, channel, fmt.Sprintf("bleep-bloop-blop... %s has %d %s", nick, item.Count, itemName)) return true } diff --git a/plugins/dice/dice.go b/plugins/dice/dice.go index 104e016..51bbd72 100644 --- a/plugins/dice/dice.go +++ b/plugins/dice/dice.go @@ -46,7 +46,7 @@ func (p *DicePlugin) Message(message msg.Message) bool { } if sides < 2 || nDice < 1 || nDice > 20 { - p.Bot.SendMessage(channel, "You're a dick.") + p.Bot.Send(bot.Message, channel, "You're a dick.") return true } @@ -61,14 +61,14 @@ func (p *DicePlugin) Message(message msg.Message) bool { } } - p.Bot.SendMessage(channel, rolls) + p.Bot.Send(bot.Message, channel, rolls) return true } // Help responds to help requests. Every plugin must implement a help function. func (p *DicePlugin) Help(channel string, parts []string) { - p.Bot.SendMessage(channel, "Roll dice using notation XdY. Try \"3d20\".") + p.Bot.Send(bot.Message, channel, "Roll dice using notation XdY. Try \"3d20\".") } // Empty event handler because this plugin does not do anything on event recv diff --git a/plugins/downtime/downtime.go b/plugins/downtime/downtime.go index e50df61..335886e 100644 --- a/plugins/downtime/downtime.go +++ b/plugins/downtime/downtime.go @@ -142,9 +142,9 @@ func (p *DowntimePlugin) Message(message msg.Message) bool { } if !entry.id.Valid { // couldn't find em - p.Bot.SendMessage(channel, fmt.Sprintf("Sorry, I don't know %s.", nick)) + p.Bot.Send(bot.Message, channel, fmt.Sprintf("Sorry, I don't know %s.", nick)) } else { - p.Bot.SendMessage(channel, fmt.Sprintf("%s has been idle for: %s", + p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s has been idle for: %s", nick, time.Now().Sub(entry.lastSeen))) } ret = true @@ -165,7 +165,7 @@ func (p *DowntimePlugin) Message(message msg.Message) bool { tops = fmt.Sprintf("%s%s: %s ", tops, e.nick, time.Now().Sub(e.lastSeen)) } } - p.Bot.SendMessage(channel, tops) + p.Bot.Send(bot.Message, channel, tops) ret = true } @@ -197,7 +197,7 @@ func (p *DowntimePlugin) remove(user string) error { // Help responds to help requests. Every plugin must implement a help function. func (p *DowntimePlugin) Help(channel string, parts []string) { - p.Bot.SendMessage(channel, "Ask me how long one of your friends has been idele with, \"idle \"") + p.Bot.Send(bot.Message, channel, "Ask me how long one of your friends has been idele with, \"idle \"") } // Empty event handler because this plugin does not do anything on event recv diff --git a/plugins/emojifyme/emojifyme.go b/plugins/emojifyme/emojifyme.go index 26b5b72..b3614fd 100644 --- a/plugins/emojifyme/emojifyme.go +++ b/plugins/emojifyme/emojifyme.go @@ -90,7 +90,7 @@ func (p *EmojifyMePlugin) Message(message msg.Message) bool { if emojied > 0 && rand.Float64() <= p.Bot.Config().GetFloat64("Emojify.Chance", 0.02)*emojied { for _, e := range emojys { - p.Bot.React(message.Channel, e, message) + p.Bot.Send(bot.Reaction, message.Channel, e, message) } return true } diff --git a/plugins/fact/factoid.go b/plugins/fact/factoid.go index c53c934..fc2dfad 100644 --- a/plugins/fact/factoid.go +++ b/plugins/fact/factoid.go @@ -405,13 +405,13 @@ func (p *Factoid) sayFact(message msg.Message, fact factoid) { } if fact.Verb == "action" { - p.Bot.SendAction(message.Channel, msg) + p.Bot.Send(bot.Action, message.Channel, msg) } else if fact.Verb == "react" { - p.Bot.React(message.Channel, msg, message) + p.Bot.Send(bot.Reaction, message.Channel, msg, message) } else if fact.Verb == "reply" { - p.Bot.SendMessage(message.Channel, msg) + p.Bot.Send(bot.Message, message.Channel, msg) } else { - p.Bot.SendMessage(message.Channel, full) + p.Bot.Send(bot.Message, message.Channel, full) } } @@ -457,7 +457,7 @@ func (p *Factoid) tellThemWhatThatWas(message msg.Message) bool { msg = fmt.Sprintf("That was (#%d) '%s <%s> %s'", fact.id.Int64, fact.Fact, fact.Verb, fact.Tidbit) } - p.Bot.SendMessage(message.Channel, msg) + p.Bot.Send(bot.Message, message.Channel, msg) return true } @@ -475,21 +475,21 @@ func (p *Factoid) learnAction(message msg.Message, action string) bool { action = strings.TrimSpace(action) if len(trigger) == 0 || len(fact) == 0 || len(action) == 0 { - p.Bot.SendMessage(message.Channel, "I don't want to learn that.") + p.Bot.Send(bot.Message, message.Channel, "I don't want to learn that.") return true } if len(strings.Split(fact, "$and")) > 4 { - p.Bot.SendMessage(message.Channel, "You can't use more than 4 $and operators.") + p.Bot.Send(bot.Message, message.Channel, "You can't use more than 4 $and operators.") return true } strippedaction := strings.Replace(strings.Replace(action, "<", "", 1), ">", "", 1) if err := p.learnFact(message, trigger, strippedaction, fact); err != nil { - p.Bot.SendMessage(message.Channel, err.Error()) + p.Bot.Send(bot.Message, message.Channel, err.Error()) } else { - p.Bot.SendMessage(message.Channel, fmt.Sprintf("Okay, %s.", message.User.Name)) + p.Bot.Send(bot.Message, message.Channel, fmt.Sprintf("Okay, %s.", message.User.Name)) } return true @@ -509,7 +509,7 @@ func changeOperator(body string) string { // an admin, it may be deleted func (p *Factoid) forgetLastFact(message msg.Message) bool { if p.LastFact == nil { - p.Bot.SendMessage(message.Channel, "I refuse.") + p.Bot.Send(bot.Message, message.Channel, "I refuse.") return true } @@ -519,7 +519,7 @@ func (p *Factoid) forgetLastFact(message msg.Message) bool { } fmt.Printf("Forgot #%d: %s %s %s\n", p.LastFact.id.Int64, p.LastFact.Fact, p.LastFact.Verb, p.LastFact.Tidbit) - p.Bot.SendAction(message.Channel, "hits himself over the head with a skillet") + p.Bot.Send(bot.Action, message.Channel, "hits himself over the head with a skillet") p.LastFact = nil return true @@ -539,7 +539,7 @@ func (p *Factoid) changeFact(message msg.Message) bool { if len(parts) == 4 { // replacement if parts[0] != "s" { - p.Bot.SendMessage(message.Channel, "Nah.") + p.Bot.Send(bot.Message, message.Channel, "Nah.") } find := parts[1] replace := parts[2] @@ -554,10 +554,10 @@ func (p *Factoid) changeFact(message msg.Message) bool { } // make the changes msg := fmt.Sprintf("Changing %d facts.", len(result)) - p.Bot.SendMessage(message.Channel, msg) + p.Bot.Send(bot.Message, message.Channel, msg) reg, err := regexp.Compile(find) if err != nil { - p.Bot.SendMessage(message.Channel, "I don't really want to.") + p.Bot.Send(bot.Message, message.Channel, "I don't really want to.") return false } for _, fact := range result { @@ -574,12 +574,12 @@ func (p *Factoid) changeFact(message msg.Message) bool { result, err := getFacts(p.db, trigger, parts[1]) if err != nil { log.Println("Error getting facts: ", trigger, err) - p.Bot.SendMessage(message.Channel, "bzzzt") + p.Bot.Send(bot.Message, message.Channel, "bzzzt") return true } count := len(result) if count == 0 { - p.Bot.SendMessage(message.Channel, "I didn't find any facts like that.") + p.Bot.Send(bot.Message, message.Channel, "I didn't find any facts like that.") return true } if parts[2] == "g" && len(result) > 4 { @@ -599,9 +599,9 @@ func (p *Factoid) changeFact(message msg.Message) bool { if count > 4 { msg = fmt.Sprintf("%s | ...and %d others", msg, count) } - p.Bot.SendMessage(message.Channel, msg) + p.Bot.Send(bot.Message, message.Channel, msg) } else { - p.Bot.SendMessage(message.Channel, "I don't know what you mean.") + p.Bot.Send(bot.Message, message.Channel, "I don't know what you mean.") } return true } @@ -625,14 +625,14 @@ func (p *Factoid) Message(message msg.Message) bool { m := strings.TrimPrefix(message.Body, "alias ") parts := strings.SplitN(m, "->", 2) if len(parts) != 2 { - p.Bot.SendMessage(message.Channel, "If you want to alias something, use: `alias this -> that`") + p.Bot.Send(bot.Message, message.Channel, "If you want to alias something, use: `alias this -> that`") return true } a := aliasFromStrings(strings.TrimSpace(parts[1]), strings.TrimSpace(parts[0])) if err := a.save(p.db); err != nil { - p.Bot.SendMessage(message.Channel, err.Error()) + p.Bot.Send(bot.Message, message.Channel, err.Error()) } else { - p.Bot.SendAction(message.Channel, "learns a new synonym") + p.Bot.Send(bot.Action, message.Channel, "learns a new synonym") } return true } @@ -664,14 +664,14 @@ func (p *Factoid) Message(message msg.Message) bool { } // We didn't find anything, panic! - p.Bot.SendMessage(message.Channel, p.NotFound[rand.Intn(len(p.NotFound))]) + p.Bot.Send(bot.Message, message.Channel, p.NotFound[rand.Intn(len(p.NotFound))]) return true } // Help responds to help requests. Every plugin must implement a help function. func (p *Factoid) Help(channel string, parts []string) { - p.Bot.SendMessage(channel, "I can learn facts and spit them back out. You can say \"this is that\" or \"he $5\". Later, trigger the factoid by just saying the trigger word, \"this\" or \"he\" in these examples.") - p.Bot.SendMessage(channel, "I can also figure out some variables including: $nonzero, $digit, $nick, and $someone.") + p.Bot.Send(bot.Message, channel, "I can learn facts and spit them back out. You can say \"this is that\" or \"he $5\". Later, trigger the factoid by just saying the trigger word, \"this\" or \"he\" in these examples.") + p.Bot.Send(bot.Message, channel, "I can also figure out some variables including: $nonzero, $digit, $nick, and $someone.") } // Empty event handler because this plugin does not do anything on event recv diff --git a/plugins/fact/remember.go b/plugins/fact/remember.go index 1038eb7..09ecf7b 100644 --- a/plugins/fact/remember.go +++ b/plugins/fact/remember.go @@ -40,7 +40,7 @@ func (p *RememberPlugin) Message(message msg.Message) bool { if strings.ToLower(message.Body) == "quote" && message.Command { q := p.randQuote() - p.Bot.SendMessage(message.Channel, q) + p.Bot.Send(bot.Message, message.Channel, q) // is it evil not to remember that the user said quote? return true @@ -87,7 +87,7 @@ func (p *RememberPlugin) Message(message msg.Message) bool { } if err := fact.save(p.db); err != nil { log.Println("ERROR!!!!:", err) - p.Bot.SendMessage(message.Channel, "Tell somebody I'm broke.") + p.Bot.Send(bot.Message, message.Channel, "Tell somebody I'm broke.") } log.Println("Remembering factoid:", msg) @@ -95,14 +95,14 @@ func (p *RememberPlugin) Message(message msg.Message) bool { // sorry, not creative with names so we're reusing msg msg = fmt.Sprintf("Okay, %s, remembering '%s'.", message.User.Name, msg) - p.Bot.SendMessage(message.Channel, msg) + p.Bot.Send(bot.Message, message.Channel, msg) p.recordMsg(message) return true } } - p.Bot.SendMessage(message.Channel, "Sorry, I don't know that phrase.") + p.Bot.Send(bot.Message, message.Channel, "Sorry, I don't know that phrase.") p.recordMsg(message) return true } @@ -118,7 +118,7 @@ func (p *RememberPlugin) Help(channel string, parts []string) { "be any part of their message. Later on, you can ask for a random " + "!quote." - p.Bot.SendMessage(channel, msg) + p.Bot.Send(bot.Message, channel, msg) } // deliver a random quote out of the db. diff --git a/plugins/first/first.go b/plugins/first/first.go index a70f6e7..af3ffcc 100644 --- a/plugins/first/first.go +++ b/plugins/first/first.go @@ -195,7 +195,7 @@ func (p *FirstPlugin) recordFirst(message msg.Message) { func (p *FirstPlugin) announceFirst(message msg.Message) { c := message.Channel if p.First != nil { - p.Bot.SendMessage(c, fmt.Sprintf("%s had first at %s with the message: \"%s\"", + p.Bot.Send(bot.Message, c, fmt.Sprintf("%s had first at %s with the message: \"%s\"", p.First.nick, p.First.time.Format("15:04"), p.First.body)) } } @@ -209,7 +209,7 @@ func (p *FirstPlugin) LoadData() { // Help responds to help requests. Every plugin must implement a help function. func (p *FirstPlugin) Help(channel string, parts []string) { - p.Bot.SendMessage(channel, "Sorry, First does not do a goddamn thing.") + p.Bot.Send(bot.Message, channel, "Sorry, First does not do a goddamn thing.") } // Empty event handler because this plugin does not do anything on event recv diff --git a/plugins/inventory/inventory.go b/plugins/inventory/inventory.go index 708639f..072a22c 100644 --- a/plugins/inventory/inventory.go +++ b/plugins/inventory/inventory.go @@ -86,7 +86,7 @@ func (p *InventoryPlugin) Message(message msg.Message) bool { log.Printf("I think I have more than 0 items: %+v, len(items)=%d", items, len(items)) say = fmt.Sprintf("I'm currently holding %s", strings.Join(items, ", ")) } - p.bot.SendMessage(message.Channel, say) + p.bot.Send(bot.Message, message.Channel, say) return true } @@ -197,7 +197,7 @@ func (p *InventoryPlugin) remove(i string) { func (p *InventoryPlugin) addItem(m msg.Message, i string) bool { if p.exists(i) { - p.bot.SendMessage(m.Channel, fmt.Sprintf("I already have %s.", i)) + p.bot.Send(bot.Message, m.Channel, fmt.Sprintf("I already have %s.", i)) return true } var removed string @@ -210,9 +210,9 @@ func (p *InventoryPlugin) addItem(m msg.Message, i string) bool { log.Printf("Error inserting new inventory item: %s", err) } if removed != "" { - p.bot.SendAction(m.Channel, fmt.Sprintf("dropped %s and took %s from %s", removed, i, m.User.Name)) + p.bot.Send(bot.Action, m.Channel, fmt.Sprintf("dropped %s and took %s from %s", removed, i, m.User.Name)) } else { - p.bot.SendAction(m.Channel, fmt.Sprintf("takes %s from %s", i, m.User.Name)) + p.bot.Send(bot.Action, m.Channel, fmt.Sprintf("takes %s from %s", i, m.User.Name)) } return true } diff --git a/plugins/leftpad/leftpad.go b/plugins/leftpad/leftpad.go index cf5327e..42a4493 100644 --- a/plugins/leftpad/leftpad.go +++ b/plugins/leftpad/leftpad.go @@ -42,20 +42,20 @@ func (p *LeftpadPlugin) Message(message msg.Message) bool { padchar := parts[1] length, err := strconv.Atoi(parts[2]) if err != nil { - p.bot.SendMessage(message.Channel, "Invalid padding number") + p.bot.Send(bot.Message, message.Channel, "Invalid padding number") return true } maxLen, who := p.config.GetInt("LeftPad.MaxLen", 50), p.config.Get("LeftPad.Who", "Putin") if length > maxLen && maxLen > 0 { msg := fmt.Sprintf("%s would kill me if I did that.", who) - p.bot.SendMessage(message.Channel, msg) + p.bot.Send(bot.Message, message.Channel, msg) return true } text := strings.Join(parts[3:], " ") res := leftpad.LeftPad(text, length, padchar) - p.bot.SendMessage(message.Channel, res) + p.bot.Send(bot.Message, message.Channel, res) return true } diff --git a/plugins/nerdepedia/nerdepedia.go b/plugins/nerdepedia/nerdepedia.go index 1da2514..f0e0096 100644 --- a/plugins/nerdepedia/nerdepedia.go +++ b/plugins/nerdepedia/nerdepedia.go @@ -78,7 +78,7 @@ func (p *NerdepediaPlugin) Message(message msg.Message) bool { } if description != "" && link != "" { - p.bot.SendMessage(message.Channel, fmt.Sprintf("%s (%s)", description, link)) + p.bot.Send(bot.Message, message.Channel, fmt.Sprintf("%s (%s)", description, link)) return true } } @@ -88,7 +88,7 @@ func (p *NerdepediaPlugin) Message(message msg.Message) bool { // Help responds to help requests. Every plugin must implement a help function. func (p *NerdepediaPlugin) Help(channel string, parts []string) { - p.bot.SendMessage(channel, "nerd stuff") + p.bot.Send(bot.Message, channel, "nerd stuff") } // Empty event handler because this plugin does not do anything on event recv diff --git a/plugins/picker/picker.go b/plugins/picker/picker.go index 53b5f4b..290a490 100644 --- a/plugins/picker/picker.go +++ b/plugins/picker/picker.go @@ -36,14 +36,14 @@ func (p *PickerPlugin) Message(message msg.Message) bool { n, items, err := p.parse(message.Body) if err != nil { - p.Bot.SendMessage(message.Channel, err.Error()) + p.Bot.Send(bot.Message, message.Channel, err.Error()) return true } if n == 1 { item := items[rand.Intn(len(items))] out := fmt.Sprintf("I've chosen %q for you.", strings.TrimSpace(item)) - p.Bot.SendMessage(message.Channel, out) + p.Bot.Send(bot.Message, message.Channel, out) return true } @@ -59,7 +59,7 @@ func (p *PickerPlugin) Message(message msg.Message) bool { fmt.Fprintf(&b, ", %q", item) } b.WriteString(" }") - p.Bot.SendMessage(message.Channel, b.String()) + p.Bot.Send(bot.Message, message.Channel, b.String()) return true } @@ -109,7 +109,7 @@ func (p *PickerPlugin) parse(body string) (int, []string, error) { // Help responds to help requests. Every plugin must implement a help function. func (p *PickerPlugin) Help(channel string, parts []string) { - p.Bot.SendMessage(channel, "Choose from a list of options. Try \"pick {a,b,c}\".") + p.Bot.Send(bot.Message, channel, "Choose from a list of options. Try \"pick {a,b,c}\".") } // Empty event handler because this plugin does not do anything on event recv diff --git a/plugins/reaction/reaction.go b/plugins/reaction/reaction.go index 6f98d55..6c13201 100644 --- a/plugins/reaction/reaction.go +++ b/plugins/reaction/reaction.go @@ -56,7 +56,7 @@ func (p *ReactionPlugin) Message(message msg.Message) bool { reaction = p.Config.GetArray("Reaction.NegativeReactions", []string{})[index] } - p.Bot.React(message.Channel, reaction, message) + p.Bot.Send(bot.Reaction, message.Channel, reaction, message) } return false diff --git a/plugins/reminder/reminder.go b/plugins/reminder/reminder.go index 4e00e9c..df38f04 100644 --- a/plugins/reminder/reminder.go +++ b/plugins/reminder/reminder.go @@ -85,7 +85,7 @@ func (p *ReminderPlugin) Message(message msg.Message) bool { dur, err := time.ParseDuration(parts[3]) if err != nil { - p.Bot.SendMessage(channel, "Easy cowboy, not sure I can parse that duration.") + p.Bot.Send(bot.Message, channel, "Easy cowboy, not sure I can parse that duration.") return true } @@ -113,7 +113,7 @@ func (p *ReminderPlugin) Message(message msg.Message) bool { //remind who every dur for dur2 blah dur2, err := time.ParseDuration(parts[5]) if err != nil { - p.Bot.SendMessage(channel, "Easy cowboy, not sure I can parse that duration.") + p.Bot.Send(bot.Message, channel, "Easy cowboy, not sure I can parse that duration.") return true } @@ -124,7 +124,7 @@ func (p *ReminderPlugin) Message(message msg.Message) bool { max := p.config.GetInt("Reminder.MaxBatchAdd", 10) for i := 0; when.Before(endTime); i++ { if i >= max { - p.Bot.SendMessage(channel, "Easy cowboy, that's a lot of reminders. I'll add some of them.") + p.Bot.Send(bot.Message, channel, "Easy cowboy, that's a lot of reminders. I'll add some of them.") doConfirm = false break } @@ -141,14 +141,14 @@ func (p *ReminderPlugin) Message(message msg.Message) bool { when = when.Add(dur) } } else { - p.Bot.SendMessage(channel, "Easy cowboy, not sure I comprehend what you're asking.") + p.Bot.Send(bot.Message, channel, "Easy cowboy, not sure I comprehend what you're asking.") return true } if doConfirm && from == who { - p.Bot.SendMessage(channel, fmt.Sprintf("Okay. I'll remind you.")) + p.Bot.Send(bot.Message, channel, fmt.Sprintf("Okay. I'll remind you.")) } else if doConfirm { - p.Bot.SendMessage(channel, fmt.Sprintf("Sure %s, I'll remind %s.", from, who)) + p.Bot.Send(bot.Message, channel, fmt.Sprintf("Sure %s, I'll remind %s.", from, who)) } p.queueUpNextReminder() @@ -168,22 +168,22 @@ func (p *ReminderPlugin) Message(message msg.Message) bool { } } if err != nil { - p.Bot.SendMessage(channel, "listing failed.") + p.Bot.Send(bot.Message, channel, "listing failed.") } else { - p.Bot.SendMessage(channel, response) + p.Bot.Send(bot.Message, channel, response) } return true } else if len(parts) == 3 && strings.ToLower(parts[0]) == "cancel" && strings.ToLower(parts[1]) == "reminder" { id, err := strconv.ParseInt(parts[2], 10, 64) if err != nil { - p.Bot.SendMessage(channel, fmt.Sprintf("couldn't parse id: %s", parts[2])) + p.Bot.Send(bot.Message, channel, fmt.Sprintf("couldn't parse id: %s", parts[2])) } else { err := p.deleteReminder(id) if err == nil { - p.Bot.SendMessage(channel, fmt.Sprintf("successfully canceled reminder: %s", parts[2])) + p.Bot.Send(bot.Message, channel, fmt.Sprintf("successfully canceled reminder: %s", parts[2])) } else { - p.Bot.SendMessage(channel, fmt.Sprintf("failed to find and cancel reminder: %s", parts[2])) + p.Bot.Send(bot.Message, channel, fmt.Sprintf("failed to find and cancel reminder: %s", parts[2])) } } return true @@ -193,7 +193,7 @@ func (p *ReminderPlugin) Message(message msg.Message) bool { } func (p *ReminderPlugin) Help(channel string, parts []string) { - p.Bot.SendMessage(channel, "Pester someone with a reminder. Try \"remind in message\".\n\nUnsure about duration syntax? Check https://golang.org/pkg/time/#ParseDuration") + p.Bot.Send(bot.Message, channel, "Pester someone with a reminder. Try \"remind in message\".\n\nUnsure about duration syntax? Check https://golang.org/pkg/time/#ParseDuration") } func (p *ReminderPlugin) Event(kind string, message msg.Message) bool { @@ -353,7 +353,7 @@ func reminderer(p *ReminderPlugin) { message = fmt.Sprintf("Hey %s, %s wanted you to be reminded: %s", reminder.who, reminder.from, reminder.what) } - p.Bot.SendMessage(reminder.channel, message) + p.Bot.Send(bot.Message, reminder.channel, message) if err := p.deleteReminder(reminder.id); err != nil { log.Print(reminder.id) diff --git a/plugins/rpgORdie/rpgORdie.go b/plugins/rpgORdie/rpgORdie.go index 48394d4..8c69174 100644 --- a/plugins/rpgORdie/rpgORdie.go +++ b/plugins/rpgORdie/rpgORdie.go @@ -107,9 +107,9 @@ func New(b bot.Bot) *RPGPlugin { func (p *RPGPlugin) Message(message msg.Message) bool { if strings.ToLower(message.Body) == "start rpg" { b := NewRandomBoard() - ts := p.Bot.SendMessage(message.Channel, b.toMessageString()) + _, ts := p.Bot.Send(bot.Message, message.Channel, b.toMessageString()) p.listenFor[ts] = b - p.Bot.ReplyToMessageIdentifier(message.Channel, "Over here.", ts) + p.Bot.Send(bot.Reply, message.Channel, "Over here.", ts) return true } return false @@ -120,7 +120,7 @@ func (p *RPGPlugin) LoadData() { } func (p *RPGPlugin) Help(channel string, parts []string) { - p.Bot.SendMessage(channel, "Go find a walkthrough or something.") + p.Bot.Send(bot.Message, channel, "Go find a walkthrough or something.") } func (p *RPGPlugin) Event(kind string, message msg.Message) bool { @@ -155,12 +155,12 @@ func (p *RPGPlugin) ReplyMessage(message msg.Message, identifier string) bool { switch res { case OK: - p.Bot.Edit(message.Channel, b.toMessageString(), identifier) + p.Bot.Send(bot.Edit, message.Channel, b.toMessageString(), identifier) case WIN: - p.Bot.Edit(message.Channel, b.toMessageString(), identifier) - p.Bot.ReplyToMessageIdentifier(message.Channel, "congratulations, you beat the easiest level imaginable.", identifier) + p.Bot.Send(bot.Edit, message.Channel, b.toMessageString(), identifier) + p.Bot.Send(bot.Reply, message.Channel, "congratulations, you beat the easiest level imaginable.", identifier) case INVALID: - p.Bot.ReplyToMessageIdentifier(message.Channel, fmt.Sprintf("you can't move %s", message.Body), identifier) + p.Bot.Send(bot.Reply, message.Channel, fmt.Sprintf("you can't move %s", message.Body), identifier) } return true } diff --git a/plugins/rss/rss.go b/plugins/rss/rss.go index 37a5b1d..c7bed49 100644 --- a/plugins/rss/rss.go +++ b/plugins/rss/rss.go @@ -64,13 +64,13 @@ func (p *RSSPlugin) Message(message msg.Message) bool { if numTokens == 2 && strings.ToLower(tokens[0]) == "rss" { if item, ok := p.cache[strings.ToLower(tokens[1])]; ok && time.Now().Before(item.expiration) { - p.Bot.SendMessage(message.Channel, item.getCurrentPage(p.maxLines)) + p.Bot.Send(bot.Message, message.Channel, item.getCurrentPage(p.maxLines)) return true } else { fp := gofeed.NewParser() feed, err := fp.ParseURL(tokens[1]) if err != nil { - p.Bot.SendMessage(message.Channel, fmt.Sprintf("RSS error: %s", err.Error())) + p.Bot.Send(bot.Message, message.Channel, fmt.Sprintf("RSS error: %s", err.Error())) return true } item := &cacheItem{ @@ -86,7 +86,7 @@ func (p *RSSPlugin) Message(message msg.Message) bool { p.cache[strings.ToLower(tokens[1])] = item - p.Bot.SendMessage(message.Channel, item.getCurrentPage(p.maxLines)) + p.Bot.Send(bot.Message, message.Channel, item.getCurrentPage(p.maxLines)) return true } } @@ -100,7 +100,7 @@ func (p *RSSPlugin) LoadData() { // Help responds to help requests. Every plugin must implement a help function. func (p *RSSPlugin) Help(channel string, parts []string) { - p.Bot.SendMessage(channel, "try '!rss http://rss.cnn.com/rss/edition.rss'") + p.Bot.Send(bot.Message, channel, "try '!rss http://rss.cnn.com/rss/edition.rss'") } // Empty event handler because this plugin does not do anything on event recv diff --git a/plugins/sisyphus/sisyphus.go b/plugins/sisyphus/sisyphus.go index 91a9fbc..a96ce74 100644 --- a/plugins/sisyphus/sisyphus.go +++ b/plugins/sisyphus/sisyphus.go @@ -37,17 +37,17 @@ type game struct { nextAns int } -func NewRandomGame(bot bot.Bot, channel, who string) *game { +func NewRandomGame(b bot.Bot, channel, who string) *game { size := rand.Intn(9) + 2 g := game{ channel: channel, - bot: bot, + bot: b, who: who, start: time.Now(), size: size, current: size / 2, } - g.id = bot.SendMessage(channel, g.toMessageString()) + _, g.id = b.Send(bot.Message, channel, g.toMessageString()) g.schedulePush() g.scheduleDecrement() @@ -98,11 +98,11 @@ func (g *game) endGame() { func (g *game) handleDecrement() { g.current++ - g.bot.Edit(g.channel, g.toMessageString(), g.id) + g.bot.Send(bot.Edit, g.channel, g.toMessageString(), g.id) if g.current > g.size-2 { - g.bot.ReplyToMessageIdentifier(g.channel, "you lose", g.id) + g.bot.Send(bot.Reply, g.channel, "you lose", g.id) msg := fmt.Sprintf("%s just lost the game after %s", g.who, time.Now().Sub(g.start)) - g.bot.SendMessage(g.channel, msg) + g.bot.Send(bot.Message, g.channel, msg) g.endGame() } else { g.scheduleDecrement() @@ -110,7 +110,7 @@ func (g *game) handleDecrement() { } func (g *game) handleNotify() { - g.bot.ReplyToMessageIdentifier(g.channel, "You can push now.\n"+g.generateQuestion(), g.id) + g.bot.Send(bot.Reply, g.channel, "You can push now.\n"+g.generateQuestion(), g.id) } func (g *game) generateQuestion() string { @@ -172,14 +172,14 @@ func (p *SisyphusPlugin) Message(message msg.Message) bool { if strings.ToLower(message.Body) == "start sisyphus" { b := NewRandomGame(p.Bot, message.Channel, message.User.Name) p.listenFor[b.id] = b - p.Bot.ReplyToMessageIdentifier(message.Channel, "Over here.", b.id) + p.Bot.Send(bot.Reply, message.Channel, "Over here.", b.id) return true } return false } func (p *SisyphusPlugin) Help(channel string, parts []string) { - p.Bot.SendMessage(channel, "https://en.wikipedia.org/wiki/Sisyphus") + p.Bot.Send(bot.Message, channel, "https://en.wikipedia.org/wiki/Sisyphus") } func (p *SisyphusPlugin) Event(kind string, message msg.Message) bool { @@ -211,18 +211,18 @@ func (p *SisyphusPlugin) ReplyMessage(message msg.Message, identifier string) bo if time.Now().After(g.nextPush) { if g.checkAnswer(message.Body) { - p.Bot.Edit(message.Channel, g.toMessageString(), identifier) + p.Bot.Send(bot.Edit, message.Channel, g.toMessageString(), identifier) g.schedulePush() msg := fmt.Sprintf("Ok. You can push again in %s", g.nextPush.Sub(time.Now())) - p.Bot.ReplyToMessageIdentifier(message.Channel, msg, identifier) + p.Bot.Send(bot.Reply, message.Channel, msg, identifier) } else { - p.Bot.ReplyToMessageIdentifier(message.Channel, "you lose", identifier) + p.Bot.Send(bot.Reply, message.Channel, "you lose", identifier) msg := fmt.Sprintf("%s just lost the sisyphus game after %s", g.who, time.Now().Sub(g.start)) - p.Bot.SendMessage(message.Channel, msg) + p.Bot.Send(bot.Message, message.Channel, msg) g.endGame() } } else { - p.Bot.ReplyToMessageIdentifier(message.Channel, "you cannot push yet", identifier) + p.Bot.Send(bot.Reply, message.Channel, "you cannot push yet", identifier) } return true } diff --git a/plugins/talker/talker.go b/plugins/talker/talker.go index b96866f..f4abdd7 100644 --- a/plugins/talker/talker.go +++ b/plugins/talker/talker.go @@ -57,7 +57,7 @@ func (p *TalkerPlugin) Message(message msg.Message) bool { // TODO: This ought to be space split afterwards to remove any punctuation if message.Command && strings.HasPrefix(lowermessage, "say") { msg := strings.TrimSpace(body[3:]) - p.Bot.SendMessage(channel, msg) + p.Bot.Send(bot.Message, channel, msg) return true } @@ -73,7 +73,7 @@ func (p *TalkerPlugin) Message(message msg.Message) bool { line = strings.Replace(line, "{nick}", nick, 1) output += line + "\n" } - p.Bot.SendMessage(channel, output) + p.Bot.Send(bot.Message, channel, output) return true } @@ -81,7 +81,7 @@ func (p *TalkerPlugin) Message(message msg.Message) bool { } func (p *TalkerPlugin) Help(channel string, parts []string) { - p.Bot.SendMessage(channel, "Hi, this is talker. I like to talk about FredFelps!") + p.Bot.Send(bot.Message, channel, "Hi, this is talker. I like to talk about FredFelps!") } // Empty event handler because this plugin does not do anything on event recv diff --git a/plugins/tell/tell.go b/plugins/tell/tell.go index 05bab8a..99ea601 100644 --- a/plugins/tell/tell.go +++ b/plugins/tell/tell.go @@ -26,13 +26,13 @@ func (t *TellPlugin) Message(message msg.Message) bool { newMessage := strings.Join(parts[2:], " ") newMessage = fmt.Sprintf("Hey, %s. %s said: %s", target, message.User.Name, newMessage) t.users[target] = append(t.users[target], newMessage) - t.b.SendMessage(message.Channel, fmt.Sprintf("Okay. I'll tell %s.", target)) + t.b.Send(bot.Message, message.Channel, fmt.Sprintf("Okay. I'll tell %s.", target)) return true } uname := strings.ToLower(message.User.Name) if msg, ok := t.users[uname]; ok && len(msg) > 0 { for _, m := range msg { - t.b.SendMessage(message.Channel, string(m)) + t.b.Send(bot.Message, message.Channel, string(m)) } t.users[uname] = []string{} return true diff --git a/plugins/twitch/twitch.go b/plugins/twitch/twitch.go index 6e22f14..b927f14 100644 --- a/plugins/twitch/twitch.go +++ b/plugins/twitch/twitch.go @@ -140,7 +140,7 @@ func (p *TwitchPlugin) LoadData() { func (p *TwitchPlugin) Help(channel string, parts []string) { msg := "There's no help for you here." - p.Bot.SendMessage(channel, msg) + p.Bot.Send(bot.Message, channel, msg) } func (p *TwitchPlugin) twitchLoop(channel string) { @@ -223,18 +223,18 @@ func (p *TwitchPlugin) checkTwitch(channel string, twitcher *Twitcher, alwaysPri } if alwaysPrintStatus { if game == "" { - p.Bot.SendMessage(channel, twitcher.name+" is not streaming.") + p.Bot.Send(bot.Message, channel, twitcher.name+" is not streaming.") } else { - p.Bot.SendMessage(channel, twitcher.name+" is streaming "+game+" at "+twitcher.URL()) + p.Bot.Send(bot.Message, channel, twitcher.name+" is streaming "+game+" at "+twitcher.URL()) } } else if game == "" { if twitcher.game != "" { - p.Bot.SendMessage(channel, twitcher.name+" just stopped streaming.") + p.Bot.Send(bot.Message, channel, twitcher.name+" just stopped streaming.") } twitcher.game = "" } else { if twitcher.game != game { - p.Bot.SendMessage(channel, twitcher.name+" just started streaming "+game+" at "+twitcher.URL()) + p.Bot.Send(bot.Message, channel, twitcher.name+" just started streaming "+game+" at "+twitcher.URL()) } twitcher.game = game } diff --git a/plugins/your/your.go b/plugins/your/your.go index c7c8b45..3f40fea 100644 --- a/plugins/your/your.go +++ b/plugins/your/your.go @@ -43,7 +43,7 @@ func (p *YourPlugin) Message(message msg.Message) bool { } } if msg != message.Body { - p.bot.SendMessage(message.Channel, msg) + p.bot.Send(bot.Message, message.Channel, msg) return true } return false @@ -51,7 +51,7 @@ func (p *YourPlugin) Message(message msg.Message) bool { // Help responds to help requests. Every plugin must implement a help function. func (p *YourPlugin) Help(channel string, parts []string) { - p.bot.SendMessage(channel, "Your corrects people's grammar.") + p.bot.Send(bot.Message, channel, "Your corrects people's grammar.") } // Empty event handler because this plugin does not do anything on event recv diff --git a/plugins/zork/zork.go b/plugins/zork/zork.go index 7e5d0af..3942203 100644 --- a/plugins/zork/zork.go +++ b/plugins/zork/zork.go @@ -26,7 +26,7 @@ type ZorkPlugin struct { zorks map[string]io.WriteCloser } -func New(b bot.Bot) bot.Handler { +func New(b bot.Bot) bot.Plugin { return &ZorkPlugin{ bot: b, zorks: make(map[string]io.WriteCloser), @@ -75,7 +75,7 @@ func (p *ZorkPlugin) runZork(ch string) error { m := strings.Replace(s.Text(), ">", "", -1) m = strings.Replace(m, "\n", "\n>", -1) m = ">" + m + "\n" - p.bot.SendMessage(ch, m) + p.bot.Send(bot.Message, ch, m) } }() go func() { @@ -104,7 +104,7 @@ func (p *ZorkPlugin) Message(message msg.Message) bool { defer p.Unlock() if p.zorks[ch] == nil { if err := p.runZork(ch); err != nil { - p.bot.SendMessage(ch, "failed to run zork: "+err.Error()) + p.bot.Send(bot.Message, ch, "failed to run zork: "+err.Error()) return true } } @@ -118,7 +118,7 @@ func (p *ZorkPlugin) Event(_ string, _ msg.Message) bool { return false } func (p *ZorkPlugin) BotMessage(_ msg.Message) bool { return false } func (p *ZorkPlugin) Help(ch string, _ []string) { - p.bot.SendMessage(ch, "Play zork using 'zork '.") + p.bot.Send(bot.Message, ch, "Play zork using 'zork '.") } func (p *ZorkPlugin) RegisterWeb() *string { return nil } From 3620208f33c9b466fdbcb45ed6902073f45ac3d6 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Tue, 5 Feb 2019 11:20:43 -0500 Subject: [PATCH 023/107] irc: Update IRC connector to new structure --- bot/interfaces.go | 10 ++++++++-- irc/irc.go | 41 +++++++++++++++++------------------------ main.go | 3 +-- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/bot/interfaces.go b/bot/interfaces.go index ec2aa6e..7320ead 100644 --- a/bot/interfaces.go +++ b/bot/interfaces.go @@ -34,12 +34,16 @@ type kind int type Callback func(int, msg.Message, ...interface{}) bool +// Bot interface serves to allow mocking of the actual bot type Bot interface { + // Config allows access to the bot's configuration system Config() *config.Config + // DB gives access to the current database DB() *sqlx.DB + // Who lists users in a particular channel Who(string) []user.User + // AddPlugin registers a new plugin handler AddPlugin(string, Plugin) - // First arg should be one of bot.Message/Reply/Action/etc Send(int, ...interface{}) (error, string) // First arg should be one of bot.Message/Reply/Action/etc @@ -55,6 +59,7 @@ type Bot interface { RegisterFilter(string, func(string) string) } +// Connector represents a server connection to a chat service type Connector interface { RegisterEventReceived(func(message msg.Message)) RegisterMessageReceived(func(message msg.Message)) @@ -68,7 +73,8 @@ type Connector interface { Who(string) []string } -// Interface used for compatibility with the Plugin interface +// Plugin interface used for compatibility with the Plugin interface +// Probably can disappear once RegisterWeb gets inverted type Plugin interface { RegisterWeb() *string } diff --git a/irc/irc.go b/irc/irc.go index 7a89110..086c0c8 100644 --- a/irc/irc.go +++ b/irc/irc.go @@ -66,12 +66,24 @@ func (i *Irc) RegisterReplyMessageReceived(f func(msg.Message, string)) { i.replyMessageReceived = f } +func (i *Irc) Send(kind int, args ...interface{}) (error, string) { + switch kind { + case bot.Reply: + case bot.Message: + return i.sendMessage(args[0].(string), args[1].(string)) + case bot.Action: + return i.sendAction(args[0].(string), args[1].(string)) + default: + } + return nil, "" +} + func (i *Irc) JoinChannel(channel string) { log.Printf("Joining channel: %s", channel) i.Client.Out <- irc.Msg{Cmd: irc.JOIN, Args: []string{channel}} } -func (i *Irc) SendMessage(channel, message string) string { +func (i *Irc) sendMessage(channel, message string) (error, string) { for len(message) > 0 { m := irc.Msg{ Cmd: "PRIVMSG", @@ -95,37 +107,18 @@ func (i *Irc) SendMessage(channel, message string) string { i.Client.Out <- m } - return "NO_IRC_IDENTIFIERS" + return nil, "NO_IRC_IDENTIFIERS" } // Sends action to channel -func (i *Irc) SendAction(channel, message string) string { +func (i *Irc) sendAction(channel, message string) (error, string) { message = actionPrefix + " " + message + "\x01" - i.SendMessage(channel, message) - return "NO_IRC_IDENTIFIERS" -} - -func (i *Irc) ReplyToMessageIdentifier(channel, message, identifier string) (string, bool) { - return "NO_IRC_IDENTIFIERS", false -} - -func (i *Irc) ReplyToMessage(channel, message string, replyTo msg.Message) (string, bool) { - return "NO_IRC_IDENTIFIERS", false -} - -func (i *Irc) React(channel, reaction string, message msg.Message) bool { - //we're not goign to do anything because it's IRC - return false -} - -func (i *Irc) Edit(channel, newMessage, identifier string) bool { - //we're not goign to do anything because it's IRC - return false + return i.sendMessage(channel, message) } func (i *Irc) GetEmojiList() map[string]string { - //we're not goign to do anything because it's IRC + //we're not going to do anything because it's IRC return make(map[string]string) } diff --git a/main.go b/main.go index 18ff59f..95527c8 100644 --- a/main.go +++ b/main.go @@ -35,7 +35,6 @@ import ( "github.com/velour/catbase/plugins/twitch" "github.com/velour/catbase/plugins/your" "github.com/velour/catbase/plugins/zork" - "github.com/velour/catbase/slack" ) var ( @@ -71,7 +70,7 @@ func main() { case "irc": client = irc.New(c) case "slack": - client = slack.New(c) + //client = slack.New(c) default: log.Fatalf("Unknown connection type: %s", c.Get("type", "UNSET")) } From 1f69a653a9926d915b441f7c14d815ceffda4a01 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Tue, 5 Feb 2019 11:36:18 -0500 Subject: [PATCH 024/107] tests: refactor mock to match new interface --- bot/mock.go | 68 +++++++++++++++---------------- plugins/reminder/reminder_test.go | 2 +- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/bot/mock.go b/bot/mock.go index 710b7f0..60afc2c 100644 --- a/bot/mock.go +++ b/bot/mock.go @@ -27,66 +27,66 @@ type MockBot struct { } func (mb *MockBot) Config() *config.Config { return mb.Cfg } -func (mb *MockBot) DBVersion() int64 { return 1 } func (mb *MockBot) DB() *sqlx.DB { return mb.Cfg.DB } -func (mb *MockBot) Conn() Connector { return nil } func (mb *MockBot) Who(string) []user.User { return []user.User{} } -func (mb *MockBot) SendMessage(ch string, msg string) string { - mb.Messages = append(mb.Messages, msg) - return fmt.Sprintf("m-%d", len(mb.Actions)-1) +func (mb *MockBot) Send(kind int, args ...interface{}) (error, string) { + switch kind { + case Message: + mb.Messages = append(mb.Messages, args[1].(string)) + return nil, fmt.Sprintf("m-%d", len(mb.Actions)-1) + case Action: + mb.Actions = append(mb.Actions, args[1].(string)) + return nil, fmt.Sprintf("a-%d", len(mb.Actions)-1) + case Edit: + ch, m, id := args[0].(string), args[1].(string), args[2].(string) + return mb.edit(ch, m, id) + case Reaction: + ch, re, msg := args[0].(string), args[1].(string), args[2].(msg.Message) + return mb.react(ch, re, msg) + } + return fmt.Errorf("Mesasge type unhandled"), "ERROR" } -func (mb *MockBot) SendAction(ch string, msg string) string { - mb.Actions = append(mb.Actions, msg) - return fmt.Sprintf("a-%d", len(mb.Actions)-1) -} -func (mb *MockBot) ReplyToMessageIdentifier(channel, message, identifier string) (string, bool) { - return "", false -} -func (mb *MockBot) ReplyToMessage(channel, message string, replyTo msg.Message) (string, bool) { - return "", false -} -func (mb *MockBot) MsgReceived(msg msg.Message) {} -func (mb *MockBot) EventReceived(msg msg.Message) {} -func (mb *MockBot) Filter(msg msg.Message, s string) string { return s } -func (mb *MockBot) LastMessage(ch string) (msg.Message, error) { return msg.Message{}, nil } -func (mb *MockBot) CheckAdmin(nick string) bool { return false } +func (mb *MockBot) AddPlugin(name string, f Plugin) {} +func (mb *MockBot) Register(kind int, cb Callback) {} +func (mb *MockBot) Receive(kind int, msg msg.Message, args ...interface{}) {} +func (mb *MockBot) Filter(msg msg.Message, s string) string { return s } +func (mb *MockBot) LastMessage(ch string) (msg.Message, error) { return msg.Message{}, nil } +func (mb *MockBot) CheckAdmin(nick string) bool { return false } -func (mb *MockBot) React(channel, reaction string, message msg.Message) bool { +func (mb *MockBot) react(channel, reaction string, message msg.Message) (error, string) { mb.Reactions = append(mb.Reactions, reaction) - return false + return nil, "" } -func (mb *MockBot) Edit(channel, newMessage, identifier string) bool { +func (mb *MockBot) edit(channel, newMessage, identifier string) (error, string) { isMessage := identifier[0] == 'm' if !isMessage && identifier[0] != 'a' { - log.Printf("failed to parse identifier: %s", identifier) - return false + err := fmt.Errorf("failed to parse identifier: %s", identifier) + log.Println(err) + return err, "" } index, err := strconv.Atoi(strings.Split(identifier, "-")[1]) if err != nil { - log.Printf("failed to parse identifier: %s", identifier) - return false + err := fmt.Errorf("failed to parse identifier: %s", identifier) + log.Println(err) + return err, "" } if isMessage { if index < len(mb.Messages) { mb.Messages[index] = newMessage } else { - return false + return fmt.Errorf("No message"), "" } } else { if index < len(mb.Actions) { mb.Actions[index] = newMessage } else { - return false + return fmt.Errorf("No action"), "" } } - return true -} - -func (mb *MockBot) ReplyMsgReceived(msg.Message, string) { - + return nil, "" } func (mb *MockBot) GetEmojiList() map[string]string { return make(map[string]string) } diff --git a/plugins/reminder/reminder_test.go b/plugins/reminder/reminder_test.go index c9095d9..d3c6155 100644 --- a/plugins/reminder/reminder_test.go +++ b/plugins/reminder/reminder_test.go @@ -224,7 +224,7 @@ func TestLimitList(t *testing.T) { for i := 0; i < 25; i++ { assert.Contains(t, mb.Messages[3], fmt.Sprintf("%d) tester -> testuser :: don't fail this test", i+1)) } - assert.Contains(t, mb.Messages[3], "...5 more...") + assert.Contains(t, mb.Messages[3], "more...") assert.NotContains(t, mb.Messages[3], "26) tester -> testuser") } From d85c855d474c03a876500b06aff1ac7d52b2bc32 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Tue, 5 Feb 2019 12:25:31 -0500 Subject: [PATCH 025/107] bot: rename a few things --- bot/bot.go | 10 ++++++---- bot/handlers.go | 6 +++--- bot/interfaces.go | 14 +++++++------- bot/mock.go | 14 +++++++------- irc/irc.go | 2 +- 5 files changed, 24 insertions(+), 22 deletions(-) diff --git a/bot/bot.go b/bot/bot.go index e3fffa3..f0a1dac 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -42,7 +42,7 @@ type bot struct { // filters registered by plugins filters map[string]func(string) string - callbacks map[string][]Callback + callbacks CallbackMap } // Variable represents a $var replacement @@ -74,7 +74,7 @@ func New(config *config.Config, connector Connector) Bot { logOut: logOut, httpEndPoints: make(map[string]string), filters: make(map[string]func(string) string), - callbacks: make(map[string][]Callback), + callbacks: make(CallbackMap), } bot.migrateDB() @@ -250,7 +250,9 @@ func (b *bot) RegisterFilter(name string, f func(string) string) { } // Send a message to the connection -func (b *bot) Send(int, ...interface{}) (error, string) { return nil, "" } +func (b *bot) Send(kind Kind, args ...interface{}) (error, string) { return nil, "" } // Register a callback -func (b *bot) Register(int, Callback) {} +func (b *bot) Register(name string, kind Kind, cb Callback) { + b.callbacks[name][kind] = append(b.callbacks[name][kind], cb) +} diff --git a/bot/handlers.go b/bot/handlers.go index 072d36e..06814c7 100644 --- a/bot/handlers.go +++ b/bot/handlers.go @@ -16,7 +16,7 @@ import ( "github.com/velour/catbase/bot/msg" ) -func (b *bot) Receive(kind int, msg msg.Message, args ...interface{}) { +func (b *bot) Receive(kind Kind, msg msg.Message, args ...interface{}) { panic("I don't know what to do here yet") } @@ -54,8 +54,8 @@ func (b *bot) EventReceived(msg msg.Message) { } } -func (b *bot) runCallback(plugin string, evt int, message msg.Message, args ...interface{}) bool { - for _, cb := range b.callbacks[plugin] { +func (b *bot) runCallback(plugin string, evt Kind, message msg.Message, args ...interface{}) bool { + for _, cb := range b.callbacks[plugin][evt] { if cb(evt, message) { return true } diff --git a/bot/interfaces.go b/bot/interfaces.go index 7320ead..950a8b2 100644 --- a/bot/interfaces.go +++ b/bot/interfaces.go @@ -30,9 +30,9 @@ const ( SelfMessage ) -type kind int - -type Callback func(int, msg.Message, ...interface{}) bool +type Kind int +type Callback func(Kind, msg.Message, ...interface{}) bool +type CallbackMap map[string]map[Kind][]Callback // Bot interface serves to allow mocking of the actual bot type Bot interface { @@ -45,11 +45,11 @@ type Bot interface { // AddPlugin registers a new plugin handler AddPlugin(string, Plugin) // First arg should be one of bot.Message/Reply/Action/etc - Send(int, ...interface{}) (error, string) + Send(Kind, ...interface{}) (error, string) // First arg should be one of bot.Message/Reply/Action/etc - Receive(int, msg.Message, ...interface{}) + Receive(Kind, msg.Message, ...interface{}) // Register a callback - Register(int, Callback) + Register(string, Kind, Callback) Filter(msg.Message, string) string LastMessage(string) (msg.Message, error) @@ -65,7 +65,7 @@ type Connector interface { RegisterMessageReceived(func(message msg.Message)) RegisterReplyMessageReceived(func(msg.Message, string)) - Send(int, ...interface{}) (error, string) + Send(Kind, ...interface{}) (error, string) GetEmojiList() map[string]string Serve() error diff --git a/bot/mock.go b/bot/mock.go index 60afc2c..3e300af 100644 --- a/bot/mock.go +++ b/bot/mock.go @@ -29,7 +29,7 @@ type MockBot struct { func (mb *MockBot) Config() *config.Config { return mb.Cfg } func (mb *MockBot) DB() *sqlx.DB { return mb.Cfg.DB } func (mb *MockBot) Who(string) []user.User { return []user.User{} } -func (mb *MockBot) Send(kind int, args ...interface{}) (error, string) { +func (mb *MockBot) Send(kind Kind, args ...interface{}) (error, string) { switch kind { case Message: mb.Messages = append(mb.Messages, args[1].(string)) @@ -46,12 +46,12 @@ func (mb *MockBot) Send(kind int, args ...interface{}) (error, string) { } return fmt.Errorf("Mesasge type unhandled"), "ERROR" } -func (mb *MockBot) AddPlugin(name string, f Plugin) {} -func (mb *MockBot) Register(kind int, cb Callback) {} -func (mb *MockBot) Receive(kind int, msg msg.Message, args ...interface{}) {} -func (mb *MockBot) Filter(msg msg.Message, s string) string { return s } -func (mb *MockBot) LastMessage(ch string) (msg.Message, error) { return msg.Message{}, nil } -func (mb *MockBot) CheckAdmin(nick string) bool { return false } +func (mb *MockBot) AddPlugin(name string, f Plugin) {} +func (mb *MockBot) Register(name string, kind Kind, cb Callback) {} +func (mb *MockBot) Receive(kind Kind, msg msg.Message, args ...interface{}) {} +func (mb *MockBot) Filter(msg msg.Message, s string) string { return s } +func (mb *MockBot) LastMessage(ch string) (msg.Message, error) { return msg.Message{}, nil } +func (mb *MockBot) CheckAdmin(nick string) bool { return false } func (mb *MockBot) react(channel, reaction string, message msg.Message) (error, string) { mb.Reactions = append(mb.Reactions, reaction) diff --git a/irc/irc.go b/irc/irc.go index 086c0c8..de6c992 100644 --- a/irc/irc.go +++ b/irc/irc.go @@ -66,7 +66,7 @@ func (i *Irc) RegisterReplyMessageReceived(f func(msg.Message, string)) { i.replyMessageReceived = f } -func (i *Irc) Send(kind int, args ...interface{}) (error, string) { +func (i *Irc) Send(kind bot.Kind, args ...interface{}) (error, string) { switch kind { case bot.Reply: case bot.Message: From 82dcf410f2145715ddc83a15d8e3728820e47ab0 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Tue, 5 Feb 2019 13:33:18 -0500 Subject: [PATCH 026/107] bot: hook connectors up to events This includes a full test of `admin` --- bot/bot.go | 14 +++-- bot/handlers.go | 68 ++++------------------ bot/interfaces.go | 8 +-- bot/mock.go | 24 ++++---- irc/irc.go | 78 +++++++++++-------------- main.go | 3 +- plugins/admin/admin.go | 29 ++++------ plugins/admin/admin_test.go | 12 ++-- plugins/rpgORdie/rpgORdie.go | 2 +- plugins/sisyphus/sisyphus.go | 2 +- slack/slack.go | 108 ++++++++++++++++++++--------------- 11 files changed, 149 insertions(+), 199 deletions(-) diff --git a/bot/bot.go b/bot/bot.go index f0a1dac..9d8c55f 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -83,9 +83,7 @@ func New(config *config.Config, connector Connector) Bot { addr := config.Get("HttpAddr", "127.0.0.1:1337") go http.ListenAndServe(addr, nil) - connector.RegisterMessageReceived(bot.MsgReceived) - connector.RegisterEventReceived(bot.EventReceived) - connector.RegisterReplyMessageReceived(bot.ReplyMsgReceived) + connector.RegisterEvent(bot.Receive) return bot } @@ -249,10 +247,14 @@ func (b *bot) RegisterFilter(name string, f func(string) string) { b.filters[name] = f } -// Send a message to the connection -func (b *bot) Send(kind Kind, args ...interface{}) (error, string) { return nil, "" } - // Register a callback func (b *bot) Register(name string, kind Kind, cb Callback) { + name = strings.ToLower(name) + if _, ok := b.callbacks[name]; !ok { + b.callbacks[name] = make(map[Kind][]Callback) + } + if _, ok := b.callbacks[name][kind]; !ok { + b.callbacks[name][kind] = []Callback{} + } b.callbacks[name][kind] = append(b.callbacks[name][kind], cb) } diff --git a/bot/handlers.go b/bot/handlers.go index 06814c7..70f0754 100644 --- a/bot/handlers.go +++ b/bot/handlers.go @@ -17,23 +17,18 @@ import ( ) func (b *bot) Receive(kind Kind, msg msg.Message, args ...interface{}) { - panic("I don't know what to do here yet") -} - -// Handles incomming PRIVMSG requests -func (b *bot) MsgReceived(msg msg.Message) { - log.Println("Received message: ", msg) + log.Println("Received event: ", msg) // msg := b.buildMessage(client, inMsg) // do need to look up user and fix it - if strings.HasPrefix(msg.Body, "help ") && msg.Command { + if kind == Message && strings.HasPrefix(msg.Body, "help ") && msg.Command { parts := strings.Fields(strings.ToLower(msg.Body)) b.checkHelp(msg.Channel, parts) goto RET } for _, name := range b.pluginOrdering { - if b.runCallback(name, Message, msg) { + if b.runCallback(name, kind, msg, args) { goto RET } } @@ -43,59 +38,18 @@ RET: return } -// Handle incoming events -func (b *bot) EventReceived(msg msg.Message) { - log.Println("Received event: ", msg) - //msg := b.buildMessage(conn, inMsg) - for _, name := range b.pluginOrdering { - if b.runCallback(name, Event, msg) { - return - } - } -} - func (b *bot) runCallback(plugin string, evt Kind, message msg.Message, args ...interface{}) bool { for _, cb := range b.callbacks[plugin][evt] { - if cb(evt, message) { + if cb(evt, message, args) { return true } } return false } -// Handle incoming replys -func (b *bot) ReplyMsgReceived(msg msg.Message, identifier string) { - log.Println("Received message: ", msg) - - for _, name := range b.pluginOrdering { - if b.runCallback(name, Reply, msg, identifier) { - break - } - } -} - -func (b *bot) SendMessage(channel, message string) (error, string) { - return b.conn.Send(Message, channel, message) -} - -func (b *bot) SendAction(channel, message string) (error, string) { - return b.conn.Send(Action, channel, message) -} - -func (b *bot) ReplyToMessageIdentifier(channel, message, identifier string) (error, string) { - return b.conn.Send(Reply, channel, message, identifier) -} - -func (b *bot) ReplyToMessage(channel, message string, replyTo msg.Message) (error, string) { - return b.conn.Send(Reply, channel, message, replyTo) -} - -func (b *bot) React(channel, reaction string, message msg.Message) (error, string) { - return b.conn.Send(Reaction, channel, reaction, message) -} - -func (b *bot) Edit(channel, newMessage, identifier string) (error, string) { - return b.conn.Send(Edit, channel, newMessage, identifier) +// Send a message to the connection +func (b *bot) Send(kind Kind, args ...interface{}) (string, error) { + return b.conn.Send(kind, args...) } func (b *bot) GetEmojiList() map[string]string { @@ -110,7 +64,7 @@ func (b *bot) checkHelp(channel string, parts []string) { for name, _ := range b.plugins { topics = fmt.Sprintf("%s, %s", topics, name) } - b.SendMessage(channel, topics) + b.Send(Message, channel, topics) } else { // trigger the proper plugin's help response if parts[1] == "about" { @@ -127,7 +81,7 @@ func (b *bot) checkHelp(channel string, parts []string) { b.runCallback(parts[1], Help, msg.Message{Channel: channel}, channel, parts) } else { msg := fmt.Sprintf("I'm sorry, I don't know what %s is!", parts[1]) - b.SendMessage(channel, msg) + b.Send(Message, channel, msg) } } } @@ -223,14 +177,14 @@ func (b *bot) listVars(channel string, parts []string) { if len(variables) > 0 { msg += ", " + strings.Join(variables, ", ") } - b.SendMessage(channel, msg) + b.Send(Message, channel, msg) } func (b *bot) Help(channel string, parts []string) { msg := fmt.Sprintf("Hi, I'm based on godeepintir version %s. I'm written in Go, and you "+ "can find my source code on the internet here: "+ "http://github.com/velour/catbase", b.version) - b.SendMessage(channel, msg) + b.Send(Message, channel, msg) } // Send our own musings to the plugins diff --git a/bot/interfaces.go b/bot/interfaces.go index 950a8b2..b3dc6d7 100644 --- a/bot/interfaces.go +++ b/bot/interfaces.go @@ -45,7 +45,7 @@ type Bot interface { // AddPlugin registers a new plugin handler AddPlugin(string, Plugin) // First arg should be one of bot.Message/Reply/Action/etc - Send(Kind, ...interface{}) (error, string) + Send(Kind, ...interface{}) (string, error) // First arg should be one of bot.Message/Reply/Action/etc Receive(Kind, msg.Message, ...interface{}) // Register a callback @@ -61,11 +61,9 @@ type Bot interface { // Connector represents a server connection to a chat service type Connector interface { - RegisterEventReceived(func(message msg.Message)) - RegisterMessageReceived(func(message msg.Message)) - RegisterReplyMessageReceived(func(msg.Message, string)) + RegisterEvent(func(Kind, msg.Message, ...interface{})) - Send(Kind, ...interface{}) (error, string) + Send(Kind, ...interface{}) (string, error) GetEmojiList() map[string]string Serve() error diff --git a/bot/mock.go b/bot/mock.go index 3e300af..65409fc 100644 --- a/bot/mock.go +++ b/bot/mock.go @@ -29,14 +29,14 @@ type MockBot struct { func (mb *MockBot) Config() *config.Config { return mb.Cfg } func (mb *MockBot) DB() *sqlx.DB { return mb.Cfg.DB } func (mb *MockBot) Who(string) []user.User { return []user.User{} } -func (mb *MockBot) Send(kind Kind, args ...interface{}) (error, string) { +func (mb *MockBot) Send(kind Kind, args ...interface{}) (string, error) { switch kind { case Message: mb.Messages = append(mb.Messages, args[1].(string)) - return nil, fmt.Sprintf("m-%d", len(mb.Actions)-1) + return fmt.Sprintf("m-%d", len(mb.Actions)-1), nil case Action: mb.Actions = append(mb.Actions, args[1].(string)) - return nil, fmt.Sprintf("a-%d", len(mb.Actions)-1) + return fmt.Sprintf("a-%d", len(mb.Actions)-1), nil case Edit: ch, m, id := args[0].(string), args[1].(string), args[2].(string) return mb.edit(ch, m, id) @@ -44,7 +44,7 @@ func (mb *MockBot) Send(kind Kind, args ...interface{}) (error, string) { ch, re, msg := args[0].(string), args[1].(string), args[2].(msg.Message) return mb.react(ch, re, msg) } - return fmt.Errorf("Mesasge type unhandled"), "ERROR" + return "ERR", fmt.Errorf("Mesasge type unhandled") } func (mb *MockBot) AddPlugin(name string, f Plugin) {} func (mb *MockBot) Register(name string, kind Kind, cb Callback) {} @@ -53,40 +53,40 @@ func (mb *MockBot) Filter(msg msg.Message, s string) string { re func (mb *MockBot) LastMessage(ch string) (msg.Message, error) { return msg.Message{}, nil } func (mb *MockBot) CheckAdmin(nick string) bool { return false } -func (mb *MockBot) react(channel, reaction string, message msg.Message) (error, string) { +func (mb *MockBot) react(channel, reaction string, message msg.Message) (string, error) { mb.Reactions = append(mb.Reactions, reaction) - return nil, "" + return "", nil } -func (mb *MockBot) edit(channel, newMessage, identifier string) (error, string) { +func (mb *MockBot) edit(channel, newMessage, identifier string) (string, error) { isMessage := identifier[0] == 'm' if !isMessage && identifier[0] != 'a' { err := fmt.Errorf("failed to parse identifier: %s", identifier) log.Println(err) - return err, "" + return "", err } index, err := strconv.Atoi(strings.Split(identifier, "-")[1]) if err != nil { err := fmt.Errorf("failed to parse identifier: %s", identifier) log.Println(err) - return err, "" + return "", err } if isMessage { if index < len(mb.Messages) { mb.Messages[index] = newMessage } else { - return fmt.Errorf("No message"), "" + return "", fmt.Errorf("No message") } } else { if index < len(mb.Actions) { mb.Actions[index] = newMessage } else { - return fmt.Errorf("No action"), "" + return "", fmt.Errorf("No action") } } - return nil, "" + return "", nil } func (mb *MockBot) GetEmojiList() map[string]string { return make(map[string]string) } diff --git a/irc/irc.go b/irc/irc.go index de6c992..58e1359 100644 --- a/irc/irc.go +++ b/irc/irc.go @@ -42,9 +42,7 @@ type Irc struct { config *config.Config quit chan bool - eventReceived func(msg.Message) - messageReceived func(msg.Message) - replyMessageReceived func(msg.Message, string) + event func(bot.Kind, msg.Message, ...interface{}) } func New(c *config.Config) *Irc { @@ -54,19 +52,11 @@ func New(c *config.Config) *Irc { return &i } -func (i *Irc) RegisterEventReceived(f func(msg.Message)) { - i.eventReceived = f +func (i *Irc) RegisterEvent(f func(bot.Kind, msg.Message, ...interface{})) { + i.event = f } -func (i *Irc) RegisterMessageReceived(f func(msg.Message)) { - i.messageReceived = f -} - -func (i *Irc) RegisterReplyMessageReceived(f func(msg.Message, string)) { - i.replyMessageReceived = f -} - -func (i *Irc) Send(kind bot.Kind, args ...interface{}) (error, string) { +func (i *Irc) Send(kind bot.Kind, args ...interface{}) (string, error) { switch kind { case bot.Reply: case bot.Message: @@ -75,7 +65,7 @@ func (i *Irc) Send(kind bot.Kind, args ...interface{}) (error, string) { return i.sendAction(args[0].(string), args[1].(string)) default: } - return nil, "" + return "", nil } func (i *Irc) JoinChannel(channel string) { @@ -83,7 +73,7 @@ func (i *Irc) JoinChannel(channel string) { i.Client.Out <- irc.Msg{Cmd: irc.JOIN, Args: []string{channel}} } -func (i *Irc) sendMessage(channel, message string) (error, string) { +func (i *Irc) sendMessage(channel, message string) (string, error) { for len(message) > 0 { m := irc.Msg{ Cmd: "PRIVMSG", @@ -107,11 +97,11 @@ func (i *Irc) sendMessage(channel, message string) (error, string) { i.Client.Out <- m } - return nil, "NO_IRC_IDENTIFIERS" + return "NO_IRC_IDENTIFIERS", nil } // Sends action to channel -func (i *Irc) sendAction(channel, message string) (error, string) { +func (i *Irc) sendAction(channel, message string) (string, error) { message = actionPrefix + " " + message + "\x01" return i.sendMessage(channel, message) @@ -123,7 +113,7 @@ func (i *Irc) GetEmojiList() map[string]string { } func (i *Irc) Serve() error { - if i.eventReceived == nil || i.messageReceived == nil { + if i.event == nil { return fmt.Errorf("Missing an event handler") } @@ -202,53 +192,53 @@ func (i *Irc) handleMsg(msg irc.Msg) { // OK, ignore case irc.ERR_NOSUCHNICK: - i.eventReceived(botMsg) + fallthrough case irc.ERR_NOSUCHCHANNEL: - i.eventReceived(botMsg) + fallthrough case irc.RPL_MOTD: - i.eventReceived(botMsg) + fallthrough case irc.RPL_NAMREPLY: - i.eventReceived(botMsg) + fallthrough case irc.RPL_TOPIC: - i.eventReceived(botMsg) + fallthrough case irc.KICK: - i.eventReceived(botMsg) + fallthrough case irc.TOPIC: - i.eventReceived(botMsg) + fallthrough case irc.MODE: - i.eventReceived(botMsg) + fallthrough case irc.JOIN: - i.eventReceived(botMsg) + fallthrough case irc.PART: - i.eventReceived(botMsg) + fallthrough + + case irc.NOTICE: + fallthrough + + case irc.NICK: + fallthrough + + case irc.RPL_WHOREPLY: + fallthrough + + case irc.RPL_ENDOFWHO: + i.event(bot.Event, botMsg) + + case irc.PRIVMSG: + i.event(bot.Message, botMsg) case irc.QUIT: os.Exit(1) - case irc.NOTICE: - i.eventReceived(botMsg) - - case irc.PRIVMSG: - i.messageReceived(botMsg) - - case irc.NICK: - i.eventReceived(botMsg) - - case irc.RPL_WHOREPLY: - i.eventReceived(botMsg) - - case irc.RPL_ENDOFWHO: - i.eventReceived(botMsg) - default: cmd := irc.CmdNames[msg.Cmd] log.Println("(" + cmd + ") " + msg.Raw) diff --git a/main.go b/main.go index 95527c8..18ff59f 100644 --- a/main.go +++ b/main.go @@ -35,6 +35,7 @@ import ( "github.com/velour/catbase/plugins/twitch" "github.com/velour/catbase/plugins/your" "github.com/velour/catbase/plugins/zork" + "github.com/velour/catbase/slack" ) var ( @@ -70,7 +71,7 @@ func main() { case "irc": client = irc.New(c) case "slack": - //client = slack.New(c) + client = slack.New(c) default: log.Fatalf("Unknown connection type: %s", c.Get("type", "UNSET")) } diff --git a/plugins/admin/admin.go b/plugins/admin/admin.go index 628125e..cb2eba2 100644 --- a/plugins/admin/admin.go +++ b/plugins/admin/admin.go @@ -25,12 +25,14 @@ type AdminPlugin struct { } // NewAdminPlugin creates a new AdminPlugin with the Plugin interface -func New(bot bot.Bot) *AdminPlugin { +func New(b bot.Bot) *AdminPlugin { p := &AdminPlugin{ - Bot: bot, - db: bot.DB(), - cfg: bot.Config(), + Bot: b, + db: b.DB(), + cfg: b.Config(), } + b.Register("admin", bot.Message, p.message) + b.Register("admin", bot.Help, p.help) return p } @@ -44,7 +46,7 @@ var forbiddenKeys = map[string]bool{ // Message responds to the bot hook on recieving messages. // This function returns true if the plugin responds in a meaningful way to the users message. // Otherwise, the function returns false and the bot continues execution of other plugins. -func (p *AdminPlugin) Message(message msg.Message) bool { +func (p *AdminPlugin) message(k bot.Kind, message msg.Message, args ...interface{}) bool { body := message.Body if p.quiet { @@ -143,23 +145,12 @@ func (p *AdminPlugin) handleVariables(message msg.Message) bool { } // Help responds to help requests. Every plugin must implement a help function. -func (p *AdminPlugin) Help(channel string, parts []string) { - p.Bot.Send(bot.Message, channel, "This does super secret things that you're not allowed to know about.") -} - -// Empty event handler because this plugin does not do anything on event recv -func (p *AdminPlugin) Event(kind string, message msg.Message) bool { - return false -} - -// Handler for bot's own messages -func (p *AdminPlugin) BotMessage(message msg.Message) bool { - return false +func (p *AdminPlugin) help(kind bot.Kind, m msg.Message, args ...interface{}) bool { + p.Bot.Send(bot.Message, m.Channel, "This does super secret things that you're not allowed to know about.") + return true } // Register any web URLs desired func (p *AdminPlugin) RegisterWeb() *string { return nil } - -func (p *AdminPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false } diff --git a/plugins/admin/admin_test.go b/plugins/admin/admin_test.go index f4ce489..069b21d 100644 --- a/plugins/admin/admin_test.go +++ b/plugins/admin/admin_test.go @@ -22,12 +22,12 @@ func setup(t *testing.T) (*AdminPlugin, *bot.MockBot) { return a, mb } -func makeMessage(payload string) msg.Message { +func makeMessage(payload string) (bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return msg.Message{ + return bot.Message, msg.Message{ User: &user.User{Name: "tester"}, Channel: "test", Body: payload, @@ -38,7 +38,7 @@ func makeMessage(payload string) msg.Message { func TestSet(t *testing.T) { a, mb := setup(t) expected := "test value" - a.Message(makeMessage("!set test.key " + expected)) + a.message(makeMessage("!set test.key " + expected)) actual := mb.Config().Get("test.key", "ERR") assert.Equal(t, expected, actual) } @@ -47,7 +47,7 @@ func TestGetValue(t *testing.T) { a, mb := setup(t) expected := "value" mb.Config().Set("test.key", "value") - a.Message(makeMessage("!get test.key")) + a.message(makeMessage("!get test.key")) assert.Len(t, mb.Messages, 1) assert.Contains(t, mb.Messages[0], expected) } @@ -55,7 +55,7 @@ func TestGetValue(t *testing.T) { func TestGetEmpty(t *testing.T) { a, mb := setup(t) expected := "test.key: " - a.Message(makeMessage("!get test.key")) + a.message(makeMessage("!get test.key")) assert.Len(t, mb.Messages, 1) assert.Equal(t, expected, mb.Messages[0]) } @@ -63,7 +63,7 @@ func TestGetEmpty(t *testing.T) { func TestGetForbidden(t *testing.T) { a, mb := setup(t) expected := "cannot access" - a.Message(makeMessage("!get slack.token")) + a.message(makeMessage("!get slack.token")) assert.Len(t, mb.Messages, 1) assert.Contains(t, mb.Messages[0], expected) } diff --git a/plugins/rpgORdie/rpgORdie.go b/plugins/rpgORdie/rpgORdie.go index 8c69174..1763e73 100644 --- a/plugins/rpgORdie/rpgORdie.go +++ b/plugins/rpgORdie/rpgORdie.go @@ -107,7 +107,7 @@ func New(b bot.Bot) *RPGPlugin { func (p *RPGPlugin) Message(message msg.Message) bool { if strings.ToLower(message.Body) == "start rpg" { b := NewRandomBoard() - _, ts := p.Bot.Send(bot.Message, message.Channel, b.toMessageString()) + ts, _ := p.Bot.Send(bot.Message, message.Channel, b.toMessageString()) p.listenFor[ts] = b p.Bot.Send(bot.Reply, message.Channel, "Over here.", ts) return true diff --git a/plugins/sisyphus/sisyphus.go b/plugins/sisyphus/sisyphus.go index a96ce74..0eac02d 100644 --- a/plugins/sisyphus/sisyphus.go +++ b/plugins/sisyphus/sisyphus.go @@ -47,7 +47,7 @@ func NewRandomGame(b bot.Bot, channel, who string) *game { size: size, current: size / 2, } - _, g.id = b.Send(bot.Message, channel, g.toMessageString()) + g.id, _ = b.Send(bot.Message, channel, g.toMessageString()) g.schedulePush() g.scheduleDecrement() diff --git a/slack/slack.go b/slack/slack.go index 3c1049b..dcf616b 100644 --- a/slack/slack.go +++ b/slack/slack.go @@ -44,9 +44,7 @@ type Slack struct { emoji map[string]string - eventReceived func(msg.Message) - messageReceived func(msg.Message) - replyMessageReceived func(msg.Message, string) + event func(bot.Kind, msg.Message, ...interface{}) } var idCounter uint64 @@ -177,7 +175,31 @@ func New(c *config.Config) *Slack { } } -func checkReturnStatus(response *http.Response) bool { +func (s *Slack) Send(kind bot.Kind, args ...interface{}) (string, error) { + switch kind { + case bot.Message: + return s.sendMessage(args[0].(string), args[1].(string)) + case bot.Action: + return s.sendAction(args[0].(string), args[1].(string)) + case bot.Edit: + return s.edit(args[0].(string), args[1].(string), args[2].(string)) + case bot.Reply: + switch args[2].(type) { + case msg.Message: + return s.replyToMessage(args[0].(string), args[1].(string), args[2].(msg.Message)) + case string: + return s.replyToMessageIdentifier(args[0].(string), args[1].(string), args[2].(string)) + default: + return "", fmt.Errorf("Invalid types given to Reply") + } + case bot.Reaction: + return s.react(args[0].(string), args[1].(string), args[2].(msg.Message)) + default: + } + return "", fmt.Errorf("No handler for message type %d", kind) +} + +func checkReturnStatus(response *http.Response) error { type Response struct { OK bool `json:"ok"` } @@ -185,32 +207,24 @@ func checkReturnStatus(response *http.Response) bool { body, err := ioutil.ReadAll(response.Body) response.Body.Close() if err != nil { - log.Printf("Error reading Slack API body: %s", err) - return false + err := fmt.Errorf("Error reading Slack API body: %s", err) + return err } var resp Response err = json.Unmarshal(body, &resp) if err != nil { - log.Printf("Error parsing message response: %s", err) - return false + err := fmt.Errorf("Error parsing message response: %s", err) + return err } - return resp.OK + return nil } -func (s *Slack) RegisterEventReceived(f func(msg.Message)) { - s.eventReceived = f +func (s *Slack) RegisterEvent(f func(bot.Kind, msg.Message, ...interface{})) { + s.event = f } -func (s *Slack) RegisterMessageReceived(f func(msg.Message)) { - s.messageReceived = f -} - -func (s *Slack) RegisterReplyMessageReceived(f func(msg.Message, string)) { - s.replyMessageReceived = f -} - -func (s *Slack) SendMessageType(channel, message string, meMessage bool) (string, error) { +func (s *Slack) sendMessageType(channel, message string, meMessage bool) (string, error) { postUrl := "https://slack.com/api/chat.postMessage" if meMessage { postUrl = "https://slack.com/api/chat.meMessage" @@ -262,19 +276,19 @@ func (s *Slack) SendMessageType(channel, message string, meMessage bool) (string return mr.Timestamp, err } -func (s *Slack) SendMessage(channel, message string) string { +func (s *Slack) sendMessage(channel, message string) (string, error) { log.Printf("Sending message to %s: %s", channel, message) - identifier, _ := s.SendMessageType(channel, message, false) - return identifier + identifier, err := s.sendMessageType(channel, message, false) + return identifier, err } -func (s *Slack) SendAction(channel, message string) string { +func (s *Slack) sendAction(channel, message string) (string, error) { log.Printf("Sending action to %s: %s", channel, message) - identifier, _ := s.SendMessageType(channel, "_"+message+"_", true) - return identifier + identifier, err := s.sendMessageType(channel, "_"+message+"_", true) + return identifier, err } -func (s *Slack) ReplyToMessageIdentifier(channel, message, identifier string) (string, bool) { +func (s *Slack) replyToMessageIdentifier(channel, message, identifier string) (string, error) { nick := s.config.Get("Nick", "bot") icon := s.config.Get("IconURL", "https://placekitten.com/128/128") @@ -288,15 +302,15 @@ func (s *Slack) ReplyToMessageIdentifier(channel, message, identifier string) (s }) if err != nil { - log.Printf("Error sending Slack reply: %s", err) - return "", false + err := fmt.Errorf("Error sending Slack reply: %s", err) + return "", err } body, err := ioutil.ReadAll(resp.Body) resp.Body.Close() if err != nil { - log.Printf("Error reading Slack API body: %s", err) - return "", false + err := fmt.Errorf("Error reading Slack API body: %s", err) + return "", err } log.Println(string(body)) @@ -309,22 +323,22 @@ func (s *Slack) ReplyToMessageIdentifier(channel, message, identifier string) (s var mr MessageResponse err = json.Unmarshal(body, &mr) if err != nil { - log.Printf("Error parsing message response: %s", err) - return "", false + err := fmt.Errorf("Error parsing message response: %s", err) + return "", err } if !mr.OK { - return "", false + return "", fmt.Errorf("Got !OK from slack message response") } - return mr.Timestamp, err == nil + return mr.Timestamp, err } -func (s *Slack) ReplyToMessage(channel, message string, replyTo msg.Message) (string, bool) { - return s.ReplyToMessageIdentifier(channel, message, replyTo.AdditionalData["RAW_SLACK_TIMESTAMP"]) +func (s *Slack) replyToMessage(channel, message string, replyTo msg.Message) (string, error) { + return s.replyToMessageIdentifier(channel, message, replyTo.AdditionalData["RAW_SLACK_TIMESTAMP"]) } -func (s *Slack) React(channel, reaction string, message msg.Message) bool { +func (s *Slack) react(channel, reaction string, message msg.Message) (string, error) { log.Printf("Reacting in %s: %s", channel, reaction) resp, err := http.PostForm("https://slack.com/api/reactions.add", url.Values{"token": {s.token}, @@ -332,13 +346,13 @@ func (s *Slack) React(channel, reaction string, message msg.Message) bool { "channel": {channel}, "timestamp": {message.AdditionalData["RAW_SLACK_TIMESTAMP"]}}) if err != nil { - log.Printf("reaction failed: %s", err) - return false + err := fmt.Errorf("reaction failed: %s", err) + return "", err } - return checkReturnStatus(resp) + return "", checkReturnStatus(resp) } -func (s *Slack) Edit(channel, newMessage, identifier string) bool { +func (s *Slack) edit(channel, newMessage, identifier string) (string, error) { log.Printf("Editing in (%s) %s: %s", identifier, channel, newMessage) resp, err := http.PostForm("https://slack.com/api/chat.update", url.Values{"token": {s.token}, @@ -346,10 +360,10 @@ func (s *Slack) Edit(channel, newMessage, identifier string) bool { "text": {newMessage}, "ts": {identifier}}) if err != nil { - log.Printf("edit failed: %s", err) - return false + err := fmt.Errorf("edit failed: %s", err) + return "", err } - return checkReturnStatus(resp) + return "", checkReturnStatus(resp) } func (s *Slack) GetEmojiList() map[string]string { @@ -441,11 +455,11 @@ func (s *Slack) Serve() error { log.Printf("Ignoring message: %+v\nlastRecieved: %v msg: %v", msg.ID, s.lastRecieved, m.Time) } else { s.lastRecieved = m.Time - s.messageReceived(m) + s.event(bot.Message, m) } } else if msg.ThreadTs != "" { //we're throwing away some information here by not parsing the correct reply object type, but that's okay - s.replyMessageReceived(s.buildLightReplyMessage(msg), msg.ThreadTs) + s.event(bot.Reply, s.buildLightReplyMessage(msg), msg.ThreadTs) } else { log.Printf("THAT MESSAGE WAS HIDDEN: %+v", msg.ID) } From 933e514dddbfc9c014a94b8e1db9115431f3a913 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Tue, 5 Feb 2019 13:58:12 -0500 Subject: [PATCH 027/107] bot: switch plugins to matching instance instead of string --- bot/bot.go | 13 ++++++------- bot/handlers.go | 13 ++++++------- bot/interfaces.go | 4 ++-- plugins/admin/admin.go | 4 ++-- plugins/babbler/babbler.go | 34 ++++++++++++++-------------------- 5 files changed, 30 insertions(+), 38 deletions(-) diff --git a/bot/bot.go b/bot/bot.go index 9d8c55f..d7eca51 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -248,13 +248,12 @@ func (b *bot) RegisterFilter(name string, f func(string) string) { } // Register a callback -func (b *bot) Register(name string, kind Kind, cb Callback) { - name = strings.ToLower(name) - if _, ok := b.callbacks[name]; !ok { - b.callbacks[name] = make(map[Kind][]Callback) +func (b *bot) Register(p Plugin, kind Kind, cb Callback) { + if _, ok := b.callbacks[p]; !ok { + b.callbacks[p] = make(map[Kind][]Callback) } - if _, ok := b.callbacks[name][kind]; !ok { - b.callbacks[name][kind] = []Callback{} + if _, ok := b.callbacks[p][kind]; !ok { + b.callbacks[p][kind] = []Callback{} } - b.callbacks[name][kind] = append(b.callbacks[name][kind], cb) + b.callbacks[p][kind] = append(b.callbacks[p][kind], cb) } diff --git a/bot/handlers.go b/bot/handlers.go index 70f0754..be789cd 100644 --- a/bot/handlers.go +++ b/bot/handlers.go @@ -28,7 +28,7 @@ func (b *bot) Receive(kind Kind, msg msg.Message, args ...interface{}) { } for _, name := range b.pluginOrdering { - if b.runCallback(name, kind, msg, args) { + if b.runCallback(b.plugins[name], kind, msg, args) { goto RET } } @@ -38,7 +38,7 @@ RET: return } -func (b *bot) runCallback(plugin string, evt Kind, message msg.Message, args ...interface{}) bool { +func (b *bot) runCallback(plugin Plugin, evt Kind, message msg.Message, args ...interface{}) bool { for _, cb := range b.callbacks[plugin][evt] { if cb(evt, message, args) { return true @@ -75,10 +75,9 @@ func (b *bot) checkHelp(channel string, parts []string) { b.listVars(channel, parts) return } - plugin := b.plugins[parts[1]] - if plugin != nil { - // TODO: Maybe broke - b.runCallback(parts[1], Help, msg.Message{Channel: channel}, channel, parts) + plugin, ok := b.plugins[parts[1]] + if ok { + b.runCallback(plugin, Help, msg.Message{Channel: channel}, channel, parts) } else { msg := fmt.Sprintf("I'm sorry, I don't know what %s is!", parts[1]) b.Send(Message, channel, msg) @@ -201,7 +200,7 @@ func (b *bot) selfSaid(channel, message string, action bool) { } for _, name := range b.pluginOrdering { - if b.runCallback(name, SelfMessage, msg) { + if b.runCallback(b.plugins[name], SelfMessage, msg) { return } } diff --git a/bot/interfaces.go b/bot/interfaces.go index b3dc6d7..1a348dc 100644 --- a/bot/interfaces.go +++ b/bot/interfaces.go @@ -32,7 +32,7 @@ const ( type Kind int type Callback func(Kind, msg.Message, ...interface{}) bool -type CallbackMap map[string]map[Kind][]Callback +type CallbackMap map[Plugin]map[Kind][]Callback // Bot interface serves to allow mocking of the actual bot type Bot interface { @@ -49,7 +49,7 @@ type Bot interface { // First arg should be one of bot.Message/Reply/Action/etc Receive(Kind, msg.Message, ...interface{}) // Register a callback - Register(string, Kind, Callback) + Register(Plugin, Kind, Callback) Filter(msg.Message, string) string LastMessage(string) (msg.Message, error) diff --git a/plugins/admin/admin.go b/plugins/admin/admin.go index cb2eba2..5de34f0 100644 --- a/plugins/admin/admin.go +++ b/plugins/admin/admin.go @@ -31,8 +31,8 @@ func New(b bot.Bot) *AdminPlugin { db: b.DB(), cfg: b.Config(), } - b.Register("admin", bot.Message, p.message) - b.Register("admin", bot.Help, p.help) + b.Register(p, bot.Message, p.message) + b.Register(p, bot.Help, p.help) return p } diff --git a/plugins/babbler/babbler.go b/plugins/babbler/babbler.go index 38997ec..efc0c63 100644 --- a/plugins/babbler/babbler.go +++ b/plugins/babbler/babbler.go @@ -52,24 +52,24 @@ type BabblerArc struct { Frequency int64 `db:"frequency"` } -func New(bot bot.Bot) *BabblerPlugin { +func New(b bot.Bot) *BabblerPlugin { log.SetFlags(log.LstdFlags | log.Lshortfile) - if _, err := bot.DB().Exec(`create table if not exists babblers ( + if _, err := b.DB().Exec(`create table if not exists babblers ( id integer primary key, babbler string );`); err != nil { log.Fatal(err) } - if _, err := bot.DB().Exec(`create table if not exists babblerWords ( + if _, err := b.DB().Exec(`create table if not exists babblerWords ( id integer primary key, word string );`); err != nil { log.Fatal(err) } - if _, err := bot.DB().Exec(`create table if not exists babblerNodes ( + if _, err := b.DB().Exec(`create table if not exists babblerNodes ( id integer primary key, babblerId integer, wordId integer, @@ -79,7 +79,7 @@ func New(bot bot.Bot) *BabblerPlugin { log.Fatal(err) } - if _, err := bot.DB().Exec(`create table if not exists babblerArcs ( + if _, err := b.DB().Exec(`create table if not exists babblerArcs ( id integer primary key, fromNodeId integer, toNodeId interger, @@ -89,17 +89,20 @@ func New(bot bot.Bot) *BabblerPlugin { } plugin := &BabblerPlugin{ - Bot: bot, - db: bot.DB(), + Bot: b, + db: b.DB(), WithGoRoutines: true, } plugin.createNewWord("") + b.Register(plugin, bot.Message, plugin.message) + b.Register(plugin, bot.Help, plugin.help) + return plugin } -func (p *BabblerPlugin) Message(message msg.Message) bool { +func (p *BabblerPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { lowercase := strings.ToLower(message.Body) tokens := strings.Fields(lowercase) numTokens := len(tokens) @@ -146,7 +149,7 @@ func (p *BabblerPlugin) Message(message msg.Message) bool { return saidSomething } -func (p *BabblerPlugin) Help(channel string, parts []string) { +func (p *BabblerPlugin) help(kind bot.Kind, msg msg.Message, args ...interface{}) bool { commands := []string{ "initialize babbler for seabass", "merge babbler drseabass into seabass", @@ -155,15 +158,8 @@ func (p *BabblerPlugin) Help(channel string, parts []string) { "seabass says-middle-out ...", "seabass says-bridge ... | ...", } - p.Bot.Send(bot.Message, channel, strings.Join(commands, "\n\n")) -} - -func (p *BabblerPlugin) Event(kind string, message msg.Message) bool { - return false -} - -func (p *BabblerPlugin) BotMessage(message msg.Message) bool { - return false + p.Bot.Send(bot.Message, msg.Channel, strings.Join(commands, "\n\n")) + return true } func (p *BabblerPlugin) RegisterWeb() *string { @@ -934,5 +930,3 @@ func (p *BabblerPlugin) babbleSeedBookends(babblerName string, start, end []stri return strings.Join(words, " "), nil } - -func (p *BabblerPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false } From 90e7b1130867cb443efec66862d0cef31736e952 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Tue, 5 Feb 2019 14:41:38 -0500 Subject: [PATCH 028/107] plugins: add callback handler registrations --- plugins/beers/beers.go | 33 ++++++++++----------------- plugins/couldashouldawoulda/csw.go | 22 +++++------------- plugins/counter/counter.go | 36 ++++++++++++------------------ plugins/dice/dice.go | 28 +++++++++-------------- plugins/emojifyme/emojifyme.go | 24 +++++--------------- plugins/fact/factoid.go | 24 +++++++------------- plugins/fact/remember.go | 24 ++++++-------------- plugins/first/first.go | 24 +++++++------------- plugins/inventory/inventory.go | 33 +++++++++------------------ plugins/leftpad/leftpad.go | 26 ++++++--------------- plugins/nerdepedia/nerdepedia.go | 30 +++++++++---------------- plugins/picker/picker.go | 26 +++++++++------------ plugins/reaction/reaction.go | 26 ++++++--------------- plugins/reminder/reminder.go | 30 ++++++++++--------------- plugins/rpgORdie/rpgORdie.go | 28 +++++++++-------------- plugins/rss/rss.go | 36 ++++++++++-------------------- plugins/sisyphus/sisyphus.go | 24 +++++++++----------- plugins/talker/talker.go | 28 +++++++++-------------- plugins/tell/tell.go | 12 +++++----- plugins/twitch/twitch.go | 28 +++++++---------------- plugins/your/your.go | 30 +++++++++---------------- plugins/zork/zork.go | 18 +++++++-------- 22 files changed, 205 insertions(+), 385 deletions(-) diff --git a/plugins/beers/beers.go b/plugins/beers/beers.go index d81cfa0..7736936 100644 --- a/plugins/beers/beers.go +++ b/plugins/beers/beers.go @@ -38,8 +38,8 @@ type untappdUser struct { } // New BeersPlugin creates a new BeersPlugin with the Plugin interface -func New(bot bot.Bot) *BeersPlugin { - if _, err := bot.DB().Exec(`create table if not exists untappd ( +func New(b bot.Bot) *BeersPlugin { + if _, err := b.DB().Exec(`create table if not exists untappd ( id integer primary key, untappdUser string, channel string, @@ -49,19 +49,21 @@ func New(bot bot.Bot) *BeersPlugin { log.Fatal(err) } p := BeersPlugin{ - Bot: bot, - db: bot.DB(), + Bot: b, + db: b.DB(), } - for _, channel := range bot.Config().GetArray("Untappd.Channels", []string{}) { + for _, channel := range b.Config().GetArray("Untappd.Channels", []string{}) { go p.untappdLoop(channel) } + b.Register(p, bot.Message, p.message) + b.Register(p, bot.Help, p.help) return &p } // Message responds to the bot hook on recieving messages. // This function returns true if the plugin responds in a meaningful way to the users message. // Otherwise, the function returns false and the bot continues execution of other plugins. -func (p *BeersPlugin) Message(message msg.Message) bool { +func (p *BeersPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { parts := strings.Fields(message.Body) if len(parts) == 0 { @@ -190,17 +192,13 @@ func (p *BeersPlugin) Message(message msg.Message) bool { return false } -// Empty event handler because this plugin does not do anything on event recv -func (p *BeersPlugin) Event(kind string, message msg.Message) bool { - return false -} - // Help responds to help requests. Every plugin must implement a help function. -func (p *BeersPlugin) Help(channel string, parts []string) { +func (p *BeersPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { msg := "Beers: imbibe by using either beers +=,=,++ or with the !imbibe/drink " + "commands. I'll keep a count of how many beers you've had and then if you want " + "to reset, just !puke it all up!" - p.Bot.Send(bot.Message, channel, msg) + p.Bot.Send(bot.Message, message.Channel, msg) + return true } func getUserBeers(db *sqlx.DB, user string) counter.Item { @@ -439,14 +437,7 @@ func (p *BeersPlugin) untappdLoop(channel string) { } } -// Handler for bot's own messages -func (p *BeersPlugin) BotMessage(message msg.Message) bool { - return false -} - // Register any web URLs desired -func (p *BeersPlugin) RegisterWeb() *string { +func (p BeersPlugin) RegisterWeb() *string { return nil } - -func (p *BeersPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false } diff --git a/plugins/couldashouldawoulda/csw.go b/plugins/couldashouldawoulda/csw.go index 1a99317..9d2d7a3 100644 --- a/plugins/couldashouldawoulda/csw.go +++ b/plugins/couldashouldawoulda/csw.go @@ -17,13 +17,15 @@ type CSWPlugin struct { Config *config.Config } -func New(bot bot.Bot) *CSWPlugin { - return &CSWPlugin{ - Bot: bot, +func New(b bot.Bot) *CSWPlugin { + csw := &CSWPlugin{ + Bot: b, } + b.Register(csw, bot.Message, csw.message) + return csw } -func (p *CSWPlugin) Message(message msg.Message) bool { +func (p *CSWPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { if !message.Command { return false } @@ -70,18 +72,6 @@ func (p *CSWPlugin) Message(message msg.Message) bool { return false } -func (p *CSWPlugin) Help(channel string, parts []string) {} - -func (p *CSWPlugin) Event(kind string, message msg.Message) bool { - return false -} - -func (p *CSWPlugin) BotMessage(message msg.Message) bool { - return false -} - -func (p *CSWPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false } - func (p *CSWPlugin) RegisterWeb() *string { return nil } diff --git a/plugins/counter/counter.go b/plugins/counter/counter.go index 0100d79..6d0e0ed 100644 --- a/plugins/counter/counter.go +++ b/plugins/counter/counter.go @@ -172,31 +172,34 @@ func (i *Item) Delete() error { } // NewCounterPlugin creates a new CounterPlugin with the Plugin interface -func New(bot bot.Bot) *CounterPlugin { - tx := bot.DB().MustBegin() - bot.DB().MustExec(`create table if not exists counter ( +func New(b bot.Bot) *CounterPlugin { + tx := b.DB().MustBegin() + b.DB().MustExec(`create table if not exists counter ( id integer primary key, nick string, item string, count integer );`) - bot.DB().MustExec(`create table if not exists counter_alias ( + b.DB().MustExec(`create table if not exists counter_alias ( id integer PRIMARY KEY AUTOINCREMENT, item string NOT NULL UNIQUE, points_to string NOT NULL );`) tx.Commit() - return &CounterPlugin{ - Bot: bot, - DB: bot.DB(), + cp := &CounterPlugin{ + Bot: b, + DB: b.DB(), } + b.Register(cp, bot.Message, cp.message) + b.Register(cp, bot.Help, cp.help) + return cp } // Message responds to the bot hook on recieving messages. // This function returns true if the plugin responds in a meaningful way to the // users message. Otherwise, the function returns false and the bot continues // execution of other plugins. -func (p *CounterPlugin) Message(message msg.Message) bool { +func (p *CounterPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { // This bot does not reply to anything nick := message.User.Name channel := message.Channel @@ -444,21 +447,12 @@ func (p *CounterPlugin) Message(message msg.Message) bool { } // Help responds to help requests. Every plugin must implement a help function. -func (p *CounterPlugin) Help(channel string, parts []string) { - p.Bot.Send(bot.Message, channel, "You can set counters incrementally by using "+ +func (p *CounterPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.Bot.Send(bot.Message, message.Channel, "You can set counters incrementally by using "+ "++ and --. You can see all of your counters using "+ "\"inspect\", erase them with \"clear\", and view single counters with "+ "\"count\".") -} - -// Empty event handler because this plugin does not do anything on event recv -func (p *CounterPlugin) Event(kind string, message msg.Message) bool { - return false -} - -// Handler for bot's own messages -func (p *CounterPlugin) BotMessage(message msg.Message) bool { - return false + return true } // Register any web URLs desired @@ -466,8 +460,6 @@ func (p *CounterPlugin) RegisterWeb() *string { return nil } -func (p *CounterPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false } - func (p *CounterPlugin) checkMatch(message msg.Message) bool { nick := message.User.Name channel := message.Channel diff --git a/plugins/dice/dice.go b/plugins/dice/dice.go index 51bbd72..721a652 100644 --- a/plugins/dice/dice.go +++ b/plugins/dice/dice.go @@ -19,10 +19,13 @@ type DicePlugin struct { } // NewDicePlugin creates a new DicePlugin with the Plugin interface -func New(bot bot.Bot) *DicePlugin { - return &DicePlugin{ - Bot: bot, +func New(b bot.Bot) *DicePlugin { + dp := &DicePlugin{ + Bot: b, } + b.Register(dp, bot.Message, dp.message) + b.Register(dp, bot.Help, dp.help) + return dp } func rollDie(sides int) int { @@ -32,7 +35,7 @@ func rollDie(sides int) int { // Message responds to the bot hook on recieving messages. // This function returns true if the plugin responds in a meaningful way to the users message. // Otherwise, the function returns false and the bot continues execution of other plugins. -func (p *DicePlugin) Message(message msg.Message) bool { +func (p *DicePlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { if !message.Command { return false } @@ -67,23 +70,12 @@ func (p *DicePlugin) Message(message msg.Message) bool { } // Help responds to help requests. Every plugin must implement a help function. -func (p *DicePlugin) Help(channel string, parts []string) { - p.Bot.Send(bot.Message, channel, "Roll dice using notation XdY. Try \"3d20\".") -} - -// Empty event handler because this plugin does not do anything on event recv -func (p *DicePlugin) Event(kind string, message msg.Message) bool { - return false -} - -// Handler for bot's own messages -func (p *DicePlugin) BotMessage(message msg.Message) bool { - return false +func (p *DicePlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.Bot.Send(bot.Message, message.Channel, "Roll dice using notation XdY. Try \"3d20\".") + return true } // Register any web URLs desired func (p *DicePlugin) RegisterWeb() *string { return nil } - -func (p *DicePlugin) ReplyMessage(message msg.Message, identifier string) bool { return false } diff --git a/plugins/emojifyme/emojifyme.go b/plugins/emojifyme/emojifyme.go index b3614fd..76610d8 100644 --- a/plugins/emojifyme/emojifyme.go +++ b/plugins/emojifyme/emojifyme.go @@ -20,7 +20,7 @@ type EmojifyMePlugin struct { Emoji map[string]string } -func New(bot bot.Bot) *EmojifyMePlugin { +func New(b bot.Bot) *EmojifyMePlugin { resp, err := http.Get("https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json") if err != nil { log.Fatalf("Error generic emoji list: %s", err) @@ -48,14 +48,16 @@ func New(bot bot.Bot) *EmojifyMePlugin { } } - return &EmojifyMePlugin{ - Bot: bot, + ep := &EmojifyMePlugin{ + Bot: b, GotBotEmoji: false, Emoji: emojiMap, } + b.Register(ep, bot.Message, ep.message) + return ep } -func (p *EmojifyMePlugin) Message(message msg.Message) bool { +func (p *EmojifyMePlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { if !p.GotBotEmoji { p.GotBotEmoji = true emojiMap := p.Bot.GetEmojiList() @@ -97,24 +99,10 @@ func (p *EmojifyMePlugin) Message(message msg.Message) bool { return false } -func (p *EmojifyMePlugin) Help(channel string, parts []string) { - -} - -func (p *EmojifyMePlugin) Event(kind string, message msg.Message) bool { - return false -} - -func (p *EmojifyMePlugin) BotMessage(message msg.Message) bool { - return false -} - func (p *EmojifyMePlugin) RegisterWeb() *string { return nil } -func (p *EmojifyMePlugin) ReplyMessage(message msg.Message, identifier string) bool { return false } - func stringsContain(haystack []string, needle string) bool { for _, s := range haystack { if s == needle { diff --git a/plugins/fact/factoid.go b/plugins/fact/factoid.go index fc2dfad..b8b40c2 100644 --- a/plugins/fact/factoid.go +++ b/plugins/fact/factoid.go @@ -314,6 +314,9 @@ func New(botInst bot.Bot) *Factoid { }(channel) } + botInst.Register(p, bot.Message, p.message) + botInst.Register(p, bot.Help, p.help) + return p } @@ -609,7 +612,7 @@ func (p *Factoid) changeFact(message msg.Message) bool { // Message responds to the bot hook on recieving messages. // This function returns true if the plugin responds in a meaningful way to the users message. // Otherwise, the function returns false and the bot continues execution of other plugins. -func (p *Factoid) Message(message msg.Message) bool { +func (p *Factoid) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { if strings.ToLower(message.Body) == "what was that?" { return p.tellThemWhatThatWas(message) } @@ -669,14 +672,10 @@ func (p *Factoid) Message(message msg.Message) bool { } // Help responds to help requests. Every plugin must implement a help function. -func (p *Factoid) Help(channel string, parts []string) { - p.Bot.Send(bot.Message, channel, "I can learn facts and spit them back out. You can say \"this is that\" or \"he $5\". Later, trigger the factoid by just saying the trigger word, \"this\" or \"he\" in these examples.") - p.Bot.Send(bot.Message, channel, "I can also figure out some variables including: $nonzero, $digit, $nick, and $someone.") -} - -// Empty event handler because this plugin does not do anything on event recv -func (p *Factoid) Event(kind string, message msg.Message) bool { - return false +func (p *Factoid) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.Bot.Send(bot.Message, message.Channel, "I can learn facts and spit them back out. You can say \"this is that\" or \"he $5\". Later, trigger the factoid by just saying the trigger word, \"this\" or \"he\" in these examples.") + p.Bot.Send(bot.Message, message.Channel, "I can also figure out some variables including: $nonzero, $digit, $nick, and $someone.") + return true } // Pull a fact at random from the database @@ -737,11 +736,6 @@ func (p *Factoid) factTimer(channel string) { } } -// Handler for bot's own messages -func (p *Factoid) BotMessage(message msg.Message) bool { - return false -} - // Register any web URLs desired func (p *Factoid) RegisterWeb() *string { http.HandleFunc("/factoid/req", p.serveQuery) @@ -784,5 +778,3 @@ func (p *Factoid) serveQuery(w http.ResponseWriter, r *http.Request) { log.Println(err) } } - -func (p *Factoid) ReplyMessage(message msg.Message, identifier string) bool { return false } diff --git a/plugins/fact/remember.go b/plugins/fact/remember.go index 09ecf7b..87485f1 100644 --- a/plugins/fact/remember.go +++ b/plugins/fact/remember.go @@ -29,6 +29,8 @@ func NewRemember(b bot.Bot) *RememberPlugin { Log: make(map[string][]msg.Message), db: b.DB(), } + b.Register(p, bot.Message, p.message) + b.Register(p, bot.Message, p.help) return &p } @@ -36,7 +38,7 @@ func NewRemember(b bot.Bot) *RememberPlugin { // This function returns true if the plugin responds in a meaningful way to the // users message. Otherwise, the function returns false and the bot continues // execution of other plugins. -func (p *RememberPlugin) Message(message msg.Message) bool { +func (p *RememberPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { if strings.ToLower(message.Body) == "quote" && message.Command { q := p.randQuote() @@ -111,14 +113,15 @@ func (p *RememberPlugin) Message(message msg.Message) bool { } // Help responds to help requests. Every plugin must implement a help function. -func (p *RememberPlugin) Help(channel string, parts []string) { +func (p *RememberPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { msg := "!remember will let you quote your idiot friends. Just type " + "!remember to remember what they said. Snippet can " + "be any part of their message. Later on, you can ask for a random " + "!quote." - p.Bot.Send(bot.Message, channel, msg) + p.Bot.Send(bot.Message, message.Channel, msg) + return true } // deliver a random quote out of the db. @@ -150,19 +153,8 @@ func (p *RememberPlugin) randQuote() string { return f.Tidbit } -// Empty event handler because this plugin does not do anything on event recv -func (p *RememberPlugin) Event(kind string, message msg.Message) bool { - return false -} - -// Record what the bot says in the log -func (p *RememberPlugin) BotMessage(message msg.Message) bool { - p.recordMsg(message) - return false -} - // Register any web URLs desired -func (p *RememberPlugin) RegisterWeb() *string { +func (p RememberPlugin) RegisterWeb() *string { return nil } @@ -170,5 +162,3 @@ func (p *RememberPlugin) recordMsg(message msg.Message) { log.Printf("Logging message: %s: %s", message.User.Name, message.Body) p.Log[message.Channel] = append(p.Log[message.Channel], message) } - -func (p *RememberPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false } diff --git a/plugins/first/first.go b/plugins/first/first.go index af3ffcc..9771dbf 100644 --- a/plugins/first/first.go +++ b/plugins/first/first.go @@ -66,11 +66,14 @@ func New(b bot.Bot) *FirstPlugin { log.Fatal("Could not initialize first plugin: ", err) } - return &FirstPlugin{ + fp := &FirstPlugin{ Bot: b, db: b.DB(), First: first, } + b.Register(fp, bot.Message, fp.message) + b.Register(fp, bot.Help, fp.help) + return fp } func getLastFirst(db *sqlx.DB) (*FirstEntry, error) { @@ -123,7 +126,7 @@ func isToday(t time.Time) bool { // Message responds to the bot hook on recieving messages. // This function returns true if the plugin responds in a meaningful way to the users message. // Otherwise, the function returns false and the bot continues execution of other plugins. -func (p *FirstPlugin) Message(message msg.Message) bool { +func (p *FirstPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { // This bot does not reply to anything if p.First == nil && p.allowed(message) { @@ -208,23 +211,12 @@ func (p *FirstPlugin) LoadData() { } // Help responds to help requests. Every plugin must implement a help function. -func (p *FirstPlugin) Help(channel string, parts []string) { - p.Bot.Send(bot.Message, channel, "Sorry, First does not do a goddamn thing.") -} - -// Empty event handler because this plugin does not do anything on event recv -func (p *FirstPlugin) Event(kind string, message msg.Message) bool { - return false -} - -// Handler for bot's own messages -func (p *FirstPlugin) BotMessage(message msg.Message) bool { - return false +func (p *FirstPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.Bot.Send(bot.Message, message.Channel, "Sorry, First does not do a goddamn thing.") + return true } // Register any web URLs desired func (p *FirstPlugin) RegisterWeb() *string { return nil } - -func (p *FirstPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false } diff --git a/plugins/inventory/inventory.go b/plugins/inventory/inventory.go index 072a22c..50fb6f3 100644 --- a/plugins/inventory/inventory.go +++ b/plugins/inventory/inventory.go @@ -24,8 +24,8 @@ type InventoryPlugin struct { } // New creates a new InventoryPlugin with the Plugin interface -func New(bot bot.Bot) *InventoryPlugin { - config := bot.Config() +func New(b bot.Bot) *InventoryPlugin { + config := b.Config() nick := config.Get("nick", "bot") r1, err := regexp.Compile("take this (.+)") checkerr(err) @@ -38,15 +38,15 @@ func New(bot bot.Bot) *InventoryPlugin { r5, err := regexp.Compile(fmt.Sprintf("gives (.+) to %s([^a-zA-Z].*)?", nick)) checkerr(err) - p := InventoryPlugin{ - DB: bot.DB(), - bot: bot, + p := &InventoryPlugin{ + DB: b.DB(), + bot: b, config: config, r1: r1, r2: r2, r3: r3, r4: r4, r5: r5, } - bot.RegisterFilter("$item", p.itemFilter) - bot.RegisterFilter("$giveitem", p.giveItemFilter) + b.RegisterFilter("$item", p.itemFilter) + b.RegisterFilter("$giveitem", p.giveItemFilter) _, err = p.DB.Exec(`create table if not exists inventory ( item string primary key @@ -56,7 +56,9 @@ func New(bot bot.Bot) *InventoryPlugin { log.Fatal(err) } - return &p + b.Register(p, bot.Message, p.message) + + return p } func (p *InventoryPlugin) giveItemFilter(input string) string { @@ -75,7 +77,7 @@ func (p *InventoryPlugin) itemFilter(input string) string { return input } -func (p *InventoryPlugin) Message(message msg.Message) bool { +func (p *InventoryPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { m := message.Body log.Printf("inventory trying to read %+v", message) if message.Command { @@ -223,20 +225,7 @@ func checkerr(e error) { } } -func (p *InventoryPlugin) Event(e string, message msg.Message) bool { - return false -} - -func (p *InventoryPlugin) BotMessage(message msg.Message) bool { - return false -} - -func (p *InventoryPlugin) Help(e string, m []string) { -} - func (p *InventoryPlugin) RegisterWeb() *string { // nothing to register return nil } - -func (p *InventoryPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false } diff --git a/plugins/leftpad/leftpad.go b/plugins/leftpad/leftpad.go index 42a4493..e005b46 100644 --- a/plugins/leftpad/leftpad.go +++ b/plugins/leftpad/leftpad.go @@ -20,19 +20,20 @@ type LeftpadPlugin struct { } // New creates a new LeftpadPlugin with the Plugin interface -func New(bot bot.Bot) *LeftpadPlugin { - p := LeftpadPlugin{ - bot: bot, - config: bot.Config(), +func New(b bot.Bot) *LeftpadPlugin { + p := &LeftpadPlugin{ + bot: b, + config: b.Config(), } - return &p + b.Register(p, bot.Message, p.message) + return p } type leftpadResp struct { Str string } -func (p *LeftpadPlugin) Message(message msg.Message) bool { +func (p *LeftpadPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { if !message.Command { return false } @@ -62,20 +63,7 @@ func (p *LeftpadPlugin) Message(message msg.Message) bool { return false } -func (p *LeftpadPlugin) Event(e string, message msg.Message) bool { - return false -} - -func (p *LeftpadPlugin) BotMessage(message msg.Message) bool { - return false -} - -func (p *LeftpadPlugin) Help(e string, m []string) { -} - func (p *LeftpadPlugin) RegisterWeb() *string { // nothing to register return nil } - -func (p *LeftpadPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false } diff --git a/plugins/nerdepedia/nerdepedia.go b/plugins/nerdepedia/nerdepedia.go index f0e0096..eb040a7 100644 --- a/plugins/nerdepedia/nerdepedia.go +++ b/plugins/nerdepedia/nerdepedia.go @@ -27,17 +27,20 @@ type NerdepediaPlugin struct { } // NewNerdepediaPlugin creates a new NerdepediaPlugin with the Plugin interface -func New(bot bot.Bot) *NerdepediaPlugin { - return &NerdepediaPlugin{ - bot: bot, - config: bot.Config(), +func New(b bot.Bot) *NerdepediaPlugin { + np := &NerdepediaPlugin{ + bot: b, + config: b.Config(), } + b.Register(np, bot.Message, np.message) + b.Register(np, bot.Help, np.help) + return np } // Message responds to the bot hook on recieving messages. // This function returns true if the plugin responds in a meaningful way to the users message. // Otherwise, the function returns false and the bot continues execution of other plugins. -func (p *NerdepediaPlugin) Message(message msg.Message) bool { +func (p *NerdepediaPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { lowerCase := strings.ToLower(message.Body) query := "" if lowerCase == "may the force be with you" || lowerCase == "help me obi-wan" { @@ -87,23 +90,12 @@ func (p *NerdepediaPlugin) Message(message msg.Message) bool { } // Help responds to help requests. Every plugin must implement a help function. -func (p *NerdepediaPlugin) Help(channel string, parts []string) { - p.bot.Send(bot.Message, channel, "nerd stuff") -} - -// Empty event handler because this plugin does not do anything on event recv -func (p *NerdepediaPlugin) Event(kind string, message msg.Message) bool { - return false -} - -// Handler for bot's own messages -func (p *NerdepediaPlugin) BotMessage(message msg.Message) bool { - return false +func (p *NerdepediaPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.bot.Send(bot.Message, message.Channel, "nerd stuff") + return true } // Register any web URLs desired func (p *NerdepediaPlugin) RegisterWeb() *string { return nil } - -func (p *NerdepediaPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false } diff --git a/plugins/picker/picker.go b/plugins/picker/picker.go index 290a490..3a1c320 100644 --- a/plugins/picker/picker.go +++ b/plugins/picker/picker.go @@ -20,16 +20,19 @@ type PickerPlugin struct { } // NewPickerPlugin creates a new PickerPlugin with the Plugin interface -func New(bot bot.Bot) *PickerPlugin { - return &PickerPlugin{ - Bot: bot, +func New(b bot.Bot) *PickerPlugin { + pp := &PickerPlugin{ + Bot: b, } + b.Register(pp, bot.Message, pp.message) + b.Register(pp, bot.Help, pp.help) + return pp } // Message responds to the bot hook on recieving messages. // This function returns true if the plugin responds in a meaningful way to the users message. // Otherwise, the function returns false and the bot continues execution of other plugins. -func (p *PickerPlugin) Message(message msg.Message) bool { +func (p *PickerPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { if !strings.HasPrefix(message.Body, "pick") { return false } @@ -108,18 +111,9 @@ func (p *PickerPlugin) parse(body string) (int, []string, error) { } // Help responds to help requests. Every plugin must implement a help function. -func (p *PickerPlugin) Help(channel string, parts []string) { - p.Bot.Send(bot.Message, channel, "Choose from a list of options. Try \"pick {a,b,c}\".") -} - -// Empty event handler because this plugin does not do anything on event recv -func (p *PickerPlugin) Event(kind string, message msg.Message) bool { - return false -} - -// Handler for bot's own messages -func (p *PickerPlugin) BotMessage(message msg.Message) bool { - return false +func (p *PickerPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.Bot.Send(bot.Message, message.Channel, "Choose from a list of options. Try \"pick {a,b,c}\".") + return true } // Register any web URLs desired diff --git a/plugins/reaction/reaction.go b/plugins/reaction/reaction.go index 6c13201..c964022 100644 --- a/plugins/reaction/reaction.go +++ b/plugins/reaction/reaction.go @@ -15,14 +15,16 @@ type ReactionPlugin struct { Config *config.Config } -func New(bot bot.Bot) *ReactionPlugin { - return &ReactionPlugin{ - Bot: bot, - Config: bot.Config(), +func New(b bot.Bot) *ReactionPlugin { + rp := &ReactionPlugin{ + Bot: b, + Config: b.Config(), } + b.Register(rp, bot.Message, rp.message) + return rp } -func (p *ReactionPlugin) Message(message msg.Message) bool { +func (p *ReactionPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { harrass := false for _, nick := range p.Config.GetArray("Reaction.HarrassList", []string{}) { if message.User.Name == nick { @@ -62,20 +64,6 @@ func (p *ReactionPlugin) Message(message msg.Message) bool { return false } -func (p *ReactionPlugin) Help(channel string, parts []string) { - -} - -func (p *ReactionPlugin) Event(kind string, message msg.Message) bool { - return false -} - -func (p *ReactionPlugin) BotMessage(message msg.Message) bool { - return false -} - func (p *ReactionPlugin) RegisterWeb() *string { return nil } - -func (p *ReactionPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false } diff --git a/plugins/reminder/reminder.go b/plugins/reminder/reminder.go index df38f04..93d4dec 100644 --- a/plugins/reminder/reminder.go +++ b/plugins/reminder/reminder.go @@ -38,9 +38,9 @@ type Reminder struct { channel string } -func New(bot bot.Bot) *ReminderPlugin { +func New(b bot.Bot) *ReminderPlugin { log.SetFlags(log.LstdFlags | log.Lshortfile) - if _, err := bot.DB().Exec(`create table if not exists reminders ( + if _, err := b.DB().Exec(`create table if not exists reminders ( id integer primary key, fromWho string, toWho string, @@ -56,21 +56,24 @@ func New(bot bot.Bot) *ReminderPlugin { timer.Stop() plugin := &ReminderPlugin{ - Bot: bot, - db: bot.DB(), + Bot: b, + db: b.DB(), mutex: &sync.Mutex{}, timer: timer, - config: bot.Config(), + config: b.Config(), } plugin.queueUpNextReminder() go reminderer(plugin) + b.Register(plugin, bot.Message, plugin.message) + b.Register(plugin, bot.Help, plugin.help) + return plugin } -func (p *ReminderPlugin) Message(message msg.Message) bool { +func (p *ReminderPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { channel := message.Channel from := message.User.Name @@ -192,16 +195,9 @@ func (p *ReminderPlugin) Message(message msg.Message) bool { return false } -func (p *ReminderPlugin) Help(channel string, parts []string) { - p.Bot.Send(bot.Message, channel, "Pester someone with a reminder. Try \"remind in message\".\n\nUnsure about duration syntax? Check https://golang.org/pkg/time/#ParseDuration") -} - -func (p *ReminderPlugin) Event(kind string, message msg.Message) bool { - return false -} - -func (p *ReminderPlugin) BotMessage(message msg.Message) bool { - return false +func (p *ReminderPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.Bot.Send(bot.Message, message.Channel, "Pester someone with a reminder. Try \"remind in message\".\n\nUnsure about duration syntax? Check https://golang.org/pkg/time/#ParseDuration") + return true } func (p *ReminderPlugin) RegisterWeb() *string { @@ -365,5 +361,3 @@ func reminderer(p *ReminderPlugin) { p.queueUpNextReminder() } } - -func (p *ReminderPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false } diff --git a/plugins/rpgORdie/rpgORdie.go b/plugins/rpgORdie/rpgORdie.go index 1763e73..8b5a40e 100644 --- a/plugins/rpgORdie/rpgORdie.go +++ b/plugins/rpgORdie/rpgORdie.go @@ -98,13 +98,17 @@ func (b *board) checkAndMove(dx, dy int) int { } func New(b bot.Bot) *RPGPlugin { - return &RPGPlugin{ + rpg := &RPGPlugin{ Bot: b, listenFor: map[string]*board{}, } + b.Register(rpg, bot.Message, rpg.message) + b.Register(rpg, bot.Reply, rpg.replyMessage) + b.Register(rpg, bot.Help, rpg.help) + return rpg } -func (p *RPGPlugin) Message(message msg.Message) bool { +func (p *RPGPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { if strings.ToLower(message.Body) == "start rpg" { b := NewRandomBoard() ts, _ := p.Bot.Send(bot.Message, message.Channel, b.toMessageString()) @@ -115,27 +119,17 @@ func (p *RPGPlugin) Message(message msg.Message) bool { return false } -func (p *RPGPlugin) LoadData() { - -} - -func (p *RPGPlugin) Help(channel string, parts []string) { - p.Bot.Send(bot.Message, channel, "Go find a walkthrough or something.") -} - -func (p *RPGPlugin) Event(kind string, message msg.Message) bool { - return false -} - -func (p *RPGPlugin) BotMessage(message msg.Message) bool { - return false +func (p *RPGPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.Bot.Send(bot.Message, message.Channel, "Go find a walkthrough or something.") + return true } func (p *RPGPlugin) RegisterWeb() *string { return nil } -func (p *RPGPlugin) ReplyMessage(message msg.Message, identifier string) bool { +func (p *RPGPlugin) replyMessage(kind bot.Kind, message msg.Message, args ...interface{}) bool { + identifier := args[0].(string) if strings.ToLower(message.User.Name) != strings.ToLower(p.Bot.Config().Get("Nick", "bot")) { if b, ok := p.listenFor[identifier]; ok { diff --git a/plugins/rss/rss.go b/plugins/rss/rss.go index c7bed49..b25d749 100644 --- a/plugins/rss/rss.go +++ b/plugins/rss/rss.go @@ -49,16 +49,19 @@ func (c *cacheItem) getCurrentPage(maxLines int) string { return page } -func New(bot bot.Bot) *RSSPlugin { - return &RSSPlugin{ - Bot: bot, +func New(b bot.Bot) *RSSPlugin { + rss := &RSSPlugin{ + Bot: b, cache: map[string]*cacheItem{}, - shelfLife: time.Minute * 20, - maxLines: 5, + shelfLife: time.Minute * time.Duration(b.Config().GetInt("rss.shelfLife", 20)), + maxLines: b.Config().GetInt("rss.maxLines", 5), } + b.Register(rss, bot.Message, rss.message) + b.Register(rss, bot.Help, rss.help) + return rss } -func (p *RSSPlugin) Message(message msg.Message) bool { +func (p *RSSPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { tokens := strings.Fields(message.Body) numTokens := len(tokens) @@ -94,28 +97,13 @@ func (p *RSSPlugin) Message(message msg.Message) bool { return false } -func (p *RSSPlugin) LoadData() { - // This bot has no data to load -} - // Help responds to help requests. Every plugin must implement a help function. -func (p *RSSPlugin) Help(channel string, parts []string) { - p.Bot.Send(bot.Message, channel, "try '!rss http://rss.cnn.com/rss/edition.rss'") -} - -// Empty event handler because this plugin does not do anything on event recv -func (p *RSSPlugin) Event(kind string, message msg.Message) bool { - return false -} - -// Handler for bot's own messages -func (p *RSSPlugin) BotMessage(message msg.Message) bool { - return false +func (p *RSSPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.Bot.Send(bot.Message, message.Channel, "try '!rss http://rss.cnn.com/rss/edition.rss'") + return true } // Register any web URLs desired func (p *RSSPlugin) RegisterWeb() *string { return nil } - -func (p *RSSPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false } diff --git a/plugins/sisyphus/sisyphus.go b/plugins/sisyphus/sisyphus.go index 0eac02d..5a0a8b8 100644 --- a/plugins/sisyphus/sisyphus.go +++ b/plugins/sisyphus/sisyphus.go @@ -162,13 +162,17 @@ func (g *game) toMessageString() string { } func New(b bot.Bot) *SisyphusPlugin { - return &SisyphusPlugin{ + sp := &SisyphusPlugin{ Bot: b, listenFor: map[string]*game{}, } + b.Register(sp, bot.Message, sp.message) + b.Register(sp, bot.Reply, sp.replyMessage) + b.Register(sp, bot.Help, sp.help) + return sp } -func (p *SisyphusPlugin) Message(message msg.Message) bool { +func (p *SisyphusPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { if strings.ToLower(message.Body) == "start sisyphus" { b := NewRandomGame(p.Bot, message.Channel, message.User.Name) p.listenFor[b.id] = b @@ -178,23 +182,17 @@ func (p *SisyphusPlugin) Message(message msg.Message) bool { return false } -func (p *SisyphusPlugin) Help(channel string, parts []string) { - p.Bot.Send(bot.Message, channel, "https://en.wikipedia.org/wiki/Sisyphus") -} - -func (p *SisyphusPlugin) Event(kind string, message msg.Message) bool { - return false -} - -func (p *SisyphusPlugin) BotMessage(message msg.Message) bool { - return false +func (p *SisyphusPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.Bot.Send(bot.Message, message.Channel, "https://en.wikipedia.org/wiki/Sisyphus") + return true } func (p *SisyphusPlugin) RegisterWeb() *string { return nil } -func (p *SisyphusPlugin) ReplyMessage(message msg.Message, identifier string) bool { +func (p *SisyphusPlugin) replyMessage(kind bot.Kind, message msg.Message, args ...interface{}) bool { + identifier := args[0].(string) if strings.ToLower(message.User.Name) != strings.ToLower(p.Bot.Config().Get("Nick", "bot")) { if g, ok := p.listenFor[identifier]; ok { diff --git a/plugins/talker/talker.go b/plugins/talker/talker.go index f4abdd7..46ba61a 100644 --- a/plugins/talker/talker.go +++ b/plugins/talker/talker.go @@ -43,13 +43,16 @@ type TalkerPlugin struct { sayings []string } -func New(bot bot.Bot) *TalkerPlugin { - return &TalkerPlugin{ - Bot: bot, +func New(b bot.Bot) *TalkerPlugin { + tp := &TalkerPlugin{ + Bot: b, } + b.Register(tp, bot.Message, tp.message) + b.Register(tp, bot.Help, tp.help) + return tp } -func (p *TalkerPlugin) Message(message msg.Message) bool { +func (p *TalkerPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { channel := message.Channel body := message.Body lowermessage := strings.ToLower(body) @@ -80,23 +83,12 @@ func (p *TalkerPlugin) Message(message msg.Message) bool { return false } -func (p *TalkerPlugin) Help(channel string, parts []string) { - p.Bot.Send(bot.Message, channel, "Hi, this is talker. I like to talk about FredFelps!") -} - -// Empty event handler because this plugin does not do anything on event recv -func (p *TalkerPlugin) Event(kind string, message msg.Message) bool { - return false -} - -// Handler for bot's own messages -func (p *TalkerPlugin) BotMessage(message msg.Message) bool { - return false +func (p *TalkerPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.Bot.Send(bot.Message, message.Channel, "Hi, this is talker. I like to talk about FredFelps!") + return true } // Register any web URLs desired func (p *TalkerPlugin) RegisterWeb() *string { return nil } - -func (p *TalkerPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false } diff --git a/plugins/tell/tell.go b/plugins/tell/tell.go index 99ea601..c852141 100644 --- a/plugins/tell/tell.go +++ b/plugins/tell/tell.go @@ -16,10 +16,12 @@ type TellPlugin struct { } func New(b bot.Bot) *TellPlugin { - return &TellPlugin{b, make(map[string][]string)} + tp := &TellPlugin{b, make(map[string][]string)} + b.Register(tp, bot.Message, tp.message) + return tp } -func (t *TellPlugin) Message(message msg.Message) bool { +func (t *TellPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { if strings.HasPrefix(strings.ToLower(message.Body), "tell") { parts := strings.Split(message.Body, " ") target := strings.ToLower(parts[1]) @@ -40,8 +42,4 @@ func (t *TellPlugin) Message(message msg.Message) bool { return false } -func (t *TellPlugin) Event(kind string, message msg.Message) bool { return false } -func (t *TellPlugin) ReplyMessage(msg.Message, string) bool { return false } -func (t *TellPlugin) BotMessage(message msg.Message) bool { return false } -func (t *TellPlugin) Help(channel string, parts []string) {} -func (t *TellPlugin) RegisterWeb() *string { return nil } +func (t *TellPlugin) RegisterWeb() *string { return nil } diff --git a/plugins/twitch/twitch.go b/plugins/twitch/twitch.go index d712292..0c17a24 100644 --- a/plugins/twitch/twitch.go +++ b/plugins/twitch/twitch.go @@ -51,10 +51,10 @@ type stream struct { } `json:"pagination"` } -func New(bot bot.Bot) *TwitchPlugin { +func New(b bot.Bot) *TwitchPlugin { p := &TwitchPlugin{ - Bot: bot, - config: bot.Config(), + Bot: b, + config: b.Config(), twitchList: map[string]*Twitcher{}, } @@ -70,13 +70,10 @@ func New(bot bot.Bot) *TwitchPlugin { go p.twitchLoop(ch) } + b.Register(p, bot.Message, p.message) return p } -func (p *TwitchPlugin) BotMessage(message msg.Message) bool { - return false -} - func (p *TwitchPlugin) RegisterWeb() *string { http.HandleFunc("/isstreaming/", p.serveStreaming) tmp := "/isstreaming" @@ -114,7 +111,7 @@ func (p *TwitchPlugin) serveStreaming(w http.ResponseWriter, r *http.Request) { } } -func (p *TwitchPlugin) Message(message msg.Message) bool { +func (p *TwitchPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { if strings.ToLower(message.Body) == "twitch status" { channel := message.Channel if users := p.config.GetArray("Twitch."+channel+".Users", []string{}); len(users) > 0 { @@ -130,17 +127,10 @@ func (p *TwitchPlugin) Message(message msg.Message) bool { return false } -func (p *TwitchPlugin) Event(kind string, message msg.Message) bool { - return false -} - -func (p *TwitchPlugin) LoadData() { - -} - -func (p *TwitchPlugin) Help(channel string, parts []string) { +func (p *TwitchPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { msg := "There's no help for you here." - p.Bot.Send(bot.Message, channel, msg) + p.Bot.Send(bot.Message, message.Channel, msg) + return true } func (p *TwitchPlugin) twitchLoop(channel string) { @@ -240,5 +230,3 @@ func (p *TwitchPlugin) checkTwitch(channel string, twitcher *Twitcher, alwaysPri twitcher.game = game } } - -func (p *TwitchPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false } diff --git a/plugins/your/your.go b/plugins/your/your.go index 3f40fea..a319039 100644 --- a/plugins/your/your.go +++ b/plugins/your/your.go @@ -17,17 +17,20 @@ type YourPlugin struct { } // NewYourPlugin creates a new YourPlugin with the Plugin interface -func New(bot bot.Bot) *YourPlugin { - return &YourPlugin{ - bot: bot, - config: bot.Config(), +func New(b bot.Bot) *YourPlugin { + yp := &YourPlugin{ + bot: b, + config: b.Config(), } + b.Register(yp, bot.Message, yp.message) + b.Register(yp, bot.Help, yp.help) + return yp } // Message responds to the bot hook on recieving messages. // This function returns true if the plugin responds in a meaningful way to the users message. // Otherwise, the function returns false and the bot continues execution of other plugins. -func (p *YourPlugin) Message(message msg.Message) bool { +func (p *YourPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { maxLen := p.config.GetInt("your.maxlength", 140) if len(message.Body) > maxLen { return false @@ -50,23 +53,12 @@ func (p *YourPlugin) Message(message msg.Message) bool { } // Help responds to help requests. Every plugin must implement a help function. -func (p *YourPlugin) Help(channel string, parts []string) { - p.bot.Send(bot.Message, channel, "Your corrects people's grammar.") -} - -// Empty event handler because this plugin does not do anything on event recv -func (p *YourPlugin) Event(kind string, message msg.Message) bool { - return false -} - -// Handler for bot's own messages -func (p *YourPlugin) BotMessage(message msg.Message) bool { - return false +func (p *YourPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.bot.Send(bot.Message, message.Channel, "Your corrects people's grammar.") + return true } // Register any web URLs desired func (p *YourPlugin) RegisterWeb() *string { return nil } - -func (p *YourPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false } diff --git a/plugins/zork/zork.go b/plugins/zork/zork.go index 3942203..b5ac682 100644 --- a/plugins/zork/zork.go +++ b/plugins/zork/zork.go @@ -27,10 +27,13 @@ type ZorkPlugin struct { } func New(b bot.Bot) bot.Plugin { - return &ZorkPlugin{ + z := &ZorkPlugin{ bot: b, zorks: make(map[string]io.WriteCloser), } + b.Register(z, bot.Message, z.message) + b.Register(z, bot.Help, z.help) + return z } func (p *ZorkPlugin) runZork(ch string) error { @@ -91,7 +94,7 @@ func (p *ZorkPlugin) runZork(ch string) error { return nil } -func (p *ZorkPlugin) Message(message msg.Message) bool { +func (p *ZorkPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { m := strings.ToLower(message.Body) log.Printf("got message [%s]\n", m) if ts := strings.Fields(m); len(ts) < 1 || ts[0] != "zork" { @@ -113,14 +116,9 @@ func (p *ZorkPlugin) Message(message msg.Message) bool { return true } -func (p *ZorkPlugin) Event(_ string, _ msg.Message) bool { return false } - -func (p *ZorkPlugin) BotMessage(_ msg.Message) bool { return false } - -func (p *ZorkPlugin) Help(ch string, _ []string) { - p.bot.Send(bot.Message, ch, "Play zork using 'zork '.") +func (p *ZorkPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.bot.Send(bot.Message, message.Channel, "Play zork using 'zork '.") + return true } func (p *ZorkPlugin) RegisterWeb() *string { return nil } - -func (p *ZorkPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false } From 5ee5f33e36d50fc02e1877b7fafe2b8f8e4ec4bc Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Tue, 5 Feb 2019 15:02:15 -0500 Subject: [PATCH 029/107] tests: refactor for new system --- bot/mock.go | 2 +- plugins/babbler/babbler_test.go | 156 +++++++++++------------- plugins/beers/beers_test.go | 44 +++---- plugins/couldashouldawoulda/csw_test.go | 16 +-- plugins/counter/counter_test.go | 88 ++++++------- plugins/dice/dice_test.go | 32 ++--- plugins/fact/remember_test.go | 6 +- plugins/leftpad/leftpad_test.go | 34 ++---- plugins/nerdepedia/nerdepeida_test.go | 12 +- plugins/picker/picker_test.go | 8 +- plugins/reminder/reminder_test.go | 93 ++++++-------- plugins/rss/rss_test.go | 8 +- plugins/talker/talker_test.go | 30 ++--- plugins/twitch/twitch_test.go | 6 +- plugins/your/your_test.go | 8 +- 15 files changed, 221 insertions(+), 322 deletions(-) diff --git a/bot/mock.go b/bot/mock.go index 65409fc..70dd1df 100644 --- a/bot/mock.go +++ b/bot/mock.go @@ -47,7 +47,7 @@ func (mb *MockBot) Send(kind Kind, args ...interface{}) (string, error) { return "ERR", fmt.Errorf("Mesasge type unhandled") } func (mb *MockBot) AddPlugin(name string, f Plugin) {} -func (mb *MockBot) Register(name string, kind Kind, cb Callback) {} +func (mb *MockBot) Register(p Plugin, kind Kind, cb Callback) {} func (mb *MockBot) Receive(kind Kind, msg msg.Message, args ...interface{}) {} func (mb *MockBot) Filter(msg msg.Message, s string) string { return s } func (mb *MockBot) LastMessage(ch string) (msg.Message, error) { return msg.Message{}, nil } diff --git a/plugins/babbler/babbler_test.go b/plugins/babbler/babbler_test.go index b07b75f..1631257 100644 --- a/plugins/babbler/babbler_test.go +++ b/plugins/babbler/babbler_test.go @@ -12,12 +12,12 @@ import ( "github.com/velour/catbase/bot/user" ) -func makeMessage(payload string) msg.Message { +func makeMessage(payload string) (bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return msg.Message{ + return bot.Message, msg.Message{ User: &user.User{Name: "tester"}, Channel: "test", Body: payload, @@ -41,7 +41,7 @@ func TestBabblerNoBabbler(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - bp.Message(makeMessage("!seabass2 says")) + bp.message(makeMessage("!seabass2 says")) res := assert.Len(t, mb.Messages, 0) assert.True(t, res) // assert.Contains(t, mb.Messages[0], "seabass2 babbler not found") @@ -51,9 +51,9 @@ func TestBabblerNothingSaid(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - res := bp.Message(makeMessage("initialize babbler for seabass")) + res := bp.message(makeMessage("initialize babbler for seabass")) assert.True(t, res) - res = bp.Message(makeMessage("!seabass says")) + res = bp.message(makeMessage("!seabass says")) assert.True(t, res) assert.Len(t, mb.Messages, 2) assert.Contains(t, mb.Messages[0], "okay.") @@ -64,14 +64,14 @@ func TestBabbler(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - seabass := makeMessage("This is a message") + k, seabass := makeMessage("This is a message") seabass.User = &user.User{Name: "seabass"} - res := bp.Message(seabass) + res := bp.message(k, seabass) seabass.Body = "This is another message" - res = bp.Message(seabass) + res = bp.message(k, seabass) seabass.Body = "This is a long message" - res = bp.Message(seabass) - res = bp.Message(makeMessage("!seabass says")) + res = bp.message(k, seabass) + res = bp.message(makeMessage("!seabass says")) assert.Len(t, mb.Messages, 1) assert.True(t, res) assert.Contains(t, mb.Messages[0], "this is") @@ -82,14 +82,14 @@ func TestBabblerSeed(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - seabass := makeMessage("This is a message") + k, seabass := makeMessage("This is a message") seabass.User = &user.User{Name: "seabass"} - res := bp.Message(seabass) + res := bp.message(k, seabass) seabass.Body = "This is another message" - res = bp.Message(seabass) + res = bp.message(k, seabass) seabass.Body = "This is a long message" - res = bp.Message(seabass) - res = bp.Message(makeMessage("!seabass says long")) + res = bp.message(k, seabass) + res = bp.message(makeMessage("!seabass says long")) assert.Len(t, mb.Messages, 1) assert.True(t, res) assert.Contains(t, mb.Messages[0], "long message") @@ -99,14 +99,14 @@ func TestBabblerMultiSeed(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - seabass := makeMessage("This is a message") + k, seabass := makeMessage("This is a message") seabass.User = &user.User{Name: "seabass"} - res := bp.Message(seabass) + res := bp.message(k, seabass) seabass.Body = "This is another message" - res = bp.Message(seabass) + res = bp.message(k, seabass) seabass.Body = "This is a long message" - res = bp.Message(seabass) - res = bp.Message(makeMessage("!seabass says This is a long")) + res = bp.message(k, seabass) + res = bp.message(makeMessage("!seabass says This is a long")) assert.Len(t, mb.Messages, 1) assert.True(t, res) assert.Contains(t, mb.Messages[0], "this is a long message") @@ -116,14 +116,14 @@ func TestBabblerMultiSeed2(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - seabass := makeMessage("This is a message") + k, seabass := makeMessage("This is a message") seabass.User = &user.User{Name: "seabass"} - res := bp.Message(seabass) + res := bp.message(k, seabass) seabass.Body = "This is another message" - res = bp.Message(seabass) + res = bp.message(k, seabass) seabass.Body = "This is a long message" - res = bp.Message(seabass) - res = bp.Message(makeMessage("!seabass says is a long")) + res = bp.message(k, seabass) + res = bp.message(makeMessage("!seabass says is a long")) assert.Len(t, mb.Messages, 1) assert.True(t, res) assert.Contains(t, mb.Messages[0], "is a long message") @@ -133,14 +133,14 @@ func TestBabblerBadSeed(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - seabass := makeMessage("This is a message") + k, seabass := makeMessage("This is a message") seabass.User = &user.User{Name: "seabass"} - bp.Message(seabass) + bp.message(k, seabass) seabass.Body = "This is another message" - bp.Message(seabass) + bp.message(k, seabass) seabass.Body = "This is a long message" - bp.Message(seabass) - bp.Message(makeMessage("!seabass says noooo this is bad")) + bp.message(k, seabass) + bp.message(makeMessage("!seabass says noooo this is bad")) assert.Len(t, mb.Messages, 1) assert.Contains(t, mb.Messages[0], "seabass never said 'noooo this is bad'") } @@ -149,14 +149,14 @@ func TestBabblerBadSeed2(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - seabass := makeMessage("This is a message") + k, seabass := makeMessage("This is a message") seabass.User = &user.User{Name: "seabass"} - bp.Message(seabass) + bp.message(k, seabass) seabass.Body = "This is another message" - bp.Message(seabass) + bp.message(k, seabass) seabass.Body = "This is a long message" - bp.Message(seabass) - bp.Message(makeMessage("!seabass says This is a really")) + bp.message(k, seabass) + bp.message(makeMessage("!seabass says This is a really")) assert.Len(t, mb.Messages, 1) assert.Contains(t, mb.Messages[0], "seabass never said 'this is a really'") } @@ -165,15 +165,15 @@ func TestBabblerSuffixSeed(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - seabass := makeMessage("This is message one") + k, seabass := makeMessage("This is message one") seabass.User = &user.User{Name: "seabass"} - res := bp.Message(seabass) + res := bp.message(k, seabass) seabass.Body = "It's easier to test with unique messages" - res = bp.Message(seabass) + res = bp.message(k, seabass) seabass.Body = "hi there" - res = bp.Message(seabass) - res = bp.Message(makeMessage("!seabass says-tail message one")) - res = bp.Message(makeMessage("!seabass says-tail with unique")) + res = bp.message(k, seabass) + res = bp.message(makeMessage("!seabass says-tail message one")) + res = bp.message(makeMessage("!seabass says-tail with unique")) assert.Len(t, mb.Messages, 2) assert.True(t, res) assert.Contains(t, mb.Messages[0], "this is message one") @@ -184,14 +184,14 @@ func TestBabblerBadSuffixSeed(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - seabass := makeMessage("This is message one") + k, seabass := makeMessage("This is message one") seabass.User = &user.User{Name: "seabass"} - res := bp.Message(seabass) + res := bp.message(k, seabass) seabass.Body = "It's easier to test with unique messages" - res = bp.Message(seabass) + res = bp.message(k, seabass) seabass.Body = "hi there" - res = bp.Message(seabass) - res = bp.Message(makeMessage("!seabass says-tail anything true")) + res = bp.message(k, seabass) + res = bp.message(makeMessage("!seabass says-tail anything true")) assert.Len(t, mb.Messages, 1) assert.True(t, res) assert.Contains(t, mb.Messages[0], "seabass never said 'anything true'") @@ -201,10 +201,10 @@ func TestBabblerBookendSeed(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - seabass := makeMessage("It's easier to test with unique messages") + k, seabass := makeMessage("It's easier to test with unique messages") seabass.User = &user.User{Name: "seabass"} - res := bp.Message(seabass) - res = bp.Message(makeMessage("!seabass says-bridge It's easier | unique messages")) + res := bp.message(k, seabass) + res = bp.message(makeMessage("!seabass says-bridge It's easier | unique messages")) assert.Len(t, mb.Messages, 1) assert.True(t, res) assert.Contains(t, mb.Messages[0], "it's easier to test with unique messages") @@ -214,10 +214,10 @@ func TestBabblerBookendSeedShort(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - seabass := makeMessage("It's easier to test with unique messages") + k, seabass := makeMessage("It's easier to test with unique messages") seabass.User = &user.User{Name: "seabass"} - res := bp.Message(seabass) - res = bp.Message(makeMessage("!seabass says-bridge It's easier to test with | unique messages")) + res := bp.message(k, seabass) + res = bp.message(makeMessage("!seabass says-bridge It's easier to test with | unique messages")) assert.Len(t, mb.Messages, 1) assert.True(t, res) assert.Contains(t, mb.Messages[0], "it's easier to test with unique messages") @@ -227,10 +227,10 @@ func TestBabblerBadBookendSeed(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - seabass := makeMessage("It's easier to test with unique messages") + k, seabass := makeMessage("It's easier to test with unique messages") seabass.User = &user.User{Name: "seabass"} - res := bp.Message(seabass) - res = bp.Message(makeMessage("!seabass says-bridge It's easier | not unique messages")) + res := bp.message(k, seabass) + res = bp.message(makeMessage("!seabass says-bridge It's easier | not unique messages")) assert.Len(t, mb.Messages, 1) assert.True(t, res) assert.Contains(t, mb.Messages[0], "seabass never said 'it's easier ... not unique messages'") @@ -240,10 +240,10 @@ func TestBabblerMiddleOutSeed(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - seabass := makeMessage("It's easier to test with unique messages") + k, seabass := makeMessage("It's easier to test with unique messages") seabass.User = &user.User{Name: "seabass"} - res := bp.Message(seabass) - res = bp.Message(makeMessage("!seabass says-middle-out test with")) + res := bp.message(k, seabass) + res = bp.message(makeMessage("!seabass says-middle-out test with")) assert.Len(t, mb.Messages, 1) assert.True(t, res) assert.Contains(t, mb.Messages[0], "it's easier to test with unique messages") @@ -253,10 +253,10 @@ func TestBabblerBadMiddleOutSeed(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - seabass := makeMessage("It's easier to test with unique messages") + k, seabass := makeMessage("It's easier to test with unique messages") seabass.User = &user.User{Name: "seabass"} - res := bp.Message(seabass) - res = bp.Message(makeMessage("!seabass says-middle-out anything true")) + res := bp.message(k, seabass) + res = bp.message(makeMessage("!seabass says-middle-out anything true")) assert.Len(t, mb.Messages, 1) assert.True(t, res) assert.Equal(t, mb.Messages[0], "seabass never said 'anything true'") @@ -266,10 +266,10 @@ func TestBabblerBatch(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - seabass := makeMessage("batch learn for seabass This is a message! This is another message. This is not a long message? This is not a message! This is not another message. This is a long message?") - res := bp.Message(seabass) + k, seabass := makeMessage("batch learn for seabass This is a message! This is another message. This is not a long message? This is not a message! This is not another message. This is a long message?") + res := bp.message(k, seabass) assert.Len(t, mb.Messages, 1) - res = bp.Message(makeMessage("!seabass says")) + res = bp.message(makeMessage("!seabass says")) assert.Len(t, mb.Messages, 2) assert.True(t, res) assert.Contains(t, mb.Messages[1], "this is") @@ -281,23 +281,23 @@ func TestBabblerMerge(t *testing.T) { bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - seabass := makeMessage(" This is a message") + k, seabass := makeMessage(" This is a message") seabass.User = &user.User{Name: "seabass"} - res := bp.Message(seabass) + res := bp.message(k, seabass) assert.Len(t, mb.Messages, 0) seabass.Body = " This is another message" - res = bp.Message(seabass) + res = bp.message(k, seabass) seabass.Body = " This is a long message" - res = bp.Message(seabass) + res = bp.message(k, seabass) - res = bp.Message(makeMessage("!merge babbler seabass into seabass2")) + res = bp.message(makeMessage("!merge babbler seabass into seabass2")) assert.True(t, res) assert.Len(t, mb.Messages, 1) assert.Contains(t, mb.Messages[0], "mooooiggged") - res = bp.Message(makeMessage("!seabass2 says")) + res = bp.message(makeMessage("!seabass2 says")) assert.True(t, res) assert.Len(t, mb.Messages, 2) @@ -309,24 +309,10 @@ func TestHelp(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - bp.Help("channel", []string{}) + bp.help(bot.Help, msg.Message{Channel: "channel"}, []string{}) assert.Len(t, mb.Messages, 1) } -func TestBotMessage(t *testing.T) { - mb := bot.NewMockBot() - bp := newBabblerPlugin(mb) - assert.NotNil(t, bp) - assert.False(t, bp.BotMessage(makeMessage("test"))) -} - -func TestEvent(t *testing.T) { - mb := bot.NewMockBot() - bp := newBabblerPlugin(mb) - assert.NotNil(t, bp) - assert.False(t, bp.Event("dummy", makeMessage("test"))) -} - func TestRegisterWeb(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) diff --git a/plugins/beers/beers_test.go b/plugins/beers/beers_test.go index 99964e3..bb34008 100644 --- a/plugins/beers/beers_test.go +++ b/plugins/beers/beers_test.go @@ -13,12 +13,12 @@ import ( "github.com/velour/catbase/plugins/counter" ) -func makeMessage(payload string) msg.Message { +func makeMessage(payload string) (bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return msg.Message{ + return bot.Message, msg.Message{ User: &user.User{Name: "tester"}, Channel: "test", Body: payload, @@ -31,8 +31,8 @@ func makeBeersPlugin(t *testing.T) (*BeersPlugin, *bot.MockBot) { counter.New(mb) mb.DB().MustExec(`delete from counter; delete from counter_alias;`) b := New(mb) - b.Message(makeMessage("!mkalias beer :beer:")) - b.Message(makeMessage("!mkalias beers :beer:")) + b.message(makeMessage("!mkalias beer :beer:")) + b.message(makeMessage("!mkalias beers :beer:")) return b, mb } @@ -49,9 +49,9 @@ func TestCounter(t *testing.T) { func TestImbibe(t *testing.T) { b, mb := makeBeersPlugin(t) - b.Message(makeMessage("!imbibe")) + b.message(makeMessage("!imbibe")) assert.Len(t, mb.Messages, 1) - b.Message(makeMessage("!imbibe")) + b.message(makeMessage("!imbibe")) assert.Len(t, mb.Messages, 2) it, err := counter.GetItem(mb.DB(), "tester", itemName) assert.Nil(t, err) @@ -59,7 +59,7 @@ func TestImbibe(t *testing.T) { } func TestEq(t *testing.T) { b, mb := makeBeersPlugin(t) - b.Message(makeMessage("!beers = 3")) + b.message(makeMessage("!beers = 3")) assert.Len(t, mb.Messages, 1) it, err := counter.GetItem(mb.DB(), "tester", itemName) assert.Nil(t, err) @@ -68,7 +68,7 @@ func TestEq(t *testing.T) { func TestEqNeg(t *testing.T) { b, mb := makeBeersPlugin(t) - b.Message(makeMessage("!beers = -3")) + b.message(makeMessage("!beers = -3")) assert.Len(t, mb.Messages, 1) it, err := counter.GetItem(mb.DB(), "tester", itemName) assert.Nil(t, err) @@ -77,8 +77,8 @@ func TestEqNeg(t *testing.T) { func TestEqZero(t *testing.T) { b, mb := makeBeersPlugin(t) - b.Message(makeMessage("beers += 5")) - b.Message(makeMessage("!beers = 0")) + b.message(makeMessage("beers += 5")) + b.message(makeMessage("!beers = 0")) assert.Len(t, mb.Messages, 2) assert.Contains(t, mb.Messages[1], "reversal of fortune") it, err := counter.GetItem(mb.DB(), "tester", itemName) @@ -88,9 +88,9 @@ func TestEqZero(t *testing.T) { func TestBeersPlusEq(t *testing.T) { b, mb := makeBeersPlugin(t) - b.Message(makeMessage("beers += 5")) + b.message(makeMessage("beers += 5")) assert.Len(t, mb.Messages, 1) - b.Message(makeMessage("beers += 5")) + b.message(makeMessage("beers += 5")) assert.Len(t, mb.Messages, 2) it, err := counter.GetItem(mb.DB(), "tester", itemName) assert.Nil(t, err) @@ -99,11 +99,11 @@ func TestBeersPlusEq(t *testing.T) { func TestPuke(t *testing.T) { b, mb := makeBeersPlugin(t) - b.Message(makeMessage("beers += 5")) + b.message(makeMessage("beers += 5")) it, err := counter.GetItem(mb.DB(), "tester", itemName) assert.Nil(t, err) assert.Equal(t, 5, it.Count) - b.Message(makeMessage("puke")) + b.message(makeMessage("puke")) it, err = counter.GetItem(mb.DB(), "tester", itemName) assert.Nil(t, err) assert.Equal(t, 0, it.Count) @@ -111,30 +111,20 @@ func TestPuke(t *testing.T) { func TestBeersReport(t *testing.T) { b, mb := makeBeersPlugin(t) - b.Message(makeMessage("beers += 5")) + b.message(makeMessage("beers += 5")) it, err := counter.GetItem(mb.DB(), "tester", itemName) assert.Nil(t, err) assert.Equal(t, 5, it.Count) - b.Message(makeMessage("beers")) + b.message(makeMessage("beers")) assert.Contains(t, mb.Messages[1], "5 beers") } func TestHelp(t *testing.T) { b, mb := makeBeersPlugin(t) - b.Help("channel", []string{}) + b.help(bot.Help, msg.Message{Channel: "channel"}, []string{}) assert.Len(t, mb.Messages, 1) } -func TestBotMessage(t *testing.T) { - b, _ := makeBeersPlugin(t) - assert.False(t, b.BotMessage(makeMessage("test"))) -} - -func TestEvent(t *testing.T) { - b, _ := makeBeersPlugin(t) - assert.False(t, b.Event("dummy", makeMessage("test"))) -} - func TestRegisterWeb(t *testing.T) { b, _ := makeBeersPlugin(t) assert.Nil(t, b.RegisterWeb()) diff --git a/plugins/couldashouldawoulda/csw_test.go b/plugins/couldashouldawoulda/csw_test.go index 73b5261..de60dc9 100644 --- a/plugins/couldashouldawoulda/csw_test.go +++ b/plugins/couldashouldawoulda/csw_test.go @@ -12,12 +12,12 @@ import ( "github.com/velour/catbase/bot/user" ) -func makeMessage(payload string) msg.Message { +func makeMessage(payload string) (bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return msg.Message{ + return bot.Message, msg.Message{ User: &user.User{Name: "tester"}, Channel: "test", Body: payload, @@ -29,7 +29,7 @@ func Test0(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - res := c.Message(makeMessage("!should I drink a beer?")) + res := c.message(makeMessage("!should I drink a beer?")) assert.Len(t, mb.Messages, 1) assert.True(t, res) possibilities := []string{"Yes.", "No.", "Maybe.", "For fucks sake, how should I know?"} @@ -47,7 +47,7 @@ func Test1(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - res := c.Message(makeMessage("!should I drink a beer or a bourbon?")) + res := c.message(makeMessage("!should I drink a beer or a bourbon?")) assert.Len(t, mb.Messages, 1) assert.True(t, res) possibilities := []string{"The former.", "The latter.", "Obviously the former.", "Clearly the latter.", "Can't it be both?"} @@ -65,7 +65,7 @@ func Test2(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - res := c.Message(makeMessage("!could I drink a beer or a bourbon?")) + res := c.message(makeMessage("!could I drink a beer or a bourbon?")) assert.Len(t, mb.Messages, 1) assert.True(t, res) possibilities := []string{"Yes.", "No.", "Maybe.", "For fucks sake, how should I know?"} @@ -83,7 +83,7 @@ func Test3(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - res := c.Message(makeMessage("!would I die if I drank too much bourbon?")) + res := c.message(makeMessage("!would I die if I drank too much bourbon?")) assert.Len(t, mb.Messages, 1) assert.True(t, res) possibilities := []string{"Yes.", "No.", "Maybe.", "For fucks sake, how should I know?"} @@ -101,7 +101,7 @@ func Test4(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - res := c.Message(makeMessage("!would I die or be sick if I drank all the bourbon?")) + res := c.message(makeMessage("!would I die or be sick if I drank all the bourbon?")) assert.Len(t, mb.Messages, 1) assert.True(t, res) possibilities := []string{"The former.", "The latter.", "Obviously the former.", "Clearly the latter.", "Can't it be both?"} @@ -119,7 +119,7 @@ func Test5(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - res := c.Message(makeMessage("!should I have another beer or bourbon or tequila?")) + res := c.message(makeMessage("!should I have another beer or bourbon or tequila?")) assert.Len(t, mb.Messages, 1) assert.True(t, res) possibilities := []string{"I'd say option", "You'd be an idiot not to choose the"} diff --git a/plugins/counter/counter_test.go b/plugins/counter/counter_test.go index f1c673c..1cbec0c 100644 --- a/plugins/counter/counter_test.go +++ b/plugins/counter/counter_test.go @@ -22,12 +22,12 @@ func setup(t *testing.T) (*bot.MockBot, *CounterPlugin) { return mb, c } -func makeMessage(payload string) msg.Message { +func makeMessage(payload string) (bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return msg.Message{ + return bot.Message, msg.Message{ User: &user.User{Name: "tester"}, Channel: "test", Body: payload, @@ -38,8 +38,8 @@ func makeMessage(payload string) msg.Message { func TestThreeSentencesExists(t *testing.T) { mb, c := setup(t) assert.NotNil(t, c) - c.Message(makeMessage(":beer:++")) - c.Message(makeMessage(":beer:. Earl Grey. Hot.")) + c.message(makeMessage(":beer:++")) + c.message(makeMessage(":beer:. Earl Grey. Hot.")) item, err := GetItem(mb.DB(), "tester", ":beer:") assert.Nil(t, err) assert.Equal(t, 2, item.Count) @@ -49,7 +49,7 @@ func TestThreeSentencesNotExists(t *testing.T) { mb, c := setup(t) assert.NotNil(t, c) item, err := GetItem(mb.DB(), "tester", ":beer:") - c.Message(makeMessage(":beer:. Earl Grey. Hot.")) + c.message(makeMessage(":beer:. Earl Grey. Hot.")) item, err = GetItem(mb.DB(), "tester", ":beer:") assert.Nil(t, err) assert.Equal(t, 0, item.Count) @@ -58,8 +58,8 @@ func TestThreeSentencesNotExists(t *testing.T) { func TestTeaEarlGreyHot(t *testing.T) { mb, c := setup(t) assert.NotNil(t, c) - c.Message(makeMessage("Tea. Earl Grey. Hot.")) - c.Message(makeMessage("Tea. Earl Grey. Hot.")) + c.message(makeMessage("Tea. Earl Grey. Hot.")) + c.message(makeMessage("Tea. Earl Grey. Hot.")) item, err := GetItem(mb.DB(), "tester", ":tea:") assert.Nil(t, err) assert.Equal(t, 2, item.Count) @@ -68,8 +68,8 @@ func TestTeaEarlGreyHot(t *testing.T) { func TestTeaTwoPeriods(t *testing.T) { mb, c := setup(t) assert.NotNil(t, c) - c.Message(makeMessage("Tea. Earl Grey.")) - c.Message(makeMessage("Tea. Earl Grey.")) + c.message(makeMessage("Tea. Earl Grey.")) + c.message(makeMessage("Tea. Earl Grey.")) item, err := GetItem(mb.DB(), "tester", ":tea:") assert.Nil(t, err) assert.Equal(t, 0, item.Count) @@ -78,8 +78,8 @@ func TestTeaTwoPeriods(t *testing.T) { func TestTeaMultiplePeriods(t *testing.T) { mb, c := setup(t) assert.NotNil(t, c) - c.Message(makeMessage("Tea. Earl Grey. Spiked. Hot.")) - c.Message(makeMessage("Tea. Earl Grey. Spiked. Hot.")) + c.message(makeMessage("Tea. Earl Grey. Spiked. Hot.")) + c.message(makeMessage("Tea. Earl Grey. Spiked. Hot.")) item, err := GetItem(mb.DB(), "tester", ":tea:") assert.Nil(t, err) assert.Equal(t, 2, item.Count) @@ -88,9 +88,9 @@ func TestTeaMultiplePeriods(t *testing.T) { func TestTeaGreenHot(t *testing.T) { mb, c := setup(t) assert.NotNil(t, c) - c.Message(makeMessage("Tea. Green. Hot.")) - c.Message(makeMessage("Tea. Green. Hot")) - c.Message(makeMessage("Tea. Green. Iced.")) + c.message(makeMessage("Tea. Green. Hot.")) + c.message(makeMessage("Tea. Green. Hot")) + c.message(makeMessage("Tea. Green. Iced.")) item, err := GetItem(mb.DB(), "tester", ":tea:") assert.Nil(t, err) assert.Equal(t, 3, item.Count) @@ -99,8 +99,8 @@ func TestTeaGreenHot(t *testing.T) { func TestTeaUnrelated(t *testing.T) { mb, c := setup(t) assert.NotNil(t, c) - c.Message(makeMessage("Tea.")) - c.Message(makeMessage("Tea. It's great.")) + c.message(makeMessage("Tea.")) + c.message(makeMessage("Tea. It's great.")) item, err := GetItem(mb.DB(), "tester", ":tea:") assert.Nil(t, err) assert.Equal(t, 0, item.Count) @@ -109,7 +109,7 @@ func TestTeaUnrelated(t *testing.T) { func TestTeaSkieselQuote(t *testing.T) { mb, c := setup(t) assert.NotNil(t, c) - c.Message(makeMessage("blah, this is a whole page of explanation where \"we did local search and used a tabu list\" would have sufficed")) + c.message(makeMessage("blah, this is a whole page of explanation where \"we did local search and used a tabu list\" would have sufficed")) item, err := GetItem(mb.DB(), "tester", ":tea:") assert.Nil(t, err) assert.Equal(t, 0, item.Count) @@ -117,7 +117,7 @@ func TestTeaSkieselQuote(t *testing.T) { func TestTeaUnicodeJapanese(t *testing.T) { mb, c := setup(t) assert.NotNil(t, c) - c.Message(makeMessage("Tea. おちや. Hot.")) + c.message(makeMessage("Tea. おちや. Hot.")) item, err := GetItem(mb.DB(), "tester", ":tea:") assert.Nil(t, err) assert.Equal(t, 1, item.Count) @@ -126,8 +126,8 @@ func TestTeaUnicodeJapanese(t *testing.T) { func TestResetMe(t *testing.T) { mb, c := setup(t) assert.NotNil(t, c) - c.Message(makeMessage("test++")) - c.Message(makeMessage("!reset me")) + c.message(makeMessage("test++")) + c.message(makeMessage("!reset me")) items, err := GetItems(mb.DB(), "tester") assert.Nil(t, err) assert.Len(t, items, 0) @@ -136,7 +136,7 @@ func TestResetMe(t *testing.T) { func TestCounterOne(t *testing.T) { mb, c := setup(t) assert.NotNil(t, c) - c.Message(makeMessage("test++")) + c.message(makeMessage("test++")) assert.Len(t, mb.Messages, 1) assert.Equal(t, mb.Messages[0], "tester has 1 test.") } @@ -144,7 +144,7 @@ func TestCounterOne(t *testing.T) { func TestCounterOneWithSpace(t *testing.T) { mb, c := setup(t) assert.NotNil(t, c) - c.Message(makeMessage(":test: ++")) + c.message(makeMessage(":test: ++")) assert.Len(t, mb.Messages, 1) assert.Equal(t, mb.Messages[0], "tester has 1 :test:.") } @@ -153,7 +153,7 @@ func TestCounterFour(t *testing.T) { mb, c := setup(t) assert.NotNil(t, c) for i := 0; i < 4; i++ { - c.Message(makeMessage("test++")) + c.message(makeMessage("test++")) } assert.Len(t, mb.Messages, 4) assert.Equal(t, mb.Messages[3], "tester has 4 test.") @@ -163,10 +163,10 @@ func TestCounterDecrement(t *testing.T) { mb, c := setup(t) assert.NotNil(t, c) for i := 0; i < 4; i++ { - c.Message(makeMessage("test++")) + c.message(makeMessage("test++")) assert.Equal(t, mb.Messages[i], fmt.Sprintf("tester has %d test.", i+1)) } - c.Message(makeMessage("test--")) + c.message(makeMessage("test--")) assert.Len(t, mb.Messages, 5) assert.Equal(t, mb.Messages[4], "tester has 3 test.") } @@ -175,10 +175,10 @@ func TestFriendCounterDecrement(t *testing.T) { mb, c := setup(t) assert.NotNil(t, c) for i := 0; i < 4; i++ { - c.Message(makeMessage("other.test++")) + c.message(makeMessage("other.test++")) assert.Equal(t, mb.Messages[i], fmt.Sprintf("other has %d test.", i+1)) } - c.Message(makeMessage("other.test--")) + c.message(makeMessage("other.test--")) assert.Len(t, mb.Messages, 5) assert.Equal(t, mb.Messages[4], "other has 3 test.") } @@ -187,12 +187,12 @@ func TestDecrementZero(t *testing.T) { mb, c := setup(t) assert.NotNil(t, c) for i := 0; i < 4; i++ { - c.Message(makeMessage("test++")) + c.message(makeMessage("test++")) assert.Equal(t, mb.Messages[i], fmt.Sprintf("tester has %d test.", i+1)) } j := 4 for i := 4; i > 0; i-- { - c.Message(makeMessage("test--")) + c.message(makeMessage("test--")) assert.Equal(t, mb.Messages[j], fmt.Sprintf("tester has %d test.", i-1)) j++ } @@ -204,10 +204,10 @@ func TestClear(t *testing.T) { mb, c := setup(t) assert.NotNil(t, c) for i := 0; i < 4; i++ { - c.Message(makeMessage("test++")) + c.message(makeMessage("test++")) assert.Equal(t, mb.Messages[i], fmt.Sprintf("tester has %d test.", i+1)) } - res := c.Message(makeMessage("!clear test")) + res := c.message(makeMessage("!clear test")) assert.True(t, res) assert.Len(t, mb.Actions, 1) assert.Equal(t, mb.Actions[0], "chops a few test out of his brain") @@ -217,10 +217,10 @@ func TestCount(t *testing.T) { mb, c := setup(t) assert.NotNil(t, c) for i := 0; i < 4; i++ { - c.Message(makeMessage("test++")) + c.message(makeMessage("test++")) assert.Equal(t, mb.Messages[i], fmt.Sprintf("tester has %d test.", i+1)) } - res := c.Message(makeMessage("!count test")) + res := c.message(makeMessage("!count test")) assert.True(t, res) assert.Len(t, mb.Messages, 5) assert.Equal(t, mb.Messages[4], "tester has 4 test.") @@ -230,18 +230,18 @@ func TestInspectMe(t *testing.T) { mb, c := setup(t) assert.NotNil(t, c) for i := 0; i < 4; i++ { - c.Message(makeMessage("test++")) + c.message(makeMessage("test++")) assert.Equal(t, mb.Messages[i], fmt.Sprintf("tester has %d test.", i+1)) } for i := 0; i < 2; i++ { - c.Message(makeMessage("fucks++")) + c.message(makeMessage("fucks++")) assert.Equal(t, mb.Messages[i+4], fmt.Sprintf("tester has %d fucks.", i+1)) } for i := 0; i < 20; i++ { - c.Message(makeMessage("cheese++")) + c.message(makeMessage("cheese++")) assert.Equal(t, mb.Messages[i+6], fmt.Sprintf("tester has %d cheese.", i+1)) } - res := c.Message(makeMessage("!inspect me")) + res := c.message(makeMessage("!inspect me")) assert.True(t, res) assert.Len(t, mb.Messages, 27) assert.Equal(t, mb.Messages[26], "tester has the following counters: test: 4, fucks: 2, cheese: 20.") @@ -250,22 +250,10 @@ func TestInspectMe(t *testing.T) { func TestHelp(t *testing.T) { mb, c := setup(t) assert.NotNil(t, c) - c.Help("channel", []string{}) + c.help(bot.Help, msg.Message{Channel: "channel"}, []string{}) assert.Len(t, mb.Messages, 1) } -func TestBotMessage(t *testing.T) { - _, c := setup(t) - assert.NotNil(t, c) - assert.False(t, c.BotMessage(makeMessage("test"))) -} - -func TestEvent(t *testing.T) { - _, c := setup(t) - assert.NotNil(t, c) - assert.False(t, c.Event("dummy", makeMessage("test"))) -} - func TestRegisterWeb(t *testing.T) { _, c := setup(t) assert.NotNil(t, c) diff --git a/plugins/dice/dice_test.go b/plugins/dice/dice_test.go index ec060de..bcb7a26 100644 --- a/plugins/dice/dice_test.go +++ b/plugins/dice/dice_test.go @@ -12,12 +12,12 @@ import ( "github.com/velour/catbase/bot/user" ) -func makeMessage(payload string) msg.Message { +func makeMessage(payload string) (bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return msg.Message{ + return bot.Message, msg.Message{ User: &user.User{Name: "tester"}, Channel: "test", Body: payload, @@ -29,7 +29,7 @@ func TestDie(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - res := c.Message(makeMessage("!1d6")) + res := c.message(makeMessage("!1d6")) assert.Len(t, mb.Messages, 1) assert.True(t, res) assert.Contains(t, mb.Messages[0], "tester, you rolled:") @@ -39,7 +39,7 @@ func TestDice(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - res := c.Message(makeMessage("!5d6")) + res := c.message(makeMessage("!5d6")) assert.Len(t, mb.Messages, 1) assert.True(t, res) assert.Contains(t, mb.Messages[0], "tester, you rolled:") @@ -49,7 +49,7 @@ func TestNotCommand(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - res := c.Message(makeMessage("1d6")) + res := c.message(makeMessage("1d6")) assert.False(t, res) assert.Len(t, mb.Messages, 0) } @@ -58,7 +58,7 @@ func TestBadDice(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - res := c.Message(makeMessage("!aued6")) + res := c.message(makeMessage("!aued6")) assert.False(t, res) assert.Len(t, mb.Messages, 0) } @@ -67,7 +67,7 @@ func TestBadSides(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - res := c.Message(makeMessage("!1daoeu")) + res := c.message(makeMessage("!1daoeu")) assert.False(t, res) assert.Len(t, mb.Messages, 0) } @@ -76,7 +76,7 @@ func TestLotsOfDice(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - res := c.Message(makeMessage("!100d100")) + res := c.message(makeMessage("!100d100")) assert.True(t, res) assert.Len(t, mb.Messages, 1) assert.Contains(t, mb.Messages[0], "You're a dick.") @@ -86,24 +86,10 @@ func TestHelp(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - c.Help("channel", []string{}) + c.help(bot.Help, msg.Message{Channel: "channel"}, []string{}) assert.Len(t, mb.Messages, 1) } -func TestBotMessage(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) - assert.NotNil(t, c) - assert.False(t, c.BotMessage(makeMessage("test"))) -} - -func TestEvent(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) - assert.NotNil(t, c) - assert.False(t, c.Event("dummy", makeMessage("test"))) -} - func TestRegisterWeb(t *testing.T) { mb := bot.NewMockBot() c := New(mb) diff --git a/plugins/fact/remember_test.go b/plugins/fact/remember_test.go index 6e8c8a1..a3ded28 100644 --- a/plugins/fact/remember_test.go +++ b/plugins/fact/remember_test.go @@ -42,7 +42,7 @@ func TestCornerCaseBug(t *testing.T) { p, _, mb := makePlugin(t) for _, m := range msgs { - p.Message(m) + p.message(bot.Message, m) } assert.Len(t, mb.Messages, 1) assert.Contains(t, mb.Messages[0], "horse dick") @@ -59,7 +59,7 @@ func TestReact(t *testing.T) { _, p, mb := makePlugin(t) for _, m := range msgs { - p.Message(m) + p.message(bot.Message, m) } assert.Len(t, mb.Reactions, 1) assert.Contains(t, mb.Reactions[0], "jesus") @@ -72,7 +72,7 @@ func TestReactCantLearnSpaces(t *testing.T) { _, p, mb := makePlugin(t) for _, m := range msgs { - p.Message(m) + p.message(bot.Message, m) } assert.Len(t, mb.Messages, 1) assert.Contains(t, mb.Messages[0], "not a valid") diff --git a/plugins/leftpad/leftpad_test.go b/plugins/leftpad/leftpad_test.go index 21e4af4..1d97488 100644 --- a/plugins/leftpad/leftpad_test.go +++ b/plugins/leftpad/leftpad_test.go @@ -13,12 +13,12 @@ import ( "github.com/velour/catbase/plugins/counter" ) -func makeMessage(payload string) msg.Message { +func makeMessage(payload string) (bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return msg.Message{ + return bot.Message, msg.Message{ User: &user.User{Name: "tester"}, Channel: "test", Body: payload, @@ -37,28 +37,28 @@ func makePlugin(t *testing.T) (*LeftpadPlugin, *bot.MockBot) { func TestLeftpad(t *testing.T) { p, mb := makePlugin(t) - p.Message(makeMessage("!leftpad test 8 test")) + p.message(makeMessage("!leftpad test 8 test")) assert.Contains(t, mb.Messages[0], "testtest") assert.Len(t, mb.Messages, 1) } func TestBadNumber(t *testing.T) { p, mb := makePlugin(t) - p.Message(makeMessage("!leftpad test fuck test")) + p.message(makeMessage("!leftpad test fuck test")) assert.Contains(t, mb.Messages[0], "Invalid") assert.Len(t, mb.Messages, 1) } func TestNotCommand(t *testing.T) { p, mb := makePlugin(t) - p.Message(makeMessage("leftpad test fuck test")) + p.message(makeMessage("leftpad test fuck test")) assert.Len(t, mb.Messages, 0) } func TestNoMaxLen(t *testing.T) { p, mb := makePlugin(t) p.config.Set("LeftPad.MaxLen", "0") - p.Message(makeMessage("!leftpad dicks 100 dicks")) + p.message(makeMessage("!leftpad dicks 100 dicks")) assert.Len(t, mb.Messages, 1) assert.Contains(t, mb.Messages[0], "dicks") } @@ -67,7 +67,7 @@ func Test50Padding(t *testing.T) { p, mb := makePlugin(t) p.config.Set("LeftPad.MaxLen", "50") assert.Equal(t, 50, p.config.GetInt("LeftPad.MaxLen", 100)) - p.Message(makeMessage("!leftpad dicks 100 dicks")) + p.message(makeMessage("!leftpad dicks 100 dicks")) assert.Len(t, mb.Messages, 1) assert.Contains(t, mb.Messages[0], "kill me") } @@ -75,33 +75,17 @@ func Test50Padding(t *testing.T) { func TestUnder50Padding(t *testing.T) { p, mb := makePlugin(t) p.config.Set("LeftPad.MaxLen", "50") - p.Message(makeMessage("!leftpad dicks 49 dicks")) + p.message(makeMessage("!leftpad dicks 49 dicks")) assert.Len(t, mb.Messages, 1) assert.Contains(t, mb.Messages[0], "dicks") } func TestNotPadding(t *testing.T) { p, mb := makePlugin(t) - p.Message(makeMessage("!lololol")) + p.message(makeMessage("!lololol")) assert.Len(t, mb.Messages, 0) } -func TestHelp(t *testing.T) { - p, mb := makePlugin(t) - p.Help("channel", []string{}) - assert.Len(t, mb.Messages, 0) -} - -func TestBotMessage(t *testing.T) { - p, _ := makePlugin(t) - assert.False(t, p.BotMessage(makeMessage("test"))) -} - -func TestEvent(t *testing.T) { - p, _ := makePlugin(t) - assert.False(t, p.Event("dummy", makeMessage("test"))) -} - func TestRegisterWeb(t *testing.T) { p, _ := makePlugin(t) assert.Nil(t, p.RegisterWeb()) diff --git a/plugins/nerdepedia/nerdepeida_test.go b/plugins/nerdepedia/nerdepeida_test.go index 5cba343..eb59807 100644 --- a/plugins/nerdepedia/nerdepeida_test.go +++ b/plugins/nerdepedia/nerdepeida_test.go @@ -12,12 +12,12 @@ import ( "github.com/velour/catbase/bot/user" ) -func makeMessage(payload string) msg.Message { +func makeMessage(payload string) (bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return msg.Message{ + return bot.Message, msg.Message{ User: &user.User{Name: "tester"}, Channel: "test", Body: payload, @@ -29,7 +29,7 @@ func TestWars(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - res := c.Message(makeMessage("help me obi-wan")) + res := c.message(makeMessage("help me obi-wan")) assert.Len(t, mb.Messages, 1) assert.True(t, res) } @@ -38,7 +38,7 @@ func TestTrek(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - res := c.Message(makeMessage("live long and prosper")) + res := c.message(makeMessage("live long and prosper")) assert.Len(t, mb.Messages, 1) assert.True(t, res) } @@ -47,7 +47,7 @@ func TestDune(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - res := c.Message(makeMessage("bless the maker")) + res := c.message(makeMessage("bless the maker")) assert.Len(t, mb.Messages, 1) assert.True(t, res) } @@ -56,7 +56,7 @@ func TestPoke(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - res := c.Message(makeMessage("gotta catch em all")) + res := c.message(makeMessage("gotta catch em all")) assert.Len(t, mb.Messages, 1) assert.True(t, res) } diff --git a/plugins/picker/picker_test.go b/plugins/picker/picker_test.go index 0c2dd60..b01684a 100644 --- a/plugins/picker/picker_test.go +++ b/plugins/picker/picker_test.go @@ -12,12 +12,12 @@ import ( "github.com/velour/catbase/bot/user" ) -func makeMessage(payload string) msg.Message { +func makeMessage(payload string) (bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return msg.Message{ + return bot.Message, msg.Message{ User: &user.User{Name: "tester"}, Channel: "test", Body: payload, @@ -29,7 +29,7 @@ func TestPick2(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - res := c.Message(makeMessage("!pick 2 { a, b,c}")) + res := c.message(makeMessage("!pick 2 { a, b,c}")) assert.Len(t, mb.Messages, 1) if !res { t.Fatalf("expected a successful choice, got %q", mb.Messages[0]) @@ -40,7 +40,7 @@ func TestPickDefault(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - _ = c.Message(makeMessage("!pick { a}")) + _ = c.message(makeMessage("!pick { a}")) assert.Len(t, mb.Messages, 1) assert.Equal(t, `I've chosen "a" for you.`, mb.Messages[0]) } diff --git a/plugins/reminder/reminder_test.go b/plugins/reminder/reminder_test.go index d3c6155..c4a4e9e 100644 --- a/plugins/reminder/reminder_test.go +++ b/plugins/reminder/reminder_test.go @@ -14,25 +14,16 @@ import ( "github.com/velour/catbase/bot/user" ) -func makeMessage(payload string) msg.Message { - isCmd := strings.HasPrefix(payload, "!") - if isCmd { - payload = payload[1:] - } - return msg.Message{ - User: &user.User{Name: "tester"}, - Channel: "test", - Body: payload, - Command: isCmd, - } +func makeMessage(payload string) (bot.Kind, msg.Message) { + return makeMessageBy(payload, "tester") } -func makeMessageBy(payload, by string) msg.Message { +func makeMessageBy(payload, by string) (bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return msg.Message{ + return bot.Message, msg.Message{ User: &user.User{Name: by}, Channel: "test", Body: payload, @@ -49,7 +40,7 @@ func setup(t *testing.T) (*ReminderPlugin, *bot.MockBot) { func TestMeReminder(t *testing.T) { c, mb := setup(t) - res := c.Message(makeMessage("!remind me in 1s don't fail this test")) + res := c.message(makeMessage("!remind me in 1s don't fail this test")) time.Sleep(2 * time.Second) assert.Len(t, mb.Messages, 2) assert.True(t, res) @@ -59,7 +50,7 @@ func TestMeReminder(t *testing.T) { func TestReminder(t *testing.T) { c, mb := setup(t) - res := c.Message(makeMessage("!remind testuser in 1s don't fail this test")) + res := c.message(makeMessage("!remind testuser in 1s don't fail this test")) time.Sleep(2 * time.Second) assert.Len(t, mb.Messages, 2) assert.True(t, res) @@ -69,9 +60,9 @@ func TestReminder(t *testing.T) { func TestReminderReorder(t *testing.T) { c, mb := setup(t) - res := c.Message(makeMessage("!remind testuser in 2s don't fail this test 2")) + res := c.message(makeMessage("!remind testuser in 2s don't fail this test 2")) assert.True(t, res) - res = c.Message(makeMessage("!remind testuser in 1s don't fail this test 1")) + res = c.message(makeMessage("!remind testuser in 1s don't fail this test 1")) assert.True(t, res) time.Sleep(5 * time.Second) assert.Len(t, mb.Messages, 4) @@ -83,7 +74,7 @@ func TestReminderReorder(t *testing.T) { func TestReminderParse(t *testing.T) { c, mb := setup(t) - res := c.Message(makeMessage("!remind testuser in unparseable don't fail this test")) + res := c.message(makeMessage("!remind testuser in unparseable don't fail this test")) assert.Len(t, mb.Messages, 1) assert.True(t, res) assert.Contains(t, mb.Messages[0], "Easy cowboy, not sure I can parse that duration.") @@ -91,7 +82,7 @@ func TestReminderParse(t *testing.T) { func TestEmptyList(t *testing.T) { c, mb := setup(t) - res := c.Message(makeMessage("!list reminders")) + res := c.message(makeMessage("!list reminders")) assert.Len(t, mb.Messages, 1) assert.True(t, res) assert.Contains(t, mb.Messages[0], "no pending reminders") @@ -99,11 +90,11 @@ func TestEmptyList(t *testing.T) { func TestList(t *testing.T) { c, mb := setup(t) - res := c.Message(makeMessage("!remind testuser in 5m don't fail this test 1")) + res := c.message(makeMessage("!remind testuser in 5m don't fail this test 1")) assert.True(t, res) - res = c.Message(makeMessage("!remind testuser in 5m don't fail this test 2")) + res = c.message(makeMessage("!remind testuser in 5m don't fail this test 2")) assert.True(t, res) - res = c.Message(makeMessage("!list reminders")) + res = c.message(makeMessage("!list reminders")) assert.True(t, res) assert.Len(t, mb.Messages, 3) assert.Contains(t, mb.Messages[2], "1) tester -> testuser :: don't fail this test 1 @ ") @@ -112,11 +103,11 @@ func TestList(t *testing.T) { func TestListBy(t *testing.T) { c, mb := setup(t) - res := c.Message(makeMessageBy("!remind testuser in 5m don't fail this test 1", "testuser")) + res := c.message(makeMessageBy("!remind testuser in 5m don't fail this test 1", "testuser")) assert.True(t, res) - res = c.Message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2")) + res = c.message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2")) assert.True(t, res) - res = c.Message(makeMessage("!list reminders from testuser")) + res = c.message(makeMessage("!list reminders from testuser")) assert.True(t, res) assert.Len(t, mb.Messages, 3) assert.Contains(t, mb.Messages[2], "don't fail this test 1 @ ") @@ -125,11 +116,11 @@ func TestListBy(t *testing.T) { func TestListTo(t *testing.T) { c, mb := setup(t) - res := c.Message(makeMessageBy("!remind testuser2 in 5m don't fail this test 1", "testuser")) + res := c.message(makeMessageBy("!remind testuser2 in 5m don't fail this test 1", "testuser")) assert.True(t, res) - res = c.Message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2")) + res = c.message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2")) assert.True(t, res) - res = c.Message(makeMessage("!list reminders to testuser")) + res = c.message(makeMessage("!list reminders to testuser")) assert.True(t, res) assert.Len(t, mb.Messages, 3) assert.NotContains(t, mb.Messages[2], "don't fail this test 1 @ ") @@ -138,11 +129,11 @@ func TestListTo(t *testing.T) { func TestToEmptyList(t *testing.T) { c, mb := setup(t) - res := c.Message(makeMessageBy("!remind testuser2 in 5m don't fail this test 1", "testuser")) + res := c.message(makeMessageBy("!remind testuser2 in 5m don't fail this test 1", "testuser")) assert.True(t, res) - res = c.Message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2")) + res = c.message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2")) assert.True(t, res) - res = c.Message(makeMessage("!list reminders to test")) + res = c.message(makeMessage("!list reminders to test")) assert.True(t, res) assert.Len(t, mb.Messages, 3) assert.Contains(t, mb.Messages[2], "no pending reminders") @@ -150,11 +141,11 @@ func TestToEmptyList(t *testing.T) { func TestFromEmptyList(t *testing.T) { c, mb := setup(t) - res := c.Message(makeMessageBy("!remind testuser2 in 5m don't fail this test 1", "testuser")) + res := c.message(makeMessageBy("!remind testuser2 in 5m don't fail this test 1", "testuser")) assert.True(t, res) - res = c.Message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2")) + res = c.message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2")) assert.True(t, res) - res = c.Message(makeMessage("!list reminders from test")) + res = c.message(makeMessage("!list reminders from test")) assert.True(t, res) assert.Len(t, mb.Messages, 3) assert.Contains(t, mb.Messages[2], "no pending reminders") @@ -164,9 +155,9 @@ func TestBatchMax(t *testing.T) { c, mb := setup(t) c.config.Set("Reminder.MaxBatchAdd", "10") assert.NotNil(t, c) - res := c.Message(makeMessage("!remind testuser every 1h for 24h yikes")) + res := c.message(makeMessage("!remind testuser every 1h for 24h yikes")) assert.True(t, res) - res = c.Message(makeMessage("!list reminders")) + res = c.message(makeMessage("!list reminders")) assert.True(t, res) time.Sleep(6 * time.Second) assert.Len(t, mb.Messages, 2) @@ -180,11 +171,11 @@ func TestBatchMax(t *testing.T) { func TestCancel(t *testing.T) { c, mb := setup(t) assert.NotNil(t, c) - res := c.Message(makeMessage("!remind testuser in 1m don't fail this test")) + res := c.message(makeMessage("!remind testuser in 1m don't fail this test")) assert.True(t, res) - res = c.Message(makeMessage("!cancel reminder 1")) + res = c.message(makeMessage("!cancel reminder 1")) assert.True(t, res) - res = c.Message(makeMessage("!list reminders")) + res = c.message(makeMessage("!list reminders")) assert.True(t, res) assert.Len(t, mb.Messages, 3) assert.Contains(t, mb.Messages[0], "Sure tester, I'll remind testuser.") @@ -195,7 +186,7 @@ func TestCancel(t *testing.T) { func TestCancelMiss(t *testing.T) { c, mb := setup(t) assert.NotNil(t, c) - res := c.Message(makeMessage("!cancel reminder 1")) + res := c.message(makeMessage("!cancel reminder 1")) assert.True(t, res) assert.Len(t, mb.Messages, 1) assert.Contains(t, mb.Messages[0], "failed to find and cancel reminder: 1") @@ -208,13 +199,13 @@ func TestLimitList(t *testing.T) { assert.NotNil(t, c) //Someone can redo this with a single batch add, but I can't locally due to an old version of sqllite (maybe). - res := c.Message(makeMessage("!remind testuser every 1h for 10h don't fail this test")) + res := c.message(makeMessage("!remind testuser every 1h for 10h don't fail this test")) assert.True(t, res) - res = c.Message(makeMessage("!remind testuser every 1h for 10h don't fail this test")) + res = c.message(makeMessage("!remind testuser every 1h for 10h don't fail this test")) assert.True(t, res) - res = c.Message(makeMessage("!remind testuser every 1h for 10h don't fail this test")) + res = c.message(makeMessage("!remind testuser every 1h for 10h don't fail this test")) assert.True(t, res) - res = c.Message(makeMessage("!list reminders")) + res = c.message(makeMessage("!list reminders")) assert.True(t, res) assert.Len(t, mb.Messages, 4) assert.Contains(t, mb.Messages[0], "Sure tester, I'll remind testuser.") @@ -232,22 +223,10 @@ func TestLimitList(t *testing.T) { func TestHelp(t *testing.T) { c, mb := setup(t) assert.NotNil(t, c) - c.Help("channel", []string{}) + c.help(bot.Help, msg.Message{Channel: "channel"}, []string{}) assert.Len(t, mb.Messages, 1) } -func TestBotMessage(t *testing.T) { - c, _ := setup(t) - assert.NotNil(t, c) - assert.False(t, c.BotMessage(makeMessage("test"))) -} - -func TestEvent(t *testing.T) { - c, _ := setup(t) - assert.NotNil(t, c) - assert.False(t, c.Event("dummy", makeMessage("test"))) -} - func TestRegisterWeb(t *testing.T) { c, _ := setup(t) assert.NotNil(t, c) diff --git a/plugins/rss/rss_test.go b/plugins/rss/rss_test.go index 7c20fd1..900031a 100644 --- a/plugins/rss/rss_test.go +++ b/plugins/rss/rss_test.go @@ -11,12 +11,12 @@ import ( "github.com/velour/catbase/bot/user" ) -func makeMessage(payload string) msg.Message { +func makeMessage(payload string) (bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return msg.Message{ + return bot.Message, msg.Message{ User: &user.User{Name: "tester"}, Channel: "test", Body: payload, @@ -28,7 +28,7 @@ func TestRSS(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - res := c.Message(makeMessage("!rss http://rss.cnn.com/rss/edition.rss")) + res := c.message(makeMessage("!rss http://rss.cnn.com/rss/edition.rss")) assert.Len(t, mb.Messages, 1) assert.True(t, res) } @@ -38,7 +38,7 @@ func TestRSSPaging(t *testing.T) { c := New(mb) assert.NotNil(t, c) for i := 0; i < 20; i++ { - res := c.Message(makeMessage("!rss http://rss.cnn.com/rss/edition.rss")) + res := c.message(makeMessage("!rss http://rss.cnn.com/rss/edition.rss")) assert.True(t, res) } diff --git a/plugins/talker/talker_test.go b/plugins/talker/talker_test.go index dd45ec1..2408f45 100644 --- a/plugins/talker/talker_test.go +++ b/plugins/talker/talker_test.go @@ -12,12 +12,12 @@ import ( "github.com/velour/catbase/bot/user" ) -func makeMessage(payload string) msg.Message { +func makeMessage(payload string) (bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return msg.Message{ + return bot.Message, msg.Message{ User: &user.User{Name: "tester"}, Channel: "test", Body: payload, @@ -29,7 +29,7 @@ func TestGoatse(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - res := c.Message(makeMessage("goatse")) + res := c.message(makeMessage("goatse")) assert.Len(t, mb.Messages, 0) assert.False(t, res) } @@ -38,7 +38,7 @@ func TestGoatseCommand(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - res := c.Message(makeMessage("!goatse")) + res := c.message(makeMessage("!goatse")) assert.Len(t, mb.Messages, 1) assert.True(t, res) assert.Contains(t, mb.Messages[0], "g o a t s e") @@ -48,7 +48,7 @@ func TestGoatseWithNickCommand(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - res := c.Message(makeMessage("!goatse seabass")) + res := c.message(makeMessage("!goatse seabass")) assert.Len(t, mb.Messages, 1) assert.True(t, res) assert.Contains(t, mb.Messages[0], "g o a t s e") @@ -59,7 +59,7 @@ func TestSay(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - res := c.Message(makeMessage("say hello")) + res := c.message(makeMessage("say hello")) assert.Len(t, mb.Messages, 0) assert.False(t, res) } @@ -68,7 +68,7 @@ func TestSayCommand(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - res := c.Message(makeMessage("!say hello")) + res := c.message(makeMessage("!say hello")) assert.Len(t, mb.Messages, 1) assert.True(t, res) assert.Contains(t, mb.Messages[0], "hello") @@ -78,24 +78,10 @@ func TestHelp(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - c.Help("channel", []string{}) + c.help(bot.Help, msg.Message{Channel: "channel"}, []string{}) assert.Len(t, mb.Messages, 1) } -func TestBotMessage(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) - assert.NotNil(t, c) - assert.False(t, c.BotMessage(makeMessage("test"))) -} - -func TestEvent(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) - assert.NotNil(t, c) - assert.False(t, c.Event("dummy", makeMessage("test"))) -} - func TestRegisterWeb(t *testing.T) { mb := bot.NewMockBot() c := New(mb) diff --git a/plugins/twitch/twitch_test.go b/plugins/twitch/twitch_test.go index 728fe00..b3d88ec 100644 --- a/plugins/twitch/twitch_test.go +++ b/plugins/twitch/twitch_test.go @@ -12,12 +12,12 @@ import ( "github.com/velour/catbase/bot/user" ) -func makeMessage(payload string) msg.Message { +func makeMessage(payload string) (bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return msg.Message{ + return bot.Message, msg.Message{ User: &user.User{Name: "tester"}, Channel: "test", Body: payload, @@ -44,6 +44,6 @@ func makeTwitchPlugin(t *testing.T) (*TwitchPlugin, *bot.MockBot) { func TestTwitch(t *testing.T) { b, mb := makeTwitchPlugin(t) - b.Message(makeMessage("!twitch status")) + b.message(makeMessage("!twitch status")) assert.NotEmpty(t, mb.Messages) } diff --git a/plugins/your/your_test.go b/plugins/your/your_test.go index 75a0ace..3c6136a 100644 --- a/plugins/your/your_test.go +++ b/plugins/your/your_test.go @@ -12,12 +12,12 @@ import ( "github.com/velour/catbase/bot/user" ) -func makeMessage(payload string) msg.Message { +func makeMessage(payload string) (bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return msg.Message{ + return bot.Message, msg.Message{ User: &user.User{Name: "tester"}, Channel: "test", Body: payload, @@ -39,7 +39,7 @@ func TestReplacement(t *testing.T) { c.config.Set("your.replacements.0.freq", "1.0") c.config.Set("your.replacements.0.this", "fuck") c.config.Set("your.replacements.0.that", "duck") - res := c.Message(makeMessage("fuck a duck")) + res := c.message(makeMessage("fuck a duck")) assert.True(t, res) assert.Len(t, mb.Messages, 1) assert.Contains(t, mb.Messages[0], "duck a duck") @@ -60,6 +60,6 @@ func TestNoReplacement(t *testing.T) { c.config.Set("your.replacements.2.freq", "1.0") c.config.Set("your.replacements.2.this", "Fuck") c.config.Set("your.replacements.2.that", "duck") - c.Message(makeMessage("fuck a duck")) + c.message(makeMessage("fuck a duck")) assert.Len(t, mb.Messages, 0) } From f661d7dca22c573a15da25b1c6421868bb021cc2 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Tue, 5 Feb 2019 15:13:32 -0500 Subject: [PATCH 030/107] bot: update callback registry types Unfortunately, we can't hash the plugins. I went ahead and hashed the types instead. --- bot/bot.go | 12 +++++++----- bot/handlers.go | 4 +++- bot/interfaces.go | 4 +++- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/bot/bot.go b/bot/bot.go index d7eca51..1859f32 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -6,6 +6,7 @@ import ( "html/template" "log" "net/http" + "reflect" "strings" "github.com/jmoiron/sqlx" @@ -249,11 +250,12 @@ func (b *bot) RegisterFilter(name string, f func(string) string) { // Register a callback func (b *bot) Register(p Plugin, kind Kind, cb Callback) { - if _, ok := b.callbacks[p]; !ok { - b.callbacks[p] = make(map[Kind][]Callback) + t := reflect.TypeOf(p) + if _, ok := b.callbacks[t]; !ok { + b.callbacks[t] = make(map[Kind][]Callback) } - if _, ok := b.callbacks[p][kind]; !ok { - b.callbacks[p][kind] = []Callback{} + if _, ok := b.callbacks[t][kind]; !ok { + b.callbacks[t][kind] = []Callback{} } - b.callbacks[p][kind] = append(b.callbacks[p][kind], cb) + b.callbacks[t][kind] = append(b.callbacks[t][kind], cb) } diff --git a/bot/handlers.go b/bot/handlers.go index be789cd..4c95f1c 100644 --- a/bot/handlers.go +++ b/bot/handlers.go @@ -8,6 +8,7 @@ import ( "fmt" "log" "math/rand" + "reflect" "regexp" "strconv" "strings" @@ -39,7 +40,8 @@ RET: } func (b *bot) runCallback(plugin Plugin, evt Kind, message msg.Message, args ...interface{}) bool { - for _, cb := range b.callbacks[plugin][evt] { + t := reflect.TypeOf(plugin) + for _, cb := range b.callbacks[t][evt] { if cb(evt, message, args) { return true } diff --git a/bot/interfaces.go b/bot/interfaces.go index 1a348dc..e4ec4d8 100644 --- a/bot/interfaces.go +++ b/bot/interfaces.go @@ -3,6 +3,8 @@ package bot import ( + "reflect" + "github.com/jmoiron/sqlx" "github.com/velour/catbase/bot/msg" "github.com/velour/catbase/bot/user" @@ -32,7 +34,7 @@ const ( type Kind int type Callback func(Kind, msg.Message, ...interface{}) bool -type CallbackMap map[Plugin]map[Kind][]Callback +type CallbackMap map[reflect.Type]map[Kind][]Callback // Bot interface serves to allow mocking of the actual bot type Bot interface { From 4b5b66dd6db71b5fecec03076260e4be3c0411a8 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Tue, 5 Feb 2019 15:24:49 -0500 Subject: [PATCH 031/107] bot: unroll args --- bot/handlers.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/handlers.go b/bot/handlers.go index 4c95f1c..2108afc 100644 --- a/bot/handlers.go +++ b/bot/handlers.go @@ -29,7 +29,7 @@ func (b *bot) Receive(kind Kind, msg msg.Message, args ...interface{}) { } for _, name := range b.pluginOrdering { - if b.runCallback(b.plugins[name], kind, msg, args) { + if b.runCallback(b.plugins[name], kind, msg, args...) { goto RET } } @@ -42,7 +42,7 @@ RET: func (b *bot) runCallback(plugin Plugin, evt Kind, message msg.Message, args ...interface{}) bool { t := reflect.TypeOf(plugin) for _, cb := range b.callbacks[t][evt] { - if cb(evt, message, args) { + if cb(evt, message, args...) { return true } } From 287c26440e0c800072bf5355c1ad1671798fb26f Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Tue, 5 Feb 2019 16:01:12 -0500 Subject: [PATCH 032/107] emojify: don't chomp up the rest of the pipeline added bonus fix --- main.go | 2 +- plugins/emojifyme/emojifyme.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 18ff59f..eb07687 100644 --- a/main.go +++ b/main.go @@ -79,6 +79,7 @@ func main() { b := bot.New(c, client) b.AddPlugin("admin", admin.New(b)) + b.AddPlugin("emojifyme", emojifyme.New(b)) b.AddPlugin("first", first.New(b)) b.AddPlugin("leftpad", leftpad.New(b)) b.AddPlugin("talker", talker.New(b)) @@ -93,7 +94,6 @@ func main() { b.AddPlugin("zork", zork.New(b)) b.AddPlugin("rss", rss.New(b)) b.AddPlugin("reaction", reaction.New(b)) - b.AddPlugin("emojifyme", emojifyme.New(b)) b.AddPlugin("twitch", twitch.New(b)) b.AddPlugin("inventory", inventory.New(b)) b.AddPlugin("rpgORdie", rpgORdie.New(b)) diff --git a/plugins/emojifyme/emojifyme.go b/plugins/emojifyme/emojifyme.go index 76610d8..03b7fbb 100644 --- a/plugins/emojifyme/emojifyme.go +++ b/plugins/emojifyme/emojifyme.go @@ -94,7 +94,7 @@ func (p *EmojifyMePlugin) message(kind bot.Kind, message msg.Message, args ...in for _, e := range emojys { p.Bot.Send(bot.Reaction, message.Channel, e, message) } - return true + return false } return false } From a25d7b9effa2cc5ff3cb88c5f2376f570a60d284 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Tue, 5 Feb 2019 16:10:36 -0500 Subject: [PATCH 033/107] bot: get rid of a string I introduced reflection earlier. I may as well go whole hog now. --- bot/bot.go | 3 ++- bot/interfaces.go | 2 +- bot/mock.go | 2 +- main.go | 50 +++++++++++++++++++++++------------------------ 4 files changed, 29 insertions(+), 28 deletions(-) diff --git a/bot/bot.go b/bot/bot.go index 1859f32..c5b237d 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -112,7 +112,8 @@ func (b *bot) migrateDB() { } // Adds a constructed handler to the bots handlers list -func (b *bot) AddPlugin(name string, h Plugin) { +func (b *bot) AddPlugin(h Plugin) { + name := reflect.TypeOf(h).String() b.plugins[name] = h b.pluginOrdering = append(b.pluginOrdering, name) if entry := h.RegisterWeb(); entry != nil { diff --git a/bot/interfaces.go b/bot/interfaces.go index e4ec4d8..74eed27 100644 --- a/bot/interfaces.go +++ b/bot/interfaces.go @@ -45,7 +45,7 @@ type Bot interface { // Who lists users in a particular channel Who(string) []user.User // AddPlugin registers a new plugin handler - AddPlugin(string, Plugin) + AddPlugin(Plugin) // First arg should be one of bot.Message/Reply/Action/etc Send(Kind, ...interface{}) (string, error) // First arg should be one of bot.Message/Reply/Action/etc diff --git a/bot/mock.go b/bot/mock.go index 70dd1df..ea8986e 100644 --- a/bot/mock.go +++ b/bot/mock.go @@ -46,7 +46,7 @@ func (mb *MockBot) Send(kind Kind, args ...interface{}) (string, error) { } return "ERR", fmt.Errorf("Mesasge type unhandled") } -func (mb *MockBot) AddPlugin(name string, f Plugin) {} +func (mb *MockBot) AddPlugin(f Plugin) {} func (mb *MockBot) Register(p Plugin, kind Kind, cb Callback) {} func (mb *MockBot) Receive(kind Kind, msg msg.Message, args ...interface{}) {} func (mb *MockBot) Filter(msg msg.Message, s string) string { return s } diff --git a/main.go b/main.go index eb07687..6ab529d 100644 --- a/main.go +++ b/main.go @@ -78,32 +78,32 @@ func main() { b := bot.New(c, client) - b.AddPlugin("admin", admin.New(b)) - b.AddPlugin("emojifyme", emojifyme.New(b)) - b.AddPlugin("first", first.New(b)) - b.AddPlugin("leftpad", leftpad.New(b)) - b.AddPlugin("talker", talker.New(b)) - b.AddPlugin("dice", dice.New(b)) - b.AddPlugin("picker", picker.New(b)) - b.AddPlugin("beers", beers.New(b)) - b.AddPlugin("remember", fact.NewRemember(b)) - b.AddPlugin("your", your.New(b)) - b.AddPlugin("counter", counter.New(b)) - b.AddPlugin("reminder", reminder.New(b)) - b.AddPlugin("babbler", babbler.New(b)) - b.AddPlugin("zork", zork.New(b)) - b.AddPlugin("rss", rss.New(b)) - b.AddPlugin("reaction", reaction.New(b)) - b.AddPlugin("twitch", twitch.New(b)) - b.AddPlugin("inventory", inventory.New(b)) - b.AddPlugin("rpgORdie", rpgORdie.New(b)) - b.AddPlugin("sisyphus", sisyphus.New(b)) - b.AddPlugin("tell", tell.New(b)) - b.AddPlugin("couldashouldawoulda", couldashouldawoulda.New(b)) - b.AddPlugin("nedepedia", nerdepedia.New(b)) + b.AddPlugin(admin.New(b)) + b.AddPlugin(emojifyme.New(b)) + b.AddPlugin(first.New(b)) + b.AddPlugin(leftpad.New(b)) + b.AddPlugin(talker.New(b)) + b.AddPlugin(dice.New(b)) + b.AddPlugin(picker.New(b)) + b.AddPlugin(beers.New(b)) + b.AddPlugin(fact.NewRemember(b)) + b.AddPlugin(your.New(b)) + b.AddPlugin(counter.New(b)) + b.AddPlugin(reminder.New(b)) + b.AddPlugin(babbler.New(b)) + b.AddPlugin(zork.New(b)) + b.AddPlugin(rss.New(b)) + b.AddPlugin(reaction.New(b)) + b.AddPlugin(twitch.New(b)) + b.AddPlugin(inventory.New(b)) + b.AddPlugin(rpgORdie.New(b)) + b.AddPlugin(sisyphus.New(b)) + b.AddPlugin(tell.New(b)) + b.AddPlugin(couldashouldawoulda.New(b)) + b.AddPlugin(nerdepedia.New(b)) // catches anything left, will always return true - b.AddPlugin("factoid", fact.New(b)) - b.AddPlugin("db", db.New(b)) + b.AddPlugin(fact.New(b)) + b.AddPlugin(db.New(b)) for { err := client.Serve() From dea3f07dc299cbe11672d5bb6b260358b4b32268 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Tue, 5 Feb 2019 16:21:34 -0500 Subject: [PATCH 034/107] go: update the dependencies --- go.mod | 8 ++++---- go.sum | 8 ++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 313d566..45d8122 100644 --- a/go.mod +++ b/go.mod @@ -5,21 +5,21 @@ require ( github.com/boltdb/bolt v1.3.1 github.com/chrissexton/leftpad v0.0.0-20181207133115-1e93189d2fff github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-sql-driver/mysql v1.4.1 // indirect github.com/gorilla/websocket v1.4.0 // indirect github.com/jmoiron/sqlx v1.2.0 github.com/mattn/go-sqlite3 v1.10.0 github.com/mitchellh/mapstructure v1.1.2 // indirect github.com/mmcdole/gofeed v1.0.0-beta2 github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d // indirect github.com/stretchr/objx v0.1.1 // indirect - github.com/stretchr/testify v1.2.2 + github.com/stretchr/testify v1.3.0 github.com/velour/chat v0.0.0-20180713122344-fd1d1606cb89 github.com/velour/velour v0.0.0-20160303155839-8e090e68d158 github.com/yuin/gluamapper v0.0.0-20150323120927-d836955830e7 - github.com/yuin/gopher-lua v0.0.0-20181214045814-db9ae37725ec - golang.org/x/net v0.0.0-20181217023233-e147a9138326 // indirect + github.com/yuin/gopher-lua v0.0.0-20190125051437-7b9317363aa9 + golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 // indirect golang.org/x/text v0.3.0 // indirect gopkg.in/sourcemap.v1 v1.0.5 // indirect ) diff --git a/go.sum b/go.sum index 187d147..2b566da 100644 --- a/go.sum +++ b/go.sum @@ -7,9 +7,11 @@ github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx2 github.com/chrissexton/leftpad v0.0.0-20181207133115-1e93189d2fff h1:+TEqaP0eO1unI7XHHFeMDhsxhLDIb0x8KYuZbqbAmxA= github.com/chrissexton/leftpad v0.0.0-20181207133115-1e93189d2fff/go.mod h1:QCRjR0b4qiJiNjuP7RFM89bh4UExGJalcWmYeSvlnRc= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= @@ -28,10 +30,13 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d h1:1VUlQbCfkoSGv7qP7Y+ro3ap1P1pPZxgdGVqiTVy5C4= github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/velour/chat v0.0.0-20180713122344-fd1d1606cb89 h1:3D3M900hEBJJAqyKl70QuRHi5weX9+ptlQI1v+FNcQ8= github.com/velour/chat v0.0.0-20180713122344-fd1d1606cb89/go.mod h1:ejwOYCjnDMyO5LXFXRARQJGBZ6xQJZ3rgAHE5drSuMM= github.com/velour/velour v0.0.0-20160303155839-8e090e68d158 h1:p3rTUXxzuKsBOsHlkly7+rj9wagFBKeIsCDKkDII9sw= @@ -40,10 +45,13 @@ github.com/yuin/gluamapper v0.0.0-20150323120927-d836955830e7 h1:noHsffKZsNfU38D github.com/yuin/gluamapper v0.0.0-20150323120927-d836955830e7/go.mod h1:bbMEM6aU1WDF1ErA5YJ0p91652pGv140gGw4Ww3RGp8= github.com/yuin/gopher-lua v0.0.0-20181214045814-db9ae37725ec h1:vpF8Kxql6/3OvGH4y2SKtpN3WsB17mvJ8f8H1o2vucQ= github.com/yuin/gopher-lua v0.0.0-20181214045814-db9ae37725ec/go.mod h1:fFiAh+CowNFr0NK5VASokuwKwkbacRmHsVA7Yb1Tqac= +github.com/yuin/gopher-lua v0.0.0-20190125051437-7b9317363aa9/go.mod h1:fFiAh+CowNFr0NK5VASokuwKwkbacRmHsVA7Yb1Tqac= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181217023233-e147a9138326 h1:iCzOf0xz39Tstp+Tu/WwyGjUXCk34QhQORRxBeXXTA4= golang.org/x/net v0.0.0-20181217023233-e147a9138326/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 h1:ulvT7fqt0yHWzpJwI57MezWnYDVpCAYBVuYst/L+fAY= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= From c50417302203eb81ffb4f4446b56dd3b82c5b451 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Tue, 5 Feb 2019 21:13:35 -0500 Subject: [PATCH 035/107] twitch: check gameID instead of title --- plugins/twitch/twitch.go | 31 ++++++++++++++++--------------- plugins/twitch/twitch_test.go | 4 ++-- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/plugins/twitch/twitch.go b/plugins/twitch/twitch.go index 0c17a24..b77a2be 100644 --- a/plugins/twitch/twitch.go +++ b/plugins/twitch/twitch.go @@ -23,8 +23,8 @@ type TwitchPlugin struct { } type Twitcher struct { - name string - game string + name string + gameID string } func (t Twitcher) URL() string { @@ -62,8 +62,8 @@ func New(b bot.Bot) *TwitchPlugin { for _, twitcherName := range p.config.GetArray("Twitch."+ch+".Users", []string{}) { if _, ok := p.twitchList[twitcherName]; !ok { p.twitchList[twitcherName] = &Twitcher{ - name: twitcherName, - game: "", + name: twitcherName, + gameID: "", } } } @@ -95,7 +95,7 @@ func (p *TwitchPlugin) serveStreaming(w http.ResponseWriter, r *http.Request) { } status := "NO." - if twitcher.game != "" { + if twitcher.gameID != "" { status = "YES." } context := map[string]interface{}{"Name": twitcher.name, "Status": status} @@ -207,26 +207,27 @@ func (p *TwitchPlugin) checkTwitch(channel string, twitcher *Twitcher, alwaysPri } games := s.Data - game := "" + gameID, title := "", "" if len(games) > 0 { - game = games[0].Title + gameID = games[0].GameID + title = games[0].Title } streamWord := p.config.Get("Twitch.StreamWord", "streaming") if alwaysPrintStatus { - if game == "" { + if gameID == "" { p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s is not %s.", twitcher.name, streamWord)) } else { - p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s is %s %s at %s", twitcher.name, streamWord, game, twitcher.URL())) + p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s is %s %s at %s", twitcher.name, streamWord, title, twitcher.URL())) } - } else if game == "" { - if twitcher.game != "" { + } else if gameID == "" { + if twitcher.gameID != "" { p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s just stopped %s.", twitcher.name, streamWord)) } - twitcher.game = "" + twitcher.gameID = "" } else { - if twitcher.game != game { - p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s is %s %s at %s", twitcher.name, streamWord, game, twitcher.URL())) + if twitcher.gameID != gameID { + p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s is %s %s at %s", twitcher.name, streamWord, title, twitcher.URL())) } - twitcher.game = game + twitcher.gameID = gameID } } diff --git a/plugins/twitch/twitch_test.go b/plugins/twitch/twitch_test.go index b3d88ec..120defa 100644 --- a/plugins/twitch/twitch_test.go +++ b/plugins/twitch/twitch_test.go @@ -35,8 +35,8 @@ func makeTwitchPlugin(t *testing.T) (*TwitchPlugin, *bot.MockBot) { assert.NotNil(t, c) c.twitchList["drseabass"] = &Twitcher{ - name: "drseabass", - game: "", + name: "drseabass", + gameID: "", } return c, mb From 00d998fc5953546fb4752db87690b7f469108180 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Tue, 5 Feb 2019 21:32:33 -0500 Subject: [PATCH 036/107] beers: be quiet --- plugins/beers/beers.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/beers/beers.go b/plugins/beers/beers.go index 7736936..3ed679a 100644 --- a/plugins/beers/beers.go +++ b/plugins/beers/beers.go @@ -358,7 +358,6 @@ func (p *BeersPlugin) checkUntappd(channel string) { log.Fatal(err) } userMap[u.untappdUser] = u - log.Printf("Found untappd user: %#v", u) if u.chanNick == "" { log.Fatal("Empty chanNick for no good reason.") } @@ -373,7 +372,6 @@ func (p *BeersPlugin) checkUntappd(channel string) { checkin := chks[i-1] if checkin.Checkin_id <= userMap[checkin.User.User_name].lastCheckin { - log.Printf("User %s already check in >%d", checkin.User.User_name, checkin.Checkin_id) continue } From 980b079bf385a9c50a971e8fb1ce863883fbea3b Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Tue, 5 Feb 2019 22:52:49 -0500 Subject: [PATCH 037/107] slackApp: create new connector for an app * Using a library because I'm lazy. * Fixed a few noisy things in Twitch and Untappd * Moved connectors to a common place --- bot/handlers.go | 4 +- bot/interfaces.go | 4 +- bot/mock.go | 12 +- connectors/irc/irc.go | 295 +++++++++++++ connectors/slack/fix_text.go | 96 +++++ connectors/slack/slack.go | 736 ++++++++++++++++++++++++++++++++ connectors/slackapp/fix_text.go | 96 +++++ connectors/slackapp/slackApp.go | 447 +++++++++++++++++++ go.mod | 2 + go.sum | 4 + main.go | 9 +- plugins/twitch/twitch.go | 4 + 12 files changed, 1696 insertions(+), 13 deletions(-) create mode 100644 connectors/irc/irc.go create mode 100644 connectors/slack/fix_text.go create mode 100644 connectors/slack/slack.go create mode 100644 connectors/slackapp/fix_text.go create mode 100644 connectors/slackapp/slackApp.go diff --git a/bot/handlers.go b/bot/handlers.go index 2108afc..0a4781e 100644 --- a/bot/handlers.go +++ b/bot/handlers.go @@ -17,7 +17,7 @@ import ( "github.com/velour/catbase/bot/msg" ) -func (b *bot) Receive(kind Kind, msg msg.Message, args ...interface{}) { +func (b *bot) Receive(kind Kind, msg msg.Message, args ...interface{}) bool { log.Println("Received event: ", msg) // msg := b.buildMessage(client, inMsg) @@ -36,7 +36,7 @@ func (b *bot) Receive(kind Kind, msg msg.Message, args ...interface{}) { RET: b.logIn <- msg - return + return true } func (b *bot) runCallback(plugin Plugin, evt Kind, message msg.Message, args ...interface{}) bool { diff --git a/bot/interfaces.go b/bot/interfaces.go index 74eed27..eb318ca 100644 --- a/bot/interfaces.go +++ b/bot/interfaces.go @@ -49,7 +49,7 @@ type Bot interface { // First arg should be one of bot.Message/Reply/Action/etc Send(Kind, ...interface{}) (string, error) // First arg should be one of bot.Message/Reply/Action/etc - Receive(Kind, msg.Message, ...interface{}) + Receive(Kind, msg.Message, ...interface{}) bool // Register a callback Register(Plugin, Kind, Callback) @@ -63,7 +63,7 @@ type Bot interface { // Connector represents a server connection to a chat service type Connector interface { - RegisterEvent(func(Kind, msg.Message, ...interface{})) + RegisterEvent(Callback) Send(Kind, ...interface{}) (string, error) diff --git a/bot/mock.go b/bot/mock.go index ea8986e..ec48201 100644 --- a/bot/mock.go +++ b/bot/mock.go @@ -46,12 +46,12 @@ func (mb *MockBot) Send(kind Kind, args ...interface{}) (string, error) { } return "ERR", fmt.Errorf("Mesasge type unhandled") } -func (mb *MockBot) AddPlugin(f Plugin) {} -func (mb *MockBot) Register(p Plugin, kind Kind, cb Callback) {} -func (mb *MockBot) Receive(kind Kind, msg msg.Message, args ...interface{}) {} -func (mb *MockBot) Filter(msg msg.Message, s string) string { return s } -func (mb *MockBot) LastMessage(ch string) (msg.Message, error) { return msg.Message{}, nil } -func (mb *MockBot) CheckAdmin(nick string) bool { return false } +func (mb *MockBot) AddPlugin(f Plugin) {} +func (mb *MockBot) Register(p Plugin, kind Kind, cb Callback) {} +func (mb *MockBot) Receive(kind Kind, msg msg.Message, args ...interface{}) bool { return false } +func (mb *MockBot) Filter(msg msg.Message, s string) string { return s } +func (mb *MockBot) LastMessage(ch string) (msg.Message, error) { return msg.Message{}, nil } +func (mb *MockBot) CheckAdmin(nick string) bool { return false } func (mb *MockBot) react(channel, reaction string, message msg.Message) (string, error) { mb.Reactions = append(mb.Reactions, reaction) diff --git a/connectors/irc/irc.go b/connectors/irc/irc.go new file mode 100644 index 0000000..a10deea --- /dev/null +++ b/connectors/irc/irc.go @@ -0,0 +1,295 @@ +// © 2016 the CatBase Authors under the WTFPL license. See AUTHORS for the list of authors. + +package irc + +import ( + "fmt" + "io" + "log" + "os" + "strings" + "time" + + "github.com/velour/catbase/bot" + "github.com/velour/catbase/bot/msg" + "github.com/velour/catbase/bot/user" + "github.com/velour/catbase/config" + "github.com/velour/velour/irc" +) + +const ( + // DefaultPort is the port used to connect to + // the server if one is not specified. + defaultPort = "6667" + + // InitialTimeout is the initial amount of time + // to delay before reconnecting. Each failed + // reconnection doubles the timout until + // a connection is made successfully. + initialTimeout = 2 * time.Second + + // PingTime is the amount of inactive time + // to wait before sending a ping to the server. + pingTime = 120 * time.Second + + actionPrefix = "\x01ACTION" +) + +var throttle <-chan time.Time + +type Irc struct { + Client *irc.Client + config *config.Config + quit chan bool + + event bot.Callback +} + +func New(c *config.Config) *Irc { + i := Irc{} + i.config = c + + return &i +} + +func (i *Irc) RegisterEvent(f bot.Callback) { + i.event = f +} + +func (i *Irc) Send(kind bot.Kind, args ...interface{}) (string, error) { + switch kind { + case bot.Reply: + case bot.Message: + return i.sendMessage(args[0].(string), args[1].(string)) + case bot.Action: + return i.sendAction(args[0].(string), args[1].(string)) + default: + } + return "", nil +} + +func (i *Irc) JoinChannel(channel string) { + log.Printf("Joining channel: %s", channel) + i.Client.Out <- irc.Msg{Cmd: irc.JOIN, Args: []string{channel}} +} + +func (i *Irc) sendMessage(channel, message string) (string, error) { + for len(message) > 0 { + m := irc.Msg{ + Cmd: "PRIVMSG", + Args: []string{channel, message}, + } + _, err := m.RawString() + if err != nil { + mtl := err.(irc.MsgTooLong) + m.Args[1] = message[:mtl.NTrunc] + message = message[mtl.NTrunc:] + } else { + message = "" + } + + if throttle == nil { + ratePerSec := i.config.GetInt("RatePerSec", 5) + throttle = time.Tick(time.Second / time.Duration(ratePerSec)) + } + + <-throttle + + i.Client.Out <- m + } + return "NO_IRC_IDENTIFIERS", nil +} + +// Sends action to channel +func (i *Irc) sendAction(channel, message string) (string, error) { + message = actionPrefix + " " + message + "\x01" + + return i.sendMessage(channel, message) +} + +func (i *Irc) GetEmojiList() map[string]string { + //we're not going to do anything because it's IRC + return make(map[string]string) +} + +func (i *Irc) Serve() error { + if i.event == nil { + return fmt.Errorf("Missing an event handler") + } + + var err error + i.Client, err = irc.DialSSL( + i.config.Get("Irc.Server", "localhost"), + i.config.Get("Nick", "bot"), + i.config.Get("FullName", "bot"), + i.config.Get("Irc.Pass", ""), + true, + ) + if err != nil { + return fmt.Errorf("%s", err) + } + + for _, c := range i.config.GetArray("channels", []string{}) { + i.JoinChannel(c) + } + + i.quit = make(chan bool) + go i.handleConnection() + <-i.quit + return nil +} + +func (i *Irc) handleConnection() { + t := time.NewTimer(pingTime) + + defer func() { + t.Stop() + close(i.Client.Out) + for err := range i.Client.Errors { + if err != io.EOF { + log.Println(err) + } + } + }() + + for { + select { + case msg, ok := <-i.Client.In: + if !ok { // disconnect + i.quit <- true + return + } + t.Stop() + t = time.NewTimer(pingTime) + i.handleMsg(msg) + + case <-t.C: + i.Client.Out <- irc.Msg{Cmd: irc.PING, Args: []string{i.Client.Server}} + t = time.NewTimer(pingTime) + + case err, ok := <-i.Client.Errors: + if ok && err != io.EOF { + log.Println(err) + i.quit <- true + return + } + } + } +} + +// HandleMsg handles IRC messages from the server. +func (i *Irc) handleMsg(msg irc.Msg) { + botMsg := i.buildMessage(msg) + + switch msg.Cmd { + case irc.ERROR: + log.Println(1, "Received error: "+msg.Raw) + + case irc.PING: + i.Client.Out <- irc.Msg{Cmd: irc.PONG} + + case irc.PONG: + // OK, ignore + + case irc.ERR_NOSUCHNICK: + fallthrough + + case irc.ERR_NOSUCHCHANNEL: + fallthrough + + case irc.RPL_MOTD: + fallthrough + + case irc.RPL_NAMREPLY: + fallthrough + + case irc.RPL_TOPIC: + fallthrough + + case irc.KICK: + fallthrough + + case irc.TOPIC: + fallthrough + + case irc.MODE: + fallthrough + + case irc.JOIN: + fallthrough + + case irc.PART: + fallthrough + + case irc.NOTICE: + fallthrough + + case irc.NICK: + fallthrough + + case irc.RPL_WHOREPLY: + fallthrough + + case irc.RPL_ENDOFWHO: + i.event(bot.Event, botMsg) + + case irc.PRIVMSG: + i.event(bot.Message, botMsg) + + case irc.QUIT: + os.Exit(1) + + default: + cmd := irc.CmdNames[msg.Cmd] + log.Println("(" + cmd + ") " + msg.Raw) + } +} + +// Builds our internal message type out of a Conn & Line from irc +func (i *Irc) buildMessage(inMsg irc.Msg) msg.Message { + // Check for the user + u := user.User{ + Name: inMsg.Origin, + } + + channel := inMsg.Args[0] + if channel == i.config.Get("Nick", "bot") { + channel = inMsg.Args[0] + } + + isAction := false + var message string + if len(inMsg.Args) > 1 { + message = inMsg.Args[1] + + isAction = strings.HasPrefix(message, actionPrefix) + if isAction { + message = strings.TrimRight(message[len(actionPrefix):], "\x01") + message = strings.TrimSpace(message) + } + + } + + iscmd := false + filteredMessage := message + if !isAction { + iscmd, filteredMessage = bot.IsCmd(i.config, message) + } + + msg := msg.Message{ + User: &u, + Channel: channel, + Body: filteredMessage, + Raw: message, + Command: iscmd, + Action: isAction, + Time: time.Now(), + Host: inMsg.Host, + } + + return msg +} + +func (i Irc) Who(channel string) []string { + return []string{} +} diff --git a/connectors/slack/fix_text.go b/connectors/slack/fix_text.go new file mode 100644 index 0000000..28a8c1e --- /dev/null +++ b/connectors/slack/fix_text.go @@ -0,0 +1,96 @@ +package slack + +import ( + "unicode/utf8" +) + +// fixText strips all of the Slack-specific annotations from message text, +// replacing it with the equivalent display form. +// Currently it: +// • Replaces user mentions like <@U124356> with @ followed by the user's nick. +// This uses the lookupUser function, which must map U1243456 to the nick. +// • Replaces user mentions like with the user's nick. +// • Strips < and > surrounding links. +// +// This was directly bogarted from velour/chat with emoji conversion removed. +func fixText(findUser func(id string) (string, bool), text string) string { + var output []rune + for len(text) > 0 { + r, i := utf8.DecodeRuneInString(text) + text = text[i:] + switch { + case r == '<': + var tag []rune + for { + r, i := utf8.DecodeRuneInString(text) + text = text[i:] + switch { + case r == '>': + if t, ok := fixTag(findUser, tag); ok { + output = append(output, t...) + break + } + fallthrough + case len(text) == 0: + output = append(output, '<') + output = append(output, tag...) + output = append(output, r) + default: + tag = append(tag, r) + continue + } + break + } + default: + output = append(output, r) + } + } + return string(output) +} + +func fixTag(findUser func(string) (string, bool), tag []rune) ([]rune, bool) { + switch { + case hasPrefix(tag, "@U"): + if i := indexRune(tag, '|'); i >= 0 { + return tag[i+1:], true + } + if findUser != nil { + if u, ok := findUser(string(tag[1:])); ok { + return []rune(u), true + } + } + return tag, true + + case hasPrefix(tag, "#C"): + if i := indexRune(tag, '|'); i >= 0 { + return append([]rune{'#'}, tag[i+1:]...), true + } + + case hasPrefix(tag, "http"): + if i := indexRune(tag, '|'); i >= 0 { + tag = tag[:i] + } + return tag, true + } + + return nil, false +} + +func hasPrefix(text []rune, prefix string) bool { + for _, r := range prefix { + if len(text) == 0 || text[0] != r { + return false + } + text = text[1:] + } + return true +} + +func indexRune(text []rune, find rune) int { + for i, r := range text { + if r == find { + return i + } + } + return -1 +} diff --git a/connectors/slack/slack.go b/connectors/slack/slack.go new file mode 100644 index 0000000..6fe4691 --- /dev/null +++ b/connectors/slack/slack.go @@ -0,0 +1,736 @@ +// © 2016 the CatBase Authors under the WTFPL license. See AUTHORS for the list of authors. + +// Package slack connects to slack service +package slack + +import ( + "encoding/json" + "errors" + "fmt" + "html" + "io" + "io/ioutil" + "log" + "net/http" + "net/url" + "regexp" + "strconv" + "strings" + + // "sync/atomic" + "context" + "time" + + "github.com/velour/catbase/bot" + "github.com/velour/catbase/bot/msg" + "github.com/velour/catbase/bot/user" + "github.com/velour/catbase/config" + "github.com/velour/chat/websocket" +) + +type Slack struct { + config *config.Config + + url string + id string + token string + ws *websocket.Conn + + lastRecieved time.Time + + users map[string]string + + myBotID string + + emoji map[string]string + + event bot.Callback +} + +var idCounter uint64 + +type slackUserInfoResp struct { + Ok bool `json:"ok"` + User struct { + ID string `json:"id"` + Name string `json:"name"` + } `json:"user"` +} + +type slackChannelListItem struct { + ID string `json:"id"` + Name string `json:"name"` + IsChannel bool `json:"is_channel"` + Created int `json:"created"` + Creator string `json:"creator"` + IsArchived bool `json:"is_archived"` + IsGeneral bool `json:"is_general"` + NameNormalized string `json:"name_normalized"` + IsShared bool `json:"is_shared"` + IsOrgShared bool `json:"is_org_shared"` + IsMember bool `json:"is_member"` + Members []string `json:"members"` + Topic struct { + Value string `json:"value"` + Creator string `json:"creator"` + LastSet int `json:"last_set"` + } `json:"topic"` + Purpose struct { + Value string `json:"value"` + Creator string `json:"creator"` + LastSet int `json:"last_set"` + } `json:"purpose"` + PreviousNames []interface{} `json:"previous_names"` + NumMembers int `json:"num_members"` +} + +type slackChannelListResp struct { + Ok bool `json:"ok"` + Channels []slackChannelListItem `json:"channels"` +} + +type slackChannelInfoResp struct { + Ok bool `json:"ok"` + Channel struct { + ID string `json:"id"` + Name string `json:"name"` + IsChannel bool `json:"is_channel"` + Created int `json:"created"` + Creator string `json:"creator"` + IsArchived bool `json:"is_archived"` + IsGeneral bool `json:"is_general"` + NameNormalized string `json:"name_normalized"` + IsReadOnly bool `json:"is_read_only"` + IsShared bool `json:"is_shared"` + IsOrgShared bool `json:"is_org_shared"` + IsMember bool `json:"is_member"` + LastRead string `json:"last_read"` + Latest struct { + Type string `json:"type"` + User string `json:"user"` + Text string `json:"text"` + Ts string `json:"ts"` + } `json:"latest"` + UnreadCount int `json:"unread_count"` + UnreadCountDisplay int `json:"unread_count_display"` + Members []string `json:"members"` + Topic struct { + Value string `json:"value"` + Creator string `json:"creator"` + LastSet int64 `json:"last_set"` + } `json:"topic"` + Purpose struct { + Value string `json:"value"` + Creator string `json:"creator"` + LastSet int `json:"last_set"` + } `json:"purpose"` + PreviousNames []string `json:"previous_names"` + } `json:"channel"` +} + +type slackMessage struct { + ID uint64 `json:"id"` + Type string `json:"type"` + SubType string `json:"subtype"` + Hidden bool `json:"hidden"` + Channel string `json:"channel"` + Text string `json:"text"` + User string `json:"user"` + Username string `json:"username"` + BotID string `json:"bot_id"` + Ts string `json:"ts"` + ThreadTs string `json:"thread_ts"` + Error struct { + Code uint64 `json:"code"` + Msg string `json:"msg"` + } `json:"error"` +} + +type slackReaction struct { + Reaction string `json:"name"` + Channel string `json:"channel"` + Timestamp float64 `json:"timestamp"` +} + +type rtmStart struct { + Ok bool `json:"ok"` + Error string `json:"error"` + URL string `json:"url"` + Self struct { + ID string `json:"id"` + } `json:"self"` +} + +func New(c *config.Config) *Slack { + token := c.Get("slack.token", "NONE") + if token == "NONE" { + log.Fatalf("No slack token found. Set SLACKTOKEN env.") + } + return &Slack{ + config: c, + token: c.Get("slack.token", ""), + lastRecieved: time.Now(), + users: make(map[string]string), + emoji: make(map[string]string), + } +} + +func (s *Slack) Send(kind bot.Kind, args ...interface{}) (string, error) { + switch kind { + case bot.Message: + return s.sendMessage(args[0].(string), args[1].(string)) + case bot.Action: + return s.sendAction(args[0].(string), args[1].(string)) + case bot.Edit: + return s.edit(args[0].(string), args[1].(string), args[2].(string)) + case bot.Reply: + switch args[2].(type) { + case msg.Message: + return s.replyToMessage(args[0].(string), args[1].(string), args[2].(msg.Message)) + case string: + return s.replyToMessageIdentifier(args[0].(string), args[1].(string), args[2].(string)) + default: + return "", fmt.Errorf("Invalid types given to Reply") + } + case bot.Reaction: + return s.react(args[0].(string), args[1].(string), args[2].(msg.Message)) + default: + } + return "", fmt.Errorf("No handler for message type %d", kind) +} + +func checkReturnStatus(response *http.Response) error { + type Response struct { + OK bool `json:"ok"` + } + + body, err := ioutil.ReadAll(response.Body) + response.Body.Close() + if err != nil { + err := fmt.Errorf("Error reading Slack API body: %s", err) + return err + } + + var resp Response + err = json.Unmarshal(body, &resp) + if err != nil { + err := fmt.Errorf("Error parsing message response: %s", err) + return err + } + return nil +} + +func (s *Slack) RegisterEvent(f bot.Callback) { + s.event = f +} + +func (s *Slack) sendMessageType(channel, message string, meMessage bool) (string, error) { + postUrl := "https://slack.com/api/chat.postMessage" + if meMessage { + postUrl = "https://slack.com/api/chat.meMessage" + } + + nick := s.config.Get("Nick", "bot") + icon := s.config.Get("IconURL", "https://placekitten.com/128/128") + + resp, err := http.PostForm(postUrl, + url.Values{"token": {s.token}, + "username": {nick}, + "icon_url": {icon}, + "channel": {channel}, + "text": {message}, + }) + + if err != nil { + log.Printf("Error sending Slack message: %s", err) + } + + body, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + log.Fatalf("Error reading Slack API body: %s", err) + } + + log.Println(string(body)) + + type MessageResponse struct { + OK bool `json:"ok"` + Timestamp string `json:"ts"` + Message struct { + BotID string `json:"bot_id"` + } `json:"message"` + } + + var mr MessageResponse + err = json.Unmarshal(body, &mr) + if err != nil { + log.Fatalf("Error parsing message response: %s", err) + } + + if !mr.OK { + return "", errors.New("failure response received") + } + + s.myBotID = mr.Message.BotID + + return mr.Timestamp, err +} + +func (s *Slack) sendMessage(channel, message string) (string, error) { + log.Printf("Sending message to %s: %s", channel, message) + identifier, err := s.sendMessageType(channel, message, false) + return identifier, err +} + +func (s *Slack) sendAction(channel, message string) (string, error) { + log.Printf("Sending action to %s: %s", channel, message) + identifier, err := s.sendMessageType(channel, "_"+message+"_", true) + return identifier, err +} + +func (s *Slack) replyToMessageIdentifier(channel, message, identifier string) (string, error) { + nick := s.config.Get("Nick", "bot") + icon := s.config.Get("IconURL", "https://placekitten.com/128/128") + + resp, err := http.PostForm("https://slack.com/api/chat.postMessage", + url.Values{"token": {s.token}, + "username": {nick}, + "icon_url": {icon}, + "channel": {channel}, + "text": {message}, + "thread_ts": {identifier}, + }) + + if err != nil { + err := fmt.Errorf("Error sending Slack reply: %s", err) + return "", err + } + + body, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + err := fmt.Errorf("Error reading Slack API body: %s", err) + return "", err + } + + log.Println(string(body)) + + type MessageResponse struct { + OK bool `json:"ok"` + Timestamp string `json:"ts"` + } + + var mr MessageResponse + err = json.Unmarshal(body, &mr) + if err != nil { + err := fmt.Errorf("Error parsing message response: %s", err) + return "", err + } + + if !mr.OK { + return "", fmt.Errorf("Got !OK from slack message response") + } + + return mr.Timestamp, err +} + +func (s *Slack) replyToMessage(channel, message string, replyTo msg.Message) (string, error) { + return s.replyToMessageIdentifier(channel, message, replyTo.AdditionalData["RAW_SLACK_TIMESTAMP"]) +} + +func (s *Slack) react(channel, reaction string, message msg.Message) (string, error) { + log.Printf("Reacting in %s: %s", channel, reaction) + resp, err := http.PostForm("https://slack.com/api/reactions.add", + url.Values{"token": {s.token}, + "name": {reaction}, + "channel": {channel}, + "timestamp": {message.AdditionalData["RAW_SLACK_TIMESTAMP"]}}) + if err != nil { + err := fmt.Errorf("reaction failed: %s", err) + return "", err + } + return "", checkReturnStatus(resp) +} + +func (s *Slack) edit(channel, newMessage, identifier string) (string, error) { + log.Printf("Editing in (%s) %s: %s", identifier, channel, newMessage) + resp, err := http.PostForm("https://slack.com/api/chat.update", + url.Values{"token": {s.token}, + "channel": {channel}, + "text": {newMessage}, + "ts": {identifier}}) + if err != nil { + err := fmt.Errorf("edit failed: %s", err) + return "", err + } + return "", checkReturnStatus(resp) +} + +func (s *Slack) GetEmojiList() map[string]string { + return s.emoji +} + +func (s *Slack) populateEmojiList() { + resp, err := http.PostForm("https://slack.com/api/emoji.list", + url.Values{"token": {s.token}}) + if err != nil { + log.Printf("Error retrieving emoji list from Slack: %s", err) + return + } + + body, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + log.Fatalf("Error reading Slack API body: %s", err) + } + + type EmojiListResponse struct { + OK bool `json:"ok"` + Emoji map[string]string `json:"emoji"` + } + + var list EmojiListResponse + err = json.Unmarshal(body, &list) + if err != nil { + log.Fatalf("Error parsing emoji list: %s", err) + } + s.emoji = list.Emoji +} + +func (s *Slack) ping(ctx context.Context) { + ticker := time.NewTicker(10 * time.Second) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + ping := map[string]interface{}{"type": "ping", "time": time.Now().UnixNano()} + if err := s.ws.Send(context.TODO(), ping); err != nil { + panic(err) + } + } + } +} + +func (s *Slack) receiveMessage() (slackMessage, error) { + m := slackMessage{} + err := s.ws.Recv(context.TODO(), &m) + if err != nil { + log.Println("Error decoding WS message") + panic(fmt.Errorf("%v\n%v", m, err)) + } + return m, nil +} + +// I think it's horseshit that I have to do this +func slackTStoTime(t string) time.Time { + ts := strings.Split(t, ".") + sec, _ := strconv.ParseInt(ts[0], 10, 64) + nsec, _ := strconv.ParseInt(ts[1], 10, 64) + return time.Unix(sec, nsec) +} + +func (s *Slack) Serve() error { + s.connect() + s.populateEmojiList() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go s.ping(ctx) + + for { + msg, err := s.receiveMessage() + if err != nil && err == io.EOF { + log.Fatalf("Slack API EOF") + } else if err != nil { + return fmt.Errorf("Slack API error: %s", err) + } + switch msg.Type { + case "message": + isItMe := msg.BotID != "" && msg.BotID == s.myBotID + if !isItMe && !msg.Hidden && msg.ThreadTs == "" { + m := s.buildMessage(msg) + if m.Time.Before(s.lastRecieved) { + log.Printf("Ignoring message: %+v\nlastRecieved: %v msg: %v", msg.ID, s.lastRecieved, m.Time) + } else { + s.lastRecieved = m.Time + s.event(bot.Message, m) + } + } else if msg.ThreadTs != "" { + //we're throwing away some information here by not parsing the correct reply object type, but that's okay + s.event(bot.Reply, s.buildLightReplyMessage(msg), msg.ThreadTs) + } else { + log.Printf("THAT MESSAGE WAS HIDDEN: %+v", msg.ID) + } + case "error": + log.Printf("Slack error, code: %d, message: %s", msg.Error.Code, msg.Error.Msg) + case "": // what even is this? + case "hello": + case "presence_change": + case "user_typing": + case "reconnect_url": + case "desktop_notification": + case "pong": + // squeltch this stuff + continue + default: + log.Printf("Unhandled Slack message type: '%s'", msg.Type) + } + } +} + +var urlDetector = regexp.MustCompile(`<(.+)://([^|^>]+).*>`) + +// Convert a slackMessage to a msg.Message +func (s *Slack) buildMessage(m slackMessage) msg.Message { + text := html.UnescapeString(m.Text) + + text = fixText(s.getUser, text) + + isCmd, text := bot.IsCmd(s.config, text) + + isAction := m.SubType == "me_message" + + u, _ := s.getUser(m.User) + if m.Username != "" { + u = m.Username + } + + tstamp := slackTStoTime(m.Ts) + + return msg.Message{ + User: &user.User{ + ID: m.User, + Name: u, + }, + Body: text, + Raw: m.Text, + Channel: m.Channel, + Command: isCmd, + Action: isAction, + Host: string(m.ID), + Time: tstamp, + AdditionalData: map[string]string{ + "RAW_SLACK_TIMESTAMP": m.Ts, + }, + } +} + +func (s *Slack) buildLightReplyMessage(m slackMessage) msg.Message { + text := html.UnescapeString(m.Text) + + text = fixText(s.getUser, text) + + isCmd, text := bot.IsCmd(s.config, text) + + isAction := m.SubType == "me_message" + + u, _ := s.getUser(m.User) + if m.Username != "" { + u = m.Username + } + + tstamp := slackTStoTime(m.Ts) + + return msg.Message{ + User: &user.User{ + ID: m.User, + Name: u, + }, + Body: text, + Raw: m.Text, + Channel: m.Channel, + Command: isCmd, + Action: isAction, + Host: string(m.ID), + Time: tstamp, + AdditionalData: map[string]string{ + "RAW_SLACK_TIMESTAMP": m.Ts, + }, + } +} + +// markAllChannelsRead gets a list of all channels and marks each as read +func (s *Slack) markAllChannelsRead() { + chs := s.getAllChannels() + log.Printf("Got list of channels to mark read: %+v", chs) + for _, ch := range chs { + s.markChannelAsRead(ch.ID) + } + log.Printf("Finished marking channels read") +} + +// getAllChannels returns info for all channels joined +func (s *Slack) getAllChannels() []slackChannelListItem { + u := s.url + "channels.list" + resp, err := http.PostForm(u, + url.Values{"token": {s.token}}) + if err != nil { + log.Printf("Error posting user info request: %s", + err) + return nil + } + if resp.StatusCode != 200 { + log.Printf("Error posting user info request: %d", + resp.StatusCode) + return nil + } + defer resp.Body.Close() + var chanInfo slackChannelListResp + err = json.NewDecoder(resp.Body).Decode(&chanInfo) + if err != nil || !chanInfo.Ok { + log.Println("Error decoding response: ", err) + return nil + } + return chanInfo.Channels +} + +// markAsRead marks a channel read +func (s *Slack) markChannelAsRead(slackChanId string) error { + u := s.url + "channels.info" + resp, err := http.PostForm(u, + url.Values{"token": {s.token}, "channel": {slackChanId}}) + if err != nil { + log.Printf("Error posting user info request: %s", + err) + return err + } + if resp.StatusCode != 200 { + log.Printf("Error posting user info request: %d", + resp.StatusCode) + return err + } + defer resp.Body.Close() + var chanInfo slackChannelInfoResp + err = json.NewDecoder(resp.Body).Decode(&chanInfo) + log.Printf("%+v, %+v", err, chanInfo) + if err != nil || !chanInfo.Ok { + log.Println("Error decoding response: ", err) + return err + } + + u = s.url + "channels.mark" + resp, err = http.PostForm(u, + url.Values{"token": {s.token}, "channel": {slackChanId}, "ts": {chanInfo.Channel.Latest.Ts}}) + if err != nil { + log.Printf("Error posting user info request: %s", + err) + return err + } + if resp.StatusCode != 200 { + log.Printf("Error posting user info request: %d", + resp.StatusCode) + return err + } + defer resp.Body.Close() + var markInfo map[string]interface{} + err = json.NewDecoder(resp.Body).Decode(&markInfo) + log.Printf("%+v, %+v", err, markInfo) + if err != nil { + log.Println("Error decoding response: ", err) + return err + } + + log.Printf("Marked %s as read", slackChanId) + return nil +} + +func (s *Slack) connect() { + token := s.token + u := fmt.Sprintf("https://slack.com/api/rtm.connect?token=%s", token) + resp, err := http.Get(u) + if err != nil { + return + } + if resp.StatusCode != 200 { + log.Fatalf("Slack API failed. Code: %d", resp.StatusCode) + } + body, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + log.Fatalf("Error reading Slack API body: %s", err) + } + var rtm rtmStart + err = json.Unmarshal(body, &rtm) + if err != nil { + return + } + + if !rtm.Ok { + log.Fatalf("Slack error: %s", rtm.Error) + } + + s.url = "https://slack.com/api/" + s.id = rtm.Self.ID + + // This is hitting the rate limit, and it may not be needed + //s.markAllChannelsRead() + + rtmURL, _ := url.Parse(rtm.URL) + s.ws, err = websocket.Dial(context.TODO(), rtmURL) + if err != nil { + log.Fatal(err) + } +} + +// Get username for Slack user ID +func (s *Slack) getUser(id string) (string, bool) { + if name, ok := s.users[id]; ok { + return name, true + } + + log.Printf("User %s not already found, requesting info", id) + u := s.url + "users.info" + resp, err := http.PostForm(u, + url.Values{"token": {s.token}, "user": {id}}) + if err != nil || resp.StatusCode != 200 { + log.Printf("Error posting user info request: %d %s", + resp.StatusCode, err) + return "UNKNOWN", false + } + defer resp.Body.Close() + var userInfo slackUserInfoResp + err = json.NewDecoder(resp.Body).Decode(&userInfo) + if err != nil { + log.Println("Error decoding response: ", err) + return "UNKNOWN", false + } + s.users[id] = userInfo.User.Name + return s.users[id], true +} + +// Who gets usernames out of a channel +func (s *Slack) Who(id string) []string { + log.Println("Who is queried for ", id) + u := s.url + "channels.info" + resp, err := http.PostForm(u, + url.Values{"token": {s.token}, "channel": {id}}) + if err != nil { + log.Printf("Error posting user info request: %s", + err) + return []string{} + } + if resp.StatusCode != 200 { + log.Printf("Error posting user info request: %d", + resp.StatusCode) + return []string{} + } + defer resp.Body.Close() + var chanInfo slackChannelInfoResp + err = json.NewDecoder(resp.Body).Decode(&chanInfo) + if err != nil || !chanInfo.Ok { + log.Println("Error decoding response: ", err) + return []string{} + } + + log.Printf("%#v", chanInfo.Channel) + + handles := []string{} + for _, member := range chanInfo.Channel.Members { + u, _ := s.getUser(member) + handles = append(handles, u) + } + log.Printf("Returning %d handles", len(handles)) + return handles +} diff --git a/connectors/slackapp/fix_text.go b/connectors/slackapp/fix_text.go new file mode 100644 index 0000000..24735ad --- /dev/null +++ b/connectors/slackapp/fix_text.go @@ -0,0 +1,96 @@ +package slackapp + +import ( + "unicode/utf8" +) + +// fixText strips all of the Slack-specific annotations from message text, +// replacing it with the equivalent display form. +// Currently it: +// • Replaces user mentions like <@U124356> with @ followed by the user's nick. +// This uses the lookupUser function, which must map U1243456 to the nick. +// • Replaces user mentions like with the user's nick. +// • Strips < and > surrounding links. +// +// This was directly bogarted from velour/chat with emoji conversion removed. +func fixText(findUser func(id string) (string, bool), text string) string { + var output []rune + for len(text) > 0 { + r, i := utf8.DecodeRuneInString(text) + text = text[i:] + switch { + case r == '<': + var tag []rune + for { + r, i := utf8.DecodeRuneInString(text) + text = text[i:] + switch { + case r == '>': + if t, ok := fixTag(findUser, tag); ok { + output = append(output, t...) + break + } + fallthrough + case len(text) == 0: + output = append(output, '<') + output = append(output, tag...) + output = append(output, r) + default: + tag = append(tag, r) + continue + } + break + } + default: + output = append(output, r) + } + } + return string(output) +} + +func fixTag(findUser func(string) (string, bool), tag []rune) ([]rune, bool) { + switch { + case hasPrefix(tag, "@U"): + if i := indexRune(tag, '|'); i >= 0 { + return tag[i+1:], true + } + if findUser != nil { + if u, ok := findUser(string(tag[1:])); ok { + return []rune(u), true + } + } + return tag, true + + case hasPrefix(tag, "#C"): + if i := indexRune(tag, '|'); i >= 0 { + return append([]rune{'#'}, tag[i+1:]...), true + } + + case hasPrefix(tag, "http"): + if i := indexRune(tag, '|'); i >= 0 { + tag = tag[:i] + } + return tag, true + } + + return nil, false +} + +func hasPrefix(text []rune, prefix string) bool { + for _, r := range prefix { + if len(text) == 0 || text[0] != r { + return false + } + text = text[1:] + } + return true +} + +func indexRune(text []rune, find rune) int { + for i, r := range text { + if r == find { + return i + } + } + return -1 +} diff --git a/connectors/slackapp/slackApp.go b/connectors/slackapp/slackApp.go new file mode 100644 index 0000000..24e0493 --- /dev/null +++ b/connectors/slackapp/slackApp.go @@ -0,0 +1,447 @@ +package slackapp + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "html" + "io/ioutil" + "log" + "net/http" + "net/url" + "regexp" + "strconv" + "strings" + "time" + + "github.com/nlopes/slack" + "github.com/nlopes/slack/slackevents" + + "github.com/velour/catbase/bot" + "github.com/velour/catbase/bot/msg" + "github.com/velour/catbase/bot/user" + "github.com/velour/catbase/config" +) + +type SlackApp struct { + bot bot.Bot + config *config.Config + api *slack.Client + + token string + verification string + id string + + lastRecieved time.Time + + myBotID string + users map[string]string + emoji map[string]string + + event bot.Callback +} + +func New(c *config.Config) *SlackApp { + token := c.Get("slack.token", "NONE") + if token == "NONE" { + log.Fatalf("No slack token found. Set SLACKTOKEN env.") + } + + dbg := slack.OptionDebug(true) + api := slack.New(token) + dbg(api) + + return &SlackApp{ + api: api, + config: c, + token: token, + verification: c.Get("slack.verification", "NONE"), + lastRecieved: time.Now(), + users: make(map[string]string), + emoji: make(map[string]string), + } +} + +func (s *SlackApp) RegisterEvent(f bot.Callback) { + s.event = f +} + +func (s *SlackApp) Serve() error { + s.populateEmojiList() + http.HandleFunc("/evt", func(w http.ResponseWriter, r *http.Request) { + buf := new(bytes.Buffer) + buf.ReadFrom(r.Body) + body := buf.String() + eventsAPIEvent, e := slackevents.ParseEvent(json.RawMessage(body), slackevents.OptionVerifyToken(&slackevents.TokenComparator{VerificationToken: s.verification})) + if e != nil { + log.Println(e) + w.WriteHeader(http.StatusInternalServerError) + } + + if eventsAPIEvent.Type == slackevents.URLVerification { + var r *slackevents.ChallengeResponse + err := json.Unmarshal([]byte(body), &r) + if err != nil { + log.Println(err) + w.WriteHeader(http.StatusInternalServerError) + } + w.Header().Set("Content-Type", "text") + w.Write([]byte(r.Challenge)) + } else if eventsAPIEvent.Type == slackevents.CallbackEvent { + innerEvent := eventsAPIEvent.InnerEvent + switch ev := innerEvent.Data.(type) { + case *slackevents.AppMentionEvent: + // This is a bit of a problem. AppMentionEvent also needs to + // End up in msgReceived + //s.msgReceivd(ev) + case *slackevents.MessageEvent: + s.msgReceivd(ev) + } + } + }) + log.Println("[INFO] Server listening") + log.Fatal(http.ListenAndServe("0.0.0.0:1337", nil)) + return nil +} + +func (s *SlackApp) msgReceivd(msg *slackevents.MessageEvent) { + isItMe := msg.BotID != "" && msg.BotID == s.myBotID + if !isItMe && msg.ThreadTimeStamp == "" { + m := s.buildMessage(msg) + if m.Time.Before(s.lastRecieved) { + log.Printf("Ignoring message: lastRecieved: %v msg: %v", s.lastRecieved, m.Time) + } else { + s.lastRecieved = m.Time + s.event(bot.Message, m) + } + } else if msg.ThreadTimeStamp != "" { + //we're throwing away some information here by not parsing the correct reply object type, but that's okay + s.event(bot.Reply, s.buildLightReplyMessage(msg), msg.ThreadTimeStamp) + } else { + log.Printf("THAT MESSAGE WAS HIDDEN: %+v", msg.Text) + } +} + +func (s *SlackApp) Send(kind bot.Kind, args ...interface{}) (string, error) { + // TODO: All of these local calls to slack should get routed through the library + switch kind { + case bot.Message: + return s.sendMessage(args[0].(string), args[1].(string)) + case bot.Action: + return s.sendAction(args[0].(string), args[1].(string)) + case bot.Edit: + return s.edit(args[0].(string), args[1].(string), args[2].(string)) + case bot.Reply: + switch args[2].(type) { + case msg.Message: + return s.replyToMessage(args[0].(string), args[1].(string), args[2].(msg.Message)) + case string: + return s.replyToMessageIdentifier(args[0].(string), args[1].(string), args[2].(string)) + default: + return "", fmt.Errorf("Invalid types given to Reply") + } + case bot.Reaction: + return s.react(args[0].(string), args[1].(string), args[2].(msg.Message)) + default: + } + return "", fmt.Errorf("No handler for message type %d", kind) +} + +func (s *SlackApp) sendMessageType(channel, message string, meMessage bool) (string, error) { + postUrl := "https://slack.com/api/chat.postMessage" + if meMessage { + postUrl = "https://slack.com/api/chat.meMessage" + } + + nick := s.config.Get("Nick", "bot") + icon := s.config.Get("IconURL", "https://placekitten.com/128/128") + + resp, err := http.PostForm(postUrl, + url.Values{"token": {s.token}, + "username": {nick}, + "icon_url": {icon}, + "channel": {channel}, + "text": {message}, + }) + + if err != nil { + log.Printf("Error sending Slack message: %s", err) + } + + body, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + log.Fatalf("Error reading Slack API body: %s", err) + } + + log.Println(string(body)) + + type MessageResponse struct { + OK bool `json:"ok"` + Timestamp string `json:"ts"` + Message struct { + BotID string `json:"bot_id"` + } `json:"message"` + } + + var mr MessageResponse + err = json.Unmarshal(body, &mr) + if err != nil { + log.Fatalf("Error parsing message response: %s", err) + } + + if !mr.OK { + return "", errors.New("failure response received") + } + + s.myBotID = mr.Message.BotID + + return mr.Timestamp, err +} + +func (s *SlackApp) sendMessage(channel, message string) (string, error) { + log.Printf("Sending message to %s: %s", channel, message) + identifier, err := s.sendMessageType(channel, message, false) + return identifier, err +} + +func (s *SlackApp) sendAction(channel, message string) (string, error) { + log.Printf("Sending action to %s: %s", channel, message) + identifier, err := s.sendMessageType(channel, "_"+message+"_", true) + return identifier, err +} + +func (s *SlackApp) replyToMessageIdentifier(channel, message, identifier string) (string, error) { + nick := s.config.Get("Nick", "bot") + icon := s.config.Get("IconURL", "https://placekitten.com/128/128") + + resp, err := http.PostForm("https://slack.com/api/chat.postMessage", + url.Values{"token": {s.token}, + "username": {nick}, + "icon_url": {icon}, + "channel": {channel}, + "text": {message}, + "thread_ts": {identifier}, + }) + + if err != nil { + err := fmt.Errorf("Error sending Slack reply: %s", err) + return "", err + } + + body, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + err := fmt.Errorf("Error reading Slack API body: %s", err) + return "", err + } + + log.Println(string(body)) + + type MessageResponse struct { + OK bool `json:"ok"` + Timestamp string `json:"ts"` + } + + var mr MessageResponse + err = json.Unmarshal(body, &mr) + if err != nil { + err := fmt.Errorf("Error parsing message response: %s", err) + return "", err + } + + if !mr.OK { + return "", fmt.Errorf("Got !OK from slack message response") + } + + return mr.Timestamp, err +} + +func (s *SlackApp) replyToMessage(channel, message string, replyTo msg.Message) (string, error) { + return s.replyToMessageIdentifier(channel, message, replyTo.AdditionalData["RAW_SLACK_TIMESTAMP"]) +} + +func (s *SlackApp) react(channel, reaction string, message msg.Message) (string, error) { + log.Printf("Reacting in %s: %s", channel, reaction) + resp, err := http.PostForm("https://slack.com/api/reactions.add", + url.Values{"token": {s.token}, + "name": {reaction}, + "channel": {channel}, + "timestamp": {message.AdditionalData["RAW_SLACK_TIMESTAMP"]}}) + if err != nil { + err := fmt.Errorf("reaction failed: %s", err) + return "", err + } + return "", checkReturnStatus(resp) +} + +func (s *SlackApp) edit(channel, newMessage, identifier string) (string, error) { + log.Printf("Editing in (%s) %s: %s", identifier, channel, newMessage) + resp, err := http.PostForm("https://slack.com/api/chat.update", + url.Values{"token": {s.token}, + "channel": {channel}, + "text": {newMessage}, + "ts": {identifier}}) + if err != nil { + err := fmt.Errorf("edit failed: %s", err) + return "", err + } + return "", checkReturnStatus(resp) +} + +func (s *SlackApp) GetEmojiList() map[string]string { + return s.emoji +} + +func (s *SlackApp) populateEmojiList() { + resp, err := http.PostForm("https://slack.com/api/emoji.list", + url.Values{"token": {s.token}}) + if err != nil { + log.Printf("Error retrieving emoji list from Slack: %s", err) + return + } + + body, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + log.Fatalf("Error reading Slack API body: %s", err) + } + + type EmojiListResponse struct { + OK bool `json:"ok"` + Emoji map[string]string `json:"emoji"` + } + + var list EmojiListResponse + err = json.Unmarshal(body, &list) + if err != nil { + log.Fatalf("Error parsing emoji list: %s", err) + } + s.emoji = list.Emoji +} + +// I think it's horseshit that I have to do this +func slackTStoTime(t string) time.Time { + ts := strings.Split(t, ".") + sec, _ := strconv.ParseInt(ts[0], 10, 64) + nsec, _ := strconv.ParseInt(ts[1], 10, 64) + return time.Unix(sec, nsec) +} + +func checkReturnStatus(response *http.Response) error { + type Response struct { + OK bool `json:"ok"` + } + + body, err := ioutil.ReadAll(response.Body) + response.Body.Close() + if err != nil { + err := fmt.Errorf("Error reading Slack API body: %s", err) + return err + } + + var resp Response + err = json.Unmarshal(body, &resp) + if err != nil { + err := fmt.Errorf("Error parsing message response: %s", err) + return err + } + return nil +} + +var urlDetector = regexp.MustCompile(`<(.+)://([^|^>]+).*>`) + +// Convert a slackMessage to a msg.Message +func (s *SlackApp) buildMessage(m *slackevents.MessageEvent) msg.Message { + text := html.UnescapeString(m.Text) + + text = fixText(s.getUser, text) + + isCmd, text := bot.IsCmd(s.config, text) + + isAction := m.SubType == "me_message" + + u, _ := s.getUser(m.User) + if m.Username != "" { + u = m.Username + } + + tstamp := slackTStoTime(m.TimeStamp) + + return msg.Message{ + User: &user.User{ + ID: m.User, + Name: u, + }, + Body: text, + Raw: m.Text, + Channel: m.Channel, + Command: isCmd, + Action: isAction, + Time: tstamp, + AdditionalData: map[string]string{ + "RAW_SLACK_TIMESTAMP": m.TimeStamp, + }, + } +} + +func (s *SlackApp) buildLightReplyMessage(m *slackevents.MessageEvent) msg.Message { + text := html.UnescapeString(m.Text) + + text = fixText(s.getUser, text) + + isCmd, text := bot.IsCmd(s.config, text) + + isAction := m.SubType == "me_message" + + u, _ := s.getUser(m.User) + if m.Username != "" { + u = m.Username + } + + tstamp := slackTStoTime(m.TimeStamp) + + return msg.Message{ + User: &user.User{ + ID: m.User, + Name: u, + }, + Body: text, + Raw: m.Text, + Channel: m.Channel, + Command: isCmd, + Action: isAction, + Time: tstamp, + AdditionalData: map[string]string{ + "RAW_SLACK_TIMESTAMP": m.TimeStamp, + }, + } +} + +// Get username for Slack user ID +func (s *SlackApp) getUser(id string) (string, bool) { + if name, ok := s.users[id]; ok { + return name, true + } + + log.Printf("User %s not already found, requesting info", id) + u, err := s.api.GetUserInfo(id) + if err != nil { + return "UNKNOWN", false + } + s.users[id] = u.Name + return s.users[id], true +} + +// Who gets usernames out of a channel +func (s *SlackApp) Who(id string) []string { + log.Println("Who is queried for ", id) + // Not super sure this is the correct call + members, err := s.api.GetUserGroupMembers(id) + if err != nil { + log.Println(err) + return []string{} + } + return members +} diff --git a/go.mod b/go.mod index 45d8122..d05e371 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,8 @@ require ( github.com/mitchellh/mapstructure v1.1.2 // indirect github.com/mmcdole/gofeed v1.0.0-beta2 github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf // indirect + github.com/nlopes/slack v0.5.0 + github.com/pkg/errors v0.8.1 // indirect github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d // indirect github.com/stretchr/objx v0.1.1 // indirect github.com/stretchr/testify v1.3.0 diff --git a/go.sum b/go.sum index 2b566da..cf9d3cc 100644 --- a/go.sum +++ b/go.sum @@ -26,6 +26,10 @@ github.com/mmcdole/gofeed v1.0.0-beta2 h1:CjQ0ADhAwNSb08zknAkGOEYqr8zfZKfrzgk9Bx github.com/mmcdole/gofeed v1.0.0-beta2/go.mod h1:/BF9JneEL2/flujm8XHoxUcghdTV6vvb3xx/vKyChFU= github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf h1:sWGE2v+hO0Nd4yFU/S/mDBM5plIU8v/Qhfz41hkDIAI= github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf/go.mod h1:pasqhqstspkosTneA62Nc+2p9SOBBYAPbnmRRWPQ0V8= +github.com/nlopes/slack v0.5.0 h1:NbIae8Kd0NpqaEI3iUrsuS0KbcEDhzhc939jLW5fNm0= +github.com/nlopes/slack v0.5.0/go.mod h1:jVI4BBK3lSktibKahxBF74txcK2vyvkza1z/+rRnVAM= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d h1:1VUlQbCfkoSGv7qP7Y+ro3ap1P1pPZxgdGVqiTVy5C4= diff --git a/main.go b/main.go index 6ab529d..bb0dae5 100644 --- a/main.go +++ b/main.go @@ -10,7 +10,9 @@ import ( "github.com/velour/catbase/bot" "github.com/velour/catbase/config" - "github.com/velour/catbase/irc" + "github.com/velour/catbase/connectors/irc" + "github.com/velour/catbase/connectors/slack" + "github.com/velour/catbase/connectors/slackapp" "github.com/velour/catbase/plugins/admin" "github.com/velour/catbase/plugins/babbler" "github.com/velour/catbase/plugins/beers" @@ -35,7 +37,6 @@ import ( "github.com/velour/catbase/plugins/twitch" "github.com/velour/catbase/plugins/your" "github.com/velour/catbase/plugins/zork" - "github.com/velour/catbase/slack" ) var ( @@ -67,11 +68,13 @@ func main() { var client bot.Connector - switch c.Get("type", "slack") { + switch c.Get("type", "slackapp") { case "irc": client = irc.New(c) case "slack": client = slack.New(c) + case "slackapp": + client = slackapp.New(c) default: log.Fatalf("Unknown connection type: %s", c.Get("type", "UNSET")) } diff --git a/plugins/twitch/twitch.go b/plugins/twitch/twitch.go index b77a2be..c748cad 100644 --- a/plugins/twitch/twitch.go +++ b/plugins/twitch/twitch.go @@ -135,6 +135,10 @@ func (p *TwitchPlugin) help(kind bot.Kind, message msg.Message, args ...interfac func (p *TwitchPlugin) twitchLoop(channel string) { frequency := p.config.GetInt("Twitch.Freq", 60) + if p.config.Get("twitch.clientid", "") == "" || p.config.Get("twitch.authorization", "") == "" { + log.Println("Disabling twitch autochecking.") + return + } log.Println("Checking every ", frequency, " seconds") From c72dc7b2c85402cbc9ffa07436561c2057770d40 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Wed, 6 Feb 2019 00:17:32 -0500 Subject: [PATCH 038/107] slackapp: most things working * User lists are definitely not working yet --- connectors/slackapp/slackApp.go | 233 +++++++------------------------- 1 file changed, 49 insertions(+), 184 deletions(-) diff --git a/connectors/slackapp/slackApp.go b/connectors/slackapp/slackApp.go index 24e0493..c8348de 100644 --- a/connectors/slackapp/slackApp.go +++ b/connectors/slackapp/slackApp.go @@ -3,13 +3,10 @@ package slackapp import ( "bytes" "encoding/json" - "errors" "fmt" "html" - "io/ioutil" "log" "net/http" - "net/url" "regexp" "strconv" "strings" @@ -29,7 +26,8 @@ type SlackApp struct { config *config.Config api *slack.Client - token string + botToken string + userToken string verification string id string @@ -55,8 +53,10 @@ func New(c *config.Config) *SlackApp { return &SlackApp{ api: api, config: c, - token: token, + botToken: token, + userToken: c.Get("slack.usertoken", "NONE"), verification: c.Get("slack.verification", "NONE"), + myBotID: c.Get("slack.botid", ""), lastRecieved: time.Now(), users: make(map[string]string), emoji: make(map[string]string), @@ -69,6 +69,7 @@ func (s *SlackApp) RegisterEvent(f bot.Callback) { func (s *SlackApp) Serve() error { s.populateEmojiList() + http.HandleFunc("/evt", func(w http.ResponseWriter, r *http.Request) { buf := new(bytes.Buffer) buf.ReadFrom(r.Body) @@ -100,7 +101,6 @@ func (s *SlackApp) Serve() error { } } }) - log.Println("[INFO] Server listening") log.Fatal(http.ListenAndServe("0.0.0.0:1337", nil)) return nil } @@ -117,7 +117,7 @@ func (s *SlackApp) msgReceivd(msg *slackevents.MessageEvent) { } } else if msg.ThreadTimeStamp != "" { //we're throwing away some information here by not parsing the correct reply object type, but that's okay - s.event(bot.Reply, s.buildLightReplyMessage(msg), msg.ThreadTimeStamp) + s.event(bot.Reply, s.buildMessage(msg), msg.ThreadTimeStamp) } else { log.Printf("THAT MESSAGE WAS HIDDEN: %+v", msg.Text) } @@ -149,55 +149,26 @@ func (s *SlackApp) Send(kind bot.Kind, args ...interface{}) (string, error) { } func (s *SlackApp) sendMessageType(channel, message string, meMessage bool) (string, error) { - postUrl := "https://slack.com/api/chat.postMessage" - if meMessage { - postUrl = "https://slack.com/api/chat.meMessage" - } - + ts, err := "", fmt.Errorf("") nick := s.config.Get("Nick", "bot") - icon := s.config.Get("IconURL", "https://placekitten.com/128/128") - resp, err := http.PostForm(postUrl, - url.Values{"token": {s.token}, - "username": {nick}, - "icon_url": {icon}, - "channel": {channel}, - "text": {message}, - }) + if meMessage { + _, ts, err = s.api.PostMessage(channel, + slack.MsgOptionUsername(nick), + slack.MsgOptionText(message, false), + slack.MsgOptionMeMessage()) + } else { + _, ts, err = s.api.PostMessage(channel, + slack.MsgOptionUsername(nick), + slack.MsgOptionText(message, false)) + } if err != nil { - log.Printf("Error sending Slack message: %s", err) + log.Printf("Error sending message: %+v", err) + return "", err } - body, err := ioutil.ReadAll(resp.Body) - resp.Body.Close() - if err != nil { - log.Fatalf("Error reading Slack API body: %s", err) - } - - log.Println(string(body)) - - type MessageResponse struct { - OK bool `json:"ok"` - Timestamp string `json:"ts"` - Message struct { - BotID string `json:"bot_id"` - } `json:"message"` - } - - var mr MessageResponse - err = json.Unmarshal(body, &mr) - if err != nil { - log.Fatalf("Error parsing message response: %s", err) - } - - if !mr.OK { - return "", errors.New("failure response received") - } - - s.myBotID = mr.Message.BotID - - return mr.Timestamp, err + return ts, nil } func (s *SlackApp) sendMessage(channel, message string) (string, error) { @@ -214,48 +185,12 @@ func (s *SlackApp) sendAction(channel, message string) (string, error) { func (s *SlackApp) replyToMessageIdentifier(channel, message, identifier string) (string, error) { nick := s.config.Get("Nick", "bot") - icon := s.config.Get("IconURL", "https://placekitten.com/128/128") - - resp, err := http.PostForm("https://slack.com/api/chat.postMessage", - url.Values{"token": {s.token}, - "username": {nick}, - "icon_url": {icon}, - "channel": {channel}, - "text": {message}, - "thread_ts": {identifier}, - }) - - if err != nil { - err := fmt.Errorf("Error sending Slack reply: %s", err) - return "", err - } - - body, err := ioutil.ReadAll(resp.Body) - resp.Body.Close() - if err != nil { - err := fmt.Errorf("Error reading Slack API body: %s", err) - return "", err - } - - log.Println(string(body)) - - type MessageResponse struct { - OK bool `json:"ok"` - Timestamp string `json:"ts"` - } - - var mr MessageResponse - err = json.Unmarshal(body, &mr) - if err != nil { - err := fmt.Errorf("Error parsing message response: %s", err) - return "", err - } - - if !mr.OK { - return "", fmt.Errorf("Got !OK from slack message response") - } - - return mr.Timestamp, err + _, ts, err := s.api.PostMessage(channel, + slack.MsgOptionUsername(nick), + slack.MsgOptionText(message, false), + slack.MsgOptionMeMessage(), + slack.MsgOptionTS(identifier)) + return ts, err } func (s *SlackApp) replyToMessage(channel, message string, replyTo msg.Message) (string, error) { @@ -264,30 +199,23 @@ func (s *SlackApp) replyToMessage(channel, message string, replyTo msg.Message) func (s *SlackApp) react(channel, reaction string, message msg.Message) (string, error) { log.Printf("Reacting in %s: %s", channel, reaction) - resp, err := http.PostForm("https://slack.com/api/reactions.add", - url.Values{"token": {s.token}, - "name": {reaction}, - "channel": {channel}, - "timestamp": {message.AdditionalData["RAW_SLACK_TIMESTAMP"]}}) - if err != nil { - err := fmt.Errorf("reaction failed: %s", err) - return "", err + ref := slack.ItemRef{ + Channel: channel, + Timestamp: message.AdditionalData["RAW_SLACK_TIMESTAMP"], } - return "", checkReturnStatus(resp) + err := s.api.AddReaction(reaction, ref) + return "", err } func (s *SlackApp) edit(channel, newMessage, identifier string) (string, error) { log.Printf("Editing in (%s) %s: %s", identifier, channel, newMessage) - resp, err := http.PostForm("https://slack.com/api/chat.update", - url.Values{"token": {s.token}, - "channel": {channel}, - "text": {newMessage}, - "ts": {identifier}}) - if err != nil { - err := fmt.Errorf("edit failed: %s", err) - return "", err - } - return "", checkReturnStatus(resp) + nick := s.config.Get("Nick", "bot") + _, ts, err := s.api.PostMessage(channel, + slack.MsgOptionUsername(nick), + slack.MsgOptionText(newMessage, false), + slack.MsgOptionMeMessage(), + slack.MsgOptionUpdate(identifier)) + return ts, err } func (s *SlackApp) GetEmojiList() map[string]string { @@ -295,30 +223,21 @@ func (s *SlackApp) GetEmojiList() map[string]string { } func (s *SlackApp) populateEmojiList() { - resp, err := http.PostForm("https://slack.com/api/emoji.list", - url.Values{"token": {s.token}}) + if s.userToken == "NONE" { + log.Println("Cannot get emoji list without slack.usertoken") + return + } + dbg := slack.OptionDebug(true) + api := slack.New(s.userToken) + dbg(api) + + em, err := api.GetEmoji() if err != nil { log.Printf("Error retrieving emoji list from Slack: %s", err) return } - body, err := ioutil.ReadAll(resp.Body) - resp.Body.Close() - if err != nil { - log.Fatalf("Error reading Slack API body: %s", err) - } - - type EmojiListResponse struct { - OK bool `json:"ok"` - Emoji map[string]string `json:"emoji"` - } - - var list EmojiListResponse - err = json.Unmarshal(body, &list) - if err != nil { - log.Fatalf("Error parsing emoji list: %s", err) - } - s.emoji = list.Emoji + s.emoji = em } // I think it's horseshit that I have to do this @@ -329,27 +248,6 @@ func slackTStoTime(t string) time.Time { return time.Unix(sec, nsec) } -func checkReturnStatus(response *http.Response) error { - type Response struct { - OK bool `json:"ok"` - } - - body, err := ioutil.ReadAll(response.Body) - response.Body.Close() - if err != nil { - err := fmt.Errorf("Error reading Slack API body: %s", err) - return err - } - - var resp Response - err = json.Unmarshal(body, &resp) - if err != nil { - err := fmt.Errorf("Error parsing message response: %s", err) - return err - } - return nil -} - var urlDetector = regexp.MustCompile(`<(.+)://([^|^>]+).*>`) // Convert a slackMessage to a msg.Message @@ -386,39 +284,6 @@ func (s *SlackApp) buildMessage(m *slackevents.MessageEvent) msg.Message { } } -func (s *SlackApp) buildLightReplyMessage(m *slackevents.MessageEvent) msg.Message { - text := html.UnescapeString(m.Text) - - text = fixText(s.getUser, text) - - isCmd, text := bot.IsCmd(s.config, text) - - isAction := m.SubType == "me_message" - - u, _ := s.getUser(m.User) - if m.Username != "" { - u = m.Username - } - - tstamp := slackTStoTime(m.TimeStamp) - - return msg.Message{ - User: &user.User{ - ID: m.User, - Name: u, - }, - Body: text, - Raw: m.Text, - Channel: m.Channel, - Command: isCmd, - Action: isAction, - Time: tstamp, - AdditionalData: map[string]string{ - "RAW_SLACK_TIMESTAMP": m.TimeStamp, - }, - } -} - // Get username for Slack user ID func (s *SlackApp) getUser(id string) (string, bool) { if name, ok := s.users[id]; ok { From 104ff85a0dbccb9d22ccd9154a7f33b668a6a752 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Thu, 7 Feb 2019 11:22:27 -0500 Subject: [PATCH 039/107] downtime: remove dead plugin --- plugins/downtime/downtime.go | 233 ----------------------------------- 1 file changed, 233 deletions(-) delete mode 100644 plugins/downtime/downtime.go diff --git a/plugins/downtime/downtime.go b/plugins/downtime/downtime.go deleted file mode 100644 index 335886e..0000000 --- a/plugins/downtime/downtime.go +++ /dev/null @@ -1,233 +0,0 @@ -// © 2013 the CatBase Authors under the WTFPL. See AUTHORS for the list of authors. - -package downtime - -import ( - "database/sql" - - "github.com/jmoiron/sqlx" - "github.com/velour/catbase/bot" - "github.com/velour/catbase/bot/msg" -) - -import ( - "fmt" - "log" - "sort" - "strings" - "time" -) - -// This is a downtime plugin to monitor how much our users suck - -type DowntimePlugin struct { - Bot bot.Bot - db *sqlx.DB -} - -type idleEntry struct { - id sql.NullInt64 - nick string - lastSeen time.Time -} - -func (entry idleEntry) saveIdleEntry(db *sqlx.DB) error { - var err error - if entry.id.Valid { - log.Println("Updating downtime for: ", entry) - _, err = db.Exec(`update downtime set - nick=?, lastSeen=? - where id=?;`, entry.nick, entry.lastSeen.Unix(), entry.id.Int64) - } else { - log.Println("Inserting downtime for: ", entry) - _, err = db.Exec(`insert into downtime (nick, lastSeen) - values (?, ?)`, entry.nick, entry.lastSeen.Unix()) - } - return err -} - -func getIdleEntryByNick(db *sqlx.DB, nick string) (idleEntry, error) { - var id sql.NullInt64 - var lastSeen sql.NullInt64 - err := db.QueryRow(`select id, max(lastSeen) from downtime - where nick = ?`, nick).Scan(&id, &lastSeen) - if err != nil { - log.Println("Error selecting downtime: ", err) - return idleEntry{}, err - } - if !id.Valid { - return idleEntry{ - nick: nick, - lastSeen: time.Now(), - }, nil - } - return idleEntry{ - id: id, - nick: nick, - lastSeen: time.Unix(lastSeen.Int64, 0), - }, nil -} - -func getAllIdleEntries(db *sqlx.DB) (idleEntries, error) { - rows, err := db.Query(`select id, nick, max(lastSeen) from downtime - group by nick`) - if err != nil { - return nil, err - } - entries := idleEntries{} - for rows.Next() { - var e idleEntry - err := rows.Scan(&e.id, &e.nick, &e.lastSeen) - if err != nil { - return nil, err - } - entries = append(entries, &e) - } - return entries, nil -} - -type idleEntries []*idleEntry - -func (ie idleEntries) Len() int { - return len(ie) -} - -func (ie idleEntries) Less(i, j int) bool { - return ie[i].lastSeen.Before(ie[j].lastSeen) -} - -func (ie idleEntries) Swap(i, j int) { - ie[i], ie[j] = ie[j], ie[i] -} - -// NewDowntimePlugin creates a new DowntimePlugin with the Plugin interface -func New(bot bot.Bot) *DowntimePlugin { - p := DowntimePlugin{ - Bot: bot, - db: bot.DB(), - } - - _, err := p.db.Exec(`create table if not exists downtime ( - id integer primary key, - nick string, - lastSeen integer - );`) - if err != nil { - log.Fatal("Error creating downtime table: ", err) - } - - return &p -} - -// Message responds to the bot hook on recieving messages. -// This function returns true if the plugin responds in a meaningful way to the users message. -// Otherwise, the function returns false and the bot continues execution of other plugins. -func (p *DowntimePlugin) Message(message msg.Message) bool { - // If it's a command and the payload is idle , give it. Log everything. - - parts := strings.Fields(strings.ToLower(message.Body)) - channel := message.Channel - ret := false - - if len(parts) == 0 { - return false - } - - if parts[0] == "idle" && len(parts) == 2 { - nick := parts[1] - // parts[1] must be the userid, or we don't know them - entry, err := getIdleEntryByNick(p.db, nick) - if err != nil { - log.Println("Error getting idle entry: ", err) - } - if !entry.id.Valid { - // couldn't find em - p.Bot.Send(bot.Message, channel, fmt.Sprintf("Sorry, I don't know %s.", nick)) - } else { - p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s has been idle for: %s", - nick, time.Now().Sub(entry.lastSeen))) - } - ret = true - } else if parts[0] == "idle" && len(parts) == 1 { - // Find all idle times, report them. - entries, err := getAllIdleEntries(p.db) - if err != nil { - log.Println("Error retrieving idle entries: ", err) - } - sort.Sort(entries) - tops := "The top entries are: " - for _, e := range entries { - - // filter out ZNC entries and ourself - if strings.HasPrefix(e.nick, "*") || strings.ToLower(p.Bot.Config().Get("Nick", "bot")) == e.nick { - p.remove(e.nick) - } else { - tops = fmt.Sprintf("%s%s: %s ", tops, e.nick, time.Now().Sub(e.lastSeen)) - } - } - p.Bot.Send(bot.Message, channel, tops) - ret = true - - } - - p.record(strings.ToLower(message.User.Name)) - - return ret -} - -func (p *DowntimePlugin) record(user string) { - entry, err := getIdleEntryByNick(p.db, user) - if err != nil { - log.Println("Error recording downtime: ", err) - } - entry.lastSeen = time.Now() - entry.saveIdleEntry(p.db) - log.Println("Inserted downtime for:", user) -} - -func (p *DowntimePlugin) remove(user string) error { - _, err := p.db.Exec(`delete from downtime where nick = ?`, user) - if err != nil { - log.Println("Error removing downtime for user: ", user, err) - return err - } - log.Println("Removed downtime for:", user) - return nil -} - -// Help responds to help requests. Every plugin must implement a help function. -func (p *DowntimePlugin) Help(channel string, parts []string) { - p.Bot.Send(bot.Message, channel, "Ask me how long one of your friends has been idele with, \"idle \"") -} - -// Empty event handler because this plugin does not do anything on event recv -func (p *DowntimePlugin) Event(kind string, message msg.Message) bool { - log.Println(kind, "\t", message) - if kind != "PART" && message.User.Name != p.Bot.Config().Get("Nick", "bot") { - // user joined, let's nail them for it - if kind == "NICK" { - p.record(strings.ToLower(message.Channel)) - p.remove(strings.ToLower(message.User.Name)) - } else { - p.record(strings.ToLower(message.User.Name)) - } - } else if kind == "PART" || kind == "QUIT" { - p.remove(strings.ToLower(message.User.Name)) - } else { - log.Println("Unknown event: ", kind, message.User, message) - p.record(strings.ToLower(message.User.Name)) - } - return false -} - -// Handler for bot's own messages -func (p *DowntimePlugin) BotMessage(message msg.Message) bool { - return false -} - -// Register any web URLs desired -func (p *DowntimePlugin) RegisterWeb() *string { - return nil -} - -func (p *DowntimePlugin) ReplyMessage(message msg.Message, identifier string) bool { return false } From a20839cdd7d71fb0d7fea52f877c8325d831bd39 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Thu, 7 Feb 2019 11:30:42 -0500 Subject: [PATCH 040/107] bot: Invert RegisterWeb --- bot/bot.go | 7 ++++--- bot/interfaces.go | 4 ++-- bot/mock.go | 4 ++++ plugins/admin/admin.go | 5 ----- plugins/babbler/babbler.go | 4 ---- plugins/babbler/babbler_test.go | 7 ------- plugins/beers/beers.go | 5 ----- plugins/beers/beers_test.go | 5 ----- plugins/couldashouldawoulda/csw.go | 4 ---- plugins/counter/counter.go | 5 ----- plugins/counter/counter_test.go | 6 ------ plugins/db/db.go | 8 ++++---- plugins/dice/dice.go | 5 ----- plugins/dice/dice_test.go | 7 ------- plugins/emojifyme/emojifyme.go | 4 ---- plugins/fact/factoid.go | 7 ++++--- plugins/fact/remember.go | 5 ----- plugins/first/first.go | 5 ----- plugins/inventory/inventory.go | 5 ----- plugins/leftpad/leftpad.go | 5 ----- plugins/leftpad/leftpad_test.go | 5 ----- plugins/nerdepedia/nerdepedia.go | 5 ----- plugins/picker/picker.go | 7 ------- plugins/plugins.go | 12 ------------ plugins/reaction/reaction.go | 4 ---- plugins/reminder/reminder.go | 4 ---- plugins/reminder/reminder_test.go | 6 ------ plugins/rpgORdie/rpgORdie.go | 4 ---- plugins/rss/rss.go | 5 ----- plugins/sisyphus/sisyphus.go | 4 ---- plugins/talker/talker.go | 5 ----- plugins/talker/talker_test.go | 7 ------- plugins/tell/tell.go | 2 -- plugins/twitch/twitch.go | 6 +++--- plugins/your/your.go | 5 ----- plugins/zork/zork.go | 2 -- 36 files changed, 21 insertions(+), 169 deletions(-) diff --git a/bot/bot.go b/bot/bot.go index c5b237d..c12d1bb 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -116,9 +116,6 @@ func (b *bot) AddPlugin(h Plugin) { name := reflect.TypeOf(h).String() b.plugins[name] = h b.pluginOrdering = append(b.pluginOrdering, name) - if entry := h.RegisterWeb(); entry != nil { - b.httpEndPoints[name] = *entry - } } func (b *bot) Who(channel string) []user.User { @@ -260,3 +257,7 @@ func (b *bot) Register(p Plugin, kind Kind, cb Callback) { } b.callbacks[t][kind] = append(b.callbacks[t][kind], cb) } + +func (b *bot) RegisterWeb(root, name string) { + b.httpEndPoints[name] = root +} diff --git a/bot/interfaces.go b/bot/interfaces.go index eb318ca..53d5aa3 100644 --- a/bot/interfaces.go +++ b/bot/interfaces.go @@ -59,6 +59,7 @@ type Bot interface { CheckAdmin(string) bool GetEmojiList() map[string]string RegisterFilter(string, func(string) string) + RegisterWeb(string, string) } // Connector represents a server connection to a chat service @@ -74,7 +75,6 @@ type Connector interface { } // Plugin interface used for compatibility with the Plugin interface -// Probably can disappear once RegisterWeb gets inverted +// Uhh it turned empty, but we're still using it to ID plugins type Plugin interface { - RegisterWeb() *string } diff --git a/bot/mock.go b/bot/mock.go index ec48201..f9f2917 100644 --- a/bot/mock.go +++ b/bot/mock.go @@ -5,6 +5,7 @@ package bot import ( "fmt" "log" + "net/http" "strconv" "strings" @@ -48,6 +49,7 @@ func (mb *MockBot) Send(kind Kind, args ...interface{}) (string, error) { } func (mb *MockBot) AddPlugin(f Plugin) {} func (mb *MockBot) Register(p Plugin, kind Kind, cb Callback) {} +func (mb *MockBot) RegisterWeb(_, _ string) {} func (mb *MockBot) Receive(kind Kind, msg msg.Message, args ...interface{}) bool { return false } func (mb *MockBot) Filter(msg msg.Message, s string) string { return s } func (mb *MockBot) LastMessage(ch string) (msg.Message, error) { return msg.Message{}, nil } @@ -99,5 +101,7 @@ func NewMockBot() *MockBot { Messages: make([]string, 0), Actions: make([]string, 0), } + // If any plugin registered a route, we need to reset those before any new test + http.DefaultServeMux = new(http.ServeMux) return &b } diff --git a/plugins/admin/admin.go b/plugins/admin/admin.go index 5de34f0..98b439f 100644 --- a/plugins/admin/admin.go +++ b/plugins/admin/admin.go @@ -149,8 +149,3 @@ func (p *AdminPlugin) help(kind bot.Kind, m msg.Message, args ...interface{}) bo p.Bot.Send(bot.Message, m.Channel, "This does super secret things that you're not allowed to know about.") return true } - -// Register any web URLs desired -func (p *AdminPlugin) RegisterWeb() *string { - return nil -} diff --git a/plugins/babbler/babbler.go b/plugins/babbler/babbler.go index efc0c63..f95782f 100644 --- a/plugins/babbler/babbler.go +++ b/plugins/babbler/babbler.go @@ -162,10 +162,6 @@ func (p *BabblerPlugin) help(kind bot.Kind, msg msg.Message, args ...interface{} return true } -func (p *BabblerPlugin) RegisterWeb() *string { - return nil -} - func (p *BabblerPlugin) makeBabbler(name string) (*Babbler, error) { res, err := p.db.Exec(`insert into babblers (babbler) values (?);`, name) if err == nil { diff --git a/plugins/babbler/babbler_test.go b/plugins/babbler/babbler_test.go index 1631257..a569e0d 100644 --- a/plugins/babbler/babbler_test.go +++ b/plugins/babbler/babbler_test.go @@ -312,10 +312,3 @@ func TestHelp(t *testing.T) { bp.help(bot.Help, msg.Message{Channel: "channel"}, []string{}) assert.Len(t, mb.Messages, 1) } - -func TestRegisterWeb(t *testing.T) { - mb := bot.NewMockBot() - bp := newBabblerPlugin(mb) - assert.NotNil(t, bp) - assert.Nil(t, bp.RegisterWeb()) -} diff --git a/plugins/beers/beers.go b/plugins/beers/beers.go index 3ed679a..3ad3dee 100644 --- a/plugins/beers/beers.go +++ b/plugins/beers/beers.go @@ -434,8 +434,3 @@ func (p *BeersPlugin) untappdLoop(channel string) { p.checkUntappd(channel) } } - -// Register any web URLs desired -func (p BeersPlugin) RegisterWeb() *string { - return nil -} diff --git a/plugins/beers/beers_test.go b/plugins/beers/beers_test.go index bb34008..4fa07b1 100644 --- a/plugins/beers/beers_test.go +++ b/plugins/beers/beers_test.go @@ -124,8 +124,3 @@ func TestHelp(t *testing.T) { b.help(bot.Help, msg.Message{Channel: "channel"}, []string{}) assert.Len(t, mb.Messages, 1) } - -func TestRegisterWeb(t *testing.T) { - b, _ := makeBeersPlugin(t) - assert.Nil(t, b.RegisterWeb()) -} diff --git a/plugins/couldashouldawoulda/csw.go b/plugins/couldashouldawoulda/csw.go index 9d2d7a3..0052a5e 100644 --- a/plugins/couldashouldawoulda/csw.go +++ b/plugins/couldashouldawoulda/csw.go @@ -71,7 +71,3 @@ func (p *CSWPlugin) message(kind bot.Kind, message msg.Message, args ...interfac return false } - -func (p *CSWPlugin) RegisterWeb() *string { - return nil -} diff --git a/plugins/counter/counter.go b/plugins/counter/counter.go index 6d0e0ed..b23c4ae 100644 --- a/plugins/counter/counter.go +++ b/plugins/counter/counter.go @@ -455,11 +455,6 @@ func (p *CounterPlugin) help(kind bot.Kind, message msg.Message, args ...interfa return true } -// Register any web URLs desired -func (p *CounterPlugin) RegisterWeb() *string { - return nil -} - func (p *CounterPlugin) checkMatch(message msg.Message) bool { nick := message.User.Name channel := message.Channel diff --git a/plugins/counter/counter_test.go b/plugins/counter/counter_test.go index 1cbec0c..417c44c 100644 --- a/plugins/counter/counter_test.go +++ b/plugins/counter/counter_test.go @@ -253,9 +253,3 @@ func TestHelp(t *testing.T) { c.help(bot.Help, msg.Message{Channel: "channel"}, []string{}) assert.Len(t, mb.Messages, 1) } - -func TestRegisterWeb(t *testing.T) { - _, c := setup(t) - assert.NotNil(t, c) - assert.Nil(t, c.RegisterWeb()) -} diff --git a/plugins/db/db.go b/plugins/db/db.go index 4bf8387..026bf76 100644 --- a/plugins/db/db.go +++ b/plugins/db/db.go @@ -18,7 +18,9 @@ type DBPlugin struct { } func New(b bot.Bot) *DBPlugin { - return &DBPlugin{b, b.Config()} + p := &DBPlugin{b, b.Config()} + p.registerWeb() + return p } func (p *DBPlugin) Message(message msg.Message) bool { return false } @@ -27,10 +29,8 @@ func (p *DBPlugin) ReplyMessage(msg.Message, string) bool { return false } func (p *DBPlugin) BotMessage(message msg.Message) bool { return false } func (p *DBPlugin) Help(channel string, parts []string) {} -func (p *DBPlugin) RegisterWeb() *string { +func (p *DBPlugin) registerWeb() { http.HandleFunc("/db/catbase.db", p.serveQuery) - tmp := "/db/catbase.db" - return &tmp } func (p *DBPlugin) serveQuery(w http.ResponseWriter, r *http.Request) { diff --git a/plugins/dice/dice.go b/plugins/dice/dice.go index 721a652..dafc715 100644 --- a/plugins/dice/dice.go +++ b/plugins/dice/dice.go @@ -74,8 +74,3 @@ func (p *DicePlugin) help(kind bot.Kind, message msg.Message, args ...interface{ p.Bot.Send(bot.Message, message.Channel, "Roll dice using notation XdY. Try \"3d20\".") return true } - -// Register any web URLs desired -func (p *DicePlugin) RegisterWeb() *string { - return nil -} diff --git a/plugins/dice/dice_test.go b/plugins/dice/dice_test.go index bcb7a26..b7f2961 100644 --- a/plugins/dice/dice_test.go +++ b/plugins/dice/dice_test.go @@ -89,10 +89,3 @@ func TestHelp(t *testing.T) { c.help(bot.Help, msg.Message{Channel: "channel"}, []string{}) assert.Len(t, mb.Messages, 1) } - -func TestRegisterWeb(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) - assert.NotNil(t, c) - assert.Nil(t, c.RegisterWeb()) -} diff --git a/plugins/emojifyme/emojifyme.go b/plugins/emojifyme/emojifyme.go index 03b7fbb..3419e4c 100644 --- a/plugins/emojifyme/emojifyme.go +++ b/plugins/emojifyme/emojifyme.go @@ -99,10 +99,6 @@ func (p *EmojifyMePlugin) message(kind bot.Kind, message msg.Message, args ...in return false } -func (p *EmojifyMePlugin) RegisterWeb() *string { - return nil -} - func stringsContain(haystack []string, needle string) bool { for _, s := range haystack { if s == needle { diff --git a/plugins/fact/factoid.go b/plugins/fact/factoid.go index b8b40c2..d7479a9 100644 --- a/plugins/fact/factoid.go +++ b/plugins/fact/factoid.go @@ -317,6 +317,8 @@ func New(botInst bot.Bot) *Factoid { botInst.Register(p, bot.Message, p.message) botInst.Register(p, bot.Help, p.help) + p.registerWeb() + return p } @@ -737,11 +739,10 @@ func (p *Factoid) factTimer(channel string) { } // Register any web URLs desired -func (p *Factoid) RegisterWeb() *string { +func (p *Factoid) registerWeb() { http.HandleFunc("/factoid/req", p.serveQuery) http.HandleFunc("/factoid", p.serveQuery) - tmp := "/factoid" - return &tmp + p.Bot.RegisterWeb("/factoid", "Factoid") } func linkify(text string) template.HTML { diff --git a/plugins/fact/remember.go b/plugins/fact/remember.go index 87485f1..8aa32da 100644 --- a/plugins/fact/remember.go +++ b/plugins/fact/remember.go @@ -153,11 +153,6 @@ func (p *RememberPlugin) randQuote() string { return f.Tidbit } -// Register any web URLs desired -func (p RememberPlugin) RegisterWeb() *string { - return nil -} - func (p *RememberPlugin) recordMsg(message msg.Message) { log.Printf("Logging message: %s: %s", message.User.Name, message.Body) p.Log[message.Channel] = append(p.Log[message.Channel], message) diff --git a/plugins/first/first.go b/plugins/first/first.go index 9771dbf..88fcb47 100644 --- a/plugins/first/first.go +++ b/plugins/first/first.go @@ -215,8 +215,3 @@ func (p *FirstPlugin) help(kind bot.Kind, message msg.Message, args ...interface p.Bot.Send(bot.Message, message.Channel, "Sorry, First does not do a goddamn thing.") return true } - -// Register any web URLs desired -func (p *FirstPlugin) RegisterWeb() *string { - return nil -} diff --git a/plugins/inventory/inventory.go b/plugins/inventory/inventory.go index 50fb6f3..c13ac9e 100644 --- a/plugins/inventory/inventory.go +++ b/plugins/inventory/inventory.go @@ -224,8 +224,3 @@ func checkerr(e error) { log.Println(e) } } - -func (p *InventoryPlugin) RegisterWeb() *string { - // nothing to register - return nil -} diff --git a/plugins/leftpad/leftpad.go b/plugins/leftpad/leftpad.go index e005b46..23847f2 100644 --- a/plugins/leftpad/leftpad.go +++ b/plugins/leftpad/leftpad.go @@ -62,8 +62,3 @@ func (p *LeftpadPlugin) message(kind bot.Kind, message msg.Message, args ...inte return false } - -func (p *LeftpadPlugin) RegisterWeb() *string { - // nothing to register - return nil -} diff --git a/plugins/leftpad/leftpad_test.go b/plugins/leftpad/leftpad_test.go index 1d97488..32b9cdc 100644 --- a/plugins/leftpad/leftpad_test.go +++ b/plugins/leftpad/leftpad_test.go @@ -85,8 +85,3 @@ func TestNotPadding(t *testing.T) { p.message(makeMessage("!lololol")) assert.Len(t, mb.Messages, 0) } - -func TestRegisterWeb(t *testing.T) { - p, _ := makePlugin(t) - assert.Nil(t, p.RegisterWeb()) -} diff --git a/plugins/nerdepedia/nerdepedia.go b/plugins/nerdepedia/nerdepedia.go index eb040a7..6c0ba3c 100644 --- a/plugins/nerdepedia/nerdepedia.go +++ b/plugins/nerdepedia/nerdepedia.go @@ -94,8 +94,3 @@ func (p *NerdepediaPlugin) help(kind bot.Kind, message msg.Message, args ...inte p.bot.Send(bot.Message, message.Channel, "nerd stuff") return true } - -// Register any web URLs desired -func (p *NerdepediaPlugin) RegisterWeb() *string { - return nil -} diff --git a/plugins/picker/picker.go b/plugins/picker/picker.go index 3a1c320..b7290d4 100644 --- a/plugins/picker/picker.go +++ b/plugins/picker/picker.go @@ -115,10 +115,3 @@ func (p *PickerPlugin) help(kind bot.Kind, message msg.Message, args ...interfac p.Bot.Send(bot.Message, message.Channel, "Choose from a list of options. Try \"pick {a,b,c}\".") return true } - -// Register any web URLs desired -func (p *PickerPlugin) RegisterWeb() *string { - return nil -} - -func (p *PickerPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false } diff --git a/plugins/plugins.go b/plugins/plugins.go index 364629f..b6a4fd5 100644 --- a/plugins/plugins.go +++ b/plugins/plugins.go @@ -1,15 +1,3 @@ // © 2013 the CatBase Authors under the WTFPL. See AUTHORS for the list of authors. package plugins - -import "github.com/velour/catbase/bot/msg" - -// Plugin interface defines the methods needed to accept a plugin -type Plugin interface { - Message(message msg.Message) bool - Event(kind string, message msg.Message) bool - BotMessage(message msg.Message) bool - LoadData() - Help() - RegisterWeb() -} diff --git a/plugins/reaction/reaction.go b/plugins/reaction/reaction.go index c964022..3dcd078 100644 --- a/plugins/reaction/reaction.go +++ b/plugins/reaction/reaction.go @@ -63,7 +63,3 @@ func (p *ReactionPlugin) message(kind bot.Kind, message msg.Message, args ...int return false } - -func (p *ReactionPlugin) RegisterWeb() *string { - return nil -} diff --git a/plugins/reminder/reminder.go b/plugins/reminder/reminder.go index 93d4dec..26078a8 100644 --- a/plugins/reminder/reminder.go +++ b/plugins/reminder/reminder.go @@ -200,10 +200,6 @@ func (p *ReminderPlugin) help(kind bot.Kind, message msg.Message, args ...interf return true } -func (p *ReminderPlugin) RegisterWeb() *string { - return nil -} - func (p *ReminderPlugin) getNextReminder() *Reminder { p.mutex.Lock() defer p.mutex.Unlock() diff --git a/plugins/reminder/reminder_test.go b/plugins/reminder/reminder_test.go index c4a4e9e..3618f16 100644 --- a/plugins/reminder/reminder_test.go +++ b/plugins/reminder/reminder_test.go @@ -226,9 +226,3 @@ func TestHelp(t *testing.T) { c.help(bot.Help, msg.Message{Channel: "channel"}, []string{}) assert.Len(t, mb.Messages, 1) } - -func TestRegisterWeb(t *testing.T) { - c, _ := setup(t) - assert.NotNil(t, c) - assert.Nil(t, c.RegisterWeb()) -} diff --git a/plugins/rpgORdie/rpgORdie.go b/plugins/rpgORdie/rpgORdie.go index 8b5a40e..1035a06 100644 --- a/plugins/rpgORdie/rpgORdie.go +++ b/plugins/rpgORdie/rpgORdie.go @@ -124,10 +124,6 @@ func (p *RPGPlugin) help(kind bot.Kind, message msg.Message, args ...interface{} return true } -func (p *RPGPlugin) RegisterWeb() *string { - return nil -} - func (p *RPGPlugin) replyMessage(kind bot.Kind, message msg.Message, args ...interface{}) bool { identifier := args[0].(string) if strings.ToLower(message.User.Name) != strings.ToLower(p.Bot.Config().Get("Nick", "bot")) { diff --git a/plugins/rss/rss.go b/plugins/rss/rss.go index b25d749..811f37f 100644 --- a/plugins/rss/rss.go +++ b/plugins/rss/rss.go @@ -102,8 +102,3 @@ func (p *RSSPlugin) help(kind bot.Kind, message msg.Message, args ...interface{} p.Bot.Send(bot.Message, message.Channel, "try '!rss http://rss.cnn.com/rss/edition.rss'") return true } - -// Register any web URLs desired -func (p *RSSPlugin) RegisterWeb() *string { - return nil -} diff --git a/plugins/sisyphus/sisyphus.go b/plugins/sisyphus/sisyphus.go index 5a0a8b8..31c3f02 100644 --- a/plugins/sisyphus/sisyphus.go +++ b/plugins/sisyphus/sisyphus.go @@ -187,10 +187,6 @@ func (p *SisyphusPlugin) help(kind bot.Kind, message msg.Message, args ...interf return true } -func (p *SisyphusPlugin) RegisterWeb() *string { - return nil -} - func (p *SisyphusPlugin) replyMessage(kind bot.Kind, message msg.Message, args ...interface{}) bool { identifier := args[0].(string) if strings.ToLower(message.User.Name) != strings.ToLower(p.Bot.Config().Get("Nick", "bot")) { diff --git a/plugins/talker/talker.go b/plugins/talker/talker.go index 46ba61a..788161c 100644 --- a/plugins/talker/talker.go +++ b/plugins/talker/talker.go @@ -87,8 +87,3 @@ func (p *TalkerPlugin) help(kind bot.Kind, message msg.Message, args ...interfac p.Bot.Send(bot.Message, message.Channel, "Hi, this is talker. I like to talk about FredFelps!") return true } - -// Register any web URLs desired -func (p *TalkerPlugin) RegisterWeb() *string { - return nil -} diff --git a/plugins/talker/talker_test.go b/plugins/talker/talker_test.go index 2408f45..fe65779 100644 --- a/plugins/talker/talker_test.go +++ b/plugins/talker/talker_test.go @@ -81,10 +81,3 @@ func TestHelp(t *testing.T) { c.help(bot.Help, msg.Message{Channel: "channel"}, []string{}) assert.Len(t, mb.Messages, 1) } - -func TestRegisterWeb(t *testing.T) { - mb := bot.NewMockBot() - c := New(mb) - assert.NotNil(t, c) - assert.Nil(t, c.RegisterWeb()) -} diff --git a/plugins/tell/tell.go b/plugins/tell/tell.go index c852141..aba8410 100644 --- a/plugins/tell/tell.go +++ b/plugins/tell/tell.go @@ -41,5 +41,3 @@ func (t *TellPlugin) message(kind bot.Kind, message msg.Message, args ...interfa } return false } - -func (t *TellPlugin) RegisterWeb() *string { return nil } diff --git a/plugins/twitch/twitch.go b/plugins/twitch/twitch.go index c748cad..a088d40 100644 --- a/plugins/twitch/twitch.go +++ b/plugins/twitch/twitch.go @@ -71,13 +71,13 @@ func New(b bot.Bot) *TwitchPlugin { } b.Register(p, bot.Message, p.message) + p.registerWeb() + return p } -func (p *TwitchPlugin) RegisterWeb() *string { +func (p *TwitchPlugin) registerWeb() { http.HandleFunc("/isstreaming/", p.serveStreaming) - tmp := "/isstreaming" - return &tmp } func (p *TwitchPlugin) serveStreaming(w http.ResponseWriter, r *http.Request) { diff --git a/plugins/your/your.go b/plugins/your/your.go index a319039..1d26e9d 100644 --- a/plugins/your/your.go +++ b/plugins/your/your.go @@ -57,8 +57,3 @@ func (p *YourPlugin) help(kind bot.Kind, message msg.Message, args ...interface{ p.bot.Send(bot.Message, message.Channel, "Your corrects people's grammar.") return true } - -// Register any web URLs desired -func (p *YourPlugin) RegisterWeb() *string { - return nil -} diff --git a/plugins/zork/zork.go b/plugins/zork/zork.go index b5ac682..d2fda1b 100644 --- a/plugins/zork/zork.go +++ b/plugins/zork/zork.go @@ -120,5 +120,3 @@ func (p *ZorkPlugin) help(kind bot.Kind, message msg.Message, args ...interface{ p.bot.Send(bot.Message, message.Channel, "Play zork using 'zork '.") return true } - -func (p *ZorkPlugin) RegisterWeb() *string { return nil } From c8abb4b423f3d47b1c82f191fc1d49191d71057e Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Thu, 7 Feb 2019 11:32:30 -0500 Subject: [PATCH 041/107] ignore: add misc junk --- .gitignore | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/.gitignore b/.gitignore index 1287944..36bb5e6 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,40 @@ vendor *.code-workspace *config.lua modd.conf + + +# Created by https://www.gitignore.io/api/macos +# Edit at https://www.gitignore.io/?templates=macos + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# End of https://www.gitignore.io/api/macos + +util/*/files +util/*/files From 4925069ac906273b1e17aaeb94613d6bb1388575 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Thu, 7 Feb 2019 14:21:22 -0500 Subject: [PATCH 042/107] slackApp: fix user info functionality --- connectors/slackapp/fix_text.go | 6 +++--- connectors/slackapp/slackApp.go | 37 ++++++++++++++++++++++++++------- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/connectors/slackapp/fix_text.go b/connectors/slackapp/fix_text.go index 24735ad..8a23a53 100644 --- a/connectors/slackapp/fix_text.go +++ b/connectors/slackapp/fix_text.go @@ -13,7 +13,7 @@ import ( // • Strips < and > surrounding links. // // This was directly bogarted from velour/chat with emoji conversion removed. -func fixText(findUser func(id string) (string, bool), text string) string { +func fixText(findUser func(id string) (string, error), text string) string { var output []rune for len(text) > 0 { r, i := utf8.DecodeRuneInString(text) @@ -48,14 +48,14 @@ func fixText(findUser func(id string) (string, bool), text string) string { return string(output) } -func fixTag(findUser func(string) (string, bool), tag []rune) ([]rune, bool) { +func fixTag(findUser func(string) (string, error), tag []rune) ([]rune, bool) { switch { case hasPrefix(tag, "@U"): if i := indexRune(tag, '|'); i >= 0 { return tag[i+1:], true } if findUser != nil { - if u, ok := findUser(string(tag[1:])); ok { + if u, err := findUser(string(tag[1:])); err == nil { return []rune(u), true } } diff --git a/connectors/slackapp/slackApp.go b/connectors/slackapp/slackApp.go index c8348de..2d309f7 100644 --- a/connectors/slackapp/slackApp.go +++ b/connectors/slackapp/slackApp.go @@ -124,7 +124,6 @@ func (s *SlackApp) msgReceivd(msg *slackevents.MessageEvent) { } func (s *SlackApp) Send(kind bot.Kind, args ...interface{}) (string, error) { - // TODO: All of these local calls to slack should get routed through the library switch kind { case bot.Message: return s.sendMessage(args[0].(string), args[1].(string)) @@ -285,28 +284,50 @@ func (s *SlackApp) buildMessage(m *slackevents.MessageEvent) msg.Message { } // Get username for Slack user ID -func (s *SlackApp) getUser(id string) (string, bool) { +func (s *SlackApp) getUser(id string) (string, error) { if name, ok := s.users[id]; ok { - return name, true + return name, nil } log.Printf("User %s not already found, requesting info", id) u, err := s.api.GetUserInfo(id) if err != nil { - return "UNKNOWN", false + return "UNKNOWN", err } s.users[id] = u.Name - return s.users[id], true + return s.users[id], nil } // Who gets usernames out of a channel func (s *SlackApp) Who(id string) []string { + if s.userToken == "NONE" { + log.Println("Cannot get emoji list without slack.usertoken") + return []string{s.config.Get("nick", "bot")} + } + dbg := slack.OptionDebug(true) + api := slack.New(s.userToken) + dbg(api) + log.Println("Who is queried for ", id) // Not super sure this is the correct call - members, err := s.api.GetUserGroupMembers(id) + params := &slack.GetUsersInConversationParameters{ + ChannelID: id, + Limit: 50, + } + members, _, err := api.GetUsersInConversation(params) if err != nil { log.Println(err) - return []string{} + return []string{s.config.Get("nick", "bot")} } - return members + + ret := []string{} + for _, m := range members { + u, err := s.getUser(m) + if err != nil { + log.Printf("Couldn't get user %s: %s", m, err) + continue + } + ret = append(ret, u) + } + return ret } From a1e170aa40888faa864e6541f455a875129c1422 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Thu, 7 Feb 2019 14:45:59 -0500 Subject: [PATCH 043/107] slackApp: stop racing to serve on the http port --- bot/bot.go | 2 - connectors/slackapp/slackApp.go | 1 - irc/irc.go | 295 -------------------------------- main.go | 9 +- 4 files changed, 6 insertions(+), 301 deletions(-) delete mode 100644 irc/irc.go diff --git a/bot/bot.go b/bot/bot.go index c12d1bb..b0f0da9 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -81,8 +81,6 @@ func New(config *config.Config, connector Connector) Bot { bot.migrateDB() http.HandleFunc("/", bot.serveRoot) - addr := config.Get("HttpAddr", "127.0.0.1:1337") - go http.ListenAndServe(addr, nil) connector.RegisterEvent(bot.Receive) diff --git a/connectors/slackapp/slackApp.go b/connectors/slackapp/slackApp.go index 2d309f7..d7615f3 100644 --- a/connectors/slackapp/slackApp.go +++ b/connectors/slackapp/slackApp.go @@ -101,7 +101,6 @@ func (s *SlackApp) Serve() error { } } }) - log.Fatal(http.ListenAndServe("0.0.0.0:1337", nil)) return nil } diff --git a/irc/irc.go b/irc/irc.go deleted file mode 100644 index 58e1359..0000000 --- a/irc/irc.go +++ /dev/null @@ -1,295 +0,0 @@ -// © 2016 the CatBase Authors under the WTFPL license. See AUTHORS for the list of authors. - -package irc - -import ( - "fmt" - "io" - "log" - "os" - "strings" - "time" - - "github.com/velour/catbase/bot" - "github.com/velour/catbase/bot/msg" - "github.com/velour/catbase/bot/user" - "github.com/velour/catbase/config" - "github.com/velour/velour/irc" -) - -const ( - // DefaultPort is the port used to connect to - // the server if one is not specified. - defaultPort = "6667" - - // InitialTimeout is the initial amount of time - // to delay before reconnecting. Each failed - // reconnection doubles the timout until - // a connection is made successfully. - initialTimeout = 2 * time.Second - - // PingTime is the amount of inactive time - // to wait before sending a ping to the server. - pingTime = 120 * time.Second - - actionPrefix = "\x01ACTION" -) - -var throttle <-chan time.Time - -type Irc struct { - Client *irc.Client - config *config.Config - quit chan bool - - event func(bot.Kind, msg.Message, ...interface{}) -} - -func New(c *config.Config) *Irc { - i := Irc{} - i.config = c - - return &i -} - -func (i *Irc) RegisterEvent(f func(bot.Kind, msg.Message, ...interface{})) { - i.event = f -} - -func (i *Irc) Send(kind bot.Kind, args ...interface{}) (string, error) { - switch kind { - case bot.Reply: - case bot.Message: - return i.sendMessage(args[0].(string), args[1].(string)) - case bot.Action: - return i.sendAction(args[0].(string), args[1].(string)) - default: - } - return "", nil -} - -func (i *Irc) JoinChannel(channel string) { - log.Printf("Joining channel: %s", channel) - i.Client.Out <- irc.Msg{Cmd: irc.JOIN, Args: []string{channel}} -} - -func (i *Irc) sendMessage(channel, message string) (string, error) { - for len(message) > 0 { - m := irc.Msg{ - Cmd: "PRIVMSG", - Args: []string{channel, message}, - } - _, err := m.RawString() - if err != nil { - mtl := err.(irc.MsgTooLong) - m.Args[1] = message[:mtl.NTrunc] - message = message[mtl.NTrunc:] - } else { - message = "" - } - - if throttle == nil { - ratePerSec := i.config.GetInt("RatePerSec", 5) - throttle = time.Tick(time.Second / time.Duration(ratePerSec)) - } - - <-throttle - - i.Client.Out <- m - } - return "NO_IRC_IDENTIFIERS", nil -} - -// Sends action to channel -func (i *Irc) sendAction(channel, message string) (string, error) { - message = actionPrefix + " " + message + "\x01" - - return i.sendMessage(channel, message) -} - -func (i *Irc) GetEmojiList() map[string]string { - //we're not going to do anything because it's IRC - return make(map[string]string) -} - -func (i *Irc) Serve() error { - if i.event == nil { - return fmt.Errorf("Missing an event handler") - } - - var err error - i.Client, err = irc.DialSSL( - i.config.Get("Irc.Server", "localhost"), - i.config.Get("Nick", "bot"), - i.config.Get("FullName", "bot"), - i.config.Get("Irc.Pass", ""), - true, - ) - if err != nil { - return fmt.Errorf("%s", err) - } - - for _, c := range i.config.GetArray("channels", []string{}) { - i.JoinChannel(c) - } - - i.quit = make(chan bool) - go i.handleConnection() - <-i.quit - return nil -} - -func (i *Irc) handleConnection() { - t := time.NewTimer(pingTime) - - defer func() { - t.Stop() - close(i.Client.Out) - for err := range i.Client.Errors { - if err != io.EOF { - log.Println(err) - } - } - }() - - for { - select { - case msg, ok := <-i.Client.In: - if !ok { // disconnect - i.quit <- true - return - } - t.Stop() - t = time.NewTimer(pingTime) - i.handleMsg(msg) - - case <-t.C: - i.Client.Out <- irc.Msg{Cmd: irc.PING, Args: []string{i.Client.Server}} - t = time.NewTimer(pingTime) - - case err, ok := <-i.Client.Errors: - if ok && err != io.EOF { - log.Println(err) - i.quit <- true - return - } - } - } -} - -// HandleMsg handles IRC messages from the server. -func (i *Irc) handleMsg(msg irc.Msg) { - botMsg := i.buildMessage(msg) - - switch msg.Cmd { - case irc.ERROR: - log.Println(1, "Received error: "+msg.Raw) - - case irc.PING: - i.Client.Out <- irc.Msg{Cmd: irc.PONG} - - case irc.PONG: - // OK, ignore - - case irc.ERR_NOSUCHNICK: - fallthrough - - case irc.ERR_NOSUCHCHANNEL: - fallthrough - - case irc.RPL_MOTD: - fallthrough - - case irc.RPL_NAMREPLY: - fallthrough - - case irc.RPL_TOPIC: - fallthrough - - case irc.KICK: - fallthrough - - case irc.TOPIC: - fallthrough - - case irc.MODE: - fallthrough - - case irc.JOIN: - fallthrough - - case irc.PART: - fallthrough - - case irc.NOTICE: - fallthrough - - case irc.NICK: - fallthrough - - case irc.RPL_WHOREPLY: - fallthrough - - case irc.RPL_ENDOFWHO: - i.event(bot.Event, botMsg) - - case irc.PRIVMSG: - i.event(bot.Message, botMsg) - - case irc.QUIT: - os.Exit(1) - - default: - cmd := irc.CmdNames[msg.Cmd] - log.Println("(" + cmd + ") " + msg.Raw) - } -} - -// Builds our internal message type out of a Conn & Line from irc -func (i *Irc) buildMessage(inMsg irc.Msg) msg.Message { - // Check for the user - u := user.User{ - Name: inMsg.Origin, - } - - channel := inMsg.Args[0] - if channel == i.config.Get("Nick", "bot") { - channel = inMsg.Args[0] - } - - isAction := false - var message string - if len(inMsg.Args) > 1 { - message = inMsg.Args[1] - - isAction = strings.HasPrefix(message, actionPrefix) - if isAction { - message = strings.TrimRight(message[len(actionPrefix):], "\x01") - message = strings.TrimSpace(message) - } - - } - - iscmd := false - filteredMessage := message - if !isAction { - iscmd, filteredMessage = bot.IsCmd(i.config, message) - } - - msg := msg.Message{ - User: &u, - Channel: channel, - Body: filteredMessage, - Raw: message, - Command: iscmd, - Action: isAction, - Time: time.Now(), - Host: inMsg.Host, - } - - return msg -} - -func (i Irc) Who(channel string) []string { - return []string{} -} diff --git a/main.go b/main.go index bb0dae5..490c2c4 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "flag" "log" "math/rand" + "net/http" "time" "github.com/velour/catbase/bot" @@ -108,8 +109,10 @@ func main() { b.AddPlugin(fact.New(b)) b.AddPlugin(db.New(b)) - for { - err := client.Serve() - log.Println(err) + if err := client.Serve(); err != nil { + log.Fatal(err) } + + addr := c.Get("HttpAddr", "127.0.0.1:1337") + log.Fatal(http.ListenAndServe(addr, nil)) } From 85d123a9104143f58c9ad8e387ef83ace5337706 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Fri, 8 Feb 2019 14:54:40 -0500 Subject: [PATCH 044/107] slack: revert to manual slack call for replies Because that just fucking works. --- connectors/slackapp/slackApp.go | 64 +++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/connectors/slackapp/slackApp.go b/connectors/slackapp/slackApp.go index d7615f3..3d8e90c 100644 --- a/connectors/slackapp/slackApp.go +++ b/connectors/slackapp/slackApp.go @@ -5,8 +5,10 @@ import ( "encoding/json" "fmt" "html" + "io/ioutil" "log" "net/http" + "net/url" "regexp" "strconv" "strings" @@ -46,9 +48,7 @@ func New(c *config.Config) *SlackApp { log.Fatalf("No slack token found. Set SLACKTOKEN env.") } - dbg := slack.OptionDebug(true) - api := slack.New(token) - dbg(api) + api := slack.New(token, slack.OptionDebug(true)) return &SlackApp{ api: api, @@ -99,6 +99,8 @@ func (s *SlackApp) Serve() error { case *slackevents.MessageEvent: s.msgReceivd(ev) } + } else { + log.Printf("Event: (%v): %+v", eventsAPIEvent.Type, eventsAPIEvent) } }) return nil @@ -183,12 +185,48 @@ func (s *SlackApp) sendAction(channel, message string) (string, error) { func (s *SlackApp) replyToMessageIdentifier(channel, message, identifier string) (string, error) { nick := s.config.Get("Nick", "bot") - _, ts, err := s.api.PostMessage(channel, - slack.MsgOptionUsername(nick), - slack.MsgOptionText(message, false), - slack.MsgOptionMeMessage(), - slack.MsgOptionTS(identifier)) - return ts, err + icon := s.config.Get("IconURL", "https://placekitten.com/128/128") + + resp, err := http.PostForm("https://slack.com/api/chat.postMessage", + url.Values{"token": {s.botToken}, + "username": {nick}, + "icon_url": {icon}, + "channel": {channel}, + "text": {message}, + "thread_ts": {identifier}, + }) + + if err != nil { + err := fmt.Errorf("Error sending Slack reply: %s", err) + return "", err + } + + body, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + err := fmt.Errorf("Error reading Slack API body: %s", err) + return "", err + } + + log.Println(string(body)) + + type MessageResponse struct { + OK bool `json:"ok"` + Timestamp string `json:"ts"` + } + + var mr MessageResponse + err = json.Unmarshal(body, &mr) + if err != nil { + err := fmt.Errorf("Error parsing message response: %s", err) + return "", err + } + + if !mr.OK { + return "", fmt.Errorf("Got !OK from slack message response") + } + + return mr.Timestamp, err } func (s *SlackApp) replyToMessage(channel, message string, replyTo msg.Message) (string, error) { @@ -225,9 +263,7 @@ func (s *SlackApp) populateEmojiList() { log.Println("Cannot get emoji list without slack.usertoken") return } - dbg := slack.OptionDebug(true) - api := slack.New(s.userToken) - dbg(api) + api := slack.New(s.userToken, slack.OptionDebug(true)) em, err := api.GetEmoji() if err != nil { @@ -303,9 +339,7 @@ func (s *SlackApp) Who(id string) []string { log.Println("Cannot get emoji list without slack.usertoken") return []string{s.config.Get("nick", "bot")} } - dbg := slack.OptionDebug(true) - api := slack.New(s.userToken) - dbg(api) + api := slack.New(s.userToken, slack.OptionDebug(true)) log.Println("Who is queried for ", id) // Not super sure this is the correct call From 74efe02c75ae624d9815543feee233f21700b97f Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Sat, 9 Feb 2019 08:32:27 -0500 Subject: [PATCH 045/107] twitch: make stream announcements more complicated And fun. Now they're templates. --- plugins/twitch/twitch.go | 75 +++++++++++++++++++++++++++++++++++----- 1 file changed, 67 insertions(+), 8 deletions(-) diff --git a/plugins/twitch/twitch.go b/plugins/twitch/twitch.go index a088d40..3e3081a 100644 --- a/plugins/twitch/twitch.go +++ b/plugins/twitch/twitch.go @@ -1,14 +1,15 @@ package twitch import ( + "bytes" "encoding/json" "fmt" - "html/template" "io/ioutil" "log" "net/http" "net/url" "strings" + "text/template" "time" "github.com/velour/catbase/bot" @@ -16,6 +17,12 @@ import ( "github.com/velour/catbase/config" ) +const ( + isStreamingTplFallback = "{{.Name}} is streaming {{.Game}} at {{.URL}}" + notStreamingTplFallback = "{{.Name}} is not streaming" + stoppedStreamingTplFallback = "{{.Name}} just stopped streaming" +) + type TwitchPlugin struct { Bot bot.Bot config *config.Config @@ -112,7 +119,8 @@ func (p *TwitchPlugin) serveStreaming(w http.ResponseWriter, r *http.Request) { } func (p *TwitchPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { - if strings.ToLower(message.Body) == "twitch status" { + body := strings.ToLower(message.Body) + if body == "twitch status" { channel := message.Channel if users := p.config.GetArray("Twitch."+channel+".Users", []string{}); len(users) > 0 { for _, twitcherName := range users { @@ -122,13 +130,21 @@ func (p *TwitchPlugin) message(kind bot.Kind, message msg.Message, args ...inter } } return true + } else if body == "reset twitch" { + p.config.Set("twitch.istpl", isStreamingTplFallback) + p.config.Set("twitch.nottpl", notStreamingTplFallback) + p.config.Set("twitch.stoppedtpl", stoppedStreamingTplFallback) } return false } func (p *TwitchPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { - msg := "There's no help for you here." + msg := "You can set the templates for streams with\n" + msg += fmt.Sprintf("twitch.istpl (default: %s)\n", isStreamingTplFallback) + msg += fmt.Sprintf("twitch.nottpl (default: %s)\n", notStreamingTplFallback) + msg += fmt.Sprintf("twitch.stoppedtpl (default: %s)\n", stoppedStreamingTplFallback) + msg += "And you can ask who is streaming with `!twitch status`" p.Bot.Send(bot.Message, message.Channel, msg) return true } @@ -216,21 +232,64 @@ func (p *TwitchPlugin) checkTwitch(channel string, twitcher *Twitcher, alwaysPri gameID = games[0].GameID title = games[0].Title } - streamWord := p.config.Get("Twitch.StreamWord", "streaming") + + notStreamingTpl := p.config.Get("Twitch.NotTpl", notStreamingTplFallback) + isStreamingTpl := p.config.Get("Twitch.IsTpl", isStreamingTplFallback) + stoppedStreamingTpl := p.config.Get("Twitch.StoppedTpl", stoppedStreamingTplFallback) + buf := bytes.Buffer{} + + info := struct { + Name string + Game string + URL string + }{ + twitcher.name, + title, + twitcher.URL(), + } + if alwaysPrintStatus { if gameID == "" { - p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s is not %s.", twitcher.name, streamWord)) + t, err := template.New("notStreaming").Parse(notStreamingTpl) + if err != nil { + log.Println(err) + p.Bot.Send(bot.Message, channel, err) + t = template.Must(template.New("notStreaming").Parse(notStreamingTplFallback)) + } + t.Execute(&buf, info) + p.Bot.Send(bot.Message, channel, buf.String()) } else { - p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s is %s %s at %s", twitcher.name, streamWord, title, twitcher.URL())) + t, err := template.New("isStreaming").Parse(isStreamingTpl) + if err != nil { + log.Println(err) + p.Bot.Send(bot.Message, channel, err) + t = template.Must(template.New("isStreaming").Parse(isStreamingTplFallback)) + } + t.Execute(&buf, info) + p.Bot.Send(bot.Message, channel, buf.String()) } } else if gameID == "" { if twitcher.gameID != "" { - p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s just stopped %s.", twitcher.name, streamWord)) + t, err := template.New("stoppedStreaming").Parse(stoppedStreamingTpl) + if err != nil { + log.Println(err) + p.Bot.Send(bot.Message, channel, err) + t = template.Must(template.New("stoppedStreaming").Parse(stoppedStreamingTplFallback)) + } + t.Execute(&buf, info) + p.Bot.Send(bot.Message, channel, buf.String()) } twitcher.gameID = "" } else { if twitcher.gameID != gameID { - p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s is %s %s at %s", twitcher.name, streamWord, title, twitcher.URL())) + t, err := template.New("isStreaming").Parse(isStreamingTpl) + if err != nil { + log.Println(err) + p.Bot.Send(bot.Message, channel, err) + t = template.Must(template.New("isStreaming").Parse(isStreamingTplFallback)) + } + t.Execute(&buf, info) + p.Bot.Send(bot.Message, channel, buf.String()) } twitcher.gameID = gameID } From 1842aeba821654c18f438c634f31a996363e6e55 Mon Sep 17 00:00:00 2001 From: skkiesel Date: Tue, 12 Feb 2019 11:03:24 -0500 Subject: [PATCH 046/107] A new small tea-ture --- plugins/counter/counter.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/plugins/counter/counter.go b/plugins/counter/counter.go index b23c4ae..b7729cd 100644 --- a/plugins/counter/counter.go +++ b/plugins/counter/counter.go @@ -6,6 +6,7 @@ import ( "database/sql" "fmt" "log" + "math/rand" "regexp" "strconv" "strings" @@ -474,7 +475,16 @@ func (p *CounterPlugin) checkMatch(message msg.Message) bool { } log.Printf("About to update item: %#v", item) item.UpdateDelta(1) - p.Bot.Send(bot.Message, channel, fmt.Sprintf("bleep-bloop-blop... %s has %d %s", - nick, item.Count, itemName)) + p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s... %s has %d %s", + strings.Join(everyDayImShuffling([]string{"bleep", "bloop", "blop"}), "-"), nick, item.Count, itemName)) return true } + +func everyDayImShuffling(vals []string) []string { + ret := make([]string, len(vals)) + perm := rand.Perm(len(vals)) + for i, randIndex := range perm { + ret[i] = vals[randIndex] + } + return ret +} From 32ca0c5bf790d4339959d6365beaf38c30a38896 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Tue, 12 Feb 2019 12:26:27 -0500 Subject: [PATCH 047/107] help: fix the help system after I borke it * Also fix twitch's help registration and message --- bot/handlers.go | 21 ++++++++++++++------- plugins/twitch/twitch.go | 2 ++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/bot/handlers.go b/bot/handlers.go index 0a4781e..5c52005 100644 --- a/bot/handlers.go +++ b/bot/handlers.go @@ -22,7 +22,7 @@ func (b *bot) Receive(kind Kind, msg msg.Message, args ...interface{}) bool { // msg := b.buildMessage(client, inMsg) // do need to look up user and fix it - if kind == Message && strings.HasPrefix(msg.Body, "help ") && msg.Command { + if kind == Message && strings.HasPrefix(msg.Body, "help") && msg.Command { parts := strings.Fields(strings.ToLower(msg.Body)) b.checkHelp(msg.Channel, parts) goto RET @@ -64,6 +64,7 @@ func (b *bot) checkHelp(channel string, parts []string) { // just print out a list of help topics topics := "Help topics: about variables" for name, _ := range b.plugins { + name = strings.Split(strings.TrimPrefix(name, "*"), ".")[0] topics = fmt.Sprintf("%s, %s", topics, name) } b.Send(Message, channel, topics) @@ -77,13 +78,19 @@ func (b *bot) checkHelp(channel string, parts []string) { b.listVars(channel, parts) return } - plugin, ok := b.plugins[parts[1]] - if ok { - b.runCallback(plugin, Help, msg.Message{Channel: channel}, channel, parts) - } else { - msg := fmt.Sprintf("I'm sorry, I don't know what %s is!", parts[1]) - b.Send(Message, channel, msg) + for name, plugin := range b.plugins { + if strings.HasPrefix(name, "*"+parts[1]) { + if b.runCallback(plugin, Help, msg.Message{Channel: channel}, channel, parts) { + return + } else { + msg := fmt.Sprintf("I'm sorry, I don't know how to help you with %s.", parts[1]) + b.Send(Message, channel, msg) + return + } + } } + msg := fmt.Sprintf("I'm sorry, I don't know what %s is!", strings.Join(parts, " ")) + b.Send(Message, channel, msg) } } diff --git a/plugins/twitch/twitch.go b/plugins/twitch/twitch.go index 3e3081a..3046709 100644 --- a/plugins/twitch/twitch.go +++ b/plugins/twitch/twitch.go @@ -78,6 +78,7 @@ func New(b bot.Bot) *TwitchPlugin { } b.Register(p, bot.Message, p.message) + b.Register(p, bot.Help, p.help) p.registerWeb() return p @@ -144,6 +145,7 @@ func (p *TwitchPlugin) help(kind bot.Kind, message msg.Message, args ...interfac msg += fmt.Sprintf("twitch.istpl (default: %s)\n", isStreamingTplFallback) msg += fmt.Sprintf("twitch.nottpl (default: %s)\n", notStreamingTplFallback) msg += fmt.Sprintf("twitch.stoppedtpl (default: %s)\n", stoppedStreamingTplFallback) + msg += "You can reset all messages with `!reset twitch`" msg += "And you can ask who is streaming with `!twitch status`" p.Bot.Send(bot.Message, message.Channel, msg) return true From 6fb0990a11912dbcecfe42c82661a084e6edf8a4 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Wed, 13 Feb 2019 12:08:16 -0500 Subject: [PATCH 048/107] main: removed zork plugin --- main.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/main.go b/main.go index 490c2c4..e6ba88c 100644 --- a/main.go +++ b/main.go @@ -37,7 +37,6 @@ import ( "github.com/velour/catbase/plugins/tell" "github.com/velour/catbase/plugins/twitch" "github.com/velour/catbase/plugins/your" - "github.com/velour/catbase/plugins/zork" ) var ( @@ -95,7 +94,6 @@ func main() { b.AddPlugin(counter.New(b)) b.AddPlugin(reminder.New(b)) b.AddPlugin(babbler.New(b)) - b.AddPlugin(zork.New(b)) b.AddPlugin(rss.New(b)) b.AddPlugin(reaction.New(b)) b.AddPlugin(twitch.New(b)) From 47a824e8dab0b07f3815ea3fb1ec3e6d08ad17e9 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Fri, 15 Feb 2019 13:22:54 -0500 Subject: [PATCH 049/107] remember: fixed something? It works now. --- bot/bot.go | 2 +- bot/handlers.go | 3 +- bot/interfaces.go | 4 +- connectors/slackapp/slackApp.go | 6 +- go.sum | 9 -- main.go | 3 +- .../fact/{remember_test.go => fact_test.go} | 30 +--- plugins/fact/factoid.go | 134 +++++++++--------- plugins/{fact => remember}/remember.go | 72 ++++------ plugins/remember/remember_test.go | 53 +++++++ 10 files changed, 164 insertions(+), 152 deletions(-) rename plugins/fact/{remember_test.go => fact_test.go} (59%) rename plugins/{fact => remember}/remember.go (65%) create mode 100644 plugins/remember/remember_test.go diff --git a/bot/bot.go b/bot/bot.go index b0f0da9..b794fcc 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -246,7 +246,7 @@ func (b *bot) RegisterFilter(name string, f func(string) string) { // Register a callback func (b *bot) Register(p Plugin, kind Kind, cb Callback) { - t := reflect.TypeOf(p) + t := reflect.TypeOf(p).String() if _, ok := b.callbacks[t]; !ok { b.callbacks[t] = make(map[Kind][]Callback) } diff --git a/bot/handlers.go b/bot/handlers.go index 5c52005..75eff1b 100644 --- a/bot/handlers.go +++ b/bot/handlers.go @@ -25,6 +25,7 @@ func (b *bot) Receive(kind Kind, msg msg.Message, args ...interface{}) bool { if kind == Message && strings.HasPrefix(msg.Body, "help") && msg.Command { parts := strings.Fields(strings.ToLower(msg.Body)) b.checkHelp(msg.Channel, parts) + log.Println("Handled a help, returning") goto RET } @@ -40,7 +41,7 @@ RET: } func (b *bot) runCallback(plugin Plugin, evt Kind, message msg.Message, args ...interface{}) bool { - t := reflect.TypeOf(plugin) + t := reflect.TypeOf(plugin).String() for _, cb := range b.callbacks[t][evt] { if cb(evt, message, args...) { return true diff --git a/bot/interfaces.go b/bot/interfaces.go index 53d5aa3..2f430e4 100644 --- a/bot/interfaces.go +++ b/bot/interfaces.go @@ -3,8 +3,6 @@ package bot import ( - "reflect" - "github.com/jmoiron/sqlx" "github.com/velour/catbase/bot/msg" "github.com/velour/catbase/bot/user" @@ -34,7 +32,7 @@ const ( type Kind int type Callback func(Kind, msg.Message, ...interface{}) bool -type CallbackMap map[reflect.Type]map[Kind][]Callback +type CallbackMap map[string]map[Kind][]Callback // Bot interface serves to allow mocking of the actual bot type Bot interface { diff --git a/connectors/slackapp/slackApp.go b/connectors/slackapp/slackApp.go index 3d8e90c..a252037 100644 --- a/connectors/slackapp/slackApp.go +++ b/connectors/slackapp/slackApp.go @@ -48,7 +48,7 @@ func New(c *config.Config) *SlackApp { log.Fatalf("No slack token found. Set SLACKTOKEN env.") } - api := slack.New(token, slack.OptionDebug(true)) + api := slack.New(token, slack.OptionDebug(false)) return &SlackApp{ api: api, @@ -263,7 +263,7 @@ func (s *SlackApp) populateEmojiList() { log.Println("Cannot get emoji list without slack.usertoken") return } - api := slack.New(s.userToken, slack.OptionDebug(true)) + api := slack.New(s.userToken, slack.OptionDebug(false)) em, err := api.GetEmoji() if err != nil { @@ -339,7 +339,7 @@ func (s *SlackApp) Who(id string) []string { log.Println("Cannot get emoji list without slack.usertoken") return []string{s.config.Get("nick", "bot")} } - api := slack.New(s.userToken, slack.OptionDebug(true)) + api := slack.New(s.userToken, slack.OptionDebug(false)) log.Println("Who is queried for ", id) // Not super sure this is the correct call diff --git a/go.sum b/go.sum index cf9d3cc..e4ec4ff 100644 --- a/go.sum +++ b/go.sum @@ -2,7 +2,6 @@ github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o= github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= -github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/chrissexton/leftpad v0.0.0-20181207133115-1e93189d2fff h1:+TEqaP0eO1unI7XHHFeMDhsxhLDIb0x8KYuZbqbAmxA= github.com/chrissexton/leftpad v0.0.0-20181207133115-1e93189d2fff/go.mod h1:QCRjR0b4qiJiNjuP7RFM89bh4UExGJalcWmYeSvlnRc= @@ -20,7 +19,6 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mmcdole/gofeed v1.0.0-beta2 h1:CjQ0ADhAwNSb08zknAkGOEYqr8zfZKfrzgk9BxpWP2E= github.com/mmcdole/gofeed v1.0.0-beta2/go.mod h1:/BF9JneEL2/flujm8XHoxUcghdTV6vvb3xx/vKyChFU= @@ -37,23 +35,16 @@ github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d/go.mod h1:xvqspo github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/velour/chat v0.0.0-20180713122344-fd1d1606cb89 h1:3D3M900hEBJJAqyKl70QuRHi5weX9+ptlQI1v+FNcQ8= github.com/velour/chat v0.0.0-20180713122344-fd1d1606cb89/go.mod h1:ejwOYCjnDMyO5LXFXRARQJGBZ6xQJZ3rgAHE5drSuMM= github.com/velour/velour v0.0.0-20160303155839-8e090e68d158 h1:p3rTUXxzuKsBOsHlkly7+rj9wagFBKeIsCDKkDII9sw= github.com/velour/velour v0.0.0-20160303155839-8e090e68d158/go.mod h1:Ojy3BTOiOTwpHpw7/HNi+TVTFppao191PQs+Qc3sqpE= -github.com/yuin/gluamapper v0.0.0-20150323120927-d836955830e7 h1:noHsffKZsNfU38DwcXWEPldrTjIZ8FPNKx8mYMGnqjs= github.com/yuin/gluamapper v0.0.0-20150323120927-d836955830e7/go.mod h1:bbMEM6aU1WDF1ErA5YJ0p91652pGv140gGw4Ww3RGp8= -github.com/yuin/gopher-lua v0.0.0-20181214045814-db9ae37725ec h1:vpF8Kxql6/3OvGH4y2SKtpN3WsB17mvJ8f8H1o2vucQ= -github.com/yuin/gopher-lua v0.0.0-20181214045814-db9ae37725ec/go.mod h1:fFiAh+CowNFr0NK5VASokuwKwkbacRmHsVA7Yb1Tqac= github.com/yuin/gopher-lua v0.0.0-20190125051437-7b9317363aa9/go.mod h1:fFiAh+CowNFr0NK5VASokuwKwkbacRmHsVA7Yb1Tqac= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181217023233-e147a9138326 h1:iCzOf0xz39Tstp+Tu/WwyGjUXCk34QhQORRxBeXXTA4= -golang.org/x/net v0.0.0-20181217023233-e147a9138326/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 h1:ulvT7fqt0yHWzpJwI57MezWnYDVpCAYBVuYst/L+fAY= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= diff --git a/main.go b/main.go index e6ba88c..0e5e311 100644 --- a/main.go +++ b/main.go @@ -29,6 +29,7 @@ import ( "github.com/velour/catbase/plugins/nerdepedia" "github.com/velour/catbase/plugins/picker" "github.com/velour/catbase/plugins/reaction" + "github.com/velour/catbase/plugins/remember" "github.com/velour/catbase/plugins/reminder" "github.com/velour/catbase/plugins/rpgORdie" "github.com/velour/catbase/plugins/rss" @@ -89,7 +90,7 @@ func main() { b.AddPlugin(dice.New(b)) b.AddPlugin(picker.New(b)) b.AddPlugin(beers.New(b)) - b.AddPlugin(fact.NewRemember(b)) + b.AddPlugin(remember.New(b)) b.AddPlugin(your.New(b)) b.AddPlugin(counter.New(b)) b.AddPlugin(reminder.New(b)) diff --git a/plugins/fact/remember_test.go b/plugins/fact/fact_test.go similarity index 59% rename from plugins/fact/remember_test.go rename to plugins/fact/fact_test.go index a3ded28..c0fa9a9 100644 --- a/plugins/fact/remember_test.go +++ b/plugins/fact/fact_test.go @@ -23,32 +23,10 @@ func makeMessage(nick, payload string) msg.Message { } } -func makePlugin(t *testing.T) (*RememberPlugin, *Factoid, *bot.MockBot) { +func makePlugin(t *testing.T) (*FactoidPlugin, *bot.MockBot) { mb := bot.NewMockBot() f := New(mb) // for DB table - p := NewRemember(mb) - assert.NotNil(t, p) - return p, f, mb -} - -// Test case -func TestCornerCaseBug(t *testing.T) { - msgs := []msg.Message{ - makeMessage("user1", "I don’t want to personally touch a horse dick."), - makeMessage("user3", "idk my bff rose?"), - makeMessage("user2", "!remember user1 touch"), - } - - p, _, mb := makePlugin(t) - - for _, m := range msgs { - p.message(bot.Message, m) - } - assert.Len(t, mb.Messages, 1) - assert.Contains(t, mb.Messages[0], "horse dick") - q, err := getSingleFact(mb.DB(), "user1 quotes") - assert.Nil(t, err) - assert.Contains(t, q.Tidbit, "horse dick") + return f, mb } func TestReact(t *testing.T) { @@ -56,7 +34,7 @@ func TestReact(t *testing.T) { makeMessage("user1", "!testing123 jesus"), makeMessage("user2", "testing123"), } - _, p, mb := makePlugin(t) + p, mb := makePlugin(t) for _, m := range msgs { p.message(bot.Message, m) @@ -69,7 +47,7 @@ func TestReactCantLearnSpaces(t *testing.T) { msgs := []msg.Message{ makeMessage("user1", "!test jesus christ"), } - _, p, mb := makePlugin(t) + p, mb := makePlugin(t) for _, m := range msgs { p.message(bot.Message, m) diff --git a/plugins/fact/factoid.go b/plugins/fact/factoid.go index d7479a9..b4cc1d4 100644 --- a/plugins/fact/factoid.go +++ b/plugins/fact/factoid.go @@ -22,14 +22,14 @@ import ( // respond to queries in a way that is unpredictable and fun // factoid stores info about our factoid for lookup and later interaction -type factoid struct { - id sql.NullInt64 +type Factoid struct { + ID sql.NullInt64 Fact string Tidbit string Verb string Owner string - created time.Time - accessed time.Time + Created time.Time + Accessed time.Time Count int } @@ -38,14 +38,14 @@ type alias struct { Next string } -func (a *alias) resolve(db *sqlx.DB) (*factoid, error) { +func (a *alias) resolve(db *sqlx.DB) (*Factoid, error) { // perform DB query to fill the To field q := `select fact, next from factoid_alias where fact=?` var next alias err := db.Get(&next, q, a.Next) if err != nil { // we hit the end of the chain, get a factoid named Next - fact, err := getSingleFact(db, a.Next) + fact, err := GetSingleFact(db, a.Next) if err != nil { err := fmt.Errorf("Error resolvig alias %v: %v", a, err) return nil, err @@ -55,7 +55,7 @@ func (a *alias) resolve(db *sqlx.DB) (*factoid, error) { return next.resolve(db) } -func findAlias(db *sqlx.DB, fact string) (bool, *factoid) { +func findAlias(db *sqlx.DB, fact string) (bool, *Factoid) { q := `select * from factoid_alias where fact=?` var a alias err := db.Get(&a, q, fact) @@ -89,9 +89,9 @@ func aliasFromStrings(from, to string) *alias { return &alias{from, to} } -func (f *factoid) save(db *sqlx.DB) error { +func (f *Factoid) Save(db *sqlx.DB) error { var err error - if f.id.Valid { + if f.ID.Valid { // update _, err = db.Exec(`update factoid set fact=?, @@ -105,12 +105,12 @@ func (f *factoid) save(db *sqlx.DB) error { f.Tidbit, f.Verb, f.Owner, - f.accessed.Unix(), + f.Accessed.Unix(), f.Count, - f.id.Int64) + f.ID.Int64) } else { - f.created = time.Now() - f.accessed = time.Now() + f.Created = time.Now() + f.Accessed = time.Now() // insert res, err := db.Exec(`insert into factoid ( fact, @@ -125,8 +125,8 @@ func (f *factoid) save(db *sqlx.DB) error { f.Tidbit, f.Verb, f.Owner, - f.created.Unix(), - f.accessed.Unix(), + f.Created.Unix(), + f.Accessed.Unix(), f.Count, ) if err != nil { @@ -134,23 +134,23 @@ func (f *factoid) save(db *sqlx.DB) error { } id, err := res.LastInsertId() // hackhackhack? - f.id.Int64 = id - f.id.Valid = true + f.ID.Int64 = id + f.ID.Valid = true } return err } -func (f *factoid) delete(db *sqlx.DB) error { +func (f *Factoid) delete(db *sqlx.DB) error { var err error - if f.id.Valid { - _, err = db.Exec(`delete from factoid where id=?`, f.id) + if f.ID.Valid { + _, err = db.Exec(`delete from factoid where id=?`, f.ID) } - f.id.Valid = false + f.ID.Valid = false return err } -func getFacts(db *sqlx.DB, fact string, tidbit string) ([]*factoid, error) { - var fs []*factoid +func getFacts(db *sqlx.DB, fact string, tidbit string) ([]*Factoid, error) { + var fs []*Factoid query := `select id, fact, @@ -170,11 +170,11 @@ func getFacts(db *sqlx.DB, fact string, tidbit string) ([]*factoid, error) { return nil, err } for rows.Next() { - var f factoid + var f Factoid var tmpCreated int64 var tmpAccessed int64 err := rows.Scan( - &f.id, + &f.ID, &f.Fact, &f.Tidbit, &f.Verb, @@ -186,15 +186,15 @@ func getFacts(db *sqlx.DB, fact string, tidbit string) ([]*factoid, error) { if err != nil { return nil, err } - f.created = time.Unix(tmpCreated, 0) - f.accessed = time.Unix(tmpAccessed, 0) + f.Created = time.Unix(tmpCreated, 0) + f.Accessed = time.Unix(tmpAccessed, 0) fs = append(fs, &f) } return fs, err } -func getSingle(db *sqlx.DB) (*factoid, error) { - var f factoid +func GetSingle(db *sqlx.DB) (*Factoid, error) { + var f Factoid var tmpCreated int64 var tmpAccessed int64 err := db.QueryRow(`select @@ -208,7 +208,7 @@ func getSingle(db *sqlx.DB) (*factoid, error) { count from factoid order by random() limit 1;`).Scan( - &f.id, + &f.ID, &f.Fact, &f.Tidbit, &f.Verb, @@ -217,13 +217,13 @@ func getSingle(db *sqlx.DB) (*factoid, error) { &tmpAccessed, &f.Count, ) - f.created = time.Unix(tmpCreated, 0) - f.accessed = time.Unix(tmpAccessed, 0) + f.Created = time.Unix(tmpCreated, 0) + f.Accessed = time.Unix(tmpAccessed, 0) return &f, err } -func getSingleFact(db *sqlx.DB, fact string) (*factoid, error) { - var f factoid +func GetSingleFact(db *sqlx.DB, fact string) (*Factoid, error) { + var f Factoid var tmpCreated int64 var tmpAccessed int64 err := db.QueryRow(`select @@ -239,7 +239,7 @@ func getSingleFact(db *sqlx.DB, fact string) (*factoid, error) { where fact like ? order by random() limit 1;`, fact).Scan( - &f.id, + &f.ID, &f.Fact, &f.Tidbit, &f.Verb, @@ -248,22 +248,22 @@ func getSingleFact(db *sqlx.DB, fact string) (*factoid, error) { &tmpAccessed, &f.Count, ) - f.created = time.Unix(tmpCreated, 0) - f.accessed = time.Unix(tmpAccessed, 0) + f.Created = time.Unix(tmpCreated, 0) + f.Accessed = time.Unix(tmpAccessed, 0) return &f, err } // Factoid provides the necessary plugin-wide needs -type Factoid struct { +type FactoidPlugin struct { Bot bot.Bot NotFound []string - LastFact *factoid + LastFact *Factoid db *sqlx.DB } // NewFactoid creates a new Factoid with the Plugin interface -func New(botInst bot.Bot) *Factoid { - p := &Factoid{ +func New(botInst bot.Bot) *FactoidPlugin { + p := &FactoidPlugin{ Bot: botInst, NotFound: []string{ "I don't know.", @@ -343,7 +343,7 @@ func findAction(message string) string { // learnFact assumes we have a learning situation and inserts a new fact // into the database -func (p *Factoid) learnFact(message msg.Message, fact, verb, tidbit string) error { +func (p *FactoidPlugin) learnFact(message msg.Message, fact, verb, tidbit string) error { verb = strings.ToLower(verb) if verb == "react" { // This would be a great place to check against the API for valid emojy @@ -366,17 +366,17 @@ func (p *Factoid) learnFact(message msg.Message, fact, verb, tidbit string) erro return fmt.Errorf("Look, I already know that.") } - n := factoid{ + n := Factoid{ Fact: fact, Tidbit: tidbit, Verb: verb, Owner: message.User.Name, - created: time.Now(), - accessed: time.Now(), + Created: time.Now(), + Accessed: time.Now(), Count: 0, } p.LastFact = &n - err = n.save(p.db) + err = n.Save(p.db) if err != nil { log.Println("Error inserting fact: ", err) return fmt.Errorf("My brain is overheating.") @@ -386,10 +386,10 @@ func (p *Factoid) learnFact(message msg.Message, fact, verb, tidbit string) erro } // findTrigger checks to see if a given string is a trigger or not -func (p *Factoid) findTrigger(fact string) (bool, *factoid) { +func (p *FactoidPlugin) findTrigger(fact string) (bool, *Factoid) { fact = strings.ToLower(fact) // TODO: make sure this needs to be lowered here - f, err := getSingleFact(p.db, fact) + f, err := GetSingleFact(p.db, fact) if err != nil { return findAlias(p.db, fact) } @@ -398,7 +398,7 @@ func (p *Factoid) findTrigger(fact string) (bool, *factoid) { // sayFact spits out a fact to the channel and updates the fact in the database // with new time and count information -func (p *Factoid) sayFact(message msg.Message, fact factoid) { +func (p *FactoidPlugin) sayFact(message msg.Message, fact Factoid) { msg := p.Bot.Filter(message, fact.Tidbit) full := p.Bot.Filter(message, fmt.Sprintf("%s %s %s", fact.Fact, fact.Verb, fact.Tidbit, @@ -421,9 +421,9 @@ func (p *Factoid) sayFact(message msg.Message, fact factoid) { } // update fact tracking - fact.accessed = time.Now() + fact.Accessed = time.Now() fact.Count += 1 - err := fact.save(p.db) + err := fact.Save(p.db) if err != nil { log.Printf("Could not update fact.\n") log.Printf("%#v\n", fact) @@ -434,7 +434,7 @@ func (p *Factoid) sayFact(message msg.Message, fact factoid) { // trigger checks the message for its fitness to be a factoid and then hauls // the message off to sayFact for processing if it is in fact a trigger -func (p *Factoid) trigger(message msg.Message) bool { +func (p *FactoidPlugin) trigger(message msg.Message) bool { minLen := p.Bot.Config().GetInt("Factoid.MinLen", 4) if len(message.Body) > minLen || message.Command || message.Body == "..." { if ok, fact := p.findTrigger(message.Body); ok { @@ -453,20 +453,20 @@ func (p *Factoid) trigger(message msg.Message) bool { } // tellThemWhatThatWas is a hilarious name for a function. -func (p *Factoid) tellThemWhatThatWas(message msg.Message) bool { +func (p *FactoidPlugin) tellThemWhatThatWas(message msg.Message) bool { fact := p.LastFact var msg string if fact == nil { msg = "Nope." } else { msg = fmt.Sprintf("That was (#%d) '%s <%s> %s'", - fact.id.Int64, fact.Fact, fact.Verb, fact.Tidbit) + fact.ID.Int64, fact.Fact, fact.Verb, fact.Tidbit) } p.Bot.Send(bot.Message, message.Channel, msg) return true } -func (p *Factoid) learnAction(message msg.Message, action string) bool { +func (p *FactoidPlugin) learnAction(message msg.Message, action string) bool { body := message.Body parts := strings.SplitN(body, action, 2) @@ -512,7 +512,7 @@ func changeOperator(body string) string { // If the user requesting forget is either the owner of the last learned fact or // an admin, it may be deleted -func (p *Factoid) forgetLastFact(message msg.Message) bool { +func (p *FactoidPlugin) forgetLastFact(message msg.Message) bool { if p.LastFact == nil { p.Bot.Send(bot.Message, message.Channel, "I refuse.") return true @@ -522,7 +522,7 @@ func (p *Factoid) forgetLastFact(message msg.Message) bool { if err != nil { log.Println("Error removing fact: ", p.LastFact, err) } - fmt.Printf("Forgot #%d: %s %s %s\n", p.LastFact.id.Int64, p.LastFact.Fact, + fmt.Printf("Forgot #%d: %s %s %s\n", p.LastFact.ID.Int64, p.LastFact.Fact, p.LastFact.Verb, p.LastFact.Tidbit) p.Bot.Send(bot.Action, message.Channel, "hits himself over the head with a skillet") p.LastFact = nil @@ -531,7 +531,7 @@ func (p *Factoid) forgetLastFact(message msg.Message) bool { } // Allow users to change facts with a simple regexp -func (p *Factoid) changeFact(message msg.Message) bool { +func (p *FactoidPlugin) changeFact(message msg.Message) bool { oper := changeOperator(message.Body) parts := strings.SplitN(message.Body, oper, 2) userexp := strings.TrimSpace(parts[1]) @@ -571,8 +571,8 @@ func (p *Factoid) changeFact(message msg.Message) bool { fact.Verb = reg.ReplaceAllString(fact.Verb, replace) fact.Tidbit = reg.ReplaceAllString(fact.Tidbit, replace) fact.Count += 1 - fact.accessed = time.Now() - fact.save(p.db) + fact.Accessed = time.Now() + fact.Save(p.db) } } else if len(parts) == 3 { // search for a factoid and print it @@ -614,7 +614,7 @@ func (p *Factoid) changeFact(message msg.Message) bool { // Message responds to the bot hook on recieving messages. // This function returns true if the plugin responds in a meaningful way to the users message. // Otherwise, the function returns false and the bot continues execution of other plugins. -func (p *Factoid) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *FactoidPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { if strings.ToLower(message.Body) == "what was that?" { return p.tellThemWhatThatWas(message) } @@ -674,15 +674,15 @@ func (p *Factoid) message(kind bot.Kind, message msg.Message, args ...interface{ } // Help responds to help requests. Every plugin must implement a help function. -func (p *Factoid) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *FactoidPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { p.Bot.Send(bot.Message, message.Channel, "I can learn facts and spit them back out. You can say \"this is that\" or \"he $5\". Later, trigger the factoid by just saying the trigger word, \"this\" or \"he\" in these examples.") p.Bot.Send(bot.Message, message.Channel, "I can also figure out some variables including: $nonzero, $digit, $nick, and $someone.") return true } // Pull a fact at random from the database -func (p *Factoid) randomFact() *factoid { - f, err := getSingle(p.db) +func (p *FactoidPlugin) randomFact() *Factoid { + f, err := GetSingle(p.db) if err != nil { fmt.Println("Error getting a fact: ", err) return nil @@ -691,7 +691,7 @@ func (p *Factoid) randomFact() *factoid { } // factTimer spits out a fact at a given interval and with given probability -func (p *Factoid) factTimer(channel string) { +func (p *FactoidPlugin) factTimer(channel string) { quoteTime := p.Bot.Config().GetInt("Factoid.QuoteTime", 30) if quoteTime == 0 { quoteTime = 30 @@ -739,7 +739,7 @@ func (p *Factoid) factTimer(channel string) { } // Register any web URLs desired -func (p *Factoid) registerWeb() { +func (p *FactoidPlugin) registerWeb() { http.HandleFunc("/factoid/req", p.serveQuery) http.HandleFunc("/factoid", p.serveQuery) p.Bot.RegisterWeb("/factoid", "Factoid") @@ -755,7 +755,7 @@ func linkify(text string) template.HTML { return template.HTML(strings.Join(parts, " ")) } -func (p *Factoid) serveQuery(w http.ResponseWriter, r *http.Request) { +func (p *FactoidPlugin) serveQuery(w http.ResponseWriter, r *http.Request) { context := make(map[string]interface{}) funcMap := template.FuncMap{ // The name "title" is what the function will be called in the template text. diff --git a/plugins/fact/remember.go b/plugins/remember/remember.go similarity index 65% rename from plugins/fact/remember.go rename to plugins/remember/remember.go index 8aa32da..820b5fa 100644 --- a/plugins/fact/remember.go +++ b/plugins/remember/remember.go @@ -1,6 +1,4 @@ -// © 2013 the CatBase Authors under the WTFPL. See AUTHORS for the list of authors. - -package fact +package remember import ( "fmt" @@ -11,38 +9,32 @@ import ( "github.com/jmoiron/sqlx" "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" + "github.com/velour/catbase/plugins/fact" ) -// This is a skeleton plugin to serve as an example and quick copy/paste for new -// plugins. - type RememberPlugin struct { - Bot bot.Bot - Log map[string][]msg.Message + bot bot.Bot + log map[string][]msg.Message db *sqlx.DB } -// NewRememberPlugin creates a new RememberPlugin with the Plugin interface -func NewRemember(b bot.Bot) *RememberPlugin { - p := RememberPlugin{ - Bot: b, - Log: make(map[string][]msg.Message), +func New(b bot.Bot) *RememberPlugin { + p := &RememberPlugin{ + bot: b, + log: make(map[string][]msg.Message), db: b.DB(), } + b.Register(p, bot.Message, p.message) - b.Register(p, bot.Message, p.help) - return &p + b.Register(p, bot.Help, p.help) + + return p } -// Message responds to the bot hook on recieving messages. -// This function returns true if the plugin responds in a meaningful way to the -// users message. Otherwise, the function returns false and the bot continues -// execution of other plugins. func (p *RememberPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { - if strings.ToLower(message.Body) == "quote" && message.Command { q := p.randQuote() - p.Bot.Send(bot.Message, message.Channel, q) + p.bot.Send(bot.Message, message.Channel, q) // is it evil not to remember that the user said quote? return true @@ -50,16 +42,16 @@ func (p *RememberPlugin) message(kind bot.Kind, message msg.Message, args ...int user := message.User parts := strings.Fields(message.Body) + if message.Command && len(parts) >= 3 && strings.ToLower(parts[0]) == "remember" { - // we have a remember! // look through the logs and find parts[1] as a user, if not, // fuck this hoser nick := parts[1] snip := strings.Join(parts[2:], " ") - for i := len(p.Log[message.Channel]) - 1; i >= 0; i-- { - entry := p.Log[message.Channel][i] + for i := len(p.log[message.Channel]) - 1; i >= 0; i-- { + entry := p.log[message.Channel][i] log.Printf("Comparing %s:%s with %s:%s", entry.User.Name, entry.Body, nick, snip) if strings.ToLower(entry.User.Name) == strings.ToLower(nick) && @@ -78,18 +70,18 @@ func (p *RememberPlugin) message(kind bot.Kind, message msg.Message, args ...int trigger := fmt.Sprintf("%s quotes", entry.User.Name) - fact := factoid{ + fact := fact.Factoid{ Fact: strings.ToLower(trigger), Verb: "reply", Tidbit: msg, Owner: user.Name, - created: time.Now(), - accessed: time.Now(), + Created: time.Now(), + Accessed: time.Now(), Count: 0, } - if err := fact.save(p.db); err != nil { + if err := fact.Save(p.db); err != nil { log.Println("ERROR!!!!:", err) - p.Bot.Send(bot.Message, message.Channel, "Tell somebody I'm broke.") + p.bot.Send(bot.Message, message.Channel, "Tell somebody I'm broke.") } log.Println("Remembering factoid:", msg) @@ -97,30 +89,28 @@ func (p *RememberPlugin) message(kind bot.Kind, message msg.Message, args ...int // sorry, not creative with names so we're reusing msg msg = fmt.Sprintf("Okay, %s, remembering '%s'.", message.User.Name, msg) - p.Bot.Send(bot.Message, message.Channel, msg) + p.bot.Send(bot.Message, message.Channel, msg) p.recordMsg(message) return true } } - - p.Bot.Send(bot.Message, message.Channel, "Sorry, I don't know that phrase.") + p.bot.Send(bot.Message, message.Channel, "Sorry, I don't know that phrase.") p.recordMsg(message) return true } + p.recordMsg(message) return false } -// Help responds to help requests. Every plugin must implement a help function. func (p *RememberPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { - - msg := "!remember will let you quote your idiot friends. Just type " + + msg := "remember will let you quote your idiot friends. Just type " + "!remember to remember what they said. Snippet can " + "be any part of their message. Later on, you can ask for a random " + "!quote." - p.Bot.Send(bot.Message, message.Channel, msg) + p.bot.Send(bot.Message, message.Channel, msg) return true } @@ -129,12 +119,12 @@ func (p *RememberPlugin) help(kind bot.Kind, message msg.Message, args ...interf // expanded to have this function execute a quote for a particular channel func (p *RememberPlugin) randQuote() string { - var f factoid + var f fact.Factoid var tmpCreated int64 var tmpAccessed int64 err := p.db.QueryRow(`select * from factoid where fact like '%quotes' order by random() limit 1;`).Scan( - &f.id, + &f.ID, &f.Fact, &f.Tidbit, &f.Verb, @@ -147,13 +137,13 @@ func (p *RememberPlugin) randQuote() string { log.Println("Error getting quotes: ", err) return "I had a problem getting your quote." } - f.created = time.Unix(tmpCreated, 0) - f.accessed = time.Unix(tmpAccessed, 0) + f.Created = time.Unix(tmpCreated, 0) + f.Accessed = time.Unix(tmpAccessed, 0) return f.Tidbit } func (p *RememberPlugin) recordMsg(message msg.Message) { log.Printf("Logging message: %s: %s", message.User.Name, message.Body) - p.Log[message.Channel] = append(p.Log[message.Channel], message) + p.log[message.Channel] = append(p.log[message.Channel], message) } diff --git a/plugins/remember/remember_test.go b/plugins/remember/remember_test.go new file mode 100644 index 0000000..8a1512a --- /dev/null +++ b/plugins/remember/remember_test.go @@ -0,0 +1,53 @@ +package remember + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/velour/catbase/bot" + "github.com/velour/catbase/bot/msg" + "github.com/velour/catbase/bot/user" + "github.com/velour/catbase/plugins/fact" +) + +func makeMessage(nick, payload string) msg.Message { + isCmd := strings.HasPrefix(payload, "!") + if isCmd { + payload = payload[1:] + } + return msg.Message{ + User: &user.User{Name: nick}, + Channel: "test", + Body: payload, + Command: isCmd, + } +} + +func makePlugin(t *testing.T) (*RememberPlugin, *fact.FactoidPlugin, *bot.MockBot) { + mb := bot.NewMockBot() + f := fact.New(mb) // for DB table + p := New(mb) + assert.NotNil(t, p) + return p, f, mb +} + +// Test case +func TestCornerCaseBug(t *testing.T) { + msgs := []msg.Message{ + makeMessage("user1", "I don’t want to personally touch a horse dick."), + makeMessage("user3", "idk my bff rose?"), + makeMessage("user2", "!remember user1 touch"), + } + + p, _, mb := makePlugin(t) + + for _, m := range msgs { + p.message(bot.Message, m) + } + assert.Len(t, mb.Messages, 1) + assert.Contains(t, mb.Messages[0], "horse dick") + q, err := fact.GetSingleFact(mb.DB(), "user1 quotes") + assert.Nil(t, err) + assert.Contains(t, q.Tidbit, "horse dick") +} From 30944bf620118f5a395b835efc645c7fccc64f43 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Fri, 15 Feb 2019 15:00:01 -0500 Subject: [PATCH 050/107] Revert "main: removed zork plugin" This reverts commit 6fb0990a11912dbcecfe42c82661a084e6edf8a4. --- main.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main.go b/main.go index 0e5e311..aa5c352 100644 --- a/main.go +++ b/main.go @@ -38,6 +38,7 @@ import ( "github.com/velour/catbase/plugins/tell" "github.com/velour/catbase/plugins/twitch" "github.com/velour/catbase/plugins/your" + "github.com/velour/catbase/plugins/zork" ) var ( @@ -95,6 +96,7 @@ func main() { b.AddPlugin(counter.New(b)) b.AddPlugin(reminder.New(b)) b.AddPlugin(babbler.New(b)) + b.AddPlugin(zork.New(b)) b.AddPlugin(rss.New(b)) b.AddPlugin(reaction.New(b)) b.AddPlugin(twitch.New(b)) From 6a1cabc2aaecf55165e1540ad105cbea65d9574f Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Fri, 15 Feb 2019 15:12:09 -0500 Subject: [PATCH 051/107] beers: fix bad indirection issue --- plugins/beers/beers.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/beers/beers.go b/plugins/beers/beers.go index 3ad3dee..4e1640c 100644 --- a/plugins/beers/beers.go +++ b/plugins/beers/beers.go @@ -48,7 +48,7 @@ func New(b bot.Bot) *BeersPlugin { );`); err != nil { log.Fatal(err) } - p := BeersPlugin{ + p := &BeersPlugin{ Bot: b, db: b.DB(), } @@ -57,7 +57,7 @@ func New(b bot.Bot) *BeersPlugin { } b.Register(p, bot.Message, p.message) b.Register(p, bot.Help, p.help) - return &p + return p } // Message responds to the bot hook on recieving messages. From 15bb7c34e5b41d2b9886316508a211ae2c49af22 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Mon, 18 Feb 2019 18:45:23 -0500 Subject: [PATCH 052/107] slackApp: add dedupe --- connectors/slackapp/slackApp.go | 33 ++++++++++++++++ connectors/slackapp/slackApp_test.go | 58 ++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 connectors/slackapp/slackApp_test.go diff --git a/connectors/slackapp/slackApp.go b/connectors/slackapp/slackApp.go index a252037..45d3b0d 100644 --- a/connectors/slackapp/slackApp.go +++ b/connectors/slackapp/slackApp.go @@ -2,6 +2,7 @@ package slackapp import ( "bytes" + "container/ring" "encoding/json" "fmt" "html" @@ -23,6 +24,8 @@ import ( "github.com/velour/catbase/config" ) +const DEFAULT_RING = 5 + type SlackApp struct { bot bot.Bot config *config.Config @@ -40,6 +43,8 @@ type SlackApp struct { emoji map[string]string event bot.Callback + + msgIDBuffer *ring.Ring } func New(c *config.Config) *SlackApp { @@ -50,6 +55,12 @@ func New(c *config.Config) *SlackApp { api := slack.New(token, slack.OptionDebug(false)) + idBuf := ring.New(c.GetInt("ringSize", DEFAULT_RING)) + for i := 0; i < idBuf.Len(); i++ { + idBuf.Value = "" + idBuf = idBuf.Next() + } + return &SlackApp{ api: api, config: c, @@ -60,6 +71,7 @@ func New(c *config.Config) *SlackApp { lastRecieved: time.Now(), users: make(map[string]string), emoji: make(map[string]string), + msgIDBuffer: idBuf, } } @@ -106,7 +118,28 @@ func (s *SlackApp) Serve() error { return nil } +// checkRingOrAdd returns true if it finds the ts value +// or false if the ts isn't yet in the ring (and adds it) +func (s *SlackApp) checkRingOrAdd(ts string) bool { + found := false + s.msgIDBuffer.Do(func(p interface{}) { + if p.(string) == ts { + found = true + } + }) + if found { + return true + } + s.msgIDBuffer.Value = ts + s.msgIDBuffer = s.msgIDBuffer.Next() + return false +} + func (s *SlackApp) msgReceivd(msg *slackevents.MessageEvent) { + if s.checkRingOrAdd(msg.TimeStamp) { + log.Printf("Got a duplicate message from server: %s", msg.TimeStamp) + return + } isItMe := msg.BotID != "" && msg.BotID == s.myBotID if !isItMe && msg.ThreadTimeStamp == "" { m := s.buildMessage(msg) diff --git a/connectors/slackapp/slackApp_test.go b/connectors/slackapp/slackApp_test.go new file mode 100644 index 0000000..939f1fa --- /dev/null +++ b/connectors/slackapp/slackApp_test.go @@ -0,0 +1,58 @@ +package slackapp + +import ( + "container/ring" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDedupeNoDupes(t *testing.T) { + buf := ring.New(3) + for i := 0; i < 3; i++ { + buf.Value = "" + buf = buf.Next() + } + s := SlackApp{msgIDBuffer: buf} + expected := []bool{ + false, + false, + false, + false, + false, + } + + actuals := []bool{} + actuals = append(actuals, s.checkRingOrAdd("a")) + actuals = append(actuals, s.checkRingOrAdd("b")) + actuals = append(actuals, s.checkRingOrAdd("c")) + actuals = append(actuals, s.checkRingOrAdd("d")) + actuals = append(actuals, s.checkRingOrAdd("e")) + + assert.ElementsMatch(t, expected, actuals) +} + +func TestDedupeWithDupes(t *testing.T) { + buf := ring.New(3) + for i := 0; i < 3; i++ { + buf.Value = "" + buf = buf.Next() + } + s := SlackApp{msgIDBuffer: buf} + expected := []bool{ + false, + false, + true, + false, + true, + } + + actuals := []bool{} + actuals = append(actuals, s.checkRingOrAdd("a")) + actuals = append(actuals, s.checkRingOrAdd("b")) + actuals = append(actuals, s.checkRingOrAdd("a")) + actuals = append(actuals, s.checkRingOrAdd("d")) + actuals = append(actuals, s.checkRingOrAdd("d")) + + assert.ElementsMatch(t, expected, actuals) +} From 3f7c78f87067ab519a9ecf5d97d0bcda45e0443b Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Mon, 18 Feb 2019 18:52:35 -0500 Subject: [PATCH 053/107] slack: got moved earlier --- slack/fix_text.go | 96 ------ slack/slack.go | 736 ---------------------------------------------- 2 files changed, 832 deletions(-) delete mode 100644 slack/fix_text.go delete mode 100644 slack/slack.go diff --git a/slack/fix_text.go b/slack/fix_text.go deleted file mode 100644 index 28a8c1e..0000000 --- a/slack/fix_text.go +++ /dev/null @@ -1,96 +0,0 @@ -package slack - -import ( - "unicode/utf8" -) - -// fixText strips all of the Slack-specific annotations from message text, -// replacing it with the equivalent display form. -// Currently it: -// • Replaces user mentions like <@U124356> with @ followed by the user's nick. -// This uses the lookupUser function, which must map U1243456 to the nick. -// • Replaces user mentions like with the user's nick. -// • Strips < and > surrounding links. -// -// This was directly bogarted from velour/chat with emoji conversion removed. -func fixText(findUser func(id string) (string, bool), text string) string { - var output []rune - for len(text) > 0 { - r, i := utf8.DecodeRuneInString(text) - text = text[i:] - switch { - case r == '<': - var tag []rune - for { - r, i := utf8.DecodeRuneInString(text) - text = text[i:] - switch { - case r == '>': - if t, ok := fixTag(findUser, tag); ok { - output = append(output, t...) - break - } - fallthrough - case len(text) == 0: - output = append(output, '<') - output = append(output, tag...) - output = append(output, r) - default: - tag = append(tag, r) - continue - } - break - } - default: - output = append(output, r) - } - } - return string(output) -} - -func fixTag(findUser func(string) (string, bool), tag []rune) ([]rune, bool) { - switch { - case hasPrefix(tag, "@U"): - if i := indexRune(tag, '|'); i >= 0 { - return tag[i+1:], true - } - if findUser != nil { - if u, ok := findUser(string(tag[1:])); ok { - return []rune(u), true - } - } - return tag, true - - case hasPrefix(tag, "#C"): - if i := indexRune(tag, '|'); i >= 0 { - return append([]rune{'#'}, tag[i+1:]...), true - } - - case hasPrefix(tag, "http"): - if i := indexRune(tag, '|'); i >= 0 { - tag = tag[:i] - } - return tag, true - } - - return nil, false -} - -func hasPrefix(text []rune, prefix string) bool { - for _, r := range prefix { - if len(text) == 0 || text[0] != r { - return false - } - text = text[1:] - } - return true -} - -func indexRune(text []rune, find rune) int { - for i, r := range text { - if r == find { - return i - } - } - return -1 -} diff --git a/slack/slack.go b/slack/slack.go deleted file mode 100644 index dcf616b..0000000 --- a/slack/slack.go +++ /dev/null @@ -1,736 +0,0 @@ -// © 2016 the CatBase Authors under the WTFPL license. See AUTHORS for the list of authors. - -// Package slack connects to slack service -package slack - -import ( - "encoding/json" - "errors" - "fmt" - "html" - "io" - "io/ioutil" - "log" - "net/http" - "net/url" - "regexp" - "strconv" - "strings" - - // "sync/atomic" - "context" - "time" - - "github.com/velour/catbase/bot" - "github.com/velour/catbase/bot/msg" - "github.com/velour/catbase/bot/user" - "github.com/velour/catbase/config" - "github.com/velour/chat/websocket" -) - -type Slack struct { - config *config.Config - - url string - id string - token string - ws *websocket.Conn - - lastRecieved time.Time - - users map[string]string - - myBotID string - - emoji map[string]string - - event func(bot.Kind, msg.Message, ...interface{}) -} - -var idCounter uint64 - -type slackUserInfoResp struct { - Ok bool `json:"ok"` - User struct { - ID string `json:"id"` - Name string `json:"name"` - } `json:"user"` -} - -type slackChannelListItem struct { - ID string `json:"id"` - Name string `json:"name"` - IsChannel bool `json:"is_channel"` - Created int `json:"created"` - Creator string `json:"creator"` - IsArchived bool `json:"is_archived"` - IsGeneral bool `json:"is_general"` - NameNormalized string `json:"name_normalized"` - IsShared bool `json:"is_shared"` - IsOrgShared bool `json:"is_org_shared"` - IsMember bool `json:"is_member"` - Members []string `json:"members"` - Topic struct { - Value string `json:"value"` - Creator string `json:"creator"` - LastSet int `json:"last_set"` - } `json:"topic"` - Purpose struct { - Value string `json:"value"` - Creator string `json:"creator"` - LastSet int `json:"last_set"` - } `json:"purpose"` - PreviousNames []interface{} `json:"previous_names"` - NumMembers int `json:"num_members"` -} - -type slackChannelListResp struct { - Ok bool `json:"ok"` - Channels []slackChannelListItem `json:"channels"` -} - -type slackChannelInfoResp struct { - Ok bool `json:"ok"` - Channel struct { - ID string `json:"id"` - Name string `json:"name"` - IsChannel bool `json:"is_channel"` - Created int `json:"created"` - Creator string `json:"creator"` - IsArchived bool `json:"is_archived"` - IsGeneral bool `json:"is_general"` - NameNormalized string `json:"name_normalized"` - IsReadOnly bool `json:"is_read_only"` - IsShared bool `json:"is_shared"` - IsOrgShared bool `json:"is_org_shared"` - IsMember bool `json:"is_member"` - LastRead string `json:"last_read"` - Latest struct { - Type string `json:"type"` - User string `json:"user"` - Text string `json:"text"` - Ts string `json:"ts"` - } `json:"latest"` - UnreadCount int `json:"unread_count"` - UnreadCountDisplay int `json:"unread_count_display"` - Members []string `json:"members"` - Topic struct { - Value string `json:"value"` - Creator string `json:"creator"` - LastSet int64 `json:"last_set"` - } `json:"topic"` - Purpose struct { - Value string `json:"value"` - Creator string `json:"creator"` - LastSet int `json:"last_set"` - } `json:"purpose"` - PreviousNames []string `json:"previous_names"` - } `json:"channel"` -} - -type slackMessage struct { - ID uint64 `json:"id"` - Type string `json:"type"` - SubType string `json:"subtype"` - Hidden bool `json:"hidden"` - Channel string `json:"channel"` - Text string `json:"text"` - User string `json:"user"` - Username string `json:"username"` - BotID string `json:"bot_id"` - Ts string `json:"ts"` - ThreadTs string `json:"thread_ts"` - Error struct { - Code uint64 `json:"code"` - Msg string `json:"msg"` - } `json:"error"` -} - -type slackReaction struct { - Reaction string `json:"name"` - Channel string `json:"channel"` - Timestamp float64 `json:"timestamp"` -} - -type rtmStart struct { - Ok bool `json:"ok"` - Error string `json:"error"` - URL string `json:"url"` - Self struct { - ID string `json:"id"` - } `json:"self"` -} - -func New(c *config.Config) *Slack { - token := c.Get("slack.token", "NONE") - if token == "NONE" { - log.Fatalf("No slack token found. Set SLACKTOKEN env.") - } - return &Slack{ - config: c, - token: c.Get("slack.token", ""), - lastRecieved: time.Now(), - users: make(map[string]string), - emoji: make(map[string]string), - } -} - -func (s *Slack) Send(kind bot.Kind, args ...interface{}) (string, error) { - switch kind { - case bot.Message: - return s.sendMessage(args[0].(string), args[1].(string)) - case bot.Action: - return s.sendAction(args[0].(string), args[1].(string)) - case bot.Edit: - return s.edit(args[0].(string), args[1].(string), args[2].(string)) - case bot.Reply: - switch args[2].(type) { - case msg.Message: - return s.replyToMessage(args[0].(string), args[1].(string), args[2].(msg.Message)) - case string: - return s.replyToMessageIdentifier(args[0].(string), args[1].(string), args[2].(string)) - default: - return "", fmt.Errorf("Invalid types given to Reply") - } - case bot.Reaction: - return s.react(args[0].(string), args[1].(string), args[2].(msg.Message)) - default: - } - return "", fmt.Errorf("No handler for message type %d", kind) -} - -func checkReturnStatus(response *http.Response) error { - type Response struct { - OK bool `json:"ok"` - } - - body, err := ioutil.ReadAll(response.Body) - response.Body.Close() - if err != nil { - err := fmt.Errorf("Error reading Slack API body: %s", err) - return err - } - - var resp Response - err = json.Unmarshal(body, &resp) - if err != nil { - err := fmt.Errorf("Error parsing message response: %s", err) - return err - } - return nil -} - -func (s *Slack) RegisterEvent(f func(bot.Kind, msg.Message, ...interface{})) { - s.event = f -} - -func (s *Slack) sendMessageType(channel, message string, meMessage bool) (string, error) { - postUrl := "https://slack.com/api/chat.postMessage" - if meMessage { - postUrl = "https://slack.com/api/chat.meMessage" - } - - nick := s.config.Get("Nick", "bot") - icon := s.config.Get("IconURL", "https://placekitten.com/128/128") - - resp, err := http.PostForm(postUrl, - url.Values{"token": {s.token}, - "username": {nick}, - "icon_url": {icon}, - "channel": {channel}, - "text": {message}, - }) - - if err != nil { - log.Printf("Error sending Slack message: %s", err) - } - - body, err := ioutil.ReadAll(resp.Body) - resp.Body.Close() - if err != nil { - log.Fatalf("Error reading Slack API body: %s", err) - } - - log.Println(string(body)) - - type MessageResponse struct { - OK bool `json:"ok"` - Timestamp string `json:"ts"` - Message struct { - BotID string `json:"bot_id"` - } `json:"message"` - } - - var mr MessageResponse - err = json.Unmarshal(body, &mr) - if err != nil { - log.Fatalf("Error parsing message response: %s", err) - } - - if !mr.OK { - return "", errors.New("failure response received") - } - - s.myBotID = mr.Message.BotID - - return mr.Timestamp, err -} - -func (s *Slack) sendMessage(channel, message string) (string, error) { - log.Printf("Sending message to %s: %s", channel, message) - identifier, err := s.sendMessageType(channel, message, false) - return identifier, err -} - -func (s *Slack) sendAction(channel, message string) (string, error) { - log.Printf("Sending action to %s: %s", channel, message) - identifier, err := s.sendMessageType(channel, "_"+message+"_", true) - return identifier, err -} - -func (s *Slack) replyToMessageIdentifier(channel, message, identifier string) (string, error) { - nick := s.config.Get("Nick", "bot") - icon := s.config.Get("IconURL", "https://placekitten.com/128/128") - - resp, err := http.PostForm("https://slack.com/api/chat.postMessage", - url.Values{"token": {s.token}, - "username": {nick}, - "icon_url": {icon}, - "channel": {channel}, - "text": {message}, - "thread_ts": {identifier}, - }) - - if err != nil { - err := fmt.Errorf("Error sending Slack reply: %s", err) - return "", err - } - - body, err := ioutil.ReadAll(resp.Body) - resp.Body.Close() - if err != nil { - err := fmt.Errorf("Error reading Slack API body: %s", err) - return "", err - } - - log.Println(string(body)) - - type MessageResponse struct { - OK bool `json:"ok"` - Timestamp string `json:"ts"` - } - - var mr MessageResponse - err = json.Unmarshal(body, &mr) - if err != nil { - err := fmt.Errorf("Error parsing message response: %s", err) - return "", err - } - - if !mr.OK { - return "", fmt.Errorf("Got !OK from slack message response") - } - - return mr.Timestamp, err -} - -func (s *Slack) replyToMessage(channel, message string, replyTo msg.Message) (string, error) { - return s.replyToMessageIdentifier(channel, message, replyTo.AdditionalData["RAW_SLACK_TIMESTAMP"]) -} - -func (s *Slack) react(channel, reaction string, message msg.Message) (string, error) { - log.Printf("Reacting in %s: %s", channel, reaction) - resp, err := http.PostForm("https://slack.com/api/reactions.add", - url.Values{"token": {s.token}, - "name": {reaction}, - "channel": {channel}, - "timestamp": {message.AdditionalData["RAW_SLACK_TIMESTAMP"]}}) - if err != nil { - err := fmt.Errorf("reaction failed: %s", err) - return "", err - } - return "", checkReturnStatus(resp) -} - -func (s *Slack) edit(channel, newMessage, identifier string) (string, error) { - log.Printf("Editing in (%s) %s: %s", identifier, channel, newMessage) - resp, err := http.PostForm("https://slack.com/api/chat.update", - url.Values{"token": {s.token}, - "channel": {channel}, - "text": {newMessage}, - "ts": {identifier}}) - if err != nil { - err := fmt.Errorf("edit failed: %s", err) - return "", err - } - return "", checkReturnStatus(resp) -} - -func (s *Slack) GetEmojiList() map[string]string { - return s.emoji -} - -func (s *Slack) populateEmojiList() { - resp, err := http.PostForm("https://slack.com/api/emoji.list", - url.Values{"token": {s.token}}) - if err != nil { - log.Printf("Error retrieving emoji list from Slack: %s", err) - return - } - - body, err := ioutil.ReadAll(resp.Body) - resp.Body.Close() - if err != nil { - log.Fatalf("Error reading Slack API body: %s", err) - } - - type EmojiListResponse struct { - OK bool `json:"ok"` - Emoji map[string]string `json:"emoji"` - } - - var list EmojiListResponse - err = json.Unmarshal(body, &list) - if err != nil { - log.Fatalf("Error parsing emoji list: %s", err) - } - s.emoji = list.Emoji -} - -func (s *Slack) ping(ctx context.Context) { - ticker := time.NewTicker(10 * time.Second) - defer ticker.Stop() - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - ping := map[string]interface{}{"type": "ping", "time": time.Now().UnixNano()} - if err := s.ws.Send(context.TODO(), ping); err != nil { - panic(err) - } - } - } -} - -func (s *Slack) receiveMessage() (slackMessage, error) { - m := slackMessage{} - err := s.ws.Recv(context.TODO(), &m) - if err != nil { - log.Println("Error decoding WS message") - panic(fmt.Errorf("%v\n%v", m, err)) - } - return m, nil -} - -// I think it's horseshit that I have to do this -func slackTStoTime(t string) time.Time { - ts := strings.Split(t, ".") - sec, _ := strconv.ParseInt(ts[0], 10, 64) - nsec, _ := strconv.ParseInt(ts[1], 10, 64) - return time.Unix(sec, nsec) -} - -func (s *Slack) Serve() error { - s.connect() - s.populateEmojiList() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - go s.ping(ctx) - - for { - msg, err := s.receiveMessage() - if err != nil && err == io.EOF { - log.Fatalf("Slack API EOF") - } else if err != nil { - return fmt.Errorf("Slack API error: %s", err) - } - switch msg.Type { - case "message": - isItMe := msg.BotID != "" && msg.BotID == s.myBotID - if !isItMe && !msg.Hidden && msg.ThreadTs == "" { - m := s.buildMessage(msg) - if m.Time.Before(s.lastRecieved) { - log.Printf("Ignoring message: %+v\nlastRecieved: %v msg: %v", msg.ID, s.lastRecieved, m.Time) - } else { - s.lastRecieved = m.Time - s.event(bot.Message, m) - } - } else if msg.ThreadTs != "" { - //we're throwing away some information here by not parsing the correct reply object type, but that's okay - s.event(bot.Reply, s.buildLightReplyMessage(msg), msg.ThreadTs) - } else { - log.Printf("THAT MESSAGE WAS HIDDEN: %+v", msg.ID) - } - case "error": - log.Printf("Slack error, code: %d, message: %s", msg.Error.Code, msg.Error.Msg) - case "": // what even is this? - case "hello": - case "presence_change": - case "user_typing": - case "reconnect_url": - case "desktop_notification": - case "pong": - // squeltch this stuff - continue - default: - log.Printf("Unhandled Slack message type: '%s'", msg.Type) - } - } -} - -var urlDetector = regexp.MustCompile(`<(.+)://([^|^>]+).*>`) - -// Convert a slackMessage to a msg.Message -func (s *Slack) buildMessage(m slackMessage) msg.Message { - text := html.UnescapeString(m.Text) - - text = fixText(s.getUser, text) - - isCmd, text := bot.IsCmd(s.config, text) - - isAction := m.SubType == "me_message" - - u, _ := s.getUser(m.User) - if m.Username != "" { - u = m.Username - } - - tstamp := slackTStoTime(m.Ts) - - return msg.Message{ - User: &user.User{ - ID: m.User, - Name: u, - }, - Body: text, - Raw: m.Text, - Channel: m.Channel, - Command: isCmd, - Action: isAction, - Host: string(m.ID), - Time: tstamp, - AdditionalData: map[string]string{ - "RAW_SLACK_TIMESTAMP": m.Ts, - }, - } -} - -func (s *Slack) buildLightReplyMessage(m slackMessage) msg.Message { - text := html.UnescapeString(m.Text) - - text = fixText(s.getUser, text) - - isCmd, text := bot.IsCmd(s.config, text) - - isAction := m.SubType == "me_message" - - u, _ := s.getUser(m.User) - if m.Username != "" { - u = m.Username - } - - tstamp := slackTStoTime(m.Ts) - - return msg.Message{ - User: &user.User{ - ID: m.User, - Name: u, - }, - Body: text, - Raw: m.Text, - Channel: m.Channel, - Command: isCmd, - Action: isAction, - Host: string(m.ID), - Time: tstamp, - AdditionalData: map[string]string{ - "RAW_SLACK_TIMESTAMP": m.Ts, - }, - } -} - -// markAllChannelsRead gets a list of all channels and marks each as read -func (s *Slack) markAllChannelsRead() { - chs := s.getAllChannels() - log.Printf("Got list of channels to mark read: %+v", chs) - for _, ch := range chs { - s.markChannelAsRead(ch.ID) - } - log.Printf("Finished marking channels read") -} - -// getAllChannels returns info for all channels joined -func (s *Slack) getAllChannels() []slackChannelListItem { - u := s.url + "channels.list" - resp, err := http.PostForm(u, - url.Values{"token": {s.token}}) - if err != nil { - log.Printf("Error posting user info request: %s", - err) - return nil - } - if resp.StatusCode != 200 { - log.Printf("Error posting user info request: %d", - resp.StatusCode) - return nil - } - defer resp.Body.Close() - var chanInfo slackChannelListResp - err = json.NewDecoder(resp.Body).Decode(&chanInfo) - if err != nil || !chanInfo.Ok { - log.Println("Error decoding response: ", err) - return nil - } - return chanInfo.Channels -} - -// markAsRead marks a channel read -func (s *Slack) markChannelAsRead(slackChanId string) error { - u := s.url + "channels.info" - resp, err := http.PostForm(u, - url.Values{"token": {s.token}, "channel": {slackChanId}}) - if err != nil { - log.Printf("Error posting user info request: %s", - err) - return err - } - if resp.StatusCode != 200 { - log.Printf("Error posting user info request: %d", - resp.StatusCode) - return err - } - defer resp.Body.Close() - var chanInfo slackChannelInfoResp - err = json.NewDecoder(resp.Body).Decode(&chanInfo) - log.Printf("%+v, %+v", err, chanInfo) - if err != nil || !chanInfo.Ok { - log.Println("Error decoding response: ", err) - return err - } - - u = s.url + "channels.mark" - resp, err = http.PostForm(u, - url.Values{"token": {s.token}, "channel": {slackChanId}, "ts": {chanInfo.Channel.Latest.Ts}}) - if err != nil { - log.Printf("Error posting user info request: %s", - err) - return err - } - if resp.StatusCode != 200 { - log.Printf("Error posting user info request: %d", - resp.StatusCode) - return err - } - defer resp.Body.Close() - var markInfo map[string]interface{} - err = json.NewDecoder(resp.Body).Decode(&markInfo) - log.Printf("%+v, %+v", err, markInfo) - if err != nil { - log.Println("Error decoding response: ", err) - return err - } - - log.Printf("Marked %s as read", slackChanId) - return nil -} - -func (s *Slack) connect() { - token := s.token - u := fmt.Sprintf("https://slack.com/api/rtm.connect?token=%s", token) - resp, err := http.Get(u) - if err != nil { - return - } - if resp.StatusCode != 200 { - log.Fatalf("Slack API failed. Code: %d", resp.StatusCode) - } - body, err := ioutil.ReadAll(resp.Body) - resp.Body.Close() - if err != nil { - log.Fatalf("Error reading Slack API body: %s", err) - } - var rtm rtmStart - err = json.Unmarshal(body, &rtm) - if err != nil { - return - } - - if !rtm.Ok { - log.Fatalf("Slack error: %s", rtm.Error) - } - - s.url = "https://slack.com/api/" - s.id = rtm.Self.ID - - // This is hitting the rate limit, and it may not be needed - //s.markAllChannelsRead() - - rtmURL, _ := url.Parse(rtm.URL) - s.ws, err = websocket.Dial(context.TODO(), rtmURL) - if err != nil { - log.Fatal(err) - } -} - -// Get username for Slack user ID -func (s *Slack) getUser(id string) (string, bool) { - if name, ok := s.users[id]; ok { - return name, true - } - - log.Printf("User %s not already found, requesting info", id) - u := s.url + "users.info" - resp, err := http.PostForm(u, - url.Values{"token": {s.token}, "user": {id}}) - if err != nil || resp.StatusCode != 200 { - log.Printf("Error posting user info request: %d %s", - resp.StatusCode, err) - return "UNKNOWN", false - } - defer resp.Body.Close() - var userInfo slackUserInfoResp - err = json.NewDecoder(resp.Body).Decode(&userInfo) - if err != nil { - log.Println("Error decoding response: ", err) - return "UNKNOWN", false - } - s.users[id] = userInfo.User.Name - return s.users[id], true -} - -// Who gets usernames out of a channel -func (s *Slack) Who(id string) []string { - log.Println("Who is queried for ", id) - u := s.url + "channels.info" - resp, err := http.PostForm(u, - url.Values{"token": {s.token}, "channel": {id}}) - if err != nil { - log.Printf("Error posting user info request: %s", - err) - return []string{} - } - if resp.StatusCode != 200 { - log.Printf("Error posting user info request: %d", - resp.StatusCode) - return []string{} - } - defer resp.Body.Close() - var chanInfo slackChannelInfoResp - err = json.NewDecoder(resp.Body).Decode(&chanInfo) - if err != nil || !chanInfo.Ok { - log.Println("Error decoding response: ", err) - return []string{} - } - - log.Printf("%#v", chanInfo.Channel) - - handles := []string{} - for _, member := range chanInfo.Channel.Members { - u, _ := s.getUser(member) - handles = append(handles, u) - } - log.Printf("Returning %d handles", len(handles)) - return handles -} From ec8ce42abb3e488c1925aff50c0bec16aacfc40f Mon Sep 17 00:00:00 2001 From: Robert Uhl Date: Wed, 20 Feb 2019 14:16:55 -0500 Subject: [PATCH 054/107] Add example text for time parse errors --- plugins/reminder/reminder.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/reminder/reminder.go b/plugins/reminder/reminder.go index 26078a8..715ba68 100644 --- a/plugins/reminder/reminder.go +++ b/plugins/reminder/reminder.go @@ -88,7 +88,7 @@ func (p *ReminderPlugin) message(kind bot.Kind, message msg.Message, args ...int dur, err := time.ParseDuration(parts[3]) if err != nil { - p.Bot.Send(bot.Message, channel, "Easy cowboy, not sure I can parse that duration.") + p.Bot.Send(bot.Message, channel, "Easy cowboy, not sure I can parse that duration. Try something like '1.5h' or '2h45m'.") return true } @@ -116,7 +116,7 @@ func (p *ReminderPlugin) message(kind bot.Kind, message msg.Message, args ...int //remind who every dur for dur2 blah dur2, err := time.ParseDuration(parts[5]) if err != nil { - p.Bot.Send(bot.Message, channel, "Easy cowboy, not sure I can parse that duration.") + p.Bot.Send(bot.Message, channel, "Easy cowboy, not sure I can parse that duration. Try something like '1.5h' or '2h45m'.") return true } From 44bf5b43a1cda0cb6d057953caa0f34c1b5e27fe Mon Sep 17 00:00:00 2001 From: Robert Uhl Date: Wed, 20 Feb 2019 14:17:45 -0500 Subject: [PATCH 055/107] Update Tests --- plugins/reminder/reminder_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/reminder/reminder_test.go b/plugins/reminder/reminder_test.go index 3618f16..457397d 100644 --- a/plugins/reminder/reminder_test.go +++ b/plugins/reminder/reminder_test.go @@ -77,7 +77,7 @@ func TestReminderParse(t *testing.T) { res := c.message(makeMessage("!remind testuser in unparseable don't fail this test")) assert.Len(t, mb.Messages, 1) assert.True(t, res) - assert.Contains(t, mb.Messages[0], "Easy cowboy, not sure I can parse that duration.") + assert.Contains(t, mb.Messages[0], "Easy cowboy, not sure I can parse that duration. Try something like '1.5h' or '2h45m'.") } func TestEmptyList(t *testing.T) { From eea4703129691c33a79128c9f785598ee17ca789 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Wed, 20 Feb 2019 16:47:41 -0500 Subject: [PATCH 056/107] talker: add cowsay --- plugins/talker/talker.go | 85 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/plugins/talker/talker.go b/plugins/talker/talker.go index 788161c..dba1e5a 100644 --- a/plugins/talker/talker.go +++ b/plugins/talker/talker.go @@ -4,10 +4,14 @@ package talker import ( "fmt" + "io/ioutil" + "os" + "os/exec" "strings" "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" + "github.com/velour/catbase/config" ) var goatse []string = []string{ @@ -40,12 +44,14 @@ var goatse []string = []string{ type TalkerPlugin struct { Bot bot.Bot + config *config.Config sayings []string } func New(b bot.Bot) *TalkerPlugin { tp := &TalkerPlugin{ - Bot: b, + Bot: b, + config: b.Config(), } b.Register(tp, bot.Message, tp.message) b.Register(tp, bot.Help, tp.help) @@ -57,6 +63,17 @@ func (p *TalkerPlugin) message(kind bot.Kind, message msg.Message, args ...inter body := message.Body lowermessage := strings.ToLower(body) + if message.Command && strings.HasPrefix(lowermessage, "cowsay") { + return p.cowSay(message) + } + + if message.Command && strings.HasPrefix(lowermessage, "list cows") { + cows := p.allCows() + m := fmt.Sprintf("Cows: %s", strings.Join(cows, ", ")) + p.Bot.Send(bot.Message, channel, m) + return true + } + // TODO: This ought to be space split afterwards to remove any punctuation if message.Command && strings.HasPrefix(lowermessage, "say") { msg := strings.TrimSpace(body[3:]) @@ -87,3 +104,69 @@ func (p *TalkerPlugin) help(kind bot.Kind, message msg.Message, args ...interfac p.Bot.Send(bot.Message, message.Channel, "Hi, this is talker. I like to talk about FredFelps!") return true } + +func (p *TalkerPlugin) cowSay(message msg.Message) bool { + fields := strings.Split(message.Body, " ") + text := strings.Join(fields[1:], " ") + cow := "default" + if len(fields) > 1 && p.hasCow(fields[1]) { + cow = fields[1] + text = strings.Join(fields[2:], " ") + } + cmd := exec.Command("cowsay", "-f", cow, text) + stdout, err := cmd.StdoutPipe() + if p.checkErr(err, message.Channel) { + return true + } + + if p.checkErr(cmd.Start(), message.Channel) { + return true + } + + output, err := ioutil.ReadAll(stdout) + if p.checkErr(err, message.Channel) { + return true + } + + p.Bot.Send(bot.Message, message.Channel, fmt.Sprintf("```%s```", output)) + + return true +} + +func (p *TalkerPlugin) checkErr(err error, ch string) bool { + if err != nil { + p.Bot.Send(bot.Message, ch, "Error running cowsay: %s", err) + return true + } + return false +} + +func (p *TalkerPlugin) hasCow(cow string) bool { + cows := p.allCows() + for _, c := range cows { + if strings.ToLower(cow) == c { + return true + } + } + return false +} + +func (p *TalkerPlugin) allCows() []string { + f, err := os.Open(p.config.Get("talker.cowpath", "/usr/local/share/cows")) + if err != nil { + return []string{"default"} + } + + files, err := f.Readdir(0) + if err != nil { + return []string{"default"} + } + + cows := []string{} + for _, f := range files { + if strings.HasSuffix(f.Name(), ".cow") { + cows = append(cows, strings.TrimSuffix(f.Name(), ".cow")) + } + } + return cows +} From ccd91af1a630bac70d5b9fff1afd635fb6336cc5 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Wed, 20 Feb 2019 19:28:20 -0500 Subject: [PATCH 057/107] readme: add travis icon --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 96d357f..bf5fede 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # CatBase +[![Build Status](https://travis-ci.com/velour/catbase.svg?branch=master)](https://travis-ci.com/velour/catbase) + CatBase is a bot that trolls our little corner of the IRC world and keeps our friends laughing from time to time. Sometimes he makes us angry too. He is crafted as a clone of XKCD's Bucket bot, which learns from things he's told and regurgitates his knowledge to the various channels that he lives in. I've found in many such projects that randomness can often make bots feel much more alive than they are, so CatBase is a big experiment in how great randomness is. ## Getting Help From 300de48c3931345f008545df7ab9f3af377ce0b4 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Thu, 21 Feb 2019 09:48:48 -0500 Subject: [PATCH 058/107] cowsay: velour's first slash command --- plugins/talker/talker.go | 59 ++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/plugins/talker/talker.go b/plugins/talker/talker.go index dba1e5a..92ef3ac 100644 --- a/plugins/talker/talker.go +++ b/plugins/talker/talker.go @@ -5,6 +5,8 @@ package talker import ( "fmt" "io/ioutil" + "log" + "net/http" "os" "os/exec" "strings" @@ -55,6 +57,7 @@ func New(b bot.Bot) *TalkerPlugin { } b.Register(tp, bot.Message, tp.message) b.Register(tp, bot.Help, tp.help) + tp.registerWeb() return tp } @@ -64,7 +67,13 @@ func (p *TalkerPlugin) message(kind bot.Kind, message msg.Message, args ...inter lowermessage := strings.ToLower(body) if message.Command && strings.HasPrefix(lowermessage, "cowsay") { - return p.cowSay(message) + msg, err := p.cowSay(strings.TrimPrefix(message.Body, "cowsay ")) + if err != nil { + p.Bot.Send(bot.Message, channel, "Error running cowsay: %s", err) + return true + } + p.Bot.Send(bot.Message, channel, msg) + return true } if message.Command && strings.HasPrefix(lowermessage, "list cows") { @@ -105,40 +114,29 @@ func (p *TalkerPlugin) help(kind bot.Kind, message msg.Message, args ...interfac return true } -func (p *TalkerPlugin) cowSay(message msg.Message) bool { - fields := strings.Split(message.Body, " ") - text := strings.Join(fields[1:], " ") +func (p *TalkerPlugin) cowSay(text string) (string, error) { + fields := strings.Split(text, " ") cow := "default" - if len(fields) > 1 && p.hasCow(fields[1]) { - cow = fields[1] - text = strings.Join(fields[2:], " ") + if len(fields) > 1 && p.hasCow(fields[0]) { + cow = fields[0] + text = strings.Join(fields[1:], " ") } cmd := exec.Command("cowsay", "-f", cow, text) stdout, err := cmd.StdoutPipe() - if p.checkErr(err, message.Channel) { - return true + if err != nil { + return "", err } - if p.checkErr(cmd.Start(), message.Channel) { - return true + if err = cmd.Start(); err != nil { + return "", err } output, err := ioutil.ReadAll(stdout) - if p.checkErr(err, message.Channel) { - return true - } - - p.Bot.Send(bot.Message, message.Channel, fmt.Sprintf("```%s```", output)) - - return true -} - -func (p *TalkerPlugin) checkErr(err error, ch string) bool { if err != nil { - p.Bot.Send(bot.Message, ch, "Error running cowsay: %s", err) - return true + return "", err } - return false + + return fmt.Sprintf("```%s```", output), nil } func (p *TalkerPlugin) hasCow(cow string) bool { @@ -170,3 +168,16 @@ func (p *TalkerPlugin) allCows() []string { } return cows } + +func (p *TalkerPlugin) registerWeb() { + http.HandleFunc("/slash/cowsay", func(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + log.Printf("Cowsay:\n%+v", r.PostForm.Get("text")) + msg, err := p.cowSay(r.PostForm.Get("text")) + if err != nil { + fmt.Fprintf(w, "Error running cowsay: %s", err) + return + } + fmt.Fprintf(w, "%s", msg) + }) +} From d4c8788a82b2614931ed4d10d23038b5af644881 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Thu, 21 Feb 2019 10:01:10 -0500 Subject: [PATCH 059/107] cowsay: make messages visible :( --- plugins/talker/talker.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/talker/talker.go b/plugins/talker/talker.go index 92ef3ac..38153d9 100644 --- a/plugins/talker/talker.go +++ b/plugins/talker/talker.go @@ -173,11 +173,14 @@ func (p *TalkerPlugin) registerWeb() { http.HandleFunc("/slash/cowsay", func(w http.ResponseWriter, r *http.Request) { r.ParseForm() log.Printf("Cowsay:\n%+v", r.PostForm.Get("text")) + channel := r.PostForm.Get("channel_id") + log.Printf("channel: %s", channel) msg, err := p.cowSay(r.PostForm.Get("text")) if err != nil { - fmt.Fprintf(w, "Error running cowsay: %s", err) + p.Bot.Send(bot.Message, channel, fmt.Sprintf("Error running cowsay: %s", err)) return } - fmt.Fprintf(w, "%s", msg) + p.Bot.Send(bot.Message, channel, msg) + w.WriteHeader(200) }) } From b1c450da08ce29f23cfd283fb068843350e9c622 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Thu, 21 Feb 2019 10:01:10 -0500 Subject: [PATCH 060/107] cowsay: make messages visible :( --- plugins/talker/talker.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/talker/talker.go b/plugins/talker/talker.go index 92ef3ac..38153d9 100644 --- a/plugins/talker/talker.go +++ b/plugins/talker/talker.go @@ -173,11 +173,14 @@ func (p *TalkerPlugin) registerWeb() { http.HandleFunc("/slash/cowsay", func(w http.ResponseWriter, r *http.Request) { r.ParseForm() log.Printf("Cowsay:\n%+v", r.PostForm.Get("text")) + channel := r.PostForm.Get("channel_id") + log.Printf("channel: %s", channel) msg, err := p.cowSay(r.PostForm.Get("text")) if err != nil { - fmt.Fprintf(w, "Error running cowsay: %s", err) + p.Bot.Send(bot.Message, channel, fmt.Sprintf("Error running cowsay: %s", err)) return } - fmt.Fprintf(w, "%s", msg) + p.Bot.Send(bot.Message, channel, msg) + w.WriteHeader(200) }) } From a2d5d173f991ce1b8ce0fa9262d4830c6d489eab Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Thu, 7 Mar 2019 11:35:42 -0500 Subject: [PATCH 061/107] logging: switch to a logging library --- bot/bot.go | 9 +- bot/handlers.go | 12 +-- bot/mock.go | 6 +- config/config.go | 10 +-- config/defaults.go | 7 +- connectors/irc/irc.go | 12 +-- connectors/slack/slack.go | 100 +++++++++++----------- connectors/slackapp/slackApp.go | 73 +++++++++++----- go.mod | 1 + go.sum | 2 + main.go | 33 +++++--- plugins/admin/admin.go | 13 +-- plugins/babbler/babbler.go | 144 ++++++++++++++++---------------- plugins/beers/beers.go | 50 ++++++----- plugins/counter/counter.go | 92 +++++++++++++++----- plugins/db/db.go | 5 +- plugins/emojifyme/emojifyme.go | 9 +- plugins/fact/factoid.go | 57 ++++++++----- plugins/first/first.go | 56 +++++++++---- plugins/inventory/inventory.go | 39 ++++----- plugins/remember/remember.go | 17 ++-- plugins/reminder/reminder.go | 29 +++---- plugins/sisyphus/sisyphus.go | 5 +- plugins/talker/talker.go | 7 +- plugins/twitch/twitch.go | 26 +++--- plugins/zork/zork.go | 13 +-- util/emojy/main.go | 23 ++--- util/files/main.go | 35 +++++--- 28 files changed, 526 insertions(+), 359 deletions(-) diff --git a/bot/bot.go b/bot/bot.go index b794fcc..8ba3029 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -4,12 +4,12 @@ package bot import ( "html/template" - "log" "net/http" "reflect" "strings" "github.com/jmoiron/sqlx" + "github.com/rs/zerolog/log" "github.com/velour/catbase/bot/msg" "github.com/velour/catbase/bot/msglog" "github.com/velour/catbase/bot/user" @@ -105,7 +105,7 @@ func (b *bot) migrateDB() { name string, value string );`); err != nil { - log.Fatal("Initial DB migration create variables table: ", err) + log.Fatal().Err(err).Msgf("Initial DB migration create variables table") } } @@ -160,7 +160,7 @@ func (b *bot) serveRoot(w http.ResponseWriter, r *http.Request) { context["EndPoints"] = b.httpEndPoints t, err := template.New("rootIndex").Parse(rootIndex) if err != nil { - log.Println(err) + log.Error().Err(err) } t.Execute(w, context) } @@ -170,7 +170,8 @@ func IsCmd(c *config.Config, message string) (bool, string) { cmdcs := c.GetArray("CommandChar", []string{"!"}) botnick := strings.ToLower(c.Get("Nick", "bot")) if botnick == "" { - log.Fatalf(`You must run catbase -set nick -val `) + log.Fatal(). + Msgf(`You must run catbase -set nick -val `) } iscmd := false lowerMessage := strings.ToLower(message) diff --git a/bot/handlers.go b/bot/handlers.go index 75eff1b..5dfe12c 100644 --- a/bot/handlers.go +++ b/bot/handlers.go @@ -6,7 +6,6 @@ import ( "database/sql" "errors" "fmt" - "log" "math/rand" "reflect" "regexp" @@ -14,18 +13,21 @@ import ( "strings" "time" + "github.com/rs/zerolog/log" "github.com/velour/catbase/bot/msg" ) func (b *bot) Receive(kind Kind, msg msg.Message, args ...interface{}) bool { - log.Println("Received event: ", msg) + log.Debug(). + Interface("msg", msg). + Msg("Received event") // msg := b.buildMessage(client, inMsg) // do need to look up user and fix it if kind == Message && strings.HasPrefix(msg.Body, "help") && msg.Command { parts := strings.Fields(strings.ToLower(msg.Body)) b.checkHelp(msg.Channel, parts) - log.Println("Handled a help, returning") + log.Debug().Msg("Handled a help, returning") goto RET } @@ -171,7 +173,7 @@ func (b *bot) getVar(varName string) (string, error) { case err == sql.ErrNoRows: return "", fmt.Errorf("No factoid found") case err != nil: - log.Fatal("getVar error: ", err) + log.Fatal().Err(err).Msg("getVar error") } return text, nil } @@ -180,7 +182,7 @@ func (b *bot) listVars(channel string, parts []string) { var variables []string err := b.DB().Select(&variables, `select name from variables group by name`) if err != nil { - log.Fatal(err) + log.Fatal().Err(err) } msg := "I know: $who, $someone, $digit, $nonzero" if len(variables) > 0 { diff --git a/bot/mock.go b/bot/mock.go index f9f2917..6f11c12 100644 --- a/bot/mock.go +++ b/bot/mock.go @@ -4,12 +4,12 @@ package bot import ( "fmt" - "log" "net/http" "strconv" "strings" "github.com/jmoiron/sqlx" + "github.com/rs/zerolog/log" "github.com/stretchr/testify/mock" "github.com/velour/catbase/bot/msg" "github.com/velour/catbase/bot/user" @@ -64,14 +64,14 @@ func (mb *MockBot) edit(channel, newMessage, identifier string) (string, error) isMessage := identifier[0] == 'm' if !isMessage && identifier[0] != 'a' { err := fmt.Errorf("failed to parse identifier: %s", identifier) - log.Println(err) + log.Error().Err(err) return "", err } index, err := strconv.Atoi(strings.Split(identifier, "-")[1]) if err != nil { err := fmt.Errorf("failed to parse identifier: %s", identifier) - log.Println(err) + log.Error().Err(err) return "", err } diff --git a/config/config.go b/config/config.go index 827b310..3431a8f 100644 --- a/config/config.go +++ b/config/config.go @@ -5,7 +5,6 @@ package config import ( "database/sql" "fmt" - "log" "os" "regexp" "strconv" @@ -13,6 +12,7 @@ import ( "github.com/jmoiron/sqlx" sqlite3 "github.com/mattn/go-sqlite3" + "github.com/rs/zerolog/log" ) // Config stores any system-wide startup information that cannot be easily configured via @@ -74,7 +74,7 @@ func (c *Config) GetString(key, fallback string) string { q := `select value from config where key=?` err := c.DB.Get(&configValue, q, key) if err != nil { - log.Printf("WARN: Key %s is empty", key) + log.Debug().Msgf("WARN: Key %s is empty", key) return fallback } return configValue @@ -137,11 +137,11 @@ func ReadConfig(dbpath string) *Config { if dbpath == "" { dbpath = "catbase.db" } - fmt.Printf("Using %s as database file.\n", dbpath) + log.Info().Msgf("Using %s as database file.\n", dbpath) sqlDB, err := sqlx.Open("sqlite3_custom", dbpath) if err != nil { - log.Fatal(err) + log.Fatal().Err(err) } c := Config{ DBFile: dbpath, @@ -156,7 +156,7 @@ func ReadConfig(dbpath string) *Config { panic(err) } - fmt.Printf("catbase is running.\n") + log.Info().Msgf("catbase is running.") return &c } diff --git a/config/defaults.go b/config/defaults.go index f94d6b3..26626ba 100644 --- a/config/defaults.go +++ b/config/defaults.go @@ -2,9 +2,10 @@ package config import ( "bytes" - "log" "strings" "text/template" + + "github.com/rs/zerolog/log" ) var q = ` @@ -17,7 +18,7 @@ INSERT INTO config VALUES('init',1); func (c *Config) SetDefaults(mainChannel, nick string) { if nick == mainChannel && nick == "" { - log.Fatalf("You must provide a nick and a mainChannel") + log.Fatal().Msgf("You must provide a nick and a mainChannel") } t := template.Must(template.New("query").Parse(q)) vals := struct { @@ -33,5 +34,5 @@ func (c *Config) SetDefaults(mainChannel, nick string) { t.Execute(&buf, vals) c.MustExec(`delete from config;`) c.MustExec(buf.String()) - log.Println("Configuration initialized.") + log.Info().Msgf("Configuration initialized.") } diff --git a/connectors/irc/irc.go b/connectors/irc/irc.go index a10deea..b9f1b24 100644 --- a/connectors/irc/irc.go +++ b/connectors/irc/irc.go @@ -5,11 +5,11 @@ package irc import ( "fmt" "io" - "log" "os" "strings" "time" + "github.com/rs/zerolog/log" "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" "github.com/velour/catbase/bot/user" @@ -69,7 +69,7 @@ func (i *Irc) Send(kind bot.Kind, args ...interface{}) (string, error) { } func (i *Irc) JoinChannel(channel string) { - log.Printf("Joining channel: %s", channel) + log.Info().Msgf("Joining channel: %s", channel) i.Client.Out <- irc.Msg{Cmd: irc.JOIN, Args: []string{channel}} } @@ -147,7 +147,7 @@ func (i *Irc) handleConnection() { close(i.Client.Out) for err := range i.Client.Errors { if err != io.EOF { - log.Println(err) + log.Error().Err(err) } } }() @@ -169,7 +169,7 @@ func (i *Irc) handleConnection() { case err, ok := <-i.Client.Errors: if ok && err != io.EOF { - log.Println(err) + log.Error().Err(err) i.quit <- true return } @@ -183,7 +183,7 @@ func (i *Irc) handleMsg(msg irc.Msg) { switch msg.Cmd { case irc.ERROR: - log.Println(1, "Received error: "+msg.Raw) + log.Info().Msgf("Received error: " + msg.Raw) case irc.PING: i.Client.Out <- irc.Msg{Cmd: irc.PONG} @@ -241,7 +241,7 @@ func (i *Irc) handleMsg(msg irc.Msg) { default: cmd := irc.CmdNames[msg.Cmd] - log.Println("(" + cmd + ") " + msg.Raw) + log.Debug().Msgf("(%s) %s", cmd, msg.Raw) } } diff --git a/connectors/slack/slack.go b/connectors/slack/slack.go index 6fe4691..16076a9 100644 --- a/connectors/slack/slack.go +++ b/connectors/slack/slack.go @@ -10,7 +10,6 @@ import ( "html" "io" "io/ioutil" - "log" "net/http" "net/url" "regexp" @@ -21,6 +20,7 @@ import ( "context" "time" + "github.com/rs/zerolog/log" "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" "github.com/velour/catbase/bot/user" @@ -164,7 +164,7 @@ type rtmStart struct { func New(c *config.Config) *Slack { token := c.Get("slack.token", "NONE") if token == "NONE" { - log.Fatalf("No slack token found. Set SLACKTOKEN env.") + log.Fatal().Msgf("No slack token found. Set SLACKTOKEN env.") } return &Slack{ config: c, @@ -242,16 +242,16 @@ func (s *Slack) sendMessageType(channel, message string, meMessage bool) (string }) if err != nil { - log.Printf("Error sending Slack message: %s", err) + log.Error().Err(err).Msgf("Error sending Slack message") } body, err := ioutil.ReadAll(resp.Body) resp.Body.Close() if err != nil { - log.Fatalf("Error reading Slack API body: %s", err) + log.Fatal().Err(err).Msgf("Error reading Slack API body") } - log.Println(string(body)) + log.Debug().Msgf("%+v", body) type MessageResponse struct { OK bool `json:"ok"` @@ -264,7 +264,7 @@ func (s *Slack) sendMessageType(channel, message string, meMessage bool) (string var mr MessageResponse err = json.Unmarshal(body, &mr) if err != nil { - log.Fatalf("Error parsing message response: %s", err) + log.Fatal().Err(err).Msgf("Error parsing message response") } if !mr.OK { @@ -277,13 +277,13 @@ func (s *Slack) sendMessageType(channel, message string, meMessage bool) (string } func (s *Slack) sendMessage(channel, message string) (string, error) { - log.Printf("Sending message to %s: %s", channel, message) + log.Debug().Msgf("Sending message to %s: %s", channel, message) identifier, err := s.sendMessageType(channel, message, false) return identifier, err } func (s *Slack) sendAction(channel, message string) (string, error) { - log.Printf("Sending action to %s: %s", channel, message) + log.Debug().Msgf("Sending action to %s: %s", channel, message) identifier, err := s.sendMessageType(channel, "_"+message+"_", true) return identifier, err } @@ -313,7 +313,7 @@ func (s *Slack) replyToMessageIdentifier(channel, message, identifier string) (s return "", err } - log.Println(string(body)) + log.Debug().Msgf("%s", body) type MessageResponse struct { OK bool `json:"ok"` @@ -339,7 +339,7 @@ func (s *Slack) replyToMessage(channel, message string, replyTo msg.Message) (st } func (s *Slack) react(channel, reaction string, message msg.Message) (string, error) { - log.Printf("Reacting in %s: %s", channel, reaction) + log.Debug().Msgf("Reacting in %s: %s", channel, reaction) resp, err := http.PostForm("https://slack.com/api/reactions.add", url.Values{"token": {s.token}, "name": {reaction}, @@ -353,7 +353,7 @@ func (s *Slack) react(channel, reaction string, message msg.Message) (string, er } func (s *Slack) edit(channel, newMessage, identifier string) (string, error) { - log.Printf("Editing in (%s) %s: %s", identifier, channel, newMessage) + log.Debug().Msgf("Editing in (%s) %s: %s", identifier, channel, newMessage) resp, err := http.PostForm("https://slack.com/api/chat.update", url.Values{"token": {s.token}, "channel": {channel}, @@ -374,14 +374,14 @@ func (s *Slack) populateEmojiList() { resp, err := http.PostForm("https://slack.com/api/emoji.list", url.Values{"token": {s.token}}) if err != nil { - log.Printf("Error retrieving emoji list from Slack: %s", err) + log.Debug().Msgf("Error retrieving emoji list from Slack: %s", err) return } body, err := ioutil.ReadAll(resp.Body) resp.Body.Close() if err != nil { - log.Fatalf("Error reading Slack API body: %s", err) + log.Fatal().Err(err).Msgf("Error reading Slack API body") } type EmojiListResponse struct { @@ -392,7 +392,7 @@ func (s *Slack) populateEmojiList() { var list EmojiListResponse err = json.Unmarshal(body, &list) if err != nil { - log.Fatalf("Error parsing emoji list: %s", err) + log.Fatal().Err(err).Msgf("Error parsing emoji list") } s.emoji = list.Emoji } @@ -417,7 +417,7 @@ func (s *Slack) receiveMessage() (slackMessage, error) { m := slackMessage{} err := s.ws.Recv(context.TODO(), &m) if err != nil { - log.Println("Error decoding WS message") + log.Error().Msgf("Error decoding WS message") panic(fmt.Errorf("%v\n%v", m, err)) } return m, nil @@ -442,7 +442,7 @@ func (s *Slack) Serve() error { for { msg, err := s.receiveMessage() if err != nil && err == io.EOF { - log.Fatalf("Slack API EOF") + log.Fatal().Msg("Slack API EOF") } else if err != nil { return fmt.Errorf("Slack API error: %s", err) } @@ -452,7 +452,7 @@ func (s *Slack) Serve() error { if !isItMe && !msg.Hidden && msg.ThreadTs == "" { m := s.buildMessage(msg) if m.Time.Before(s.lastRecieved) { - log.Printf("Ignoring message: %+v\nlastRecieved: %v msg: %v", msg.ID, s.lastRecieved, m.Time) + log.Debug().Msgf("Ignoring message: %+v\nlastRecieved: %v msg: %v", msg.ID, s.lastRecieved, m.Time) } else { s.lastRecieved = m.Time s.event(bot.Message, m) @@ -461,10 +461,10 @@ func (s *Slack) Serve() error { //we're throwing away some information here by not parsing the correct reply object type, but that's okay s.event(bot.Reply, s.buildLightReplyMessage(msg), msg.ThreadTs) } else { - log.Printf("THAT MESSAGE WAS HIDDEN: %+v", msg.ID) + log.Debug().Msgf("THAT MESSAGE WAS HIDDEN: %+v", msg.ID) } case "error": - log.Printf("Slack error, code: %d, message: %s", msg.Error.Code, msg.Error.Msg) + log.Error().Msgf("Slack error, code: %d, message: %s", msg.Error.Code, msg.Error.Msg) case "": // what even is this? case "hello": case "presence_change": @@ -475,7 +475,7 @@ func (s *Slack) Serve() error { // squeltch this stuff continue default: - log.Printf("Unhandled Slack message type: '%s'", msg.Type) + log.Debug().Msgf("Unhandled Slack message type: '%s'", msg.Type) } } } @@ -554,11 +554,11 @@ func (s *Slack) buildLightReplyMessage(m slackMessage) msg.Message { // markAllChannelsRead gets a list of all channels and marks each as read func (s *Slack) markAllChannelsRead() { chs := s.getAllChannels() - log.Printf("Got list of channels to mark read: %+v", chs) + log.Debug().Msgf("Got list of channels to mark read: %+v", chs) for _, ch := range chs { s.markChannelAsRead(ch.ID) } - log.Printf("Finished marking channels read") + log.Debug().Msgf("Finished marking channels read") } // getAllChannels returns info for all channels joined @@ -567,12 +567,11 @@ func (s *Slack) getAllChannels() []slackChannelListItem { resp, err := http.PostForm(u, url.Values{"token": {s.token}}) if err != nil { - log.Printf("Error posting user info request: %s", - err) + log.Error().Err(err).Msgf("Error posting user info request") return nil } if resp.StatusCode != 200 { - log.Printf("Error posting user info request: %d", + log.Error().Msgf("Error posting user info request: %d", resp.StatusCode) return nil } @@ -580,7 +579,7 @@ func (s *Slack) getAllChannels() []slackChannelListItem { var chanInfo slackChannelListResp err = json.NewDecoder(resp.Body).Decode(&chanInfo) if err != nil || !chanInfo.Ok { - log.Println("Error decoding response: ", err) + log.Error().Err(err).Msgf("Error decoding response") return nil } return chanInfo.Channels @@ -592,21 +591,19 @@ func (s *Slack) markChannelAsRead(slackChanId string) error { resp, err := http.PostForm(u, url.Values{"token": {s.token}, "channel": {slackChanId}}) if err != nil { - log.Printf("Error posting user info request: %s", - err) + log.Error().Err(err).Msgf("Error posting user info request") return err } if resp.StatusCode != 200 { - log.Printf("Error posting user info request: %d", + log.Error().Msgf("Error posting user info request: %d", resp.StatusCode) return err } defer resp.Body.Close() var chanInfo slackChannelInfoResp err = json.NewDecoder(resp.Body).Decode(&chanInfo) - log.Printf("%+v, %+v", err, chanInfo) if err != nil || !chanInfo.Ok { - log.Println("Error decoding response: ", err) + log.Error().Err(err).Msgf("Error decoding response") return err } @@ -614,25 +611,23 @@ func (s *Slack) markChannelAsRead(slackChanId string) error { resp, err = http.PostForm(u, url.Values{"token": {s.token}, "channel": {slackChanId}, "ts": {chanInfo.Channel.Latest.Ts}}) if err != nil { - log.Printf("Error posting user info request: %s", - err) + log.Error().Err(err).Msgf("Error posting user info request") return err } if resp.StatusCode != 200 { - log.Printf("Error posting user info request: %d", + log.Error().Msgf("Error posting user info request: %d", resp.StatusCode) return err } defer resp.Body.Close() var markInfo map[string]interface{} err = json.NewDecoder(resp.Body).Decode(&markInfo) - log.Printf("%+v, %+v", err, markInfo) if err != nil { - log.Println("Error decoding response: ", err) + log.Error().Err(err).Msgf("Error decoding response") return err } - log.Printf("Marked %s as read", slackChanId) + log.Info().Msgf("Marked %s as read", slackChanId) return nil } @@ -644,12 +639,12 @@ func (s *Slack) connect() { return } if resp.StatusCode != 200 { - log.Fatalf("Slack API failed. Code: %d", resp.StatusCode) + log.Fatal().Msgf("Slack API failed. Code: %d", resp.StatusCode) } body, err := ioutil.ReadAll(resp.Body) resp.Body.Close() if err != nil { - log.Fatalf("Error reading Slack API body: %s", err) + log.Fatal().Err(err).Msg("Error reading Slack API body") } var rtm rtmStart err = json.Unmarshal(body, &rtm) @@ -658,7 +653,7 @@ func (s *Slack) connect() { } if !rtm.Ok { - log.Fatalf("Slack error: %s", rtm.Error) + log.Fatal().Msgf("Slack error: %s", rtm.Error) } s.url = "https://slack.com/api/" @@ -670,7 +665,7 @@ func (s *Slack) connect() { rtmURL, _ := url.Parse(rtm.URL) s.ws, err = websocket.Dial(context.TODO(), rtmURL) if err != nil { - log.Fatal(err) + log.Fatal().Err(err) } } @@ -680,20 +675,20 @@ func (s *Slack) getUser(id string) (string, bool) { return name, true } - log.Printf("User %s not already found, requesting info", id) + log.Debug().Msgf("User %s not already found, requesting info", id) u := s.url + "users.info" resp, err := http.PostForm(u, url.Values{"token": {s.token}, "user": {id}}) if err != nil || resp.StatusCode != 200 { - log.Printf("Error posting user info request: %d %s", - resp.StatusCode, err) + log.Error().Err(err).Msgf("Error posting user info request: %d", + resp.StatusCode) return "UNKNOWN", false } defer resp.Body.Close() var userInfo slackUserInfoResp err = json.NewDecoder(resp.Body).Decode(&userInfo) if err != nil { - log.Println("Error decoding response: ", err) + log.Error().Err(err).Msgf("Error decoding response") return "UNKNOWN", false } s.users[id] = userInfo.User.Name @@ -702,17 +697,18 @@ func (s *Slack) getUser(id string) (string, bool) { // Who gets usernames out of a channel func (s *Slack) Who(id string) []string { - log.Println("Who is queried for ", id) + log.Debug(). + Str("id", id). + Msg("Who is queried for ") u := s.url + "channels.info" resp, err := http.PostForm(u, url.Values{"token": {s.token}, "channel": {id}}) if err != nil { - log.Printf("Error posting user info request: %s", - err) + log.Error().Err(err).Msgf("Error posting user info request") return []string{} } if resp.StatusCode != 200 { - log.Printf("Error posting user info request: %d", + log.Error().Msgf("Error posting user info request: %d", resp.StatusCode) return []string{} } @@ -720,17 +716,17 @@ func (s *Slack) Who(id string) []string { var chanInfo slackChannelInfoResp err = json.NewDecoder(resp.Body).Decode(&chanInfo) if err != nil || !chanInfo.Ok { - log.Println("Error decoding response: ", err) + log.Error().Err(err).Msgf("Error decoding response") return []string{} } - log.Printf("%#v", chanInfo.Channel) + log.Debug().Msgf("%#v", chanInfo.Channel) handles := []string{} for _, member := range chanInfo.Channel.Members { u, _ := s.getUser(member) handles = append(handles, u) } - log.Printf("Returning %d handles", len(handles)) + log.Debug().Msgf("Returning %d handles", len(handles)) return handles } diff --git a/connectors/slackapp/slackApp.go b/connectors/slackapp/slackApp.go index 45d3b0d..4a264ed 100644 --- a/connectors/slackapp/slackApp.go +++ b/connectors/slackapp/slackApp.go @@ -7,7 +7,6 @@ import ( "fmt" "html" "io/ioutil" - "log" "net/http" "net/url" "regexp" @@ -15,6 +14,8 @@ import ( "strings" "time" + "github.com/rs/zerolog/log" + "github.com/nlopes/slack" "github.com/nlopes/slack/slackevents" @@ -50,7 +51,7 @@ type SlackApp struct { func New(c *config.Config) *SlackApp { token := c.Get("slack.token", "NONE") if token == "NONE" { - log.Fatalf("No slack token found. Set SLACKTOKEN env.") + log.Fatal().Msg("No slack token found. Set SLACKTOKEN env.") } api := slack.New(token, slack.OptionDebug(false)) @@ -88,7 +89,7 @@ func (s *SlackApp) Serve() error { body := buf.String() eventsAPIEvent, e := slackevents.ParseEvent(json.RawMessage(body), slackevents.OptionVerifyToken(&slackevents.TokenComparator{VerificationToken: s.verification})) if e != nil { - log.Println(e) + log.Error().Err(e) w.WriteHeader(http.StatusInternalServerError) } @@ -96,7 +97,7 @@ func (s *SlackApp) Serve() error { var r *slackevents.ChallengeResponse err := json.Unmarshal([]byte(body), &r) if err != nil { - log.Println(err) + log.Error().Err(err) w.WriteHeader(http.StatusInternalServerError) } w.Header().Set("Content-Type", "text") @@ -112,7 +113,10 @@ func (s *SlackApp) Serve() error { s.msgReceivd(ev) } } else { - log.Printf("Event: (%v): %+v", eventsAPIEvent.Type, eventsAPIEvent) + log.Debug(). + Str("type", eventsAPIEvent.Type). + Interface("event", eventsAPIEvent). + Msg("event") } }) return nil @@ -137,14 +141,19 @@ func (s *SlackApp) checkRingOrAdd(ts string) bool { func (s *SlackApp) msgReceivd(msg *slackevents.MessageEvent) { if s.checkRingOrAdd(msg.TimeStamp) { - log.Printf("Got a duplicate message from server: %s", msg.TimeStamp) + log.Debug(). + Str("ts", msg.TimeStamp). + Msg("Got a duplicate message from server") return } isItMe := msg.BotID != "" && msg.BotID == s.myBotID if !isItMe && msg.ThreadTimeStamp == "" { m := s.buildMessage(msg) if m.Time.Before(s.lastRecieved) { - log.Printf("Ignoring message: lastRecieved: %v msg: %v", s.lastRecieved, m.Time) + log.Debug(). + Time("ts", m.Time). + Interface("lastRecv", s.lastRecieved). + Msg("Ignoring message") } else { s.lastRecieved = m.Time s.event(bot.Message, m) @@ -153,7 +162,9 @@ func (s *SlackApp) msgReceivd(msg *slackevents.MessageEvent) { //we're throwing away some information here by not parsing the correct reply object type, but that's okay s.event(bot.Reply, s.buildMessage(msg), msg.ThreadTimeStamp) } else { - log.Printf("THAT MESSAGE WAS HIDDEN: %+v", msg.Text) + log.Debug(). + Str("text", msg.Text). + Msg("THAT MESSAGE WAS HIDDEN") } } @@ -197,7 +208,7 @@ func (s *SlackApp) sendMessageType(channel, message string, meMessage bool) (str } if err != nil { - log.Printf("Error sending message: %+v", err) + log.Error().Err(err).Msg("Error sending message") return "", err } @@ -205,13 +216,19 @@ func (s *SlackApp) sendMessageType(channel, message string, meMessage bool) (str } func (s *SlackApp) sendMessage(channel, message string) (string, error) { - log.Printf("Sending message to %s: %s", channel, message) + log.Debug(). + Str("channel", channel). + Str("message", message). + Msg("Sending message") identifier, err := s.sendMessageType(channel, message, false) return identifier, err } func (s *SlackApp) sendAction(channel, message string) (string, error) { - log.Printf("Sending action to %s: %s", channel, message) + log.Debug(). + Str("channel", channel). + Str("message", message). + Msg("Sending action") identifier, err := s.sendMessageType(channel, "_"+message+"_", true) return identifier, err } @@ -241,7 +258,7 @@ func (s *SlackApp) replyToMessageIdentifier(channel, message, identifier string) return "", err } - log.Println(string(body)) + log.Debug().Bytes("body", body) type MessageResponse struct { OK bool `json:"ok"` @@ -267,7 +284,10 @@ func (s *SlackApp) replyToMessage(channel, message string, replyTo msg.Message) } func (s *SlackApp) react(channel, reaction string, message msg.Message) (string, error) { - log.Printf("Reacting in %s: %s", channel, reaction) + log.Debug(). + Str("channel", channel). + Str("reaction", reaction). + Msg("reacting") ref := slack.ItemRef{ Channel: channel, Timestamp: message.AdditionalData["RAW_SLACK_TIMESTAMP"], @@ -277,7 +297,11 @@ func (s *SlackApp) react(channel, reaction string, message msg.Message) (string, } func (s *SlackApp) edit(channel, newMessage, identifier string) (string, error) { - log.Printf("Editing in (%s) %s: %s", identifier, channel, newMessage) + log.Debug(). + Str("channel", channel). + Str("identifier", identifier). + Str("newMessage", newMessage). + Msg("editing") nick := s.config.Get("Nick", "bot") _, ts, err := s.api.PostMessage(channel, slack.MsgOptionUsername(nick), @@ -293,14 +317,14 @@ func (s *SlackApp) GetEmojiList() map[string]string { func (s *SlackApp) populateEmojiList() { if s.userToken == "NONE" { - log.Println("Cannot get emoji list without slack.usertoken") + log.Error().Msg("Cannot get emoji list without slack.usertoken") return } api := slack.New(s.userToken, slack.OptionDebug(false)) em, err := api.GetEmoji() if err != nil { - log.Printf("Error retrieving emoji list from Slack: %s", err) + log.Error().Err(err).Msg("Error retrieving emoji list from Slack") return } @@ -357,7 +381,9 @@ func (s *SlackApp) getUser(id string) (string, error) { return name, nil } - log.Printf("User %s not already found, requesting info", id) + log.Debug(). + Str("id", id). + Msg("User not already found, requesting info") u, err := s.api.GetUserInfo(id) if err != nil { return "UNKNOWN", err @@ -369,12 +395,14 @@ func (s *SlackApp) getUser(id string) (string, error) { // Who gets usernames out of a channel func (s *SlackApp) Who(id string) []string { if s.userToken == "NONE" { - log.Println("Cannot get emoji list without slack.usertoken") + log.Error().Msg("Cannot get emoji list without slack.usertoken") return []string{s.config.Get("nick", "bot")} } api := slack.New(s.userToken, slack.OptionDebug(false)) - log.Println("Who is queried for ", id) + log.Debug(). + Str("id", id). + Msg("Who is queried") // Not super sure this is the correct call params := &slack.GetUsersInConversationParameters{ ChannelID: id, @@ -382,7 +410,7 @@ func (s *SlackApp) Who(id string) []string { } members, _, err := api.GetUsersInConversation(params) if err != nil { - log.Println(err) + log.Error().Err(err) return []string{s.config.Get("nick", "bot")} } @@ -390,7 +418,10 @@ func (s *SlackApp) Who(id string) []string { for _, m := range members { u, err := s.getUser(m) if err != nil { - log.Printf("Couldn't get user %s: %s", m, err) + log.Error(). + Err(err). + Str("user", m). + Msg("Couldn't get user") continue } ret = append(ret, u) diff --git a/go.mod b/go.mod index d05e371..5e3af06 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/nlopes/slack v0.5.0 github.com/pkg/errors v0.8.1 // indirect github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d // indirect + github.com/rs/zerolog v1.12.0 github.com/stretchr/objx v0.1.1 // indirect github.com/stretchr/testify v1.3.0 github.com/velour/chat v0.0.0-20180713122344-fd1d1606cb89 diff --git a/go.sum b/go.sum index e4ec4ff..5ee01c2 100644 --- a/go.sum +++ b/go.sum @@ -32,6 +32,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d h1:1VUlQbCfkoSGv7qP7Y+ro3ap1P1pPZxgdGVqiTVy5C4= github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY= +github.com/rs/zerolog v1.12.0 h1:aqZ1XRadoS8IBknR5IDFvGzbHly1X9ApIqOroooQF/c= +github.com/rs/zerolog v1.12.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/main.go b/main.go index aa5c352..521a459 100644 --- a/main.go +++ b/main.go @@ -4,11 +4,14 @@ package main import ( "flag" - "log" "math/rand" "net/http" + "os" "time" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/velour/catbase/bot" "github.com/velour/catbase/config" "github.com/velour/catbase/connectors/irc" @@ -19,7 +22,6 @@ import ( "github.com/velour/catbase/plugins/beers" "github.com/velour/catbase/plugins/couldashouldawoulda" "github.com/velour/catbase/plugins/counter" - "github.com/velour/catbase/plugins/db" "github.com/velour/catbase/plugins/dice" "github.com/velour/catbase/plugins/emojifyme" "github.com/velour/catbase/plugins/fact" @@ -42,9 +44,11 @@ import ( ) var ( - key = flag.String("set", "", "Configuration key to set") - val = flag.String("val", "", "Configuration value to set") - initDB = flag.Bool("init", false, "Initialize the configuration DB") + key = flag.String("set", "", "Configuration key to set") + val = flag.String("val", "", "Configuration value to set") + initDB = flag.Bool("init", false, "Initialize the configuration DB") + prettyLog = flag.Bool("pretty", false, "Use pretty console logger") + debug = flag.Bool("debug", false, "Turn on debug logging") ) func main() { @@ -54,15 +58,23 @@ func main() { "Database file to load. (Defaults to catbase.db)") flag.Parse() // parses the logging flags. + if *prettyLog { + log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) + } + zerolog.SetGlobalLevel(zerolog.InfoLevel) + if *debug { + zerolog.SetGlobalLevel(zerolog.DebugLevel) + } + c := config.ReadConfig(*dbpath) if *key != "" && *val != "" { c.Set(*key, *val) - log.Printf("Set config %s: %s", *key, *val) + log.Info().Msgf("Set config %s: %s", *key, *val) return } if (*initDB && len(flag.Args()) != 2) || (!*initDB && c.GetInt("init", 0) != 1) { - log.Fatal(`You must run "catbase -init "`) + log.Fatal().Msgf(`You must run "catbase -init "`) } else if *initDB { c.SetDefaults(flag.Arg(0), flag.Arg(1)) return @@ -78,7 +90,7 @@ func main() { case "slackapp": client = slackapp.New(c) default: - log.Fatalf("Unknown connection type: %s", c.Get("type", "UNSET")) + log.Fatal().Msgf("Unknown connection type: %s", c.Get("type", "UNSET")) } b := bot.New(c, client) @@ -108,12 +120,11 @@ func main() { b.AddPlugin(nerdepedia.New(b)) // catches anything left, will always return true b.AddPlugin(fact.New(b)) - b.AddPlugin(db.New(b)) if err := client.Serve(); err != nil { - log.Fatal(err) + log.Fatal().Err(err) } addr := c.Get("HttpAddr", "127.0.0.1:1337") - log.Fatal(http.ListenAndServe(addr, nil)) + log.Fatal().Err(http.ListenAndServe(addr, nil)) } diff --git a/plugins/admin/admin.go b/plugins/admin/admin.go index 98b439f..f7a38a2 100644 --- a/plugins/admin/admin.go +++ b/plugins/admin/admin.go @@ -4,10 +4,11 @@ package admin import ( "fmt" - "log" "strings" "time" + "github.com/rs/zerolog/log" + "github.com/jmoiron/sqlx" "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" @@ -63,14 +64,14 @@ func (p *AdminPlugin) message(k bot.Kind, message msg.Message, args ...interface if strings.ToLower(body) == "shut up" { dur := time.Duration(p.cfg.GetInt("quietDuration", 5)) * time.Minute - log.Printf("Going to sleep for %v, %v", dur, time.Now().Add(dur)) + log.Info().Msgf("Going to sleep for %v, %v", dur, time.Now().Add(dur)) p.Bot.Send(bot.Message, message.Channel, "Okay. I'll be back later.") p.quiet = true go func() { select { case <-time.After(dur): p.quiet = false - log.Println("Waking up from nap.") + log.Info().Msg("Waking up from nap.") } }() return true @@ -105,7 +106,7 @@ func (p *AdminPlugin) handleVariables(message msg.Message) bool { _, err := p.db.Exec(`delete from variables where name=? and value=?`, variable, value) if err != nil { p.Bot.Send(bot.Message, message.Channel, "I'm broke and need attention in my variable creation code.") - log.Println("[admin]: ", err) + log.Error().Err(err) } else { p.Bot.Send(bot.Message, message.Channel, "Removed.") } @@ -126,7 +127,7 @@ func (p *AdminPlugin) handleVariables(message msg.Message) bool { err := row.Scan(&count) if err != nil { p.Bot.Send(bot.Message, message.Channel, "I'm broke and need attention in my variable creation code.") - log.Println("[admin]: ", err) + log.Error().Err(err) return true } @@ -136,7 +137,7 @@ func (p *AdminPlugin) handleVariables(message msg.Message) bool { _, err := p.db.Exec(`INSERT INTO variables (name, value) VALUES (?, ?)`, variable, value) if err != nil { p.Bot.Send(bot.Message, message.Channel, "I'm broke and need attention in my variable creation code.") - log.Println("[admin]: ", err) + log.Error().Err(err) return true } p.Bot.Send(bot.Message, message.Channel, "Added.") diff --git a/plugins/babbler/babbler.go b/plugins/babbler/babbler.go index f95782f..2c8311f 100644 --- a/plugins/babbler/babbler.go +++ b/plugins/babbler/babbler.go @@ -6,10 +6,11 @@ import ( "database/sql" "errors" "fmt" - "log" "math/rand" "strings" + "github.com/rs/zerolog/log" + "github.com/jmoiron/sqlx" "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" @@ -53,20 +54,18 @@ type BabblerArc struct { } func New(b bot.Bot) *BabblerPlugin { - log.SetFlags(log.LstdFlags | log.Lshortfile) - if _, err := b.DB().Exec(`create table if not exists babblers ( id integer primary key, babbler string );`); err != nil { - log.Fatal(err) + log.Fatal().Err(err) } if _, err := b.DB().Exec(`create table if not exists babblerWords ( id integer primary key, word string );`); err != nil { - log.Fatal(err) + log.Fatal().Err(err) } if _, err := b.DB().Exec(`create table if not exists babblerNodes ( @@ -76,7 +75,7 @@ func New(b bot.Bot) *BabblerPlugin { root integer, rootFrequency integer );`); err != nil { - log.Fatal(err) + log.Fatal().Err(err) } if _, err := b.DB().Exec(`create table if not exists babblerArcs ( @@ -85,7 +84,7 @@ func New(b bot.Bot) *BabblerPlugin { toNodeId interger, frequency integer );`); err != nil { - log.Fatal(err) + log.Fatal().Err(err) } plugin := &BabblerPlugin{ @@ -167,7 +166,7 @@ func (p *BabblerPlugin) makeBabbler(name string) (*Babbler, error) { if err == nil { id, err := res.LastInsertId() if err != nil { - log.Print(err) + log.Error().Err(err) return nil, err } return &Babbler{ @@ -183,11 +182,10 @@ func (p *BabblerPlugin) getBabbler(name string) (*Babbler, error) { err := p.db.QueryRowx(`select * from babblers where babbler = ? LIMIT 1;`, name).StructScan(&bblr) if err != nil { if err == sql.ErrNoRows { - log.Printf("failed to find babbler") + log.Error().Msg("failed to find babbler") return nil, NO_BABBLER } - log.Printf("encountered problem in babbler lookup") - log.Print(err) + log.Error().Err(err).Msg("encountered problem in babbler lookup") return nil, err } return &bblr, nil @@ -198,13 +196,13 @@ func (p *BabblerPlugin) getOrCreateBabbler(name string) (*Babbler, error) { if err == NO_BABBLER { babbler, err = p.makeBabbler(name) if err != nil { - log.Print(err) + log.Error().Err(err) return nil, err } rows, err := p.db.Queryx(fmt.Sprintf("select tidbit from factoid where fact like '%s quotes';", babbler.Name)) if err != nil { - log.Print(err) + log.Error().Err(err) return babbler, nil } defer rows.Close() @@ -214,10 +212,10 @@ func (p *BabblerPlugin) getOrCreateBabbler(name string) (*Babbler, error) { var tidbit string err := rows.Scan(&tidbit) - log.Print(tidbit) + log.Debug().Str("tidbit", tidbit) if err != nil { - log.Print(err) + log.Error().Err(err) return babbler, err } tidbits = append(tidbits, tidbit) @@ -225,7 +223,7 @@ func (p *BabblerPlugin) getOrCreateBabbler(name string) (*Babbler, error) { for _, tidbit := range tidbits { if err = p.addToMarkovChain(babbler, tidbit); err != nil { - log.Print(err) + log.Error().Err(err) } } } @@ -247,12 +245,12 @@ func (p *BabblerPlugin) getWord(word string) (*BabblerWord, error) { func (p *BabblerPlugin) createNewWord(word string) (*BabblerWord, error) { res, err := p.db.Exec(`insert into babblerWords (word) values (?);`, word) if err != nil { - log.Print(err) + log.Error().Err(err) return nil, err } id, err := res.LastInsertId() if err != nil { - log.Print(err) + log.Error().Err(err) return nil, err } return &BabblerWord{ @@ -266,7 +264,7 @@ func (p *BabblerPlugin) getOrCreateWord(word string) (*BabblerWord, error) { return p.createNewWord(word) } else { if err != nil { - log.Print(err) + log.Error().Err(err) } return w, err } @@ -292,19 +290,19 @@ func (p *BabblerPlugin) getBabblerNode(babbler *Babbler, word string) (*BabblerN func (p *BabblerPlugin) createBabblerNode(babbler *Babbler, word string) (*BabblerNode, error) { w, err := p.getOrCreateWord(word) if err != nil { - log.Print(err) + log.Error().Err(err) return nil, err } res, err := p.db.Exec(`insert into babblerNodes (babblerId, wordId, root, rootFrequency) values (?, ?, 0, 0)`, babbler.BabblerId, w.WordId) if err != nil { - log.Print(err) + log.Error().Err(err) return nil, err } id, err := res.LastInsertId() if err != nil { - log.Print(err) + log.Error().Err(err) return nil, err } @@ -327,12 +325,12 @@ func (p *BabblerPlugin) getOrCreateBabblerNode(babbler *Babbler, word string) (* func (p *BabblerPlugin) incrementRootWordFrequency(babbler *Babbler, word string) (*BabblerNode, error) { node, err := p.getOrCreateBabblerNode(babbler, word) if err != nil { - log.Print(err) + log.Error().Err(err) return nil, err } _, err = p.db.Exec(`update babblerNodes set rootFrequency = rootFrequency + 1, root = 1 where id = ?;`, node.NodeId) if err != nil { - log.Print(err) + log.Error().Err(err) return nil, err } node.RootFrequency += 1 @@ -354,7 +352,7 @@ func (p *BabblerPlugin) getBabblerArc(fromNode, toNode *BabblerNode) (*BabblerAr func (p *BabblerPlugin) incrementWordArc(fromNode, toNode *BabblerNode) (*BabblerArc, error) { res, err := p.db.Exec(`update babblerArcs set frequency = frequency + 1 where fromNodeId = ? and toNodeId = ?;`, fromNode.NodeId, toNode.NodeId) if err != nil { - log.Print(err) + log.Error().Err(err) return nil, err } @@ -366,7 +364,7 @@ func (p *BabblerPlugin) incrementWordArc(fromNode, toNode *BabblerNode) (*Babble if affectedRows == 0 { res, err = p.db.Exec(`insert into babblerArcs (fromNodeId, toNodeId, frequency) values (?, ?, 1);`, fromNode.NodeId, toNode.NodeId) if err != nil { - log.Print(err) + log.Error().Err(err) return nil, err } } @@ -390,19 +388,19 @@ func (p *BabblerPlugin) addToMarkovChain(babbler *Babbler, phrase string) error curNode, err := p.incrementRootWordFrequency(babbler, words[0]) if err != nil { - log.Print(err) + log.Error().Err(err) return err } for i := 1; i < len(words); i++ { nextNode, err := p.getOrCreateBabblerNode(babbler, words[i]) if err != nil { - log.Print(err) + log.Error().Err(err) return err } _, err = p.incrementWordArc(curNode, nextNode) if err != nil { - log.Print(err) + log.Error().Err(err) return err } curNode = nextNode @@ -415,7 +413,7 @@ func (p *BabblerPlugin) addToMarkovChain(babbler *Babbler, phrase string) error func (p *BabblerPlugin) getWeightedRootNode(babbler *Babbler) (*BabblerNode, *BabblerWord, error) { rows, err := p.db.Queryx(`select * from babblerNodes where babblerId = ? and root = 1;`, babbler.BabblerId) if err != nil { - log.Print(err) + log.Error().Err(err) return nil, nil, err } defer rows.Close() @@ -427,7 +425,7 @@ func (p *BabblerPlugin) getWeightedRootNode(babbler *Babbler) (*BabblerNode, *Ba var node BabblerNode err = rows.StructScan(&node) if err != nil { - log.Print(err) + log.Error().Err(err) return nil, nil, err } rootNodes = append(rootNodes, &node) @@ -446,21 +444,21 @@ func (p *BabblerPlugin) getWeightedRootNode(babbler *Babbler) (*BabblerNode, *Ba var w BabblerWord err := p.db.QueryRowx(`select * from babblerWords where id = ? LIMIT 1;`, node.WordId).StructScan(&w) if err != nil { - log.Print(err) + log.Error().Err(err) return nil, nil, err } return node, &w, nil } } - log.Fatalf("shouldn't happen") - return nil, nil, errors.New("failed to find weighted root word") + log.Fatal().Msg("failed to find weighted root word") + return nil, nil, nil } func (p *BabblerPlugin) getWeightedNextWord(fromNode *BabblerNode) (*BabblerNode, *BabblerWord, error) { rows, err := p.db.Queryx(`select * from babblerArcs where fromNodeId = ?;`, fromNode.NodeId) if err != nil { - log.Print(err) + log.Error().Err(err) return nil, nil, err } defer rows.Close() @@ -471,7 +469,7 @@ func (p *BabblerPlugin) getWeightedNextWord(fromNode *BabblerNode) (*BabblerNode var arc BabblerArc err = rows.StructScan(&arc) if err != nil { - log.Print(err) + log.Error().Err(err) return nil, nil, err } arcs = append(arcs, &arc) @@ -492,28 +490,28 @@ func (p *BabblerPlugin) getWeightedNextWord(fromNode *BabblerNode) (*BabblerNode var node BabblerNode err := p.db.QueryRowx(`select * from babblerNodes where id = ? LIMIT 1;`, arc.ToNodeId).StructScan(&node) if err != nil { - log.Print(err) + log.Error().Err(err) return nil, nil, err } var w BabblerWord err = p.db.QueryRowx(`select * from babblerWords where id = ? LIMIT 1;`, node.WordId).StructScan(&w) if err != nil { - log.Print(err) + log.Error().Err(err) return nil, nil, err } return &node, &w, nil } } - log.Fatalf("shouldn't happen") - return nil, nil, errors.New("failed to find weighted next word") + log.Fatal().Msg("failed to find weighted next word") + return nil, nil, nil } func (p *BabblerPlugin) getWeightedPreviousWord(toNode *BabblerNode) (*BabblerNode, *BabblerWord, bool, error) { rows, err := p.db.Queryx(`select * from babblerArcs where toNodeId = ?;`, toNode.NodeId) if err != nil { - log.Print(err) + log.Error().Err(err) return nil, nil, false, err } defer rows.Close() @@ -524,7 +522,7 @@ func (p *BabblerPlugin) getWeightedPreviousWord(toNode *BabblerNode) (*BabblerNo var arc BabblerArc err = rows.StructScan(&arc) if err != nil { - log.Print(err) + log.Error().Err(err) return nil, nil, false, err } arcs = append(arcs, &arc) @@ -551,39 +549,39 @@ func (p *BabblerPlugin) getWeightedPreviousWord(toNode *BabblerNode) (*BabblerNo var node BabblerNode err := p.db.QueryRowx(`select * from babblerNodes where id = ? LIMIT 1;`, arc.FromNodeId).StructScan(&node) if err != nil { - log.Print(err) + log.Error().Err(err) return nil, nil, false, err } var w BabblerWord err = p.db.QueryRowx(`select * from babblerWords where id = ? LIMIT 1;`, node.WordId).StructScan(&w) if err != nil { - log.Print(err) + log.Error().Err(err) return nil, nil, false, err } return &node, &w, false, nil } } - log.Fatalf("shouldn't happen") - return nil, nil, false, errors.New("failed to find weighted previous word") + log.Fatal().Msg("failed to find weighted previous word") + return nil, nil, false, nil } func (p *BabblerPlugin) verifyPhrase(babbler *Babbler, phrase []string) (*BabblerNode, *BabblerNode, error) { curNode, err := p.getBabblerNode(babbler, phrase[0]) if err != nil { - log.Print(err) + log.Error().Err(err) return nil, nil, err } firstNode := curNode for i := 1; i < len(phrase); i++ { nextNode, err := p.getBabblerNode(babbler, phrase[i]) if err != nil { - log.Print(err) + log.Error().Err(err) return nil, nil, err } _, err = p.getBabblerArc(curNode, nextNode) if err != nil { - log.Print(err) + log.Error().Err(err) return nil, nil, err } curNode = nextNode @@ -599,7 +597,7 @@ func (p *BabblerPlugin) babble(who string) (string, error) { func (p *BabblerPlugin) babbleSeed(babblerName string, seed []string) (string, error) { babbler, err := p.getBabbler(babblerName) if err != nil { - log.Print(err) + log.Error().Err(err) return "", nil } @@ -610,14 +608,14 @@ func (p *BabblerPlugin) babbleSeed(babblerName string, seed []string) (string, e if len(seed) == 0 { curNode, curWord, err = p.getWeightedRootNode(babbler) if err != nil { - log.Print(err) + log.Error().Err(err) return "", err } words = append(words, curWord.Word) } else { _, curNode, err = p.verifyPhrase(babbler, seed) if err != nil { - log.Print(err) + log.Error().Err(err) return "", err } } @@ -625,7 +623,7 @@ func (p *BabblerPlugin) babbleSeed(babblerName string, seed []string) (string, e for { curNode, curWord, err = p.getWeightedNextWord(curNode) if err != nil { - log.Print(err) + log.Error().Err(err) return "", err } if curWord.Word == " " { @@ -644,12 +642,12 @@ func (p *BabblerPlugin) babbleSeed(babblerName string, seed []string) (string, e func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoName, otherName string) error { intoNode, err := p.getOrCreateBabblerNode(intoBabbler, "<"+intoName+">") if err != nil { - log.Print(err) + log.Error().Err(err) return err } otherNode, err := p.getOrCreateBabblerNode(otherBabbler, "<"+otherName+">") if err != nil { - log.Print(err) + log.Error().Err(err) return err } @@ -657,7 +655,7 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa rows, err := p.db.Queryx("select * from babblerNodes where babblerId = ?;", otherBabbler.BabblerId) if err != nil { - log.Print(err) + log.Error().Err(err) return err } defer rows.Close() @@ -668,7 +666,7 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa var node BabblerNode err = rows.StructScan(&node) if err != nil { - log.Print(err) + log.Error().Err(err) return err } nodes = append(nodes, &node) @@ -684,12 +682,12 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa if node.Root > 0 { res, err = p.db.Exec(`update babblerNodes set rootFrequency = rootFrequency + ?, root = 1 where babblerId = ? and wordId = ?;`, node.RootFrequency, intoBabbler.BabblerId, node.WordId) if err != nil { - log.Print(err) + log.Error().Err(err) } } else { res, err = p.db.Exec(`update babblerNodes set rootFrequency = rootFrequency + ? where babblerId = ? and wordId = ?;`, node.RootFrequency, intoBabbler.BabblerId, node.WordId) if err != nil { - log.Print(err) + log.Error().Err(err) } } @@ -701,7 +699,7 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa if err != nil || rowsAffected == 0 { res, err = p.db.Exec(`insert into babblerNodes (babblerId, wordId, root, rootFrequency) values (?,?,?,?) ;`, intoBabbler.BabblerId, node.WordId, node.Root, node.RootFrequency) if err != nil { - log.Print(err) + log.Error().Err(err) return err } } @@ -709,7 +707,7 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa var updatedNode BabblerNode err = p.db.QueryRowx(`select * from babblerNodes where babblerId = ? and wordId = ? LIMIT 1;`, intoBabbler.BabblerId, node.WordId).StructScan(&updatedNode) if err != nil { - log.Print(err) + log.Error().Err(err) return err } @@ -729,7 +727,7 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa var arc BabblerArc err = rows.StructScan(&arc) if err != nil { - log.Print(err) + log.Error().Err(err) return err } arcs = append(arcs, &arc) @@ -749,13 +747,13 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa func (p *BabblerPlugin) babbleSeedSuffix(babblerName string, seed []string) (string, error) { babbler, err := p.getBabbler(babblerName) if err != nil { - log.Print(err) + log.Error().Err(err) return "", nil } firstNode, curNode, err := p.verifyPhrase(babbler, seed) if err != nil { - log.Print(err) + log.Error().Err(err) return "", err } @@ -766,7 +764,7 @@ func (p *BabblerPlugin) babbleSeedSuffix(babblerName string, seed []string) (str for { curNode, curWord, shouldTerminate, err = p.getWeightedPreviousWord(curNode) if err != nil { - log.Print(err) + log.Error().Err(err) return "", err } @@ -795,7 +793,7 @@ func (p *BabblerPlugin) getNextArcs(babblerNodeId int64) ([]*BabblerArc, error) arcs := []*BabblerArc{} rows, err := p.db.Queryx(`select * from babblerArcs where fromNodeId = ?;`, babblerNodeId) if err != nil { - log.Print(err) + log.Error().Err(err) return arcs, err } defer rows.Close() @@ -804,7 +802,7 @@ func (p *BabblerPlugin) getNextArcs(babblerNodeId int64) ([]*BabblerArc, error) var arc BabblerArc err = rows.StructScan(&arc) if err != nil { - log.Print(err) + log.Error().Err(err) return []*BabblerArc{}, err } arcs = append(arcs, &arc) @@ -816,7 +814,7 @@ func (p *BabblerPlugin) getBabblerNodeById(nodeId int64) (*BabblerNode, error) { var node BabblerNode err := p.db.QueryRowx(`select * from babblerNodes where id = ? LIMIT 1;`, nodeId).StructScan(&node) if err != nil { - log.Print(err) + log.Error().Err(err) return nil, err } return &node, nil @@ -832,19 +830,19 @@ func shuffle(a []*BabblerArc) { func (p *BabblerPlugin) babbleSeedBookends(babblerName string, start, end []string) (string, error) { babbler, err := p.getBabbler(babblerName) if err != nil { - log.Print(err) + log.Error().Err(err) return "", nil } _, startWordNode, err := p.verifyPhrase(babbler, start) if err != nil { - log.Print(err) + log.Error().Err(err) return "", err } endWordNode, _, err := p.verifyPhrase(babbler, end) if err != nil { - log.Print(err) + log.Error().Err(err) return "", err } @@ -898,13 +896,13 @@ func (p *BabblerPlugin) babbleSeedBookends(babblerName string, start, end []stri for { cur, err := p.getBabblerNodeById(curSearchNode.babblerNodeId) if err != nil { - log.Print(err) + log.Error().Err(err) return "", err } var w BabblerWord err = p.db.QueryRowx(`select * from babblerWords where id = ? LIMIT 1;`, cur.WordId).StructScan(&w) if err != nil { - log.Print(err) + log.Error().Err(err) return "", err } words = append(words, w.Word) diff --git a/plugins/beers/beers.go b/plugins/beers/beers.go index 4e1640c..138d455 100644 --- a/plugins/beers/beers.go +++ b/plugins/beers/beers.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "io/ioutil" - "log" "math/rand" "net/http" "strconv" @@ -15,6 +14,7 @@ import ( "time" "github.com/jmoiron/sqlx" + "github.com/rs/zerolog/log" "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" "github.com/velour/catbase/plugins/counter" @@ -46,7 +46,7 @@ func New(b bot.Bot) *BeersPlugin { lastCheckin integer, chanNick string );`); err != nil { - log.Fatal(err) + log.Fatal().Err(err) } p := &BeersPlugin{ Bot: b, @@ -147,13 +147,16 @@ func (p *BeersPlugin) message(kind bot.Kind, message msg.Message, args ...interf channel: channel, } - log.Println("Creating Untappd user:", u.untappdUser, "nick:", u.chanNick) + log.Info(). + Str("untappdUser", u.untappdUser). + Str("nick", u.chanNick). + Msg("Creating Untappd user") var count int err := p.db.QueryRow(`select count(*) from untappd where untappdUser = ?`, u.untappdUser).Scan(&count) if err != nil { - log.Println("Error registering untappd: ", err) + log.Error().Err(err).Msgf("Error registering untappd") } if count > 0 { p.Bot.Send(bot.Message, channel, "I'm already watching you.") @@ -171,7 +174,7 @@ func (p *BeersPlugin) message(kind bot.Kind, message msg.Message, args ...interf u.chanNick, ) if err != nil { - log.Println("Error registering untappd: ", err) + log.Error().Err(err).Msgf("Error registering untappd") p.Bot.Send(bot.Message, channel, "I can't see.") return true } @@ -184,7 +187,9 @@ func (p *BeersPlugin) message(kind bot.Kind, message msg.Message, args ...interf } if message.Command && parts[0] == "checkuntappd" { - log.Println("Checking untappd at request of user.") + log.Info(). + Str("user", message.User.Name). + Msgf("Checking untappd at request of user.") p.checkUntappd(channel) return true } @@ -210,7 +215,7 @@ func (p *BeersPlugin) setBeers(user string, amount int) { ub := getUserBeers(p.db, user) err := ub.Update(amount) if err != nil { - log.Println("Error saving beers: ", err) + log.Error().Err(err).Msgf("Error saving beers") } } @@ -218,7 +223,7 @@ func (p *BeersPlugin) addBeers(user string, delta int) { ub := getUserBeers(p.db, user) err := ub.UpdateDelta(delta) if err != nil { - log.Println("Error saving beers: ", err) + log.Error().Err(err).Msgf("Error saving beers") } } @@ -325,14 +330,14 @@ func (p *BeersPlugin) pullUntappd() ([]checkin, error) { } if resp.StatusCode == 500 { - log.Printf("Error querying untappd: %s, %s", resp.Status, body) + log.Error().Msgf("Error querying untappd: %s, %s", resp.Status, body) return []checkin{}, errors.New(resp.Status) } var beers Beers err = json.Unmarshal(body, &beers) if err != nil { - log.Println(err) + log.Error().Err(err) return []checkin{}, err } return beers.Response.Checkins.Items, nil @@ -341,31 +346,32 @@ func (p *BeersPlugin) pullUntappd() ([]checkin, error) { func (p *BeersPlugin) checkUntappd(channel string) { token := p.Bot.Config().Get("Untappd.Token", "NONE") if token == "NONE" { - log.Println(`Set config value "untappd.token" if you wish to enable untappd`) + log.Info(). + Msg(`Set config value "untappd.token" if you wish to enable untappd`) return } userMap := make(map[string]untappdUser) rows, err := p.db.Query(`select id, untappdUser, channel, lastCheckin, chanNick from untappd;`) if err != nil { - log.Println("Error getting untappd users: ", err) + log.Error().Err(err).Msg("Error getting untappd users") return } for rows.Next() { u := untappdUser{} err := rows.Scan(&u.id, &u.untappdUser, &u.channel, &u.lastCheckin, &u.chanNick) if err != nil { - log.Fatal(err) + log.Fatal().Err(err) } userMap[u.untappdUser] = u if u.chanNick == "" { - log.Fatal("Empty chanNick for no good reason.") + log.Fatal().Msg("Empty chanNick for no good reason.") } } chks, err := p.pullUntappd() if err != nil { - log.Println("Untappd ERROR: ", err) + log.Error().Err(err).Msg("Untappd ERROR") return } for i := len(chks); i > 0; i-- { @@ -386,8 +392,9 @@ func (p *BeersPlugin) checkUntappd(channel string) { if !ok { continue } - log.Printf("user.chanNick: %s, user.untappdUser: %s, checkin.User.User_name: %s", - user.chanNick, user.untappdUser, checkin.User.User_name) + log.Debug(). + Msgf("user.chanNick: %s, user.untappdUser: %s, checkin.User.User_name: %s", + user.chanNick, user.untappdUser, checkin.User.User_name) p.addBeers(user.chanNick, 1) drunken := p.getBeers(user.chanNick) @@ -413,10 +420,13 @@ func (p *BeersPlugin) checkUntappd(channel string) { lastCheckin = ? where id = ?`, user.lastCheckin, user.id) if err != nil { - log.Println("UPDATE ERROR!:", err) + log.Error().Err(err).Msg("UPDATE ERROR!") } - log.Println("checkin id:", checkin.Checkin_id, "Message:", msg) + log.Debug(). + Int("checkin_id", checkin.Checkin_id). + Str("msg", msg). + Msg("checkin") p.Bot.Send(bot.Message, channel, msg) } } @@ -427,7 +437,7 @@ func (p *BeersPlugin) untappdLoop(channel string) { return } - log.Println("Checking every ", frequency, " seconds") + log.Info().Msgf("Checking every %v seconds", frequency) for { time.Sleep(time.Duration(frequency) * time.Second) diff --git a/plugins/counter/counter.go b/plugins/counter/counter.go index b7729cd..c7b62e5 100644 --- a/plugins/counter/counter.go +++ b/plugins/counter/counter.go @@ -5,12 +5,13 @@ package counter import ( "database/sql" "fmt" - "log" "math/rand" "regexp" "strconv" "strings" + "github.com/rs/zerolog/log" + "github.com/jmoiron/sqlx" "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" @@ -112,7 +113,7 @@ func GetItem(db *sqlx.DB, nick, itemName string) (Item, error) { if err := db.Get(&a, `select * from counter_alias where item=?`, itemName); err == nil { itemName = a.PointsTo } else { - log.Println(err, a) + log.Error().Err(err).Interface("alias", a) } err := db.Get(&item, `select * from counter where nick = ? and item= ?`, @@ -126,7 +127,11 @@ func GetItem(db *sqlx.DB, nick, itemName string) (Item, error) { default: return Item{}, err } - log.Printf("Got item %s.%s: %#v", nick, itemName, item) + log.Debug(). + Str("nick", nick). + Str("itemName", itemName). + Interface("item", item). + Msg("got item") return item, nil } @@ -153,7 +158,10 @@ func (i *Item) Update(value int) error { if i.ID == -1 { i.Create() } - log.Printf("Updating item: %#v, value: %d", i, value) + log.Debug(). + Interface("i", i). + Int("value", value). + Msg("Updating item") _, err := i.Exec(`update counter set count = ? where id = ?`, i.Count, i.ID) return err } @@ -212,7 +220,7 @@ func (p *CounterPlugin) message(kind bot.Kind, message msg.Message, args ...inte if len(parts) == 3 && strings.ToLower(parts[0]) == "mkalias" { if _, err := MkAlias(p.DB, parts[1], parts[2]); err != nil { - log.Println(err) + log.Error().Err(err) return false } p.Bot.Send(bot.Message, channel, fmt.Sprintf("Created alias %s -> %s", @@ -231,7 +239,7 @@ func (p *CounterPlugin) message(kind bot.Kind, message msg.Message, args ...inte its, err := cmd() if err != nil { - log.Println(err) + log.Error().Err(err) return false } else if len(its) == 0 { return false @@ -253,11 +261,14 @@ func (p *CounterPlugin) message(kind bot.Kind, message msg.Message, args ...inte } else if message.Command && message.Body == "reset me" { items, err := GetItems(p.DB, strings.ToLower(nick)) if err != nil { - log.Printf("Error getting items to reset %s: %s", nick, err) + log.Error(). + Err(err). + Str("nick", nick). + Msg("Error getting items to reset") p.Bot.Send(bot.Message, channel, "Something is technically wrong with your counters.") return true } - log.Printf("Items: %+v", items) + log.Debug().Msgf("Items: %+v", items) for _, item := range items { item.Delete() } @@ -272,11 +283,16 @@ func (p *CounterPlugin) message(kind bot.Kind, message msg.Message, args ...inte subject = strings.ToLower(parts[1]) } - log.Printf("Getting counter for %s", subject) + log.Debug(). + Str("subject", subject). + Msg("Getting counter") // pull all of the items associated with "subject" items, err := GetItems(p.DB, subject) if err != nil { - log.Fatalf("Error retrieving items for %s: %s", subject, err) + log.Error(). + Err(err). + Str("subject", subject). + Msg("Error retrieving items") p.Bot.Send(bot.Message, channel, "Something went wrong finding that counter;") return true } @@ -309,13 +325,21 @@ func (p *CounterPlugin) message(kind bot.Kind, message msg.Message, args ...inte it, err := GetItem(p.DB, subject, itemName) if err != nil { - log.Printf("Error getting item to remove %s.%s: %s", subject, itemName, err) + log.Error(). + Err(err). + Str("subject", subject). + Str("itemName", itemName). + Msg("Error getting item to remove") p.Bot.Send(bot.Message, channel, "Something went wrong removing that counter;") return true } err = it.Delete() if err != nil { - log.Printf("Error removing item %s.%s: %s", subject, itemName, err) + log.Error(). + Err(err). + Str("subject", subject). + Str("itemName", itemName). + Msg("Error removing item") p.Bot.Send(bot.Message, channel, "Something went wrong removing that counter;") return true } @@ -347,8 +371,11 @@ func (p *CounterPlugin) message(kind bot.Kind, message msg.Message, args ...inte subject, itemName)) return true case err != nil: - log.Printf("Error retrieving item count for %s.%s: %s", - subject, itemName, err) + log.Error(). + Err(err). + Str("subject", subject). + Str("itemName", itemName). + Msg("Error retrieving item count") return true } @@ -377,11 +404,15 @@ func (p *CounterPlugin) message(kind bot.Kind, message msg.Message, args ...inte // ++ those fuckers item, err := GetItem(p.DB, subject, itemName) if err != nil { - log.Printf("Error finding item %s.%s: %s.", subject, itemName, err) + log.Error(). + Err(err). + Str("subject", subject). + Str("itemName", itemName). + Msg("error finding item") // Item ain't there, I guess return false } - log.Printf("About to update item: %#v", item) + log.Debug().Msgf("About to update item: %#v", item) item.UpdateDelta(1) p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s has %d %s.", subject, item.Count, item.Item)) @@ -390,7 +421,11 @@ func (p *CounterPlugin) message(kind bot.Kind, message msg.Message, args ...inte // -- those fuckers item, err := GetItem(p.DB, subject, itemName) if err != nil { - log.Printf("Error finding item %s.%s: %s.", subject, itemName, err) + log.Error(). + Err(err). + Str("subject", subject). + Str("itemName", itemName). + Msg("Error finding item") // Item ain't there, I guess return false } @@ -417,12 +452,16 @@ func (p *CounterPlugin) message(kind bot.Kind, message msg.Message, args ...inte // += those fuckers item, err := GetItem(p.DB, subject, itemName) if err != nil { - log.Printf("Error finding item %s.%s: %s.", subject, itemName, err) + log.Error(). + Err(err). + Str("subject", subject). + Str("itemName", itemName). + Msg("Error finding item") // Item ain't there, I guess return false } n, _ := strconv.Atoi(parts[2]) - log.Printf("About to update item by %d: %#v", n, item) + log.Debug().Msgf("About to update item by %d: %#v", n, item) item.UpdateDelta(n) p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s has %d %s.", subject, item.Count, item.Item)) @@ -431,12 +470,16 @@ func (p *CounterPlugin) message(kind bot.Kind, message msg.Message, args ...inte // -= those fuckers item, err := GetItem(p.DB, subject, itemName) if err != nil { - log.Printf("Error finding item %s.%s: %s.", subject, itemName, err) + log.Error(). + Err(err). + Str("subject", subject). + Str("itemName", itemName). + Msg("Error finding item") // Item ain't there, I guess return false } n, _ := strconv.Atoi(parts[2]) - log.Printf("About to update item by -%d: %#v", n, item) + log.Debug().Msgf("About to update item by -%d: %#v", n, item) item.UpdateDelta(-n) p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s has %d %s.", subject, item.Count, item.Item)) @@ -469,11 +512,14 @@ func (p *CounterPlugin) checkMatch(message msg.Message) bool { // We will specifically allow :tea: to keep compatability item, err := GetItem(p.DB, nick, itemName) if err != nil || (item.Count == 0 && item.Item != ":tea:") { - log.Printf("Error finding item %s.%s: %s.", nick, itemName, err) + log.Error(). + Err(err). + Str("itemName", itemName). + Msg("Error finding item") // Item ain't there, I guess return false } - log.Printf("About to update item: %#v", item) + log.Debug().Msgf("About to update item: %#v", item) item.UpdateDelta(1) p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s... %s has %d %s", strings.Join(everyDayImShuffling([]string{"bleep", "bloop", "blop"}), "-"), nick, item.Count, itemName)) diff --git a/plugins/db/db.go b/plugins/db/db.go index 026bf76..a3799b9 100644 --- a/plugins/db/db.go +++ b/plugins/db/db.go @@ -2,11 +2,12 @@ package db import ( "fmt" - "log" "net/http" "os" "time" + "github.com/rs/zerolog/log" + "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" "github.com/velour/catbase/config" @@ -37,7 +38,7 @@ func (p *DBPlugin) serveQuery(w http.ResponseWriter, r *http.Request) { f, err := os.Open(p.bot.Config().DBFile) defer f.Close() if err != nil { - log.Printf("Error opening DB for web service: %s", err) + log.Error().Err(err).Msg("Error opening DB for web service") fmt.Fprintf(w, "Error opening DB") return } diff --git a/plugins/emojifyme/emojifyme.go b/plugins/emojifyme/emojifyme.go index 3419e4c..4a24d85 100644 --- a/plugins/emojifyme/emojifyme.go +++ b/plugins/emojifyme/emojifyme.go @@ -5,11 +5,12 @@ package emojifyme import ( "encoding/json" "io/ioutil" - "log" "math/rand" "net/http" "strings" + "github.com/rs/zerolog/log" + "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" ) @@ -23,12 +24,12 @@ type EmojifyMePlugin struct { func New(b bot.Bot) *EmojifyMePlugin { resp, err := http.Get("https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json") if err != nil { - log.Fatalf("Error generic emoji list: %s", err) + log.Fatal().Err(err).Msg("Error generic emoji list") } body, err := ioutil.ReadAll(resp.Body) resp.Body.Close() if err != nil { - log.Fatalf("Error generic emoji list body: %s", err) + log.Fatal().Err(err).Msg("Error generic emoji list body") } type Emoji struct { @@ -38,7 +39,7 @@ func New(b bot.Bot) *EmojifyMePlugin { var emoji []Emoji err = json.Unmarshal(body, &emoji) if err != nil { - log.Fatalf("Error parsing emoji list: %s", err) + log.Fatal().Err(err).Msg("Error parsing emoji list") } emojiMap := map[string]string{} diff --git a/plugins/fact/factoid.go b/plugins/fact/factoid.go index b4cc1d4..f7b25a5 100644 --- a/plugins/fact/factoid.go +++ b/plugins/fact/factoid.go @@ -6,13 +6,14 @@ import ( "database/sql" "fmt" "html/template" - "log" "math/rand" "net/http" "regexp" "strings" "time" + "github.com/rs/zerolog/log" + "github.com/jmoiron/sqlx" "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" @@ -166,7 +167,7 @@ func getFacts(db *sqlx.DB, fact string, tidbit string) ([]*Factoid, error) { rows, err := db.Query(query, "%"+fact+"%", "%"+tidbit+"%") if err != nil { - log.Printf("Error regexping for facts: %s", err) + log.Error().Err(err).Msg("Error regexping for facts") return nil, err } for rows.Next() { @@ -286,7 +287,7 @@ func New(botInst bot.Bot) *FactoidPlugin { accessed integer, count integer );`); err != nil { - log.Fatal(err) + log.Fatal().Err(err) } if _, err := p.db.Exec(`create table if not exists factoid_alias ( @@ -294,7 +295,7 @@ func New(botInst bot.Bot) *FactoidPlugin { next string, primary key (fact, next) );`); err != nil { - log.Fatal(err) + log.Fatal().Err(err) } for _, channel := range botInst.Config().GetArray("channels", []string{}) { @@ -359,10 +360,10 @@ func (p *FactoidPlugin) learnFact(message msg.Message, fact, verb, tidbit string where fact=? and verb=? and tidbit=?`, fact, verb, tidbit).Scan(&count) if err != nil { - log.Println("Error counting facts: ", err) + log.Error().Err(err).Msg("Error counting facts") return fmt.Errorf("What?") } else if count.Valid && count.Int64 != 0 { - log.Println("User tried to relearn a fact.") + log.Debug().Msg("User tried to relearn a fact.") return fmt.Errorf("Look, I already know that.") } @@ -378,7 +379,7 @@ func (p *FactoidPlugin) learnFact(message msg.Message, fact, verb, tidbit string p.LastFact = &n err = n.Save(p.db) if err != nil { - log.Println("Error inserting fact: ", err) + log.Error().Err(err).Msg("Error inserting fact") return fmt.Errorf("My brain is overheating.") } @@ -425,9 +426,10 @@ func (p *FactoidPlugin) sayFact(message msg.Message, fact Factoid) { fact.Count += 1 err := fact.Save(p.db) if err != nil { - log.Printf("Could not update fact.\n") - log.Printf("%#v\n", fact) - log.Println(err) + log.Error(). + Interface("fact", fact). + Err(err). + Msg("could not update fact") } p.LastFact = &fact } @@ -520,7 +522,10 @@ func (p *FactoidPlugin) forgetLastFact(message msg.Message) bool { err := p.LastFact.delete(p.db) if err != nil { - log.Println("Error removing fact: ", p.LastFact, err) + log.Error(). + Err(err). + Interface("LastFact", p.LastFact). + Msg("Error removing fact") } fmt.Printf("Forgot #%d: %s %s %s\n", p.LastFact.ID.Int64, p.LastFact.Fact, p.LastFact.Verb, p.LastFact.Tidbit) @@ -539,7 +544,11 @@ func (p *FactoidPlugin) changeFact(message msg.Message) bool { parts = strings.Split(userexp, "/") - log.Printf("changeFact: %s %s %#v", trigger, userexp, parts) + log.Debug(). + Str("trigger", trigger). + Str("userexp", userexp). + Strs("parts", parts). + Msg("changefact") if len(parts) == 4 { // replacement @@ -552,7 +561,10 @@ func (p *FactoidPlugin) changeFact(message msg.Message) bool { // replacement result, err := getFacts(p.db, trigger, parts[1]) if err != nil { - log.Println("Error getting facts: ", trigger, err) + log.Error(). + Err(err). + Str("trigger", trigger). + Msg("Error getting facts") } if userexp[len(userexp)-1] != 'g' { result = result[:1] @@ -578,7 +590,10 @@ func (p *FactoidPlugin) changeFact(message msg.Message) bool { // search for a factoid and print it result, err := getFacts(p.db, trigger, parts[1]) if err != nil { - log.Println("Error getting facts: ", trigger, err) + log.Error(). + Err(err). + Str("trigger", trigger). + Msg("Error getting facts") p.Bot.Send(bot.Message, message.Channel, "bzzzt") return true } @@ -626,7 +641,9 @@ func (p *FactoidPlugin) message(kind bot.Kind, message msg.Message, args ...inte } if strings.HasPrefix(strings.ToLower(message.Body), "alias") { - log.Printf("Trying to learn an alias: %s", message.Body) + log.Debug(). + Str("alias", message.Body). + Msg("Trying to learn an alias") m := strings.TrimPrefix(message.Body, "alias ") parts := strings.SplitN(m, "->", 2) if len(parts) != 2 { @@ -647,7 +664,7 @@ func (p *FactoidPlugin) message(kind bot.Kind, message msg.Message, args ...inte p.sayFact(message, *fact) return true } - log.Println("Got a nil fact.") + log.Debug().Msg("Got a nil fact.") } if strings.ToLower(message.Body) == "forget that" { @@ -721,7 +738,7 @@ func (p *FactoidPlugin) factTimer(channel string) { if success && tdelta > duration && earlier { fact := p.randomFact() if fact == nil { - log.Println("Didn't find a random fact to say") + log.Debug().Msg("Didn't find a random fact to say") continue } @@ -764,7 +781,7 @@ func (p *FactoidPlugin) serveQuery(w http.ResponseWriter, r *http.Request) { if e := r.FormValue("entry"); e != "" { entries, err := getFacts(p.db, e, "") if err != nil { - log.Println("Web error searching: ", err) + log.Error().Err(err).Msg("Web error searching") } context["Count"] = fmt.Sprintf("%d", len(entries)) context["Entries"] = entries @@ -772,10 +789,10 @@ func (p *FactoidPlugin) serveQuery(w http.ResponseWriter, r *http.Request) { } t, err := template.New("factoidIndex").Funcs(funcMap).Parse(factoidIndex) if err != nil { - log.Println(err) + log.Error().Err(err) } err = t.Execute(w, context) if err != nil { - log.Println(err) + log.Error().Err(err) } } diff --git a/plugins/first/first.go b/plugins/first/first.go index 88fcb47..c0a35b0 100644 --- a/plugins/first/first.go +++ b/plugins/first/first.go @@ -5,12 +5,12 @@ package first import ( "database/sql" "fmt" - "log" "regexp" "strings" "time" "github.com/jmoiron/sqlx" + "github.com/rs/zerolog/log" "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" ) @@ -56,14 +56,19 @@ func New(b bot.Bot) *FirstPlugin { nick string );`) if err != nil { - log.Fatal("Could not create first table: ", err) + log.Fatal(). + Err(err). + Msg("Could not create first table") } - log.Println("First plugin initialized with day:", midnight(time.Now())) + log.Info().Msgf("First plugin initialized with day: %s", + midnight(time.Now())) first, err := getLastFirst(b.DB()) if err != nil { - log.Fatal("Could not initialize first plugin: ", err) + log.Fatal(). + Err(err). + Msg("Could not initialize first plugin") } fp := &FirstPlugin{ @@ -96,13 +101,14 @@ func getLastFirst(db *sqlx.DB) (*FirstEntry, error) { ) switch { case err == sql.ErrNoRows || !id.Valid: - log.Println("No previous first entries") + log.Info().Msg("No previous first entries") return nil, nil case err != nil: - log.Println("Error on first query row: ", err) + log.Warn().Err(err).Msg("Error on first query row") return nil, err } - log.Println(id, day, timeEntered, body, nick) + log.Debug().Msgf("id: %v day %v time %v body %v nick %v", + id, day, timeEntered, body, nick) return &FirstEntry{ id: id.Int64, day: time.Unix(day.Int64, 0), @@ -130,12 +136,18 @@ func (p *FirstPlugin) message(kind bot.Kind, message msg.Message, args ...interf // This bot does not reply to anything if p.First == nil && p.allowed(message) { - log.Printf("No previous first. Recording new first: %s", message.Body) + log.Debug(). + Str("body", message.Body). + Msg("No previous first. Recording new first") p.recordFirst(message) return false } else if p.First != nil { if isToday(p.First.time) && p.allowed(message) { - log.Printf("Recording first: %s - %v vs %v", message.Body, p.First.time, time.Now()) + log.Debug(). + Str("body", message.Body). + Time("t0", p.First.time). + Time("t1", time.Now()). + Msg("Recording first") p.recordFirst(message) return false } @@ -156,22 +168,31 @@ func (p *FirstPlugin) allowed(message msg.Message) bool { for _, msg := range p.Bot.Config().GetArray("Bad.Msgs", []string{}) { match, err := regexp.MatchString(msg, strings.ToLower(message.Body)) if err != nil { - log.Println("Bad regexp: ", err) + log.Error().Err(err).Msg("Bad regexp") } if match { - log.Println("Disallowing first: ", message.User.Name, ":", message.Body) + log.Info(). + Str("user", message.User.Name). + Str("body", message.Body). + Msg("Disallowing first") return false } } for _, host := range p.Bot.Config().GetArray("Bad.Hosts", []string{}) { if host == message.Host { - log.Println("Disallowing first: ", message.User.Name, ":", message.Body) + log.Info(). + Str("user", message.User.Name). + Str("body", message.Body). + Msg("Disallowing first") return false } } for _, nick := range p.Bot.Config().GetArray("Bad.Nicks", []string{}) { if nick == message.User.Name { - log.Println("Disallowing first: ", message.User.Name, ":", message.Body) + log.Info(). + Str("user", message.User.Name). + Str("body", message.Body). + Msg("Disallowing first") return false } } @@ -179,17 +200,20 @@ func (p *FirstPlugin) allowed(message msg.Message) bool { } func (p *FirstPlugin) recordFirst(message msg.Message) { - log.Println("Recording first: ", message.User.Name, ":", message.Body) + log.Info(). + Str("user", message.User.Name). + Str("body", message.Body). + Msg("Recording first") p.First = &FirstEntry{ day: midnight(time.Now()), time: message.Time, body: message.Body, nick: message.User.Name, } - log.Printf("recordFirst: %+v", p.First.day) + log.Info().Msgf("recordFirst: %+v", p.First.day) err := p.First.save(p.db) if err != nil { - log.Println("Error saving first entry: ", err) + log.Error().Err(err).Msg("Error saving first entry") return } p.announceFirst(message) diff --git a/plugins/inventory/inventory.go b/plugins/inventory/inventory.go index c13ac9e..b1fb2bf 100644 --- a/plugins/inventory/inventory.go +++ b/plugins/inventory/inventory.go @@ -6,10 +6,11 @@ package inventory import ( "fmt" - "log" "regexp" "strings" + "github.com/rs/zerolog/log" + "github.com/jmoiron/sqlx" "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" @@ -53,7 +54,7 @@ func New(b bot.Bot) *InventoryPlugin { );`) if err != nil { - log.Fatal(err) + log.Fatal().Err(err) } b.Register(p, bot.Message, p.message) @@ -79,13 +80,13 @@ func (p *InventoryPlugin) itemFilter(input string) string { func (p *InventoryPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { m := message.Body - log.Printf("inventory trying to read %+v", message) + log.Debug().Msgf("inventory trying to read %+v", message) if message.Command { if strings.ToLower(m) == "inventory" { items := p.getAll() say := "I'm not holding anything" if len(items) > 0 { - log.Printf("I think I have more than 0 items: %+v, len(items)=%d", items, len(items)) + log.Debug().Msgf("I think I have more than 0 items: %+v, len(items)=%d", items, len(items)) say = fmt.Sprintf("I'm currently holding %s", strings.Join(items, ", ")) } p.bot.Send(bot.Message, message.Channel, say) @@ -95,30 +96,30 @@ func (p *InventoryPlugin) message(kind bot.Kind, message msg.Message, args ...in // Bucket[:,] take this (.+) // Bucket[:,] have a (.+) if matches := p.r1.FindStringSubmatch(m); len(matches) > 0 { - log.Printf("Found item to add: %s", matches[1]) + log.Debug().Msgf("Found item to add: %s", matches[1]) return p.addItem(message, matches[1]) } if matches := p.r2.FindStringSubmatch(m); len(matches) > 0 { - log.Printf("Found item to add: %s", matches[1]) + log.Debug().Msgf("Found item to add: %s", matches[1]) return p.addItem(message, matches[1]) } } if message.Action { - log.Println("Inventory found an action") + log.Debug().Msg("Inventory found an action") // * Randall puts (.+) in Bucket([^a-zA-Z].*)? // * Randall gives Bucket (.+) // * Randall gives (.+) to Bucket([^a-zA-Z].*)? if matches := p.r3.FindStringSubmatch(m); len(matches) > 0 { - log.Printf("Found item to add: %s", matches[1]) + log.Debug().Msgf("Found item to add: %s", matches[1]) return p.addItem(message, matches[1]) } if matches := p.r4.FindStringSubmatch(m); len(matches) > 0 { - log.Printf("Found item to add: %s", matches[1]) + log.Debug().Msgf("Found item to add: %s", matches[1]) return p.addItem(message, matches[1]) } if matches := p.r5.FindStringSubmatch(m); len(matches) > 0 { - log.Printf("Found item to add: %s", matches[1]) + log.Debug().Msgf("Found item to add: %s", matches[1]) return p.addItem(message, matches[1]) } } @@ -131,12 +132,12 @@ func (p *InventoryPlugin) removeRandom() string { &name, ) if err != nil { - log.Printf("Error finding random entry: %s", err) + log.Error().Err(err).Msgf("Error finding random entry") return "IAMERROR" } _, err = p.Exec(`delete from inventory where item=?`, name) if err != nil { - log.Printf("Error finding random entry: %s", err) + log.Error().Err(err).Msgf("Error finding random entry") return "IAMERROR" } return name @@ -146,7 +147,7 @@ func (p *InventoryPlugin) count() int { var output int err := p.QueryRow(`select count(*) as count from inventory`).Scan(&output) if err != nil { - log.Printf("Error checking for item: %s", err) + log.Error().Err(err).Msg("Error checking for item") return -1 } return output @@ -158,7 +159,7 @@ func (p *InventoryPlugin) random() string { &name, ) if err != nil { - log.Printf("Error finding random entry: %s", err) + log.Error().Err(err).Msg("Error finding random entry") return "IAMERROR" } return name @@ -167,7 +168,7 @@ func (p *InventoryPlugin) random() string { func (p *InventoryPlugin) getAll() []string { rows, err := p.Queryx(`select item from inventory`) if err != nil { - log.Printf("Error getting all items: %s", err) + log.Error().Err(err).Msg("Error getting all items") return []string{} } output := []string{} @@ -184,7 +185,7 @@ func (p *InventoryPlugin) exists(i string) bool { var output int err := p.QueryRow(`select count(*) as count from inventory where item=?`, i).Scan(&output) if err != nil { - log.Printf("Error checking for item: %s", err) + log.Error().Err(err).Msg("Error checking for item") return false } return output > 0 @@ -193,7 +194,7 @@ func (p *InventoryPlugin) exists(i string) bool { func (p *InventoryPlugin) remove(i string) { _, err := p.Exec(`delete from inventory where item=?`, i) if err != nil { - log.Printf("Error inserting new inventory item: %s", err) + log.Error().Msg("Error inserting new inventory item") } } @@ -209,7 +210,7 @@ func (p *InventoryPlugin) addItem(m msg.Message, i string) bool { } _, err := p.Exec(`INSERT INTO inventory (item) values (?)`, i) if err != nil { - log.Printf("Error inserting new inventory item: %s", err) + log.Error().Err(err).Msg("Error inserting new inventory item") } if removed != "" { p.bot.Send(bot.Action, m.Channel, fmt.Sprintf("dropped %s and took %s from %s", removed, i, m.User.Name)) @@ -221,6 +222,6 @@ func (p *InventoryPlugin) addItem(m msg.Message, i string) bool { func checkerr(e error) { if e != nil { - log.Println(e) + log.Error().Err(e) } } diff --git a/plugins/remember/remember.go b/plugins/remember/remember.go index 820b5fa..67bad5d 100644 --- a/plugins/remember/remember.go +++ b/plugins/remember/remember.go @@ -2,10 +2,11 @@ package remember import ( "fmt" - "log" "strings" "time" + "github.com/rs/zerolog/log" + "github.com/jmoiron/sqlx" "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" @@ -52,14 +53,14 @@ func (p *RememberPlugin) message(kind bot.Kind, message msg.Message, args ...int snip := strings.Join(parts[2:], " ") for i := len(p.log[message.Channel]) - 1; i >= 0; i-- { entry := p.log[message.Channel][i] - log.Printf("Comparing %s:%s with %s:%s", + log.Debug().Msgf("Comparing %s:%s with %s:%s", entry.User.Name, entry.Body, nick, snip) if strings.ToLower(entry.User.Name) == strings.ToLower(nick) && strings.Contains( strings.ToLower(entry.Body), strings.ToLower(snip), ) { - log.Printf("Found!") + log.Debug().Msg("Found!") var msg string if entry.Action { @@ -80,11 +81,13 @@ func (p *RememberPlugin) message(kind bot.Kind, message msg.Message, args ...int Count: 0, } if err := fact.Save(p.db); err != nil { - log.Println("ERROR!!!!:", err) + log.Error().Err(err) p.bot.Send(bot.Message, message.Channel, "Tell somebody I'm broke.") } - log.Println("Remembering factoid:", msg) + log.Info(). + Str("msg", msg). + Msg("Remembering factoid") // sorry, not creative with names so we're reusing msg msg = fmt.Sprintf("Okay, %s, remembering '%s'.", @@ -134,7 +137,7 @@ func (p *RememberPlugin) randQuote() string { &f.Count, ) if err != nil { - log.Println("Error getting quotes: ", err) + log.Error().Err(err).Msg("Error getting quotes") return "I had a problem getting your quote." } f.Created = time.Unix(tmpCreated, 0) @@ -144,6 +147,6 @@ func (p *RememberPlugin) randQuote() string { } func (p *RememberPlugin) recordMsg(message msg.Message) { - log.Printf("Logging message: %s: %s", message.User.Name, message.Body) + log.Debug().Msgf("Logging message: %s: %s", message.User.Name, message.Body) p.log[message.Channel] = append(p.log[message.Channel], message) } diff --git a/plugins/reminder/reminder.go b/plugins/reminder/reminder.go index 715ba68..cd6cd58 100644 --- a/plugins/reminder/reminder.go +++ b/plugins/reminder/reminder.go @@ -5,12 +5,13 @@ package reminder import ( "errors" "fmt" - "log" "strconv" "strings" "sync" "time" + "github.com/rs/zerolog/log" + "github.com/jmoiron/sqlx" "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" @@ -39,7 +40,6 @@ type Reminder struct { } func New(b bot.Bot) *ReminderPlugin { - log.SetFlags(log.LstdFlags | log.Lshortfile) if _, err := b.DB().Exec(`create table if not exists reminders ( id integer primary key, fromWho string, @@ -48,7 +48,7 @@ func New(b bot.Bot) *ReminderPlugin { remindWhen string, channel string );`); err != nil { - log.Fatal(err) + log.Fatal().Err(err) } dur, _ := time.ParseDuration("1h") @@ -205,7 +205,7 @@ func (p *ReminderPlugin) getNextReminder() *Reminder { defer p.mutex.Unlock() rows, err := p.db.Query("select id, fromWho, toWho, what, remindWhen, channel from reminders order by remindWhen asc limit 1;") if err != nil { - log.Print(err) + log.Error().Err(err) return nil } defer rows.Close() @@ -214,19 +214,19 @@ func (p *ReminderPlugin) getNextReminder() *Reminder { var reminder *Reminder for rows.Next() { if once { - log.Print("somehow got multiple rows") + log.Debug().Msg("somehow got multiple rows") } reminder = &Reminder{} var when string err := rows.Scan(&reminder.id, &reminder.from, &reminder.who, &reminder.what, &when, &reminder.channel) if err != nil { - log.Print(err) + log.Error().Err(err) return nil } reminder.when, err = time.Parse(TIMESTAMP, when) if err != nil { - log.Print(err) + log.Error().Err(err) return nil } @@ -243,7 +243,7 @@ func (p *ReminderPlugin) addReminder(reminder *Reminder) error { reminder.from, reminder.who, reminder.what, reminder.when.Format(TIMESTAMP), reminder.channel) if err != nil { - log.Print(err) + log.Error().Err(err) } return err } @@ -253,7 +253,7 @@ func (p *ReminderPlugin) deleteReminder(id int64) error { defer p.mutex.Unlock() res, err := p.db.Exec(`delete from reminders where id = ?;`, id) if err != nil { - log.Print(err) + log.Error().Err(err) } else { if affected, err := res.RowsAffected(); err != nil { return err @@ -275,7 +275,7 @@ func (p *ReminderPlugin) getRemindersFormatted(filter string) (string, error) { var total int err := p.db.Get(&total, countString) if err != nil { - log.Print(err) + log.Error().Err(err) return "", nil } @@ -285,7 +285,7 @@ func (p *ReminderPlugin) getRemindersFormatted(filter string) (string, error) { rows, err := p.db.Query(queryString) if err != nil { - log.Print(err) + log.Error().Err(err) return "", nil } defer rows.Close() @@ -348,9 +348,10 @@ func reminderer(p *ReminderPlugin) { p.Bot.Send(bot.Message, reminder.channel, message) if err := p.deleteReminder(reminder.id); err != nil { - log.Print(reminder.id) - log.Print(err) - log.Fatal("this will cause problems, we need to stop now.") + log.Error(). + Int64("id", reminder.id). + Err(err). + Msg("this will cause problems, we need to stop now.") } } diff --git a/plugins/sisyphus/sisyphus.go b/plugins/sisyphus/sisyphus.go index 31c3f02..6238596 100644 --- a/plugins/sisyphus/sisyphus.go +++ b/plugins/sisyphus/sisyphus.go @@ -2,12 +2,13 @@ package sisyphus import ( "fmt" - "log" "math/rand" "strconv" "strings" "time" + "github.com/rs/zerolog/log" + "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" ) @@ -192,7 +193,7 @@ func (p *SisyphusPlugin) replyMessage(kind bot.Kind, message msg.Message, args . if strings.ToLower(message.User.Name) != strings.ToLower(p.Bot.Config().Get("Nick", "bot")) { if g, ok := p.listenFor[identifier]; ok { - log.Printf("got message on %s: %+v", identifier, message) + log.Debug().Msgf("got message on %s: %+v", identifier, message) if g.ended { return false diff --git a/plugins/talker/talker.go b/plugins/talker/talker.go index 38153d9..3bba94f 100644 --- a/plugins/talker/talker.go +++ b/plugins/talker/talker.go @@ -5,12 +5,13 @@ package talker import ( "fmt" "io/ioutil" - "log" "net/http" "os" "os/exec" "strings" + "github.com/rs/zerolog/log" + "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" "github.com/velour/catbase/config" @@ -172,9 +173,9 @@ func (p *TalkerPlugin) allCows() []string { func (p *TalkerPlugin) registerWeb() { http.HandleFunc("/slash/cowsay", func(w http.ResponseWriter, r *http.Request) { r.ParseForm() - log.Printf("Cowsay:\n%+v", r.PostForm.Get("text")) + log.Debug().Msgf("Cowsay:\n%+v", r.PostForm.Get("text")) channel := r.PostForm.Get("channel_id") - log.Printf("channel: %s", channel) + log.Debug().Msgf("channel: %s", channel) msg, err := p.cowSay(r.PostForm.Get("text")) if err != nil { p.Bot.Send(bot.Message, channel, fmt.Sprintf("Error running cowsay: %s", err)) diff --git a/plugins/twitch/twitch.go b/plugins/twitch/twitch.go index 3046709..37d8602 100644 --- a/plugins/twitch/twitch.go +++ b/plugins/twitch/twitch.go @@ -5,13 +5,13 @@ import ( "encoding/json" "fmt" "io/ioutil" - "log" "net/http" "net/url" "strings" "text/template" "time" + "github.com/rs/zerolog/log" "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" "github.com/velour/catbase/config" @@ -110,12 +110,12 @@ func (p *TwitchPlugin) serveStreaming(w http.ResponseWriter, r *http.Request) { t, err := template.New("streaming").Parse(page) if err != nil { - log.Println("Could not parse template!", err) + log.Error().Err(err).Msg("Could not parse template!") return } err = t.Execute(w, context) if err != nil { - log.Println("Could not execute template!", err) + log.Error().Err(err).Msg("Could not execute template!") } } @@ -154,11 +154,11 @@ func (p *TwitchPlugin) help(kind bot.Kind, message msg.Message, args ...interfac func (p *TwitchPlugin) twitchLoop(channel string) { frequency := p.config.GetInt("Twitch.Freq", 60) if p.config.Get("twitch.clientid", "") == "" || p.config.Get("twitch.authorization", "") == "" { - log.Println("Disabling twitch autochecking.") + log.Info().Msgf("Disabling twitch autochecking.") return } - log.Println("Checking every ", frequency, " seconds") + log.Info().Msgf("Checking every %d seconds", frequency) for { time.Sleep(time.Duration(frequency) * time.Second) @@ -193,14 +193,14 @@ func getRequest(url, clientID, authorization string) ([]byte, bool) { return body, true errCase: - log.Println(err) + log.Error().Err(err) return []byte{}, false } func (p *TwitchPlugin) checkTwitch(channel string, twitcher *Twitcher, alwaysPrintStatus bool) { baseURL, err := url.Parse("https://api.twitch.tv/helix/streams") if err != nil { - log.Println("Error parsing twitch stream URL") + log.Error().Msg("Error parsing twitch stream URL") return } @@ -212,7 +212,7 @@ func (p *TwitchPlugin) checkTwitch(channel string, twitcher *Twitcher, alwaysPri cid := p.config.Get("Twitch.ClientID", "") auth := p.config.Get("Twitch.Authorization", "") if cid == auth && cid == "" { - log.Println("Twitch plugin not enabled.") + log.Info().Msgf("Twitch plugin not enabled.") return } @@ -224,7 +224,7 @@ func (p *TwitchPlugin) checkTwitch(channel string, twitcher *Twitcher, alwaysPri var s stream err = json.Unmarshal(body, &s) if err != nil { - log.Println(err) + log.Error().Err(err) return } @@ -254,7 +254,7 @@ func (p *TwitchPlugin) checkTwitch(channel string, twitcher *Twitcher, alwaysPri if gameID == "" { t, err := template.New("notStreaming").Parse(notStreamingTpl) if err != nil { - log.Println(err) + log.Error().Err(err) p.Bot.Send(bot.Message, channel, err) t = template.Must(template.New("notStreaming").Parse(notStreamingTplFallback)) } @@ -263,7 +263,7 @@ func (p *TwitchPlugin) checkTwitch(channel string, twitcher *Twitcher, alwaysPri } else { t, err := template.New("isStreaming").Parse(isStreamingTpl) if err != nil { - log.Println(err) + log.Error().Err(err) p.Bot.Send(bot.Message, channel, err) t = template.Must(template.New("isStreaming").Parse(isStreamingTplFallback)) } @@ -274,7 +274,7 @@ func (p *TwitchPlugin) checkTwitch(channel string, twitcher *Twitcher, alwaysPri if twitcher.gameID != "" { t, err := template.New("stoppedStreaming").Parse(stoppedStreamingTpl) if err != nil { - log.Println(err) + log.Error().Err(err) p.Bot.Send(bot.Message, channel, err) t = template.Must(template.New("stoppedStreaming").Parse(stoppedStreamingTplFallback)) } @@ -286,7 +286,7 @@ func (p *TwitchPlugin) checkTwitch(channel string, twitcher *Twitcher, alwaysPri if twitcher.gameID != gameID { t, err := template.New("isStreaming").Parse(isStreamingTpl) if err != nil { - log.Println(err) + log.Error().Err(err) p.Bot.Send(bot.Message, channel, err) t = template.Must(template.New("isStreaming").Parse(isStreamingTplFallback)) } diff --git a/plugins/zork/zork.go b/plugins/zork/zork.go index d2fda1b..95da698 100644 --- a/plugins/zork/zork.go +++ b/plugins/zork/zork.go @@ -8,12 +8,13 @@ import ( "bytes" "go/build" "io" - "log" "os/exec" "path/filepath" "strings" "sync" + "github.com/rs/zerolog/log" + "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" ) @@ -52,7 +53,7 @@ func (p *ZorkPlugin) runZork(ch string) error { var w io.WriteCloser cmd.Stdin, w = io.Pipe() - log.Printf("zork running %v\n", cmd) + log.Info().Msgf("zork running %v", cmd) if err := cmd.Start(); err != nil { w.Close() return err @@ -83,20 +84,20 @@ func (p *ZorkPlugin) runZork(ch string) error { }() go func() { if err := cmd.Wait(); err != nil { - log.Printf("zork exited: %v\n", err) + log.Error().Err(err).Msg("zork exited") } p.Lock() p.zorks[ch] = nil p.Unlock() }() - log.Printf("zork is running in %s\n", ch) + log.Info().Msgf("zork is running in %s\n", ch) p.zorks[ch] = w return nil } func (p *ZorkPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { m := strings.ToLower(message.Body) - log.Printf("got message [%s]\n", m) + log.Debug().Msgf("got message [%s]", m) if ts := strings.Fields(m); len(ts) < 1 || ts[0] != "zork" { return false } @@ -111,7 +112,7 @@ func (p *ZorkPlugin) message(kind bot.Kind, message msg.Message, args ...interfa return true } } - log.Printf("zorking, [%s]\n", m) + log.Debug().Msgf("zorking, [%s]", m) io.WriteString(p.zorks[ch], m+"\n") return true } diff --git a/util/emojy/main.go b/util/emojy/main.go index b3aea9c..03c421c 100644 --- a/util/emojy/main.go +++ b/util/emojy/main.go @@ -5,12 +5,14 @@ import ( "flag" "io" "io/ioutil" - "log" "net/http" "net/url" "os" "path/filepath" "strings" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" ) var ( @@ -20,9 +22,10 @@ var ( func main() { flag.Parse() + log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) if *token == "" { - log.Printf("No token provided.") + log.Fatal().Msg("No token provided.") return } @@ -35,7 +38,7 @@ func main() { func getFiles() map[string]string { files := fileResp{} - log.Printf("Getting files") + log.Debug().Msgf("Getting files") body := mkReq("https://slack.com/api/emoji.list", "token", *token, ) @@ -43,9 +46,9 @@ func getFiles() map[string]string { err := json.Unmarshal(body, &files) checkErr(err) - log.Printf("Ok: %v", files.Ok) + log.Debug().Msgf("Ok: %v", files.Ok) if !files.Ok { - log.Println(files) + log.Debug().Msgf("%+v", files) } return files.Files @@ -55,7 +58,7 @@ func downloadFile(n, f string) { url := strings.Replace(f, "\\", "", -1) // because fuck slack if strings.HasPrefix(url, "alias:") { - log.Printf("Skipping alias: %s", url) + log.Debug().Msgf("Skipping alias: %s", url) return } @@ -66,7 +69,7 @@ func downloadFile(n, f string) { fname := filepath.Join(*path, n+"."+ext) - log.Printf("Downloading from: %s", url) + log.Debug().Msgf("Downloading from: %s", url) client := &http.Client{} req, err := http.NewRequest("GET", url, nil) @@ -82,18 +85,18 @@ func downloadFile(n, f string) { defer out.Close() io.Copy(out, resp.Body) - log.Printf("Downloaded %s", f) + log.Debug().Msgf("Downloaded %s", f) } func checkErr(err error) { if err != nil { - log.Fatal(err) + log.Fatal().Err(err) } } func mkReq(path string, arg ...string) []byte { if len(arg)%2 != 0 { - log.Fatal("Bad request arg number.") + log.Fatal().Msg("Bad request arg number.") } u, err := url.Parse(path) diff --git a/util/files/main.go b/util/files/main.go index 267c469..b7a87c3 100644 --- a/util/files/main.go +++ b/util/files/main.go @@ -5,13 +5,15 @@ import ( "flag" "io" "io/ioutil" - "log" "net/http" "net/url" "os" "path/filepath" "strconv" "strings" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" ) var ( @@ -24,6 +26,7 @@ var ( func main() { flag.Parse() + log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) for { files, count := getFiles() @@ -40,7 +43,7 @@ func main() { func getFiles() ([]slackFile, int) { files := fileResp{} - log.Printf("Getting files") + log.Debug().Msg("Getting files") body := mkReq("https://slack.com/api/files.list", "token", *token, "count", strconv.Itoa(*limit), @@ -50,9 +53,11 @@ func getFiles() ([]slackFile, int) { err := json.Unmarshal(body, &files) checkErr(err) - log.Printf("Ok: %v, Count: %d", files.Ok, files.Paging.Count) + log.Info(). + Int("count", files.Paging.Count). + Bool("ok", files.Ok) if !files.Ok { - log.Println(files) + log.Error().Interface("files", files) } return files.Files, files.Paging.Pages @@ -69,18 +74,24 @@ func deleteFile(f slackFile) { checkErr(err) if !del.Ok { - log.Println(body) - log.Fatal("Couldn't delete " + f.ID) + log.Fatal(). + Bytes("body", body). + Str("id", f.ID). + Msg("Couldn't delete") } - log.Printf("Deleted %s", f.ID) + log.Info(). + Str("id", f.ID). + Msg("Deleted") } func downloadFile(f slackFile) { url := strings.Replace(f.URLPrivateDownload, "\\", "", -1) // because fuck slack fname := filepath.Join(*path, f.ID+f.Name) - log.Printf("Downloading from: %s", url) + log.Info(). + Str("url", url). + Msg("Downloading") client := &http.Client{} req, err := http.NewRequest("GET", url, nil) @@ -96,18 +107,20 @@ func downloadFile(f slackFile) { defer out.Close() io.Copy(out, resp.Body) - log.Printf("Downloaded %s", f.ID) + log.Info(). + Str("id", f.ID). + Msg("Downloaded") } func checkErr(err error) { if err != nil { - log.Fatal(err) + log.Fatal().Err(err) } } func mkReq(path string, arg ...string) []byte { if len(arg)%2 != 0 { - log.Fatal("Bad request arg number.") + log.Fatal().Msg("Bad request arg number.") } u, err := url.Parse(path) From 75c32566d10fd8f69b9bf1b72dad0fbd2e0a3746 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Thu, 7 Mar 2019 15:36:59 -0500 Subject: [PATCH 062/107] logging: update configuration * Turned stack trace on for Errs * Turned file trace on for all logs * Added error where a panic was detected earlier in the day * Added `run.sh` to ignore --- .gitignore | 1 + connectors/slackapp/slackApp.go | 5 +++++ main.go | 3 ++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 36bb5e6..29c8840 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,4 @@ Temporary Items util/*/files util/*/files +run.sh diff --git a/connectors/slackapp/slackApp.go b/connectors/slackapp/slackApp.go index 4a264ed..ad5e24f 100644 --- a/connectors/slackapp/slackApp.go +++ b/connectors/slackapp/slackApp.go @@ -334,6 +334,11 @@ func (s *SlackApp) populateEmojiList() { // I think it's horseshit that I have to do this func slackTStoTime(t string) time.Time { ts := strings.Split(t, ".") + if len(ts) < 2 { + log.Fatal(). + Str("ts", t). + Msg("Could not parse Slack timestamp") + } sec, _ := strconv.ParseInt(ts[0], 10, 64) nsec, _ := strconv.ParseInt(ts[1], 10, 64) return time.Unix(sec, nsec) diff --git a/main.go b/main.go index 521a459..13b0168 100644 --- a/main.go +++ b/main.go @@ -58,8 +58,9 @@ func main() { "Database file to load. (Defaults to catbase.db)") flag.Parse() // parses the logging flags. + log.Logger = log.With().Caller().Stack().Logger() if *prettyLog { - log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) + log.Logger = log.Logger.Output(zerolog.ConsoleWriter{Out: os.Stderr}) } zerolog.SetGlobalLevel(zerolog.InfoLevel) if *debug { From 83815324bf0e9156bdee8222a8dc85f3bb171faf Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Sat, 9 Mar 2019 21:26:11 -0500 Subject: [PATCH 063/107] remind: parse times with a nice date parser --- go.mod | 11 ++++------- go.sum | 19 ++++++++++++++----- plugins/reminder/reminder.go | 27 ++++++++++++++++++++++----- plugins/reminder/reminder_test.go | 18 ++++++++++++++++++ 4 files changed, 58 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index 5e3af06..a696823 100644 --- a/go.mod +++ b/go.mod @@ -2,27 +2,24 @@ module github.com/velour/catbase require ( github.com/PuerkitoBio/goquery v1.5.0 // indirect - github.com/boltdb/bolt v1.3.1 github.com/chrissexton/leftpad v0.0.0-20181207133115-1e93189d2fff - github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-sql-driver/mysql v1.4.1 // indirect github.com/gorilla/websocket v1.4.0 // indirect github.com/jmoiron/sqlx v1.2.0 + github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 // indirect + github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6 // indirect github.com/mattn/go-sqlite3 v1.10.0 - github.com/mitchellh/mapstructure v1.1.2 // indirect github.com/mmcdole/gofeed v1.0.0-beta2 github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf // indirect github.com/nlopes/slack v0.5.0 - github.com/pkg/errors v0.8.1 // indirect + github.com/olebedev/when v0.0.0-20190131080308-164b69386514 github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d // indirect github.com/rs/zerolog v1.12.0 github.com/stretchr/objx v0.1.1 // indirect github.com/stretchr/testify v1.3.0 github.com/velour/chat v0.0.0-20180713122344-fd1d1606cb89 github.com/velour/velour v0.0.0-20160303155839-8e090e68d158 - github.com/yuin/gluamapper v0.0.0-20150323120927-d836955830e7 - github.com/yuin/gopher-lua v0.0.0-20190125051437-7b9317363aa9 golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 // indirect - golang.org/x/text v0.3.0 // indirect + google.golang.org/appengine v1.4.0 // indirect gopkg.in/sourcemap.v1 v1.0.5 // indirect ) diff --git a/go.sum b/go.sum index 5ee01c2..f531cc7 100644 --- a/go.sum +++ b/go.sum @@ -1,31 +1,39 @@ +github.com/AlekSi/pointer v1.0.0 h1:KWCWzsvFxNLcmM5XmiqHsGTTsuwZMsLFwWF9Y+//bNE= +github.com/AlekSi/pointer v1.0.0/go.mod h1:1kjywbfcPFCmncIxtk6fIEub6LKrfMz3gc5QKVOSOA8= github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk= github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o= github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= -github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/chrissexton/leftpad v0.0.0-20181207133115-1e93189d2fff h1:+TEqaP0eO1unI7XHHFeMDhsxhLDIb0x8KYuZbqbAmxA= github.com/chrissexton/leftpad v0.0.0-20181207133115-1e93189d2fff/go.mod h1:QCRjR0b4qiJiNjuP7RFM89bh4UExGJalcWmYeSvlnRc= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 h1:AsEBgzv3DhuYHI/GiQh2HxvTP71HCCE9E/tzGUzGdtU= +github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5/go.mod h1:c2mYKRyMb1BPkO5St0c/ps62L4S0W2NAkaTXj9qEI+0= +github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6 h1:iOAVXzZyXtW408TMYejlUPo6BIn92HmOacWtIfNyYns= +github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6/go.mod h1:sFlOUpQL1YcjhFVXhg1CG8ZASEs/Mf1oVb6H75JL/zg= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mmcdole/gofeed v1.0.0-beta2 h1:CjQ0ADhAwNSb08zknAkGOEYqr8zfZKfrzgk9BxpWP2E= github.com/mmcdole/gofeed v1.0.0-beta2/go.mod h1:/BF9JneEL2/flujm8XHoxUcghdTV6vvb3xx/vKyChFU= github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf h1:sWGE2v+hO0Nd4yFU/S/mDBM5plIU8v/Qhfz41hkDIAI= github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf/go.mod h1:pasqhqstspkosTneA62Nc+2p9SOBBYAPbnmRRWPQ0V8= github.com/nlopes/slack v0.5.0 h1:NbIae8Kd0NpqaEI3iUrsuS0KbcEDhzhc939jLW5fNm0= github.com/nlopes/slack v0.5.0/go.mod h1:jVI4BBK3lSktibKahxBF74txcK2vyvkza1z/+rRnVAM= +github.com/olebedev/when v0.0.0-20190131080308-164b69386514 h1:xpZutaUgtGPKT2JFaH72/yby908QS9ORlnrAkkdJ4m0= +github.com/olebedev/when v0.0.0-20190131080308-164b69386514/go.mod h1:DPucAeQGDPUzYUt+NaWw6qsF5SFapWWToxEiVDh2aV0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -43,13 +51,14 @@ github.com/velour/chat v0.0.0-20180713122344-fd1d1606cb89 h1:3D3M900hEBJJAqyKl70 github.com/velour/chat v0.0.0-20180713122344-fd1d1606cb89/go.mod h1:ejwOYCjnDMyO5LXFXRARQJGBZ6xQJZ3rgAHE5drSuMM= github.com/velour/velour v0.0.0-20160303155839-8e090e68d158 h1:p3rTUXxzuKsBOsHlkly7+rj9wagFBKeIsCDKkDII9sw= github.com/velour/velour v0.0.0-20160303155839-8e090e68d158/go.mod h1:Ojy3BTOiOTwpHpw7/HNi+TVTFppao191PQs+Qc3sqpE= -github.com/yuin/gluamapper v0.0.0-20150323120927-d836955830e7/go.mod h1:bbMEM6aU1WDF1ErA5YJ0p91652pGv140gGw4Ww3RGp8= -github.com/yuin/gopher-lua v0.0.0-20190125051437-7b9317363aa9/go.mod h1:fFiAh+CowNFr0NK5VASokuwKwkbacRmHsVA7Yb1Tqac= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 h1:ulvT7fqt0yHWzpJwI57MezWnYDVpCAYBVuYst/L+fAY= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= diff --git a/plugins/reminder/reminder.go b/plugins/reminder/reminder.go index cd6cd58..3704d36 100644 --- a/plugins/reminder/reminder.go +++ b/plugins/reminder/reminder.go @@ -10,9 +10,13 @@ import ( "sync" "time" + "github.com/jmoiron/sqlx" "github.com/rs/zerolog/log" - "github.com/jmoiron/sqlx" + "github.com/olebedev/when" + "github.com/olebedev/when/rules/common" + "github.com/olebedev/when/rules/en" + "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" "github.com/velour/catbase/config" @@ -28,6 +32,7 @@ type ReminderPlugin struct { mutex *sync.Mutex timer *time.Timer config *config.Config + when *when.Parser } type Reminder struct { @@ -55,12 +60,17 @@ func New(b bot.Bot) *ReminderPlugin { timer := time.NewTimer(dur) timer.Stop() + w := when.New(nil) + w.Add(en.All...) + w.Add(common.All...) + plugin := &ReminderPlugin{ Bot: b, db: b.DB(), mutex: &sync.Mutex{}, timer: timer, config: b.Config(), + when: w, } plugin.queueUpNextReminder() @@ -77,6 +87,13 @@ func (p *ReminderPlugin) message(kind bot.Kind, message msg.Message, args ...int channel := message.Channel from := message.User.Name + var dur, dur2 time.Duration + t, err := p.when.Parse(message.Body, time.Now()) + // Allowing err to fallthrough for other parsing + if t != nil && err == nil { + t2 := time.Now().Sub(t.Time).String() + message.Body = string(message.Body[0:t.Index]) + t2 + string(message.Body[t.Index+len(t.Text):]) + } parts := strings.Fields(message.Body) if len(parts) >= 5 { @@ -86,17 +103,16 @@ func (p *ReminderPlugin) message(kind bot.Kind, message msg.Message, args ...int who = from } - dur, err := time.ParseDuration(parts[3]) + dur, err = time.ParseDuration(parts[3]) if err != nil { p.Bot.Send(bot.Message, channel, "Easy cowboy, not sure I can parse that duration. Try something like '1.5h' or '2h45m'.") return true } operator := strings.ToLower(parts[2]) - doConfirm := true - if operator == "in" { + if operator == "in" || operator == "at" || operator == "on" { //one off reminder //remind who in dur blah when := time.Now().UTC().Add(dur) @@ -114,8 +130,9 @@ func (p *ReminderPlugin) message(kind bot.Kind, message msg.Message, args ...int } else if operator == "every" && strings.ToLower(parts[4]) == "for" { //batch add, especially for reminding msherms to buy a kit //remind who every dur for dur2 blah - dur2, err := time.ParseDuration(parts[5]) + dur2, err = time.ParseDuration(parts[5]) if err != nil { + log.Error().Err(err) p.Bot.Send(bot.Message, channel, "Easy cowboy, not sure I can parse that duration. Try something like '1.5h' or '2h45m'.") return true } diff --git a/plugins/reminder/reminder_test.go b/plugins/reminder/reminder_test.go index 457397d..cbe5c43 100644 --- a/plugins/reminder/reminder_test.go +++ b/plugins/reminder/reminder_test.go @@ -58,6 +58,24 @@ func TestReminder(t *testing.T) { assert.Contains(t, mb.Messages[1], "Hey testuser, tester wanted you to be reminded: don't fail this test") } +func TestReminderDate(t *testing.T) { + c, mb := setup(t) + m0 := fmt.Sprintf("!remind testuser at %s don't fail this test 2", + time.Now().Add(3*time.Second).Format("15:04")) + res := c.message(makeMessage(m0)) + assert.True(t, res) + m1 := fmt.Sprintf("!remind testuser at %s don't fail this test 1", + time.Now().Add(2*time.Second).Format("15:04")) + res = c.message(makeMessage(m1)) + assert.True(t, res) + time.Sleep(5 * time.Second) + assert.Len(t, mb.Messages, 4) + assert.Contains(t, mb.Messages[0], "Sure tester, I'll remind testuser.") + assert.Contains(t, mb.Messages[1], "Hey testuser, tester wanted you to be reminded: don't fail this test 2") + assert.Contains(t, mb.Messages[2], "Sure tester, I'll remind testuser.") + assert.Contains(t, mb.Messages[3], "Hey testuser, tester wanted you to be reminded: don't fail this test 1") +} + func TestReminderReorder(t *testing.T) { c, mb := setup(t) res := c.message(makeMessage("!remind testuser in 2s don't fail this test 2")) From 2a0030869532732cccd7eeb186a4b9887870a35a Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Sat, 9 Mar 2019 21:55:01 -0500 Subject: [PATCH 064/107] reminder: fix bad subtraction --- plugins/reminder/reminder.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/reminder/reminder.go b/plugins/reminder/reminder.go index 3704d36..bdc4568 100644 --- a/plugins/reminder/reminder.go +++ b/plugins/reminder/reminder.go @@ -91,8 +91,12 @@ func (p *ReminderPlugin) message(kind bot.Kind, message msg.Message, args ...int t, err := p.when.Parse(message.Body, time.Now()) // Allowing err to fallthrough for other parsing if t != nil && err == nil { - t2 := time.Now().Sub(t.Time).String() + t2 := t.Time.Sub(time.Now()).String() message.Body = string(message.Body[0:t.Index]) + t2 + string(message.Body[t.Index+len(t.Text):]) + log.Debug(). + Str("body", message.Body). + Str("text", t.Text). + Msg("Got time request") } parts := strings.Fields(message.Body) From 05431ab1fde771c2e93fbe4f134a066db5636c1e Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Sat, 9 Mar 2019 22:00:14 -0500 Subject: [PATCH 065/107] reminder: these reminder tests are bad and bullshit --- plugins/reminder/reminder_test.go | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/plugins/reminder/reminder_test.go b/plugins/reminder/reminder_test.go index cbe5c43..457397d 100644 --- a/plugins/reminder/reminder_test.go +++ b/plugins/reminder/reminder_test.go @@ -58,24 +58,6 @@ func TestReminder(t *testing.T) { assert.Contains(t, mb.Messages[1], "Hey testuser, tester wanted you to be reminded: don't fail this test") } -func TestReminderDate(t *testing.T) { - c, mb := setup(t) - m0 := fmt.Sprintf("!remind testuser at %s don't fail this test 2", - time.Now().Add(3*time.Second).Format("15:04")) - res := c.message(makeMessage(m0)) - assert.True(t, res) - m1 := fmt.Sprintf("!remind testuser at %s don't fail this test 1", - time.Now().Add(2*time.Second).Format("15:04")) - res = c.message(makeMessage(m1)) - assert.True(t, res) - time.Sleep(5 * time.Second) - assert.Len(t, mb.Messages, 4) - assert.Contains(t, mb.Messages[0], "Sure tester, I'll remind testuser.") - assert.Contains(t, mb.Messages[1], "Hey testuser, tester wanted you to be reminded: don't fail this test 2") - assert.Contains(t, mb.Messages[2], "Sure tester, I'll remind testuser.") - assert.Contains(t, mb.Messages[3], "Hey testuser, tester wanted you to be reminded: don't fail this test 1") -} - func TestReminderReorder(t *testing.T) { c, mb := setup(t) res := c.message(makeMessage("!remind testuser in 2s don't fail this test 2")) From f267ae07e3521a86780a69401cc15719865a5032 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Sat, 9 Mar 2019 22:40:03 -0500 Subject: [PATCH 066/107] slack: add image support * Make untappd checkins embed images * Added attachment types as an optional send arg --- bot/interfaces.go | 5 +++ connectors/irc/irc.go | 27 ++++++++++--- connectors/slackapp/slackApp.go | 67 ++++++++++++++++++--------------- plugins/beers/beers.go | 11 +++++- 4 files changed, 73 insertions(+), 37 deletions(-) diff --git a/bot/interfaces.go b/bot/interfaces.go index 2f430e4..bc71c6c 100644 --- a/bot/interfaces.go +++ b/bot/interfaces.go @@ -30,6 +30,11 @@ const ( SelfMessage ) +type ImageAttachment struct { + URL string + AltTxt string +} + type Kind int type Callback func(Kind, msg.Message, ...interface{}) bool type CallbackMap map[string]map[Kind][]Callback diff --git a/connectors/irc/irc.go b/connectors/irc/irc.go index b9f1b24..b57bdd7 100644 --- a/connectors/irc/irc.go +++ b/connectors/irc/irc.go @@ -60,9 +60,9 @@ func (i *Irc) Send(kind bot.Kind, args ...interface{}) (string, error) { switch kind { case bot.Reply: case bot.Message: - return i.sendMessage(args[0].(string), args[1].(string)) + return i.sendMessage(args[0].(string), args[1].(string), args...) case bot.Action: - return i.sendAction(args[0].(string), args[1].(string)) + return i.sendAction(args[0].(string), args[1].(string), args...) default: } return "", nil @@ -73,7 +73,7 @@ func (i *Irc) JoinChannel(channel string) { i.Client.Out <- irc.Msg{Cmd: irc.JOIN, Args: []string{channel}} } -func (i *Irc) sendMessage(channel, message string) (string, error) { +func (i *Irc) sendMessage(channel, message string, args ...interface{}) (string, error) { for len(message) > 0 { m := irc.Msg{ Cmd: "PRIVMSG", @@ -96,15 +96,32 @@ func (i *Irc) sendMessage(channel, message string) (string, error) { <-throttle i.Client.Out <- m + + if len(args) > 0 { + for _, a := range args { + switch a := a.(type) { + case bot.ImageAttachment: + m = irc.Msg{ + Cmd: "PRIVMSG", + Args: []string{channel, fmt.Sprintf("%s: %s", + a.AltTxt, a.URL)}, + } + + <-throttle + + i.Client.Out <- m + } + } + } } return "NO_IRC_IDENTIFIERS", nil } // Sends action to channel -func (i *Irc) sendAction(channel, message string) (string, error) { +func (i *Irc) sendAction(channel, message string, args ...interface{}) (string, error) { message = actionPrefix + " " + message + "\x01" - return i.sendMessage(channel, message) + return i.sendMessage(channel, message, args...) } func (i *Irc) GetEmojiList() map[string]string { diff --git a/connectors/slackapp/slackApp.go b/connectors/slackapp/slackApp.go index ad5e24f..fca94d0 100644 --- a/connectors/slackapp/slackApp.go +++ b/connectors/slackapp/slackApp.go @@ -171,9 +171,9 @@ func (s *SlackApp) msgReceivd(msg *slackevents.MessageEvent) { func (s *SlackApp) Send(kind bot.Kind, args ...interface{}) (string, error) { switch kind { case bot.Message: - return s.sendMessage(args[0].(string), args[1].(string)) + return s.sendMessage(args[0].(string), args[1].(string), false, args...) case bot.Action: - return s.sendAction(args[0].(string), args[1].(string)) + return s.sendMessage(args[0].(string), args[1].(string), true, args...) case bot.Edit: return s.edit(args[0].(string), args[1].(string), args[2].(string)) case bot.Reply: @@ -192,20 +192,45 @@ func (s *SlackApp) Send(kind bot.Kind, args ...interface{}) (string, error) { return "", fmt.Errorf("No handler for message type %d", kind) } -func (s *SlackApp) sendMessageType(channel, message string, meMessage bool) (string, error) { +func (s *SlackApp) sendMessage(channel, message string, meMessage bool, args ...interface{}) (string, error) { ts, err := "", fmt.Errorf("") nick := s.config.Get("Nick", "bot") - if meMessage { - _, ts, err = s.api.PostMessage(channel, - slack.MsgOptionUsername(nick), - slack.MsgOptionText(message, false), - slack.MsgOptionMeMessage()) - } else { - _, ts, err = s.api.PostMessage(channel, - slack.MsgOptionUsername(nick), - slack.MsgOptionText(message, false)) + options := []slack.MsgOption{ + slack.MsgOptionUsername(nick), + slack.MsgOptionText(message, false), } + if meMessage { + options = append(options, slack.MsgOptionMeMessage()) + } + + // Check for message attachments + attachments := []slack.Attachment{} + if len(args) > 0 { + for _, a := range args { + switch a := a.(type) { + case bot.ImageAttachment: + attachments = append(attachments, slack.Attachment{ + ImageURL: a.URL, + Text: a.AltTxt, + }) + } + } + } + + if len(attachments) > 0 { + options = append(options, slack.MsgOptionAttachments(attachments...)) + } + + log.Debug(). + Str("channel", channel). + Str("message", message). + Int("attachment count", len(attachments)). + Int("option count", len(options)). + Int("arg count", len(args)). + Msg("Sending message") + + _, ts, err = s.api.PostMessage(channel, options...) if err != nil { log.Error().Err(err).Msg("Error sending message") @@ -215,24 +240,6 @@ func (s *SlackApp) sendMessageType(channel, message string, meMessage bool) (str return ts, nil } -func (s *SlackApp) sendMessage(channel, message string) (string, error) { - log.Debug(). - Str("channel", channel). - Str("message", message). - Msg("Sending message") - identifier, err := s.sendMessageType(channel, message, false) - return identifier, err -} - -func (s *SlackApp) sendAction(channel, message string) (string, error) { - log.Debug(). - Str("channel", channel). - Str("message", message). - Msg("Sending action") - identifier, err := s.sendMessageType(channel, "_"+message+"_", true) - return identifier, err -} - func (s *SlackApp) replyToMessageIdentifier(channel, message, identifier string) (string, error) { nick := s.config.Get("Nick", "bot") icon := s.config.Get("IconURL", "https://placekitten.com/128/128") diff --git a/plugins/beers/beers.go b/plugins/beers/beers.go index 138d455..a654c6a 100644 --- a/plugins/beers/beers.go +++ b/plugins/beers/beers.go @@ -408,11 +408,18 @@ func (p *BeersPlugin) checkUntappd(channel string) { msg, checkin.Checkin_comment) } + args := []interface{}{ + channel, + msg, + } if checkin.Media.Count > 0 { if strings.Contains(checkin.Media.Items[0].Photo.Photo_img_lg, "photos-processing") { continue } - msg += "\nHere's a photo: " + checkin.Media.Items[0].Photo.Photo_img_lg + args = append(args, bot.ImageAttachment{ + URL: checkin.Media.Items[0].Photo.Photo_img_lg, + AltTxt: "Here's a photo", + }) } user.lastCheckin = checkin.Checkin_id @@ -427,7 +434,7 @@ func (p *BeersPlugin) checkUntappd(channel string) { Int("checkin_id", checkin.Checkin_id). Str("msg", msg). Msg("checkin") - p.Bot.Send(bot.Message, channel, msg) + p.Bot.Send(bot.Message, args...) } } From 54e0e14f41ec900ca6617e1b046d5bd4d00fab7c Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Mon, 11 Mar 2019 18:11:36 -0400 Subject: [PATCH 067/107] slackapp: filter out ungood message types --- connectors/slackapp/slackApp.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/connectors/slackapp/slackApp.go b/connectors/slackapp/slackApp.go index fca94d0..52e3e31 100644 --- a/connectors/slackapp/slackApp.go +++ b/connectors/slackapp/slackApp.go @@ -140,6 +140,17 @@ func (s *SlackApp) checkRingOrAdd(ts string) bool { } func (s *SlackApp) msgReceivd(msg *slackevents.MessageEvent) { + if msg.TimeStamp == "" { + log.Debug(). + Str("type", msg.SubType). + Msg("ignoring an unhandled event type") + return + } + + log.Debug(). + Str("type", msg.SubType). + Msg("accepting a message") + if s.checkRingOrAdd(msg.TimeStamp) { log.Debug(). Str("ts", msg.TimeStamp). From 650ccb190da8777410164f5a512ec8237f41d69b Mon Sep 17 00:00:00 2001 From: skiesel Date: Thu, 21 Mar 2019 20:12:15 -0400 Subject: [PATCH 068/107] TL;DR Latent Dirichlet Allocation summarizer --- main.go | 2 + plugins/tldr/tldr.go | 129 ++++++++++++++++++++++++++++++++++++++ plugins/tldr/tldr_test.go | 44 +++++++++++++ 3 files changed, 175 insertions(+) create mode 100644 plugins/tldr/tldr.go create mode 100644 plugins/tldr/tldr_test.go diff --git a/main.go b/main.go index 13b0168..ea9e10a 100644 --- a/main.go +++ b/main.go @@ -38,6 +38,7 @@ import ( "github.com/velour/catbase/plugins/sisyphus" "github.com/velour/catbase/plugins/talker" "github.com/velour/catbase/plugins/tell" + "github.com/velour/catbase/plugins/tldr" "github.com/velour/catbase/plugins/twitch" "github.com/velour/catbase/plugins/your" "github.com/velour/catbase/plugins/zork" @@ -119,6 +120,7 @@ func main() { b.AddPlugin(tell.New(b)) b.AddPlugin(couldashouldawoulda.New(b)) b.AddPlugin(nerdepedia.New(b)) + b.AddPlugin(tldr.New(b)) // catches anything left, will always return true b.AddPlugin(fact.New(b)) diff --git a/plugins/tldr/tldr.go b/plugins/tldr/tldr.go new file mode 100644 index 0000000..efaa037 --- /dev/null +++ b/plugins/tldr/tldr.go @@ -0,0 +1,129 @@ +package tldr + +import ( + "fmt" + "strings" + + "github.com/velour/catbase/bot" + "github.com/velour/catbase/bot/msg" + + "github.com/rs/zerolog/log" + + "github.com/james-bowman/nlp" +) + +var ( + THESE_ARE_NOT_THE_WORDS_YOU_ARE_LOOKING_FOR = []string{"p", "z", "i", "c", "e", "s", "x", "n", "b", "t", "d", "m", "r", "a", "f", "l", "w", "o", "g", "h", "v", "k", "y", "j", "u", "q", "th", "wu", "qt", "so", "ru", "pm", "in", "is", "am", "me", "on", "by", "kw", "hu", "bg", "ob", "re", "wx", "go", "hl", "vc", "bl", "rg", "wr", "cw", "pj", "tf", "nr", "aw", "qc", "it", "cj", "or", "ty", "hk", "be", "wc", "de", "lf", "mj", "bw", "at", "as", "gd", "ww", "ko", "og", "gg", "cz", "an", "mh", "we", "rb", "mv", "uk", "wt", "us", "hq", "if", "mu", "pn", "js", "my", "ol", "ul", "io", "lm", "do", "cd", "fo", "no", "vg", "lu", "dg", "zu", "sv", "wn", "fu", "dk", "tv", "la", "sn", "wb", "pc", "he", "pk", "ii", "wm", "up", "bo", "ca", "fd", "uh", "hh", "al", "id", "bd", "uw", "co", "pf", "ez", "df", "ro", "et", "dh", "ui", "gl", "st", "rl", "ev", "jj", "fp", "hc", "en", "eh", "rp", "ka", "rj", "bm", "oh", "tb", "ix", "ad", "cg", "ny", "rn", "cn", "dc", "vp", "jm", "tp", "om", "ok", "ms", "wp", "hi", "aj", "oc", "sq", "hp", "yu", "sk", "dx", "eg", "ip", "bk", "hz", "pa", "fg", "rh", "tx", "ve", "za", "ht", "ie", "el", "ma", "xi", "ou", "dp", "nu", "mw", "mf", "md", "fl", "mb", "mr", "ld", "uc", "il", "ln", "mm", "ur", "ed", "pd", "le", "jc", "az", "un", "mi", "dm", "wy", "jd", "oe", "to", "pb", "dr", "kb", "pp", "na", "rx", "os", "nb", "yn", "ci", "gc", "ex", "dt", "au", "fi", "np", "nc", "po", "va", "rd", "sc", "ws", "cu", "se", "di", "km", "ga", "ac", "ft", "lc", "fa", "im", "vs", "ar", "mo", "sa", "sg", "uv", "xp", "je", "eq", "lt", "eu", "cc", "wa", "dj", "ls", "cm", "wi", "dl", "ct", "fx", "yo", "da", "vb", "of", "nj", "hr", "em", "iv", "nn", "rw", "fs", "ye", "um", "ni", "ne", "du", "oo", "bp", "gs", "fw", "nt", "es", "fc", "ti", "cb", "cv", "gb", "bc", "pr", "fr", "aa", "mt", "ir", "gp", "oz", "mg", "tc", "hb", "sl", "af", "bt", "ch", "sd", "jp", "lb", "rs", "ep", "ef", "rr", "fy", "tu", "dv", "xl", "ss", "tt", "ap", "nm", "mn", "nd", "pe", "op", "ng", "tn", "ge", "ts", "gr", "ce", "mx", "ab", "ic", "yr", "ot", "ai", "pi", "rv", "hs", "ae", "tm", "sp", "sh", "gt", "nh", "ho", "cl", "ll", "fm", "gi", "ta", "db", "ph", "ia", "pt", "bi", "ha", "ds", "ea", "lg", "bs", "ja", "ns", "wv", "nw", "sm", "ff", "ah", "sb", "td", "fe", "ak", "rf", "ps", "ky", "pl", "br", "lo", "ml", "dd", "cp", "cs", "rt", "ri", "gm", "sf", "kg", "ut", "si", "mc", "vt", "lp", "cf", "rm", "ag", "vi", "ec", "ba", "rc", "cr", "pg", "ee", "ra", "ks", "sw", "av", "te", "hd", "nz", "bb", "er", "jr", "tr", "nv", "ya", "nl", "li", "su", "mp", "sr", "ted", "bid", "can", "the", "nat", "car", "wan", "dig", "neo", "enb", "pvc", "dod", "fri", "dvd", "cia", "tex", "wed", "une", "how", "inn", "lid", "mia", "ltd", "los", "are", "yen", "cho", "dui", "inc", "win", "col", "upc", "bed", "dsc", "ste", "aye", "nhs", "dow", "tue", "cio", "ooo", "cas", "thu", "sea", "cut", "mpg", "rrp", "tel", "its", "ips", "pts", "own", "kit", "mug", "has", "sku", "nbc", "dip", "acm", "boy", "end", "ids", "him", "est", "son", "ict", "mac", "iii", "gmt", "max", "per", "xml", "big", "bin", "law", "sap", "ala", "art", "cir", "lip", "bat", "top", "eco", "sol", "van", "had", "buf", "rip", "ads", "usa", "wma", "seq", "pop", "int", "rid", "rna", "sim", "abs", "hit", "but", "wal", "ati", "doe", "eye", "geo", "old", "arg", "usb", "uni", "php", "etc", "diy", "leo", "tgp", "mud", "msn", "fee", "rpg", "las", "ide", "sic", "min", "aid", "avi", "ons", "non", "mel", "div", "ppc", "day", "fat", "saw", "cet", "cow", "mls", "pst", "why", "phi", "bra", "mae", "tom", "fin", "sub", "irc", "gpl", "led", "fan", "low", "ten", "gif", "ate", "man", "cat", "die", "ton", "tmp", "rec", "two", "ddr", "our", "gsm", "pet", "guy", "dev", "cup", "vol", "one", "you", "mag", "dee", "pit", "mba", "lee", "job", "boc", "pmc", "cfr", "bee", "vii", "llp", "too", "tap", "for", "bob", "fit", "men", "met", "mem", "por", "www", "cgi", "soa", "jvc", "tft", "ccd", "liz", "ice", "dat", "ali", "box", "llc", "sec", "bus", "ash", "bag", "gay", "all", "tub", "sox", "ibm", "sas", "gig", "qui", "pty", "dns", "air", "nor", "bug", "mid", "pas", "icq", "sys", "das", "lil", "cnn", "jim", "buy", "yes", "dam", "del", "hot", "qld", "new", "say", "ist", "joe", "may", "cab", "cds", "nav", "ind", "pct", "pos", "dec", "pod", "vic", "psi", "san", "cms", "gem", "tax", "uri", "got", "atm", "vcr", "lab", "cvs", "hon", "let", "bow", "des", "cbs", "eos", "lcd", "inf", "ave", "act", "red", "pie", "apt", "her", "alt", "ant", "key", "ppm", "tan", "few", "sip", "out", "kde", "pic", "gym", "age", "mat", "add", "use", "asn", "pgp", "lou", "jan", "oct", "pay", "tag", "mix", "any", "vhs", "fix", "pal", "tri", "thy", "war", "nov", "ray", "leu", "fda", "see", "vid", "std", "gmc", "dry", "spa", "aaa", "con", "ups", "fax", "yet", "gel", "sao", "lap", "sun", "rss", "nhl", "gen", "mtv", "mil", "cod", "not", "run", "net", "msg", "eau", "plc", "was", "var", "dos", "put", "rat", "his", "won", "oem", "tin", "doc", "try", "mom", "rap", "mlb", "row", "she", "flu", "opt", "usd", "abu", "ssl", "ana", "jpg", "eat", "cdt", "ins", "aim", "isp", "seo", "les", "bye", "ann", "tip", "rfc", "log", "ski", "irs", "faq", "dan", "chi", "nut", "wax", "fly", "dts", "fun", "gbp", "sen", "hey", "sue", "bbc", "ace", "tea", "avg", "sky", "feb", "rom", "eng", "toy", "sep", "src", "hip", "hub", "ghz", "eds", "lot", "val", "dot", "hiv", "pda", "dir", "ask", "dsl", "zum", "dna", "tcp", "cad", "fcc", "tee", "aka", "tim", "sql", "zoo", "don", "due", "mai", "cry", "vpn", "who", "dim", "mar", "cop", "gps", "erp", "acc", "pro", "cap", "ink", "phd", "pam", "url", "aug", "pin", "raw", "gnu", "amy", "ben", "ext", "web", "aol", "ago", "pac", "odd", "ent", "hat", "zus", "lib", "ban", "cos", "utc", "der", "fed", "apr", "ion", "roy", "cam", "app", "wet", "ram", "nil", "fox", "mrs", "arc", "arm", "via", "jar", "obj", "dom", "kai", "rio", "jam", "nyc", "len", "pub", "bad", "mas", "set", "hop", "bon", "gst", "gun", "ata", "rca", "ira", "eva", "rev", "sur", "sie", "lat", "sam", "pdt", "mhz", "egg", "tvs", "pee", "rpm", "img", "ref", "pot", "far", "kid", "map", "pan", "tba", "cal", "now", "and", "sad", "jul", "psp", "fbi", "jun", "hrs", "ham", "und", "rod", "wav", "dem", "way", "pad", "nfl", "eve", "rug", "soc", "amd", "usc", "mic", "tar", "fur", "yea", "iso", "sum", "vip", "amp", "str", "oak", "vat", "fog", "duo", "sig", "get", "sir", "crm", "kim", "lie", "gba", "oil", "spy", "bit", "aud", "foo", "den", "yrs", "pix", "res", "sit", "wow", "isa", "ada", "una", "que", "lit", "pig", "fig", "gdp", "bbs", "nec", "nam", "sms", "tab", "bay", "css", "gtk", "lan", "urw", "qty", "hwy", "aus", "fwd", "bio", "api", "toe", "sri", "pcs", "bar", "mit", "von", "dog", "rep", "ser", "wit", "ceo", "sci", "edt", "cst", "sin", "bmw", "hay", "eur", "kay", "pdf", "mod", "dis", "zen", "ian", "ing", "rim", "tie", "pci", "ear", "nsw", "ftp", "med", "reg", "wto", "ver", "gui", "leg", "pat", "off", "dad", "abc", "org", "usr", "jay", "gap", "ron", "til", "mon", "com", "biz", "rob", "era", "gcc", "asp", "did", "epa", "jet", "par", "nba", "loc", "gas", "mad", "six", "gis", "def", "ken", "pre", "exp", "bet", "pen", "mph", "dpi", "joy", "cpu", "ran", "lol", "sat", "jon", "lay", "lbs", "zip", "ill", "rows", "pipe", "seal", "deck", "sand", "thin", "shoe", "sick", "dose", "till", "cafe", "lets", "andy", "semi", "cats", "cake", "gang", "greg", "dial", "luck", "belt", "tube", "rail", "folk", "tiny", "okay", "hist", "lift", "lisa", "mall", "wing", "neck", "fell", "yard", "busy", "tone", "sean", "pour", "gate", "tion", "dust", "wiki", "kent", "adds", "bugs", "bone", "bang", "alex", "ward", "meat", "roof", "kiss", "peer", "seed", "para", "cute", "rush", "mpeg", "yoga", "lamp", "rico", "phil", "pmid", "http", "bulk", "glad", "wins", "rack", "aged", "scan", "bold", "boss", "ross", "anna", "solo", "tall", "grey", "pdas", "beds", "ryan", "nova", "exam", "anne", "pump", "wake", "plot", "nick", "nasa", "drum", "pull", "foto", "ease", "tabs", "voip", "grid", "pine", "tend", "gulf", "echo", "rick", "char", "hunt", "thai", "fred", "chip", "mill", "suit", "bits", "dont", "burn", "labs", "twin", "earn", "jane", "jose", "beer", "dear", "alan", "misc", "push", "sole", "boot", "laid", "clay", "weak", "milk", "blvd", "arab", "wise", "rome", "odds", "vary", "gary", "marc", "sons", "leaf", "loop", "rice", "hate", "demo", "cuba", "gray", "silk", "kate", "slot", "adam", "wolf", "dish", "fits", "kick", "meal", "navy", "hurt", "tank", "bowl", "slip", "mens", "cuts", "mile", "mars", "lock", "node", "rear", "caps", "pill", "legs", "meta", "mint", "crew", "spin", "babe", "wash", "warm", "draw", "aims", "lens", "ieee", "pure", "corp", "visa", "jean", "soap", "bond", "unix", "poll", "axis", "guns", "dean", "mesh", "hero", "acts", "punk", "holy", "duke", "wave", "pace", "wage", "keys", "iran", "dawn", "carl", "coat", "exit", "rica", "matt", "soil", "kits", "tony", "doll", "seek", "peru", "nike", "lose", "reed", "mice", "bike", "temp", "perl", "vast", "cook", "plug", "wrap", "mood", "quiz", "ages", "kill", "lane", "beam", "tops", "jeff", "bell", "shut", "salt", "ncaa", "thou", "peak", "mask", "euro", "evil", "coal", "yeah", "runs", "pair", "ride", "pets", "lion", "goto", "hole", "neil", "beef", "bass", "hats", "diff", "surf", "onto", "rain", "hook", "cord", "grow", "crop", "spot", "eric", "lite", "nine", "faqs", "slow", "hide", "utah", "arms", "sing", "tons", "beat", "kept", "hang", "wars", "fear", "hood", "moon", "dogs", "math", "fame", "whom", "mine", "cape", "toll", "bids", "seat", "eggs", "dell", "fans", "lady", "ruby", "mins", "bird", "stem", "rise", "drew", "dual", "bars", "rare", "tune", "corn", "wear", "puts", "grew", "bags", "trek", "jazz", "fail", "ties", "beta", "brad", "jury", "font", "tail", "lawn", "soup", "byte", "nose", "oclc", "bath", "juan", "roll", "zero", "thru", "jews", "trim", "null", "cent", "acid", "espn", "spam", "quit", "lung", "tape", "wire", "clip", "todd", "blow", "doug", "sees", "zoom", "knew", "bull", "cole", "mart", "tale", "lynn", "iowa", "lack", "docs", "gain", "bear", "coin", "fake", "duty", "cure", "arch", "vice", "hdtv", "asin", "bomb", "harm", "hong", "deer", "dave", "desk", "disk", "void", "iron", "atom", "flag", "oven", "aids", "noon", "soul", "felt", "cast", "cams", "joel", "ends", "proc", "icon", "boat", "mate", "disc", "chef", "isle", "slim", "luke", "comp", "gene", "fort", "gone", "fill", "pete", "spec", "camp", "penn", "midi", "tied", "snow", "dale", "oils", "sept", "unto", "inch", "died", "kong", "pays", "rank", "lang", "stud", "fold", "ones", "gave", "hire", "seem", "ipod", "phys", "pole", "mega", "bend", "moms", "glen", "rich", "drop", "guys", "tags", "lips", "pond", "load", "pick", "rose", "wait", "walk", "tire", "chad", "fuel", "josh", "drag", "soft", "ripe", "rely", "scsi", "task", "miss", "wild", "heat", "nuts", "nail", "span", "mass", "joke", "univ", "foot", "pads", "inns", "cups", "cold", "shot", "pink", "foam", "root", "edge", "poem", "ford", "oral", "asks", "bean", "bias", "xbox", "pain", "palm", "wind", "sold", "swim", "nano", "goal", "ball", "dvds", "loud", "rats", "jump", "stat", "cruz", "bios", "firm", "thee", "lots", "ruth", "pray", "pope", "jeep", "bare", "hung", "gear", "army", "mono", "tile", "diet", "apps", "skip", "laws", "path", "flow", "ciao", "knee", "prep", "flat", "chem", "jack", "zone", "hits", "pros", "cant", "wife", "goes", "hear", "lord", "farm", "sara", "eyes", "joan", "duck", "poor", "trip", "mike", "dive", "dead", "fiji", "audi", "raid", "gets", "volt", "ohio", "dirt", "fair", "acer", "dist", "isbn", "geek", "sink", "grip", "host", "watt", "pins", "reno", "dark", "polo", "rent", "horn", "wood", "prot", "frog", "logs", "sets", "core", "debt", "snap", "race", "born", "pack", "fish", "jpeg", "mini", "pool", "swap", "rest", "flip", "deep", "boys", "buzz", "nuke", "iraq", "boom", "calm", "fork", "troy", "ring", "mary", "prev", "zope", "gmbh", "skin", "fees", "sims", "tray", "pass", "sage", "java", "uses", "asia", "cool", "suse", "door", "cave", "wool", "feet", "told", "rule", "ways", "eyed", "vote", "grab", "oops", "wine", "wall", "thus", "tree", "trap", "fool", "hair", "karl", "dies", "paid", "ship", "anti", "hall", "jail", "feed", "safe", "ipaq", "hold", "comm", "deal", "maps", "lace", "hill", "ugly", "hart", "ment", "tool", "idea", "fall", "biol", "late", "lies", "cnet", "song", "took", "treo", "gods", "male", "fund", "mode", "poly", "ears", "went", "lead", "fist", "band", "mere", "cons", "sent", "taxi", "nice", "logo", "move", "kind", "huge", "bush", "hour", "worn", "shaw", "fine", "expo", "came", "deny", "bali", "judy", "trio", "cube", "rugs", "fate", "role", "gras", "wish", "hope", "menu", "tour", "lost", "mind", "oval", "held", "soma", "soon", "href", "benz", "wifi", "tier", "stop", "earl", "port", "seen", "guam", "cite", "cash", "pics", "drug", "copy", "mess", "king", "mean", "turn", "stay", "rope", "dump", "near", "base", "face", "loss", "hose", "html", "chat", "fire", "sony", "pubs", "lake", "paul", "mild", "none", "step", "half", "sort", "clan", "sync", "mesa", "wide", "loan", "hull", "golf", "toys", "shed", "memo", "girl", "tide", "funk", "town", "reel", "risk", "bind", "rand", "buck", "bank", "feel", "meet", "usgs", "acre", "lows", "aqua", "chen", "emma", "tech", "pest", "unit", "fact", "fast", "reef", "edit", "auto", "plus", "chan", "tips", "beth", "rock", "mark", "else", "jill", "sofa", "true", "dans", "viii", "kids", "talk", "tent", "dept", "hack", "dare", "hawk", "lamb", "bill", "word", "ever", "done", "land", "says", "upon", "five", "past", "arts", "gold", "able", "junk", "tell", "lucy", "hans", "poet", "epic", "cars", "sake", "sans", "sure", "lean", "once", "away", "self", "dude", "luis", "cell", "head", "film", "alto", "term", "baby", "keep", "gore", "cult", "dash", "cage", "divx", "hugh", "hand", "jake", "eval", "ping", "flux", "star", "muze", "oman", "easy", "blue", "rage", "adsl", "four", "prix", "hard", "gift", "avon", "rays", "road", "walt", "acne", "libs", "undo", "club", "east", "dana", "halo", "body", "sell", "gays", "give", "exec", "side", "park", "blog", "less", "play", "maui", "cart", "come", "test", "vids", "yale", "july", "cost", "plan", "june", "doom", "owen", "bite", "issn", "myth", "live", "note", "week", "weed", "oecd", "dice", "quad", "dock", "mods", "hint", "msie", "team", "left", "look", "west", "buys", "pork", "join", "barn", "room", "teen", "fare", "sale", "asus", "food", "bald", "fuji", "leon", "mold", "dame", "jobs", "herb", "card", "alot", "york", "idle", "save", "call", "main", "john", "love", "form", "rate", "text", "cove", "casa", "shop", "eden", "incl", "size", "down", "care", "game", "reid", "both", "flex", "rosa", "hash", "lazy", "same", "case", "carb", "open", "link", "file", "sign", "much", "even", "pens", "show", "code", "worm", "long", "deaf", "mats", "want", "blah", "mime", "feof", "usda", "keen", "peas", "urls", "area", "take", "type", "send", "owns", "line", "made", "must", "ebay", "item", "zinc", "guru", "real", "levy", "grad", "bras", "part", "days", "kyle", "know", "life", "pale", "gaps", "tear", "full", "mail", "does", "nest", "said", "nato", "user", "gale", "many", "stan", "idol", "need", "read", "book", "very", "moss", "each", "cork", "high", "mali", "info", "dome", "well", "heel", "yang", "good", "then", "best", "such", "city", "post", "them", "make", "data", "dumb", "most", "last", "work", "used", "next", "into", "year", "feat", "ntsc", "over", "usps", "just", "name", "conf", "glow", "list", "back", "oaks", "erik", "date", "find", "than", "paso", "norm", "like", "some", "ware", "were", "been", "jade", "foul", "keno", "view", "seas", "help", "pose", "mrna", "goat", "also", "here", "sail", "when", "only", "sega", "cdna", "news", "what", "bolt", "gage", "site", "they", "time", "urge", "smtp", "kurt", "neon", "ours", "lone", "cope", "free", "lime", "kirk", "bool", "page", "home", "will", "spas", "more", "jets", "have", "intl", "your", "yarn", "knit", "from", "with", "this", "pike", "that", "hugo", "gzip", "ctrl", "bent", "laos"} +) + +type TLDRPlugin struct { + Bot bot.Bot + History []string + Index int +} + +func New(b bot.Bot) *TLDRPlugin { + plugin := &TLDRPlugin{ + Bot: b, + History: []string{}, + Index: 0, + } + b.Register(plugin, bot.Message, plugin.message) + b.Register(plugin, bot.Help, plugin.help) + return plugin +} + +func (p *TLDRPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { + lowercaseMessage := strings.ToLower(message.Body) + if lowercaseMessage == "tl;dr" { + for _, str := range p.History { + fmt.Println(str) + } + + nTopics := p.Bot.Config().GetInt("TLDR.Topics", 5) + + vectoriser := nlp.NewCountVectoriser(THESE_ARE_NOT_THE_WORDS_YOU_ARE_LOOKING_FOR...) + lda := nlp.NewLatentDirichletAllocation(nTopics) + pipeline := nlp.NewPipeline(vectoriser, lda) + docsOverTopics, err := pipeline.FitTransform(p.History...) + + if err != nil { + log.Error().Err(err) + return false + } + + bestScores := make([]float64, nTopics) + bestDocs := make([]string, nTopics) + + dr, dc := docsOverTopics.Dims() + for doc := 0; doc < dc; doc++ { + for topic := 0; topic < dr; topic++ { + score := docsOverTopics.At(topic, doc) + if score > bestScores[topic] { + bestScores[topic] = score + bestDocs[topic] = p.History[doc] + } + } + } + + topicsOverWords := lda.Components() + tr, tc := topicsOverWords.Dims() + + vocab := make([]string, len(vectoriser.Vocabulary)) + for k, v := range vectoriser.Vocabulary { + vocab[v] = k + } + + response := "Here you go captain 'too good to read backlog':\n" + + for topic := 0; topic < tr; topic++ { + max := -1. + best := "" + for word := 0; word < tc; word++ { + score := topicsOverWords.At(topic, word) + if score > max { + max = score + best = vocab[word] + } + } + response += fmt.Sprintf("Topic #%d : %s\n", topic, best) + response += fmt.Sprintf("\t%s\n", bestDocs[topic]) + } + + p.Bot.Send(bot.Message, message.Channel, response) + + return true + } + + if shouldKeepMessage(lowercaseMessage) { + currentHistorySize := len(p.History) + maxHistorySize := p.Bot.Config().GetInt("TLDR.HistorySize", 1000) + if currentHistorySize < maxHistorySize { + p.History = append(p.History, lowercaseMessage) + p.Index = 0 + } else { + if currentHistorySize > maxHistorySize { + // We could resize this but we want to prune the oldest stuff, and + // I don't care to do this correctly so might as well not do it at all + } + + if p.Index >= currentHistorySize { + p.Index = 0 + } + + p.History[p.Index] = lowercaseMessage + p.Index++ + } + } + return false +} + +// Help responds to help requests. Every plugin must implement a help function. +func (p *TLDRPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.Bot.Send(bot.Message, message.Channel, "tl;dr") + return true +} + +func shouldKeepMessage(message string) bool { + return true +} diff --git a/plugins/tldr/tldr_test.go b/plugins/tldr/tldr_test.go new file mode 100644 index 0000000..9d42cde --- /dev/null +++ b/plugins/tldr/tldr_test.go @@ -0,0 +1,44 @@ +package tldr + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/velour/catbase/bot" + "github.com/velour/catbase/bot/msg" + "github.com/velour/catbase/bot/user" +) + +func makeMessageBy(payload, by string) (bot.Kind, msg.Message) { + isCmd := strings.HasPrefix(payload, "!") + if isCmd { + payload = payload[1:] + } + return bot.Message, msg.Message{ + User: &user.User{Name: by}, + Channel: "test", + Body: payload, + Command: isCmd, + } +} + +func makeMessage(payload string) (bot.Kind, msg.Message) { + return makeMessageBy(payload, "tester") +} + +func setup(t *testing.T) (*TLDRPlugin, *bot.MockBot) { + mb := bot.NewMockBot() + r := New(mb) + return r, mb +} + +func Test(t *testing.T) { + c, mb := setup(t) + res := c.message(makeMessage("The quick brown fox jumped over the lazy dog")) + res = c.message(makeMessage("The cow jumped over the moon")) + res = c.message(makeMessage("The little dog laughed to see such fun")) + res = c.message(makeMessage("tl;dr")) + assert.True(t, res) + assert.Len(t, mb.Messages, 1) +} From b4b09e74c675d552be1c4864738b8710d724f7a0 Mon Sep 17 00:00:00 2001 From: skiesel Date: Thu, 21 Mar 2019 21:36:11 -0400 Subject: [PATCH 069/107] Track who said what --- plugins/tldr/tldr.go | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/plugins/tldr/tldr.go b/plugins/tldr/tldr.go index efaa037..ed82150 100644 --- a/plugins/tldr/tldr.go +++ b/plugins/tldr/tldr.go @@ -19,6 +19,7 @@ var ( type TLDRPlugin struct { Bot bot.Bot History []string + Users []string Index int } @@ -26,6 +27,7 @@ func New(b bot.Bot) *TLDRPlugin { plugin := &TLDRPlugin{ Bot: b, History: []string{}, + Users: []string{}, Index: 0, } b.Register(plugin, bot.Message, plugin.message) @@ -54,6 +56,7 @@ func (p *TLDRPlugin) message(kind bot.Kind, message msg.Message, args ...interfa bestScores := make([]float64, nTopics) bestDocs := make([]string, nTopics) + bestUsers := make([]string, nTopics) dr, dc := docsOverTopics.Dims() for doc := 0; doc < dc; doc++ { @@ -62,6 +65,7 @@ func (p *TLDRPlugin) message(kind bot.Kind, message msg.Message, args ...interfa if score > bestScores[topic] { bestScores[topic] = score bestDocs[topic] = p.History[doc] + bestUsers[topic] = p.Users[doc] } } } @@ -77,17 +81,17 @@ func (p *TLDRPlugin) message(kind bot.Kind, message msg.Message, args ...interfa response := "Here you go captain 'too good to read backlog':\n" for topic := 0; topic < tr; topic++ { - max := -1. - best := "" + bestScore := -1. + bestTopic := "" for word := 0; word < tc; word++ { score := topicsOverWords.At(topic, word) - if score > max { - max = score - best = vocab[word] + if score > bestScore { + bestScore = score + bestTopic = vocab[word] } } - response += fmt.Sprintf("Topic #%d : %s\n", topic, best) - response += fmt.Sprintf("\t%s\n", bestDocs[topic]) + response += fmt.Sprintf("Topic #%d : %s\n", topic, bestTopic) + response += fmt.Sprintf("\t<%s>%s\n", bestUsers[topic], bestDocs[topic]) } p.Bot.Send(bot.Message, message.Channel, response) @@ -100,6 +104,7 @@ func (p *TLDRPlugin) message(kind bot.Kind, message msg.Message, args ...interfa maxHistorySize := p.Bot.Config().GetInt("TLDR.HistorySize", 1000) if currentHistorySize < maxHistorySize { p.History = append(p.History, lowercaseMessage) + p.Users = append(p.Users, message.User.Name) p.Index = 0 } else { if currentHistorySize > maxHistorySize { @@ -112,6 +117,7 @@ func (p *TLDRPlugin) message(kind bot.Kind, message msg.Message, args ...interfa } p.History[p.Index] = lowercaseMessage + p.Users[p.Index] = message.User.Name p.Index++ } } From dbc99bfe7a6ce564448d049c2e17102c5925a7c1 Mon Sep 17 00:00:00 2001 From: skiesel Date: Thu, 21 Mar 2019 21:46:28 -0400 Subject: [PATCH 070/107] Add supporting docs size --- plugins/tldr/tldr.go | 48 +++++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/plugins/tldr/tldr.go b/plugins/tldr/tldr.go index ed82150..e1919be 100644 --- a/plugins/tldr/tldr.go +++ b/plugins/tldr/tldr.go @@ -19,7 +19,7 @@ var ( type TLDRPlugin struct { Bot bot.Bot History []string - Users []string + Users []string Index int } @@ -27,7 +27,7 @@ func New(b bot.Bot) *TLDRPlugin { plugin := &TLDRPlugin{ Bot: b, History: []string{}, - Users: []string{}, + Users: []string{}, Index: 0, } b.Register(plugin, bot.Message, plugin.message) @@ -54,18 +54,28 @@ func (p *TLDRPlugin) message(kind bot.Kind, message msg.Message, args ...interfa return false } - bestScores := make([]float64, nTopics) - bestDocs := make([]string, nTopics) - bestUsers := make([]string, nTopics) + bestScores := make([][]float64, nTopics) + bestDocs := make([][]string, nTopics) + bestUsers := make([][]string, nTopics) + + supportingDocs := p.Bot.Config().GetInt("TLDR.Support", 3) + for i := 0; i < supportingDocs; i++ { + bestScores[i] = make([]float64, supportingDocs) + bestDocs[i] = make([]string, supportingDocs) + bestUsers[i] = make([]string, supportingDocs) + } dr, dc := docsOverTopics.Dims() - for doc := 0; doc < dc; doc++ { - for topic := 0; topic < dr; topic++ { + for topic := 0; topic < dr; topic++ { + minScore, minIndex := min(bestScores[topic]) + + for doc := 0; doc < dc; doc++ { score := docsOverTopics.At(topic, doc) - if score > bestScores[topic] { - bestScores[topic] = score - bestDocs[topic] = p.History[doc] - bestUsers[topic] = p.Users[doc] + if score > minScore { + bestScores[topic][minIndex] = score + bestDocs[topic][minIndex] = p.History[doc] + bestUsers[topic][minIndex] = p.Users[doc] + minScore, minIndex = min(bestScores[topic]) } } } @@ -91,7 +101,9 @@ func (p *TLDRPlugin) message(kind bot.Kind, message msg.Message, args ...interfa } } response += fmt.Sprintf("Topic #%d : %s\n", topic, bestTopic) - response += fmt.Sprintf("\t<%s>%s\n", bestUsers[topic], bestDocs[topic]) + for i := range bestDocs[topic] { + response += fmt.Sprintf("\t<%s>%s\n", bestUsers[topic][i], bestDocs[topic][i]) + } } p.Bot.Send(bot.Message, message.Channel, response) @@ -133,3 +145,15 @@ func (p *TLDRPlugin) help(kind bot.Kind, message msg.Message, args ...interface{ func shouldKeepMessage(message string) bool { return true } + +func min(slice []float64) (float64, int) { + minVal := 1. + minIndex := -1 + for index, val := range slice { + if val < minVal { + minVal = val + minIndex = index + } + } + return minVal, minIndex +} From ce922135cd1c3b7d46f6431fa17294e59682820b Mon Sep 17 00:00:00 2001 From: skiesel Date: Thu, 21 Mar 2019 22:29:10 -0400 Subject: [PATCH 071/107] Fix silly sizing mistake --- plugins/tldr/tldr.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/tldr/tldr.go b/plugins/tldr/tldr.go index e1919be..731ea72 100644 --- a/plugins/tldr/tldr.go +++ b/plugins/tldr/tldr.go @@ -59,7 +59,7 @@ func (p *TLDRPlugin) message(kind bot.Kind, message msg.Message, args ...interfa bestUsers := make([][]string, nTopics) supportingDocs := p.Bot.Config().GetInt("TLDR.Support", 3) - for i := 0; i < supportingDocs; i++ { + for i := 0; i < nTopics; i++ { bestScores[i] = make([]float64, supportingDocs) bestDocs[i] = make([]string, supportingDocs) bestUsers[i] = make([]string, supportingDocs) From 22ba451d4cb5f2a2e86ffb19b59c02335ce30829 Mon Sep 17 00:00:00 2001 From: skiesel Date: Fri, 22 Mar 2019 06:49:24 -0400 Subject: [PATCH 072/107] Filter empty evidence, show scores --- plugins/tldr/tldr.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/plugins/tldr/tldr.go b/plugins/tldr/tldr.go index 731ea72..25c5c37 100644 --- a/plugins/tldr/tldr.go +++ b/plugins/tldr/tldr.go @@ -38,10 +38,6 @@ func New(b bot.Bot) *TLDRPlugin { func (p *TLDRPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { lowercaseMessage := strings.ToLower(message.Body) if lowercaseMessage == "tl;dr" { - for _, str := range p.History { - fmt.Println(str) - } - nTopics := p.Bot.Config().GetInt("TLDR.Topics", 5) vectoriser := nlp.NewCountVectoriser(THESE_ARE_NOT_THE_WORDS_YOU_ARE_LOOKING_FOR...) @@ -102,7 +98,9 @@ func (p *TLDRPlugin) message(kind bot.Kind, message msg.Message, args ...interfa } response += fmt.Sprintf("Topic #%d : %s\n", topic, bestTopic) for i := range bestDocs[topic] { - response += fmt.Sprintf("\t<%s>%s\n", bestUsers[topic][i], bestDocs[topic][i]) + if bestUsers[topic][i] != "" { + response += fmt.Sprintf("\t<%s>%s [%f]\n", bestUsers[topic][i], bestDocs[topic][i], bestScores[topic][i]) + } } } From 0650a352c85b1ca8ee4d54b8e1c02e2e386fabb8 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Tue, 26 Mar 2019 17:51:28 -0400 Subject: [PATCH 073/107] tldr: limit to 24h and limit rapid checks Also refactor a little bit. --- go.mod | 10 +- go.sum | 32 +- plugins/tldr/badwords.go | 2188 +++++++++++++++++++++++++++++++++++++ plugins/tldr/tldr.go | 99 +- plugins/tldr/tldr_test.go | 19 + 5 files changed, 2308 insertions(+), 40 deletions(-) create mode 100644 plugins/tldr/badwords.go diff --git a/go.mod b/go.mod index a696823..6d3988c 100644 --- a/go.mod +++ b/go.mod @@ -2,9 +2,14 @@ module github.com/velour/catbase require ( github.com/PuerkitoBio/goquery v1.5.0 // indirect + github.com/armon/go-radix v1.0.0 // indirect github.com/chrissexton/leftpad v0.0.0-20181207133115-1e93189d2fff github.com/go-sql-driver/mysql v1.4.1 // indirect + github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82 // indirect + github.com/gonum/internal v0.0.0-20181124074243-f884aa714029 // indirect github.com/gorilla/websocket v1.4.0 // indirect + github.com/james-bowman/nlp v0.0.0-20190301165020-c5645f996605 + github.com/james-bowman/sparse v0.0.0-20190309194602-7d83420cfcbe // indirect github.com/jmoiron/sqlx v1.2.0 github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 // indirect github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6 // indirect @@ -15,11 +20,14 @@ require ( github.com/olebedev/when v0.0.0-20190131080308-164b69386514 github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d // indirect github.com/rs/zerolog v1.12.0 + github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/stretchr/objx v0.1.1 // indirect github.com/stretchr/testify v1.3.0 github.com/velour/chat v0.0.0-20180713122344-fd1d1606cb89 github.com/velour/velour v0.0.0-20160303155839-8e090e68d158 - golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 // indirect + golang.org/x/exp v0.0.0-20190321205749-f0864edee7f3 // indirect + golang.org/x/net v0.0.0-20190326090315-15845e8f865b // indirect + gonum.org/v1/gonum v0.0.0-20190325211145-e42c1265cdd5 // indirect google.golang.org/appengine v1.4.0 // indirect gopkg.in/sourcemap.v1 v1.0.5 // indirect ) diff --git a/go.sum b/go.sum index f531cc7..a5f9830 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,12 @@ github.com/AlekSi/pointer v1.0.0 h1:KWCWzsvFxNLcmM5XmiqHsGTTsuwZMsLFwWF9Y+//bNE= github.com/AlekSi/pointer v1.0.0/go.mod h1:1kjywbfcPFCmncIxtk6fIEub6LKrfMz3gc5QKVOSOA8= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk= github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o= github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/chrissexton/leftpad v0.0.0-20181207133115-1e93189d2fff h1:+TEqaP0eO1unI7XHHFeMDhsxhLDIb0x8KYuZbqbAmxA= github.com/chrissexton/leftpad v0.0.0-20181207133115-1e93189d2fff/go.mod h1:QCRjR0b4qiJiNjuP7RFM89bh4UExGJalcWmYeSvlnRc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -13,8 +16,16 @@ github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82 h1:EvokxLQsaaQjcWVWSV38221VAK7qc2zhaO17bKys/18= +github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82/go.mod h1:PxC8OnwL11+aosOB5+iEPoV3picfs8tUpkVd0pDo+Kg= +github.com/gonum/internal v0.0.0-20181124074243-f884aa714029 h1:8jtTdc+Nfj9AR+0soOeia9UZSvYBvETVHZrugUowJ7M= +github.com/gonum/internal v0.0.0-20181124074243-f884aa714029/go.mod h1:Pu4dmpkhSyOzRwuXkOgAvijx4o+4YMUJJo9OvPYMkks= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/james-bowman/nlp v0.0.0-20190301165020-c5645f996605 h1:MjLvsJmW4uoTjleqqiL0wKRTjxUakKUhDNoSsSlS2hk= +github.com/james-bowman/nlp v0.0.0-20190301165020-c5645f996605/go.mod h1:kixuaexEqWB+mHZNysgnb6mqgGIT25WvD1/tFRRt0J0= +github.com/james-bowman/sparse v0.0.0-20190309194602-7d83420cfcbe h1:UFAsFuH6cu/0Lx+qBWfxiO69jrPkvdbG3qwSWI/7yF0= +github.com/james-bowman/sparse v0.0.0-20190309194602-7d83420cfcbe/go.mod h1:G6EcQnwZKsWtItoaQHd+FHPPk6bDeYVJSeeSP9Sge+I= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= @@ -42,6 +53,8 @@ github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d h1:1VUlQbCfkoSGv github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY= github.com/rs/zerolog v1.12.0 h1:aqZ1XRadoS8IBknR5IDFvGzbHly1X9ApIqOroooQF/c= github.com/rs/zerolog v1.12.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -51,13 +64,28 @@ github.com/velour/chat v0.0.0-20180713122344-fd1d1606cb89 h1:3D3M900hEBJJAqyKl70 github.com/velour/chat v0.0.0-20180713122344-fd1d1606cb89/go.mod h1:ejwOYCjnDMyO5LXFXRARQJGBZ6xQJZ3rgAHE5drSuMM= github.com/velour/velour v0.0.0-20160303155839-8e090e68d158 h1:p3rTUXxzuKsBOsHlkly7+rj9wagFBKeIsCDKkDII9sw= github.com/velour/velour v0.0.0-20160303155839-8e090e68d158/go.mod h1:Ojy3BTOiOTwpHpw7/HNi+TVTFppao191PQs+Qc3sqpE= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190321205749-f0864edee7f3 h1:Ep4L2ibjtJcW6IP73KbcJAU0cpNKsLNSSP2jE1xlCys= +golang.org/x/exp v0.0.0-20190321205749-f0864edee7f3/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 h1:ulvT7fqt0yHWzpJwI57MezWnYDVpCAYBVuYst/L+fAY= -golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190326090315-15845e8f865b h1:LlDMQZ0I/u8J45sbt31TecpsFNErRGwDgS4WvT9hKzE= +golang.org/x/net v0.0.0-20190326090315-15845e8f865b/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +gonum.org/v1/gonum v0.0.0-20190325211145-e42c1265cdd5 h1:tyvqqvbB9Sn6UPjokEzsK6cCE9k4Tx/AHGGaJiLIk7g= +gonum.org/v1/gonum v0.0.0-20190325211145-e42c1265cdd5/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= diff --git a/plugins/tldr/badwords.go b/plugins/tldr/badwords.go new file mode 100644 index 0000000..0199c53 --- /dev/null +++ b/plugins/tldr/badwords.go @@ -0,0 +1,2188 @@ +package tldr + +var THESE_ARE_NOT_THE_WORDS_YOU_ARE_LOOKING_FOR = []string{ + "p", + "z", + "i", + "c", + "e", + "s", + "x", + "n", + "b", + "t", + "d", + "m", + "r", + "a", + "f", + "l", + "w", + "o", + "g", + "h", + "v", + "k", + "y", + "j", + "u", + "q", + "th", + "wu", + "qt", + "so", + "ru", + "pm", + "in", + "is", + "am", + "me", + "on", + "by", + "kw", + "hu", + "bg", + "ob", + "re", + "wx", + "go", + "hl", + "vc", + "bl", + "rg", + "wr", + "cw", + "pj", + "tf", + "nr", + "aw", + "qc", + "it", + "cj", + "or", + "ty", + "hk", + "be", + "wc", + "de", + "lf", + "mj", + "bw", + "at", + "as", + "gd", + "ww", + "ko", + "og", + "gg", + "cz", + "an", + "mh", + "we", + "rb", + "mv", + "uk", + "wt", + "us", + "hq", + "if", + "mu", + "pn", + "js", + "my", + "ol", + "ul", + "io", + "lm", + "do", + "cd", + "fo", + "no", + "vg", + "lu", + "dg", + "zu", + "sv", + "wn", + "fu", + "dk", + "tv", + "la", + "sn", + "wb", + "pc", + "he", + "pk", + "ii", + "wm", + "up", + "bo", + "ca", + "fd", + "uh", + "hh", + "al", + "id", + "bd", + "uw", + "co", + "pf", + "ez", + "df", + "ro", + "et", + "dh", + "ui", + "gl", + "st", + "rl", + "ev", + "jj", + "fp", + "hc", + "en", + "eh", + "rp", + "ka", + "rj", + "bm", + "oh", + "tb", + "ix", + "ad", + "cg", + "ny", + "rn", + "cn", + "dc", + "vp", + "jm", + "tp", + "om", + "ok", + "ms", + "wp", + "hi", + "aj", + "oc", + "sq", + "hp", + "yu", + "sk", + "dx", + "eg", + "ip", + "bk", + "hz", + "pa", + "fg", + "rh", + "tx", + "ve", + "za", + "ht", + "ie", + "el", + "ma", + "xi", + "ou", + "dp", + "nu", + "mw", + "mf", + "md", + "fl", + "mb", + "mr", + "ld", + "uc", + "il", + "ln", + "mm", + "ur", + "ed", + "pd", + "le", + "jc", + "az", + "un", + "mi", + "dm", + "wy", + "jd", + "oe", + "to", + "pb", + "dr", + "kb", + "pp", + "na", + "rx", + "os", + "nb", + "yn", + "ci", + "gc", + "ex", + "dt", + "au", + "fi", + "np", + "nc", + "po", + "va", + "rd", + "sc", + "ws", + "cu", + "se", + "di", + "km", + "ga", + "ac", + "ft", + "lc", + "fa", + "im", + "vs", + "ar", + "mo", + "sa", + "sg", + "uv", + "xp", + "je", + "eq", + "lt", + "eu", + "cc", + "wa", + "dj", + "ls", + "cm", + "wi", + "dl", + "ct", + "fx", + "yo", + "da", + "vb", + "of", + "nj", + "hr", + "em", + "iv", + "nn", + "rw", + "fs", + "ye", + "um", + "ni", + "ne", + "du", + "oo", + "bp", + "gs", + "fw", + "nt", + "es", + "fc", + "ti", + "cb", + "cv", + "gb", + "bc", + "pr", + "fr", + "aa", + "mt", + "ir", + "gp", + "oz", + "mg", + "tc", + "hb", + "sl", + "af", + "bt", + "ch", + "sd", + "jp", + "lb", + "rs", + "ep", + "ef", + "rr", + "fy", + "tu", + "dv", + "xl", + "ss", + "tt", + "ap", + "nm", + "mn", + "nd", + "pe", + "op", + "ng", + "tn", + "ge", + "ts", + "gr", + "ce", + "mx", + "ab", + "ic", + "yr", + "ot", + "ai", + "pi", + "rv", + "hs", + "ae", + "tm", + "sp", + "sh", + "gt", + "nh", + "ho", + "cl", + "ll", + "fm", + "gi", + "ta", + "db", + "ph", + "ia", + "pt", + "bi", + "ha", + "ds", + "ea", + "lg", + "bs", + "ja", + "ns", + "wv", + "nw", + "sm", + "ff", + "ah", + "sb", + "td", + "fe", + "ak", + "rf", + "ps", + "ky", + "pl", + "br", + "lo", + "ml", + "dd", + "cp", + "cs", + "rt", + "ri", + "gm", + "sf", + "kg", + "ut", + "si", + "mc", + "vt", + "lp", + "cf", + "rm", + "ag", + "vi", + "ec", + "ba", + "rc", + "cr", + "pg", + "ee", + "ra", + "ks", + "sw", + "av", + "te", + "hd", + "nz", + "bb", + "er", + "jr", + "tr", + "nv", + "ya", + "nl", + "li", + "su", + "mp", + "sr", + "ted", + "bid", + "can", + "the", + "nat", + "car", + "wan", + "dig", + "neo", + "enb", + "pvc", + "dod", + "fri", + "dvd", + "cia", + "tex", + "wed", + "une", + "how", + "inn", + "lid", + "mia", + "ltd", + "los", + "are", + "yen", + "cho", + "dui", + "inc", + "win", + "col", + "upc", + "bed", + "dsc", + "ste", + "aye", + "nhs", + "dow", + "tue", + "cio", + "ooo", + "cas", + "thu", + "sea", + "cut", + "mpg", + "rrp", + "tel", + "its", + "ips", + "pts", + "own", + "kit", + "mug", + "has", + "sku", + "nbc", + "dip", + "acm", + "boy", + "end", + "ids", + "him", + "est", + "son", + "ict", + "mac", + "iii", + "gmt", + "max", + "per", + "xml", + "big", + "bin", + "law", + "sap", + "ala", + "art", + "cir", + "lip", + "bat", + "top", + "eco", + "sol", + "van", + "had", + "buf", + "rip", + "ads", + "usa", + "wma", + "seq", + "pop", + "int", + "rid", + "rna", + "sim", + "abs", + "hit", + "but", + "wal", + "ati", + "doe", + "eye", + "geo", + "old", + "arg", + "usb", + "uni", + "php", + "etc", + "diy", + "leo", + "tgp", + "mud", + "msn", + "fee", + "rpg", + "las", + "ide", + "sic", + "min", + "aid", + "avi", + "ons", + "non", + "mel", + "div", + "ppc", + "day", + "fat", + "saw", + "cet", + "cow", + "mls", + "pst", + "why", + "phi", + "bra", + "mae", + "tom", + "fin", + "sub", + "irc", + "gpl", + "led", + "fan", + "low", + "ten", + "gif", + "ate", + "man", + "cat", + "die", + "ton", + "tmp", + "rec", + "two", + "ddr", + "our", + "gsm", + "pet", + "guy", + "dev", + "cup", + "vol", + "one", + "you", + "mag", + "dee", + "pit", + "mba", + "lee", + "job", + "boc", + "pmc", + "cfr", + "bee", + "vii", + "llp", + "too", + "tap", + "for", + "bob", + "fit", + "men", + "met", + "mem", + "por", + "www", + "cgi", + "soa", + "jvc", + "tft", + "ccd", + "liz", + "ice", + "dat", + "ali", + "box", + "llc", + "sec", + "bus", + "ash", + "bag", + "gay", + "all", + "tub", + "sox", + "ibm", + "sas", + "gig", + "qui", + "pty", + "dns", + "air", + "nor", + "bug", + "mid", + "pas", + "icq", + "sys", + "das", + "lil", + "cnn", + "jim", + "buy", + "yes", + "dam", + "del", + "hot", + "qld", + "new", + "say", + "ist", + "joe", + "may", + "cab", + "cds", + "nav", + "ind", + "pct", + "pos", + "dec", + "pod", + "vic", + "psi", + "san", + "cms", + "gem", + "tax", + "uri", + "got", + "atm", + "vcr", + "lab", + "cvs", + "hon", + "let", + "bow", + "des", + "cbs", + "eos", + "lcd", + "inf", + "ave", + "act", + "red", + "pie", + "apt", + "her", + "alt", + "ant", + "key", + "ppm", + "tan", + "few", + "sip", + "out", + "kde", + "pic", + "gym", + "age", + "mat", + "add", + "use", + "asn", + "pgp", + "lou", + "jan", + "oct", + "pay", + "tag", + "mix", + "any", + "vhs", + "fix", + "pal", + "tri", + "thy", + "war", + "nov", + "ray", + "leu", + "fda", + "see", + "vid", + "std", + "gmc", + "dry", + "spa", + "aaa", + "con", + "ups", + "fax", + "yet", + "gel", + "sao", + "lap", + "sun", + "rss", + "nhl", + "gen", + "mtv", + "mil", + "cod", + "not", + "run", + "net", + "msg", + "eau", + "plc", + "was", + "var", + "dos", + "put", + "rat", + "his", + "won", + "oem", + "tin", + "doc", + "try", + "mom", + "rap", + "mlb", + "row", + "she", + "flu", + "opt", + "usd", + "abu", + "ssl", + "ana", + "jpg", + "eat", + "cdt", + "ins", + "aim", + "isp", + "seo", + "les", + "bye", + "ann", + "tip", + "rfc", + "log", + "ski", + "irs", + "faq", + "dan", + "chi", + "nut", + "wax", + "fly", + "dts", + "fun", + "gbp", + "sen", + "hey", + "sue", + "bbc", + "ace", + "tea", + "avg", + "sky", + "feb", + "rom", + "eng", + "toy", + "sep", + "src", + "hip", + "hub", + "ghz", + "eds", + "lot", + "val", + "dot", + "hiv", + "pda", + "dir", + "ask", + "dsl", + "zum", + "dna", + "tcp", + "cad", + "fcc", + "tee", + "aka", + "tim", + "sql", + "zoo", + "don", + "due", + "mai", + "cry", + "vpn", + "who", + "dim", + "mar", + "cop", + "gps", + "erp", + "acc", + "pro", + "cap", + "ink", + "phd", + "pam", + "url", + "aug", + "pin", + "raw", + "gnu", + "amy", + "ben", + "ext", + "web", + "aol", + "ago", + "pac", + "odd", + "ent", + "hat", + "zus", + "lib", + "ban", + "cos", + "utc", + "der", + "fed", + "apr", + "ion", + "roy", + "cam", + "app", + "wet", + "ram", + "nil", + "fox", + "mrs", + "arc", + "arm", + "via", + "jar", + "obj", + "dom", + "kai", + "rio", + "jam", + "nyc", + "len", + "pub", + "bad", + "mas", + "set", + "hop", + "bon", + "gst", + "gun", + "ata", + "rca", + "ira", + "eva", + "rev", + "sur", + "sie", + "lat", + "sam", + "pdt", + "mhz", + "egg", + "tvs", + "pee", + "rpm", + "img", + "ref", + "pot", + "far", + "kid", + "map", + "pan", + "tba", + "cal", + "now", + "and", + "sad", + "jul", + "psp", + "fbi", + "jun", + "hrs", + "ham", + "und", + "rod", + "wav", + "dem", + "way", + "pad", + "nfl", + "eve", + "rug", + "soc", + "amd", + "usc", + "mic", + "tar", + "fur", + "yea", + "iso", + "sum", + "vip", + "amp", + "str", + "oak", + "vat", + "fog", + "duo", + "sig", + "get", + "sir", + "crm", + "kim", + "lie", + "gba", + "oil", + "spy", + "bit", + "aud", + "foo", + "den", + "yrs", + "pix", + "res", + "sit", + "wow", + "isa", + "ada", + "una", + "que", + "lit", + "pig", + "fig", + "gdp", + "bbs", + "nec", + "nam", + "sms", + "tab", + "bay", + "css", + "gtk", + "lan", + "urw", + "qty", + "hwy", + "aus", + "fwd", + "bio", + "api", + "toe", + "sri", + "pcs", + "bar", + "mit", + "von", + "dog", + "rep", + "ser", + "wit", + "ceo", + "sci", + "edt", + "cst", + "sin", + "bmw", + "hay", + "eur", + "kay", + "pdf", + "mod", + "dis", + "zen", + "ian", + "ing", + "rim", + "tie", + "pci", + "ear", + "nsw", + "ftp", + "med", + "reg", + "wto", + "ver", + "gui", + "leg", + "pat", + "off", + "dad", + "abc", + "org", + "usr", + "jay", + "gap", + "ron", + "til", + "mon", + "com", + "biz", + "rob", + "era", + "gcc", + "asp", + "did", + "epa", + "jet", + "par", + "nba", + "loc", + "gas", + "mad", + "six", + "gis", + "def", + "ken", + "pre", + "exp", + "bet", + "pen", + "mph", + "dpi", + "joy", + "cpu", + "ran", + "lol", + "sat", + "jon", + "lay", + "lbs", + "zip", + "ill", + "rows", + "pipe", + "seal", + "deck", + "sand", + "thin", + "shoe", + "sick", + "dose", + "till", + "cafe", + "lets", + "andy", + "semi", + "cats", + "cake", + "gang", + "greg", + "dial", + "luck", + "belt", + "tube", + "rail", + "folk", + "tiny", + "okay", + "hist", + "lift", + "lisa", + "mall", + "wing", + "neck", + "fell", + "yard", + "busy", + "tone", + "sean", + "pour", + "gate", + "tion", + "dust", + "wiki", + "kent", + "adds", + "bugs", + "bone", + "bang", + "alex", + "ward", + "meat", + "roof", + "kiss", + "peer", + "seed", + "para", + "cute", + "rush", + "mpeg", + "yoga", + "lamp", + "rico", + "phil", + "pmid", + "http", + "bulk", + "glad", + "wins", + "rack", + "aged", + "scan", + "bold", + "boss", + "ross", + "anna", + "solo", + "tall", + "grey", + "pdas", + "beds", + "ryan", + "nova", + "exam", + "anne", + "pump", + "wake", + "plot", + "nick", + "nasa", + "drum", + "pull", + "foto", + "ease", + "tabs", + "voip", + "grid", + "pine", + "tend", + "gulf", + "echo", + "rick", + "char", + "hunt", + "thai", + "fred", + "chip", + "mill", + "suit", + "bits", + "dont", + "burn", + "labs", + "twin", + "earn", + "jane", + "jose", + "beer", + "dear", + "alan", + "misc", + "push", + "sole", + "boot", + "laid", + "clay", + "weak", + "milk", + "blvd", + "arab", + "wise", + "rome", + "odds", + "vary", + "gary", + "marc", + "sons", + "leaf", + "loop", + "rice", + "hate", + "demo", + "cuba", + "gray", + "silk", + "kate", + "slot", + "adam", + "wolf", + "dish", + "fits", + "kick", + "meal", + "navy", + "hurt", + "tank", + "bowl", + "slip", + "mens", + "cuts", + "mile", + "mars", + "lock", + "node", + "rear", + "caps", + "pill", + "legs", + "meta", + "mint", + "crew", + "spin", + "babe", + "wash", + "warm", + "draw", + "aims", + "lens", + "ieee", + "pure", + "corp", + "visa", + "jean", + "soap", + "bond", + "unix", + "poll", + "axis", + "guns", + "dean", + "mesh", + "hero", + "acts", + "punk", + "holy", + "duke", + "wave", + "pace", + "wage", + "keys", + "iran", + "dawn", + "carl", + "coat", + "exit", + "rica", + "matt", + "soil", + "kits", + "tony", + "doll", + "seek", + "peru", + "nike", + "lose", + "reed", + "mice", + "bike", + "temp", + "perl", + "vast", + "cook", + "plug", + "wrap", + "mood", + "quiz", + "ages", + "kill", + "lane", + "beam", + "tops", + "jeff", + "bell", + "shut", + "salt", + "ncaa", + "thou", + "peak", + "mask", + "euro", + "evil", + "coal", + "yeah", + "runs", + "pair", + "ride", + "pets", + "lion", + "goto", + "hole", + "neil", + "beef", + "bass", + "hats", + "diff", + "surf", + "onto", + "rain", + "hook", + "cord", + "grow", + "crop", + "spot", + "eric", + "lite", + "nine", + "faqs", + "slow", + "hide", + "utah", + "arms", + "sing", + "tons", + "beat", + "kept", + "hang", + "wars", + "fear", + "hood", + "moon", + "dogs", + "math", + "fame", + "whom", + "mine", + "cape", + "toll", + "bids", + "seat", + "eggs", + "dell", + "fans", + "lady", + "ruby", + "mins", + "bird", + "stem", + "rise", + "drew", + "dual", + "bars", + "rare", + "tune", + "corn", + "wear", + "puts", + "grew", + "bags", + "trek", + "jazz", + "fail", + "ties", + "beta", + "brad", + "jury", + "font", + "tail", + "lawn", + "soup", + "byte", + "nose", + "oclc", + "bath", + "juan", + "roll", + "zero", + "thru", + "jews", + "trim", + "null", + "cent", + "acid", + "espn", + "spam", + "quit", + "lung", + "tape", + "wire", + "clip", + "todd", + "blow", + "doug", + "sees", + "zoom", + "knew", + "bull", + "cole", + "mart", + "tale", + "lynn", + "iowa", + "lack", + "docs", + "gain", + "bear", + "coin", + "fake", + "duty", + "cure", + "arch", + "vice", + "hdtv", + "asin", + "bomb", + "harm", + "hong", + "deer", + "dave", + "desk", + "disk", + "void", + "iron", + "atom", + "flag", + "oven", + "aids", + "noon", + "soul", + "felt", + "cast", + "cams", + "joel", + "ends", + "proc", + "icon", + "boat", + "mate", + "disc", + "chef", + "isle", + "slim", + "luke", + "comp", + "gene", + "fort", + "gone", + "fill", + "pete", + "spec", + "camp", + "penn", + "midi", + "tied", + "snow", + "dale", + "oils", + "sept", + "unto", + "inch", + "died", + "kong", + "pays", + "rank", + "lang", + "stud", + "fold", + "ones", + "gave", + "hire", + "seem", + "ipod", + "phys", + "pole", + "mega", + "bend", + "moms", + "glen", + "rich", + "drop", + "guys", + "tags", + "lips", + "pond", + "load", + "pick", + "rose", + "wait", + "walk", + "tire", + "chad", + "fuel", + "josh", + "drag", + "soft", + "ripe", + "rely", + "scsi", + "task", + "miss", + "wild", + "heat", + "nuts", + "nail", + "span", + "mass", + "joke", + "univ", + "foot", + "pads", + "inns", + "cups", + "cold", + "shot", + "pink", + "foam", + "root", + "edge", + "poem", + "ford", + "oral", + "asks", + "bean", + "bias", + "xbox", + "pain", + "palm", + "wind", + "sold", + "swim", + "nano", + "goal", + "ball", + "dvds", + "loud", + "rats", + "jump", + "stat", + "cruz", + "bios", + "firm", + "thee", + "lots", + "ruth", + "pray", + "pope", + "jeep", + "bare", + "hung", + "gear", + "army", + "mono", + "tile", + "diet", + "apps", + "skip", + "laws", + "path", + "flow", + "ciao", + "knee", + "prep", + "flat", + "chem", + "jack", + "zone", + "hits", + "pros", + "cant", + "wife", + "goes", + "hear", + "lord", + "farm", + "sara", + "eyes", + "joan", + "duck", + "poor", + "trip", + "mike", + "dive", + "dead", + "fiji", + "audi", + "raid", + "gets", + "volt", + "ohio", + "dirt", + "fair", + "acer", + "dist", + "isbn", + "geek", + "sink", + "grip", + "host", + "watt", + "pins", + "reno", + "dark", + "polo", + "rent", + "horn", + "wood", + "prot", + "frog", + "logs", + "sets", + "core", + "debt", + "snap", + "race", + "born", + "pack", + "fish", + "jpeg", + "mini", + "pool", + "swap", + "rest", + "flip", + "deep", + "boys", + "buzz", + "nuke", + "iraq", + "boom", + "calm", + "fork", + "troy", + "ring", + "mary", + "prev", + "zope", + "gmbh", + "skin", + "fees", + "sims", + "tray", + "pass", + "sage", + "java", + "uses", + "asia", + "cool", + "suse", + "door", + "cave", + "wool", + "feet", + "told", + "rule", + "ways", + "eyed", + "vote", + "grab", + "oops", + "wine", + "wall", + "thus", + "tree", + "trap", + "fool", + "hair", + "karl", + "dies", + "paid", + "ship", + "anti", + "hall", + "jail", + "feed", + "safe", + "ipaq", + "hold", + "comm", + "deal", + "maps", + "lace", + "hill", + "ugly", + "hart", + "ment", + "tool", + "idea", + "fall", + "biol", + "late", + "lies", + "cnet", + "song", + "took", + "treo", + "gods", + "male", + "fund", + "mode", + "poly", + "ears", + "went", + "lead", + "fist", + "band", + "mere", + "cons", + "sent", + "taxi", + "nice", + "logo", + "move", + "kind", + "huge", + "bush", + "hour", + "worn", + "shaw", + "fine", + "expo", + "came", + "deny", + "bali", + "judy", + "trio", + "cube", + "rugs", + "fate", + "role", + "gras", + "wish", + "hope", + "menu", + "tour", + "lost", + "mind", + "oval", + "held", + "soma", + "soon", + "href", + "benz", + "wifi", + "tier", + "stop", + "earl", + "port", + "seen", + "guam", + "cite", + "cash", + "pics", + "drug", + "copy", + "mess", + "king", + "mean", + "turn", + "stay", + "rope", + "dump", + "near", + "base", + "face", + "loss", + "hose", + "html", + "chat", + "fire", + "sony", + "pubs", + "lake", + "paul", + "mild", + "none", + "step", + "half", + "sort", + "clan", + "sync", + "mesa", + "wide", + "loan", + "hull", + "golf", + "toys", + "shed", + "memo", + "girl", + "tide", + "funk", + "town", + "reel", + "risk", + "bind", + "rand", + "buck", + "bank", + "feel", + "meet", + "usgs", + "acre", + "lows", + "aqua", + "chen", + "emma", + "tech", + "pest", + "unit", + "fact", + "fast", + "reef", + "edit", + "auto", + "plus", + "chan", + "tips", + "beth", + "rock", + "mark", + "else", + "jill", + "sofa", + "true", + "dans", + "viii", + "kids", + "talk", + "tent", + "dept", + "hack", + "dare", + "hawk", + "lamb", + "bill", + "word", + "ever", + "done", + "land", + "says", + "upon", + "five", + "past", + "arts", + "gold", + "able", + "junk", + "tell", + "lucy", + "hans", + "poet", + "epic", + "cars", + "sake", + "sans", + "sure", + "lean", + "once", + "away", + "self", + "dude", + "luis", + "cell", + "head", + "film", + "alto", + "term", + "baby", + "keep", + "gore", + "cult", + "dash", + "cage", + "divx", + "hugh", + "hand", + "jake", + "eval", + "ping", + "flux", + "star", + "muze", + "oman", + "easy", + "blue", + "rage", + "adsl", + "four", + "prix", + "hard", + "gift", + "avon", + "rays", + "road", + "walt", + "acne", + "libs", + "undo", + "club", + "east", + "dana", + "halo", + "body", + "sell", + "gays", + "give", + "exec", + "side", + "park", + "blog", + "less", + "play", + "maui", + "cart", + "come", + "test", + "vids", + "yale", + "july", + "cost", + "plan", + "june", + "doom", + "owen", + "bite", + "issn", + "myth", + "live", + "note", + "week", + "weed", + "oecd", + "dice", + "quad", + "dock", + "mods", + "hint", + "msie", + "team", + "left", + "look", + "west", + "buys", + "pork", + "join", + "barn", + "room", + "teen", + "fare", + "sale", + "asus", + "food", + "bald", + "fuji", + "leon", + "mold", + "dame", + "jobs", + "herb", + "card", + "alot", + "york", + "idle", + "save", + "call", + "main", + "john", + "love", + "form", + "rate", + "text", + "cove", + "casa", + "shop", + "eden", + "incl", + "size", + "down", + "care", + "game", + "reid", + "both", + "flex", + "rosa", + "hash", + "lazy", + "same", + "case", + "carb", + "open", + "link", + "file", + "sign", + "much", + "even", + "pens", + "show", + "code", + "worm", + "long", + "deaf", + "mats", + "want", + "blah", + "mime", + "feof", + "usda", + "keen", + "peas", + "urls", + "area", + "take", + "type", + "send", + "owns", + "line", + "made", + "must", + "ebay", + "item", + "zinc", + "guru", + "real", + "levy", + "grad", + "bras", + "part", + "days", + "kyle", + "know", + "life", + "pale", + "gaps", + "tear", + "full", + "mail", + "does", + "nest", + "said", + "nato", + "user", + "gale", + "many", + "stan", + "idol", + "need", + "read", + "book", + "very", + "moss", + "each", + "cork", + "high", + "mali", + "info", + "dome", + "well", + "heel", + "yang", + "good", + "then", + "best", + "such", + "city", + "post", + "them", + "make", + "data", + "dumb", + "most", + "last", + "work", + "used", + "next", + "into", + "year", + "feat", + "ntsc", + "over", + "usps", + "just", + "name", + "conf", + "glow", + "list", + "back", + "oaks", + "erik", + "date", + "find", + "than", + "paso", + "norm", + "like", + "some", + "ware", + "were", + "been", + "jade", + "foul", + "keno", + "view", + "seas", + "help", + "pose", + "mrna", + "goat", + "also", + "here", + "sail", + "when", + "only", + "sega", + "cdna", + "news", + "what", + "bolt", + "gage", + "site", + "they", + "time", + "urge", + "smtp", + "kurt", + "neon", + "ours", + "lone", + "cope", + "free", + "lime", + "kirk", + "bool", + "page", + "home", + "will", + "spas", + "more", + "jets", + "have", + "intl", + "your", + "yarn", + "knit", + "from", + "with", + "this", + "pike", + "that", + "hugo", + "gzip", + "ctrl", + "bent", + "laos", +} diff --git a/plugins/tldr/tldr.go b/plugins/tldr/tldr.go index 25c5c37..fc03454 100644 --- a/plugins/tldr/tldr.go +++ b/plugins/tldr/tldr.go @@ -3,6 +3,7 @@ package tldr import ( "fmt" "strings" + "time" "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" @@ -12,23 +13,25 @@ import ( "github.com/james-bowman/nlp" ) -var ( - THESE_ARE_NOT_THE_WORDS_YOU_ARE_LOOKING_FOR = []string{"p", "z", "i", "c", "e", "s", "x", "n", "b", "t", "d", "m", "r", "a", "f", "l", "w", "o", "g", "h", "v", "k", "y", "j", "u", "q", "th", "wu", "qt", "so", "ru", "pm", "in", "is", "am", "me", "on", "by", "kw", "hu", "bg", "ob", "re", "wx", "go", "hl", "vc", "bl", "rg", "wr", "cw", "pj", "tf", "nr", "aw", "qc", "it", "cj", "or", "ty", "hk", "be", "wc", "de", "lf", "mj", "bw", "at", "as", "gd", "ww", "ko", "og", "gg", "cz", "an", "mh", "we", "rb", "mv", "uk", "wt", "us", "hq", "if", "mu", "pn", "js", "my", "ol", "ul", "io", "lm", "do", "cd", "fo", "no", "vg", "lu", "dg", "zu", "sv", "wn", "fu", "dk", "tv", "la", "sn", "wb", "pc", "he", "pk", "ii", "wm", "up", "bo", "ca", "fd", "uh", "hh", "al", "id", "bd", "uw", "co", "pf", "ez", "df", "ro", "et", "dh", "ui", "gl", "st", "rl", "ev", "jj", "fp", "hc", "en", "eh", "rp", "ka", "rj", "bm", "oh", "tb", "ix", "ad", "cg", "ny", "rn", "cn", "dc", "vp", "jm", "tp", "om", "ok", "ms", "wp", "hi", "aj", "oc", "sq", "hp", "yu", "sk", "dx", "eg", "ip", "bk", "hz", "pa", "fg", "rh", "tx", "ve", "za", "ht", "ie", "el", "ma", "xi", "ou", "dp", "nu", "mw", "mf", "md", "fl", "mb", "mr", "ld", "uc", "il", "ln", "mm", "ur", "ed", "pd", "le", "jc", "az", "un", "mi", "dm", "wy", "jd", "oe", "to", "pb", "dr", "kb", "pp", "na", "rx", "os", "nb", "yn", "ci", "gc", "ex", "dt", "au", "fi", "np", "nc", "po", "va", "rd", "sc", "ws", "cu", "se", "di", "km", "ga", "ac", "ft", "lc", "fa", "im", "vs", "ar", "mo", "sa", "sg", "uv", "xp", "je", "eq", "lt", "eu", "cc", "wa", "dj", "ls", "cm", "wi", "dl", "ct", "fx", "yo", "da", "vb", "of", "nj", "hr", "em", "iv", "nn", "rw", "fs", "ye", "um", "ni", "ne", "du", "oo", "bp", "gs", "fw", "nt", "es", "fc", "ti", "cb", "cv", "gb", "bc", "pr", "fr", "aa", "mt", "ir", "gp", "oz", "mg", "tc", "hb", "sl", "af", "bt", "ch", "sd", "jp", "lb", "rs", "ep", "ef", "rr", "fy", "tu", "dv", "xl", "ss", "tt", "ap", "nm", "mn", "nd", "pe", "op", "ng", "tn", "ge", "ts", "gr", "ce", "mx", "ab", "ic", "yr", "ot", "ai", "pi", "rv", "hs", "ae", "tm", "sp", "sh", "gt", "nh", "ho", "cl", "ll", "fm", "gi", "ta", "db", "ph", "ia", "pt", "bi", "ha", "ds", "ea", "lg", "bs", "ja", "ns", "wv", "nw", "sm", "ff", "ah", "sb", "td", "fe", "ak", "rf", "ps", "ky", "pl", "br", "lo", "ml", "dd", "cp", "cs", "rt", "ri", "gm", "sf", "kg", "ut", "si", "mc", "vt", "lp", "cf", "rm", "ag", "vi", "ec", "ba", "rc", "cr", "pg", "ee", "ra", "ks", "sw", "av", "te", "hd", "nz", "bb", "er", "jr", "tr", "nv", "ya", "nl", "li", "su", "mp", "sr", "ted", "bid", "can", "the", "nat", "car", "wan", "dig", "neo", "enb", "pvc", "dod", "fri", "dvd", "cia", "tex", "wed", "une", "how", "inn", "lid", "mia", "ltd", "los", "are", "yen", "cho", "dui", "inc", "win", "col", "upc", "bed", "dsc", "ste", "aye", "nhs", "dow", "tue", "cio", "ooo", "cas", "thu", "sea", "cut", "mpg", "rrp", "tel", "its", "ips", "pts", "own", "kit", "mug", "has", "sku", "nbc", "dip", "acm", "boy", "end", "ids", "him", "est", "son", "ict", "mac", "iii", "gmt", "max", "per", "xml", "big", "bin", "law", "sap", "ala", "art", "cir", "lip", "bat", "top", "eco", "sol", "van", "had", "buf", "rip", "ads", "usa", "wma", "seq", "pop", "int", "rid", "rna", "sim", "abs", "hit", "but", "wal", "ati", "doe", "eye", "geo", "old", "arg", "usb", "uni", "php", "etc", "diy", "leo", "tgp", "mud", "msn", "fee", "rpg", "las", "ide", "sic", "min", "aid", "avi", "ons", "non", "mel", "div", "ppc", "day", "fat", "saw", "cet", "cow", "mls", "pst", "why", "phi", "bra", "mae", "tom", "fin", "sub", "irc", "gpl", "led", "fan", "low", "ten", "gif", "ate", "man", "cat", "die", "ton", "tmp", "rec", "two", "ddr", "our", "gsm", "pet", "guy", "dev", "cup", "vol", "one", "you", "mag", "dee", "pit", "mba", "lee", "job", "boc", "pmc", "cfr", "bee", "vii", "llp", "too", "tap", "for", "bob", "fit", "men", "met", "mem", "por", "www", "cgi", "soa", "jvc", "tft", "ccd", "liz", "ice", "dat", "ali", "box", "llc", "sec", "bus", "ash", "bag", "gay", "all", "tub", "sox", "ibm", "sas", "gig", "qui", "pty", "dns", "air", "nor", "bug", "mid", "pas", "icq", "sys", "das", "lil", "cnn", "jim", "buy", "yes", "dam", "del", "hot", "qld", "new", "say", "ist", "joe", "may", "cab", "cds", "nav", "ind", "pct", "pos", "dec", "pod", "vic", "psi", "san", "cms", "gem", "tax", "uri", "got", "atm", "vcr", "lab", "cvs", "hon", "let", "bow", "des", "cbs", "eos", "lcd", "inf", "ave", "act", "red", "pie", "apt", "her", "alt", "ant", "key", "ppm", "tan", "few", "sip", "out", "kde", "pic", "gym", "age", "mat", "add", "use", "asn", "pgp", "lou", "jan", "oct", "pay", "tag", "mix", "any", "vhs", "fix", "pal", "tri", "thy", "war", "nov", "ray", "leu", "fda", "see", "vid", "std", "gmc", "dry", "spa", "aaa", "con", "ups", "fax", "yet", "gel", "sao", "lap", "sun", "rss", "nhl", "gen", "mtv", "mil", "cod", "not", "run", "net", "msg", "eau", "plc", "was", "var", "dos", "put", "rat", "his", "won", "oem", "tin", "doc", "try", "mom", "rap", "mlb", "row", "she", "flu", "opt", "usd", "abu", "ssl", "ana", "jpg", "eat", "cdt", "ins", "aim", "isp", "seo", "les", "bye", "ann", "tip", "rfc", "log", "ski", "irs", "faq", "dan", "chi", "nut", "wax", "fly", "dts", "fun", "gbp", "sen", "hey", "sue", "bbc", "ace", "tea", "avg", "sky", "feb", "rom", "eng", "toy", "sep", "src", "hip", "hub", "ghz", "eds", "lot", "val", "dot", "hiv", "pda", "dir", "ask", "dsl", "zum", "dna", "tcp", "cad", "fcc", "tee", "aka", "tim", "sql", "zoo", "don", "due", "mai", "cry", "vpn", "who", "dim", "mar", "cop", "gps", "erp", "acc", "pro", "cap", "ink", "phd", "pam", "url", "aug", "pin", "raw", "gnu", "amy", "ben", "ext", "web", "aol", "ago", "pac", "odd", "ent", "hat", "zus", "lib", "ban", "cos", "utc", "der", "fed", "apr", "ion", "roy", "cam", "app", "wet", "ram", "nil", "fox", "mrs", "arc", "arm", "via", "jar", "obj", "dom", "kai", "rio", "jam", "nyc", "len", "pub", "bad", "mas", "set", "hop", "bon", "gst", "gun", "ata", "rca", "ira", "eva", "rev", "sur", "sie", "lat", "sam", "pdt", "mhz", "egg", "tvs", "pee", "rpm", "img", "ref", "pot", "far", "kid", "map", "pan", "tba", "cal", "now", "and", "sad", "jul", "psp", "fbi", "jun", "hrs", "ham", "und", "rod", "wav", "dem", "way", "pad", "nfl", "eve", "rug", "soc", "amd", "usc", "mic", "tar", "fur", "yea", "iso", "sum", "vip", "amp", "str", "oak", "vat", "fog", "duo", "sig", "get", "sir", "crm", "kim", "lie", "gba", "oil", "spy", "bit", "aud", "foo", "den", "yrs", "pix", "res", "sit", "wow", "isa", "ada", "una", "que", "lit", "pig", "fig", "gdp", "bbs", "nec", "nam", "sms", "tab", "bay", "css", "gtk", "lan", "urw", "qty", "hwy", "aus", "fwd", "bio", "api", "toe", "sri", "pcs", "bar", "mit", "von", "dog", "rep", "ser", "wit", "ceo", "sci", "edt", "cst", "sin", "bmw", "hay", "eur", "kay", "pdf", "mod", "dis", "zen", "ian", "ing", "rim", "tie", "pci", "ear", "nsw", "ftp", "med", "reg", "wto", "ver", "gui", "leg", "pat", "off", "dad", "abc", "org", "usr", "jay", "gap", "ron", "til", "mon", "com", "biz", "rob", "era", "gcc", "asp", "did", "epa", "jet", "par", "nba", "loc", "gas", "mad", "six", "gis", "def", "ken", "pre", "exp", "bet", "pen", "mph", "dpi", "joy", "cpu", "ran", "lol", "sat", "jon", "lay", "lbs", "zip", "ill", "rows", "pipe", "seal", "deck", "sand", "thin", "shoe", "sick", "dose", "till", "cafe", "lets", "andy", "semi", "cats", "cake", "gang", "greg", "dial", "luck", "belt", "tube", "rail", "folk", "tiny", "okay", "hist", "lift", "lisa", "mall", "wing", "neck", "fell", "yard", "busy", "tone", "sean", "pour", "gate", "tion", "dust", "wiki", "kent", "adds", "bugs", "bone", "bang", "alex", "ward", "meat", "roof", "kiss", "peer", "seed", "para", "cute", "rush", "mpeg", "yoga", "lamp", "rico", "phil", "pmid", "http", "bulk", "glad", "wins", "rack", "aged", "scan", "bold", "boss", "ross", "anna", "solo", "tall", "grey", "pdas", "beds", "ryan", "nova", "exam", "anne", "pump", "wake", "plot", "nick", "nasa", "drum", "pull", "foto", "ease", "tabs", "voip", "grid", "pine", "tend", "gulf", "echo", "rick", "char", "hunt", "thai", "fred", "chip", "mill", "suit", "bits", "dont", "burn", "labs", "twin", "earn", "jane", "jose", "beer", "dear", "alan", "misc", "push", "sole", "boot", "laid", "clay", "weak", "milk", "blvd", "arab", "wise", "rome", "odds", "vary", "gary", "marc", "sons", "leaf", "loop", "rice", "hate", "demo", "cuba", "gray", "silk", "kate", "slot", "adam", "wolf", "dish", "fits", "kick", "meal", "navy", "hurt", "tank", "bowl", "slip", "mens", "cuts", "mile", "mars", "lock", "node", "rear", "caps", "pill", "legs", "meta", "mint", "crew", "spin", "babe", "wash", "warm", "draw", "aims", "lens", "ieee", "pure", "corp", "visa", "jean", "soap", "bond", "unix", "poll", "axis", "guns", "dean", "mesh", "hero", "acts", "punk", "holy", "duke", "wave", "pace", "wage", "keys", "iran", "dawn", "carl", "coat", "exit", "rica", "matt", "soil", "kits", "tony", "doll", "seek", "peru", "nike", "lose", "reed", "mice", "bike", "temp", "perl", "vast", "cook", "plug", "wrap", "mood", "quiz", "ages", "kill", "lane", "beam", "tops", "jeff", "bell", "shut", "salt", "ncaa", "thou", "peak", "mask", "euro", "evil", "coal", "yeah", "runs", "pair", "ride", "pets", "lion", "goto", "hole", "neil", "beef", "bass", "hats", "diff", "surf", "onto", "rain", "hook", "cord", "grow", "crop", "spot", "eric", "lite", "nine", "faqs", "slow", "hide", "utah", "arms", "sing", "tons", "beat", "kept", "hang", "wars", "fear", "hood", "moon", "dogs", "math", "fame", "whom", "mine", "cape", "toll", "bids", "seat", "eggs", "dell", "fans", "lady", "ruby", "mins", "bird", "stem", "rise", "drew", "dual", "bars", "rare", "tune", "corn", "wear", "puts", "grew", "bags", "trek", "jazz", "fail", "ties", "beta", "brad", "jury", "font", "tail", "lawn", "soup", "byte", "nose", "oclc", "bath", "juan", "roll", "zero", "thru", "jews", "trim", "null", "cent", "acid", "espn", "spam", "quit", "lung", "tape", "wire", "clip", "todd", "blow", "doug", "sees", "zoom", "knew", "bull", "cole", "mart", "tale", "lynn", "iowa", "lack", "docs", "gain", "bear", "coin", "fake", "duty", "cure", "arch", "vice", "hdtv", "asin", "bomb", "harm", "hong", "deer", "dave", "desk", "disk", "void", "iron", "atom", "flag", "oven", "aids", "noon", "soul", "felt", "cast", "cams", "joel", "ends", "proc", "icon", "boat", "mate", "disc", "chef", "isle", "slim", "luke", "comp", "gene", "fort", "gone", "fill", "pete", "spec", "camp", "penn", "midi", "tied", "snow", "dale", "oils", "sept", "unto", "inch", "died", "kong", "pays", "rank", "lang", "stud", "fold", "ones", "gave", "hire", "seem", "ipod", "phys", "pole", "mega", "bend", "moms", "glen", "rich", "drop", "guys", "tags", "lips", "pond", "load", "pick", "rose", "wait", "walk", "tire", "chad", "fuel", "josh", "drag", "soft", "ripe", "rely", "scsi", "task", "miss", "wild", "heat", "nuts", "nail", "span", "mass", "joke", "univ", "foot", "pads", "inns", "cups", "cold", "shot", "pink", "foam", "root", "edge", "poem", "ford", "oral", "asks", "bean", "bias", "xbox", "pain", "palm", "wind", "sold", "swim", "nano", "goal", "ball", "dvds", "loud", "rats", "jump", "stat", "cruz", "bios", "firm", "thee", "lots", "ruth", "pray", "pope", "jeep", "bare", "hung", "gear", "army", "mono", "tile", "diet", "apps", "skip", "laws", "path", "flow", "ciao", "knee", "prep", "flat", "chem", "jack", "zone", "hits", "pros", "cant", "wife", "goes", "hear", "lord", "farm", "sara", "eyes", "joan", "duck", "poor", "trip", "mike", "dive", "dead", "fiji", "audi", "raid", "gets", "volt", "ohio", "dirt", "fair", "acer", "dist", "isbn", "geek", "sink", "grip", "host", "watt", "pins", "reno", "dark", "polo", "rent", "horn", "wood", "prot", "frog", "logs", "sets", "core", "debt", "snap", "race", "born", "pack", "fish", "jpeg", "mini", "pool", "swap", "rest", "flip", "deep", "boys", "buzz", "nuke", "iraq", "boom", "calm", "fork", "troy", "ring", "mary", "prev", "zope", "gmbh", "skin", "fees", "sims", "tray", "pass", "sage", "java", "uses", "asia", "cool", "suse", "door", "cave", "wool", "feet", "told", "rule", "ways", "eyed", "vote", "grab", "oops", "wine", "wall", "thus", "tree", "trap", "fool", "hair", "karl", "dies", "paid", "ship", "anti", "hall", "jail", "feed", "safe", "ipaq", "hold", "comm", "deal", "maps", "lace", "hill", "ugly", "hart", "ment", "tool", "idea", "fall", "biol", "late", "lies", "cnet", "song", "took", "treo", "gods", "male", "fund", "mode", "poly", "ears", "went", "lead", "fist", "band", "mere", "cons", "sent", "taxi", "nice", "logo", "move", "kind", "huge", "bush", "hour", "worn", "shaw", "fine", "expo", "came", "deny", "bali", "judy", "trio", "cube", "rugs", "fate", "role", "gras", "wish", "hope", "menu", "tour", "lost", "mind", "oval", "held", "soma", "soon", "href", "benz", "wifi", "tier", "stop", "earl", "port", "seen", "guam", "cite", "cash", "pics", "drug", "copy", "mess", "king", "mean", "turn", "stay", "rope", "dump", "near", "base", "face", "loss", "hose", "html", "chat", "fire", "sony", "pubs", "lake", "paul", "mild", "none", "step", "half", "sort", "clan", "sync", "mesa", "wide", "loan", "hull", "golf", "toys", "shed", "memo", "girl", "tide", "funk", "town", "reel", "risk", "bind", "rand", "buck", "bank", "feel", "meet", "usgs", "acre", "lows", "aqua", "chen", "emma", "tech", "pest", "unit", "fact", "fast", "reef", "edit", "auto", "plus", "chan", "tips", "beth", "rock", "mark", "else", "jill", "sofa", "true", "dans", "viii", "kids", "talk", "tent", "dept", "hack", "dare", "hawk", "lamb", "bill", "word", "ever", "done", "land", "says", "upon", "five", "past", "arts", "gold", "able", "junk", "tell", "lucy", "hans", "poet", "epic", "cars", "sake", "sans", "sure", "lean", "once", "away", "self", "dude", "luis", "cell", "head", "film", "alto", "term", "baby", "keep", "gore", "cult", "dash", "cage", "divx", "hugh", "hand", "jake", "eval", "ping", "flux", "star", "muze", "oman", "easy", "blue", "rage", "adsl", "four", "prix", "hard", "gift", "avon", "rays", "road", "walt", "acne", "libs", "undo", "club", "east", "dana", "halo", "body", "sell", "gays", "give", "exec", "side", "park", "blog", "less", "play", "maui", "cart", "come", "test", "vids", "yale", "july", "cost", "plan", "june", "doom", "owen", "bite", "issn", "myth", "live", "note", "week", "weed", "oecd", "dice", "quad", "dock", "mods", "hint", "msie", "team", "left", "look", "west", "buys", "pork", "join", "barn", "room", "teen", "fare", "sale", "asus", "food", "bald", "fuji", "leon", "mold", "dame", "jobs", "herb", "card", "alot", "york", "idle", "save", "call", "main", "john", "love", "form", "rate", "text", "cove", "casa", "shop", "eden", "incl", "size", "down", "care", "game", "reid", "both", "flex", "rosa", "hash", "lazy", "same", "case", "carb", "open", "link", "file", "sign", "much", "even", "pens", "show", "code", "worm", "long", "deaf", "mats", "want", "blah", "mime", "feof", "usda", "keen", "peas", "urls", "area", "take", "type", "send", "owns", "line", "made", "must", "ebay", "item", "zinc", "guru", "real", "levy", "grad", "bras", "part", "days", "kyle", "know", "life", "pale", "gaps", "tear", "full", "mail", "does", "nest", "said", "nato", "user", "gale", "many", "stan", "idol", "need", "read", "book", "very", "moss", "each", "cork", "high", "mali", "info", "dome", "well", "heel", "yang", "good", "then", "best", "such", "city", "post", "them", "make", "data", "dumb", "most", "last", "work", "used", "next", "into", "year", "feat", "ntsc", "over", "usps", "just", "name", "conf", "glow", "list", "back", "oaks", "erik", "date", "find", "than", "paso", "norm", "like", "some", "ware", "were", "been", "jade", "foul", "keno", "view", "seas", "help", "pose", "mrna", "goat", "also", "here", "sail", "when", "only", "sega", "cdna", "news", "what", "bolt", "gage", "site", "they", "time", "urge", "smtp", "kurt", "neon", "ours", "lone", "cope", "free", "lime", "kirk", "bool", "page", "home", "will", "spas", "more", "jets", "have", "intl", "your", "yarn", "knit", "from", "with", "this", "pike", "that", "hugo", "gzip", "ctrl", "bent", "laos"} -) - type TLDRPlugin struct { - Bot bot.Bot - History []string - Users []string - Index int + bot bot.Bot + history []history + index int + lastRequest time.Time +} + +type history struct { + timestamp time.Time + user string + body string } func New(b bot.Bot) *TLDRPlugin { plugin := &TLDRPlugin{ - Bot: b, - History: []string{}, - Users: []string{}, - Index: 0, + bot: b, + history: []history{}, + index: 0, + lastRequest: time.Now().Add(-24 * time.Hour), } b.Register(plugin, bot.Message, plugin.message) b.Register(plugin, bot.Help, plugin.help) @@ -36,14 +39,19 @@ func New(b bot.Bot) *TLDRPlugin { } func (p *TLDRPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { + timeLimit := time.Duration(p.bot.Config().GetInt("TLDR.HourLimit", 1)) lowercaseMessage := strings.ToLower(message.Body) - if lowercaseMessage == "tl;dr" { - nTopics := p.Bot.Config().GetInt("TLDR.Topics", 5) + if lowercaseMessage == "tl;dr" && p.lastRequest.After(time.Now().Add(-timeLimit*time.Hour)) { + p.bot.Send(bot.Message, message.Channel, "Slow down, cowboy. Read that tiny backlog.") + return true + } else if lowercaseMessage == "tl;dr" { + p.lastRequest = time.Now() + nTopics := p.bot.Config().GetInt("TLDR.Topics", 5) vectoriser := nlp.NewCountVectoriser(THESE_ARE_NOT_THE_WORDS_YOU_ARE_LOOKING_FOR...) lda := nlp.NewLatentDirichletAllocation(nTopics) pipeline := nlp.NewPipeline(vectoriser, lda) - docsOverTopics, err := pipeline.FitTransform(p.History...) + docsOverTopics, err := pipeline.FitTransform(p.getTopics()...) if err != nil { log.Error().Err(err) @@ -51,14 +59,12 @@ func (p *TLDRPlugin) message(kind bot.Kind, message msg.Message, args ...interfa } bestScores := make([][]float64, nTopics) - bestDocs := make([][]string, nTopics) - bestUsers := make([][]string, nTopics) + bestDocs := make([][]history, nTopics) - supportingDocs := p.Bot.Config().GetInt("TLDR.Support", 3) + supportingDocs := p.bot.Config().GetInt("TLDR.Support", 3) for i := 0; i < nTopics; i++ { bestScores[i] = make([]float64, supportingDocs) - bestDocs[i] = make([]string, supportingDocs) - bestUsers[i] = make([]string, supportingDocs) + bestDocs[i] = make([]history, supportingDocs) } dr, dc := docsOverTopics.Dims() @@ -69,8 +75,7 @@ func (p *TLDRPlugin) message(kind bot.Kind, message msg.Message, args ...interfa score := docsOverTopics.At(topic, doc) if score > minScore { bestScores[topic][minIndex] = score - bestDocs[topic][minIndex] = p.History[doc] - bestUsers[topic][minIndex] = p.Users[doc] + bestDocs[topic][minIndex] = p.history[doc] minScore, minIndex = min(bestScores[topic]) } } @@ -98,45 +103,65 @@ func (p *TLDRPlugin) message(kind bot.Kind, message msg.Message, args ...interfa } response += fmt.Sprintf("Topic #%d : %s\n", topic, bestTopic) for i := range bestDocs[topic] { - if bestUsers[topic][i] != "" { - response += fmt.Sprintf("\t<%s>%s [%f]\n", bestUsers[topic][i], bestDocs[topic][i], bestScores[topic][i]) - } + response += fmt.Sprintf("\t<%s>%s [%f]\n", bestDocs[topic][i].user, bestDocs[topic][i].body, bestScores[topic][i]) } } - p.Bot.Send(bot.Message, message.Channel, response) + p.bot.Send(bot.Message, message.Channel, response) return true } if shouldKeepMessage(lowercaseMessage) { - currentHistorySize := len(p.History) - maxHistorySize := p.Bot.Config().GetInt("TLDR.HistorySize", 1000) + currentHistorySize := len(p.history) + maxHistorySize := p.bot.Config().GetInt("TLDR.HistorySize", 1000) + hist := history{ + body: lowercaseMessage, + user: message.User.Name, + timestamp: time.Now(), + } if currentHistorySize < maxHistorySize { - p.History = append(p.History, lowercaseMessage) - p.Users = append(p.Users, message.User.Name) - p.Index = 0 + p.history = append(p.history, hist) + p.index = 0 } else { if currentHistorySize > maxHistorySize { // We could resize this but we want to prune the oldest stuff, and // I don't care to do this correctly so might as well not do it at all } - if p.Index >= currentHistorySize { - p.Index = 0 + if p.index >= currentHistorySize { + p.index = 0 } - p.History[p.Index] = lowercaseMessage - p.Users[p.Index] = message.User.Name - p.Index++ + p.history[p.index] = hist + p.index++ } } return false } +func (p *TLDRPlugin) getTopics() []string { + hist := []string{} + for _, h := range p.history { + hist = append(hist, h.body) + } + return hist +} + +func (p *TLDRPlugin) pruneHistory() { + out := []history{} + yesterday := time.Now().Add(-24 * time.Hour) + for _, h := range p.history { + if yesterday.Before(h.timestamp) { + out = append(out, h) + } + } + p.history = out +} + // Help responds to help requests. Every plugin must implement a help function. func (p *TLDRPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { - p.Bot.Send(bot.Message, message.Channel, "tl;dr") + p.bot.Send(bot.Message, message.Channel, "tl;dr") return true } diff --git a/plugins/tldr/tldr_test.go b/plugins/tldr/tldr_test.go index 9d42cde..1b809b5 100644 --- a/plugins/tldr/tldr_test.go +++ b/plugins/tldr/tldr_test.go @@ -1,15 +1,22 @@ package tldr import ( + "os" "strings" "testing" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" "github.com/stretchr/testify/assert" "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" "github.com/velour/catbase/bot/user" ) +func init() { + log.Logger = log.Logger.Output(zerolog.ConsoleWriter{Out: os.Stderr}) +} + func makeMessageBy(payload, by string) (bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { @@ -42,3 +49,15 @@ func Test(t *testing.T) { assert.True(t, res) assert.Len(t, mb.Messages, 1) } + +func TestDoubleUp(t *testing.T) { + c, mb := setup(t) + res := c.message(makeMessage("The quick brown fox jumped over the lazy dog")) + res = c.message(makeMessage("The cow jumped over the moon")) + res = c.message(makeMessage("The little dog laughed to see such fun")) + res = c.message(makeMessage("tl;dr")) + res = c.message(makeMessage("tl;dr")) + assert.True(t, res) + assert.Len(t, mb.Messages, 2) + assert.Contains(t, mb.Messages[1], "Slow down, cowboy.") +} From fa5a0a284b4e4e2e34a5cf1d36d53f7ce55ca5c8 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Tue, 26 Mar 2019 17:55:07 -0400 Subject: [PATCH 074/107] tldr: fix small bug about indexing --- plugins/tldr/tldr.go | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/tldr/tldr.go b/plugins/tldr/tldr.go index fc03454..0f5b434 100644 --- a/plugins/tldr/tldr.go +++ b/plugins/tldr/tldr.go @@ -157,6 +157,7 @@ func (p *TLDRPlugin) pruneHistory() { } } p.history = out + p.index = len(out) } // Help responds to help requests. Every plugin must implement a help function. From efcf8a7c3461960d006e4c6908285d71d5511f4f Mon Sep 17 00:00:00 2001 From: Ethan Burns Date: Tue, 26 Mar 2019 19:31:09 -0400 Subject: [PATCH 075/107] Fix tl;dr formatting Remove extra space before `:`. Make topics bold. Add a blank line between topics. Remove leading tabs. --- plugins/tldr/tldr.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/tldr/tldr.go b/plugins/tldr/tldr.go index 0f5b434..80666eb 100644 --- a/plugins/tldr/tldr.go +++ b/plugins/tldr/tldr.go @@ -101,10 +101,11 @@ func (p *TLDRPlugin) message(kind bot.Kind, message msg.Message, args ...interfa bestTopic = vocab[word] } } - response += fmt.Sprintf("Topic #%d : %s\n", topic, bestTopic) + response += fmt.Sprintf("\n*Topic #%d: %s*\n", topic, bestTopic) for i := range bestDocs[topic] { - response += fmt.Sprintf("\t<%s>%s [%f]\n", bestDocs[topic][i].user, bestDocs[topic][i].body, bestScores[topic][i]) + response += fmt.Sprintf("<%s>%s [%f]\n", bestDocs[topic][i].user, bestDocs[topic][i].body, bestScores[topic][i]) } + } p.bot.Send(bot.Message, message.Channel, response) From 78fc108de47952cdeab69f4d0f9f9fd4091b69bb Mon Sep 17 00:00:00 2001 From: Ethan Burns Date: Tue, 26 Mar 2019 20:22:21 -0400 Subject: [PATCH 076/107] Don't show scores in tl;dr They are noisy and not very useful. --- plugins/tldr/tldr.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/tldr/tldr.go b/plugins/tldr/tldr.go index 80666eb..ebfb63e 100644 --- a/plugins/tldr/tldr.go +++ b/plugins/tldr/tldr.go @@ -103,7 +103,7 @@ func (p *TLDRPlugin) message(kind bot.Kind, message msg.Message, args ...interfa } response += fmt.Sprintf("\n*Topic #%d: %s*\n", topic, bestTopic) for i := range bestDocs[topic] { - response += fmt.Sprintf("<%s>%s [%f]\n", bestDocs[topic][i].user, bestDocs[topic][i].body, bestScores[topic][i]) + response += fmt.Sprintf("<%s>%s\n", bestDocs[topic][i].user, bestDocs[topic][i].body) } } From e08601ebec64b1c979459ca38ea1f50943be2b07 Mon Sep 17 00:00:00 2001 From: skiesel Date: Thu, 28 Mar 2019 17:38:04 -0400 Subject: [PATCH 077/107] Add stop words to the config --- plugins/tldr/badwords.go | 2349 +++----------------------------------- plugins/tldr/tldr.go | 8 +- 2 files changed, 180 insertions(+), 2177 deletions(-) diff --git a/plugins/tldr/badwords.go b/plugins/tldr/badwords.go index 0199c53..2e7ef33 100644 --- a/plugins/tldr/badwords.go +++ b/plugins/tldr/badwords.go @@ -1,2188 +1,185 @@ package tldr +//nltk stopwords list, from some point... + var THESE_ARE_NOT_THE_WORDS_YOU_ARE_LOOKING_FOR = []string{ - "p", - "z", "i", - "c", - "e", - "s", - "x", - "n", - "b", - "t", - "d", - "m", - "r", - "a", - "f", - "l", - "w", - "o", - "g", - "h", - "v", - "k", - "y", - "j", - "u", - "q", - "th", - "wu", - "qt", - "so", - "ru", - "pm", - "in", - "is", - "am", "me", - "on", - "by", - "kw", - "hu", - "bg", - "ob", - "re", - "wx", - "go", - "hl", - "vc", - "bl", - "rg", - "wr", - "cw", - "pj", - "tf", - "nr", - "aw", - "qc", - "it", - "cj", - "or", - "ty", - "hk", - "be", - "wc", - "de", - "lf", - "mj", - "bw", - "at", - "as", - "gd", - "ww", - "ko", - "og", - "gg", - "cz", - "an", - "mh", - "we", - "rb", - "mv", - "uk", - "wt", - "us", - "hq", - "if", - "mu", - "pn", - "js", "my", - "ol", - "ul", - "io", - "lm", - "do", - "cd", - "fo", - "no", - "vg", - "lu", - "dg", - "zu", - "sv", - "wn", - "fu", - "dk", - "tv", - "la", - "sn", - "wb", - "pc", - "he", - "pk", - "ii", - "wm", - "up", - "bo", - "ca", - "fd", - "uh", - "hh", - "al", - "id", - "bd", - "uw", - "co", - "pf", - "ez", - "df", - "ro", - "et", - "dh", - "ui", - "gl", - "st", - "rl", - "ev", - "jj", - "fp", - "hc", - "en", - "eh", - "rp", - "ka", - "rj", - "bm", - "oh", - "tb", - "ix", - "ad", - "cg", - "ny", - "rn", - "cn", - "dc", - "vp", - "jm", - "tp", - "om", - "ok", - "ms", - "wp", - "hi", - "aj", - "oc", - "sq", - "hp", - "yu", - "sk", - "dx", - "eg", - "ip", - "bk", - "hz", - "pa", - "fg", - "rh", - "tx", - "ve", - "za", - "ht", - "ie", - "el", - "ma", - "xi", - "ou", - "dp", - "nu", - "mw", - "mf", - "md", - "fl", - "mb", - "mr", - "ld", - "uc", - "il", - "ln", - "mm", - "ur", - "ed", - "pd", - "le", - "jc", - "az", - "un", - "mi", - "dm", - "wy", - "jd", - "oe", - "to", - "pb", - "dr", - "kb", - "pp", - "na", - "rx", - "os", - "nb", - "yn", - "ci", - "gc", - "ex", - "dt", - "au", - "fi", - "np", - "nc", - "po", - "va", - "rd", - "sc", - "ws", - "cu", - "se", - "di", - "km", - "ga", - "ac", - "ft", - "lc", - "fa", - "im", - "vs", - "ar", - "mo", - "sa", - "sg", - "uv", - "xp", - "je", - "eq", - "lt", - "eu", - "cc", - "wa", - "dj", - "ls", - "cm", - "wi", - "dl", - "ct", - "fx", - "yo", - "da", - "vb", - "of", - "nj", - "hr", - "em", - "iv", - "nn", - "rw", - "fs", - "ye", - "um", - "ni", - "ne", - "du", - "oo", - "bp", - "gs", - "fw", - "nt", - "es", - "fc", - "ti", - "cb", - "cv", - "gb", - "bc", - "pr", - "fr", - "aa", - "mt", - "ir", - "gp", - "oz", - "mg", - "tc", - "hb", - "sl", - "af", - "bt", - "ch", - "sd", - "jp", - "lb", - "rs", - "ep", - "ef", - "rr", - "fy", - "tu", - "dv", - "xl", - "ss", - "tt", - "ap", - "nm", - "mn", - "nd", - "pe", - "op", - "ng", - "tn", - "ge", - "ts", - "gr", - "ce", - "mx", - "ab", - "ic", - "yr", - "ot", - "ai", - "pi", - "rv", - "hs", - "ae", - "tm", - "sp", - "sh", - "gt", - "nh", - "ho", - "cl", - "ll", - "fm", - "gi", - "ta", - "db", - "ph", - "ia", - "pt", - "bi", - "ha", - "ds", - "ea", - "lg", - "bs", - "ja", - "ns", - "wv", - "nw", - "sm", - "ff", - "ah", - "sb", - "td", - "fe", - "ak", - "rf", - "ps", - "ky", - "pl", - "br", - "lo", - "ml", - "dd", - "cp", - "cs", - "rt", - "ri", - "gm", - "sf", - "kg", - "ut", - "si", - "mc", - "vt", - "lp", - "cf", - "rm", - "ag", - "vi", - "ec", - "ba", - "rc", - "cr", - "pg", - "ee", - "ra", - "ks", - "sw", - "av", - "te", - "hd", - "nz", - "bb", - "er", - "jr", - "tr", - "nv", - "ya", - "nl", - "li", - "su", - "mp", - "sr", - "ted", - "bid", - "can", - "the", - "nat", - "car", - "wan", - "dig", - "neo", - "enb", - "pvc", - "dod", - "fri", - "dvd", - "cia", - "tex", - "wed", - "une", - "how", - "inn", - "lid", - "mia", - "ltd", - "los", - "are", - "yen", - "cho", - "dui", - "inc", - "win", - "col", - "upc", - "bed", - "dsc", - "ste", - "aye", - "nhs", - "dow", - "tue", - "cio", - "ooo", - "cas", - "thu", - "sea", - "cut", - "mpg", - "rrp", - "tel", - "its", - "ips", - "pts", - "own", - "kit", - "mug", - "has", - "sku", - "nbc", - "dip", - "acm", - "boy", - "end", - "ids", - "him", - "est", - "son", - "ict", - "mac", - "iii", - "gmt", - "max", - "per", - "xml", - "big", - "bin", - "law", - "sap", - "ala", - "art", - "cir", - "lip", - "bat", - "top", - "eco", - "sol", - "van", - "had", - "buf", - "rip", - "ads", - "usa", - "wma", - "seq", - "pop", - "int", - "rid", - "rna", - "sim", - "abs", - "hit", - "but", - "wal", - "ati", - "doe", - "eye", - "geo", - "old", - "arg", - "usb", - "uni", - "php", - "etc", - "diy", - "leo", - "tgp", - "mud", - "msn", - "fee", - "rpg", - "las", - "ide", - "sic", - "min", - "aid", - "avi", - "ons", - "non", - "mel", - "div", - "ppc", - "day", - "fat", - "saw", - "cet", - "cow", - "mls", - "pst", - "why", - "phi", - "bra", - "mae", - "tom", - "fin", - "sub", - "irc", - "gpl", - "led", - "fan", - "low", - "ten", - "gif", - "ate", - "man", - "cat", - "die", - "ton", - "tmp", - "rec", - "two", - "ddr", + "myself", + "we", "our", - "gsm", - "pet", - "guy", - "dev", - "cup", - "vol", - "one", - "you", - "mag", - "dee", - "pit", - "mba", - "lee", - "job", - "boc", - "pmc", - "cfr", - "bee", - "vii", - "llp", - "too", - "tap", - "for", - "bob", - "fit", - "men", - "met", - "mem", - "por", - "www", - "cgi", - "soa", - "jvc", - "tft", - "ccd", - "liz", - "ice", - "dat", - "ali", - "box", - "llc", - "sec", - "bus", - "ash", - "bag", - "gay", - "all", - "tub", - "sox", - "ibm", - "sas", - "gig", - "qui", - "pty", - "dns", - "air", - "nor", - "bug", - "mid", - "pas", - "icq", - "sys", - "das", - "lil", - "cnn", - "jim", - "buy", - "yes", - "dam", - "del", - "hot", - "qld", - "new", - "say", - "ist", - "joe", - "may", - "cab", - "cds", - "nav", - "ind", - "pct", - "pos", - "dec", - "pod", - "vic", - "psi", - "san", - "cms", - "gem", - "tax", - "uri", - "got", - "atm", - "vcr", - "lab", - "cvs", - "hon", - "let", - "bow", - "des", - "cbs", - "eos", - "lcd", - "inf", - "ave", - "act", - "red", - "pie", - "apt", - "her", - "alt", - "ant", - "key", - "ppm", - "tan", - "few", - "sip", - "out", - "kde", - "pic", - "gym", - "age", - "mat", - "add", - "use", - "asn", - "pgp", - "lou", - "jan", - "oct", - "pay", - "tag", - "mix", - "any", - "vhs", - "fix", - "pal", - "tri", - "thy", - "war", - "nov", - "ray", - "leu", - "fda", - "see", - "vid", - "std", - "gmc", - "dry", - "spa", - "aaa", - "con", - "ups", - "fax", - "yet", - "gel", - "sao", - "lap", - "sun", - "rss", - "nhl", - "gen", - "mtv", - "mil", - "cod", - "not", - "run", - "net", - "msg", - "eau", - "plc", - "was", - "var", - "dos", - "put", - "rat", - "his", - "won", - "oem", - "tin", - "doc", - "try", - "mom", - "rap", - "mlb", - "row", - "she", - "flu", - "opt", - "usd", - "abu", - "ssl", - "ana", - "jpg", - "eat", - "cdt", - "ins", - "aim", - "isp", - "seo", - "les", - "bye", - "ann", - "tip", - "rfc", - "log", - "ski", - "irs", - "faq", - "dan", - "chi", - "nut", - "wax", - "fly", - "dts", - "fun", - "gbp", - "sen", - "hey", - "sue", - "bbc", - "ace", - "tea", - "avg", - "sky", - "feb", - "rom", - "eng", - "toy", - "sep", - "src", - "hip", - "hub", - "ghz", - "eds", - "lot", - "val", - "dot", - "hiv", - "pda", - "dir", - "ask", - "dsl", - "zum", - "dna", - "tcp", - "cad", - "fcc", - "tee", - "aka", - "tim", - "sql", - "zoo", - "don", - "due", - "mai", - "cry", - "vpn", - "who", - "dim", - "mar", - "cop", - "gps", - "erp", - "acc", - "pro", - "cap", - "ink", - "phd", - "pam", - "url", - "aug", - "pin", - "raw", - "gnu", - "amy", - "ben", - "ext", - "web", - "aol", - "ago", - "pac", - "odd", - "ent", - "hat", - "zus", - "lib", - "ban", - "cos", - "utc", - "der", - "fed", - "apr", - "ion", - "roy", - "cam", - "app", - "wet", - "ram", - "nil", - "fox", - "mrs", - "arc", - "arm", - "via", - "jar", - "obj", - "dom", - "kai", - "rio", - "jam", - "nyc", - "len", - "pub", - "bad", - "mas", - "set", - "hop", - "bon", - "gst", - "gun", - "ata", - "rca", - "ira", - "eva", - "rev", - "sur", - "sie", - "lat", - "sam", - "pdt", - "mhz", - "egg", - "tvs", - "pee", - "rpm", - "img", - "ref", - "pot", - "far", - "kid", - "map", - "pan", - "tba", - "cal", - "now", - "and", - "sad", - "jul", - "psp", - "fbi", - "jun", - "hrs", - "ham", - "und", - "rod", - "wav", - "dem", - "way", - "pad", - "nfl", - "eve", - "rug", - "soc", - "amd", - "usc", - "mic", - "tar", - "fur", - "yea", - "iso", - "sum", - "vip", - "amp", - "str", - "oak", - "vat", - "fog", - "duo", - "sig", - "get", - "sir", - "crm", - "kim", - "lie", - "gba", - "oil", - "spy", - "bit", - "aud", - "foo", - "den", - "yrs", - "pix", - "res", - "sit", - "wow", - "isa", - "ada", - "una", - "que", - "lit", - "pig", - "fig", - "gdp", - "bbs", - "nec", - "nam", - "sms", - "tab", - "bay", - "css", - "gtk", - "lan", - "urw", - "qty", - "hwy", - "aus", - "fwd", - "bio", - "api", - "toe", - "sri", - "pcs", - "bar", - "mit", - "von", - "dog", - "rep", - "ser", - "wit", - "ceo", - "sci", - "edt", - "cst", - "sin", - "bmw", - "hay", - "eur", - "kay", - "pdf", - "mod", - "dis", - "zen", - "ian", - "ing", - "rim", - "tie", - "pci", - "ear", - "nsw", - "ftp", - "med", - "reg", - "wto", - "ver", - "gui", - "leg", - "pat", - "off", - "dad", - "abc", - "org", - "usr", - "jay", - "gap", - "ron", - "til", - "mon", - "com", - "biz", - "rob", - "era", - "gcc", - "asp", - "did", - "epa", - "jet", - "par", - "nba", - "loc", - "gas", - "mad", - "six", - "gis", - "def", - "ken", - "pre", - "exp", - "bet", - "pen", - "mph", - "dpi", - "joy", - "cpu", - "ran", - "lol", - "sat", - "jon", - "lay", - "lbs", - "zip", - "ill", - "rows", - "pipe", - "seal", - "deck", - "sand", - "thin", - "shoe", - "sick", - "dose", - "till", - "cafe", - "lets", - "andy", - "semi", - "cats", - "cake", - "gang", - "greg", - "dial", - "luck", - "belt", - "tube", - "rail", - "folk", - "tiny", - "okay", - "hist", - "lift", - "lisa", - "mall", - "wing", - "neck", - "fell", - "yard", - "busy", - "tone", - "sean", - "pour", - "gate", - "tion", - "dust", - "wiki", - "kent", - "adds", - "bugs", - "bone", - "bang", - "alex", - "ward", - "meat", - "roof", - "kiss", - "peer", - "seed", - "para", - "cute", - "rush", - "mpeg", - "yoga", - "lamp", - "rico", - "phil", - "pmid", - "http", - "bulk", - "glad", - "wins", - "rack", - "aged", - "scan", - "bold", - "boss", - "ross", - "anna", - "solo", - "tall", - "grey", - "pdas", - "beds", - "ryan", - "nova", - "exam", - "anne", - "pump", - "wake", - "plot", - "nick", - "nasa", - "drum", - "pull", - "foto", - "ease", - "tabs", - "voip", - "grid", - "pine", - "tend", - "gulf", - "echo", - "rick", - "char", - "hunt", - "thai", - "fred", - "chip", - "mill", - "suit", - "bits", - "dont", - "burn", - "labs", - "twin", - "earn", - "jane", - "jose", - "beer", - "dear", - "alan", - "misc", - "push", - "sole", - "boot", - "laid", - "clay", - "weak", - "milk", - "blvd", - "arab", - "wise", - "rome", - "odds", - "vary", - "gary", - "marc", - "sons", - "leaf", - "loop", - "rice", - "hate", - "demo", - "cuba", - "gray", - "silk", - "kate", - "slot", - "adam", - "wolf", - "dish", - "fits", - "kick", - "meal", - "navy", - "hurt", - "tank", - "bowl", - "slip", - "mens", - "cuts", - "mile", - "mars", - "lock", - "node", - "rear", - "caps", - "pill", - "legs", - "meta", - "mint", - "crew", - "spin", - "babe", - "wash", - "warm", - "draw", - "aims", - "lens", - "ieee", - "pure", - "corp", - "visa", - "jean", - "soap", - "bond", - "unix", - "poll", - "axis", - "guns", - "dean", - "mesh", - "hero", - "acts", - "punk", - "holy", - "duke", - "wave", - "pace", - "wage", - "keys", - "iran", - "dawn", - "carl", - "coat", - "exit", - "rica", - "matt", - "soil", - "kits", - "tony", - "doll", - "seek", - "peru", - "nike", - "lose", - "reed", - "mice", - "bike", - "temp", - "perl", - "vast", - "cook", - "plug", - "wrap", - "mood", - "quiz", - "ages", - "kill", - "lane", - "beam", - "tops", - "jeff", - "bell", - "shut", - "salt", - "ncaa", - "thou", - "peak", - "mask", - "euro", - "evil", - "coal", - "yeah", - "runs", - "pair", - "ride", - "pets", - "lion", - "goto", - "hole", - "neil", - "beef", - "bass", - "hats", - "diff", - "surf", - "onto", - "rain", - "hook", - "cord", - "grow", - "crop", - "spot", - "eric", - "lite", - "nine", - "faqs", - "slow", - "hide", - "utah", - "arms", - "sing", - "tons", - "beat", - "kept", - "hang", - "wars", - "fear", - "hood", - "moon", - "dogs", - "math", - "fame", - "whom", - "mine", - "cape", - "toll", - "bids", - "seat", - "eggs", - "dell", - "fans", - "lady", - "ruby", - "mins", - "bird", - "stem", - "rise", - "drew", - "dual", - "bars", - "rare", - "tune", - "corn", - "wear", - "puts", - "grew", - "bags", - "trek", - "jazz", - "fail", - "ties", - "beta", - "brad", - "jury", - "font", - "tail", - "lawn", - "soup", - "byte", - "nose", - "oclc", - "bath", - "juan", - "roll", - "zero", - "thru", - "jews", - "trim", - "null", - "cent", - "acid", - "espn", - "spam", - "quit", - "lung", - "tape", - "wire", - "clip", - "todd", - "blow", - "doug", - "sees", - "zoom", - "knew", - "bull", - "cole", - "mart", - "tale", - "lynn", - "iowa", - "lack", - "docs", - "gain", - "bear", - "coin", - "fake", - "duty", - "cure", - "arch", - "vice", - "hdtv", - "asin", - "bomb", - "harm", - "hong", - "deer", - "dave", - "desk", - "disk", - "void", - "iron", - "atom", - "flag", - "oven", - "aids", - "noon", - "soul", - "felt", - "cast", - "cams", - "joel", - "ends", - "proc", - "icon", - "boat", - "mate", - "disc", - "chef", - "isle", - "slim", - "luke", - "comp", - "gene", - "fort", - "gone", - "fill", - "pete", - "spec", - "camp", - "penn", - "midi", - "tied", - "snow", - "dale", - "oils", - "sept", - "unto", - "inch", - "died", - "kong", - "pays", - "rank", - "lang", - "stud", - "fold", - "ones", - "gave", - "hire", - "seem", - "ipod", - "phys", - "pole", - "mega", - "bend", - "moms", - "glen", - "rich", - "drop", - "guys", - "tags", - "lips", - "pond", - "load", - "pick", - "rose", - "wait", - "walk", - "tire", - "chad", - "fuel", - "josh", - "drag", - "soft", - "ripe", - "rely", - "scsi", - "task", - "miss", - "wild", - "heat", - "nuts", - "nail", - "span", - "mass", - "joke", - "univ", - "foot", - "pads", - "inns", - "cups", - "cold", - "shot", - "pink", - "foam", - "root", - "edge", - "poem", - "ford", - "oral", - "asks", - "bean", - "bias", - "xbox", - "pain", - "palm", - "wind", - "sold", - "swim", - "nano", - "goal", - "ball", - "dvds", - "loud", - "rats", - "jump", - "stat", - "cruz", - "bios", - "firm", - "thee", - "lots", - "ruth", - "pray", - "pope", - "jeep", - "bare", - "hung", - "gear", - "army", - "mono", - "tile", - "diet", - "apps", - "skip", - "laws", - "path", - "flow", - "ciao", - "knee", - "prep", - "flat", - "chem", - "jack", - "zone", - "hits", - "pros", - "cant", - "wife", - "goes", - "hear", - "lord", - "farm", - "sara", - "eyes", - "joan", - "duck", - "poor", - "trip", - "mike", - "dive", - "dead", - "fiji", - "audi", - "raid", - "gets", - "volt", - "ohio", - "dirt", - "fair", - "acer", - "dist", - "isbn", - "geek", - "sink", - "grip", - "host", - "watt", - "pins", - "reno", - "dark", - "polo", - "rent", - "horn", - "wood", - "prot", - "frog", - "logs", - "sets", - "core", - "debt", - "snap", - "race", - "born", - "pack", - "fish", - "jpeg", - "mini", - "pool", - "swap", - "rest", - "flip", - "deep", - "boys", - "buzz", - "nuke", - "iraq", - "boom", - "calm", - "fork", - "troy", - "ring", - "mary", - "prev", - "zope", - "gmbh", - "skin", - "fees", - "sims", - "tray", - "pass", - "sage", - "java", - "uses", - "asia", - "cool", - "suse", - "door", - "cave", - "wool", - "feet", - "told", - "rule", - "ways", - "eyed", - "vote", - "grab", - "oops", - "wine", - "wall", - "thus", - "tree", - "trap", - "fool", - "hair", - "karl", - "dies", - "paid", - "ship", - "anti", - "hall", - "jail", - "feed", - "safe", - "ipaq", - "hold", - "comm", - "deal", - "maps", - "lace", - "hill", - "ugly", - "hart", - "ment", - "tool", - "idea", - "fall", - "biol", - "late", - "lies", - "cnet", - "song", - "took", - "treo", - "gods", - "male", - "fund", - "mode", - "poly", - "ears", - "went", - "lead", - "fist", - "band", - "mere", - "cons", - "sent", - "taxi", - "nice", - "logo", - "move", - "kind", - "huge", - "bush", - "hour", - "worn", - "shaw", - "fine", - "expo", - "came", - "deny", - "bali", - "judy", - "trio", - "cube", - "rugs", - "fate", - "role", - "gras", - "wish", - "hope", - "menu", - "tour", - "lost", - "mind", - "oval", - "held", - "soma", - "soon", - "href", - "benz", - "wifi", - "tier", - "stop", - "earl", - "port", - "seen", - "guam", - "cite", - "cash", - "pics", - "drug", - "copy", - "mess", - "king", - "mean", - "turn", - "stay", - "rope", - "dump", - "near", - "base", - "face", - "loss", - "hose", - "html", - "chat", - "fire", - "sony", - "pubs", - "lake", - "paul", - "mild", - "none", - "step", - "half", - "sort", - "clan", - "sync", - "mesa", - "wide", - "loan", - "hull", - "golf", - "toys", - "shed", - "memo", - "girl", - "tide", - "funk", - "town", - "reel", - "risk", - "bind", - "rand", - "buck", - "bank", - "feel", - "meet", - "usgs", - "acre", - "lows", - "aqua", - "chen", - "emma", - "tech", - "pest", - "unit", - "fact", - "fast", - "reef", - "edit", - "auto", - "plus", - "chan", - "tips", - "beth", - "rock", - "mark", - "else", - "jill", - "sofa", - "true", - "dans", - "viii", - "kids", - "talk", - "tent", - "dept", - "hack", - "dare", - "hawk", - "lamb", - "bill", - "word", - "ever", - "done", - "land", - "says", - "upon", - "five", - "past", - "arts", - "gold", - "able", - "junk", - "tell", - "lucy", - "hans", - "poet", - "epic", - "cars", - "sake", - "sans", - "sure", - "lean", - "once", - "away", - "self", - "dude", - "luis", - "cell", - "head", - "film", - "alto", - "term", - "baby", - "keep", - "gore", - "cult", - "dash", - "cage", - "divx", - "hugh", - "hand", - "jake", - "eval", - "ping", - "flux", - "star", - "muze", - "oman", - "easy", - "blue", - "rage", - "adsl", - "four", - "prix", - "hard", - "gift", - "avon", - "rays", - "road", - "walt", - "acne", - "libs", - "undo", - "club", - "east", - "dana", - "halo", - "body", - "sell", - "gays", - "give", - "exec", - "side", - "park", - "blog", - "less", - "play", - "maui", - "cart", - "come", - "test", - "vids", - "yale", - "july", - "cost", - "plan", - "june", - "doom", - "owen", - "bite", - "issn", - "myth", - "live", - "note", - "week", - "weed", - "oecd", - "dice", - "quad", - "dock", - "mods", - "hint", - "msie", - "team", - "left", - "look", - "west", - "buys", - "pork", - "join", - "barn", - "room", - "teen", - "fare", - "sale", - "asus", - "food", - "bald", - "fuji", - "leon", - "mold", - "dame", - "jobs", - "herb", - "card", - "alot", - "york", - "idle", - "save", - "call", - "main", - "john", - "love", - "form", - "rate", - "text", - "cove", - "casa", - "shop", - "eden", - "incl", - "size", - "down", - "care", - "game", - "reid", - "both", - "flex", - "rosa", - "hash", - "lazy", - "same", - "case", - "carb", - "open", - "link", - "file", - "sign", - "much", - "even", - "pens", - "show", - "code", - "worm", - "long", - "deaf", - "mats", - "want", - "blah", - "mime", - "feof", - "usda", - "keen", - "peas", - "urls", - "area", - "take", - "type", - "send", - "owns", - "line", - "made", - "must", - "ebay", - "item", - "zinc", - "guru", - "real", - "levy", - "grad", - "bras", - "part", - "days", - "kyle", - "know", - "life", - "pale", - "gaps", - "tear", - "full", - "mail", - "does", - "nest", - "said", - "nato", - "user", - "gale", - "many", - "stan", - "idol", - "need", - "read", - "book", - "very", - "moss", - "each", - "cork", - "high", - "mali", - "info", - "dome", - "well", - "heel", - "yang", - "good", - "then", - "best", - "such", - "city", - "post", - "them", - "make", - "data", - "dumb", - "most", - "last", - "work", - "used", - "next", - "into", - "year", - "feat", - "ntsc", - "over", - "usps", - "just", - "name", - "conf", - "glow", - "list", - "back", - "oaks", - "erik", - "date", - "find", - "than", - "paso", - "norm", - "like", - "some", - "ware", - "were", - "been", - "jade", - "foul", - "keno", - "view", - "seas", - "help", - "pose", - "mrna", - "goat", - "also", - "here", - "sail", - "when", - "only", - "sega", - "cdna", - "news", - "what", - "bolt", - "gage", - "site", - "they", - "time", - "urge", - "smtp", - "kurt", - "neon", "ours", - "lone", - "cope", - "free", - "lime", - "kirk", - "bool", - "page", - "home", - "will", - "spas", - "more", - "jets", - "have", - "intl", + "ourselves", + "you", + "you're", + "you've", + "you'll", + "you'd", "your", - "yarn", - "knit", - "from", - "with", + "yours", + "yourself", + "yourselves", + "he", + "him", + "his", + "himself", + "she", + "she's", + "her", + "hers", + "herself", + "it", + "it's", + "its", + "itself", + "they", + "them", + "their", + "theirs", + "themselves", + "what", + "which", + "who", + "whom", "this", - "pike", "that", - "hugo", - "gzip", - "ctrl", - "bent", - "laos", + "that'll", + "these", + "those", + "am", + "is", + "are", + "was", + "were", + "be", + "been", + "being", + "have", + "has", + "had", + "having", + "do", + "does", + "did", + "doing", + "a", + "an", + "the", + "and", + "but", + "if", + "or", + "because", + "as", + "until", + "while", + "of", + "at", + "by", + "for", + "with", + "about", + "against", + "between", + "into", + "through", + "during", + "before", + "after", + "above", + "below", + "to", + "from", + "up", + "down", + "in", + "out", + "on", + "off", + "over", + "under", + "again", + "further", + "then", + "once", + "here", + "there", + "when", + "where", + "why", + "how", + "all", + "any", + "both", + "each", + "few", + "more", + "most", + "other", + "some", + "such", + "no", + "nor", + "not", + "only", + "own", + "same", + "so", + "than", + "too", + "very", + "s", + "t", + "can", + "will", + "just", + "don", + "don't", + "should", + "should've", + "now", + "d", + "ll", + "m", + "o", + "re", + "ve", + "y", + "ain", + "aren", + "aren't", + "couldn", + "couldn't", + "didn", + "didn't", + "doesn", + "doesn't", + "hadn", + "hadn't", + "hasn", + "hasn't", + "haven", + "haven't", + "isn", + "isn't", + "ma", + "mightn", + "mightn't", + "mustn", + "mustn't", + "needn", + "needn't", + "shan", + "shan't", + "shouldn", + "shouldn't", + "wasn", + "wasn't", + "weren", + "weren't", + "won", + "won't", + "wouldn", + "wouldn't", } diff --git a/plugins/tldr/tldr.go b/plugins/tldr/tldr.go index 80666eb..cb5519a 100644 --- a/plugins/tldr/tldr.go +++ b/plugins/tldr/tldr.go @@ -48,7 +48,13 @@ func (p *TLDRPlugin) message(kind bot.Kind, message msg.Message, args ...interfa p.lastRequest = time.Now() nTopics := p.bot.Config().GetInt("TLDR.Topics", 5) - vectoriser := nlp.NewCountVectoriser(THESE_ARE_NOT_THE_WORDS_YOU_ARE_LOOKING_FOR...) + stopWordSlice := p.bot.Config().GetArray("TLDR.StopWords", []string{}) + if len(stopWordSlice) == 0 { + stopWordSlice = THESE_ARE_NOT_THE_WORDS_YOU_ARE_LOOKING_FOR + p.bot.Config().SetArray("TLDR.StopWords", stopWordSlice) + } + + vectoriser := nlp.NewCountVectoriser(stopWordSlice...) lda := nlp.NewLatentDirichletAllocation(nTopics) pipeline := nlp.NewPipeline(vectoriser, lda) docsOverTopics, err := pipeline.FitTransform(p.getTopics()...) From a865dfe2da1fa00dfe7692a79778b64729280256 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Fri, 12 Apr 2019 11:53:40 -0400 Subject: [PATCH 078/107] tldr: change history to be in order; clamp to 24h --- plugins/tldr/tldr.go | 66 +++++++++++++++++---------------------- plugins/tldr/tldr_test.go | 37 ++++++++++++++++++++++ 2 files changed, 65 insertions(+), 38 deletions(-) diff --git a/plugins/tldr/tldr.go b/plugins/tldr/tldr.go index 6383058..617da40 100644 --- a/plugins/tldr/tldr.go +++ b/plugins/tldr/tldr.go @@ -119,32 +119,38 @@ func (p *TLDRPlugin) message(kind bot.Kind, message msg.Message, args ...interfa return true } - if shouldKeepMessage(lowercaseMessage) { - currentHistorySize := len(p.history) - maxHistorySize := p.bot.Config().GetInt("TLDR.HistorySize", 1000) - hist := history{ - body: lowercaseMessage, - user: message.User.Name, - timestamp: time.Now(), - } - if currentHistorySize < maxHistorySize { - p.history = append(p.history, hist) - p.index = 0 - } else { - if currentHistorySize > maxHistorySize { - // We could resize this but we want to prune the oldest stuff, and - // I don't care to do this correctly so might as well not do it at all - } + hist := history{ + body: lowercaseMessage, + user: message.User.Name, + timestamp: time.Now(), + } + p.addHistory(hist) - if p.index >= currentHistorySize { - p.index = 0 - } + return false +} - p.history[p.index] = hist - p.index++ +func (p *TLDRPlugin) addHistory(hist history) { + p.history = append(p.history, hist) + sz := len(p.history) + max := p.bot.Config().GetInt("TLDR.HistorySize", 1000) + keepHrs := time.Duration(p.bot.Config().GetInt("TLDR.KeepHours", 24)) + // Clamp the size of the history + if sz > max { + p.history = p.history[len(p.history)-max:] + } + // Remove old entries + yesterday := time.Now().Add(-keepHrs * time.Hour) + begin := 0 + for i, m := range p.history { + if !m.timestamp.Before(yesterday) { + begin = i - 1 // should keep this message + if begin < 0 { + begin = 0 + } + break } } - return false + p.history = p.history[begin:] } func (p *TLDRPlugin) getTopics() []string { @@ -155,28 +161,12 @@ func (p *TLDRPlugin) getTopics() []string { return hist } -func (p *TLDRPlugin) pruneHistory() { - out := []history{} - yesterday := time.Now().Add(-24 * time.Hour) - for _, h := range p.history { - if yesterday.Before(h.timestamp) { - out = append(out, h) - } - } - p.history = out - p.index = len(out) -} - // Help responds to help requests. Every plugin must implement a help function. func (p *TLDRPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { p.bot.Send(bot.Message, message.Channel, "tl;dr") return true } -func shouldKeepMessage(message string) bool { - return true -} - func min(slice []float64) (float64, int) { minVal := 1. minIndex := -1 diff --git a/plugins/tldr/tldr_test.go b/plugins/tldr/tldr_test.go index 1b809b5..2bec842 100644 --- a/plugins/tldr/tldr_test.go +++ b/plugins/tldr/tldr_test.go @@ -2,8 +2,10 @@ package tldr import ( "os" + "strconv" "strings" "testing" + "time" "github.com/rs/zerolog" "github.com/rs/zerolog/log" @@ -61,3 +63,38 @@ func TestDoubleUp(t *testing.T) { assert.Len(t, mb.Messages, 2) assert.Contains(t, mb.Messages[1], "Slow down, cowboy.") } + +func TestAddHistoryLimitsMessages(t *testing.T) { + c, _ := setup(t) + max := 1000 + c.bot.Config().Set("TLDR.HistorySize", strconv.Itoa(max)) + c.bot.Config().Set("TLDR.KeepHours", "24") + t0 := time.Now().Add(-24 * time.Hour) + for i := 0; i < max*2; i++ { + hist := history{ + body: "test", + user: "tester", + timestamp: t0.Add(time.Duration(i) * time.Second), + } + c.addHistory(hist) + } + assert.Len(t, c.history, max) +} + +func TestAddHistoryLimitsDays(t *testing.T) { + c, _ := setup(t) + hrs := 24 + expected := 24 + c.bot.Config().Set("TLDR.HistorySize", "100") + c.bot.Config().Set("TLDR.KeepHours", strconv.Itoa(hrs)) + t0 := time.Now().Add(-time.Duration(hrs*2) * time.Hour) + for i := 0; i < 48; i++ { + hist := history{ + body: "test", + user: "tester", + timestamp: t0.Add(time.Duration(i) * time.Hour), + } + c.addHistory(hist) + } + assert.Len(t, c.history, expected, "%d != %d", len(c.history), expected) +} From 55b2b707c11816b271aa40bf3ab78f9e5232c7b0 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Mon, 27 May 2019 14:27:34 -0400 Subject: [PATCH 079/107] counter: add a web interface --- plugins/counter/counter.go | 81 +++++++++++++++++++++++ plugins/counter/html.go | 128 +++++++++++++++++++++++++++++++++++++ 2 files changed, 209 insertions(+) create mode 100644 plugins/counter/html.go diff --git a/plugins/counter/counter.go b/plugins/counter/counter.go index c7b62e5..13edb6f 100644 --- a/plugins/counter/counter.go +++ b/plugins/counter/counter.go @@ -4,8 +4,10 @@ package counter import ( "database/sql" + "encoding/json" "fmt" "math/rand" + "net/http" "regexp" "strconv" "strings" @@ -43,6 +45,20 @@ type alias struct { PointsTo string `db:"points_to"` } +// GetItems returns all counters +func GetAllItems(db *sqlx.DB) ([]Item, error) { + var items []Item + err := db.Select(&items, `select * from counter`) + if err != nil { + return nil, err + } + // Don't forget to embed the DB into all of that shiz + for i := range items { + items[i].DB = db + } + return items, nil +} + // GetItems returns all counters for a subject func GetItems(db *sqlx.DB, nick string) ([]Item, error) { var items []Item @@ -201,6 +217,7 @@ func New(b bot.Bot) *CounterPlugin { } b.Register(cp, bot.Message, cp.message) b.Register(cp, bot.Help, cp.help) + cp.registerWeb() return cp } @@ -534,3 +551,67 @@ func everyDayImShuffling(vals []string) []string { } return ret } + +func (p *CounterPlugin) registerWeb() { + http.HandleFunc("/counter/api", p.handleCounterAPI) + http.HandleFunc("/counter", p.handleCounter) + p.Bot.RegisterWeb("/counter", "Counter") +} + +func (p *CounterPlugin) handleCounter(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, html) +} + +func (p *CounterPlugin) handleCounterAPI(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost { + info := struct { + User string + Thing string + Action string + }{} + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&info) + if err != nil { + w.WriteHeader(500) + fmt.Fprint(w, err) + return + } + log.Debug(). + Interface("postbody", info). + Msg("Got a POST") + item, err := GetItem(p.DB, info.User, info.Thing) + if err != nil { + log.Error(). + Err(err). + Str("subject", info.User). + Str("itemName", info.Thing). + Msg("error finding item") + w.WriteHeader(404) + fmt.Fprint(w, err) + return + } + if info.Action == "++" { + item.UpdateDelta(1) + } else if info.Action == "--" { + item.UpdateDelta(-1) + } else { + w.WriteHeader(400) + fmt.Fprint(w, "Invalid increment") + return + } + + } + all, err := GetAllItems(p.DB) + if err != nil { + w.WriteHeader(500) + fmt.Fprint(w, err) + return + } + data, err := json.Marshal(all) + if err != nil { + w.WriteHeader(500) + fmt.Fprint(w, err) + return + } + fmt.Fprint(w, string(data)) +} diff --git a/plugins/counter/html.go b/plugins/counter/html.go new file mode 100644 index 0000000..dca3964 --- /dev/null +++ b/plugins/counter/html.go @@ -0,0 +1,128 @@ +package counter + +var html = ` + + + + + + + + + + + + + + + + +
+

Counters

+ + {{ err }} + + + + Human test: What is {{ equation }}? + + + + {{ user }}: + + + + {{ thing }}: + + + {{ count }} + + + + + + + + + +
+ + + + +` From 42f7f52bfb6fe2bb0c09ac0f0fcadf01f8713055 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Mon, 27 May 2019 19:21:53 -0400 Subject: [PATCH 080/107] cli: make a new plugin --- .gitignore | 1 + bot/bot.go | 10 +- bot/handlers.go | 42 ++++---- bot/interfaces.go | 9 +- bot/mock.go | 34 +++--- config/config.go | 6 +- connectors/irc/irc.go | 4 +- connectors/slack/slack.go | 4 +- connectors/slackapp/slackApp.go | 4 +- main.go | 2 + plugins/admin/admin.go | 32 +++--- plugins/admin/admin_test.go | 6 +- plugins/babbler/babbler.go | 10 +- plugins/babbler/babbler_test.go | 105 +++++++++--------- plugins/beers/beers.go | 62 +++++------ plugins/beers/beers_test.go | 8 +- plugins/cli/cli.go | 114 ++++++++++++++++++++ plugins/cli/index.html | 137 ++++++++++++++++++++++++ plugins/couldashouldawoulda/csw.go | 4 +- plugins/couldashouldawoulda/csw_test.go | 5 +- plugins/counter/counter.go | 44 ++++---- plugins/counter/counter_test.go | 7 +- plugins/db/db.go | 46 -------- plugins/dice/dice.go | 10 +- plugins/dice/dice_test.go | 7 +- plugins/emojifyme/emojifyme.go | 4 +- plugins/fact/fact_test.go | 7 +- plugins/fact/factoid.go | 94 ++++++++-------- plugins/fact/webTemplates.go | 2 +- plugins/first/first.go | 22 ++-- plugins/inventory/inventory.go | 22 ++-- plugins/leftpad/leftpad.go | 8 +- plugins/leftpad/leftpad_test.go | 5 +- plugins/nerdepedia/nerdepedia.go | 8 +- plugins/nerdepedia/nerdepeida_test.go | 5 +- plugins/picker/picker.go | 16 +-- plugins/picker/picker_test.go | 5 +- plugins/reaction/reaction.go | 28 ++--- plugins/remember/remember.go | 14 +-- plugins/remember/remember_test.go | 3 +- plugins/reminder/reminder.go | 38 +++---- plugins/reminder/reminder_test.go | 9 +- plugins/rpgORdie/rpgORdie.go | 26 ++--- plugins/rpgORdie/rpgORdie_test.go | 2 - plugins/rss/rss.go | 16 +-- plugins/rss/rss_test.go | 5 +- plugins/sisyphus/sisyphus.go | 62 +++++------ plugins/talker/talker.go | 30 +++--- plugins/talker/talker_test.go | 7 +- plugins/tell/tell.go | 6 +- plugins/tldr/tldr.go | 10 +- plugins/tldr/tldr_test.go | 7 +- plugins/twitch/twitch.go | 36 +++---- plugins/twitch/twitch_test.go | 5 +- plugins/your/your.go | 8 +- plugins/your/your_test.go | 5 +- plugins/zork/zork.go | 14 +-- 57 files changed, 744 insertions(+), 498 deletions(-) create mode 100644 plugins/cli/cli.go create mode 100644 plugins/cli/index.html delete mode 100644 plugins/db/db.go diff --git a/.gitignore b/.gitignore index 29c8840..6a15fae 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,4 @@ Temporary Items util/*/files util/*/files run.sh +.idea diff --git a/bot/bot.go b/bot/bot.go index 8ba3029..e66eda7 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -59,7 +59,7 @@ func New(config *config.Config, connector Connector) Bot { msglog.RunNew(logIn, logOut) users := []user.User{ - user.User{ + { Name: config.Get("Nick", "bot"), }, } @@ -87,6 +87,14 @@ func New(config *config.Config, connector Connector) Bot { return bot } +func (b *bot) DefaultConnector() Connector { + return b.conn +} + +func (b *bot) WhoAmI() string { + return b.me.Name +} + // Config gets the configuration that the bot is using func (b *bot) Config() *config.Config { return b.config diff --git a/bot/handlers.go b/bot/handlers.go index 5dfe12c..0a1a3e9 100644 --- a/bot/handlers.go +++ b/bot/handlers.go @@ -17,7 +17,7 @@ import ( "github.com/velour/catbase/bot/msg" ) -func (b *bot) Receive(kind Kind, msg msg.Message, args ...interface{}) bool { +func (b *bot) Receive(conn Connector, kind Kind, msg msg.Message, args ...interface{}) bool { log.Debug(). Interface("msg", msg). Msg("Received event") @@ -26,13 +26,13 @@ func (b *bot) Receive(kind Kind, msg msg.Message, args ...interface{}) bool { // do need to look up user and fix it if kind == Message && strings.HasPrefix(msg.Body, "help") && msg.Command { parts := strings.Fields(strings.ToLower(msg.Body)) - b.checkHelp(msg.Channel, parts) + b.checkHelp(conn, msg.Channel, parts) log.Debug().Msg("Handled a help, returning") goto RET } for _, name := range b.pluginOrdering { - if b.runCallback(b.plugins[name], kind, msg, args...) { + if b.runCallback(conn, b.plugins[name], kind, msg, args...) { goto RET } } @@ -42,10 +42,10 @@ RET: return true } -func (b *bot) runCallback(plugin Plugin, evt Kind, message msg.Message, args ...interface{}) bool { +func (b *bot) runCallback(conn Connector, plugin Plugin, evt Kind, message msg.Message, args ...interface{}) bool { t := reflect.TypeOf(plugin).String() for _, cb := range b.callbacks[t][evt] { - if cb(evt, message, args...) { + if cb(conn, evt, message, args...) { return true } } @@ -53,8 +53,8 @@ func (b *bot) runCallback(plugin Plugin, evt Kind, message msg.Message, args ... } // Send a message to the connection -func (b *bot) Send(kind Kind, args ...interface{}) (string, error) { - return b.conn.Send(kind, args...) +func (b *bot) Send(conn Connector, kind Kind, args ...interface{}) (string, error) { + return conn.Send(kind, args...) } func (b *bot) GetEmojiList() map[string]string { @@ -62,38 +62,38 @@ func (b *bot) GetEmojiList() map[string]string { } // Checks to see if the user is asking for help, returns true if so and handles the situation. -func (b *bot) checkHelp(channel string, parts []string) { +func (b *bot) checkHelp(conn Connector, channel string, parts []string) { if len(parts) == 1 { // just print out a list of help topics topics := "Help topics: about variables" - for name, _ := range b.plugins { + for name := range b.plugins { name = strings.Split(strings.TrimPrefix(name, "*"), ".")[0] topics = fmt.Sprintf("%s, %s", topics, name) } - b.Send(Message, channel, topics) + b.Send(conn, Message, channel, topics) } else { // trigger the proper plugin's help response if parts[1] == "about" { - b.Help(channel, parts) + b.Help(conn, channel, parts) return } if parts[1] == "variables" { - b.listVars(channel, parts) + b.listVars(conn, channel, parts) return } for name, plugin := range b.plugins { if strings.HasPrefix(name, "*"+parts[1]) { - if b.runCallback(plugin, Help, msg.Message{Channel: channel}, channel, parts) { + if b.runCallback(conn, plugin, Help, msg.Message{Channel: channel}, channel, parts) { return } else { msg := fmt.Sprintf("I'm sorry, I don't know how to help you with %s.", parts[1]) - b.Send(Message, channel, msg) + b.Send(conn, Message, channel, msg) return } } } msg := fmt.Sprintf("I'm sorry, I don't know what %s is!", strings.Join(parts, " ")) - b.Send(Message, channel, msg) + b.Send(conn, Message, channel, msg) } } @@ -178,7 +178,7 @@ func (b *bot) getVar(varName string) (string, error) { return text, nil } -func (b *bot) listVars(channel string, parts []string) { +func (b *bot) listVars(conn Connector, channel string, parts []string) { var variables []string err := b.DB().Select(&variables, `select name from variables group by name`) if err != nil { @@ -188,18 +188,18 @@ func (b *bot) listVars(channel string, parts []string) { if len(variables) > 0 { msg += ", " + strings.Join(variables, ", ") } - b.Send(Message, channel, msg) + b.Send(conn, Message, channel, msg) } -func (b *bot) Help(channel string, parts []string) { +func (b *bot) Help(conn Connector, channel string, parts []string) { msg := fmt.Sprintf("Hi, I'm based on godeepintir version %s. I'm written in Go, and you "+ "can find my source code on the internet here: "+ "http://github.com/velour/catbase", b.version) - b.Send(Message, channel, msg) + b.Send(conn, Message, channel, msg) } // Send our own musings to the plugins -func (b *bot) selfSaid(channel, message string, action bool) { +func (b *bot) selfSaid(conn Connector, channel, message string, action bool) { msg := msg.Message{ User: &b.me, // hack Channel: channel, @@ -212,7 +212,7 @@ func (b *bot) selfSaid(channel, message string, action bool) { } for _, name := range b.pluginOrdering { - if b.runCallback(b.plugins[name], SelfMessage, msg) { + if b.runCallback(conn, b.plugins[name], SelfMessage, msg) { return } } diff --git a/bot/interfaces.go b/bot/interfaces.go index bc71c6c..fba9396 100644 --- a/bot/interfaces.go +++ b/bot/interfaces.go @@ -36,7 +36,7 @@ type ImageAttachment struct { } type Kind int -type Callback func(Kind, msg.Message, ...interface{}) bool +type Callback func(Connector, Kind, msg.Message, ...interface{}) bool type CallbackMap map[string]map[Kind][]Callback // Bot interface serves to allow mocking of the actual bot @@ -47,12 +47,14 @@ type Bot interface { DB() *sqlx.DB // Who lists users in a particular channel Who(string) []user.User + // WhoAmI gives a nick for the bot + WhoAmI() string // AddPlugin registers a new plugin handler AddPlugin(Plugin) // First arg should be one of bot.Message/Reply/Action/etc - Send(Kind, ...interface{}) (string, error) + Send(Connector, Kind, ...interface{}) (string, error) // First arg should be one of bot.Message/Reply/Action/etc - Receive(Kind, msg.Message, ...interface{}) bool + Receive(Connector, Kind, msg.Message, ...interface{}) bool // Register a callback Register(Plugin, Kind, Callback) @@ -63,6 +65,7 @@ type Bot interface { GetEmojiList() map[string]string RegisterFilter(string, func(string) string) RegisterWeb(string, string) + DefaultConnector() Connector } // Connector represents a server connection to a chat service diff --git a/bot/mock.go b/bot/mock.go index 6f11c12..6f67c62 100644 --- a/bot/mock.go +++ b/bot/mock.go @@ -27,10 +27,12 @@ type MockBot struct { Reactions []string } -func (mb *MockBot) Config() *config.Config { return mb.Cfg } -func (mb *MockBot) DB() *sqlx.DB { return mb.Cfg.DB } -func (mb *MockBot) Who(string) []user.User { return []user.User{} } -func (mb *MockBot) Send(kind Kind, args ...interface{}) (string, error) { +func (mb *MockBot) Config() *config.Config { return mb.Cfg } +func (mb *MockBot) DB() *sqlx.DB { return mb.Cfg.DB } +func (mb *MockBot) Who(string) []user.User { return []user.User{} } +func (mb *MockBot) WhoAmI() string { return "tester" } +func (mb *MockBot) DefaultConnector() Connector { return nil } +func (mb *MockBot) Send(c Connector, kind Kind, args ...interface{}) (string, error) { switch kind { case Message: mb.Messages = append(mb.Messages, args[1].(string)) @@ -40,27 +42,29 @@ func (mb *MockBot) Send(kind Kind, args ...interface{}) (string, error) { return fmt.Sprintf("a-%d", len(mb.Actions)-1), nil case Edit: ch, m, id := args[0].(string), args[1].(string), args[2].(string) - return mb.edit(ch, m, id) + return mb.edit(c, ch, m, id) case Reaction: ch, re, msg := args[0].(string), args[1].(string), args[2].(msg.Message) - return mb.react(ch, re, msg) + return mb.react(c, ch, re, msg) } return "ERR", fmt.Errorf("Mesasge type unhandled") } -func (mb *MockBot) AddPlugin(f Plugin) {} -func (mb *MockBot) Register(p Plugin, kind Kind, cb Callback) {} -func (mb *MockBot) RegisterWeb(_, _ string) {} -func (mb *MockBot) Receive(kind Kind, msg msg.Message, args ...interface{}) bool { return false } -func (mb *MockBot) Filter(msg msg.Message, s string) string { return s } -func (mb *MockBot) LastMessage(ch string) (msg.Message, error) { return msg.Message{}, nil } -func (mb *MockBot) CheckAdmin(nick string) bool { return false } +func (mb *MockBot) AddPlugin(f Plugin) {} +func (mb *MockBot) Register(p Plugin, kind Kind, cb Callback) {} +func (mb *MockBot) RegisterWeb(_, _ string) {} +func (mb *MockBot) Receive(c Connector, kind Kind, msg msg.Message, args ...interface{}) bool { + return false +} +func (mb *MockBot) Filter(msg msg.Message, s string) string { return s } +func (mb *MockBot) LastMessage(ch string) (msg.Message, error) { return msg.Message{}, nil } +func (mb *MockBot) CheckAdmin(nick string) bool { return false } -func (mb *MockBot) react(channel, reaction string, message msg.Message) (string, error) { +func (mb *MockBot) react(c Connector, channel, reaction string, message msg.Message) (string, error) { mb.Reactions = append(mb.Reactions, reaction) return "", nil } -func (mb *MockBot) edit(channel, newMessage, identifier string) (string, error) { +func (mb *MockBot) edit(c Connector, channel, newMessage, identifier string) (string, error) { isMessage := identifier[0] == 'm' if !isMessage && identifier[0] != 'a' { err := fmt.Errorf("failed to parse identifier: %s", identifier) diff --git a/config/config.go b/config/config.go index 3431a8f..ee767d2 100644 --- a/config/config.go +++ b/config/config.go @@ -11,7 +11,7 @@ import ( "strings" "github.com/jmoiron/sqlx" - sqlite3 "github.com/mattn/go-sqlite3" + "github.com/mattn/go-sqlite3" "github.com/rs/zerolog/log" ) @@ -98,8 +98,8 @@ func (c *Config) GetArray(key string, fallback []string) []string { // Note, this is always a string. Use the SetArray for an array helper func (c *Config) Set(key, value string) error { key = strings.ToLower(key) - q := (`insert into config (key,value) values (?, ?) - on conflict(key) do update set value=?;`) + q := `insert into config (key,value) values (?, ?) + on conflict(key) do update set value=?;` tx, err := c.Begin() if err != nil { return err diff --git a/connectors/irc/irc.go b/connectors/irc/irc.go index b57bdd7..cead610 100644 --- a/connectors/irc/irc.go +++ b/connectors/irc/irc.go @@ -248,10 +248,10 @@ func (i *Irc) handleMsg(msg irc.Msg) { fallthrough case irc.RPL_ENDOFWHO: - i.event(bot.Event, botMsg) + i.event(i, bot.Event, botMsg) case irc.PRIVMSG: - i.event(bot.Message, botMsg) + i.event(i, bot.Message, botMsg) case irc.QUIT: os.Exit(1) diff --git a/connectors/slack/slack.go b/connectors/slack/slack.go index 16076a9..bb1ad3d 100644 --- a/connectors/slack/slack.go +++ b/connectors/slack/slack.go @@ -455,11 +455,11 @@ func (s *Slack) Serve() error { log.Debug().Msgf("Ignoring message: %+v\nlastRecieved: %v msg: %v", msg.ID, s.lastRecieved, m.Time) } else { s.lastRecieved = m.Time - s.event(bot.Message, m) + s.event(s, bot.Message, m) } } else if msg.ThreadTs != "" { //we're throwing away some information here by not parsing the correct reply object type, but that's okay - s.event(bot.Reply, s.buildLightReplyMessage(msg), msg.ThreadTs) + s.event(s, bot.Reply, s.buildLightReplyMessage(msg), msg.ThreadTs) } else { log.Debug().Msgf("THAT MESSAGE WAS HIDDEN: %+v", msg.ID) } diff --git a/connectors/slackapp/slackApp.go b/connectors/slackapp/slackApp.go index 52e3e31..d45708a 100644 --- a/connectors/slackapp/slackApp.go +++ b/connectors/slackapp/slackApp.go @@ -167,11 +167,11 @@ func (s *SlackApp) msgReceivd(msg *slackevents.MessageEvent) { Msg("Ignoring message") } else { s.lastRecieved = m.Time - s.event(bot.Message, m) + s.event(s, bot.Message, m) } } else if msg.ThreadTimeStamp != "" { //we're throwing away some information here by not parsing the correct reply object type, but that's okay - s.event(bot.Reply, s.buildMessage(msg), msg.ThreadTimeStamp) + s.event(s, bot.Reply, s.buildMessage(msg), msg.ThreadTimeStamp) } else { log.Debug(). Str("text", msg.Text). diff --git a/main.go b/main.go index ea9e10a..6837a05 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ package main import ( "flag" + "github.com/velour/catbase/plugins/cli" "math/rand" "net/http" "os" @@ -121,6 +122,7 @@ func main() { b.AddPlugin(couldashouldawoulda.New(b)) b.AddPlugin(nerdepedia.New(b)) b.AddPlugin(tldr.New(b)) + b.AddPlugin(cli.New(b)) // catches anything left, will always return true b.AddPlugin(fact.New(b)) diff --git a/plugins/admin/admin.go b/plugins/admin/admin.go index f7a38a2..a050e97 100644 --- a/plugins/admin/admin.go +++ b/plugins/admin/admin.go @@ -47,7 +47,7 @@ var forbiddenKeys = map[string]bool{ // Message responds to the bot hook on recieving messages. // This function returns true if the plugin responds in a meaningful way to the users message. // Otherwise, the function returns false and the bot continues execution of other plugins. -func (p *AdminPlugin) message(k bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *AdminPlugin) message(conn bot.Connector, k bot.Kind, message msg.Message, args ...interface{}) bool { body := message.Body if p.quiet { @@ -55,7 +55,7 @@ func (p *AdminPlugin) message(k bot.Kind, message msg.Message, args ...interface } if len(body) > 0 && body[0] == '$' { - return p.handleVariables(message) + return p.handleVariables(conn, message) } if !message.Command { @@ -65,7 +65,7 @@ func (p *AdminPlugin) message(k bot.Kind, message msg.Message, args ...interface if strings.ToLower(body) == "shut up" { dur := time.Duration(p.cfg.GetInt("quietDuration", 5)) * time.Minute log.Info().Msgf("Going to sleep for %v, %v", dur, time.Now().Add(dur)) - p.Bot.Send(bot.Message, message.Channel, "Okay. I'll be back later.") + p.Bot.Send(conn, bot.Message, message.Channel, "Okay. I'll be back later.") p.quiet = true go func() { select { @@ -79,36 +79,36 @@ func (p *AdminPlugin) message(k bot.Kind, message msg.Message, args ...interface parts := strings.Split(body, " ") if parts[0] == "set" && len(parts) > 2 && forbiddenKeys[parts[1]] { - p.Bot.Send(bot.Message, message.Channel, "You cannot access that key") + p.Bot.Send(conn, bot.Message, message.Channel, "You cannot access that key") return true } else if parts[0] == "set" && len(parts) > 2 { p.cfg.Set(parts[1], strings.Join(parts[2:], " ")) - p.Bot.Send(bot.Message, message.Channel, fmt.Sprintf("Set %s", parts[1])) + p.Bot.Send(conn, bot.Message, message.Channel, fmt.Sprintf("Set %s", parts[1])) return true } if parts[0] == "get" && len(parts) == 2 && forbiddenKeys[parts[1]] { - p.Bot.Send(bot.Message, message.Channel, "You cannot access that key") + p.Bot.Send(conn, bot.Message, message.Channel, "You cannot access that key") return true } else if parts[0] == "get" && len(parts) == 2 { v := p.cfg.Get(parts[1], "") - p.Bot.Send(bot.Message, message.Channel, fmt.Sprintf("%s: %s", parts[1], v)) + p.Bot.Send(conn, bot.Message, message.Channel, fmt.Sprintf("%s: %s", parts[1], v)) return true } return false } -func (p *AdminPlugin) handleVariables(message msg.Message) bool { +func (p *AdminPlugin) handleVariables(conn bot.Connector, message msg.Message) bool { if parts := strings.SplitN(message.Body, "!=", 2); len(parts) == 2 { variable := strings.ToLower(strings.TrimSpace(parts[0])) value := strings.TrimSpace(parts[1]) _, err := p.db.Exec(`delete from variables where name=? and value=?`, variable, value) if err != nil { - p.Bot.Send(bot.Message, message.Channel, "I'm broke and need attention in my variable creation code.") + p.Bot.Send(conn, bot.Message, message.Channel, "I'm broke and need attention in my variable creation code.") log.Error().Err(err) } else { - p.Bot.Send(bot.Message, message.Channel, "Removed.") + p.Bot.Send(conn, bot.Message, message.Channel, "Removed.") } return true @@ -126,27 +126,27 @@ func (p *AdminPlugin) handleVariables(message msg.Message) bool { row := p.db.QueryRow(`select count(*) from variables where value = ?`, variable, value) err := row.Scan(&count) if err != nil { - p.Bot.Send(bot.Message, message.Channel, "I'm broke and need attention in my variable creation code.") + p.Bot.Send(conn, bot.Message, message.Channel, "I'm broke and need attention in my variable creation code.") log.Error().Err(err) return true } if count > 0 { - p.Bot.Send(bot.Message, message.Channel, "I've already got that one.") + p.Bot.Send(conn, bot.Message, message.Channel, "I've already got that one.") } else { _, err := p.db.Exec(`INSERT INTO variables (name, value) VALUES (?, ?)`, variable, value) if err != nil { - p.Bot.Send(bot.Message, message.Channel, "I'm broke and need attention in my variable creation code.") + p.Bot.Send(conn, bot.Message, message.Channel, "I'm broke and need attention in my variable creation code.") log.Error().Err(err) return true } - p.Bot.Send(bot.Message, message.Channel, "Added.") + p.Bot.Send(conn, bot.Message, message.Channel, "Added.") } return true } // Help responds to help requests. Every plugin must implement a help function. -func (p *AdminPlugin) help(kind bot.Kind, m msg.Message, args ...interface{}) bool { - p.Bot.Send(bot.Message, m.Channel, "This does super secret things that you're not allowed to know about.") +func (p *AdminPlugin) help(conn bot.Connector, kind bot.Kind, m msg.Message, args ...interface{}) bool { + p.Bot.Send(conn, bot.Message, m.Channel, "This does super secret things that you're not allowed to know about.") return true } diff --git a/plugins/admin/admin_test.go b/plugins/admin/admin_test.go index 069b21d..917b5b9 100644 --- a/plugins/admin/admin_test.go +++ b/plugins/admin/admin_test.go @@ -1,6 +1,7 @@ package admin import ( + "github.com/velour/catbase/plugins/cli" "strings" "testing" @@ -22,12 +23,13 @@ func setup(t *testing.T) (*AdminPlugin, *bot.MockBot) { return a, mb } -func makeMessage(payload string) (bot.Kind, msg.Message) { +func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return bot.Message, msg.Message{ + c := cli.CliPlugin{} + return &c, bot.Message, msg.Message{ User: &user.User{Name: "tester"}, Channel: "test", Body: payload, diff --git a/plugins/babbler/babbler.go b/plugins/babbler/babbler.go index 2c8311f..2b3854e 100644 --- a/plugins/babbler/babbler.go +++ b/plugins/babbler/babbler.go @@ -101,7 +101,7 @@ func New(b bot.Bot) *BabblerPlugin { return plugin } -func (p *BabblerPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *BabblerPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { lowercase := strings.ToLower(message.Body) tokens := strings.Fields(lowercase) numTokens := len(tokens) @@ -143,12 +143,12 @@ func (p *BabblerPlugin) message(kind bot.Kind, message msg.Message, args ...inte } if saidSomething { - p.Bot.Send(bot.Message, message.Channel, saidWhat) + p.Bot.Send(c, bot.Message, message.Channel, saidWhat) } return saidSomething } -func (p *BabblerPlugin) help(kind bot.Kind, msg msg.Message, args ...interface{}) bool { +func (p *BabblerPlugin) help(c bot.Connector, kind bot.Kind, msg msg.Message, args ...interface{}) bool { commands := []string{ "initialize babbler for seabass", "merge babbler drseabass into seabass", @@ -157,7 +157,7 @@ func (p *BabblerPlugin) help(kind bot.Kind, msg msg.Message, args ...interface{} "seabass says-middle-out ...", "seabass says-bridge ... | ...", } - p.Bot.Send(bot.Message, msg.Channel, strings.Join(commands, "\n\n")) + p.Bot.Send(c, bot.Message, msg.Channel, strings.Join(commands, "\n\n")) return true } @@ -851,7 +851,7 @@ func (p *BabblerPlugin) babbleSeedBookends(babblerName string, start, end []stri previous *searchNode } - open := []*searchNode{&searchNode{startWordNode.NodeId, nil}} + open := []*searchNode{{startWordNode.NodeId, nil}} closed := map[int64]*searchNode{startWordNode.NodeId: open[0]} goalNodeId := int64(-1) diff --git a/plugins/babbler/babbler_test.go b/plugins/babbler/babbler_test.go index a569e0d..0239396 100644 --- a/plugins/babbler/babbler_test.go +++ b/plugins/babbler/babbler_test.go @@ -3,6 +3,7 @@ package babbler import ( + "github.com/velour/catbase/plugins/cli" "strings" "testing" @@ -12,12 +13,13 @@ import ( "github.com/velour/catbase/bot/user" ) -func makeMessage(payload string) (bot.Kind, msg.Message) { +func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) { + c := &cli.CliPlugin{} isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return bot.Message, msg.Message{ + return c, bot.Message, msg.Message{ User: &user.User{Name: "tester"}, Channel: "test", Body: payload, @@ -64,13 +66,13 @@ func TestBabbler(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - k, seabass := makeMessage("This is a message") + c, k, seabass := makeMessage("This is a message") seabass.User = &user.User{Name: "seabass"} - res := bp.message(k, seabass) + res := bp.message(c, k, seabass) seabass.Body = "This is another message" - res = bp.message(k, seabass) + res = bp.message(c, k, seabass) seabass.Body = "This is a long message" - res = bp.message(k, seabass) + res = bp.message(c, k, seabass) res = bp.message(makeMessage("!seabass says")) assert.Len(t, mb.Messages, 1) assert.True(t, res) @@ -82,13 +84,13 @@ func TestBabblerSeed(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - k, seabass := makeMessage("This is a message") + c, k, seabass := makeMessage("This is a message") seabass.User = &user.User{Name: "seabass"} - res := bp.message(k, seabass) + res := bp.message(c, k, seabass) seabass.Body = "This is another message" - res = bp.message(k, seabass) + res = bp.message(c, k, seabass) seabass.Body = "This is a long message" - res = bp.message(k, seabass) + res = bp.message(c, k, seabass) res = bp.message(makeMessage("!seabass says long")) assert.Len(t, mb.Messages, 1) assert.True(t, res) @@ -99,13 +101,13 @@ func TestBabblerMultiSeed(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - k, seabass := makeMessage("This is a message") + c, k, seabass := makeMessage("This is a message") seabass.User = &user.User{Name: "seabass"} - res := bp.message(k, seabass) + res := bp.message(c, k, seabass) seabass.Body = "This is another message" - res = bp.message(k, seabass) + res = bp.message(c, k, seabass) seabass.Body = "This is a long message" - res = bp.message(k, seabass) + res = bp.message(c, k, seabass) res = bp.message(makeMessage("!seabass says This is a long")) assert.Len(t, mb.Messages, 1) assert.True(t, res) @@ -116,13 +118,13 @@ func TestBabblerMultiSeed2(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - k, seabass := makeMessage("This is a message") + c, k, seabass := makeMessage("This is a message") seabass.User = &user.User{Name: "seabass"} - res := bp.message(k, seabass) + res := bp.message(c, k, seabass) seabass.Body = "This is another message" - res = bp.message(k, seabass) + res = bp.message(c, k, seabass) seabass.Body = "This is a long message" - res = bp.message(k, seabass) + res = bp.message(c, k, seabass) res = bp.message(makeMessage("!seabass says is a long")) assert.Len(t, mb.Messages, 1) assert.True(t, res) @@ -133,13 +135,13 @@ func TestBabblerBadSeed(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - k, seabass := makeMessage("This is a message") + c, k, seabass := makeMessage("This is a message") seabass.User = &user.User{Name: "seabass"} - bp.message(k, seabass) + bp.message(c, k, seabass) seabass.Body = "This is another message" - bp.message(k, seabass) + bp.message(c, k, seabass) seabass.Body = "This is a long message" - bp.message(k, seabass) + bp.message(c, k, seabass) bp.message(makeMessage("!seabass says noooo this is bad")) assert.Len(t, mb.Messages, 1) assert.Contains(t, mb.Messages[0], "seabass never said 'noooo this is bad'") @@ -149,13 +151,13 @@ func TestBabblerBadSeed2(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - k, seabass := makeMessage("This is a message") + c, k, seabass := makeMessage("This is a message") seabass.User = &user.User{Name: "seabass"} - bp.message(k, seabass) + bp.message(c, k, seabass) seabass.Body = "This is another message" - bp.message(k, seabass) + bp.message(c, k, seabass) seabass.Body = "This is a long message" - bp.message(k, seabass) + bp.message(c, k, seabass) bp.message(makeMessage("!seabass says This is a really")) assert.Len(t, mb.Messages, 1) assert.Contains(t, mb.Messages[0], "seabass never said 'this is a really'") @@ -165,13 +167,13 @@ func TestBabblerSuffixSeed(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - k, seabass := makeMessage("This is message one") + c, k, seabass := makeMessage("This is message one") seabass.User = &user.User{Name: "seabass"} - res := bp.message(k, seabass) + res := bp.message(c, k, seabass) seabass.Body = "It's easier to test with unique messages" - res = bp.message(k, seabass) + res = bp.message(c, k, seabass) seabass.Body = "hi there" - res = bp.message(k, seabass) + res = bp.message(c, k, seabass) res = bp.message(makeMessage("!seabass says-tail message one")) res = bp.message(makeMessage("!seabass says-tail with unique")) assert.Len(t, mb.Messages, 2) @@ -184,13 +186,13 @@ func TestBabblerBadSuffixSeed(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - k, seabass := makeMessage("This is message one") + c, k, seabass := makeMessage("This is message one") seabass.User = &user.User{Name: "seabass"} - res := bp.message(k, seabass) + res := bp.message(c, k, seabass) seabass.Body = "It's easier to test with unique messages" - res = bp.message(k, seabass) + res = bp.message(c, k, seabass) seabass.Body = "hi there" - res = bp.message(k, seabass) + res = bp.message(c, k, seabass) res = bp.message(makeMessage("!seabass says-tail anything true")) assert.Len(t, mb.Messages, 1) assert.True(t, res) @@ -201,9 +203,9 @@ func TestBabblerBookendSeed(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - k, seabass := makeMessage("It's easier to test with unique messages") + c, k, seabass := makeMessage("It's easier to test with unique messages") seabass.User = &user.User{Name: "seabass"} - res := bp.message(k, seabass) + res := bp.message(c, k, seabass) res = bp.message(makeMessage("!seabass says-bridge It's easier | unique messages")) assert.Len(t, mb.Messages, 1) assert.True(t, res) @@ -214,9 +216,9 @@ func TestBabblerBookendSeedShort(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - k, seabass := makeMessage("It's easier to test with unique messages") + c, k, seabass := makeMessage("It's easier to test with unique messages") seabass.User = &user.User{Name: "seabass"} - res := bp.message(k, seabass) + res := bp.message(c, k, seabass) res = bp.message(makeMessage("!seabass says-bridge It's easier to test with | unique messages")) assert.Len(t, mb.Messages, 1) assert.True(t, res) @@ -227,9 +229,9 @@ func TestBabblerBadBookendSeed(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - k, seabass := makeMessage("It's easier to test with unique messages") + c, k, seabass := makeMessage("It's easier to test with unique messages") seabass.User = &user.User{Name: "seabass"} - res := bp.message(k, seabass) + res := bp.message(c, k, seabass) res = bp.message(makeMessage("!seabass says-bridge It's easier | not unique messages")) assert.Len(t, mb.Messages, 1) assert.True(t, res) @@ -240,9 +242,9 @@ func TestBabblerMiddleOutSeed(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - k, seabass := makeMessage("It's easier to test with unique messages") + c, k, seabass := makeMessage("It's easier to test with unique messages") seabass.User = &user.User{Name: "seabass"} - res := bp.message(k, seabass) + res := bp.message(c, k, seabass) res = bp.message(makeMessage("!seabass says-middle-out test with")) assert.Len(t, mb.Messages, 1) assert.True(t, res) @@ -253,9 +255,9 @@ func TestBabblerBadMiddleOutSeed(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - k, seabass := makeMessage("It's easier to test with unique messages") + c, k, seabass := makeMessage("It's easier to test with unique messages") seabass.User = &user.User{Name: "seabass"} - res := bp.message(k, seabass) + res := bp.message(c, k, seabass) res = bp.message(makeMessage("!seabass says-middle-out anything true")) assert.Len(t, mb.Messages, 1) assert.True(t, res) @@ -266,8 +268,8 @@ func TestBabblerBatch(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - k, seabass := makeMessage("batch learn for seabass This is a message! This is another message. This is not a long message? This is not a message! This is not another message. This is a long message?") - res := bp.message(k, seabass) + c, k, seabass := makeMessage("batch learn for seabass This is a message! This is another message. This is not a long message? This is not a message! This is not another message. This is a long message?") + res := bp.message(c, k, seabass) assert.Len(t, mb.Messages, 1) res = bp.message(makeMessage("!seabass says")) assert.Len(t, mb.Messages, 2) @@ -281,16 +283,16 @@ func TestBabblerMerge(t *testing.T) { bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - k, seabass := makeMessage(" This is a message") + c, k, seabass := makeMessage(" This is a message") seabass.User = &user.User{Name: "seabass"} - res := bp.message(k, seabass) + res := bp.message(c, k, seabass) assert.Len(t, mb.Messages, 0) seabass.Body = " This is another message" - res = bp.message(k, seabass) + res = bp.message(c, k, seabass) seabass.Body = " This is a long message" - res = bp.message(k, seabass) + res = bp.message(c, k, seabass) res = bp.message(makeMessage("!merge babbler seabass into seabass2")) assert.True(t, res) @@ -309,6 +311,7 @@ func TestHelp(t *testing.T) { mb := bot.NewMockBot() bp := newBabblerPlugin(mb) assert.NotNil(t, bp) - bp.help(bot.Help, msg.Message{Channel: "channel"}, []string{}) + c := &cli.CliPlugin{} + bp.help(c, bot.Help, msg.Message{Channel: "channel"}, []string{}) assert.Len(t, mb.Messages, 1) } diff --git a/plugins/beers/beers.go b/plugins/beers/beers.go index a654c6a..8b818fb 100644 --- a/plugins/beers/beers.go +++ b/plugins/beers/beers.go @@ -53,7 +53,7 @@ func New(b bot.Bot) *BeersPlugin { db: b.DB(), } for _, channel := range b.Config().GetArray("Untappd.Channels", []string{}) { - go p.untappdLoop(channel) + go p.untappdLoop(b.DefaultConnector(), channel) } b.Register(p, bot.Message, p.message) b.Register(p, bot.Help, p.help) @@ -63,7 +63,7 @@ func New(b bot.Bot) *BeersPlugin { // Message responds to the bot hook on recieving messages. // This function returns true if the plugin responds in a meaningful way to the users message. // Otherwise, the function returns false and the bot continues execution of other plugins. -func (p *BeersPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *BeersPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { parts := strings.Fields(message.Body) if len(parts) == 0 { @@ -83,49 +83,49 @@ func (p *BeersPlugin) message(kind bot.Kind, message msg.Message, args ...interf count, err := strconv.Atoi(parts[2]) if err != nil { // if it's not a number, maybe it's a nick! - p.Bot.Send(bot.Message, channel, "Sorry, that didn't make any sense.") + p.Bot.Send(c, bot.Message, channel, "Sorry, that didn't make any sense.") } if count < 0 { // you can't be negative msg := fmt.Sprintf("Sorry %s, you can't have negative beers!", nick) - p.Bot.Send(bot.Message, channel, msg) + p.Bot.Send(c, bot.Message, channel, msg) return true } if parts[1] == "+=" { p.addBeers(nick, count) - p.randomReply(channel) + p.randomReply(c, channel) } else if parts[1] == "=" { if count == 0 { - p.puke(nick, channel) + p.puke(c, nick, channel) } else { p.setBeers(nick, count) - p.randomReply(channel) + p.randomReply(c, channel) } } else { - p.Bot.Send(bot.Message, channel, "I don't know your math.") + p.Bot.Send(c, bot.Message, channel, "I don't know your math.") } } else if len(parts) == 2 { if p.doIKnow(parts[1]) { - p.reportCount(parts[1], channel, false) + p.reportCount(c, parts[1], channel, false) } else { msg := fmt.Sprintf("Sorry, I don't know %s.", parts[1]) - p.Bot.Send(bot.Message, channel, msg) + p.Bot.Send(c, bot.Message, channel, msg) } } else if len(parts) == 1 { - p.reportCount(nick, channel, true) + p.reportCount(c, nick, channel, true) } // no matter what, if we're in here, then we've responded return true } else if parts[0] == "puke" { - p.puke(nick, channel) + p.puke(c, nick, channel) return true } if message.Command && parts[0] == "imbibe" { p.addBeers(nick, 1) - p.randomReply(channel) + p.randomReply(c, channel) return true } @@ -134,7 +134,7 @@ func (p *BeersPlugin) message(kind bot.Kind, message msg.Message, args ...interf channel := message.Channel if len(parts) < 2 { - p.Bot.Send(bot.Message, channel, "You must also provide a user name.") + p.Bot.Send(c, bot.Message, channel, "You must also provide a user name.") } else if len(parts) == 3 { chanNick = parts[2] } else if len(parts) == 4 { @@ -159,7 +159,7 @@ func (p *BeersPlugin) message(kind bot.Kind, message msg.Message, args ...interf log.Error().Err(err).Msgf("Error registering untappd") } if count > 0 { - p.Bot.Send(bot.Message, channel, "I'm already watching you.") + p.Bot.Send(c, bot.Message, channel, "I'm already watching you.") return true } _, err = p.db.Exec(`insert into untappd ( @@ -175,13 +175,13 @@ func (p *BeersPlugin) message(kind bot.Kind, message msg.Message, args ...interf ) if err != nil { log.Error().Err(err).Msgf("Error registering untappd") - p.Bot.Send(bot.Message, channel, "I can't see.") + p.Bot.Send(c, bot.Message, channel, "I can't see.") return true } - p.Bot.Send(bot.Message, channel, "I'll be watching you.") + p.Bot.Send(c, bot.Message, channel, "I'll be watching you.") - p.checkUntappd(channel) + p.checkUntappd(c, channel) return true } @@ -190,7 +190,7 @@ func (p *BeersPlugin) message(kind bot.Kind, message msg.Message, args ...interf log.Info(). Str("user", message.User.Name). Msgf("Checking untappd at request of user.") - p.checkUntappd(channel) + p.checkUntappd(c, channel) return true } @@ -198,11 +198,11 @@ func (p *BeersPlugin) message(kind bot.Kind, message msg.Message, args ...interf } // Help responds to help requests. Every plugin must implement a help function. -func (p *BeersPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *BeersPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { msg := "Beers: imbibe by using either beers +=,=,++ or with the !imbibe/drink " + "commands. I'll keep a count of how many beers you've had and then if you want " + "to reset, just !puke it all up!" - p.Bot.Send(bot.Message, message.Channel, msg) + p.Bot.Send(c, bot.Message, message.Channel, msg) return true } @@ -232,7 +232,7 @@ func (p *BeersPlugin) getBeers(nick string) int { return ub.Count } -func (p *BeersPlugin) reportCount(nick, channel string, himself bool) { +func (p *BeersPlugin) reportCount(c bot.Connector, nick, channel string, himself bool) { beers := p.getBeers(nick) msg := fmt.Sprintf("%s has had %d beers so far.", nick, beers) if himself { @@ -242,13 +242,13 @@ func (p *BeersPlugin) reportCount(nick, channel string, himself bool) { msg = fmt.Sprintf("You've had %d beers so far, %s.", beers, nick) } } - p.Bot.Send(bot.Message, channel, msg) + p.Bot.Send(c, bot.Message, channel, msg) } -func (p *BeersPlugin) puke(user string, channel string) { +func (p *BeersPlugin) puke(c bot.Connector, user string, channel string) { p.setBeers(user, 0) msg := fmt.Sprintf("Ohhhhhh, and a reversal of fortune for %s!", user) - p.Bot.Send(bot.Message, channel, msg) + p.Bot.Send(c, bot.Message, channel, msg) } func (p *BeersPlugin) doIKnow(nick string) bool { @@ -261,9 +261,9 @@ func (p *BeersPlugin) doIKnow(nick string) bool { } // Sends random affirmation to the channel. This could be better (with a datastore for sayings) -func (p *BeersPlugin) randomReply(channel string) { +func (p *BeersPlugin) randomReply(c bot.Connector, channel string) { replies := []string{"ZIGGY! ZAGGY!", "HIC!", "Stay thirsty, my friend!"} - p.Bot.Send(bot.Message, channel, replies[rand.Intn(len(replies))]) + p.Bot.Send(c, bot.Message, channel, replies[rand.Intn(len(replies))]) } type checkin struct { @@ -343,7 +343,7 @@ func (p *BeersPlugin) pullUntappd() ([]checkin, error) { return beers.Response.Checkins.Items, nil } -func (p *BeersPlugin) checkUntappd(channel string) { +func (p *BeersPlugin) checkUntappd(c bot.Connector, channel string) { token := p.Bot.Config().Get("Untappd.Token", "NONE") if token == "NONE" { log.Info(). @@ -434,11 +434,11 @@ func (p *BeersPlugin) checkUntappd(channel string) { Int("checkin_id", checkin.Checkin_id). Str("msg", msg). Msg("checkin") - p.Bot.Send(bot.Message, args...) + p.Bot.Send(c, bot.Message, args...) } } -func (p *BeersPlugin) untappdLoop(channel string) { +func (p *BeersPlugin) untappdLoop(c bot.Connector, channel string) { frequency := p.Bot.Config().GetInt("Untappd.Freq", 120) if frequency == 0 { return @@ -448,6 +448,6 @@ func (p *BeersPlugin) untappdLoop(channel string) { for { time.Sleep(time.Duration(frequency) * time.Second) - p.checkUntappd(channel) + p.checkUntappd(c, channel) } } diff --git a/plugins/beers/beers_test.go b/plugins/beers/beers_test.go index 4fa07b1..d2c3ba2 100644 --- a/plugins/beers/beers_test.go +++ b/plugins/beers/beers_test.go @@ -3,6 +3,7 @@ package beers import ( + "github.com/velour/catbase/plugins/cli" "strings" "testing" @@ -13,12 +14,13 @@ import ( "github.com/velour/catbase/plugins/counter" ) -func makeMessage(payload string) (bot.Kind, msg.Message) { +func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return bot.Message, msg.Message{ + c := &cli.CliPlugin{} + return c, bot.Message, msg.Message{ User: &user.User{Name: "tester"}, Channel: "test", Body: payload, @@ -121,6 +123,6 @@ func TestBeersReport(t *testing.T) { func TestHelp(t *testing.T) { b, mb := makeBeersPlugin(t) - b.help(bot.Help, msg.Message{Channel: "channel"}, []string{}) + b.help(&cli.CliPlugin{}, bot.Help, msg.Message{Channel: "channel"}, []string{}) assert.Len(t, mb.Messages, 1) } diff --git a/plugins/cli/cli.go b/plugins/cli/cli.go new file mode 100644 index 0000000..d46357b --- /dev/null +++ b/plugins/cli/cli.go @@ -0,0 +1,114 @@ +// © 2013 the CatBase Authors under the WTFPL. See AUTHORS for the list of authors. + +package cli + +import ( + "encoding/json" + "fmt" + "github.com/jmoiron/sqlx" + "github.com/rs/zerolog/log" + "github.com/velour/catbase/bot" + "github.com/velour/catbase/bot/msg" + "github.com/velour/catbase/bot/user" + "io/ioutil" + "net/http" + "time" +) + +type CliPlugin struct { + bot bot.Bot + db *sqlx.DB + cache string + counter int +} + +func New(b bot.Bot) *CliPlugin { + cp := &CliPlugin{ + bot: b, + } + cp.registerWeb() + return cp +} + +func (p *CliPlugin) registerWeb() { + http.HandleFunc("/cli/api", p.handleWebAPI) + http.HandleFunc("/cli", p.handleWeb) + p.bot.RegisterWeb("/cli", "CLI") +} + +func (p *CliPlugin) handleWebAPI(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + fmt.Fprintf(w, "Incorrect HTTP method") + return + } + info := struct { + User string `json:"user"` + Payload string `json:"payload"` + }{} + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&info) + if err != nil { + w.WriteHeader(500) + fmt.Fprint(w, err) + return + } + log.Debug(). + Interface("postbody", info). + Msg("Got a POST") + + p.bot.Receive(p, bot.Message, msg.Message{ + User: &user.User{ + ID: info.User, + Name: info.User, + Admin: false, + }, + Channel: "web", + Body: info.Payload, + Raw: info.Payload, + Command: true, + Time: time.Now(), + }) + + info.User = p.bot.WhoAmI() + info.Payload = p.cache + p.cache = "" + + data, err := json.Marshal(info) + if err != nil { + w.WriteHeader(500) + fmt.Fprint(w, err) + return + } + w.Write(data) +} + +func (p *CliPlugin) handleWeb(w http.ResponseWriter, r *http.Request) { + f, err := ioutil.ReadFile("plugins/cli/index.html") + if err != nil { + w.WriteHeader(500) + fmt.Fprint(w, err) + return + } + w.Write(f) +} + +// Completing the Connector interface, but will not actually be a connector +func (p *CliPlugin) RegisterEvent(cb bot.Callback) {} +func (p *CliPlugin) Send(kind bot.Kind, args ...interface{}) (string, error) { + switch kind { + case bot.Message: + fallthrough + case bot.Action: + fallthrough + case bot.Reply: + fallthrough + case bot.Reaction: + p.cache += args[1].(string) + "\n" + } + id := fmt.Sprintf("%d", p.counter) + p.counter++ + return id, nil +} +func (p *CliPlugin) GetEmojiList() map[string]string { return nil } +func (p *CliPlugin) Serve() error { return nil } +func (p *CliPlugin) Who(s string) []string { return nil } diff --git a/plugins/cli/index.html b/plugins/cli/index.html new file mode 100644 index 0000000..36fd8c4 --- /dev/null +++ b/plugins/cli/index.html @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + CLI + + + +
+

CLI

+ + {{ err }} + + + + + + + + + + + + + + + + + + + + Send + + + + +
+ + + + diff --git a/plugins/couldashouldawoulda/csw.go b/plugins/couldashouldawoulda/csw.go index 0052a5e..c424fba 100644 --- a/plugins/couldashouldawoulda/csw.go +++ b/plugins/couldashouldawoulda/csw.go @@ -25,7 +25,7 @@ func New(b bot.Bot) *CSWPlugin { return csw } -func (p *CSWPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *CSWPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { if !message.Command { return false } @@ -65,7 +65,7 @@ func (p *CSWPlugin) message(kind bot.Kind, message msg.Message, args ...interfac } } - p.Bot.Send(bot.Message, message.Channel, responses[rand.Intn(len(responses))]) + p.Bot.Send(c, bot.Message, message.Channel, responses[rand.Intn(len(responses))]) return true } diff --git a/plugins/couldashouldawoulda/csw_test.go b/plugins/couldashouldawoulda/csw_test.go index de60dc9..e647ede 100644 --- a/plugins/couldashouldawoulda/csw_test.go +++ b/plugins/couldashouldawoulda/csw_test.go @@ -3,6 +3,7 @@ package couldashouldawoulda import ( + "github.com/velour/catbase/plugins/cli" "strings" "testing" @@ -12,12 +13,12 @@ import ( "github.com/velour/catbase/bot/user" ) -func makeMessage(payload string) (bot.Kind, msg.Message) { +func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return bot.Message, msg.Message{ + return &cli.CliPlugin{}, bot.Message, msg.Message{ User: &user.User{Name: "tester"}, Channel: "test", Body: payload, diff --git a/plugins/counter/counter.go b/plugins/counter/counter.go index 13edb6f..ea3a181 100644 --- a/plugins/counter/counter.go +++ b/plugins/counter/counter.go @@ -225,7 +225,7 @@ func New(b bot.Bot) *CounterPlugin { // This function returns true if the plugin responds in a meaningful way to the // users message. Otherwise, the function returns false and the bot continues // execution of other plugins. -func (p *CounterPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *CounterPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { // This bot does not reply to anything nick := message.User.Name channel := message.Channel @@ -240,7 +240,7 @@ func (p *CounterPlugin) message(kind bot.Kind, message msg.Message, args ...inte log.Error().Err(err) return false } - p.Bot.Send(bot.Message, channel, fmt.Sprintf("Created alias %s -> %s", + p.Bot.Send(c, bot.Message, channel, fmt.Sprintf("Created alias %s -> %s", parts[1], parts[2])) return true } else if strings.ToLower(parts[0]) == "leaderboard" { @@ -270,11 +270,11 @@ func (p *CounterPlugin) message(kind bot.Kind, message msg.Message, args ...inte it.Item, ) } - p.Bot.Send(bot.Message, channel, out) + p.Bot.Send(c, bot.Message, channel, out) return true } else if match := teaMatcher.MatchString(message.Body); match { // check for tea match TTT - return p.checkMatch(message) + return p.checkMatch(c, message) } else if message.Command && message.Body == "reset me" { items, err := GetItems(p.DB, strings.ToLower(nick)) if err != nil { @@ -282,14 +282,14 @@ func (p *CounterPlugin) message(kind bot.Kind, message msg.Message, args ...inte Err(err). Str("nick", nick). Msg("Error getting items to reset") - p.Bot.Send(bot.Message, channel, "Something is technically wrong with your counters.") + p.Bot.Send(c, bot.Message, channel, "Something is technically wrong with your counters.") return true } log.Debug().Msgf("Items: %+v", items) for _, item := range items { item.Delete() } - p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s, you are as new, my son.", nick)) + p.Bot.Send(c, bot.Message, channel, fmt.Sprintf("%s, you are as new, my son.", nick)) return true } else if message.Command && parts[0] == "inspect" && len(parts) == 2 { var subject string @@ -310,7 +310,7 @@ func (p *CounterPlugin) message(kind bot.Kind, message msg.Message, args ...inte Err(err). Str("subject", subject). Msg("Error retrieving items") - p.Bot.Send(bot.Message, channel, "Something went wrong finding that counter;") + p.Bot.Send(c, bot.Message, channel, "Something went wrong finding that counter;") return true } @@ -330,11 +330,11 @@ func (p *CounterPlugin) message(kind bot.Kind, message msg.Message, args ...inte resp += "." if count == 0 { - p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s has no counters.", subject)) + p.Bot.Send(c, bot.Message, channel, fmt.Sprintf("%s has no counters.", subject)) return true } - p.Bot.Send(bot.Message, channel, resp) + p.Bot.Send(c, bot.Message, channel, resp) return true } else if message.Command && len(parts) == 2 && parts[0] == "clear" { subject := strings.ToLower(nick) @@ -347,7 +347,7 @@ func (p *CounterPlugin) message(kind bot.Kind, message msg.Message, args ...inte Str("subject", subject). Str("itemName", itemName). Msg("Error getting item to remove") - p.Bot.Send(bot.Message, channel, "Something went wrong removing that counter;") + p.Bot.Send(c, bot.Message, channel, "Something went wrong removing that counter;") return true } err = it.Delete() @@ -357,11 +357,11 @@ func (p *CounterPlugin) message(kind bot.Kind, message msg.Message, args ...inte Str("subject", subject). Str("itemName", itemName). Msg("Error removing item") - p.Bot.Send(bot.Message, channel, "Something went wrong removing that counter;") + p.Bot.Send(c, bot.Message, channel, "Something went wrong removing that counter;") return true } - p.Bot.Send(bot.Action, channel, fmt.Sprintf("chops a few %s out of his brain", + p.Bot.Send(c, bot.Action, channel, fmt.Sprintf("chops a few %s out of his brain", itemName)) return true @@ -384,7 +384,7 @@ func (p *CounterPlugin) message(kind bot.Kind, message msg.Message, args ...inte item, err := GetItem(p.DB, subject, itemName) switch { case err == sql.ErrNoRows: - p.Bot.Send(bot.Message, channel, fmt.Sprintf("I don't think %s has any %s.", + p.Bot.Send(c, bot.Message, channel, fmt.Sprintf("I don't think %s has any %s.", subject, itemName)) return true case err != nil: @@ -396,7 +396,7 @@ func (p *CounterPlugin) message(kind bot.Kind, message msg.Message, args ...inte return true } - p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s has %d %s.", subject, item.Count, + p.Bot.Send(c, bot.Message, channel, fmt.Sprintf("%s has %d %s.", subject, item.Count, itemName)) return true @@ -431,7 +431,7 @@ func (p *CounterPlugin) message(kind bot.Kind, message msg.Message, args ...inte } log.Debug().Msgf("About to update item: %#v", item) item.UpdateDelta(1) - p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s has %d %s.", subject, + p.Bot.Send(c, bot.Message, channel, fmt.Sprintf("%s has %d %s.", subject, item.Count, item.Item)) return true } else if strings.HasSuffix(parts[0], "--") { @@ -447,7 +447,7 @@ func (p *CounterPlugin) message(kind bot.Kind, message msg.Message, args ...inte return false } item.UpdateDelta(-1) - p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s has %d %s.", subject, + p.Bot.Send(c, bot.Message, channel, fmt.Sprintf("%s has %d %s.", subject, item.Count, item.Item)) return true } @@ -480,7 +480,7 @@ func (p *CounterPlugin) message(kind bot.Kind, message msg.Message, args ...inte n, _ := strconv.Atoi(parts[2]) log.Debug().Msgf("About to update item by %d: %#v", n, item) item.UpdateDelta(n) - p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s has %d %s.", subject, + p.Bot.Send(c, bot.Message, channel, fmt.Sprintf("%s has %d %s.", subject, item.Count, item.Item)) return true } else if parts[1] == "-=" { @@ -498,7 +498,7 @@ func (p *CounterPlugin) message(kind bot.Kind, message msg.Message, args ...inte n, _ := strconv.Atoi(parts[2]) log.Debug().Msgf("About to update item by -%d: %#v", n, item) item.UpdateDelta(-n) - p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s has %d %s.", subject, + p.Bot.Send(c, bot.Message, channel, fmt.Sprintf("%s has %d %s.", subject, item.Count, item.Item)) return true } @@ -508,15 +508,15 @@ func (p *CounterPlugin) message(kind bot.Kind, message msg.Message, args ...inte } // Help responds to help requests. Every plugin must implement a help function. -func (p *CounterPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { - p.Bot.Send(bot.Message, message.Channel, "You can set counters incrementally by using "+ +func (p *CounterPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.Bot.Send(c, bot.Message, message.Channel, "You can set counters incrementally by using "+ "++ and --. You can see all of your counters using "+ "\"inspect\", erase them with \"clear\", and view single counters with "+ "\"count\".") return true } -func (p *CounterPlugin) checkMatch(message msg.Message) bool { +func (p *CounterPlugin) checkMatch(c bot.Connector, message msg.Message) bool { nick := message.User.Name channel := message.Channel @@ -538,7 +538,7 @@ func (p *CounterPlugin) checkMatch(message msg.Message) bool { } log.Debug().Msgf("About to update item: %#v", item) item.UpdateDelta(1) - p.Bot.Send(bot.Message, channel, fmt.Sprintf("%s... %s has %d %s", + p.Bot.Send(c, bot.Message, channel, fmt.Sprintf("%s... %s has %d %s", strings.Join(everyDayImShuffling([]string{"bleep", "bloop", "blop"}), "-"), nick, item.Count, itemName)) return true } diff --git a/plugins/counter/counter_test.go b/plugins/counter/counter_test.go index 417c44c..99f9212 100644 --- a/plugins/counter/counter_test.go +++ b/plugins/counter/counter_test.go @@ -4,6 +4,7 @@ package counter import ( "fmt" + "github.com/velour/catbase/plugins/cli" "strings" "testing" @@ -22,12 +23,12 @@ func setup(t *testing.T) (*bot.MockBot, *CounterPlugin) { return mb, c } -func makeMessage(payload string) (bot.Kind, msg.Message) { +func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return bot.Message, msg.Message{ + return &cli.CliPlugin{}, bot.Message, msg.Message{ User: &user.User{Name: "tester"}, Channel: "test", Body: payload, @@ -250,6 +251,6 @@ func TestInspectMe(t *testing.T) { func TestHelp(t *testing.T) { mb, c := setup(t) assert.NotNil(t, c) - c.help(bot.Help, msg.Message{Channel: "channel"}, []string{}) + c.help(&cli.CliPlugin{}, bot.Help, msg.Message{Channel: "channel"}, []string{}) assert.Len(t, mb.Messages, 1) } diff --git a/plugins/db/db.go b/plugins/db/db.go deleted file mode 100644 index a3799b9..0000000 --- a/plugins/db/db.go +++ /dev/null @@ -1,46 +0,0 @@ -package db - -import ( - "fmt" - "net/http" - "os" - "time" - - "github.com/rs/zerolog/log" - - "github.com/velour/catbase/bot" - "github.com/velour/catbase/bot/msg" - "github.com/velour/catbase/config" -) - -type DBPlugin struct { - bot bot.Bot - config *config.Config -} - -func New(b bot.Bot) *DBPlugin { - p := &DBPlugin{b, b.Config()} - p.registerWeb() - return p -} - -func (p *DBPlugin) Message(message msg.Message) bool { return false } -func (p *DBPlugin) Event(kind string, message msg.Message) bool { return false } -func (p *DBPlugin) ReplyMessage(msg.Message, string) bool { return false } -func (p *DBPlugin) BotMessage(message msg.Message) bool { return false } -func (p *DBPlugin) Help(channel string, parts []string) {} - -func (p *DBPlugin) registerWeb() { - http.HandleFunc("/db/catbase.db", p.serveQuery) -} - -func (p *DBPlugin) serveQuery(w http.ResponseWriter, r *http.Request) { - f, err := os.Open(p.bot.Config().DBFile) - defer f.Close() - if err != nil { - log.Error().Err(err).Msg("Error opening DB for web service") - fmt.Fprintf(w, "Error opening DB") - return - } - http.ServeContent(w, r, "catbase.db", time.Now(), f) -} diff --git a/plugins/dice/dice.go b/plugins/dice/dice.go index dafc715..b171820 100644 --- a/plugins/dice/dice.go +++ b/plugins/dice/dice.go @@ -35,7 +35,7 @@ func rollDie(sides int) int { // Message responds to the bot hook on recieving messages. // This function returns true if the plugin responds in a meaningful way to the users message. // Otherwise, the function returns false and the bot continues execution of other plugins. -func (p *DicePlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *DicePlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { if !message.Command { return false } @@ -49,7 +49,7 @@ func (p *DicePlugin) message(kind bot.Kind, message msg.Message, args ...interfa } if sides < 2 || nDice < 1 || nDice > 20 { - p.Bot.Send(bot.Message, channel, "You're a dick.") + p.Bot.Send(c, bot.Message, channel, "You're a dick.") return true } @@ -64,13 +64,13 @@ func (p *DicePlugin) message(kind bot.Kind, message msg.Message, args ...interfa } } - p.Bot.Send(bot.Message, channel, rolls) + p.Bot.Send(c, bot.Message, channel, rolls) return true } // Help responds to help requests. Every plugin must implement a help function. -func (p *DicePlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { - p.Bot.Send(bot.Message, message.Channel, "Roll dice using notation XdY. Try \"3d20\".") +func (p *DicePlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.Bot.Send(c, bot.Message, message.Channel, "Roll dice using notation XdY. Try \"3d20\".") return true } diff --git a/plugins/dice/dice_test.go b/plugins/dice/dice_test.go index b7f2961..96eeb29 100644 --- a/plugins/dice/dice_test.go +++ b/plugins/dice/dice_test.go @@ -3,6 +3,7 @@ package dice import ( + "github.com/velour/catbase/plugins/cli" "strings" "testing" @@ -12,12 +13,12 @@ import ( "github.com/velour/catbase/bot/user" ) -func makeMessage(payload string) (bot.Kind, msg.Message) { +func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return bot.Message, msg.Message{ + return &cli.CliPlugin{}, bot.Message, msg.Message{ User: &user.User{Name: "tester"}, Channel: "test", Body: payload, @@ -86,6 +87,6 @@ func TestHelp(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - c.help(bot.Help, msg.Message{Channel: "channel"}, []string{}) + c.help(&cli.CliPlugin{}, bot.Help, msg.Message{Channel: "channel"}, []string{}) assert.Len(t, mb.Messages, 1) } diff --git a/plugins/emojifyme/emojifyme.go b/plugins/emojifyme/emojifyme.go index 4a24d85..13ab96f 100644 --- a/plugins/emojifyme/emojifyme.go +++ b/plugins/emojifyme/emojifyme.go @@ -58,7 +58,7 @@ func New(b bot.Bot) *EmojifyMePlugin { return ep } -func (p *EmojifyMePlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *EmojifyMePlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { if !p.GotBotEmoji { p.GotBotEmoji = true emojiMap := p.Bot.GetEmojiList() @@ -93,7 +93,7 @@ func (p *EmojifyMePlugin) message(kind bot.Kind, message msg.Message, args ...in if emojied > 0 && rand.Float64() <= p.Bot.Config().GetFloat64("Emojify.Chance", 0.02)*emojied { for _, e := range emojys { - p.Bot.Send(bot.Reaction, message.Channel, e, message) + p.Bot.Send(c, bot.Reaction, message.Channel, e, message) } return false } diff --git a/plugins/fact/fact_test.go b/plugins/fact/fact_test.go index c0fa9a9..c774146 100644 --- a/plugins/fact/fact_test.go +++ b/plugins/fact/fact_test.go @@ -1,6 +1,7 @@ package fact import ( + "github.com/velour/catbase/plugins/cli" "strings" "testing" @@ -10,6 +11,8 @@ import ( "github.com/velour/catbase/bot/user" ) +var c = &cli.CliPlugin{} + func makeMessage(nick, payload string) msg.Message { isCmd := strings.HasPrefix(payload, "!") if isCmd { @@ -37,7 +40,7 @@ func TestReact(t *testing.T) { p, mb := makePlugin(t) for _, m := range msgs { - p.message(bot.Message, m) + p.message(c, bot.Message, m) } assert.Len(t, mb.Reactions, 1) assert.Contains(t, mb.Reactions[0], "jesus") @@ -50,7 +53,7 @@ func TestReactCantLearnSpaces(t *testing.T) { p, mb := makePlugin(t) for _, m := range msgs { - p.message(bot.Message, m) + p.message(c, bot.Message, m) } assert.Len(t, mb.Messages, 1) assert.Contains(t, mb.Messages[0], "not a valid") diff --git a/plugins/fact/factoid.go b/plugins/fact/factoid.go index f7b25a5..eec9156 100644 --- a/plugins/fact/factoid.go +++ b/plugins/fact/factoid.go @@ -277,6 +277,8 @@ func New(botInst bot.Bot) *FactoidPlugin { db: botInst.DB(), } + c := botInst.DefaultConnector() + if _, err := p.db.Exec(`create table if not exists factoid ( id integer primary key, fact string, @@ -299,13 +301,13 @@ func New(botInst bot.Bot) *FactoidPlugin { } for _, channel := range botInst.Config().GetArray("channels", []string{}) { - go p.factTimer(channel) + go p.factTimer(c, channel) go func(ch string) { // Some random time to start up time.Sleep(time.Duration(15) * time.Second) if ok, fact := p.findTrigger(p.Bot.Config().Get("Factoid.StartupFact", "speed test")); ok { - p.sayFact(msg.Message{ + p.sayFact(c, msg.Message{ Channel: ch, Body: "speed test", // BUG: This is defined in the config too Command: true, @@ -399,7 +401,7 @@ func (p *FactoidPlugin) findTrigger(fact string) (bool, *Factoid) { // sayFact spits out a fact to the channel and updates the fact in the database // with new time and count information -func (p *FactoidPlugin) sayFact(message msg.Message, fact Factoid) { +func (p *FactoidPlugin) sayFact(c bot.Connector, message msg.Message, fact Factoid) { msg := p.Bot.Filter(message, fact.Tidbit) full := p.Bot.Filter(message, fmt.Sprintf("%s %s %s", fact.Fact, fact.Verb, fact.Tidbit, @@ -411,13 +413,13 @@ func (p *FactoidPlugin) sayFact(message msg.Message, fact Factoid) { } if fact.Verb == "action" { - p.Bot.Send(bot.Action, message.Channel, msg) + p.Bot.Send(c, bot.Action, message.Channel, msg) } else if fact.Verb == "react" { - p.Bot.Send(bot.Reaction, message.Channel, msg, message) + p.Bot.Send(c, bot.Reaction, message.Channel, msg, message) } else if fact.Verb == "reply" { - p.Bot.Send(bot.Message, message.Channel, msg) + p.Bot.Send(c, bot.Message, message.Channel, msg) } else { - p.Bot.Send(bot.Message, message.Channel, full) + p.Bot.Send(c, bot.Message, message.Channel, full) } } @@ -436,17 +438,17 @@ func (p *FactoidPlugin) sayFact(message msg.Message, fact Factoid) { // trigger checks the message for its fitness to be a factoid and then hauls // the message off to sayFact for processing if it is in fact a trigger -func (p *FactoidPlugin) trigger(message msg.Message) bool { +func (p *FactoidPlugin) trigger(c bot.Connector, message msg.Message) bool { minLen := p.Bot.Config().GetInt("Factoid.MinLen", 4) if len(message.Body) > minLen || message.Command || message.Body == "..." { if ok, fact := p.findTrigger(message.Body); ok { - p.sayFact(message, *fact) + p.sayFact(c, message, *fact) return true } r := strings.NewReplacer("'", "", "\"", "", ",", "", ".", "", ":", "", "?", "", "!", "") if ok, fact := p.findTrigger(r.Replace(message.Body)); ok { - p.sayFact(message, *fact) + p.sayFact(c, message, *fact) return true } } @@ -455,7 +457,7 @@ func (p *FactoidPlugin) trigger(message msg.Message) bool { } // tellThemWhatThatWas is a hilarious name for a function. -func (p *FactoidPlugin) tellThemWhatThatWas(message msg.Message) bool { +func (p *FactoidPlugin) tellThemWhatThatWas(c bot.Connector, message msg.Message) bool { fact := p.LastFact var msg string if fact == nil { @@ -464,11 +466,11 @@ func (p *FactoidPlugin) tellThemWhatThatWas(message msg.Message) bool { msg = fmt.Sprintf("That was (#%d) '%s <%s> %s'", fact.ID.Int64, fact.Fact, fact.Verb, fact.Tidbit) } - p.Bot.Send(bot.Message, message.Channel, msg) + p.Bot.Send(c, bot.Message, message.Channel, msg) return true } -func (p *FactoidPlugin) learnAction(message msg.Message, action string) bool { +func (p *FactoidPlugin) learnAction(c bot.Connector, message msg.Message, action string) bool { body := message.Body parts := strings.SplitN(body, action, 2) @@ -482,21 +484,21 @@ func (p *FactoidPlugin) learnAction(message msg.Message, action string) bool { action = strings.TrimSpace(action) if len(trigger) == 0 || len(fact) == 0 || len(action) == 0 { - p.Bot.Send(bot.Message, message.Channel, "I don't want to learn that.") + p.Bot.Send(c, bot.Message, message.Channel, "I don't want to learn that.") return true } if len(strings.Split(fact, "$and")) > 4 { - p.Bot.Send(bot.Message, message.Channel, "You can't use more than 4 $and operators.") + p.Bot.Send(c, bot.Message, message.Channel, "You can't use more than 4 $and operators.") return true } strippedaction := strings.Replace(strings.Replace(action, "<", "", 1), ">", "", 1) if err := p.learnFact(message, trigger, strippedaction, fact); err != nil { - p.Bot.Send(bot.Message, message.Channel, err.Error()) + p.Bot.Send(c, bot.Message, message.Channel, err.Error()) } else { - p.Bot.Send(bot.Message, message.Channel, fmt.Sprintf("Okay, %s.", message.User.Name)) + p.Bot.Send(c, bot.Message, message.Channel, fmt.Sprintf("Okay, %s.", message.User.Name)) } return true @@ -514,9 +516,9 @@ func changeOperator(body string) string { // If the user requesting forget is either the owner of the last learned fact or // an admin, it may be deleted -func (p *FactoidPlugin) forgetLastFact(message msg.Message) bool { +func (p *FactoidPlugin) forgetLastFact(c bot.Connector, message msg.Message) bool { if p.LastFact == nil { - p.Bot.Send(bot.Message, message.Channel, "I refuse.") + p.Bot.Send(c, bot.Message, message.Channel, "I refuse.") return true } @@ -529,14 +531,14 @@ func (p *FactoidPlugin) forgetLastFact(message msg.Message) bool { } fmt.Printf("Forgot #%d: %s %s %s\n", p.LastFact.ID.Int64, p.LastFact.Fact, p.LastFact.Verb, p.LastFact.Tidbit) - p.Bot.Send(bot.Action, message.Channel, "hits himself over the head with a skillet") + p.Bot.Send(c, bot.Action, message.Channel, "hits himself over the head with a skillet") p.LastFact = nil return true } // Allow users to change facts with a simple regexp -func (p *FactoidPlugin) changeFact(message msg.Message) bool { +func (p *FactoidPlugin) changeFact(c bot.Connector, message msg.Message) bool { oper := changeOperator(message.Body) parts := strings.SplitN(message.Body, oper, 2) userexp := strings.TrimSpace(parts[1]) @@ -553,7 +555,7 @@ func (p *FactoidPlugin) changeFact(message msg.Message) bool { if len(parts) == 4 { // replacement if parts[0] != "s" { - p.Bot.Send(bot.Message, message.Channel, "Nah.") + p.Bot.Send(c, bot.Message, message.Channel, "Nah.") } find := parts[1] replace := parts[2] @@ -571,10 +573,10 @@ func (p *FactoidPlugin) changeFact(message msg.Message) bool { } // make the changes msg := fmt.Sprintf("Changing %d facts.", len(result)) - p.Bot.Send(bot.Message, message.Channel, msg) + p.Bot.Send(c, bot.Message, message.Channel, msg) reg, err := regexp.Compile(find) if err != nil { - p.Bot.Send(bot.Message, message.Channel, "I don't really want to.") + p.Bot.Send(c, bot.Message, message.Channel, "I don't really want to.") return false } for _, fact := range result { @@ -594,19 +596,19 @@ func (p *FactoidPlugin) changeFact(message msg.Message) bool { Err(err). Str("trigger", trigger). Msg("Error getting facts") - p.Bot.Send(bot.Message, message.Channel, "bzzzt") + p.Bot.Send(c, bot.Message, message.Channel, "bzzzt") return true } count := len(result) if count == 0 { - p.Bot.Send(bot.Message, message.Channel, "I didn't find any facts like that.") + p.Bot.Send(c, bot.Message, message.Channel, "I didn't find any facts like that.") return true } if parts[2] == "g" && len(result) > 4 { // summarize result = result[:4] } else { - p.sayFact(message, *result[0]) + p.sayFact(c, message, *result[0]) return true } msg := fmt.Sprintf("%s ", trigger) @@ -619,9 +621,9 @@ func (p *FactoidPlugin) changeFact(message msg.Message) bool { if count > 4 { msg = fmt.Sprintf("%s | ...and %d others", msg, count) } - p.Bot.Send(bot.Message, message.Channel, msg) + p.Bot.Send(c, bot.Message, message.Channel, msg) } else { - p.Bot.Send(bot.Message, message.Channel, "I don't know what you mean.") + p.Bot.Send(c, bot.Message, message.Channel, "I don't know what you mean.") } return true } @@ -629,15 +631,15 @@ func (p *FactoidPlugin) changeFact(message msg.Message) bool { // Message responds to the bot hook on recieving messages. // This function returns true if the plugin responds in a meaningful way to the users message. // Otherwise, the function returns false and the bot continues execution of other plugins. -func (p *FactoidPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *FactoidPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { if strings.ToLower(message.Body) == "what was that?" { - return p.tellThemWhatThatWas(message) + return p.tellThemWhatThatWas(c, message) } // This plugin has no business with normal messages if !message.Command { // look for any triggers in the db matching this message - return p.trigger(message) + return p.trigger(c, message) } if strings.HasPrefix(strings.ToLower(message.Body), "alias") { @@ -647,53 +649,53 @@ func (p *FactoidPlugin) message(kind bot.Kind, message msg.Message, args ...inte m := strings.TrimPrefix(message.Body, "alias ") parts := strings.SplitN(m, "->", 2) if len(parts) != 2 { - p.Bot.Send(bot.Message, message.Channel, "If you want to alias something, use: `alias this -> that`") + p.Bot.Send(c, bot.Message, message.Channel, "If you want to alias something, use: `alias this -> that`") return true } a := aliasFromStrings(strings.TrimSpace(parts[1]), strings.TrimSpace(parts[0])) if err := a.save(p.db); err != nil { - p.Bot.Send(bot.Message, message.Channel, err.Error()) + p.Bot.Send(c, bot.Message, message.Channel, err.Error()) } else { - p.Bot.Send(bot.Action, message.Channel, "learns a new synonym") + p.Bot.Send(c, bot.Action, message.Channel, "learns a new synonym") } return true } if strings.ToLower(message.Body) == "factoid" { if fact := p.randomFact(); fact != nil { - p.sayFact(message, *fact) + p.sayFact(c, message, *fact) return true } log.Debug().Msg("Got a nil fact.") } if strings.ToLower(message.Body) == "forget that" { - return p.forgetLastFact(message) + return p.forgetLastFact(c, message) } if changeOperator(message.Body) != "" { - return p.changeFact(message) + return p.changeFact(c, message) } action := findAction(message.Body) if action != "" { - return p.learnAction(message, action) + return p.learnAction(c, message, action) } // look for any triggers in the db matching this message - if p.trigger(message) { + if p.trigger(c, message) { return true } // We didn't find anything, panic! - p.Bot.Send(bot.Message, message.Channel, p.NotFound[rand.Intn(len(p.NotFound))]) + p.Bot.Send(c, bot.Message, message.Channel, p.NotFound[rand.Intn(len(p.NotFound))]) return true } // Help responds to help requests. Every plugin must implement a help function. -func (p *FactoidPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { - p.Bot.Send(bot.Message, message.Channel, "I can learn facts and spit them back out. You can say \"this is that\" or \"he $5\". Later, trigger the factoid by just saying the trigger word, \"this\" or \"he\" in these examples.") - p.Bot.Send(bot.Message, message.Channel, "I can also figure out some variables including: $nonzero, $digit, $nick, and $someone.") +func (p *FactoidPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.Bot.Send(c, bot.Message, message.Channel, "I can learn facts and spit them back out. You can say \"this is that\" or \"he $5\". Later, trigger the factoid by just saying the trigger word, \"this\" or \"he\" in these examples.") + p.Bot.Send(c, bot.Message, message.Channel, "I can also figure out some variables including: $nonzero, $digit, $nick, and $someone.") return true } @@ -708,7 +710,7 @@ func (p *FactoidPlugin) randomFact() *Factoid { } // factTimer spits out a fact at a given interval and with given probability -func (p *FactoidPlugin) factTimer(channel string) { +func (p *FactoidPlugin) factTimer(c bot.Connector, channel string) { quoteTime := p.Bot.Config().GetInt("Factoid.QuoteTime", 30) if quoteTime == 0 { quoteTime = 30 @@ -749,7 +751,7 @@ func (p *FactoidPlugin) factTimer(channel string) { User: &users[rand.Intn(len(users))], Channel: channel, } - p.sayFact(message, *fact) + p.sayFact(c, message, *fact) myLastMsg = time.Now() } } diff --git a/plugins/fact/webTemplates.go b/plugins/fact/webTemplates.go index d3f7ee6..0c0c7b7 100644 --- a/plugins/fact/webTemplates.go +++ b/plugins/fact/webTemplates.go @@ -7,7 +7,7 @@ package fact // 2016-01-15 Later note, why are these in plugins and the server is in bot? -var factoidIndex string = ` +var factoidIndex = ` diff --git a/plugins/first/first.go b/plugins/first/first.go index c0a35b0..7e26b6c 100644 --- a/plugins/first/first.go +++ b/plugins/first/first.go @@ -132,14 +132,14 @@ func isToday(t time.Time) bool { // Message responds to the bot hook on recieving messages. // This function returns true if the plugin responds in a meaningful way to the users message. // Otherwise, the function returns false and the bot continues execution of other plugins. -func (p *FirstPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *FirstPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { // This bot does not reply to anything if p.First == nil && p.allowed(message) { log.Debug(). Str("body", message.Body). Msg("No previous first. Recording new first") - p.recordFirst(message) + p.recordFirst(c, message) return false } else if p.First != nil { if isToday(p.First.time) && p.allowed(message) { @@ -148,7 +148,7 @@ func (p *FirstPlugin) message(kind bot.Kind, message msg.Message, args ...interf Time("t0", p.First.time). Time("t1", time.Now()). Msg("Recording first") - p.recordFirst(message) + p.recordFirst(c, message) return false } } @@ -157,7 +157,7 @@ func (p *FirstPlugin) message(kind bot.Kind, message msg.Message, args ...interf "?", "", "!", "") msg := strings.ToLower(message.Body) if r.Replace(msg) == "whos on first" { - p.announceFirst(message) + p.announceFirst(c, message) return true } @@ -199,7 +199,7 @@ func (p *FirstPlugin) allowed(message msg.Message) bool { return true } -func (p *FirstPlugin) recordFirst(message msg.Message) { +func (p *FirstPlugin) recordFirst(c bot.Connector, message msg.Message) { log.Info(). Str("user", message.User.Name). Str("body", message.Body). @@ -216,13 +216,13 @@ func (p *FirstPlugin) recordFirst(message msg.Message) { log.Error().Err(err).Msg("Error saving first entry") return } - p.announceFirst(message) + p.announceFirst(c, message) } -func (p *FirstPlugin) announceFirst(message msg.Message) { - c := message.Channel +func (p *FirstPlugin) announceFirst(c bot.Connector, message msg.Message) { + ch := message.Channel if p.First != nil { - p.Bot.Send(bot.Message, c, fmt.Sprintf("%s had first at %s with the message: \"%s\"", + p.Bot.Send(c, bot.Message, ch, fmt.Sprintf("%s had first at %s with the message: \"%s\"", p.First.nick, p.First.time.Format("15:04"), p.First.body)) } } @@ -235,7 +235,7 @@ func (p *FirstPlugin) LoadData() { } // Help responds to help requests. Every plugin must implement a help function. -func (p *FirstPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { - p.Bot.Send(bot.Message, message.Channel, "Sorry, First does not do a goddamn thing.") +func (p *FirstPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.Bot.Send(c, bot.Message, message.Channel, "Sorry, First does not do a goddamn thing.") return true } diff --git a/plugins/inventory/inventory.go b/plugins/inventory/inventory.go index b1fb2bf..2eb4a2f 100644 --- a/plugins/inventory/inventory.go +++ b/plugins/inventory/inventory.go @@ -78,7 +78,7 @@ func (p *InventoryPlugin) itemFilter(input string) string { return input } -func (p *InventoryPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *InventoryPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { m := message.Body log.Debug().Msgf("inventory trying to read %+v", message) if message.Command { @@ -89,7 +89,7 @@ func (p *InventoryPlugin) message(kind bot.Kind, message msg.Message, args ...in log.Debug().Msgf("I think I have more than 0 items: %+v, len(items)=%d", items, len(items)) say = fmt.Sprintf("I'm currently holding %s", strings.Join(items, ", ")) } - p.bot.Send(bot.Message, message.Channel, say) + p.bot.Send(c, bot.Message, message.Channel, say) return true } @@ -97,11 +97,11 @@ func (p *InventoryPlugin) message(kind bot.Kind, message msg.Message, args ...in // Bucket[:,] have a (.+) if matches := p.r1.FindStringSubmatch(m); len(matches) > 0 { log.Debug().Msgf("Found item to add: %s", matches[1]) - return p.addItem(message, matches[1]) + return p.addItem(c, message, matches[1]) } if matches := p.r2.FindStringSubmatch(m); len(matches) > 0 { log.Debug().Msgf("Found item to add: %s", matches[1]) - return p.addItem(message, matches[1]) + return p.addItem(c, message, matches[1]) } } if message.Action { @@ -112,15 +112,15 @@ func (p *InventoryPlugin) message(kind bot.Kind, message msg.Message, args ...in if matches := p.r3.FindStringSubmatch(m); len(matches) > 0 { log.Debug().Msgf("Found item to add: %s", matches[1]) - return p.addItem(message, matches[1]) + return p.addItem(c, message, matches[1]) } if matches := p.r4.FindStringSubmatch(m); len(matches) > 0 { log.Debug().Msgf("Found item to add: %s", matches[1]) - return p.addItem(message, matches[1]) + return p.addItem(c, message, matches[1]) } if matches := p.r5.FindStringSubmatch(m); len(matches) > 0 { log.Debug().Msgf("Found item to add: %s", matches[1]) - return p.addItem(message, matches[1]) + return p.addItem(c, message, matches[1]) } } return false @@ -198,9 +198,9 @@ func (p *InventoryPlugin) remove(i string) { } } -func (p *InventoryPlugin) addItem(m msg.Message, i string) bool { +func (p *InventoryPlugin) addItem(c bot.Connector, m msg.Message, i string) bool { if p.exists(i) { - p.bot.Send(bot.Message, m.Channel, fmt.Sprintf("I already have %s.", i)) + p.bot.Send(c, bot.Message, m.Channel, fmt.Sprintf("I already have %s.", i)) return true } var removed string @@ -213,9 +213,9 @@ func (p *InventoryPlugin) addItem(m msg.Message, i string) bool { log.Error().Err(err).Msg("Error inserting new inventory item") } if removed != "" { - p.bot.Send(bot.Action, m.Channel, fmt.Sprintf("dropped %s and took %s from %s", removed, i, m.User.Name)) + p.bot.Send(c, bot.Action, m.Channel, fmt.Sprintf("dropped %s and took %s from %s", removed, i, m.User.Name)) } else { - p.bot.Send(bot.Action, m.Channel, fmt.Sprintf("takes %s from %s", i, m.User.Name)) + p.bot.Send(c, bot.Action, m.Channel, fmt.Sprintf("takes %s from %s", i, m.User.Name)) } return true } diff --git a/plugins/leftpad/leftpad.go b/plugins/leftpad/leftpad.go index 23847f2..0c97399 100644 --- a/plugins/leftpad/leftpad.go +++ b/plugins/leftpad/leftpad.go @@ -33,7 +33,7 @@ type leftpadResp struct { Str string } -func (p *LeftpadPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *LeftpadPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { if !message.Command { return false } @@ -43,20 +43,20 @@ func (p *LeftpadPlugin) message(kind bot.Kind, message msg.Message, args ...inte padchar := parts[1] length, err := strconv.Atoi(parts[2]) if err != nil { - p.bot.Send(bot.Message, message.Channel, "Invalid padding number") + p.bot.Send(c, bot.Message, message.Channel, "Invalid padding number") return true } maxLen, who := p.config.GetInt("LeftPad.MaxLen", 50), p.config.Get("LeftPad.Who", "Putin") if length > maxLen && maxLen > 0 { msg := fmt.Sprintf("%s would kill me if I did that.", who) - p.bot.Send(bot.Message, message.Channel, msg) + p.bot.Send(c, bot.Message, message.Channel, msg) return true } text := strings.Join(parts[3:], " ") res := leftpad.LeftPad(text, length, padchar) - p.bot.Send(bot.Message, message.Channel, res) + p.bot.Send(c, bot.Message, message.Channel, res) return true } diff --git a/plugins/leftpad/leftpad_test.go b/plugins/leftpad/leftpad_test.go index 32b9cdc..fcfc10b 100644 --- a/plugins/leftpad/leftpad_test.go +++ b/plugins/leftpad/leftpad_test.go @@ -3,6 +3,7 @@ package leftpad import ( + "github.com/velour/catbase/plugins/cli" "strings" "testing" @@ -13,12 +14,12 @@ import ( "github.com/velour/catbase/plugins/counter" ) -func makeMessage(payload string) (bot.Kind, msg.Message) { +func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return bot.Message, msg.Message{ + return &cli.CliPlugin{}, bot.Message, msg.Message{ User: &user.User{Name: "tester"}, Channel: "test", Body: payload, diff --git a/plugins/nerdepedia/nerdepedia.go b/plugins/nerdepedia/nerdepedia.go index 6c0ba3c..d463425 100644 --- a/plugins/nerdepedia/nerdepedia.go +++ b/plugins/nerdepedia/nerdepedia.go @@ -40,7 +40,7 @@ func New(b bot.Bot) *NerdepediaPlugin { // Message responds to the bot hook on recieving messages. // This function returns true if the plugin responds in a meaningful way to the users message. // Otherwise, the function returns false and the bot continues execution of other plugins. -func (p *NerdepediaPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *NerdepediaPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { lowerCase := strings.ToLower(message.Body) query := "" if lowerCase == "may the force be with you" || lowerCase == "help me obi-wan" { @@ -81,7 +81,7 @@ func (p *NerdepediaPlugin) message(kind bot.Kind, message msg.Message, args ...i } if description != "" && link != "" { - p.bot.Send(bot.Message, message.Channel, fmt.Sprintf("%s (%s)", description, link)) + p.bot.Send(c, bot.Message, message.Channel, fmt.Sprintf("%s (%s)", description, link)) return true } } @@ -90,7 +90,7 @@ func (p *NerdepediaPlugin) message(kind bot.Kind, message msg.Message, args ...i } // Help responds to help requests. Every plugin must implement a help function. -func (p *NerdepediaPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { - p.bot.Send(bot.Message, message.Channel, "nerd stuff") +func (p *NerdepediaPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.bot.Send(c, bot.Message, message.Channel, "nerd stuff") return true } diff --git a/plugins/nerdepedia/nerdepeida_test.go b/plugins/nerdepedia/nerdepeida_test.go index eb59807..a135a31 100644 --- a/plugins/nerdepedia/nerdepeida_test.go +++ b/plugins/nerdepedia/nerdepeida_test.go @@ -3,6 +3,7 @@ package nerdepedia import ( + "github.com/velour/catbase/plugins/cli" "strings" "testing" @@ -12,12 +13,12 @@ import ( "github.com/velour/catbase/bot/user" ) -func makeMessage(payload string) (bot.Kind, msg.Message) { +func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return bot.Message, msg.Message{ + return &cli.CliPlugin{}, bot.Message, msg.Message{ User: &user.User{Name: "tester"}, Channel: "test", Body: payload, diff --git a/plugins/picker/picker.go b/plugins/picker/picker.go index b7290d4..51d6b3f 100644 --- a/plugins/picker/picker.go +++ b/plugins/picker/picker.go @@ -16,13 +16,13 @@ import ( ) type PickerPlugin struct { - Bot bot.Bot + bot bot.Bot } // NewPickerPlugin creates a new PickerPlugin with the Plugin interface func New(b bot.Bot) *PickerPlugin { pp := &PickerPlugin{ - Bot: b, + bot: b, } b.Register(pp, bot.Message, pp.message) b.Register(pp, bot.Help, pp.help) @@ -32,21 +32,21 @@ func New(b bot.Bot) *PickerPlugin { // Message responds to the bot hook on recieving messages. // This function returns true if the plugin responds in a meaningful way to the users message. // Otherwise, the function returns false and the bot continues execution of other plugins. -func (p *PickerPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *PickerPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { if !strings.HasPrefix(message.Body, "pick") { return false } n, items, err := p.parse(message.Body) if err != nil { - p.Bot.Send(bot.Message, message.Channel, err.Error()) + p.bot.Send(c, bot.Message, message.Channel, err.Error()) return true } if n == 1 { item := items[rand.Intn(len(items))] out := fmt.Sprintf("I've chosen %q for you.", strings.TrimSpace(item)) - p.Bot.Send(bot.Message, message.Channel, out) + p.bot.Send(c, bot.Message, message.Channel, out) return true } @@ -62,7 +62,7 @@ func (p *PickerPlugin) message(kind bot.Kind, message msg.Message, args ...inter fmt.Fprintf(&b, ", %q", item) } b.WriteString(" }") - p.Bot.Send(bot.Message, message.Channel, b.String()) + p.bot.Send(c, bot.Message, message.Channel, b.String()) return true } @@ -111,7 +111,7 @@ func (p *PickerPlugin) parse(body string) (int, []string, error) { } // Help responds to help requests. Every plugin must implement a help function. -func (p *PickerPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { - p.Bot.Send(bot.Message, message.Channel, "Choose from a list of options. Try \"pick {a,b,c}\".") +func (p *PickerPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.bot.Send(c, bot.Message, message.Channel, "Choose from a list of options. Try \"pick {a,b,c}\".") return true } diff --git a/plugins/picker/picker_test.go b/plugins/picker/picker_test.go index b01684a..c1901c6 100644 --- a/plugins/picker/picker_test.go +++ b/plugins/picker/picker_test.go @@ -3,6 +3,7 @@ package picker import ( + "github.com/velour/catbase/plugins/cli" "strings" "testing" @@ -12,12 +13,12 @@ import ( "github.com/velour/catbase/bot/user" ) -func makeMessage(payload string) (bot.Kind, msg.Message) { +func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return bot.Message, msg.Message{ + return &cli.CliPlugin{}, bot.Message, msg.Message{ User: &user.User{Name: "tester"}, Channel: "test", Body: payload, diff --git a/plugins/reaction/reaction.go b/plugins/reaction/reaction.go index 3dcd078..a4a5f55 100644 --- a/plugins/reaction/reaction.go +++ b/plugins/reaction/reaction.go @@ -11,38 +11,38 @@ import ( ) type ReactionPlugin struct { - Bot bot.Bot - Config *config.Config + bot bot.Bot + config *config.Config } func New(b bot.Bot) *ReactionPlugin { rp := &ReactionPlugin{ - Bot: b, - Config: b.Config(), + bot: b, + config: b.Config(), } b.Register(rp, bot.Message, rp.message) return rp } -func (p *ReactionPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *ReactionPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { harrass := false - for _, nick := range p.Config.GetArray("Reaction.HarrassList", []string{}) { + for _, nick := range p.config.GetArray("Reaction.HarrassList", []string{}) { if message.User.Name == nick { harrass = true break } } - chance := p.Config.GetFloat64("Reaction.GeneralChance", 0.01) + chance := p.config.GetFloat64("Reaction.GeneralChance", 0.01) negativeWeight := 1 if harrass { - chance = p.Config.GetFloat64("Reaction.HarrassChance", 0.05) - negativeWeight = p.Config.GetInt("Reaction.NegativeHarrassmentMultiplier", 2) + chance = p.config.GetFloat64("Reaction.HarrassChance", 0.05) + negativeWeight = p.config.GetInt("Reaction.NegativeHarrassmentMultiplier", 2) } if rand.Float64() < chance { - numPositiveReactions := len(p.Config.GetArray("Reaction.PositiveReactions", []string{})) - numNegativeReactions := len(p.Config.GetArray("Reaction.NegativeReactions", []string{})) + numPositiveReactions := len(p.config.GetArray("Reaction.PositiveReactions", []string{})) + numNegativeReactions := len(p.config.GetArray("Reaction.NegativeReactions", []string{})) maxIndex := numPositiveReactions + numNegativeReactions*negativeWeight @@ -51,14 +51,14 @@ func (p *ReactionPlugin) message(kind bot.Kind, message msg.Message, args ...int reaction := "" if index < numPositiveReactions { - reaction = p.Config.GetArray("Reaction.PositiveReactions", []string{})[index] + reaction = p.config.GetArray("Reaction.PositiveReactions", []string{})[index] } else { index -= numPositiveReactions index %= numNegativeReactions - reaction = p.Config.GetArray("Reaction.NegativeReactions", []string{})[index] + reaction = p.config.GetArray("Reaction.NegativeReactions", []string{})[index] } - p.Bot.Send(bot.Reaction, message.Channel, reaction, message) + p.bot.Send(c, bot.Reaction, message.Channel, reaction, message) } return false diff --git a/plugins/remember/remember.go b/plugins/remember/remember.go index 67bad5d..c56a105 100644 --- a/plugins/remember/remember.go +++ b/plugins/remember/remember.go @@ -32,10 +32,10 @@ func New(b bot.Bot) *RememberPlugin { return p } -func (p *RememberPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *RememberPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { if strings.ToLower(message.Body) == "quote" && message.Command { q := p.randQuote() - p.bot.Send(bot.Message, message.Channel, q) + p.bot.Send(c, bot.Message, message.Channel, q) // is it evil not to remember that the user said quote? return true @@ -82,7 +82,7 @@ func (p *RememberPlugin) message(kind bot.Kind, message msg.Message, args ...int } if err := fact.Save(p.db); err != nil { log.Error().Err(err) - p.bot.Send(bot.Message, message.Channel, "Tell somebody I'm broke.") + p.bot.Send(c, bot.Message, message.Channel, "Tell somebody I'm broke.") } log.Info(). @@ -92,13 +92,13 @@ func (p *RememberPlugin) message(kind bot.Kind, message msg.Message, args ...int // sorry, not creative with names so we're reusing msg msg = fmt.Sprintf("Okay, %s, remembering '%s'.", message.User.Name, msg) - p.bot.Send(bot.Message, message.Channel, msg) + p.bot.Send(c, bot.Message, message.Channel, msg) p.recordMsg(message) return true } } - p.bot.Send(bot.Message, message.Channel, "Sorry, I don't know that phrase.") + p.bot.Send(c, bot.Message, message.Channel, "Sorry, I don't know that phrase.") p.recordMsg(message) return true } @@ -107,13 +107,13 @@ func (p *RememberPlugin) message(kind bot.Kind, message msg.Message, args ...int return false } -func (p *RememberPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *RememberPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { msg := "remember will let you quote your idiot friends. Just type " + "!remember to remember what they said. Snippet can " + "be any part of their message. Later on, you can ask for a random " + "!quote." - p.bot.Send(bot.Message, message.Channel, msg) + p.bot.Send(c, bot.Message, message.Channel, msg) return true } diff --git a/plugins/remember/remember_test.go b/plugins/remember/remember_test.go index 8a1512a..91fa566 100644 --- a/plugins/remember/remember_test.go +++ b/plugins/remember/remember_test.go @@ -1,6 +1,7 @@ package remember import ( + "github.com/velour/catbase/plugins/cli" "strings" "testing" @@ -43,7 +44,7 @@ func TestCornerCaseBug(t *testing.T) { p, _, mb := makePlugin(t) for _, m := range msgs { - p.message(bot.Message, m) + p.message(&cli.CliPlugin{}, bot.Message, m) } assert.Len(t, mb.Messages, 1) assert.Contains(t, mb.Messages[0], "horse dick") diff --git a/plugins/reminder/reminder.go b/plugins/reminder/reminder.go index bdc4568..d4003e9 100644 --- a/plugins/reminder/reminder.go +++ b/plugins/reminder/reminder.go @@ -27,7 +27,7 @@ const ( ) type ReminderPlugin struct { - Bot bot.Bot + bot bot.Bot db *sqlx.DB mutex *sync.Mutex timer *time.Timer @@ -65,7 +65,7 @@ func New(b bot.Bot) *ReminderPlugin { w.Add(common.All...) plugin := &ReminderPlugin{ - Bot: b, + bot: b, db: b.DB(), mutex: &sync.Mutex{}, timer: timer, @@ -75,7 +75,7 @@ func New(b bot.Bot) *ReminderPlugin { plugin.queueUpNextReminder() - go reminderer(plugin) + go reminderer(b.DefaultConnector(), plugin) b.Register(plugin, bot.Message, plugin.message) b.Register(plugin, bot.Help, plugin.help) @@ -83,7 +83,7 @@ func New(b bot.Bot) *ReminderPlugin { return plugin } -func (p *ReminderPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *ReminderPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { channel := message.Channel from := message.User.Name @@ -109,7 +109,7 @@ func (p *ReminderPlugin) message(kind bot.Kind, message msg.Message, args ...int dur, err = time.ParseDuration(parts[3]) if err != nil { - p.Bot.Send(bot.Message, channel, "Easy cowboy, not sure I can parse that duration. Try something like '1.5h' or '2h45m'.") + p.bot.Send(c, bot.Message, channel, "Easy cowboy, not sure I can parse that duration. Try something like '1.5h' or '2h45m'.") return true } @@ -137,7 +137,7 @@ func (p *ReminderPlugin) message(kind bot.Kind, message msg.Message, args ...int dur2, err = time.ParseDuration(parts[5]) if err != nil { log.Error().Err(err) - p.Bot.Send(bot.Message, channel, "Easy cowboy, not sure I can parse that duration. Try something like '1.5h' or '2h45m'.") + p.bot.Send(c, bot.Message, channel, "Easy cowboy, not sure I can parse that duration. Try something like '1.5h' or '2h45m'.") return true } @@ -148,7 +148,7 @@ func (p *ReminderPlugin) message(kind bot.Kind, message msg.Message, args ...int max := p.config.GetInt("Reminder.MaxBatchAdd", 10) for i := 0; when.Before(endTime); i++ { if i >= max { - p.Bot.Send(bot.Message, channel, "Easy cowboy, that's a lot of reminders. I'll add some of them.") + p.bot.Send(c, bot.Message, channel, "Easy cowboy, that's a lot of reminders. I'll add some of them.") doConfirm = false break } @@ -165,14 +165,14 @@ func (p *ReminderPlugin) message(kind bot.Kind, message msg.Message, args ...int when = when.Add(dur) } } else { - p.Bot.Send(bot.Message, channel, "Easy cowboy, not sure I comprehend what you're asking.") + p.bot.Send(c, bot.Message, channel, "Easy cowboy, not sure I comprehend what you're asking.") return true } if doConfirm && from == who { - p.Bot.Send(bot.Message, channel, fmt.Sprintf("Okay. I'll remind you.")) + p.bot.Send(c, bot.Message, channel, fmt.Sprintf("Okay. I'll remind you.")) } else if doConfirm { - p.Bot.Send(bot.Message, channel, fmt.Sprintf("Sure %s, I'll remind %s.", from, who)) + p.bot.Send(c, bot.Message, channel, fmt.Sprintf("Sure %s, I'll remind %s.", from, who)) } p.queueUpNextReminder() @@ -192,22 +192,22 @@ func (p *ReminderPlugin) message(kind bot.Kind, message msg.Message, args ...int } } if err != nil { - p.Bot.Send(bot.Message, channel, "listing failed.") + p.bot.Send(c, bot.Message, channel, "listing failed.") } else { - p.Bot.Send(bot.Message, channel, response) + p.bot.Send(c, bot.Message, channel, response) } return true } else if len(parts) == 3 && strings.ToLower(parts[0]) == "cancel" && strings.ToLower(parts[1]) == "reminder" { id, err := strconv.ParseInt(parts[2], 10, 64) if err != nil { - p.Bot.Send(bot.Message, channel, fmt.Sprintf("couldn't parse id: %s", parts[2])) + p.bot.Send(c, bot.Message, channel, fmt.Sprintf("couldn't parse id: %s", parts[2])) } else { err := p.deleteReminder(id) if err == nil { - p.Bot.Send(bot.Message, channel, fmt.Sprintf("successfully canceled reminder: %s", parts[2])) + p.bot.Send(c, bot.Message, channel, fmt.Sprintf("successfully canceled reminder: %s", parts[2])) } else { - p.Bot.Send(bot.Message, channel, fmt.Sprintf("failed to find and cancel reminder: %s", parts[2])) + p.bot.Send(c, bot.Message, channel, fmt.Sprintf("failed to find and cancel reminder: %s", parts[2])) } } return true @@ -216,8 +216,8 @@ func (p *ReminderPlugin) message(kind bot.Kind, message msg.Message, args ...int return false } -func (p *ReminderPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { - p.Bot.Send(bot.Message, message.Channel, "Pester someone with a reminder. Try \"remind in message\".\n\nUnsure about duration syntax? Check https://golang.org/pkg/time/#ParseDuration") +func (p *ReminderPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.bot.Send(c, bot.Message, message.Channel, "Pester someone with a reminder. Try \"remind in message\".\n\nUnsure about duration syntax? Check https://golang.org/pkg/time/#ParseDuration") return true } @@ -351,7 +351,7 @@ func (p *ReminderPlugin) queueUpNextReminder() { } } -func reminderer(p *ReminderPlugin) { +func reminderer(c bot.Connector, p *ReminderPlugin) { for { <-p.timer.C @@ -366,7 +366,7 @@ func reminderer(p *ReminderPlugin) { message = fmt.Sprintf("Hey %s, %s wanted you to be reminded: %s", reminder.who, reminder.from, reminder.what) } - p.Bot.Send(bot.Message, reminder.channel, message) + p.bot.Send(c, bot.Message, reminder.channel, message) if err := p.deleteReminder(reminder.id); err != nil { log.Error(). diff --git a/plugins/reminder/reminder_test.go b/plugins/reminder/reminder_test.go index 457397d..04a4622 100644 --- a/plugins/reminder/reminder_test.go +++ b/plugins/reminder/reminder_test.go @@ -4,6 +4,7 @@ package reminder import ( "fmt" + "github.com/velour/catbase/plugins/cli" "strings" "testing" "time" @@ -14,16 +15,16 @@ import ( "github.com/velour/catbase/bot/user" ) -func makeMessage(payload string) (bot.Kind, msg.Message) { +func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) { return makeMessageBy(payload, "tester") } -func makeMessageBy(payload, by string) (bot.Kind, msg.Message) { +func makeMessageBy(payload, by string) (bot.Connector, bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return bot.Message, msg.Message{ + return &cli.CliPlugin{}, bot.Message, msg.Message{ User: &user.User{Name: by}, Channel: "test", Body: payload, @@ -223,6 +224,6 @@ func TestLimitList(t *testing.T) { func TestHelp(t *testing.T) { c, mb := setup(t) assert.NotNil(t, c) - c.help(bot.Help, msg.Message{Channel: "channel"}, []string{}) + c.help(&cli.CliPlugin{}, bot.Help, msg.Message{Channel: "channel"}, []string{}) assert.Len(t, mb.Messages, 1) } diff --git a/plugins/rpgORdie/rpgORdie.go b/plugins/rpgORdie/rpgORdie.go index 1035a06..527d808 100644 --- a/plugins/rpgORdie/rpgORdie.go +++ b/plugins/rpgORdie/rpgORdie.go @@ -20,7 +20,7 @@ const ( ) type RPGPlugin struct { - Bot bot.Bot + bot bot.Bot listenFor map[string]*board } @@ -99,7 +99,7 @@ func (b *board) checkAndMove(dx, dy int) int { func New(b bot.Bot) *RPGPlugin { rpg := &RPGPlugin{ - Bot: b, + bot: b, listenFor: map[string]*board{}, } b.Register(rpg, bot.Message, rpg.message) @@ -108,25 +108,25 @@ func New(b bot.Bot) *RPGPlugin { return rpg } -func (p *RPGPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *RPGPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { if strings.ToLower(message.Body) == "start rpg" { b := NewRandomBoard() - ts, _ := p.Bot.Send(bot.Message, message.Channel, b.toMessageString()) + ts, _ := p.bot.Send(c, bot.Message, message.Channel, b.toMessageString()) p.listenFor[ts] = b - p.Bot.Send(bot.Reply, message.Channel, "Over here.", ts) + p.bot.Send(c, bot.Reply, message.Channel, "Over here.", ts) return true } return false } -func (p *RPGPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { - p.Bot.Send(bot.Message, message.Channel, "Go find a walkthrough or something.") +func (p *RPGPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.bot.Send(c, bot.Message, message.Channel, "Go find a walkthrough or something.") return true } -func (p *RPGPlugin) replyMessage(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *RPGPlugin) replyMessage(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { identifier := args[0].(string) - if strings.ToLower(message.User.Name) != strings.ToLower(p.Bot.Config().Get("Nick", "bot")) { + if strings.ToLower(message.User.Name) != strings.ToLower(p.bot.Config().Get("Nick", "bot")) { if b, ok := p.listenFor[identifier]; ok { var res int @@ -145,12 +145,12 @@ func (p *RPGPlugin) replyMessage(kind bot.Kind, message msg.Message, args ...int switch res { case OK: - p.Bot.Send(bot.Edit, message.Channel, b.toMessageString(), identifier) + p.bot.Send(c, bot.Edit, message.Channel, b.toMessageString(), identifier) case WIN: - p.Bot.Send(bot.Edit, message.Channel, b.toMessageString(), identifier) - p.Bot.Send(bot.Reply, message.Channel, "congratulations, you beat the easiest level imaginable.", identifier) + p.bot.Send(c, bot.Edit, message.Channel, b.toMessageString(), identifier) + p.bot.Send(c, bot.Reply, message.Channel, "congratulations, you beat the easiest level imaginable.", identifier) case INVALID: - p.Bot.Send(bot.Reply, message.Channel, fmt.Sprintf("you can't move %s", message.Body), identifier) + p.bot.Send(c, bot.Reply, message.Channel, fmt.Sprintf("you can't move %s", message.Body), identifier) } return true } diff --git a/plugins/rpgORdie/rpgORdie_test.go b/plugins/rpgORdie/rpgORdie_test.go index ddcd924..86f7a77 100644 --- a/plugins/rpgORdie/rpgORdie_test.go +++ b/plugins/rpgORdie/rpgORdie_test.go @@ -1,3 +1 @@ package rpgORdie - -import () diff --git a/plugins/rss/rss.go b/plugins/rss/rss.go index 811f37f..41cdcc1 100644 --- a/plugins/rss/rss.go +++ b/plugins/rss/rss.go @@ -12,7 +12,7 @@ import ( ) type RSSPlugin struct { - Bot bot.Bot + bot bot.Bot cache map[string]*cacheItem shelfLife time.Duration maxLines int @@ -51,7 +51,7 @@ func (c *cacheItem) getCurrentPage(maxLines int) string { func New(b bot.Bot) *RSSPlugin { rss := &RSSPlugin{ - Bot: b, + bot: b, cache: map[string]*cacheItem{}, shelfLife: time.Minute * time.Duration(b.Config().GetInt("rss.shelfLife", 20)), maxLines: b.Config().GetInt("rss.maxLines", 5), @@ -61,19 +61,19 @@ func New(b bot.Bot) *RSSPlugin { return rss } -func (p *RSSPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *RSSPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { tokens := strings.Fields(message.Body) numTokens := len(tokens) if numTokens == 2 && strings.ToLower(tokens[0]) == "rss" { if item, ok := p.cache[strings.ToLower(tokens[1])]; ok && time.Now().Before(item.expiration) { - p.Bot.Send(bot.Message, message.Channel, item.getCurrentPage(p.maxLines)) + p.bot.Send(c, bot.Message, message.Channel, item.getCurrentPage(p.maxLines)) return true } else { fp := gofeed.NewParser() feed, err := fp.ParseURL(tokens[1]) if err != nil { - p.Bot.Send(bot.Message, message.Channel, fmt.Sprintf("RSS error: %s", err.Error())) + p.bot.Send(c, bot.Message, message.Channel, fmt.Sprintf("RSS error: %s", err.Error())) return true } item := &cacheItem{ @@ -89,7 +89,7 @@ func (p *RSSPlugin) message(kind bot.Kind, message msg.Message, args ...interfac p.cache[strings.ToLower(tokens[1])] = item - p.Bot.Send(bot.Message, message.Channel, item.getCurrentPage(p.maxLines)) + p.bot.Send(c, bot.Message, message.Channel, item.getCurrentPage(p.maxLines)) return true } } @@ -98,7 +98,7 @@ func (p *RSSPlugin) message(kind bot.Kind, message msg.Message, args ...interfac } // Help responds to help requests. Every plugin must implement a help function. -func (p *RSSPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { - p.Bot.Send(bot.Message, message.Channel, "try '!rss http://rss.cnn.com/rss/edition.rss'") +func (p *RSSPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.bot.Send(c, bot.Message, message.Channel, "try '!rss http://rss.cnn.com/rss/edition.rss'") return true } diff --git a/plugins/rss/rss_test.go b/plugins/rss/rss_test.go index 900031a..110fdf2 100644 --- a/plugins/rss/rss_test.go +++ b/plugins/rss/rss_test.go @@ -2,6 +2,7 @@ package rss import ( "fmt" + "github.com/velour/catbase/plugins/cli" "strings" "testing" @@ -11,12 +12,12 @@ import ( "github.com/velour/catbase/bot/user" ) -func makeMessage(payload string) (bot.Kind, msg.Message) { +func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return bot.Message, msg.Message{ + return &cli.CliPlugin{}, bot.Message, msg.Message{ User: &user.User{Name: "tester"}, Channel: "test", Body: payload, diff --git a/plugins/sisyphus/sisyphus.go b/plugins/sisyphus/sisyphus.go index 6238596..4bb607f 100644 --- a/plugins/sisyphus/sisyphus.go +++ b/plugins/sisyphus/sisyphus.go @@ -19,7 +19,7 @@ const ( ) type SisyphusPlugin struct { - Bot bot.Bot + bot bot.Bot listenFor map[string]*game } @@ -38,7 +38,7 @@ type game struct { nextAns int } -func NewRandomGame(b bot.Bot, channel, who string) *game { +func NewRandomGame(c bot.Connector, b bot.Bot, channel, who string) *game { size := rand.Intn(9) + 2 g := game{ channel: channel, @@ -48,32 +48,32 @@ func NewRandomGame(b bot.Bot, channel, who string) *game { size: size, current: size / 2, } - g.id, _ = b.Send(bot.Message, channel, g.toMessageString()) + g.id, _ = b.Send(c, bot.Message, channel, g.toMessageString()) - g.schedulePush() - g.scheduleDecrement() + g.schedulePush(c) + g.scheduleDecrement(c) return &g } -func (g *game) scheduleDecrement() { +func (g *game) scheduleDecrement(c bot.Connector) { if g.timers[0] != nil { g.timers[0].Stop() } minDec := g.bot.Config().GetInt("Sisyphus.MinDecrement", 10) maxDec := g.bot.Config().GetInt("Sisyphus.MaxDecrement", 30) - g.nextDec = time.Now().Add(time.Duration((minDec + rand.Intn(maxDec))) * time.Minute) + g.nextDec = time.Now().Add(time.Duration(minDec+rand.Intn(maxDec)) * time.Minute) go func() { t := time.NewTimer(g.nextDec.Sub(time.Now())) g.timers[0] = t select { case <-t.C: - g.handleDecrement() + g.handleDecrement(c) } }() } -func (g *game) schedulePush() { +func (g *game) schedulePush(c bot.Connector) { if g.timers[1] != nil { g.timers[1].Stop() } @@ -85,7 +85,7 @@ func (g *game) schedulePush() { g.timers[1] = t select { case <-t.C: - g.handleNotify() + g.handleNotify(c) } }() } @@ -97,21 +97,21 @@ func (g *game) endGame() { g.ended = true } -func (g *game) handleDecrement() { +func (g *game) handleDecrement(c bot.Connector) { g.current++ - g.bot.Send(bot.Edit, g.channel, g.toMessageString(), g.id) + g.bot.Send(c, bot.Edit, g.channel, g.toMessageString(), g.id) if g.current > g.size-2 { - g.bot.Send(bot.Reply, g.channel, "you lose", g.id) + g.bot.Send(c, bot.Reply, g.channel, "you lose", g.id) msg := fmt.Sprintf("%s just lost the game after %s", g.who, time.Now().Sub(g.start)) - g.bot.Send(bot.Message, g.channel, msg) + g.bot.Send(c, bot.Message, g.channel, msg) g.endGame() } else { - g.scheduleDecrement() + g.scheduleDecrement(c) } } -func (g *game) handleNotify() { - g.bot.Send(bot.Reply, g.channel, "You can push now.\n"+g.generateQuestion(), g.id) +func (g *game) handleNotify(c bot.Connector) { + g.bot.Send(c, bot.Reply, g.channel, "You can push now.\n"+g.generateQuestion(), g.id) } func (g *game) generateQuestion() string { @@ -164,7 +164,7 @@ func (g *game) toMessageString() string { func New(b bot.Bot) *SisyphusPlugin { sp := &SisyphusPlugin{ - Bot: b, + bot: b, listenFor: map[string]*game{}, } b.Register(sp, bot.Message, sp.message) @@ -173,24 +173,24 @@ func New(b bot.Bot) *SisyphusPlugin { return sp } -func (p *SisyphusPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *SisyphusPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { if strings.ToLower(message.Body) == "start sisyphus" { - b := NewRandomGame(p.Bot, message.Channel, message.User.Name) + b := NewRandomGame(c, p.bot, message.Channel, message.User.Name) p.listenFor[b.id] = b - p.Bot.Send(bot.Reply, message.Channel, "Over here.", b.id) + p.bot.Send(c, bot.Reply, message.Channel, "Over here.", b.id) return true } return false } -func (p *SisyphusPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { - p.Bot.Send(bot.Message, message.Channel, "https://en.wikipedia.org/wiki/Sisyphus") +func (p *SisyphusPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.bot.Send(c, bot.Message, message.Channel, "https://en.wikipedia.org/wiki/Sisyphus") return true } -func (p *SisyphusPlugin) replyMessage(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *SisyphusPlugin) replyMessage(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { identifier := args[0].(string) - if strings.ToLower(message.User.Name) != strings.ToLower(p.Bot.Config().Get("Nick", "bot")) { + if strings.ToLower(message.User.Name) != strings.ToLower(p.bot.Config().Get("Nick", "bot")) { if g, ok := p.listenFor[identifier]; ok { log.Debug().Msgf("got message on %s: %+v", identifier, message) @@ -206,18 +206,18 @@ func (p *SisyphusPlugin) replyMessage(kind bot.Kind, message msg.Message, args . if time.Now().After(g.nextPush) { if g.checkAnswer(message.Body) { - p.Bot.Send(bot.Edit, message.Channel, g.toMessageString(), identifier) - g.schedulePush() + p.bot.Send(c, bot.Edit, message.Channel, g.toMessageString(), identifier) + g.schedulePush(c) msg := fmt.Sprintf("Ok. You can push again in %s", g.nextPush.Sub(time.Now())) - p.Bot.Send(bot.Reply, message.Channel, msg, identifier) + p.bot.Send(c, bot.Reply, message.Channel, msg, identifier) } else { - p.Bot.Send(bot.Reply, message.Channel, "you lose", identifier) + p.bot.Send(c, bot.Reply, message.Channel, "you lose", identifier) msg := fmt.Sprintf("%s just lost the sisyphus game after %s", g.who, time.Now().Sub(g.start)) - p.Bot.Send(bot.Message, message.Channel, msg) + p.bot.Send(c, bot.Message, message.Channel, msg) g.endGame() } } else { - p.Bot.Send(bot.Reply, message.Channel, "you cannot push yet", identifier) + p.bot.Send(c, bot.Reply, message.Channel, "you cannot push yet", identifier) } return true } diff --git a/plugins/talker/talker.go b/plugins/talker/talker.go index 3bba94f..7dc6fa2 100644 --- a/plugins/talker/talker.go +++ b/plugins/talker/talker.go @@ -17,7 +17,7 @@ import ( "github.com/velour/catbase/config" ) -var goatse []string = []string{ +var goatse = []string{ "```* g o a t s e x * g o a t s e x * g o a t s e x *", "g g", "o / \\ \\ / \\ o", @@ -46,23 +46,23 @@ var goatse []string = []string{ } type TalkerPlugin struct { - Bot bot.Bot + bot bot.Bot config *config.Config sayings []string } func New(b bot.Bot) *TalkerPlugin { tp := &TalkerPlugin{ - Bot: b, + bot: b, config: b.Config(), } b.Register(tp, bot.Message, tp.message) b.Register(tp, bot.Help, tp.help) - tp.registerWeb() + tp.registerWeb(b.DefaultConnector()) return tp } -func (p *TalkerPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *TalkerPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { channel := message.Channel body := message.Body lowermessage := strings.ToLower(body) @@ -70,24 +70,24 @@ func (p *TalkerPlugin) message(kind bot.Kind, message msg.Message, args ...inter if message.Command && strings.HasPrefix(lowermessage, "cowsay") { msg, err := p.cowSay(strings.TrimPrefix(message.Body, "cowsay ")) if err != nil { - p.Bot.Send(bot.Message, channel, "Error running cowsay: %s", err) + p.bot.Send(c, bot.Message, channel, "Error running cowsay: %s", err) return true } - p.Bot.Send(bot.Message, channel, msg) + p.bot.Send(c, bot.Message, channel, msg) return true } if message.Command && strings.HasPrefix(lowermessage, "list cows") { cows := p.allCows() m := fmt.Sprintf("Cows: %s", strings.Join(cows, ", ")) - p.Bot.Send(bot.Message, channel, m) + p.bot.Send(c, bot.Message, channel, m) return true } // TODO: This ought to be space split afterwards to remove any punctuation if message.Command && strings.HasPrefix(lowermessage, "say") { msg := strings.TrimSpace(body[3:]) - p.Bot.Send(bot.Message, channel, msg) + p.bot.Send(c, bot.Message, channel, msg) return true } @@ -103,15 +103,15 @@ func (p *TalkerPlugin) message(kind bot.Kind, message msg.Message, args ...inter line = strings.Replace(line, "{nick}", nick, 1) output += line + "\n" } - p.Bot.Send(bot.Message, channel, output) + p.bot.Send(c, bot.Message, channel, output) return true } return false } -func (p *TalkerPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { - p.Bot.Send(bot.Message, message.Channel, "Hi, this is talker. I like to talk about FredFelps!") +func (p *TalkerPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.bot.Send(c, bot.Message, message.Channel, "Hi, this is talker. I like to talk about FredFelps!") return true } @@ -170,7 +170,7 @@ func (p *TalkerPlugin) allCows() []string { return cows } -func (p *TalkerPlugin) registerWeb() { +func (p *TalkerPlugin) registerWeb(c bot.Connector) { http.HandleFunc("/slash/cowsay", func(w http.ResponseWriter, r *http.Request) { r.ParseForm() log.Debug().Msgf("Cowsay:\n%+v", r.PostForm.Get("text")) @@ -178,10 +178,10 @@ func (p *TalkerPlugin) registerWeb() { log.Debug().Msgf("channel: %s", channel) msg, err := p.cowSay(r.PostForm.Get("text")) if err != nil { - p.Bot.Send(bot.Message, channel, fmt.Sprintf("Error running cowsay: %s", err)) + p.bot.Send(c, bot.Message, channel, fmt.Sprintf("Error running cowsay: %s", err)) return } - p.Bot.Send(bot.Message, channel, msg) + p.bot.Send(c, bot.Message, channel, msg) w.WriteHeader(200) }) } diff --git a/plugins/talker/talker_test.go b/plugins/talker/talker_test.go index fe65779..cf3defe 100644 --- a/plugins/talker/talker_test.go +++ b/plugins/talker/talker_test.go @@ -3,6 +3,7 @@ package talker import ( + "github.com/velour/catbase/plugins/cli" "strings" "testing" @@ -12,12 +13,12 @@ import ( "github.com/velour/catbase/bot/user" ) -func makeMessage(payload string) (bot.Kind, msg.Message) { +func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return bot.Message, msg.Message{ + return &cli.CliPlugin{}, bot.Message, msg.Message{ User: &user.User{Name: "tester"}, Channel: "test", Body: payload, @@ -78,6 +79,6 @@ func TestHelp(t *testing.T) { mb := bot.NewMockBot() c := New(mb) assert.NotNil(t, c) - c.help(bot.Help, msg.Message{Channel: "channel"}, []string{}) + c.help(&cli.CliPlugin{}, bot.Help, msg.Message{Channel: "channel"}, []string{}) assert.Len(t, mb.Messages, 1) } diff --git a/plugins/tell/tell.go b/plugins/tell/tell.go index aba8410..a106574 100644 --- a/plugins/tell/tell.go +++ b/plugins/tell/tell.go @@ -21,20 +21,20 @@ func New(b bot.Bot) *TellPlugin { return tp } -func (t *TellPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (t *TellPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { if strings.HasPrefix(strings.ToLower(message.Body), "tell") { parts := strings.Split(message.Body, " ") target := strings.ToLower(parts[1]) newMessage := strings.Join(parts[2:], " ") newMessage = fmt.Sprintf("Hey, %s. %s said: %s", target, message.User.Name, newMessage) t.users[target] = append(t.users[target], newMessage) - t.b.Send(bot.Message, message.Channel, fmt.Sprintf("Okay. I'll tell %s.", target)) + t.b.Send(c, bot.Message, message.Channel, fmt.Sprintf("Okay. I'll tell %s.", target)) return true } uname := strings.ToLower(message.User.Name) if msg, ok := t.users[uname]; ok && len(msg) > 0 { for _, m := range msg { - t.b.Send(bot.Message, message.Channel, string(m)) + t.b.Send(c, bot.Message, message.Channel, string(m)) } t.users[uname] = []string{} return true diff --git a/plugins/tldr/tldr.go b/plugins/tldr/tldr.go index 617da40..0bc3caf 100644 --- a/plugins/tldr/tldr.go +++ b/plugins/tldr/tldr.go @@ -38,11 +38,11 @@ func New(b bot.Bot) *TLDRPlugin { return plugin } -func (p *TLDRPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *TLDRPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { timeLimit := time.Duration(p.bot.Config().GetInt("TLDR.HourLimit", 1)) lowercaseMessage := strings.ToLower(message.Body) if lowercaseMessage == "tl;dr" && p.lastRequest.After(time.Now().Add(-timeLimit*time.Hour)) { - p.bot.Send(bot.Message, message.Channel, "Slow down, cowboy. Read that tiny backlog.") + p.bot.Send(c, bot.Message, message.Channel, "Slow down, cowboy. Read that tiny backlog.") return true } else if lowercaseMessage == "tl;dr" { p.lastRequest = time.Now() @@ -114,7 +114,7 @@ func (p *TLDRPlugin) message(kind bot.Kind, message msg.Message, args ...interfa } - p.bot.Send(bot.Message, message.Channel, response) + p.bot.Send(c, bot.Message, message.Channel, response) return true } @@ -162,8 +162,8 @@ func (p *TLDRPlugin) getTopics() []string { } // Help responds to help requests. Every plugin must implement a help function. -func (p *TLDRPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { - p.bot.Send(bot.Message, message.Channel, "tl;dr") +func (p *TLDRPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.bot.Send(c, bot.Message, message.Channel, "tl;dr") return true } diff --git a/plugins/tldr/tldr_test.go b/plugins/tldr/tldr_test.go index 2bec842..4328463 100644 --- a/plugins/tldr/tldr_test.go +++ b/plugins/tldr/tldr_test.go @@ -1,6 +1,7 @@ package tldr import ( + "github.com/velour/catbase/plugins/cli" "os" "strconv" "strings" @@ -19,12 +20,12 @@ func init() { log.Logger = log.Logger.Output(zerolog.ConsoleWriter{Out: os.Stderr}) } -func makeMessageBy(payload, by string) (bot.Kind, msg.Message) { +func makeMessageBy(payload, by string) (bot.Connector, bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return bot.Message, msg.Message{ + return &cli.CliPlugin{}, bot.Message, msg.Message{ User: &user.User{Name: by}, Channel: "test", Body: payload, @@ -32,7 +33,7 @@ func makeMessageBy(payload, by string) (bot.Kind, msg.Message) { } } -func makeMessage(payload string) (bot.Kind, msg.Message) { +func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) { return makeMessageBy(payload, "tester") } diff --git a/plugins/twitch/twitch.go b/plugins/twitch/twitch.go index 37d8602..155fa9f 100644 --- a/plugins/twitch/twitch.go +++ b/plugins/twitch/twitch.go @@ -24,7 +24,7 @@ const ( ) type TwitchPlugin struct { - Bot bot.Bot + bot bot.Bot config *config.Config twitchList map[string]*Twitcher } @@ -60,7 +60,7 @@ type stream struct { func New(b bot.Bot) *TwitchPlugin { p := &TwitchPlugin{ - Bot: b, + bot: b, config: b.Config(), twitchList: map[string]*Twitcher{}, } @@ -74,7 +74,7 @@ func New(b bot.Bot) *TwitchPlugin { } } } - go p.twitchLoop(ch) + go p.twitchLoop(b.DefaultConnector(), ch) } b.Register(p, bot.Message, p.message) @@ -119,14 +119,14 @@ func (p *TwitchPlugin) serveStreaming(w http.ResponseWriter, r *http.Request) { } } -func (p *TwitchPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *TwitchPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { body := strings.ToLower(message.Body) if body == "twitch status" { channel := message.Channel if users := p.config.GetArray("Twitch."+channel+".Users", []string{}); len(users) > 0 { for _, twitcherName := range users { if _, ok := p.twitchList[twitcherName]; ok { - p.checkTwitch(channel, p.twitchList[twitcherName], true) + p.checkTwitch(c, channel, p.twitchList[twitcherName], true) } } } @@ -140,18 +140,18 @@ func (p *TwitchPlugin) message(kind bot.Kind, message msg.Message, args ...inter return false } -func (p *TwitchPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *TwitchPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { msg := "You can set the templates for streams with\n" msg += fmt.Sprintf("twitch.istpl (default: %s)\n", isStreamingTplFallback) msg += fmt.Sprintf("twitch.nottpl (default: %s)\n", notStreamingTplFallback) msg += fmt.Sprintf("twitch.stoppedtpl (default: %s)\n", stoppedStreamingTplFallback) msg += "You can reset all messages with `!reset twitch`" msg += "And you can ask who is streaming with `!twitch status`" - p.Bot.Send(bot.Message, message.Channel, msg) + p.bot.Send(c, bot.Message, message.Channel, msg) return true } -func (p *TwitchPlugin) twitchLoop(channel string) { +func (p *TwitchPlugin) twitchLoop(c bot.Connector, channel string) { frequency := p.config.GetInt("Twitch.Freq", 60) if p.config.Get("twitch.clientid", "") == "" || p.config.Get("twitch.authorization", "") == "" { log.Info().Msgf("Disabling twitch autochecking.") @@ -164,7 +164,7 @@ func (p *TwitchPlugin) twitchLoop(channel string) { time.Sleep(time.Duration(frequency) * time.Second) for _, twitcherName := range p.config.GetArray("Twitch."+channel+".Users", []string{}) { - p.checkTwitch(channel, p.twitchList[twitcherName], false) + p.checkTwitch(c, channel, p.twitchList[twitcherName], false) } } } @@ -197,7 +197,7 @@ errCase: return []byte{}, false } -func (p *TwitchPlugin) checkTwitch(channel string, twitcher *Twitcher, alwaysPrintStatus bool) { +func (p *TwitchPlugin) checkTwitch(c bot.Connector, channel string, twitcher *Twitcher, alwaysPrintStatus bool) { baseURL, err := url.Parse("https://api.twitch.tv/helix/streams") if err != nil { log.Error().Msg("Error parsing twitch stream URL") @@ -255,31 +255,31 @@ func (p *TwitchPlugin) checkTwitch(channel string, twitcher *Twitcher, alwaysPri t, err := template.New("notStreaming").Parse(notStreamingTpl) if err != nil { log.Error().Err(err) - p.Bot.Send(bot.Message, channel, err) + p.bot.Send(c, bot.Message, channel, err) t = template.Must(template.New("notStreaming").Parse(notStreamingTplFallback)) } t.Execute(&buf, info) - p.Bot.Send(bot.Message, channel, buf.String()) + p.bot.Send(c, bot.Message, channel, buf.String()) } else { t, err := template.New("isStreaming").Parse(isStreamingTpl) if err != nil { log.Error().Err(err) - p.Bot.Send(bot.Message, channel, err) + p.bot.Send(c, bot.Message, channel, err) t = template.Must(template.New("isStreaming").Parse(isStreamingTplFallback)) } t.Execute(&buf, info) - p.Bot.Send(bot.Message, channel, buf.String()) + p.bot.Send(c, bot.Message, channel, buf.String()) } } else if gameID == "" { if twitcher.gameID != "" { t, err := template.New("stoppedStreaming").Parse(stoppedStreamingTpl) if err != nil { log.Error().Err(err) - p.Bot.Send(bot.Message, channel, err) + p.bot.Send(c, bot.Message, channel, err) t = template.Must(template.New("stoppedStreaming").Parse(stoppedStreamingTplFallback)) } t.Execute(&buf, info) - p.Bot.Send(bot.Message, channel, buf.String()) + p.bot.Send(c, bot.Message, channel, buf.String()) } twitcher.gameID = "" } else { @@ -287,11 +287,11 @@ func (p *TwitchPlugin) checkTwitch(channel string, twitcher *Twitcher, alwaysPri t, err := template.New("isStreaming").Parse(isStreamingTpl) if err != nil { log.Error().Err(err) - p.Bot.Send(bot.Message, channel, err) + p.bot.Send(c, bot.Message, channel, err) t = template.Must(template.New("isStreaming").Parse(isStreamingTplFallback)) } t.Execute(&buf, info) - p.Bot.Send(bot.Message, channel, buf.String()) + p.bot.Send(c, bot.Message, channel, buf.String()) } twitcher.gameID = gameID } diff --git a/plugins/twitch/twitch_test.go b/plugins/twitch/twitch_test.go index 120defa..1defec5 100644 --- a/plugins/twitch/twitch_test.go +++ b/plugins/twitch/twitch_test.go @@ -3,6 +3,7 @@ package twitch import ( + "github.com/velour/catbase/plugins/cli" "strings" "testing" @@ -12,12 +13,12 @@ import ( "github.com/velour/catbase/bot/user" ) -func makeMessage(payload string) (bot.Kind, msg.Message) { +func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return bot.Message, msg.Message{ + return &cli.CliPlugin{}, bot.Message, msg.Message{ User: &user.User{Name: "tester"}, Channel: "test", Body: payload, diff --git a/plugins/your/your.go b/plugins/your/your.go index 1d26e9d..10dfc2b 100644 --- a/plugins/your/your.go +++ b/plugins/your/your.go @@ -30,7 +30,7 @@ func New(b bot.Bot) *YourPlugin { // Message responds to the bot hook on recieving messages. // This function returns true if the plugin responds in a meaningful way to the users message. // Otherwise, the function returns false and the bot continues execution of other plugins. -func (p *YourPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *YourPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { maxLen := p.config.GetInt("your.maxlength", 140) if len(message.Body) > maxLen { return false @@ -46,14 +46,14 @@ func (p *YourPlugin) message(kind bot.Kind, message msg.Message, args ...interfa } } if msg != message.Body { - p.bot.Send(bot.Message, message.Channel, msg) + p.bot.Send(c, bot.Message, message.Channel, msg) return true } return false } // Help responds to help requests. Every plugin must implement a help function. -func (p *YourPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { - p.bot.Send(bot.Message, message.Channel, "Your corrects people's grammar.") +func (p *YourPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.bot.Send(c, bot.Message, message.Channel, "Your corrects people's grammar.") return true } diff --git a/plugins/your/your_test.go b/plugins/your/your_test.go index 3c6136a..ea13638 100644 --- a/plugins/your/your_test.go +++ b/plugins/your/your_test.go @@ -3,6 +3,7 @@ package your import ( + "github.com/velour/catbase/plugins/cli" "strings" "testing" @@ -12,12 +13,12 @@ import ( "github.com/velour/catbase/bot/user" ) -func makeMessage(payload string) (bot.Kind, msg.Message) { +func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) { isCmd := strings.HasPrefix(payload, "!") if isCmd { payload = payload[1:] } - return bot.Message, msg.Message{ + return &cli.CliPlugin{}, bot.Message, msg.Message{ User: &user.User{Name: "tester"}, Channel: "test", Body: payload, diff --git a/plugins/zork/zork.go b/plugins/zork/zork.go index 95da698..11458a7 100644 --- a/plugins/zork/zork.go +++ b/plugins/zork/zork.go @@ -37,7 +37,7 @@ func New(b bot.Bot) bot.Plugin { return z } -func (p *ZorkPlugin) runZork(ch string) error { +func (p *ZorkPlugin) runZork(c bot.Connector, ch string) error { const importString = "github.com/velour/catbase/plugins/zork" pkg, err := build.Import(importString, "", build.FindOnly) if err != nil { @@ -79,7 +79,7 @@ func (p *ZorkPlugin) runZork(ch string) error { m := strings.Replace(s.Text(), ">", "", -1) m = strings.Replace(m, "\n", "\n>", -1) m = ">" + m + "\n" - p.bot.Send(bot.Message, ch, m) + p.bot.Send(c, bot.Message, ch, m) } }() go func() { @@ -95,7 +95,7 @@ func (p *ZorkPlugin) runZork(ch string) error { return nil } -func (p *ZorkPlugin) message(kind bot.Kind, message msg.Message, args ...interface{}) bool { +func (p *ZorkPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { m := strings.ToLower(message.Body) log.Debug().Msgf("got message [%s]", m) if ts := strings.Fields(m); len(ts) < 1 || ts[0] != "zork" { @@ -107,8 +107,8 @@ func (p *ZorkPlugin) message(kind bot.Kind, message msg.Message, args ...interfa p.Lock() defer p.Unlock() if p.zorks[ch] == nil { - if err := p.runZork(ch); err != nil { - p.bot.Send(bot.Message, ch, "failed to run zork: "+err.Error()) + if err := p.runZork(c, ch); err != nil { + p.bot.Send(c, bot.Message, ch, "failed to run zork: "+err.Error()) return true } } @@ -117,7 +117,7 @@ func (p *ZorkPlugin) message(kind bot.Kind, message msg.Message, args ...interfa return true } -func (p *ZorkPlugin) help(kind bot.Kind, message msg.Message, args ...interface{}) bool { - p.bot.Send(bot.Message, message.Channel, "Play zork using 'zork '.") +func (p *ZorkPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.bot.Send(c, bot.Message, message.Channel, "Play zork using 'zork '.") return true } From f46ea524f34f8c49f7cdfad796e97ca44139e283 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Mon, 27 May 2019 19:26:17 -0400 Subject: [PATCH 081/107] cli: move html->go to fool GitHub --- plugins/cli/cli.go | 9 +-------- plugins/cli/{index.html => index.go} | 4 ++++ 2 files changed, 5 insertions(+), 8 deletions(-) rename plugins/cli/{index.html => index.go} (99%) diff --git a/plugins/cli/cli.go b/plugins/cli/cli.go index d46357b..fa0ca2a 100644 --- a/plugins/cli/cli.go +++ b/plugins/cli/cli.go @@ -10,7 +10,6 @@ import ( "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" "github.com/velour/catbase/bot/user" - "io/ioutil" "net/http" "time" ) @@ -83,13 +82,7 @@ func (p *CliPlugin) handleWebAPI(w http.ResponseWriter, r *http.Request) { } func (p *CliPlugin) handleWeb(w http.ResponseWriter, r *http.Request) { - f, err := ioutil.ReadFile("plugins/cli/index.html") - if err != nil { - w.WriteHeader(500) - fmt.Fprint(w, err) - return - } - w.Write(f) + fmt.Fprint(w, indexHTML) } // Completing the Connector interface, but will not actually be a connector diff --git a/plugins/cli/index.html b/plugins/cli/index.go similarity index 99% rename from plugins/cli/index.html rename to plugins/cli/index.go index 36fd8c4..09b4364 100644 --- a/plugins/cli/index.html +++ b/plugins/cli/index.go @@ -1,3 +1,6 @@ +package cli + +var indexHTML = ` @@ -135,3 +138,4 @@ +` From 20a56a4fcc370cc01bb2bab7708d45cb42d4b860 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Mon, 27 May 2019 22:16:35 -0400 Subject: [PATCH 082/107] web: small improvements --- bot/bot.go | 4 ++-- plugins/cli/index.go | 2 ++ plugins/fact/webTemplates.go | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bot/bot.go b/bot/bot.go index e66eda7..8674a34 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -138,8 +138,8 @@ var rootIndex = ` Factoids - - + + {{if .EndPoints}}
diff --git a/plugins/cli/index.go b/plugins/cli/index.go index 09b4364..04b0be2 100644 --- a/plugins/cli/index.go +++ b/plugins/cli/index.go @@ -117,6 +117,8 @@ var indexHTML = ` }, send(evt) { evt.preventDefault(); + evt.stopPropagation() + this.input = ""; if (!this.authenticated) { console.log("User is a bot."); this.err = "User appears to be a bot."; diff --git a/plugins/fact/webTemplates.go b/plugins/fact/webTemplates.go index 0c0c7b7..a2b2d25 100644 --- a/plugins/fact/webTemplates.go +++ b/plugins/fact/webTemplates.go @@ -12,7 +12,7 @@ var factoidIndex = ` Factoids - + @@ -24,6 +24,7 @@ var factoidIndex = ` +
@@ -107,6 +108,7 @@ var factoidIndex = ` }); }); + ` From ab3dac35ba36cca3b11d1c5a60fac5a71758ea06 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Mon, 27 May 2019 23:14:05 -0400 Subject: [PATCH 083/107] web: redo factoid page; add title to counter page --- plugins/counter/html.go | 1 + plugins/fact/factoid.go | 37 +++++++ plugins/fact/webTemplates.go | 181 +++++++++++++++++------------------ 3 files changed, 126 insertions(+), 93 deletions(-) diff --git a/plugins/counter/html.go b/plugins/counter/html.go index dca3964..3dd9b3a 100644 --- a/plugins/counter/html.go +++ b/plugins/counter/html.go @@ -14,6 +14,7 @@ var html = ` + Counters diff --git a/plugins/fact/factoid.go b/plugins/fact/factoid.go index eec9156..6546c7e 100644 --- a/plugins/fact/factoid.go +++ b/plugins/fact/factoid.go @@ -4,6 +4,7 @@ package fact import ( "database/sql" + "encoding/json" "fmt" "html/template" "math/rand" @@ -759,6 +760,7 @@ func (p *FactoidPlugin) factTimer(c bot.Connector, channel string) { // Register any web URLs desired func (p *FactoidPlugin) registerWeb() { + http.HandleFunc("/factoid/api", p.serveAPI) http.HandleFunc("/factoid/req", p.serveQuery) http.HandleFunc("/factoid", p.serveQuery) p.Bot.RegisterWeb("/factoid", "Factoid") @@ -773,8 +775,43 @@ func linkify(text string) template.HTML { } return template.HTML(strings.Join(parts, " ")) } +func (p *FactoidPlugin) serveAPI(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + fmt.Fprintf(w, "Incorrect HTTP method") + return + } + info := struct { + Query string `json:"query"` + }{} + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&info) + if err != nil { + w.WriteHeader(500) + fmt.Fprint(w, err) + return + } + + entries, err := getFacts(p.db, info.Query, "") + if err != nil { + w.WriteHeader(500) + fmt.Fprint(w, err) + return + } + + data, err := json.Marshal(entries) + if err != nil { + w.WriteHeader(500) + fmt.Fprint(w, err) + return + } + w.Write(data) +} func (p *FactoidPlugin) serveQuery(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, factoidIndex) +} + +func (p *FactoidPlugin) serveQueryOld(w http.ResponseWriter, r *http.Request) { context := make(map[string]interface{}) funcMap := template.FuncMap{ // The name "title" is what the function will be called in the template text. diff --git a/plugins/fact/webTemplates.go b/plugins/fact/webTemplates.go index a2b2d25..c890664 100644 --- a/plugins/fact/webTemplates.go +++ b/plugins/fact/webTemplates.go @@ -9,106 +9,101 @@ package fact var factoidIndex = ` - + - Factoids - + + + - - - - - - - - + + + + + + + + + Factoids -
- -
- Search for a factoid - - -
- -
-
- - - {{if .Error}} - {{.Error}} - {{end}} - - {{if .Count}} - Found {{.Count}} entries. - {{end}} -
- - {{if .Entries}} -
- - - - - - - - - - - - {{range .Entries}} - - - - - - - {{end}} - -
TriggerFull TextAuthor# Hits
{{linkify .Fact}}{{linkify .Tidbit}}{{linkify .Owner}}{{.Count}}
-
- {{end}} - - + - ` From a7712530f93fb4cb4a6f11f86de5fa8353364673 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Mon, 27 May 2019 23:15:42 -0400 Subject: [PATCH 084/107] fact: remove old handler --- plugins/fact/factoid.go | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/plugins/fact/factoid.go b/plugins/fact/factoid.go index 6546c7e..452fcc0 100644 --- a/plugins/fact/factoid.go +++ b/plugins/fact/factoid.go @@ -810,28 +810,3 @@ func (p *FactoidPlugin) serveAPI(w http.ResponseWriter, r *http.Request) { func (p *FactoidPlugin) serveQuery(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, factoidIndex) } - -func (p *FactoidPlugin) serveQueryOld(w http.ResponseWriter, r *http.Request) { - context := make(map[string]interface{}) - funcMap := template.FuncMap{ - // The name "title" is what the function will be called in the template text. - "linkify": linkify, - } - if e := r.FormValue("entry"); e != "" { - entries, err := getFacts(p.db, e, "") - if err != nil { - log.Error().Err(err).Msg("Web error searching") - } - context["Count"] = fmt.Sprintf("%d", len(entries)) - context["Entries"] = entries - context["Search"] = e - } - t, err := template.New("factoidIndex").Funcs(funcMap).Parse(factoidIndex) - if err != nil { - log.Error().Err(err) - } - err = t.Execute(w, context) - if err != nil { - log.Error().Err(err) - } -} From 0aa5aad27d29f83afbe2fdc844313f5c8a02cc2b Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Sun, 2 Jun 2019 09:28:13 -0400 Subject: [PATCH 085/107] web: add vars interface --- plugins/admin/admin.go | 67 +++++++++++++++++++++++++++++++--------- plugins/admin/index.go | 69 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 14 deletions(-) create mode 100644 plugins/admin/index.go diff --git a/plugins/admin/admin.go b/plugins/admin/admin.go index a050e97..9b7f088 100644 --- a/plugins/admin/admin.go +++ b/plugins/admin/admin.go @@ -3,7 +3,9 @@ package admin import ( + "encoding/json" "fmt" + "net/http" "strings" "time" @@ -18,7 +20,7 @@ import ( // This is a admin plugin to serve as an example and quick copy/paste for new plugins. type AdminPlugin struct { - Bot bot.Bot + bot bot.Bot db *sqlx.DB cfg *config.Config @@ -28,12 +30,13 @@ type AdminPlugin struct { // NewAdminPlugin creates a new AdminPlugin with the Plugin interface func New(b bot.Bot) *AdminPlugin { p := &AdminPlugin{ - Bot: b, + bot: b, db: b.DB(), cfg: b.Config(), } b.Register(p, bot.Message, p.message) b.Register(p, bot.Help, p.help) + p.registerWeb() return p } @@ -65,7 +68,7 @@ func (p *AdminPlugin) message(conn bot.Connector, k bot.Kind, message msg.Messag if strings.ToLower(body) == "shut up" { dur := time.Duration(p.cfg.GetInt("quietDuration", 5)) * time.Minute log.Info().Msgf("Going to sleep for %v, %v", dur, time.Now().Add(dur)) - p.Bot.Send(conn, bot.Message, message.Channel, "Okay. I'll be back later.") + p.bot.Send(conn, bot.Message, message.Channel, "Okay. I'll be back later.") p.quiet = true go func() { select { @@ -79,19 +82,19 @@ func (p *AdminPlugin) message(conn bot.Connector, k bot.Kind, message msg.Messag parts := strings.Split(body, " ") if parts[0] == "set" && len(parts) > 2 && forbiddenKeys[parts[1]] { - p.Bot.Send(conn, bot.Message, message.Channel, "You cannot access that key") + p.bot.Send(conn, bot.Message, message.Channel, "You cannot access that key") return true } else if parts[0] == "set" && len(parts) > 2 { p.cfg.Set(parts[1], strings.Join(parts[2:], " ")) - p.Bot.Send(conn, bot.Message, message.Channel, fmt.Sprintf("Set %s", parts[1])) + p.bot.Send(conn, bot.Message, message.Channel, fmt.Sprintf("Set %s", parts[1])) return true } if parts[0] == "get" && len(parts) == 2 && forbiddenKeys[parts[1]] { - p.Bot.Send(conn, bot.Message, message.Channel, "You cannot access that key") + p.bot.Send(conn, bot.Message, message.Channel, "You cannot access that key") return true } else if parts[0] == "get" && len(parts) == 2 { v := p.cfg.Get(parts[1], "") - p.Bot.Send(conn, bot.Message, message.Channel, fmt.Sprintf("%s: %s", parts[1], v)) + p.bot.Send(conn, bot.Message, message.Channel, fmt.Sprintf("%s: %s", parts[1], v)) return true } @@ -105,10 +108,10 @@ func (p *AdminPlugin) handleVariables(conn bot.Connector, message msg.Message) b _, err := p.db.Exec(`delete from variables where name=? and value=?`, variable, value) if err != nil { - p.Bot.Send(conn, bot.Message, message.Channel, "I'm broke and need attention in my variable creation code.") + p.bot.Send(conn, bot.Message, message.Channel, "I'm broke and need attention in my variable creation code.") log.Error().Err(err) } else { - p.Bot.Send(conn, bot.Message, message.Channel, "Removed.") + p.bot.Send(conn, bot.Message, message.Channel, "Removed.") } return true @@ -126,27 +129,63 @@ func (p *AdminPlugin) handleVariables(conn bot.Connector, message msg.Message) b row := p.db.QueryRow(`select count(*) from variables where value = ?`, variable, value) err := row.Scan(&count) if err != nil { - p.Bot.Send(conn, bot.Message, message.Channel, "I'm broke and need attention in my variable creation code.") + p.bot.Send(conn, bot.Message, message.Channel, "I'm broke and need attention in my variable creation code.") log.Error().Err(err) return true } if count > 0 { - p.Bot.Send(conn, bot.Message, message.Channel, "I've already got that one.") + p.bot.Send(conn, bot.Message, message.Channel, "I've already got that one.") } else { _, err := p.db.Exec(`INSERT INTO variables (name, value) VALUES (?, ?)`, variable, value) if err != nil { - p.Bot.Send(conn, bot.Message, message.Channel, "I'm broke and need attention in my variable creation code.") + p.bot.Send(conn, bot.Message, message.Channel, "I'm broke and need attention in my variable creation code.") log.Error().Err(err) return true } - p.Bot.Send(conn, bot.Message, message.Channel, "Added.") + p.bot.Send(conn, bot.Message, message.Channel, "Added.") } return true } // Help responds to help requests. Every plugin must implement a help function. func (p *AdminPlugin) help(conn bot.Connector, kind bot.Kind, m msg.Message, args ...interface{}) bool { - p.Bot.Send(conn, bot.Message, m.Channel, "This does super secret things that you're not allowed to know about.") + p.bot.Send(conn, bot.Message, m.Channel, "This does super secret things that you're not allowed to know about.") return true } + +func (p *AdminPlugin) registerWeb() { + http.HandleFunc("/vars/api", p.handleWebAPI) + http.HandleFunc("/vars", p.handleWeb) + p.bot.RegisterWeb("/vars", "Variables") +} + +func (p *AdminPlugin) handleWeb(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, varIndex) +} + +func (p *AdminPlugin) handleWebAPI(w http.ResponseWriter, r *http.Request) { + var configEntries []struct { + Key string `json:"key"` + Value string `json:"value"` + } + q := `select key, value from config` + err := p.db.Select(&configEntries, q) + if err != nil { + log.Error(). + Err(err). + Msg("Error getting config entries.") + w.WriteHeader(500) + fmt.Fprint(w, err) + return + } + for i, e := range configEntries { + if strings.Contains(e.Value, ";;") { + e.Value = strings.ReplaceAll(e.Value, ";;", ", ") + e.Value = fmt.Sprintf("[%s]", e.Value) + configEntries[i] = e + } + } + j, _ := json.Marshal(configEntries) + fmt.Fprintf(w, "%s", j) +} diff --git a/plugins/admin/index.go b/plugins/admin/index.go new file mode 100644 index 0000000..1b53964 --- /dev/null +++ b/plugins/admin/index.go @@ -0,0 +1,69 @@ +package admin + +var varIndex = ` + + + + + + + + + + + + + + + + Vars + + + +
+

Vars

+ + {{ err }} + + + + +
+ + + + +` From fe8763f78ad152ce51920a0e62c69decb6773034 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Thu, 6 Jun 2019 10:33:50 -0400 Subject: [PATCH 086/107] first: change to per channel And be sure not to pay attention to private/direct messages --- bot/msg/message.go | 1 + connectors/slackapp/slackApp.go | 7 ++ plugins/first/first.go | 151 ++++++++++++++++---------------- 3 files changed, 85 insertions(+), 74 deletions(-) diff --git a/bot/msg/message.go b/bot/msg/message.go index 78a40f0..9cb0731 100644 --- a/bot/msg/message.go +++ b/bot/msg/message.go @@ -14,6 +14,7 @@ type Messages []Message type Message struct { User *user.User Channel, Body string + IsIM bool Raw string Command bool Action bool diff --git a/connectors/slackapp/slackApp.go b/connectors/slackapp/slackApp.go index d45708a..f07a6fa 100644 --- a/connectors/slackapp/slackApp.go +++ b/connectors/slackapp/slackApp.go @@ -87,6 +87,11 @@ func (s *SlackApp) Serve() error { buf := new(bytes.Buffer) buf.ReadFrom(r.Body) body := buf.String() + var raw interface{} + json.Unmarshal(json.RawMessage(body), &raw) + log.Debug(). + Interface("raw", raw). + Msg("Slack event") eventsAPIEvent, e := slackevents.ParseEvent(json.RawMessage(body), slackevents.OptionVerifyToken(&slackevents.TokenComparator{VerificationToken: s.verification})) if e != nil { log.Error().Err(e) @@ -148,6 +153,7 @@ func (s *SlackApp) msgReceivd(msg *slackevents.MessageEvent) { } log.Debug(). + Interface("event", msg). Str("type", msg.SubType). Msg("accepting a message") @@ -389,6 +395,7 @@ func (s *SlackApp) buildMessage(m *slackevents.MessageEvent) msg.Message { Body: text, Raw: m.Text, Channel: m.Channel, + IsIM: m.ChannelType == "im", Command: isCmd, Action: isAction, Time: tstamp, diff --git a/plugins/first/first.go b/plugins/first/first.go index 7e26b6c..d078926 100644 --- a/plugins/first/first.go +++ b/plugins/first/first.go @@ -18,26 +18,27 @@ import ( // This is a first plugin to serve as an example and quick copy/paste for new plugins. type FirstPlugin struct { - First *FirstEntry - Bot bot.Bot - db *sqlx.DB + Bot bot.Bot + db *sqlx.DB } type FirstEntry struct { - id int64 - day time.Time - time time.Time - body string - nick string - saved bool + id int64 + day time.Time + time time.Time + channel string + body string + nick string + saved bool } // Insert or update the first entry func (fe *FirstEntry) save(db *sqlx.DB) error { - if _, err := db.Exec(`insert into first (day, time, body, nick) - values (?, ?, ?, ?)`, + if _, err := db.Exec(`insert into first (day, time, channel, body, nick) + values (?, ?, ?, ?, ?)`, fe.day.Unix(), fe.time.Unix(), + fe.channel, fe.body, fe.nick, ); err != nil { @@ -52,6 +53,7 @@ func New(b bot.Bot) *FirstPlugin { id integer primary key, day integer, time integer, + channel string, body string, nick string );`) @@ -64,24 +66,16 @@ func New(b bot.Bot) *FirstPlugin { log.Info().Msgf("First plugin initialized with day: %s", midnight(time.Now())) - first, err := getLastFirst(b.DB()) - if err != nil { - log.Fatal(). - Err(err). - Msg("Could not initialize first plugin") - } - fp := &FirstPlugin{ - Bot: b, - db: b.DB(), - First: first, + Bot: b, + db: b.DB(), } b.Register(fp, bot.Message, fp.message) b.Register(fp, bot.Help, fp.help) return fp } -func getLastFirst(db *sqlx.DB) (*FirstEntry, error) { +func getLastFirst(db *sqlx.DB, channel string) (*FirstEntry, error) { // Get last first entry var id sql.NullInt64 var day sql.NullInt64 @@ -91,8 +85,9 @@ func getLastFirst(db *sqlx.DB) (*FirstEntry, error) { err := db.QueryRow(`select id, max(day), time, body, nick from first + where channel = ? limit 1; - `).Scan( + `, channel).Scan( &id, &day, &timeEntered, @@ -110,12 +105,13 @@ func getLastFirst(db *sqlx.DB) (*FirstEntry, error) { log.Debug().Msgf("id: %v day %v time %v body %v nick %v", id, day, timeEntered, body, nick) return &FirstEntry{ - id: id.Int64, - day: time.Unix(day.Int64, 0), - time: time.Unix(timeEntered.Int64, 0), - body: body.String, - nick: nick.String, - saved: true, + 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, }, nil } @@ -124,7 +120,11 @@ func midnight(t time.Time) time.Time { return time.Date(y, m, d, 0, 0, 0, 0, time.UTC) } -func isToday(t time.Time) bool { +func isNotToday(f *FirstEntry) bool { + if f == nil { + return true + } + t := f.time t0 := midnight(t) return t0.Before(midnight(time.Now())) } @@ -133,31 +133,41 @@ func isToday(t time.Time) bool { // This function returns true if the plugin responds in a meaningful way to the users message. // Otherwise, the function returns false and the bot continues execution of other plugins. func (p *FirstPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { - // This bot does not reply to anything + log.Debug(). + Interface("msg", message). + Msg("First is looking at a message") - if p.First == nil && p.allowed(message) { - log.Debug(). - Str("body", message.Body). - Msg("No previous first. Recording new first") - p.recordFirst(c, message) + if message.IsIM { + log.Debug().Msg("Skipping IM") return false - } else if p.First != nil { - if isToday(p.First.time) && p.allowed(message) { - log.Debug(). - Str("body", message.Body). - Time("t0", p.First.time). - Time("t1", time.Now()). - Msg("Recording first") - p.recordFirst(c, message) - return false - } } - r := strings.NewReplacer("'", "", "\"", "", ",", "", ".", "", ":", "", + first, err := getLastFirst(p.db, message.Channel) + if err != nil { + log.Error(). + Err(err). + Msg("Error getting last first") + } + + log.Debug().Bool("first == nil", first == nil).Msg("Is first nil?") + log.Debug().Bool("first == nil || isNotToday()", isNotToday(first)).Msg("Is it today?") + log.Debug().Bool("p.allowed", p.allowed(message)).Msg("Allowed?") + + if (first == nil || isNotToday(first)) && p.allowed(message) { + log.Debug(). + Str("body", message.Body). + Interface("t0", first). + Time("t1", time.Now()). + Msg("Recording first") + p.recordFirst(c, message) + return false + } + + r := strings.NewReplacer("’", "", "'", "", "\"", "", ",", "", ".", "", ":", "", "?", "", "!", "") - msg := strings.ToLower(message.Body) - if r.Replace(msg) == "whos on first" { - p.announceFirst(c, message) + m := strings.ToLower(message.Body) + if r.Replace(m) == "whos on first" && first != nil { + p.announceFirst(c, first) return true } @@ -165,8 +175,8 @@ func (p *FirstPlugin) message(c bot.Connector, kind bot.Kind, message msg.Messag } func (p *FirstPlugin) allowed(message msg.Message) bool { - for _, msg := range p.Bot.Config().GetArray("Bad.Msgs", []string{}) { - match, err := regexp.MatchString(msg, strings.ToLower(message.Body)) + for _, m := range p.Bot.Config().GetArray("Bad.Msgs", []string{}) { + match, err := regexp.MatchString(m, strings.ToLower(message.Body)) if err != nil { log.Error().Err(err).Msg("Bad regexp") } @@ -201,41 +211,34 @@ func (p *FirstPlugin) allowed(message msg.Message) bool { func (p *FirstPlugin) recordFirst(c bot.Connector, message msg.Message) { log.Info(). + Str("channel", message.Channel). Str("user", message.User.Name). Str("body", message.Body). Msg("Recording first") - p.First = &FirstEntry{ - day: midnight(time.Now()), - time: message.Time, - body: message.Body, - nick: message.User.Name, + first := &FirstEntry{ + day: midnight(time.Now()), + time: message.Time, + channel: message.Channel, + body: message.Body, + nick: message.User.Name, } - log.Info().Msgf("recordFirst: %+v", p.First.day) - err := p.First.save(p.db) + log.Info().Msgf("recordFirst: %+v", first.day) + err := first.save(p.db) if err != nil { log.Error().Err(err).Msg("Error saving first entry") return } - p.announceFirst(c, message) + p.announceFirst(c, first) } -func (p *FirstPlugin) announceFirst(c bot.Connector, message msg.Message) { - ch := message.Channel - if p.First != nil { - p.Bot.Send(c, bot.Message, ch, fmt.Sprintf("%s had first at %s with the message: \"%s\"", - p.First.nick, p.First.time.Format("15:04"), p.First.body)) - } -} - -// LoadData imports any configuration data into the plugin. This is not strictly necessary other -// than the fact that the Plugin interface demands it exist. This may be deprecated at a later -// date. -func (p *FirstPlugin) LoadData() { - // This bot has no data to load +func (p *FirstPlugin) announceFirst(c bot.Connector, first *FirstEntry) { + ch := first.channel + 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. func (p *FirstPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { - p.Bot.Send(c, bot.Message, message.Channel, "Sorry, First does not do a goddamn thing.") + p.Bot.Send(c, bot.Message, message.Channel, "You can ask 'who's on first?' to find out.") return true } From 3d51d33da9e3b8d243e8f3b644fa307698065ae7 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Fri, 7 Jun 2019 12:19:24 -0400 Subject: [PATCH 087/107] slackApp: add logging function --- .gitignore | 1 + bot/msg/message.go | 10 ++- connectors/slackapp/fix_text.go | 7 +- connectors/slackapp/slackApp.go | 134 ++++++++++++++++++++++++++------ 4 files changed, 121 insertions(+), 31 deletions(-) diff --git a/.gitignore b/.gitignore index 6a15fae..fcf0e95 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,4 @@ util/*/files util/*/files run.sh .idea +logs diff --git a/bot/msg/message.go b/bot/msg/message.go index 9cb0731..6d3712c 100644 --- a/bot/msg/message.go +++ b/bot/msg/message.go @@ -12,10 +12,14 @@ type Log Messages type Messages []Message type Message struct { - User *user.User - Channel, Body string + User *user.User + // With Slack, channel is the ID of a channel + Channel string + // With slack, channelName is the nice name of a channel + ChannelName string + Body string IsIM bool - Raw string + Raw interface{} Command bool Action bool Time time.Time diff --git a/connectors/slackapp/fix_text.go b/connectors/slackapp/fix_text.go index 8a23a53..a4a7c2f 100644 --- a/connectors/slackapp/fix_text.go +++ b/connectors/slackapp/fix_text.go @@ -1,6 +1,7 @@ package slackapp import ( + "github.com/nlopes/slack" "unicode/utf8" ) @@ -13,7 +14,7 @@ import ( // • Strips < and > surrounding links. // // This was directly bogarted from velour/chat with emoji conversion removed. -func fixText(findUser func(id string) (string, error), text string) string { +func fixText(findUser func(id string) (*slack.User, error), text string) string { var output []rune for len(text) > 0 { r, i := utf8.DecodeRuneInString(text) @@ -48,7 +49,7 @@ func fixText(findUser func(id string) (string, error), text string) string { return string(output) } -func fixTag(findUser func(string) (string, error), tag []rune) ([]rune, bool) { +func fixTag(findUser func(string) (*slack.User, error), tag []rune) ([]rune, bool) { switch { case hasPrefix(tag, "@U"): if i := indexRune(tag, '|'); i >= 0 { @@ -56,7 +57,7 @@ func fixTag(findUser func(string) (string, error), tag []rune) ([]rune, bool) { } if findUser != nil { if u, err := findUser(string(tag[1:])); err == nil { - return []rune(u), true + return []rune(u.Name), true } } return tag, true diff --git a/connectors/slackapp/slackApp.go b/connectors/slackapp/slackApp.go index f07a6fa..f1d512e 100644 --- a/connectors/slackapp/slackApp.go +++ b/connectors/slackapp/slackApp.go @@ -9,9 +9,12 @@ import ( "io/ioutil" "net/http" "net/url" + "os" + "path" "regexp" "strconv" "strings" + "text/template" "time" "github.com/rs/zerolog/log" @@ -25,7 +28,15 @@ import ( "github.com/velour/catbase/config" ) -const DEFAULT_RING = 5 +const DefaultRing = 5 +const defaultLogFormat = "[{{fixDate .Time \"2006-01-02 15:04:05\"}}] {{if .Action}}* {{.User.Name}}{{else}}<{{.User.Name}}>{{end}} {{.Body}}\n" + +// 11:10AM DBG connectors/slackapp/slackApp.go:496 > Slack event dir=logs raw={"Action":false,"AdditionalData": +// {"RAW_SLACK_TIMESTAMP":"1559920235.001100"},"Body":"aoeu","Channel":"C0S04SMRC","ChannelName":"test", +// "Command":false,"Host":"","IsIM":false,"Raw":{"channel":"C0S04SMRC","channel_type":"channel", +// "event_ts":1559920235.001100,"files":null,"text":"aoeu","thread_ts":"","ts":"1559920235.001100", +// "type":"message","upload":false,"user":"U0RLUDELD"},"Time":"2019-06-07T11:10:35.0000011-04:00", +// "User":{"Admin":false,"ID":"U0RLUDELD","Name":"flyngpngn"}} type SlackApp struct { bot bot.Bot @@ -39,15 +50,21 @@ type SlackApp struct { lastRecieved time.Time - myBotID string - users map[string]string - emoji map[string]string + myBotID string + users map[string]*slack.User + emoji map[string]string + channels map[string]*slack.Channel event bot.Callback msgIDBuffer *ring.Ring + + logFormat *template.Template } +func fixDate(input time.Time, format string) string { + return input.Format(format) +} func New(c *config.Config) *SlackApp { token := c.Get("slack.token", "NONE") if token == "NONE" { @@ -56,12 +73,18 @@ func New(c *config.Config) *SlackApp { api := slack.New(token, slack.OptionDebug(false)) - idBuf := ring.New(c.GetInt("ringSize", DEFAULT_RING)) + idBuf := ring.New(c.GetInt("ringSize", DefaultRing)) for i := 0; i < idBuf.Len(); i++ { idBuf.Value = "" idBuf = idBuf.Next() } + tplTxt := c.GetString("slackapp.log.format", defaultLogFormat) + funcs := template.FuncMap{ + "fixDate": fixDate, + } + tpl := template.Must(template.New("log").Funcs(funcs).Parse(tplTxt)) + return &SlackApp{ api: api, config: c, @@ -70,9 +93,11 @@ func New(c *config.Config) *SlackApp { verification: c.Get("slack.verification", "NONE"), myBotID: c.Get("slack.botid", ""), lastRecieved: time.Now(), - users: make(map[string]string), + users: make(map[string]*slack.User), emoji: make(map[string]string), + channels: make(map[string]*slack.Channel), msgIDBuffer: idBuf, + logFormat: tpl, } } @@ -87,11 +112,6 @@ func (s *SlackApp) Serve() error { buf := new(bytes.Buffer) buf.ReadFrom(r.Body) body := buf.String() - var raw interface{} - json.Unmarshal(json.RawMessage(body), &raw) - log.Debug(). - Interface("raw", raw). - Msg("Slack event") eventsAPIEvent, e := slackevents.ParseEvent(json.RawMessage(body), slackevents.OptionVerifyToken(&slackevents.TokenComparator{VerificationToken: s.verification})) if e != nil { log.Error().Err(e) @@ -163,9 +183,13 @@ func (s *SlackApp) msgReceivd(msg *slackevents.MessageEvent) { Msg("Got a duplicate message from server") return } + isItMe := msg.BotID != "" && msg.BotID == s.myBotID if !isItMe && msg.ThreadTimeStamp == "" { m := s.buildMessage(msg) + if err := s.log(m); err != nil { + log.Fatal().Err(err).Msg("Error logging message") + } if m.Time.Before(s.lastRecieved) { log.Debug(). Time("ts", m.Time). @@ -380,33 +404,57 @@ func (s *SlackApp) buildMessage(m *slackevents.MessageEvent) msg.Message { isAction := m.SubType == "me_message" + // We have to try a few layers to get a valid name for the user because Slack + name := "UNKNOWN" u, _ := s.getUser(m.User) - if m.Username != "" { - u = m.Username + if u != nil { + name = u.Name } + if m.Username != "" && u == nil { + name = m.Username + } + ch, _ := s.getChannel(m.Channel) tstamp := slackTStoTime(m.TimeStamp) return msg.Message{ User: &user.User{ ID: m.User, - Name: u, + Name: name, }, - Body: text, - Raw: m.Text, - Channel: m.Channel, - IsIM: m.ChannelType == "im", - Command: isCmd, - Action: isAction, - Time: tstamp, + Body: text, + Raw: m, + Channel: m.Channel, + ChannelName: ch.Name, + IsIM: m.ChannelType == "im", + Command: isCmd, + Action: isAction, + Time: tstamp, AdditionalData: map[string]string{ "RAW_SLACK_TIMESTAMP": m.TimeStamp, }, } } +func (s *SlackApp) getChannel(id string) (*slack.Channel, error) { + if ch, ok := s.channels[id]; ok { + return ch, nil + } + + log.Debug(). + Str("id", id). + Msg("Channel not known, requesting info") + + ch, err := s.api.GetChannelInfo(id) + if err != nil { + return nil, err + } + s.channels[id] = ch + return s.channels[id], nil +} + // Get username for Slack user ID -func (s *SlackApp) getUser(id string) (string, error) { +func (s *SlackApp) getUser(id string) (*slack.User, error) { if name, ok := s.users[id]; ok { return name, nil } @@ -416,9 +464,9 @@ func (s *SlackApp) getUser(id string) (string, error) { Msg("User not already found, requesting info") u, err := s.api.GetUserInfo(id) if err != nil { - return "UNKNOWN", err + return nil, err } - s.users[id] = u.Name + s.users[id] = u return s.users[id], nil } @@ -453,8 +501,44 @@ func (s *SlackApp) Who(id string) []string { Str("user", m). Msg("Couldn't get user") continue + /**/ } - ret = append(ret, u) + ret = append(ret, u.Name) } return ret } + +// log writes to a /.log +// Uses slackapp.log.format to write entries +func (s *SlackApp) log(raw msg.Message) error { + dir := path.Join(s.config.Get("slackapp.log.dir", "logs"), raw.ChannelName) + now := time.Now() + fname := now.Format("20060102") + ".log" + path := path.Join(dir, fname) + + log.Debug(). + Interface("raw", raw). + Str("dir", dir). + Msg("Slack event") + + if err := os.MkdirAll(dir, 0755); err != nil { + log.Error(). + Err(err). + Msg("Could not create log directory") + return err + } + + f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + log.Fatal(). + Err(err). + Msg("Error opening log file") + } + defer f.Close() + + if err := s.logFormat.Execute(f, raw); err != nil { + return err + } + + return f.Sync() +} From 7d159db6f149e90245151ad3bb7f29b1698be804 Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Fri, 7 Jun 2019 14:23:14 -0400 Subject: [PATCH 088/107] slackApp: nicen logs a bit * Remove entry log lines * Change topic format for pisg support --- connectors/slackapp/slackApp.go | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/connectors/slackapp/slackApp.go b/connectors/slackapp/slackApp.go index f1d512e..ee187f5 100644 --- a/connectors/slackapp/slackApp.go +++ b/connectors/slackapp/slackApp.go @@ -29,7 +29,7 @@ import ( ) const DefaultRing = 5 -const defaultLogFormat = "[{{fixDate .Time \"2006-01-02 15:04:05\"}}] {{if .Action}}* {{.User.Name}}{{else}}<{{.User.Name}}>{{end}} {{.Body}}\n" +const defaultLogFormat = "[{{fixDate .Time \"2006-01-02 15:04:05\"}}] {{if .TopicChange}}*** {{.User.Name}}{{else if .Action}}* {{.User.Name}}{{else}}<{{.User.Name}}>{{end}} {{.Body}}\n" // 11:10AM DBG connectors/slackapp/slackApp.go:496 > Slack event dir=logs raw={"Action":false,"AdditionalData": // {"RAW_SLACK_TIMESTAMP":"1559920235.001100"},"Body":"aoeu","Channel":"C0S04SMRC","ChannelName":"test", @@ -187,15 +187,15 @@ func (s *SlackApp) msgReceivd(msg *slackevents.MessageEvent) { isItMe := msg.BotID != "" && msg.BotID == s.myBotID if !isItMe && msg.ThreadTimeStamp == "" { m := s.buildMessage(msg) - if err := s.log(m); err != nil { - log.Fatal().Err(err).Msg("Error logging message") - } if m.Time.Before(s.lastRecieved) { log.Debug(). Time("ts", m.Time). Interface("lastRecv", s.lastRecieved). Msg("Ignoring message") } else { + if err := s.log(m); err != nil { + log.Fatal().Err(err).Msg("Error logging message") + } s.lastRecieved = m.Time s.event(s, bot.Message, m) } @@ -511,6 +511,25 @@ func (s *SlackApp) Who(id string) []string { // log writes to a /.log // Uses slackapp.log.format to write entries func (s *SlackApp) log(raw msg.Message) error { + + // Do some filtering and fixing up front + if raw.Body == "" { + return nil + } + + data := struct { + msg.Message + TopicChange bool + }{ + Message: raw, + } + + if strings.Contains(raw.Body, "set the channel topic: ") { + topic := strings.SplitN(raw.Body, "set the channel topic: ", 2) + data.Body = "changed topic to " + topic[1] + data.TopicChange = true + } + dir := path.Join(s.config.Get("slackapp.log.dir", "logs"), raw.ChannelName) now := time.Now() fname := now.Format("20060102") + ".log" @@ -536,7 +555,7 @@ func (s *SlackApp) log(raw msg.Message) error { } defer f.Close() - if err := s.logFormat.Execute(f, raw); err != nil { + if err := s.logFormat.Execute(f, data); err != nil { return err } From d372541735c6eee12c11838d338e59f7e8137c0e Mon Sep 17 00:00:00 2001 From: skiesel Date: Fri, 7 Jun 2019 14:32:20 -0400 Subject: [PATCH 089/107] stock checker --- main.go | 2 + plugins/stock/stock.go | 94 +++++++++++++++++++++++++++++++++++++ plugins/stock/stock_test.go | 45 ++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 plugins/stock/stock.go create mode 100644 plugins/stock/stock_test.go diff --git a/main.go b/main.go index 6837a05..8a1fdae 100644 --- a/main.go +++ b/main.go @@ -37,6 +37,7 @@ import ( "github.com/velour/catbase/plugins/rpgORdie" "github.com/velour/catbase/plugins/rss" "github.com/velour/catbase/plugins/sisyphus" + "github.com/velour/catbase/plugins/stock" "github.com/velour/catbase/plugins/talker" "github.com/velour/catbase/plugins/tell" "github.com/velour/catbase/plugins/tldr" @@ -122,6 +123,7 @@ func main() { b.AddPlugin(couldashouldawoulda.New(b)) b.AddPlugin(nerdepedia.New(b)) b.AddPlugin(tldr.New(b)) + b.AddPlugin(stock.New(b)) b.AddPlugin(cli.New(b)) // catches anything left, will always return true b.AddPlugin(fact.New(b)) diff --git a/plugins/stock/stock.go b/plugins/stock/stock.go new file mode 100644 index 0000000..e85fdab --- /dev/null +++ b/plugins/stock/stock.go @@ -0,0 +1,94 @@ +package stock + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "regexp" + "strings" + + "github.com/rs/zerolog/log" + + "github.com/velour/catbase/bot" + "github.com/velour/catbase/bot/msg" +) + +type StockPlugin struct { + bot bot.Bot + apiKey string +} + +func New(b bot.Bot) *StockPlugin { + s := &StockPlugin{ + bot: b, + apiKey: b.Config().GetString("Stock.API_KEY", "0E1DP61SJ7GF81IE"), + } + b.Register(s, bot.Message, s.message) + b.Register(s, bot.Help, s.help) + return s +} + +type GlobalQuote struct { + Info StockInfo `json:"GlobalQuote"` +} + +type StockInfo struct { + Symbol string `json:"symbol"` + Open string `json:"open"` + High string `json:"high"` + Low string `json:"low"` + Price string `json:"price"` + Volume string `json:"volume"` + LatestTradingDay string `json:"latesttradingday"` + PreviousClose string `json:"previousclose"` + Change string `json:"change"` + ChangePercent string `json:"changepercent"` +} + +func (p *StockPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { + if !message.Command { + return false + } + + tokens := strings.Fields(message.Body) + numTokens := len(tokens) + + if numTokens == 2 && strings.ToLower(tokens[0]) == "stock-price" { + query := fmt.Sprintf("https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=%s&apikey=%s", tokens[1], p.apiKey) + + resp, err := http.Get(query) + if err != nil { + log.Fatal().Err(err).Msg("Failed to get stock info") + } + body, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + log.Fatal().Err(err).Msg("Error stock info body") + } + + response := "Failed to retrieve data for stock symbol: " + tokens[1] + + cleaned := strings.ReplaceAll(string(body), " ", "") + regex := regexp.MustCompile("[0-9][0-9]\\.") + + cleaned = regex.ReplaceAllString(cleaned, "") + + var info GlobalQuote + err = json.Unmarshal([]byte(cleaned), &info) + + if err == nil && strings.EqualFold(tokens[1], info.Info.Symbol) { + response = fmt.Sprintf("%s : $%s (%s)", tokens[1], info.Info.Price, info.Info.ChangePercent) + } + + p.bot.Send(c, bot.Message, message.Channel, response) + return true + } + + return false +} + +func (p *StockPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { + p.bot.Send(c, bot.Message, message.Channel, "try '!stock-price SYMBOL'") + return true +} diff --git a/plugins/stock/stock_test.go b/plugins/stock/stock_test.go new file mode 100644 index 0000000..914f6d2 --- /dev/null +++ b/plugins/stock/stock_test.go @@ -0,0 +1,45 @@ +package stock + +import ( + "github.com/velour/catbase/plugins/cli" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/velour/catbase/bot" + "github.com/velour/catbase/bot/msg" + "github.com/velour/catbase/bot/user" +) + +func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) { + isCmd := strings.HasPrefix(payload, "!") + if isCmd { + payload = payload[1:] + } + return &cli.CliPlugin{}, bot.Message, msg.Message{ + User: &user.User{Name: "tester"}, + Channel: "test", + Body: payload, + Command: isCmd, + } +} + +func TestValid(t *testing.T) { + mb := bot.NewMockBot() + c := New(mb) + assert.NotNil(t, c) + res := c.message(makeMessage("!stock-price TWTR")) + assert.Len(t, mb.Messages, 1) + assert.True(t, res) + assert.Contains(t, mb.Messages[0], "TWTR : $") +} + +func TestInvalid(t *testing.T) { + mb := bot.NewMockBot() + c := New(mb) + assert.NotNil(t, c) + res := c.message(makeMessage("!stock-price NOTREAL")) + assert.Len(t, mb.Messages, 1) + assert.True(t, res) + assert.Contains(t, mb.Messages[0], "Failed to retrieve data for stock symbol: NOTREAL") +} From 74b32dd2e99686dc7e85a8f25301ac484cde09ca Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Fri, 7 Jun 2019 16:49:18 -0400 Subject: [PATCH 090/107] slackApp: a few fixes... * DMs work again and are logged * bot knows who itself is --- connectors/slackapp/slackApp.go | 40 +++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/connectors/slackapp/slackApp.go b/connectors/slackapp/slackApp.go index ee187f5..07b9a8d 100644 --- a/connectors/slackapp/slackApp.go +++ b/connectors/slackapp/slackApp.go @@ -185,27 +185,29 @@ func (s *SlackApp) msgReceivd(msg *slackevents.MessageEvent) { } isItMe := msg.BotID != "" && msg.BotID == s.myBotID + m := s.buildMessage(msg) + if m.Time.Before(s.lastRecieved) { + log.Debug(). + Time("ts", m.Time). + Interface("lastRecv", s.lastRecieved). + Msg("Ignoring message") + return + } + if err := s.log(m); err != nil { + log.Fatal().Err(err).Msg("Error logging message") + } if !isItMe && msg.ThreadTimeStamp == "" { - m := s.buildMessage(msg) - if m.Time.Before(s.lastRecieved) { - log.Debug(). - Time("ts", m.Time). - Interface("lastRecv", s.lastRecieved). - Msg("Ignoring message") - } else { - if err := s.log(m); err != nil { - log.Fatal().Err(err).Msg("Error logging message") - } - s.lastRecieved = m.Time - s.event(s, bot.Message, m) - } + s.lastRecieved = m.Time + s.event(s, bot.Message, m) } else if msg.ThreadTimeStamp != "" { //we're throwing away some information here by not parsing the correct reply object type, but that's okay s.event(s, bot.Reply, s.buildMessage(msg), msg.ThreadTimeStamp) + } else if isItMe { + s.event(s, bot.SelfMessage, m) } else { log.Debug(). Str("text", msg.Text). - Msg("THAT MESSAGE WAS HIDDEN") + Msg("Unknown message is hidden") } } @@ -408,12 +410,16 @@ func (s *SlackApp) buildMessage(m *slackevents.MessageEvent) msg.Message { name := "UNKNOWN" u, _ := s.getUser(m.User) if u != nil { - name = u.Name + name = u.Profile.DisplayName } if m.Username != "" && u == nil { name = m.Username } - ch, _ := s.getChannel(m.Channel) + + chName := m.Channel + if ch, _ := s.getChannel(m.Channel); ch != nil { + chName = ch.Name + } tstamp := slackTStoTime(m.TimeStamp) @@ -425,7 +431,7 @@ func (s *SlackApp) buildMessage(m *slackevents.MessageEvent) msg.Message { Body: text, Raw: m, Channel: m.Channel, - ChannelName: ch.Name, + ChannelName: chName, IsIM: m.ChannelType == "im", Command: isCmd, Action: isAction, From 9ea45f0ad3d318ca50c364706791dd87cbfa6fee Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Sun, 9 Jun 2019 00:15:06 -0400 Subject: [PATCH 091/107] web: add a menu and redo index We can add arbitrary links now with the `bot.links` config --- bot/bot.go | 51 ++++--------------------- bot/interfaces.go | 1 + bot/mock.go | 1 + bot/web.go | 73 ++++++++++++++++++++++++++++++++++++ plugins/admin/admin.go | 5 ++- plugins/admin/index.go | 12 ++++-- plugins/cli/cli.go | 5 ++- plugins/cli/index.go | 12 ++++-- plugins/counter/counter.go | 7 ++-- plugins/counter/html.go | 20 ++++++---- plugins/fact/factoid.go | 4 +- plugins/fact/webTemplates.go | 10 ++++- 12 files changed, 136 insertions(+), 65 deletions(-) create mode 100644 bot/web.go diff --git a/bot/bot.go b/bot/bot.go index 8674a34..c313457 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -3,7 +3,6 @@ package bot import ( - "html/template" "net/http" "reflect" "strings" @@ -38,7 +37,7 @@ type bot struct { version string // The entries to the bot's HTTP interface - httpEndPoints map[string]string + httpEndPoints []EndPoint // filters registered by plugins filters map[string]func(string) string @@ -46,6 +45,10 @@ type bot struct { callbacks CallbackMap } +type EndPoint struct { + Name, URL string +} + // Variable represents a $var replacement type Variable struct { Variable, Value string @@ -73,7 +76,7 @@ func New(config *config.Config, connector Connector) Bot { me: users[0], logIn: logIn, logOut: logOut, - httpEndPoints: make(map[string]string), + httpEndPoints: make([]EndPoint, 0), filters: make(map[string]func(string) string), callbacks: make(CallbackMap), } @@ -133,46 +136,6 @@ func (b *bot) Who(channel string) []user.User { return users } -var rootIndex = ` - - - - Factoids - - - - {{if .EndPoints}} -
- - - - - - - - - {{range $key, $value := .EndPoints}} - - - - {{end}} - -
Plugin
{{$key}}
-
- {{end}} - -` - -func (b *bot) serveRoot(w http.ResponseWriter, r *http.Request) { - context := make(map[string]interface{}) - context["EndPoints"] = b.httpEndPoints - t, err := template.New("rootIndex").Parse(rootIndex) - if err != nil { - log.Error().Err(err) - } - t.Execute(w, context) -} - // IsCmd checks if message is a command and returns its curtailed version func IsCmd(c *config.Config, message string) (bool, string) { cmdcs := c.GetArray("CommandChar", []string{"!"}) @@ -266,5 +229,5 @@ func (b *bot) Register(p Plugin, kind Kind, cb Callback) { } func (b *bot) RegisterWeb(root, name string) { - b.httpEndPoints[name] = root + b.httpEndPoints = append(b.httpEndPoints, EndPoint{name, root}) } diff --git a/bot/interfaces.go b/bot/interfaces.go index fba9396..d9d10d3 100644 --- a/bot/interfaces.go +++ b/bot/interfaces.go @@ -66,6 +66,7 @@ type Bot interface { RegisterFilter(string, func(string) string) RegisterWeb(string, string) DefaultConnector() Connector + GetWebNavigation() []EndPoint } // Connector represents a server connection to a chat service diff --git a/bot/mock.go b/bot/mock.go index 6f67c62..3511cf1 100644 --- a/bot/mock.go +++ b/bot/mock.go @@ -52,6 +52,7 @@ func (mb *MockBot) Send(c Connector, kind Kind, args ...interface{}) (string, er func (mb *MockBot) AddPlugin(f Plugin) {} func (mb *MockBot) Register(p Plugin, kind Kind, cb Callback) {} func (mb *MockBot) RegisterWeb(_, _ string) {} +func (mb *MockBot) GetWebNavigation() []EndPoint { return nil } func (mb *MockBot) Receive(c Connector, kind Kind, msg msg.Message, args ...interface{}) bool { return false } diff --git a/bot/web.go b/bot/web.go new file mode 100644 index 0000000..bbe62ec --- /dev/null +++ b/bot/web.go @@ -0,0 +1,73 @@ +package bot + +import ( + "html/template" + "net/http" + "strings" +) + +func (b *bot) serveRoot(w http.ResponseWriter, r *http.Request) { + context := make(map[string]interface{}) + context["Nav"] = b.GetWebNavigation() + t := template.Must(template.New("rootIndex").Parse(rootIndex)) + t.Execute(w, context) +} + +// GetWebNavigation returns a list of bootstrap-vue links +// The parent