Compare commits

..

No commits in common. "8b38429a31a940011810b5df97334ca195daec87" and "a7f05c75fd9f8806874176fe734f048b20f6dfe3" have entirely different histories.

17 changed files with 152 additions and 127 deletions

4
.gitignore vendored
View File

@ -74,7 +74,3 @@ gus.sh
rathaus.sh rathaus.sh
run.sh run.sh
impact.ttf impact.ttf
.env
*.store
rathaus_discord.sh
util/migrate/migrate

View File

@ -67,9 +67,7 @@ func ParseValues(r *regexp.Regexp, body string) RegexValues {
func (b *bot) runCallback(conn Connector, plugin Plugin, evt Kind, message msg.Message, args ...interface{}) bool { func (b *bot) runCallback(conn Connector, plugin Plugin, evt Kind, message msg.Message, args ...interface{}) bool {
t := reflect.TypeOf(plugin).String() t := reflect.TypeOf(plugin).String()
for _, spec := range b.callbacks[t][evt] { for _, spec := range b.callbacks[t][evt] {
log.Debug().Msgf("Trying callback %v with regex %v for msg %v", t, spec.Regex.String(), message.Body)
if spec.Regex.MatchString(message.Body) { if spec.Regex.MatchString(message.Body) {
log.Debug().Msgf("Running callback")
req := Request{ req := Request{
Conn: conn, Conn: conn,
Kind: evt, Kind: evt,

View File

@ -1,7 +1,6 @@
package achievements package achievements
import ( import (
"database/sql"
"fmt" "fmt"
bh "github.com/timshannon/bolthold" bh "github.com/timshannon/bolthold"
"regexp" "regexp"
@ -180,7 +179,7 @@ func (p *AchievementsPlugin) Grant(nick, emojy string) (Award, error) {
Holder: nick, Holder: nick,
Emojy: emojy, Emojy: emojy,
Description: trophy.Description, Description: trophy.Description,
Granted: sql.NullTime{Time: time.Now()}, Granted: time.Now(),
} }
if err = p.store.Insert(bh.NextSequence(), &award); err != nil { if err = p.store.Insert(bh.NextSequence(), &award); err != nil {
return Award{}, err return Award{}, err
@ -206,18 +205,17 @@ func (p *AchievementsPlugin) Create(emojy, description, creator string) (Trophy,
} }
type Trophy struct { type Trophy struct {
Emojy string `boltholderKey:"Emojy"` Emojy string
Description string Description string
Creator string Creator string
} }
type Award struct { type Award struct {
ID uint64 `boltholderKey:"ID"` ID int64 `boltholderid:"ID"`
Holder string
Emojy string Emojy string
Description string Description string
Holder string Granted time.Time
Amount int64
Granted sql.NullTime
} }
func (p *AchievementsPlugin) AllTrophies() ([]Trophy, error) { func (p *AchievementsPlugin) AllTrophies() ([]Trophy, error) {

View File

@ -4,14 +4,18 @@ package fact
import ( import (
"embed" "embed"
"encoding/json"
"fmt" "fmt"
bh "github.com/timshannon/bolthold" bh "github.com/timshannon/bolthold"
"html/template"
"math/rand" "math/rand"
"net/http"
"net/url" "net/url"
"regexp" "regexp"
"strings" "strings"
"time" "time"
"github.com/go-chi/chi/v5"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/velour/catbase/bot" "github.com/velour/catbase/bot"
@ -36,20 +40,16 @@ type Factoid struct {
Count int Count int
} }
type Alias struct { type alias struct {
Fact string Fact string
Next string Next string
} }
func (a Alias) Key() string { func (a *alias) resolve(store *bh.Store) (*Factoid, error) {
return a.Fact + a.Next
}
func (a *Alias) resolve(store *bh.Store) (*Factoid, error) {
// perform db query to fill the To field // perform db query to fill the To field
// todo: remove this query // todo: remove this query
//q := `select fact, next from factoid_alias where fact=?` //q := `select fact, next from factoid_alias where fact=?`
var next Alias var next alias
err := store.FindOne(&next, bh.Where("Fact").Eq(a.Next)) err := store.FindOne(&next, bh.Where("Fact").Eq(a.Next))
if err != nil { if err != nil {
// we hit the end of the chain, get a factoid named Next // we hit the end of the chain, get a factoid named Next
@ -66,7 +66,7 @@ func (a *Alias) resolve(store *bh.Store) (*Factoid, error) {
func findAlias(store *bh.Store, fact string) (bool, *Factoid) { func findAlias(store *bh.Store, fact string) (bool, *Factoid) {
// todo: remove this query // todo: remove this query
//q := `select * from factoid_alias where fact=?` //q := `select * from factoid_alias where fact=?`
var a Alias var a alias
err := store.FindOne(&a, bh.Where("Fact").Eq(fact)) err := store.FindOne(&a, bh.Where("Fact").Eq(fact))
if err != nil { if err != nil {
return false, nil return false, nil
@ -75,9 +75,9 @@ func findAlias(store *bh.Store, fact string) (bool, *Factoid) {
return err == nil, f return err == nil, f
} }
func (a *Alias) save(store *bh.Store) error { func (a *alias) save(store *bh.Store) error {
//q := `select * from factoid_alias where fact=?` //q := `select * from factoid_alias where fact=?`
var offender Alias var offender alias
err := store.FindOne(&offender, bh.Where("Fact").Eq(a.Next)) err := store.FindOne(&offender, bh.Where("Fact").Eq(a.Next))
if err == nil { if err == nil {
return fmt.Errorf("DANGER: an opposite alias already exists") return fmt.Errorf("DANGER: an opposite alias already exists")
@ -95,8 +95,8 @@ func (a *Alias) save(store *bh.Store) error {
return nil return nil
} }
func aliasFromStrings(from, to string) *Alias { func aliasFromStrings(from, to string) *alias {
return &Alias{from, to} return &alias{from, to}
} }
func (f *Factoid) Save(store *bh.Store) error { func (f *Factoid) Save(store *bh.Store) error {
@ -123,7 +123,7 @@ func (f *Factoid) delete(store *bh.Store) error {
func getFacts(store *bh.Store, fact string, tidbit string) ([]*Factoid, error) { func getFacts(store *bh.Store, fact string, tidbit string) ([]*Factoid, error) {
var fs []*Factoid var fs []*Factoid
err := store.Find(&fs, bh.Where("Fact").RegExp(regexp.MustCompile(`.*`+fact+`.*`)).And("Tidbit").RegExp(regexp.MustCompile(`.*`+tidbit+`.*`))) err := store.Find(&fs, bh.Where("Fact").Contains(fact).And("Tidbit").Contains(tidbit))
if err != nil { if err != nil {
log.Error().Err(err).Msg("Error regexping for facts") log.Error().Err(err).Msg("Error regexping for facts")
return nil, err return nil, err
@ -669,3 +669,58 @@ func (p *FactoidPlugin) factTimer(c bot.Connector, channel string) {
} }
} }
} }
// Register any web URLs desired
func (p *FactoidPlugin) registerWeb() {
r := chi.NewRouter()
r.HandleFunc("/api", p.serveAPI)
r.HandleFunc("/req", p.serveQuery)
r.HandleFunc("/", p.serveQuery)
p.Bot.RegisterWebName(r, "/factoid", "Factoid")
}
func linkify(text string) template.HTML {
parts := strings.Fields(text)
for i, word := range parts {
if strings.HasPrefix(word, "http") {
parts[i] = fmt.Sprintf("<a href=\"%s\">%s</a>", word, word)
}
}
return template.HTML(strings.Join(parts, " "))
}
func (p *FactoidPlugin) serveAPI(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
fmt.Fprintf(w, "Incorrect HTTP method")
return
}
info := struct {
Query string `json:"query"`
}{}
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&info)
if err != nil {
w.WriteHeader(500)
fmt.Fprint(w, err)
return
}
entries, err := getFacts(p.store, info.Query, "")
if err != nil {
w.WriteHeader(500)
fmt.Fprint(w, err)
return
}
data, err := json.Marshal(entries)
if err != nil {
w.WriteHeader(500)
fmt.Fprint(w, err)
return
}
w.Write(data)
}
func (p *FactoidPlugin) serveQuery(w http.ResponseWriter, r *http.Request) {
index, _ := embeddedFS.ReadFile("index.html")
w.Write(index)
}

View File

@ -1,68 +0,0 @@
package fact
import (
"encoding/json"
"fmt"
"github.com/go-chi/chi/v5"
"github.com/rs/zerolog/log"
"html/template"
"net/http"
"strings"
)
// Register any web URLs desired
func (p *FactoidPlugin) registerWeb() {
r := chi.NewRouter()
r.HandleFunc("/api", p.serveAPI)
r.HandleFunc("/req", p.serveQuery)
r.HandleFunc("/", p.serveQuery)
p.Bot.RegisterWebName(r, "/factoid", "Factoid")
}
func linkify(text string) template.HTML {
parts := strings.Fields(text)
for i, word := range parts {
if strings.HasPrefix(word, "http") {
parts[i] = fmt.Sprintf("<a href=\"%s\">%s</a>", word, word)
}
}
return template.HTML(strings.Join(parts, " "))
}
func (p *FactoidPlugin) serveAPI(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
fmt.Fprintf(w, "Incorrect HTTP method")
return
}
info := struct {
Query string `json:"query"`
}{}
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&info)
if err != nil {
w.WriteHeader(500)
fmt.Fprint(w, err)
return
}
entries, err := getFacts(p.store, info.Query, "")
if err != nil {
w.WriteHeader(500)
fmt.Fprint(w, err)
return
}
log.Debug().Msgf("api request got %d entries", len(entries))
data, err := json.Marshal(entries)
if err != nil {
w.WriteHeader(500)
fmt.Fprint(w, err)
return
}
w.Write(data)
}
func (p *FactoidPlugin) serveQuery(w http.ResponseWriter, r *http.Request) {
index, _ := embeddedFS.ReadFile("index.html")
w.Write(index)
}

View File

@ -264,11 +264,11 @@ func (p *ReminderPlugin) getRemindersFormatted(filter, who string) (string, erro
if filter == "" || who == "" { if filter == "" || who == "" {
total, _ = p.store.Count(Reminder{}, &bh.Query{}) total, _ = p.store.Count(Reminder{}, &bh.Query{})
err = p.store.Find(&reminders, (&bh.Query{}).SortBy("When").Limit(max)) err = p.store.Find(&reminders, (&bh.Query{}).SortBy("ID").Limit(max))
} else { } else {
log.Debug().Msgf("Looking for reminders where %s eq %s", filter, who) log.Debug().Msgf("Looking for reminders where %s eq %s", filter, who)
total, _ = p.store.Count(Reminder{}, bh.Where(filter).Eq(who)) total, _ = p.store.Count(Reminder{}, bh.Where(filter).Eq(who))
err = p.store.Find(&reminders, bh.Where(filter).Eq(who).SortBy("When").Limit(max)) err = p.store.Find(&reminders, bh.Where(filter).Eq(who).SortBy("ID").Limit(max))
} }
if err != nil { if err != nil {
log.Error().Err(err).Msgf("error finding reminders") log.Error().Err(err).Msgf("error finding reminders")
@ -281,7 +281,7 @@ func (p *ReminderPlugin) getRemindersFormatted(filter, who string) (string, erro
txt := "" txt := ""
for counter, reminder := range reminders { for counter, reminder := range reminders {
txt += fmt.Sprintf("%d) %s -> %s :: %s @ %s\n", reminder.ID, reminder.From, reminder.Who, reminder.What, reminder.When.Format(time.RFC3339)) txt += fmt.Sprintf("%d) %s -> %s :: %s @ %s (%d)\n", reminder.ID, reminder.From, reminder.Who, reminder.What, reminder.When, reminder.ID)
counter++ counter++
} }

View File

@ -212,24 +212,30 @@ func TestCancelMiss(t *testing.T) {
func TestLimitList(t *testing.T) { func TestLimitList(t *testing.T) {
c, mb, td := setup(t) c, mb, td := setup(t)
defer td() defer td()
c.config.Set("Reminder.MaxBatchAdd", "100") c.config.Set("Reminder.MaxBatchAdd", "10")
c.config.Set("Reminder.MaxList", "25") c.config.Set("Reminder.MaxList", "25")
assert.NotNil(t, c) assert.NotNil(t, c)
//Someone can redo this with a single batch add, but I can't locally due to an old version of sqllite (maybe). //Someone can redo this with a single batch add, but I can't locally due to an old version of sqllite (maybe).
res := c.message(makeMessage("!remind testuser every 1h for 30h don't fail this test")) res := c.message(makeMessage("!remind testuser every 1h for 10h don't fail this test"))
assert.True(t, res)
res = c.message(makeMessage("!remind testuser every 1h for 10h don't fail this test"))
assert.True(t, res)
res = c.message(makeMessage("!remind testuser every 1h for 10h don't fail this test"))
assert.True(t, res) assert.True(t, res)
res = c.message(makeMessage("!list reminders")) res = c.message(makeMessage("!list reminders"))
assert.True(t, res) assert.True(t, res)
assert.Len(t, mb.Messages, 2) assert.Len(t, mb.Messages, 4)
assert.Contains(t, mb.Messages[0], "Sure tester, I'll remind testuser.") assert.Contains(t, mb.Messages[0], "Sure tester, I'll remind testuser.")
assert.Contains(t, mb.Messages[1], "Sure tester, I'll remind testuser.")
assert.Contains(t, mb.Messages[2], "Sure tester, I'll remind testuser.")
for i := 0; i < 25; i++ { for i := 0; i < 25; i++ {
assert.Contains(t, mb.Messages[1], fmt.Sprintf("%d) tester -> testuser :: don't fail this test", i+1)) assert.Contains(t, mb.Messages[3], fmt.Sprintf("%d) tester -> testuser :: don't fail this test", i+1))
} }
assert.Contains(t, mb.Messages[1], "more...") assert.Contains(t, mb.Messages[3], "more...")
assert.NotContains(t, mb.Messages[1], "26) tester -> testuser") assert.NotContains(t, mb.Messages[3], "26) tester -> testuser")
} }
func TestHelp(t *testing.T) { func TestHelp(t *testing.T) {

View File

@ -146,7 +146,7 @@ type Wire struct {
// ID // ID
ID int64 `boltholdIndex:"ID"` ID int64 `boltholdIndex:"ID"`
// The URL to make a request to // The URL to make a request to
URL string URL ScanableURL
// The regex which will trigger this REST action // The regex which will trigger this REST action
ParseRegex string `db:"parse_regex"` ParseRegex string `db:"parse_regex"`
regex *regexp.Regexp regex *regexp.Regexp
@ -230,11 +230,10 @@ func (p *RestPlugin) mkWire(r bot.Request) (Wire, error) {
return w, err return w, err
} }
_, err = url.Parse(r.Values["url"]) w.URL.URL, err = url.Parse(r.Values["url"])
if err != nil { if err != nil {
return w, err return w, err
} }
w.URL = r.Values["url"]
w.ReturnField = r.Values["returnField"] w.ReturnField = r.Values["returnField"]
@ -288,7 +287,7 @@ func (p *RestPlugin) mkHandler(w Wire) bot.ResponseHandler {
values[k] = url.QueryEscape(r.Values[k]) values[k] = url.QueryEscape(r.Values[k])
} }
log.Debug().Interface("values", values).Msgf("r.Values") log.Debug().Interface("values", values).Msgf("r.Values")
urlStr := w.URL urlStr := w.URL.String()
parse, err := template.New(urlStr).Parse(urlStr) parse, err := template.New(urlStr).Parse(urlStr)
if p.handleErr(err, r) { if p.handleErr(err, r) {
return true return true

View File

@ -23,7 +23,7 @@ func migrateApppass(db *sqlx.DB, store *bh.Store) error {
return err return err
} }
for _, i := range all { for _, i := range all {
if err := store.Insert(bh.NextSequence(), i); err != nil { if err := store.Insert(i.ID, i); err != nil {
return err return err
} }
} }

View File

@ -1,29 +1,44 @@
package main package main
import ( import (
"database/sql"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
bh "github.com/timshannon/bolthold" bh "github.com/timshannon/bolthold"
"github.com/velour/catbase/plugins/achievements"
goals2 "github.com/velour/catbase/plugins/goals" goals2 "github.com/velour/catbase/plugins/goals"
) )
type Trophy struct {
Emojy string
Description string
Creator string
}
type Award struct {
ID int64 `boltholderid:"ID"`
Holder string
Emojy string
Description string
Granted sql.NullTime
Amount int64
}
func migrateAwards(db *sqlx.DB, store *bh.Store) error { func migrateAwards(db *sqlx.DB, store *bh.Store) error {
awards := []achievements.Award{} awards := []Award{}
log.Printf("Migrating %T", awards) log.Printf("Migrating %T", awards)
err := db.Select(&awards, `select * from awards`) err := db.Select(&awards, `select * from awards`)
if err != nil { if err != nil {
return err return err
} }
for _, a := range awards { for _, a := range awards {
err := store.Insert(bh.NextSequence(), &a) err := store.Insert(a.ID, a)
if err != nil { if err != nil {
return err return err
} }
} }
log.Printf("Migrated %d awards", len(awards)) log.Printf("Migrated %d awards", len(awards))
trophies := []achievements.Trophy{} trophies := []Trophy{}
err = db.Select(&trophies, `select * from trophies`) err = db.Select(&trophies, `select * from trophies`)
for _, t := range trophies { for _, t := range trophies {
err := store.Insert(t.Emojy, t) err := store.Insert(t.Emojy, t)
@ -39,7 +54,7 @@ func migrateAwards(db *sqlx.DB, store *bh.Store) error {
return err return err
} }
for _, i := range goals { for _, i := range goals {
if err := store.Insert(bh.NextSequence(), &i); err != nil { if err := store.Insert(i.ID, i); err != nil {
return err return err
} }
} }

View File

@ -25,7 +25,7 @@ func migrateCounter(db *sqlx.DB, store *bh.Store) error {
if i.UserID.Valid { if i.UserID.Valid {
it.UserID = i.UserID.String it.UserID = i.UserID.String
} }
if err := store.Insert(bh.NextSequence(), &it); err != nil { if err := store.Insert(i.ID, it); err != nil {
return err return err
} }
} }
@ -36,7 +36,7 @@ func migrateCounter(db *sqlx.DB, store *bh.Store) error {
return err return err
} }
for _, i := range all { for _, i := range all {
if err := store.Insert(bh.NextSequence(), &i); err != nil { if err := store.Insert(i.ID, i); err != nil {
return err return err
} }
} }

View File

@ -3,7 +3,6 @@ package main
import ( import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
bh "github.com/timshannon/bolthold" bh "github.com/timshannon/bolthold"
fact2 "github.com/velour/catbase/plugins/fact"
_ "modernc.org/sqlite" _ "modernc.org/sqlite"
"time" "time"
@ -11,12 +10,28 @@ import (
) )
type SQLFactoid struct { type SQLFactoid struct {
fact2.Factoid Factoid
Created int64 Created int64
Accessed int64 Accessed int64
} }
type Factoid struct {
ID uint64 `boltholdKey:"ID"`
Fact string
Tidbit string
Verb string
Owner string
Created time.Time
Accessed time.Time
Count int
}
type Alias struct {
Fact string
Next string
}
func migrateFacts(db *sqlx.DB, store *bh.Store) error { func migrateFacts(db *sqlx.DB, store *bh.Store) error {
q := `select * from factoid` q := `select * from factoid`
allFacts := []SQLFactoid{} allFacts := []SQLFactoid{}
@ -28,7 +43,7 @@ func migrateFacts(db *sqlx.DB, store *bh.Store) error {
f := sqlf.Factoid f := sqlf.Factoid
f.Accessed = time.Unix(sqlf.Accessed, 0) f.Accessed = time.Unix(sqlf.Accessed, 0)
f.Created = time.Unix(sqlf.Created, 0) f.Created = time.Unix(sqlf.Created, 0)
if err := store.Insert(bh.NextSequence(), &f); err != nil { if err := store.Insert(f.ID, f); err != nil {
return err return err
} }
} }
@ -36,14 +51,14 @@ func migrateFacts(db *sqlx.DB, store *bh.Store) error {
log.Printf("Migrated %d facts", len(allFacts)) log.Printf("Migrated %d facts", len(allFacts))
q = `select * from factoid_alias` q = `select * from factoid_alias`
allAliases := []fact2.Alias{} allAliases := []Alias{}
if err := db.Select(&allAliases, q); err != nil { if err := db.Select(&allAliases, q); err != nil {
return err return err
} }
for _, f := range allAliases { for _, f := range allAliases {
if err := store.Insert(f.Key(), &f); err != nil { if err := store.Insert(bh.NextSequence(), &f); err != nil {
return err return err
} }
} }

View File

@ -25,7 +25,7 @@ func migrateFirst(db *sqlx.DB, store *bh.Store) error {
fe := i.FirstEntry fe := i.FirstEntry
fe.Day = time.Unix(i.Day, 0) fe.Day = time.Unix(i.Day, 0)
fe.Time = time.Unix(i.Time, 0) fe.Time = time.Unix(i.Time, 0)
if err := store.Insert(bh.NextSequence(), &fe); err != nil { if err := store.Insert(i.ID, fe); err != nil {
return err return err
} }
} }

View File

@ -23,7 +23,7 @@ func migrateReminders(db *sqlx.DB, store *bh.Store) error {
for _, r := range allReminders { for _, r := range allReminders {
rem := r.Reminder rem := r.Reminder
rem.When, _ = time.Parse("2006-01-02 15:04:05", r.When) rem.When, _ = time.Parse("2006-01-02 15:04:05", r.When)
if err := store.Insert(bh.NextSequence(), &rem); err != nil { if err := store.Insert(r.ID, rem); err != nil {
return err return err
} }
} }

View File

@ -15,7 +15,7 @@ func migrateTell(db *sqlx.DB, store *bh.Store) error {
} }
for _, t := range tells { for _, t := range tells {
if err := store.Insert(bh.NextSequence(), &t); err != nil { if err := store.Insert(t.ID, t); err != nil {
return err return err
} }
} }

View File

@ -25,7 +25,7 @@ func migrateWebshit(db *sqlx.DB, store *bh.Store) error {
} }
for _, b := range balances { for _, b := range balances {
if err := store.Insert(bh.NextSequence(), &b); err != nil { if err := store.Insert(b.User, b); err != nil {
return err return err
} }
} }
@ -39,7 +39,7 @@ func migrateWebshit(db *sqlx.DB, store *bh.Store) error {
bid := b.Bid bid := b.Bid
bid.Placed = time.Unix(b.Placed, 0) bid.Placed = time.Unix(b.Placed, 0)
bid.Processed = time.Unix(b.Processed, 0) bid.Processed = time.Unix(b.Processed, 0)
if err := store.Insert(bh.NextSequence(), &bid); err != nil { if err := store.Insert(b.ID, bid); err != nil {
return err return err
} }
} }

View File

@ -5,16 +5,27 @@ import (
bh "github.com/timshannon/bolthold" bh "github.com/timshannon/bolthold"
"github.com/velour/catbase/plugins/rest" "github.com/velour/catbase/plugins/rest"
"log" "log"
"net/url"
) )
type WireSQL struct {
rest.Wire
// The URL to make a request to
URL string
}
func migrateWires(db *sqlx.DB, store *bh.Store) error { func migrateWires(db *sqlx.DB, store *bh.Store) error {
wires := []rest.Wire{} wires := []WireSQL{}
log.Printf("Migrating %T", wires) log.Printf("Migrating %T", wires)
if err := db.Select(&wires, `select * from wires`); err != nil { if err := db.Select(&wires, `select * from wires`); err != nil {
return err return err
} }
for _, w := range wires { for _, w := range wires {
if err := store.Insert(bh.NextSequence(), &w); err != nil { wire := w.Wire
u, _ := url.Parse(w.URL)
wire.URL = rest.ScanableURL{u}
if err := store.Insert(w.ID, wire); err != nil {
return err return err
} }
} }