This commit is contained in:
Chris Sexton 2021-12-20 12:40:10 -05:00
parent d89569cdc6
commit fafbd2ce64
33 changed files with 1010 additions and 1492 deletions

View File

@ -8,9 +8,9 @@ jobs:
steps:
- name: Set up Go 1.16
uses: actions/setup-go@v1
uses: actions/setup-go@v2
with:
go-version: 1.16.x
go-version: '^1.18'
id: go
- name: Check out code into the Go module directory

View File

@ -4,6 +4,7 @@ package bot
import (
"fmt"
bh "github.com/timshannon/bolthold"
"math/rand"
"net/http"
"reflect"
@ -13,7 +14,6 @@ import (
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/jmoiron/sqlx"
"github.com/rs/zerolog/log"
"github.com/velour/catbase/bot/history"
"github.com/velour/catbase/bot/msg"
@ -111,8 +111,6 @@ func New(config *config.Config, connector Connector) Bot {
history: history.New(historySz),
}
bot.migrateDB()
bot.RefreshPluginBlacklist()
bot.RefreshPluginWhitelist()
@ -161,33 +159,13 @@ func (b *bot) Config() *config.Config {
return b.config
}
func (b *bot) DB() *sqlx.DB {
return b.config.DB
}
// TODO: remove
//func (b *bot) DB() *sqlx.DB {
// return b.config.DB()
//}
// Create any tables if necessary based on version of DB
// Plugins should create their own tables, these are only for official bot stuff
// Note: This does not return an error. Database issues are all fatal at this stage.
func (b *bot) migrateDB() {
if _, err := b.DB().Exec(`create table if not exists variables (
id integer primary key,
name string,
value string
);`); err != nil {
log.Fatal().Err(err).Msgf("Initial db migration create variables table")
}
if _, err := b.DB().Exec(`create table if not exists pluginBlacklist (
channel string,
name string,
primary key (channel, name)
);`); err != nil {
log.Fatal().Err(err).Msgf("Initial db migration create blacklist table")
}
if _, err := b.DB().Exec(`create table if not exists pluginWhitelist (
name string primary key
);`); err != nil {
log.Fatal().Err(err).Msgf("Initial db migration create whitelist table")
}
func (b *bot) Store() *bh.Store {
return b.config.Store()
}
// Adds a constructed handler to the bots handlers list
@ -330,13 +308,19 @@ func (b *bot) SetQuiet(status bool) {
b.quiet = status
}
type blacklistItem struct {
Channel string
Name string
}
type whitelistItem struct {
Name string
}
// RefreshPluginBlacklist loads data for which plugins are disabled for particular channels
func (b *bot) RefreshPluginBlacklist() error {
blacklistItems := []struct {
Channel string
Name string
}{}
if err := b.DB().Select(&blacklistItems, `select channel, name from pluginBlacklist`); err != nil {
blacklistItems := []blacklistItem{}
if err := b.Store().Find(&blacklistItems, &bh.Query{}); err != nil {
return fmt.Errorf("%w", err)
}
b.pluginBlacklist = make(map[string]bool)
@ -349,12 +333,10 @@ func (b *bot) RefreshPluginBlacklist() error {
// RefreshPluginWhitelist loads data for which plugins are enabled
func (b *bot) RefreshPluginWhitelist() error {
whitelistItems := []struct {
Name string
}{
whitelistItems := []whitelistItem{
{Name: "admin"}, // we must always ensure admin is on!
}
if err := b.DB().Select(&whitelistItems, `select name from pluginWhitelist`); err != nil {
if err := b.Store().Find(&whitelistItems, &bh.Query{}); err != nil {
return fmt.Errorf("%w", err)
}
b.pluginWhitelist = make(map[string]bool)
@ -411,9 +393,8 @@ func (b *bot) CheckPassword(secret, password string) bool {
secret = parts[0]
password = parts[1]
}
q := `select encoded_pass from apppass where secret = ?`
encodedPasswords := [][]byte{}
b.DB().Select(&encodedPasswords, q, secret)
b.Store().Find(&encodedPasswords, bh.Where("secret").Eq(secret))
for _, p := range encodedPasswords {
if err := bcrypt.CompareHashAndPassword(p, []byte(password)); err == nil {
return true

View File

@ -4,10 +4,10 @@ package bot
import (
"bytes"
"database/sql"
"encoding/json"
"errors"
"fmt"
bh "github.com/timshannon/bolthold"
"io/ioutil"
"math/rand"
"net/http"
@ -216,27 +216,33 @@ func (b *bot) Filter(message msg.Message, input string) string {
return input
}
type variable struct {
Text string
}
func (b *bot) getVar(varName string) (string, error) {
var text string
err := b.DB().Get(&text, `select value from variables where name=? order by random() limit 1`, varName)
switch {
case err == sql.ErrNoRows:
return "", fmt.Errorf("No factoid found")
case err != nil:
var v []variable
err := b.Store().Find(&v, bh.Where("name").Eq(varName))
if err != nil {
log.Fatal().Err(err).Msg("getVar error")
}
return text, nil
entry := v[rand.Intn(len(v))]
return entry.Text, nil
}
func (b *bot) listVars(conn Connector, channel string, parts []string) {
var variables []string
err := b.DB().Select(&variables, `select name from variables group by name`)
var variables []variable
err := b.Store().Find(&variables, &bh.Query{})
if err != nil {
log.Fatal().Err(err)
}
msg := "I know: $who, $someone, $digit, $nonzero, $time, $now, $msg"
if len(variables) > 0 {
msg += ", " + strings.Join(variables, ", ")
out := []string{}
for _, v := range variables {
out = append(out, v.Text)
}
if len(out) > 0 {
msg += ", " + strings.Join(out, ", ")
}
b.Send(conn, Message, channel, msg)
}

View File

@ -3,11 +3,10 @@
package bot
import (
bh "github.com/timshannon/bolthold"
"net/http"
"regexp"
"github.com/jmoiron/sqlx"
"github.com/velour/catbase/bot/msg"
"github.com/velour/catbase/bot/user"
"github.com/velour/catbase/config"
@ -81,7 +80,8 @@ type Bot interface {
Config() *config.Config
// DB gives access to the current database
DB() *sqlx.DB
//DB() *sqlx.DB
Store() *bh.Store
// Who lists users in a particular channel
// The channel should be represented as an ID for slack (check the connector for details)

View File

@ -29,7 +29,7 @@ type MockBot struct {
}
func (mb *MockBot) Config() *config.Config { return mb.Cfg }
func (mb *MockBot) DB() *sqlx.DB { return mb.Cfg.DB }
func (mb *MockBot) DB() *sqlx.DB { return mb.Cfg.DB() }
func (mb *MockBot) Who(string) []user.User { return []user.User{} }
func (mb *MockBot) WhoAmI() string { return "tester" }
func (mb *MockBot) DefaultConnector() Connector { return nil }

View File

@ -3,15 +3,15 @@
package config
import (
"database/sql"
"encoding/json"
"fmt"
"os"
"regexp"
"strconv"
"strings"
sqlite3 "github.com/mattn/go-sqlite3"
_ "modernc.org/sqlite"
bh "github.com/timshannon/bolthold"
"github.com/jmoiron/sqlx"
"github.com/rs/zerolog/log"
@ -20,20 +20,34 @@ import (
// Config stores any system-wide startup information that cannot be easily configured via
// the database
type Config struct {
*sqlx.DB
db *sqlx.DB
store *bh.Store
DBFile string
secrets map[string]Secret
}
// Secret is a config value that is loaded permanently and not ever displayed
type Secret struct {
// Value is a config value that is loaded permanently and not ever displayed
type Value struct {
// Key is the key field of the table
Key string `db:"key"`
// Value represents the secret that must not be shared
Value string `db:"value"`
}
// Secret is a separate type (for storage differentiation)
type Secret Value
// DB returns the SQL database instance
func (c *Config) DB() *sqlx.DB {
return c.db
}
func (c *Config) Store() *bh.Store {
return c.store
}
// GetFloat64 returns the config value for a string key
// It will first look in the env vars for the key
// It will check the db for the key if an env DNE
@ -97,14 +111,13 @@ func (c *Config) GetString(key, fallback string) string {
if v, found := c.secrets[key]; found {
return v.Value
}
var configValue string
q := `select value from config where key=?`
err := c.DB.Get(&configValue, q, key)
var configValue Value
err := c.store.Get(key, &configValue)
if err != nil {
log.Debug().Msgf("WARN: Key %s is empty", key)
return fallback
}
return configValue
return configValue.Value
}
func (c *Config) GetMap(key string, fallback map[string]string) map[string]string {
@ -136,20 +149,8 @@ func (c *Config) GetArray(key string, fallback []string) []string {
}
func (c *Config) Unset(key string) error {
q := `delete from config where key=?`
tx, err := c.Begin()
if err != nil {
return err
}
_, err = tx.Exec(q, key)
if err != nil {
return err
}
err = tx.Commit()
if err != nil {
return err
}
return nil
err := c.store.Delete(key, &Value{})
return err
}
// Set changes the value for a configuration in the database
@ -157,27 +158,14 @@ func (c *Config) Unset(key string) error {
func (c *Config) Set(key, value string) error {
key = strings.ToLower(key)
value = strings.Trim(value, "`")
q := `insert into config (key,value) values (?, ?)
on conflict(key) do update set value=?;`
tx, err := c.Begin()
if err != nil {
return err
}
_, err = tx.Exec(q, key, value, value)
if err != nil {
return err
}
err = tx.Commit()
if err != nil {
return err
}
return nil
err := c.store.Update(key, Value{key, value})
return err
}
func (c *Config) RefreshSecrets() error {
q := `select key, value from secrets`
var secrets []Secret
err := c.Select(&secrets, q)
err := c.store.Find(&secrets, &bh.Query{})
if err != nil {
return err
}
@ -214,36 +202,43 @@ func (c *Config) SetArray(key string, values []string) error {
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)
},
})
}
//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
func ReadConfig(dbpath string) *Config {
if dbpath == "" {
dbpath = "catbase.db"
}
store, err := bh.Open(strings.ReplaceAll(dbpath, ".db", ".store"), 0666, nil)
if err != nil {
log.Fatal().Err(err).Msgf("could not open bolthold")
}
log.Info().Msgf("Using %s as database file.\n", dbpath)
sqlDB, err := sqlx.Open("sqlite3_custom", dbpath)
sqlDB, err := sqlx.Open("sqlite", dbpath)
if err != nil {
log.Fatal().Err(err)
log.Fatal().Err(err).Msgf("could not open sqlite")
}
c := Config{
DBFile: dbpath,
secrets: map[string]Secret{},
store: store,
}
c.DB = sqlDB
c.db = sqlDB
if _, err := c.Exec(`create table if not exists config (
if _, err := c.db.Exec(`create table if not exists config (
key string,
value string,
primary key (key)
@ -251,7 +246,7 @@ func ReadConfig(dbpath string) *Config {
log.Fatal().Err(err).Msgf("failed to initialize config")
}
if _, err := c.Exec(`create table if not exists secrets (
if _, err := c.db.Exec(`create table if not exists secrets (
key string,
value string,
primary key (key)
@ -265,5 +260,60 @@ func ReadConfig(dbpath string) *Config {
log.Info().Msgf("catbase is running.")
if err := c.migrate(); err != nil {
log.Fatal().Err(err).Msgf("could not migrate")
}
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

@ -32,7 +32,7 @@ func (c *Config) SetDefaults(mainChannel, nick string) {
}
var buf bytes.Buffer
t.Execute(&buf, vals)
c.MustExec(`delete from config;`)
c.MustExec(buf.String())
c.db.MustExec(`delete from config;`)
c.db.MustExec(buf.String())
log.Info().Msgf("Configuration initialized.")
}

6
go.mod
View File

@ -29,7 +29,7 @@ require (
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 // indirect
github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82 // indirect
github.com/gonum/internal v0.0.0-20181124074243-f884aa714029 // indirect
github.com/google/uuid v1.1.1
github.com/google/uuid v1.3.0
github.com/inconshreveable/log15 v0.0.0-20200109203555-b30bc20e4fd1 // indirect
github.com/itchyny/gojq v0.12.3
github.com/james-bowman/nlp v0.0.0-20191016091239-d9dbfaff30c6
@ -41,7 +41,6 @@ require (
github.com/kevinburke/rest v0.0.0-20200429221318-0d2892b400f8 // indirect
github.com/kevinburke/twilio-go v0.0.0-20200424172635-4f0b2357b852
github.com/mattn/go-colorable v0.1.6 // indirect
github.com/mattn/go-sqlite3 v1.14.8
github.com/mmcdole/gofeed v1.0.0-beta2
github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
@ -55,6 +54,7 @@ require (
github.com/stretchr/objx v0.2.0 // indirect
github.com/stretchr/testify v1.4.0
github.com/temoto/robotstxt v1.1.1 // indirect
github.com/timshannon/bolthold v0.0.0-20210913165410-232392fc8a6a
github.com/trubitsyn/go-zero-width v1.0.1
github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 // indirect
github.com/ttacon/libphonenumber v1.1.0 // indirect
@ -62,7 +62,6 @@ require (
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
golang.org/x/exp v0.0.0-20191014171548-69215a2ee97e // 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/text v0.3.7 // indirect
gonum.org/v1/gonum v0.6.0 // indirect
@ -70,4 +69,5 @@ require (
gopkg.in/go-playground/webhooks.v5 v5.13.0
gopkg.in/sourcemap.v1 v1.0.5 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
modernc.org/sqlite v1.14.2
)

151
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
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/go.mod h1:ORH5Qp2bskd9NzSfKqAF7tKfONsEkCarTE5ESr/RVBw=
github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad h1:Qk76DOWdOp+GlyDKBAG3Klr9cn7N+LcYc82AZ2S7+cA=
@ -68,9 +70,11 @@ 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/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/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/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
@ -88,6 +92,8 @@ github.com/james-bowman/sparse v0.0.0-20190423065201-80c6877364c7/go.mod h1:G6Ec
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/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/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak=
github.com/kevinburke/go-types v0.0.0-20200309064045-f2d4aea18a7a h1:Z7+SSApKiwPjNic+NF9+j7h657Uyvdp/jA3iTKhpj4E=
@ -106,8 +112,8 @@ github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHX
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-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU=
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
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/go.mod h1:/BF9JneEL2/flujm8XHoxUcghdTV6vvb3xx/vKyChFU=
github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf h1:sWGE2v+hO0Nd4yFU/S/mDBM5plIU8v/Qhfz41hkDIAI=
@ -122,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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
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/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
@ -142,6 +150,8 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/temoto/robotstxt v1.1.1 h1:Gh8RCs8ouX3hRSxxK7B1mO5RFByQ4CmJZDwgom++JaA=
github.com/temoto/robotstxt v1.1.1/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo=
github.com/timshannon/bolthold v0.0.0-20210913165410-232392fc8a6a h1:oIi7H/bwFUYKYhzKbHc+3MvHRWqhQwXVB4LweLMiVy0=
github.com/timshannon/bolthold v0.0.0-20210913165410-232392fc8a6a/go.mod h1:iSvujNDmpZ6eQX+bg/0X3lF7LEmZ8N77g2a/J/+Zt2U=
github.com/trubitsyn/go-zero-width v1.0.1 h1:AAZhtyGXW79T5BouAF0R9FtDhGcp7IGbLZo2Id3N+m8=
github.com/trubitsyn/go-zero-width v1.0.1/go.mod h1:gGhBV4CZHjqXBYSgaxTCKZj+dXJndhdm1zAtAChtIUI=
github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 h1:5u+EJUQiosu3JFX0XS0qTf5FznsMOzTjGqavBGuCbo0=
@ -150,10 +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/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/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
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/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-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-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/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -168,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/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.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-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-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-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-20211118161319-6a13c67c3ce4 h1:DZshvxDdVoeKIbudAdFEKi+f70l51luSy/7b76ibTY0=
golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@ -186,15 +204,21 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/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-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-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-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-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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.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.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@ -203,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-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-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-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-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.6.0 h1:DJy6UzXbahnGUf1ujUNkh/NEtK14qMo2nvlBPs4U5yw=
gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU=
@ -223,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/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
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=

View File

@ -2,11 +2,11 @@ package achievements
import (
"fmt"
bh "github.com/timshannon/bolthold"
"regexp"
"strings"
"time"
"github.com/jmoiron/sqlx"
"github.com/rs/zerolog/log"
"github.com/velour/catbase/bot"
@ -16,20 +16,16 @@ import (
// A plugin to track our misdeeds
type AchievementsPlugin struct {
bot bot.Bot
cfg *config.Config
db *sqlx.DB
bot bot.Bot
cfg *config.Config
store *bh.Store
}
func New(b bot.Bot) *AchievementsPlugin {
ap := &AchievementsPlugin{
bot: b,
cfg: b.Config(),
db: b.DB(),
}
err := ap.mkDB()
if err != nil {
log.Fatal().Err(err).Msg("unable to create achievements tables")
bot: b,
cfg: b.Config(),
store: b.Store(),
}
ap.register()
@ -50,41 +46,10 @@ func (p *AchievementsPlugin) register() {
p.bot.Register(p, bot.Help, p.help)
}
func (p *AchievementsPlugin) mkDB() error {
trophiesTable := `create table if not exists trophies (
emojy string primary key,
description string,
creator string
);`
awardsTable := `create table if not exists awards (
id integer primary key,
emojy string references trophies(emojy) on delete restrict on update cascade,
holder string,
amount integer default 0,
granted timestamp CURRENT_TIMESTAMP
);`
tx, err := p.db.Beginx()
if err != nil {
return err
}
if _, err = tx.Exec(trophiesTable); err != nil {
return err
}
if _, err = tx.Exec(awardsTable); err != nil {
return err
}
err = tx.Commit()
if err != nil {
return err
}
return nil
}
func (p *AchievementsPlugin) GetAwards(nick string) []Award {
var awards []Award
q := `select * from awards inner join trophies on awards.emojy=trophies.emojy where holder=?`
if err := p.db.Select(&awards, q, nick); err != nil {
err := p.store.Find(&awards, bh.Where("holder").Eq(nick))
if err != nil {
log.Error().Err(err).Msg("could not select awards")
}
return awards
@ -206,20 +171,21 @@ func (p *AchievementsPlugin) help(c bot.Connector, kind bot.Kind, message msg.Me
// Award is used by other plugins to register a particular award for a user
func (p *AchievementsPlugin) Grant(nick, emojy string) (Award, error) {
empty := Award{}
q := `insert into awards (emojy,holder) values (?, ?)`
tx, err := p.db.Beginx()
trophy, err := p.FindTrophy(emojy)
if err != nil {
return empty, err
return Award{}, err
}
if _, err := tx.Exec(q, emojy, nick); err != nil {
tx.Rollback()
return empty, err
award := Award{
Holder: nick,
Emojy: emojy,
Description: trophy.Description,
Granted: time.Now(),
}
if err := tx.Commit(); err != nil {
return empty, err
if err = p.store.Insert(bh.NextSequence(), &award); err != nil {
return Award{}, err
}
return p.FindAward(emojy)
return award, err
}
func (p *AchievementsPlugin) Create(emojy, description, creator string) (Trophy, error) {
@ -228,22 +194,14 @@ func (p *AchievementsPlugin) Create(emojy, description, creator string) (Trophy,
return t, fmt.Errorf("the trophy %s already exists", emojy)
}
q := `insert into trophies (emojy,description,creator) values (?,?,?)`
tx, err := p.db.Beginx()
if err != nil {
return Trophy{}, err
}
_, err = tx.Exec(q, emojy, description, creator)
if err != nil {
tx.Rollback()
return Trophy{}, err
}
err = tx.Commit()
return Trophy{
t = Trophy{
Emojy: emojy,
Description: description,
Creator: creator,
}, err
}
err = p.store.Insert(emojy, t)
return t, err
}
type Trophy struct {
@ -253,34 +211,21 @@ type Trophy struct {
}
type Award struct {
Trophy
ID int64
Holder string
Amount int
Granted *time.Time
}
func (a *Award) Save() error {
return nil
ID int64 `boltholderid:"ID"`
Holder string
Emojy string
Description string
Granted time.Time
}
func (p *AchievementsPlugin) AllTrophies() ([]Trophy, error) {
q := `select * from trophies order by creator`
var t []Trophy
err := p.db.Select(&t, q)
err := p.store.Find(&t, &bh.Query{})
return t, err
}
func (p *AchievementsPlugin) FindTrophy(emojy string) (Trophy, error) {
q := `select * from trophies where emojy=?`
var t Trophy
err := p.db.Get(&t, q, emojy)
err := p.store.Find(&t, bh.Where("emojy").Eq(emojy))
return t, err
}
func (p *AchievementsPlugin) FindAward(emojy string) (Award, error) {
q := `select * from awards inner join trophies on awards.emojy=trophies.emojy where trophies.emojy=?`
var a Award
err := p.db.Get(&a, q, emojy)
return a, err
}

View File

@ -4,6 +4,7 @@ package admin
import (
"fmt"
bh "github.com/timshannon/bolthold"
"os"
"regexp"
"strings"
@ -11,8 +12,6 @@ import (
"github.com/rs/zerolog/log"
"github.com/jmoiron/sqlx"
"github.com/velour/catbase/bot"
"github.com/velour/catbase/bot/msg"
"github.com/velour/catbase/config"
@ -21,9 +20,9 @@ import (
// This is a admin plugin to serve as an example and quick copy/paste for new plugins.
type AdminPlugin struct {
bot bot.Bot
db *sqlx.DB
cfg *config.Config
bot bot.Bot
store *bh.Store
cfg *config.Config
quiet bool
}
@ -31,13 +30,11 @@ type AdminPlugin struct {
// New creates a new AdminPlugin with the Plugin interface
func New(b bot.Bot) *AdminPlugin {
p := &AdminPlugin{
bot: b,
db: b.DB(),
cfg: b.Config(),
bot: b,
store: b.Store(),
cfg: b.Config(),
}
p.mkDB()
b.RegisterRegex(p, bot.Message, comeBackRegex, p.comeBackCmd)
b.RegisterRegexCmd(p, bot.Message, shutupRegex, p.shutupCmd)
b.RegisterRegexCmd(p, bot.Message, addBlacklistRegex, p.isAdmin(p.addBlacklistCmd))
@ -72,16 +69,6 @@ var forbiddenKeys = map[string]bool{
"meme.memes": true,
}
func (p *AdminPlugin) mkDB() {
q := `create table if not exists apppass (
id integer primary key autoincrement,
secret string not null,
encoded_pass string not null,
cost integer default 10
)`
p.db.MustExec(q)
}
var shutupRegex = regexp.MustCompile(`(?i)^shut up$`)
var comeBackRegex = regexp.MustCompile(`(?i)^come back$`)
@ -243,12 +230,11 @@ func (p *AdminPlugin) passwordCmd(r bot.Request) bool {
}
func (p *AdminPlugin) variableSetCmd(r bot.Request) bool {
variable := strings.ToLower(r.Values["var"])
key := strings.ToLower(r.Values["var"])
value := r.Values["value"]
variable := bot.Variable{key, value}
var count int64
row := p.db.QueryRow(`select count(*) from variables where value = ?`, variable, value)
err := row.Scan(&count)
count, err := p.store.Count(bot.Variable{}, bh.Where("value").Eq(value))
if err != nil {
p.bot.Send(r.Conn, bot.Message, r.Msg.Channel, "I'm broke and need attention in my variable creation code.")
log.Error().Err(err)
@ -258,7 +244,7 @@ func (p *AdminPlugin) variableSetCmd(r bot.Request) bool {
if count > 0 {
p.bot.Send(r.Conn, bot.Message, r.Msg.Channel, "I've already got that one.")
} else {
_, err := p.db.Exec(`INSERT INTO variables (name, value) VALUES (?, ?)`, variable, value)
err := p.store.Insert(bh.NextSequence(), variable)
if err != nil {
p.bot.Send(r.Conn, bot.Message, r.Msg.Channel, "I'm broke and need attention in my variable creation code.")
log.Error().Err(err)
@ -273,7 +259,8 @@ func (p *AdminPlugin) variableUnSetCmd(r bot.Request) bool {
variable := strings.ToLower(r.Values["var"])
value := r.Values["value"]
_, err := p.db.Exec(`delete from variables where name=? and value=?`, variable, value)
err := p.store.Delete(variable, bot.Variable{})
p.store.DeleteMatching(bot.Variable{}, bh.Where("name").Eq(variable).And("value").Eq(value))
if err != nil {
p.bot.Send(r.Conn, bot.Message, r.Msg.Channel, "I'm broke and need attention in my variable creation code.")
log.Error().Err(err)
@ -390,9 +377,10 @@ func (p *AdminPlugin) modList(query, channel, plugin string) error {
plugins := p.bot.GetPluginNames()
for _, pp := range plugins {
if pp == plugin {
if _, err := p.db.Exec(query, channel, plugin); err != nil {
return fmt.Errorf("%w", err)
}
// todo: yikes
//if _, err := p.db.Exec(query, channel, plugin); err != nil {
// return fmt.Errorf("%w", err)
//}
err := p.bot.RefreshPluginWhitelist()
if err != nil {
return fmt.Errorf("%w", err)

View File

@ -6,6 +6,8 @@ import (
"embed"
"encoding/json"
"fmt"
bh "github.com/timshannon/bolthold"
"github.com/velour/catbase/config"
"io/ioutil"
"net/http"
"strings"
@ -106,11 +108,18 @@ func (p *AdminPlugin) handleAppPassAPI(w http.ResponseWriter, r *http.Request) {
}
req.PassEntry.Pass = fmt.Sprintf("%x", md5.Sum(b))
}
q := `insert into apppass (secret, encoded_pass, cost) values (?, ?, ?)`
req.PassEntry.EncodePass()
check := bcrypt.CompareHashAndPassword(req.PassEntry.encodedPass, []byte(req.PassEntry.Pass))
entry := PassEntry{
Secret: req.PassEntry.Secret,
encodedPass: req.PassEntry.encodedPass,
Cost: req.PassEntry.Cost,
Pass: "",
}
log.Debug().
Str("secret", req.PassEntry.Secret).
Str("encoded", string(req.PassEntry.encodedPass)).
@ -118,17 +127,11 @@ func (p *AdminPlugin) handleAppPassAPI(w http.ResponseWriter, r *http.Request) {
Interface("check", check).
Msg("debug pass creation")
res, err := p.db.Exec(q, req.PassEntry.Secret, req.PassEntry.encodedPass, req.PassEntry.Cost)
err := p.store.Insert(bh.NextSequence(), &entry)
if err != nil {
writeErr(w, err)
return
}
id, err := res.LastInsertId()
if err != nil {
writeErr(w, err)
return
}
req.PassEntry.ID = id
j, _ := json.Marshal(req.PassEntry)
fmt.Fprint(w, string(j))
return
@ -137,16 +140,14 @@ func (p *AdminPlugin) handleAppPassAPI(w http.ResponseWriter, r *http.Request) {
writeErr(w, fmt.Errorf("missing ID"))
return
}
q := `delete from apppass where id = ?`
_, err := p.db.Exec(q, req.PassEntry.ID)
err := p.store.Delete(req.PassEntry.ID, PassEntry{})
if err != nil {
writeErr(w, err)
return
}
}
q := `select id,secret from apppass where secret = ?`
passEntries := []PassEntry{}
err := p.db.Select(&passEntries, q, req.PassEntry.Secret)
err := p.store.Find(&passEntries, bh.Where("secret").Eq(req.PassEntry.Secret))
if err != nil {
writeErr(w, err)
return
@ -172,12 +173,8 @@ func (p *AdminPlugin) handleVars(w http.ResponseWriter, r *http.Request) {
}
func (p *AdminPlugin) handleVarsAPI(w http.ResponseWriter, r *http.Request) {
var configEntries []struct {
Key string `json:"key"`
Value string `json:"value"`
}
q := `select key, value from config`
err := p.db.Select(&configEntries, q)
configEntries := []config.Value{}
err := p.store.Find(&configEntries, &bh.Query{})
if err != nil {
log.Error().
Err(err).

View File

@ -6,13 +6,14 @@ import (
"database/sql"
"errors"
"fmt"
bh "github.com/timshannon/bolthold"
"github.com/velour/catbase/plugins/remember"
"math/rand"
"regexp"
"strings"
"github.com/rs/zerolog/log"
"github.com/jmoiron/sqlx"
"github.com/velour/catbase/bot"
"github.com/velour/catbase/bot/msg"
)
@ -25,74 +26,65 @@ var (
type BabblerPlugin struct {
Bot bot.Bot
db *sqlx.DB
store *bh.Store
WithGoRoutines bool
handlers bot.HandlerTable
}
type Babbler struct {
BabblerId int64 `db:"id"`
BabblerId int64 `db:"id" boltholdid:"BabblerId"`
Name string `db:"babbler"`
}
func getBabbler(store *bh.Store, id int64) (*Babbler, error) {
res := &Babbler{}
err := store.Get(id, res)
return res, err
}
type BabblerWord struct {
WordId int64 `db:"id"`
WordId int64 `db:"id" boltholdid:"WordId"`
Word string `db:"word"`
}
func getWord(store *bh.Store, id int64) (*BabblerWord, error) {
res := &BabblerWord{}
err := store.Get(id, res)
return res, err
}
type BabblerNode struct {
NodeId int64 `db:"id"`
NodeId int64 `db:"id" boltholdid:"NodeId"`
BabblerId int64 `db:"babblerId"`
WordId int64 `db:"wordId"`
Root int64 `db:"root"`
RootFrequency int64 `db:"rootFrequency"`
}
func getNode(store *bh.Store, id int64) (*BabblerNode, error) {
res := &BabblerNode{}
err := store.Get(id, res)
return res, err
}
type BabblerArc struct {
ArcId int64 `db:"id"`
ArcId int64 `db:"id" boltholdid:"ArcId"`
FromNodeId int64 `db:"fromNodeId"`
ToNodeId int64 `db:"toNodeId"`
Frequency int64 `db:"frequency"`
}
func getArc(store *bh.Store, id int64) (*BabblerArc, error) {
res := &BabblerArc{}
err := store.Get(id, res)
return res, err
}
func New(b bot.Bot) *BabblerPlugin {
if _, err := b.DB().Exec(`create table if not exists babblers (
id integer primary key,
babbler string
);`); err != nil {
log.Fatal().Err(err)
}
if _, err := b.DB().Exec(`create table if not exists babblerWords (
id integer primary key,
word string
);`); err != nil {
log.Fatal().Err(err)
}
if _, err := b.DB().Exec(`create table if not exists babblerNodes (
id integer primary key,
babblerId integer,
wordId integer,
root integer,
rootFrequency integer
);`); err != nil {
log.Fatal().Err(err)
}
if _, err := b.DB().Exec(`create table if not exists babblerArcs (
id integer primary key,
fromNodeId integer,
toNodeId interger,
frequency integer
);`); err != nil {
log.Fatal().Err(err)
}
plugin := &BabblerPlugin{
Bot: b,
db: b.DB(),
store: b.Store(),
WithGoRoutines: true,
}
@ -195,24 +187,20 @@ func (p *BabblerPlugin) help(c bot.Connector, kind bot.Kind, msg msg.Message, ar
}
func (p *BabblerPlugin) makeBabbler(name string) (*Babbler, error) {
res, err := p.db.Exec(`insert into babblers (babbler) values (?);`, name)
if err == nil {
id, err := res.LastInsertId()
if err != nil {
log.Error().Err(err)
return nil, err
}
return &Babbler{
BabblerId: id,
Name: name,
}, nil
b := &Babbler{
Name: name,
}
return nil, err
err := p.store.Insert(bh.NextSequence(), b)
if err != nil {
log.Error().Err(err)
return nil, err
}
return b, err
}
func (p *BabblerPlugin) getBabbler(name string) (*Babbler, error) {
var bblr Babbler
err := p.db.QueryRowx(`select * from babblers where babbler = ? LIMIT 1;`, name).StructScan(&bblr)
err := p.store.FindOne(&bblr, bh.Where("babbler").Eq(name))
if err != nil {
if err == sql.ErrNoRows {
log.Error().Msg("failed to find babbler")
@ -233,29 +221,9 @@ func (p *BabblerPlugin) getOrCreateBabbler(name string) (*Babbler, error) {
return nil, err
}
rows, err := p.db.Queryx(fmt.Sprintf("select tidbit from factoid where fact like '%s quotes';", babbler.Name))
if err != nil {
log.Error().Err(err)
return babbler, nil
}
defer rows.Close()
tidbits := []string{}
for rows.Next() {
var tidbit string
err := rows.Scan(&tidbit)
log.Debug().Str("tidbit", tidbit)
if err != nil {
log.Error().Err(err)
return babbler, err
}
tidbits = append(tidbits, tidbit)
}
for _, tidbit := range tidbits {
if err = p.addToMarkovChain(babbler, tidbit); err != nil {
quotes := remember.AllQuotesFrom(p.store, babbler.Name)
for _, q := range quotes {
if err = p.addToMarkovChain(babbler, q.Tidbit); err != nil {
log.Error().Err(err)
}
}
@ -265,9 +233,9 @@ func (p *BabblerPlugin) getOrCreateBabbler(name string) (*Babbler, error) {
func (p *BabblerPlugin) getWord(word string) (*BabblerWord, error) {
var w BabblerWord
err := p.db.QueryRowx(`select * from babblerWords where word = ? LIMIT 1;`, word).StructScan(&w)
err := p.store.FindOne(&w, bh.Where("word").Eq(word).Limit(1))
if err != nil {
if err == sql.ErrNoRows {
if err == bh.ErrNotFound {
return nil, NEVER_SAID
}
return nil, err
@ -276,20 +244,13 @@ func (p *BabblerPlugin) getWord(word string) (*BabblerWord, error) {
}
func (p *BabblerPlugin) createNewWord(word string) (*BabblerWord, error) {
res, err := p.db.Exec(`insert into babblerWords (word) values (?);`, word)
w := &BabblerWord{Word: word}
err := p.store.Insert(bh.NextSequence(), w)
if err != nil {
log.Error().Err(err)
return nil, err
}
id, err := res.LastInsertId()
if err != nil {
log.Error().Err(err)
return nil, err
}
return &BabblerWord{
WordId: id,
Word: word,
}, nil
return w, nil
}
func (p *BabblerPlugin) getOrCreateWord(word string) (*BabblerWord, error) {
@ -310,9 +271,9 @@ func (p *BabblerPlugin) getBabblerNode(babbler *Babbler, word string) (*BabblerN
}
var node BabblerNode
err = p.db.QueryRowx(`select * from babblerNodes where babblerId = ? and wordId = ? LIMIT 1;`, babbler.BabblerId, w.WordId).StructScan(&node)
err = p.store.FindOne(&node, bh.Where("babblerId").Eq(babbler.BabblerId).And("wordId").Eq(w.WordId))
if err != nil {
if err == sql.ErrNoRows {
if err == bh.ErrNotFound {
return nil, NEVER_SAID
}
return nil, err
@ -327,24 +288,19 @@ func (p *BabblerPlugin) createBabblerNode(babbler *Babbler, word string) (*Babbl
return nil, err
}
res, err := p.db.Exec(`insert into babblerNodes (babblerId, wordId, root, rootFrequency) values (?, ?, 0, 0)`, babbler.BabblerId, w.WordId)
if err != nil {
log.Error().Err(err)
return nil, err
}
id, err := res.LastInsertId()
if err != nil {
log.Error().Err(err)
return nil, err
}
return &BabblerNode{
NodeId: id,
bn := &BabblerNode{
WordId: w.WordId,
Root: 0,
RootFrequency: 0,
}, nil
}
err = p.store.Insert(bh.NextSequence(), bn)
if err != nil {
log.Error().Err(err)
return nil, err
}
return bn, nil
}
func (p *BabblerPlugin) getOrCreateBabblerNode(babbler *Babbler, word string) (*BabblerNode, error) {
@ -361,7 +317,12 @@ func (p *BabblerPlugin) incrementRootWordFrequency(babbler *Babbler, word string
log.Error().Err(err)
return nil, err
}
_, err = p.db.Exec(`update babblerNodes set rootFrequency = rootFrequency + 1, root = 1 where id = ?;`, node.NodeId)
err = p.store.UpdateMatching(BabblerNode{}, bh.Where("id").Eq(node.NodeId), func(record interface{}) error {
r := record.(BabblerNode)
r.RootFrequency += 1
r.Root = 1
return p.store.Update(r.NodeId, r)
})
if err != nil {
log.Error().Err(err)
return nil, err
@ -372,9 +333,9 @@ func (p *BabblerPlugin) incrementRootWordFrequency(babbler *Babbler, word string
func (p *BabblerPlugin) getBabblerArc(fromNode, toNode *BabblerNode) (*BabblerArc, error) {
var arc BabblerArc
err := p.db.QueryRowx(`select * from babblerArcs where fromNodeId = ? and toNodeId = ?;`, fromNode.NodeId, toNode.NodeId).StructScan(&arc)
err := p.store.FindOne(&arc, bh.Where("fromNodeId").Eq(fromNode.NodeId).And("toNodeId").Eq(toNode.NodeId))
if err != nil {
if err == sql.ErrNoRows {
if err == bh.ErrNotFound {
return nil, NEVER_SAID
}
return nil, err
@ -383,24 +344,32 @@ func (p *BabblerPlugin) getBabblerArc(fromNode, toNode *BabblerNode) (*BabblerAr
}
func (p *BabblerPlugin) incrementWordArc(fromNode, toNode *BabblerNode) (*BabblerArc, error) {
res, err := p.db.Exec(`update babblerArcs set frequency = frequency + 1 where fromNodeId = ? and toNodeId = ?;`, fromNode.NodeId, toNode.NodeId)
affectedRows := 0
err := p.store.UpdateMatching(BabblerArc{},
bh.Where("fromNodeId").Eq(fromNode.NodeId).And("toNodeId").Eq(toNode.NodeId),
func(record interface{}) error {
affectedRows++
r := record.(BabblerArc)
r.Frequency += 1
return p.store.Update(r.ArcId, r)
})
if err != nil {
log.Error().Err(err)
return nil, err
}
affectedRows := int64(0)
if err == nil {
affectedRows, _ = res.RowsAffected()
}
if affectedRows == 0 {
res, err = p.db.Exec(`insert into babblerArcs (fromNodeId, toNodeId, frequency) values (?, ?, 1);`, fromNode.NodeId, toNode.NodeId)
p.store.Insert(bh.NextSequence(), BabblerArc{
FromNodeId: fromNode.NodeId,
ToNodeId: toNode.NodeId,
Frequency: 1,
})
if err != nil {
log.Error().Err(err)
return nil, err
}
}
return p.getBabblerArc(fromNode, toNode)
}
@ -444,25 +413,17 @@ func (p *BabblerPlugin) addToMarkovChain(babbler *Babbler, phrase string) error
}
func (p *BabblerPlugin) getWeightedRootNode(babbler *Babbler) (*BabblerNode, *BabblerWord, error) {
rows, err := p.db.Queryx(`select * from babblerNodes where babblerId = ? and root = 1;`, babbler.BabblerId)
rootNodes := []*BabblerNode{}
err := p.store.Find(&rootNodes, bh.Where("babblerId").Eq(babbler.BabblerId).And("root").Eq(1))
if err != nil {
log.Error().Err(err)
return nil, nil, err
}
defer rows.Close()
rootNodes := []*BabblerNode{}
total := int64(0)
for rows.Next() {
var node BabblerNode
err = rows.StructScan(&node)
if err != nil {
log.Error().Err(err)
return nil, nil, err
}
rootNodes = append(rootNodes, &node)
total += node.RootFrequency
for _, n := range rootNodes {
total += n.RootFrequency
}
if len(rootNodes) == 0 {
@ -474,13 +435,12 @@ func (p *BabblerPlugin) getWeightedRootNode(babbler *Babbler) (*BabblerNode, *Ba
for _, node := range rootNodes {
total += node.RootFrequency
if total >= which {
var w BabblerWord
err := p.db.QueryRowx(`select * from babblerWords where id = ? LIMIT 1;`, node.WordId).StructScan(&w)
w, err := getWord(p.store, node.WordId)
if err != nil {
log.Error().Err(err)
return nil, nil, err
}
return node, &w, nil
return node, w, nil
}
}
@ -489,24 +449,15 @@ func (p *BabblerPlugin) getWeightedRootNode(babbler *Babbler) (*BabblerNode, *Ba
}
func (p *BabblerPlugin) getWeightedNextWord(fromNode *BabblerNode) (*BabblerNode, *BabblerWord, error) {
rows, err := p.db.Queryx(`select * from babblerArcs where fromNodeId = ?;`, fromNode.NodeId)
arcs := []BabblerArc{}
err := p.store.Find(&arcs, bh.Where("fromNodeId").Eq(fromNode.NodeId))
if err != nil {
log.Error().Err(err)
return nil, nil, err
}
defer rows.Close()
arcs := []*BabblerArc{}
total := int64(0)
for rows.Next() {
var arc BabblerArc
err = rows.StructScan(&arc)
if err != nil {
log.Error().Err(err)
return nil, nil, err
}
arcs = append(arcs, &arc)
total += arc.Frequency
for _, a := range arcs {
total += a.Frequency
}
if len(arcs) == 0 {
@ -520,20 +471,18 @@ func (p *BabblerPlugin) getWeightedNextWord(fromNode *BabblerNode) (*BabblerNode
total += arc.Frequency
if total >= which {
var node BabblerNode
err := p.db.QueryRowx(`select * from babblerNodes where id = ? LIMIT 1;`, arc.ToNodeId).StructScan(&node)
node, err := getNode(p.store, arc.ToNodeId)
if err != nil {
log.Error().Err(err)
return nil, nil, err
}
var w BabblerWord
err = p.db.QueryRowx(`select * from babblerWords where id = ? LIMIT 1;`, node.WordId).StructScan(&w)
w, err := getWord(p.store, node.WordId)
if err != nil {
log.Error().Err(err)
return nil, nil, err
}
return &node, &w, nil
return node, w, nil
}
}
@ -542,23 +491,15 @@ func (p *BabblerPlugin) getWeightedNextWord(fromNode *BabblerNode) (*BabblerNode
}
func (p *BabblerPlugin) getWeightedPreviousWord(toNode *BabblerNode) (*BabblerNode, *BabblerWord, bool, error) {
rows, err := p.db.Queryx(`select * from babblerArcs where toNodeId = ?;`, toNode.NodeId)
arcs := []*BabblerArc{}
err := p.store.Find(&arcs, bh.Where("toNodeId").Eq(toNode.NodeId))
if err != nil {
log.Error().Err(err)
return nil, nil, false, err
}
defer rows.Close()
arcs := []*BabblerArc{}
total := int64(0)
for rows.Next() {
var arc BabblerArc
err = rows.StructScan(&arc)
if err != nil {
log.Error().Err(err)
return nil, nil, false, err
}
arcs = append(arcs, &arc)
for _, arc := range arcs {
total += arc.Frequency
}
@ -575,24 +516,21 @@ func (p *BabblerPlugin) getWeightedPreviousWord(toNode *BabblerNode) (*BabblerNo
total = 0
for _, arc := range arcs {
total += arc.Frequency
if total >= which {
var node BabblerNode
err := p.db.QueryRowx(`select * from babblerNodes where id = ? LIMIT 1;`, arc.FromNodeId).StructScan(&node)
node, err := getNode(p.store, arc.FromNodeId)
if err != nil {
log.Error().Err(err)
return nil, nil, false, err
}
var w BabblerWord
err = p.db.QueryRowx(`select * from babblerWords where id = ? LIMIT 1;`, node.WordId).StructScan(&w)
w, err := getWord(p.store, node.WordId)
if err != nil {
log.Error().Err(err)
return nil, nil, false, err
}
return &node, &w, false, nil
return node, w, false, nil
}
}
log.Fatal().Msg("failed to find weighted previous word")
@ -686,51 +624,49 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa
mapping := map[int64]*BabblerNode{}
rows, err := p.db.Queryx("select * from babblerNodes where babblerId = ?;", otherBabbler.BabblerId)
nodes := []*BabblerNode{}
err = p.store.Find(&nodes, bh.Where("babblerId").Eq(otherBabbler.BabblerId))
if err != nil {
log.Error().Err(err)
return err
}
defer rows.Close()
nodes := []*BabblerNode{}
for rows.Next() {
var node BabblerNode
err = rows.StructScan(&node)
if err != nil {
log.Error().Err(err)
return err
}
nodes = append(nodes, &node)
}
for _, node := range nodes {
var res sql.Result
if node.NodeId == otherNode.NodeId {
node.WordId = intoNode.WordId
}
affected := 0
if node.Root > 0 {
res, err = p.db.Exec(`update babblerNodes set rootFrequency = rootFrequency + ?, root = 1 where babblerId = ? and wordId = ?;`, node.RootFrequency, intoBabbler.BabblerId, node.WordId)
err = p.store.UpdateMatching(BabblerNode{},
bh.Where("babblerId").Eq(intoBabbler.BabblerId).And("wordId").Eq(node.WordId),
func(record interface{}) error {
affected++
r := record.(BabblerNode)
r.RootFrequency += node.RootFrequency
r.Root = 1
return p.store.Update(r.BabblerId, r)
})
if err != nil {
log.Error().Err(err)
}
} else {
res, err = p.db.Exec(`update babblerNodes set rootFrequency = rootFrequency + ? where babblerId = ? and wordId = ?;`, node.RootFrequency, intoBabbler.BabblerId, node.WordId)
err = p.store.UpdateMatching(BabblerNode{},
bh.Where("babblerId").Eq(intoBabbler.BabblerId).And("wordId").Eq(node.WordId),
func(record interface{}) error {
affected++
r := record.(BabblerNode)
r.RootFrequency += node.RootFrequency
return p.store.Update(r.BabblerId, r)
})
if err != nil {
log.Error().Err(err)
}
}
rowsAffected := int64(-1)
if err == nil {
rowsAffected, _ = res.RowsAffected()
}
if err != nil || rowsAffected == 0 {
res, err = p.db.Exec(`insert into babblerNodes (babblerId, wordId, root, rootFrequency) values (?,?,?,?) ;`, intoBabbler.BabblerId, node.WordId, node.Root, node.RootFrequency)
if err != nil || affected == 0 {
node.BabblerId = intoBabbler.BabblerId
err = p.store.Insert(bh.NextSequence(), &node)
if err != nil {
log.Error().Err(err)
return err
@ -738,7 +674,8 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa
}
var updatedNode BabblerNode
err = p.db.QueryRowx(`select * from babblerNodes where babblerId = ? and wordId = ? LIMIT 1;`, intoBabbler.BabblerId, node.WordId).StructScan(&updatedNode)
err = p.store.FindOne(&updatedNode,
bh.Where("babblerId").Eq(intoBabbler.BabblerId).And("wordId").Eq(node.WordId))
if err != nil {
log.Error().Err(err)
return err
@ -748,23 +685,11 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa
}
for oldNodeId, newNode := range mapping {
rows, err := p.db.Queryx("select * from babblerArcs where fromNodeId = ?;", oldNodeId)
arcs := []*BabblerArc{}
err = p.store.Find(&arcs, bh.Where("fromNodeId").Eq(oldNodeId))
if err != nil {
return err
}
defer rows.Close()
arcs := []*BabblerArc{}
for rows.Next() {
var arc BabblerArc
err = rows.StructScan(&arc)
if err != nil {
log.Error().Err(err)
return err
}
arcs = append(arcs, &arc)
}
for _, arc := range arcs {
_, err := p.incrementWordArc(newNode, mapping[arc.ToNodeId])
@ -824,33 +749,21 @@ func (p *BabblerPlugin) babbleSeedSuffix(babblerName string, seed []string) (str
func (p *BabblerPlugin) getNextArcs(babblerNodeId int64) ([]*BabblerArc, error) {
arcs := []*BabblerArc{}
rows, err := p.db.Queryx(`select * from babblerArcs where fromNodeId = ?;`, babblerNodeId)
err := p.store.Find(&arcs, bh.Where("fromNodeId").Eq(babblerNodeId))
if err != nil {
log.Error().Err(err)
return arcs, err
}
defer rows.Close()
for rows.Next() {
var arc BabblerArc
err = rows.StructScan(&arc)
if err != nil {
log.Error().Err(err)
return []*BabblerArc{}, err
}
arcs = append(arcs, &arc)
}
return arcs, nil
}
func (p *BabblerPlugin) getBabblerNodeById(nodeId int64) (*BabblerNode, error) {
var node BabblerNode
err := p.db.QueryRowx(`select * from babblerNodes where id = ? LIMIT 1;`, nodeId).StructScan(&node)
node, err := getNode(p.store, nodeId)
if err != nil {
log.Error().Err(err)
return nil, err
}
return &node, nil
return node, nil
}
func shuffle(a []*BabblerArc) {
@ -932,8 +845,7 @@ func (p *BabblerPlugin) babbleSeedBookends(babblerName string, start, end []stri
log.Error().Err(err)
return "", err
}
var w BabblerWord
err = p.db.QueryRowx(`select * from babblerWords where id = ? LIMIT 1;`, cur.WordId).StructScan(&w)
w, err := getWord(p.store, cur.WordId)
if err != nil {
log.Error().Err(err)
return "", err

View File

@ -7,6 +7,7 @@ import (
"encoding/json"
"errors"
"fmt"
bh "github.com/timshannon/bolthold"
"image"
"image/png"
"io/ioutil"
@ -21,7 +22,6 @@ import (
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/jmoiron/sqlx"
"github.com/nfnt/resize"
"github.com/rs/zerolog/log"
@ -31,24 +31,21 @@ import (
"github.com/velour/catbase/plugins/counter"
)
// This is a skeleton plugin to serve as an example and quick copy/paste for new plugins.
var cachedImages = map[string][]byte{}
const DEFAULT_ITEM = "🍺"
type BeersPlugin struct {
b bot.Bot
c *config.Config
db *sqlx.DB
b bot.Bot
c *config.Config
store *bh.Store
untapdCache map[int]bool
handlers bot.HandlerTable
}
type untappdUser struct {
id int64
untappdUser string
untappdUser string `boltholdid:"untappdUser"`
channel string
lastCheckin int
chanNick string
@ -56,19 +53,10 @@ type untappdUser struct {
// New BeersPlugin creates a new BeersPlugin with the Plugin interface
func New(b bot.Bot) *BeersPlugin {
if _, err := b.DB().Exec(`create table if not exists untappd (
id integer primary key,
untappdUser string,
channel string,
lastCheckin integer,
chanNick string
);`); err != nil {
log.Fatal().Err(err)
}
p := &BeersPlugin{
b: b,
c: b.Config(),
db: b.DB(),
b: b,
c: b.Config(),
store: b.Store(),
untapdCache: make(map[int]bool),
}
@ -182,9 +170,7 @@ func (p *BeersPlugin) register() {
Str("nick", u.chanNick).
Msg("Creating Untappd user")
var count int
err := p.db.QueryRow(`select count(*) from untappd
where untappdUser = ?`, u.untappdUser).Scan(&count)
count, err := p.store.Count(untappdUser{}, bh.Where("untappdUser").Eq(u.untappdUser))
if err != nil {
log.Error().Err(err).Msgf("Error registering untappd")
}
@ -192,17 +178,7 @@ func (p *BeersPlugin) register() {
p.b.Send(r.Conn, bot.Message, channel, "I'm already watching you.")
return true
}
_, err = p.db.Exec(`insert into untappd (
untappdUser,
channel,
lastCheckin,
chanNick
) values (?, ?, ?, ?);`,
u.untappdUser,
u.channel,
0,
u.chanNick,
)
err = p.store.Insert(u.untappdUser, u)
if err != nil {
log.Error().Err(err).Msgf("Error registering untappd")
p.b.Send(r.Conn, bot.Message, channel, "I can't see.")
@ -240,15 +216,15 @@ func (p *BeersPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message,
return true
}
func getUserBeers(db *sqlx.DB, user, id, itemName string) counter.Item {
func getUserBeers(store *bh.Store, user, id, itemName string) counter.Item {
// TODO: really ought to have an ID here
booze, _ := counter.GetUserItem(db, user, id, itemName)
booze, _ := counter.GetUserItem(store, user, id, itemName)
return booze
}
func (p *BeersPlugin) setBeers(r *bot.Request, user, id string, amount int) {
itemName := p.c.Get("beers.itemname", DEFAULT_ITEM)
ub := getUserBeers(p.db, user, id, itemName)
ub := getUserBeers(p.store, user, id, itemName)
err := ub.Update(r, amount)
if err != nil {
log.Error().Err(err).Msgf("Error saving beers")
@ -257,7 +233,7 @@ func (p *BeersPlugin) setBeers(r *bot.Request, user, id string, amount int) {
func (p *BeersPlugin) addBeers(r *bot.Request, user, id string, delta int) {
itemName := p.c.Get("beers.itemname", DEFAULT_ITEM)
ub := getUserBeers(p.db, user, id, itemName)
ub := getUserBeers(p.store, user, id, itemName)
err := ub.UpdateDelta(r, delta)
if err != nil {
log.Error().Err(err).Msgf("Error saving beers")
@ -266,7 +242,7 @@ func (p *BeersPlugin) addBeers(r *bot.Request, user, id string, delta int) {
func (p *BeersPlugin) getBeers(user, id string) int {
itemName := p.c.Get("beers.itemname", DEFAULT_ITEM)
ub := getUserBeers(p.db, user, id, itemName)
ub := getUserBeers(p.store, user, id, itemName)
return ub.Count
}
@ -401,17 +377,13 @@ func (p *BeersPlugin) checkUntappd(c bot.Connector, channel string) {
}
userMap := make(map[string]untappdUser)
rows, err := p.db.Query(`select id, untappdUser, channel, lastCheckin, chanNick from untappd;`)
users := []untappdUser{}
err := p.store.Find(&users, &bh.Query{})
if err != nil {
log.Error().Err(err).Msg("Error getting untappd users")
return
}
for rows.Next() {
u := untappdUser{}
err := rows.Scan(&u.id, &u.untappdUser, &u.channel, &u.lastCheckin, &u.chanNick)
if err != nil {
log.Fatal().Err(err)
}
for _, u := range users {
userMap[u.untappdUser] = u
if u.chanNick == "" {
log.Fatal().Msg("Empty chanNick for no good reason.")
@ -497,9 +469,8 @@ func (p *BeersPlugin) sendCheckin(c bot.Connector, channel string, user untappdU
args = append([]interface{}{channel, msg}, args...)
user.lastCheckin = checkin.Checkin_id
_, err := p.db.Exec(`update untappd set
lastCheckin = ?
where id = ?`, user.lastCheckin, user.id)
err := p.store.Update(user.untappdUser, user)
if err != nil {
log.Error().Err(err).Msg("UPDATE ERROR!")
}

View File

@ -3,9 +3,9 @@ package countdown
import (
"fmt"
bh "github.com/timshannon/bolthold"
"time"
"github.com/jmoiron/sqlx"
"github.com/rs/zerolog/log"
"github.com/velour/catbase/bot"
@ -16,16 +16,16 @@ var nextYear = time.Date(time.Now().Year()+1, time.January, 1, 0, 0, 0, 1, time.
var thisYear = time.Now().Year()
type CountdownPlugin struct {
b bot.Bot
db *sqlx.DB
c *config.Config
b bot.Bot
store *bh.Store
c *config.Config
}
func New(b bot.Bot) *CountdownPlugin {
p := &CountdownPlugin{
b: b,
db: b.DB(),
c: b.Config(),
b: b,
store: b.Store(),
c: b.Config(),
}
p.scheduleNYE(p.newYearRollover)
@ -48,27 +48,28 @@ func (p *CountdownPlugin) scheduleNYE(f func()) {
func (p *CountdownPlugin) newYearRollover() {
log.Debug().Msgf("Happy new year!")
tx, err := p.db.Beginx()
if err != nil {
logError(err, "error getting transaction")
return
}
q := fmt.Sprintf(`create table counter_%d as select * from counter`, thisYear)
_, err = tx.Exec(q)
if err != nil {
logError(err, "error running insert into")
logError(tx.Rollback(), "error with insert rollback")
return
}
q = `delete from counter`
_, err = tx.Exec(q)
if err != nil {
logError(err, "error running delete")
logError(tx.Rollback(), "error with delete rollback")
return
}
err = tx.Commit()
logError(err, "error committing transaction")
// todo: something about rolling over
//tx, err := p.db.Beginx()
//if err != nil {
// logError(err, "error getting transaction")
// return
//}
//q := fmt.Sprintf(`create table counter_%d as select * from counter`, thisYear)
//_, err = tx.Exec(q)
//if err != nil {
// logError(err, "error running insert into")
// logError(tx.Rollback(), "error with insert rollback")
// return
//}
//q = `delete from counter`
//_, err = tx.Exec(q)
//if err != nil {
// logError(err, "error running delete")
// logError(tx.Rollback(), "error with delete rollback")
// return
//}
//err = tx.Commit()
//logError(err, "error committing transaction")
}
func logError(err error, msg string) {

View File

@ -59,7 +59,7 @@ func (p *CounterPlugin) mkIncrementAPI(delta int) func(w http.ResponseWriter, r
return
}
item, err := GetUserItem(p.db, userName, u.ID, itemName)
item, err := GetUserItem(p.store, userName, u.ID, itemName)
if err != nil {
log.Error().Err(err).Msg("error finding item")
w.WriteHeader(400)
@ -136,7 +136,7 @@ func (p *CounterPlugin) handleCounterAPI(w http.ResponseWriter, r *http.Request)
return
}
nick, id := p.resolveUser(bot.Request{Conn: p.b.DefaultConnector()}, info.User)
item, err := GetUserItem(p.db, nick, id, info.Thing)
item, err := GetUserItem(p.store, nick, id, info.Thing)
if err != nil {
log.Error().
Err(err).
@ -158,7 +158,7 @@ func (p *CounterPlugin) handleCounterAPI(w http.ResponseWriter, r *http.Request)
}
}
all, err := GetAllItems(p.db)
all, err := GetAllItems(p.store)
if err != nil {
w.WriteHeader(500)
fmt.Fprint(w, err)

View File

@ -3,6 +3,7 @@ package counter
import (
"database/sql"
"fmt"
bh "github.com/timshannon/bolthold"
"math/rand"
"regexp"
"strconv"
@ -11,8 +12,6 @@ import (
"github.com/rs/zerolog/log"
"github.com/velour/catbase/config"
"github.com/jmoiron/sqlx"
"github.com/velour/catbase/bot"
"github.com/velour/catbase/bot/msg"
)
@ -20,13 +19,13 @@ import (
// This is a counter plugin to count arbitrary things.
type CounterPlugin struct {
b bot.Bot
db *sqlx.DB
cfg *config.Config
b bot.Bot
store *bh.Store
cfg *config.Config
}
type Item struct {
*sqlx.DB
*bh.Store
ID int64
Nick string
@ -36,7 +35,7 @@ type Item struct {
}
type alias struct {
*sqlx.DB
*bh.Store
ID int64
Item string
@ -44,116 +43,97 @@ type alias struct {
}
// GetItems returns all counters
func GetAllItems(db *sqlx.DB) ([]Item, error) {
func GetAllItems(store *bh.Store) ([]Item, error) {
var items []Item
err := db.Select(&items, `select * from counter`)
err := store.Find(&items, &bh.Query{})
if err != nil {
return nil, err
}
// Don't forget to embed the db into all of that shiz
for i := range items {
items[i].DB = db
items[i].Store = store
}
return items, nil
}
// GetItems returns all counters for a subject
func GetItems(db *sqlx.DB, nick, id string) ([]Item, error) {
func GetItems(store *bh.Store, nick, id string) ([]Item, error) {
var items []Item
var err error
q := &bh.Query{}
if id != "" {
err = db.Select(&items, `select * from counter where userid = ?`, id)
q = bh.Where("userid").Eq(id)
} else {
err = db.Select(&items, `select * from counter where nick = ?`, nick)
q = bh.Where("nick").Eq(nick)
}
if err != nil {
if err = store.Find(&items, q); err != nil {
return nil, err
}
// Don't forget to embed the db into all of that shiz
for i := range items {
items[i].DB = db
items[i].Store = store
}
return items, nil
}
func LeaderAll(db *sqlx.DB) ([]Item, error) {
s := `select id,item,nick,count from (select id,item,nick,count,max(abs(count)) from counter group by item having count(nick) > 1 and max(abs(count)) > 1) order by count desc`
func LeaderAll(store *bh.Store) ([]Item, error) {
//s := `select id,item,nick,count from (select id,item,nick,count,max(abs(count)) from counter group by item having count(nick) > 1 and max(abs(count)) > 1) order by count desc`
// todo: translate that query
var items []Item
err := db.Select(&items, s)
err := store.Find(&items, &bh.Query{})
if err != nil {
log.Error().Msgf("Error querying leaderboard: %s", err)
return nil, err
}
for i := range items {
items[i].DB = db
items[i].Store = store
}
return items, nil
}
func Leader(db *sqlx.DB, itemName string) ([]Item, error) {
func Leader(store *bh.Store, itemName string) ([]Item, error) {
itemName = strings.ToLower(itemName)
s := `select * from counter where item=? order by count desc`
//s := `select * from counter where item=? order by count desc`
// todo: remove that when we verify this works
var items []Item
err := db.Select(&items, s, itemName)
err := store.Find(&items, bh.Where("item").Eq(itemName).SortBy("count").Reverse())
if err != nil {
return nil, err
}
for i := range items {
items[i].DB = db
items[i].Store = store
}
return items, nil
}
func RmAlias(db *sqlx.DB, aliasName string) error {
q := `delete from counter_alias where item = ?`
tx, err := db.Beginx()
if err != nil {
return err
}
_, err = tx.Exec(q, aliasName)
if err != nil {
if err := tx.Rollback(); err != nil {
return err
}
return err
}
err = tx.Commit()
return err
func RmAlias(store *bh.Store, aliasName string) error {
return store.Delete(alias{}, aliasName)
}
func MkAlias(db *sqlx.DB, aliasName, pointsTo string) (*alias, error) {
func MkAlias(store *bh.Store, aliasName, pointsTo string) (*alias, error) {
aliasName = strings.ToLower(aliasName)
pointsTo = strings.ToLower(pointsTo)
res, err := db.Exec(`insert into counter_alias (item, points_to) values (?, ?)`,
aliasName, pointsTo)
if err != nil {
_, err := db.Exec(`update counter_alias set points_to=? where item=?`, pointsTo, aliasName)
if err != nil {
return nil, err
}
var a alias
if err := db.Get(&a, `select * from counter_alias where item=?`, aliasName); err != nil {
return nil, err
}
return &a, nil
alias := &alias{
Store: store,
Item: aliasName,
PointsTo: pointsTo,
}
id, _ := res.LastInsertId()
return &alias{db, id, aliasName, pointsTo}, nil
err := store.Insert(bh.NextSequence(), alias)
return alias, err
}
// GetUserItem returns a specific counter for all subjects
func GetItem(db *sqlx.DB, itemName string) ([]Item, error) {
func GetItem(store *bh.Store, itemName string) ([]Item, error) {
itemName = trimUnicode(itemName)
var items []Item
var a alias
if err := db.Get(&a, `select * from counter_alias where item=?`, itemName); err == nil {
if err := store.FindOne(&a, bh.Where("item").Eq(itemName)); err == nil {
itemName = a.PointsTo
} else {
log.Error().Err(err).Interface("alias", a)
}
err := db.Select(&items, `select * from counter where item= ?`, itemName)
err := store.Find(&items, bh.Where("item").Eq(itemName))
if err != nil {
return nil, err
}
@ -162,37 +142,30 @@ func GetItem(db *sqlx.DB, itemName string) ([]Item, error) {
Interface("items", items).
Msg("got item")
for _, i := range items {
i.DB = db
i.Store = store
}
return items, nil
}
// GetUserItem returns a specific counter for a subject
func GetUserItem(db *sqlx.DB, nick, id, itemName string) (Item, error) {
func GetUserItem(store *bh.Store, nick, id, itemName string) (Item, error) {
itemName = trimUnicode(itemName)
var item Item
item.DB = db
item.Store = store
var a alias
if err := db.Get(&a, `select * from counter_alias where item=?`, itemName); err == nil {
if err := store.FindOne(&a, bh.Where("item").Eq(itemName)); err == nil {
itemName = a.PointsTo
} else {
log.Error().Err(err).Interface("alias", a)
}
var err error
q := bh.Where("nick").Eq(nick).And("item").Eq(itemName)
if id != "" {
err = db.Get(&item, `select * from counter where userid = ? and item= ?`, id, itemName)
} else {
err = db.Get(&item, `select * from counter where nick = ? and item= ?`, nick, itemName)
q = bh.Where("userid").Eq(id).And("item").Eq(itemName)
}
switch err {
case sql.ErrNoRows:
item.ID = -1
item.Nick = nick
item.Item = itemName
item.UserID = id
case nil:
default:
err = store.FindOne(&item, q)
if err != nil {
return Item{}, err
}
log.Debug().
@ -207,14 +180,7 @@ func GetUserItem(db *sqlx.DB, nick, id, itemName string) (Item, error) {
// GetUserItem returns a specific counter for a subject
// Create saves a counter
func (i *Item) Create() error {
res, err := i.Exec(`insert into counter (nick, item, count, userid) values (?, ?, ?, ?);`,
i.Nick, i.Item, i.Count, i.UserID)
if err != nil {
return err
}
id, _ := res.LastInsertId()
// hackhackhack?
i.ID = id
err := i.Store.Insert(bh.NextSequence(), i)
return err
}
@ -232,7 +198,7 @@ func (i *Item) Update(r *bot.Request, value int) error {
Interface("i", i).
Int("value", value).
Msg("Updating item")
_, err := i.Exec(`update counter set count = ? where id = ?`, i.Count, i.ID)
err := i.Store.Update(i.ID, i)
if err == nil {
sendUpdate(r, i.Nick, i.Item, i.Count)
}
@ -248,82 +214,49 @@ func (i *Item) UpdateDelta(r *bot.Request, delta int) error {
// Delete removes a counter from the database
func (i *Item) Delete() error {
_, err := i.Exec(`delete from counter where id = ?`, i.ID)
err := i.Store.Delete(i.ID, Item{})
i.ID = -1
return err
}
func (p *CounterPlugin) migrate(r bot.Request) bool {
db := p.db
nicks := []string{}
err := db.Select(&nicks, `select distinct nick from counter where userid is null`)
if err != nil {
log.Error().Err(err).Msg("could not get nick list")
return false
}
log.Debug().Msgf("Migrating %d nicks to IDs", len(nicks))
tx := db.MustBegin()
for _, nick := range nicks {
user, err := r.Conn.Profile(nick)
if err != nil {
continue
}
if _, err = tx.Exec(`update counter set userid=? where nick=?`, user.ID, nick); err != nil {
log.Error().Err(err).Msg("Could not migrate users")
continue
}
}
if err := tx.Commit(); err != nil {
log.Error().Err(err).Msg("Could not migrate users")
}
// todo: probably don't need this anymore
//db := p.db
//
//nicks := []string{}
//err := db.Select(&nicks, `select distinct nick from counter where userid is null`)
//if err != nil {
// log.Error().Err(err).Msg("could not get nick list")
// return false
//}
//
//log.Debug().Msgf("Migrating %d nicks to IDs", len(nicks))
//
//tx := db.MustBegin()
//
//for _, nick := range nicks {
// user, err := r.Conn.Profile(nick)
// if err != nil {
// continue
// }
// if _, err = tx.Exec(`update counter set userid=? where nick=?`, user.ID, nick); err != nil {
// log.Error().Err(err).Msg("Could not migrate users")
// continue
// }
//}
//
//if err := tx.Commit(); err != nil {
// log.Error().Err(err).Msg("Could not migrate users")
//}
return false
}
func setupDB(b bot.Bot) error {
db := b.DB()
tx := db.MustBegin()
db.MustExec(`create table if not exists counter (
id integer primary key,
nick string,
item string,
count integer
);`)
db.MustExec(`create table if not exists counter_alias (
id integer PRIMARY KEY AUTOINCREMENT,
item string NOT NULL UNIQUE,
points_to string NOT NULL
);`)
tx.Commit()
tx = db.MustBegin()
count := 0
err := tx.Get(&count, `SELECT count(*) FROM pragma_table_info('counter') where name='userid'`)
if err != nil {
return err
}
if count == 0 {
tx.MustExec(`alter table counter add column userid string`)
}
tx.Commit()
return nil
}
// NewCounterPlugin creates a new CounterPlugin with the Plugin interface
func New(b bot.Bot) *CounterPlugin {
if err := setupDB(b); err != nil {
panic(err)
}
cp := &CounterPlugin{
b: b,
db: b.DB(),
cfg: b.Config(),
b: b,
store: b.Store(),
cfg: b.Config(),
}
b.RegisterRegex(cp, bot.Startup, regexp.MustCompile(`.*`), cp.migrate)
@ -376,7 +309,7 @@ func (p *CounterPlugin) mkAliasCmd(r bot.Request) bool {
p.b.Send(r.Conn, bot.Message, fmt.Sprintf("You must provide all fields for an alias: %s", mkAliasRegex))
return true
}
if _, err := MkAlias(p.db, what, to); err != nil {
if _, err := MkAlias(p.store, what, to); err != nil {
log.Error().Err(err).Msg("Could not mkalias")
p.b.Send(r.Conn, bot.Message, r.Msg.Channel, "We're gonna need too much db space to make an alias for your mom.")
return true
@ -392,7 +325,7 @@ func (p *CounterPlugin) rmAliasCmd(r bot.Request) bool {
p.b.Send(r.Conn, bot.Message, r.Msg.Channel, "You must specify an alias to remove.")
return true
}
if err := RmAlias(p.db, what); err != nil {
if err := RmAlias(p.store, what); err != nil {
log.Error().Err(err).Msg("could not RmAlias")
p.b.Send(r.Conn, bot.Message, r.Msg.Channel, "`sudo rm your mom` => Nope, she's staying with me.")
return true
@ -407,10 +340,10 @@ func (p *CounterPlugin) leaderboardCmd(r bot.Request) bool {
what := r.Values["what"]
if what == "" {
cmd = func() ([]Item, error) { return LeaderAll(p.db) }
cmd = func() ([]Item, error) { return LeaderAll(p.store) }
} else {
itNameTxt = fmt.Sprintf(" for %s", what)
cmd = func() ([]Item, error) { return Leader(p.db, what) }
cmd = func() ([]Item, error) { return Leader(p.store, what) }
}
its, err := cmd()
@ -438,7 +371,7 @@ func (p *CounterPlugin) resetCmd(r bot.Request) bool {
nick, id := p.resolveUser(r, "")
channel := r.Msg.Channel
items, err := GetItems(p.db, nick, id)
items, err := GetItems(p.store, nick, id)
if err != nil {
log.Error().
Err(err).
@ -472,7 +405,7 @@ func (p *CounterPlugin) inspectCmd(r bot.Request) bool {
Str("id", id).
Msg("Getting counter")
// pull all of the items associated with "subject"
items, err := GetItems(p.db, nick, id)
items, err := GetItems(p.store, nick, id)
if err != nil {
log.Error().
Err(err).
@ -513,7 +446,7 @@ func (p *CounterPlugin) clearCmd(r bot.Request) bool {
channel := r.Msg.Channel
c := r.Conn
it, err := GetUserItem(p.db, nick, id, itemName)
it, err := GetUserItem(p.store, nick, id, itemName)
if err != nil {
log.Error().
Err(err).
@ -551,7 +484,7 @@ func (p *CounterPlugin) countCmd(r bot.Request) bool {
}
var item Item
item, err := GetUserItem(p.db, nick, id, itemName)
item, err := GetUserItem(p.store, nick, id, itemName)
switch {
case err == sql.ErrNoRows:
p.b.Send(r.Conn, bot.Message, r.Msg.Channel, fmt.Sprintf("I don't think %s has any %s.",
@ -581,7 +514,7 @@ func (p *CounterPlugin) incrementCmd(r bot.Request) bool {
itemName := r.Values["thing"]
channel := r.Msg.Channel
// ++ those fuckers
item, err := GetUserItem(p.db, nick, id, itemName)
item, err := GetUserItem(p.store, nick, id, itemName)
if err != nil {
log.Error().
Err(err).
@ -606,7 +539,7 @@ func (p *CounterPlugin) decrementCmd(r bot.Request) bool {
itemName := r.Values["thing"]
channel := r.Msg.Channel
// -- those fuckers
item, err := GetUserItem(p.db, nick, id, itemName)
item, err := GetUserItem(p.store, nick, id, itemName)
if err != nil {
log.Error().
Err(err).
@ -628,7 +561,7 @@ func (p *CounterPlugin) addToCmd(r bot.Request) bool {
itemName := r.Values["thing"]
channel := r.Msg.Channel
// += those fuckers
item, err := GetUserItem(p.db, nick, id, itemName)
item, err := GetUserItem(p.store, nick, id, itemName)
if err != nil {
log.Error().
Err(err).
@ -652,7 +585,7 @@ func (p *CounterPlugin) removeFromCmd(r bot.Request) bool {
itemName := r.Values["thing"]
channel := r.Msg.Channel
// -= those fuckers
item, err := GetUserItem(p.db, nick, id, itemName)
item, err := GetUserItem(p.store, nick, id, itemName)
if err != nil {
log.Error().
Err(err).
@ -693,7 +626,7 @@ func (p *CounterPlugin) teaMatchCmd(r bot.Request) bool {
itemName := strings.ToLower(submatches[1])
// We will specifically allow :tea: to keep compatability
item, err := GetUserItem(p.db, nick, id, itemName)
item, err := GetUserItem(p.store, nick, id, itemName)
if err != nil || (item.Count == 0 && item.Item != ":tea:") {
log.Error().
Err(err).

View File

@ -3,10 +3,10 @@
package fact
import (
"database/sql"
"embed"
"encoding/json"
"fmt"
bh "github.com/timshannon/bolthold"
"html/template"
"math/rand"
"net/http"
@ -18,8 +18,6 @@ import (
"github.com/go-chi/chi/v5"
"github.com/rs/zerolog/log"
"github.com/jmoiron/sqlx"
"github.com/velour/catbase/bot"
"github.com/velour/catbase/bot/msg"
)
@ -32,7 +30,7 @@ var embeddedFS embed.FS
// Factoid stores info about our factoid for lookup and later interaction
type Factoid struct {
ID sql.NullInt64
ID int64 `boltholdid:"ID"`
Fact string
Tidbit string
Verb string
@ -47,47 +45,50 @@ type alias struct {
Next string
}
func (a *alias) resolve(db *sqlx.DB) (*Factoid, error) {
func (a *alias) resolve(store *bh.Store) (*Factoid, error) {
// perform db query to fill the To field
q := `select fact, next from factoid_alias where fact=?`
// todo: remove this query
//q := `select fact, next from factoid_alias where fact=?`
var next alias
err := db.Get(&next, q, a.Next)
err := store.FindOne(&next, bh.Where("fact").Eq(a.Next))
if err != nil {
// we hit the end of the chain, get a factoid named Next
fact, err := GetSingleFact(db, a.Next)
fact, err := GetSingleFact(store, a.Next)
if err != nil {
err := fmt.Errorf("Error resolvig alias %v: %v", a, err)
return nil, err
}
return fact, nil
}
return next.resolve(db)
return next.resolve(store)
}
func findAlias(db *sqlx.DB, fact string) (bool, *Factoid) {
q := `select * from factoid_alias where fact=?`
func findAlias(store *bh.Store, fact string) (bool, *Factoid) {
// todo: remove this query
//q := `select * from factoid_alias where fact=?`
var a alias
err := db.Get(&a, q, fact)
err := store.FindOne(&a, bh.Where("fact").Eq(fact))
if err != nil {
return false, nil
}
f, err := a.resolve(db)
f, err := a.resolve(store)
return err == nil, f
}
func (a *alias) save(db *sqlx.DB) error {
q := `select * from factoid_alias where fact=?`
func (a *alias) save(store *bh.Store) error {
//q := `select * from factoid_alias where fact=?`
var offender alias
err := db.Get(&offender, q, a.Next)
err := store.FindOne(&offender, bh.Where("fact").Eq(a.Next))
if err == nil {
return fmt.Errorf("DANGER: an opposite alias already exists")
}
_, err = a.resolve(db)
_, err = a.resolve(store)
if err != nil {
return fmt.Errorf("there is no fact at that destination")
}
q = `insert or replace into factoid_alias (fact, next) values (?, ?)`
_, err = db.Exec(q, a.Fact, a.Next)
err = store.Upsert(a.Fact, a)
//q = `insert or replace into factoid_alias (fact, next) values (?, ?)`
// todo: remove query
if err != nil {
return err
}
@ -98,168 +99,54 @@ func aliasFromStrings(from, to string) *alias {
return &alias{from, to}
}
func (f *Factoid) Save(db *sqlx.DB) error {
func (f *Factoid) Save(store *bh.Store) error {
var err error
if f.ID.Valid {
// update
_, err = db.Exec(`update factoid set
fact=?,
tidbit=?,
verb=?,
owner=?,
accessed=?,
count=?
where id=?`,
f.Fact,
f.Tidbit,
f.Verb,
f.Owner,
f.Accessed.Unix(),
f.Count,
f.ID.Int64)
if f.ID != 0 {
f.Accessed = time.Now()
err = store.Update(f.ID, f)
} else {
f.Created = time.Now()
f.Accessed = time.Now()
// insert
res, err := db.Exec(`insert into factoid (
fact,
tidbit,
verb,
owner,
created,
accessed,
count
) values (?, ?, ?, ?, ?, ?, ?);`,
f.Fact,
f.Tidbit,
f.Verb,
f.Owner,
f.Created.Unix(),
f.Accessed.Unix(),
f.Count,
)
if err != nil {
return err
}
id, err := res.LastInsertId()
// hackhackhack?
f.ID.Int64 = id
f.ID.Valid = true
err = store.Insert(bh.NextSequence(), f)
}
return err
}
func (f *Factoid) delete(db *sqlx.DB) error {
func (f *Factoid) delete(store *bh.Store) error {
var err error
if f.ID.Valid {
_, err = db.Exec(`delete from factoid where id=?`, f.ID)
if f.ID != 0 {
err = store.Delete(f.ID, Factoid{})
}
f.ID.Valid = false
f.ID = 0
return err
}
func getFacts(db *sqlx.DB, fact string, tidbit string) ([]*Factoid, error) {
func getFacts(store *bh.Store, fact string, tidbit string) ([]*Factoid, error) {
var fs []*Factoid
query := `select
id,
fact,
tidbit,
verb,
owner,
created,
accessed,
count
from factoid
where fact like ?
and tidbit like ?;`
rows, err := db.Query(query,
"%"+fact+"%", "%"+tidbit+"%")
err := store.Find(&fs, bh.Where("fact").Contains(fact).And("tidbit").Contains(tidbit))
if err != nil {
log.Error().Err(err).Msg("Error regexping for facts")
return nil, err
}
for rows.Next() {
var f Factoid
var tmpCreated int64
var tmpAccessed int64
err := rows.Scan(
&f.ID,
&f.Fact,
&f.Tidbit,
&f.Verb,
&f.Owner,
&tmpCreated,
&tmpAccessed,
&f.Count,
)
if err != nil {
return nil, err
}
f.Created = time.Unix(tmpCreated, 0)
f.Accessed = time.Unix(tmpAccessed, 0)
fs = append(fs, &f)
}
return fs, err
}
func GetSingle(db *sqlx.DB) (*Factoid, error) {
var f Factoid
var tmpCreated int64
var tmpAccessed int64
err := db.QueryRow(`select
id,
fact,
tidbit,
verb,
owner,
created,
accessed,
count
from factoid
order by random() limit 1;`).Scan(
&f.ID,
&f.Fact,
&f.Tidbit,
&f.Verb,
&f.Owner,
&tmpCreated,
&tmpAccessed,
&f.Count,
)
f.Created = time.Unix(tmpCreated, 0)
f.Accessed = time.Unix(tmpAccessed, 0)
return &f, err
func GetSingle(store *bh.Store) (*Factoid, error) {
var allMatching []Factoid
if err := store.Find(&allMatching, &bh.Query{}); err != nil {
return nil, err
}
f := allMatching[rand.Intn(len(allMatching))]
return &f, nil
}
func GetSingleFact(db *sqlx.DB, fact string) (*Factoid, error) {
var f Factoid
var tmpCreated int64
var tmpAccessed int64
err := db.QueryRow(`select
id,
fact,
tidbit,
verb,
owner,
created,
accessed,
count
from factoid
where fact like ?
order by random() limit 1;`,
fact).Scan(
&f.ID,
&f.Fact,
&f.Tidbit,
&f.Verb,
&f.Owner,
&tmpCreated,
&tmpAccessed,
&f.Count,
)
f.Created = time.Unix(tmpCreated, 0)
f.Accessed = time.Unix(tmpAccessed, 0)
return &f, err
func GetSingleFact(store *bh.Store, fact string) (*Factoid, error) {
var allMatching []Factoid
if err := store.Find(&allMatching, bh.Where("fact").Contains(fact)); err != nil {
return nil, err
}
f := allMatching[rand.Intn(len(allMatching))]
return &f, nil
}
// Factoid provides the necessary plugin-wide needs
@ -267,7 +154,7 @@ type FactoidPlugin struct {
Bot bot.Bot
NotFound []string
LastFact *Factoid
db *sqlx.DB
store *bh.Store
handlers bot.HandlerTable
}
@ -283,32 +170,11 @@ func New(botInst bot.Bot) *FactoidPlugin {
"NOPE! NOPE! NOPE!",
"One time, I learned how to jump rope.",
},
db: botInst.DB(),
store: botInst.Store(),
}
c := botInst.DefaultConnector()
if _, err := p.db.Exec(`create table if not exists factoid (
id integer primary key,
fact string,
tidbit string,
verb string,
owner string,
created integer,
accessed integer,
count integer
);`); err != nil {
log.Fatal().Err(err)
}
if _, err := p.db.Exec(`create table if not exists factoid_alias (
fact string,
next string,
primary key (fact, next)
);`); err != nil {
log.Fatal().Err(err)
}
for _, channel := range botInst.Config().GetArray("channels", []string{}) {
go p.factTimer(c, channel)
@ -366,14 +232,11 @@ func (p *FactoidPlugin) learnFact(message msg.Message, fact, verb, tidbit string
}
}
var count sql.NullInt64
err := p.db.QueryRow(`select count(*) from factoid
where fact=? and verb=? and tidbit=?`,
fact, verb, tidbit).Scan(&count)
count, err := p.store.Count(Factoid{}, bh.Where("fact").Eq(fact).And("verb").Eq(verb).And("tidbit").Eq(tidbit))
if err != nil {
log.Error().Err(err).Msg("Error counting facts")
return fmt.Errorf("What?")
} else if count.Valid && count.Int64 != 0 {
} else if count != 0 {
log.Debug().Msg("User tried to relearn a fact.")
return fmt.Errorf("Look, I already know that.")
}
@ -388,7 +251,7 @@ func (p *FactoidPlugin) learnFact(message msg.Message, fact, verb, tidbit string
Count: 0,
}
p.LastFact = &n
err = n.Save(p.db)
err = n.Save(p.store)
if err != nil {
log.Error().Err(err).Msg("Error inserting fact")
return fmt.Errorf("My brain is overheating.")
@ -401,9 +264,9 @@ func (p *FactoidPlugin) learnFact(message msg.Message, fact, verb, tidbit string
func (p *FactoidPlugin) findTrigger(fact string) (bool, *Factoid) {
fact = strings.ToLower(fact) // TODO: make sure this needs to be lowered here
f, err := GetSingleFact(p.db, fact)
f, err := GetSingleFact(p.store, fact)
if err != nil {
return findAlias(p.db, fact)
return findAlias(p.store, fact)
}
return true, f
}
@ -437,7 +300,7 @@ func (p *FactoidPlugin) sayFact(c bot.Connector, message msg.Message, fact Facto
// update fact tracking
fact.Accessed = time.Now()
fact.Count += 1
err := fact.Save(p.db)
err := fact.Save(p.store)
if err != nil {
log.Error().
Interface("fact", fact).
@ -506,7 +369,7 @@ func (p *FactoidPlugin) tellThemWhatThatWas(c bot.Connector, message msg.Message
msg = "Nope."
} else {
msg = fmt.Sprintf("That was (#%d) '%s <%s> %s'",
fact.ID.Int64, fact.Fact, fact.Verb, fact.Tidbit)
fact.ID, fact.Fact, fact.Verb, fact.Tidbit)
}
p.Bot.Send(c, bot.Message, message.Channel, msg)
return true
@ -564,14 +427,14 @@ func (p *FactoidPlugin) forgetLastFact(c bot.Connector, message msg.Message) boo
return true
}
err := p.LastFact.delete(p.db)
err := p.LastFact.delete(p.store)
if err != nil {
log.Error().
Err(err).
Interface("LastFact", p.LastFact).
Msg("Error removing fact")
}
fmt.Printf("Forgot #%d: %s %s %s\n", p.LastFact.ID.Int64, p.LastFact.Fact,
fmt.Printf("Forgot #%d: %s %s %s\n", p.LastFact.ID, p.LastFact.Fact,
p.LastFact.Verb, p.LastFact.Tidbit)
p.Bot.Send(c, bot.Action, message.Channel, "hits himself over the head with a skillet")
p.LastFact = nil
@ -603,7 +466,7 @@ func (p *FactoidPlugin) changeFact(c bot.Connector, message msg.Message) bool {
replace := parts[2]
// replacement
result, err := getFacts(p.db, trigger, parts[1])
result, err := getFacts(p.store, trigger, parts[1])
if err != nil {
log.Error().
Err(err).
@ -628,11 +491,11 @@ func (p *FactoidPlugin) changeFact(c bot.Connector, message msg.Message) bool {
fact.Tidbit = reg.ReplaceAllString(fact.Tidbit, replace)
fact.Count += 1
fact.Accessed = time.Now()
fact.Save(p.db)
fact.Save(p.store)
}
} else if len(parts) == 3 {
// search for a factoid and print it
result, err := getFacts(p.db, trigger, parts[1])
result, err := getFacts(p.store, trigger, parts[1])
if err != nil {
log.Error().
Err(err).
@ -684,7 +547,7 @@ func (p *FactoidPlugin) register() {
to := r.Values["to"]
log.Debug().Msgf("alias: %+v", r)
a := aliasFromStrings(from, to)
if err := a.save(p.db); err != nil {
if err := a.save(p.store); err != nil {
p.Bot.Send(r.Conn, bot.Message, r.Msg.Channel, err.Error())
} else {
p.Bot.Send(r.Conn, bot.Action, r.Msg.Channel, "learns a new synonym")
@ -748,7 +611,7 @@ func (p *FactoidPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message
// Pull a fact at random from the database
func (p *FactoidPlugin) randomFact() *Factoid {
f, err := GetSingle(p.db)
f, err := GetSingle(p.store)
if err != nil {
fmt.Println("Error getting a fact: ", err)
return nil
@ -838,7 +701,7 @@ func (p *FactoidPlugin) serveAPI(w http.ResponseWriter, r *http.Request) {
return
}
entries, err := getFacts(p.db, info.Query, "")
entries, err := getFacts(p.store, info.Query, "")
if err != nil {
w.WriteHeader(500)
fmt.Fprint(w, err)

View File

@ -3,13 +3,12 @@
package first
import (
"database/sql"
"fmt"
bh "github.com/timshannon/bolthold"
"regexp"
"strings"
"time"
"github.com/jmoiron/sqlx"
"github.com/rs/zerolog/log"
"github.com/velour/catbase/config"
@ -22,7 +21,7 @@ import (
type FirstPlugin struct {
bot bot.Bot
config *config.Config
db *sqlx.DB
store *bh.Store
handlers bot.HandlerTable
enabled bool
}
@ -38,59 +37,23 @@ type FirstEntry struct {
}
// Insert or update the first entry
func (fe *FirstEntry) save(db *sqlx.DB) error {
if _, err := db.Exec(`insert into first (day, time, channel, body, nick)
values (?, ?, ?, ?, ?)`,
fe.day.Unix(),
fe.time.Unix(),
fe.channel,
fe.body,
fe.nick,
); err != nil {
return err
}
return nil
func (fe *FirstEntry) save(store *bh.Store) error {
return store.Insert(bh.NextSequence(), fe)
}
func (fe *FirstEntry) delete(db *sqlx.DB) error {
tx, err := db.Beginx()
if err != nil {
return err
}
_, err = tx.Exec(`delete from first where id=?`, fe.id)
if err != nil {
return err
}
err = tx.Commit()
if err != nil {
return err
}
return nil
func (fe *FirstEntry) delete(store *bh.Store) error {
return store.Delete(fe.id, FirstEntry{})
}
// NewFirstPlugin creates a new FirstPlugin with the Plugin interface
func New(b bot.Bot) *FirstPlugin {
_, err := b.DB().Exec(`create table if not exists first (
id integer primary key,
day integer,
time integer,
channel string,
body string,
nick string
);`)
if err != nil {
log.Fatal().
Err(err).
Msg("Could not create first table")
}
log.Info().Msgf("First plugin initialized with day: %s",
Midnight(time.Now()))
fp := &FirstPlugin{
bot: b,
config: b.Config(),
db: b.DB(),
store: b.Store(),
enabled: true,
}
fp.register()
@ -98,44 +61,14 @@ func New(b bot.Bot) *FirstPlugin {
return fp
}
func getLastFirst(db *sqlx.DB, channel string) (*FirstEntry, error) {
// Get last first entry
var id sql.NullInt64
var day sql.NullInt64
var timeEntered sql.NullInt64
var body sql.NullString
var nick sql.NullString
err := db.QueryRow(`select
id, max(day), time, body, nick from first
where channel = ?
limit 1;
`, channel).Scan(
&id,
&day,
&timeEntered,
&body,
&nick,
)
switch {
case err == sql.ErrNoRows || !id.Valid:
log.Info().Msg("No previous first entries")
return nil, nil
case err != nil:
log.Warn().Err(err).Msg("Error on first query row")
func getLastFirst(store *bh.Store, channel string) (*FirstEntry, error) {
fe := &FirstEntry{}
err := store.FindOne(fe, bh.Where("channel").Eq(channel))
if err != nil {
return nil, err
}
log.Debug().Msgf("id: %v day %v time %v body %v nick %v",
id, day, timeEntered, body, nick)
return &FirstEntry{
id: id.Int64,
day: time.Unix(day.Int64, 0),
time: time.Unix(timeEntered.Int64, 0),
channel: channel,
body: body.String,
nick: nick.String,
saved: true,
}, nil
log.Debug().Msgf("id: %v day %v time %v body %v nick %v", fe)
return fe, nil
}
func Midnight(t time.Time) time.Time {
@ -159,7 +92,7 @@ func (p *FirstPlugin) register() {
{Kind: bot.Message, IsCmd: false,
Regex: regexp.MustCompile(`(?i)^who'?s on first the most.?$`),
Handler: func(r bot.Request) bool {
first, err := getLastFirst(p.db, r.Msg.Channel)
first, err := getLastFirst(p.store, r.Msg.Channel)
if first != nil && err == nil {
p.leaderboard(r.Conn, r.Msg.Channel)
return true
@ -169,7 +102,7 @@ func (p *FirstPlugin) register() {
{Kind: bot.Message, IsCmd: false,
Regex: regexp.MustCompile(`(?i)^who'?s on first.?$`),
Handler: func(r bot.Request) bool {
first, err := getLastFirst(p.db, r.Msg.Channel)
first, err := getLastFirst(p.store, r.Msg.Channel)
if first != nil && err == nil {
p.announceFirst(r.Conn, first)
return true
@ -183,13 +116,13 @@ func (p *FirstPlugin) register() {
p.bot.Send(r.Conn, bot.Message, r.Msg.Channel, "You are not authorized to do that.")
return true
}
fe, err := getLastFirst(p.db, r.Msg.Channel)
fe, err := getLastFirst(p.store, r.Msg.Channel)
if err != nil {
p.bot.Send(r.Conn, bot.Message, r.Msg.Channel, "Could not find a first entry.")
return true
}
p.enabled = false
err = fe.delete(p.db)
err = fe.delete(p.store)
if err != nil {
p.bot.Send(r.Conn, bot.Message, r.Msg.Channel,
fmt.Sprintf("Could not delete first entry: %s", err))
@ -215,7 +148,7 @@ func (p *FirstPlugin) register() {
return false
}
first, err := getLastFirst(p.db, r.Msg.Channel)
first, err := getLastFirst(p.store, r.Msg.Channel)
if err != nil {
log.Error().
Err(err).
@ -303,7 +236,7 @@ func (p *FirstPlugin) recordFirst(c bot.Connector, message msg.Message) {
nick: message.User.Name,
}
log.Info().Msgf("recordFirst: %+v", first.day)
err := first.save(p.db)
err := first.save(p.store)
if err != nil {
log.Error().Err(err).Msg("Error saving first entry")
return
@ -312,27 +245,31 @@ func (p *FirstPlugin) recordFirst(c bot.Connector, message msg.Message) {
}
func (p *FirstPlugin) leaderboard(c bot.Connector, ch string) error {
q := `select max(channel) channel, max(nick) nick, count(id) count
from first
group by channel, nick
having channel = ?
order by count desc
limit 3`
res := []struct {
Channel string
Nick string
Count int
}{}
err := p.db.Select(&res, q, ch)
// todo: remove this once we verify stuff
//q := `select max(channel) channel, max(nick) nick, count(id) count
// from first
// group by channel, nick
// having channel = ?
// order by count desc
// limit 3`
groups, err := p.store.FindAggregate(FirstEntry{}, bh.Where("channel").Eq(ch), "channel", "nick")
if err != nil {
return err
}
talismans := []string{":gold-trophy:", ":silver-trophy:", ":bronze-trophy:"}
msg := "First leaderboard:\n"
for i, e := range res {
msg += fmt.Sprintf("%s %d %s\n", talismans[i], e.Count, e.Nick)
if len(groups) != 1 {
return fmt.Errorf("found %d groups but expected 1", len(groups))
}
p.bot.Send(c, bot.Message, ch, msg)
//res := groups[0]
//talismans := []string{":gold-trophy:", ":silver-trophy:", ":bronze-trophy:"}
//msg := "First leaderboard:\n"
//n := res.Count()
//fe := FirstEntry{}
//
//for i, e := range res {
// msg += fmt.Sprintf("%s %d %s\n", talismans[i], e.Count, e.Nick)
//}
//p.bot.Send(c, bot.Message, ch, msg)
// todo: care about this
return nil
}

View File

@ -2,12 +2,12 @@ package goals
import (
"fmt"
bh "github.com/timshannon/bolthold"
"regexp"
"sort"
"strconv"
"time"
"github.com/jmoiron/sqlx"
"github.com/rs/zerolog/log"
"github.com/velour/catbase/bot"
@ -19,38 +19,22 @@ import (
type GoalsPlugin struct {
b bot.Bot
cfg *config.Config
db *sqlx.DB
store *bh.Store
handlers bot.HandlerTable
}
func New(b bot.Bot) *GoalsPlugin {
p := &GoalsPlugin{
b: b,
cfg: b.Config(),
db: b.DB(),
b: b,
cfg: b.Config(),
store: b.Store(),
}
p.mkDB()
p.registerCmds()
b.Register(p, bot.Help, p.help)
counter.RegisterUpdate(p.update)
return p
}
func (p *GoalsPlugin) mkDB() {
_, err := p.db.Exec(`create table if not exists goals (
id integer primary key,
kind string not null,
who string not null,
what string not null,
amount integer,
unique (who, what, kind)
)`)
if err != nil {
log.Fatal().Msgf("could not create goals db: %s", err)
}
}
func (p *GoalsPlugin) registerCmds() {
p.handlers = bot.HandlerTable{
{Kind: bot.Message, IsCmd: true,
@ -144,7 +128,7 @@ func (p *GoalsPlugin) check(c bot.Connector, ch, kind, what, who string) {
}
func (p *GoalsPlugin) checkCompetition(c bot.Connector, ch, what, who string) {
items, err := counter.GetItem(p.db, what)
items, err := counter.GetItem(p.store, what)
if err != nil || len(items) == 0 {
p.b.Send(c, bot.Message, ch, fmt.Sprintf("I couldn't find any %s", what))
return
@ -204,7 +188,7 @@ func (p *GoalsPlugin) checkGoal(c bot.Connector, ch, what, who string) {
Str("id", id).
Str("what", what).
Msg("looking for item")
item, err := counter.GetUserItem(p.db, nick, id, what)
item, err := counter.GetUserItem(p.store, nick, id, what)
if err != nil {
p.b.Send(c, bot.Message, ch, fmt.Sprintf("I couldn't find any %s", what))
return
@ -253,7 +237,7 @@ func parseCmd(r *regexp.Regexp, body string) cmd {
}
type goal struct {
ID int64
ID int64 `boltholdid:"ID"`
Kind string
Who string
What string
@ -275,8 +259,7 @@ func (p *GoalsPlugin) newGoal(kind, who, what string, amount int) goal {
func (p *GoalsPlugin) getGoal(who, what string) ([]*goal, error) {
gs := []*goal{}
err := p.db.Select(&gs, `select * from goals where who = ? and what = ?`,
who, what)
err := p.store.Find(&gs, bh.Where("who").Eq(who).And("what").Eq(what))
if err != nil {
return nil, err
}
@ -288,8 +271,7 @@ func (p *GoalsPlugin) getGoal(who, what string) ([]*goal, error) {
func (p *GoalsPlugin) getGoalKind(kind, who, what string) (*goal, error) {
g := &goal{gp: p}
err := p.db.Get(g, `select * from goals where kind = ? and who = ? and what = ?`,
kind, who, what)
err := p.store.FindOne(&g, bh.Where("kind").Eq(kind).And("who").Eq(who).And("what").Eq(what))
if err != nil {
return nil, err
}
@ -297,15 +279,10 @@ func (p *GoalsPlugin) getGoalKind(kind, who, what string) (*goal, error) {
}
func (g *goal) Save() error {
res, err := g.gp.db.Exec(`insert or replace into goals (who, what, kind, amount) values (?, ?, ? ,?)`,
g.Who, g.What, g.Kind, g.Amount)
err := g.gp.store.Insert(bh.NextSequence(), &g)
if err != nil {
return err
}
dbID, err := res.LastInsertId()
if err == nil && dbID != g.ID && g.ID == 0 {
g.ID = dbID
}
return nil
}
@ -313,7 +290,7 @@ func (g goal) Delete() error {
if g.ID == -1 {
return nil
}
_, err := g.gp.db.Exec(`delete from goals where id = ?`, g.ID)
err := g.gp.store.Delete(goal{}, g.ID)
return err
}

View File

@ -6,19 +6,20 @@ package inventory
import (
"fmt"
bh "github.com/timshannon/bolthold"
"math/rand"
"regexp"
"strings"
"github.com/rs/zerolog/log"
"github.com/jmoiron/sqlx"
"github.com/velour/catbase/bot"
"github.com/velour/catbase/bot/msg"
"github.com/velour/catbase/config"
)
type InventoryPlugin struct {
*sqlx.DB
*bh.Store
bot bot.Bot
config *config.Config
handlers bot.HandlerTable
@ -29,7 +30,7 @@ func New(b bot.Bot) *InventoryPlugin {
config := b.Config()
p := &InventoryPlugin{
DB: b.DB(),
Store: b.Store(),
bot: b,
config: config,
}
@ -37,15 +38,15 @@ func New(b bot.Bot) *InventoryPlugin {
b.RegisterFilter("$item", p.itemFilter)
b.RegisterFilter("$giveitem", p.giveItemFilter)
p.DB.MustExec(`create table if not exists inventory (
item string primary key
);`)
p.register()
return p
}
type Item struct {
Name string `boltholdid:"Name"`
}
func (p *InventoryPlugin) giveItemFilter(input string) string {
for strings.Contains(input, "$giveitem") {
item := p.random()
@ -107,72 +108,62 @@ func (p *InventoryPlugin) register() {
}
func (p *InventoryPlugin) removeRandom() string {
var name string
err := p.QueryRow(`select item from inventory order by random() limit 1`).Scan(
&name,
)
items := []Item{}
err := p.Find(&items, &bh.Query{})
if err != nil {
log.Error().Err(err).Msgf("Error finding random entry")
return "IAMERROR"
}
_, err = p.Exec(`delete from inventory where item=?`, name)
if err != nil {
log.Error().Err(err).Msgf("Error finding random entry")
return "IAMERROR"
}
return name
item := items[rand.Intn(len(items))]
err = p.Delete(item.Name, Item{})
return item.Name
}
func (p *InventoryPlugin) count() int {
var output int
err := p.QueryRow(`select count(*) as count from inventory`).Scan(&output)
count, err := p.Store.Count(Item{}, &bh.Query{})
if err != nil {
log.Error().Err(err).Msg("Error checking for item")
return -1
}
return output
return count
}
func (p *InventoryPlugin) random() string {
var name string
err := p.QueryRow(`select item from inventory order by random() limit 1`).Scan(
&name,
)
items := []Item{}
err := p.Find(&items, &bh.Query{})
if err != nil {
log.Error().Err(err).Msg("Error finding random entry")
log.Error().Err(err).Msgf("Error finding random entry")
return "IAMERROR"
}
return name
item := items[rand.Intn(len(items))]
return item.Name
}
func (p *InventoryPlugin) getAll() []string {
rows, err := p.Queryx(`select item from inventory`)
items := []Item{}
err := p.Find(&items, &bh.Query{})
if err != nil {
log.Error().Err(err).Msg("Error getting all items")
return []string{}
}
output := []string{}
for rows.Next() {
var item string
rows.Scan(&item)
output = append(output, item)
for _, item := range items {
output = append(output, item.Name)
}
rows.Close()
return output
}
func (p *InventoryPlugin) exists(i string) bool {
var output int
err := p.QueryRow(`select count(*) as count from inventory where item=?`, i).Scan(&output)
count, err := p.Store.Count(Item{}, bh.Where("item").Eq(i))
if err != nil {
log.Error().Err(err).Msg("Error checking for item")
return false
}
return output > 0
return count > 0
}
func (p *InventoryPlugin) remove(i string) {
_, err := p.Exec(`delete from inventory where item=?`, i)
err := p.Store.Delete(i, Item{})
if err != nil {
log.Error().Msg("Error inserting new inventory item")
}
@ -188,7 +179,7 @@ func (p *InventoryPlugin) addItem(c bot.Connector, m msg.Message, i string) bool
if p.count() > max {
removed = p.removeRandom()
}
_, err := p.Exec(`INSERT INTO inventory (item) values (?)`, i)
err := p.Store.Insert(i, Item{Name: i})
if err != nil {
log.Error().Err(err).Msg("Error inserting new inventory item")
}

View File

@ -2,12 +2,12 @@ package last
import (
"fmt"
bh "github.com/timshannon/bolthold"
"regexp"
"time"
"github.com/velour/catbase/config"
"github.com/jmoiron/sqlx"
"github.com/rs/zerolog/log"
"github.com/velour/catbase/bot"
@ -15,9 +15,9 @@ import (
)
type LastPlugin struct {
b bot.Bot
db *sqlx.DB
c *config.Config
b bot.Bot
store *bh.Store
c *config.Config
handlers bot.HandlerTable
channels map[string]bool
@ -26,37 +26,14 @@ type LastPlugin struct {
func New(b bot.Bot) *LastPlugin {
p := &LastPlugin{
b: b,
db: b.DB(),
store: b.Store(),
c: b.Config(),
channels: map[string]bool{},
}
if err := p.migrate(); err != nil {
panic(err)
}
p.register()
return p
}
func (p *LastPlugin) migrate() error {
tx, err := p.db.Beginx()
if err != nil {
return err
}
_, err = tx.Exec(`create table if not exists last (
day integer,
channel string not null,
ts int not null,
who string not null,
message string not null,
constraint last_key primary key (day, channel) on conflict replace
)`)
if err != nil {
tx.Rollback()
return err
}
return tx.Commit()
}
func (p *LastPlugin) register() {
p.handlers = bot.HandlerTable{
{
@ -133,13 +110,16 @@ func (p *LastPlugin) recordLast(r bot.Request) bool {
}
}
_, err := p.db.Exec(
`insert into last values (?, ?, ?, ?, ?)`,
day.Unix(), ch, time.Now().Unix(), who, r.Msg.Body)
if err != nil {
log.Error().Err(err).Msgf("Could not record last.")
l := &last{
Day: day.Unix(),
TS: time.Now().Unix(),
Channel: ch,
Who: who,
Message: r.Msg.Body,
}
return false
// todo: I think last might depend on a key being something more real
return p.store.Insert(bh.NextSequence(), l) == nil
}
type last struct {
@ -155,9 +135,9 @@ func (p *LastPlugin) yesterdaysLast(ch string) (last, error) {
midnight := first.Midnight(time.Now())
q := `select * from last where channel = ? and day < ? and day >= ? order by day limit 1`
log.Debug().Str("q", q).Msgf("yesterdaysLast: %d to %d", midnight.Unix(), midnight.Add(-24*time.Hour).Unix())
err := p.db.Get(&l, q, ch, midnight.Unix(), midnight.Add(-24*time.Hour).Unix())
err := p.store.FindOne(&l, bh.Where("channel").Eq(ch).And("day").Lt(midnight.Unix()).And("day").Ge(midnight.Add(-24*time.Hour).Unix()))
if err != nil {
return l, err
return last{}, err
}
return l, nil
}

View File

@ -2,6 +2,7 @@ package newsbid
import (
"fmt"
bh "github.com/timshannon/bolthold"
"regexp"
"sort"
"strconv"
@ -14,22 +15,23 @@ import (
)
type NewsBid struct {
bot bot.Bot
db *sqlx.DB
ws *webshit.Webshit
bot bot.Bot
db *sqlx.DB
store *bh.Store
ws *webshit.Webshit
}
func New(b bot.Bot) *NewsBid {
ws := webshit.NewConfig(b.DB(), webshit.Config{
ws := webshit.NewConfig(b.Store(), webshit.Config{
HNFeed: b.Config().GetString("webshit.hnfeed", "topstories"),
HNLimit: b.Config().GetInt("webshit.hnlimit", 10),
BalanceReferesh: b.Config().GetInt("webshit.balancerefresh", 100),
HouseName: b.Config().GetString("webshit.housename", "house"),
})
p := &NewsBid{
bot: b,
db: b.DB(),
ws: ws,
bot: b,
store: b.Store(),
ws: ws,
}
p.bot.RegisterRegexCmd(p, bot.Message, balanceRegex, p.balanceCmd)

View File

@ -3,6 +3,7 @@ package webshit
import (
"bytes"
"fmt"
bh "github.com/timshannon/bolthold"
"net/url"
"strconv"
"time"
@ -10,7 +11,6 @@ import (
"github.com/velour/catbase/plugins/newsbid/webshit/hn"
"github.com/PuerkitoBio/goquery"
"github.com/jmoiron/sqlx"
"github.com/mmcdole/gofeed"
"github.com/rs/zerolog/log"
)
@ -23,12 +23,12 @@ type Config struct {
}
type Webshit struct {
db *sqlx.DB
store *bh.Store
config Config
}
type Bid struct {
ID int
ID int64 `boltholdid:"ID"`
User string
Title string
URL string
@ -37,16 +37,12 @@ type Bid struct {
BidStr string
PlacedScore int `db:"placed_score"`
ProcessedScore int `db:"processed_score"`
Placed int64
Processed int64
}
func (b Bid) PlacedParsed() time.Time {
return time.Unix(b.Placed, 0)
Placed time.Time
Processed bool
}
type Balance struct {
User string
User string `boltholdid:"User"`
Balance int
Score int
}
@ -65,34 +61,11 @@ type WeeklyResult struct {
Score int
}
func NewConfig(db *sqlx.DB, cfg Config) *Webshit {
w := &Webshit{db: db, config: cfg}
w.setup()
func NewConfig(store *bh.Store, cfg Config) *Webshit {
w := &Webshit{store: store, config: cfg}
return w
}
// setup will create any necessary SQL tables and populate them with minimal data
func (w *Webshit) setup() {
w.db.MustExec(`create table if not exists webshit_bids (
id integer primary key autoincrement,
user string,
title string,
url string,
hnid string,
bid integer,
bidstr string,
placed_score integer,
processed_score integer,
placed integer,
processed integer
)`)
w.db.MustExec(`create table if not exists webshit_balances (
user string primary key,
balance int,
score int
)`)
}
func (w *Webshit) Check(last int64) ([]WeeklyResult, int64, error) {
stories, published, err := w.GetWeekly()
if err != nil {
@ -105,7 +78,7 @@ func (w *Webshit) Check(last int64) ([]WeeklyResult, int64, error) {
}
var bids []Bid
if err = w.db.Select(&bids, `select user,title,url,hnid,bid,bidstr from webshit_bids where processed=0`); err != nil {
if err = w.store.Find(&bids, bh.Where("processed").Eq(false)); err != nil {
return nil, 0, err
}
@ -133,14 +106,20 @@ func (w *Webshit) Check(last int64) ([]WeeklyResult, int64, error) {
}
// Delete all those bids
if _, err = w.db.Exec(`update webshit_bids set processed=? where processed=0`,
time.Now().Unix()); err != nil {
if err = w.store.UpdateMatching(Bid{}, bh.Where("processed").Eq(false), func(record interface{}) error {
r := record.(*Bid)
r.Processed = true
return w.store.Update(r.ID, r)
}); err != nil {
return nil, 0, err
}
// Set all balances to 100
if _, err = w.db.Exec(`update webshit_balances set balance=?`,
w.config.BalanceReferesh); err != nil {
if err = w.store.UpdateMatching(Balance{}, &bh.Query{}, func(record interface{}) error {
r := record.(*Balance)
r.Balance = w.config.BalanceReferesh
return w.store.Update(r.User, r)
}); err != nil {
return nil, 0, err
}
@ -156,7 +135,7 @@ func (w *Webshit) checkBids(bids []Bid, storyMap map[string]hn.Item) []WeeklyRes
houseScore := 0
for _, b := range bids {
score := w.GetScore(b.User)
score := w.GetBalance(b.User).Score
if _, ok := wr[b.User]; !ok {
wr[b.User] = WeeklyResult{
User: b.User,
@ -193,7 +172,7 @@ func (w *Webshit) checkBids(bids []Bid, storyMap map[string]hn.Item) []WeeklyRes
wr[houseName] = WeeklyResult{
User: houseName,
Score: w.GetScore(houseName) + houseScore,
Score: w.GetBalance(houseName).Score + houseScore,
Won: houseScore,
}
@ -240,29 +219,22 @@ func (w *Webshit) GetWeekly() (hn.Items, *time.Time, error) {
// GetBalances returns the current balance for all known users
// Any unknown user has a default balance on their first bid
func (w *Webshit) GetBalance(user string) int {
q := `select balance from webshit_balances where user=?`
var balance int
err := w.db.Get(&balance, q, user)
func (w *Webshit) GetBalance(user string) Balance {
var balance Balance
err := w.store.Get(user, &balance)
if err != nil {
return 100
return Balance{
User: user,
Balance: 100,
Score: 0,
}
}
return balance
}
func (w *Webshit) GetScore(user string) int {
q := `select score from webshit_balances where user=?`
var score int
err := w.db.Get(&score, q, user)
if err != nil {
return 0
}
return score
}
func (w *Webshit) GetAllBids() ([]Bid, error) {
var bids []Bid
err := w.db.Select(&bids, `select * from webshit_bids where processed=0`)
err := w.store.Find(&bids, bh.Where("processed").Eq(false))
if err != nil {
return nil, err
}
@ -271,7 +243,7 @@ func (w *Webshit) GetAllBids() ([]Bid, error) {
func (w *Webshit) GetAllBalances() (Balances, error) {
var balances Balances
err := w.db.Select(&balances, `select * from webshit_balances`)
err := w.store.Find(&balances, &bh.Query{})
if err != nil {
return nil, err
}
@ -280,7 +252,7 @@ func (w *Webshit) GetAllBalances() (Balances, error) {
// Bid allows a user to place a bid on a particular story
func (w *Webshit) Bid(user string, amount int, bidStr, URL string) (Bid, error) {
bal := w.GetBalance(user)
bal := w.GetBalance(user).Balance
if amount < 0 {
return Bid{}, fmt.Errorf("cannot bid less than 0")
}
@ -292,32 +264,24 @@ func (w *Webshit) Bid(user string, amount int, bidStr, URL string) (Bid, error)
return Bid{}, err
}
ts := time.Now().Unix()
tx := w.db.MustBegin()
_, err = tx.Exec(`insert into webshit_bids (user,title,url,hnid,bid,bidstr,placed,processed,placed_score,processed_score) values (?,?,?,?,?,?,?,0,?,0)`,
user, story.Title, story.URL, story.ID, amount, bidStr, ts, story.Score)
if err != nil {
if err := tx.Rollback(); err != nil {
return Bid{}, err
}
return Bid{}, err
}
q := `insert into webshit_balances (user,balance,score) values (?,?,0)
on conflict(user) do update set balance=?`
_, err = tx.Exec(q, user, bal-amount, bal-amount)
if err != nil {
tx.Rollback()
return Bid{}, err
}
err = tx.Commit()
return Bid{
bid := Bid{
User: user,
Title: story.Title,
URL: story.URL,
Placed: ts,
}, err
Placed: time.Now(),
}
err = w.store.Insert(Bid{}, bid)
if err != nil {
return Bid{}, err
}
err = w.store.Upsert(user, bal)
if err != nil {
return Bid{}, err
}
return bid, nil
}
// getStoryByURL scrapes the URL for a title
@ -337,18 +301,14 @@ func (w *Webshit) getStoryByURL(URL string) (hn.Item, error) {
}
func (w *Webshit) updateScores(results []WeeklyResult) error {
tx := w.db.MustBegin()
for _, res := range results {
if _, err := tx.Exec(`insert into webshit_balances (user, balance, score) values (?, 0, ?) on conflict(user) do update set score=excluded.score`,
res.User, res.Score); err != nil {
if err := tx.Rollback(); err != nil {
return err
}
bal := w.GetBalance(res.User)
bal.Score = res.Score
if err := w.store.Insert(res.User, bal); err != nil {
return err
}
}
err := tx.Commit()
return err
return nil
}
func wrMapToSlice(wr map[string]WeeklyResult) []WeeklyResult {

View File

@ -28,7 +28,6 @@ func New(b bot.Bot) *QuoteGame {
p := &QuoteGame{
b: b,
c: b.Config(),
db: b.DB(),
currentGame: nil,
}
p.register()

View File

@ -2,29 +2,30 @@ package remember
import (
"fmt"
bh "github.com/timshannon/bolthold"
"math/rand"
"regexp"
"strings"
"time"
"github.com/rs/zerolog/log"
"github.com/jmoiron/sqlx"
"github.com/velour/catbase/bot"
"github.com/velour/catbase/bot/msg"
"github.com/velour/catbase/plugins/fact"
)
type RememberPlugin struct {
bot bot.Bot
log map[string][]msg.Message
db *sqlx.DB
bot bot.Bot
log map[string][]msg.Message
store *bh.Store
}
func New(b bot.Bot) *RememberPlugin {
p := &RememberPlugin{
bot: b,
log: make(map[string][]msg.Message),
db: b.DB(),
bot: b,
log: make(map[string][]msg.Message),
store: b.Store(),
}
b.RegisterRegex(p, bot.Message, rememberRegex, p.rememberCmd)
@ -78,7 +79,7 @@ func (p *RememberPlugin) rememberCmd(r bot.Request) bool {
Accessed: time.Now(),
Count: 0,
}
if err := fact.Save(p.db); err != nil {
if err := fact.Save(p.store); err != nil {
log.Error().Err(err)
p.bot.Send(r.Conn, bot.Message, r.Msg.Channel, "Tell somebody I'm broke.")
}
@ -109,32 +110,32 @@ func (p *RememberPlugin) help(c bot.Connector, kind bot.Kind, message msg.Messag
return true
}
// deliver a random quote out of the db.
func (p *RememberPlugin) randQuote() string {
return RandQuote(p.store)
}
// AllQuotesFrom delivers quotes out of the db from who.
func AllQuotesFrom(store *bh.Store, who string) []fact.Factoid {
allQuotes := []fact.Factoid{}
err := store.Find(&allQuotes, bh.Where("fact").Eq(who+" quotes"))
if err != nil {
log.Error().Err(err).Msg("Error getting quotes")
return []fact.Factoid{}
}
return allQuotes
}
// RandQuote delivers a random quote out of the db.
// Note: this is the same cache for all channels joined. This plugin needs to be
// expanded to have this function execute a quote for a particular channel
func (p *RememberPlugin) randQuote() string {
var f fact.Factoid
var tmpCreated int64
var tmpAccessed int64
err := p.db.QueryRow(`select * from factoid where fact like '%quotes'
order by random() limit 1;`).Scan(
&f.ID,
&f.Fact,
&f.Tidbit,
&f.Verb,
&f.Owner,
&tmpCreated,
&tmpAccessed,
&f.Count,
)
func RandQuote(store *bh.Store) string {
allQuotes := []fact.Factoid{}
err := store.Find(&allQuotes, bh.Where("fact").RegExp(regexp.MustCompile(`.+ quotes$`)))
if err != nil {
log.Error().Err(err).Msg("Error getting quotes")
return "I had a problem getting your quote."
}
f.Created = time.Unix(tmpCreated, 0)
f.Accessed = time.Unix(tmpAccessed, 0)
f := allQuotes[rand.Intn(len(allQuotes))]
return f.Tidbit
}

View File

@ -3,23 +3,23 @@
package reminder
import (
"errors"
"fmt"
"strconv"
"strings"
"sync"
"time"
"github.com/jmoiron/sqlx"
"github.com/rs/zerolog/log"
"github.com/olebedev/when"
"github.com/olebedev/when/rules/common"
"github.com/olebedev/when/rules/en"
bh "github.com/timshannon/bolthold"
"github.com/velour/catbase/bot"
"github.com/velour/catbase/bot/msg"
"github.com/velour/catbase/config"
"sync"
"time"
)
import (
"fmt"
"github.com/rs/zerolog/log"
"strconv"
"strings"
"github.com/velour/catbase/plugins/sms"
)
@ -29,7 +29,7 @@ const (
type ReminderPlugin struct {
bot bot.Bot
db *sqlx.DB
store *bh.Store
mutex *sync.Mutex
timer *time.Timer
config *config.Config
@ -46,17 +46,6 @@ type Reminder struct {
}
func New(b bot.Bot) *ReminderPlugin {
if _, err := b.DB().Exec(`create table if not exists reminders (
id integer primary key,
fromWho string,
toWho string,
what string,
remindWhen string,
channel string
);`); err != nil {
log.Fatal().Err(err)
}
dur, _ := time.ParseDuration("1h")
timer := time.NewTimer(dur)
timer.Stop()
@ -67,7 +56,7 @@ func New(b bot.Bot) *ReminderPlugin {
plugin := &ReminderPlugin{
bot: b,
db: b.DB(),
store: b.Store(),
mutex: &sync.Mutex{},
timer: timer,
config: b.Config(),
@ -225,45 +214,22 @@ func (p *ReminderPlugin) help(c bot.Connector, kind bot.Kind, message msg.Messag
func (p *ReminderPlugin) getNextReminder() *Reminder {
p.mutex.Lock()
defer p.mutex.Unlock()
rows, err := p.db.Query("select id, fromWho, toWho, what, remindWhen, channel from reminders order by remindWhen asc limit 1;")
reminder := Reminder{}
res, err := p.store.FindAggregate(Reminder{}, &bh.Query{}, "")
if err != nil {
log.Error().Err(err)
return nil
}
defer rows.Close()
res[0].Max("remindWhen", &reminder)
once := false
var reminder *Reminder
for rows.Next() {
if once {
log.Debug().Msg("somehow got multiple rows")
}
reminder = &Reminder{}
var when string
err := rows.Scan(&reminder.id, &reminder.from, &reminder.who, &reminder.what, &when, &reminder.channel)
if err != nil {
log.Error().Err(err)
return nil
}
reminder.when, err = time.Parse(TIMESTAMP, when)
if err != nil {
log.Error().Err(err)
return nil
}
once = true
}
return reminder
return &reminder
}
func (p *ReminderPlugin) addReminder(reminder *Reminder) error {
p.mutex.Lock()
defer p.mutex.Unlock()
_, err := p.db.Exec(`insert into reminders (fromWho, toWho, what, remindWhen, channel) values (?, ?, ?, ?, ?);`,
reminder.from, reminder.who, reminder.what, reminder.when.Format(TIMESTAMP), reminder.channel)
err := p.store.Insert(bh.NextSequence(), reminder)
if err != nil {
log.Error().Err(err)
}
@ -273,29 +239,21 @@ func (p *ReminderPlugin) addReminder(reminder *Reminder) error {
func (p *ReminderPlugin) deleteReminder(id int64) error {
p.mutex.Lock()
defer p.mutex.Unlock()
res, err := p.db.Exec(`delete from reminders where id = ?;`, id)
if err != nil {
log.Error().Err(err)
} else {
if affected, err := res.RowsAffected(); err != nil {
return err
} else if affected != 1 {
return errors.New("didn't delete any rows")
}
}
err := p.store.Delete(id, Reminder{})
return err
}
func (p *ReminderPlugin) getRemindersFormatted(filter string) (string, error) {
func (p *ReminderPlugin) getRemindersFormatted(filter, who string) (string, error) {
max := p.config.GetInt("Reminder.MaxList", 25)
queryString := fmt.Sprintf("select id, fromWho, toWho, what, remindWhen from reminders %s order by remindWhen asc limit %d;", filter, max)
countString := fmt.Sprintf("select COUNT(*) from reminders %s;", filter)
p.mutex.Lock()
defer p.mutex.Unlock()
var total int
err := p.db.Get(&total, countString)
q := bh.Where(filter).Eq(who)
if filter == "" || who == "" {
q = &bh.Query{}
}
total, err := p.store.Count(Reminder{}, q)
if err != nil {
log.Error().Err(err)
return "", nil
@ -305,43 +263,36 @@ func (p *ReminderPlugin) getRemindersFormatted(filter string) (string, error) {
return "no pending reminders", nil
}
rows, err := p.db.Query(queryString)
reminders := []Reminder{}
err = p.store.Find(&reminders, q.SortBy("id").Limit(max))
if err != nil {
log.Error().Err(err)
return "", nil
}
defer rows.Close()
reminders := ""
counter := 1
reminder := &Reminder{}
for rows.Next() {
var when string
err := rows.Scan(&reminder.id, &reminder.from, &reminder.who, &reminder.what, &when)
if err != nil {
return "", err
}
reminders += fmt.Sprintf("%d) %s -> %s :: %s @ %s (%d)\n", counter, reminder.from, reminder.who, reminder.what, when, reminder.id)
txt := ""
for counter, reminder := range reminders {
txt += fmt.Sprintf("%d) %s -> %s :: %s @ %s (%d)\n", counter, reminder.from, reminder.who, reminder.what, reminder.when, reminder.id)
counter++
}
remaining := total - max
if remaining > 0 {
reminders += fmt.Sprintf("...%d more...\n", remaining)
txt += fmt.Sprintf("...%d more...\n", remaining)
}
return reminders, nil
return txt, nil
}
func (p *ReminderPlugin) getAllRemindersFormatted(channel string) (string, error) {
return p.getRemindersFormatted("")
return p.getRemindersFormatted("", "")
}
func (p *ReminderPlugin) getAllRemindersFromMeFormatted(channel, me string) (string, error) {
return p.getRemindersFormatted(fmt.Sprintf("where fromWho = '%s'", me))
return p.getRemindersFormatted("fromWho", me)
}
func (p *ReminderPlugin) getAllRemindersToMeFormatted(channel, me string) (string, error) {
return p.getRemindersFormatted(fmt.Sprintf("where toWho = '%s'", me))
return p.getRemindersFormatted("toWho", me)
}
func (p *ReminderPlugin) queueUpNextReminder() {

View File

@ -3,10 +3,10 @@ package rest
import (
"bytes"
"crypto/sha512"
"database/sql"
"encoding/json"
"errors"
"fmt"
bh "github.com/timshannon/bolthold"
"io/ioutil"
"net/http"
"net/url"
@ -18,13 +18,12 @@ import (
"github.com/itchyny/gojq"
"github.com/rs/zerolog/log"
"github.com/jmoiron/sqlx"
"github.com/velour/catbase/bot"
)
type RestPlugin struct {
b bot.Bot
db *sqlx.DB
b bot.Bot
store *bh.Store
handlers bot.HandlerTable
}
@ -50,29 +49,13 @@ var postProcessors = map[string]postProcessor{
func New(b bot.Bot) *RestPlugin {
p := &RestPlugin{
b: b,
db: b.DB(),
store: b.Store(),
handlers: bot.HandlerTable{},
}
p.setupDB()
p.register()
return p
}
func (p *RestPlugin) setupDB() {
tx := p.db.MustBegin()
tx.MustExec(`
create table if not exists wires (
id integer primary key autoincrement,
url text not null,
parse_regex text not null,
return_field text not null,
body text not null
)`)
if err := tx.Commit(); err != nil {
panic(err)
}
}
func (p *RestPlugin) register() {
p.handlers = bot.HandlerTable{
bot.HandlerSpec{Kind: bot.Message, IsCmd: true,
@ -161,7 +144,7 @@ func (s *ScanableURL) Scan(src interface{}) error {
type wire struct {
// ID
ID sql.NullInt64
ID int64 `boltholdIndex:"ID"`
// The URL to make a request to
URL ScanableURL
// The regex which will trigger this REST action
@ -182,40 +165,28 @@ func (w wire) String() string {
func (p *RestPlugin) getWires() ([]wire, error) {
wires := []wire{}
err := p.db.Select(&wires, `select * from wires`)
err := p.store.Find(&wires, &bh.Query{})
return wires, err
}
func (p *RestPlugin) deleteWire(id int64) error {
_, err := p.db.Exec(`delete from wires where id=?`, id)
err := p.store.Delete(id, wire{})
return err
}
func (w *wire) Update(db *sqlx.DB) error {
if !w.ID.Valid {
return w.Save(db)
func (w *wire) Update(store *bh.Store) error {
if w.ID == -1 {
return w.Save(store)
}
id, _ := w.ID.Value()
_, err := db.Exec(`update wires set url=?, parse_regex=?, return_field=? where id=?`,
w.URL.String(), w.ParseRegex.String(), w.ReturnField, id)
err := store.Update(w.ID, w)
return err
}
func (w *wire) Save(db *sqlx.DB) error {
if w.ID.Valid {
return w.Update(db)
func (w *wire) Save(store *bh.Store) error {
if w.ID > -1 {
return w.Update(store)
}
res, err := db.Exec(`insert into wires (url, parse_regex, return_field) values (?, ?, ?)`,
w.URL.String(), w.ParseRegex.String(), w.ReturnField)
if err != nil {
return err
}
id, err := res.LastInsertId()
if err != nil {
return err
}
_ = w.ID.Scan(id)
return nil
return store.Insert(bh.NextSequence(), &w)
}
func (p *RestPlugin) listWires(r bot.Request) bool {
@ -227,8 +198,7 @@ func (p *RestPlugin) listWires(r bot.Request) bool {
}
msg = "Current wires:"
for _, w := range wires {
id, _ := w.ID.Value()
msg += fmt.Sprintf("\n\t%d: `%s` => %s", id, w.ParseRegex, w.URL)
msg += fmt.Sprintf("\n\t%d: `%s` => %s", w.ID, w.ParseRegex, w.URL)
}
SEND:
p.b.Send(r.Conn, bot.Message, r.Msg.Channel, msg)
@ -277,7 +247,7 @@ func (p *RestPlugin) handleWire(r bot.Request) bool {
msg = err.Error()
goto SEND
}
err = w.Save(p.db)
err = w.Save(p.store)
if err != nil {
msg = err.Error()
goto SEND

View File

@ -1,59 +1,55 @@
package roles
import (
"github.com/jmoiron/sqlx"
"github.com/rs/zerolog/log"
bh "github.com/timshannon/bolthold"
)
type Role struct {
ID int64 `json:"id"`
ID int64 `json:"id" boltholdIndex:"ID"`
Name string `json:"name"`
Offering string `json:"offering"`
*sqlx.DB
*bh.Store
}
func NewRole(db *sqlx.DB, name, offering string) *Role {
func NewRole(store *bh.Store, name, offering string) *Role {
return &Role{
Name: name,
Offering: offering,
DB: db,
Store: store,
}
}
func (r *Role) Save() error {
q := `insert or replace into roles (name, offering) values (?, ?)`
res, err := r.Exec(q, r.Name, r.Offering)
if checkErr(err) {
return err
if r.ID == -1 {
r.Store.Upsert(bh.NextSequence(), r)
} else {
r.Store.Upsert(r.ID, r)
}
id, err := res.LastInsertId()
if checkErr(err) {
return err
}
r.ID = id
return nil
}
func getRolesByOffering(db *sqlx.DB, offering string) ([]*Role, error) {
func getRolesByOffering(store *bh.Store, offering string) ([]*Role, error) {
roles := []*Role{}
err := db.Select(&roles, `select * from roles where offering=?`, offering)
err := store.Find(&roles, bh.Where("offering").Eq(offering))
if checkErr(err) {
return nil, err
}
for _, r := range roles {
r.DB = db
r.Store = store
r.Store = store
}
return roles, nil
}
func getRole(db *sqlx.DB, name, offering string) (*Role, error) {
func getRole(store *bh.Store, name, offering string) (*Role, error) {
role := &Role{}
err := db.Get(role, `select * from roles where role=? and offering=?`, role, offering)
err := store.Find(&role, bh.Where("role").Eq(role).And("offering").Eq(offering))
if checkErr(err) {
return nil, err
}
role.DB = db
role.Store = store
return role, nil
}

View File

@ -2,27 +2,27 @@ package roles
import (
"fmt"
bh "github.com/timshannon/bolthold"
"regexp"
"strings"
"github.com/jmoiron/sqlx"
"github.com/rs/zerolog/log"
"github.com/velour/catbase/bot"
"github.com/velour/catbase/config"
)
type RolesPlugin struct {
b bot.Bot
c *config.Config
db *sqlx.DB
h bot.HandlerTable
b bot.Bot
c *config.Config
store *bh.Store
h bot.HandlerTable
}
func New(b bot.Bot) *RolesPlugin {
p := &RolesPlugin{
b: b,
c: b.Config(),
db: b.DB(),
b: b,
c: b.Config(),
store: b.Store(),
}
p.RegisterWeb()
p.Register()

View File

@ -4,10 +4,10 @@ import (
"embed"
"encoding/json"
"fmt"
bh "github.com/timshannon/bolthold"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/jmoiron/sqlx"
"github.com/rs/zerolog/log"
"github.com/velour/catbase/bot"
"github.com/velour/catbase/config"
@ -17,16 +17,16 @@ import (
var embeddedFS embed.FS
type SecretsPlugin struct {
b bot.Bot
c *config.Config
db *sqlx.DB
b bot.Bot
c *config.Config
store *bh.Store
}
func New(b bot.Bot) *SecretsPlugin {
p := &SecretsPlugin{
b: b,
c: b.Config(),
db: b.DB(),
b: b,
c: b.Config(),
store: b.Store(),
}
p.registerWeb()
return p
@ -47,8 +47,7 @@ func (p *SecretsPlugin) registerWeb() {
}
func (p *SecretsPlugin) registerSecret(key, value string) error {
q := `insert into secrets (key, value) values (?, ?)`
_, err := p.db.Exec(q, key, value)
err := p.store.Insert(key, config.Secret{key, value})
if err != nil {
return err
}
@ -56,8 +55,7 @@ func (p *SecretsPlugin) registerSecret(key, value string) error {
}
func (p *SecretsPlugin) removeSecret(key string) error {
q := `delete from secrets where key=?`
_, err := p.db.Exec(q, key)
err := p.store.Delete(key, config.Secret{})
if err != nil {
return err
}
@ -65,8 +63,7 @@ func (p *SecretsPlugin) removeSecret(key string) error {
}
func (p *SecretsPlugin) updateSecret(key, value string) error {
q := `update secrets set value=? where key=?`
_, err := p.db.Exec(q, value, key)
err := p.store.Update(key, config.Secret{key, value})
if err != nil {
return err
}

View File

@ -2,6 +2,7 @@ package sms
import (
"fmt"
bh "github.com/timshannon/bolthold"
"net/http"
"regexp"
"strings"
@ -18,8 +19,14 @@ import (
var plugin *SMSPlugin
type SMSPlugin struct {
b bot.Bot
c *config.Config
b bot.Bot
c *config.Config
store *bh.Store
}
type Person struct {
Who string
Num string
}
func New(b bot.Bot) *SMSPlugin {
@ -27,10 +34,10 @@ func New(b bot.Bot) *SMSPlugin {
return plugin
}
plugin = &SMSPlugin{
b: b,
c: b.Config(),
b: b,
c: b.Config(),
store: b.Store(),
}
plugin.setup()
plugin.registerWeb()
b.RegisterRegex(plugin, bot.Message, deleteRegex, plugin.deleteCmd)
@ -155,51 +162,24 @@ func (p *SMSPlugin) receive(w http.ResponseWriter, r *http.Request) {
}
func (p *SMSPlugin) add(who, num string) error {
tx, err := p.b.DB().Beginx()
if err != nil {
return err
}
_, err = tx.Exec(`insert or replace into sms (who, num) values (?, ?)`, who, num)
if err != nil {
return err
}
err = tx.Commit()
return err
person := Person{who, num}
return p.store.Upsert(who, person)
}
func (p *SMSPlugin) delete(who string) error {
tx, err := p.b.DB().Beginx()
if err != nil {
return err
}
_, err = tx.Exec(`delete from sms where who=?`, who)
if err != nil {
return err
}
err = tx.Commit()
return err
return p.store.Delete(who, Person{})
}
func (p *SMSPlugin) get(who string) (string, error) {
num := ""
err := p.b.DB().Get(&num, `select num from sms where who=?`, who)
return num, err
person := Person{}
err := p.store.Get(who, &person)
return person.Num, err
}
func (p *SMSPlugin) getByNumber(num string) (string, error) {
who := ""
err := p.b.DB().Get(&who, `select who from sms where num=?`, num)
return who, err
}
func (p *SMSPlugin) setup() {
_, err := p.b.DB().Exec(`create table if not exists sms (
who string primary key,
num string
)`)
if err != nil {
log.Fatal().Err(err).Msgf("could not create sms table")
}
person := Person{}
err := p.store.Find(&person, bh.Where("num").Eq(num))
return person.Who, err
}
func (p *SMSPlugin) deleteCmd(r bot.Request) bool {

View File

@ -2,12 +2,11 @@ package tell
import (
"fmt"
bh "github.com/timshannon/bolthold"
"strings"
"github.com/rs/zerolog/log"
"github.com/jmoiron/sqlx"
"github.com/velour/catbase/bot"
"github.com/velour/catbase/bot/msg"
)
@ -15,49 +14,37 @@ import (
type delayedMsg string
type TellPlugin struct {
b bot.Bot
db *sqlx.DB
b bot.Bot
store *bh.Store
}
func New(b bot.Bot) *TellPlugin {
tp := &TellPlugin{b, b.DB()}
tp := &TellPlugin{b, b.Store()}
b.Register(tp, bot.Message, tp.message)
tp.createDB()
return tp
}
type tell struct {
ID int
ID int `boltholdIndex:"ID"`
Who string
What string
}
func (t *TellPlugin) createDB() {
q := `create table if not exists tell (
id integer primary key autoincrement,
who string,
what string
)`
t.db.MustExec(q)
}
func (t *TellPlugin) getTells() []tell {
result := []tell{}
q := `select * from tell`
t.db.Select(&result, q)
t.store.Find(&result, &bh.Query{})
return result
}
func (t *TellPlugin) rmTell(entry tell) {
q := `delete from tell where id=?`
if _, err := t.db.Exec(q, entry.ID); err != nil {
if err := t.store.Delete(entry.ID, tell{}); err != nil {
log.Error().Err(err).Msg("could not remove tell")
}
}
func (t *TellPlugin) addTell(who, what string) error {
q := `insert into tell (who, what) values (?, ?)`
_, err := t.db.Exec(q, who, what)
tell := tell{Who: who, What: what}
err := t.store.Insert(bh.NextSequence(), tell)
if err != nil {
log.Error().Err(err).Msg("could not add tell")
}