This commit is contained in:
Chris Sexton 2021-12-20 23:31:19 -05:00
parent fafbd2ce64
commit 748e39c290
21 changed files with 445 additions and 286 deletions

View File

@ -10,7 +10,7 @@ jobs:
- name: Set up Go 1.16 - name: Set up Go 1.16
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:
go-version: '^1.18' go-version: '^1.17'
id: go id: go
- name: Check out code into the Go module directory - name: Check out code into the Go module directory

View File

@ -308,18 +308,18 @@ func (b *bot) SetQuiet(status bool) {
b.quiet = status b.quiet = status
} }
type blacklistItem struct { type BlacklistItem struct {
Channel string Channel string
Name string Name string
} }
type whitelistItem struct { type WhitelistItem struct {
Name string Name string
} }
// RefreshPluginBlacklist loads data for which plugins are disabled for particular channels // RefreshPluginBlacklist loads data for which plugins are disabled for particular channels
func (b *bot) RefreshPluginBlacklist() error { func (b *bot) RefreshPluginBlacklist() error {
blacklistItems := []blacklistItem{} blacklistItems := []BlacklistItem{}
if err := b.Store().Find(&blacklistItems, &bh.Query{}); err != nil { if err := b.Store().Find(&blacklistItems, &bh.Query{}); err != nil {
return fmt.Errorf("%w", err) return fmt.Errorf("%w", err)
} }
@ -333,7 +333,7 @@ func (b *bot) RefreshPluginBlacklist() error {
// RefreshPluginWhitelist loads data for which plugins are enabled // RefreshPluginWhitelist loads data for which plugins are enabled
func (b *bot) RefreshPluginWhitelist() error { func (b *bot) RefreshPluginWhitelist() error {
whitelistItems := []whitelistItem{ whitelistItems := []WhitelistItem{
{Name: "admin"}, // we must always ensure admin is on! {Name: "admin"}, // we must always ensure admin is on!
} }
if err := b.Store().Find(&whitelistItems, &bh.Query{}); err != nil { if err := b.Store().Find(&whitelistItems, &bh.Query{}); err != nil {

View File

@ -4,12 +4,14 @@ package bot
import ( import (
"fmt" "fmt"
bh "github.com/timshannon/bolthold"
"io/ioutil"
"net/http" "net/http"
"os"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"github.com/jmoiron/sqlx"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"github.com/velour/catbase/bot/msg" "github.com/velour/catbase/bot/msg"
@ -19,17 +21,19 @@ import (
type MockBot struct { type MockBot struct {
mock.Mock mock.Mock
db *sqlx.DB store *bh.Store
Cfg *config.Config Cfg *config.Config
Messages []string Messages []string
Actions []string Actions []string
Reactions []string Reactions []string
storeFile *os.File
} }
func (mb *MockBot) Config() *config.Config { return mb.Cfg } 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) Who(string) []user.User { return []user.User{} }
func (mb *MockBot) WhoAmI() string { return "tester" } func (mb *MockBot) WhoAmI() string { return "tester" }
func (mb *MockBot) DefaultConnector() Connector { return nil } 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 (mb *MockBot) RegisterFilter(s string, f func(string) string) {}
func NewMockBot() *MockBot { 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{ b := MockBot{
Cfg: cfg, Cfg: cfg,
Messages: make([]string, 0), Messages: make([]string, 0),
Actions: 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 // If any plugin registered a route, we need to reset those before any new test
http.DefaultServeMux = new(http.ServeMux) 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) CheckPassword(secret, password string) bool { return true }
func (mb *MockBot) ListenAndServe() {} func (mb *MockBot) ListenAndServe() {}
func (mb *MockBot) PubToASub(subject string, payload interface{}) {} func (mb *MockBot) PubToASub(subject string, payload interface{}) {}
func (mb *MockBot) TearDown() error {
mb.store.Close()
return os.Remove(mb.storeFile.Name())
}

View File

@ -31,9 +31,9 @@ type Config struct {
// Value is a config value that is loaded permanently and not ever displayed // Value is a config value that is loaded permanently and not ever displayed
type Value struct { type Value struct {
// Key is the key field of the table // 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 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) // Secret is a separate type (for storage differentiation)
@ -159,7 +159,7 @@ func (c *Config) Set(key, value string) error {
key = strings.ToLower(key) key = strings.ToLower(key)
value = strings.Trim(value, "`") value = strings.Trim(value, "`")
err := c.store.Update(key, Value{key, value}) err := c.store.Upsert(key, Value{key, value})
return err 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 // 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 == "" { if dbpath == "" {
dbpath = "catbase.db" dbpath = "catbase.db"
} }
store, err := bh.Open(strings.ReplaceAll(dbpath, ".db", ".store"), 0666, nil) store, err := bh.Open(storepath, 0666, nil)
if err != nil { if err != nil {
log.Fatal().Err(err).Msgf("could not open bolthold") log.Fatal().Err(err).Msgf("could not open bolthold")
} }

View File

@ -7,6 +7,7 @@ import (
"io" "io"
"math/rand" "math/rand"
"os" "os"
"strings"
"time" "time"
"github.com/velour/catbase/bot/msg" "github.com/velour/catbase/bot/msg"
@ -46,7 +47,6 @@ import (
"github.com/velour/catbase/plugins/fact" "github.com/velour/catbase/plugins/fact"
"github.com/velour/catbase/plugins/first" "github.com/velour/catbase/plugins/first"
"github.com/velour/catbase/plugins/git" "github.com/velour/catbase/plugins/git"
"github.com/velour/catbase/plugins/impossible"
"github.com/velour/catbase/plugins/inventory" "github.com/velour/catbase/plugins/inventory"
"github.com/velour/catbase/plugins/leftpad" "github.com/velour/catbase/plugins/leftpad"
"github.com/velour/catbase/plugins/nerdepedia" "github.com/velour/catbase/plugins/nerdepedia"
@ -92,7 +92,7 @@ func main() {
zerolog.SetGlobalLevel(zerolog.DebugLevel) zerolog.SetGlobalLevel(zerolog.DebugLevel)
} }
c := config.ReadConfig(*dbpath) c := config.ReadConfig(*dbpath, strings.ReplaceAll(*dbpath, ".db", ".store"))
if *key != "" && *val != "" { if *key != "" && *val != "" {
c.Set(*key, *val) c.Set(*key, *val)
@ -159,7 +159,7 @@ func main() {
b.AddPlugin(newsbid.New(b)) b.AddPlugin(newsbid.New(b))
b.AddPlugin(twitter.New(b)) b.AddPlugin(twitter.New(b))
b.AddPlugin(git.New(b)) b.AddPlugin(git.New(b))
b.AddPlugin(impossible.New(b)) //b.AddPlugin(impossible.New(b))
b.AddPlugin(cli.New(b)) b.AddPlugin(cli.New(b))
b.AddPlugin(aoc.New(b)) b.AddPlugin(aoc.New(b))
b.AddPlugin(meme.New(b)) b.AddPlugin(meme.New(b))

View File

@ -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 { 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 { func (p *AdminPlugin) rmWhitelist(plugin string) error {
if plugin == "admin" { if plugin == "admin" {
return fmt.Errorf("you cannot disable the admin plugin") 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 { func (p *AdminPlugin) addBlacklist(channel, plugin string) error {
if plugin == "admin" { if plugin == "admin" {
return fmt.Errorf("you cannot disable the admin plugin") 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 { func (p *AdminPlugin) rmBlacklist(channel, plugin string) error {
return p.modList(`delete from pluginBlacklist where channel=? and name=?`, channel, plugin) defer p.bot.RefreshPluginBlacklist()
} return p.store.Delete(channel+plugin, bot.BlacklistItem{
Channel: channel,
func (p *AdminPlugin) modList(query, channel, plugin string) error { Name: plugin,
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
} }

View File

@ -33,7 +33,7 @@ type BabblerPlugin struct {
} }
type Babbler struct { type Babbler struct {
BabblerId int64 `db:"id" boltholdid:"BabblerId"` BabblerID int64 `db:"id" boltholdid:"BabblerID"`
Name string `db:"babbler"` Name string `db:"babbler"`
} }
@ -44,8 +44,8 @@ func getBabbler(store *bh.Store, id int64) (*Babbler, error) {
} }
type BabblerWord struct { type BabblerWord struct {
WordId int64 `db:"id" boltholdid:"WordId"` WordID int64 `db:"ID" boltholdid:"WordID"`
Word string `db:"word"` Word string `db:"Word"`
} }
func getWord(store *bh.Store, id int64) (*BabblerWord, error) { 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 { type BabblerNode struct {
NodeId int64 `db:"id" boltholdid:"NodeId"` NodeId int64 `db:"ID" boltholdid:"NodeId"`
BabblerId int64 `db:"babblerId"` BabblerID int64 `db:"BabblerID"`
WordId int64 `db:"wordId"` WordID int64 `db:"WordID"`
Root int64 `db:"root"` Root int64 `db:"Root"`
RootFrequency int64 `db:"rootFrequency"` RootFrequency int64 `db:"RootFrequency"`
} }
func getNode(store *bh.Store, id int64) (*BabblerNode, error) { 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 { type BabblerArc struct {
ArcId int64 `db:"id" boltholdid:"ArcId"` ArcId int64 `db:"ID" boltholdid:"ArcId"`
FromNodeId int64 `db:"fromNodeId"` FromNodeId int64 `db:"FromNodeId"`
ToNodeId int64 `db:"toNodeId"` ToNodeId int64 `db:"ToNodeId"`
Frequency int64 `db:"frequency"` Frequency int64 `db:"Frequency"`
} }
func getArc(store *bh.Store, id int64) (*BabblerArc, error) { 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) { func (p *BabblerPlugin) getBabbler(name string) (*Babbler, error) {
var bblr Babbler 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 != nil {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
log.Error().Msg("failed to find babbler") 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) { func (p *BabblerPlugin) getWord(word string) (*BabblerWord, error) {
var w BabblerWord 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 != nil {
if err == bh.ErrNotFound { if err == bh.ErrNotFound {
return nil, NEVER_SAID return nil, NEVER_SAID
@ -271,7 +271,7 @@ func (p *BabblerPlugin) getBabblerNode(babbler *Babbler, word string) (*BabblerN
} }
var node BabblerNode 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 != nil {
if err == bh.ErrNotFound { if err == bh.ErrNotFound {
return nil, NEVER_SAID return nil, NEVER_SAID
@ -289,7 +289,7 @@ func (p *BabblerPlugin) createBabblerNode(babbler *Babbler, word string) (*Babbl
} }
bn := &BabblerNode{ bn := &BabblerNode{
WordId: w.WordId, WordID: w.WordID,
Root: 0, Root: 0,
RootFrequency: 0, RootFrequency: 0,
} }
@ -317,7 +317,7 @@ func (p *BabblerPlugin) incrementRootWordFrequency(babbler *Babbler, word string
log.Error().Err(err) log.Error().Err(err)
return nil, 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 := record.(BabblerNode)
r.RootFrequency += 1 r.RootFrequency += 1
r.Root = 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) { func (p *BabblerPlugin) getBabblerArc(fromNode, toNode *BabblerNode) (*BabblerArc, error) {
var arc BabblerArc 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 != nil {
if err == bh.ErrNotFound { if err == bh.ErrNotFound {
return nil, NEVER_SAID 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) { func (p *BabblerPlugin) incrementWordArc(fromNode, toNode *BabblerNode) (*BabblerArc, error) {
affectedRows := 0 affectedRows := 0
err := p.store.UpdateMatching(BabblerArc{}, 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 { func(record interface{}) error {
affectedRows++ affectedRows++
r := record.(BabblerArc) 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) { func (p *BabblerPlugin) getWeightedRootNode(babbler *Babbler) (*BabblerNode, *BabblerWord, error) {
rootNodes := []*BabblerNode{} 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 { if err != nil {
log.Error().Err(err) log.Error().Err(err)
return nil, nil, err return nil, nil, err
@ -435,7 +435,7 @@ func (p *BabblerPlugin) getWeightedRootNode(babbler *Babbler) (*BabblerNode, *Ba
for _, node := range rootNodes { for _, node := range rootNodes {
total += node.RootFrequency total += node.RootFrequency
if total >= which { if total >= which {
w, err := getWord(p.store, node.WordId) w, err := getWord(p.store, node.WordID)
if err != nil { if err != nil {
log.Error().Err(err) log.Error().Err(err)
return nil, nil, 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) { func (p *BabblerPlugin) getWeightedNextWord(fromNode *BabblerNode) (*BabblerNode, *BabblerWord, error) {
arcs := []BabblerArc{} 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 { if err != nil {
log.Error().Err(err) log.Error().Err(err)
return nil, nil, err return nil, nil, err
@ -477,7 +477,7 @@ func (p *BabblerPlugin) getWeightedNextWord(fromNode *BabblerNode) (*BabblerNode
return nil, nil, err return nil, nil, err
} }
w, err := getWord(p.store, node.WordId) w, err := getWord(p.store, node.WordID)
if err != nil { if err != nil {
log.Error().Err(err) log.Error().Err(err)
return nil, nil, 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) { func (p *BabblerPlugin) getWeightedPreviousWord(toNode *BabblerNode) (*BabblerNode, *BabblerWord, bool, error) {
arcs := []*BabblerArc{} 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 { if err != nil {
log.Error().Err(err) log.Error().Err(err)
return nil, nil, false, err return nil, nil, false, err
@ -525,7 +525,7 @@ func (p *BabblerPlugin) getWeightedPreviousWord(toNode *BabblerNode) (*BabblerNo
return nil, nil, false, err return nil, nil, false, err
} }
w, err := getWord(p.store, node.WordId) w, err := getWord(p.store, node.WordID)
if err != nil { if err != nil {
log.Error().Err(err) log.Error().Err(err)
return nil, nil, false, err return nil, nil, false, err
@ -625,7 +625,7 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa
mapping := map[int64]*BabblerNode{} mapping := map[int64]*BabblerNode{}
nodes := []*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 { if err != nil {
log.Error().Err(err) log.Error().Err(err)
return err return err
@ -633,31 +633,31 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa
for _, node := range nodes { for _, node := range nodes {
if node.NodeId == otherNode.NodeId { if node.NodeId == otherNode.NodeId {
node.WordId = intoNode.WordId node.WordID = intoNode.WordID
} }
affected := 0 affected := 0
if node.Root > 0 { if node.Root > 0 {
err = p.store.UpdateMatching(BabblerNode{}, 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 { func(record interface{}) error {
affected++ affected++
r := record.(BabblerNode) r := record.(BabblerNode)
r.RootFrequency += node.RootFrequency r.RootFrequency += node.RootFrequency
r.Root = 1 r.Root = 1
return p.store.Update(r.BabblerId, r) return p.store.Update(r.BabblerID, r)
}) })
if err != nil { if err != nil {
log.Error().Err(err) log.Error().Err(err)
} }
} else { } else {
err = p.store.UpdateMatching(BabblerNode{}, 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 { func(record interface{}) error {
affected++ affected++
r := record.(BabblerNode) r := record.(BabblerNode)
r.RootFrequency += node.RootFrequency r.RootFrequency += node.RootFrequency
return p.store.Update(r.BabblerId, r) return p.store.Update(r.BabblerID, r)
}) })
if err != nil { if err != nil {
log.Error().Err(err) log.Error().Err(err)
@ -665,7 +665,7 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa
} }
if err != nil || affected == 0 { if err != nil || affected == 0 {
node.BabblerId = intoBabbler.BabblerId node.BabblerID = intoBabbler.BabblerID
err = p.store.Insert(bh.NextSequence(), &node) err = p.store.Insert(bh.NextSequence(), &node)
if err != nil { if err != nil {
log.Error().Err(err) log.Error().Err(err)
@ -675,7 +675,7 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa
var updatedNode BabblerNode var updatedNode BabblerNode
err = p.store.FindOne(&updatedNode, 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 { if err != nil {
log.Error().Err(err) log.Error().Err(err)
return err return err
@ -686,7 +686,7 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa
for oldNodeId, newNode := range mapping { for oldNodeId, newNode := range mapping {
arcs := []*BabblerArc{} 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 { if err != nil {
return err return err
} }
@ -749,7 +749,7 @@ func (p *BabblerPlugin) babbleSeedSuffix(babblerName string, seed []string) (str
func (p *BabblerPlugin) getNextArcs(babblerNodeId int64) ([]*BabblerArc, error) { func (p *BabblerPlugin) getNextArcs(babblerNodeId int64) ([]*BabblerArc, error) {
arcs := []*BabblerArc{} 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 { if err != nil {
log.Error().Err(err) log.Error().Err(err)
return arcs, err return arcs, err
@ -845,7 +845,7 @@ func (p *BabblerPlugin) babbleSeedBookends(babblerName string, start, end []stri
log.Error().Err(err) log.Error().Err(err)
return "", err return "", err
} }
w, err := getWord(p.store, cur.WordId) w, err := getWord(p.store, cur.WordID)
if err != nil { if err != nil {
log.Error().Err(err) log.Error().Err(err)
return "", err return "", err

View File

@ -216,7 +216,7 @@ func (p *BeersPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message,
return true 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 // TODO: really ought to have an ID here
booze, _ := counter.GetUserItem(store, user, id, itemName) booze, _ := counter.GetUserItem(store, user, id, itemName)
return booze return booze

View File

@ -4,6 +4,7 @@ import (
"database/sql" "database/sql"
"fmt" "fmt"
bh "github.com/timshannon/bolthold" bh "github.com/timshannon/bolthold"
"math"
"math/rand" "math/rand"
"regexp" "regexp"
"strconv" "strconv"
@ -25,9 +26,10 @@ type CounterPlugin struct {
} }
type Item struct { type Item struct {
*bh.Store store *bh.Store
created bool
ID int64 ID uint64 `boltholdKey:"ID"`
Nick string Nick string
Item string Item string
Count int Count int
@ -35,11 +37,12 @@ type Item struct {
} }
type alias struct { type alias struct {
*bh.Store store *bh.Store
created bool
ID int64 ID uint64 `boltholdKey:"ID"`
Item string Item string
PointsTo string `db:"points_to"` PointsTo string `db:"PointsTo"`
} }
// GetItems returns all counters // 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 // Don't forget to embed the db into all of that shiz
for i := range items { for i := range items {
items[i].Store = store items[i].store = store
} }
return items, nil return items, nil
} }
@ -62,16 +65,16 @@ func GetItems(store *bh.Store, nick, id string) ([]Item, error) {
var err error var err error
q := &bh.Query{} q := &bh.Query{}
if id != "" { if id != "" {
q = bh.Where("userid").Eq(id) q = bh.Where("UserID").Eq(id)
} else { } else {
q = bh.Where("nick").Eq(nick) q = bh.Where("Nick").Eq(nick)
} }
if err = store.Find(&items, q); err != nil { if err = store.Find(&items, q); err != nil {
return nil, err return nil, err
} }
// Don't forget to embed the db into all of that shiz // Don't forget to embed the db into all of that shiz
for i := range items { for i := range items {
items[i].Store = store items[i].store = store
} }
return items, nil return items, nil
} }
@ -86,7 +89,7 @@ func LeaderAll(store *bh.Store) ([]Item, error) {
return nil, err return nil, err
} }
for i := range items { for i := range items {
items[i].Store = store items[i].store = store
} }
return items, nil 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` //s := `select * from counter where item=? order by count desc`
// todo: remove that when we verify this works // todo: remove that when we verify this works
var items []Item 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 { if err != nil {
return nil, err return nil, err
} }
for i := range items { for i := range items {
items[i].Store = store items[i].store = store
} }
return items, nil return items, nil
} }
func RmAlias(store *bh.Store, aliasName string) error { 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) { func MkAlias(store *bh.Store, aliasName, pointsTo string) (*alias, error) {
aliasName = strings.ToLower(aliasName) aliasName = strings.ToLower(aliasName)
pointsTo = strings.ToLower(pointsTo) pointsTo = strings.ToLower(pointsTo)
alias := &alias{ alias := &alias{
Store: store, store: store,
Item: aliasName, Item: aliasName,
PointsTo: pointsTo, PointsTo: pointsTo,
created: true,
} }
err := store.Insert(bh.NextSequence(), alias) err := store.Insert(bh.NextSequence(), alias)
return alias, err return alias, err
@ -127,13 +135,13 @@ func GetItem(store *bh.Store, itemName string) ([]Item, error) {
itemName = trimUnicode(itemName) itemName = trimUnicode(itemName)
var items []Item var items []Item
var a alias 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 itemName = a.PointsTo
} else { } else {
log.Error().Err(err).Interface("alias", a) 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 { if err != nil {
return nil, err return nil, err
} }
@ -142,32 +150,45 @@ func GetItem(store *bh.Store, itemName string) ([]Item, error) {
Interface("items", items). Interface("items", items).
Msg("got item") Msg("got item")
for _, i := range items { for _, i := range items {
i.Store = store i.store = store
} }
return items, nil return items, nil
} }
// GetUserItem returns a specific counter for a subject // 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) itemName = trimUnicode(itemName)
var item Item item := &Item{}
item.Store = store item.Nick = nick
item.Item = itemName
item.UserID = id
var a alias 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 itemName = a.PointsTo
item.Item = itemName
} else { } 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 != "" { 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 && err == bh.ErrNotFound {
if err != nil { log.Debug().Msgf("We gotta create this item: %v", item)
return Item{}, err 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(). log.Debug().
Str("nick", nick). Str("nick", nick).
Str("id", id). 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 // GetUserItem returns a specific counter for a subject
// Create saves a counter // Create saves a counter
func (i *Item) Create() error { 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 return err
} }
@ -188,17 +212,19 @@ func (i *Item) Create() error {
// This will create or delete the item if necessary // This will create or delete the item if necessary
func (i *Item) Update(r *bot.Request, value int) error { func (i *Item) Update(r *bot.Request, value int) error {
i.Count = value i.Count = value
if i.Count == 0 && i.ID != -1 { if i.Count == 0 && i.created {
return i.Delete() return i.Delete()
} }
if i.ID == -1 { if !i.created {
i.Create() if err := i.Create(); err != nil {
log.Error().Err(err).Msg("could not create")
}
} }
log.Debug(). log.Debug().
Interface("i", i). Interface("i", i).
Int("value", value). Int("value", value).
Msg("Updating item") Msg("Updating item")
err := i.Store.Update(i.ID, i) err := i.store.Update(i.ID, i)
if err == nil { if err == nil {
sendUpdate(r, i.Nick, i.Item, i.Count) 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 // Delete removes a counter from the database
func (i *Item) Delete() error { func (i *Item) Delete() error {
err := i.Store.Delete(i.ID, Item{}) err := i.store.Delete(i.ID, Item{})
i.ID = -1 i.created = false
i.ID = math.MaxUint64
return err 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)) p.b.Send(r.Conn, bot.Message, fmt.Sprintf("You must provide all fields for an alias: %s", mkAliasRegex))
return true 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") 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.") 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 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", p.b.Send(r.Conn, bot.Message, r.Msg.Channel, fmt.Sprintf("Created alias %s -> %s",
what, to)) what, to))
return true return true
@ -416,6 +445,8 @@ func (p *CounterPlugin) inspectCmd(r bot.Request) bool {
return true return true
} }
log.Debug().Msgf("items: %v", items)
resp := fmt.Sprintf("%s has the following counters:", nick) resp := fmt.Sprintf("%s has the following counters:", nick)
count := 0 count := 0
for _, it := range items { for _, it := range items {
@ -483,7 +514,6 @@ func (p *CounterPlugin) countCmd(r bot.Request) bool {
nick, id = p.resolveUser(r, r.Values["who"]) nick, id = p.resolveUser(r, r.Values["who"])
} }
var item Item
item, err := GetUserItem(p.store, nick, id, itemName) item, err := GetUserItem(p.store, nick, id, itemName)
switch { switch {
case err == sql.ErrNoRows: case err == sql.ErrNoRows:
@ -515,7 +545,8 @@ func (p *CounterPlugin) incrementCmd(r bot.Request) bool {
channel := r.Msg.Channel channel := r.Msg.Channel
// ++ those fuckers // ++ those fuckers
item, err := GetUserItem(p.store, nick, id, itemName) 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(). log.Error().
Err(err). Err(err).
Str("nick", nick). Str("nick", nick).

View File

@ -17,13 +17,16 @@ import (
"github.com/velour/catbase/bot/user" "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() mb := bot.NewMockBot()
c := New(mb) c := New(mb)
mb.DB().MustExec(`delete from counter; delete from counter_alias;`) _, err := MkAlias(mb.Store(), "tea", ":tea:")
_, err := MkAlias(mb.DB(), "tea", ":tea:")
assert.Nil(t, err) 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 { 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) { func TestMkAlias(t *testing.T) {
mb, c := setup(t) mb, c, td := setup(t)
defer td()
assert.NotNil(t, c) assert.NotNil(t, c)
c.mkAliasCmd(makeMessage("mkalias fuck mornings", mkAliasRegex)) c.mkAliasCmd(makeMessage("mkalias fuck mornings", mkAliasRegex))
c.incrementCmd(makeMessage("fuck++", incrementRegex)) 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.Nil(t, err)
assert.Equal(t, 1, item.Count) assert.Equal(t, 1, item.Count)
} }
func TestRmAlias(t *testing.T) { func TestRmAlias(t *testing.T) {
mb, c := setup(t) mb, c, td := setup(t)
defer td()
assert.NotNil(t, c) assert.NotNil(t, c)
c.mkAliasCmd(makeMessage("mkalias fuck mornings", mkAliasRegex)) c.mkAliasCmd(makeMessage("mkalias fuck mornings", mkAliasRegex))
c.rmAliasCmd(makeMessage("rmalias fuck", rmAliasRegex)) c.rmAliasCmd(makeMessage("rmalias fuck", rmAliasRegex))
c.incrementCmd(makeMessage("fuck++", incrementRegex)) 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.Nil(t, err)
assert.Equal(t, 0, item.Count) assert.Equal(t, 0, item.Count)
} }
func TestThreeSentencesExists(t *testing.T) { func TestThreeSentencesExists(t *testing.T) {
mb, c := setup(t) mb, c, td := setup(t)
defer td()
assert.NotNil(t, c) assert.NotNil(t, c)
c.incrementCmd(makeMessage(":beer:++", incrementRegex)) c.incrementCmd(makeMessage(":beer:++", incrementRegex))
c.teaMatchCmd(makeMessage(":beer:. Earl Grey. Hot.", teaRegex)) 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.Nil(t, err)
assert.Equal(t, 2, item.Count) assert.Equal(t, 2, item.Count)
} }
func TestThreeSentencesNotExists(t *testing.T) { func TestThreeSentencesNotExists(t *testing.T) {
mb, c := setup(t) mb, c, td := setup(t)
defer td()
assert.NotNil(t, c) 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)) 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.Nil(t, err)
assert.Equal(t, 0, item.Count) assert.Equal(t, 0, item.Count)
} }
func TestTeaEarlGreyHot(t *testing.T) { func TestTeaEarlGreyHot(t *testing.T) {
mb, c := setup(t) mb, c, td := setup(t)
defer td()
assert.NotNil(t, c) assert.NotNil(t, c)
c.teaMatchCmd(makeMessage("Tea. Earl Grey. Hot.", teaRegex)) c.teaMatchCmd(makeMessage("Tea. Earl Grey. Hot.", teaRegex))
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.Nil(t, err)
assert.Equal(t, 2, item.Count) assert.Equal(t, 2, item.Count)
} }
func TestTeaTwoPeriods(t *testing.T) { func TestTeaTwoPeriods(t *testing.T) {
mb, c := setup(t) mb, c, td := setup(t)
defer td()
assert.NotNil(t, c) assert.NotNil(t, c)
c.teaMatchCmd(makeMessage("Tea. Earl Grey.", teaRegex)) c.teaMatchCmd(makeMessage("Tea. Earl Grey.", teaRegex))
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.Nil(t, err)
assert.Equal(t, 0, item.Count) assert.Equal(t, 0, item.Count)
} }
func TestTeaMultiplePeriods(t *testing.T) { func TestTeaMultiplePeriods(t *testing.T) {
mb, c := setup(t) mb, c, td := setup(t)
defer td()
assert.NotNil(t, c) assert.NotNil(t, c)
c.teaMatchCmd(makeMessage("Tea. Earl Grey. Spiked. Hot.", teaRegex)) c.teaMatchCmd(makeMessage("Tea. Earl Grey. Spiked. Hot.", teaRegex))
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.Nil(t, err)
assert.Equal(t, 2, item.Count) assert.Equal(t, 2, item.Count)
} }
func TestTeaGreenHot(t *testing.T) { func TestTeaGreenHot(t *testing.T) {
mb, c := setup(t) mb, c, td := setup(t)
defer td()
assert.NotNil(t, c) assert.NotNil(t, c)
c.teaMatchCmd(makeMessage("Tea. Green. Hot.", teaRegex)) c.teaMatchCmd(makeMessage("Tea. Green. Hot.", teaRegex))
c.teaMatchCmd(makeMessage("Tea. Green. Hot", teaRegex)) c.teaMatchCmd(makeMessage("Tea. Green. Hot", teaRegex))
c.teaMatchCmd(makeMessage("Tea. Green. Iced.", 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.Nil(t, err)
assert.Equal(t, 3, item.Count) assert.Equal(t, 3, item.Count)
} }
func TestTeaUnrelated(t *testing.T) { func TestTeaUnrelated(t *testing.T) {
mb, c := setup(t) mb, c, td := setup(t)
defer td()
assert.NotNil(t, c) assert.NotNil(t, c)
c.teaMatchCmd(makeMessage("Tea.", teaRegex)) c.teaMatchCmd(makeMessage("Tea.", teaRegex))
c.teaMatchCmd(makeMessage("Tea. It's great.", 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.Nil(t, err)
assert.Equal(t, 0, item.Count) assert.Equal(t, 0, item.Count)
} }
func TestTeaSkieselQuote(t *testing.T) { func TestTeaSkieselQuote(t *testing.T) {
mb, c := setup(t) mb, c, td := setup(t)
defer td()
assert.NotNil(t, c) 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)) 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.Nil(t, err)
assert.Equal(t, 0, item.Count) assert.Equal(t, 0, item.Count)
} }
func TestTeaUnicodeJapanese(t *testing.T) { func TestTeaUnicodeJapanese(t *testing.T) {
mb, c := setup(t) mb, c, td := setup(t)
defer td()
assert.NotNil(t, c) assert.NotNil(t, c)
c.teaMatchCmd(makeMessage("Tea. おちや. Hot.", teaRegex)) 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.Nil(t, err)
assert.Equal(t, 1, item.Count) assert.Equal(t, 1, item.Count)
} }
func TestResetMe(t *testing.T) { func TestResetMe(t *testing.T) {
mb, c := setup(t) mb, c, td := setup(t)
defer td()
assert.NotNil(t, c) assert.NotNil(t, c)
c.incrementCmd(makeMessage("test++", incrementRegex)) c.incrementCmd(makeMessage("test++", incrementRegex))
c.resetCmd(makeMessage("!reset me", resetRegex)) 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.Nil(t, err)
assert.Len(t, items, 0) assert.Len(t, items, 0)
} }
func TestCounterOne(t *testing.T) { func TestCounterOne(t *testing.T) {
mb, c := setup(t) mb, c, td := setup(t)
defer td()
assert.NotNil(t, c) assert.NotNil(t, c)
c.incrementCmd(makeMessage("test++", incrementRegex)) c.incrementCmd(makeMessage("test++", incrementRegex))
assert.Len(t, mb.Messages, 1) assert.Len(t, mb.Messages, 1)
@ -171,7 +187,8 @@ func TestCounterOne(t *testing.T) {
} }
func TestCounterOneWithSpace(t *testing.T) { func TestCounterOneWithSpace(t *testing.T) {
mb, c := setup(t) mb, c, td := setup(t)
defer td()
assert.NotNil(t, c) assert.NotNil(t, c)
c.incrementCmd(makeMessage(":test: ++", incrementRegex)) c.incrementCmd(makeMessage(":test: ++", incrementRegex))
assert.Len(t, mb.Messages, 1) assert.Len(t, mb.Messages, 1)
@ -179,7 +196,8 @@ func TestCounterOneWithSpace(t *testing.T) {
} }
func TestCounterFour(t *testing.T) { func TestCounterFour(t *testing.T) {
mb, c := setup(t) mb, c, td := setup(t)
defer td()
assert.NotNil(t, c) assert.NotNil(t, c)
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
c.incrementCmd(makeMessage("test++", incrementRegex)) c.incrementCmd(makeMessage("test++", incrementRegex))
@ -189,7 +207,8 @@ func TestCounterFour(t *testing.T) {
} }
func TestCounterDecrement(t *testing.T) { func TestCounterDecrement(t *testing.T) {
mb, c := setup(t) mb, c, td := setup(t)
defer td()
assert.NotNil(t, c) assert.NotNil(t, c)
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
c.incrementCmd(makeMessage("test++", incrementRegex)) c.incrementCmd(makeMessage("test++", incrementRegex))
@ -201,7 +220,8 @@ func TestCounterDecrement(t *testing.T) {
} }
func TestFriendCounterDecrement(t *testing.T) { func TestFriendCounterDecrement(t *testing.T) {
mb, c := setup(t) mb, c, td := setup(t)
defer td()
assert.NotNil(t, c) assert.NotNil(t, c)
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
c.incrementCmd(makeMessage("other.test++", incrementRegex)) c.incrementCmd(makeMessage("other.test++", incrementRegex))
@ -213,7 +233,8 @@ func TestFriendCounterDecrement(t *testing.T) {
} }
func TestDecrementZero(t *testing.T) { func TestDecrementZero(t *testing.T) {
mb, c := setup(t) mb, c, td := setup(t)
defer td()
assert.NotNil(t, c) assert.NotNil(t, c)
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
c.incrementCmd(makeMessage("test++", incrementRegex)) c.incrementCmd(makeMessage("test++", incrementRegex))
@ -230,7 +251,8 @@ func TestDecrementZero(t *testing.T) {
} }
func TestClear(t *testing.T) { func TestClear(t *testing.T) {
mb, c := setup(t) mb, c, td := setup(t)
defer td()
assert.NotNil(t, c) assert.NotNil(t, c)
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
c.incrementCmd(makeMessage("test++", incrementRegex)) c.incrementCmd(makeMessage("test++", incrementRegex))
@ -243,7 +265,8 @@ func TestClear(t *testing.T) {
} }
func TestCount(t *testing.T) { func TestCount(t *testing.T) {
mb, c := setup(t) mb, c, td := setup(t)
defer td()
assert.NotNil(t, c) assert.NotNil(t, c)
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
c.incrementCmd(makeMessage("test++", incrementRegex)) c.incrementCmd(makeMessage("test++", incrementRegex))
@ -257,7 +280,8 @@ func TestCount(t *testing.T) {
} }
func TestInspectMe(t *testing.T) { func TestInspectMe(t *testing.T) {
mb, c := setup(t) mb, c, td := setup(t)
defer td()
assert.NotNil(t, c) assert.NotNil(t, c)
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
c.incrementCmd(makeMessage("test++", incrementRegex)) c.incrementCmd(makeMessage("test++", incrementRegex))
@ -278,7 +302,8 @@ func TestInspectMe(t *testing.T) {
} }
func TestHelp(t *testing.T) { func TestHelp(t *testing.T) {
mb, c := setup(t) mb, c, td := setup(t)
defer td()
assert.NotNil(t, c) 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.Greater(t, len(mb.Messages), 1) assert.Greater(t, len(mb.Messages), 1)

View File

@ -50,7 +50,7 @@ func (a *alias) resolve(store *bh.Store) (*Factoid, error) {
// todo: remove this query // todo: remove this query
//q := `select fact, next from factoid_alias where fact=?` //q := `select fact, next from factoid_alias where fact=?`
var next alias 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 { if err != nil {
// we hit the end of the chain, get a factoid named Next // we hit the end of the chain, get a factoid named Next
fact, err := GetSingleFact(store, a.Next) fact, err := GetSingleFact(store, a.Next)
@ -67,7 +67,7 @@ func findAlias(store *bh.Store, fact string) (bool, *Factoid) {
// todo: remove this query // todo: remove this query
//q := `select * from factoid_alias where fact=?` //q := `select * from factoid_alias where fact=?`
var a alias var a alias
err := store.FindOne(&a, bh.Where("fact").Eq(fact)) err := store.FindOne(&a, bh.Where("Fact").Eq(fact))
if err != nil { if err != nil {
return false, 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 { func (a *alias) save(store *bh.Store) error {
//q := `select * from factoid_alias where fact=?` //q := `select * from factoid_alias where fact=?`
var offender alias 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 { if err == nil {
return fmt.Errorf("DANGER: an opposite alias already exists") 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) { func getFacts(store *bh.Store, fact string, tidbit string) ([]*Factoid, error) {
var fs []*Factoid 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 { if err != nil {
log.Error().Err(err).Msg("Error regexping for facts") log.Error().Err(err).Msg("Error regexping for facts")
return nil, err return nil, err
@ -142,9 +142,12 @@ func GetSingle(store *bh.Store) (*Factoid, error) {
func GetSingleFact(store *bh.Store, fact string) (*Factoid, error) { func GetSingleFact(store *bh.Store, fact string) (*Factoid, error) {
var allMatching []Factoid 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 return nil, err
} }
if len(allMatching) == 0 {
return nil, fmt.Errorf("no entries")
}
f := allMatching[rand.Intn(len(allMatching))] f := allMatching[rand.Intn(len(allMatching))]
return &f, nil 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 { if err != nil {
log.Error().Err(err).Msg("Error counting facts") log.Error().Err(err).Msg("Error counting facts")
return fmt.Errorf("What?") return fmt.Errorf("What?")

View File

@ -27,13 +27,13 @@ type FirstPlugin struct {
} }
type FirstEntry struct { type FirstEntry struct {
id int64 ID uint64 `boltholdKey:"ID"`
day time.Time Day time.Time
time time.Time Time time.Time
channel string Channel string
body string Body string
nick string Nick string
saved bool Saved bool
} }
// Insert or update the first entry // 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 { 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 // NewFirstPlugin creates a new FirstPlugin with the Plugin interface
func New(b bot.Bot) *FirstPlugin { 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())) Midnight(time.Now()))
fp := &FirstPlugin{ fp := &FirstPlugin{
@ -63,11 +63,11 @@ func New(b bot.Bot) *FirstPlugin {
func getLastFirst(store *bh.Store, channel string) (*FirstEntry, error) { func getLastFirst(store *bh.Store, channel string) (*FirstEntry, error) {
fe := &FirstEntry{} fe := &FirstEntry{}
err := store.FindOne(fe, bh.Where("channel").Eq(channel)) err := store.FindOne(fe, bh.Where("Channel").Eq(channel))
if err != nil { if err != nil {
return nil, err 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 return fe, nil
} }
@ -80,7 +80,7 @@ func (p *FirstPlugin) isNotToday(f *FirstEntry) bool {
if f == nil { if f == nil {
return true return true
} }
t := f.time t := f.Time
t0 := Midnight(t) t0 := Midnight(t)
jitter := time.Duration(p.config.GetInt("first.jitter", 0)) jitter := time.Duration(p.config.GetInt("first.jitter", 0))
t0 = t0.Add(jitter * time.Millisecond) t0 = t0.Add(jitter * time.Millisecond)
@ -138,7 +138,7 @@ func (p *FirstPlugin) register() {
log.Debug().Msgf("Re-enabled first") log.Debug().Msgf("Re-enabled first")
}() }()
p.bot.Send(r.Conn, bot.Message, r.Msg.Channel, 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 return true
}}, }},
{Kind: bot.Message, IsCmd: false, {Kind: bot.Message, IsCmd: false,
@ -161,7 +161,7 @@ func (p *FirstPlugin) register() {
if (first == nil || p.isNotToday(first)) && p.allowed(r.Msg) { if (first == nil || p.isNotToday(first)) && p.allowed(r.Msg) {
log.Debug(). log.Debug().
Str("body", r.Msg.Body). Str("Body", r.Msg.Body).
Interface("t0", first). Interface("t0", first).
Time("t1", time.Now()). Time("t1", time.Now()).
Msg("Recording first") Msg("Recording first")
@ -196,7 +196,7 @@ func (p *FirstPlugin) allowed(message msg.Message) bool {
if match { if match {
log.Info(). log.Info().
Str("user", message.User.Name). Str("user", message.User.Name).
Str("body", message.Body). Str("Body", message.Body).
Msg("Disallowing first") Msg("Disallowing first")
return false return false
} }
@ -205,7 +205,7 @@ func (p *FirstPlugin) allowed(message msg.Message) bool {
if host == message.Host { if host == message.Host {
log.Info(). log.Info().
Str("user", message.User.Name). Str("user", message.User.Name).
Str("body", message.Body). Str("Body", message.Body).
Msg("Disallowing first") Msg("Disallowing first")
return false return false
} }
@ -214,7 +214,7 @@ func (p *FirstPlugin) allowed(message msg.Message) bool {
if nick == message.User.Name { if nick == message.User.Name {
log.Info(). log.Info().
Str("user", message.User.Name). Str("user", message.User.Name).
Str("body", message.Body). Str("Body", message.Body).
Msg("Disallowing first") Msg("Disallowing first")
return false return false
} }
@ -224,18 +224,18 @@ func (p *FirstPlugin) allowed(message msg.Message) bool {
func (p *FirstPlugin) recordFirst(c bot.Connector, message msg.Message) { func (p *FirstPlugin) recordFirst(c bot.Connector, message msg.Message) {
log.Info(). log.Info().
Str("channel", message.Channel). Str("Channel", message.Channel).
Str("user", message.User.Name). Str("user", message.User.Name).
Str("body", message.Body). Str("Body", message.Body).
Msg("Recording first") Msg("Recording first")
first := &FirstEntry{ first := &FirstEntry{
day: Midnight(time.Now()), Day: Midnight(time.Now()),
time: time.Now(), Time: time.Now(),
channel: message.Channel, Channel: message.Channel,
body: message.Body, Body: message.Body,
nick: message.User.Name, Nick: message.User.Name,
} }
log.Info().Msgf("recordFirst: %+v", first.day) log.Info().Msgf("recordFirst: %+v", first.Day)
err := first.save(p.store) err := first.save(p.store)
if err != nil { if err != nil {
log.Error().Err(err).Msg("Error saving first entry") 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 { func (p *FirstPlugin) leaderboard(c bot.Connector, ch string) error {
// todo: remove this once we verify stuff // 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 // from first
// group by channel, nick // group by Channel, Nick
// having channel = ? // having Channel = ?
// order by count desc // order by count desc
// limit 3` // 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 { if err != nil {
return err 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) { 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\"", 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. // Help responds to help requests. Every plugin must implement a help function.

View File

@ -237,7 +237,7 @@ func parseCmd(r *regexp.Regexp, body string) cmd {
} }
type goal struct { type goal struct {
ID int64 `boltholdid:"ID"` ID uint64 `boltholdid:"ID"`
Kind string Kind string
Who string Who string
What string What string
@ -246,9 +246,9 @@ type goal struct {
gp *GoalsPlugin gp *GoalsPlugin
} }
func (p *GoalsPlugin) newGoal(kind, who, what string, amount int) goal { func (p *GoalsPlugin) newGoal(kind, who, what string, amount int) *goal {
return goal{ return &goal{
ID: -1, ID: 0,
Kind: kind, Kind: kind,
Who: who, Who: who,
What: what, 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) { func (p *GoalsPlugin) getGoal(who, what string) ([]*goal, error) {
gs := []*goal{} 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 { if err != nil {
return nil, err 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) { func (p *GoalsPlugin) getGoalKind(kind, who, what string) (*goal, error) {
g := &goal{gp: p} 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 { if err != nil {
return nil, err return nil, err
} }
@ -279,7 +279,7 @@ func (p *GoalsPlugin) getGoalKind(kind, who, what string) (*goal, error) {
} }
func (g *goal) Save() 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 { if err != nil {
return err return err
} }
@ -287,7 +287,7 @@ func (g *goal) Save() error {
} }
func (g goal) Delete() error { func (g goal) Delete() error {
if g.ID == -1 { if g.ID == 0 {
return nil return nil
} }
err := g.gp.store.Delete(goal{}, g.ID) 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 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()) today := float64(now().YearDay())
thisYear := time.Date(now().Year(), 0, 0, 0, 0, 0, 0, time.UTC) 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) 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 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) remaining := p.calculateRemaining(i, g)
txt := "" txt := ""
if remaining < 0 { if remaining < 0 {

View File

@ -1,6 +1,11 @@
package goals package goals
import ( import (
"github.com/velour/catbase/bot/msg"
"github.com/velour/catbase/bot/user"
"github.com/velour/catbase/plugins/cli"
"regexp"
"strings"
"testing" "testing"
"time" "time"
@ -10,11 +15,46 @@ import (
"github.com/velour/catbase/plugins/counter" "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) { func TestRemainingSomeToGo(t *testing.T) {
mb := bot.NewMockBot() mb := bot.NewMockBot()
i := counter.Item{ i := &counter.Item{
DB: mb.DB(),
ID: 0, ID: 0,
Nick: "", Nick: "",
Item: "", Item: "",
@ -45,8 +85,7 @@ func TestRemainingSomeToGo(t *testing.T) {
func TestRemainingAheadOfCurve(t *testing.T) { func TestRemainingAheadOfCurve(t *testing.T) {
mb := bot.NewMockBot() mb := bot.NewMockBot()
i := counter.Item{ i := &counter.Item{
DB: mb.DB(),
ID: 0, ID: 0,
Nick: "", Nick: "",
Item: "", Item: "",
@ -73,3 +112,10 @@ func TestRemainingAheadOfCurve(t *testing.T) {
assert.Equal(t, expected, actual) assert.Equal(t, expected, actual)
} }
func TestRegister(t *testing.T) {
_, gp, td := setup(t)
defer td()
sendMessage(gp, "register goal :tea: 5")
}

View File

@ -154,7 +154,7 @@ func (p *InventoryPlugin) getAll() []string {
} }
func (p *InventoryPlugin) exists(i string) bool { 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 { if err != nil {
log.Error().Err(err).Msg("Error checking for item") log.Error().Err(err).Msg("Error checking for item")
return false return false

View File

@ -135,7 +135,7 @@ func (p *LastPlugin) yesterdaysLast(ch string) (last, error) {
midnight := first.Midnight(time.Now()) midnight := first.Midnight(time.Now())
q := `select * from last where channel = ? and day < ? and day >= ? order by day limit 1` 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()) 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 { if err != nil {
return last{}, err return last{}, err
} }

View File

@ -78,7 +78,7 @@ func (w *Webshit) Check(last int64) ([]WeeklyResult, int64, error) {
} }
var bids []Bid 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 return nil, 0, err
} }
@ -106,7 +106,7 @@ func (w *Webshit) Check(last int64) ([]WeeklyResult, int64, error) {
} }
// Delete all those bids // 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 := record.(*Bid)
r.Processed = true r.Processed = true
return w.store.Update(r.ID, r) return w.store.Update(r.ID, r)
@ -234,7 +234,7 @@ func (w *Webshit) GetBalance(user string) Balance {
func (w *Webshit) GetAllBids() ([]Bid, error) { func (w *Webshit) GetAllBids() ([]Bid, error) {
var bids []Bid 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -117,7 +117,7 @@ func (p *RememberPlugin) randQuote() string {
// AllQuotesFrom delivers quotes out of the db from who. // AllQuotesFrom delivers quotes out of the db from who.
func AllQuotesFrom(store *bh.Store, who string) []fact.Factoid { func AllQuotesFrom(store *bh.Store, who string) []fact.Factoid {
allQuotes := []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 { if err != nil {
log.Error().Err(err).Msg("Error getting quotes") log.Error().Err(err).Msg("Error getting quotes")
return []fact.Factoid{} 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 // expanded to have this function execute a quote for a particular channel
func RandQuote(store *bh.Store) string { func RandQuote(store *bh.Store) string {
allQuotes := []fact.Factoid{} 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 { if err != nil {
log.Error().Err(err).Msg("Error getting quotes") log.Error().Err(err).Msg("Error getting quotes")
return "I had a problem getting your quote." return "I had a problem getting your quote."

View File

@ -37,12 +37,12 @@ type ReminderPlugin struct {
} }
type Reminder struct { type Reminder struct {
id int64 ID uint64 `boltholdKey:"ID"`
from string From string
who string Who string
what string What string
when time.Time When time.Time
channel string Channel string
} }
func New(b bot.Bot) *ReminderPlugin { 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" { if operator == "in" || operator == "at" || operator == "on" {
//one off reminder //one off reminder
//remind who in dur blah //remind Who in dur blah
when := time.Now().UTC().Add(dur) when := time.Now().UTC().Add(dur)
what := strings.Join(parts[4:], " ") what := strings.Join(parts[4:], " ")
p.addReminder(&Reminder{ p.addReminder(&Reminder{
id: -1, From: from,
from: from, Who: who,
who: who, What: what,
what: what, When: when,
when: when, Channel: channel,
channel: channel,
}) })
} else if operator == "every" && strings.ToLower(parts[4]) == "for" { } else if operator == "every" && strings.ToLower(parts[4]) == "for" {
//batch add, especially for reminding msherms to buy a kit //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]) dur2, err = time.ParseDuration(parts[5])
if err != nil { if err != nil {
log.Error().Err(err) log.Error().Err(err)
@ -144,18 +143,17 @@ func (p *ReminderPlugin) message(c bot.Connector, kind bot.Kind, message msg.Mes
} }
p.addReminder(&Reminder{ p.addReminder(&Reminder{
id: int64(-1), From: from,
from: from, Who: who,
who: who, What: what,
what: what, When: when,
when: when, Channel: channel,
channel: channel,
}) })
when = when.Add(dur) when = when.Add(dur)
} }
} else { } 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 return true
} }
@ -177,7 +175,7 @@ func (p *ReminderPlugin) message(c bot.Connector, kind bot.Kind, message msg.Mes
} else if len(parts) == 4 { } else if len(parts) == 4 {
if strings.ToLower(parts[2]) == "to" { if strings.ToLower(parts[2]) == "to" {
response, err = p.getAllRemindersToMeFormatted(channel, strings.ToLower(parts[3])) 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])) 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 return true
} else if len(parts) == 3 && strings.ToLower(parts[0]) == "cancel" && strings.ToLower(parts[1]) == "reminder" { } 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 { 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 { } else {
err := p.deleteReminder(id) err := p.deleteReminder(id)
@ -221,7 +219,11 @@ func (p *ReminderPlugin) getNextReminder() *Reminder {
log.Error().Err(err) log.Error().Err(err)
return nil 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 return &reminder
} }
@ -231,12 +233,13 @@ func (p *ReminderPlugin) addReminder(reminder *Reminder) error {
defer p.mutex.Unlock() defer p.mutex.Unlock()
err := p.store.Insert(bh.NextSequence(), reminder) err := p.store.Insert(bh.NextSequence(), reminder)
if err != nil { 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() p.mutex.Lock()
defer p.mutex.Unlock() defer p.mutex.Unlock()
err := p.store.Delete(id, Reminder{}) err := p.store.Delete(id, Reminder{})
@ -264,14 +267,14 @@ func (p *ReminderPlugin) getRemindersFormatted(filter, who string) (string, erro
} }
reminders := []Reminder{} 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 { if err != nil {
log.Error().Err(err) log.Error().Err(err)
return "", nil return "", nil
} }
txt := "" txt := ""
for counter, reminder := range reminders { 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++ counter++
} }
@ -288,18 +291,18 @@ func (p *ReminderPlugin) getAllRemindersFormatted(channel string) (string, error
} }
func (p *ReminderPlugin) getAllRemindersFromMeFormatted(channel, me 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) { func (p *ReminderPlugin) getAllRemindersToMeFormatted(channel, me string) (string, error) {
return p.getRemindersFormatted("toWho", me) return p.getRemindersFormatted("ToWho", me)
} }
func (p *ReminderPlugin) queueUpNextReminder() { func (p *ReminderPlugin) queueUpNextReminder() {
nextReminder := p.getNextReminder() nextReminder := p.getNextReminder()
if nextReminder != nil { 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() reminder := p.getNextReminder()
if reminder != nil && time.Now().UTC().After(reminder.when) { if reminder != nil && time.Now().UTC().After(reminder.When) {
var message string var message string
if reminder.from == reminder.who { if reminder.From == reminder.Who {
reminder.from = "you" reminder.From = "you"
message = fmt.Sprintf("Hey %s, you wanted to be reminded: %s", reminder.who, reminder.what) message = fmt.Sprintf("Hey %s, you wanted to be reminded: %s", reminder.Who, reminder.What)
} else { } 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) 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") log.Error().Err(err).Msgf("could not send reminder")
} }
if err := p.deleteReminder(reminder.id); err != nil { if err := p.deleteReminder(reminder.ID); err != nil {
log.Error(). log.Fatal().
Int64("id", reminder.id). Uint64("ID", reminder.ID).
Err(err). Err(err).
Msg("this will cause problems, we need to stop now.") Msg("this will cause problems, we need to stop now.")
} }

View File

@ -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() mb := bot.NewMockBot()
r := New(mb) r := New(mb)
mb.DB().MustExec(`delete from reminders; delete from config;`) return r, mb, func() {
return r, mb mb.TearDown()
}
} }
func TestMeReminder(t *testing.T) { 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")) res := c.message(makeMessage("!remind me in 1s don't fail this test"))
time.Sleep(2 * time.Second) time.Sleep(2 * time.Second)
assert.Len(t, mb.Messages, 2) assert.Len(t, mb.Messages, 2)
@ -50,7 +52,8 @@ func TestMeReminder(t *testing.T) {
} }
func TestReminder(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")) res := c.message(makeMessage("!remind testuser in 1s don't fail this test"))
time.Sleep(2 * time.Second) time.Sleep(2 * time.Second)
assert.Len(t, mb.Messages, 2) assert.Len(t, mb.Messages, 2)
@ -60,7 +63,8 @@ func TestReminder(t *testing.T) {
} }
func TestReminderReorder(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")) res := c.message(makeMessage("!remind testuser in 2s don't fail this test 2"))
assert.True(t, res) assert.True(t, res)
res = c.message(makeMessage("!remind testuser in 1s don't fail this test 1")) res = c.message(makeMessage("!remind testuser in 1s don't fail this test 1"))
@ -74,7 +78,8 @@ func TestReminderReorder(t *testing.T) {
} }
func TestReminderParse(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")) res := c.message(makeMessage("!remind testuser in unparseable don't fail this test"))
assert.Len(t, mb.Messages, 1) assert.Len(t, mb.Messages, 1)
assert.True(t, res) assert.True(t, res)
@ -82,7 +87,8 @@ func TestReminderParse(t *testing.T) {
} }
func TestEmptyList(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")) res := c.message(makeMessage("!list reminders"))
assert.Len(t, mb.Messages, 1) assert.Len(t, mb.Messages, 1)
assert.True(t, res) assert.True(t, res)
@ -90,7 +96,8 @@ func TestEmptyList(t *testing.T) {
} }
func TestList(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")) res := c.message(makeMessage("!remind testuser in 5m don't fail this test 1"))
assert.True(t, res) assert.True(t, res)
res = c.message(makeMessage("!remind testuser in 5m don't fail this test 2")) res = c.message(makeMessage("!remind testuser in 5m don't fail this test 2"))
@ -103,12 +110,13 @@ func TestList(t *testing.T) {
} }
func TestListBy(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")) res := c.message(makeMessageBy("!remind testuser in 5m don't fail this test 1", "testuser"))
assert.True(t, res) assert.True(t, res)
res = c.message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2")) res = c.message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2"))
assert.True(t, res) 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.True(t, res)
assert.Len(t, mb.Messages, 3) assert.Len(t, mb.Messages, 3)
assert.Contains(t, mb.Messages[2], "don't fail this test 1 @ ") 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) { 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")) res := c.message(makeMessageBy("!remind testuser2 in 5m don't fail this test 1", "testuser"))
assert.True(t, res) assert.True(t, res)
res = c.message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2")) res = c.message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2"))
@ -129,7 +138,8 @@ func TestListTo(t *testing.T) {
} }
func TestToEmptyList(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")) res := c.message(makeMessageBy("!remind testuser2 in 5m don't fail this test 1", "testuser"))
assert.True(t, res) assert.True(t, res)
res = c.message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2")) res = c.message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2"))
@ -141,19 +151,21 @@ func TestToEmptyList(t *testing.T) {
} }
func TestFromEmptyList(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")) res := c.message(makeMessageBy("!remind testuser2 in 5m don't fail this test 1", "testuser"))
assert.True(t, res) assert.True(t, res)
res = c.message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2")) res = c.message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2"))
assert.True(t, res) 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.True(t, res)
assert.Len(t, mb.Messages, 3) assert.Len(t, mb.Messages, 3)
assert.Contains(t, mb.Messages[2], "no pending reminders") assert.Contains(t, mb.Messages[2], "no pending reminders")
} }
func TestBatchMax(t *testing.T) { func TestBatchMax(t *testing.T) {
c, mb := setup(t) c, mb, td := setup(t)
defer td()
c.config.Set("Reminder.MaxBatchAdd", "10") c.config.Set("Reminder.MaxBatchAdd", "10")
assert.NotNil(t, c) assert.NotNil(t, c)
res := c.message(makeMessage("!remind testuser every 1h for 24h yikes")) res := c.message(makeMessage("!remind testuser every 1h for 24h yikes"))
@ -170,7 +182,8 @@ func TestBatchMax(t *testing.T) {
} }
func TestCancel(t *testing.T) { func TestCancel(t *testing.T) {
c, mb := setup(t) c, mb, td := setup(t)
defer td()
assert.NotNil(t, c) assert.NotNil(t, c)
res := c.message(makeMessage("!remind testuser in 1m don't fail this test")) res := c.message(makeMessage("!remind testuser in 1m don't fail this test"))
assert.True(t, res) assert.True(t, res)
@ -185,7 +198,8 @@ func TestCancel(t *testing.T) {
} }
func TestCancelMiss(t *testing.T) { func TestCancelMiss(t *testing.T) {
c, mb := setup(t) c, mb, td := setup(t)
defer td()
assert.NotNil(t, c) assert.NotNil(t, c)
res := c.message(makeMessage("!cancel reminder 1")) res := c.message(makeMessage("!cancel reminder 1"))
assert.True(t, res) assert.True(t, res)
@ -194,7 +208,8 @@ func TestCancelMiss(t *testing.T) {
} }
func TestLimitList(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.MaxBatchAdd", "10")
c.config.Set("Reminder.MaxList", "25") c.config.Set("Reminder.MaxList", "25")
assert.NotNil(t, c) assert.NotNil(t, c)
@ -222,8 +237,9 @@ func TestLimitList(t *testing.T) {
} }
func TestHelp(t *testing.T) { func TestHelp(t *testing.T) {
c, mb := setup(t) c, mb, td := setup(t)
defer td()
assert.NotNil(t, c) 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) assert.Len(t, mb.Messages, 1)
} }

37
util/bolthold/util.go Normal file
View File

@ -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()
}
}