goals working-ish

This commit is contained in:
Chris Sexton 2020-05-25 14:05:21 -04:00 committed by Chris Sexton
parent b13385774e
commit b1f46d6517
3 changed files with 279 additions and 0 deletions

View File

@ -11,6 +11,7 @@ import (
"github.com/velour/catbase/plugins/achievements" "github.com/velour/catbase/plugins/achievements"
"github.com/velour/catbase/plugins/aoc" "github.com/velour/catbase/plugins/aoc"
"github.com/velour/catbase/plugins/counter/goals"
"github.com/velour/catbase/plugins/meme" "github.com/velour/catbase/plugins/meme"
"github.com/velour/catbase/plugins/sms" "github.com/velour/catbase/plugins/sms"
"github.com/velour/catbase/plugins/twitter" "github.com/velour/catbase/plugins/twitter"
@ -121,6 +122,7 @@ func main() {
b.AddPlugin(remember.New(b)) b.AddPlugin(remember.New(b))
b.AddPlugin(your.New(b)) b.AddPlugin(your.New(b))
b.AddPlugin(counter.New(b)) b.AddPlugin(counter.New(b))
b.AddPlugin(goals.New(b))
b.AddPlugin(reminder.New(b)) b.AddPlugin(reminder.New(b))
b.AddPlugin(babbler.New(b)) b.AddPlugin(babbler.New(b))
b.AddPlugin(zork.New(b)) b.AddPlugin(zork.New(b))

View File

@ -14,6 +14,7 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"github.com/velour/catbase/bot" "github.com/velour/catbase/bot"
"github.com/velour/catbase/bot/msg" "github.com/velour/catbase/bot/msg"
) )
@ -197,6 +198,9 @@ func (i *Item) Update(value int) error {
Int("value", value). Int("value", value).
Msg("Updating item") Msg("Updating item")
_, err := i.Exec(`update counter set count = ? where id = ?`, i.Count, i.ID) _, err := i.Exec(`update counter set count = ? where id = ?`, i.Count, i.ID)
if err == nil {
sendUpdate(i.Nick, i.Item, i.Count)
}
return err return err
} }
@ -236,6 +240,7 @@ func New(b bot.Bot) *CounterPlugin {
b.Register(cp, bot.Message, cp.message) b.Register(cp, bot.Message, cp.message)
b.Register(cp, bot.Help, cp.help) b.Register(cp, bot.Help, cp.help)
cp.registerWeb() cp.registerWeb()
return cp return cp
} }
@ -661,3 +666,24 @@ func (p *CounterPlugin) handleCounterAPI(w http.ResponseWriter, r *http.Request)
} }
fmt.Fprint(w, string(data)) fmt.Fprint(w, string(data))
} }
type Update struct {
Who string
What string
Amount int
}
type updateFunc func(Update)
var updateFuncs = []updateFunc{}
func RegisterUpdate(f updateFunc) {
log.Debug().Msgf("registering update func")
updateFuncs = append(updateFuncs, f)
}
func sendUpdate(who, what string, amount int) {
log.Debug().Msgf("sending updates to %d places", len(updateFuncs))
for _, f := range updateFuncs {
f(Update{who, what, amount})
}
}

View File

@ -0,0 +1,251 @@
package goals
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/jmoiron/sqlx"
"github.com/rs/zerolog/log"
"github.com/velour/catbase/bot"
"github.com/velour/catbase/bot/msg"
"github.com/velour/catbase/config"
"github.com/velour/catbase/plugins/counter"
)
type GoalsPlugin struct {
b bot.Bot
cfg *config.Config
db *sqlx.DB
}
func New(b bot.Bot) *GoalsPlugin {
p := &GoalsPlugin{
b: b,
cfg: b.Config(),
db: b.DB(),
}
p.mkDB()
b.Register(p, bot.Message, p.message)
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)
}
}
var registerSelf = regexp.MustCompile(`(?i)^register (?P<type>competition|goal) (?P<what>[[:punct:][:alnum:]]+)\s?(?P<amount>[[:digit:]]+)?`)
var registerOther = regexp.MustCompile(`(?i)^register (?P<type>competition|goal) for (?P<who>[[:punct:][:alnum:]]+) (?P<what>[[:punct:][:alnum:]]+)\s?(?P<amount>[[:digit:]]+)?`)
var deRegisterSelf = regexp.MustCompile(`(?i)^deregister (?P<type>competition|goal) (?P<what>[[:punct:][:alnum:]]+)`)
var deRegisterOther = regexp.MustCompile(`(?i)^deregister (?P<type>competition|goal) for (?P<who>[[:punct:][:alnum:]]+) (?P<what>.*)`)
var checkSelf = regexp.MustCompile(`(?i)^check (?P<type>competition|goal) (?P<what>[[:punct:][:alnum:]]+)`)
var checkOther = regexp.MustCompile(`(?i)^check (?P<type>competition|goal) for (?P<who>[[:punct:][:alnum:]]+) (?P<what>[[:punct:][:alnum:]]+)`)
func (p *GoalsPlugin) message(conn bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
body := strings.TrimSpace(message.Body)
who := message.User.Name
ch := message.Channel
if registerSelf.MatchString(body) {
c := parseCmd(registerSelf, body)
amount, _ := strconv.Atoi(c["amount"])
return p.register(conn, ch, c["type"], c["what"], who, amount)
}
if registerOther.MatchString(body) {
c := parseCmd(registerSelf, body)
amount, _ := strconv.Atoi(c["amount"])
return p.register(conn, ch, c["type"], c["what"], c["who"], amount)
}
if deRegisterSelf.MatchString(body) {
c := parseCmd(deRegisterSelf, body)
return p.deregister(conn, ch, c["type"], c["what"], who)
}
if deRegisterOther.MatchString(body) {
c := parseCmd(deRegisterOther, body)
return p.deregister(conn, ch, c["type"], c["what"], c["who"])
}
if checkSelf.MatchString(body) {
c := parseCmd(checkSelf, body)
return p.check(conn, ch, c["type"], c["what"], who)
}
if checkOther.MatchString(body) {
c := parseCmd(checkOther, body)
return p.check(conn, ch, c["type"], c["what"], c["who"])
}
return false
}
func (p *GoalsPlugin) register(c bot.Connector, ch, kind, what, who string, howMuch int) bool {
p.b.Send(c, bot.Message, ch, fmt.Sprintf("registering %s %s for %s in amount %d", kind, what, who, howMuch))
if kind == "goal" && howMuch == 0 {
p.b.Send(c, bot.Message, ch,
fmt.Sprintf("%s, you need to have a goal amount if you want to have a goal for %s.", who, what))
return true
}
g := p.newGoal(kind, who, what, howMuch)
err := g.Save()
if err != nil {
log.Error().Err(err).Msgf("could not create goal")
p.b.Send(c, bot.Message, ch, "I couldn't create that goal for some reason.")
return true
}
p.b.Send(c, bot.Message, ch, fmt.Sprintf("%s created", kind))
return true
}
func (p *GoalsPlugin) deregister(c bot.Connector, ch, kind, what, who string) bool {
p.b.Send(c, bot.Message, ch, fmt.Sprintf("deregistering %s for %s", what, who))
g, err := p.getGoal(kind, who, what)
if err != nil {
log.Error().Err(err).Msgf("could not find goal to delete")
p.b.Send(c, bot.Message, ch, "I couldn't find that item to deregister.")
return true
}
err = g.Delete()
if err != nil {
log.Error().Err(err).Msgf("could not delete goal")
p.b.Send(c, bot.Message, ch, "I couldn't deregister that.")
return true
}
p.b.Send(c, bot.Message, ch, fmt.Sprintf("%s %s deregistered", kind, what))
return true
}
func (p *GoalsPlugin) check(c bot.Connector, ch, kind, what, who string) bool {
return p.checkGoal(c, ch, what, who)
}
func (p *GoalsPlugin) checkCompetition(c bot.Connector, ch, what, who string) bool {
return true
}
func (p *GoalsPlugin) checkGoal(c bot.Connector, ch, what, who string) bool {
kind := "goal"
p.b.Send(c, bot.Message, ch, fmt.Sprintf("checking %s for %s", what, who))
g, err := p.getGoal(kind, who, what)
if err != nil {
p.b.Send(c, bot.Message, ch, fmt.Sprintf("I couldn't find %s %s", kind, what))
}
item, err := counter.GetItem(p.db, who, what)
if err != nil {
p.b.Send(c, bot.Message, ch, fmt.Sprintf("I couldn't find any %s", what))
return true
}
perc := float64(item.Count) / float64(g.Amount) * 100.0
m := fmt.Sprintf("You have %d out of %d for %s. You're %.2f%% of the way there!",
item.Count, g.Amount, what, perc)
p.b.Send(c, bot.Message, ch, m)
return true
}
func (p *GoalsPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
ch := message.Channel
msg := "Goals can set goals and competition for your counters."
msg += fmt.Sprintf("\nRegister with `%s` for yourself", registerSelf)
msg += fmt.Sprintf("\nRegister with `%s` for other people", registerOther)
msg += fmt.Sprintf("\nDeregister with `%s` for yourself", deRegisterSelf)
msg += fmt.Sprintf("\nDeregister with `%s` for other people", deRegisterOther)
msg += fmt.Sprintf("\nCheck with `%s` for yourself", checkSelf)
msg += fmt.Sprintf("\nCheck with `%s` for other people", checkOther)
p.b.Send(c, bot.Message, ch, msg)
return true
}
type cmd map[string]string
func parseCmd(r *regexp.Regexp, body string) cmd {
out := cmd{}
subs := r.FindStringSubmatch(body)
if len(subs) == 0 {
return out
}
for i, n := range r.SubexpNames() {
out[n] = subs[i]
}
return out
}
type goal struct {
ID int64
Kind string
Who string
What string
Amount int
gp *GoalsPlugin
}
func (p *GoalsPlugin) newGoal(kind, who, what string, amount int) goal {
return goal{
ID: -1,
Kind: kind,
Who: who,
What: what,
Amount: amount,
gp: p,
}
}
func (p *GoalsPlugin) getGoal(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)
if err != nil {
return nil, err
}
return g, nil
}
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)
if err != nil {
return err
}
dbID, err := res.LastInsertId()
if err == nil && dbID != g.ID && g.ID == 0 {
g.ID = dbID
}
return nil
}
func (g goal) Delete() error {
if g.ID == -1 {
return nil
}
_, err := g.gp.db.Exec(`delete from goals where id = ?`, g.ID)
return err
}
func (p *GoalsPlugin) update(u counter.Update) {
log.Debug().Msgf("entered update for %#v", u)
_, err := p.getGoal("goal", u.Who, u.What)
if err != nil {
log.Error().Err(err).Msgf("could not get goal for %#v", u)
return
}
chs := p.cfg.GetArray("channels", []string{})
c := p.b.DefaultConnector()
for _, ch := range chs {
p.checkGoal(c, ch, u.What, u.Who)
}
}