diff --git a/bot/handlers.go b/bot/handlers.go index 2b34fa7..5b740f4 100644 --- a/bot/handlers.go +++ b/bot/handlers.go @@ -131,6 +131,10 @@ func (b *bot) checkHelp(conn Connector, channel string, parts []string) { } } +func msgIsMsg(m msg.Message) bool { + return m.Kind == Message || m.Kind == Action || m.Kind == Reply +} + func (b *bot) LastMessage(channel string) (msg.Message, error) { log := <-b.logOut if len(log) == 0 { @@ -138,7 +142,7 @@ func (b *bot) LastMessage(channel string) (msg.Message, error) { } for i := len(log) - 1; i >= 0; i-- { msg := log[i] - if strings.ToLower(msg.Channel) == strings.ToLower(channel) { + if msgIsMsg(msg) && strings.ToLower(msg.Channel) == strings.ToLower(channel) { return msg, nil } } diff --git a/bot/msg/message.go b/bot/msg/message.go index 2078665..c1b79db 100644 --- a/bot/msg/message.go +++ b/bot/msg/message.go @@ -13,6 +13,7 @@ type Messages []Message type Message struct { ID string + Kind int User *user.User // With Slack, channel is the ID of a channel Channel string diff --git a/connectors/discord/discord.go b/connectors/discord/discord.go index 7f1d757..748212c 100644 --- a/connectors/discord/discord.go +++ b/connectors/discord/discord.go @@ -246,6 +246,7 @@ func (d *Discord) messageReactAdd(s *discordgo.Session, m *discordgo.MessageReac } msg := msg.Message{ ID: m.MessageID, + Kind: bot.Reaction, User: &author, Channel: m.ChannelID, ChannelName: ch.Name, @@ -277,6 +278,7 @@ func (d *Discord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreate msg := msg.Message{ ID: m.ID, + Kind: bot.Message, User: &author, Channel: m.ChannelID, ChannelName: ch.Name, diff --git a/connectors/irc/irc.go b/connectors/irc/irc.go index 824623b..c9cb408 100644 --- a/connectors/irc/irc.go +++ b/connectors/irc/irc.go @@ -253,9 +253,11 @@ func (i *Irc) handleMsg(msg irc.Msg) { fallthrough case irc.RPL_ENDOFWHO: + botMsg.Kind = bot.Event i.event(i, bot.Event, botMsg) case irc.PRIVMSG: + botMsg.Kind = bot.Message i.event(i, bot.Message, botMsg) case irc.QUIT: diff --git a/connectors/slackapp/slackApp.go b/connectors/slackapp/slackApp.go index fe381a4..7d57c07 100644 --- a/connectors/slackapp/slackApp.go +++ b/connectors/slackapp/slackApp.go @@ -469,7 +469,8 @@ func (s *SlackApp) buildMessage(m *slackevents.MessageEvent) msg.Message { tstamp := slackTStoTime(m.TimeStamp) return msg.Message{ - ID: m.TimeStamp, + ID: m.TimeStamp, + Kind: bot.Message, User: &user.User{ ID: m.User, Name: name, diff --git a/plugins/fact/fact.go b/plugins/fact/fact.go new file mode 100644 index 0000000..2b43721 --- /dev/null +++ b/plugins/fact/fact.go @@ -0,0 +1,241 @@ +package fact + +import ( + "database/sql" + "fmt" + "github.com/jmoiron/sqlx" + "github.com/rs/zerolog/log" + "time" +) + +// Factoid stores info about our factoid for lookup and later interaction +type Factoid struct { + ID sql.NullInt64 + Fact string + Tidbit string + Verb string + Owner string + Created time.Time + Accessed time.Time + Count int +} + +type alias struct { + Fact string + Next string +} + +func (a *alias) resolve(db *sqlx.DB) (*Factoid, error) { + // perform db query to fill the To field + q := `select fact, next from factoid_alias where fact=?` + var next alias + err := db.Get(&next, q, a.Next) + if err != nil { + // we hit the end of the chain, get a factoid named Next + fact, err := GetSingleFact(db, 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) +} + +func findAlias(db *sqlx.DB, fact string) (bool, *Factoid) { + q := `select * from factoid_alias where fact=?` + var a alias + err := db.Get(&a, q, fact) + if err != nil { + return false, nil + } + f, err := a.resolve(db) + return err == nil, f +} + +func (a *alias) save(db *sqlx.DB) error { + q := `select * from factoid_alias where fact=?` + var offender alias + err := db.Get(&offender, q, a.Next) + if err == nil { + return fmt.Errorf("DANGER: an opposite alias already exists") + } + _, err = a.resolve(db) + 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) + if err != nil { + return err + } + return nil +} + +func aliasFromStrings(from, to string) *alias { + return &alias{from, to} +} + +func (f *Factoid) Save(db *sqlx.DB) 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) + } 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 + } + return err +} + +func (f *Factoid) delete(db *sqlx.DB) error { + var err error + if f.ID.Valid { + _, err = db.Exec(`delete from factoid where id=?`, f.ID) + } + f.ID.Valid = false + return err +} + +func getFacts(db *sqlx.DB, 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+"%") + 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 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 +} diff --git a/plugins/fact/factoid.go b/plugins/fact/factoid.go index 0437ff4..ee60a45 100644 --- a/plugins/fact/factoid.go +++ b/plugins/fact/factoid.go @@ -4,18 +4,14 @@ package fact import ( "database/sql" - "embed" - "encoding/json" "fmt" - "html/template" + "github.com/velour/catbase/config" "math/rand" - "net/http" "net/url" "regexp" "strings" "time" - "github.com/go-chi/chi/v5" "github.com/rs/zerolog/log" "github.com/jmoiron/sqlx" @@ -27,246 +23,11 @@ import ( // The factoid plugin provides a learning system to the bot so that it can // respond to queries in a way that is unpredictable and fun -//go:embed *.html -var embeddedFS embed.FS - -// Factoid stores info about our factoid for lookup and later interaction -type Factoid struct { - ID sql.NullInt64 - Fact string - Tidbit string - Verb string - Owner string - Created time.Time - Accessed time.Time - Count int -} - -type alias struct { - Fact string - Next string -} - -func (a *alias) resolve(db *sqlx.DB) (*Factoid, error) { - // perform db query to fill the To field - q := `select fact, next from factoid_alias where fact=?` - var next alias - err := db.Get(&next, q, a.Next) - if err != nil { - // we hit the end of the chain, get a factoid named Next - fact, err := GetSingleFact(db, 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) -} - -func findAlias(db *sqlx.DB, fact string) (bool, *Factoid) { - q := `select * from factoid_alias where fact=?` - var a alias - err := db.Get(&a, q, fact) - if err != nil { - return false, nil - } - f, err := a.resolve(db) - return err == nil, f -} - -func (a *alias) save(db *sqlx.DB) error { - q := `select * from factoid_alias where fact=?` - var offender alias - err := db.Get(&offender, q, a.Next) - if err == nil { - return fmt.Errorf("DANGER: an opposite alias already exists") - } - _, err = a.resolve(db) - 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) - if err != nil { - return err - } - return nil -} - -func aliasFromStrings(from, to string) *alias { - return &alias{from, to} -} - -func (f *Factoid) Save(db *sqlx.DB) 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) - } 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 - } - return err -} - -func (f *Factoid) delete(db *sqlx.DB) error { - var err error - if f.ID.Valid { - _, err = db.Exec(`delete from factoid where id=?`, f.ID) - } - f.ID.Valid = false - return err -} - -func getFacts(db *sqlx.DB, 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+"%") - 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 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 -} - // Factoid provides the necessary plugin-wide needs type FactoidPlugin struct { - Bot bot.Bot - NotFound []string - LastFact *Factoid + b bot.Bot + c *config.Config + lastFact *Factoid db *sqlx.DB handlers bot.HandlerTable } @@ -274,15 +35,8 @@ type FactoidPlugin struct { // NewFactoid creates a new Factoid with the Plugin interface func New(botInst bot.Bot) *FactoidPlugin { p := &FactoidPlugin{ - Bot: botInst, - NotFound: []string{ - "I don't know.", - "NONONONO", - "((", - "*pukes*", - "NOPE! NOPE! NOPE!", - "One time, I learned how to jump rope.", - }, + b: botInst, + c: botInst.Config(), db: botInst.DB(), } @@ -309,13 +63,13 @@ func New(botInst bot.Bot) *FactoidPlugin { log.Fatal().Err(err) } - for _, channel := range botInst.Config().GetArray("channels", []string{}) { + for _, channel := range p.c.GetArray("channels", []string{}) { go p.factTimer(c, channel) go func(ch string) { // Some random time to start up time.Sleep(time.Duration(15) * time.Second) - if ok, fact := p.findTrigger(p.Bot.Config().Get("Factoid.StartupFact", "speed test")); ok { + if ok, fact := p.findTrigger(p.c.Get("Factoid.StartupFact", "speed test")); ok { p.sayFact(c, msg.Message{ Channel: ch, Body: "speed test", // BUG: This is defined in the config too @@ -387,7 +141,7 @@ func (p *FactoidPlugin) learnFact(message msg.Message, fact, verb, tidbit string Accessed: time.Now(), Count: 0, } - p.LastFact = &n + p.lastFact = &n err = n.Save(p.db) if err != nil { log.Error().Err(err).Msg("Error inserting fact") @@ -411,8 +165,8 @@ func (p *FactoidPlugin) findTrigger(fact string) (bool, *Factoid) { // sayFact spits out a fact to the channel and updates the fact in the database // with new time and count information func (p *FactoidPlugin) sayFact(c bot.Connector, message msg.Message, fact Factoid) { - msg := p.Bot.Filter(message, fact.Tidbit) - full := p.Bot.Filter(message, fmt.Sprintf("%s %s %s", + msg := p.b.Filter(message, fact.Tidbit) + full := p.b.Filter(message, fmt.Sprintf("%s %s %s", fact.Fact, fact.Verb, fact.Tidbit, )) for i, m := 0, strings.Split(msg, "$and"); i < len(m) && i < 4; i++ { @@ -422,15 +176,15 @@ func (p *FactoidPlugin) sayFact(c bot.Connector, message msg.Message, fact Facto } if fact.Verb == "action" { - p.Bot.Send(c, bot.Action, message.Channel, msg) + p.b.Send(c, bot.Action, message.Channel, msg) } else if fact.Verb == "react" { - p.Bot.Send(c, bot.Reaction, message.Channel, msg, message) + p.b.Send(c, bot.Reaction, message.Channel, msg, message) } else if fact.Verb == "reply" { - p.Bot.Send(c, bot.Message, message.Channel, msg) + p.b.Send(c, bot.Message, message.Channel, msg) } else if fact.Verb == "image" { p.sendImage(c, message, msg) } else { - p.Bot.Send(c, bot.Message, message.Channel, full) + p.b.Send(c, bot.Message, message.Channel, full) } } @@ -444,7 +198,7 @@ func (p *FactoidPlugin) sayFact(c bot.Connector, message msg.Message, fact Facto Err(err). Msg("could not update fact") } - p.LastFact = &fact + p.lastFact = &fact } func (p *FactoidPlugin) sendImage(c bot.Connector, message msg.Message, msg string) { @@ -472,16 +226,16 @@ func (p *FactoidPlugin) sendImage(c bot.Connector, message msg.Message, msg stri URL: imgSrc, AltTxt: txt, } - p.Bot.Send(c, bot.Message, message.Channel, "", img) + p.b.Send(c, bot.Message, message.Channel, "", img) return } - p.Bot.Send(c, bot.Message, message.Channel, txt) + p.b.Send(c, bot.Message, message.Channel, txt) } // trigger checks the message for its fitness to be a factoid and then hauls // the message off to sayFact for processing if it is in fact a trigger func (p *FactoidPlugin) trigger(c bot.Connector, message msg.Message) bool { - minLen := p.Bot.Config().GetInt("Factoid.MinLen", 4) + minLen := p.c.GetInt("Factoid.MinLen", 4) if len(message.Body) > minLen || message.Command || message.Body == "..." { if ok, fact := p.findTrigger(message.Body); ok { p.sayFact(c, message, *fact) @@ -500,7 +254,7 @@ func (p *FactoidPlugin) trigger(c bot.Connector, message msg.Message) bool { // tellThemWhatThatWas is a hilarious name for a function. func (p *FactoidPlugin) tellThemWhatThatWas(c bot.Connector, message msg.Message) bool { - fact := p.LastFact + fact := p.lastFact var msg string if fact == nil { msg = "Nope." @@ -508,7 +262,7 @@ func (p *FactoidPlugin) tellThemWhatThatWas(c bot.Connector, message msg.Message msg = fmt.Sprintf("That was (#%d) '%s <%s> %s'", fact.ID.Int64, fact.Fact, fact.Verb, fact.Tidbit) } - p.Bot.Send(c, bot.Message, message.Channel, msg) + p.b.Send(c, bot.Message, message.Channel, msg) return true } @@ -526,21 +280,21 @@ func (p *FactoidPlugin) learnAction(c bot.Connector, message msg.Message, action action = strings.TrimSpace(action) if len(trigger) == 0 || len(fact) == 0 || len(action) == 0 { - p.Bot.Send(c, bot.Message, message.Channel, "I don't want to learn that.") + p.b.Send(c, bot.Message, message.Channel, "I don't want to learn that.") return true } if len(strings.Split(fact, "$and")) > 4 { - p.Bot.Send(c, bot.Message, message.Channel, "You can't use more than 4 $and operators.") + p.b.Send(c, bot.Message, message.Channel, "You can't use more than 4 $and operators.") return true } strippedaction := strings.Replace(strings.Replace(action, "<", "", 1), ">", "", 1) if err := p.learnFact(message, trigger, strippedaction, fact); err != nil { - p.Bot.Send(c, bot.Message, message.Channel, err.Error()) + p.b.Send(c, bot.Message, message.Channel, err.Error()) } else { - p.Bot.Send(c, bot.Message, message.Channel, fmt.Sprintf("Okay, %s.", message.User.Name)) + p.b.Send(c, bot.Message, message.Channel, fmt.Sprintf("Okay, %s.", message.User.Name)) } return true @@ -559,22 +313,22 @@ func changeOperator(body string) string { // If the user requesting forget is either the owner of the last learned fact or // an admin, it may be deleted func (p *FactoidPlugin) forgetLastFact(c bot.Connector, message msg.Message) bool { - if p.LastFact == nil { - p.Bot.Send(c, bot.Message, message.Channel, "I refuse.") + if p.lastFact == nil { + p.b.Send(c, bot.Message, message.Channel, "I refuse.") return true } - err := p.LastFact.delete(p.db) + err := p.lastFact.delete(p.db) if err != nil { log.Error(). Err(err). - Interface("LastFact", p.LastFact). + Interface("lastFact", p.lastFact). Msg("Error removing fact") } - fmt.Printf("Forgot #%d: %s %s %s\n", p.LastFact.ID.Int64, 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 + fmt.Printf("Forgot #%d: %s %s %s\n", p.lastFact.ID.Int64, p.lastFact.Fact, + p.lastFact.Verb, p.lastFact.Tidbit) + p.b.Send(c, bot.Action, message.Channel, "hits himself over the head with a skillet") + p.lastFact = nil return true } @@ -597,7 +351,7 @@ func (p *FactoidPlugin) changeFact(c bot.Connector, message msg.Message) bool { if len(parts) == 4 { // replacement if parts[0] != "s" { - p.Bot.Send(c, bot.Message, message.Channel, "Nah.") + p.b.Send(c, bot.Message, message.Channel, "Nah.") } find := parts[1] replace := parts[2] @@ -615,10 +369,10 @@ func (p *FactoidPlugin) changeFact(c bot.Connector, message msg.Message) bool { } // make the changes msg := fmt.Sprintf("Changing %d facts.", len(result)) - p.Bot.Send(c, bot.Message, message.Channel, msg) + p.b.Send(c, bot.Message, message.Channel, msg) reg, err := regexp.Compile(find) if err != nil { - p.Bot.Send(c, bot.Message, message.Channel, "I don't really want to.") + p.b.Send(c, bot.Message, message.Channel, "I don't really want to.") return false } for _, fact := range result { @@ -638,12 +392,12 @@ func (p *FactoidPlugin) changeFact(c bot.Connector, message msg.Message) bool { Err(err). Str("trigger", trigger). Msg("Error getting facts") - p.Bot.Send(c, bot.Message, message.Channel, "bzzzt") + p.b.Send(c, bot.Message, message.Channel, "bzzzt") return true } count := len(result) if count == 0 { - p.Bot.Send(c, bot.Message, message.Channel, "I didn't find any facts like that.") + p.b.Send(c, bot.Message, message.Channel, "I didn't find any facts like that.") return true } if parts[2] == "g" && len(result) > 4 { @@ -663,9 +417,9 @@ func (p *FactoidPlugin) changeFact(c bot.Connector, message msg.Message) bool { if count > 4 { msg = fmt.Sprintf("%s | ...and %d others", msg, count) } - p.Bot.Send(c, bot.Message, message.Channel, msg) + p.b.Send(c, bot.Message, message.Channel, msg) } else { - p.Bot.Send(c, bot.Message, message.Channel, "I don't know what you mean.") + p.b.Send(c, bot.Message, message.Channel, "I don't know what you mean.") } return true } @@ -685,9 +439,9 @@ func (p *FactoidPlugin) register() { log.Debug().Msgf("alias: %+v", r) a := aliasFromStrings(from, to) if err := a.save(p.db); err != nil { - p.Bot.Send(r.Conn, bot.Message, r.Msg.Channel, err.Error()) + p.b.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") + p.b.Send(r.Conn, bot.Action, r.Msg.Channel, "learns a new synonym") } return true }}, @@ -731,18 +485,27 @@ func (p *FactoidPlugin) register() { return true } + notFound := p.c.GetArray("fact.notfound", []string{ + "I don't know.", + "NONONONO", + "((", + "*pukes*", + "NOPE! NOPE! NOPE!", + "One time, I learned how to jump rope.", + }) + // We didn't find anything, panic! - p.Bot.Send(c, bot.Message, message.Channel, p.NotFound[rand.Intn(len(p.NotFound))]) + p.b.Send(c, bot.Message, message.Channel, notFound[rand.Intn(len(notFound))]) return true }}, } - p.Bot.RegisterTable(p, p.handlers) + p.b.RegisterTable(p, p.handlers) } // Help responds to help requests. Every plugin must implement a help function. func (p *FactoidPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...any) bool { - p.Bot.Send(c, bot.Message, message.Channel, "I can learn facts and spit them back out. You can say \"this is that\" or \"he $5\". Later, trigger the factoid by just saying the trigger word, \"this\" or \"he\" in these examples.") - p.Bot.Send(c, bot.Message, message.Channel, "I can also figure out some variables including: $nonzero, $digit, $nick, and $someone.") + p.b.Send(c, bot.Message, message.Channel, "I can learn facts and spit them back out. You can say \"this is that\" or \"he $5\". Later, trigger the factoid by just saying the trigger word, \"this\" or \"he\" in these examples.") + p.b.Send(c, bot.Message, message.Channel, "I can also figure out some variables including: $nonzero, $digit, $nick, and $someone.") return true } @@ -758,17 +521,17 @@ func (p *FactoidPlugin) randomFact() *Factoid { // factTimer spits out a fact at a given interval and with given probability func (p *FactoidPlugin) factTimer(c bot.Connector, channel string) { - quoteTime := p.Bot.Config().GetInt("Factoid.QuoteTime", 30) + quoteTime := p.c.GetInt("Factoid.QuoteTime", 30) if quoteTime == 0 { quoteTime = 30 - p.Bot.Config().Set("Factoid.QuoteTime", "30") + p.c.Set("Factoid.QuoteTime", "30") } duration := time.Duration(quoteTime) * time.Minute myLastMsg := time.Now() for { - time.Sleep(time.Duration(5) * time.Second) // why 5? + time.Sleep(time.Duration(5) * time.Second) // why 5? // no seriously, why 5? - lastmsg, err := p.Bot.LastMessage(channel) + lastmsg, err := p.b.LastMessage(channel) if err != nil { // Probably no previous message to time off of continue @@ -777,11 +540,7 @@ func (p *FactoidPlugin) factTimer(c bot.Connector, channel string) { tdelta := time.Since(lastmsg.Time) earlier := time.Since(myLastMsg) > tdelta chance := rand.Float64() - quoteChance := p.Bot.Config().GetFloat64("Factoid.QuoteChance", 0.99) - if quoteChance == 0.0 { - quoteChance = 0.99 - p.Bot.Config().Set("Factoid.QuoteChance", "0.99") - } + quoteChance := p.c.GetFloat64("Factoid.QuoteChance", 0.99) success := chance < quoteChance if success && tdelta > duration && earlier { @@ -791,7 +550,7 @@ func (p *FactoidPlugin) factTimer(c bot.Connector, channel string) { continue } - users := p.Bot.Who(channel) + users := p.b.Who(channel) // we need to fabricate a message so that bot.Filter can operate message := msg.Message{ @@ -803,58 +562,3 @@ 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("%s", 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.db, 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) -} diff --git a/plugins/fact/web.go b/plugins/fact/web.go new file mode 100644 index 0000000..7a39d46 --- /dev/null +++ b/plugins/fact/web.go @@ -0,0 +1,70 @@ +package fact + +import ( + "embed" + "encoding/json" + "fmt" + "github.com/go-chi/chi/v5" + "html/template" + "net/http" + "strings" +) + +//go:embed *.html +var embeddedFS embed.FS + +// 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.b.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("%s", 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.db, 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) +}