Initial mongo->sqlite move

Tons of bugs, I'm sure. This commit  mostly gets the bot moving towards
SQLite. It builds, but many plugins have a log.Fatal to prevent their
use and it has not been tested.
This commit is contained in:
Chris Sexton 2016-01-15 01:12:26 -05:00
parent cdbe9a81d7
commit 1efa7ebcd4
16 changed files with 361 additions and 395 deletions

View File

@ -3,6 +3,7 @@
package bot package bot
import ( import (
"database/sql"
"html/template" "html/template"
"log" "log"
"net/http" "net/http"
@ -11,7 +12,8 @@ import (
"code.google.com/p/velour/irc" "code.google.com/p/velour/irc"
"github.com/chrissexton/alepale/config" "github.com/chrissexton/alepale/config"
"labix.org/v2/mgo"
_ "github.com/mattn/go-sqlite3"
) )
const actionPrefix = "\x01ACTION" const actionPrefix = "\x01ACTION"
@ -35,11 +37,9 @@ type Bot struct {
Config *config.Config Config *config.Config
// Mongo connection and db allow botwide access to the database // SQL DB
DbSession *mgo.Session DB *sql.DB
Db *mgo.Database DBVersion int64
varColl *mgo.Collection
logIn chan Message logIn chan Message
logOut chan Messages logOut chan Messages
@ -101,13 +101,11 @@ type Variable struct {
// NewBot creates a Bot for a given connection and set of handlers. // NewBot creates a Bot for a given connection and set of handlers.
func NewBot(config *config.Config, c *irc.Client) *Bot { func NewBot(config *config.Config, c *irc.Client) *Bot {
session, err := mgo.Dial(config.DbServer) sqlDB, err := sql.Open("sqlite3", config.DbFile)
if err != nil { if err != nil {
panic(err) log.Fatal(err)
} }
db := session.DB(config.DbName)
logIn := make(chan Message) logIn := make(chan Message)
logOut := make(chan Messages) logOut := make(chan Messages)
@ -126,15 +124,15 @@ func NewBot(config *config.Config, c *irc.Client) *Bot {
Users: users, Users: users,
Me: users[0], Me: users[0],
Client: c, Client: c,
DbSession: session, DB: sqlDB,
Db: db,
varColl: db.C("variables"),
logIn: logIn, logIn: logIn,
logOut: logOut, logOut: logOut,
Version: config.Version, Version: config.Version,
httpEndPoints: make(map[string]string), httpEndPoints: make(map[string]string),
} }
bot.migrateDB()
http.HandleFunc("/", bot.serveRoot) http.HandleFunc("/", bot.serveRoot)
if config.HttpAddr == "" { if config.HttpAddr == "" {
config.HttpAddr = "127.0.0.1:1337" config.HttpAddr = "127.0.0.1:1337"
@ -144,6 +142,49 @@ func NewBot(config *config.Config, c *irc.Client) *Bot {
return bot return bot
} }
// Create any tables if necessary based on version of DB
// Plugins should create their own tables, these are only for official bot stuff
// Note: This does not return an error. Database issues are all fatal at this stage.
func (b *Bot) migrateDB() {
_, err := b.DB.Exec(`create table if not exists version (version integer);`)
if err != nil {
log.Fatal(err)
}
var version int64
err = b.DB.QueryRow("select max(version) from version").Scan(&version)
switch {
case err == sql.ErrNoRows:
log.Printf("No versions, we're the first!.")
_, err := b.DB.Exec(`insert into version (version) values (1)`)
if err != nil {
log.Fatal(err)
}
case err != nil:
log.Fatal(err)
default:
b.DBVersion = version
log.Printf("Database version: %d\n", version)
}
if version == 1 {
if _, err := b.DB.Exec(`create table if not exists variables (
id integer primary key,
name string,
perms string,
type string
);`); err != nil {
log.Fatal(err)
}
if _, err := b.DB.Exec(`create table if not exists values (
id integer primary key,
varId integer,
value string
);`); err != nil {
log.Fatal(err)
}
}
}
// Adds a constructed handler to the bots handlers list // Adds a constructed handler to the bots handlers list
func (b *Bot) AddHandler(name string, h Handler) { func (b *Bot) AddHandler(name string, h Handler) {
b.Plugins[strings.ToLower(name)] = h b.Plugins[strings.ToLower(name)] = h

View File

@ -3,15 +3,17 @@
package bot package bot
import ( import (
"code.google.com/p/velour/irc" "database/sql"
"errors" "errors"
"fmt" "fmt"
"labix.org/v2/mgo/bson" "log"
"math/rand" "math/rand"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"code.google.com/p/velour/irc"
) )
// Interface used for compatibility with the Plugin interface // Interface used for compatibility with the Plugin interface
@ -180,28 +182,43 @@ func (b *Bot) Filter(message Message, input string) string {
blacklist := make(map[string]bool) blacklist := make(map[string]bool)
blacklist["$and"] = true blacklist["$and"] = true
for len(varname) > 0 && !blacklist[varname] { for len(varname) > 0 && !blacklist[varname] {
var result []Variable text, err := b.getVar(varname)
b.varColl.Find(bson.M{"variable": varname}).All(&result) if err != nil {
if len(result) == 0 {
blacklist[varname] = true blacklist[varname] = true
continue continue
} }
variable := result[rand.Intn(len(result))] input = strings.Replace(input, varname, text, 1)
input = strings.Replace(input, varname, variable.Value, 1)
varname = r.FindString(input) varname = r.FindString(input)
} }
return input return input
} }
func (b *Bot) getVar(varName string) (string, error) {
var text string
err := b.DB.QueryRow("select v.value from variables as va inner join values as v on va.id = va.id = v.varId order by random() limit 1").Scan(&text)
switch {
case err == sql.ErrNoRows:
return "", fmt.Errorf("No factoid found")
case err != nil:
log.Fatal(err)
}
return text, nil
}
func (b *Bot) listVars(channel string, parts []string) { func (b *Bot) listVars(channel string, parts []string) {
var result []string rows, err := b.DB.Query(`select name from variables`)
err := b.varColl.Find(bson.M{}).Distinct("variable", &result)
if err != nil { if err != nil {
panic(err) log.Fatal(err)
} }
msg := "I know: $who, $someone, $digit, $nonzero" msg := "I know: $who, $someone, $digit, $nonzero"
for _, variable := range result { for rows.Next() {
var variable string
err := rows.Scan(&variable)
if err != nil {
log.Println("Error scanning variable.")
continue
}
msg = fmt.Sprintf("%s, %s", msg, variable) msg = fmt.Sprintf("%s, %s", msg, variable)
} }
b.SendMessage(channel, msg) b.SendMessage(channel, msg)

View File

@ -2,13 +2,6 @@
package bot package bot
import (
// "labix.org/v2/mgo"
"labix.org/v2/mgo"
"labix.org/v2/mgo/bson"
"log"
)
// User type stores user history. This is a vehicle that will follow the user for the active // User type stores user history. This is a vehicle that will follow the user for the active
// session // session
type User struct { type User struct {
@ -26,6 +19,18 @@ type User struct {
//bot *Bot //bot *Bot
} }
var users map[string]*User
func (b *Bot) GetUser(nick string) *User {
if _, ok := users[nick]; !ok {
users[nick] = &User{
Name: nick,
Admin: b.checkAdmin(nick),
}
}
return users[nick]
}
func (b *Bot) NewUser(nick string) *User { func (b *Bot) NewUser(nick string) *User {
return &User{ return &User{
Name: nick, Name: nick,
@ -33,77 +38,6 @@ func (b *Bot) NewUser(nick string) *User {
} }
} }
func (b *Bot) GetUser(nick string) *User {
coll := b.Db.C("users")
query := coll.Find(bson.M{"name": nick})
var user *User
count, err := query.Count()
if err != nil {
user = b.NewUser(nick)
coll.Insert(*user)
} else if count == 1 {
err = query.One(&user)
if err != nil {
log.Printf("ERROR adding user: %s -- %s\n", nick, err)
}
} else if count == 0 {
// create the user
log.Printf("Creating new user: %s\n", nick)
user = b.NewUser(nick)
coll.Insert(user)
} else {
log.Printf("Error: %s appears to have more than one user?\n", nick)
query.One(&user)
}
// grab linked user, if any
if user.Parent != "" {
query := coll.Find(bson.M{"Name": user.Parent})
if count, err := query.Count(); err != nil && count == 1 {
query.One(user)
} else {
log.Printf("Error: bad linkage on %s -> %s.\n",
user.Name,
user.Parent)
}
}
found := false
for _, u := range b.Users {
if u.Name == user.Name {
found = true
}
}
if !found {
b.Users = append(b.Users, *user)
}
return user
}
// Modify user entry to be a link to other, return other
func (u *User) LinkUser(coll *mgo.Collection, other *User) *User {
user := u
other.Alts = append(other.Alts, user.Alts...)
user.Alts = []string{}
user.Parent = other.Name
err := coll.Update(bson.M{"name": u.Name}, u)
if err != nil {
log.Printf("Error updating user: %s\n", u.Name)
}
err = coll.Update(bson.M{"name": other.Name}, other)
if err != nil {
log.Printf("Error updating other user: %s\n", other.Name)
}
return other
}
func (b *Bot) checkAdmin(nick string) bool { func (b *Bot) checkAdmin(nick string) bool {
for _, u := range b.Config.Admins { for _, u := range b.Config.Admins {
if nick == u { if nick == u {

View File

@ -9,6 +9,7 @@ import "io/ioutil"
// Config stores any system-wide startup information that cannot be easily configured via // Config stores any system-wide startup information that cannot be easily configured via
// the database // the database
type Config struct { type Config struct {
DbFile string
DbName string DbName string
DbServer string DbServer string
Channels []string Channels []string

View File

@ -3,26 +3,28 @@
package plugins package plugins
import ( import (
"database/sql"
"fmt" "fmt"
"github.com/chrissexton/alepale/bot" "log"
"labix.org/v2/mgo"
"labix.org/v2/mgo/bson"
"math/rand" "math/rand"
"strings" "strings"
"time" "time"
"github.com/chrissexton/alepale/bot"
) )
// This is a admin plugin to serve as an example and quick copy/paste for new plugins. // This is a admin plugin to serve as an example and quick copy/paste for new plugins.
type AdminPlugin struct { type AdminPlugin struct {
Bot *bot.Bot Bot *bot.Bot
factC, remC, beerC, varC *mgo.Collection DB *sql.DB
} }
// NewAdminPlugin creates a new AdminPlugin with the Plugin interface // NewAdminPlugin creates a new AdminPlugin with the Plugin interface
func NewAdminPlugin(bot *bot.Bot) *AdminPlugin { func NewAdminPlugin(bot *bot.Bot) *AdminPlugin {
p := &AdminPlugin{ p := &AdminPlugin{
Bot: bot, Bot: bot,
DB: bot.DB,
} }
p.LoadData() p.LoadData()
return p return p
@ -56,20 +58,25 @@ func (p *AdminPlugin) handleVariables(message bot.Message) bool {
variable := strings.TrimSpace(parts[0]) variable := strings.TrimSpace(parts[0])
value := parts[1] value := parts[1]
q := p.varC.Find(bson.M{"variable": variable, "value": value}) var count int64
if n, _ := q.Count(); n != 0 { var varId int64
p.Bot.SendMessage(message.Channel, "I've already got that one.") err := p.DB.QueryRow(`select count(*), varId from variables vs inner join values v on vs.id = v.varId where vs.name = ? and v.value = ?`, variable, value).Scan(&count)
return true switch {
case err == sql.ErrNoRows:
_, err := p.DB.Exec(`insert into values (varId, value) values (?, ?)`, varId, value)
if err != nil {
log.Println(err)
} }
p.varC.Insert(bot.Variable{
Variable: variable,
Value: value,
})
msg := fmt.Sprintf("Added '%s' to %s.\n", value, variable) msg := fmt.Sprintf("Added '%s' to %s.\n", value, variable)
p.Bot.SendMessage(message.Channel, msg) p.Bot.SendMessage(message.Channel, msg)
return true return true
case err != nil:
p.Bot.SendMessage(message.Channel, "I'm broke and need attention in my variable creation code.")
log.Println(err)
return true
}
p.Bot.SendMessage(message.Channel, "I've already got that one.")
return true
} }
// LoadData imports any configuration data into the plugin. This is not strictly necessary other // LoadData imports any configuration data into the plugin. This is not strictly necessary other
@ -78,10 +85,6 @@ func (p *AdminPlugin) handleVariables(message bot.Message) bool {
func (p *AdminPlugin) LoadData() { func (p *AdminPlugin) LoadData() {
// This bot has no data to load // This bot has no data to load
rand.Seed(time.Now().Unix()) rand.Seed(time.Now().Unix())
p.factC = p.Bot.Db.C("factoid")
p.remC = p.Bot.Db.C("remember")
p.beerC = p.Bot.Db.C("beers")
p.varC = p.Bot.Db.C("variables")
} }
// Help responds to help requests. Every plugin must implement a help function. // Help responds to help requests. Every plugin must implement a help function.

View File

@ -3,6 +3,7 @@
package plugins package plugins
import ( import (
"database/sql"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -15,21 +16,56 @@ import (
"time" "time"
"github.com/chrissexton/alepale/bot" "github.com/chrissexton/alepale/bot"
"labix.org/v2/mgo"
"labix.org/v2/mgo/bson"
) )
// This is a skeleton plugin to serve as an example and quick copy/paste for new plugins. // This is a skeleton plugin to serve as an example and quick copy/paste for new plugins.
type BeersPlugin struct { type BeersPlugin struct {
Bot *bot.Bot Bot *bot.Bot
Coll *mgo.Collection db *sql.DB
}
type userBeers struct {
id int64
nick string
count int
lastDrunk time.Time
saved bool
}
type untappdUser struct {
id int64
untappdUser string
channel string
lastCheckin int
chanNick string
} }
// NewBeersPlugin creates a new BeersPlugin with the Plugin interface // NewBeersPlugin creates a new BeersPlugin with the Plugin interface
func NewBeersPlugin(bot *bot.Bot) *BeersPlugin { func NewBeersPlugin(bot *bot.Bot) *BeersPlugin {
if bot.DBVersion == 1 {
if _, err := bot.DB.Exec(`create table if not exists beers (
id integer primary key,
nick string,
count integer,
lastDrunk integer
);`); err != nil {
log.Fatal(err)
}
if _, err := bot.DB.Exec(`create table if not exists untappd (
id integer primary key,
untappdUser string
channel string
lastCheckin integer
chanNick string
);`); err != nil {
log.Fatal(err)
}
}
p := BeersPlugin{ p := BeersPlugin{
Bot: bot, Bot: bot,
db: bot.DB,
} }
p.LoadData() p.LoadData()
for _, channel := range bot.Config.UntappdChannels { for _, channel := range bot.Config.UntappdChannels {
@ -38,31 +74,39 @@ func NewBeersPlugin(bot *bot.Bot) *BeersPlugin {
return &p return &p
} }
type userBeers struct { func (u *userBeers) Save(db *sql.DB) error {
Nick string if !u.saved {
Beercount int res, err := db.Exec(`insert into beers (
Lastdrunk time.Time nick string,
Momentum float64 count integer,
New bool lastDrunk integer
} ) values (?, ?, ?)`, u.nick, u.count, u.lastDrunk)
func (u *userBeers) Save(coll *mgo.Collection) {
_, err := coll.Upsert(bson.M{"nick": u.Nick}, u)
if err != nil { if err != nil {
panic(err) return err
} }
id, err := res.LastInsertId()
if err != nil {
return err
}
u.id = id
}
return nil
} }
func getUserBeers(coll *mgo.Collection, nick string) *userBeers { func getUserBeers(db *sql.DB, nick string) *userBeers {
ub := userBeers{New: true} var ub userBeers
coll.Find(bson.M{"nick": nick}).One(&ub) err := db.QueryRow(`select id, nick, count, lastDrunk from beers
if ub.New == true { where nick = ?`, nick).Scan(
ub.New = false &ub.id,
ub.Nick = nick &ub.nick,
ub.Beercount = 0 &ub.count,
ub.Momentum = 0 &ub.lastDrunk,
ub.Save(coll) )
if err != nil && err != sql.ErrNoRows {
log.Println(err)
return nil
} }
return &ub return &ub
} }
@ -160,16 +204,33 @@ func (p *BeersPlugin) Message(message bot.Message) bool {
channel = parts[3] channel = parts[3]
} }
u := untappdUser{ u := untappdUser{
UntappdUser: parts[1], untappdUser: parts[1],
ChanNick: chanNick, chanNick: chanNick,
Channel: channel, channel: channel,
} }
log.Println("Creating Untappd user:", u.UntappdUser, "nick:", u.ChanNick) log.Println("Creating Untappd user:", u.untappdUser, "nick:", u.chanNick)
_, err := p.Coll.Upsert(bson.M{"untappduser": u.UntappdUser}, u) var count int
err := p.db.QueryRow(`select count(*) from untappd
where untappdUser = ?`, u.untappdUser).Scan(&count)
if err != nil { if err != nil {
log.Println("ERROR!!!:", err) log.Println("Error registering untappd: ", err)
}
if count > 0 {
p.Bot.SendMessage(channel, "I'm already watching you.")
return true
}
_, err = p.db.Exec(`insert into untappd (
untappdUser,
channel,
lastCheckin,
chanNick
) values (?, ?, ?, ?);`)
if err != nil {
log.Println("Error registering untappd: ", err)
p.Bot.SendMessage(channel, "I can't see.")
return true
} }
p.Bot.SendMessage(channel, "I'll be watching you.") p.Bot.SendMessage(channel, "I'll be watching you.")
@ -196,7 +257,6 @@ func (p *BeersPlugin) Event(kind string, 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 *BeersPlugin) LoadData() { func (p *BeersPlugin) LoadData() {
p.Coll = p.Bot.Db.C("beers")
rand.Seed(time.Now().Unix()) rand.Seed(time.Now().Unix())
} }
@ -209,10 +269,10 @@ func (p *BeersPlugin) Help(channel string, parts []string) {
} }
func (p *BeersPlugin) setBeers(user string, amount int) { func (p *BeersPlugin) setBeers(user string, amount int) {
ub := getUserBeers(p.Coll, user) ub := getUserBeers(p.db, user)
ub.Beercount = amount ub.count = amount
ub.Lastdrunk = time.Now() ub.lastDrunk = time.Now()
ub.Save(p.Coll) ub.Save(p.db)
} }
func (p *BeersPlugin) addBeers(user string) { func (p *BeersPlugin) addBeers(user string) {
@ -220,9 +280,8 @@ func (p *BeersPlugin) addBeers(user string) {
} }
func (p *BeersPlugin) getBeers(nick string) int { func (p *BeersPlugin) getBeers(nick string) int {
ub := getUserBeers(p.Coll, nick) ub := getUserBeers(p.db, nick)
return ub.count
return ub.Beercount
} }
func (p *BeersPlugin) reportCount(nick, channel string, himself bool) { func (p *BeersPlugin) reportCount(nick, channel string, himself bool) {
@ -245,9 +304,10 @@ func (p *BeersPlugin) puke(user string, channel string) {
} }
func (p *BeersPlugin) doIKnow(nick string) bool { func (p *BeersPlugin) doIKnow(nick string) bool {
count, err := p.Coll.Find(bson.M{"nick": nick}).Count() var count int
err := p.db.QueryRow(`select count(*) from beers where nick = ?`, nick).Scan(&count)
if err != nil { if err != nil {
panic(err) return false
} }
return count > 0 return count > 0
} }
@ -288,15 +348,6 @@ type Beers struct {
Response resp Response resp
} }
type untappdUser struct {
Id bson.ObjectId `bson:"_id,omitempty"`
UntappdUser string
Channel string
LastCheckin int
ChanNick string
KnownCheckins [5]int
}
func (p *BeersPlugin) pullUntappd() ([]checkin, error) { func (p *BeersPlugin) pullUntappd() ([]checkin, error) {
access_token := "?access_token=" + p.Bot.Config.UntappdToken access_token := "?access_token=" + p.Bot.Config.UntappdToken
baseUrl := "https://api.untappd.com/v4/checkin/recent/" baseUrl := "https://api.untappd.com/v4/checkin/recent/"
@ -331,11 +382,20 @@ func (p *BeersPlugin) checkUntappd(channel string) {
} }
var users []untappdUser var users []untappdUser
p.Coll.Find(bson.M{"untappduser": bson.M{"$exists": true}, "channel": channel}).All(&users) rows, err := p.db.Query(`select *from untappd`)
if err != nil {
log.Println("Error getting untappd users: ", err)
return
}
for rows.Next() {
u := untappdUser{}
rows.Scan(&u.id, &u.untappdUser, &u.channel, &u.lastCheckin, &u.chanNick)
users = append(users, u)
}
userMap := make(map[string]untappdUser) userMap := make(map[string]untappdUser)
for _, u := range users { for _, u := range users {
userMap[u.UntappdUser] = u userMap[u.untappdUser] = u
} }
chks, err := p.pullUntappd() chks, err := p.pullUntappd()
@ -347,7 +407,7 @@ func (p *BeersPlugin) checkUntappd(channel string) {
continue continue
} }
if checkin.Checkin_id <= userMap[checkin.User.User_name].LastCheckin { if checkin.Checkin_id <= userMap[checkin.User.User_name].lastCheckin {
continue continue
} }
@ -362,18 +422,20 @@ func (p *BeersPlugin) checkUntappd(channel string) {
if !ok { if !ok {
continue continue
} }
p.addBeers(user.ChanNick) p.addBeers(user.chanNick)
drunken := p.getBeers(user.ChanNick) drunken := p.getBeers(user.chanNick)
msg := fmt.Sprintf("%s just drank %s by %s%s, bringing his drunkeness to %d", msg := fmt.Sprintf("%s just drank %s by %s%s, bringing his drunkeness to %d",
user.ChanNick, beerName, breweryName, venue, drunken) user.chanNick, beerName, breweryName, venue, drunken)
if checkin.Checkin_comment != "" { if checkin.Checkin_comment != "" {
msg = fmt.Sprintf("%s -- %s", msg = fmt.Sprintf("%s -- %s",
msg, checkin.Checkin_comment) msg, checkin.Checkin_comment)
} }
user.LastCheckin = checkin.Checkin_id user.lastCheckin = checkin.Checkin_id
err := p.Coll.Update(bson.M{"_id": user.Id}, user) _, err := p.db.Exec(`update untappd set
lastCheckin = ?
where id = ?`, user.lastCheckin, user.id)
if err != nil { if err != nil {
log.Println("UPDATE ERROR!:", err) log.Println("UPDATE ERROR!:", err)
} }

View File

@ -3,21 +3,23 @@
package plugins package plugins
import ( import (
"database/sql"
"fmt" "fmt"
"github.com/chrissexton/alepale/bot" "log"
"labix.org/v2/mgo"
"labix.org/v2/mgo/bson"
"strings" "strings"
"github.com/chrissexton/alepale/bot"
) )
// This is a counter plugin to count arbitrary things. // This is a counter plugin to count arbitrary things.
type CounterPlugin struct { type CounterPlugin struct {
Bot *bot.Bot Bot *bot.Bot
Coll *mgo.Collection DB *sql.DB
} }
type Item struct { type Item struct {
ID int64
Nick string Nick string
Item string Item string
Count int Count int
@ -25,9 +27,19 @@ type Item struct {
// NewCounterPlugin creates a new CounterPlugin with the Plugin interface // NewCounterPlugin creates a new CounterPlugin with the Plugin interface
func NewCounterPlugin(bot *bot.Bot) *CounterPlugin { func NewCounterPlugin(bot *bot.Bot) *CounterPlugin {
if bot.DBVersion == 1 {
if _, err := bot.DB.Exec(`create table if not exists counter (
id integer primary key,
nick string,
item string,
count integer
);`); err != nil {
log.Fatal(err)
}
}
return &CounterPlugin{ return &CounterPlugin{
Bot: bot, Bot: bot,
Coll: bot.Db.C("counter"), DB: bot.DB,
} }
} }
@ -55,38 +67,47 @@ func (p *CounterPlugin) Message(message bot.Message) bool {
} }
// pull all of the items associated with "subject" // pull all of the items associated with "subject"
var items []Item rows, err := p.DB.Query(`select * from counter where nick = ?`, subject)
p.Coll.Find(bson.M{"nick": subject}).All(&items) if err != nil {
log.Fatal(err)
if len(items) == 0 {
p.Bot.SendMessage(channel, fmt.Sprintf("%s has no counters.", subject))
return true
} }
resp := fmt.Sprintf("%s has the following counters:", subject) resp := fmt.Sprintf("%s has the following counters:", subject)
for i, item := range items { count := 0
if i != 0 { for rows.Next() {
count += 1
var it Item
rows.Scan(&it.Nick, &it.Item, &it.Count)
if count > 1 {
resp = fmt.Sprintf("%s, ", resp) resp = fmt.Sprintf("%s, ", resp)
} }
resp = fmt.Sprintf("%s %s: %d", resp, item.Item, item.Count) resp = fmt.Sprintf("%s %s: %d", resp, it.Item, it.Count)
if i > 20 { if count > 20 {
fmt.Sprintf("%s, and a few others", resp) fmt.Sprintf("%s, and a few others", resp)
break break
} }
} }
resp = fmt.Sprintf("%s.", resp) resp = fmt.Sprintf("%s.", resp)
if count == 0 {
p.Bot.SendMessage(channel, fmt.Sprintf("%s has no counters.", subject))
return true
}
p.Bot.SendMessage(channel, resp) p.Bot.SendMessage(channel, resp)
return true return true
} else if message.Command && len(parts) == 2 && parts[0] == "clear" { } else if message.Command && len(parts) == 2 && parts[0] == "clear" {
subject := strings.ToLower(nick) subject := strings.ToLower(nick)
itemName := strings.ToLower(parts[1]) itemName := strings.ToLower(parts[1])
p.Coll.Remove(bson.M{"nick": subject, "item": itemName}) if _, err := p.DB.Exec(`delete from counters where nick = ? and item = ?`, subject, itemName); err != nil {
p.Bot.SendMessage(channel, "Something went wrong removing that counter;")
return true
}
p.Bot.SendAction(channel, fmt.Sprintf("chops a few %s out of his brain", p.Bot.SendAction(channel, fmt.Sprintf("chops a few %s out of his brain",
itemName)) itemName))
return true return true
} else if message.Command && parts[0] == "count" { } else if message.Command && parts[0] == "count" {
@ -105,11 +126,18 @@ func (p *CounterPlugin) Message(message bot.Message) bool {
} }
var item Item var item Item
err := p.Coll.Find(bson.M{"nick": subject, "item": itemName}).One(&item) err := p.DB.QueryRow(`select nick, item, count from counters
if err != nil { where nick = ? and item = ?`, subject, itemName).Scan(
&item.Nick, &item.Item, &item.Count,
)
switch {
case err == sql.ErrNoRows:
p.Bot.SendMessage(channel, fmt.Sprintf("I don't think %s has any %s.", p.Bot.SendMessage(channel, fmt.Sprintf("I don't think %s has any %s.",
subject, itemName)) subject, itemName))
return true return true
case err != nil:
log.Println(err)
return true
} }
p.Bot.SendMessage(channel, fmt.Sprintf("%s has %d %s.", subject, item.Count, p.Bot.SendMessage(channel, fmt.Sprintf("%s has %d %s.", subject, item.Count,
@ -149,22 +177,32 @@ func (p *CounterPlugin) Message(message bot.Message) bool {
func (p *CounterPlugin) update(subject, itemName string, delta int) Item { func (p *CounterPlugin) update(subject, itemName string, delta int) Item {
var item Item var item Item
err := p.Coll.Find(bson.M{"nick": subject, "item": itemName}).One(&item) err := p.DB.QueryRow(`select id, nick, item, count from counter
if err != nil { where nick = ? and item = ?;`, subject, itemName).Scan(
&item.ID, &item.Nick, &item.Item, &item.Count,
)
switch {
case err == sql.ErrNoRows:
// insert it // insert it
item = Item{ res, err := p.DB.Exec(`insert into counter (nick, item, count)
Nick: subject, values (?, ?, ?)`, subject, itemName, delta)
Item: itemName, if err != nil {
Count: delta, log.Println(err)
} }
p.Coll.Insert(item) id, err := res.LastInsertId()
} else { return Item{id, subject, itemName, delta}
// update it case err != nil:
log.Println(err)
return item
default:
item.Count += delta item.Count += delta
p.Coll.Update(bson.M{"nick": subject, "item": itemName}, item) _, err := p.DB.Exec(`update counter set count = ? where id = ?`, item.Count, item.ID)
if err != nil {
log.Println(err)
} }
return item return item
} }
}
// LoadData imports any configuration data into the plugin. This is not // LoadData imports any configuration data into the plugin. This is not
// strictly necessary other than the fact that the Plugin interface demands it // strictly necessary other than the fact that the Plugin interface demands it

View File

@ -6,12 +6,13 @@ import "github.com/chrissexton/alepale/bot"
import ( import (
"fmt" "fmt"
"labix.org/v2/mgo"
"labix.org/v2/mgo/bson"
"log" "log"
"sort" "sort"
"strings" "strings"
"time" "time"
"labix.org/v2/mgo"
"labix.org/v2/mgo/bson"
) )
// This is a downtime plugin to monitor how much our users suck // This is a downtime plugin to monitor how much our users suck
@ -123,7 +124,9 @@ func (p *DowntimePlugin) remove(user string) {
// 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 *DowntimePlugin) LoadData() { func (p *DowntimePlugin) LoadData() {
p.Coll = p.Bot.Db.C("downtime") // Mongo is removed, this plugin will crash if started
log.Fatal("The Downtime plugin has not been upgraded to SQL yet.")
// p.Coll = p.Bot.Db.C("downtime")
} }
// Help responds to help requests. Every plugin must implement a help function. // Help responds to help requests. Every plugin must implement a help function.

View File

@ -420,7 +420,9 @@ 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() {
p.Coll = p.Bot.Db.C("factoid") // Mongo is removed, this plugin will crash if started
log.Fatal("The Factoid plugin has not been upgraded to SQL yet.")
// p.Coll = p.Bot.Db.C("factoid")
rand.Seed(time.Now().Unix()) rand.Seed(time.Now().Unix())
} }

View File

@ -23,9 +23,11 @@ type FeedPlugin struct {
// NewFeedPlugin creates a new FeedPlugin with the Plugin interface // NewFeedPlugin creates a new FeedPlugin with the Plugin interface
func NewFeedPlugin(bot *bot.Bot) *FeedPlugin { func NewFeedPlugin(bot *bot.Bot) *FeedPlugin {
// Mongo is removed, this plugin will crash if started
log.Fatal("The Feed plugin has not been upgraded to SQL yet.")
p := FeedPlugin{ p := FeedPlugin{
Bot: bot, Bot: bot,
Coll: bot.Db.C("feed"), // Coll: bot.Db.C("feed"),
} }
go p.pollFeeds() go p.pollFeeds()
return &p return &p

View File

@ -4,13 +4,14 @@ package plugins
import ( import (
"fmt" "fmt"
"github.com/chrissexton/alepale/bot"
"labix.org/v2/mgo"
"labix.org/v2/mgo/bson"
"log" "log"
"regexp" "regexp"
"strings" "strings"
"time" "time"
"github.com/chrissexton/alepale/bot"
"labix.org/v2/mgo"
"labix.org/v2/mgo/bson"
) )
// This is a first plugin to serve as an example and quick copy/paste for new plugins. // This is a first plugin to serve as an example and quick copy/paste for new plugins.
@ -30,7 +31,10 @@ type FirstEntry struct {
// NewFirstPlugin creates a new FirstPlugin with the Plugin interface // NewFirstPlugin creates a new FirstPlugin with the Plugin interface
func NewFirstPlugin(b *bot.Bot) *FirstPlugin { func NewFirstPlugin(b *bot.Bot) *FirstPlugin {
coll := b.Db.C("first") // Mongo is removed, this plugin will crash if started
log.Fatal("The First plugin has not been upgraded to SQL yet.")
var coll *mgo.Collection
// coll := b.Db.C("first")
var firsts []FirstEntry var firsts []FirstEntry
query := bson.M{"day": midnight(time.Now())} query := bson.M{"day": midnight(time.Now())}
log.Println("Day:", midnight(time.Now())) log.Println("Day:", midnight(time.Now()))

View File

@ -4,17 +4,16 @@ package plugins
import ( import (
"fmt" "fmt"
"github.com/chrissexton/alepale/bot"
"github.com/chrissexton/kakapo/lisp"
"labix.org/v2/mgo"
"log" "log"
"strings" "strings"
"time" "time"
"github.com/chrissexton/alepale/bot"
"github.com/chrissexton/kakapo/lisp"
) )
type LispPlugin struct { type LispPlugin struct {
Bot *bot.Bot Bot *bot.Bot
Coll *mgo.Collection
} }
type Program struct { type Program struct {
@ -27,7 +26,6 @@ type Program struct {
func NewLispPlugin(bot *bot.Bot) *LispPlugin { func NewLispPlugin(bot *bot.Bot) *LispPlugin {
return &LispPlugin{ return &LispPlugin{
Bot: bot, Bot: bot,
Coll: bot.Db.C("lisp"),
} }
} }
@ -43,7 +41,6 @@ func (p *LispPlugin) Message(message bot.Message) bool {
Contents: strings.Replace(message.Body, "lisp:", "", 1), Contents: strings.Replace(message.Body, "lisp:", "", 1),
} }
log.Println("Evaluating:", prog) log.Println("Evaluating:", prog)
p.Coll.Insert(prog)
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {

View File

@ -75,8 +75,7 @@ type PluginConfig struct {
Values map[string]interface{} Values map[string]interface{}
} }
// Loads plugin config out of the DB // Loads plugin config (could be out of a DB or something)
// Stored in db.plugins.find("name": name)
func GetPluginConfig(name string) PluginConfig { func GetPluginConfig(name string) PluginConfig {
return PluginConfig{ return PluginConfig{
Name: "TestPlugin", Name: "TestPlugin",

View File

@ -4,13 +4,14 @@ package plugins
import ( import (
"fmt" "fmt"
"github.com/chrissexton/alepale/bot"
"labix.org/v2/mgo"
"labix.org/v2/mgo/bson"
"log" "log"
"math/rand" "math/rand"
"strings" "strings"
"time" "time"
"github.com/chrissexton/alepale/bot"
"labix.org/v2/mgo"
"labix.org/v2/mgo/bson"
) )
// This is a skeleton plugin to serve as an example and quick copy/paste for new // This is a skeleton plugin to serve as an example and quick copy/paste for new
@ -94,14 +95,15 @@ func (p *RememberPlugin) Message(message bot.Message) bool {
if len(msgs) == len(snips) { if len(msgs) == len(snips) {
msg := strings.Join(msgs, "$and") msg := strings.Join(msgs, "$and")
var funcres bson.M var funcres bson.M
err := p.Bot.Db.Run( // Needs to be upgraded to SQL
bson.M{"eval": "return counter(\"factoid\");"}, // err := p.Bot.Db.Run(
&funcres, // bson.M{"eval": "return counter(\"factoid\");"},
) // &funcres,
// )
if err != nil { // if err != nil {
panic(err) // panic(err)
} // }
id := int(funcres["retval"].(float64)) id := int(funcres["retval"].(float64))
fact := Factoid{ fact := Factoid{
@ -116,7 +118,7 @@ func (p *RememberPlugin) Message(message bot.Message) bool {
LastAccessed: time.Now(), LastAccessed: time.Now(),
AccessCount: 0, AccessCount: 0,
} }
if err = p.Coll.Insert(fact); err != nil { if err := p.Coll.Insert(fact); err != nil {
log.Println("ERROR!!!!:", err) log.Println("ERROR!!!!:", err)
} }
@ -142,7 +144,9 @@ func (p *RememberPlugin) Message(message bot.Message) bool {
// necessary other than the fact that the Plugin interface demands it exist. // necessary other than the fact that the Plugin interface demands it exist.
// This may be deprecated at a later date. // This may be deprecated at a later date.
func (p *RememberPlugin) LoadData() { func (p *RememberPlugin) LoadData() {
p.Coll = p.Bot.Db.C("factoid") // Mongo is removed, this plugin will crash if started
log.Fatal("The Remember plugin has not been upgraded to SQL yet.")
// p.Coll = p.Bot.Db.C("factoid")
rand.Seed(time.Now().Unix()) rand.Seed(time.Now().Unix())
} }

View File

@ -1,143 +0,0 @@
// © 2013 the AlePale Authors under the WTFPL. See AUTHORS for the list of authors.
package plugins
import (
"bitbucket.org/phlyingpenguin/twitter"
"github.com/chrissexton/alepale/bot"
"github.com/garyburd/go-oauth/oauth"
"labix.org/v2/mgo"
"labix.org/v2/mgo/bson"
"log"
"net/url"
"strings"
"time"
)
// Plugin to squak tweets at the channel.
type TwitterPlugin struct {
Bot *bot.Bot
Coll *mgo.Collection
Client *twitter.Client
}
type twitterUser struct {
Id bson.ObjectId `bson:"_id,omitempty"`
User string
Tweets []string
}
// NewTwitterPlugin creates a new TwitterPlugin with the Plugin interface
func NewTwitterPlugin(bot *bot.Bot) *TwitterPlugin {
return &TwitterPlugin{
Bot: bot,
Coll: bot.Db.C("twitter"),
}
}
func (p *TwitterPlugin) say(message bot.Message, body string) bool {
p.Bot.SendMessage(message.Channel, body)
return true
}
// Check for commands accepted
// follow, unfollow
func (p *TwitterPlugin) checkCommand(message bot.Message) bool {
parts := strings.Split(message.Body, " ")
if parts[0] == "follow" {
if len(parts) != 2 {
return p.say(message, "I don't get it.")
}
user := parts[1]
res := p.Coll.Find(bson.M{"user": user})
if count, _ := res.Count(); count > 0 {
return p.say(message, "I'm already following "+user+"!")
}
p.Coll.Insert(twitterUser{User: user})
} else if parts[0] == "unfollow" {
if len(parts) != 2 {
return p.say(message, "I don't get it.")
}
user := parts[1]
p.Coll.Remove(bson.M{"user": user})
return p.say(message, "Fuck "+user)
}
return false
}
// 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 *TwitterPlugin) Message(message bot.Message) bool {
if message.Command {
return p.checkCommand(message)
}
return false
}
// 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 *TwitterPlugin) LoadData() {
// This bot has no data to load
p.Client = twitter.New(&oauth.Credentials{
Token: p.Bot.Config.TwitterConsumerKey,
Secret: p.Bot.Config.TwitterConsumerSecret,
})
p.Client.SetAuth(&oauth.Credentials{
Token: p.Bot.Config.TwitterUserKey,
Secret: p.Bot.Config.TwitterUserSecret,
})
_, err := p.Client.VerifyCredentials(nil)
if err != nil {
log.Println("Could not auth with twitter:", err)
} else {
go p.checkMessages()
}
}
// Help responds to help requests. Every plugin must implement a help function.
func (p *TwitterPlugin) Help(channel string, parts []string) {
p.Bot.SendMessage(channel, "Tell me to follow or unfollow twitter users!")
}
// Empty event handler because this plugin does not do anything on event recv
func (p *TwitterPlugin) Event(kind string, message bot.Message) bool {
return false
}
func (p *TwitterPlugin) checkMessages() {
for {
time.Sleep(time.Minute * 30)
var u url.Values
u.Set("screen_name", "phlyingpenguin")
data, err := p.Client.UserTimeline(u)
if err != nil {
log.Println(err)
} else {
log.Println(data)
}
}
}
// Handler for bot's own messages
func (p *TwitterPlugin) BotMessage(message bot.Message) bool {
return false
}
// Register any web URLs desired
func (p *TwitterPlugin) RegisterWeb() *string {
return nil
}

View File

@ -5,6 +5,8 @@ package plugins
// I hate this, but I'm creating strings of the templates to avoid having to // I hate this, but I'm creating strings of the templates to avoid having to
// track where templates reside. // track where templates reside.
// 2016-01-15 Later note, why are these in plugins and the server is in bot?
var factoidIndex string = ` var factoidIndex string = `
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>