diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 58ca2fc..8213f5d 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -10,7 +10,7 @@ jobs: - name: Set up Go 1.16 uses: actions/setup-go@v2 with: - go-version: '^1.18' + go-version: '^1.17' id: go - name: Check out code into the Go module directory diff --git a/bot/bot.go b/bot/bot.go index baeadfa..61c6fc1 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -308,18 +308,18 @@ func (b *bot) SetQuiet(status bool) { b.quiet = status } -type blacklistItem struct { +type BlacklistItem struct { Channel string Name string } -type whitelistItem struct { +type WhitelistItem struct { Name string } // RefreshPluginBlacklist loads data for which plugins are disabled for particular channels func (b *bot) RefreshPluginBlacklist() error { - blacklistItems := []blacklistItem{} + blacklistItems := []BlacklistItem{} if err := b.Store().Find(&blacklistItems, &bh.Query{}); err != nil { return fmt.Errorf("%w", err) } @@ -333,7 +333,7 @@ func (b *bot) RefreshPluginBlacklist() error { // RefreshPluginWhitelist loads data for which plugins are enabled func (b *bot) RefreshPluginWhitelist() error { - whitelistItems := []whitelistItem{ + whitelistItems := []WhitelistItem{ {Name: "admin"}, // we must always ensure admin is on! } if err := b.Store().Find(&whitelistItems, &bh.Query{}); err != nil { diff --git a/bot/mock.go b/bot/mock.go index c2b0a59..38179a8 100644 --- a/bot/mock.go +++ b/bot/mock.go @@ -4,12 +4,14 @@ package bot import ( "fmt" + bh "github.com/timshannon/bolthold" + "io/ioutil" "net/http" + "os" "regexp" "strconv" "strings" - "github.com/jmoiron/sqlx" "github.com/rs/zerolog/log" "github.com/stretchr/testify/mock" "github.com/velour/catbase/bot/msg" @@ -19,17 +21,19 @@ import ( type MockBot struct { mock.Mock - db *sqlx.DB + store *bh.Store Cfg *config.Config Messages []string Actions []string Reactions []string + + storeFile *os.File } func (mb *MockBot) Config() *config.Config { return mb.Cfg } -func (mb *MockBot) DB() *sqlx.DB { return mb.Cfg.DB() } +func (mb *MockBot) Store() *bh.Store { return mb.Cfg.Store() } func (mb *MockBot) Who(string) []user.User { return []user.User{} } func (mb *MockBot) WhoAmI() string { return "tester" } func (mb *MockBot) DefaultConnector() Connector { return nil } @@ -107,11 +111,17 @@ 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("file::memory:?mode=memory&cache=shared") + storeFile, err := ioutil.TempFile(os.TempDir(), "prefix-") + if err != nil { + panic(err) + } + cfg := config.ReadConfig("file::memory:?mode=memory&cache=shared", storeFile.Name()) b := MockBot{ - Cfg: cfg, - Messages: make([]string, 0), - Actions: make([]string, 0), + Cfg: cfg, + Messages: make([]string, 0), + Actions: make([]string, 0), + storeFile: storeFile, + store: cfg.Store(), } // If any plugin registered a route, we need to reset those before any new test http.DefaultServeMux = new(http.ServeMux) @@ -127,3 +137,7 @@ func (mb *MockBot) URLFormat(title, url string) string { return title func (mb *MockBot) CheckPassword(secret, password string) bool { return true } func (mb *MockBot) ListenAndServe() {} func (mb *MockBot) PubToASub(subject string, payload interface{}) {} +func (mb *MockBot) TearDown() error { + mb.store.Close() + return os.Remove(mb.storeFile.Name()) +} diff --git a/config/config.go b/config/config.go index d45e46e..745cf8c 100644 --- a/config/config.go +++ b/config/config.go @@ -31,9 +31,9 @@ type Config struct { // Value is a config value that is loaded permanently and not ever displayed type Value struct { // Key is the key field of the table - Key string `db:"key"` + Key string `db:"key" json:"key"` // Value represents the secret that must not be shared - Value string `db:"value"` + Value string `db:"value" json:"value"` } // Secret is a separate type (for storage differentiation) @@ -159,7 +159,7 @@ func (c *Config) Set(key, value string) error { key = strings.ToLower(key) value = strings.Trim(value, "`") - err := c.store.Update(key, Value{key, value}) + err := c.store.Upsert(key, Value{key, value}) return err } @@ -215,12 +215,12 @@ func (c *Config) SetArray(key string, values []string) error { //} // Readconfig loads the config data out of a JSON file located in cfile -func ReadConfig(dbpath string) *Config { +func ReadConfig(dbpath, storepath string) *Config { if dbpath == "" { dbpath = "catbase.db" } - store, err := bh.Open(strings.ReplaceAll(dbpath, ".db", ".store"), 0666, nil) + store, err := bh.Open(storepath, 0666, nil) if err != nil { log.Fatal().Err(err).Msgf("could not open bolthold") } diff --git a/main.go b/main.go index 5b30dae..ba2ca6d 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "io" "math/rand" "os" + "strings" "time" "github.com/velour/catbase/bot/msg" @@ -46,7 +47,6 @@ import ( "github.com/velour/catbase/plugins/fact" "github.com/velour/catbase/plugins/first" "github.com/velour/catbase/plugins/git" - "github.com/velour/catbase/plugins/impossible" "github.com/velour/catbase/plugins/inventory" "github.com/velour/catbase/plugins/leftpad" "github.com/velour/catbase/plugins/nerdepedia" @@ -92,7 +92,7 @@ func main() { zerolog.SetGlobalLevel(zerolog.DebugLevel) } - c := config.ReadConfig(*dbpath) + c := config.ReadConfig(*dbpath, strings.ReplaceAll(*dbpath, ".db", ".store")) if *key != "" && *val != "" { c.Set(*key, *val) @@ -159,7 +159,7 @@ func main() { b.AddPlugin(newsbid.New(b)) b.AddPlugin(twitter.New(b)) b.AddPlugin(git.New(b)) - b.AddPlugin(impossible.New(b)) + //b.AddPlugin(impossible.New(b)) b.AddPlugin(cli.New(b)) b.AddPlugin(aoc.New(b)) b.AddPlugin(meme.New(b)) diff --git a/plugins/admin/admin.go b/plugins/admin/admin.go index 78fe744..c1f4989 100644 --- a/plugins/admin/admin.go +++ b/plugins/admin/admin.go @@ -349,49 +349,33 @@ func (p *AdminPlugin) help(conn bot.Connector, kind bot.Kind, m msg.Message, arg } func (p *AdminPlugin) addWhitelist(plugin string) error { - return p.modList(`insert or replace into pluginWhitelist values (?)`, "", plugin) + defer p.bot.RefreshPluginWhitelist() + return p.store.Upsert(plugin, bot.WhitelistItem{plugin}) } func (p *AdminPlugin) rmWhitelist(plugin string) error { if plugin == "admin" { return fmt.Errorf("you cannot disable the admin plugin") } - return p.modList(`delete from pluginWhitelist where name=?`, "", plugin) + defer p.bot.RefreshPluginWhitelist() + return p.store.Delete(plugin, bot.WhitelistItem{}) } func (p *AdminPlugin) addBlacklist(channel, plugin string) error { if plugin == "admin" { return fmt.Errorf("you cannot disable the admin plugin") } - return p.modList(`insert or replace into pluginBlacklist values (?, ?)`, channel, plugin) + defer p.bot.RefreshPluginBlacklist() + return p.store.Upsert(channel+plugin, bot.BlacklistItem{ + Channel: channel, + Name: plugin, + }) } func (p *AdminPlugin) rmBlacklist(channel, plugin string) error { - return p.modList(`delete from pluginBlacklist where channel=? and name=?`, channel, plugin) -} - -func (p *AdminPlugin) modList(query, channel, plugin string) error { - if channel == "" && plugin != "" { - channel = plugin // hack - } - plugins := p.bot.GetPluginNames() - for _, pp := range plugins { - if pp == plugin { - // todo: yikes - //if _, err := p.db.Exec(query, channel, plugin); err != nil { - // return fmt.Errorf("%w", err) - //} - err := p.bot.RefreshPluginWhitelist() - if err != nil { - return fmt.Errorf("%w", err) - } - err = p.bot.RefreshPluginBlacklist() - if err != nil { - return fmt.Errorf("%w", err) - } - return nil - } - } - err := fmt.Errorf("unknown plugin named '%s'", plugin) - return err + defer p.bot.RefreshPluginBlacklist() + return p.store.Delete(channel+plugin, bot.BlacklistItem{ + Channel: channel, + Name: plugin, + }) } diff --git a/plugins/babbler/babbler.go b/plugins/babbler/babbler.go index 45f9880..832d906 100644 --- a/plugins/babbler/babbler.go +++ b/plugins/babbler/babbler.go @@ -33,7 +33,7 @@ type BabblerPlugin struct { } type Babbler struct { - BabblerId int64 `db:"id" boltholdid:"BabblerId"` + BabblerID int64 `db:"id" boltholdid:"BabblerID"` Name string `db:"babbler"` } @@ -44,8 +44,8 @@ func getBabbler(store *bh.Store, id int64) (*Babbler, error) { } type BabblerWord struct { - WordId int64 `db:"id" boltholdid:"WordId"` - Word string `db:"word"` + WordID int64 `db:"ID" boltholdid:"WordID"` + Word string `db:"Word"` } func getWord(store *bh.Store, id int64) (*BabblerWord, error) { @@ -55,11 +55,11 @@ func getWord(store *bh.Store, id int64) (*BabblerWord, error) { } type BabblerNode struct { - NodeId int64 `db:"id" boltholdid:"NodeId"` - BabblerId int64 `db:"babblerId"` - WordId int64 `db:"wordId"` - Root int64 `db:"root"` - RootFrequency int64 `db:"rootFrequency"` + NodeId int64 `db:"ID" boltholdid:"NodeId"` + BabblerID int64 `db:"BabblerID"` + WordID int64 `db:"WordID"` + Root int64 `db:"Root"` + RootFrequency int64 `db:"RootFrequency"` } func getNode(store *bh.Store, id int64) (*BabblerNode, error) { @@ -69,10 +69,10 @@ func getNode(store *bh.Store, id int64) (*BabblerNode, error) { } type BabblerArc struct { - ArcId int64 `db:"id" boltholdid:"ArcId"` - FromNodeId int64 `db:"fromNodeId"` - ToNodeId int64 `db:"toNodeId"` - Frequency int64 `db:"frequency"` + ArcId int64 `db:"ID" boltholdid:"ArcId"` + FromNodeId int64 `db:"FromNodeId"` + ToNodeId int64 `db:"ToNodeId"` + Frequency int64 `db:"Frequency"` } func getArc(store *bh.Store, id int64) (*BabblerArc, error) { @@ -200,7 +200,7 @@ func (p *BabblerPlugin) makeBabbler(name string) (*Babbler, error) { func (p *BabblerPlugin) getBabbler(name string) (*Babbler, error) { var bblr Babbler - err := p.store.FindOne(&bblr, bh.Where("babbler").Eq(name)) + err := p.store.FindOne(&bblr, bh.Where("Babbler").Eq(name)) if err != nil { if err == sql.ErrNoRows { log.Error().Msg("failed to find babbler") @@ -233,7 +233,7 @@ func (p *BabblerPlugin) getOrCreateBabbler(name string) (*Babbler, error) { func (p *BabblerPlugin) getWord(word string) (*BabblerWord, error) { var w BabblerWord - err := p.store.FindOne(&w, bh.Where("word").Eq(word).Limit(1)) + err := p.store.FindOne(&w, bh.Where("Word").Eq(word).Limit(1)) if err != nil { if err == bh.ErrNotFound { return nil, NEVER_SAID @@ -271,7 +271,7 @@ func (p *BabblerPlugin) getBabblerNode(babbler *Babbler, word string) (*BabblerN } var node BabblerNode - err = p.store.FindOne(&node, bh.Where("babblerId").Eq(babbler.BabblerId).And("wordId").Eq(w.WordId)) + err = p.store.FindOne(&node, bh.Where("BabblerID").Eq(babbler.BabblerID).And("WordID").Eq(w.WordID)) if err != nil { if err == bh.ErrNotFound { return nil, NEVER_SAID @@ -289,7 +289,7 @@ func (p *BabblerPlugin) createBabblerNode(babbler *Babbler, word string) (*Babbl } bn := &BabblerNode{ - WordId: w.WordId, + WordID: w.WordID, Root: 0, RootFrequency: 0, } @@ -317,7 +317,7 @@ func (p *BabblerPlugin) incrementRootWordFrequency(babbler *Babbler, word string log.Error().Err(err) return nil, err } - err = p.store.UpdateMatching(BabblerNode{}, bh.Where("id").Eq(node.NodeId), func(record interface{}) error { + err = p.store.UpdateMatching(BabblerNode{}, bh.Where("ID").Eq(node.NodeId), func(record interface{}) error { r := record.(BabblerNode) r.RootFrequency += 1 r.Root = 1 @@ -333,7 +333,7 @@ func (p *BabblerPlugin) incrementRootWordFrequency(babbler *Babbler, word string func (p *BabblerPlugin) getBabblerArc(fromNode, toNode *BabblerNode) (*BabblerArc, error) { var arc BabblerArc - err := p.store.FindOne(&arc, bh.Where("fromNodeId").Eq(fromNode.NodeId).And("toNodeId").Eq(toNode.NodeId)) + err := p.store.FindOne(&arc, bh.Where("FromNodeID").Eq(fromNode.NodeId).And("ToNodeID").Eq(toNode.NodeId)) if err != nil { if err == bh.ErrNotFound { return nil, NEVER_SAID @@ -346,7 +346,7 @@ func (p *BabblerPlugin) getBabblerArc(fromNode, toNode *BabblerNode) (*BabblerAr func (p *BabblerPlugin) incrementWordArc(fromNode, toNode *BabblerNode) (*BabblerArc, error) { affectedRows := 0 err := p.store.UpdateMatching(BabblerArc{}, - bh.Where("fromNodeId").Eq(fromNode.NodeId).And("toNodeId").Eq(toNode.NodeId), + bh.Where("FromNodeId").Eq(fromNode.NodeId).And("ToNodeId").Eq(toNode.NodeId), func(record interface{}) error { affectedRows++ r := record.(BabblerArc) @@ -414,7 +414,7 @@ func (p *BabblerPlugin) addToMarkovChain(babbler *Babbler, phrase string) error func (p *BabblerPlugin) getWeightedRootNode(babbler *Babbler) (*BabblerNode, *BabblerWord, error) { rootNodes := []*BabblerNode{} - err := p.store.Find(&rootNodes, bh.Where("babblerId").Eq(babbler.BabblerId).And("root").Eq(1)) + err := p.store.Find(&rootNodes, bh.Where("BabblerID").Eq(babbler.BabblerID).And("Root").Eq(1)) if err != nil { log.Error().Err(err) return nil, nil, err @@ -435,7 +435,7 @@ func (p *BabblerPlugin) getWeightedRootNode(babbler *Babbler) (*BabblerNode, *Ba for _, node := range rootNodes { total += node.RootFrequency if total >= which { - w, err := getWord(p.store, node.WordId) + w, err := getWord(p.store, node.WordID) if err != nil { log.Error().Err(err) return nil, nil, err @@ -450,7 +450,7 @@ func (p *BabblerPlugin) getWeightedRootNode(babbler *Babbler) (*BabblerNode, *Ba func (p *BabblerPlugin) getWeightedNextWord(fromNode *BabblerNode) (*BabblerNode, *BabblerWord, error) { arcs := []BabblerArc{} - err := p.store.Find(&arcs, bh.Where("fromNodeId").Eq(fromNode.NodeId)) + err := p.store.Find(&arcs, bh.Where("FromNodeID").Eq(fromNode.NodeId)) if err != nil { log.Error().Err(err) return nil, nil, err @@ -477,7 +477,7 @@ func (p *BabblerPlugin) getWeightedNextWord(fromNode *BabblerNode) (*BabblerNode return nil, nil, err } - w, err := getWord(p.store, node.WordId) + w, err := getWord(p.store, node.WordID) if err != nil { log.Error().Err(err) return nil, nil, err @@ -492,7 +492,7 @@ func (p *BabblerPlugin) getWeightedNextWord(fromNode *BabblerNode) (*BabblerNode func (p *BabblerPlugin) getWeightedPreviousWord(toNode *BabblerNode) (*BabblerNode, *BabblerWord, bool, error) { arcs := []*BabblerArc{} - err := p.store.Find(&arcs, bh.Where("toNodeId").Eq(toNode.NodeId)) + err := p.store.Find(&arcs, bh.Where("ToNodeID").Eq(toNode.NodeId)) if err != nil { log.Error().Err(err) return nil, nil, false, err @@ -525,7 +525,7 @@ func (p *BabblerPlugin) getWeightedPreviousWord(toNode *BabblerNode) (*BabblerNo return nil, nil, false, err } - w, err := getWord(p.store, node.WordId) + w, err := getWord(p.store, node.WordID) if err != nil { log.Error().Err(err) return nil, nil, false, err @@ -625,7 +625,7 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa mapping := map[int64]*BabblerNode{} nodes := []*BabblerNode{} - err = p.store.Find(&nodes, bh.Where("babblerId").Eq(otherBabbler.BabblerId)) + err = p.store.Find(&nodes, bh.Where("BabblerID").Eq(otherBabbler.BabblerID)) if err != nil { log.Error().Err(err) return err @@ -633,31 +633,31 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa for _, node := range nodes { if node.NodeId == otherNode.NodeId { - node.WordId = intoNode.WordId + node.WordID = intoNode.WordID } affected := 0 if node.Root > 0 { err = p.store.UpdateMatching(BabblerNode{}, - bh.Where("babblerId").Eq(intoBabbler.BabblerId).And("wordId").Eq(node.WordId), + bh.Where("BabblerID").Eq(intoBabbler.BabblerID).And("WordID").Eq(node.WordID), func(record interface{}) error { affected++ r := record.(BabblerNode) r.RootFrequency += node.RootFrequency r.Root = 1 - return p.store.Update(r.BabblerId, r) + return p.store.Update(r.BabblerID, r) }) if err != nil { log.Error().Err(err) } } else { err = p.store.UpdateMatching(BabblerNode{}, - bh.Where("babblerId").Eq(intoBabbler.BabblerId).And("wordId").Eq(node.WordId), + bh.Where("BabblerID").Eq(intoBabbler.BabblerID).And("WordID").Eq(node.WordID), func(record interface{}) error { affected++ r := record.(BabblerNode) r.RootFrequency += node.RootFrequency - return p.store.Update(r.BabblerId, r) + return p.store.Update(r.BabblerID, r) }) if err != nil { log.Error().Err(err) @@ -665,7 +665,7 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa } if err != nil || affected == 0 { - node.BabblerId = intoBabbler.BabblerId + node.BabblerID = intoBabbler.BabblerID err = p.store.Insert(bh.NextSequence(), &node) if err != nil { log.Error().Err(err) @@ -675,7 +675,7 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa var updatedNode BabblerNode err = p.store.FindOne(&updatedNode, - bh.Where("babblerId").Eq(intoBabbler.BabblerId).And("wordId").Eq(node.WordId)) + bh.Where("BabblerID").Eq(intoBabbler.BabblerID).And("WordID").Eq(node.WordID)) if err != nil { log.Error().Err(err) return err @@ -686,7 +686,7 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa for oldNodeId, newNode := range mapping { arcs := []*BabblerArc{} - err = p.store.Find(&arcs, bh.Where("fromNodeId").Eq(oldNodeId)) + err = p.store.Find(&arcs, bh.Where("FromNodeID").Eq(oldNodeId)) if err != nil { return err } @@ -749,7 +749,7 @@ func (p *BabblerPlugin) babbleSeedSuffix(babblerName string, seed []string) (str func (p *BabblerPlugin) getNextArcs(babblerNodeId int64) ([]*BabblerArc, error) { arcs := []*BabblerArc{} - err := p.store.Find(&arcs, bh.Where("fromNodeId").Eq(babblerNodeId)) + err := p.store.Find(&arcs, bh.Where("FromNodeID").Eq(babblerNodeId)) if err != nil { log.Error().Err(err) return arcs, err @@ -845,7 +845,7 @@ func (p *BabblerPlugin) babbleSeedBookends(babblerName string, start, end []stri log.Error().Err(err) return "", err } - w, err := getWord(p.store, cur.WordId) + w, err := getWord(p.store, cur.WordID) if err != nil { log.Error().Err(err) return "", err diff --git a/plugins/beers/beers.go b/plugins/beers/beers.go index b369b9d..ece44da 100644 --- a/plugins/beers/beers.go +++ b/plugins/beers/beers.go @@ -216,7 +216,7 @@ func (p *BeersPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, return true } -func getUserBeers(store *bh.Store, user, id, itemName string) counter.Item { +func getUserBeers(store *bh.Store, user, id, itemName string) *counter.Item { // TODO: really ought to have an ID here booze, _ := counter.GetUserItem(store, user, id, itemName) return booze diff --git a/plugins/counter/counter.go b/plugins/counter/counter.go index 5a314cb..e94efa7 100644 --- a/plugins/counter/counter.go +++ b/plugins/counter/counter.go @@ -4,6 +4,7 @@ import ( "database/sql" "fmt" bh "github.com/timshannon/bolthold" + "math" "math/rand" "regexp" "strconv" @@ -25,9 +26,10 @@ type CounterPlugin struct { } type Item struct { - *bh.Store + store *bh.Store + created bool - ID int64 + ID uint64 `boltholdKey:"ID"` Nick string Item string Count int @@ -35,11 +37,12 @@ type Item struct { } type alias struct { - *bh.Store + store *bh.Store + created bool - ID int64 + ID uint64 `boltholdKey:"ID"` Item string - PointsTo string `db:"points_to"` + PointsTo string `db:"PointsTo"` } // GetItems returns all counters @@ -51,7 +54,7 @@ func GetAllItems(store *bh.Store) ([]Item, error) { } // Don't forget to embed the db into all of that shiz for i := range items { - items[i].Store = store + items[i].store = store } return items, nil } @@ -62,16 +65,16 @@ func GetItems(store *bh.Store, nick, id string) ([]Item, error) { var err error q := &bh.Query{} if id != "" { - q = bh.Where("userid").Eq(id) + q = bh.Where("UserID").Eq(id) } else { - q = bh.Where("nick").Eq(nick) + q = bh.Where("Nick").Eq(nick) } if err = store.Find(&items, q); err != nil { return nil, err } // Don't forget to embed the db into all of that shiz for i := range items { - items[i].Store = store + items[i].store = store } return items, nil } @@ -86,7 +89,7 @@ func LeaderAll(store *bh.Store) ([]Item, error) { return nil, err } for i := range items { - items[i].Store = store + items[i].store = store } return items, nil } @@ -96,27 +99,32 @@ func Leader(store *bh.Store, itemName string) ([]Item, error) { //s := `select * from counter where item=? order by count desc` // todo: remove that when we verify this works var items []Item - err := store.Find(&items, bh.Where("item").Eq(itemName).SortBy("count").Reverse()) + err := store.Find(&items, bh.Where("Item").Eq(itemName).SortBy("Count").Reverse()) if err != nil { return nil, err } for i := range items { - items[i].Store = store + items[i].store = store } return items, nil } func RmAlias(store *bh.Store, aliasName string) error { - return store.Delete(alias{}, aliasName) + a := &alias{} + if err := store.FindOne(a, bh.Where("Item").Eq(aliasName)); err != nil { + return err + } + return store.Delete(a.ID, alias{}) } func MkAlias(store *bh.Store, aliasName, pointsTo string) (*alias, error) { aliasName = strings.ToLower(aliasName) pointsTo = strings.ToLower(pointsTo) alias := &alias{ - Store: store, + store: store, Item: aliasName, PointsTo: pointsTo, + created: true, } err := store.Insert(bh.NextSequence(), alias) return alias, err @@ -127,13 +135,13 @@ func GetItem(store *bh.Store, itemName string) ([]Item, error) { itemName = trimUnicode(itemName) var items []Item var a alias - if err := store.FindOne(&a, bh.Where("item").Eq(itemName)); err == nil { + if err := store.FindOne(&a, bh.Where("Item").Eq(itemName)); err == nil { itemName = a.PointsTo } else { log.Error().Err(err).Interface("alias", a) } - err := store.Find(&items, bh.Where("item").Eq(itemName)) + err := store.Find(&items, bh.Where("Item").Eq(itemName)) if err != nil { return nil, err } @@ -142,32 +150,45 @@ func GetItem(store *bh.Store, itemName string) ([]Item, error) { Interface("items", items). Msg("got item") for _, i := range items { - i.Store = store + i.store = store } return items, nil } // GetUserItem returns a specific counter for a subject -func GetUserItem(store *bh.Store, nick, id, itemName string) (Item, error) { +func GetUserItem(store *bh.Store, nick, id, itemName string) (*Item, error) { itemName = trimUnicode(itemName) - var item Item - item.Store = store + item := &Item{} + item.Nick = nick + item.Item = itemName + item.UserID = id var a alias - if err := store.FindOne(&a, bh.Where("item").Eq(itemName)); err == nil { + err := store.FindOne(&a, bh.Where("Item").Eq(itemName)) + if err == nil { + log.Debug().Msgf("itemName now is %s", a.PointsTo) itemName = a.PointsTo + item.Item = itemName } else { - log.Error().Err(err).Interface("alias", a) + log.Error().Err(err).Interface("alias", a).Msgf("error finding alias") } - var err error - q := bh.Where("nick").Eq(nick).And("item").Eq(itemName) if id != "" { - q = bh.Where("userid").Eq(id).And("item").Eq(itemName) + err = store.FindOne(item, bh.Where("UserID").Eq(id).And("Item").Eq(itemName)) + } else { + err = store.FindOne(item, bh.Where("Nick").Eq(nick).And("Item").Eq(itemName)) } - err = store.FindOne(&item, q) - if err != nil { - return Item{}, err + if err != nil && err == bh.ErrNotFound { + log.Debug().Msgf("We gotta create this item: %v", item) + item.store = store + if err := item.Create(); err != nil { + return nil, err + } + return item, nil + } else if err != nil { + return nil, err } + item.store = store + item.created = true log.Debug(). Str("nick", nick). Str("id", id). @@ -180,7 +201,10 @@ func GetUserItem(store *bh.Store, nick, id, itemName string) (Item, error) { // GetUserItem returns a specific counter for a subject // Create saves a counter func (i *Item) Create() error { - err := i.Store.Insert(bh.NextSequence(), i) + log.Debug().Msgf("i %v, store %v", i, i.store) + err := i.store.Insert(bh.NextSequence(), i) + i.created = true + log.Debug().Msgf("we just inserted an item, %v", i) return err } @@ -188,17 +212,19 @@ func (i *Item) Create() error { // This will create or delete the item if necessary func (i *Item) Update(r *bot.Request, value int) error { i.Count = value - if i.Count == 0 && i.ID != -1 { + if i.Count == 0 && i.created { return i.Delete() } - if i.ID == -1 { - i.Create() + if !i.created { + if err := i.Create(); err != nil { + log.Error().Err(err).Msg("could not create") + } } log.Debug(). Interface("i", i). Int("value", value). Msg("Updating item") - err := i.Store.Update(i.ID, i) + err := i.store.Update(i.ID, i) if err == nil { sendUpdate(r, i.Nick, i.Item, i.Count) } @@ -214,8 +240,9 @@ func (i *Item) UpdateDelta(r *bot.Request, delta int) error { // Delete removes a counter from the database func (i *Item) Delete() error { - err := i.Store.Delete(i.ID, Item{}) - i.ID = -1 + err := i.store.Delete(i.ID, Item{}) + i.created = false + i.ID = math.MaxUint64 return err } @@ -309,11 +336,13 @@ func (p *CounterPlugin) mkAliasCmd(r bot.Request) bool { p.b.Send(r.Conn, bot.Message, fmt.Sprintf("You must provide all fields for an alias: %s", mkAliasRegex)) return true } - if _, err := MkAlias(p.store, what, to); err != nil { + alias, err := MkAlias(p.store, what, to) + if err != nil { log.Error().Err(err).Msg("Could not mkalias") p.b.Send(r.Conn, bot.Message, r.Msg.Channel, "We're gonna need too much db space to make an alias for your mom.") return true } + log.Debug().Msgf("alias created: %v", alias) p.b.Send(r.Conn, bot.Message, r.Msg.Channel, fmt.Sprintf("Created alias %s -> %s", what, to)) return true @@ -416,6 +445,8 @@ func (p *CounterPlugin) inspectCmd(r bot.Request) bool { return true } + log.Debug().Msgf("items: %v", items) + resp := fmt.Sprintf("%s has the following counters:", nick) count := 0 for _, it := range items { @@ -483,7 +514,6 @@ func (p *CounterPlugin) countCmd(r bot.Request) bool { nick, id = p.resolveUser(r, r.Values["who"]) } - var item Item item, err := GetUserItem(p.store, nick, id, itemName) switch { case err == sql.ErrNoRows: @@ -515,7 +545,8 @@ func (p *CounterPlugin) incrementCmd(r bot.Request) bool { channel := r.Msg.Channel // ++ those fuckers item, err := GetUserItem(p.store, nick, id, itemName) - if err != nil { + log.Debug().Msgf("GetUserItem: %v", item) + if err != nil && err != bh.ErrNotFound { log.Error(). Err(err). Str("nick", nick). diff --git a/plugins/counter/counter_test.go b/plugins/counter/counter_test.go index f30998b..33c8199 100644 --- a/plugins/counter/counter_test.go +++ b/plugins/counter/counter_test.go @@ -17,13 +17,16 @@ import ( "github.com/velour/catbase/bot/user" ) -func setup(t *testing.T) (*bot.MockBot, *CounterPlugin) { +func setup(t *testing.T) (*bot.MockBot, *CounterPlugin, func()) { mb := bot.NewMockBot() c := New(mb) - mb.DB().MustExec(`delete from counter; delete from counter_alias;`) - _, err := MkAlias(mb.DB(), "tea", ":tea:") + _, err := MkAlias(mb.Store(), "tea", ":tea:") assert.Nil(t, err) - return mb, c + return mb, c, func() { + if err := mb.TearDown(); err != nil { + panic(err) + } + } } func makeMessage(payload string, r *regexp.Regexp) bot.Request { @@ -44,126 +47,139 @@ func makeMessage(payload string, r *regexp.Regexp) bot.Request { } func TestMkAlias(t *testing.T) { - mb, c := setup(t) + mb, c, td := setup(t) + defer td() assert.NotNil(t, c) c.mkAliasCmd(makeMessage("mkalias fuck mornings", mkAliasRegex)) c.incrementCmd(makeMessage("fuck++", incrementRegex)) - item, err := GetUserItem(mb.DB(), "tester", "id", "mornings") + item, err := GetUserItem(mb.Store(), "tester", "id", "mornings") assert.Nil(t, err) assert.Equal(t, 1, item.Count) } func TestRmAlias(t *testing.T) { - mb, c := setup(t) + mb, c, td := setup(t) + defer td() assert.NotNil(t, c) c.mkAliasCmd(makeMessage("mkalias fuck mornings", mkAliasRegex)) c.rmAliasCmd(makeMessage("rmalias fuck", rmAliasRegex)) c.incrementCmd(makeMessage("fuck++", incrementRegex)) - item, err := GetUserItem(mb.DB(), "tester", "id", "mornings") + item, err := GetUserItem(mb.Store(), "tester", "id", "mornings") assert.Nil(t, err) assert.Equal(t, 0, item.Count) } func TestThreeSentencesExists(t *testing.T) { - mb, c := setup(t) + mb, c, td := setup(t) + defer td() assert.NotNil(t, c) c.incrementCmd(makeMessage(":beer:++", incrementRegex)) c.teaMatchCmd(makeMessage(":beer:. Earl Grey. Hot.", teaRegex)) - item, err := GetUserItem(mb.DB(), "tester", "id", ":beer:") + item, err := GetUserItem(mb.Store(), "tester", "id", ":beer:") assert.Nil(t, err) assert.Equal(t, 2, item.Count) } func TestThreeSentencesNotExists(t *testing.T) { - mb, c := setup(t) + mb, c, td := setup(t) + defer td() assert.NotNil(t, c) - item, err := GetUserItem(mb.DB(), "tester", "id", ":beer:") + item, err := GetUserItem(mb.Store(), "tester", "id", ":beer:") c.teaMatchCmd(makeMessage(":beer:. Earl Grey. Hot.", teaRegex)) - item, err = GetUserItem(mb.DB(), "tester", "id", ":beer:") + item, err = GetUserItem(mb.Store(), "tester", "id", ":beer:") assert.Nil(t, err) assert.Equal(t, 0, item.Count) } func TestTeaEarlGreyHot(t *testing.T) { - mb, c := setup(t) + mb, c, td := setup(t) + defer td() assert.NotNil(t, c) c.teaMatchCmd(makeMessage("Tea. Earl Grey. Hot.", teaRegex)) c.teaMatchCmd(makeMessage("Tea. Earl Grey. Hot.", teaRegex)) - item, err := GetUserItem(mb.DB(), "tester", "id", ":tea:") + item, err := GetUserItem(mb.Store(), "tester", "id", ":tea:") assert.Nil(t, err) assert.Equal(t, 2, item.Count) } func TestTeaTwoPeriods(t *testing.T) { - mb, c := setup(t) + mb, c, td := setup(t) + defer td() assert.NotNil(t, c) c.teaMatchCmd(makeMessage("Tea. Earl Grey.", teaRegex)) c.teaMatchCmd(makeMessage("Tea. Earl Grey.", teaRegex)) - item, err := GetUserItem(mb.DB(), "tester", "id", ":tea:") + item, err := GetUserItem(mb.Store(), "tester", "id", ":tea:") assert.Nil(t, err) assert.Equal(t, 0, item.Count) } func TestTeaMultiplePeriods(t *testing.T) { - mb, c := setup(t) + mb, c, td := setup(t) + defer td() assert.NotNil(t, c) c.teaMatchCmd(makeMessage("Tea. Earl Grey. Spiked. Hot.", teaRegex)) c.teaMatchCmd(makeMessage("Tea. Earl Grey. Spiked. Hot.", teaRegex)) - item, err := GetUserItem(mb.DB(), "tester", "id", ":tea:") + item, err := GetUserItem(mb.Store(), "tester", "id", ":tea:") assert.Nil(t, err) assert.Equal(t, 2, item.Count) } func TestTeaGreenHot(t *testing.T) { - mb, c := setup(t) + mb, c, td := setup(t) + defer td() assert.NotNil(t, c) c.teaMatchCmd(makeMessage("Tea. Green. Hot.", teaRegex)) c.teaMatchCmd(makeMessage("Tea. Green. Hot", teaRegex)) c.teaMatchCmd(makeMessage("Tea. Green. Iced.", teaRegex)) - item, err := GetUserItem(mb.DB(), "tester", "id", ":tea:") + item, err := GetUserItem(mb.Store(), "tester", "id", ":tea:") assert.Nil(t, err) assert.Equal(t, 3, item.Count) } func TestTeaUnrelated(t *testing.T) { - mb, c := setup(t) + mb, c, td := setup(t) + defer td() assert.NotNil(t, c) c.teaMatchCmd(makeMessage("Tea.", teaRegex)) c.teaMatchCmd(makeMessage("Tea. It's great.", teaRegex)) - item, err := GetUserItem(mb.DB(), "tester", "id", ":tea:") + item, err := GetUserItem(mb.Store(), "tester", "id", ":tea:") assert.Nil(t, err) assert.Equal(t, 0, item.Count) } func TestTeaSkieselQuote(t *testing.T) { - mb, c := setup(t) + mb, c, td := setup(t) + defer td() assert.NotNil(t, c) c.teaMatchCmd(makeMessage("blah, this is a whole page of explanation where \"we did local search and used a tabu list\" would have sufficed", teaRegex)) - item, err := GetUserItem(mb.DB(), "tester", "id", ":tea:") + item, err := GetUserItem(mb.Store(), "tester", "id", ":tea:") assert.Nil(t, err) assert.Equal(t, 0, item.Count) } func TestTeaUnicodeJapanese(t *testing.T) { - mb, c := setup(t) + mb, c, td := setup(t) + defer td() assert.NotNil(t, c) c.teaMatchCmd(makeMessage("Tea. ใŠใกใ‚„. Hot.", teaRegex)) - item, err := GetUserItem(mb.DB(), "tester", "id", ":tea:") + item, err := GetUserItem(mb.Store(), "tester", "id", ":tea:") assert.Nil(t, err) assert.Equal(t, 1, item.Count) } func TestResetMe(t *testing.T) { - mb, c := setup(t) + mb, c, td := setup(t) + defer td() assert.NotNil(t, c) c.incrementCmd(makeMessage("test++", incrementRegex)) c.resetCmd(makeMessage("!reset me", resetRegex)) - items, err := GetItems(mb.DB(), "tester", "id") + items, err := GetItems(mb.Store(), "tester", "id") assert.Nil(t, err) assert.Len(t, items, 0) } func TestCounterOne(t *testing.T) { - mb, c := setup(t) + mb, c, td := setup(t) + defer td() assert.NotNil(t, c) c.incrementCmd(makeMessage("test++", incrementRegex)) assert.Len(t, mb.Messages, 1) @@ -171,7 +187,8 @@ func TestCounterOne(t *testing.T) { } func TestCounterOneWithSpace(t *testing.T) { - mb, c := setup(t) + mb, c, td := setup(t) + defer td() assert.NotNil(t, c) c.incrementCmd(makeMessage(":test: ++", incrementRegex)) assert.Len(t, mb.Messages, 1) @@ -179,7 +196,8 @@ func TestCounterOneWithSpace(t *testing.T) { } func TestCounterFour(t *testing.T) { - mb, c := setup(t) + mb, c, td := setup(t) + defer td() assert.NotNil(t, c) for i := 0; i < 4; i++ { c.incrementCmd(makeMessage("test++", incrementRegex)) @@ -189,7 +207,8 @@ func TestCounterFour(t *testing.T) { } func TestCounterDecrement(t *testing.T) { - mb, c := setup(t) + mb, c, td := setup(t) + defer td() assert.NotNil(t, c) for i := 0; i < 4; i++ { c.incrementCmd(makeMessage("test++", incrementRegex)) @@ -201,7 +220,8 @@ func TestCounterDecrement(t *testing.T) { } func TestFriendCounterDecrement(t *testing.T) { - mb, c := setup(t) + mb, c, td := setup(t) + defer td() assert.NotNil(t, c) for i := 0; i < 4; i++ { c.incrementCmd(makeMessage("other.test++", incrementRegex)) @@ -213,7 +233,8 @@ func TestFriendCounterDecrement(t *testing.T) { } func TestDecrementZero(t *testing.T) { - mb, c := setup(t) + mb, c, td := setup(t) + defer td() assert.NotNil(t, c) for i := 0; i < 4; i++ { c.incrementCmd(makeMessage("test++", incrementRegex)) @@ -230,7 +251,8 @@ func TestDecrementZero(t *testing.T) { } func TestClear(t *testing.T) { - mb, c := setup(t) + mb, c, td := setup(t) + defer td() assert.NotNil(t, c) for i := 0; i < 4; i++ { c.incrementCmd(makeMessage("test++", incrementRegex)) @@ -243,7 +265,8 @@ func TestClear(t *testing.T) { } func TestCount(t *testing.T) { - mb, c := setup(t) + mb, c, td := setup(t) + defer td() assert.NotNil(t, c) for i := 0; i < 4; i++ { c.incrementCmd(makeMessage("test++", incrementRegex)) @@ -257,7 +280,8 @@ func TestCount(t *testing.T) { } func TestInspectMe(t *testing.T) { - mb, c := setup(t) + mb, c, td := setup(t) + defer td() assert.NotNil(t, c) for i := 0; i < 4; i++ { c.incrementCmd(makeMessage("test++", incrementRegex)) @@ -278,7 +302,8 @@ func TestInspectMe(t *testing.T) { } func TestHelp(t *testing.T) { - mb, c := setup(t) + mb, c, td := setup(t) + defer td() assert.NotNil(t, c) c.help(&cli.CliPlugin{}, bot.Help, msg.Message{Channel: "channel"}, []string{}) assert.Greater(t, len(mb.Messages), 1) diff --git a/plugins/fact/factoid.go b/plugins/fact/factoid.go index 1fe33d8..465e213 100644 --- a/plugins/fact/factoid.go +++ b/plugins/fact/factoid.go @@ -50,7 +50,7 @@ func (a *alias) resolve(store *bh.Store) (*Factoid, error) { // todo: remove this query //q := `select fact, next from factoid_alias where fact=?` var next alias - err := store.FindOne(&next, bh.Where("fact").Eq(a.Next)) + err := store.FindOne(&next, bh.Where("Fact").Eq(a.Next)) if err != nil { // we hit the end of the chain, get a factoid named Next fact, err := GetSingleFact(store, a.Next) @@ -67,7 +67,7 @@ func findAlias(store *bh.Store, fact string) (bool, *Factoid) { // todo: remove this query //q := `select * from factoid_alias where fact=?` var a alias - err := store.FindOne(&a, bh.Where("fact").Eq(fact)) + err := store.FindOne(&a, bh.Where("Fact").Eq(fact)) if err != nil { return false, nil } @@ -78,7 +78,7 @@ func findAlias(store *bh.Store, fact string) (bool, *Factoid) { func (a *alias) save(store *bh.Store) error { //q := `select * from factoid_alias where fact=?` var offender alias - err := store.FindOne(&offender, bh.Where("fact").Eq(a.Next)) + err := store.FindOne(&offender, bh.Where("Fact").Eq(a.Next)) if err == nil { return fmt.Errorf("DANGER: an opposite alias already exists") } @@ -123,7 +123,7 @@ func (f *Factoid) delete(store *bh.Store) error { func getFacts(store *bh.Store, fact string, tidbit string) ([]*Factoid, error) { var fs []*Factoid - err := store.Find(&fs, bh.Where("fact").Contains(fact).And("tidbit").Contains(tidbit)) + err := store.Find(&fs, bh.Where("Fact").Contains(fact).And("Tidbit").Contains(tidbit)) if err != nil { log.Error().Err(err).Msg("Error regexping for facts") return nil, err @@ -142,9 +142,12 @@ func GetSingle(store *bh.Store) (*Factoid, error) { func GetSingleFact(store *bh.Store, fact string) (*Factoid, error) { var allMatching []Factoid - if err := store.Find(&allMatching, bh.Where("fact").Contains(fact)); err != nil { + if err := store.Find(&allMatching, bh.Where("Fact").Contains(fact)); err != nil { return nil, err } + if len(allMatching) == 0 { + return nil, fmt.Errorf("no entries") + } f := allMatching[rand.Intn(len(allMatching))] return &f, nil } @@ -232,7 +235,7 @@ func (p *FactoidPlugin) learnFact(message msg.Message, fact, verb, tidbit string } } - count, err := p.store.Count(Factoid{}, bh.Where("fact").Eq(fact).And("verb").Eq(verb).And("tidbit").Eq(tidbit)) + count, err := p.store.Count(Factoid{}, bh.Where("Fact").Eq(fact).And("Verb").Eq(verb).And("Tidbit").Eq(tidbit)) if err != nil { log.Error().Err(err).Msg("Error counting facts") return fmt.Errorf("What?") diff --git a/plugins/first/first.go b/plugins/first/first.go index d17292a..a5f1e50 100644 --- a/plugins/first/first.go +++ b/plugins/first/first.go @@ -27,13 +27,13 @@ type FirstPlugin struct { } type FirstEntry struct { - id int64 - day time.Time - time time.Time - channel string - body string - nick string - saved bool + ID uint64 `boltholdKey:"ID"` + Day time.Time + Time time.Time + Channel string + Body string + Nick string + Saved bool } // Insert or update the first entry @@ -42,12 +42,12 @@ func (fe *FirstEntry) save(store *bh.Store) error { } func (fe *FirstEntry) delete(store *bh.Store) error { - return store.Delete(fe.id, FirstEntry{}) + return store.Delete(fe.ID, FirstEntry{}) } // NewFirstPlugin creates a new FirstPlugin with the Plugin interface func New(b bot.Bot) *FirstPlugin { - log.Info().Msgf("First plugin initialized with day: %s", + log.Info().Msgf("First plugin initialized with Day: %s", Midnight(time.Now())) fp := &FirstPlugin{ @@ -63,11 +63,11 @@ func New(b bot.Bot) *FirstPlugin { func getLastFirst(store *bh.Store, channel string) (*FirstEntry, error) { fe := &FirstEntry{} - err := store.FindOne(fe, bh.Where("channel").Eq(channel)) + err := store.FindOne(fe, bh.Where("Channel").Eq(channel)) if err != nil { return nil, err } - log.Debug().Msgf("id: %v day %v time %v body %v nick %v", fe) + log.Debug().Msgf("ID: %v Day %v Time %v Body %v Nick %v", fe) return fe, nil } @@ -80,7 +80,7 @@ func (p *FirstPlugin) isNotToday(f *FirstEntry) bool { if f == nil { return true } - t := f.time + t := f.Time t0 := Midnight(t) jitter := time.Duration(p.config.GetInt("first.jitter", 0)) t0 = t0.Add(jitter * time.Millisecond) @@ -138,7 +138,7 @@ func (p *FirstPlugin) register() { log.Debug().Msgf("Re-enabled first") }() p.bot.Send(r.Conn, bot.Message, r.Msg.Channel, - fmt.Sprintf("Deleted first entry: '%s' and set a random timer for when first will happen next.", fe.body)) + fmt.Sprintf("Deleted first entry: '%s' and set a random timer for when first will happen next.", fe.Body)) return true }}, {Kind: bot.Message, IsCmd: false, @@ -161,7 +161,7 @@ func (p *FirstPlugin) register() { if (first == nil || p.isNotToday(first)) && p.allowed(r.Msg) { log.Debug(). - Str("body", r.Msg.Body). + Str("Body", r.Msg.Body). Interface("t0", first). Time("t1", time.Now()). Msg("Recording first") @@ -196,7 +196,7 @@ func (p *FirstPlugin) allowed(message msg.Message) bool { if match { log.Info(). Str("user", message.User.Name). - Str("body", message.Body). + Str("Body", message.Body). Msg("Disallowing first") return false } @@ -205,7 +205,7 @@ func (p *FirstPlugin) allowed(message msg.Message) bool { if host == message.Host { log.Info(). Str("user", message.User.Name). - Str("body", message.Body). + Str("Body", message.Body). Msg("Disallowing first") return false } @@ -214,7 +214,7 @@ func (p *FirstPlugin) allowed(message msg.Message) bool { if nick == message.User.Name { log.Info(). Str("user", message.User.Name). - Str("body", message.Body). + Str("Body", message.Body). Msg("Disallowing first") return false } @@ -224,18 +224,18 @@ func (p *FirstPlugin) allowed(message msg.Message) bool { func (p *FirstPlugin) recordFirst(c bot.Connector, message msg.Message) { log.Info(). - Str("channel", message.Channel). + Str("Channel", message.Channel). Str("user", message.User.Name). - Str("body", message.Body). + Str("Body", message.Body). Msg("Recording first") first := &FirstEntry{ - day: Midnight(time.Now()), - time: time.Now(), - channel: message.Channel, - body: message.Body, - nick: message.User.Name, + Day: Midnight(time.Now()), + Time: time.Now(), + Channel: message.Channel, + Body: message.Body, + Nick: message.User.Name, } - log.Info().Msgf("recordFirst: %+v", first.day) + log.Info().Msgf("recordFirst: %+v", first.Day) err := first.save(p.store) if err != nil { log.Error().Err(err).Msg("Error saving first entry") @@ -246,13 +246,13 @@ func (p *FirstPlugin) recordFirst(c bot.Connector, message msg.Message) { func (p *FirstPlugin) leaderboard(c bot.Connector, ch string) error { // todo: remove this once we verify stuff - //q := `select max(channel) channel, max(nick) nick, count(id) count + //q := `select max(Channel) Channel, max(Nick) Nick, count(ID) count // from first - // group by channel, nick - // having channel = ? + // group by Channel, Nick + // having Channel = ? // order by count desc // limit 3` - groups, err := p.store.FindAggregate(FirstEntry{}, bh.Where("channel").Eq(ch), "channel", "nick") + groups, err := p.store.FindAggregate(FirstEntry{}, bh.Where("Channel").Eq(ch), "Channel", "Nick") if err != nil { return err } @@ -274,9 +274,9 @@ func (p *FirstPlugin) leaderboard(c bot.Connector, ch string) error { } func (p *FirstPlugin) announceFirst(c bot.Connector, first *FirstEntry) { - ch := first.channel + ch := first.Channel p.bot.Send(c, bot.Message, ch, fmt.Sprintf("%s had first at %s with the message: \"%s\"", - first.nick, first.time.Format("15:04"), first.body)) + first.Nick, first.Time.Format("15:04"), first.Body)) } // Help responds to help requests. Every plugin must implement a help function. diff --git a/plugins/goals/goals.go b/plugins/goals/goals.go index 041750c..89d2d13 100644 --- a/plugins/goals/goals.go +++ b/plugins/goals/goals.go @@ -237,7 +237,7 @@ func parseCmd(r *regexp.Regexp, body string) cmd { } type goal struct { - ID int64 `boltholdid:"ID"` + ID uint64 `boltholdid:"ID"` Kind string Who string What string @@ -246,9 +246,9 @@ type goal struct { gp *GoalsPlugin } -func (p *GoalsPlugin) newGoal(kind, who, what string, amount int) goal { - return goal{ - ID: -1, +func (p *GoalsPlugin) newGoal(kind, who, what string, amount int) *goal { + return &goal{ + ID: 0, Kind: kind, Who: who, What: what, @@ -259,7 +259,7 @@ func (p *GoalsPlugin) newGoal(kind, who, what string, amount int) goal { func (p *GoalsPlugin) getGoal(who, what string) ([]*goal, error) { gs := []*goal{} - err := p.store.Find(&gs, bh.Where("who").Eq(who).And("what").Eq(what)) + err := p.store.Find(&gs, bh.Where("Who").Eq(who).And("What").Eq(what)) if err != nil { return nil, err } @@ -271,7 +271,7 @@ func (p *GoalsPlugin) getGoal(who, what string) ([]*goal, error) { func (p *GoalsPlugin) getGoalKind(kind, who, what string) (*goal, error) { g := &goal{gp: p} - err := p.store.FindOne(&g, bh.Where("kind").Eq(kind).And("who").Eq(who).And("what").Eq(what)) + err := p.store.FindOne(g, bh.Where("Kind").Eq(kind).And("Who").Eq(who).And("What").Eq(what)) if err != nil { return nil, err } @@ -279,7 +279,7 @@ func (p *GoalsPlugin) getGoalKind(kind, who, what string) (*goal, error) { } func (g *goal) Save() error { - err := g.gp.store.Insert(bh.NextSequence(), &g) + err := g.gp.store.Insert(bh.NextSequence(), g) if err != nil { return err } @@ -287,7 +287,7 @@ func (g *goal) Save() error { } func (g goal) Delete() error { - if g.ID == -1 { + if g.ID == 0 { return nil } err := g.gp.store.Delete(goal{}, g.ID) @@ -313,7 +313,7 @@ func (p *GoalsPlugin) update(r bot.Request, u counter.Update) { var now = time.Now -func (p *GoalsPlugin) calculateRemaining(i counter.Item, g *goal) int { +func (p *GoalsPlugin) calculateRemaining(i *counter.Item, g *goal) int { today := float64(now().YearDay()) thisYear := time.Date(now().Year(), 0, 0, 0, 0, 0, 0, time.UTC) nextYear := time.Date(now().Year()+1, 0, 0, 0, 0, 0, 0, time.UTC) @@ -328,7 +328,7 @@ func (p *GoalsPlugin) calculateRemaining(i counter.Item, g *goal) int { return diff } -func (p *GoalsPlugin) remainingText(i counter.Item, g *goal) string { +func (p *GoalsPlugin) remainingText(i *counter.Item, g *goal) string { remaining := p.calculateRemaining(i, g) txt := "" if remaining < 0 { diff --git a/plugins/goals/goals_test.go b/plugins/goals/goals_test.go index 9998557..e53c83d 100644 --- a/plugins/goals/goals_test.go +++ b/plugins/goals/goals_test.go @@ -1,6 +1,11 @@ package goals import ( + "github.com/velour/catbase/bot/msg" + "github.com/velour/catbase/bot/user" + "github.com/velour/catbase/plugins/cli" + "regexp" + "strings" "testing" "time" @@ -10,11 +15,46 @@ import ( "github.com/velour/catbase/plugins/counter" ) +func setup(t *testing.T) (*bot.MockBot, *GoalsPlugin, func()) { + mb := bot.NewMockBot() + c := New(mb) + return mb, c, func() { + if err := mb.TearDown(); err != nil { + panic(err) + } + } +} + +func makeMessage(payload string, r *regexp.Regexp) bot.Request { + isCmd := strings.HasPrefix(payload, "!") + if isCmd { + payload = payload[1:] + } + values := bot.ParseValues(r, payload) + return bot.Request{ + Conn: &cli.CliPlugin{}, + Msg: msg.Message{ + User: &user.User{Name: "tester", ID: "id"}, + Body: payload, + Command: isCmd, + }, + Values: values, + } +} + +func sendMessage(gp *GoalsPlugin, message string) { + for _, h := range gp.handlers { + r := makeMessage(message, h.Regex) + if h.Regex.MatchString(message) { + h.Handler(r) + } + } +} + func TestRemainingSomeToGo(t *testing.T) { mb := bot.NewMockBot() - i := counter.Item{ - DB: mb.DB(), + i := &counter.Item{ ID: 0, Nick: "", Item: "", @@ -45,8 +85,7 @@ func TestRemainingSomeToGo(t *testing.T) { func TestRemainingAheadOfCurve(t *testing.T) { mb := bot.NewMockBot() - i := counter.Item{ - DB: mb.DB(), + i := &counter.Item{ ID: 0, Nick: "", Item: "", @@ -73,3 +112,10 @@ func TestRemainingAheadOfCurve(t *testing.T) { assert.Equal(t, expected, actual) } + +func TestRegister(t *testing.T) { + _, gp, td := setup(t) + defer td() + + sendMessage(gp, "register goal :tea: 5") +} diff --git a/plugins/inventory/inventory.go b/plugins/inventory/inventory.go index b9f90aa..9738dec 100644 --- a/plugins/inventory/inventory.go +++ b/plugins/inventory/inventory.go @@ -154,7 +154,7 @@ func (p *InventoryPlugin) getAll() []string { } func (p *InventoryPlugin) exists(i string) bool { - count, err := p.Store.Count(Item{}, bh.Where("item").Eq(i)) + count, err := p.Store.Count(Item{}, bh.Where("Item").Eq(i)) if err != nil { log.Error().Err(err).Msg("Error checking for item") return false diff --git a/plugins/last/last.go b/plugins/last/last.go index 3ebaafa..0c01729 100644 --- a/plugins/last/last.go +++ b/plugins/last/last.go @@ -135,7 +135,7 @@ func (p *LastPlugin) yesterdaysLast(ch string) (last, error) { midnight := first.Midnight(time.Now()) q := `select * from last where channel = ? and day < ? and day >= ? order by day limit 1` log.Debug().Str("q", q).Msgf("yesterdaysLast: %d to %d", midnight.Unix(), midnight.Add(-24*time.Hour).Unix()) - err := p.store.FindOne(&l, bh.Where("channel").Eq(ch).And("day").Lt(midnight.Unix()).And("day").Ge(midnight.Add(-24*time.Hour).Unix())) + err := p.store.FindOne(&l, bh.Where("Channel").Eq(ch).And("Day").Lt(midnight.Unix()).And("Day").Ge(midnight.Add(-24*time.Hour).Unix())) if err != nil { return last{}, err } diff --git a/plugins/newsbid/webshit/webshit.go b/plugins/newsbid/webshit/webshit.go index e6a96c0..d1175c2 100644 --- a/plugins/newsbid/webshit/webshit.go +++ b/plugins/newsbid/webshit/webshit.go @@ -78,7 +78,7 @@ func (w *Webshit) Check(last int64) ([]WeeklyResult, int64, error) { } var bids []Bid - if err = w.store.Find(&bids, bh.Where("processed").Eq(false)); err != nil { + if err = w.store.Find(&bids, bh.Where("Processed").Eq(false)); err != nil { return nil, 0, err } @@ -106,7 +106,7 @@ func (w *Webshit) Check(last int64) ([]WeeklyResult, int64, error) { } // Delete all those bids - if err = w.store.UpdateMatching(Bid{}, bh.Where("processed").Eq(false), func(record interface{}) error { + if err = w.store.UpdateMatching(Bid{}, bh.Where("Processed").Eq(false), func(record interface{}) error { r := record.(*Bid) r.Processed = true return w.store.Update(r.ID, r) @@ -234,7 +234,7 @@ func (w *Webshit) GetBalance(user string) Balance { func (w *Webshit) GetAllBids() ([]Bid, error) { var bids []Bid - err := w.store.Find(&bids, bh.Where("processed").Eq(false)) + err := w.store.Find(&bids, bh.Where("Processed").Eq(false)) if err != nil { return nil, err } diff --git a/plugins/remember/remember.go b/plugins/remember/remember.go index f9b497e..6c7ce16 100644 --- a/plugins/remember/remember.go +++ b/plugins/remember/remember.go @@ -117,7 +117,7 @@ func (p *RememberPlugin) randQuote() string { // AllQuotesFrom delivers quotes out of the db from who. func AllQuotesFrom(store *bh.Store, who string) []fact.Factoid { allQuotes := []fact.Factoid{} - err := store.Find(&allQuotes, bh.Where("fact").Eq(who+" quotes")) + err := store.Find(&allQuotes, bh.Where("Fact").Eq(who+" quotes")) if err != nil { log.Error().Err(err).Msg("Error getting quotes") return []fact.Factoid{} @@ -130,7 +130,7 @@ func AllQuotesFrom(store *bh.Store, who string) []fact.Factoid { // expanded to have this function execute a quote for a particular channel func RandQuote(store *bh.Store) string { allQuotes := []fact.Factoid{} - err := store.Find(&allQuotes, bh.Where("fact").RegExp(regexp.MustCompile(`.+ quotes$`))) + err := store.Find(&allQuotes, bh.Where("Fact").RegExp(regexp.MustCompile(`.+ quotes$`))) if err != nil { log.Error().Err(err).Msg("Error getting quotes") return "I had a problem getting your quote." diff --git a/plugins/reminder/reminder.go b/plugins/reminder/reminder.go index a579301..19f55a2 100644 --- a/plugins/reminder/reminder.go +++ b/plugins/reminder/reminder.go @@ -37,12 +37,12 @@ type ReminderPlugin struct { } type Reminder struct { - id int64 - from string - who string - what string - when time.Time - channel string + ID uint64 `boltholdKey:"ID"` + From string + Who string + What string + When time.Time + Channel string } func New(b bot.Bot) *ReminderPlugin { @@ -108,22 +108,21 @@ func (p *ReminderPlugin) message(c bot.Connector, kind bot.Kind, message msg.Mes if operator == "in" || operator == "at" || operator == "on" { //one off reminder - //remind who in dur blah + //remind Who in dur blah when := time.Now().UTC().Add(dur) what := strings.Join(parts[4:], " ") p.addReminder(&Reminder{ - id: -1, - from: from, - who: who, - what: what, - when: when, - channel: channel, + From: from, + Who: who, + What: what, + When: when, + Channel: channel, }) } else if operator == "every" && strings.ToLower(parts[4]) == "for" { //batch add, especially for reminding msherms to buy a kit - //remind who every dur for dur2 blah + //remind Who every dur for dur2 blah dur2, err = time.ParseDuration(parts[5]) if err != nil { log.Error().Err(err) @@ -144,18 +143,17 @@ func (p *ReminderPlugin) message(c bot.Connector, kind bot.Kind, message msg.Mes } p.addReminder(&Reminder{ - id: int64(-1), - from: from, - who: who, - what: what, - when: when, - channel: channel, + From: from, + Who: who, + What: what, + When: when, + Channel: channel, }) when = when.Add(dur) } } else { - p.bot.Send(c, bot.Message, channel, "Easy cowboy, not sure I comprehend what you're asking.") + p.bot.Send(c, bot.Message, channel, "Easy cowboy, not sure I comprehend What you're asking.") return true } @@ -177,7 +175,7 @@ func (p *ReminderPlugin) message(c bot.Connector, kind bot.Kind, message msg.Mes } else if len(parts) == 4 { if strings.ToLower(parts[2]) == "to" { response, err = p.getAllRemindersToMeFormatted(channel, strings.ToLower(parts[3])) - } else if strings.ToLower(parts[2]) == "from" { + } else if strings.ToLower(parts[2]) == "From" { response, err = p.getAllRemindersFromMeFormatted(channel, strings.ToLower(parts[3])) } } @@ -188,9 +186,9 @@ func (p *ReminderPlugin) message(c bot.Connector, kind bot.Kind, message msg.Mes } return true } else if len(parts) == 3 && strings.ToLower(parts[0]) == "cancel" && strings.ToLower(parts[1]) == "reminder" { - id, err := strconv.ParseInt(parts[2], 10, 64) + id, err := strconv.ParseUint(parts[2], 10, 64) if err != nil { - p.bot.Send(c, bot.Message, channel, fmt.Sprintf("couldn't parse id: %s", parts[2])) + p.bot.Send(c, bot.Message, channel, fmt.Sprintf("couldn't parse ID: %s", parts[2])) } else { err := p.deleteReminder(id) @@ -221,7 +219,11 @@ func (p *ReminderPlugin) getNextReminder() *Reminder { log.Error().Err(err) return nil } - res[0].Max("remindWhen", &reminder) + if len(res) == 0 { + log.Error().Msg("No next reminder in system.") + return nil + } + res[0].Max("RemindWhen", &reminder) return &reminder } @@ -231,12 +233,13 @@ func (p *ReminderPlugin) addReminder(reminder *Reminder) error { defer p.mutex.Unlock() err := p.store.Insert(bh.NextSequence(), reminder) if err != nil { - log.Error().Err(err) + log.Error().Err(err).Msgf("error creating reminder") + return err } - return err + return nil } -func (p *ReminderPlugin) deleteReminder(id int64) error { +func (p *ReminderPlugin) deleteReminder(id uint64) error { p.mutex.Lock() defer p.mutex.Unlock() err := p.store.Delete(id, Reminder{}) @@ -264,14 +267,14 @@ func (p *ReminderPlugin) getRemindersFormatted(filter, who string) (string, erro } reminders := []Reminder{} - err = p.store.Find(&reminders, q.SortBy("id").Limit(max)) + err = p.store.Find(&reminders, q.SortBy("ID").Limit(max)) if err != nil { log.Error().Err(err) return "", nil } txt := "" for counter, reminder := range reminders { - txt += fmt.Sprintf("%d) %s -> %s :: %s @ %s (%d)\n", counter, reminder.from, reminder.who, reminder.what, reminder.when, reminder.id) + txt += fmt.Sprintf("%d) %s -> %s :: %s @ %s (%d)\n", counter, reminder.From, reminder.Who, reminder.What, reminder.When, reminder.ID) counter++ } @@ -288,18 +291,18 @@ func (p *ReminderPlugin) getAllRemindersFormatted(channel string) (string, error } func (p *ReminderPlugin) getAllRemindersFromMeFormatted(channel, me string) (string, error) { - return p.getRemindersFormatted("fromWho", me) + return p.getRemindersFormatted("FromWho", me) } func (p *ReminderPlugin) getAllRemindersToMeFormatted(channel, me string) (string, error) { - return p.getRemindersFormatted("toWho", me) + return p.getRemindersFormatted("ToWho", me) } func (p *ReminderPlugin) queueUpNextReminder() { nextReminder := p.getNextReminder() if nextReminder != nil { - p.timer.Reset(nextReminder.when.Sub(time.Now().UTC())) + p.timer.Reset(nextReminder.When.Sub(time.Now().UTC())) } } @@ -309,24 +312,24 @@ func reminderer(c bot.Connector, p *ReminderPlugin) { reminder := p.getNextReminder() - if reminder != nil && time.Now().UTC().After(reminder.when) { + if reminder != nil && time.Now().UTC().After(reminder.When) { var message string - if reminder.from == reminder.who { - reminder.from = "you" - message = fmt.Sprintf("Hey %s, you wanted to be reminded: %s", reminder.who, reminder.what) + if reminder.From == reminder.Who { + reminder.From = "you" + message = fmt.Sprintf("Hey %s, you wanted to be reminded: %s", reminder.Who, reminder.What) } else { - message = fmt.Sprintf("Hey %s, %s wanted you to be reminded: %s", reminder.who, reminder.from, reminder.what) + message = fmt.Sprintf("Hey %s, %s wanted you to be reminded: %s", reminder.Who, reminder.From, reminder.What) } - p.bot.Send(c, bot.Message, reminder.channel, message) + p.bot.Send(c, bot.Message, reminder.Channel, message) smsPlugin := sms.New(p.bot) - if err := smsPlugin.Send(reminder.who, message); err != nil { + if err := smsPlugin.Send(reminder.Who, message); err != nil { log.Error().Err(err).Msgf("could not send reminder") } - if err := p.deleteReminder(reminder.id); err != nil { - log.Error(). - Int64("id", reminder.id). + if err := p.deleteReminder(reminder.ID); err != nil { + log.Fatal(). + Uint64("ID", reminder.ID). Err(err). Msg("this will cause problems, we need to stop now.") } diff --git a/plugins/reminder/reminder_test.go b/plugins/reminder/reminder_test.go index 04a4622..01ecca7 100644 --- a/plugins/reminder/reminder_test.go +++ b/plugins/reminder/reminder_test.go @@ -32,15 +32,17 @@ func makeMessageBy(payload, by string) (bot.Connector, bot.Kind, msg.Message) { } } -func setup(t *testing.T) (*ReminderPlugin, *bot.MockBot) { +func setup(t *testing.T) (*ReminderPlugin, *bot.MockBot, func()) { mb := bot.NewMockBot() r := New(mb) - mb.DB().MustExec(`delete from reminders; delete from config;`) - return r, mb + return r, mb, func() { + mb.TearDown() + } } func TestMeReminder(t *testing.T) { - c, mb := setup(t) + c, mb, td := setup(t) + defer td() res := c.message(makeMessage("!remind me in 1s don't fail this test")) time.Sleep(2 * time.Second) assert.Len(t, mb.Messages, 2) @@ -50,7 +52,8 @@ func TestMeReminder(t *testing.T) { } func TestReminder(t *testing.T) { - c, mb := setup(t) + c, mb, td := setup(t) + defer td() res := c.message(makeMessage("!remind testuser in 1s don't fail this test")) time.Sleep(2 * time.Second) assert.Len(t, mb.Messages, 2) @@ -60,7 +63,8 @@ func TestReminder(t *testing.T) { } func TestReminderReorder(t *testing.T) { - c, mb := setup(t) + c, mb, td := setup(t) + defer td() 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")) @@ -74,7 +78,8 @@ func TestReminderReorder(t *testing.T) { } func TestReminderParse(t *testing.T) { - c, mb := setup(t) + c, mb, td := setup(t) + defer td() res := c.message(makeMessage("!remind testuser in unparseable don't fail this test")) assert.Len(t, mb.Messages, 1) assert.True(t, res) @@ -82,7 +87,8 @@ func TestReminderParse(t *testing.T) { } func TestEmptyList(t *testing.T) { - c, mb := setup(t) + c, mb, td := setup(t) + defer td() res := c.message(makeMessage("!list reminders")) assert.Len(t, mb.Messages, 1) assert.True(t, res) @@ -90,7 +96,8 @@ func TestEmptyList(t *testing.T) { } func TestList(t *testing.T) { - c, mb := setup(t) + c, mb, td := setup(t) + defer td() 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")) @@ -103,12 +110,13 @@ func TestList(t *testing.T) { } func TestListBy(t *testing.T) { - c, mb := setup(t) + c, mb, td := setup(t) + defer td() 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")) assert.True(t, res) - res = c.message(makeMessage("!list reminders from testuser")) + res = c.message(makeMessage("!list reminders From testuser")) assert.True(t, res) assert.Len(t, mb.Messages, 3) assert.Contains(t, mb.Messages[2], "don't fail this test 1 @ ") @@ -116,7 +124,8 @@ func TestListBy(t *testing.T) { } func TestListTo(t *testing.T) { - c, mb := setup(t) + c, mb, td := setup(t) + defer td() 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")) @@ -129,7 +138,8 @@ func TestListTo(t *testing.T) { } func TestToEmptyList(t *testing.T) { - c, mb := setup(t) + c, mb, td := setup(t) + defer td() 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")) @@ -141,19 +151,21 @@ func TestToEmptyList(t *testing.T) { } func TestFromEmptyList(t *testing.T) { - c, mb := setup(t) + c, mb, td := setup(t) + defer td() 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")) assert.True(t, res) - res = c.message(makeMessage("!list reminders from test")) + res = c.message(makeMessage("!list reminders From test")) assert.True(t, res) assert.Len(t, mb.Messages, 3) assert.Contains(t, mb.Messages[2], "no pending reminders") } func TestBatchMax(t *testing.T) { - c, mb := setup(t) + c, mb, td := setup(t) + defer td() c.config.Set("Reminder.MaxBatchAdd", "10") assert.NotNil(t, c) res := c.message(makeMessage("!remind testuser every 1h for 24h yikes")) @@ -170,7 +182,8 @@ func TestBatchMax(t *testing.T) { } func TestCancel(t *testing.T) { - c, mb := setup(t) + c, mb, td := setup(t) + defer td() assert.NotNil(t, c) res := c.message(makeMessage("!remind testuser in 1m don't fail this test")) assert.True(t, res) @@ -185,7 +198,8 @@ func TestCancel(t *testing.T) { } func TestCancelMiss(t *testing.T) { - c, mb := setup(t) + c, mb, td := setup(t) + defer td() assert.NotNil(t, c) res := c.message(makeMessage("!cancel reminder 1")) assert.True(t, res) @@ -194,7 +208,8 @@ func TestCancelMiss(t *testing.T) { } func TestLimitList(t *testing.T) { - c, mb := setup(t) + c, mb, td := setup(t) + defer td() c.config.Set("Reminder.MaxBatchAdd", "10") c.config.Set("Reminder.MaxList", "25") assert.NotNil(t, c) @@ -222,8 +237,9 @@ func TestLimitList(t *testing.T) { } func TestHelp(t *testing.T) { - c, mb := setup(t) + c, mb, td := setup(t) + defer td() assert.NotNil(t, c) - c.help(&cli.CliPlugin{}, bot.Help, msg.Message{Channel: "channel"}, []string{}) + c.help(&cli.CliPlugin{}, bot.Help, msg.Message{Channel: "Channel"}, []string{}) assert.Len(t, mb.Messages, 1) } diff --git a/util/bolthold/util.go b/util/bolthold/util.go new file mode 100644 index 0000000..f1d2628 --- /dev/null +++ b/util/bolthold/util.go @@ -0,0 +1,37 @@ +package main + +import ( + "flag" + bh "github.com/timshannon/bolthold" + "os" +) + +var ( + storePath = flag.String("store", "rathaus.store", "store file") + action = flag.String("action", "", "action to perform") + what = flag.String("what", "", "what to clean") +) + +func main() { + flag.Parse() + + if *action == "" { + flag.Usage() + os.Exit(1) + } + + store, err := bh.Open(*storePath, 0666, nil) + if err != nil { + panic(err) + } + defer store.Close() + + if *action == "clear" && *what != "" { + tx, _ := store.Bolt().Begin(true) + err := tx.DeleteBucket([]byte(*what)) + if err != nil { + panic(err) + } + tx.Commit() + } +}