Merge pull request #131 from velour/configurator

Configurator
This commit is contained in:
Chris Sexton 2019-01-21 15:14:45 -05:00 committed by GitHub
commit 396d3acec6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 486 additions and 1035 deletions

1
.gitignore vendored
View File

@ -29,3 +29,4 @@ vendor
.vscode/
*.code-workspace
*config.lua
modd.conf

View File

@ -3,7 +3,6 @@
package bot
import (
"database/sql"
"html/template"
"log"
"net/http"
@ -32,13 +31,6 @@ type bot struct {
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
logIn chan msg.Message
logOut chan msg.Messages
@ -64,7 +56,7 @@ func New(config *config.Config, connector Connector) Bot {
users := []user.User{
user.User{
Name: config.Nick,
Name: config.Get("Nick"),
},
}
@ -75,10 +67,8 @@ func New(config *config.Config, connector Connector) Bot {
conn: connector,
users: users,
me: users[0],
db: config.DBConn,
logIn: logIn,
logOut: logOut,
version: config.Version,
httpEndPoints: make(map[string]string),
filters: make(map[string]func(string) string),
}
@ -86,10 +76,12 @@ func New(config *config.Config, connector Connector) Bot {
bot.migrateDB()
http.HandleFunc("/", bot.serveRoot)
if config.HttpAddr == "" {
config.HttpAddr = "127.0.0.1:1337"
addr := config.Get("HttpAddr")
if addr == "" {
addr = "127.0.0.1:1337"
config.Set("HttpAddr", addr)
}
go http.ListenAndServe(config.HttpAddr, nil)
go http.ListenAndServe(addr, nil)
connector.RegisterMessageReceived(bot.MsgReceived)
connector.RegisterEventReceived(bot.EventReceived)
@ -103,39 +95,15 @@ 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
return b.config.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);`)
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)
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)
} else {
log.Printf("No versions, we're the first!.")
_, err := b.db.Exec(`insert into version (version) values (1)`)
if err != nil {
log.Fatal("Initial DB migration insert: ", err)
}
}
if _, err := b.db.Exec(`create table if not exists variables (
if _, err := b.DB().Exec(`create table if not exists variables (
id integer primary key,
name string,
value string
@ -204,8 +172,15 @@ func (b *bot) serveRoot(w http.ResponseWriter, r *http.Request) {
// Checks if message is a command and returns its curtailed version
func IsCmd(c *config.Config, message string) (bool, string) {
cmdcs := c.CommandChar
botnick := strings.ToLower(c.Nick)
cmdcs := c.GetArray("CommandChar")
if len(cmdcs) == 0 {
cmdcs = []string{"!"}
c.SetArray("CommandChar", cmdcs)
}
botnick := strings.ToLower(c.Get("Nick"))
if botnick == "" {
log.Fatalf(`You must run catbase -set nick -val <your bot nick>`)
}
iscmd := false
lowerMessage := strings.ToLower(message)
@ -237,7 +212,7 @@ func IsCmd(c *config.Config, message string) (bool, string) {
}
func (b *bot) CheckAdmin(nick string) bool {
for _, u := range b.Config().Admins {
for _, u := range b.Config().GetArray("Admins") {
if nick == u {
return true
}
@ -265,7 +240,7 @@ func (b *bot) NewUser(nick string) *user.User {
}
func (b *bot) checkAdmin(nick string) bool {
for _, u := range b.Config().Admins {
for _, u := range b.Config().GetArray("Admins") {
if nick == u {
return true
}

View File

@ -192,7 +192,7 @@ func (b *bot) Filter(message msg.Message, input string) string {
func (b *bot) getVar(varName string) (string, error) {
var text string
err := b.db.Get(&text, `select value from variables where name=? order by random() limit 1`, varName)
err := b.DB().Get(&text, `select value from variables where name=? order by random() limit 1`, varName)
switch {
case err == sql.ErrNoRows:
return "", fmt.Errorf("No factoid found")
@ -204,7 +204,7 @@ func (b *bot) getVar(varName string) (string, error) {
func (b *bot) listVars(channel string, parts []string) {
var variables []string
err := b.db.Select(&variables, `select name from variables group by name`)
err := b.DB().Select(&variables, `select name from variables group by name`)
if err != nil {
log.Fatal(err)
}

View File

@ -11,7 +11,6 @@ import (
type Bot interface {
Config() *config.Config
DBVersion() int64
DB() *sqlx.DB
Who(string) []user.User
AddHandler(string, Handler)

View File

@ -19,16 +19,16 @@ type MockBot struct {
mock.Mock
db *sqlx.DB
Cfg config.Config
Cfg *config.Config
Messages []string
Actions []string
Reactions []string
}
func (mb *MockBot) Config() *config.Config { return &mb.Cfg }
func (mb *MockBot) Config() *config.Config { return mb.Cfg }
func (mb *MockBot) DBVersion() int64 { return 1 }
func (mb *MockBot) DB() *sqlx.DB { return mb.db }
func (mb *MockBot) DB() *sqlx.DB { return mb.Cfg.DB }
func (mb *MockBot) Conn() Connector { return nil }
func (mb *MockBot) Who(string) []user.User { return []user.User{} }
func (mb *MockBot) AddHandler(name string, f Handler) {}
@ -94,12 +94,9 @@ func (mb *MockBot) GetEmojiList() map[string]string { return make
func (mb *MockBot) RegisterFilter(s string, f func(string) string) {}
func NewMockBot() *MockBot {
db, err := sqlx.Open("sqlite3_custom", ":memory:")
if err != nil {
log.Fatal("Failed to open database:", err)
}
cfg := config.ReadConfig("file::memory:?mode=memory&cache=shared")
b := MockBot{
db: db,
Cfg: cfg,
Messages: make([]string, 0),
Actions: make([]string, 0),
}

View File

@ -6,111 +6,118 @@ import (
"database/sql"
"fmt"
"log"
"os"
"regexp"
"strconv"
"strings"
"github.com/jmoiron/sqlx"
sqlite3 "github.com/mattn/go-sqlite3"
"github.com/yuin/gluamapper"
lua "github.com/yuin/gopher-lua"
)
// Config stores any system-wide startup information that cannot be easily configured via
// the database
type Config struct {
DBConn *sqlx.DB
*sqlx.DB
DB struct {
File string
Name string
Server string
DBFile string
}
// GetFloat64 returns the config value for a string key
// It will first look in the env vars for the key
// It will check the DB for the key if an env DNE
// Finally, it will return a zero value if the key does not exist
// It will attempt to convert the value to a float64 if it exists
func (c *Config) GetFloat64(key string) float64 {
f, err := strconv.ParseFloat(c.GetString(key), 64)
if err != nil {
return 0.0
}
Channels []string
MainChannel string
Plugins []string
Type string
Irc struct {
Server, Pass string
return f
}
// GetInt returns the config value for a string key
// It will first look in the env vars for the key
// It will check the DB for the key if an env DNE
// Finally, it will return a zero value if the key does not exist
// It will attempt to convert the value to an int if it exists
func (c *Config) GetInt(key string) int {
i, err := strconv.Atoi(c.GetString(key))
if err != nil {
return 0
}
Slack struct {
Token string
return i
}
// Get is a shortcut for GetString
func (c *Config) Get(key string) string {
return c.GetString(key)
}
func envkey(key string) string {
key = strings.ToUpper(key)
key = strings.Replace(key, ".", "", -1)
return key
}
// GetString returns the config value for a string key
// It will first look in the env vars for the key
// It will check the DB for the key if an env DNE
// Finally, it will return a zero value if the key does not exist
// It will convert the value to a string if it exists
func (c *Config) GetString(key string) string {
key = strings.ToLower(key)
if v, found := os.LookupEnv(envkey(key)); found {
return v
}
Nick string
IconURL string
FullName string
Version string
CommandChar []string
RatePerSec float64
LogLength int
Admins []string
HttpAddr string
Untappd struct {
Token string
Freq int
Channels []string
var configValue string
q := `select value from config where key=?`
err := c.DB.Get(&configValue, q, key)
if err != nil {
log.Printf("WARN: Key %s is empty", key)
return ""
}
Twitch struct {
Freq int
Users map[string][]string //channel -> usernames
ClientID string
Authorization string
return configValue
}
// GetArray returns the string slice config value for a string key
// It will first look in the env vars for the key with ;; separated values
// Look, I'm too lazy to do parsing to ensure that a comma is what the user meant
// It will check the DB for the key if an env DNE
// Finally, it will return a zero value if the key does not exist
// This will do no conversion.
func (c *Config) GetArray(key string) []string {
val := c.GetString(key)
if val == "" {
return []string{}
}
EnforceNicks bool
WelcomeMsgs []string
TwitterConsumerKey string
TwitterConsumerSecret string
TwitterUserKey string
TwitterUserSecret string
BadMsgs []string
Bad struct {
Msgs []string
Nicks []string
Hosts []string
return strings.Split(val, ";;")
}
// Set changes the value for a configuration in the database
// Note, this is always a string. Use the SetArray for an array helper
func (c *Config) Set(key, value string) error {
key = strings.ToLower(key)
q := (`insert into config (key,value) values (?, ?)
on conflict(key) do update set value=?;`)
tx, err := c.Begin()
if err != nil {
return err
}
Your struct {
MaxLength int
Replacements []Replacement
_, err = tx.Exec(q, key, value, value)
if err != nil {
return err
}
LeftPad struct {
MaxLen int
Who string
}
Factoid struct {
MinLen int
QuoteChance float64
QuoteTime int
StartupFact string
}
Babbler struct {
DefaultUsers []string
}
Reminder struct {
MaxBatchAdd int
}
Stats struct {
DBPath string
Sightings []string
}
Emojify struct {
Chance float64
Scoreless []string
}
Reaction struct {
GeneralChance float64
HarrassChance float64
NegativeHarrassmentMultiplier int
HarrassList []string
PositiveReactions []string
NegativeReactions []string
}
Inventory struct {
Max int
}
Sisyphus struct {
MinDecrement int
MaxDecrement int
MinPush int
MaxPush int
err = tx.Commit()
if err != nil {
return err
}
return nil
}
func (c *Config) SetArray(key string, values []string) error {
vals := strings.Join(values, ";;")
return c.Set(key, vals)
}
func init() {
@ -125,38 +132,31 @@ func init() {
})
}
type Replacement struct {
This string
That string
Frequency float64
}
// Readconfig loads the config data out of a JSON file located in cfile
func Readconfig(version, cfile string) *Config {
fmt.Printf("Using %s as config file.\n", cfile)
L := lua.NewState()
if err := L.DoFile(cfile); err != nil {
panic(err)
func ReadConfig(dbpath string) *Config {
if dbpath == "" {
dbpath = "catbase.db"
}
fmt.Printf("Using %s as database file.\n", dbpath)
var c Config
if err := gluamapper.Map(L.GetGlobal("config").(*lua.LTable), &c); err != nil {
panic(err)
}
c.Version = version
if c.Type == "" {
c.Type = "irc"
}
fmt.Printf("godeepintir version %s running.\n", c.Version)
sqlDB, err := sqlx.Open("sqlite3_custom", c.DB.File)
sqlDB, err := sqlx.Open("sqlite3_custom", dbpath)
if err != nil {
log.Fatal(err)
}
c.DBConn = sqlDB
c := Config{
DBFile: dbpath,
}
c.DB = sqlDB
if _, err := c.Exec(`create table if not exists config (
key string,
value string,
primary key (key)
);`); err != nil {
panic(err)
}
fmt.Printf("catbase is running.\n")
return &c
}

23
config/config_test.go Normal file
View File

@ -0,0 +1,23 @@
package config
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSetGet(t *testing.T) {
cfg := ReadConfig(":memory:")
expected := "value"
cfg.Set("test", expected)
actual := cfg.Get("test")
assert.Equal(t, expected, actual, "Config did not store values")
}
func TestSetGetArray(t *testing.T) {
cfg := ReadConfig(":memory:")
expected := []string{"a", "b", "c"}
cfg.SetArray("test", expected)
actual := cfg.GetArray("test")
assert.Equal(t, expected, actual, "Config did not store values")
}

57
config/defaults.go Normal file
View File

@ -0,0 +1,57 @@
package config
import (
"bytes"
"html/template"
"log"
"strings"
)
var q = `
INSERT INTO config VALUES('type','slack');
INSERT INTO config VALUES('nick','{{.Nick}}');
INSERT INTO config VALUES('channels','{{.Channel}}');
INSERT INTO config VALUES('factoid.quotetime',30);
INSERT INTO config VALUES('reaction.negativereactions','bullshit;;fake;;tableflip;;vomit');
INSERT INTO config VALUES('reaction.positivereactions','+1;;authorized;;aw_yeah;;yeah_man;;joy');
INSERT INTO config VALUES('reaction.generalchance',0.01);
INSERT INTO config VALUES('reaction.harrasschance',0.05);
INSERT INTO config VALUES('commandchar','!;;¡');
INSERT INTO config VALUES('factoid.startupfact','speed test');
INSERT INTO config VALUES('factoid.quotechance',0.99);
INSERT INTO config VALUES('factoid.minlen',4);
INSERT INTO config VALUES('untappd.channels','{{.Channel}}');
INSERT INTO config VALUES('twitch.channels','{{.Channel}}');
INSERT INTO config VALUES('twitch.{{.ChannelKey}}.users','drseabass;;phlyingpenguin;;stack5;;geoffwithaj;;msherms;;eaburns;;sheltim;;rathaus;;rcuhljr');
INSERT INTO config VALUES('twitch.freq',60);
INSERT INTO config VALUES('leftpad.maxlen',50);
INSERT INTO config VALUES('untappd.freq',60);
INSERT INTO config VALUES('your.replacements.0.freq',1);
INSERT INTO config VALUES('your.replacements.0.this','fuck');
INSERT INTO config VALUES('your.replacements.0.that','duck');
INSERT INTO config VALUES('your.replacements','0;;1;;2');
INSERT INTO config VALUES('httpaddr','127.0.0.1:1337');
INSERT INTO config VALUES('your.maxlength',140);
INSERT INTO config VALUES('init',1);
`
func (c *Config) SetDefaults(mainChannel, nick string) {
if nick == mainChannel && nick == "" {
log.Fatalf("You must provide a nick and a mainChannel")
}
t := template.Must(template.New("query").Parse(q))
vals := struct {
Nick string
Channel string
ChannelKey string
}{
nick,
mainChannel,
strings.ToLower(mainChannel),
}
var buf bytes.Buffer
t.Execute(&buf, vals)
c.MustExec(`delete from config;`)
c.MustExec(buf.String())
log.Println("Configuration initialized.")
}

View File

@ -87,7 +87,7 @@ func (i *Irc) SendMessage(channel, message string) string {
}
if throttle == nil {
ratePerSec := i.config.RatePerSec
ratePerSec := i.config.GetInt("RatePerSec")
throttle = time.Tick(time.Second / time.Duration(ratePerSec))
}
@ -136,17 +136,17 @@ func (i *Irc) Serve() error {
var err error
i.Client, err = irc.DialSSL(
i.config.Irc.Server,
i.config.Nick,
i.config.FullName,
i.config.Irc.Pass,
i.config.Get("Irc.Server"),
i.config.Get("Nick"),
i.config.Get("FullName"),
i.config.Get("Irc.Pass"),
true,
)
if err != nil {
return fmt.Errorf("%s", err)
}
for _, c := range i.config.Channels {
for _, c := range i.config.GetArray("channels") {
i.JoinChannel(c)
}
@ -270,7 +270,7 @@ func (i *Irc) buildMessage(inMsg irc.Msg) msg.Message {
}
channel := inMsg.Args[0]
if channel == i.config.Nick {
if channel == i.config.Get("Nick") {
channel = inMsg.Args[0]
}

34
main.go
View File

@ -38,23 +38,47 @@ import (
"github.com/velour/catbase/slack"
)
var (
key = flag.String("set", "", "Configuration key to set")
val = flag.String("val", "", "Configuration value to set")
initDB = flag.Bool("init", false, "Initialize the configuration DB")
)
func main() {
rand.Seed(time.Now().Unix())
var cfile = flag.String("config", "config.lua",
"Config file to load. (Defaults to config.lua)")
var dbpath = flag.String("db", "catbase.db",
"Database file to load. (Defaults to catbase.db)")
flag.Parse() // parses the logging flags.
c := config.Readconfig(Version, *cfile)
c := config.ReadConfig(*dbpath)
if *key != "" && *val != "" {
c.Set(*key, *val)
log.Printf("Set config %s: %s", *key, *val)
return
}
if (*initDB && len(flag.Args()) != 2) || (!*initDB && c.GetInt("init") != 1) {
log.Fatal(`You must run "catbase -init <channel> <nick>"`)
} else if *initDB {
c.SetDefaults(flag.Arg(0), flag.Arg(1))
return
}
var client bot.Connector
switch c.Type {
t := c.Get("type")
if t == "" {
c.Set("type", "slack")
t = "slack"
}
switch c.Get("type") {
case "irc":
client = irc.New(c)
case "slack":
client = slack.New(c)
default:
log.Fatalf("Unknown connection type: %s", c.Type)
log.Fatalf("Unknown connection type: %s", c.Get("type"))
}
b := bot.New(c, client)

View File

@ -13,7 +13,6 @@ import (
"github.com/jmoiron/sqlx"
"github.com/velour/catbase/bot"
"github.com/velour/catbase/bot/msg"
"github.com/velour/catbase/config"
)
var (
@ -25,7 +24,6 @@ var (
type BabblerPlugin struct {
Bot bot.Bot
db *sqlx.DB
config *config.Config
WithGoRoutines bool
}
@ -93,7 +91,6 @@ func New(bot bot.Bot) *BabblerPlugin {
plugin := &BabblerPlugin{
Bot: bot,
db: bot.DB(),
config: bot.Config(),
WithGoRoutines: true,
}

View File

@ -28,13 +28,18 @@ func makeMessage(payload string) msg.Message {
func newBabblerPlugin(mb *bot.MockBot) *BabblerPlugin {
bp := New(mb)
bp.WithGoRoutines = false
mb.DB().MustExec(`
delete from babblers;
delete from babblerWords;
delete from babblerNodes;
delete from babblerArcs;
`)
return bp
}
func TestBabblerNoBabbler(t *testing.T) {
mb := bot.NewMockBot()
bp := newBabblerPlugin(mb)
bp.config.Babbler.DefaultUsers = []string{"seabass"}
assert.NotNil(t, bp)
bp.Message(makeMessage("!seabass2 says"))
res := assert.Len(t, mb.Messages, 0)
@ -45,7 +50,6 @@ func TestBabblerNoBabbler(t *testing.T) {
func TestBabblerNothingSaid(t *testing.T) {
mb := bot.NewMockBot()
bp := newBabblerPlugin(mb)
bp.config.Babbler.DefaultUsers = []string{"seabass"}
assert.NotNil(t, bp)
res := bp.Message(makeMessage("initialize babbler for seabass"))
assert.True(t, res)
@ -59,7 +63,6 @@ func TestBabblerNothingSaid(t *testing.T) {
func TestBabbler(t *testing.T) {
mb := bot.NewMockBot()
bp := newBabblerPlugin(mb)
bp.config.Babbler.DefaultUsers = []string{"seabass"}
assert.NotNil(t, bp)
seabass := makeMessage("This is a message")
seabass.User = &user.User{Name: "seabass"}
@ -78,7 +81,6 @@ func TestBabbler(t *testing.T) {
func TestBabblerSeed(t *testing.T) {
mb := bot.NewMockBot()
bp := newBabblerPlugin(mb)
bp.config.Babbler.DefaultUsers = []string{"seabass"}
assert.NotNil(t, bp)
seabass := makeMessage("This is a message")
seabass.User = &user.User{Name: "seabass"}
@ -96,7 +98,6 @@ func TestBabblerSeed(t *testing.T) {
func TestBabblerMultiSeed(t *testing.T) {
mb := bot.NewMockBot()
bp := newBabblerPlugin(mb)
bp.config.Babbler.DefaultUsers = []string{"seabass"}
assert.NotNil(t, bp)
seabass := makeMessage("This is a message")
seabass.User = &user.User{Name: "seabass"}
@ -114,7 +115,6 @@ func TestBabblerMultiSeed(t *testing.T) {
func TestBabblerMultiSeed2(t *testing.T) {
mb := bot.NewMockBot()
bp := newBabblerPlugin(mb)
bp.config.Babbler.DefaultUsers = []string{"seabass"}
assert.NotNil(t, bp)
seabass := makeMessage("This is a message")
seabass.User = &user.User{Name: "seabass"}
@ -132,7 +132,6 @@ func TestBabblerMultiSeed2(t *testing.T) {
func TestBabblerBadSeed(t *testing.T) {
mb := bot.NewMockBot()
bp := newBabblerPlugin(mb)
bp.config.Babbler.DefaultUsers = []string{"seabass"}
assert.NotNil(t, bp)
seabass := makeMessage("This is a message")
seabass.User = &user.User{Name: "seabass"}
@ -149,7 +148,6 @@ func TestBabblerBadSeed(t *testing.T) {
func TestBabblerBadSeed2(t *testing.T) {
mb := bot.NewMockBot()
bp := newBabblerPlugin(mb)
bp.config.Babbler.DefaultUsers = []string{"seabass"}
assert.NotNil(t, bp)
seabass := makeMessage("This is a message")
seabass.User = &user.User{Name: "seabass"}
@ -166,7 +164,6 @@ func TestBabblerBadSeed2(t *testing.T) {
func TestBabblerSuffixSeed(t *testing.T) {
mb := bot.NewMockBot()
bp := newBabblerPlugin(mb)
bp.config.Babbler.DefaultUsers = []string{"seabass"}
assert.NotNil(t, bp)
seabass := makeMessage("This is message one")
seabass.User = &user.User{Name: "seabass"}
@ -186,7 +183,6 @@ func TestBabblerSuffixSeed(t *testing.T) {
func TestBabblerBadSuffixSeed(t *testing.T) {
mb := bot.NewMockBot()
bp := newBabblerPlugin(mb)
bp.config.Babbler.DefaultUsers = []string{"seabass"}
assert.NotNil(t, bp)
seabass := makeMessage("This is message one")
seabass.User = &user.User{Name: "seabass"}
@ -204,7 +200,6 @@ func TestBabblerBadSuffixSeed(t *testing.T) {
func TestBabblerBookendSeed(t *testing.T) {
mb := bot.NewMockBot()
bp := newBabblerPlugin(mb)
bp.config.Babbler.DefaultUsers = []string{"seabass"}
assert.NotNil(t, bp)
seabass := makeMessage("It's easier to test with unique messages")
seabass.User = &user.User{Name: "seabass"}
@ -218,7 +213,6 @@ func TestBabblerBookendSeed(t *testing.T) {
func TestBabblerBookendSeedShort(t *testing.T) {
mb := bot.NewMockBot()
bp := newBabblerPlugin(mb)
bp.config.Babbler.DefaultUsers = []string{"seabass"}
assert.NotNil(t, bp)
seabass := makeMessage("It's easier to test with unique messages")
seabass.User = &user.User{Name: "seabass"}
@ -232,7 +226,6 @@ func TestBabblerBookendSeedShort(t *testing.T) {
func TestBabblerBadBookendSeed(t *testing.T) {
mb := bot.NewMockBot()
bp := newBabblerPlugin(mb)
bp.config.Babbler.DefaultUsers = []string{"seabass"}
assert.NotNil(t, bp)
seabass := makeMessage("It's easier to test with unique messages")
seabass.User = &user.User{Name: "seabass"}
@ -246,7 +239,6 @@ func TestBabblerBadBookendSeed(t *testing.T) {
func TestBabblerMiddleOutSeed(t *testing.T) {
mb := bot.NewMockBot()
bp := newBabblerPlugin(mb)
bp.config.Babbler.DefaultUsers = []string{"seabass"}
assert.NotNil(t, bp)
seabass := makeMessage("It's easier to test with unique messages")
seabass.User = &user.User{Name: "seabass"}
@ -260,7 +252,6 @@ func TestBabblerMiddleOutSeed(t *testing.T) {
func TestBabblerBadMiddleOutSeed(t *testing.T) {
mb := bot.NewMockBot()
bp := newBabblerPlugin(mb)
bp.config.Babbler.DefaultUsers = []string{"seabass"}
assert.NotNil(t, bp)
seabass := makeMessage("It's easier to test with unique messages")
seabass.User = &user.User{Name: "seabass"}
@ -274,7 +265,6 @@ func TestBabblerBadMiddleOutSeed(t *testing.T) {
func TestBabblerBatch(t *testing.T) {
mb := bot.NewMockBot()
bp := newBabblerPlugin(mb)
bp.config.Babbler.DefaultUsers = []string{"seabass"}
assert.NotNil(t, bp)
seabass := makeMessage("batch learn for seabass This is a message! This is another message. This is not a long message? This is not a message! This is not another message. This is a long message?")
res := bp.Message(seabass)
@ -289,7 +279,6 @@ func TestBabblerBatch(t *testing.T) {
func TestBabblerMerge(t *testing.T) {
mb := bot.NewMockBot()
bp := newBabblerPlugin(mb)
bp.config.Babbler.DefaultUsers = []string{"seabass"}
assert.NotNil(t, bp)
seabass := makeMessage("<seabass> This is a message")

View File

@ -37,9 +37,8 @@ type untappdUser struct {
chanNick string
}
// NewBeersPlugin creates a new BeersPlugin with the Plugin interface
// New BeersPlugin creates a new BeersPlugin with the Plugin interface
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,
@ -49,13 +48,11 @@ func New(bot bot.Bot) *BeersPlugin {
);`); err != nil {
log.Fatal(err)
}
}
p := BeersPlugin{
Bot: bot,
db: bot.DB(),
}
p.LoadData()
for _, channel := range bot.Config().Untappd.Channels {
for _, channel := range bot.Config().GetArray("Untappd.Channels") {
go p.untappdLoop(channel)
}
return &p
@ -198,13 +195,6 @@ func (p *BeersPlugin) Event(kind string, message msg.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 *BeersPlugin) LoadData() {
rand.Seed(time.Now().Unix())
}
// Help responds to help requests. Every plugin must implement a help function.
func (p *BeersPlugin) Help(channel string, parts []string) {
msg := "Beers: imbibe by using either beers +=,=,++ or with the !imbibe/drink " +
@ -316,7 +306,12 @@ type Beers struct {
}
func (p *BeersPlugin) pullUntappd() ([]checkin, error) {
access_token := "?access_token=" + p.Bot.Config().Untappd.Token
token := p.Bot.Config().Get("Untappd.Token")
if token == "" {
return []checkin{}, fmt.Errorf("No untappd token")
}
access_token := "?access_token=" + token
baseUrl := "https://api.untappd.com/v4/checkin/recent/"
url := baseUrl + access_token + "&limit=25"
@ -346,9 +341,9 @@ func (p *BeersPlugin) pullUntappd() ([]checkin, error) {
}
func (p *BeersPlugin) checkUntappd(channel string) {
token := p.Bot.Config().Untappd.Token
if token == "" || token == "<Your Token>" {
log.Println("No Untappd token, cannot enable plugin.")
token := p.Bot.Config().Get("Untappd.Token")
if token == "" {
log.Println(`Set config value "untappd.token" if you wish to enable untappd`)
return
}
@ -431,7 +426,10 @@ func (p *BeersPlugin) checkUntappd(channel string) {
}
func (p *BeersPlugin) untappdLoop(channel string) {
frequency := p.Bot.Config().Untappd.Freq
frequency := p.Bot.Config().GetInt("Untappd.Freq")
if frequency == 0 {
return
}
log.Println("Checking every ", frequency, " seconds")

View File

@ -29,13 +29,24 @@ func makeMessage(payload string) msg.Message {
func makeBeersPlugin(t *testing.T) (*BeersPlugin, *bot.MockBot) {
mb := bot.NewMockBot()
counter.New(mb)
mb.DB().MustExec(`delete from counter; delete from counter_alias;`)
b := New(mb)
assert.NotNil(t, b)
b.Message(makeMessage("!mkalias beer :beer:"))
b.Message(makeMessage("!mkalias beers :beer:"))
return b, mb
}
func TestCounter(t *testing.T) {
_, mb := makeBeersPlugin(t)
i, err := counter.GetItem(mb.DB(), "tester", "test")
if !assert.Nil(t, err) {
t.Log(err)
t.Fatal()
}
err = i.Update(5)
assert.Nil(t, err)
}
func TestImbibe(t *testing.T) {
b, mb := makeBeersPlugin(t)
b.Message(makeMessage("!imbibe"))

View File

@ -133,6 +133,9 @@ func GetItem(db *sqlx.DB, nick, itemName string) (Item, error) {
func (i *Item) Create() error {
res, err := i.Exec(`insert into counter (nick, item, count) values (?, ?, ?);`,
i.Nick, i.Item, i.Count)
if err != nil {
return err
}
id, _ := res.LastInsertId()
// hackhackhack?
i.ID = id
@ -170,21 +173,19 @@ func (i *Item) Delete() error {
// NewCounterPlugin creates a new CounterPlugin with the Plugin interface
func New(bot bot.Bot) *CounterPlugin {
if _, err := bot.DB().Exec(`create table if not exists counter (
tx := bot.DB().MustBegin()
bot.DB().MustExec(`create table if not exists counter (
id integer primary key,
nick string,
item string,
count integer
);`); err != nil {
log.Fatal(err)
}
if _, err := bot.DB().Exec(`create table if not exists counter_alias (
);`)
bot.DB().MustExec(`create table if not exists counter_alias (
id integer PRIMARY KEY AUTOINCREMENT,
item string NOT NULL UNIQUE,
points_to string NOT NULL
);`); err != nil {
log.Fatal(err)
}
);`)
tx.Commit()
return &CounterPlugin{
Bot: bot,
DB: bot.DB(),

View File

@ -16,6 +16,7 @@ import (
func setup(t *testing.T) (*bot.MockBot, *CounterPlugin) {
mb := bot.NewMockBot()
c := New(mb)
mb.DB().MustExec(`delete from counter; delete from counter_alias;`)
_, err := MkAlias(mb.DB(), "tea", ":tea:")
assert.Nil(t, err)
return mb, c

View File

@ -34,7 +34,7 @@ func (p *DBPlugin) RegisterWeb() *string {
}
func (p *DBPlugin) serveQuery(w http.ResponseWriter, r *http.Request) {
f, err := os.Open(p.bot.Config().DB.File)
f, err := os.Open(p.bot.Config().DBFile)
defer f.Close()
if err != nil {
log.Printf("Error opening DB for web service: %s", err)

View File

@ -107,7 +107,6 @@ func New(bot bot.Bot) *DowntimePlugin {
db: bot.DB(),
}
if bot.DBVersion() == 1 {
_, err := p.db.Exec(`create table if not exists downtime (
id integer primary key,
nick string,
@ -116,7 +115,6 @@ func New(bot bot.Bot) *DowntimePlugin {
if err != nil {
log.Fatal("Error creating downtime table: ", err)
}
}
return &p
}
@ -161,7 +159,7 @@ func (p *DowntimePlugin) Message(message msg.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().Get("Nick")) == e.nick {
p.remove(e.nick)
} else {
tops = fmt.Sprintf("%s%s: %s ", tops, e.nick, time.Now().Sub(e.lastSeen))
@ -205,7 +203,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 msg.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().Get("Nick") {
// user joined, let's nail them for it
if kind == "NICK" {
p.record(strings.ToLower(message.Channel))

View File

@ -64,7 +64,7 @@ func (p *EmojifyMePlugin) Message(message msg.Message) bool {
}
}
inertTokens := p.Bot.Config().Emojify.Scoreless
inertTokens := p.Bot.Config().GetArray("Emojify.Scoreless")
emojied := 0.0
tokens := strings.Fields(strings.ToLower(message.Body))
for i, token := range tokens {
@ -93,7 +93,7 @@ func (p *EmojifyMePlugin) Message(message msg.Message) bool {
}
}
}
if emojied > 0 && rand.Float64() <= p.Bot.Config().Emojify.Chance*emojied {
if emojied > 0 && rand.Float64() <= p.Bot.Config().GetFloat64("Emojify.Chance")*emojied {
modified := strings.Join(tokens, " ")
p.Bot.SendMessage(message.Channel, modified)
return true

View File

@ -297,13 +297,13 @@ func New(botInst bot.Bot) *Factoid {
log.Fatal(err)
}
for _, channel := range botInst.Config().Channels {
for _, channel := range botInst.Config().GetArray("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().Factoid.StartupFact); ok {
if ok, fact := p.findTrigger(p.Bot.Config().Get("Factoid.StartupFact")); ok {
p.sayFact(msg.Message{
Channel: ch,
Body: "speed test", // BUG: This is defined in the config too
@ -430,7 +430,7 @@ func (p *Factoid) sayFact(message msg.Message, fact factoid) {
// 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 *Factoid) trigger(message msg.Message) bool {
minLen := p.Bot.Config().Factoid.MinLen
minLen := p.Bot.Config().GetInt("Factoid.MinLen")
if len(message.Body) > minLen || message.Command || message.Body == "..." {
if ok, fact := p.findTrigger(message.Body); ok {
p.sayFact(message, *fact)
@ -691,7 +691,12 @@ func (p *Factoid) randomFact() *factoid {
// factTimer spits out a fact at a given interval and with given probability
func (p *Factoid) factTimer(channel string) {
duration := time.Duration(p.Bot.Config().Factoid.QuoteTime) * time.Minute
quoteTime := p.Bot.Config().GetInt("Factoid.QuoteTime")
if quoteTime == 0 {
quoteTime = 30
p.Bot.Config().Set("Factoid.QuoteTime", "30")
}
duration := time.Duration(quoteTime) * time.Minute
myLastMsg := time.Now()
for {
time.Sleep(time.Duration(5) * time.Second) // why 5?
@ -705,7 +710,12 @@ func (p *Factoid) factTimer(channel string) {
tdelta := time.Since(lastmsg.Time)
earlier := time.Since(myLastMsg) > tdelta
chance := rand.Float64()
success := chance < p.Bot.Config().Factoid.QuoteChance
quoteChance := p.Bot.Config().GetFloat64("Factoid.QuoteChance")
if quoteChance == 0.0 {
quoteChance = 0.99
p.Bot.Config().Set("Factoid.QuoteChance", "0.99")
}
success := chance < quoteChance
if success && tdelta > duration && earlier {
fact := p.randomFact()

View File

@ -48,7 +48,6 @@ func (fe *FirstEntry) save(db *sqlx.DB) error {
// NewFirstPlugin creates a new FirstPlugin with the Plugin interface
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,
@ -59,7 +58,6 @@ func New(b bot.Bot) *FirstPlugin {
if err != nil {
log.Fatal("Could not create first table: ", err)
}
}
log.Println("First plugin initialized with day:", midnight(time.Now()))
@ -152,7 +150,7 @@ func (p *FirstPlugin) Message(message msg.Message) bool {
}
func (p *FirstPlugin) allowed(message msg.Message) bool {
for _, msg := range p.Bot.Config().Bad.Msgs {
for _, msg := range p.Bot.Config().GetArray("Bad.Msgs") {
match, err := regexp.MatchString(msg, strings.ToLower(message.Body))
if err != nil {
log.Println("Bad regexp: ", err)
@ -162,13 +160,13 @@ func (p *FirstPlugin) allowed(message msg.Message) bool {
return false
}
}
for _, host := range p.Bot.Config().Bad.Hosts {
for _, host := range p.Bot.Config().GetArray("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().GetArray("Bad.Nicks") {
if nick == message.User.Name {
log.Println("Disallowing first: ", message.User.Name, ":", message.Body)
return false

View File

@ -8,6 +8,7 @@ import (
"fmt"
"log"
"regexp"
"strconv"
"strings"
"github.com/jmoiron/sqlx"
@ -26,15 +27,16 @@ type InventoryPlugin struct {
// New creates a new InventoryPlugin with the Plugin interface
func New(bot bot.Bot) *InventoryPlugin {
config := bot.Config()
nick := config.Get("nick")
r1, err := regexp.Compile("take this (.+)")
checkerr(err)
r2, err := regexp.Compile("have a (.+)")
checkerr(err)
r3, err := regexp.Compile(fmt.Sprintf("puts (.+) in %s([^a-zA-Z].*)?", config.Nick))
r3, err := regexp.Compile(fmt.Sprintf("puts (.+) in %s([^a-zA-Z].*)?", nick))
checkerr(err)
r4, err := regexp.Compile(fmt.Sprintf("gives %s (.+)", config.Nick))
r4, err := regexp.Compile(fmt.Sprintf("gives %s (.+)", nick))
checkerr(err)
r5, err := regexp.Compile(fmt.Sprintf("gives (.+) to %s([^a-zA-Z].*)?", config.Nick))
r5, err := regexp.Compile(fmt.Sprintf("gives (.+) to %s([^a-zA-Z].*)?", nick))
checkerr(err)
p := InventoryPlugin{
@ -200,7 +202,12 @@ func (p *InventoryPlugin) addItem(m msg.Message, i string) bool {
return true
}
var removed string
if p.count() > p.config.Inventory.Max {
max := p.config.GetInt("inventory.max")
if max == 0 {
max = 10
p.config.Set("inventory.max", strconv.Itoa(max))
}
if p.count() > max {
removed = p.removeRandom()
}
_, err := p.Exec(`INSERT INTO inventory (item) values (?)`, i)

View File

@ -45,8 +45,13 @@ func (p *LeftpadPlugin) Message(message msg.Message) bool {
p.bot.SendMessage(message.Channel, "Invalid padding number")
return true
}
if length > p.config.LeftPad.MaxLen && p.config.LeftPad.MaxLen > 0 {
msg := fmt.Sprintf("%s would kill me if I did that.", p.config.LeftPad.Who)
maxLen, who := p.config.GetInt("LeftPad.MaxLen"), p.config.Get("LeftPad.Who")
if who == "" {
who = "Putin"
p.config.Set("LeftPad.MaxLen", who)
}
if length > maxLen && maxLen > 0 {
msg := fmt.Sprintf("%s would kill me if I did that.", p.config.Get("LeftPad.Who"))
p.bot.SendMessage(message.Channel, msg)
return true
}

View File

@ -31,6 +31,7 @@ func makePlugin(t *testing.T) (*LeftpadPlugin, *bot.MockBot) {
counter.New(mb)
p := New(mb)
assert.NotNil(t, p)
p.config.Set("LeftPad.MaxLen", "0")
return p, mb
}
@ -56,6 +57,7 @@ func TestNotCommand(t *testing.T) {
func TestNoMaxLen(t *testing.T) {
p, mb := makePlugin(t)
p.config.Set("LeftPad.MaxLen", "0")
p.Message(makeMessage("!leftpad dicks 100 dicks"))
assert.Len(t, mb.Messages, 1)
assert.Contains(t, mb.Messages[0], "dicks")
@ -63,7 +65,8 @@ func TestNoMaxLen(t *testing.T) {
func Test50Padding(t *testing.T) {
p, mb := makePlugin(t)
p.config.LeftPad.MaxLen = 50
p.config.Set("LeftPad.MaxLen", "50")
assert.Equal(t, 50, p.config.GetInt("LeftPad.MaxLen"))
p.Message(makeMessage("!leftpad dicks 100 dicks"))
assert.Len(t, mb.Messages, 1)
assert.Contains(t, mb.Messages[0], "kill me")
@ -71,7 +74,7 @@ func Test50Padding(t *testing.T) {
func TestUnder50Padding(t *testing.T) {
p, mb := makePlugin(t)
p.config.LeftPad.MaxLen = 50
p.config.Set("LeftPad.MaxLen", "50")
p.Message(makeMessage("!leftpad dicks 49 dicks"))
assert.Len(t, mb.Messages, 1)
assert.Contains(t, mb.Messages[0], "dicks")

View File

@ -24,23 +24,23 @@ func New(bot bot.Bot) *ReactionPlugin {
func (p *ReactionPlugin) Message(message msg.Message) bool {
harrass := false
for _, nick := range p.Config.Reaction.HarrassList {
for _, nick := range p.Config.GetArray("Reaction.HarrassList") {
if message.User.Name == nick {
harrass = true
break
}
}
chance := p.Config.Reaction.GeneralChance
chance := p.Config.GetFloat64("Reaction.GeneralChance")
negativeWeight := 1
if harrass {
chance = p.Config.Reaction.HarrassChance
negativeWeight = p.Config.Reaction.NegativeHarrassmentMultiplier
chance = p.Config.GetFloat64("Reaction.HarrassChance")
negativeWeight = p.Config.GetInt("Reaction.NegativeHarrassmentMultiplier")
}
if rand.Float64() < chance {
numPositiveReactions := len(p.Config.Reaction.PositiveReactions)
numNegativeReactions := len(p.Config.Reaction.NegativeReactions)
numPositiveReactions := len(p.Config.GetArray("Reaction.PositiveReactions"))
numNegativeReactions := len(p.Config.GetArray("Reaction.NegativeReactions"))
maxIndex := numPositiveReactions + numNegativeReactions*negativeWeight
@ -49,11 +49,11 @@ func (p *ReactionPlugin) Message(message msg.Message) bool {
reaction := ""
if index < numPositiveReactions {
reaction = p.Config.Reaction.PositiveReactions[index]
reaction = p.Config.GetArray("Reaction.PositiveReactions")[index]
} else {
index -= numPositiveReactions
index %= numNegativeReactions
reaction = p.Config.Reaction.NegativeReactions[index]
reaction = p.Config.GetArray("Reaction.NegativeReactions")[index]
}
p.Bot.React(message.Channel, reaction, message)

View File

@ -40,7 +40,6 @@ type Reminder struct {
func New(bot bot.Bot) *ReminderPlugin {
log.SetFlags(log.LstdFlags | log.Lshortfile)
if bot.DBVersion() == 1 {
if _, err := bot.DB().Exec(`create table if not exists reminders (
id integer primary key,
fromWho string,
@ -51,7 +50,6 @@ func New(bot bot.Bot) *ReminderPlugin {
);`); err != nil {
log.Fatal(err)
}
}
dur, _ := time.ParseDuration("1h")
timer := time.NewTimer(dur)
@ -124,7 +122,12 @@ func (p *ReminderPlugin) Message(message msg.Message) bool {
what := strings.Join(parts[6:], " ")
for i := 0; when.Before(endTime); i++ {
if i >= p.config.Reminder.MaxBatchAdd {
max := p.config.GetInt("Reminder.MaxBatchAdd")
if max == 0 {
max = 10
p.config.Set("reminder.maxbatchadd", strconv.Itoa(max))
}
if i >= max {
p.Bot.SendMessage(channel, "Easy cowboy, that's a lot of reminders. I'll add some of them.")
doConfirm = false
break

View File

@ -40,10 +40,15 @@ func makeMessageBy(payload, by string) msg.Message {
}
}
func TestMeReminder(t *testing.T) {
func setup(t *testing.T) (*ReminderPlugin, *bot.MockBot) {
mb := bot.NewMockBot()
c := New(mb)
assert.NotNil(t, c)
r := New(mb)
mb.DB().MustExec(`delete from reminders; delete from config;`)
return r, mb
}
func TestMeReminder(t *testing.T) {
c, mb := setup(t)
res := c.Message(makeMessage("!remind me in 1s don't fail this test"))
time.Sleep(2 * time.Second)
assert.Len(t, mb.Messages, 2)
@ -53,9 +58,7 @@ func TestMeReminder(t *testing.T) {
}
func TestReminder(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
assert.NotNil(t, c)
c, mb := setup(t)
res := c.Message(makeMessage("!remind testuser in 1s don't fail this test"))
time.Sleep(2 * time.Second)
assert.Len(t, mb.Messages, 2)
@ -65,9 +68,7 @@ func TestReminder(t *testing.T) {
}
func TestReminderReorder(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
assert.NotNil(t, c)
c, mb := setup(t)
res := c.Message(makeMessage("!remind testuser in 2s don't fail this test 2"))
assert.True(t, res)
res = c.Message(makeMessage("!remind testuser in 1s don't fail this test 1"))
@ -81,9 +82,7 @@ func TestReminderReorder(t *testing.T) {
}
func TestReminderParse(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
assert.NotNil(t, c)
c, mb := setup(t)
res := c.Message(makeMessage("!remind testuser in unparseable don't fail this test"))
assert.Len(t, mb.Messages, 1)
assert.True(t, res)
@ -91,9 +90,7 @@ func TestReminderParse(t *testing.T) {
}
func TestEmptyList(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
assert.NotNil(t, c)
c, mb := setup(t)
res := c.Message(makeMessage("!list reminders"))
assert.Len(t, mb.Messages, 1)
assert.True(t, res)
@ -101,9 +98,7 @@ func TestEmptyList(t *testing.T) {
}
func TestList(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
assert.NotNil(t, c)
c, mb := setup(t)
res := c.Message(makeMessage("!remind testuser in 5m don't fail this test 1"))
assert.True(t, res)
res = c.Message(makeMessage("!remind testuser in 5m don't fail this test 2"))
@ -116,9 +111,7 @@ func TestList(t *testing.T) {
}
func TestListBy(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
assert.NotNil(t, c)
c, mb := setup(t)
res := c.Message(makeMessageBy("!remind testuser in 5m don't fail this test 1", "testuser"))
assert.True(t, res)
res = c.Message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2"))
@ -131,9 +124,7 @@ func TestListBy(t *testing.T) {
}
func TestListTo(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
assert.NotNil(t, c)
c, mb := setup(t)
res := c.Message(makeMessageBy("!remind testuser2 in 5m don't fail this test 1", "testuser"))
assert.True(t, res)
res = c.Message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2"))
@ -146,9 +137,7 @@ func TestListTo(t *testing.T) {
}
func TestToEmptyList(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
assert.NotNil(t, c)
c, mb := setup(t)
res := c.Message(makeMessageBy("!remind testuser2 in 5m don't fail this test 1", "testuser"))
assert.True(t, res)
res = c.Message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2"))
@ -160,9 +149,7 @@ func TestToEmptyList(t *testing.T) {
}
func TestFromEmptyList(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
assert.NotNil(t, c)
c, mb := setup(t)
res := c.Message(makeMessageBy("!remind testuser2 in 5m don't fail this test 1", "testuser"))
assert.True(t, res)
res = c.Message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2"))
@ -173,24 +160,9 @@ func TestFromEmptyList(t *testing.T) {
assert.Contains(t, mb.Messages[2], "no pending reminders")
}
func TestBatch(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
c.config.Reminder.MaxBatchAdd = 50
assert.NotNil(t, c)
res := c.Message(makeMessage("!remind testuser every 1ms for 5ms yikes"))
assert.True(t, res)
time.Sleep(2 * time.Second)
assert.Len(t, mb.Messages, 6)
for i := 0; i < 5; i++ {
assert.Contains(t, mb.Messages[i+1], "Hey testuser, tester wanted you to be reminded: yikes")
}
}
func TestBatchMax(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
c.config.Reminder.MaxBatchAdd = 10
c, mb := setup(t)
c.config.Set("Reminder.MaxBatchAdd", "10")
assert.NotNil(t, c)
res := c.Message(makeMessage("!remind testuser every 1h for 24h yikes"))
assert.True(t, res)
@ -206,8 +178,7 @@ func TestBatchMax(t *testing.T) {
}
func TestCancel(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
c, mb := setup(t)
assert.NotNil(t, c)
res := c.Message(makeMessage("!remind testuser in 1m don't fail this test"))
assert.True(t, res)
@ -222,8 +193,7 @@ func TestCancel(t *testing.T) {
}
func TestCancelMiss(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
c, mb := setup(t)
assert.NotNil(t, c)
res := c.Message(makeMessage("!cancel reminder 1"))
assert.True(t, res)
@ -232,30 +202,26 @@ func TestCancelMiss(t *testing.T) {
}
func TestHelp(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
c, mb := setup(t)
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)
c, _ := setup(t)
assert.NotNil(t, c)
assert.False(t, c.BotMessage(makeMessage("test")))
}
func TestEvent(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
c, _ := setup(t)
assert.NotNil(t, c)
assert.False(t, c.Event("dummy", makeMessage("test")))
}
func TestRegisterWeb(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
c, _ := setup(t)
assert.NotNil(t, c)
assert.Nil(t, c.RegisterWeb())
}

View File

@ -136,7 +136,7 @@ func (p *RPGPlugin) RegisterWeb() *string {
}
func (p *RPGPlugin) ReplyMessage(message msg.Message, identifier string) bool {
if strings.ToLower(message.User.Name) != strings.ToLower(p.Bot.Config().Nick) {
if strings.ToLower(message.User.Name) != strings.ToLower(p.Bot.Config().Get("Nick")) {
if b, ok := p.listenFor[identifier]; ok {
var res int

View File

@ -59,8 +59,14 @@ func (g *game) scheduleDecrement() {
if g.timers[0] != nil {
g.timers[0].Stop()
}
minDec := g.bot.Config().Sisyphus.MinDecrement
maxDec := g.bot.Config().Sisyphus.MinDecrement
minDec := g.bot.Config().GetInt("Sisyphus.MinDecrement")
maxDec := g.bot.Config().GetInt("Sisyphus.MaxDecrement")
if maxDec == minDec && maxDec == 0 {
maxDec = 30
minDec = 10
g.bot.Config().Set("Sisyphus.MinDecrement", strconv.Itoa(minDec))
g.bot.Config().Set("Sisyphus.MaxDecrement", strconv.Itoa(maxDec))
}
g.nextDec = time.Now().Add(time.Duration((minDec + rand.Intn(maxDec))) * time.Minute)
go func() {
t := time.NewTimer(g.nextDec.Sub(time.Now()))
@ -76,8 +82,14 @@ func (g *game) schedulePush() {
if g.timers[1] != nil {
g.timers[1].Stop()
}
minPush := g.bot.Config().Sisyphus.MinPush
maxPush := g.bot.Config().Sisyphus.MaxPush
minPush := g.bot.Config().GetInt("Sisyphus.MinPush")
maxPush := g.bot.Config().GetInt("Sisyphus.MaxPush")
if minPush == maxPush && maxPush == 0 {
minPush = 1
maxPush = 10
g.bot.Config().Set("Sisyphus.MinPush", strconv.Itoa(minPush))
g.bot.Config().Set("Sisyphus.MaxPush", strconv.Itoa(maxPush))
}
g.nextPush = time.Now().Add(time.Duration(rand.Intn(maxPush)+minPush) * time.Minute)
go func() {
t := time.NewTimer(g.nextPush.Sub(time.Now()))
@ -195,7 +207,7 @@ func (p *SisyphusPlugin) RegisterWeb() *string {
}
func (p *SisyphusPlugin) ReplyMessage(message msg.Message, identifier string) bool {
if strings.ToLower(message.User.Name) != strings.ToLower(p.Bot.Config().Nick) {
if strings.ToLower(message.User.Name) != strings.ToLower(p.Bot.Config().Get("Nick")) {
if g, ok := p.listenFor[identifier]; ok {
log.Printf("got message on %s: %+v", identifier, message)

View File

@ -1,279 +0,0 @@
// © 2016 the CatBase Authors under the WTFPL license. See AUTHORS for the list of authors.
// Stats contains the plugin that allows the bot record chat statistics
package stats
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/boltdb/bolt"
"github.com/velour/catbase/bot"
"github.com/velour/catbase/bot/msg"
"github.com/velour/catbase/config"
)
const (
DayFormat = "2006-01-02"
HourFormat = "2006-01-02-15"
HourBucket = "hour"
UserBucket = "user"
SightingBucket = "sighting"
)
type StatsPlugin struct {
bot bot.Bot
config *config.Config
}
// New creates a new StatsPlugin with the Plugin interface
func New(bot bot.Bot) *StatsPlugin {
p := StatsPlugin{
bot: bot,
config: bot.Config(),
}
return &p
}
type stat struct {
// date formatted: DayFormat
day string
// category
bucket string
// specific unique individual
key string
val value
}
func mkDay() string {
return time.Now().Format(DayFormat)
}
// The value type is here in the future growth case that we might want to put a
// struct of more interesting information into the DB
type value int
func (v value) Bytes() ([]byte, error) {
b, err := json.Marshal(v)
return b, err
}
func valueFromBytes(b []byte) (value, error) {
var v value
err := json.Unmarshal(b, &v)
return v, err
}
type stats []stat
// mkStat converts raw data to a stat struct
// Expected a string representation of the date formatted: DayFormat
func mkStat(day string, bucket, key, val []byte) (stat, error) {
v, err := valueFromBytes(val)
if err != nil {
log.Printf("mkStat: error getting value from bytes: %s", err)
return stat{}, err
}
return stat{
day: day,
bucket: string(bucket),
key: string(key),
val: v,
}, nil
}
// Another future-proofing function I shouldn't have written
func (v value) add(other value) value {
return v + other
}
func openDB(path string) (*bolt.DB, error) {
db, err := bolt.Open(path, 0600, &bolt.Options{
Timeout: 1 * time.Second,
})
if err != nil {
log.Printf("Couldn't open BoltDB for stats (%s): %s", path, err)
return nil, err
}
return db, err
}
// statFromDB takes a location specification and returns the data at that path
// Expected a string representation of the date formatted: DayFormat
func statFromDB(path, day, bucket, key string) (stat, error) {
db, err := openDB(path)
if err != nil {
return stat{}, err
}
defer db.Close()
buk := []byte(bucket)
k := []byte(key)
tx, err := db.Begin(true)
if err != nil {
log.Println("statFromDB: Error beginning the Tx")
return stat{}, err
}
defer tx.Rollback()
d, err := tx.CreateBucketIfNotExists([]byte(day))
if err != nil {
log.Println("statFromDB: Error creating the bucket")
return stat{}, err
}
b, err := d.CreateBucketIfNotExists(buk)
if err != nil {
log.Println("statFromDB: Error creating the bucket")
return stat{}, err
}
v := b.Get(k)
if err := tx.Commit(); err != nil {
log.Println("statFromDB: Error commiting the Tx")
return stat{}, err
}
if v == nil {
return stat{day, bucket, key, 0}, nil
}
return mkStat(day, buk, k, v)
}
// toDB takes a stat and records it, adding to the value in the DB if necessary
func (s stats) toDB(path string) error {
db, err := openDB(path)
if err != nil {
return err
}
defer db.Close()
for _, stat := range s {
err = db.Update(func(tx *bolt.Tx) error {
d, err := tx.CreateBucketIfNotExists([]byte(stat.day))
if err != nil {
log.Println("toDB: Error creating bucket")
return err
}
b, err := d.CreateBucketIfNotExists([]byte(stat.bucket))
if err != nil {
log.Println("toDB: Error creating bucket")
return err
}
valueInDB := b.Get([]byte(stat.key))
if valueInDB != nil {
val, err := valueFromBytes(valueInDB)
if err != nil {
log.Println("toDB: Error getting value from bytes")
return err
}
stat.val = stat.val.add(val)
}
v, err := stat.val.Bytes()
if err != nil {
return err
}
if stat.key == "" {
log.Println("Keys should not be empty")
return nil
}
log.Printf("Putting value in: '%s' %b, %+v", stat.key, []byte(stat.key), stat)
err = b.Put([]byte(stat.key), v)
return err
})
if err != nil {
return err
}
}
return nil
}
func (p *StatsPlugin) record(message msg.Message) {
statGenerators := []func(msg.Message) stats{
p.mkUserStat,
p.mkHourStat,
p.mkChannelStat,
p.mkSightingStat,
}
allStats := stats{}
for _, mk := range statGenerators {
stats := mk(message)
if stats != nil {
allStats = append(allStats, stats...)
}
}
allStats.toDB(p.bot.Config().Stats.DBPath)
}
func (p *StatsPlugin) Message(message msg.Message) bool {
p.record(message)
return false
}
func (p *StatsPlugin) Event(e string, message msg.Message) bool {
p.record(message)
return false
}
func (p *StatsPlugin) BotMessage(message msg.Message) bool {
p.record(message)
return false
}
func (p *StatsPlugin) Help(e string, m []string) {
}
func (p *StatsPlugin) serveQuery(w http.ResponseWriter, r *http.Request) {
f, err := os.Open(p.bot.Config().Stats.DBPath)
defer f.Close()
if err != nil {
log.Printf("Error opening DB for web service: %s", err)
fmt.Fprintf(w, "Error opening DB")
return
}
http.ServeContent(w, r, "stats.db", time.Now(), f)
}
func (p *StatsPlugin) RegisterWeb() *string {
http.HandleFunc("/stats", p.serveQuery)
tmp := "/stats"
return &tmp
}
func (p *StatsPlugin) mkUserStat(message msg.Message) stats {
return stats{stat{mkDay(), UserBucket, message.User.Name, 1}}
}
func (p *StatsPlugin) mkHourStat(message msg.Message) stats {
hr := time.Now().Hour()
return stats{stat{mkDay(), HourBucket, strconv.Itoa(hr), 1}}
}
func (p *StatsPlugin) mkSightingStat(message msg.Message) stats {
stats := stats{}
for _, name := range p.bot.Config().Stats.Sightings {
if strings.Contains(message.Body, name+" sighting") {
stats = append(stats, stat{mkDay(), SightingBucket, name, 1})
}
}
return stats
}
func (p *StatsPlugin) mkChannelStat(message msg.Message) stats {
return stats{stat{mkDay(), "channel", message.Channel, 1}}
}
func (p *StatsPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }

View File

@ -1,290 +0,0 @@
package stats
import (
"encoding/json"
"os"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/velour/catbase/bot"
"github.com/velour/catbase/bot/msg"
"github.com/velour/catbase/bot/user"
)
var dbPath = "test.db"
func TestJSON(t *testing.T) {
expected := 5
b, err := json.Marshal(expected)
assert.Nil(t, err)
t.Logf("%+v", expected)
t.Log(string(b))
}
func TestValueConversion(t *testing.T) {
expected := value(5)
b, err := expected.Bytes()
assert.Nil(t, err)
t.Log(string(b))
actual, err := valueFromBytes(b)
assert.Nil(t, err)
assert.Equal(t, actual, expected)
}
func rmDB(t *testing.T) {
err := os.Remove(dbPath)
if err != nil && !strings.Contains(err.Error(), "no such file or directory") {
t.Fatal(err)
}
}
func TestWithDB(t *testing.T) {
rmDB(t)
t.Run("TestDBReadWrite", func(t *testing.T) {
day := mkDay()
bucket := "testBucket"
key := "testKey"
expected := stats{stat{
day,
bucket,
key,
1,
}}
err := expected.toDB(dbPath)
assert.Nil(t, err)
actual, err := statFromDB(dbPath, day, bucket, key)
assert.Nil(t, err)
assert.Equal(t, actual, expected[0])
})
rmDB(t)
t.Run("TestDBAddStatInLoop", func(t *testing.T) {
day := mkDay()
bucket := "testBucket"
key := "testKey"
expected := value(25)
statPack := stats{stat{
day,
bucket,
key,
5,
}}
for i := 0; i < 5; i++ {
err := statPack.toDB(dbPath)
assert.Nil(t, err)
}
actual, err := statFromDB(dbPath, day, bucket, key)
assert.Nil(t, err)
assert.Equal(t, actual.val, expected)
})
rmDB(t)
t.Run("TestDBAddStats", func(t *testing.T) {
day := mkDay()
bucket := "testBucket"
key := "testKey"
expected := value(5)
statPack := stats{}
for i := 0; i < 5; i++ {
statPack = append(statPack, stat{
day,
bucket,
key,
1,
})
}
err := statPack.toDB(dbPath)
assert.Nil(t, err)
actual, err := statFromDB(dbPath, day, bucket, key)
assert.Nil(t, err)
assert.Equal(t, actual.val, expected)
})
rmDB(t)
}
func makeMessage(payload string) msg.Message {
isCmd := strings.HasPrefix(payload, "!")
if isCmd {
payload = payload[1:]
}
return msg.Message{
User: &user.User{Name: "tester"},
Channel: "test",
Body: payload,
Command: isCmd,
}
}
func testUserCounter(t *testing.T, count int) {
day := mkDay()
expected := value(count)
mb := bot.NewMockBot()
mb.Cfg.Stats.DBPath = dbPath
s := New(mb)
assert.NotNil(t, s)
for i := 0; i < count; i++ {
s.Message(makeMessage("test"))
}
_, err := os.Stat(dbPath)
assert.Nil(t, err)
stat, err := statFromDB(mb.Config().Stats.DBPath, day, "user", "tester")
assert.Nil(t, err)
actual := stat.val
assert.Equal(t, actual, expected)
}
func TestMessages(t *testing.T) {
_, err := os.Stat(dbPath)
assert.NotNil(t, err)
t.Run("TestOneUserCounter", func(t *testing.T) {
day := mkDay()
count := 5
expected := value(count)
mb := bot.NewMockBot()
mb.Cfg.Stats.DBPath = dbPath
s := New(mb)
assert.NotNil(t, s)
for i := 0; i < count; i++ {
s.Message(makeMessage("test"))
}
_, err := os.Stat(dbPath)
assert.Nil(t, err)
stat, err := statFromDB(mb.Config().Stats.DBPath, day, "user", "tester")
assert.Nil(t, err)
actual := stat.val
assert.Equal(t, actual, expected)
})
rmDB(t)
t.Run("TestTenUserCounter", func(t *testing.T) {
day := mkDay()
count := 5
expected := value(count)
mb := bot.NewMockBot()
mb.Cfg.Stats.DBPath = dbPath
s := New(mb)
assert.NotNil(t, s)
for i := 0; i < count; i++ {
s.Message(makeMessage("test"))
}
_, err := os.Stat(dbPath)
assert.Nil(t, err)
stat, err := statFromDB(mb.Config().Stats.DBPath, day, "user", "tester")
assert.Nil(t, err)
actual := stat.val
assert.Equal(t, actual, expected)
})
rmDB(t)
t.Run("TestChannelCounter", func(t *testing.T) {
day := mkDay()
count := 5
expected := value(count)
mb := bot.NewMockBot()
mb.Cfg.Stats.DBPath = dbPath
s := New(mb)
assert.NotNil(t, s)
for i := 0; i < count; i++ {
s.Message(makeMessage("test"))
}
_, err := os.Stat(dbPath)
assert.Nil(t, err)
stat, err := statFromDB(mb.Config().Stats.DBPath, day, "channel", "test")
assert.Nil(t, err)
actual := stat.val
assert.Equal(t, actual, expected)
})
rmDB(t)
t.Run("TestSightingCounter", func(t *testing.T) {
day := mkDay()
count := 5
expected := value(count)
mb := bot.NewMockBot()
mb.Cfg.Stats.DBPath = dbPath
mb.Cfg.Stats.Sightings = []string{"user", "nobody"}
s := New(mb)
assert.NotNil(t, s)
for i := 0; i < count; i++ {
s.Message(makeMessage("user sighting"))
}
_, err := os.Stat(dbPath)
assert.Nil(t, err)
stat, err := statFromDB(mb.Config().Stats.DBPath, day, "sighting", "user")
assert.Nil(t, err)
actual := stat.val
assert.Equal(t, actual, expected)
})
rmDB(t)
t.Run("TestSightingCounterNoResults", func(t *testing.T) {
day := mkDay()
count := 5
expected := value(0)
mb := bot.NewMockBot()
mb.Cfg.Stats.DBPath = dbPath
mb.Cfg.Stats.Sightings = []string{}
s := New(mb)
assert.NotNil(t, s)
for i := 0; i < count; i++ {
s.Message(makeMessage("user sighting"))
}
_, err := os.Stat(dbPath)
assert.Nil(t, err)
stat, err := statFromDB(mb.Config().Stats.DBPath, day, "sighting", "user")
assert.Nil(t, err)
actual := stat.val
assert.Equal(t, actual, expected)
})
rmDB(t)
}

View File

@ -4,7 +4,6 @@ package talker
import (
"fmt"
"math/rand"
"strings"
"github.com/velour/catbase/bot"
@ -41,15 +40,12 @@ var goatse []string = []string{
type TalkerPlugin struct {
Bot bot.Bot
enforceNicks bool
sayings []string
}
func New(bot bot.Bot) *TalkerPlugin {
return &TalkerPlugin{
Bot: bot,
enforceNicks: bot.Config().EnforceNicks,
sayings: bot.Config().WelcomeMsgs,
}
}
@ -81,13 +77,6 @@ func (p *TalkerPlugin) Message(message msg.Message) bool {
return true
}
if p.enforceNicks && len(message.User.Name) != 9 {
msg := fmt.Sprintf("Hey %s, we really like to have 9 character nicks because we're crazy OCD and stuff.",
message.User.Name)
p.Bot.SendMessage(message.Channel, msg)
return true
}
return false
}
@ -97,14 +86,6 @@ 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 msg.Message) bool {
if kind == "JOIN" && strings.ToLower(message.User.Name) != strings.ToLower(p.Bot.Config().Nick) {
if len(p.sayings) == 0 {
return false
}
msg := fmt.Sprintf(p.sayings[rand.Intn(len(p.sayings))], message.User.Name)
p.Bot.SendMessage(message.Channel, msg)
return true
}
return false
}

View File

@ -74,47 +74,6 @@ func TestSayCommand(t *testing.T) {
assert.Contains(t, mb.Messages[0], "hello")
}
func TestNineChars(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
c.enforceNicks = true
assert.NotNil(t, c)
res := c.Message(makeMessage("hello there"))
assert.Len(t, mb.Messages, 1)
assert.True(t, res)
assert.Contains(t, mb.Messages[0], "OCD")
}
func TestWelcome(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
c.sayings = []string{"Hi"}
assert.NotNil(t, c)
res := c.Event("JOIN", makeMessage("hello there"))
assert.Len(t, mb.Messages, 1)
assert.True(t, res)
assert.Contains(t, mb.Messages[0], "Hi")
}
func TestNoSayings(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
c.sayings = []string{}
assert.NotNil(t, c)
res := c.Event("JOIN", makeMessage("hello there"))
assert.Len(t, mb.Messages, 0)
assert.False(t, res)
}
func TestNonJoinEvent(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
assert.NotNil(t, c)
res := c.Event("SPLURT", makeMessage("hello there"))
assert.Len(t, mb.Messages, 0)
assert.False(t, res)
}
func TestHelp(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)

View File

@ -58,8 +58,8 @@ func New(bot bot.Bot) *TwitchPlugin {
twitchList: map[string]*Twitcher{},
}
for _, users := range p.config.Twitch.Users {
for _, twitcherName := range users {
for _, ch := range p.config.GetArray("Twitch.Channels") {
for _, twitcherName := range p.config.GetArray("Twitch." + ch + ".Users") {
if _, ok := p.twitchList[twitcherName]; !ok {
p.twitchList[twitcherName] = &Twitcher{
name: twitcherName,
@ -67,10 +67,7 @@ func New(bot bot.Bot) *TwitchPlugin {
}
}
}
}
for channel := range p.config.Twitch.Users {
go p.twitchLoop(channel)
go p.twitchLoop(ch)
}
return p
@ -120,9 +117,9 @@ func (p *TwitchPlugin) serveStreaming(w http.ResponseWriter, r *http.Request) {
func (p *TwitchPlugin) Message(message msg.Message) bool {
if strings.ToLower(message.Body) == "twitch status" {
channel := message.Channel
if _, ok := p.config.Twitch.Users[channel]; ok {
for _, twitcherName := range p.config.Twitch.Users[channel] {
if _, ok = p.twitchList[twitcherName]; ok {
if users := p.config.GetArray("Twitch." + channel + ".Users"); len(users) > 0 {
for _, twitcherName := range users {
if _, ok := p.twitchList[twitcherName]; ok {
p.checkTwitch(channel, p.twitchList[twitcherName], true)
}
}
@ -147,14 +144,14 @@ func (p *TwitchPlugin) Help(channel string, parts []string) {
}
func (p *TwitchPlugin) twitchLoop(channel string) {
frequency := p.config.Twitch.Freq
frequency := p.config.GetInt("Twitch.Freq")
log.Println("Checking every ", frequency, " seconds")
for {
time.Sleep(time.Duration(frequency) * time.Second)
for _, twitcherName := range p.config.Twitch.Users[channel] {
for _, twitcherName := range p.config.GetArray("Twitch." + channel + ".Users") {
p.checkTwitch(channel, p.twitchList[twitcherName], false)
}
}
@ -200,8 +197,8 @@ func (p *TwitchPlugin) checkTwitch(channel string, twitcher *Twitcher, alwaysPri
baseURL.RawQuery = query.Encode()
cid := p.config.Twitch.ClientID
auth := p.config.Twitch.Authorization
cid := p.config.Get("Twitch.ClientID")
auth := p.config.Get("Twitch.Authorization")
body, ok := getRequest(baseURL.String(), cid, auth)
if !ok {

View File

@ -28,7 +28,8 @@ func makeMessage(payload string) msg.Message {
func makeTwitchPlugin(t *testing.T) (*TwitchPlugin, *bot.MockBot) {
mb := bot.NewMockBot()
c := New(mb)
c.config.Twitch.Users = map[string][]string{"test": []string{"drseabass"}}
c.config.SetArray("Twitch.Channels", []string{"test"})
c.config.SetArray("Twitch.test.Users", []string{"drseabass"})
assert.NotNil(t, c)
c.twitchList["drseabass"] = &Twitcher{

View File

@ -4,6 +4,7 @@ package your
import (
"math/rand"
"strconv"
"strings"
"github.com/velour/catbase/bot"
@ -28,13 +29,21 @@ func New(bot bot.Bot) *YourPlugin {
// 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 *YourPlugin) Message(message msg.Message) bool {
if len(message.Body) > p.config.Your.MaxLength {
maxLen := p.config.GetInt("your.maxlength")
if maxLen == 0 {
maxLen = 140
p.config.Set("your.maxlength", strconv.Itoa(maxLen))
}
if len(message.Body) > maxLen {
return false
}
msg := message.Body
for _, replacement := range p.config.Your.Replacements {
if rand.Float64() < replacement.Frequency {
r := strings.NewReplacer(replacement.This, replacement.That)
for _, replacement := range p.config.GetArray("Your.Replacements") {
freq := p.config.GetFloat64("your.replacements." + replacement + ".freq")
this := p.config.Get("your.replacements." + replacement + ".this")
that := p.config.Get("your.replacements." + replacement + ".that")
if rand.Float64() < freq {
r := strings.NewReplacer(this, that)
msg = r.Replace(msg)
}
}

View File

@ -10,7 +10,6 @@ import (
"github.com/velour/catbase/bot"
"github.com/velour/catbase/bot/msg"
"github.com/velour/catbase/bot/user"
"github.com/velour/catbase/config"
)
func makeMessage(payload string) msg.Message {
@ -26,46 +25,41 @@ func makeMessage(payload string) msg.Message {
}
}
func TestReplacement(t *testing.T) {
func setup(t *testing.T) (*YourPlugin, *bot.MockBot) {
mb := bot.NewMockBot()
c := New(mb)
assert.NotNil(t, c)
c.config.Your.MaxLength = 1000
c.config.Your.Replacements = []config.Replacement{
config.Replacement{
This: "fuck",
That: "duck",
Frequency: 1.0,
},
}
mb.DB().MustExec(`delete from config;`)
return c, mb
}
func TestReplacement(t *testing.T) {
c, mb := setup(t)
c.config.Set("Your.MaxLength", "1000")
c.config.SetArray("your.replacements", []string{"0"})
c.config.Set("your.replacements.0.freq", "1.0")
c.config.Set("your.replacements.0.this", "fuck")
c.config.Set("your.replacements.0.that", "duck")
res := c.Message(makeMessage("fuck a duck"))
assert.Len(t, mb.Messages, 1)
assert.True(t, res)
assert.Len(t, mb.Messages, 1)
assert.Contains(t, mb.Messages[0], "duck a duck")
}
func TestNoReplacement(t *testing.T) {
mb := bot.NewMockBot()
c := New(mb)
assert.NotNil(t, c)
c.config.Your.MaxLength = 1000
c.config.Your.Replacements = []config.Replacement{
config.Replacement{
This: "nope",
That: "duck",
Frequency: 1.0,
},
config.Replacement{
This: " fuck",
That: "duck",
Frequency: 1.0,
},
config.Replacement{
This: "Fuck",
That: "duck",
Frequency: 1.0,
},
}
c, mb := setup(t)
c.config.Set("Your.MaxLength", "1000")
c.config.SetArray("your.replacements", []string{"0", "1", "2"})
c.config.Set("your.replacements.0.freq", "1.0")
c.config.Set("your.replacements.0.this", "nope")
c.config.Set("your.replacements.0.that", "duck")
c.config.Set("your.replacements.1.freq", "1.0")
c.config.Set("your.replacements.1.this", "nope")
c.config.Set("your.replacements.1.that", "duck")
c.config.Set("your.replacements.2.freq", "1.0")
c.config.Set("your.replacements.2.this", "Fuck")
c.config.Set("your.replacements.2.that", "duck")
c.Message(makeMessage("fuck a duck"))
assert.Len(t, mb.Messages, 0)
}

View File

@ -210,11 +210,15 @@ func (s *Slack) SendMessageType(channel, message string, meMessage bool) (string
postUrl = "https://slack.com/api/chat.meMessage"
}
nick := s.config.Nick
icon := s.config.IconURL
nick := s.config.Get("Nick")
icon := s.config.Get("IconURL")
if icon == "" {
icon = "https://placekitten.com/400/400"
log.Println("Set config item IconURL to customize appearance!")
}
resp, err := http.PostForm(postUrl,
url.Values{"token": {s.config.Slack.Token},
url.Values{"token": {s.config.Get("Slack.Token")},
"username": {nick},
"icon_url": {icon},
"channel": {channel},
@ -269,11 +273,11 @@ func (s *Slack) SendAction(channel, message string) string {
}
func (s *Slack) ReplyToMessageIdentifier(channel, message, identifier string) (string, bool) {
nick := s.config.Nick
icon := s.config.IconURL
nick := s.config.Get("Nick")
icon := s.config.Get("IconURL")
resp, err := http.PostForm("https://slack.com/api/chat.postMessage",
url.Values{"token": {s.config.Slack.Token},
url.Values{"token": {s.config.Get("Slack.Token")},
"username": {nick},
"icon_url": {icon},
"channel": {channel},
@ -321,7 +325,7 @@ func (s *Slack) ReplyToMessage(channel, message string, replyTo msg.Message) (st
func (s *Slack) React(channel, reaction string, message msg.Message) bool {
log.Printf("Reacting in %s: %s", channel, reaction)
resp, err := http.PostForm("https://slack.com/api/reactions.add",
url.Values{"token": {s.config.Slack.Token},
url.Values{"token": {s.config.Get("Slack.Token")},
"name": {reaction},
"channel": {channel},
"timestamp": {message.AdditionalData["RAW_SLACK_TIMESTAMP"]}})
@ -335,7 +339,7 @@ func (s *Slack) React(channel, reaction string, message msg.Message) bool {
func (s *Slack) Edit(channel, newMessage, identifier string) bool {
log.Printf("Editing in (%s) %s: %s", identifier, channel, newMessage)
resp, err := http.PostForm("https://slack.com/api/chat.update",
url.Values{"token": {s.config.Slack.Token},
url.Values{"token": {s.config.Get("Slack.Token")},
"channel": {channel},
"text": {newMessage},
"ts": {identifier}})
@ -352,7 +356,7 @@ func (s *Slack) GetEmojiList() map[string]string {
func (s *Slack) populateEmojiList() {
resp, err := http.PostForm("https://slack.com/api/emoji.list",
url.Values{"token": {s.config.Slack.Token}})
url.Values{"token": {s.config.Get("Slack.Token")}})
if err != nil {
log.Printf("Error retrieving emoji list from Slack: %s", err)
return
@ -545,7 +549,7 @@ func (s *Slack) markAllChannelsRead() {
func (s *Slack) getAllChannels() []slackChannelListItem {
u := s.url + "channels.list"
resp, err := http.PostForm(u,
url.Values{"token": {s.config.Slack.Token}})
url.Values{"token": {s.config.Get("Slack.Token")}})
if err != nil {
log.Printf("Error posting user info request: %s",
err)
@ -570,7 +574,7 @@ func (s *Slack) getAllChannels() []slackChannelListItem {
func (s *Slack) markChannelAsRead(slackChanId string) error {
u := s.url + "channels.info"
resp, err := http.PostForm(u,
url.Values{"token": {s.config.Slack.Token}, "channel": {slackChanId}})
url.Values{"token": {s.config.Get("Slack.Token")}, "channel": {slackChanId}})
if err != nil {
log.Printf("Error posting user info request: %s",
err)
@ -592,7 +596,7 @@ func (s *Slack) markChannelAsRead(slackChanId string) error {
u = s.url + "channels.mark"
resp, err = http.PostForm(u,
url.Values{"token": {s.config.Slack.Token}, "channel": {slackChanId}, "ts": {chanInfo.Channel.Latest.Ts}})
url.Values{"token": {s.config.Get("Slack.Token")}, "channel": {slackChanId}, "ts": {chanInfo.Channel.Latest.Ts}})
if err != nil {
log.Printf("Error posting user info request: %s",
err)
@ -617,7 +621,7 @@ func (s *Slack) markChannelAsRead(slackChanId string) error {
}
func (s *Slack) connect() {
token := s.config.Slack.Token
token := s.config.Get("Slack.Token")
u := fmt.Sprintf("https://slack.com/api/rtm.connect?token=%s", token)
resp, err := http.Get(u)
if err != nil {
@ -663,7 +667,7 @@ func (s *Slack) getUser(id string) (string, bool) {
log.Printf("User %s not already found, requesting info", id)
u := s.url + "users.info"
resp, err := http.PostForm(u,
url.Values{"token": {s.config.Slack.Token}, "user": {id}})
url.Values{"token": {s.config.Get("Slack.Token")}, "user": {id}})
if err != nil || resp.StatusCode != 200 {
log.Printf("Error posting user info request: %d %s",
resp.StatusCode, err)
@ -685,7 +689,7 @@ func (s *Slack) Who(id string) []string {
log.Println("Who is queried for ", id)
u := s.url + "channels.info"
resp, err := http.PostForm(u,
url.Values{"token": {s.config.Slack.Token}, "channel": {id}})
url.Values{"token": {s.config.Get("Slack.Token")}, "channel": {id}})
if err != nil {
log.Printf("Error posting user info request: %s",
err)