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 (
2016-01-17 15:29:30 +00:00
"database/sql"
2012-08-26 19:15:04 +00:00
"fmt"
2022-05-31 15:19:37 +00:00
"github.com/velour/catbase/config"
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-03-19 18:02:46 +00:00
"github.com/jmoiron/sqlx"
2019-12-30 15:26:17 +00:00
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
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 {
2022-05-31 15:19:37 +00:00
b bot . Bot
c * config . Config
lastFact * Factoid
2016-03-19 18:02:46 +00:00
db * sqlx . DB
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 {
2022-05-31 15:19:37 +00:00
b : botInst ,
c : botInst . Config ( ) ,
2016-03-30 14:00:20 +00:00
db : botInst . DB ( ) ,
2016-01-17 15:29:30 +00:00
}
2019-05-27 23:21:53 +00:00
c := botInst . DefaultConnector ( )
2017-10-24 19:14:01 +00:00
if _ , err := p . db . Exec ( ` create table if not exists factoid (
2016-01-17 15:29:30 +00:00
id integer primary key ,
fact string ,
tidbit string ,
verb string ,
owner string ,
created integer ,
accessed integer ,
count integer
2017-10-24 19:14:01 +00:00
) ; ` ) ; err != nil {
2019-03-07 16:35:42 +00:00
log . Fatal ( ) . Err ( err )
2017-10-24 19:14:01 +00:00
}
if _ , err := p . db . Exec ( ` create table if not exists factoid_alias (
fact string ,
next string ,
primary key ( fact , next )
) ; ` ) ; err != nil {
2019-03-07 16:35:42 +00:00
log . Fatal ( ) . Err ( err )
2012-08-26 19:15:04 +00:00
}
2016-01-17 15:29:30 +00:00
2022-05-31 15:19:37 +00:00
for _ , channel := range p . c . 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 )
2022-05-31 15:19:37 +00:00
if ok , fact := p . findTrigger ( p . c . 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
2016-01-17 15:29:30 +00:00
var count sql . NullInt64
err := p . db . QueryRow ( ` select count ( * ) from factoid
where fact = ? and verb = ? and tidbit = ? ` ,
fact , verb , tidbit ) . Scan ( & count )
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?" )
2016-01-17 15:29:30 +00:00
} else if count . Valid && count . Int64 != 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
}
2022-05-31 15:19:37 +00:00
p . lastFact = & n
2019-02-15 18:22:54 +00:00
err = n . Save ( p . db )
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 ) {
2022-10-25 16:09:32 +00:00
fact = strings . TrimSpace ( strings . ToLower ( fact ) )
2012-08-26 19:15:04 +00:00
2019-02-15 18:22:54 +00:00
f , err := GetSingleFact ( p . db , fact )
2016-01-17 15:29:30 +00:00
if err != nil {
2022-10-25 17:11:15 +00:00
log . Error ( ) . Err ( err ) . Msg ( "GetSingleFact" )
2017-10-24 19:14:01 +00:00
return findAlias ( p . db , 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 ) {
2022-05-31 15:19:37 +00:00
msg := p . b . Filter ( message , fact . Tidbit )
full := p . b . 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" {
2022-05-31 15:19:37 +00:00
p . b . Send ( c , bot . Action , message . Channel , msg )
2019-01-20 17:33:19 +00:00
} else if fact . Verb == "react" {
2022-05-31 15:19:37 +00:00
p . b . Send ( c , bot . Reaction , message . Channel , msg , message )
2016-03-29 14:20:44 +00:00
} else if fact . Verb == "reply" {
2022-05-31 15:19:37 +00:00
p . b . 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 {
2022-05-31 15:19:37 +00:00
p . b . 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
2019-02-15 18:22:54 +00:00
err := fact . Save ( p . db )
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
}
2022-05-31 15:19:37 +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 ,
}
2022-05-31 15:19:37 +00:00
p . b . Send ( c , bot . Message , message . Channel , "" , img )
2019-12-30 15:26:17 +00:00
return
}
2022-05-31 15:19:37 +00:00
p . b . Send ( c , bot . Message , message . Channel , txt )
2019-12-30 15:26:17 +00:00
}
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 {
2022-05-31 15:19:37 +00:00
minLen := p . c . 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 {
2022-05-31 15:19:37 +00:00
fact := p . lastFact
2012-08-29 21:06:15 +00:00
var msg string
if fact == nil {
msg = "Nope."
} else {
msg = fmt . Sprintf ( "That was (#%d) '%s <%s> %s'" ,
2019-02-15 18:22:54 +00:00
fact . ID . Int64 , fact . Fact , fact . Verb , fact . Tidbit )
2012-08-29 21:06:15 +00:00
}
2022-05-31 15:19:37 +00:00
p . b . 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 {
2022-05-31 15:19:37 +00:00
p . b . 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 {
2022-05-31 15:19:37 +00:00
p . b . 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 {
2022-05-31 15:19:37 +00:00
p . b . Send ( c , bot . Message , message . Channel , err . Error ( ) )
2012-08-29 16:50:52 +00:00
} else {
2022-05-31 15:19:37 +00:00
p . b . 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 {
2022-05-31 15:19:37 +00:00
if p . lastFact == nil {
p . b . 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
2022-05-31 15:19:37 +00:00
err := p . lastFact . delete ( p . db )
2017-06-07 18:46:34 +00:00
if err != nil {
2019-03-07 16:35:42 +00:00
log . Error ( ) .
Err ( err ) .
2022-05-31 15:19:37 +00:00
Interface ( "lastFact" , p . lastFact ) .
2019-03-07 16:35:42 +00:00
Msg ( "Error removing fact" )
2012-08-29 16:50:52 +00:00
}
2022-05-31 15:19:37 +00:00
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
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" {
2022-05-31 15:19:37 +00:00
p . b . Send ( c , bot . Message , message . Channel , "Nah." )
2012-08-29 21:06:15 +00:00
}
find := parts [ 1 ]
replace := parts [ 2 ]
// replacement
2016-04-05 13:46:03 +00:00
result , err := getFacts ( p . db , 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 ) )
2022-05-31 15:19:37 +00:00
p . b . Send ( c , bot . Message , message . Channel , msg )
2012-08-29 21:06:15 +00:00
reg , err := regexp . Compile ( find )
if err != nil {
2022-05-31 15:19:37 +00:00
p . b . 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 ( )
fact . Save ( p . db )
2012-08-29 21:06:15 +00:00
}
} else if len ( parts ) == 3 {
// search for a factoid and print it
2016-04-05 13:46:03 +00:00
result , err := getFacts ( p . db , 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" )
2022-05-31 15:19:37 +00:00
p . b . 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 {
2022-05-31 15:19:37 +00:00
p . b . 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 )
}
2022-05-31 15:19:37 +00:00
p . b . Send ( c , bot . Message , message . Channel , msg )
2012-08-29 21:06:15 +00:00
} else {
2022-05-31 15:19:37 +00:00
p . b . 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 )
if err := a . save ( p . db ) ; err != nil {
2022-05-31 15:19:37 +00:00
p . b . Send ( r . Conn , bot . Message , r . Msg . Channel , err . Error ( ) )
2021-02-02 02:25:19 +00:00
} else {
2022-05-31 15:19:37 +00:00
p . b . Send ( r . Conn , bot . Action , r . Msg . Channel , "learns a new synonym" )
2021-02-02 02:25:19 +00:00
}
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 )
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
}
2022-05-31 15:19:37 +00:00
notFound := p . c . GetArray ( "fact.notfound" , [ ] string {
"I don't know." ,
"NONONONO" ,
"((" ,
"*pukes*" ,
"NOPE! NOPE! NOPE!" ,
"One time, I learned how to jump rope." ,
} )
2021-02-02 02:25:19 +00:00
// We didn't find anything, panic!
2022-05-31 15:19:37 +00:00
p . b . Send ( c , bot . Message , message . Channel , notFound [ rand . Intn ( len ( notFound ) ) ] )
2021-02-02 02:25:19 +00:00
return true
} } ,
}
2022-05-31 15:19:37 +00:00
p . b . RegisterTable ( p , p . handlers )
2012-08-26 19:15:04 +00:00
}
// Help responds to help requests. Every plugin must implement a help function.
2022-03-22 01:32:44 +00:00
func ( p * FactoidPlugin ) help ( c bot . Connector , kind bot . Kind , message msg . Message , args ... any ) bool {
2022-05-31 15:19:37 +00:00
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 . b . 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 {
f , err := GetSingle ( p . db )
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 ) {
2022-05-31 15:19:37 +00:00
quoteTime := p . c . GetInt ( "Factoid.QuoteTime" , 30 )
2019-01-21 19:24:03 +00:00
if quoteTime == 0 {
quoteTime = 30
2022-05-31 15:19:37 +00:00
p . c . Set ( "Factoid.QuoteTime" , "30" )
2019-01-21 19:24:03 +00:00
}
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 {
2022-05-31 15:19:37 +00:00
time . Sleep ( time . Duration ( 5 ) * time . Second ) // why 5? // no seriously, why 5?
2012-10-11 20:28:00 +00:00
2022-05-31 15:19:37 +00:00
lastmsg , err := p . b . 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 ( )
2022-05-31 15:19:37 +00:00
quoteChance := p . c . GetFloat64 ( "Factoid.QuoteChance" , 0.99 )
2019-01-21 19:24:03 +00:00
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
}
2022-05-31 15:19:37 +00:00
users := p . b . Who ( channel )
2016-03-30 14:00:20 +00:00
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
}
}
}