Adde some framework for editing and deleting facts

This commit is contained in:
Chris Sexton 2012-08-29 12:50:52 -04:00
parent 24a879285e
commit 34e6f65b1b
1 changed files with 108 additions and 43 deletions

View File

@ -11,7 +11,8 @@ import (
"time" "time"
) )
// This is a factoid plugin to serve as an example and quick copy/paste for new plugins. // 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
// factoid stores info about our factoid for lookup and later interaction // factoid stores info about our factoid for lookup and later interaction
type Factoid struct { type Factoid struct {
@ -27,11 +28,13 @@ type Factoid struct {
AccessCount int AccessCount int
} }
// FactoidPlugin provides the necessary plugin-wide needs
type FactoidPlugin struct { type FactoidPlugin struct {
Bot *bot.Bot Bot *bot.Bot
Coll *mgo.Collection Coll *mgo.Collection
NotFound []string NotFound []string
LastFact Factoid LastFact *Factoid
LastLearned *Factoid
} }
// NewFactoidPlugin creates a new FactoidPlugin with the Plugin interface // NewFactoidPlugin creates a new FactoidPlugin with the Plugin interface
@ -55,7 +58,8 @@ func NewFactoidPlugin(bot *bot.Bot) *FactoidPlugin {
return p return p
} }
func (p *FactoidPlugin) findAction(message string) string { // findAction simply regexes a string for the action verb
func findAction(message string) string {
r, err := regexp.Compile("<.+?>| is | are ") r, err := regexp.Compile("<.+?>| is | are ")
if err != nil { if err != nil {
panic(err) panic(err)
@ -64,6 +68,8 @@ func (p *FactoidPlugin) findAction(message string) string {
return action return action
} }
// learnFact assumes we have a learning situation and inserts a new fact
// into the database
func (p *FactoidPlugin) learnFact(message bot.Message, trigger, operator, fact string) bool { func (p *FactoidPlugin) learnFact(message bot.Message, trigger, operator, fact string) bool {
// if it's an action, we only want the fact part of it in the fulltext // if it's an action, we only want the fact part of it in the fulltext
full := fact full := fact
@ -94,9 +100,12 @@ func (p *FactoidPlugin) learnFact(message bot.Message, trigger, operator, fact s
CreatedBy: message.User.Name, CreatedBy: message.User.Name,
} }
p.Coll.Insert(newfact) p.Coll.Insert(newfact)
p.LastLearned = &newfact
fmt.Println(newfact.Id)
return true return true
} }
// findTrigger checks to see if a given string is a trigger or not
func (p *FactoidPlugin) findTrigger(message string) (bool, *Factoid) { func (p *FactoidPlugin) findTrigger(message string) (bool, *Factoid) {
var results []Factoid var results []Factoid
iter := p.Coll.Find(bson.M{"trigger": strings.ToLower(message)}).Iter() iter := p.Coll.Find(bson.M{"trigger": strings.ToLower(message)}).Iter()
@ -114,6 +123,8 @@ func (p *FactoidPlugin) findTrigger(message string) (bool, *Factoid) {
return true, &fact return true, &fact
} }
// sayFact spits out a fact to the channel and updates the fact in the database
// with new time and count information
func (p *FactoidPlugin) sayFact(message bot.Message, fact Factoid) { func (p *FactoidPlugin) sayFact(message bot.Message, fact Factoid) {
msg := p.Bot.Filter(message, fact.FullText) msg := p.Bot.Filter(message, fact.FullText)
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++ {
@ -137,9 +148,11 @@ func (p *FactoidPlugin) sayFact(message bot.Message, fact Factoid) {
fmt.Printf("Could not update fact.\n") fmt.Printf("Could not update fact.\n")
fmt.Printf("%#v\n", fact) fmt.Printf("%#v\n", fact)
} }
p.LastFact = fact p.LastFact = &fact
} }
// trigger checks the message for its fitness to be a factoid and then hauls
// the message off to sayFact for processing if it is in fact a trigger
func (p *FactoidPlugin) trigger(message bot.Message) bool { func (p *FactoidPlugin) trigger(message bot.Message) bool {
if len(message.Body) > 4 || message.Command || message.Body == "..." { if len(message.Body) > 4 || message.Command || message.Body == "..." {
if ok, fact := p.findTrigger(message.Body); ok { if ok, fact := p.findTrigger(message.Body); ok {
@ -151,28 +164,17 @@ func (p *FactoidPlugin) trigger(message bot.Message) bool {
return false return false
} }
// Message responds to the bot hook on recieving messages. // tellThemWhatThatWas is a hilarious name for a function.
// This function returns true if the plugin responds in a meaningful way to the users message. func (p *FactoidPlugin) tellThemWhatThatWas(message bot.Message) bool {
// Otherwise, the function returns false and the bot continues execution of other plugins.
func (p *FactoidPlugin) Message(message bot.Message) bool {
// This bot does not reply to anything
body := strings.TrimSpace(message.Body)
if strings.ToLower(message.Body) == "what was that?" {
fact := p.LastFact fact := p.LastFact
msg := fmt.Sprintf("That was (#%d) '%s <%s> %s'", fact.Id, fact.Trigger, fact.Operator, fact.Action) msg := fmt.Sprintf("That was (#%d) '%s <%s> %s'", fact.Id, fact.Trigger, fact.Operator, fact.Action)
p.Bot.SendMessage(message.Channel, msg) p.Bot.SendMessage(message.Channel, msg)
return true return true
} }
// This plugin has no business with normal messages func (p *FactoidPlugin) learnAction(message bot.Message, action string) bool {
if !message.Command { body := message.Body
// look for any triggers in the db matching this message
return p.trigger(message)
}
action := p.findAction(body)
if action != "" {
parts := strings.SplitN(body, action, 2) 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) // This could fail if is were the last word or it weren't in the sentence (like no spaces)
if len(parts) != 2 { if len(parts) != 2 {
@ -181,7 +183,7 @@ func (p *FactoidPlugin) Message(message bot.Message) bool {
trigger := strings.TrimSpace(parts[0]) trigger := strings.TrimSpace(parts[0])
fact := strings.TrimSpace(parts[1]) fact := strings.TrimSpace(parts[1])
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.SendMessage(message.Channel, "I don't want to learn that.") p.Bot.SendMessage(message.Channel, "I don't want to learn that.")
@ -204,11 +206,73 @@ func (p *FactoidPlugin) Message(message bot.Message) bool {
return true return true
} }
// Checks body for the ~= operator
func changeOperator(body string) bool {
return strings.Contains(body, "~=")
}
// If the user requesting forget is either the owner of the last learned fact or
// an admin, it may be deleted
func (p *FactoidPlugin) forgetLastFact(message bot.Message) bool {
if p.LastFact == nil {
p.Bot.SendMessage(message.Channel, "I don't remember anything.")
return true
}
if message.User.Admin || message.User.Name == p.LastFact.CreatedBy {
p.Bot.SendMessage(message.Channel, "Forgetting a fact")
err := p.Coll.Remove(bson.M{"_id": p.LastFact.Id})
if err != nil {
panic(err)
}
fmt.Println(p.LastFact.Id)
p.Bot.SendMessage(message.Channel, "I'll just look the other way.")
p.LastFact = nil
} else {
p.Bot.SendMessage(message.Channel, "You don't own that fact.")
}
return true
}
// Allow users to change facts with a simple regexp
func (p *FactoidPlugin) changeFact(message bot.Message) bool {
p.Bot.SendMessage(message.Channel, "Okay, changing fact.")
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.
func (p *FactoidPlugin) Message(message bot.Message) bool {
if strings.ToLower(message.Body) == "what was that?" {
return p.tellThemWhatThatWas(message)
}
// This plugin has no business with normal messages
if !message.Command {
// look for any triggers in the db matching this message
return p.trigger(message)
}
if message.Body == "forget that" {
return p.forgetLastFact(message)
}
if changeOperator(message.Body) {
return p.changeFact(message)
}
action := findAction(message.Body)
if action != "" {
return p.learnAction(message, action)
}
// look for any triggers in the db matching this message // look for any triggers in the db matching this message
if p.trigger(message) { if p.trigger(message) {
return true return true
} }
// We didn't find anything, panic!
p.Bot.SendMessage(message.Channel, p.NotFound[rand.Intn(len(p.NotFound))]) p.Bot.SendMessage(message.Channel, p.NotFound[rand.Intn(len(p.NotFound))])
return true return true
} }
@ -217,7 +281,6 @@ func (p *FactoidPlugin) Message(message bot.Message) bool {
// than the fact that the Plugin interface demands it exist. This may be deprecated at a later // than the fact that the Plugin interface demands it exist. This may be deprecated at a later
// date. // date.
func (p *FactoidPlugin) LoadData() { func (p *FactoidPlugin) LoadData() {
// This bot has no data to load
p.Coll = p.Bot.Db.C("factoid") p.Coll = p.Bot.Db.C("factoid")
rand.Seed(time.Now().Unix()) rand.Seed(time.Now().Unix())
} }
@ -233,6 +296,7 @@ func (p *FactoidPlugin) Event(kind string, message bot.Message) bool {
return false return false
} }
// Pull a fact at random from the database
func (p *FactoidPlugin) randomFact() *Factoid { func (p *FactoidPlugin) randomFact() *Factoid {
var results []Factoid var results []Factoid
iter := p.Coll.Find(bson.M{}).Iter() iter := p.Coll.Find(bson.M{}).Iter()
@ -250,6 +314,7 @@ func (p *FactoidPlugin) randomFact() *Factoid {
return &fact return &fact
} }
// factTimer spits out a fact at a given interval and with given probability
func (p *FactoidPlugin) factTimer(channel string) { func (p *FactoidPlugin) factTimer(channel string) {
for { for {
time.Sleep(time.Duration(p.Bot.Config.QuoteTime) * time.Minute) time.Sleep(time.Duration(p.Bot.Config.QuoteTime) * time.Minute)