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
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

View File

@ -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 {

View File

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

View File

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

View File

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

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 {
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,
})
}

View File

@ -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

View File

@ -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

View File

@ -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).

View File

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

View File

@ -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?")

View File

@ -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.

View File

@ -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 {

View File

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

View File

@ -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

View File

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

View File

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

View File

@ -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."

View File

@ -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.")
}

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

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