// © 2013 the CatBase Authors under the WTFPL. See AUTHORS for the list of authors. package first import ( "database/sql" "fmt" "log" "regexp" "strings" "time" "github.com/jmoiron/sqlx" "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" ) // This is a first plugin to serve as an example and quick copy/paste for new plugins. type FirstPlugin struct { First *FirstEntry Bot bot.Bot db *sqlx.DB } type FirstEntry struct { id int64 day time.Time time time.Time body string nick string saved bool } // Insert or update the first entry func (fe *FirstEntry) save(db *sqlx.DB) error { if _, err := db.Exec(`insert into first (day, time, body, nick) values (?, ?, ?, ?)`, fe.day.Unix(), fe.time.Unix(), fe.body, fe.nick, ); err != nil { return err } return nil } // NewFirstPlugin creates a new FirstPlugin with the Plugin interface func New(b bot.Bot) *FirstPlugin { _, err := b.DB().Exec(`create table if not exists last ( id integer primary key, day integer, time integer, body string, nick string );`) if err != nil { log.Fatal("Could not create first table: ", err) } _, err = b.DB().Exec(`create table if not exists first ( id integer primary key, day integer, time integer, body string, nick string );`) if err != nil { log.Fatal("Could not create first table: ", err) } log.Println("First plugin initialized with day:", midnight(time.Now())) first, _, err := getLastFirst(b.DB()) if err != nil { log.Fatal("Could not initialize first plugin: ", err) } return &FirstPlugin{ Bot: b, db: b.DB(), First: first, } } func getLastFirst(db *sqlx.DB) (*FirstEntry, *FirstEntry, error) { // Get last first entry var id sql.NullInt64 var day sql.NullInt64 var timeEntered sql.NullInt64 var body sql.NullString var nick sql.NullString err := db.QueryRow(`select id, max(day), time, body, nick from first limit 1; `).Scan( &id, &day, &timeEntered, &body, &nick, ) switch { case err == sql.ErrNoRows || !id.Valid: log.Println("No previous first entries") return nil, nil, nil case err != nil: log.Println("Error on first query row: ", err) return nil, nil, err } log.Println(id, day, timeEntered, body, nick) first := &FirstEntry{ id: id.Int64, day: time.Unix(day.Int64, 0), time: time.Unix(timeEntered.Int64, 0), body: body.String, nick: nick.String, saved: true, } err = db.QueryRow(`select id, max(day), time, body, nick from last limit 1; `).Scan( &id, &day, &timeEntered, &body, &nick, ) switch { case err == sql.ErrNoRows || !id.Valid: log.Println("No previous last entries") return nil, nil, nil case err != nil: log.Println("Error on last query row: ", err) return nil, nil, err } log.Println(id, day, timeEntered, body, nick) last := &FirstEntry{ id: id.Int64, day: time.Unix(day.Int64, 0), time: time.Unix(timeEntered.Int64, 0), body: body.String, nick: nick.String, saved: true, } return first, last, nil } func midnight(t time.Time) time.Time { y, m, d := t.Date() return time.Date(y, m, d, 0, 0, 0, 0, time.UTC) } func isToday(t time.Time) bool { t0 := midnight(t) return t0.Before(midnight(time.Now())) } // 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 *FirstPlugin) Message(message msg.Message) bool { // This bot does not reply to anything if p.First == nil && p.allowed(message) { log.Printf("No previous first. Recording new first: %s", message.Body) p.recordFirst(message) return false } else if p.First != nil { if isToday(p.First.time) && p.allowed(message) { log.Printf("Recording first: %s - %v vs %v", message.Body, p.First.time, time.Now()) p.recordFirst(message) return false } } r := strings.NewReplacer("'", "", "\"", "", ",", "", ".", "", ":", "", "?", "", "!", "") msg := strings.ToLower(message.Body) if r.Replace(msg) == "whos on first" { p.announceFirst(message) return true } return false } func (p *FirstPlugin) allowed(message msg.Message) bool { for _, msg := range p.Bot.Config().Bad.Msgs { match, err := regexp.MatchString(msg, strings.ToLower(message.Body)) if err != nil { log.Println("Bad regexp: ", err) } if match { log.Println("Disallowing first: ", message.User.Name, ":", message.Body) return false } } for _, host := range p.Bot.Config().Bad.Hosts { if host == message.Host { log.Println("Disallowing first: ", message.User.Name, ":", message.Body) return false } } for _, nick := range p.Bot.Config().Bad.Nicks { if nick == message.User.Name { log.Println("Disallowing first: ", message.User.Name, ":", message.Body) return false } } return true } func (p *FirstPlugin) recordFirst(message msg.Message) { log.Println("Recording first: ", message.User.Name, ":", message.Body) p.First = &FirstEntry{ day: midnight(time.Now()), time: message.Time, body: message.Body, nick: message.User.Name, } log.Printf("recordFirst: %+v", p.First.day) err := p.First.save(p.db) if err != nil { log.Println("Error saving first entry: ", err) return } p.announceFirst(message) } func (p *FirstPlugin) announceFirst(message msg.Message) { c := message.Channel if p.First != nil { p.Bot.SendMessage(c, fmt.Sprintf("%s had first at %s with the message: \"%s\"", p.First.nick, p.First.time.Format("15:04"), p.First.body)) } } // LoadData imports any configuration data into the plugin. This is not strictly necessary other // than the fact that the Plugin interface demands it exist. This may be deprecated at a later // date. func (p *FirstPlugin) LoadData() { // This bot has no data to load } // Help responds to help requests. Every plugin must implement a help function. func (p *FirstPlugin) Help(channel string, parts []string) { p.Bot.SendMessage(channel, "Sorry, First does not do a goddamn thing.") } // Empty event handler because this plugin does not do anything on event recv func (p *FirstPlugin) Event(kind string, message msg.Message) bool { return false } // Handler for bot's own messages func (p *FirstPlugin) BotMessage(message msg.Message) bool { return false } // Register any web URLs desired func (p *FirstPlugin) RegisterWeb() *string { return nil } func (p *FirstPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }