From a8d0f3fd3458915bc72c1a2d68a0df740ae33b5a Mon Sep 17 00:00:00 2001 From: Chris Sexton Date: Sun, 20 Jan 2019 15:21:26 -0500 Subject: [PATCH 1/3] 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 2/3] 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 3/3] 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")},