package achievements import ( "fmt" "regexp" "strings" "time" "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" ) // A plugin to track our misdeeds type AchievementsPlugin struct { bot bot.Bot cfg *config.Config db *sqlx.DB } 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") } b.Register(ap, bot.Message, ap.message) b.Register(ap, bot.Help, ap.help) return ap } 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 { log.Error().Err(err).Msg("could not select awards") } return awards } var grantRegex = regexp.MustCompile(`(?i)grant (?P(?::[[:word:][:punct:]]+:\s?)+) to :?(?P[[:word:]]+):?`) var createRegex = regexp.MustCompile(`(?i)create trophy (?P(?::[[:word:][:punct:]]+:\s?)+) (?P.+)`) var greatRegex = regexp.MustCompile(`(?i)how great (?:am i|is :?(?P[[:word:]]+))[[:punct:]]*`) func (p *AchievementsPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { nick := message.User.Name if greatRegex.MatchString(message.Body) { submatches := greatRegex.FindAllStringSubmatch(message.Body, -1) who := submatches[0][1] if who == "" { who = nick } awards := p.GetAwards(who) if len(awards) == 0 { m := fmt.Sprintf("%s has no achievements to their name. They really suck.", who) if who == nick { m = fmt.Sprintf("You have no achievements to your name. "+ "You are a sad and terrible specimen of the human condition, %s.", who) } p.bot.Send(c, bot.Message, message.Channel, m) } else { m := fmt.Sprintf("Wow, let's all clap for %s. Look at these awards:", who) for _, a := range awards { m += fmt.Sprintf("\n%s - %s", a.Emojy, a.Description) } p.bot.Send(c, bot.Message, message.Channel, m) } return true } if message.Command && grantRegex.MatchString(message.Body) { submatches := grantRegex.FindAllStringSubmatch(message.Body, -1) emojy := submatches[0][1] receiver := submatches[0][2] trophy, err := p.FindTrophy(emojy) if err != nil { log.Error().Err(err).Msg("could not find trophy") msg := fmt.Sprintf("The %s award doesn't exist.", emojy) p.bot.Send(c, bot.Message, message.Channel, msg) return true } if nick == trophy.Creator { a, err := p.Grant(receiver, emojy) if err != nil { log.Error().Err(err).Msg("could not award trophy") } msg := fmt.Sprintf("Congrats %s. You just got the %s award for %s.", receiver, emojy, a.Description) p.bot.Send(c, bot.Message, message.Channel, msg) } else { msg := fmt.Sprintf("Sorry, %s. %s owns that trophy.", nick, trophy.Creator) p.bot.Send(c, bot.Message, message.Channel, msg) } return true } if message.Command && createRegex.MatchString(message.Body) { submatches := createRegex.FindAllStringSubmatch(message.Body, -1) emojy := submatches[0][1] description := submatches[0][2] t, err := p.Create(emojy, description, nick) if err != nil { log.Error().Err(err).Msg("could not create trophy") if strings.Contains(err.Error(), "exists") { p.bot.Send(c, bot.Message, message.Channel, err.Error()) return true } p.bot.Send(c, bot.Message, message.Channel, "I'm too humble to ever award that trophy") return true } resp := fmt.Sprintf("Okay %s. I have crafted a one-of-a-kind %s trophy to give for %s", nick, t.Emojy, t.Description) p.bot.Send(c, bot.Message, message.Channel, resp) return true } return false } func (p *AchievementsPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { ch := message.Channel me := p.bot.WhoAmI() msg := "The achievements plugins awards trophies." msg += fmt.Sprintf("\nYou can create a trophy with `%s, create trophy `", me) msg += fmt.Sprintf("\nYou can award a trophy with `%s, grant to `", me) msg += "\nYou can see others awards with `How great is `" msg += "\nYou can see your awards with `How great am I?`" p.bot.Send(c, bot.Message, ch, msg) return true } // 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() if err != nil { return empty, err } if _, err := tx.Exec(q, emojy, nick); err != nil { tx.Rollback() return empty, err } if err := tx.Commit(); err != nil { return empty, err } return p.FindAward(emojy) } func (p *AchievementsPlugin) Create(emojy, description, creator string) (Trophy, error) { t, err := p.FindTrophy(emojy) if err == nil { 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{ Emojy: emojy, Description: description, Creator: creator, }, err } type Trophy struct { Emojy string Description string Creator string } type Award struct { Trophy ID int64 Holder string Amount int Granted *time.Time } func (a *Award) Save() error { return nil } func (p *AchievementsPlugin) FindTrophy(emojy string) (Trophy, error) { q := `select * from trophies where emojy=?` var t Trophy err := p.db.Get(&t, q, 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 }