Make testing great again! Add examples in counter

* Made bot.Bot an interface and added a mock with an in-memory database
for plugins to use.
* Remove logger nonsense
* Rename Counter New
This commit is contained in:
Chris Sexton 2016-03-30 10:00:20 -04:00
parent a34afa97ad
commit ef40d335eb
20 changed files with 384 additions and 292 deletions

View File

@ -16,33 +16,33 @@ import (
"github.com/velour/catbase/config"
)
// Bot type provides storage for bot-wide information, configs, and database connections
type Bot struct {
// bot type provides storage for bot-wide information, configs, and database connections
type bot struct {
// Each plugin must be registered in our plugins handler. To come: a map so that this
// will allow plugins to respond to specific kinds of events
Plugins map[string]Handler
PluginOrdering []string
plugins map[string]Handler
pluginOrdering []string
// Users holds information about all of our friends
Users []User
users []User
// Represents the bot
Me User
me User
Config *config.Config
config *config.Config
Conn Connector
conn Connector
// SQL DB
// TODO: I think it'd be nice to use https://github.com/jmoiron/sqlx so that
// the select/update/etc statements could be simplified with struct
// marshalling.
DB *sqlx.DB
DBVersion int64
db *sqlx.DB
dbVersion int64
logIn chan Message
logOut chan Messages
Version string
version string
// The entries to the bot's HTTP interface
httpEndPoints map[string]string
@ -109,8 +109,8 @@ func init() {
})
}
// NewBot creates a Bot for a given connection and set of handlers.
func NewBot(config *config.Config, connector Connector) *Bot {
// Newbot creates a bot for a given connection and set of handlers.
func New(config *config.Config, connector Connector) Bot {
sqlDB, err := sqlx.Open("sqlite3_custom", config.DB.File)
if err != nil {
log.Fatal(err)
@ -127,17 +127,17 @@ func NewBot(config *config.Config, connector Connector) *Bot {
},
}
bot := &Bot{
Config: config,
Plugins: make(map[string]Handler),
PluginOrdering: make([]string, 0),
Conn: connector,
Users: users,
Me: users[0],
DB: sqlDB,
bot := &bot{
config: config,
plugins: make(map[string]Handler),
pluginOrdering: make([]string, 0),
conn: connector,
users: users,
me: users[0],
db: sqlDB,
logIn: logIn,
logOut: logOut,
Version: config.Version,
version: config.Version,
httpEndPoints: make(map[string]string),
}
@ -155,32 +155,45 @@ func NewBot(config *config.Config, connector Connector) *Bot {
return bot
}
// Config gets the configuration that the bot is using
func (b *bot) Config() *config.Config {
return b.config
}
func (b *bot) DBVersion() int64 {
return b.dbVersion
}
func (b *bot) DB() *sqlx.DB {
return b.db
}
// 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);`)
func (b *bot) migrateDB() {
_, err := b.db.Exec(`create table if not exists version (version integer);`)
if err != nil {
log.Fatal("Initial DB migration create version table: ", err)
}
var version sql.NullInt64
err = b.DB.QueryRow("select max(version) from version").Scan(&version)
err = b.db.QueryRow("select max(version) from version").Scan(&version)
if err != nil {
log.Fatal("Initial DB migration get version: ", err)
}
if version.Valid {
b.DBVersion = version.Int64
log.Printf("Database version: %v\n", b.DBVersion)
b.dbVersion = version.Int64
log.Printf("Database version: %v\n", b.dbVersion)
} else {
log.Printf("No versions, we're the first!.")
_, err := b.DB.Exec(`insert into version (version) values (1)`)
_, err := b.db.Exec(`insert into version (version) values (1)`)
if err != nil {
log.Fatal("Initial DB migration insert: ", err)
}
}
if b.DBVersion == 1 {
if _, err := b.DB.Exec(`create table if not exists variables (
if b.dbVersion == 1 {
if _, err := b.db.Exec(`create table if not exists variables (
id integer primary key,
name string,
perms string,
@ -188,7 +201,7 @@ func (b *Bot) migrateDB() {
);`); err != nil {
log.Fatal("Initial DB migration create variables table: ", err)
}
if _, err := b.DB.Exec(`create table if not exists 'values' (
if _, err := b.db.Exec(`create table if not exists 'values' (
id integer primary key,
varId integer,
value string
@ -199,18 +212,18 @@ func (b *Bot) migrateDB() {
}
// Adds a constructed handler to the bots handlers list
func (b *Bot) AddHandler(name string, h Handler) {
b.Plugins[strings.ToLower(name)] = h
b.PluginOrdering = append(b.PluginOrdering, name)
func (b *bot) AddHandler(name string, h Handler) {
b.plugins[strings.ToLower(name)] = h
b.pluginOrdering = append(b.pluginOrdering, name)
if entry := h.RegisterWeb(); entry != nil {
b.httpEndPoints[name] = *entry
}
}
func (b *Bot) Who(channel string) []User {
func (b *bot) Who(channel string) []User {
out := []User{}
for _, u := range b.Users {
if u.Name != b.Config.Nick {
for _, u := range b.users {
if u.Name != b.Config().Nick {
out = append(out, u)
}
}
@ -247,7 +260,7 @@ var rootIndex string = `
</html>
`
func (b *Bot) serveRoot(w http.ResponseWriter, r *http.Request) {
func (b *bot) serveRoot(w http.ResponseWriter, r *http.Request) {
context := make(map[string]interface{})
context["EndPoints"] = b.httpEndPoints
t, err := template.New("rootIndex").Parse(rootIndex)

View File

@ -15,7 +15,7 @@ import (
)
// Handles incomming PRIVMSG requests
func (b *Bot) MsgReceived(msg Message) {
func (b *bot) MsgReceived(msg Message) {
log.Println("Received message: ", msg)
// msg := b.buildMessage(client, inMsg)
@ -27,8 +27,8 @@ func (b *Bot) MsgReceived(msg Message) {
goto RET
}
for _, name := range b.PluginOrdering {
p := b.Plugins[name]
for _, name := range b.pluginOrdering {
p := b.plugins[name]
if p.Message(msg) {
break
}
@ -40,31 +40,31 @@ RET:
}
// Handle incoming events
func (b *Bot) EventReceived(msg Message) {
func (b *bot) EventReceived(msg Message) {
log.Println("Received event: ", msg)
//msg := b.buildMessage(conn, inMsg)
for _, name := range b.PluginOrdering {
p := b.Plugins[name]
for _, name := range b.pluginOrdering {
p := b.plugins[name]
if p.Event(msg.Body, msg) { // TODO: could get rid of msg.Body
break
}
}
}
func (b *Bot) SendMessage(channel, message string) {
b.Conn.SendMessage(channel, message)
func (b *bot) SendMessage(channel, message string) {
b.conn.SendMessage(channel, message)
}
func (b *Bot) SendAction(channel, message string) {
b.Conn.SendAction(channel, message)
func (b *bot) SendAction(channel, message string) {
b.conn.SendAction(channel, message)
}
// Checks to see if the user is asking for help, returns true if so and handles the situation.
func (b *Bot) checkHelp(channel string, parts []string) {
func (b *bot) checkHelp(channel string, parts []string) {
if len(parts) == 1 {
// just print out a list of help topics
topics := "Help topics: about variables"
for name, _ := range b.Plugins {
for name, _ := range b.plugins {
topics = fmt.Sprintf("%s, %s", topics, name)
}
b.SendMessage(channel, topics)
@ -78,7 +78,7 @@ func (b *Bot) checkHelp(channel string, parts []string) {
b.listVars(channel, parts)
return
}
plugin := b.Plugins[parts[1]]
plugin := b.plugins[parts[1]]
if plugin != nil {
plugin.Help(channel, parts)
} else {
@ -88,7 +88,7 @@ func (b *Bot) checkHelp(channel string, parts []string) {
}
}
func (b *Bot) LastMessage(channel string) (Message, error) {
func (b *bot) LastMessage(channel string) (Message, error) {
log := <-b.logOut
if len(log) == 0 {
return Message{}, errors.New("No messages found.")
@ -103,7 +103,7 @@ func (b *Bot) LastMessage(channel string) (Message, error) {
}
// Take an input string and mutate it based on $vars in the string
func (b *Bot) Filter(message Message, input string) string {
func (b *bot) Filter(message Message, input string) string {
rand.Seed(time.Now().Unix())
if strings.Contains(input, "$NICK") {
@ -155,9 +155,9 @@ func (b *Bot) Filter(message Message, input string) string {
return input
}
func (b *Bot) getVar(varName string) (string, error) {
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)
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")
@ -167,8 +167,8 @@ func (b *Bot) getVar(varName string) (string, error) {
return text, nil
}
func (b *Bot) listVars(channel string, parts []string) {
rows, err := b.DB.Query(`select name from variables`)
func (b *bot) listVars(channel string, parts []string) {
rows, err := b.db.Query(`select name from variables`)
if err != nil {
log.Fatal(err)
}
@ -185,17 +185,17 @@ func (b *Bot) listVars(channel string, parts []string) {
b.SendMessage(channel, msg)
}
func (b *Bot) Help(channel string, parts []string) {
func (b *bot) Help(channel string, parts []string) {
msg := fmt.Sprintf("Hi, I'm based on godeepintir version %s. I'm written in Go, and you "+
"can find my source code on the internet here: "+
"http://github.com/velour/catbase", b.Version)
"http://github.com/velour/catbase", b.version)
b.SendMessage(channel, msg)
}
// Send our own musings to the plugins
func (b *Bot) selfSaid(channel, message string, action bool) {
func (b *bot) selfSaid(channel, message string, action bool) {
msg := Message{
User: &b.Me, // hack
User: &b.me, // hack
Channel: channel,
Body: message,
Raw: message, // hack
@ -205,8 +205,8 @@ func (b *Bot) selfSaid(channel, message string, action bool) {
Host: "0.0.0.0", // hack
}
for _, name := range b.PluginOrdering {
p := b.Plugins[name]
for _, name := range b.pluginOrdering {
p := b.plugins[name]
if p.BotMessage(msg) {
break
}

View File

@ -2,6 +2,25 @@
package bot
import (
"github.com/jmoiron/sqlx"
"github.com/velour/catbase/config"
)
type Bot interface {
Config() *config.Config
DBVersion() int64
DB() *sqlx.DB
Who(string) []User
AddHandler(string, Handler)
SendMessage(string, string)
SendAction(string, string)
MsgReceived(Message)
EventReceived(Message)
Filter(Message, string) string
LastMessage(string) (Message, error)
}
type Connector interface {
RegisterEventReceived(func(message Message))
RegisterMessageReceived(func(message Message))

48
bot/mock.go Normal file
View File

@ -0,0 +1,48 @@
// © 2016 the CatBase Authors under the WTFPL license. See AUTHORS for the list of authors.
package bot
import (
"log"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/mock"
"github.com/velour/catbase/config"
)
type MockBot struct {
mock.Mock
db *sqlx.DB
Messages []string
Actions []string
}
func (mb *MockBot) Config() *config.Config { return &config.Config{} }
func (mb *MockBot) DBVersion() int64 { return 1 }
func (mb *MockBot) DB() *sqlx.DB { return mb.db }
func (mb *MockBot) Who(string) []User { return []User{} }
func (mb *MockBot) AddHandler(name string, f Handler) {}
func (mb *MockBot) SendMessage(ch string, msg string) {
mb.Messages = append(mb.Messages, msg)
}
func (mb *MockBot) SendAction(ch string, msg string) {
mb.Actions = append(mb.Actions, msg)
}
func (mb *MockBot) MsgReceived(msg Message) {}
func (mb *MockBot) EventReceived(msg Message) {}
func (mb *MockBot) Filter(msg Message, s string) string { return "" }
func (mb *MockBot) LastMessage(ch string) (Message, error) { return Message{}, nil }
func NewMockBot() *MockBot {
db, err := sqlx.Open("sqlite3_custom", ":memory:")
if err != nil {
log.Fatal("Failed to open database:", err)
}
b := MockBot{
db: db,
Messages: make([]string, 0),
Actions: make([]string, 0),
}
return &b
}

View File

@ -16,12 +16,12 @@ type User struct {
Admin bool
//bot *Bot
//bot *bot
}
var users = map[string]*User{}
func (b *Bot) GetUser(nick string) *User {
func (b *bot) GetUser(nick string) *User {
if _, ok := users[nick]; !ok {
users[nick] = &User{
Name: nick,
@ -31,15 +31,15 @@ func (b *Bot) GetUser(nick string) *User {
return users[nick]
}
func (b *Bot) NewUser(nick string) *User {
func (b *bot) NewUser(nick string) *User {
return &User{
Name: nick,
Admin: b.checkAdmin(nick),
}
}
func (b *Bot) checkAdmin(nick string) bool {
for _, u := range b.Config.Admins {
func (b *bot) checkAdmin(nick string) bool {
for _, u := range b.Config().Admins {
if nick == u {
return true
}

20
main.go
View File

@ -9,10 +9,8 @@ import (
"github.com/velour/catbase/bot"
"github.com/velour/catbase/config"
"github.com/velour/catbase/irc"
"github.com/velour/catbase/plugins"
"github.com/velour/catbase/plugins/admin"
"github.com/velour/catbase/plugins/beers"
"github.com/velour/catbase/plugins/counter"
"github.com/velour/catbase/plugins/dice"
"github.com/velour/catbase/plugins/downtime"
"github.com/velour/catbase/plugins/fact"
@ -39,22 +37,20 @@ func main() {
log.Fatalf("Unknown connection type: %s", c.Type)
}
b := bot.NewBot(c, client)
b := bot.New(c, client)
// b.AddHandler(plugins.NewTestPlugin(b))
b.AddHandler("admin", admin.NewAdminPlugin(b))
b.AddHandler("admin", admin.New(b))
// b.AddHandler("first", plugins.NewFirstPlugin(b))
b.AddHandler("leftpad", leftpad.New(b))
b.AddHandler("downtime", downtime.NewDowntimePlugin(b))
b.AddHandler("downtime", downtime.New(b))
b.AddHandler("talker", talker.New(b))
b.AddHandler("dice", dice.NewDicePlugin(b))
b.AddHandler("beers", beers.NewBeersPlugin(b))
b.AddHandler("counter", counter.NewCounterPlugin(b))
b.AddHandler("remember", fact.NewRememberPlugin(b))
b.AddHandler("skeleton", plugins.NewSkeletonPlugin(b))
b.AddHandler("your", your.NewYourPlugin(b))
b.AddHandler("dice", dice.New(b))
b.AddHandler("beers", beers.New(b))
b.AddHandler("remember", fact.NewRemember(b))
b.AddHandler("your", your.New(b))
// catches anything left, will always return true
b.AddHandler("factoid", fact.NewFactoidPlugin(b))
b.AddHandler("factoid", fact.New(b))
client.Serve()
}

View File

@ -17,15 +17,15 @@ import (
// This is a admin plugin to serve as an example and quick copy/paste for new plugins.
type AdminPlugin struct {
Bot *bot.Bot
Bot bot.Bot
DB *sqlx.DB
}
// NewAdminPlugin creates a new AdminPlugin with the Plugin interface
func NewAdminPlugin(bot *bot.Bot) *AdminPlugin {
func New(bot bot.Bot) *AdminPlugin {
p := &AdminPlugin{
Bot: bot,
DB: bot.DB,
DB: bot.DB(),
}
p.LoadData()
return p

View File

@ -22,7 +22,7 @@ import (
// This is a skeleton plugin to serve as an example and quick copy/paste for new plugins.
type BeersPlugin struct {
Bot *bot.Bot
Bot bot.Bot
db *sqlx.DB
}
@ -35,9 +35,9 @@ type untappdUser struct {
}
// NewBeersPlugin creates a new BeersPlugin with the Plugin interface
func NewBeersPlugin(bot *bot.Bot) *BeersPlugin {
if bot.DBVersion == 1 {
if _, err := bot.DB.Exec(`create table if not exists untappd (
func New(bot bot.Bot) *BeersPlugin {
if bot.DBVersion() == 1 {
if _, err := bot.DB().Exec(`create table if not exists untappd (
id integer primary key,
untappdUser string,
channel string,
@ -49,10 +49,10 @@ func NewBeersPlugin(bot *bot.Bot) *BeersPlugin {
}
p := BeersPlugin{
Bot: bot,
db: bot.DB,
db: bot.DB(),
}
p.LoadData()
for _, channel := range bot.Config.Untappd.Channels {
for _, channel := range bot.Config().Untappd.Channels {
go p.untappdLoop(channel)
}
return &p
@ -313,7 +313,7 @@ type Beers struct {
}
func (p *BeersPlugin) pullUntappd() ([]checkin, error) {
access_token := "?access_token=" + p.Bot.Config.Untappd.Token
access_token := "?access_token=" + p.Bot.Config().Untappd.Token
baseUrl := "https://api.untappd.com/v4/checkin/recent/"
url := baseUrl + access_token + "&limit=25"
@ -343,7 +343,7 @@ func (p *BeersPlugin) pullUntappd() ([]checkin, error) {
}
func (p *BeersPlugin) checkUntappd(channel string) {
token := p.Bot.Config.Untappd.Token
token := p.Bot.Config().Untappd.Token
if token == "" || token == "<Your Token>" {
log.Println("No Untappd token, cannot enable plugin.")
return
@ -418,7 +418,7 @@ func (p *BeersPlugin) checkUntappd(channel string) {
}
func (p *BeersPlugin) untappdLoop(channel string) {
frequency := p.Bot.Config.Untappd.Freq
frequency := p.Bot.Config().Untappd.Freq
log.Println("Checking every ", frequency, " seconds")

View File

@ -15,7 +15,7 @@ import (
// This is a counter plugin to count arbitrary things.
type CounterPlugin struct {
Bot *bot.Bot
Bot bot.Bot
DB *sqlx.DB
}
@ -102,9 +102,9 @@ func (i *Item) Delete() error {
}
// NewCounterPlugin creates a new CounterPlugin with the Plugin interface
func NewCounterPlugin(bot *bot.Bot) *CounterPlugin {
if bot.DBVersion == 1 {
if _, err := bot.DB.Exec(`create table if not exists counter (
func New(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,
@ -115,7 +115,7 @@ func NewCounterPlugin(bot *bot.Bot) *CounterPlugin {
}
return &CounterPlugin{
Bot: bot,
DB: bot.DB,
DB: bot.DB(),
}
}
@ -156,7 +156,7 @@ func (p *CounterPlugin) Message(message bot.Message) bool {
for _, it := range items {
count += 1
if count > 1 {
resp += ", "
resp += ","
}
resp += fmt.Sprintf(" %s: %d", it.Item, it.Count)
if count > 20 {
@ -271,13 +271,6 @@ func (p *CounterPlugin) Message(message bot.Message) bool {
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 *CounterPlugin) LoadData() {
// This bot has no data to load
}
// Help responds to help requests. Every plugin must implement a help function.
func (p *CounterPlugin) Help(channel string, parts []string) {
p.Bot.SendMessage(channel, "You can set counters incrementally by using "+

View File

@ -0,0 +1,168 @@
// © 2016 the CatBase Authors under the WTFPL license. See AUTHORS for the list of authors.
package counter
import (
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/velour/catbase/bot"
)
func makeMessage(payload string) bot.Message {
isCmd := strings.HasPrefix(payload, "!")
if isCmd {
payload = payload[1:]
}
return bot.Message{
User: &bot.User{Name: "tester"},
Channel: "test",
Body: payload,
Command: isCmd,
}
}
func TestCounterOne(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
assert.NotNil(t, c)
c.Message(makeMessage("test++"))
assert.Len(t, mb.Messages, 1)
assert.Equal(t, mb.Messages[0], "tester has 1 test.")
}
func TestCounterFour(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
assert.NotNil(t, c)
for i := 0; i < 4; i++ {
c.Message(makeMessage("test++"))
}
assert.Len(t, mb.Messages, 4)
assert.Equal(t, mb.Messages[3], "tester has 4 test.")
}
func TestCounterDecrement(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
assert.NotNil(t, c)
for i := 0; i < 4; i++ {
c.Message(makeMessage("test++"))
assert.Equal(t, mb.Messages[i], fmt.Sprintf("tester has %d test.", i+1))
}
c.Message(makeMessage("test--"))
assert.Len(t, mb.Messages, 5)
assert.Equal(t, mb.Messages[4], "tester has 3 test.")
}
func TestFriendCounterDecrement(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
assert.NotNil(t, c)
for i := 0; i < 4; i++ {
c.Message(makeMessage("other.test++"))
assert.Equal(t, mb.Messages[i], fmt.Sprintf("other has %d test.", i+1))
}
c.Message(makeMessage("other.test--"))
assert.Len(t, mb.Messages, 5)
assert.Equal(t, mb.Messages[4], "other has 3 test.")
}
func TestDecrementZero(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
assert.NotNil(t, c)
for i := 0; i < 4; i++ {
c.Message(makeMessage("test++"))
assert.Equal(t, mb.Messages[i], fmt.Sprintf("tester has %d test.", i+1))
}
j := 4
for i := 4; i > 0; i-- {
c.Message(makeMessage("test--"))
assert.Equal(t, mb.Messages[j], fmt.Sprintf("tester has %d test.", i-1))
j++
}
assert.Len(t, mb.Messages, 8)
assert.Equal(t, mb.Messages[7], "tester has 0 test.")
}
func TestClear(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
assert.NotNil(t, c)
for i := 0; i < 4; i++ {
c.Message(makeMessage("test++"))
assert.Equal(t, mb.Messages[i], fmt.Sprintf("tester has %d test.", i+1))
}
res := c.Message(makeMessage("!clear test"))
assert.True(t, res)
assert.Len(t, mb.Actions, 1)
assert.Equal(t, mb.Actions[0], "chops a few test out of his brain")
}
func TestCount(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
assert.NotNil(t, c)
for i := 0; i < 4; i++ {
c.Message(makeMessage("test++"))
assert.Equal(t, mb.Messages[i], fmt.Sprintf("tester has %d test.", i+1))
}
res := c.Message(makeMessage("!count test"))
assert.True(t, res)
assert.Len(t, mb.Messages, 5)
assert.Equal(t, mb.Messages[4], "tester has 4 test.")
}
func TestInspectMe(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
assert.NotNil(t, c)
for i := 0; i < 4; i++ {
c.Message(makeMessage("test++"))
assert.Equal(t, mb.Messages[i], fmt.Sprintf("tester has %d test.", i+1))
}
for i := 0; i < 2; i++ {
c.Message(makeMessage("fucks++"))
assert.Equal(t, mb.Messages[i+4], fmt.Sprintf("tester has %d fucks.", i+1))
}
for i := 0; i < 20; i++ {
c.Message(makeMessage("cheese++"))
assert.Equal(t, mb.Messages[i+6], fmt.Sprintf("tester has %d cheese.", i+1))
}
res := c.Message(makeMessage("!inspect me"))
assert.True(t, res)
assert.Len(t, mb.Messages, 27)
assert.Equal(t, mb.Messages[26], "tester has the following counters: test: 4, fucks: 2, cheese: 20.")
}
func TestHelp(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
assert.NotNil(t, c)
c.Help("channel", []string{})
assert.Len(t, mb.Messages, 1)
}
func TestBotMessage(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
assert.NotNil(t, c)
assert.False(t, c.BotMessage(makeMessage("test")))
}
func TestEvent(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
assert.NotNil(t, c)
assert.False(t, c.Event("dummy", makeMessage("test")))
}
func TestRegisterWeb(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
assert.NotNil(t, c)
assert.Nil(t, c.RegisterWeb())
}

View File

@ -15,11 +15,11 @@ import (
// This is a dice plugin to serve as an example and quick copy/paste for new plugins.
type DicePlugin struct {
Bot *bot.Bot
Bot bot.Bot
}
// NewDicePlugin creates a new DicePlugin with the Plugin interface
func NewDicePlugin(bot *bot.Bot) *DicePlugin {
func New(bot bot.Bot) *DicePlugin {
return &DicePlugin{
Bot: bot,
}

View File

@ -20,7 +20,7 @@ import (
// This is a downtime plugin to monitor how much our users suck
type DowntimePlugin struct {
Bot *bot.Bot
Bot bot.Bot
db *sqlx.DB
}
@ -100,13 +100,13 @@ func (ie idleEntries) Swap(i, j int) {
}
// NewDowntimePlugin creates a new DowntimePlugin with the Plugin interface
func NewDowntimePlugin(bot *bot.Bot) *DowntimePlugin {
func New(bot bot.Bot) *DowntimePlugin {
p := DowntimePlugin{
Bot: bot,
db: bot.DB,
db: bot.DB(),
}
if bot.DBVersion == 1 {
if bot.DBVersion() == 1 {
_, err := p.db.Exec(`create table if not exists downtime (
id integer primary key,
nick string,
@ -160,7 +160,7 @@ func (p *DowntimePlugin) Message(message bot.Message) bool {
for _, e := range entries {
// filter out ZNC entries and ourself
if strings.HasPrefix(e.nick, "*") || strings.ToLower(p.Bot.Config.Nick) == e.nick {
if strings.HasPrefix(e.nick, "*") || strings.ToLower(p.Bot.Config().Nick) == e.nick {
p.remove(e.nick)
} else {
tops = fmt.Sprintf("%s%s: %s ", tops, e.nick, time.Now().Sub(e.lastSeen))
@ -204,7 +204,7 @@ func (p *DowntimePlugin) Help(channel string, parts []string) {
// Empty event handler because this plugin does not do anything on event recv
func (p *DowntimePlugin) Event(kind string, message bot.Message) bool {
log.Println(kind, "\t", message)
if kind != "PART" && message.User.Name != p.Bot.Config.Nick {
if kind != "PART" && message.User.Name != p.Bot.Config().Nick {
// user joined, let's nail them for it
if kind == "NICK" {
p.record(strings.ToLower(message.Channel))

View File

@ -196,14 +196,14 @@ func getSingleFact(db *sqlx.DB, fact string) (*factoid, error) {
// FactoidPlugin provides the necessary plugin-wide needs
type FactoidPlugin struct {
Bot *bot.Bot
Bot bot.Bot
NotFound []string
LastFact *factoid
db *sqlx.DB
}
// NewFactoidPlugin creates a new FactoidPlugin with the Plugin interface
func NewFactoidPlugin(botInst *bot.Bot) *FactoidPlugin {
func New(botInst bot.Bot) *FactoidPlugin {
p := &FactoidPlugin{
Bot: botInst,
NotFound: []string{
@ -214,7 +214,7 @@ func NewFactoidPlugin(botInst *bot.Bot) *FactoidPlugin {
"NOPE! NOPE! NOPE!",
"One time, I learned how to jump rope.",
},
db: botInst.DB,
db: botInst.DB(),
}
_, err := p.db.Exec(`create table if not exists factoid (
@ -231,13 +231,13 @@ func NewFactoidPlugin(botInst *bot.Bot) *FactoidPlugin {
log.Fatal(err)
}
for _, channel := range botInst.Config.Channels {
for _, channel := range botInst.Config().Channels {
go p.factTimer(channel)
go func(ch string) {
// Some random time to start up
time.Sleep(time.Duration(15) * time.Second)
if ok, fact := p.findTrigger(p.Bot.Config.StartupFact); ok {
if ok, fact := p.findTrigger(p.Bot.Config().StartupFact); ok {
p.sayFact(bot.Message{
Channel: ch,
Body: "speed test", // BUG: This is defined in the config too
@ -596,7 +596,7 @@ func (p *FactoidPlugin) randomFact() *factoid {
// factTimer spits out a fact at a given interval and with given probability
func (p *FactoidPlugin) factTimer(channel string) {
duration := time.Duration(p.Bot.Config.QuoteTime) * time.Minute
duration := time.Duration(p.Bot.Config().QuoteTime) * time.Minute
myLastMsg := time.Now()
for {
time.Sleep(time.Duration(5) * time.Second)
@ -609,7 +609,7 @@ func (p *FactoidPlugin) factTimer(channel string) {
tdelta := time.Since(lastmsg.Time)
earlier := time.Since(myLastMsg) > tdelta
chance := rand.Float64()
success := chance < p.Bot.Config.QuoteChance
success := chance < p.Bot.Config().QuoteChance
if success && tdelta > duration && earlier {
fact := p.randomFact()
@ -617,9 +617,11 @@ func (p *FactoidPlugin) factTimer(channel string) {
continue
}
users := p.Bot.Who(channel)
// we need to fabricate a message so that bot.Filter can operate
message := bot.Message{
User: &p.Bot.Users[rand.Intn(len(p.Bot.Users))],
User: &users[rand.Intn(len(users))],
Channel: channel,
}
p.sayFact(message, *fact)

View File

@ -17,17 +17,17 @@ import (
// plugins.
type RememberPlugin struct {
Bot *bot.Bot
Bot bot.Bot
Log map[string][]bot.Message
db *sqlx.DB
}
// NewRememberPlugin creates a new RememberPlugin with the Plugin interface
func NewRememberPlugin(b *bot.Bot) *RememberPlugin {
func NewRemember(b bot.Bot) *RememberPlugin {
p := RememberPlugin{
Bot: b,
Log: make(map[string][]bot.Message),
db: b.DB,
db: b.DB(),
}
return &p
}
@ -167,8 +167,8 @@ func (p *RememberPlugin) quoteTimer(channel string) {
for {
// this pisses me off: You can't multiply int * time.Duration so it
// has to look ugly as shit.
time.Sleep(time.Duration(p.Bot.Config.QuoteTime) * time.Minute)
chance := 1.0 / p.Bot.Config.QuoteChance
time.Sleep(time.Duration(p.Bot.Config().QuoteTime) * time.Minute)
chance := 1.0 / p.Bot.Config().QuoteChance
if rand.Intn(int(chance)) == 0 {
msg := p.randQuote()
p.Bot.SendMessage(channel, msg)

View File

@ -18,7 +18,7 @@ import (
type FirstPlugin struct {
First *FirstEntry
Bot *bot.Bot
Bot bot.Bot
db *sqlx.DB
}
@ -46,9 +46,9 @@ func (fe *FirstEntry) save(db *sqlx.DB) error {
}
// NewFirstPlugin creates a new FirstPlugin with the Plugin interface
func NewFirstPlugin(b *bot.Bot) *FirstPlugin {
if b.DBVersion == 1 {
_, err := b.DB.Exec(`create table if not exists first (
func New(b bot.Bot) *FirstPlugin {
if b.DBVersion() == 1 {
_, err := b.DB().Exec(`create table if not exists first (
id integer primary key,
day integer,
time integer,
@ -62,14 +62,14 @@ func NewFirstPlugin(b *bot.Bot) *FirstPlugin {
log.Println("First plugin initialized with day:", midnight(time.Now()))
first, err := getLastFirst(b.DB)
first, err := getLastFirst(b.DB())
if err != nil {
log.Fatal("Could not initialize first plugin: ", err)
}
return &FirstPlugin{
Bot: b,
db: b.DB,
db: b.DB(),
First: first,
}
}
@ -151,7 +151,7 @@ func (p *FirstPlugin) Message(message bot.Message) bool {
}
func (p *FirstPlugin) allowed(message bot.Message) bool {
for _, msg := range p.Bot.Config.Bad.Msgs {
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)
@ -161,13 +161,13 @@ func (p *FirstPlugin) allowed(message bot.Message) bool {
return false
}
}
for _, host := range p.Bot.Config.Bad.Hosts {
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 {
for _, nick := range p.Bot.Config().Bad.Nicks {
if nick == message.User.Name {
log.Println("Disallowing first: ", message.User.Name, ":", message.Body)
return false

View File

@ -15,11 +15,11 @@ import (
)
type LeftpadPlugin struct {
bot *bot.Bot
bot bot.Bot
}
// New creates a new LeftpadPlugin with the Plugin interface
func New(bot *bot.Bot) *LeftpadPlugin {
func New(bot bot.Bot) *LeftpadPlugin {
p := LeftpadPlugin{
bot: bot,
}

View File

@ -2,7 +2,6 @@
package plugins
import "fmt"
import "github.com/velour/catbase/bot"
// Plugin interface defines the methods needed to accept a plugin
@ -14,96 +13,3 @@ type Plugin interface {
Help()
RegisterWeb()
}
// ---- Below are some example plugins
// Creates a new TestPlugin with the Plugin interface
func NewTestPlugin(bot *bot.Bot) *TestPlugin {
tp := TestPlugin{}
tp.LoadData()
tp.Bot = bot
return &tp
}
// TestPlugin type allows our plugin to store persistent state information
type TestPlugin struct {
Bot *bot.Bot
Responds []string
Name string
Feces string
helpmsg []string
}
func (p *TestPlugin) LoadData() {
config := GetPluginConfig("TestPlugin")
p.Name = config.Name
p.Feces = config.Values["Feces"].(string)
p.helpmsg = []string{
"TestPlugin just shows off how shit works.",
}
}
func (p *TestPlugin) Message(message bot.Message) bool {
user := message.User
channel := message.Channel
body := message.Body
fmt.Println(user, body)
fmt.Println("My plugin name is:", p.Name, " My feces are:", p.Feces)
p.Bot.SendMessage(channel, body)
return true
}
func (p *TestPlugin) Help(message bot.Message) {
for _, msg := range p.helpmsg {
p.Bot.SendMessage(message.Channel, msg)
}
}
// Empty event handler because this plugin does not do anything on event recv
func (p *TestPlugin) Event(kind string, message bot.Message) bool {
return false
}
// Handler for bot's own messages
func (p *TestPlugin) BotMessage(message bot.Message) bool {
return false
}
type PluginConfig struct {
Name string
Values map[string]interface{}
}
// Loads plugin config (could be out of a DB or something)
func GetPluginConfig(name string) PluginConfig {
return PluginConfig{
Name: "TestPlugin",
Values: map[string]interface{}{
"Feces": "test",
"Responds": "fucker",
},
}
}
// FalsePlugin shows how plugin fallthrough works for handling messages
type FalsePlugin struct{}
func (fp FalsePlugin) Message(user, message string) bool {
fmt.Println("FalsePlugin returning false.")
return false
}
func (fp FalsePlugin) LoadData() {
}
// Empty event handler because this plugin does not do anything on event recv
func (p *FalsePlugin) Event(kind string, message bot.Message) bool {
return false
}
// Handler for bot's own messages
func (p *FalsePlugin) BotMessage(message bot.Message) bool {
return false
}

View File

@ -1,53 +0,0 @@
// © 2013 the CatBase Authors under the WTFPL. See AUTHORS for the list of authors.
package plugins
import "github.com/velour/catbase/bot"
// This is a skeleton plugin to serve as an example and quick copy/paste for new plugins.
type SkeletonPlugin struct {
Bot *bot.Bot
}
// NewSkeletonPlugin creates a new SkeletonPlugin with the Plugin interface
func NewSkeletonPlugin(bot *bot.Bot) *SkeletonPlugin {
return &SkeletonPlugin{
Bot: bot,
}
}
// 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 *SkeletonPlugin) Message(message bot.Message) bool {
// This bot does not reply to anything
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 *SkeletonPlugin) LoadData() {
// This bot has no data to load
}
// Help responds to help requests. Every plugin must implement a help function.
func (p *SkeletonPlugin) Help(channel string, parts []string) {
p.Bot.SendMessage(channel, "Sorry, Skeleton does not do a goddamn thing.")
}
// Empty event handler because this plugin does not do anything on event recv
func (p *SkeletonPlugin) Event(kind string, message bot.Message) bool {
return false
}
// Handler for bot's own messages
func (p *SkeletonPlugin) BotMessage(message bot.Message) bool {
return false
}
// Register any web URLs desired
func (p *SkeletonPlugin) RegisterWeb() *string {
return nil
}

View File

@ -40,14 +40,14 @@ var goatse []string = []string{
}
type TalkerPlugin struct {
Bot *bot.Bot
Bot bot.Bot
enforceNicks bool
}
func New(bot *bot.Bot) *TalkerPlugin {
func New(bot bot.Bot) *TalkerPlugin {
return &TalkerPlugin{
Bot: bot,
enforceNicks: bot.Config.EnforceNicks,
enforceNicks: bot.Config().EnforceNicks,
}
}
@ -105,11 +105,11 @@ func (p *TalkerPlugin) Help(channel string, parts []string) {
// Empty event handler because this plugin does not do anything on event recv
func (p *TalkerPlugin) Event(kind string, message bot.Message) bool {
sayings := p.Bot.Config.WelcomeMsgs
sayings := p.Bot.Config().WelcomeMsgs
if len(sayings) == 0 {
return false
}
if kind == "JOIN" && strings.ToLower(message.User.Name) != strings.ToLower(p.Bot.Config.Nick) {
if kind == "JOIN" && strings.ToLower(message.User.Name) != strings.ToLower(p.Bot.Config().Nick) {
msg := fmt.Sprintf(sayings[rand.Intn(len(sayings))], message.User.Name)
p.Bot.SendMessage(message.Channel, msg)
return true

View File

@ -12,11 +12,11 @@ import (
)
type YourPlugin struct {
bot *bot.Bot
bot bot.Bot
}
// NewYourPlugin creates a new YourPlugin with the Plugin interface
func NewYourPlugin(bot *bot.Bot) *YourPlugin {
func New(bot bot.Bot) *YourPlugin {
rand.Seed(time.Now().Unix())
return &YourPlugin{
bot: bot,
@ -28,7 +28,7 @@ func NewYourPlugin(bot *bot.Bot) *YourPlugin {
// Otherwise, the function returns false and the bot continues execution of other plugins.
func (p *YourPlugin) Message(message bot.Message) bool {
lower := strings.ToLower(message.Body)
config := p.bot.Config.Your
config := p.bot.Config().Your
if len(message.Body) > config.MaxLength {
return false
}