2016-01-17 18:00:44 +00:00
// © 2013 the CatBase Authors under the WTFPL. See AUTHORS for the list of authors.
2013-12-10 23:37:07 +00:00
2016-03-24 17:32:40 +00:00
package fact
2012-08-26 19:15:04 +00:00
import (
2021-07-29 16:33:03 +00:00
"embed"
2012-08-26 19:15:04 +00:00
"fmt"
2021-12-20 17:40:10 +00:00
bh "github.com/timshannon/bolthold"
2012-08-26 19:15:04 +00:00
"math/rand"
2019-12-30 15:26:17 +00:00
"net/url"
2012-08-26 19:15:04 +00:00
"regexp"
"strings"
"time"
2014-04-21 01:07:55 +00:00
2019-03-07 16:35:42 +00:00
"github.com/rs/zerolog/log"
2016-01-17 18:00:44 +00:00
"github.com/velour/catbase/bot"
2016-04-01 14:20:03 +00:00
"github.com/velour/catbase/bot/msg"
2012-08-26 19:15:04 +00:00
)
2013-01-22 17:39:27 +00:00
// The factoid plugin provides a learning system to the bot so that it can
2012-08-29 16:50:52 +00:00
// respond to queries in a way that is unpredictable and fun
2012-08-26 19:15:04 +00:00
2021-07-29 16:33:03 +00:00
//go:embed *.html
var embeddedFS embed . FS
2021-10-05 23:05:27 +00:00
// Factoid stores info about our factoid for lookup and later interaction
2019-02-15 18:22:54 +00:00
type Factoid struct {
2021-12-21 19:08:20 +00:00
ID uint64 ` boltholdKey:"ID" `
2016-03-29 14:20:44 +00:00
Fact string
Tidbit string
Verb string
Owner string
2019-02-15 18:22:54 +00:00
Created time . Time
Accessed time . Time
2016-03-29 14:20:44 +00:00
Count int
2016-01-17 15:29:30 +00:00
}
2021-12-22 07:53:02 +00:00
type Alias struct {
2017-10-24 19:14:01 +00:00
Fact string
Next string
}
2021-12-22 07:53:02 +00:00
func ( a Alias ) Key ( ) string {
return a . Fact + a . Next
}
func ( a * Alias ) resolve ( store * bh . Store ) ( * Factoid , error ) {
2021-07-21 18:52:45 +00:00
// perform db query to fill the To field
2021-12-20 17:40:10 +00:00
// todo: remove this query
//q := `select fact, next from factoid_alias where fact=?`
2021-12-22 07:53:02 +00:00
var next Alias
2021-12-21 04:31:19 +00:00
err := store . FindOne ( & next , bh . Where ( "Fact" ) . Eq ( a . Next ) )
2017-10-24 19:14:01 +00:00
if err != nil {
// we hit the end of the chain, get a factoid named Next
2021-12-20 17:40:10 +00:00
fact , err := GetSingleFact ( store , a . Next )
2017-10-24 19:14:01 +00:00
if err != nil {
2017-10-25 21:55:55 +00:00
err := fmt . Errorf ( "Error resolvig alias %v: %v" , a , err )
return nil , err
2017-10-24 19:14:01 +00:00
}
2017-10-25 21:55:55 +00:00
return fact , nil
2017-10-24 19:14:01 +00:00
}
2021-12-20 17:40:10 +00:00
return next . resolve ( store )
2017-10-24 19:14:01 +00:00
}
2021-12-20 17:40:10 +00:00
func findAlias ( store * bh . Store , fact string ) ( bool , * Factoid ) {
// todo: remove this query
//q := `select * from factoid_alias where fact=?`
2021-12-22 07:53:02 +00:00
var a Alias
2021-12-21 04:31:19 +00:00
err := store . FindOne ( & a , bh . Where ( "Fact" ) . Eq ( fact ) )
2017-10-24 19:14:01 +00:00
if err != nil {
return false , nil
}
2021-12-20 17:40:10 +00:00
f , err := a . resolve ( store )
2017-10-25 21:55:55 +00:00
return err == nil , f
2017-10-24 19:14:01 +00:00
}
2021-12-22 07:53:02 +00:00
func ( a * Alias ) save ( store * bh . Store ) error {
2021-12-20 17:40:10 +00:00
//q := `select * from factoid_alias where fact=?`
2021-12-22 07:53:02 +00:00
var offender Alias
2021-12-21 04:31:19 +00:00
err := store . FindOne ( & offender , bh . Where ( "Fact" ) . Eq ( a . Next ) )
2017-10-25 22:16:33 +00:00
if err == nil {
2017-10-25 21:55:55 +00:00
return fmt . Errorf ( "DANGER: an opposite alias already exists" )
}
2021-12-20 17:40:10 +00:00
_ , err = a . resolve ( store )
2017-10-25 21:55:55 +00:00
if err != nil {
return fmt . Errorf ( "there is no fact at that destination" )
}
2021-12-20 17:40:10 +00:00
err = store . Upsert ( a . Fact , a )
//q = `insert or replace into factoid_alias (fact, next) values (?, ?)`
// todo: remove query
2017-10-24 19:14:01 +00:00
if err != nil {
return err
}
return nil
}
2021-12-22 07:53:02 +00:00
func aliasFromStrings ( from , to string ) * Alias {
return & Alias { from , to }
2017-10-24 19:14:01 +00:00
}
2021-12-20 17:40:10 +00:00
func ( f * Factoid ) Save ( store * bh . Store ) error {
2016-01-17 15:29:30 +00:00
var err error
2021-12-20 17:40:10 +00:00
if f . ID != 0 {
f . Accessed = time . Now ( )
err = store . Update ( f . ID , f )
2016-01-17 15:29:30 +00:00
} else {
2019-02-15 18:22:54 +00:00
f . Created = time . Now ( )
f . Accessed = time . Now ( )
2021-12-20 17:40:10 +00:00
err = store . Insert ( bh . NextSequence ( ) , f )
2016-01-17 15:29:30 +00:00
}
return err
}
2021-12-20 17:40:10 +00:00
func ( f * Factoid ) delete ( store * bh . Store ) error {
2016-01-17 15:29:30 +00:00
var err error
2021-12-20 17:40:10 +00:00
if f . ID != 0 {
err = store . Delete ( f . ID , Factoid { } )
2016-01-17 15:29:30 +00:00
}
2021-12-20 17:40:10 +00:00
f . ID = 0
2016-01-17 15:29:30 +00:00
return err
}
2021-12-20 17:40:10 +00:00
func getFacts ( store * bh . Store , fact string , tidbit string ) ( [ ] * Factoid , error ) {
2019-02-15 18:22:54 +00:00
var fs [ ] * Factoid
2021-12-22 07:53:02 +00:00
err := store . Find ( & fs , bh . Where ( "Fact" ) . RegExp ( regexp . MustCompile ( ` .* ` + fact + ` .* ` ) ) . And ( "Tidbit" ) . RegExp ( regexp . MustCompile ( ` .* ` + tidbit + ` .* ` ) ) )
2016-03-29 16:34:04 +00:00
if err != nil {
2019-03-07 16:35:42 +00:00
log . Error ( ) . Err ( err ) . Msg ( "Error regexping for facts" )
2016-03-29 16:34:04 +00:00
return nil , err
}
2016-01-17 15:29:30 +00:00
return fs , err
}
2021-12-20 17:40:10 +00:00
func GetSingle ( store * bh . Store ) ( * Factoid , error ) {
var allMatching [ ] Factoid
if err := store . Find ( & allMatching , & bh . Query { } ) ; err != nil {
return nil , err
}
f := allMatching [ rand . Intn ( len ( allMatching ) ) ]
return & f , nil
}
func GetSingleFact ( store * bh . Store , fact string ) ( * Factoid , error ) {
var allMatching [ ] Factoid
2021-12-21 04:31:19 +00:00
if err := store . Find ( & allMatching , bh . Where ( "Fact" ) . Contains ( fact ) ) ; err != nil {
2021-12-20 17:40:10 +00:00
return nil , err
}
2021-12-21 04:31:19 +00:00
if len ( allMatching ) == 0 {
return nil , fmt . Errorf ( "no entries" )
}
2021-12-20 17:40:10 +00:00
f := allMatching [ rand . Intn ( len ( allMatching ) ) ]
return & f , nil
2012-08-26 19:15:04 +00:00
}
2016-04-08 16:18:34 +00:00
// Factoid provides the necessary plugin-wide needs
2019-02-15 18:22:54 +00:00
type FactoidPlugin struct {
2016-03-30 14:00:20 +00:00
Bot bot . Bot
2012-08-26 20:15:50 +00:00
NotFound [ ] string
2019-02-15 18:22:54 +00:00
LastFact * Factoid
2021-12-20 17:40:10 +00:00
store * bh . Store
2021-02-02 02:25:19 +00:00
handlers bot . HandlerTable
2012-08-26 19:15:04 +00:00
}
2016-04-08 16:18:34 +00:00
// NewFactoid creates a new Factoid with the Plugin interface
2019-02-15 18:22:54 +00:00
func New ( botInst bot . Bot ) * FactoidPlugin {
p := & FactoidPlugin {
2016-01-17 15:29:30 +00:00
Bot : botInst ,
2012-08-26 20:15:50 +00:00
NotFound : [ ] string {
"I don't know." ,
"NONONONO" ,
"((" ,
"*pukes*" ,
"NOPE! NOPE! NOPE!" ,
"One time, I learned how to jump rope." ,
} ,
2021-12-20 17:40:10 +00:00
store : botInst . Store ( ) ,
2016-01-17 15:29:30 +00:00
}
2019-05-27 23:21:53 +00:00
c := botInst . DefaultConnector ( )
2019-01-22 00:16:57 +00:00
for _ , channel := range botInst . Config ( ) . GetArray ( "channels" , [ ] string { } ) {
2019-05-27 23:21:53 +00:00
go p . factTimer ( c , channel )
2013-08-28 01:52:27 +00:00
2016-03-19 18:02:46 +00:00
go func ( ch string ) {
2013-08-28 01:52:27 +00:00
// Some random time to start up
2013-09-01 02:29:13 +00:00
time . Sleep ( time . Duration ( 15 ) * time . Second )
2019-01-22 00:16:57 +00:00
if ok , fact := p . findTrigger ( p . Bot . Config ( ) . Get ( "Factoid.StartupFact" , "speed test" ) ) ; ok {
2019-05-27 23:21:53 +00:00
p . sayFact ( c , msg . Message {
2016-03-19 15:38:18 +00:00
Channel : ch ,
2016-01-17 15:29:30 +00:00
Body : "speed test" , // BUG: This is defined in the config too
2013-08-28 01:52:27 +00:00
Command : true ,
Action : false ,
} , * fact )
}
2016-03-19 15:38:18 +00:00
} ( channel )
2012-08-27 15:34:21 +00:00
}
2013-08-28 01:52:27 +00:00
2021-02-02 02:25:19 +00:00
p . register ( )
2019-02-05 19:41:38 +00:00
botInst . Register ( p , bot . Help , p . help )
2019-02-07 16:30:42 +00:00
p . registerWeb ( )
2012-08-26 19:15:04 +00:00
return p
}
2012-08-29 16:50:52 +00:00
// findAction simply regexes a string for the action verb
func findAction ( message string ) string {
2012-11-11 16:08:37 +00:00
r , err := regexp . Compile ( "<.+?>" )
2012-08-26 19:15:04 +00:00
if err != nil {
panic ( err )
}
action := r . FindString ( message )
2013-09-05 01:56:03 +00:00
if action == "" {
if strings . Contains ( message , " is " ) {
return "is"
} else if strings . Contains ( message , " are " ) {
return "are"
}
}
2012-08-26 19:15:04 +00:00
return action
}
2012-08-29 16:50:52 +00:00
// learnFact assumes we have a learning situation and inserts a new fact
// into the database
2019-02-15 18:22:54 +00:00
func ( p * FactoidPlugin ) learnFact ( message msg . Message , fact , verb , tidbit string ) error {
2016-01-17 15:29:30 +00:00
verb = strings . ToLower ( verb )
2019-01-20 17:33:19 +00:00
if verb == "react" {
// This would be a great place to check against the API for valid emojy
// I'm too lazy for that
tidbit = strings . Replace ( tidbit , ":" , "" , - 1 )
if len ( strings . Split ( tidbit , " " ) ) > 1 {
return fmt . Errorf ( "That's not a valid emojy." )
}
}
2012-08-26 20:15:50 +00:00
2021-12-21 04:31:19 +00:00
count , err := p . store . Count ( Factoid { } , bh . Where ( "Fact" ) . Eq ( fact ) . And ( "Verb" ) . Eq ( verb ) . And ( "Tidbit" ) . Eq ( tidbit ) )
2016-01-17 15:29:30 +00:00
if err != nil {
2019-03-07 16:35:42 +00:00
log . Error ( ) . Err ( err ) . Msg ( "Error counting facts" )
2019-01-20 17:33:19 +00:00
return fmt . Errorf ( "What?" )
2021-12-20 17:40:10 +00:00
} else if count != 0 {
2019-03-07 16:35:42 +00:00
log . Debug ( ) . Msg ( "User tried to relearn a fact." )
2019-01-20 17:33:19 +00:00
return fmt . Errorf ( "Look, I already know that." )
2012-08-26 20:15:50 +00:00
}
2019-02-15 18:22:54 +00:00
n := Factoid {
2016-03-29 14:20:44 +00:00
Fact : fact ,
Tidbit : tidbit ,
Verb : verb ,
Owner : message . User . Name ,
2019-02-15 18:22:54 +00:00
Created : time . Now ( ) ,
Accessed : time . Now ( ) ,
2016-03-29 14:20:44 +00:00
Count : 0 ,
2016-01-17 15:29:30 +00:00
}
p . LastFact = & n
2021-12-20 17:40:10 +00:00
err = n . Save ( p . store )
2012-08-27 15:34:21 +00:00
if err != nil {
2019-03-07 16:35:42 +00:00
log . Error ( ) . Err ( err ) . Msg ( "Error inserting fact" )
2019-01-20 17:33:19 +00:00
return fmt . Errorf ( "My brain is overheating." )
2012-08-27 15:34:21 +00:00
}
2019-01-20 17:33:19 +00:00
return nil
2012-08-26 19:15:04 +00:00
}
2012-08-29 16:50:52 +00:00
// findTrigger checks to see if a given string is a trigger or not
2019-02-15 18:22:54 +00:00
func ( p * FactoidPlugin ) findTrigger ( fact string ) ( bool , * Factoid ) {
2016-01-17 15:29:30 +00:00
fact = strings . ToLower ( fact ) // TODO: make sure this needs to be lowered here
2012-08-26 19:15:04 +00:00
2021-12-20 17:40:10 +00:00
f , err := GetSingleFact ( p . store , fact )
2016-01-17 15:29:30 +00:00
if err != nil {
2021-12-20 17:40:10 +00:00
return findAlias ( p . store , fact )
2012-08-26 19:15:04 +00:00
}
2016-01-17 15:29:30 +00:00
return true , f
2012-08-26 20:15:50 +00:00
}
2012-08-29 16:50:52 +00:00
// sayFact spits out a fact to the channel and updates the fact in the database
// with new time and count information
2019-05-27 23:21:53 +00:00
func ( p * FactoidPlugin ) sayFact ( c bot . Connector , message msg . Message , fact Factoid ) {
2016-03-29 14:20:44 +00:00
msg := p . Bot . Filter ( message , fact . Tidbit )
2016-01-17 15:29:30 +00:00
full := p . Bot . Filter ( message , fmt . Sprintf ( "%s %s %s" ,
2016-03-29 14:20:44 +00:00
fact . Fact , fact . Verb , fact . Tidbit ,
2016-01-17 15:29:30 +00:00
) )
2012-08-27 15:34:21 +00:00
for i , m := 0 , strings . Split ( msg , "$and" ) ; i < len ( m ) && i < 4 ; i ++ {
msg := strings . TrimSpace ( m [ i ] )
if len ( msg ) == 0 {
continue
}
2016-03-29 14:20:44 +00:00
if fact . Verb == "action" {
2019-05-27 23:21:53 +00:00
p . Bot . Send ( c , bot . Action , message . Channel , msg )
2019-01-20 17:33:19 +00:00
} else if fact . Verb == "react" {
2019-05-27 23:21:53 +00:00
p . Bot . Send ( c , bot . Reaction , message . Channel , msg , message )
2016-03-29 14:20:44 +00:00
} else if fact . Verb == "reply" {
2019-05-27 23:21:53 +00:00
p . Bot . Send ( c , bot . Message , message . Channel , msg )
2019-12-30 15:26:17 +00:00
} else if fact . Verb == "image" {
p . sendImage ( c , message , msg )
2016-01-17 15:29:30 +00:00
} else {
2019-05-27 23:21:53 +00:00
p . Bot . Send ( c , bot . Message , message . Channel , full )
2012-08-27 15:34:21 +00:00
}
}
2012-08-29 14:24:34 +00:00
// update fact tracking
2019-02-15 18:22:54 +00:00
fact . Accessed = time . Now ( )
2016-03-29 14:20:44 +00:00
fact . Count += 1
2021-12-20 17:40:10 +00:00
err := fact . Save ( p . store )
2012-08-29 14:24:34 +00:00
if err != nil {
2019-03-07 16:35:42 +00:00
log . Error ( ) .
Interface ( "fact" , fact ) .
Err ( err ) .
Msg ( "could not update fact" )
2012-08-29 14:24:34 +00:00
}
2012-08-29 16:50:52 +00:00
p . LastFact = & fact
2012-08-27 15:34:21 +00:00
}
2019-12-30 15:26:17 +00:00
func ( p * FactoidPlugin ) sendImage ( c bot . Connector , message msg . Message , msg string ) {
imgSrc := ""
txt := ""
for _ , w := range strings . Split ( msg , " " ) {
if _ , err := url . Parse ( w ) ; err == nil && strings . HasPrefix ( w , "http" ) {
log . Debug ( ) . Msgf ( "Valid image found: %s" , w )
imgSrc = w
} else {
txt = txt + " " + w
log . Debug ( ) . Msgf ( "Adding %s to txt %s" , w , txt )
}
}
log . Debug ( ) .
Str ( "imgSrc" , imgSrc ) .
Str ( "txt" , txt ) .
Str ( "msg" , msg ) .
Msg ( "Sending image attachment" )
if imgSrc != "" {
2020-05-21 18:56:18 +00:00
if txt == "" {
txt = imgSrc
}
2019-12-30 15:26:17 +00:00
img := bot . ImageAttachment {
URL : imgSrc ,
AltTxt : txt ,
}
p . Bot . Send ( c , bot . Message , message . Channel , "" , img )
return
}
p . Bot . Send ( c , bot . Message , message . Channel , txt )
}
2012-08-29 16:50:52 +00:00
// 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
2019-05-27 23:21:53 +00:00
func ( p * FactoidPlugin ) trigger ( c bot . Connector , message msg . Message ) bool {
2019-01-22 00:16:57 +00:00
minLen := p . Bot . Config ( ) . GetInt ( "Factoid.MinLen" , 4 )
2016-04-01 14:45:45 +00:00
if len ( message . Body ) > minLen || message . Command || message . Body == "..." {
2012-08-26 20:15:50 +00:00
if ok , fact := p . findTrigger ( message . Body ) ; ok {
2019-05-27 23:21:53 +00:00
p . sayFact ( c , message , * fact )
2012-08-26 20:15:50 +00:00
return true
}
2012-12-30 16:26:26 +00:00
r := strings . NewReplacer ( "'" , "" , "\"" , "" , "," , "" , "." , "" , ":" , "" ,
2013-01-07 21:08:34 +00:00
"?" , "" , "!" , "" )
2012-11-11 16:14:40 +00:00
if ok , fact := p . findTrigger ( r . Replace ( message . Body ) ) ; ok {
2019-05-27 23:21:53 +00:00
p . sayFact ( c , message , * fact )
2012-11-11 16:14:40 +00:00
return true
}
2012-08-26 20:15:50 +00:00
}
return false
2012-08-26 19:15:04 +00:00
}
2012-08-29 16:50:52 +00:00
// tellThemWhatThatWas is a hilarious name for a function.
2019-05-27 23:21:53 +00:00
func ( p * FactoidPlugin ) tellThemWhatThatWas ( c bot . Connector , message msg . Message ) bool {
2012-08-29 21:06:15 +00:00
fact := p . LastFact
var msg string
if fact == nil {
msg = "Nope."
} else {
msg = fmt . Sprintf ( "That was (#%d) '%s <%s> %s'" ,
2021-12-20 17:40:10 +00:00
fact . ID , fact . Fact , fact . Verb , fact . Tidbit )
2012-08-29 21:06:15 +00:00
}
2019-05-27 23:21:53 +00:00
p . Bot . Send ( c , bot . Message , message . Channel , msg )
2012-08-29 21:06:15 +00:00
return true
2012-08-29 16:50:52 +00:00
}
2019-05-27 23:21:53 +00:00
func ( p * FactoidPlugin ) learnAction ( c bot . Connector , message msg . Message , action string ) bool {
2012-08-29 16:50:52 +00:00
body := message . Body
parts := strings . SplitN ( body , action , 2 )
// This could fail if is were the last word or it weren't in the sentence (like no spaces)
if len ( parts ) != 2 {
return false
2012-08-27 15:34:21 +00:00
}
2012-08-29 16:50:52 +00:00
trigger := strings . TrimSpace ( parts [ 0 ] )
fact := strings . TrimSpace ( parts [ 1 ] )
action = strings . TrimSpace ( action )
if len ( trigger ) == 0 || len ( fact ) == 0 || len ( action ) == 0 {
2019-05-27 23:21:53 +00:00
p . Bot . Send ( c , bot . Message , message . Channel , "I don't want to learn that." )
2012-08-29 16:50:52 +00:00
return true
2012-08-26 19:15:04 +00:00
}
2012-08-29 16:50:52 +00:00
if len ( strings . Split ( fact , "$and" ) ) > 4 {
2019-05-27 23:21:53 +00:00
p . Bot . Send ( c , bot . Message , message . Channel , "You can't use more than 4 $and operators." )
2012-08-29 16:50:52 +00:00
return true
}
2012-08-26 19:15:04 +00:00
2012-08-29 16:50:52 +00:00
strippedaction := strings . Replace ( strings . Replace ( action , "<" , "" , 1 ) , ">" , "" , 1 )
2012-08-26 19:15:04 +00:00
2019-01-20 17:33:19 +00:00
if err := p . learnFact ( message , trigger , strippedaction , fact ) ; err != nil {
2019-05-27 23:21:53 +00:00
p . Bot . Send ( c , bot . Message , message . Channel , err . Error ( ) )
2012-08-29 16:50:52 +00:00
} else {
2019-05-27 23:21:53 +00:00
p . Bot . Send ( c , bot . Message , message . Channel , fmt . Sprintf ( "Okay, %s." , message . User . Name ) )
2012-08-29 16:50:52 +00:00
}
2012-08-26 20:35:13 +00:00
2012-08-29 16:50:52 +00:00
return true
}
2012-08-26 19:40:57 +00:00
2013-03-18 21:44:49 +00:00
// Checks body for the ~= operator returns it
func changeOperator ( body string ) string {
if strings . Contains ( body , "=~" ) {
return "=~"
} else if strings . Contains ( body , "~=" ) {
return "~="
}
return ""
2012-08-29 16:50:52 +00:00
}
2012-08-26 19:15:04 +00:00
2012-08-29 16:50:52 +00:00
// If the user requesting forget is either the owner of the last learned fact or
// an admin, it may be deleted
2019-05-27 23:21:53 +00:00
func ( p * FactoidPlugin ) forgetLastFact ( c bot . Connector , message msg . Message ) bool {
2012-08-29 16:50:52 +00:00
if p . LastFact == nil {
2019-05-27 23:21:53 +00:00
p . Bot . Send ( c , bot . Message , message . Channel , "I refuse." )
2012-08-29 16:50:52 +00:00
return true
}
2017-06-07 18:46:34 +00:00
2021-12-20 17:40:10 +00:00
err := p . LastFact . delete ( p . store )
2017-06-07 18:46:34 +00:00
if err != nil {
2019-03-07 16:35:42 +00:00
log . Error ( ) .
Err ( err ) .
Interface ( "LastFact" , p . LastFact ) .
Msg ( "Error removing fact" )
2012-08-29 16:50:52 +00:00
}
2021-12-20 17:40:10 +00:00
fmt . Printf ( "Forgot #%d: %s %s %s\n" , p . LastFact . ID , p . LastFact . Fact ,
2017-06-07 18:46:34 +00:00
p . LastFact . Verb , p . LastFact . Tidbit )
2019-05-27 23:21:53 +00:00
p . Bot . Send ( c , bot . Action , message . Channel , "hits himself over the head with a skillet" )
2017-06-07 18:46:34 +00:00
p . LastFact = nil
2012-08-26 19:15:04 +00:00
2012-08-29 16:50:52 +00:00
return true
}
// Allow users to change facts with a simple regexp
2019-05-27 23:21:53 +00:00
func ( p * FactoidPlugin ) changeFact ( c bot . Connector , message msg . Message ) bool {
2013-03-18 21:44:49 +00:00
oper := changeOperator ( message . Body )
parts := strings . SplitN ( message . Body , oper , 2 )
2012-08-29 21:06:15 +00:00
userexp := strings . TrimSpace ( parts [ 1 ] )
trigger := strings . TrimSpace ( parts [ 0 ] )
parts = strings . Split ( userexp , "/" )
2016-04-05 13:46:03 +00:00
2019-03-07 16:35:42 +00:00
log . Debug ( ) .
Str ( "trigger" , trigger ) .
Str ( "userexp" , userexp ) .
Strs ( "parts" , parts ) .
Msg ( "changefact" )
2016-04-05 13:46:03 +00:00
2012-08-29 21:06:15 +00:00
if len ( parts ) == 4 {
// replacement
if parts [ 0 ] != "s" {
2019-05-27 23:21:53 +00:00
p . Bot . Send ( c , bot . Message , message . Channel , "Nah." )
2012-08-29 21:06:15 +00:00
}
find := parts [ 1 ]
replace := parts [ 2 ]
// replacement
2021-12-20 17:40:10 +00:00
result , err := getFacts ( p . store , trigger , parts [ 1 ] )
2016-01-17 15:29:30 +00:00
if err != nil {
2019-03-07 16:35:42 +00:00
log . Error ( ) .
Err ( err ) .
Str ( "trigger" , trigger ) .
Msg ( "Error getting facts" )
2016-01-17 15:29:30 +00:00
}
2017-06-08 14:02:10 +00:00
if userexp [ len ( userexp ) - 1 ] != 'g' {
2017-06-07 18:56:14 +00:00
result = result [ : 1 ]
}
2012-08-29 21:06:15 +00:00
// make the changes
msg := fmt . Sprintf ( "Changing %d facts." , len ( result ) )
2019-05-27 23:21:53 +00:00
p . Bot . Send ( c , bot . Message , message . Channel , msg )
2012-08-29 21:06:15 +00:00
reg , err := regexp . Compile ( find )
if err != nil {
2019-05-27 23:21:53 +00:00
p . Bot . Send ( c , bot . Message , message . Channel , "I don't really want to." )
2012-08-29 21:06:15 +00:00
return false
}
for _ , fact := range result {
2016-03-29 14:20:44 +00:00
fact . Fact = reg . ReplaceAllString ( fact . Fact , replace )
fact . Fact = strings . ToLower ( fact . Fact )
fact . Verb = reg . ReplaceAllString ( fact . Verb , replace )
fact . Tidbit = reg . ReplaceAllString ( fact . Tidbit , replace )
fact . Count += 1
2019-02-15 18:22:54 +00:00
fact . Accessed = time . Now ( )
2021-12-20 17:40:10 +00:00
fact . Save ( p . store )
2012-08-29 21:06:15 +00:00
}
} else if len ( parts ) == 3 {
// search for a factoid and print it
2021-12-20 17:40:10 +00:00
result , err := getFacts ( p . store , trigger , parts [ 1 ] )
2016-01-17 15:29:30 +00:00
if err != nil {
2019-03-07 16:35:42 +00:00
log . Error ( ) .
Err ( err ) .
Str ( "trigger" , trigger ) .
Msg ( "Error getting facts" )
2019-05-27 23:21:53 +00:00
p . Bot . Send ( c , bot . Message , message . Channel , "bzzzt" )
2016-04-05 13:46:03 +00:00
return true
2016-01-17 15:29:30 +00:00
}
count := len ( result )
2016-04-05 13:46:03 +00:00
if count == 0 {
2019-05-27 23:21:53 +00:00
p . Bot . Send ( c , bot . Message , message . Channel , "I didn't find any facts like that." )
2016-04-05 13:46:03 +00:00
return true
}
if parts [ 2 ] == "g" && len ( result ) > 4 {
2012-08-29 21:06:15 +00:00
// summarize
2016-01-17 15:29:30 +00:00
result = result [ : 4 ]
2012-08-29 21:06:15 +00:00
} else {
2019-05-27 23:21:53 +00:00
p . sayFact ( c , message , * result [ 0 ] )
2012-08-29 21:06:15 +00:00
return true
}
msg := fmt . Sprintf ( "%s " , trigger )
for i , fact := range result {
if i != 0 {
msg = fmt . Sprintf ( "%s |" , msg )
}
2016-03-29 14:20:44 +00:00
msg = fmt . Sprintf ( "%s <%s> %s" , msg , fact . Verb , fact . Tidbit )
2012-08-29 21:06:15 +00:00
}
if count > 4 {
msg = fmt . Sprintf ( "%s | ...and %d others" , msg , count )
}
2019-05-27 23:21:53 +00:00
p . Bot . Send ( c , bot . Message , message . Channel , msg )
2012-08-29 21:06:15 +00:00
} else {
2019-05-27 23:21:53 +00:00
p . Bot . Send ( c , bot . Message , message . Channel , "I don't know what you mean." )
2012-08-29 21:06:15 +00:00
}
2012-08-29 16:50:52 +00:00
return true
}
2021-02-02 02:25:19 +00:00
func ( p * FactoidPlugin ) register ( ) {
p . handlers = bot . HandlerTable {
bot . HandlerSpec { Kind : bot . Message , IsCmd : true ,
Regex : regexp . MustCompile ( ` (?i)^what was that\??$ ` ) ,
Handler : func ( r bot . Request ) bool {
return p . tellThemWhatThatWas ( r . Conn , r . Msg )
} } ,
bot . HandlerSpec { Kind : bot . Message , IsCmd : true ,
Regex : regexp . MustCompile ( ` (?i)^alias (?P<from>\S+) to (?P<to>\S+)$ ` ) ,
Handler : func ( r bot . Request ) bool {
from := r . Values [ "from" ]
to := r . Values [ "to" ]
log . Debug ( ) . Msgf ( "alias: %+v" , r )
a := aliasFromStrings ( from , to )
2021-12-20 17:40:10 +00:00
if err := a . save ( p . store ) ; err != nil {
2021-02-02 02:25:19 +00:00
p . Bot . 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" )
}
return true
} } ,
bot . HandlerSpec { Kind : bot . Message , IsCmd : true ,
Regex : regexp . MustCompile ( ` (?i)^factoid$ ` ) ,
Handler : func ( r bot . Request ) bool {
fact := p . randomFact ( )
p . sayFact ( r . Conn , r . Msg , * fact )
return true
} } ,
bot . HandlerSpec { Kind : bot . Message , IsCmd : true ,
Regex : regexp . MustCompile ( ` (?i)^forget that$ ` ) ,
Handler : func ( r bot . Request ) bool {
return p . forgetLastFact ( r . Conn , r . Msg )
} } ,
bot . HandlerSpec { Kind : bot . Message , IsCmd : false ,
Regex : regexp . MustCompile ( ` .* ` ) ,
Handler : func ( r bot . Request ) bool {
message := r . Msg
c := r . Conn
log . Debug ( ) . Msgf ( "Message: %+v" , r )
// This plugin has no business with normal messages
if ! message . Command {
// look for any triggers in the db matching this message
return p . trigger ( c , message )
}
if changeOperator ( message . Body ) != "" {
return p . changeFact ( c , message )
}
action := findAction ( message . Body )
if action != "" {
return p . learnAction ( c , message , action )
}
// look for any triggers in the db matching this message
if p . trigger ( c , message ) {
return true
}
// We didn't find anything, panic!
p . Bot . Send ( c , bot . Message , message . Channel , p . NotFound [ rand . Intn ( len ( p . NotFound ) ) ] )
return true
} } ,
}
p . Bot . RegisterTable ( p , p . handlers )
2012-08-26 19:15:04 +00:00
}
// Help responds to help requests. Every plugin must implement a help function.
2019-05-27 23:21:53 +00:00
func ( p * FactoidPlugin ) help ( c bot . Connector , kind bot . Kind , message msg . Message , args ... interface { } ) 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 . Bot . Send ( c , bot . Message , message . Channel , "I can also figure out some variables including: $nonzero, $digit, $nick, and $someone." )
2019-02-05 19:41:38 +00:00
return true
2012-08-26 19:15:04 +00:00
}
2012-08-27 15:34:21 +00:00
2012-08-29 16:50:52 +00:00
// Pull a fact at random from the database
2019-02-15 18:22:54 +00:00
func ( p * FactoidPlugin ) randomFact ( ) * Factoid {
2021-12-20 17:40:10 +00:00
f , err := GetSingle ( p . store )
2012-08-27 15:34:21 +00:00
if err != nil {
2016-01-17 15:29:30 +00:00
fmt . Println ( "Error getting a fact: " , err )
2013-06-01 20:46:16 +00:00
return nil
2012-08-27 15:34:21 +00:00
}
2016-01-17 15:29:30 +00:00
return f
2012-08-27 15:34:21 +00:00
}
2012-08-29 16:50:52 +00:00
// factTimer spits out a fact at a given interval and with given probability
2019-05-27 23:21:53 +00:00
func ( p * FactoidPlugin ) factTimer ( c bot . Connector , channel string ) {
2019-01-22 00:16:57 +00:00
quoteTime := p . Bot . Config ( ) . GetInt ( "Factoid.QuoteTime" , 30 )
2019-01-21 19:24:03 +00:00
if quoteTime == 0 {
quoteTime = 30
p . Bot . Config ( ) . Set ( "Factoid.QuoteTime" , "30" )
}
duration := time . Duration ( quoteTime ) * time . Minute
2012-10-11 20:28:00 +00:00
myLastMsg := time . Now ( )
2012-08-27 15:34:21 +00:00
for {
2016-04-21 15:19:38 +00:00
time . Sleep ( time . Duration ( 5 ) * time . Second ) // why 5?
2012-10-11 20:28:00 +00:00
2013-06-17 01:03:43 +00:00
lastmsg , err := p . Bot . LastMessage ( channel )
2012-10-11 20:28:00 +00:00
if err != nil {
2016-04-21 15:19:38 +00:00
// Probably no previous message to time off of
2012-10-11 20:28:00 +00:00
continue
}
tdelta := time . Since ( lastmsg . Time )
earlier := time . Since ( myLastMsg ) > tdelta
chance := rand . Float64 ( )
2019-01-22 00:16:57 +00:00
quoteChance := p . Bot . Config ( ) . GetFloat64 ( "Factoid.QuoteChance" , 0.99 )
2019-01-21 19:24:03 +00:00
if quoteChance == 0.0 {
quoteChance = 0.99
p . Bot . Config ( ) . Set ( "Factoid.QuoteChance" , "0.99" )
}
success := chance < quoteChance
2012-10-11 20:28:00 +00:00
if success && tdelta > duration && earlier {
2012-08-27 15:34:21 +00:00
fact := p . randomFact ( )
if fact == nil {
2019-03-07 16:35:42 +00:00
log . Debug ( ) . Msg ( "Didn't find a random fact to say" )
2012-08-27 15:34:21 +00:00
continue
}
2016-03-30 14:00:20 +00:00
users := p . Bot . Who ( channel )
2012-08-27 15:34:21 +00:00
// we need to fabricate a message so that bot.Filter can operate
2016-04-01 14:20:03 +00:00
message := msg . Message {
2016-03-30 14:00:20 +00:00
User : & users [ rand . Intn ( len ( users ) ) ] ,
2012-08-27 15:34:21 +00:00
Channel : channel ,
}
2019-05-27 23:21:53 +00:00
p . sayFact ( c , message , * fact )
2012-10-11 20:28:00 +00:00
myLastMsg = time . Now ( )
2012-08-27 15:34:21 +00:00
}
}
}