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"
2019-05-28 03:14:05 +00:00
"encoding/json"
2012-08-26 19:15:04 +00:00
"fmt"
2013-06-01 17:10:15 +00:00
"html/template"
2012-08-26 19:15:04 +00:00
"math/rand"
2013-06-01 17:10:15 +00:00
"net/http"
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"
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
// factoid stores info about our factoid for lookup and later interaction
2019-02-15 18:22:54 +00:00
type Factoid struct {
ID sql . NullInt64
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
}
2017-10-24 19:14:01 +00:00
type alias struct {
Fact string
Next string
}
2019-02-15 18:22:54 +00:00
func ( a * alias ) resolve ( db * sqlx . DB ) ( * Factoid , error ) {
2017-10-24 19:14:01 +00:00
// 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
2019-02-15 18:22:54 +00:00
fact , err := GetSingleFact ( db , 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
}
return next . resolve ( db )
}
2019-02-15 18:22:54 +00:00
func findAlias ( db * sqlx . DB , fact string ) ( bool , * Factoid ) {
2017-10-24 19:14:01 +00:00
q := ` select * from factoid_alias where fact=? `
var a alias
err := db . Get ( & a , q , fact )
if err != nil {
return false , nil
}
2017-10-25 21:55:55 +00:00
f , err := a . resolve ( db )
return err == nil , f
2017-10-24 19:14:01 +00:00
}
func ( a * alias ) save ( db * sqlx . DB ) error {
2017-10-25 22:16:33 +00:00
q := ` select * from factoid_alias where fact=? `
var offender alias
err := db . Get ( & offender , q , a . Next )
if err == nil {
2017-10-25 21:55:55 +00:00
return fmt . Errorf ( "DANGER: an opposite alias already exists" )
}
2017-10-25 22:16:33 +00:00
_ , err = a . resolve ( db )
2017-10-25 21:55:55 +00:00
if err != nil {
return fmt . Errorf ( "there is no fact at that destination" )
}
2017-10-25 22:16:33 +00:00
q = ` insert or replace into factoid_alias (fact, next) values (?, ?) `
2017-10-25 21:55:55 +00:00
_ , err = db . Exec ( q , a . Fact , a . Next )
2017-10-24 19:14:01 +00:00
if err != nil {
return err
}
return nil
}
func aliasFromStrings ( from , to string ) * alias {
return & alias { from , to }
}
2019-02-15 18:22:54 +00:00
func ( f * Factoid ) Save ( db * sqlx . DB ) error {
2016-01-17 15:29:30 +00:00
var err error
2019-02-15 18:22:54 +00:00
if f . ID . Valid {
2016-01-17 15:29:30 +00:00
// update
_ , err = db . Exec ( ` update factoid set
fact = ? ,
tidbit = ? ,
verb = ? ,
owner = ? ,
accessed = ? ,
count = ?
where id = ? ` ,
2016-03-29 14:20:44 +00:00
f . Fact ,
f . Tidbit ,
f . Verb ,
f . Owner ,
2019-02-15 18:22:54 +00:00
f . Accessed . Unix ( ) ,
2016-03-29 14:20:44 +00:00
f . Count ,
2019-02-15 18:22:54 +00:00
f . ID . Int64 )
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 ( )
2016-01-17 15:29:30 +00:00
// insert
res , err := db . Exec ( ` insert into factoid (
fact ,
tidbit ,
verb ,
owner ,
created ,
accessed ,
count
) values ( ? , ? , ? , ? , ? , ? , ? ) ; ` ,
2016-03-29 14:20:44 +00:00
f . Fact ,
f . Tidbit ,
f . Verb ,
f . Owner ,
2019-02-15 18:22:54 +00:00
f . Created . Unix ( ) ,
f . Accessed . Unix ( ) ,
2016-03-29 14:20:44 +00:00
f . Count ,
2016-01-17 15:29:30 +00:00
)
if err != nil {
return err
}
id , err := res . LastInsertId ( )
// hackhackhack?
2019-02-15 18:22:54 +00:00
f . ID . Int64 = id
f . ID . Valid = true
2016-01-17 15:29:30 +00:00
}
return err
}
2019-02-15 18:22:54 +00:00
func ( f * Factoid ) delete ( db * sqlx . DB ) error {
2016-01-17 15:29:30 +00:00
var err error
2019-02-15 18:22:54 +00:00
if f . ID . Valid {
_ , err = db . Exec ( ` delete from factoid where id=? ` , f . ID )
2016-01-17 15:29:30 +00:00
}
2019-02-15 18:22:54 +00:00
f . ID . Valid = false
2016-01-17 15:29:30 +00:00
return err
}
2019-02-15 18:22:54 +00:00
func getFacts ( db * sqlx . DB , fact string , tidbit string ) ( [ ] * Factoid , error ) {
var fs [ ] * Factoid
2016-04-15 18:48:35 +00:00
query := ` select
2016-01-17 15:29:30 +00:00
id ,
fact ,
tidbit ,
verb ,
owner ,
created ,
accessed ,
count
from factoid
2016-04-15 18:48:35 +00:00
where fact like ?
and tidbit like ? ; `
rows , err := db . Query ( query ,
"%" + fact + "%" , "%" + 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
for rows . Next ( ) {
2019-02-15 18:22:54 +00:00
var f Factoid
2016-01-17 15:29:30 +00:00
var tmpCreated int64
var tmpAccessed int64
err := rows . Scan (
2019-02-15 18:22:54 +00:00
& f . ID ,
2016-03-29 14:20:44 +00:00
& f . Fact ,
& f . Tidbit ,
& f . Verb ,
& f . Owner ,
2016-01-17 15:29:30 +00:00
& tmpCreated ,
& tmpAccessed ,
2016-03-29 14:20:44 +00:00
& f . Count ,
2016-01-17 15:29:30 +00:00
)
if err != nil {
return nil , err
}
2019-02-15 18:22:54 +00:00
f . Created = time . Unix ( tmpCreated , 0 )
f . Accessed = time . Unix ( tmpAccessed , 0 )
2016-01-17 15:29:30 +00:00
fs = append ( fs , & f )
}
return fs , err
}
2019-02-15 18:22:54 +00:00
func GetSingle ( db * sqlx . DB ) ( * Factoid , error ) {
var f Factoid
2016-01-17 15:29:30 +00:00
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 (
2019-02-15 18:22:54 +00:00
& f . ID ,
2016-03-29 14:20:44 +00:00
& f . Fact ,
& f . Tidbit ,
& f . Verb ,
& f . Owner ,
2016-01-17 15:29:30 +00:00
& tmpCreated ,
& tmpAccessed ,
2016-03-29 14:20:44 +00:00
& f . Count ,
2016-01-17 15:29:30 +00:00
)
2019-02-15 18:22:54 +00:00
f . Created = time . Unix ( tmpCreated , 0 )
f . Accessed = time . Unix ( tmpAccessed , 0 )
2016-01-17 15:29:30 +00:00
return & f , err
}
2019-02-15 18:22:54 +00:00
func GetSingleFact ( db * sqlx . DB , fact string ) ( * Factoid , error ) {
var f Factoid
2016-01-17 15:29:30 +00:00
var tmpCreated int64
var tmpAccessed int64
err := db . QueryRow ( ` select
id ,
fact ,
tidbit ,
verb ,
owner ,
created ,
accessed ,
count
from factoid
2016-04-08 16:18:34 +00:00
where fact like ?
2016-01-17 15:29:30 +00:00
order by random ( ) limit 1 ; ` ,
fact ) . Scan (
2019-02-15 18:22:54 +00:00
& f . ID ,
2016-03-29 14:20:44 +00:00
& f . Fact ,
& f . Tidbit ,
& f . Verb ,
& f . Owner ,
2016-01-17 15:29:30 +00:00
& tmpCreated ,
& tmpAccessed ,
2016-03-29 14:20:44 +00:00
& f . Count ,
2016-01-17 15:29:30 +00:00
)
2019-02-15 18:22:54 +00:00
f . Created = time . Unix ( tmpCreated , 0 )
f . Accessed = time . Unix ( tmpAccessed , 0 )
2016-01-17 15:29:30 +00:00
return & f , err
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
2016-03-19 18:02:46 +00:00
db * sqlx . DB
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." ,
} ,
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
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
2019-02-05 19:41:38 +00:00
botInst . Register ( p , bot . Message , p . message )
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
}
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 ) {
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
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 {
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 ) {
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 )
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
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
}
2012-08-29 16:50:52 +00:00
p . LastFact = & fact
2012-08-27 15:34:21 +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 {
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'" ,
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
}
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
err := p . LastFact . delete ( p . db )
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
}
2019-02-15 18:22:54 +00:00
fmt . Printf ( "Forgot #%d: %s %s %s\n" , p . LastFact . ID . Int64 , 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
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 ) )
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 ( )
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" )
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
}
// Message responds to the bot hook on recieving messages.
// This function returns true if the plugin responds in a meaningful way to the users message.
// Otherwise, the function returns false and the bot continues execution of other plugins.
2019-05-27 23:21:53 +00:00
func ( p * FactoidPlugin ) message ( c bot . Connector , kind bot . Kind , message msg . Message , args ... interface { } ) bool {
2012-08-29 16:50:52 +00:00
if strings . ToLower ( message . Body ) == "what was that?" {
2019-05-27 23:21:53 +00:00
return p . tellThemWhatThatWas ( c , message )
2012-08-29 16:50:52 +00:00
}
// This plugin has no business with normal messages
if ! message . Command {
// look for any triggers in the db matching this message
2019-05-27 23:21:53 +00:00
return p . trigger ( c , message )
2012-08-29 16:50:52 +00:00
}
2017-10-24 19:14:01 +00:00
if strings . HasPrefix ( strings . ToLower ( message . Body ) , "alias" ) {
2019-03-07 16:35:42 +00:00
log . Debug ( ) .
Str ( "alias" , message . Body ) .
Msg ( "Trying to learn an alias" )
2017-10-24 19:14:01 +00:00
m := strings . TrimPrefix ( message . Body , "alias " )
parts := strings . SplitN ( m , "->" , 2 )
if len ( parts ) != 2 {
2019-05-27 23:21:53 +00:00
p . Bot . Send ( c , bot . Message , message . Channel , "If you want to alias something, use: `alias this -> that`" )
2017-10-24 19:14:01 +00:00
return true
}
a := aliasFromStrings ( strings . TrimSpace ( parts [ 1 ] ) , strings . TrimSpace ( parts [ 0 ] ) )
2017-10-25 21:55:55 +00:00
if err := a . save ( p . db ) ; err != nil {
2019-05-27 23:21:53 +00:00
p . Bot . Send ( c , bot . Message , message . Channel , err . Error ( ) )
2017-10-25 21:55:55 +00:00
} else {
2019-05-27 23:21:53 +00:00
p . Bot . Send ( c , bot . Action , message . Channel , "learns a new synonym" )
2017-10-25 21:55:55 +00:00
}
2017-10-24 19:14:01 +00:00
return true
}
2013-06-01 20:46:16 +00:00
if strings . ToLower ( message . Body ) == "factoid" {
if fact := p . randomFact ( ) ; fact != nil {
2019-05-27 23:21:53 +00:00
p . sayFact ( c , message , * fact )
2013-06-01 20:46:16 +00:00
return true
}
2019-03-07 16:35:42 +00:00
log . Debug ( ) . Msg ( "Got a nil fact." )
2013-06-01 20:46:16 +00:00
}
2013-01-22 19:46:51 +00:00
if strings . ToLower ( message . Body ) == "forget that" {
2019-05-27 23:21:53 +00:00
return p . forgetLastFact ( c , message )
2012-08-29 16:50:52 +00:00
}
2013-03-18 21:44:49 +00:00
if changeOperator ( message . Body ) != "" {
2019-05-27 23:21:53 +00:00
return p . changeFact ( c , message )
2012-08-29 16:50:52 +00:00
}
action := findAction ( message . Body )
if action != "" {
2019-05-27 23:21:53 +00:00
return p . learnAction ( c , message , action )
2012-08-26 19:15:04 +00:00
}
// look for any triggers in the db matching this message
2019-05-27 23:21:53 +00:00
if p . trigger ( c , message ) {
2012-08-26 19:15:04 +00:00
return true
}
2012-08-29 16:50:52 +00:00
// We didn't find anything, panic!
2019-05-27 23:21:53 +00:00
p . Bot . Send ( c , bot . Message , message . Channel , p . NotFound [ rand . Intn ( len ( p . NotFound ) ) ] )
2012-08-26 19:15:04 +00:00
return true
}
// 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 {
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 ) {
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
}
}
}
2013-05-08 00:08:18 +00:00
2013-06-01 17:10:15 +00:00
// Register any web URLs desired
2019-02-15 18:22:54 +00:00
func ( p * FactoidPlugin ) registerWeb ( ) {
2019-05-28 03:14:05 +00:00
http . HandleFunc ( "/factoid/api" , p . serveAPI )
2013-06-01 17:10:15 +00:00
http . HandleFunc ( "/factoid/req" , p . serveQuery )
2013-06-01 18:40:06 +00:00
http . HandleFunc ( "/factoid" , p . serveQuery )
2019-02-07 16:30:42 +00:00
p . Bot . RegisterWeb ( "/factoid" , "Factoid" )
2013-06-01 17:10:15 +00:00
}
2013-06-01 21:24:05 +00:00
func linkify ( text string ) template . HTML {
2016-05-11 16:10:15 +00:00
parts := strings . Fields ( text )
2013-06-01 21:24:05 +00:00
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 , " " ) )
}
2019-05-28 03:14:05 +00:00
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 )
}
2013-06-01 21:24:05 +00:00
2019-02-15 18:22:54 +00:00
func ( p * FactoidPlugin ) serveQuery ( w http . ResponseWriter , r * http . Request ) {
2019-05-28 03:14:05 +00:00
fmt . Fprint ( w , factoidIndex )
}