Compare commits

..

No commits in common. "a34770adaa94426c4a4c1f49335da32f1af85c41" and "748e39c290498f7560e3a4d37b0ae494bc7cc876" have entirely different histories.

29 changed files with 512 additions and 359 deletions

View File

@ -3,6 +3,7 @@ FROM alpine:edge
RUN apk add --no-cache git RUN apk add --no-cache git
RUN apk add --no-cache musl-dev RUN apk add --no-cache musl-dev
RUN apk add --no-cache gcc RUN apk add --no-cache gcc
RUN apk add --no-cache sqlite
RUN apk add --no-cache go RUN apk add --no-cache go
RUN apk add --no-cache perl RUN apk add --no-cache perl
RUN apk add --no-cache make RUN apk add --no-cache make

View File

@ -6,6 +6,8 @@ This bot was written a long time back in the spare time of a busy person. The co
## Never going to get done: ## Never going to get done:
* Migrate SQL to something that can marshal into structs
* https://github.com/jmoiron/sqlx
* Fix plugin structure to not have so many exported fields. None of them need to be exporting the bot reference, for example. * Fix plugin structure to not have so many exported fields. None of them need to be exporting the bot reference, for example.
* Perhaps refactor a bit so stuff can be tested * Perhaps refactor a bit so stuff can be tested
* Fix names in factoid to actually match the bucket terminology. Some things are migrated, but not everything. There should be no instances of: * Fix names in factoid to actually match the bucket terminology. Some things are migrated, but not everything. There should be no instances of:
@ -13,6 +15,9 @@ This bot was written a long time back in the spare time of a busy person. The co
* Action * Action
* FullText * FullText
* Operator * Operator
* Figure out something better for time?
* SQLite has a datetime, but the driver can't seem to handle null
* SQLite sometimes returns a different date string, which appers to be what the driver is trying to translate from/to
* Implement factoid aliasing * Implement factoid aliasing
* Implement an object system for the give/take commands * Implement an object system for the give/take commands
* Create some kind of web reference page * Create some kind of web reference page

View File

@ -159,6 +159,11 @@ func (b *bot) Config() *config.Config {
return b.config return b.config
} }
// TODO: remove
//func (b *bot) DB() *sqlx.DB {
// return b.config.DB()
//}
func (b *bot) Store() *bh.Store { func (b *bot) Store() *bh.Store {
return b.config.Store() return b.config.Store()
} }

View File

@ -79,7 +79,8 @@ type Bot interface {
// Config allows access to the bot's configuration system // Config allows access to the bot's configuration system
Config() *config.Config Config() *config.Config
// Store gives access to the current database // DB gives access to the current database
//DB() *sqlx.DB
Store() *bh.Store Store() *bh.Store
// Who lists users in a particular channel // Who lists users in a particular channel

View File

@ -115,8 +115,7 @@ func NewMockBot() *MockBot {
if err != nil { if err != nil {
panic(err) panic(err)
} }
log.Debug().Msgf("MockBot temp store: %s", storeFile.Name()) cfg := config.ReadConfig("file::memory:?mode=memory&cache=shared", storeFile.Name())
cfg := config.ReadConfig(storeFile.Name())
b := MockBot{ b := MockBot{
Cfg: cfg, Cfg: cfg,
Messages: make([]string, 0), Messages: make([]string, 0),

View File

@ -9,14 +9,19 @@ import (
"strconv" "strconv"
"strings" "strings"
_ "modernc.org/sqlite"
bh "github.com/timshannon/bolthold" bh "github.com/timshannon/bolthold"
"github.com/jmoiron/sqlx"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
// Config stores any system-wide startup information that cannot be easily configured via // Config stores any system-wide startup information that cannot be easily configured via
// the database // the database
type Config struct { type Config struct {
db *sqlx.DB
store *bh.Store store *bh.Store
DBFile string DBFile string
@ -34,6 +39,11 @@ type Value struct {
// Secret is a separate type (for storage differentiation) // Secret is a separate type (for storage differentiation)
type Secret Value type Secret Value
// DB returns the SQL database instance
func (c *Config) DB() *sqlx.DB {
return c.db
}
func (c *Config) Store() *bh.Store { func (c *Config) Store() *bh.Store {
return c.store return c.store
} }
@ -192,19 +202,57 @@ func (c *Config) SetArray(key string, values []string) error {
return c.Set(key, vals) return c.Set(key, vals)
} }
//func init() {
// regex := func(re, s string) (bool, error) {
// return regexp.MatchString(re, s)
// }
// sql.Register("sqlite3_custom",
// &sqlite3.SQLiteDriver{
// ConnectHook: func(conn *sqlite3.SQLiteConn) error {
// return conn.RegisterFunc("REGEXP", regex, true)
// },
// })
//}
// 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(storepath string) *Config { func ReadConfig(dbpath, storepath string) *Config {
if dbpath == "" {
dbpath = "catbase.db"
}
store, err := bh.Open(storepath, 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")
} }
log.Info().Msgf("Using %s as database file.\n", storepath) log.Info().Msgf("Using %s as database file.\n", dbpath)
sqlDB, err := sqlx.Open("sqlite", dbpath)
if err != nil {
log.Fatal().Err(err).Msgf("could not open sqlite")
}
c := Config{ c := Config{
DBFile: dbpath,
secrets: map[string]Secret{}, secrets: map[string]Secret{},
store: store, store: store,
} }
c.db = sqlDB
if _, err := c.db.Exec(`create table if not exists config (
key string,
value string,
primary key (key)
);`); err != nil {
log.Fatal().Err(err).Msgf("failed to initialize config")
}
if _, err := c.db.Exec(`create table if not exists secrets (
key string,
value string,
primary key (key)
);`); err != nil {
log.Fatal().Err(err).Msgf("failed to initialize config")
}
if err := c.RefreshSecrets(); err != nil { if err := c.RefreshSecrets(); err != nil {
log.Fatal().Err(err).Msgf("failed to initialize config") log.Fatal().Err(err).Msgf("failed to initialize config")
@ -212,5 +260,60 @@ func ReadConfig(storepath string) *Config {
log.Info().Msgf("catbase is running.") log.Info().Msgf("catbase is running.")
if err := c.migrate(); err != nil {
log.Fatal().Err(err).Msgf("could not migrate")
}
return &c return &c
} }
func (c *Config) migrate() error {
// check countSqliteEntries of config/secrets in sqlite
var countSqliteEntries int64
err := c.db.Get(&countSqliteEntries, "select count(*) as countSqliteEntries from config")
if err != nil {
return err
}
countBoltEntries, err := c.store.Count(Value{}, &bh.Query{})
if err != nil {
return err
}
countBoltSecrets, err := c.store.Count(Secret{}, &bh.Query{})
if err != nil {
return err
}
log.Debug().Msgf("Found %d sqlite entries and %d bolt entries and %d bolt secrets", countSqliteEntries, countBoltEntries, countBoltSecrets)
if countSqliteEntries == 0 || countBoltEntries > 0 {
return nil
}
var items []Value
c.db.Select(&items, "select key, value from config")
log.Debug().Msgf("%d entries to migrate", len(items))
c.RefreshSecrets()
secrets := c.GetAllSecrets()
// add all to the bolthold
for _, it := range items {
err := c.store.Insert(it.Key, it)
if err != nil {
return err
}
}
for _, s := range secrets {
err := c.store.Insert(s.Key, s)
if err != nil {
return err
}
}
return nil
}

View File

@ -1,20 +1,13 @@
package config package config
import ( import (
"io/ioutil"
"os"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestSetGet(t *testing.T) { func TestSetGet(t *testing.T) {
storeFile, err := ioutil.TempFile(os.TempDir(), "prefix-") cfg := ReadConfig(":memory:")
if err != nil {
panic(err)
}
defer os.Remove(storeFile.Name())
cfg := ReadConfig(storeFile.Name())
expected := "value" expected := "value"
cfg.Set("test", expected) cfg.Set("test", expected)
actual := cfg.Get("test", "NOPE") actual := cfg.Get("test", "NOPE")
@ -22,12 +15,7 @@ func TestSetGet(t *testing.T) {
} }
func TestSetGetArray(t *testing.T) { func TestSetGetArray(t *testing.T) {
storeFile, err := ioutil.TempFile(os.TempDir(), "prefix-") cfg := ReadConfig(":memory:")
if err != nil {
panic(err)
}
defer os.Remove(storeFile.Name())
cfg := ReadConfig(storeFile.Name())
expected := []string{"a", "b", "c"} expected := []string{"a", "b", "c"}
cfg.SetArray("test", expected) cfg.SetArray("test", expected)
actual := cfg.GetArray("test", []string{"NOPE"}) actual := cfg.GetArray("test", []string{"NOPE"})

View File

@ -1,28 +1,38 @@
package config package config
import ( import (
"bytes"
"strings"
"text/template"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
func (c *Config) SetDefaults(mainChannel, nick string) { var q = `
values := []Value{ INSERT INTO config VALUES('nick','{{.Nick}}');
{"nick", nick}, INSERT INTO config VALUES('channels','{{.Channel}}');
{"channels", mainChannel}, INSERT INTO config VALUES('untappd.channels','{{.Channel}}');
{"untappd.channels", mainChannel}, INSERT INTO config VALUES('twitch.channels','{{.Channel}}');
{"twitch.channels", mainChannel}, INSERT INTO config VALUES('init',1);
{"init", "1"}, `
}
func (c *Config) SetDefaults(mainChannel, nick string) {
if nick == mainChannel && nick == "" { if nick == mainChannel && nick == "" {
log.Fatal().Msgf("You must provide a nick and a mainChannel") log.Fatal().Msgf("You must provide a nick and a mainChannel")
} }
t := template.Must(template.New("query").Parse(q))
for _, v := range values { vals := struct {
err := c.store.Insert(v.Key, v) Nick string
if err != nil { Channel string
panic(err) ChannelKey string
}{
nick,
mainChannel,
strings.ToLower(mainChannel),
} }
} var buf bytes.Buffer
t.Execute(&buf, vals)
c.db.MustExec(`delete from config;`)
c.db.MustExec(buf.String())
log.Info().Msgf("Configuration initialized.") log.Info().Msgf("Configuration initialized.")
} }

3
go.mod
View File

@ -34,6 +34,7 @@ require (
github.com/itchyny/gojq v0.12.3 github.com/itchyny/gojq v0.12.3
github.com/james-bowman/nlp v0.0.0-20191016091239-d9dbfaff30c6 github.com/james-bowman/nlp v0.0.0-20191016091239-d9dbfaff30c6
github.com/james-bowman/sparse v0.0.0-20190423065201-80c6877364c7 // indirect github.com/james-bowman/sparse v0.0.0-20190423065201-80c6877364c7 // indirect
github.com/jmoiron/sqlx v1.2.0
github.com/kennygrant/sanitize v1.2.4 // indirect github.com/kennygrant/sanitize v1.2.4 // indirect
github.com/kevinburke/go-types v0.0.0-20200309064045-f2d4aea18a7a // indirect github.com/kevinburke/go-types v0.0.0-20200309064045-f2d4aea18a7a // indirect
github.com/kevinburke/go.uuid v1.2.0 // indirect github.com/kevinburke/go.uuid v1.2.0 // indirect
@ -61,7 +62,6 @@ require (
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
golang.org/x/exp v0.0.0-20191014171548-69215a2ee97e // indirect golang.org/x/exp v0.0.0-20191014171548-69215a2ee97e // indirect
golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4 // indirect golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4 // indirect
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 // indirect golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.3.7 // indirect
gonum.org/v1/gonum v0.6.0 // indirect gonum.org/v1/gonum v0.6.0 // indirect
@ -69,4 +69,5 @@ require (
gopkg.in/go-playground/webhooks.v5 v5.13.0 gopkg.in/go-playground/webhooks.v5 v5.13.0
gopkg.in/sourcemap.v1 v1.0.5 // indirect gopkg.in/sourcemap.v1 v1.0.5 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
modernc.org/sqlite v1.14.2
) )

147
go.sum
View File

@ -37,6 +37,8 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-jsonpointer v0.0.0-20160814072949-ba0abeacc3dc h1:tP7tkU+vIsEOKiK+l/NSLN4uUtkyuxc6hgYpQeCWAeI= github.com/dustin/go-jsonpointer v0.0.0-20160814072949-ba0abeacc3dc h1:tP7tkU+vIsEOKiK+l/NSLN4uUtkyuxc6hgYpQeCWAeI=
github.com/dustin/go-jsonpointer v0.0.0-20160814072949-ba0abeacc3dc/go.mod h1:ORH5Qp2bskd9NzSfKqAF7tKfONsEkCarTE5ESr/RVBw= github.com/dustin/go-jsonpointer v0.0.0-20160814072949-ba0abeacc3dc/go.mod h1:ORH5Qp2bskd9NzSfKqAF7tKfONsEkCarTE5ESr/RVBw=
github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad h1:Qk76DOWdOp+GlyDKBAG3Klr9cn7N+LcYc82AZ2S7+cA= github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad h1:Qk76DOWdOp+GlyDKBAG3Klr9cn7N+LcYc82AZ2S7+cA=
@ -48,6 +50,8 @@ github.com/garyburd/go-oauth v0.0.0-20180319155456-bca2e7f09a17/go.mod h1:HfkOCN
github.com/go-chi/chi/v5 v5.0.3 h1:khYQBdPivkYG1s1TAzDQG1f6eX4kD2TItYVZexL5rS4= github.com/go-chi/chi/v5 v5.0.3 h1:khYQBdPivkYG1s1TAzDQG1f6eX4kD2TItYVZexL5rS4=
github.com/go-chi/chi/v5 v5.0.3/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.0.3/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
@ -66,6 +70,8 @@ github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82 h1:EvokxLQsaaQjcWVWSV
github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82/go.mod h1:PxC8OnwL11+aosOB5+iEPoV3picfs8tUpkVd0pDo+Kg= github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82/go.mod h1:PxC8OnwL11+aosOB5+iEPoV3picfs8tUpkVd0pDo+Kg=
github.com/gonum/internal v0.0.0-20181124074243-f884aa714029 h1:8jtTdc+Nfj9AR+0soOeia9UZSvYBvETVHZrugUowJ7M= github.com/gonum/internal v0.0.0-20181124074243-f884aa714029 h1:8jtTdc+Nfj9AR+0soOeia9UZSvYBvETVHZrugUowJ7M=
github.com/gonum/internal v0.0.0-20181124074243-f884aa714029/go.mod h1:Pu4dmpkhSyOzRwuXkOgAvijx4o+4YMUJJo9OvPYMkks= github.com/gonum/internal v0.0.0-20181124074243-f884aa714029/go.mod h1:Pu4dmpkhSyOzRwuXkOgAvijx4o+4YMUJJo9OvPYMkks=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -83,7 +89,11 @@ github.com/james-bowman/nlp v0.0.0-20191016091239-d9dbfaff30c6 h1:k8+n5sfvxlixRN
github.com/james-bowman/nlp v0.0.0-20191016091239-d9dbfaff30c6/go.mod h1:kixuaexEqWB+mHZNysgnb6mqgGIT25WvD1/tFRRt0J0= github.com/james-bowman/nlp v0.0.0-20191016091239-d9dbfaff30c6/go.mod h1:kixuaexEqWB+mHZNysgnb6mqgGIT25WvD1/tFRRt0J0=
github.com/james-bowman/sparse v0.0.0-20190423065201-80c6877364c7 h1:ph/BDQQDL41apnHSN48I5GyNOQXXAlc79HwGqDSXCss= github.com/james-bowman/sparse v0.0.0-20190423065201-80c6877364c7 h1:ph/BDQQDL41apnHSN48I5GyNOQXXAlc79HwGqDSXCss=
github.com/james-bowman/sparse v0.0.0-20190423065201-80c6877364c7/go.mod h1:G6EcQnwZKsWtItoaQHd+FHPPk6bDeYVJSeeSP9Sge+I= github.com/james-bowman/sparse v0.0.0-20190423065201-80c6877364c7/go.mod h1:G6EcQnwZKsWtItoaQHd+FHPPk6bDeYVJSeeSP9Sge+I=
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kennygrant/sanitize v1.2.4 h1:gN25/otpP5vAsO2djbMhF/LQX6R7+O1TB4yv8NzpJ3o= github.com/kennygrant/sanitize v1.2.4 h1:gN25/otpP5vAsO2djbMhF/LQX6R7+O1TB4yv8NzpJ3o=
github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak= github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak=
github.com/kevinburke/go-types v0.0.0-20200309064045-f2d4aea18a7a h1:Z7+SSApKiwPjNic+NF9+j7h657Uyvdp/jA3iTKhpj4E= github.com/kevinburke/go-types v0.0.0-20200309064045-f2d4aea18a7a h1:Z7+SSApKiwPjNic+NF9+j7h657Uyvdp/jA3iTKhpj4E=
@ -94,11 +104,16 @@ github.com/kevinburke/rest v0.0.0-20200429221318-0d2892b400f8 h1:KpuDJTaTPQAyWqE
github.com/kevinburke/rest v0.0.0-20200429221318-0d2892b400f8/go.mod h1:pD+iEcdAGVXld5foVN4e24zb/6fnb60tgZPZ3P/3T/I= github.com/kevinburke/rest v0.0.0-20200429221318-0d2892b400f8/go.mod h1:pD+iEcdAGVXld5foVN4e24zb/6fnb60tgZPZ3P/3T/I=
github.com/kevinburke/twilio-go v0.0.0-20200424172635-4f0b2357b852 h1:wJMykIkD7A4tlwQNzqBJ23hkLlKtRKYeNNt+n8ASqWE= github.com/kevinburke/twilio-go v0.0.0-20200424172635-4f0b2357b852 h1:wJMykIkD7A4tlwQNzqBJ23hkLlKtRKYeNNt+n8ASqWE=
github.com/kevinburke/twilio-go v0.0.0-20200424172635-4f0b2357b852/go.mod h1:Fm9alkN1/LPVY1eqD/psyMwPWE4VWl4P01/nTYZKzBk= github.com/kevinburke/twilio-go v0.0.0-20200424172635-4f0b2357b852/go.mod h1:Fm9alkN1/LPVY1eqD/psyMwPWE4VWl4P01/nTYZKzBk=
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mmcdole/gofeed v1.0.0-beta2 h1:CjQ0ADhAwNSb08zknAkGOEYqr8zfZKfrzgk9BxpWP2E= github.com/mmcdole/gofeed v1.0.0-beta2 h1:CjQ0ADhAwNSb08zknAkGOEYqr8zfZKfrzgk9BxpWP2E=
github.com/mmcdole/gofeed v1.0.0-beta2/go.mod h1:/BF9JneEL2/flujm8XHoxUcghdTV6vvb3xx/vKyChFU= github.com/mmcdole/gofeed v1.0.0-beta2/go.mod h1:/BF9JneEL2/flujm8XHoxUcghdTV6vvb3xx/vKyChFU=
github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf h1:sWGE2v+hO0Nd4yFU/S/mDBM5plIU8v/Qhfz41hkDIAI= github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf h1:sWGE2v+hO0Nd4yFU/S/mDBM5plIU8v/Qhfz41hkDIAI=
@ -113,6 +128,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d h1:1VUlQbCfkoSGv7qP7Y+ro3ap1P1pPZxgdGVqiTVy5C4= github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d h1:1VUlQbCfkoSGv7qP7Y+ro3ap1P1pPZxgdGVqiTVy5C4=
github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY= github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
@ -143,12 +160,15 @@ github.com/ttacon/libphonenumber v1.1.0 h1:tC6kE4t8UI4OqQVQjW5q8gSWhG2wnY5moEpSE
github.com/ttacon/libphonenumber v1.1.0/go.mod h1:E0TpmdVMq5dyVlQ7oenAkhsLu86OkUl+yR4OAxyEg/M= github.com/ttacon/libphonenumber v1.1.0/go.mod h1:E0TpmdVMq5dyVlQ7oenAkhsLu86OkUl+yR4OAxyEg/M=
github.com/velour/velour v0.0.0-20160303155839-8e090e68d158 h1:p3rTUXxzuKsBOsHlkly7+rj9wagFBKeIsCDKkDII9sw= github.com/velour/velour v0.0.0-20160303155839-8e090e68d158 h1:p3rTUXxzuKsBOsHlkly7+rj9wagFBKeIsCDKkDII9sw=
github.com/velour/velour v0.0.0-20160303155839-8e090e68d158/go.mod h1:Ojy3BTOiOTwpHpw7/HNi+TVTFppao191PQs+Qc3sqpE= github.com/velour/velour v0.0.0-20160303155839-8e090e68d158/go.mod h1:Ojy3BTOiOTwpHpw7/HNi+TVTFppao191PQs+Qc3sqpE=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI= golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI=
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -163,12 +183,15 @@ golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDA
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4 h1:DZshvxDdVoeKIbudAdFEKi+f70l51luSy/7b76ibTY0= golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4 h1:DZshvxDdVoeKIbudAdFEKi+f70l51luSy/7b76ibTY0=
golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@ -182,15 +205,20 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210301091718-77cc2087c03b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210301091718-77cc2087c03b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 h1:kwrAHlwJ0DUBZwQ238v+Uod/3eZ8B2K5rYsUHBQvzmI= golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 h1:kwrAHlwJ0DUBZwQ238v+Uod/3eZ8B2K5rYsUHBQvzmI=
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@ -199,8 +227,14 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 h1:M8tBwCtWD/cZV9DZpFYRUgaymAYAr+aIUTWzDaM3uPs=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
gonum.org/v1/gonum v0.6.0 h1:DJy6UzXbahnGUf1ujUNkh/NEtK14qMo2nvlBPs4U5yw= gonum.org/v1/gonum v0.6.0 h1:DJy6UzXbahnGUf1ujUNkh/NEtK14qMo2nvlBPs4U5yw=
gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU=
@ -219,4 +253,117 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU=
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.34.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.4/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.5/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.7/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.8/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.18 h1:rMZhRcWrba0y3nVmdiQ7kxAgOOSq2m2f2VzjHLgEs6U=
modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60=
modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw=
modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI=
modernc.org/ccgo/v3 v3.11.1/go.mod h1:lWHxfsn13L3f7hgGsGlU28D9eUOf6y3ZYHKoPaKU0ag=
modernc.org/ccgo/v3 v3.11.3/go.mod h1:0oHunRBMBiXOKdaglfMlRPBALQqsfrCKXgw9okQ3GEw=
modernc.org/ccgo/v3 v3.12.4/go.mod h1:Bk+m6m2tsooJchP/Yk5ji56cClmN6R1cqc9o/YtbgBQ=
modernc.org/ccgo/v3 v3.12.6/go.mod h1:0Ji3ruvpFPpz+yu+1m0wk68pdr/LENABhTrDkMDWH6c=
modernc.org/ccgo/v3 v3.12.8/go.mod h1:Hq9keM4ZfjCDuDXxaHptpv9N24JhgBZmUG5q60iLgUo=
modernc.org/ccgo/v3 v3.12.11/go.mod h1:0jVcmyDwDKDGWbcrzQ+xwJjbhZruHtouiBEvDfoIsdg=
modernc.org/ccgo/v3 v3.12.14/go.mod h1:GhTu1k0YCpJSuWwtRAEHAol5W7g1/RRfS4/9hc9vF5I=
modernc.org/ccgo/v3 v3.12.18/go.mod h1:jvg/xVdWWmZACSgOiAhpWpwHWylbJaSzayCqNOJKIhs=
modernc.org/ccgo/v3 v3.12.20/go.mod h1:aKEdssiu7gVgSy/jjMastnv/q6wWGRbszbheXgWRHc8=
modernc.org/ccgo/v3 v3.12.21/go.mod h1:ydgg2tEprnyMn159ZO/N4pLBqpL7NOkJ88GT5zNU2dE=
modernc.org/ccgo/v3 v3.12.22/go.mod h1:nyDVFMmMWhMsgQw+5JH6B6o4MnZ+UQNw1pp52XYFPRk=
modernc.org/ccgo/v3 v3.12.25/go.mod h1:UaLyWI26TwyIT4+ZFNjkyTbsPsY3plAEB6E7L/vZV3w=
modernc.org/ccgo/v3 v3.12.29/go.mod h1:FXVjG7YLf9FetsS2OOYcwNhcdOLGt8S9bQ48+OP75cE=
modernc.org/ccgo/v3 v3.12.36/go.mod h1:uP3/Fiezp/Ga8onfvMLpREq+KUjUmYMxXPO8tETHtA8=
modernc.org/ccgo/v3 v3.12.38/go.mod h1:93O0G7baRST1vNj4wnZ49b1kLxt0xCW5Hsa2qRaZPqc=
modernc.org/ccgo/v3 v3.12.43/go.mod h1:k+DqGXd3o7W+inNujK15S5ZYuPoWYLpF5PYougCmthU=
modernc.org/ccgo/v3 v3.12.46/go.mod h1:UZe6EvMSqOxaJ4sznY7b23/k13R8XNlyWsO5bAmSgOE=
modernc.org/ccgo/v3 v3.12.47/go.mod h1:m8d6p0zNps187fhBwzY/ii6gxfjob1VxWb919Nk1HUk=
modernc.org/ccgo/v3 v3.12.50/go.mod h1:bu9YIwtg+HXQxBhsRDE+cJjQRuINuT9PUK4orOco/JI=
modernc.org/ccgo/v3 v3.12.51/go.mod h1:gaIIlx4YpmGO2bLye04/yeblmvWEmE4BBBls4aJXFiE=
modernc.org/ccgo/v3 v3.12.53/go.mod h1:8xWGGTFkdFEWBEsUmi+DBjwu/WLy3SSOrqEmKUjMeEg=
modernc.org/ccgo/v3 v3.12.54/go.mod h1:yANKFTm9llTFVX1FqNKHE0aMcQb1fuPJx6p8AcUx+74=
modernc.org/ccgo/v3 v3.12.55/go.mod h1:rsXiIyJi9psOwiBkplOaHye5L4MOOaCjHg1Fxkj7IeU=
modernc.org/ccgo/v3 v3.12.56/go.mod h1:ljeFks3faDseCkr60JMpeDb2GSO3TKAmrzm7q9YOcMU=
modernc.org/ccgo/v3 v3.12.57/go.mod h1:hNSF4DNVgBl8wYHpMvPqQWDQx8luqxDnNGCMM4NFNMc=
modernc.org/ccgo/v3 v3.12.60/go.mod h1:k/Nn0zdO1xHVWjPYVshDeWKqbRWIfif5dtsIOCUVMqM=
modernc.org/ccgo/v3 v3.12.65/go.mod h1:D6hQtKxPNZiY6wDBtehSGKFKmyXn53F8nGTpH+POmS4=
modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/EhQ=
modernc.org/ccgo/v3 v3.12.67/go.mod h1:Bll3KwKvGROizP2Xj17GEGOTrlvB1XcVaBrC90ORO84=
modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3cQ=
modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY=
modernc.org/ccgo/v3 v3.12.82 h1:wudcnJyjLj1aQQCXF3IM9Gz2X6UNjw+afIghzdtn0v8=
modernc.org/ccgo/v3 v3.12.82/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w=
modernc.org/ccorpus v1.11.1 h1:K0qPfpVG1MJh5BYazccnmhywH4zHuOgJXgbjzyp6dWA=
modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q=
modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg=
modernc.org/libc v1.11.2/go.mod h1:ioIyrl3ETkugDO3SGZ+6EOKvlP3zSOycUETe4XM4n8M=
modernc.org/libc v1.11.5/go.mod h1:k3HDCP95A6U111Q5TmG3nAyUcp3kR5YFZTeDS9v8vSU=
modernc.org/libc v1.11.6/go.mod h1:ddqmzR6p5i4jIGK1d/EiSw97LBcE3dK24QEwCFvgNgE=
modernc.org/libc v1.11.11/go.mod h1:lXEp9QOOk4qAYOtL3BmMve99S5Owz7Qyowzvg6LiZso=
modernc.org/libc v1.11.13/go.mod h1:ZYawJWlXIzXy2Pzghaf7YfM8OKacP3eZQI81PDLFdY8=
modernc.org/libc v1.11.16/go.mod h1:+DJquzYi+DMRUtWI1YNxrlQO6TcA5+dRRiq8HWBWRC8=
modernc.org/libc v1.11.19/go.mod h1:e0dgEame6mkydy19KKaVPBeEnyJB4LGNb0bBH1EtQ3I=
modernc.org/libc v1.11.24/go.mod h1:FOSzE0UwookyT1TtCJrRkvsOrX2k38HoInhw+cSCUGk=
modernc.org/libc v1.11.26/go.mod h1:SFjnYi9OSd2W7f4ct622o/PAYqk7KHv6GS8NZULIjKY=
modernc.org/libc v1.11.27/go.mod h1:zmWm6kcFXt/jpzeCgfvUNswM0qke8qVwxqZrnddlDiE=
modernc.org/libc v1.11.28/go.mod h1:Ii4V0fTFcbq3qrv3CNn+OGHAvzqMBvC7dBNyC4vHZlg=
modernc.org/libc v1.11.31/go.mod h1:FpBncUkEAtopRNJj8aRo29qUiyx5AvAlAxzlx9GNaVM=
modernc.org/libc v1.11.34/go.mod h1:+Tzc4hnb1iaX/SKAutJmfzES6awxfU1BPvrrJO0pYLg=
modernc.org/libc v1.11.37/go.mod h1:dCQebOwoO1046yTrfUE5nX1f3YpGZQKNcITUYWlrAWo=
modernc.org/libc v1.11.39/go.mod h1:mV8lJMo2S5A31uD0k1cMu7vrJbSA3J3waQJxpV4iqx8=
modernc.org/libc v1.11.42/go.mod h1:yzrLDU+sSjLE+D4bIhS7q1L5UwXDOw99PLSX0BlZvSQ=
modernc.org/libc v1.11.44/go.mod h1:KFq33jsma7F5WXiYelU8quMJasCCTnHK0mkri4yPHgA=
modernc.org/libc v1.11.45/go.mod h1:Y192orvfVQQYFzCNsn+Xt0Hxt4DiO4USpLNXBlXg/tM=
modernc.org/libc v1.11.47/go.mod h1:tPkE4PzCTW27E6AIKIR5IwHAQKCAtudEIeAV1/SiyBg=
modernc.org/libc v1.11.49/go.mod h1:9JrJuK5WTtoTWIFQ7QjX2Mb/bagYdZdscI3xrvHbXjE=
modernc.org/libc v1.11.51/go.mod h1:R9I8u9TS+meaWLdbfQhq2kFknTW0O3aw3kEMqDDxMaM=
modernc.org/libc v1.11.53/go.mod h1:5ip5vWYPAoMulkQ5XlSJTy12Sz5U6blOQiYasilVPsU=
modernc.org/libc v1.11.54/go.mod h1:S/FVnskbzVUrjfBqlGFIPA5m7UwB3n9fojHhCNfSsnw=
modernc.org/libc v1.11.55/go.mod h1:j2A5YBRm6HjNkoSs/fzZrSxCuwWqcMYTDPLNx0URn3M=
modernc.org/libc v1.11.56/go.mod h1:pakHkg5JdMLt2OgRadpPOTnyRXm/uzu+Yyg/LSLdi18=
modernc.org/libc v1.11.58/go.mod h1:ns94Rxv0OWyoQrDqMFfWwka2BcaF6/61CqJRK9LP7S8=
modernc.org/libc v1.11.70/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw=
modernc.org/libc v1.11.71/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw=
modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0=
modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI=
modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE=
modernc.org/libc v1.11.87 h1:PzIzOqtlzMDDcCzJ5cUP6h/Ku6Fa9iyflP2ccTY64aE=
modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY=
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14=
modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.14.2 h1:ohsW2+e+Qe2To1W6GNezzKGwjXwSax6R+CrhRxVaFbE=
modernc.org/sqlite v1.14.2/go.mod h1:yqfn85u8wVOE6ub5UT8VI9JjhrwBUUCNyTACN0h6Sx8=
modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs=
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
modernc.org/tcl v1.8.13 h1:V0sTNBw0Re86PvXZxuCub3oO9WrSTqALgrwNZNvLFGw=
modernc.org/tcl v1.8.13/go.mod h1:V+q/Ef0IJaNUSECieLU4o+8IScapxnMyFV6i/7uQlAY=
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.2.19 h1:BGyRFWhDVn5LFS5OcX4Yd/MlpRTOc7hOPTdcIpCiUao=
modernc.org/z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

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"
@ -76,8 +77,8 @@ var (
func main() { func main() {
rand.Seed(time.Now().Unix()) rand.Seed(time.Now().Unix())
var storePath = flag.String("store", "catbase.store", var dbpath = flag.String("db", "catbase.db",
"Database file to load. (Defaults to catbase.store)") "Database file to load. (Defaults to catbase.db)")
flag.Parse() // parses the logging flags. flag.Parse() // parses the logging flags.
var output io.Writer = os.Stdout var output io.Writer = os.Stdout
@ -91,7 +92,7 @@ func main() {
zerolog.SetGlobalLevel(zerolog.DebugLevel) zerolog.SetGlobalLevel(zerolog.DebugLevel)
} }
c := config.ReadConfig(*storePath) c := config.ReadConfig(*dbpath, strings.ReplaceAll(*dbpath, ".db", ".store"))
if *key != "" && *val != "" { if *key != "" && *val != "" {
c.Set(*key, *val) c.Set(*key, *val)

View File

@ -18,16 +18,15 @@ var (
mb *bot.MockBot mb *bot.MockBot
) )
func setup(t *testing.T) (*AdminPlugin, *bot.MockBot, func()) { func setup(t *testing.T) (*AdminPlugin, *bot.MockBot) {
mb = bot.NewMockBot() mb = bot.NewMockBot()
a = New(mb) a = New(mb)
mb.DB().MustExec(`delete from config`)
err := mb.Config().Set("admins", "tester") err := mb.Config().Set("admins", "tester")
if err != nil { if err != nil {
t.FailNow() t.FailNow()
} }
return a, mb, func() { return a, mb
mb.TearDown()
}
} }
func makeMessage(payload string, r *regexp.Regexp) bot.Request { func makeMessage(payload string, r *regexp.Regexp) bot.Request {
@ -51,8 +50,7 @@ func makeMessage(payload string, r *regexp.Regexp) bot.Request {
} }
func TestSet(t *testing.T) { func TestSet(t *testing.T) {
a, mb, td := setup(t) a, mb := setup(t)
defer td()
expected := "test value" expected := "test value"
a.setConfigCmd(makeMessage("!set test.key "+expected, setConfigRegex)) a.setConfigCmd(makeMessage("!set test.key "+expected, setConfigRegex))
actual := mb.Config().Get("test.key", "ERR") actual := mb.Config().Get("test.key", "ERR")
@ -60,8 +58,7 @@ func TestSet(t *testing.T) {
} }
func TestGetValue(t *testing.T) { func TestGetValue(t *testing.T) {
a, mb, td := setup(t) a, mb := setup(t)
defer td()
expected := "value" expected := "value"
mb.Config().Set("test.key", "value") mb.Config().Set("test.key", "value")
a.getConfigCmd(makeMessage("!get test.key", getConfigRegex)) a.getConfigCmd(makeMessage("!get test.key", getConfigRegex))
@ -70,8 +67,7 @@ func TestGetValue(t *testing.T) {
} }
func TestGetEmpty(t *testing.T) { func TestGetEmpty(t *testing.T) {
a, mb, td := setup(t) a, mb := setup(t)
defer td()
expected := "test.key: <unknown>" expected := "test.key: <unknown>"
a.getConfigCmd(makeMessage("!get test.key", getConfigRegex)) a.getConfigCmd(makeMessage("!get test.key", getConfigRegex))
assert.Len(t, mb.Messages, 1) assert.Len(t, mb.Messages, 1)
@ -79,8 +75,7 @@ func TestGetEmpty(t *testing.T) {
} }
func TestGetForbidden(t *testing.T) { func TestGetForbidden(t *testing.T) {
a, mb, td := setup(t) a, mb := setup(t)
defer td()
expected := "cannot access" expected := "cannot access"
a.getConfigCmd(makeMessage("!get slack.token", getConfigRegex)) a.getConfigCmd(makeMessage("!get slack.token", getConfigRegex))
assert.Len(t, mb.Messages, 1) assert.Len(t, mb.Messages, 1)

View File

@ -3,11 +3,11 @@
package babbler package babbler
import ( import (
"database/sql"
"errors" "errors"
"fmt" "fmt"
bh "github.com/timshannon/bolthold" bh "github.com/timshannon/bolthold"
"github.com/velour/catbase/plugins/remember" "github.com/velour/catbase/plugins/remember"
"math"
"math/rand" "math/rand"
"regexp" "regexp"
"strings" "strings"
@ -33,8 +33,8 @@ type BabblerPlugin struct {
} }
type Babbler struct { type Babbler struct {
BabblerID uint64 `db:"BabblerID" boltholdKey:"BabblerID"` BabblerID int64 `db:"id" boltholdid:"BabblerID"`
Name string `db:"Name"` Name string `db:"babbler"`
} }
func getBabbler(store *bh.Store, id int64) (*Babbler, error) { func getBabbler(store *bh.Store, id int64) (*Babbler, error) {
@ -44,48 +44,38 @@ func getBabbler(store *bh.Store, id int64) (*Babbler, error) {
} }
type BabblerWord struct { type BabblerWord struct {
WordID uint64 `db:"WordID" boltholdKey:"WordID"` WordID int64 `db:"ID" boltholdid:"WordID"`
Word string `db:"Word"` Word string `db:"Word"`
} }
func getWord(store *bh.Store, id uint64) (*BabblerWord, error) { func getWord(store *bh.Store, id int64) (*BabblerWord, error) {
res := &BabblerWord{} res := &BabblerWord{}
err := store.Get(id, res) err := store.Get(id, res)
return res, err return res, err
} }
type BabblerNode struct { type BabblerNode struct {
NodeID uint64 `db:"NodeID" boltholdKey:"NodeID"` NodeId int64 `db:"ID" boltholdid:"NodeId"`
BabblerID uint64 `db:"BabblerID"` BabblerID int64 `db:"BabblerID"`
WordID uint64 `db:"WordID"` WordID int64 `db:"WordID"`
Root uint64 `db:"Root"` Root int64 `db:"Root"`
RootFrequency int64 `db:"RootFrequency"` RootFrequency int64 `db:"RootFrequency"`
} }
func getNode(store *bh.Store, id uint64) (*BabblerNode, error) { func getNode(store *bh.Store, id int64) (*BabblerNode, error) {
res := &BabblerNode{} res := &BabblerNode{}
all := []BabblerNode{} err := store.Get(id, res)
err := store.Find(&all, &bh.Query{})
if err != nil {
log.Error().Err(err).Msg("error finding all")
return nil, err
}
err = store.Get(id, res)
if err != nil {
log.Error().Err(err).Msgf("error getting %v", id)
return nil, err
}
return res, err return res, err
} }
type BabblerArc struct { type BabblerArc struct {
ArcID uint64 `db:"ArcID" boltholdKey:"ArcID"` ArcId int64 `db:"ID" boltholdid:"ArcId"`
FromNodeID uint64 `db:"FromNodeID"` FromNodeId int64 `db:"FromNodeId"`
ToNodeID uint64 `db:"ToNodeID"` ToNodeId int64 `db:"ToNodeId"`
Frequency int64 `db:"Frequency"` Frequency int64 `db:"Frequency"`
} }
func getArc(store *bh.Store, id uint64) (*BabblerArc, error) { func getArc(store *bh.Store, id int64) (*BabblerArc, error) {
res := &BabblerArc{} res := &BabblerArc{}
err := store.Get(id, res) err := store.Get(id, res)
return res, err return res, err
@ -210,13 +200,13 @@ 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("Name").Eq(name)) err := p.store.FindOne(&bblr, bh.Where("Babbler").Eq(name))
if err != nil { if err != nil {
if err == bh.ErrNotFound { if err == sql.ErrNoRows {
log.Error().Msgf("failed to find babbler for %s", name) log.Error().Msg("failed to find babbler")
return nil, NO_BABBLER return nil, NO_BABBLER
} }
log.Error().Err(err).Msg("encountered problem in babbler lookup for %s") log.Error().Err(err).Msg("encountered problem in babbler lookup")
return nil, err return nil, err
} }
return &bblr, nil return &bblr, nil
@ -227,14 +217,14 @@ func (p *BabblerPlugin) getOrCreateBabbler(name string) (*Babbler, error) {
if err == NO_BABBLER { if err == NO_BABBLER {
babbler, err = p.makeBabbler(name) babbler, err = p.makeBabbler(name)
if err != nil { if err != nil {
log.Error().Err(err).Msg("error making babbler") log.Error().Err(err)
return nil, err return nil, err
} }
quotes := remember.AllQuotesFrom(p.store, babbler.Name) quotes := remember.AllQuotesFrom(p.store, babbler.Name)
for _, q := range quotes { for _, q := range quotes {
if err = p.addToMarkovChain(babbler, q.Tidbit); err != nil { if err = p.addToMarkovChain(babbler, q.Tidbit); err != nil {
log.Error().Err(err).Msg("error adding to chain") log.Error().Err(err)
} }
} }
} }
@ -299,7 +289,6 @@ func (p *BabblerPlugin) createBabblerNode(babbler *Babbler, word string) (*Babbl
} }
bn := &BabblerNode{ bn := &BabblerNode{
BabblerID: babbler.BabblerID,
WordID: w.WordID, WordID: w.WordID,
Root: 0, Root: 0,
RootFrequency: 0, RootFrequency: 0,
@ -325,16 +314,17 @@ func (p *BabblerPlugin) getOrCreateBabblerNode(babbler *Babbler, word string) (*
func (p *BabblerPlugin) incrementRootWordFrequency(babbler *Babbler, word string) (*BabblerNode, error) { func (p *BabblerPlugin) incrementRootWordFrequency(babbler *Babbler, word string) (*BabblerNode, error) {
node, err := p.getOrCreateBabblerNode(babbler, word) node, err := p.getOrCreateBabblerNode(babbler, word)
if err != nil { if err != nil {
log.Error().Err(err).Msg("error getOrCreateBabblerNode") 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 {
node.RootFrequency += 1 r := record.(BabblerNode)
node.Root = 1 r.RootFrequency += 1
err = p.store.Update(node.NodeID, node) r.Root = 1
return p.store.Update(r.NodeId, r)
})
if err != nil { if err != nil {
log.Error().Err(err).Msg("error updating matching") log.Error().Err(err)
return nil, err return nil, err
} }
node.RootFrequency += 1 node.RootFrequency += 1
@ -343,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
@ -356,29 +346,26 @@ 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, ok := record.(*BabblerArc) r := record.(BabblerArc)
if !ok {
return fmt.Errorf("incorrect type: expected BabblerArc, got %T", record)
}
r.Frequency += 1 r.Frequency += 1
return nil return p.store.Update(r.ArcId, r)
}) })
if err != nil { if err != nil {
log.Error().Err(err).Msg("error updating arcs") log.Error().Err(err)
return nil, err return nil, err
} }
if affectedRows == 0 { if affectedRows == 0 {
err = p.store.Insert(bh.NextSequence(), BabblerArc{ p.store.Insert(bh.NextSequence(), BabblerArc{
FromNodeID: fromNode.NodeID, FromNodeId: fromNode.NodeId,
ToNodeID: toNode.NodeID, ToNodeId: toNode.NodeId,
Frequency: 1, Frequency: 1,
}) })
if err != nil { if err != nil {
log.Error().Err(err).Msg("error inserting arc") log.Error().Err(err)
return nil, err return nil, err
} }
} }
@ -403,19 +390,19 @@ func (p *BabblerPlugin) addToMarkovChain(babbler *Babbler, phrase string) error
curNode, err := p.incrementRootWordFrequency(babbler, words[0]) curNode, err := p.incrementRootWordFrequency(babbler, words[0])
if err != nil { if err != nil {
log.Error().Err(err).Msg("incrementRootWordFrequency") log.Error().Err(err)
return err return err
} }
for i := 1; i < len(words); i++ { for i := 1; i < len(words); i++ {
nextNode, err := p.getOrCreateBabblerNode(babbler, words[i]) nextNode, err := p.getOrCreateBabblerNode(babbler, words[i])
if err != nil { if err != nil {
log.Error().Err(err).Msg("getOrCreateBabblerNode") log.Error().Err(err)
return err return err
} }
_, err = p.incrementWordArc(curNode, nextNode) _, err = p.incrementWordArc(curNode, nextNode)
if err != nil { if err != nil {
log.Error().Err(err).Msg("incrementWordArc") log.Error().Err(err)
return err return err
} }
curNode = nextNode curNode = nextNode
@ -427,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(uint64(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
@ -463,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
@ -484,15 +471,15 @@ func (p *BabblerPlugin) getWeightedNextWord(fromNode *BabblerNode) (*BabblerNode
total += arc.Frequency total += arc.Frequency
if total >= which { if total >= which {
node, err := getNode(p.store, arc.ToNodeID) node, err := getNode(p.store, arc.ToNodeId)
if err != nil { if err != nil {
log.Error().Err(err).Msg("getNode") log.Error().Err(err)
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).Msg("getWord") log.Error().Err(err)
return nil, nil, err return nil, nil, err
} }
return node, w, nil return node, w, nil
@ -505,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
@ -532,7 +519,7 @@ func (p *BabblerPlugin) getWeightedPreviousWord(toNode *BabblerNode) (*BabblerNo
total += arc.Frequency total += arc.Frequency
if total >= which { if total >= which {
node, err := getNode(p.store, arc.FromNodeID) node, err := getNode(p.store, arc.FromNodeId)
if err != nil { if err != nil {
log.Error().Err(err) log.Error().Err(err)
return nil, nil, false, err return nil, nil, false, err
@ -581,7 +568,7 @@ func (p *BabblerPlugin) babble(who string) (string, error) {
func (p *BabblerPlugin) babbleSeed(babblerName string, seed []string) (string, error) { func (p *BabblerPlugin) babbleSeed(babblerName string, seed []string) (string, error) {
babbler, err := p.getBabbler(babblerName) babbler, err := p.getBabbler(babblerName)
if err != nil { if err != nil {
log.Error().Err(err).Msg("error getting babbler") log.Error().Err(err)
return "", nil return "", nil
} }
@ -592,14 +579,14 @@ func (p *BabblerPlugin) babbleSeed(babblerName string, seed []string) (string, e
if len(seed) == 0 { if len(seed) == 0 {
curNode, curWord, err = p.getWeightedRootNode(babbler) curNode, curWord, err = p.getWeightedRootNode(babbler)
if err != nil { if err != nil {
log.Error().Err(err).Msg("error getWeightedRootNode") log.Error().Err(err)
return "", err return "", err
} }
words = append(words, curWord.Word) words = append(words, curWord.Word)
} else { } else {
_, curNode, err = p.verifyPhrase(babbler, seed) _, curNode, err = p.verifyPhrase(babbler, seed)
if err != nil { if err != nil {
log.Error().Err(err).Msg("error verifyPhrase") log.Error().Err(err)
return "", err return "", err
} }
} }
@ -607,7 +594,7 @@ func (p *BabblerPlugin) babbleSeed(babblerName string, seed []string) (string, e
for { for {
curNode, curWord, err = p.getWeightedNextWord(curNode) curNode, curWord, err = p.getWeightedNextWord(curNode)
if err != nil { if err != nil {
log.Error().Err(err).Msg("err getWeightedNextWord") log.Error().Err(err)
return "", err return "", err
} }
if curWord.Word == " " { if curWord.Word == " " {
@ -635,7 +622,7 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa
return err return err
} }
mapping := map[uint64]*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))
@ -645,7 +632,7 @@ 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
} }
@ -654,14 +641,11 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa
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 {
r, ok := record.(*BabblerNode)
if !ok {
return fmt.Errorf("expected BabblerNode, got %T", record)
}
affected++ affected++
r := record.(BabblerNode)
r.RootFrequency += node.RootFrequency r.RootFrequency += node.RootFrequency
r.Root = 1 r.Root = 1
return nil return p.store.Update(r.BabblerID, r)
}) })
if err != nil { if err != nil {
log.Error().Err(err) log.Error().Err(err)
@ -670,13 +654,10 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa
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 {
r, ok := record.(*BabblerNode)
if !ok {
return fmt.Errorf("expected BabblerNode, got %T", record)
}
affected++ affected++
r := record.(BabblerNode)
r.RootFrequency += node.RootFrequency r.RootFrequency += node.RootFrequency
return nil return p.store.Update(r.BabblerID, r)
}) })
if err != nil { if err != nil {
log.Error().Err(err) log.Error().Err(err)
@ -685,25 +666,22 @@ 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).Msg("error inserting node") log.Error().Err(err)
return err return err
} }
log.Debug().Msgf("Inserted: %+v", node)
} else {
log.Error().Err(err).Int("affected", affected).Msgf("problem before insert")
} }
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).Msg("error finding updated node and also why do we need this?") log.Error().Err(err)
return err return err
} }
mapping[node.NodeID] = &updatedNode mapping[node.NodeId] = &updatedNode
} }
for oldNodeId, newNode := range mapping { for oldNodeId, newNode := range mapping {
@ -714,7 +692,7 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa
} }
for _, arc := range arcs { for _, arc := range arcs {
_, err := p.incrementWordArc(newNode, mapping[arc.ToNodeID]) _, err := p.incrementWordArc(newNode, mapping[arc.ToNodeId])
if err != nil { if err != nil {
return err return err
} }
@ -769,7 +747,7 @@ func (p *BabblerPlugin) babbleSeedSuffix(babblerName string, seed []string) (str
return strings.TrimSpace(strings.Join(words, " ")), nil return strings.TrimSpace(strings.Join(words, " ")), nil
} }
func (p *BabblerPlugin) getNextArcs(babblerNodeId uint64) ([]*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 {
@ -779,7 +757,7 @@ func (p *BabblerPlugin) getNextArcs(babblerNodeId uint64) ([]*BabblerArc, error)
return arcs, nil return arcs, nil
} }
func (p *BabblerPlugin) getBabblerNodeById(nodeId uint64) (*BabblerNode, error) { func (p *BabblerPlugin) getBabblerNodeById(nodeId int64) (*BabblerNode, error) {
node, err := getNode(p.store, nodeId) node, err := getNode(p.store, nodeId)
if err != nil { if err != nil {
log.Error().Err(err) log.Error().Err(err)
@ -815,13 +793,13 @@ func (p *BabblerPlugin) babbleSeedBookends(babblerName string, start, end []stri
} }
type searchNode struct { type searchNode struct {
babblerNodeId uint64 babblerNodeId int64
previous *searchNode previous *searchNode
} }
open := []*searchNode{{startWordNode.NodeID, nil}} open := []*searchNode{{startWordNode.NodeId, nil}}
closed := map[uint64]*searchNode{startWordNode.NodeID: open[0]} closed := map[int64]*searchNode{startWordNode.NodeId: open[0]}
goalNodeId := uint64(math.MaxUint64) goalNodeId := int64(-1)
for i := 0; i < len(open) && i < 1000; i++ { for i := 0; i < len(open) && i < 1000; i++ {
cur := open[i] cur := open[i]
@ -834,12 +812,12 @@ func (p *BabblerPlugin) babbleSeedBookends(babblerName string, start, end []stri
shuffle(arcs) shuffle(arcs)
for _, arc := range arcs { for _, arc := range arcs {
if _, ok := closed[arc.ToNodeID]; !ok { if _, ok := closed[arc.ToNodeId]; !ok {
child := &searchNode{arc.ToNodeID, cur} child := &searchNode{arc.ToNodeId, cur}
open = append(open, child) open = append(open, child)
closed[arc.ToNodeID] = child closed[arc.ToNodeId] = child
if arc.ToNodeID == endWordNode.NodeID { if arc.ToNodeId == endWordNode.NodeId {
goalNodeId = cur.babblerNodeId goalNodeId = cur.babblerNodeId
//add a little randomization in through maybe searching beyond this solution? //add a little randomization in through maybe searching beyond this solution?
if rand.Intn(4) == 0 { if rand.Intn(4) == 0 {
@ -850,7 +828,7 @@ func (p *BabblerPlugin) babbleSeedBookends(babblerName string, start, end []stri
} }
} }
if goalNodeId == math.MaxUint64 { if goalNodeId == -1 {
return "", errors.New("couldn't find path") return "", errors.New("couldn't find path")
} else if closed[goalNodeId].previous == nil { } else if closed[goalNodeId].previous == nil {
seeds := append(start, end...) seeds := append(start, end...)

View File

@ -3,9 +3,6 @@
package babbler package babbler
import ( import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"os"
"regexp" "regexp"
"strings" "strings"
"testing" "testing"
@ -18,18 +15,12 @@ import (
"github.com/velour/catbase/bot/user" "github.com/velour/catbase/bot/user"
) )
func init() {
output := zerolog.ConsoleWriter{Out: os.Stdout}
log.Logger = log.Output(output).With().Caller().Stack().Logger()
}
func makeMessage(payload string, r *regexp.Regexp) bot.Request { func makeMessage(payload string, r *regexp.Regexp) bot.Request {
c := &cli.CliPlugin{} c := &cli.CliPlugin{}
isCmd := strings.HasPrefix(payload, "!") isCmd := strings.HasPrefix(payload, "!")
if isCmd { if isCmd {
payload = payload[1:] payload = payload[1:]
} }
log.Debug().Msgf("isCmd: %v, payload: %v", isCmd, payload)
return bot.Request{ return bot.Request{
Conn: c, Conn: c,
Kind: bot.Message, Kind: bot.Message,
@ -43,12 +34,16 @@ func makeMessage(payload string, r *regexp.Regexp) bot.Request {
} }
} }
func newBabblerPlugin(mb *bot.MockBot) (*BabblerPlugin, func()) { func newBabblerPlugin(mb *bot.MockBot) *BabblerPlugin {
bp := New(mb) bp := New(mb)
bp.WithGoRoutines = false bp.WithGoRoutines = false
return bp, func() { mb.DB().MustExec(`
mb.TearDown() delete from babblers;
} delete from babblerWords;
delete from babblerNodes;
delete from babblerArcs;
`)
return bp
} }
func testMessage(p *BabblerPlugin, msg string) bool { func testMessage(p *BabblerPlugin, msg string) bool {
@ -65,19 +60,17 @@ func testMessage(p *BabblerPlugin, msg string) bool {
func TestBabblerNoBabbler(t *testing.T) { func TestBabblerNoBabbler(t *testing.T) {
mb := bot.NewMockBot() mb := bot.NewMockBot()
bp, td := newBabblerPlugin(mb) bp := newBabblerPlugin(mb)
defer td()
assert.NotNil(t, bp) assert.NotNil(t, bp)
testMessage(bp, "!seabass2 says") testMessage(bp, "!seabass2 says")
res := assert.Len(t, mb.Messages, 1) res := assert.Len(t, mb.Messages, 0)
assert.True(t, res) assert.True(t, res)
assert.Contains(t, mb.Messages[0], "seabass2 babbler not found") // assert.Contains(t, mb.Messages[0], "seabass2 babbler not found")
} }
func TestBabblerNothingSaid(t *testing.T) { func TestBabblerNothingSaid(t *testing.T) {
mb := bot.NewMockBot() mb := bot.NewMockBot()
bp, td := newBabblerPlugin(mb) bp := newBabblerPlugin(mb)
defer td()
assert.NotNil(t, bp) assert.NotNil(t, bp)
res := testMessage(bp, "initialize babbler for seabass") res := testMessage(bp, "initialize babbler for seabass")
assert.True(t, res) assert.True(t, res)
@ -91,15 +84,13 @@ func TestBabblerNothingSaid(t *testing.T) {
func TestBabbler(t *testing.T) { func TestBabbler(t *testing.T) {
mb := bot.NewMockBot() mb := bot.NewMockBot()
bp, _ := newBabblerPlugin(mb) bp := newBabblerPlugin(mb)
//defer td()
assert.NotNil(t, bp) assert.NotNil(t, bp)
testMessage(bp, "!initialize babbler for tester") testMessage(bp, "!initialize babbler for tester")
testMessage(bp, "This is a message") testMessage(bp, "This is a message")
testMessage(bp, "This is another message") testMessage(bp, "This is another message")
testMessage(bp, "This is a long message") testMessage(bp, "This is a long message")
res := testMessage(bp, "!tester says") res := testMessage(bp, "!tester says")
log.Debug().Msgf("messages: %+v", mb.Messages)
assert.True(t, res) assert.True(t, res)
if assert.Len(t, mb.Messages, 1) { if assert.Len(t, mb.Messages, 1) {
assert.Contains(t, mb.Messages[0], "this is") assert.Contains(t, mb.Messages[0], "this is")
@ -109,8 +100,7 @@ func TestBabbler(t *testing.T) {
func TestBabblerSeed(t *testing.T) { func TestBabblerSeed(t *testing.T) {
mb := bot.NewMockBot() mb := bot.NewMockBot()
bp, td := newBabblerPlugin(mb) bp := newBabblerPlugin(mb)
defer td()
assert.NotNil(t, bp) assert.NotNil(t, bp)
testMessage(bp, "This is a message") testMessage(bp, "This is a message")
@ -125,8 +115,7 @@ func TestBabblerSeed(t *testing.T) {
func TestBabblerMultiSeed(t *testing.T) { func TestBabblerMultiSeed(t *testing.T) {
mb := bot.NewMockBot() mb := bot.NewMockBot()
bp, td := newBabblerPlugin(mb) bp := newBabblerPlugin(mb)
defer td()
assert.NotNil(t, bp) assert.NotNil(t, bp)
testMessage(bp, "This is a message") testMessage(bp, "This is a message")
@ -141,8 +130,7 @@ func TestBabblerMultiSeed(t *testing.T) {
func TestBabblerBadSeed(t *testing.T) { func TestBabblerBadSeed(t *testing.T) {
mb := bot.NewMockBot() mb := bot.NewMockBot()
bp, td := newBabblerPlugin(mb) bp := newBabblerPlugin(mb)
defer td()
assert.NotNil(t, bp) assert.NotNil(t, bp)
testMessage(bp, "This is a message") testMessage(bp, "This is a message")
@ -157,8 +145,7 @@ func TestBabblerBadSeed(t *testing.T) {
func TestBabblerBadSeed2(t *testing.T) { func TestBabblerBadSeed2(t *testing.T) {
mb := bot.NewMockBot() mb := bot.NewMockBot()
bp, td := newBabblerPlugin(mb) bp := newBabblerPlugin(mb)
defer td()
assert.NotNil(t, bp) assert.NotNil(t, bp)
testMessage(bp, "This is a message") testMessage(bp, "This is a message")
@ -173,8 +160,7 @@ func TestBabblerBadSeed2(t *testing.T) {
func TestBabblerSuffixSeed(t *testing.T) { func TestBabblerSuffixSeed(t *testing.T) {
mb := bot.NewMockBot() mb := bot.NewMockBot()
bp, td := newBabblerPlugin(mb) bp := newBabblerPlugin(mb)
defer td()
assert.NotNil(t, bp) assert.NotNil(t, bp)
testMessage(bp, "This is message one") testMessage(bp, "This is message one")
@ -190,8 +176,7 @@ func TestBabblerSuffixSeed(t *testing.T) {
func TestBabblerBadSuffixSeed(t *testing.T) { func TestBabblerBadSuffixSeed(t *testing.T) {
mb := bot.NewMockBot() mb := bot.NewMockBot()
bp, td := newBabblerPlugin(mb) bp := newBabblerPlugin(mb)
defer td()
assert.NotNil(t, bp) assert.NotNil(t, bp)
testMessage(bp, "This is message one") testMessage(bp, "This is message one")
@ -205,8 +190,7 @@ func TestBabblerBadSuffixSeed(t *testing.T) {
func TestBabblerBookendSeed(t *testing.T) { func TestBabblerBookendSeed(t *testing.T) {
mb := bot.NewMockBot() mb := bot.NewMockBot()
bp, td := newBabblerPlugin(mb) bp := newBabblerPlugin(mb)
defer td()
assert.NotNil(t, bp) assert.NotNil(t, bp)
testMessage(bp, "This is message one") testMessage(bp, "This is message one")
@ -220,8 +204,7 @@ func TestBabblerBookendSeed(t *testing.T) {
func TestBabblerBadBookendSeed(t *testing.T) { func TestBabblerBadBookendSeed(t *testing.T) {
mb := bot.NewMockBot() mb := bot.NewMockBot()
bp, td := newBabblerPlugin(mb) bp := newBabblerPlugin(mb)
defer td()
assert.NotNil(t, bp) assert.NotNil(t, bp)
testMessage(bp, "This is message one") testMessage(bp, "This is message one")
@ -235,8 +218,7 @@ func TestBabblerBadBookendSeed(t *testing.T) {
func TestBabblerMiddleOutSeed(t *testing.T) { func TestBabblerMiddleOutSeed(t *testing.T) {
mb := bot.NewMockBot() mb := bot.NewMockBot()
bp, td := newBabblerPlugin(mb) bp := newBabblerPlugin(mb)
defer td()
assert.NotNil(t, bp) assert.NotNil(t, bp)
testMessage(bp, "This is message one") testMessage(bp, "This is message one")
@ -250,8 +232,7 @@ func TestBabblerMiddleOutSeed(t *testing.T) {
func TestBabblerBadMiddleOutSeed(t *testing.T) { func TestBabblerBadMiddleOutSeed(t *testing.T) {
mb := bot.NewMockBot() mb := bot.NewMockBot()
bp, td := newBabblerPlugin(mb) bp := newBabblerPlugin(mb)
defer td()
assert.NotNil(t, bp) assert.NotNil(t, bp)
testMessage(bp, "This is message one") testMessage(bp, "This is message one")
@ -265,8 +246,7 @@ func TestBabblerBadMiddleOutSeed(t *testing.T) {
func TestBabblerMerge(t *testing.T) { func TestBabblerMerge(t *testing.T) {
mb := bot.NewMockBot() mb := bot.NewMockBot()
bp, td := newBabblerPlugin(mb) bp := newBabblerPlugin(mb)
defer td()
assert.NotNil(t, bp) assert.NotNil(t, bp)
testMessage(bp, "<tester> This is a message") testMessage(bp, "<tester> This is a message")
@ -290,8 +270,7 @@ func TestBabblerMerge(t *testing.T) {
func TestHelp(t *testing.T) { func TestHelp(t *testing.T) {
mb := bot.NewMockBot() mb := bot.NewMockBot()
bp, td := newBabblerPlugin(mb) bp := newBabblerPlugin(mb)
defer td()
assert.NotNil(t, bp) assert.NotNil(t, bp)
c := &cli.CliPlugin{} c := &cli.CliPlugin{}
bp.help(c, bot.Help, msg.Message{Channel: "channel"}, []string{}) bp.help(c, bot.Help, msg.Message{Channel: "channel"}, []string{})

View File

@ -2,7 +2,6 @@ package babbler
import ( import (
"fmt" "fmt"
"github.com/rs/zerolog/log"
"strings" "strings"
) )
@ -18,20 +17,11 @@ func (p *BabblerPlugin) addToBabbler(babblerName, whatWasSaid string) {
babblerID, err := p.getOrCreateBabbler(babblerName) babblerID, err := p.getOrCreateBabbler(babblerName)
if err == nil { if err == nil {
if p.WithGoRoutines { if p.WithGoRoutines {
go func() { go p.addToMarkovChain(babblerID, whatWasSaid)
if err := p.addToMarkovChain(babblerID, whatWasSaid); err != nil {
log.Error().Err(err).Msg("addToMarkovChain")
}
}()
} else { } else {
err = p.addToMarkovChain(babblerID, whatWasSaid) p.addToMarkovChain(babblerID, whatWasSaid)
if err != nil {
log.Error().Err(err).Msg("error adding to chain")
} }
} }
} else {
log.Error().Err(err).Msg("error getting or creating babbler")
}
} }
func (p *BabblerPlugin) getBabble(who string, tokens []string) string { func (p *BabblerPlugin) getBabble(who string, tokens []string) string {
@ -39,9 +29,9 @@ func (p *BabblerPlugin) getBabble(who string, tokens []string) string {
if err != nil { if err != nil {
if err == NO_BABBLER { if err == NO_BABBLER {
return fmt.Sprintf("%s babbler not found.", who) // return fmt.Sprintf("%s babbler not found.", who), true
return ""
} }
log.Debug().Err(err).Msg("error getting babbler")
} else { } else {
var saying string var saying string
@ -56,17 +46,12 @@ func (p *BabblerPlugin) getBabble(who string, tokens []string) string {
return fmt.Sprintf("%s hasn't said anything yet.", who) return fmt.Sprintf("%s hasn't said anything yet.", who)
} else if err == NEVER_SAID { } else if err == NEVER_SAID {
return fmt.Sprintf("%s never said '%s'", who, strings.Join(tokens, " ")) return fmt.Sprintf("%s never said '%s'", who, strings.Join(tokens, " "))
} else {
log.Error().Err(err).Msgf("error babbling")
return fmt.Sprintf("babbler encountered an error for %s", who)
} }
} else if saying != "" { } else if saying != "" {
return saying return saying
} else {
return "for some reason the saying was empty"
} }
} }
return "IDK how we got here" return ""
} }
func (p *BabblerPlugin) getBabbleWithSuffix(who string, tokens []string) string { func (p *BabblerPlugin) getBabbleWithSuffix(who string, tokens []string) string {

View File

@ -45,10 +45,10 @@ type BeersPlugin struct {
} }
type untappdUser struct { type untappdUser struct {
UntappdUser string `boltholdKey:"UntappdUser"` untappdUser string `boltholdid:"untappdUser"`
Channel string channel string
LastCheckin int lastCheckin int
ChanNick string chanNick string
} }
// New BeersPlugin creates a new BeersPlugin with the Plugin interface // New BeersPlugin creates a new BeersPlugin with the Plugin interface
@ -160,17 +160,17 @@ func (p *BeersPlugin) register() {
untappdNick := r.Values["who"] untappdNick := r.Values["who"]
u := untappdUser{ u := untappdUser{
UntappdUser: untappdNick, untappdUser: untappdNick,
ChanNick: chanNick, chanNick: chanNick,
Channel: channel, channel: channel,
} }
log.Info(). log.Info().
Str("UntappdUser", u.UntappdUser). Str("untappdUser", u.untappdUser).
Str("nick", u.ChanNick). Str("nick", u.chanNick).
Msg("Creating Untappd user") Msg("Creating Untappd user")
count, err := p.store.Count(untappdUser{}, bh.Where("UntappdUser").Eq(u.UntappdUser)) count, err := p.store.Count(untappdUser{}, bh.Where("untappdUser").Eq(u.untappdUser))
if err != nil { if err != nil {
log.Error().Err(err).Msgf("Error registering untappd") log.Error().Err(err).Msgf("Error registering untappd")
} }
@ -178,7 +178,7 @@ func (p *BeersPlugin) register() {
p.b.Send(r.Conn, bot.Message, channel, "I'm already watching you.") p.b.Send(r.Conn, bot.Message, channel, "I'm already watching you.")
return true return true
} }
err = p.store.Insert(u.UntappdUser, u) err = p.store.Insert(u.untappdUser, u)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("Error registering untappd") log.Error().Err(err).Msgf("Error registering untappd")
p.b.Send(r.Conn, bot.Message, channel, "I can't see.") p.b.Send(r.Conn, bot.Message, channel, "I can't see.")
@ -270,7 +270,7 @@ func (p *BeersPlugin) doIKnow(nick, id string) bool {
return count > 0 return count > 0
} }
// Sends random affirmation to the Channel. This could be better (with a datastore for sayings) // Sends random affirmation to the channel. This could be better (with a datastore for sayings)
func (p *BeersPlugin) randomReply(c bot.Connector, channel string) { func (p *BeersPlugin) randomReply(c bot.Connector, channel string) {
replies := []string{"ZIGGY! ZAGGY!", "HIC!", "Stay thirsty, my friend!"} replies := []string{"ZIGGY! ZAGGY!", "HIC!", "Stay thirsty, my friend!"}
p.b.Send(c, bot.Message, channel, replies[rand.Intn(len(replies))]) p.b.Send(c, bot.Message, channel, replies[rand.Intn(len(replies))])
@ -384,9 +384,9 @@ func (p *BeersPlugin) checkUntappd(c bot.Connector, channel string) {
return return
} }
for _, u := range users { for _, u := range users {
userMap[u.UntappdUser] = u userMap[u.untappdUser] = u
if u.ChanNick == "" { if u.chanNick == "" {
log.Fatal().Msg("Empty ChanNick for no good reason.") log.Fatal().Msg("Empty chanNick for no good reason.")
} }
} }
@ -398,7 +398,7 @@ func (p *BeersPlugin) checkUntappd(c bot.Connector, channel string) {
for i := len(chks); i > 0; i-- { for i := len(chks); i > 0; i-- {
checkin := chks[i-1] checkin := chks[i-1]
if checkin.Checkin_id <= userMap[checkin.User.User_name].LastCheckin { if checkin.Checkin_id <= userMap[checkin.User.User_name].lastCheckin {
continue continue
} }
@ -421,8 +421,8 @@ func (p *BeersPlugin) sendCheckin(c bot.Connector, channel string, user untappdU
breweryName := checkin.Brewery["brewery_name"].(string) breweryName := checkin.Brewery["brewery_name"].(string)
log.Debug(). log.Debug().
Msgf("user.ChanNick: %s, user.UntappdUser: %s, checkin.User.User_name: %s", Msgf("user.chanNick: %s, user.untappdUser: %s, checkin.User.User_name: %s",
user.ChanNick, user.UntappdUser, checkin.User.User_name) user.chanNick, user.untappdUser, checkin.User.User_name)
args := []interface{}{} args := []interface{}{}
if checkin.Badges.Count > 0 { if checkin.Badges.Count > 0 {
@ -453,11 +453,11 @@ func (p *BeersPlugin) sendCheckin(c bot.Connector, channel string, user untappdU
} }
// Don't add beers till after a photo has been detected (or failed once) // Don't add beers till after a photo has been detected (or failed once)
p.addBeers(nil, user.ChanNick, "", 1) p.addBeers(nil, user.chanNick, "", 1)
drunken := p.getBeers(user.ChanNick, "") drunken := p.getBeers(user.chanNick, "")
msg := fmt.Sprintf("%s just drank %s by %s%s, bringing his drunkeness to %d", msg := fmt.Sprintf("%s just drank %s by %s%s, bringing his drunkeness to %d",
user.ChanNick, beerName, breweryName, venue, drunken) user.chanNick, beerName, breweryName, venue, drunken)
if checkin.Rating_score > 0 { if checkin.Rating_score > 0 {
msg = fmt.Sprintf("%s. Rating: %.2f", msg, checkin.Rating_score) msg = fmt.Sprintf("%s. Rating: %.2f", msg, checkin.Rating_score)
} }
@ -468,9 +468,9 @@ func (p *BeersPlugin) sendCheckin(c bot.Connector, channel string, user untappdU
args = append([]interface{}{channel, msg}, args...) args = append([]interface{}{channel, msg}, args...)
user.LastCheckin = checkin.Checkin_id user.lastCheckin = checkin.Checkin_id
err := p.store.Update(user.UntappdUser, user) err := p.store.Update(user.untappdUser, user)
if err != nil { if err != nil {
log.Error().Err(err).Msg("UPDATE ERROR!") log.Error().Err(err).Msg("UPDATE ERROR!")
} }

View File

@ -48,21 +48,19 @@ func testMessage(p *BeersPlugin, msg string) bool {
return false return false
} }
func makeBeersPlugin(t *testing.T) (*BeersPlugin, *bot.MockBot, func()) { func makeBeersPlugin(t *testing.T) (*BeersPlugin, *bot.MockBot) {
mb := bot.NewMockBot() mb := bot.NewMockBot()
counter.New(mb) counter.New(mb)
mb.DB().MustExec(`delete from counter; delete from counter_alias;`)
b := New(mb) b := New(mb)
counter.MkAlias(mb.Store(), "beer", DEFAULT_ITEM) counter.MkAlias(mb.DB(), "beer", DEFAULT_ITEM)
counter.MkAlias(mb.Store(), "beers", DEFAULT_ITEM) counter.MkAlias(mb.DB(), "beers", DEFAULT_ITEM)
return b, mb, func() { return b, mb
mb.TearDown()
}
} }
func TestCounter(t *testing.T) { func TestCounter(t *testing.T) {
_, mb, td := makeBeersPlugin(t) _, mb := makeBeersPlugin(t)
defer td() i, err := counter.GetUserItem(mb.DB(), "tester", "id", "test")
i, err := counter.GetUserItem(mb.Store(), "tester", "id", "test")
if !assert.Nil(t, err) { if !assert.Nil(t, err) {
t.Log(err) t.Log(err)
t.Fatal() t.Fatal()
@ -72,68 +70,62 @@ func TestCounter(t *testing.T) {
} }
func TestImbibe(t *testing.T) { func TestImbibe(t *testing.T) {
b, mb, td := makeBeersPlugin(t) b, mb := makeBeersPlugin(t)
defer td()
testMessage(b, "imbibe") testMessage(b, "imbibe")
assert.Len(t, mb.Messages, 1) assert.Len(t, mb.Messages, 1)
testMessage(b, "imbibe") testMessage(b, "imbibe")
assert.Len(t, mb.Messages, 2) assert.Len(t, mb.Messages, 2)
it, err := counter.GetUserItem(mb.Store(), "tester", "id", DEFAULT_ITEM) it, err := counter.GetUserItem(mb.DB(), "tester", "id", DEFAULT_ITEM)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 2, it.Count) assert.Equal(t, 2, it.Count)
} }
func TestEq(t *testing.T) { func TestEq(t *testing.T) {
b, mb, td := makeBeersPlugin(t) b, mb := makeBeersPlugin(t)
defer td()
testMessage(b, "beers = 3") testMessage(b, "beers = 3")
assert.Len(t, mb.Messages, 1) assert.Len(t, mb.Messages, 1)
it, err := counter.GetUserItem(mb.Store(), "tester", "id", DEFAULT_ITEM) it, err := counter.GetUserItem(mb.DB(), "tester", "id", DEFAULT_ITEM)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 3, it.Count) assert.Equal(t, 3, it.Count)
} }
func TestEqZero(t *testing.T) { func TestEqZero(t *testing.T) {
b, mb, td := makeBeersPlugin(t) b, mb := makeBeersPlugin(t)
defer td()
testMessage(b, "beers += 5") testMessage(b, "beers += 5")
testMessage(b, "beers = 0") testMessage(b, "beers = 0")
assert.Len(t, mb.Messages, 2) assert.Len(t, mb.Messages, 2)
assert.Contains(t, mb.Messages[1], "reversal of fortune") assert.Contains(t, mb.Messages[1], "reversal of fortune")
it, err := counter.GetUserItem(mb.Store(), "tester", "id", DEFAULT_ITEM) it, err := counter.GetUserItem(mb.DB(), "tester", "id", DEFAULT_ITEM)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 0, it.Count) assert.Equal(t, 0, it.Count)
} }
func TestBeersPlusEq(t *testing.T) { func TestBeersPlusEq(t *testing.T) {
b, mb, td := makeBeersPlugin(t) b, mb := makeBeersPlugin(t)
defer td()
testMessage(b, "beers += 5") testMessage(b, "beers += 5")
assert.Len(t, mb.Messages, 1) assert.Len(t, mb.Messages, 1)
testMessage(b, "beers += 5") testMessage(b, "beers += 5")
assert.Len(t, mb.Messages, 2) assert.Len(t, mb.Messages, 2)
it, err := counter.GetUserItem(mb.Store(), "tester", "id", DEFAULT_ITEM) it, err := counter.GetUserItem(mb.DB(), "tester", "id", DEFAULT_ITEM)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 10, it.Count) assert.Equal(t, 10, it.Count)
} }
func TestPuke(t *testing.T) { func TestPuke(t *testing.T) {
b, mb, td := makeBeersPlugin(t) b, mb := makeBeersPlugin(t)
defer td()
testMessage(b, "beers += 5") testMessage(b, "beers += 5")
it, err := counter.GetUserItem(mb.Store(), "tester", "id", DEFAULT_ITEM) it, err := counter.GetUserItem(mb.DB(), "tester", "id", DEFAULT_ITEM)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 5, it.Count) assert.Equal(t, 5, it.Count)
testMessage(b, "puke") testMessage(b, "puke")
it, err = counter.GetUserItem(mb.Store(), "tester", "id", DEFAULT_ITEM) it, err = counter.GetUserItem(mb.DB(), "tester", "id", DEFAULT_ITEM)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 0, it.Count) assert.Equal(t, 0, it.Count)
} }
func TestBeersReport(t *testing.T) { func TestBeersReport(t *testing.T) {
b, mb, td := makeBeersPlugin(t) b, mb := makeBeersPlugin(t)
defer td()
testMessage(b, "beers += 5") testMessage(b, "beers += 5")
it, err := counter.GetUserItem(mb.Store(), "tester", "id", DEFAULT_ITEM) it, err := counter.GetUserItem(mb.DB(), "tester", "id", DEFAULT_ITEM)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, 5, it.Count) assert.Equal(t, 5, it.Count)
testMessage(b, "beers") testMessage(b, "beers")
@ -143,8 +135,7 @@ func TestBeersReport(t *testing.T) {
} }
func TestHelp(t *testing.T) { func TestHelp(t *testing.T) {
b, mb, td := makeBeersPlugin(t) b, mb := makeBeersPlugin(t)
defer td() b.help(&cli.CliPlugin{}, bot.Help, msg.Message{Channel: "channel"}, []string{})
b.help(&cli.CliPlugin{}, bot.Help, msg.Message{Channel: "Channel"}, []string{})
assert.Len(t, mb.Messages, 1) assert.Len(t, mb.Messages, 1)
} }

View File

@ -10,6 +10,7 @@ import (
"time" "time"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/jmoiron/sqlx"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/velour/catbase/bot" "github.com/velour/catbase/bot"
@ -22,6 +23,7 @@ var embeddedFS embed.FS
type CliPlugin struct { type CliPlugin struct {
bot bot.Bot bot bot.Bot
db *sqlx.DB
cache string cache string
counter int counter int
} }

View File

@ -30,7 +30,7 @@ var embeddedFS embed.FS
// Factoid stores info about our factoid for lookup and later interaction // Factoid stores info about our factoid for lookup and later interaction
type Factoid struct { type Factoid struct {
ID uint64 `boltholdKey:"ID"` ID int64 `boltholdid:"ID"`
Fact string Fact string
Tidbit string Tidbit string
Verb string Verb string

View File

@ -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).SortBy("Day").Reverse()) err := store.FindOne(fe, bh.Where("Channel").Eq(channel))
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Debug().Msgf("getLastFirst: %+v", fe) log.Debug().Msgf("ID: %v Day %v Time %v Body %v Nick %v", fe)
return fe, nil return fe, nil
} }

View File

@ -237,7 +237,7 @@ func parseCmd(r *regexp.Regexp, body string) cmd {
} }
type goal struct { type goal struct {
ID uint64 `boltholdKey:"ID"` ID uint64 `boltholdid:"ID"`
Kind string Kind string
Who string Who string
What string What string

View File

@ -44,7 +44,7 @@ func New(b bot.Bot) *InventoryPlugin {
} }
type Item struct { type Item struct {
Name string `boltholdKey:"Name"` Name string `boltholdid:"Name"`
} }
func (p *InventoryPlugin) giveItemFilter(input string) string { func (p *InventoryPlugin) giveItemFilter(input string) string {

View File

@ -8,6 +8,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/jmoiron/sqlx"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/velour/catbase/bot" "github.com/velour/catbase/bot"
"github.com/velour/catbase/plugins/newsbid/webshit" "github.com/velour/catbase/plugins/newsbid/webshit"
@ -15,6 +16,7 @@ import (
type NewsBid struct { type NewsBid struct {
bot bot.Bot bot bot.Bot
db *sqlx.DB
store *bh.Store store *bh.Store
ws *webshit.Webshit ws *webshit.Webshit
} }
@ -54,7 +56,7 @@ func (p *NewsBid) balanceCmd(r bot.Request) bool {
bal := p.ws.GetBalance(r.Msg.User.Name) bal := p.ws.GetBalance(r.Msg.User.Name)
p.bot.Send(r.Conn, bot.Message, r.Msg.Channel, fmt.Sprintf("%s, your current balance is %d.", p.bot.Send(r.Conn, bot.Message, r.Msg.Channel, fmt.Sprintf("%s, your current balance is %d.",
r.Msg.User.Name, bal.Balance)) r.Msg.User.Name, bal))
return true return true
} }

View File

@ -28,7 +28,7 @@ type Webshit struct {
} }
type Bid struct { type Bid struct {
ID int64 `boltholdKey:"ID"` ID int64 `boltholdid:"ID"`
User string User string
Title string Title string
URL string URL string
@ -42,7 +42,7 @@ type Bid struct {
} }
type Balance struct { type Balance struct {
User string `boltholdKey:"User"` User string `boltholdid:"User"`
Balance int Balance int
Score int Score int
} }

View File

@ -2,13 +2,12 @@ package quotegame
import ( import (
"fmt" "fmt"
bh "github.com/timshannon/bolthold"
"github.com/velour/catbase/plugins/fact"
"math/rand" "math/rand"
"regexp" "regexp"
"strings" "strings"
"time" "time"
"github.com/jmoiron/sqlx"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/velour/catbase/bot" "github.com/velour/catbase/bot"
"github.com/velour/catbase/config" "github.com/velour/catbase/config"
@ -17,7 +16,7 @@ import (
type QuoteGame struct { type QuoteGame struct {
b bot.Bot b bot.Bot
c *config.Config c *config.Config
store *bh.Store db *sqlx.DB
handlers []bot.HandlerSpec handlers []bot.HandlerSpec
@ -29,7 +28,6 @@ func New(b bot.Bot) *QuoteGame {
p := &QuoteGame{ p := &QuoteGame{
b: b, b: b,
c: b.Config(), c: b.Config(),
store: b.Store(),
currentGame: nil, currentGame: nil,
} }
p.register() p.register()
@ -59,39 +57,15 @@ type quote struct {
Tidbit string Tidbit string
} }
func (p *QuoteGame) getAllQuotes() ([]fact.Factoid, error) { func (p *QuoteGame) getAllQuotes() ([]quote, error) {
threshold := p.c.GetInt("quotegame.threshold", 10) threshold := p.c.GetInt("quotegame.threshold", 10)
q := `select fact, tidbit from factoid where fact like '%quotes' group by fact having count(fact) > ?`
facts := []fact.Factoid{} quotes := []quote{}
err := p.db.Select(&quotes, q, threshold)
res, err := p.store.FindAggregate(fact.Factoid{},
bh.Where("Fact").RegExp(regexp.MustCompile(`.+\squotes$`)),
"Fact")
for _, group := range res {
if group.Count() > threshold {
// keep this group
//factName := ""
//group.Group(&factName)
fact := &fact.Factoid{}
group.Reduction(&fact)
facts = append(facts, *fact)
}
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
return quotes, nil
return facts, nil
// todo: remove
//q := `select fact, tidbit from factoid where fact like '%quotes' group by fact having count(fact) > ?`
//quotes := []fact.Factoid{}
//err := p.store.Find(&quotes, bh.Where("Fact").RegExp(regexp.MustCompile(`.*quotes$`)))
//err := p.db.Select(&quotes, q, threshold)
//if err != nil {
// return nil, err
//}
//return quotes, nil
} }
func (p *QuoteGame) getRandomquote() (string, string, error) { func (p *QuoteGame) getRandomquote() (string, string, error) {

View File

@ -58,8 +58,7 @@ func TestCornerCaseBug(t *testing.T) {
p.rememberCmd(rememberMsg) p.rememberCmd(rememberMsg)
assert.Len(t, mb.Messages, 1) assert.Len(t, mb.Messages, 1)
assert.Contains(t, mb.Messages[0], "horse dick") assert.Contains(t, mb.Messages[0], "horse dick")
q, err := fact.GetSingleFact(mb.Store(), "user1 quotes") q, err := fact.GetSingleFact(mb.DB(), "user1 quotes")
if assert.Nil(t, err) { assert.Nil(t, err)
assert.Contains(t, q.Tidbit, "horse dick") assert.Contains(t, q.Tidbit, "horse dick")
}
} }

View File

@ -112,21 +112,13 @@ func (p *ReminderPlugin) message(c bot.Connector, kind bot.Kind, message msg.Mes
when := time.Now().UTC().Add(dur) when := time.Now().UTC().Add(dur)
what := strings.Join(parts[4:], " ") what := strings.Join(parts[4:], " ")
rem := &Reminder{ p.addReminder(&Reminder{
From: from, From: from,
Who: who, Who: who,
What: what, What: what,
When: when, When: when,
Channel: channel, Channel: channel,
} })
log.Debug().Msgf("Adding reminder: %v", rem)
p.addReminder(rem)
all := []Reminder{}
p.store.Find(&all, &bh.Query{})
log.Debug().Msgf("All reminders: %v", all)
} 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
@ -183,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]))
} }
} }
@ -222,14 +214,16 @@ func (p *ReminderPlugin) getNextReminder() *Reminder {
defer p.mutex.Unlock() defer p.mutex.Unlock()
reminder := Reminder{} reminder := Reminder{}
err := p.store.FindOne(&reminder, (&bh.Query{}).SortBy("When")) res, err := p.store.FindAggregate(Reminder{}, &bh.Query{}, "")
if err != nil && err != bh.ErrNotFound { if err != nil {
log.Error().Err(err).Msgf("aggregate reminders failed") log.Error().Err(err)
return nil return nil
} else if err == bh.ErrNotFound { }
if len(res) == 0 {
log.Error().Msg("No next reminder in system.") log.Error().Msg("No next reminder in system.")
return nil return nil
} }
res[0].Max("RemindWhen", &reminder)
return &reminder return &reminder
} }
@ -258,20 +252,13 @@ func (p *ReminderPlugin) getRemindersFormatted(filter, who string) (string, erro
p.mutex.Lock() p.mutex.Lock()
defer p.mutex.Unlock() defer p.mutex.Unlock()
var total int q := bh.Where(filter).Eq(who)
var err error
reminders := []Reminder{}
if filter == "" || who == "" { if filter == "" || who == "" {
total, _ = p.store.Count(Reminder{}, &bh.Query{}) q = &bh.Query{}
err = p.store.Find(&reminders, (&bh.Query{}).SortBy("ID").Limit(max))
} else {
log.Debug().Msgf("Looking for reminders where %s eq %s", filter, who)
total, _ = p.store.Count(Reminder{}, bh.Where(filter).Eq(who))
err = p.store.Find(&reminders, bh.Where(filter).Eq(who).SortBy("ID").Limit(max))
} }
total, err := p.store.Count(Reminder{}, q)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("error finding reminders") log.Error().Err(err)
return "", nil return "", nil
} }
@ -279,9 +266,15 @@ func (p *ReminderPlugin) getRemindersFormatted(filter, who string) (string, erro
return "no pending reminders", nil return "no pending reminders", nil
} }
reminders := []Reminder{}
err = p.store.Find(&reminders, q.SortBy("ID").Limit(max))
if err != nil {
log.Error().Err(err)
return "", nil
}
txt := "" txt := ""
for counter, reminder := range reminders { for counter, reminder := range reminders {
txt += fmt.Sprintf("%d) %s -> %s :: %s @ %s (%d)\n", reminder.ID, 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++
} }
@ -298,11 +291,11 @@ 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("From", 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("Who", me) return p.getRemindersFormatted("ToWho", me)
} }
func (p *ReminderPlugin) queueUpNextReminder() { func (p *ReminderPlugin) queueUpNextReminder() {
@ -334,7 +327,6 @@ func reminderer(c bot.Connector, p *ReminderPlugin) {
log.Error().Err(err).Msgf("could not send reminder") log.Error().Err(err).Msgf("could not send reminder")
} }
log.Debug().Msgf("trying to delete: %v", reminder)
if err := p.deleteReminder(reminder.ID); err != nil { if err := p.deleteReminder(reminder.ID); err != nil {
log.Fatal(). log.Fatal().
Uint64("ID", reminder.ID). Uint64("ID", reminder.ID).

View File

@ -65,13 +65,11 @@ func TestReminder(t *testing.T) {
func TestReminderReorder(t *testing.T) { func TestReminderReorder(t *testing.T) {
c, mb, td := setup(t) c, mb, td := setup(t)
defer td() defer td()
min, max := 1, 2 res := c.message(makeMessage("!remind testuser in 2s don't fail this test 2"))
res := c.message(makeMessage(fmt.Sprintf("!remind testuser in %ds don't fail this test 1", min)))
assert.True(t, res) assert.True(t, res)
res = c.message(makeMessage(fmt.Sprintf("!remind testuser in %ds don't fail this test 2", max))) res = c.message(makeMessage("!remind testuser in 1s don't fail this test 1"))
assert.True(t, res) assert.True(t, res)
time.Sleep(5 * time.Second)
time.Sleep(time.Duration(max+1) * time.Second)
assert.Len(t, mb.Messages, 4) assert.Len(t, mb.Messages, 4)
assert.Contains(t, mb.Messages[0], "Sure tester, I'll remind testuser.") assert.Contains(t, mb.Messages[0], "Sure tester, I'll remind testuser.")
assert.Contains(t, mb.Messages[1], "Sure tester, I'll remind testuser.") assert.Contains(t, mb.Messages[1], "Sure tester, I'll remind testuser.")
@ -118,7 +116,7 @@ func TestListBy(t *testing.T) {
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 @ ")

View File

@ -32,17 +32,15 @@ func makeMessage(payload string) bot.Request {
} }
} }
func setup(t *testing.T) (*YourPlugin, *bot.MockBot, func()) { func setup(t *testing.T) (*YourPlugin, *bot.MockBot) {
mb := bot.NewMockBot() mb := bot.NewMockBot()
c := New(mb) c := New(mb)
return c, mb, func() { mb.DB().MustExec(`delete from config;`)
mb.TearDown() return c, mb
}
} }
func TestReplacement(t *testing.T) { func TestReplacement(t *testing.T) {
c, mb, td := setup(t) c, mb := setup(t)
defer td()
c.config.Set("Your.MaxLength", "1000") c.config.Set("Your.MaxLength", "1000")
c.config.SetArray("your.replacements", []string{"0"}) c.config.SetArray("your.replacements", []string{"0"})
c.config.Set("your.replacements.0.freq", "1.0") c.config.Set("your.replacements.0.freq", "1.0")
@ -55,8 +53,7 @@ func TestReplacement(t *testing.T) {
} }
func TestNoReplacement(t *testing.T) { func TestNoReplacement(t *testing.T) {
c, mb, td := setup(t) c, mb := setup(t)
defer td()
c.config.Set("Your.MaxLength", "1000") c.config.Set("Your.MaxLength", "1000")
c.config.SetArray("your.replacements", []string{"0", "1", "2"}) c.config.SetArray("your.replacements", []string{"0", "1", "2"})
c.config.Set("your.replacements.0.freq", "1.0") c.config.Set("your.replacements.0.freq", "1.0")