mirror of https://github.com/velour/catbase.git
wip
This commit is contained in:
parent
d89569cdc6
commit
fafbd2ce64
|
@ -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
|
||||
|
|
61
bot/bot.go
61
bot/bot.go
|
@ -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
|
||||
}
|
||||
|
||||
// RefreshPluginBlacklist loads data for which plugins are disabled for particular channels
|
||||
func (b *bot) RefreshPluginBlacklist() error {
|
||||
blacklistItems := []struct {
|
||||
type blacklistItem struct {
|
||||
Channel string
|
||||
Name string
|
||||
}{}
|
||||
if err := b.DB().Select(&blacklistItems, `select channel, name from pluginBlacklist`); err != nil {
|
||||
}
|
||||
|
||||
type whitelistItem struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
// RefreshPluginBlacklist loads data for which plugins are disabled for particular channels
|
||||
func (b *bot) RefreshPluginBlacklist() error {
|
||||
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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 }
|
||||
|
|
160
config/config.go
160
config/config.go
|
@ -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 {
|
||||
err := c.store.Delete(key, &Value{})
|
||||
return err
|
||||
}
|
||||
_, err = tx.Exec(q, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
err := c.store.Update(key, Value{key, value})
|
||||
return err
|
||||
}
|
||||
_, err = tx.Exec(q, key, value, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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
6
go.mod
|
@ -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
151
go.sum
|
@ -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=
|
||||
|
|
|
@ -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"
|
||||
|
@ -18,18 +18,14 @@ import (
|
|||
type AchievementsPlugin struct {
|
||||
bot bot.Bot
|
||||
cfg *config.Config
|
||||
db *sqlx.DB
|
||||
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")
|
||||
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
|
||||
ID int64 `boltholderid:"ID"`
|
||||
Holder string
|
||||
Amount int
|
||||
Granted *time.Time
|
||||
}
|
||||
|
||||
func (a *Award) Save() error {
|
||||
return nil
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
@ -22,7 +21,7 @@ import (
|
|||
|
||||
type AdminPlugin struct {
|
||||
bot bot.Bot
|
||||
db *sqlx.DB
|
||||
store *bh.Store
|
||||
cfg *config.Config
|
||||
|
||||
quiet bool
|
||||
|
@ -32,12 +31,10 @@ type AdminPlugin struct {
|
|||
func New(b bot.Bot) *AdminPlugin {
|
||||
p := &AdminPlugin{
|
||||
bot: b,
|
||||
db: b.DB(),
|
||||
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)
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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()
|
||||
b := &Babbler{
|
||||
Name: name,
|
||||
}
|
||||
err := p.store.Insert(bh.NextSequence(), b)
|
||||
if err != nil {
|
||||
log.Error().Err(err)
|
||||
return nil, err
|
||||
}
|
||||
return &Babbler{
|
||||
BabblerId: id,
|
||||
Name: name,
|
||||
}, nil
|
||||
}
|
||||
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)
|
||||
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)
|
||||
err := p.store.Find(&arcs, bh.Where("toNodeId").Eq(toNode.NodeId))
|
||||
if err != nil {
|
||||
log.Error().Err(err)
|
||||
return nil, nil, false, err
|
||||
}
|
||||
arcs = append(arcs, &arc)
|
||||
|
||||
total := int64(0)
|
||||
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)
|
||||
if err != nil {
|
||||
log.Error().Err(err)
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
nodes := []*BabblerNode{}
|
||||
|
||||
for rows.Next() {
|
||||
var node BabblerNode
|
||||
err = rows.StructScan(&node)
|
||||
err = p.store.Find(&nodes, bh.Where("babblerId").Eq(otherBabbler.BabblerId))
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
arcs := []*BabblerArc{}
|
||||
|
||||
for rows.Next() {
|
||||
var arc BabblerArc
|
||||
err = rows.StructScan(&arc)
|
||||
err = p.store.Find(&arcs, bh.Where("fromNodeId").Eq(oldNodeId))
|
||||
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
|
||||
|
|
|
@ -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,8 +31,6 @@ 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 = "🍺"
|
||||
|
@ -40,15 +38,14 @@ const DEFAULT_ITEM = "🍺"
|
|||
type BeersPlugin struct {
|
||||
b bot.Bot
|
||||
c *config.Config
|
||||
db *sqlx.DB
|
||||
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(),
|
||||
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!")
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
@ -17,14 +17,14 @@ var thisYear = time.Now().Year()
|
|||
|
||||
type CountdownPlugin struct {
|
||||
b bot.Bot
|
||||
db *sqlx.DB
|
||||
store *bh.Store
|
||||
c *config.Config
|
||||
}
|
||||
|
||||
func New(b bot.Bot) *CountdownPlugin {
|
||||
p := &CountdownPlugin{
|
||||
b: b,
|
||||
db: b.DB(),
|
||||
store: b.Store(),
|
||||
c: b.Config(),
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
@ -21,12 +20,12 @@ import (
|
|||
|
||||
type CounterPlugin struct {
|
||||
b bot.Bot
|
||||
db *sqlx.DB
|
||||
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
|
||||
alias := &alias{
|
||||
Store: store,
|
||||
Item: aliasName,
|
||||
PointsTo: pointsTo,
|
||||
}
|
||||
var a alias
|
||||
if err := db.Get(&a, `select * from counter_alias where item=?`, aliasName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &a, nil
|
||||
}
|
||||
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,81 +214,48 @@ 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")
|
||||
// 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
|
||||
}
|
||||
|
||||
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(),
|
||||
store: b.Store(),
|
||||
cfg: b.Config(),
|
||||
}
|
||||
|
||||
|
@ -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).
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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,7 +19,7 @@ import (
|
|||
type GoalsPlugin struct {
|
||||
b bot.Bot
|
||||
cfg *config.Config
|
||||
db *sqlx.DB
|
||||
store *bh.Store
|
||||
handlers bot.HandlerTable
|
||||
}
|
||||
|
||||
|
@ -27,30 +27,14 @@ func New(b bot.Bot) *GoalsPlugin {
|
|||
p := &GoalsPlugin{
|
||||
b: b,
|
||||
cfg: b.Config(),
|
||||
db: b.DB(),
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
@ -16,7 +16,7 @@ import (
|
|||
|
||||
type LastPlugin struct {
|
||||
b bot.Bot
|
||||
db *sqlx.DB
|
||||
store *bh.Store
|
||||
c *config.Config
|
||||
|
||||
handlers bot.HandlerTable
|
||||
|
@ -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
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package newsbid
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
bh "github.com/timshannon/bolthold"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
@ -16,11 +17,12 @@ import (
|
|||
type NewsBid struct {
|
||||
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),
|
||||
|
@ -28,7 +30,7 @@ func New(b bot.Bot) *NewsBid {
|
|||
})
|
||||
p := &NewsBid{
|
||||
bot: b,
|
||||
db: b.DB(),
|
||||
store: b.Store(),
|
||||
ws: ws,
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -28,7 +28,6 @@ func New(b bot.Bot) *QuoteGame {
|
|||
p := &QuoteGame{
|
||||
b: b,
|
||||
c: b.Config(),
|
||||
db: b.DB(),
|
||||
currentGame: nil,
|
||||
}
|
||||
p.register()
|
||||
|
|
|
@ -2,13 +2,14 @@ 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"
|
||||
|
@ -17,14 +18,14 @@ import (
|
|||
type RememberPlugin struct {
|
||||
bot bot.Bot
|
||||
log map[string][]msg.Message
|
||||
db *sqlx.DB
|
||||
store *bh.Store
|
||||
}
|
||||
|
||||
func New(b bot.Bot) *RememberPlugin {
|
||||
p := &RememberPlugin{
|
||||
bot: b,
|
||||
log: make(map[string][]msg.Message),
|
||||
db: b.DB(),
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -2,10 +2,10 @@ 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"
|
||||
|
@ -14,7 +14,7 @@ import (
|
|||
type RolesPlugin struct {
|
||||
b bot.Bot
|
||||
c *config.Config
|
||||
db *sqlx.DB
|
||||
store *bh.Store
|
||||
h bot.HandlerTable
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ func New(b bot.Bot) *RolesPlugin {
|
|||
p := &RolesPlugin{
|
||||
b: b,
|
||||
c: b.Config(),
|
||||
db: b.DB(),
|
||||
store: b.Store(),
|
||||
}
|
||||
p.RegisterWeb()
|
||||
p.Register()
|
||||
|
|
|
@ -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"
|
||||
|
@ -19,14 +19,14 @@ var embeddedFS embed.FS
|
|||
type SecretsPlugin struct {
|
||||
b bot.Bot
|
||||
c *config.Config
|
||||
db *sqlx.DB
|
||||
store *bh.Store
|
||||
}
|
||||
|
||||
func New(b bot.Bot) *SecretsPlugin {
|
||||
p := &SecretsPlugin{
|
||||
b: b,
|
||||
c: b.Config(),
|
||||
db: b.DB(),
|
||||
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
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package sms
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
bh "github.com/timshannon/bolthold"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
@ -20,6 +21,12 @@ var plugin *SMSPlugin
|
|||
type SMSPlugin struct {
|
||||
b bot.Bot
|
||||
c *config.Config
|
||||
store *bh.Store
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
Who string
|
||||
Num string
|
||||
}
|
||||
|
||||
func New(b bot.Bot) *SMSPlugin {
|
||||
|
@ -29,8 +36,8 @@ func New(b bot.Bot) *SMSPlugin {
|
|||
plugin = &SMSPlugin{
|
||||
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 {
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
@ -16,48 +15,36 @@ type delayedMsg string
|
|||
|
||||
type TellPlugin struct {
|
||||
b bot.Bot
|
||||
db *sqlx.DB
|
||||
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")
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue