factoid: refactor and ignore reaction

- factoid is a mess, but I at least split it into a few parts
- need to move config values from factoid to fact
- need to rename the whole thing to fact
- probably need to go over it with a fine toothed comb
- 5 seconds, why 5 seconds? I still don't know
- added kind to the message type so we can filter reactions out
This commit is contained in:
Chris Sexton 2022-05-31 11:19:37 -04:00
parent 46e61f69d0
commit 4e3cbbafad
8 changed files with 384 additions and 359 deletions

View File

@ -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) { func (b *bot) LastMessage(channel string) (msg.Message, error) {
log := <-b.logOut log := <-b.logOut
if len(log) == 0 { 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-- { for i := len(log) - 1; i >= 0; i-- {
msg := log[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 return msg, nil
} }
} }

View File

@ -13,6 +13,7 @@ type Messages []Message
type Message struct { type Message struct {
ID string ID string
Kind int
User *user.User User *user.User
// With Slack, channel is the ID of a channel // With Slack, channel is the ID of a channel
Channel string Channel string

View File

@ -246,6 +246,7 @@ func (d *Discord) messageReactAdd(s *discordgo.Session, m *discordgo.MessageReac
} }
msg := msg.Message{ msg := msg.Message{
ID: m.MessageID, ID: m.MessageID,
Kind: bot.Reaction,
User: &author, User: &author,
Channel: m.ChannelID, Channel: m.ChannelID,
ChannelName: ch.Name, ChannelName: ch.Name,
@ -277,6 +278,7 @@ func (d *Discord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreate
msg := msg.Message{ msg := msg.Message{
ID: m.ID, ID: m.ID,
Kind: bot.Message,
User: &author, User: &author,
Channel: m.ChannelID, Channel: m.ChannelID,
ChannelName: ch.Name, ChannelName: ch.Name,

View File

@ -253,9 +253,11 @@ func (i *Irc) handleMsg(msg irc.Msg) {
fallthrough fallthrough
case irc.RPL_ENDOFWHO: case irc.RPL_ENDOFWHO:
botMsg.Kind = bot.Event
i.event(i, bot.Event, botMsg) i.event(i, bot.Event, botMsg)
case irc.PRIVMSG: case irc.PRIVMSG:
botMsg.Kind = bot.Message
i.event(i, bot.Message, botMsg) i.event(i, bot.Message, botMsg)
case irc.QUIT: case irc.QUIT:

View File

@ -469,7 +469,8 @@ func (s *SlackApp) buildMessage(m *slackevents.MessageEvent) msg.Message {
tstamp := slackTStoTime(m.TimeStamp) tstamp := slackTStoTime(m.TimeStamp)
return msg.Message{ return msg.Message{
ID: m.TimeStamp, ID: m.TimeStamp,
Kind: bot.Message,
User: &user.User{ User: &user.User{
ID: m.User, ID: m.User,
Name: name, Name: name,

241
plugins/fact/fact.go Normal file
View File

@ -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
}

View File

@ -4,18 +4,14 @@ package fact
import ( import (
"database/sql" "database/sql"
"embed"
"encoding/json"
"fmt" "fmt"
"html/template" "github.com/velour/catbase/config"
"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/jmoiron/sqlx" "github.com/jmoiron/sqlx"
@ -27,246 +23,11 @@ import (
// The factoid plugin provides a learning system to the bot so that it can // 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 // 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 // Factoid provides the necessary plugin-wide needs
type FactoidPlugin struct { type FactoidPlugin struct {
Bot bot.Bot b bot.Bot
NotFound []string c *config.Config
LastFact *Factoid lastFact *Factoid
db *sqlx.DB db *sqlx.DB
handlers bot.HandlerTable handlers bot.HandlerTable
} }
@ -274,15 +35,8 @@ type FactoidPlugin struct {
// NewFactoid creates a new Factoid with the Plugin interface // NewFactoid creates a new Factoid with the Plugin interface
func New(botInst bot.Bot) *FactoidPlugin { func New(botInst bot.Bot) *FactoidPlugin {
p := &FactoidPlugin{ p := &FactoidPlugin{
Bot: botInst, b: botInst,
NotFound: []string{ c: botInst.Config(),
"I don't know.",
"NONONONO",
"((",
"*pukes*",
"NOPE! NOPE! NOPE!",
"One time, I learned how to jump rope.",
},
db: botInst.DB(), db: botInst.DB(),
} }
@ -309,13 +63,13 @@ func New(botInst bot.Bot) *FactoidPlugin {
log.Fatal().Err(err) 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 p.factTimer(c, channel)
go func(ch string) { go func(ch string) {
// Some random time to start up // Some random time to start up
time.Sleep(time.Duration(15) * time.Second) 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{ p.sayFact(c, msg.Message{
Channel: ch, Channel: ch,
Body: "speed test", // BUG: This is defined in the config too 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(), Accessed: time.Now(),
Count: 0, Count: 0,
} }
p.LastFact = &n p.lastFact = &n
err = n.Save(p.db) err = n.Save(p.db)
if err != nil { if err != nil {
log.Error().Err(err).Msg("Error inserting fact") 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 // sayFact spits out a fact to the channel and updates the fact in the database
// with new time and count information // with new time and count information
func (p *FactoidPlugin) sayFact(c bot.Connector, message msg.Message, fact Factoid) { func (p *FactoidPlugin) sayFact(c bot.Connector, message msg.Message, fact Factoid) {
msg := p.Bot.Filter(message, fact.Tidbit) msg := p.b.Filter(message, fact.Tidbit)
full := p.Bot.Filter(message, fmt.Sprintf("%s %s %s", full := p.b.Filter(message, fmt.Sprintf("%s %s %s",
fact.Fact, fact.Verb, fact.Tidbit, fact.Fact, fact.Verb, fact.Tidbit,
)) ))
for i, m := 0, strings.Split(msg, "$and"); i < len(m) && i < 4; i++ { 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" { 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" { } 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" { } 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" { } else if fact.Verb == "image" {
p.sendImage(c, message, msg) p.sendImage(c, message, msg)
} else { } 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). Err(err).
Msg("could not update fact") Msg("could not update fact")
} }
p.LastFact = &fact p.lastFact = &fact
} }
func (p *FactoidPlugin) sendImage(c bot.Connector, message msg.Message, msg string) { 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, URL: imgSrc,
AltTxt: txt, AltTxt: txt,
} }
p.Bot.Send(c, bot.Message, message.Channel, "", img) p.b.Send(c, bot.Message, message.Channel, "", img)
return 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 // 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 // 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 { 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 len(message.Body) > minLen || message.Command || message.Body == "..." {
if ok, fact := p.findTrigger(message.Body); ok { if ok, fact := p.findTrigger(message.Body); ok {
p.sayFact(c, message, *fact) 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. // tellThemWhatThatWas is a hilarious name for a function.
func (p *FactoidPlugin) tellThemWhatThatWas(c bot.Connector, message msg.Message) bool { func (p *FactoidPlugin) tellThemWhatThatWas(c bot.Connector, message msg.Message) bool {
fact := p.LastFact fact := p.lastFact
var msg string var msg string
if fact == nil { if fact == nil {
msg = "Nope." 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'", msg = fmt.Sprintf("That was (#%d) '%s <%s> %s'",
fact.ID.Int64, fact.Fact, fact.Verb, fact.Tidbit) 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 return true
} }
@ -526,21 +280,21 @@ func (p *FactoidPlugin) learnAction(c bot.Connector, message msg.Message, action
action = strings.TrimSpace(action) action = strings.TrimSpace(action)
if len(trigger) == 0 || len(fact) == 0 || len(action) == 0 { 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 return true
} }
if len(strings.Split(fact, "$and")) > 4 { 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 return true
} }
strippedaction := strings.Replace(strings.Replace(action, "<", "", 1), ">", "", 1) strippedaction := strings.Replace(strings.Replace(action, "<", "", 1), ">", "", 1)
if err := p.learnFact(message, trigger, strippedaction, fact); err != nil { 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 { } 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 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 // If the user requesting forget is either the owner of the last learned fact or
// an admin, it may be deleted // an admin, it may be deleted
func (p *FactoidPlugin) forgetLastFact(c bot.Connector, message msg.Message) bool { func (p *FactoidPlugin) forgetLastFact(c bot.Connector, message msg.Message) bool {
if p.LastFact == nil { if p.lastFact == nil {
p.Bot.Send(c, bot.Message, message.Channel, "I refuse.") p.b.Send(c, bot.Message, message.Channel, "I refuse.")
return true return true
} }
err := p.LastFact.delete(p.db) err := p.lastFact.delete(p.db)
if err != nil { if err != nil {
log.Error(). log.Error().
Err(err). Err(err).
Interface("LastFact", p.LastFact). Interface("lastFact", p.lastFact).
Msg("Error removing fact") 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.Int64, p.lastFact.Fact,
p.LastFact.Verb, p.LastFact.Tidbit) p.lastFact.Verb, p.lastFact.Tidbit)
p.Bot.Send(c, bot.Action, message.Channel, "hits himself over the head with a skillet") p.b.Send(c, bot.Action, message.Channel, "hits himself over the head with a skillet")
p.LastFact = nil p.lastFact = nil
return true return true
} }
@ -597,7 +351,7 @@ func (p *FactoidPlugin) changeFact(c bot.Connector, message msg.Message) bool {
if len(parts) == 4 { if len(parts) == 4 {
// replacement // replacement
if parts[0] != "s" { 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] find := parts[1]
replace := parts[2] replace := parts[2]
@ -615,10 +369,10 @@ func (p *FactoidPlugin) changeFact(c bot.Connector, message msg.Message) bool {
} }
// make the changes // make the changes
msg := fmt.Sprintf("Changing %d facts.", len(result)) 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) reg, err := regexp.Compile(find)
if err != nil { 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 return false
} }
for _, fact := range result { for _, fact := range result {
@ -638,12 +392,12 @@ func (p *FactoidPlugin) changeFact(c bot.Connector, message msg.Message) bool {
Err(err). Err(err).
Str("trigger", trigger). Str("trigger", trigger).
Msg("Error getting facts") Msg("Error getting facts")
p.Bot.Send(c, bot.Message, message.Channel, "bzzzt") p.b.Send(c, bot.Message, message.Channel, "bzzzt")
return true return true
} }
count := len(result) count := len(result)
if count == 0 { 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 return true
} }
if parts[2] == "g" && len(result) > 4 { 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 { if count > 4 {
msg = fmt.Sprintf("%s | ...and %d others", msg, count) 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 { } 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 return true
} }
@ -685,9 +439,9 @@ func (p *FactoidPlugin) register() {
log.Debug().Msgf("alias: %+v", r) log.Debug().Msgf("alias: %+v", r)
a := aliasFromStrings(from, to) a := aliasFromStrings(from, to)
if err := a.save(p.db); err != nil { 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 { } 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 return true
}}, }},
@ -731,18 +485,27 @@ func (p *FactoidPlugin) register() {
return true 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! // 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 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. // 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 { 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 <has> $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 learn facts and spit them back out. You can say \"this is that\" or \"he <has> $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 also figure out some variables including: $nonzero, $digit, $nick, and $someone.")
return true return true
} }
@ -758,17 +521,17 @@ func (p *FactoidPlugin) randomFact() *Factoid {
// factTimer spits out a fact at a given interval and with given probability // factTimer spits out a fact at a given interval and with given probability
func (p *FactoidPlugin) factTimer(c bot.Connector, channel string) { 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 { if quoteTime == 0 {
quoteTime = 30 quoteTime = 30
p.Bot.Config().Set("Factoid.QuoteTime", "30") p.c.Set("Factoid.QuoteTime", "30")
} }
duration := time.Duration(quoteTime) * time.Minute duration := time.Duration(quoteTime) * time.Minute
myLastMsg := time.Now() myLastMsg := time.Now()
for { 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 { if err != nil {
// Probably no previous message to time off of // Probably no previous message to time off of
continue continue
@ -777,11 +540,7 @@ func (p *FactoidPlugin) factTimer(c bot.Connector, channel string) {
tdelta := time.Since(lastmsg.Time) tdelta := time.Since(lastmsg.Time)
earlier := time.Since(myLastMsg) > tdelta earlier := time.Since(myLastMsg) > tdelta
chance := rand.Float64() chance := rand.Float64()
quoteChance := p.Bot.Config().GetFloat64("Factoid.QuoteChance", 0.99) quoteChance := p.c.GetFloat64("Factoid.QuoteChance", 0.99)
if quoteChance == 0.0 {
quoteChance = 0.99
p.Bot.Config().Set("Factoid.QuoteChance", "0.99")
}
success := chance < quoteChance success := chance < quoteChance
if success && tdelta > duration && earlier { if success && tdelta > duration && earlier {
@ -791,7 +550,7 @@ func (p *FactoidPlugin) factTimer(c bot.Connector, channel string) {
continue continue
} }
users := p.Bot.Who(channel) users := p.b.Who(channel)
// we need to fabricate a message so that bot.Filter can operate // we need to fabricate a message so that bot.Filter can operate
message := msg.Message{ 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("<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.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)
}

70
plugins/fact/web.go Normal file
View File

@ -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("<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.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)
}