mirror of https://github.com/velour/catbase.git
commit
396d3acec6
|
@ -29,3 +29,4 @@ vendor
|
|||
.vscode/
|
||||
*.code-workspace
|
||||
*config.lua
|
||||
modd.conf
|
||||
|
|
63
bot/bot.go
63
bot/bot.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
|
||||
type Bot interface {
|
||||
Config() *config.Config
|
||||
DBVersion() int64
|
||||
DB() *sqlx.DB
|
||||
Who(string) []user.User
|
||||
AddHandler(string, Handler)
|
||||
|
|
13
bot/mock.go
13
bot/mock.go
|
@ -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),
|
||||
}
|
||||
|
|
226
config/config.go
226
config/config.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
|
@ -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.")
|
||||
}
|
14
irc/irc.go
14
irc/irc.go
|
@ -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
34
main.go
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -37,25 +37,22 @@ 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 (
|
||||
if _, err := bot.DB().Exec(`create table if not exists untappd (
|
||||
id integer primary key,
|
||||
untappdUser string,
|
||||
channel string,
|
||||
lastCheckin integer,
|
||||
chanNick string
|
||||
);`); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
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")
|
||||
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -107,15 +107,13 @@ func New(bot bot.Bot) *DowntimePlugin {
|
|||
db: bot.DB(),
|
||||
}
|
||||
|
||||
if bot.DBVersion() == 1 {
|
||||
_, err := p.db.Exec(`create table if not exists downtime (
|
||||
_, err := p.db.Exec(`create table if not exists downtime (
|
||||
id integer primary key,
|
||||
nick string,
|
||||
lastSeen integer
|
||||
);`)
|
||||
if err != nil {
|
||||
log.Fatal("Error creating downtime table: ", err)
|
||||
}
|
||||
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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -48,17 +48,15 @@ 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 (
|
||||
_, err := b.DB().Exec(`create table if not exists first (
|
||||
id integer primary key,
|
||||
day integer,
|
||||
time integer,
|
||||
body string,
|
||||
nick string
|
||||
);`)
|
||||
if err != nil {
|
||||
log.Fatal("Could not create first table: ", err)
|
||||
}
|
||||
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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -40,8 +40,7 @@ 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 (
|
||||
if _, err := bot.DB().Exec(`create table if not exists reminders (
|
||||
id integer primary key,
|
||||
fromWho string,
|
||||
toWho string,
|
||||
|
@ -49,8 +48,7 @@ func New(bot bot.Bot) *ReminderPlugin {
|
|||
remindWhen string,
|
||||
channel string
|
||||
);`); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
dur, _ := time.ParseDuration("1h")
|
||||
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 }
|
|
@ -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)
|
||||
}
|
|
@ -4,7 +4,6 @@ package talker
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
|
||||
"github.com/velour/catbase/bot"
|
||||
|
@ -40,16 +39,13 @@ var goatse []string = []string{
|
|||
}
|
||||
|
||||
type TalkerPlugin struct {
|
||||
Bot bot.Bot
|
||||
enforceNicks bool
|
||||
sayings []string
|
||||
Bot bot.Bot
|
||||
sayings []string
|
||||
}
|
||||
|
||||
func New(bot bot.Bot) *TalkerPlugin {
|
||||
return &TalkerPlugin{
|
||||
Bot: bot,
|
||||
enforceNicks: bot.Config().EnforceNicks,
|
||||
sayings: bot.Config().WelcomeMsgs,
|
||||
Bot: bot,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue