mirror of https://github.com/velour/catbase.git
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
216862bc10
|
@ -29,3 +29,45 @@ vendor
|
||||||
.vscode/
|
.vscode/
|
||||||
*.code-workspace
|
*.code-workspace
|
||||||
*config.lua
|
*config.lua
|
||||||
|
modd.conf
|
||||||
|
|
||||||
|
|
||||||
|
# Created by https://www.gitignore.io/api/macos
|
||||||
|
# Edit at https://www.gitignore.io/?templates=macos
|
||||||
|
|
||||||
|
### macOS ###
|
||||||
|
# General
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Icon must end with two \r
|
||||||
|
Icon
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Files that might appear in the root of a volume
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
.com.apple.timemachine.donotpresent
|
||||||
|
|
||||||
|
# Directories potentially created on remote AFP share
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
|
||||||
|
# End of https://www.gitignore.io/api/macos
|
||||||
|
|
||||||
|
util/*/files
|
||||||
|
util/*/files
|
||||||
|
run.sh
|
||||||
|
.idea
|
||||||
|
logs
|
||||||
|
util/files
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
# CatBase
|
# CatBase
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.com/velour/catbase.svg?branch=master)](https://travis-ci.com/velour/catbase)
|
||||||
|
|
||||||
CatBase is a bot that trolls our little corner of the IRC world and keeps our friends laughing from time to time. Sometimes he makes us angry too. He is crafted as a clone of XKCD's Bucket bot, which learns from things he's told and regurgitates his knowledge to the various channels that he lives in. I've found in many such projects that randomness can often make bots feel much more alive than they are, so CatBase is a big experiment in how great randomness is.
|
CatBase is a bot that trolls our little corner of the IRC world and keeps our friends laughing from time to time. Sometimes he makes us angry too. He is crafted as a clone of XKCD's Bucket bot, which learns from things he's told and regurgitates his knowledge to the various channels that he lives in. I've found in many such projects that randomness can often make bots feel much more alive than they are, so CatBase is a big experiment in how great randomness is.
|
||||||
|
|
||||||
## Getting Help
|
## Getting Help
|
||||||
|
|
176
bot/bot.go
176
bot/bot.go
|
@ -3,13 +3,15 @@
|
||||||
package bot
|
package bot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"fmt"
|
||||||
"html/template"
|
"math/rand"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
"github.com/velour/catbase/bot/msglog"
|
"github.com/velour/catbase/bot/msglog"
|
||||||
"github.com/velour/catbase/bot/user"
|
"github.com/velour/catbase/bot/user"
|
||||||
|
@ -20,7 +22,7 @@ import (
|
||||||
type bot struct {
|
type bot struct {
|
||||||
// Each plugin must be registered in our plugins handler. To come: a map so that this
|
// Each plugin must be registered in our plugins handler. To come: a map so that this
|
||||||
// will allow plugins to respond to specific kinds of events
|
// will allow plugins to respond to specific kinds of events
|
||||||
plugins map[string]Handler
|
plugins map[string]Plugin
|
||||||
pluginOrdering []string
|
pluginOrdering []string
|
||||||
|
|
||||||
// Users holds information about all of our friends
|
// Users holds information about all of our friends
|
||||||
|
@ -32,30 +34,33 @@ type bot struct {
|
||||||
|
|
||||||
conn Connector
|
conn Connector
|
||||||
|
|
||||||
// SQL DB
|
|
||||||
// TODO: I think it'd be nice to use https://github.com/jmoiron/sqlx so that
|
|
||||||
// the select/update/etc statements could be simplified with struct
|
|
||||||
// marshalling.
|
|
||||||
db *sqlx.DB
|
|
||||||
dbVersion int64
|
|
||||||
|
|
||||||
logIn chan msg.Message
|
logIn chan msg.Message
|
||||||
logOut chan msg.Messages
|
logOut chan msg.Messages
|
||||||
|
|
||||||
version string
|
version string
|
||||||
|
|
||||||
// The entries to the bot's HTTP interface
|
// The entries to the bot's HTTP interface
|
||||||
httpEndPoints map[string]string
|
httpEndPoints []EndPoint
|
||||||
|
|
||||||
// filters registered by plugins
|
// filters registered by plugins
|
||||||
filters map[string]func(string) string
|
filters map[string]func(string) string
|
||||||
|
|
||||||
|
callbacks CallbackMap
|
||||||
|
|
||||||
|
password string
|
||||||
|
passwordCreated time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EndPoint struct {
|
||||||
|
Name, URL string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variable represents a $var replacement
|
||||||
type Variable struct {
|
type Variable struct {
|
||||||
Variable, Value string
|
Variable, Value string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Newbot creates a bot for a given connection and set of handlers.
|
// New creates a bot for a given connection and set of handlers.
|
||||||
func New(config *config.Config, connector Connector) Bot {
|
func New(config *config.Config, connector Connector) Bot {
|
||||||
logIn := make(chan msg.Message)
|
logIn := make(chan msg.Message)
|
||||||
logOut := make(chan msg.Messages)
|
logOut := make(chan msg.Messages)
|
||||||
|
@ -63,94 +68,69 @@ func New(config *config.Config, connector Connector) Bot {
|
||||||
msglog.RunNew(logIn, logOut)
|
msglog.RunNew(logIn, logOut)
|
||||||
|
|
||||||
users := []user.User{
|
users := []user.User{
|
||||||
user.User{
|
{
|
||||||
Name: config.Nick,
|
Name: config.Get("Nick", "bot"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
bot := &bot{
|
bot := &bot{
|
||||||
config: config,
|
config: config,
|
||||||
plugins: make(map[string]Handler),
|
plugins: make(map[string]Plugin),
|
||||||
pluginOrdering: make([]string, 0),
|
pluginOrdering: make([]string, 0),
|
||||||
conn: connector,
|
conn: connector,
|
||||||
users: users,
|
users: users,
|
||||||
me: users[0],
|
me: users[0],
|
||||||
db: config.DBConn,
|
|
||||||
logIn: logIn,
|
logIn: logIn,
|
||||||
logOut: logOut,
|
logOut: logOut,
|
||||||
version: config.Version,
|
httpEndPoints: make([]EndPoint, 0),
|
||||||
httpEndPoints: make(map[string]string),
|
|
||||||
filters: make(map[string]func(string) string),
|
filters: make(map[string]func(string) string),
|
||||||
|
callbacks: make(CallbackMap),
|
||||||
}
|
}
|
||||||
|
|
||||||
bot.migrateDB()
|
bot.migrateDB()
|
||||||
|
|
||||||
http.HandleFunc("/", bot.serveRoot)
|
http.HandleFunc("/", bot.serveRoot)
|
||||||
if config.HttpAddr == "" {
|
|
||||||
config.HttpAddr = "127.0.0.1:1337"
|
|
||||||
}
|
|
||||||
go http.ListenAndServe(config.HttpAddr, nil)
|
|
||||||
|
|
||||||
connector.RegisterMessageReceived(bot.MsgReceived)
|
connector.RegisterEvent(bot.Receive)
|
||||||
connector.RegisterEventReceived(bot.EventReceived)
|
|
||||||
connector.RegisterReplyMessageReceived(bot.ReplyMsgReceived)
|
|
||||||
|
|
||||||
return bot
|
return bot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *bot) DefaultConnector() Connector {
|
||||||
|
return b.conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bot) WhoAmI() string {
|
||||||
|
return b.me.Name
|
||||||
|
}
|
||||||
|
|
||||||
// Config gets the configuration that the bot is using
|
// Config gets the configuration that the bot is using
|
||||||
func (b *bot) Config() *config.Config {
|
func (b *bot) Config() *config.Config {
|
||||||
return b.config
|
return b.config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bot) DBVersion() int64 {
|
|
||||||
return b.dbVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *bot) DB() *sqlx.DB {
|
func (b *bot) DB() *sqlx.DB {
|
||||||
return b.db
|
return b.config.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create any tables if necessary based on version of DB
|
// Create any tables if necessary based on version of DB
|
||||||
// Plugins should create their own tables, these are only for official bot stuff
|
// 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.
|
// Note: This does not return an error. Database issues are all fatal at this stage.
|
||||||
func (b *bot) migrateDB() {
|
func (b *bot) migrateDB() {
|
||||||
_, err := b.db.Exec(`create table if not exists version (version integer);`)
|
if _, err := b.DB().Exec(`create table if not exists variables (
|
||||||
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 (
|
|
||||||
id integer primary key,
|
id integer primary key,
|
||||||
name string,
|
name string,
|
||||||
value string
|
value string
|
||||||
);`); err != nil {
|
);`); err != nil {
|
||||||
log.Fatal("Initial DB migration create variables table: ", err)
|
log.Fatal().Err(err).Msgf("Initial DB migration create variables table")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds a constructed handler to the bots handlers list
|
// Adds a constructed handler to the bots handlers list
|
||||||
func (b *bot) AddHandler(name string, h Handler) {
|
func (b *bot) AddPlugin(h Plugin) {
|
||||||
|
name := reflect.TypeOf(h).String()
|
||||||
b.plugins[name] = h
|
b.plugins[name] = h
|
||||||
b.pluginOrdering = append(b.pluginOrdering, name)
|
b.pluginOrdering = append(b.pluginOrdering, name)
|
||||||
if entry := h.RegisterWeb(); entry != nil {
|
|
||||||
b.httpEndPoints[name] = *entry
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bot) Who(channel string) []user.User {
|
func (b *bot) Who(channel string) []user.User {
|
||||||
|
@ -162,50 +142,14 @@ func (b *bot) Who(channel string) []user.User {
|
||||||
return users
|
return users
|
||||||
}
|
}
|
||||||
|
|
||||||
var rootIndex string = `
|
// IsCmd checks if message is a command and returns its curtailed version
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Factoids</title>
|
|
||||||
<link rel="stylesheet" href="http://yui.yahooapis.com/pure/0.1.0/pure-min.css">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
</head>
|
|
||||||
{{if .EndPoints}}
|
|
||||||
<div style="padding-top: 1em;">
|
|
||||||
<table class="pure-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Plugin</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
{{range $key, $value := .EndPoints}}
|
|
||||||
<tr>
|
|
||||||
<td><a href="{{$value}}">{{$key}}</a></td>
|
|
||||||
</tr>
|
|
||||||
{{end}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
</html>
|
|
||||||
`
|
|
||||||
|
|
||||||
func (b *bot) serveRoot(w http.ResponseWriter, r *http.Request) {
|
|
||||||
context := make(map[string]interface{})
|
|
||||||
context["EndPoints"] = b.httpEndPoints
|
|
||||||
t, err := template.New("rootIndex").Parse(rootIndex)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
t.Execute(w, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if message is a command and returns its curtailed version
|
|
||||||
func IsCmd(c *config.Config, message string) (bool, string) {
|
func IsCmd(c *config.Config, message string) (bool, string) {
|
||||||
cmdcs := c.CommandChar
|
cmdcs := c.GetArray("CommandChar", []string{"!"})
|
||||||
botnick := strings.ToLower(c.Nick)
|
botnick := strings.ToLower(c.Get("Nick", "bot"))
|
||||||
|
if botnick == "" {
|
||||||
|
log.Fatal().
|
||||||
|
Msgf(`You must run catbase -set nick -val <your bot nick>`)
|
||||||
|
}
|
||||||
iscmd := false
|
iscmd := false
|
||||||
lowerMessage := strings.ToLower(message)
|
lowerMessage := strings.ToLower(message)
|
||||||
|
|
||||||
|
@ -237,7 +181,7 @@ func IsCmd(c *config.Config, message string) (bool, string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bot) CheckAdmin(nick string) bool {
|
func (b *bot) CheckAdmin(nick string) bool {
|
||||||
for _, u := range b.Config().Admins {
|
for _, u := range b.Config().GetArray("Admins", []string{}) {
|
||||||
if nick == u {
|
if nick == u {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -265,7 +209,7 @@ func (b *bot) NewUser(nick string) *user.User {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bot) checkAdmin(nick string) bool {
|
func (b *bot) checkAdmin(nick string) bool {
|
||||||
for _, u := range b.Config().Admins {
|
for _, u := range b.Config().GetArray("Admins", []string{}) {
|
||||||
if nick == u {
|
if nick == u {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -277,3 +221,31 @@ func (b *bot) checkAdmin(nick string) bool {
|
||||||
func (b *bot) RegisterFilter(name string, f func(string) string) {
|
func (b *bot) RegisterFilter(name string, f func(string) string) {
|
||||||
b.filters[name] = f
|
b.filters[name] = f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register a callback
|
||||||
|
func (b *bot) Register(p Plugin, kind Kind, cb Callback) {
|
||||||
|
t := reflect.TypeOf(p).String()
|
||||||
|
if _, ok := b.callbacks[t]; !ok {
|
||||||
|
b.callbacks[t] = make(map[Kind][]Callback)
|
||||||
|
}
|
||||||
|
if _, ok := b.callbacks[t][kind]; !ok {
|
||||||
|
b.callbacks[t][kind] = []Callback{}
|
||||||
|
}
|
||||||
|
b.callbacks[t][kind] = append(b.callbacks[t][kind], cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bot) RegisterWeb(root, name string) {
|
||||||
|
b.httpEndPoints = append(b.httpEndPoints, EndPoint{name, root})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bot) GetPassword() string {
|
||||||
|
if b.passwordCreated.Before(time.Now().Add(-24 * time.Hour)) {
|
||||||
|
adjs := b.config.GetArray("bot.passwordAdjectives", []string{"very"})
|
||||||
|
nouns := b.config.GetArray("bot.passwordNouns", []string{"noun"})
|
||||||
|
verbs := b.config.GetArray("bot.passwordVerbs", []string{"do"})
|
||||||
|
a, n, v := adjs[rand.Intn(len(adjs))], nouns[rand.Intn(len(nouns))], verbs[rand.Intn(len(verbs))]
|
||||||
|
b.passwordCreated = time.Now()
|
||||||
|
b.password = fmt.Sprintf("%s-%s-%s", a, n, v)
|
||||||
|
}
|
||||||
|
return b.password
|
||||||
|
}
|
||||||
|
|
123
bot/handlers.go
123
bot/handlers.go
|
@ -6,86 +6,55 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handles incomming PRIVMSG requests
|
func (b *bot) Receive(conn Connector, kind Kind, msg msg.Message, args ...interface{}) bool {
|
||||||
func (b *bot) MsgReceived(msg msg.Message) {
|
log.Debug().
|
||||||
log.Println("Received message: ", msg)
|
Interface("msg", msg).
|
||||||
|
Msg("Received event")
|
||||||
|
|
||||||
// msg := b.buildMessage(client, inMsg)
|
// msg := b.buildMessage(client, inMsg)
|
||||||
// do need to look up user and fix it
|
// do need to look up user and fix it
|
||||||
if strings.HasPrefix(msg.Body, "help ") && msg.Command {
|
if kind == Message && strings.HasPrefix(msg.Body, "help") && msg.Command {
|
||||||
parts := strings.Fields(strings.ToLower(msg.Body))
|
parts := strings.Fields(strings.ToLower(msg.Body))
|
||||||
b.checkHelp(msg.Channel, parts)
|
b.checkHelp(conn, msg.Channel, parts)
|
||||||
|
log.Debug().Msg("Handled a help, returning")
|
||||||
goto RET
|
goto RET
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, name := range b.pluginOrdering {
|
for _, name := range b.pluginOrdering {
|
||||||
p := b.plugins[name]
|
if b.runCallback(conn, b.plugins[name], kind, msg, args...) {
|
||||||
if p.Message(msg) {
|
goto RET
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RET:
|
RET:
|
||||||
b.logIn <- msg
|
b.logIn <- msg
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle incoming events
|
func (b *bot) runCallback(conn Connector, plugin Plugin, evt Kind, message msg.Message, args ...interface{}) bool {
|
||||||
func (b *bot) EventReceived(msg msg.Message) {
|
t := reflect.TypeOf(plugin).String()
|
||||||
log.Println("Received event: ", msg)
|
for _, cb := range b.callbacks[t][evt] {
|
||||||
//msg := b.buildMessage(conn, inMsg)
|
if cb(conn, evt, message, args...) {
|
||||||
for _, name := range b.pluginOrdering {
|
return true
|
||||||
p := b.plugins[name]
|
|
||||||
if p.Event(msg.Body, msg) { // TODO: could get rid of msg.Body
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle incoming replys
|
// Send a message to the connection
|
||||||
func (b *bot) ReplyMsgReceived(msg msg.Message, identifier string) {
|
func (b *bot) Send(conn Connector, kind Kind, args ...interface{}) (string, error) {
|
||||||
log.Println("Received message: ", msg)
|
return conn.Send(kind, args...)
|
||||||
|
|
||||||
for _, name := range b.pluginOrdering {
|
|
||||||
p := b.plugins[name]
|
|
||||||
if p.ReplyMessage(msg, identifier) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *bot) SendMessage(channel, message string) string {
|
|
||||||
return b.conn.SendMessage(channel, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *bot) SendAction(channel, message string) string {
|
|
||||||
return b.conn.SendAction(channel, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *bot) ReplyToMessageIdentifier(channel, message, identifier string) (string, bool) {
|
|
||||||
return b.conn.ReplyToMessageIdentifier(channel, message, identifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *bot) ReplyToMessage(channel, message string, replyTo msg.Message) (string, bool) {
|
|
||||||
return b.conn.ReplyToMessage(channel, message, replyTo)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *bot) React(channel, reaction string, message msg.Message) bool {
|
|
||||||
return b.conn.React(channel, reaction, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *bot) Edit(channel, newMessage, identifier string) bool {
|
|
||||||
return b.conn.Edit(channel, newMessage, identifier)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bot) GetEmojiList() map[string]string {
|
func (b *bot) GetEmojiList() map[string]string {
|
||||||
|
@ -93,32 +62,39 @@ func (b *bot) GetEmojiList() map[string]string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks to see if the user is asking for help, returns true if so and handles the situation.
|
// Checks to see if the user is asking for help, returns true if so and handles the situation.
|
||||||
func (b *bot) checkHelp(channel string, parts []string) {
|
func (b *bot) checkHelp(conn Connector, channel string, parts []string) {
|
||||||
if len(parts) == 1 {
|
if len(parts) == 1 {
|
||||||
// just print out a list of help topics
|
// just print out a list of help topics
|
||||||
topics := "Help topics: about variables"
|
topics := "Help topics: about variables"
|
||||||
for name, _ := range b.plugins {
|
for name := range b.plugins {
|
||||||
|
name = strings.Split(strings.TrimPrefix(name, "*"), ".")[0]
|
||||||
topics = fmt.Sprintf("%s, %s", topics, name)
|
topics = fmt.Sprintf("%s, %s", topics, name)
|
||||||
}
|
}
|
||||||
b.SendMessage(channel, topics)
|
b.Send(conn, Message, channel, topics)
|
||||||
} else {
|
} else {
|
||||||
// trigger the proper plugin's help response
|
// trigger the proper plugin's help response
|
||||||
if parts[1] == "about" {
|
if parts[1] == "about" {
|
||||||
b.Help(channel, parts)
|
b.Help(conn, channel, parts)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if parts[1] == "variables" {
|
if parts[1] == "variables" {
|
||||||
b.listVars(channel, parts)
|
b.listVars(conn, channel, parts)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
plugin := b.plugins[parts[1]]
|
for name, plugin := range b.plugins {
|
||||||
if plugin != nil {
|
if strings.HasPrefix(name, "*"+parts[1]) {
|
||||||
plugin.Help(channel, parts)
|
if b.runCallback(conn, plugin, Help, msg.Message{Channel: channel}, channel, parts) {
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
msg := fmt.Sprintf("I'm sorry, I don't know what %s is!", parts[1])
|
msg := fmt.Sprintf("I'm sorry, I don't know how to help you with %s.", parts[1])
|
||||||
b.SendMessage(channel, msg)
|
b.Send(conn, Message, channel, msg)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
msg := fmt.Sprintf("I'm sorry, I don't know what %s is!", strings.Join(parts, " "))
|
||||||
|
b.Send(conn, Message, channel, msg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bot) LastMessage(channel string) (msg.Message, error) {
|
func (b *bot) LastMessage(channel string) (msg.Message, error) {
|
||||||
|
@ -192,38 +168,38 @@ func (b *bot) Filter(message msg.Message, input string) string {
|
||||||
|
|
||||||
func (b *bot) getVar(varName string) (string, error) {
|
func (b *bot) getVar(varName string) (string, error) {
|
||||||
var text string
|
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 {
|
switch {
|
||||||
case err == sql.ErrNoRows:
|
case err == sql.ErrNoRows:
|
||||||
return "", fmt.Errorf("No factoid found")
|
return "", fmt.Errorf("No factoid found")
|
||||||
case err != nil:
|
case err != nil:
|
||||||
log.Fatal("getVar error: ", err)
|
log.Fatal().Err(err).Msg("getVar error")
|
||||||
}
|
}
|
||||||
return text, nil
|
return text, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bot) listVars(channel string, parts []string) {
|
func (b *bot) listVars(conn Connector, channel string, parts []string) {
|
||||||
var variables []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 {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal().Err(err)
|
||||||
}
|
}
|
||||||
msg := "I know: $who, $someone, $digit, $nonzero"
|
msg := "I know: $who, $someone, $digit, $nonzero"
|
||||||
if len(variables) > 0 {
|
if len(variables) > 0 {
|
||||||
msg += ", " + strings.Join(variables, ", ")
|
msg += ", " + strings.Join(variables, ", ")
|
||||||
}
|
}
|
||||||
b.SendMessage(channel, msg)
|
b.Send(conn, Message, channel, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bot) Help(channel string, parts []string) {
|
func (b *bot) Help(conn Connector, channel string, parts []string) {
|
||||||
msg := fmt.Sprintf("Hi, I'm based on godeepintir version %s. I'm written in Go, and you "+
|
msg := fmt.Sprintf("Hi, I'm based on godeepintir version %s. I'm written in Go, and you "+
|
||||||
"can find my source code on the internet here: "+
|
"can find my source code on the internet here: "+
|
||||||
"http://github.com/velour/catbase", b.version)
|
"http://github.com/velour/catbase", b.version)
|
||||||
b.SendMessage(channel, msg)
|
b.Send(conn, Message, channel, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send our own musings to the plugins
|
// Send our own musings to the plugins
|
||||||
func (b *bot) selfSaid(channel, message string, action bool) {
|
func (b *bot) selfSaid(conn Connector, channel, message string, action bool) {
|
||||||
msg := msg.Message{
|
msg := msg.Message{
|
||||||
User: &b.me, // hack
|
User: &b.me, // hack
|
||||||
Channel: channel,
|
Channel: channel,
|
||||||
|
@ -236,9 +212,8 @@ func (b *bot) selfSaid(channel, message string, action bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, name := range b.pluginOrdering {
|
for _, name := range b.pluginOrdering {
|
||||||
p := b.plugins[name]
|
if b.runCallback(conn, b.plugins[name], SelfMessage, msg) {
|
||||||
if p.BotMessage(msg) {
|
return
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,51 +9,80 @@ import (
|
||||||
"github.com/velour/catbase/config"
|
"github.com/velour/catbase/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_ = iota
|
||||||
|
|
||||||
|
// Message any standard chat
|
||||||
|
Message
|
||||||
|
// Reply something containing a message reference
|
||||||
|
Reply
|
||||||
|
// Action any /me action
|
||||||
|
Action
|
||||||
|
// Reaction Icon reaction if service supports it
|
||||||
|
Reaction
|
||||||
|
// Edit message ref'd new message to replace
|
||||||
|
Edit
|
||||||
|
// Not sure what event is
|
||||||
|
Event
|
||||||
|
// Help is used when the bot help system is triggered
|
||||||
|
Help
|
||||||
|
// SelfMessage triggers when the bot is sending a message
|
||||||
|
SelfMessage
|
||||||
|
)
|
||||||
|
|
||||||
|
type ImageAttachment struct {
|
||||||
|
URL string
|
||||||
|
AltTxt string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Kind int
|
||||||
|
type Callback func(Connector, Kind, msg.Message, ...interface{}) bool
|
||||||
|
type CallbackMap map[string]map[Kind][]Callback
|
||||||
|
|
||||||
|
// Bot interface serves to allow mocking of the actual bot
|
||||||
type Bot interface {
|
type Bot interface {
|
||||||
|
// Config allows access to the bot's configuration system
|
||||||
Config() *config.Config
|
Config() *config.Config
|
||||||
DBVersion() int64
|
// DB gives access to the current database
|
||||||
DB() *sqlx.DB
|
DB() *sqlx.DB
|
||||||
|
// Who lists users in a particular channel
|
||||||
Who(string) []user.User
|
Who(string) []user.User
|
||||||
AddHandler(string, Handler)
|
// WhoAmI gives a nick for the bot
|
||||||
SendMessage(string, string) string
|
WhoAmI() string
|
||||||
SendAction(string, string) string
|
// AddPlugin registers a new plugin handler
|
||||||
ReplyToMessageIdentifier(string, string, string) (string, bool)
|
AddPlugin(Plugin)
|
||||||
ReplyToMessage(string, string, msg.Message) (string, bool)
|
// First arg should be one of bot.Message/Reply/Action/etc
|
||||||
React(string, string, msg.Message) bool
|
Send(Connector, Kind, ...interface{}) (string, error)
|
||||||
Edit(string, string, string) bool
|
// First arg should be one of bot.Message/Reply/Action/etc
|
||||||
MsgReceived(msg.Message)
|
Receive(Connector, Kind, msg.Message, ...interface{}) bool
|
||||||
ReplyMsgReceived(msg.Message, string)
|
// Register a callback
|
||||||
EventReceived(msg.Message)
|
Register(Plugin, Kind, Callback)
|
||||||
|
|
||||||
Filter(msg.Message, string) string
|
Filter(msg.Message, string) string
|
||||||
LastMessage(string) (msg.Message, error)
|
LastMessage(string) (msg.Message, error)
|
||||||
|
|
||||||
CheckAdmin(string) bool
|
CheckAdmin(string) bool
|
||||||
GetEmojiList() map[string]string
|
GetEmojiList() map[string]string
|
||||||
RegisterFilter(string, func(string) string)
|
RegisterFilter(string, func(string) string)
|
||||||
|
RegisterWeb(string, string)
|
||||||
|
DefaultConnector() Connector
|
||||||
|
GetWebNavigation() []EndPoint
|
||||||
|
GetPassword() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Connector represents a server connection to a chat service
|
||||||
type Connector interface {
|
type Connector interface {
|
||||||
RegisterEventReceived(func(message msg.Message))
|
RegisterEvent(Callback)
|
||||||
RegisterMessageReceived(func(message msg.Message))
|
|
||||||
RegisterReplyMessageReceived(func(msg.Message, string))
|
Send(Kind, ...interface{}) (string, error)
|
||||||
|
|
||||||
SendMessage(channel, message string) string
|
|
||||||
SendAction(channel, message string) string
|
|
||||||
ReplyToMessageIdentifier(string, string, string) (string, bool)
|
|
||||||
ReplyToMessage(string, string, msg.Message) (string, bool)
|
|
||||||
React(string, string, msg.Message) bool
|
|
||||||
Edit(string, string, string) bool
|
|
||||||
GetEmojiList() map[string]string
|
GetEmojiList() map[string]string
|
||||||
Serve() error
|
Serve() error
|
||||||
|
|
||||||
Who(string) []string
|
Who(string) []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interface used for compatibility with the Plugin interface
|
// Plugin interface used for compatibility with the Plugin interface
|
||||||
type Handler interface {
|
// Uhh it turned empty, but we're still using it to ID plugins
|
||||||
Message(message msg.Message) bool
|
type Plugin interface {
|
||||||
Event(kind string, message msg.Message) bool
|
|
||||||
ReplyMessage(msg.Message, string) bool
|
|
||||||
BotMessage(message msg.Message) bool
|
|
||||||
Help(channel string, parts []string)
|
|
||||||
RegisterWeb() *string
|
|
||||||
}
|
}
|
||||||
|
|
90
bot/mock.go
90
bot/mock.go
|
@ -4,11 +4,12 @@ package bot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
"github.com/velour/catbase/bot/user"
|
"github.com/velour/catbase/bot/user"
|
||||||
|
@ -19,85 +20,94 @@ type MockBot struct {
|
||||||
mock.Mock
|
mock.Mock
|
||||||
db *sqlx.DB
|
db *sqlx.DB
|
||||||
|
|
||||||
Cfg config.Config
|
Cfg *config.Config
|
||||||
|
|
||||||
Messages []string
|
Messages []string
|
||||||
Actions []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.Cfg.DB }
|
||||||
func (mb *MockBot) DB() *sqlx.DB { return mb.db }
|
|
||||||
func (mb *MockBot) Conn() Connector { return nil }
|
|
||||||
func (mb *MockBot) Who(string) []user.User { return []user.User{} }
|
func (mb *MockBot) Who(string) []user.User { return []user.User{} }
|
||||||
func (mb *MockBot) AddHandler(name string, f Handler) {}
|
func (mb *MockBot) WhoAmI() string { return "tester" }
|
||||||
func (mb *MockBot) SendMessage(ch string, msg string) string {
|
func (mb *MockBot) DefaultConnector() Connector { return nil }
|
||||||
mb.Messages = append(mb.Messages, msg)
|
func (mb *MockBot) GetPassword() string { return "12345" }
|
||||||
return fmt.Sprintf("m-%d", len(mb.Actions)-1)
|
func (mb *MockBot) Send(c Connector, kind Kind, args ...interface{}) (string, error) {
|
||||||
|
switch kind {
|
||||||
|
case Message:
|
||||||
|
mb.Messages = append(mb.Messages, args[1].(string))
|
||||||
|
return fmt.Sprintf("m-%d", len(mb.Actions)-1), nil
|
||||||
|
case Action:
|
||||||
|
mb.Actions = append(mb.Actions, args[1].(string))
|
||||||
|
return fmt.Sprintf("a-%d", len(mb.Actions)-1), nil
|
||||||
|
case Edit:
|
||||||
|
ch, m, id := args[0].(string), args[1].(string), args[2].(string)
|
||||||
|
return mb.edit(c, ch, m, id)
|
||||||
|
case Reaction:
|
||||||
|
ch, re, msg := args[0].(string), args[1].(string), args[2].(msg.Message)
|
||||||
|
return mb.react(c, ch, re, msg)
|
||||||
|
}
|
||||||
|
return "ERR", fmt.Errorf("Mesasge type unhandled")
|
||||||
}
|
}
|
||||||
func (mb *MockBot) SendAction(ch string, msg string) string {
|
func (mb *MockBot) AddPlugin(f Plugin) {}
|
||||||
mb.Actions = append(mb.Actions, msg)
|
func (mb *MockBot) Register(p Plugin, kind Kind, cb Callback) {}
|
||||||
return fmt.Sprintf("a-%d", len(mb.Actions)-1)
|
func (mb *MockBot) RegisterWeb(_, _ string) {}
|
||||||
|
func (mb *MockBot) GetWebNavigation() []EndPoint { return nil }
|
||||||
|
func (mb *MockBot) Receive(c Connector, kind Kind, msg msg.Message, args ...interface{}) bool {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
func (mb *MockBot) ReplyToMessageIdentifier(channel, message, identifier string) (string, bool) {
|
func (mb *MockBot) Filter(msg msg.Message, s string) string { return s }
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
func (mb *MockBot) ReplyToMessage(channel, message string, replyTo msg.Message) (string, bool) {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
func (mb *MockBot) MsgReceived(msg msg.Message) {}
|
|
||||||
func (mb *MockBot) EventReceived(msg msg.Message) {}
|
|
||||||
func (mb *MockBot) Filter(msg msg.Message, s string) string { return "" }
|
|
||||||
func (mb *MockBot) LastMessage(ch string) (msg.Message, error) { return msg.Message{}, nil }
|
func (mb *MockBot) LastMessage(ch string) (msg.Message, error) { return msg.Message{}, nil }
|
||||||
func (mb *MockBot) CheckAdmin(nick string) bool { return false }
|
func (mb *MockBot) CheckAdmin(nick string) bool { return false }
|
||||||
|
|
||||||
func (mb *MockBot) React(channel, reaction string, message msg.Message) bool { return false }
|
func (mb *MockBot) react(c Connector, channel, reaction string, message msg.Message) (string, error) {
|
||||||
|
mb.Reactions = append(mb.Reactions, reaction)
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
func (mb *MockBot) Edit(channel, newMessage, identifier string) bool {
|
func (mb *MockBot) edit(c Connector, channel, newMessage, identifier string) (string, error) {
|
||||||
isMessage := identifier[0] == 'm'
|
isMessage := identifier[0] == 'm'
|
||||||
if !isMessage && identifier[0] != 'a' {
|
if !isMessage && identifier[0] != 'a' {
|
||||||
log.Printf("failed to parse identifier: %s", identifier)
|
err := fmt.Errorf("failed to parse identifier: %s", identifier)
|
||||||
return false
|
log.Error().Err(err)
|
||||||
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
index, err := strconv.Atoi(strings.Split(identifier, "-")[1])
|
index, err := strconv.Atoi(strings.Split(identifier, "-")[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("failed to parse identifier: %s", identifier)
|
err := fmt.Errorf("failed to parse identifier: %s", identifier)
|
||||||
return false
|
log.Error().Err(err)
|
||||||
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if isMessage {
|
if isMessage {
|
||||||
if index < len(mb.Messages) {
|
if index < len(mb.Messages) {
|
||||||
mb.Messages[index] = newMessage
|
mb.Messages[index] = newMessage
|
||||||
} else {
|
} else {
|
||||||
return false
|
return "", fmt.Errorf("No message")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if index < len(mb.Actions) {
|
if index < len(mb.Actions) {
|
||||||
mb.Actions[index] = newMessage
|
mb.Actions[index] = newMessage
|
||||||
} else {
|
} else {
|
||||||
return false
|
return "", fmt.Errorf("No action")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return "", nil
|
||||||
}
|
|
||||||
|
|
||||||
func (mb *MockBot) ReplyMsgReceived(msg.Message, string) {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mb *MockBot) GetEmojiList() map[string]string { return make(map[string]string) }
|
func (mb *MockBot) GetEmojiList() map[string]string { return make(map[string]string) }
|
||||||
func (mb *MockBot) RegisterFilter(s string, f func(string) string) {}
|
func (mb *MockBot) RegisterFilter(s string, f func(string) string) {}
|
||||||
|
|
||||||
func NewMockBot() *MockBot {
|
func NewMockBot() *MockBot {
|
||||||
db, err := sqlx.Open("sqlite3_custom", ":memory:")
|
cfg := config.ReadConfig("file::memory:?mode=memory&cache=shared")
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Failed to open database:", err)
|
|
||||||
}
|
|
||||||
b := MockBot{
|
b := MockBot{
|
||||||
db: db,
|
Cfg: cfg,
|
||||||
Messages: make([]string, 0),
|
Messages: make([]string, 0),
|
||||||
Actions: make([]string, 0),
|
Actions: make([]string, 0),
|
||||||
}
|
}
|
||||||
|
// If any plugin registered a route, we need to reset those before any new test
|
||||||
|
http.DefaultServeMux = new(http.ServeMux)
|
||||||
return &b
|
return &b
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,13 @@ type Messages []Message
|
||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
User *user.User
|
User *user.User
|
||||||
Channel, Body string
|
// With Slack, channel is the ID of a channel
|
||||||
Raw string
|
Channel string
|
||||||
|
// With slack, channelName is the nice name of a channel
|
||||||
|
ChannelName string
|
||||||
|
Body string
|
||||||
|
IsIM bool
|
||||||
|
Raw interface{}
|
||||||
Command bool
|
Command bool
|
||||||
Action bool
|
Action bool
|
||||||
Time time.Time
|
Time time.Time
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
package bot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *bot) serveRoot(w http.ResponseWriter, r *http.Request) {
|
||||||
|
context := make(map[string]interface{})
|
||||||
|
context["Nav"] = b.GetWebNavigation()
|
||||||
|
t := template.Must(template.New("rootIndex").Parse(rootIndex))
|
||||||
|
t.Execute(w, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWebNavigation returns a list of bootstrap-vue <b-nav-item> links
|
||||||
|
// The parent <nav> is not included so each page may display it as
|
||||||
|
// best fits
|
||||||
|
func (b *bot) GetWebNavigation() []EndPoint {
|
||||||
|
endpoints := b.httpEndPoints
|
||||||
|
moreEndpoints := b.config.GetArray("bot.links", []string{})
|
||||||
|
for _, e := range moreEndpoints {
|
||||||
|
link := strings.SplitN(e, ":", 2)
|
||||||
|
if len(link) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
endpoints = append(endpoints, EndPoint{link[0], link[1]})
|
||||||
|
}
|
||||||
|
return endpoints
|
||||||
|
}
|
||||||
|
|
||||||
|
var rootIndex = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<!-- Load required Bootstrap and BootstrapVue CSS -->
|
||||||
|
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
|
||||||
|
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.css" />
|
||||||
|
|
||||||
|
<!-- Load polyfills to support older browsers -->
|
||||||
|
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CMutationObserver"></script>
|
||||||
|
|
||||||
|
<!-- Load Vue followed by BootstrapVue -->
|
||||||
|
<script src="//unpkg.com/vue@latest/dist/vue.min.js"></script>
|
||||||
|
<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.js"></script>
|
||||||
|
<script src="https://unpkg.com/vue-router"></script>
|
||||||
|
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Factoids</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="app">
|
||||||
|
<b-navbar>
|
||||||
|
<b-navbar-brand>catbase</b-navbar-brand>
|
||||||
|
<b-navbar-nav>
|
||||||
|
<b-nav-item v-for="item in nav" :href="item.URL">{{ "{{ item.Name }}" }}</b-nav-item>
|
||||||
|
</b-navbar-nav>
|
||||||
|
</b-navbar>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var app = new Vue({
|
||||||
|
el: '#app',
|
||||||
|
data: {
|
||||||
|
err: '',
|
||||||
|
nav: {{ .Nav }},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
232
config/config.go
232
config/config.go
|
@ -5,112 +5,119 @@ package config
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
sqlite3 "github.com/mattn/go-sqlite3"
|
"github.com/mattn/go-sqlite3"
|
||||||
"github.com/yuin/gluamapper"
|
"github.com/rs/zerolog/log"
|
||||||
lua "github.com/yuin/gopher-lua"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config stores any system-wide startup information that cannot be easily configured via
|
// Config stores any system-wide startup information that cannot be easily configured via
|
||||||
// the database
|
// the database
|
||||||
type Config struct {
|
type Config struct {
|
||||||
DBConn *sqlx.DB
|
*sqlx.DB
|
||||||
|
|
||||||
DB struct {
|
DBFile string
|
||||||
File string
|
}
|
||||||
Name string
|
|
||||||
Server 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, fallback float64) float64 {
|
||||||
|
f, err := strconv.ParseFloat(c.GetString(key, fmt.Sprintf("%f", fallback)), 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0.0
|
||||||
}
|
}
|
||||||
Channels []string
|
return f
|
||||||
MainChannel string
|
}
|
||||||
Plugins []string
|
|
||||||
Type string
|
// GetInt returns the config value for a string key
|
||||||
Irc struct {
|
// It will first look in the env vars for the key
|
||||||
Server, Pass string
|
// 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, fallback int) int {
|
||||||
|
i, err := strconv.Atoi(c.GetString(key, strconv.Itoa(fallback)))
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
Slack struct {
|
return i
|
||||||
Token string
|
}
|
||||||
|
|
||||||
|
// Get is a shortcut for GetString
|
||||||
|
func (c *Config) Get(key, fallback string) string {
|
||||||
|
return c.GetString(key, fallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
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, fallback string) string {
|
||||||
|
key = strings.ToLower(key)
|
||||||
|
if v, found := os.LookupEnv(envkey(key)); found {
|
||||||
|
return v
|
||||||
}
|
}
|
||||||
Nick string
|
var configValue string
|
||||||
IconURL string
|
q := `select value from config where key=?`
|
||||||
FullName string
|
err := c.DB.Get(&configValue, q, key)
|
||||||
Version string
|
if err != nil {
|
||||||
CommandChar []string
|
log.Debug().Msgf("WARN: Key %s is empty", key)
|
||||||
RatePerSec float64
|
return fallback
|
||||||
LogLength int
|
|
||||||
Admins []string
|
|
||||||
HttpAddr string
|
|
||||||
Untappd struct {
|
|
||||||
Token string
|
|
||||||
Freq int
|
|
||||||
Channels []string
|
|
||||||
}
|
}
|
||||||
Twitch struct {
|
return configValue
|
||||||
Freq int
|
}
|
||||||
Users map[string][]string //channel -> usernames
|
|
||||||
ClientID string
|
// GetArray returns the string slice config value for a string key
|
||||||
Authorization string
|
// 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, fallback []string) []string {
|
||||||
|
val := c.GetString(key, "")
|
||||||
|
if val == "" {
|
||||||
|
return fallback
|
||||||
}
|
}
|
||||||
EnforceNicks bool
|
return strings.Split(val, ";;")
|
||||||
WelcomeMsgs []string
|
}
|
||||||
TwitterConsumerKey string
|
|
||||||
TwitterConsumerSecret string
|
// Set changes the value for a configuration in the database
|
||||||
TwitterUserKey string
|
// Note, this is always a string. Use the SetArray for an array helper
|
||||||
TwitterUserSecret string
|
func (c *Config) Set(key, value string) error {
|
||||||
BadMsgs []string
|
key = strings.ToLower(key)
|
||||||
Bad struct {
|
q := `insert into config (key,value) values (?, ?)
|
||||||
Msgs []string
|
on conflict(key) do update set value=?;`
|
||||||
Nicks []string
|
tx, err := c.Begin()
|
||||||
Hosts []string
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
Your struct {
|
_, err = tx.Exec(q, key, value, value)
|
||||||
MaxLength int
|
if err != nil {
|
||||||
Replacements []Replacement
|
return err
|
||||||
}
|
}
|
||||||
LeftPad struct {
|
err = tx.Commit()
|
||||||
MaxLen int
|
if err != nil {
|
||||||
Who string
|
return err
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) SetArray(key string, values []string) error {
|
||||||
|
vals := strings.Join(values, ";;")
|
||||||
|
return c.Set(key, vals)
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
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
|
// Readconfig loads the config data out of a JSON file located in cfile
|
||||||
func Readconfig(version, cfile string) *Config {
|
func ReadConfig(dbpath string) *Config {
|
||||||
fmt.Printf("Using %s as config file.\n", cfile)
|
if dbpath == "" {
|
||||||
L := lua.NewState()
|
dbpath = "catbase.db"
|
||||||
if err := L.DoFile(cfile); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
|
log.Info().Msgf("Using %s as database file.\n", dbpath)
|
||||||
|
|
||||||
var c Config
|
sqlDB, err := sqlx.Open("sqlite3_custom", dbpath)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal().Err(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)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().Msgf("catbase is running.")
|
||||||
|
|
||||||
return &c
|
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", "NOPE")
|
||||||
|
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", []string{"NOPE"})
|
||||||
|
assert.Equal(t, expected, actual, "Config did not store values")
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var q = `
|
||||||
|
INSERT INTO config VALUES('nick','{{.Nick}}');
|
||||||
|
INSERT INTO config VALUES('channels','{{.Channel}}');
|
||||||
|
INSERT INTO config VALUES('untappd.channels','{{.Channel}}');
|
||||||
|
INSERT INTO config VALUES('twitch.channels','{{.Channel}}');
|
||||||
|
INSERT INTO config VALUES('init',1);
|
||||||
|
`
|
||||||
|
|
||||||
|
func (c *Config) SetDefaults(mainChannel, nick string) {
|
||||||
|
if nick == mainChannel && nick == "" {
|
||||||
|
log.Fatal().Msgf("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.Info().Msgf("Configuration initialized.")
|
||||||
|
}
|
|
@ -5,11 +5,11 @@ package irc
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
"github.com/velour/catbase/bot/user"
|
"github.com/velour/catbase/bot/user"
|
||||||
|
@ -42,9 +42,7 @@ type Irc struct {
|
||||||
config *config.Config
|
config *config.Config
|
||||||
quit chan bool
|
quit chan bool
|
||||||
|
|
||||||
eventReceived func(msg.Message)
|
event bot.Callback
|
||||||
messageReceived func(msg.Message)
|
|
||||||
replyMessageReceived func(msg.Message, string)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(c *config.Config) *Irc {
|
func New(c *config.Config) *Irc {
|
||||||
|
@ -54,24 +52,28 @@ func New(c *config.Config) *Irc {
|
||||||
return &i
|
return &i
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Irc) RegisterEventReceived(f func(msg.Message)) {
|
func (i *Irc) RegisterEvent(f bot.Callback) {
|
||||||
i.eventReceived = f
|
i.event = f
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Irc) RegisterMessageReceived(f func(msg.Message)) {
|
func (i *Irc) Send(kind bot.Kind, args ...interface{}) (string, error) {
|
||||||
i.messageReceived = f
|
switch kind {
|
||||||
}
|
case bot.Reply:
|
||||||
|
case bot.Message:
|
||||||
func (i *Irc) RegisterReplyMessageReceived(f func(msg.Message, string)) {
|
return i.sendMessage(args[0].(string), args[1].(string), args...)
|
||||||
i.replyMessageReceived = f
|
case bot.Action:
|
||||||
|
return i.sendAction(args[0].(string), args[1].(string), args...)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Irc) JoinChannel(channel string) {
|
func (i *Irc) JoinChannel(channel string) {
|
||||||
log.Printf("Joining channel: %s", channel)
|
log.Info().Msgf("Joining channel: %s", channel)
|
||||||
i.Client.Out <- irc.Msg{Cmd: irc.JOIN, Args: []string{channel}}
|
i.Client.Out <- irc.Msg{Cmd: irc.JOIN, Args: []string{channel}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Irc) SendMessage(channel, message string) string {
|
func (i *Irc) sendMessage(channel, message string, args ...interface{}) (string, error) {
|
||||||
for len(message) > 0 {
|
for len(message) > 0 {
|
||||||
m := irc.Msg{
|
m := irc.Msg{
|
||||||
Cmd: "PRIVMSG",
|
Cmd: "PRIVMSG",
|
||||||
|
@ -87,66 +89,64 @@ func (i *Irc) SendMessage(channel, message string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
if throttle == nil {
|
if throttle == nil {
|
||||||
ratePerSec := i.config.RatePerSec
|
ratePerSec := i.config.GetInt("RatePerSec", 5)
|
||||||
throttle = time.Tick(time.Second / time.Duration(ratePerSec))
|
throttle = time.Tick(time.Second / time.Duration(ratePerSec))
|
||||||
}
|
}
|
||||||
|
|
||||||
<-throttle
|
<-throttle
|
||||||
|
|
||||||
i.Client.Out <- m
|
i.Client.Out <- m
|
||||||
|
|
||||||
|
if len(args) > 0 {
|
||||||
|
for _, a := range args {
|
||||||
|
switch a := a.(type) {
|
||||||
|
case bot.ImageAttachment:
|
||||||
|
m = irc.Msg{
|
||||||
|
Cmd: "PRIVMSG",
|
||||||
|
Args: []string{channel, fmt.Sprintf("%s: %s",
|
||||||
|
a.AltTxt, a.URL)},
|
||||||
}
|
}
|
||||||
return "NO_IRC_IDENTIFIERS"
|
|
||||||
|
<-throttle
|
||||||
|
|
||||||
|
i.Client.Out <- m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "NO_IRC_IDENTIFIERS", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sends action to channel
|
// Sends action to channel
|
||||||
func (i *Irc) SendAction(channel, message string) string {
|
func (i *Irc) sendAction(channel, message string, args ...interface{}) (string, error) {
|
||||||
message = actionPrefix + " " + message + "\x01"
|
message = actionPrefix + " " + message + "\x01"
|
||||||
|
|
||||||
i.SendMessage(channel, message)
|
return i.sendMessage(channel, message, args...)
|
||||||
return "NO_IRC_IDENTIFIERS"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Irc) ReplyToMessageIdentifier(channel, message, identifier string) (string, bool) {
|
|
||||||
return "NO_IRC_IDENTIFIERS", false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Irc) ReplyToMessage(channel, message string, replyTo msg.Message) (string, bool) {
|
|
||||||
return "NO_IRC_IDENTIFIERS", false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Irc) React(channel, reaction string, message msg.Message) bool {
|
|
||||||
//we're not goign to do anything because it's IRC
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Irc) Edit(channel, newMessage, identifier string) bool {
|
|
||||||
//we're not goign to do anything because it's IRC
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Irc) GetEmojiList() map[string]string {
|
func (i *Irc) GetEmojiList() map[string]string {
|
||||||
//we're not goign to do anything because it's IRC
|
//we're not going to do anything because it's IRC
|
||||||
return make(map[string]string)
|
return make(map[string]string)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Irc) Serve() error {
|
func (i *Irc) Serve() error {
|
||||||
if i.eventReceived == nil || i.messageReceived == nil {
|
if i.event == nil {
|
||||||
return fmt.Errorf("Missing an event handler")
|
return fmt.Errorf("Missing an event handler")
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
i.Client, err = irc.DialSSL(
|
i.Client, err = irc.DialSSL(
|
||||||
i.config.Irc.Server,
|
i.config.Get("Irc.Server", "localhost"),
|
||||||
i.config.Nick,
|
i.config.Get("Nick", "bot"),
|
||||||
i.config.FullName,
|
i.config.Get("FullName", "bot"),
|
||||||
i.config.Irc.Pass,
|
i.config.Get("Irc.Pass", ""),
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s", err)
|
return fmt.Errorf("%s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range i.config.Channels {
|
for _, c := range i.config.GetArray("channels", []string{}) {
|
||||||
i.JoinChannel(c)
|
i.JoinChannel(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,7 +164,7 @@ func (i *Irc) handleConnection() {
|
||||||
close(i.Client.Out)
|
close(i.Client.Out)
|
||||||
for err := range i.Client.Errors {
|
for err := range i.Client.Errors {
|
||||||
if err != io.EOF {
|
if err != io.EOF {
|
||||||
log.Println(err)
|
log.Error().Err(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -186,7 +186,7 @@ func (i *Irc) handleConnection() {
|
||||||
|
|
||||||
case err, ok := <-i.Client.Errors:
|
case err, ok := <-i.Client.Errors:
|
||||||
if ok && err != io.EOF {
|
if ok && err != io.EOF {
|
||||||
log.Println(err)
|
log.Error().Err(err)
|
||||||
i.quit <- true
|
i.quit <- true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -200,7 +200,7 @@ func (i *Irc) handleMsg(msg irc.Msg) {
|
||||||
|
|
||||||
switch msg.Cmd {
|
switch msg.Cmd {
|
||||||
case irc.ERROR:
|
case irc.ERROR:
|
||||||
log.Println(1, "Received error: "+msg.Raw)
|
log.Info().Msgf("Received error: " + msg.Raw)
|
||||||
|
|
||||||
case irc.PING:
|
case irc.PING:
|
||||||
i.Client.Out <- irc.Msg{Cmd: irc.PONG}
|
i.Client.Out <- irc.Msg{Cmd: irc.PONG}
|
||||||
|
@ -209,56 +209,56 @@ func (i *Irc) handleMsg(msg irc.Msg) {
|
||||||
// OK, ignore
|
// OK, ignore
|
||||||
|
|
||||||
case irc.ERR_NOSUCHNICK:
|
case irc.ERR_NOSUCHNICK:
|
||||||
i.eventReceived(botMsg)
|
fallthrough
|
||||||
|
|
||||||
case irc.ERR_NOSUCHCHANNEL:
|
case irc.ERR_NOSUCHCHANNEL:
|
||||||
i.eventReceived(botMsg)
|
fallthrough
|
||||||
|
|
||||||
case irc.RPL_MOTD:
|
case irc.RPL_MOTD:
|
||||||
i.eventReceived(botMsg)
|
fallthrough
|
||||||
|
|
||||||
case irc.RPL_NAMREPLY:
|
case irc.RPL_NAMREPLY:
|
||||||
i.eventReceived(botMsg)
|
fallthrough
|
||||||
|
|
||||||
case irc.RPL_TOPIC:
|
case irc.RPL_TOPIC:
|
||||||
i.eventReceived(botMsg)
|
fallthrough
|
||||||
|
|
||||||
case irc.KICK:
|
case irc.KICK:
|
||||||
i.eventReceived(botMsg)
|
fallthrough
|
||||||
|
|
||||||
case irc.TOPIC:
|
case irc.TOPIC:
|
||||||
i.eventReceived(botMsg)
|
fallthrough
|
||||||
|
|
||||||
case irc.MODE:
|
case irc.MODE:
|
||||||
i.eventReceived(botMsg)
|
fallthrough
|
||||||
|
|
||||||
case irc.JOIN:
|
case irc.JOIN:
|
||||||
i.eventReceived(botMsg)
|
fallthrough
|
||||||
|
|
||||||
case irc.PART:
|
case irc.PART:
|
||||||
i.eventReceived(botMsg)
|
fallthrough
|
||||||
|
|
||||||
|
case irc.NOTICE:
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case irc.NICK:
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case irc.RPL_WHOREPLY:
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case irc.RPL_ENDOFWHO:
|
||||||
|
i.event(i, bot.Event, botMsg)
|
||||||
|
|
||||||
|
case irc.PRIVMSG:
|
||||||
|
i.event(i, bot.Message, botMsg)
|
||||||
|
|
||||||
case irc.QUIT:
|
case irc.QUIT:
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
||||||
case irc.NOTICE:
|
|
||||||
i.eventReceived(botMsg)
|
|
||||||
|
|
||||||
case irc.PRIVMSG:
|
|
||||||
i.messageReceived(botMsg)
|
|
||||||
|
|
||||||
case irc.NICK:
|
|
||||||
i.eventReceived(botMsg)
|
|
||||||
|
|
||||||
case irc.RPL_WHOREPLY:
|
|
||||||
i.eventReceived(botMsg)
|
|
||||||
|
|
||||||
case irc.RPL_ENDOFWHO:
|
|
||||||
i.eventReceived(botMsg)
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
cmd := irc.CmdNames[msg.Cmd]
|
cmd := irc.CmdNames[msg.Cmd]
|
||||||
log.Println("(" + cmd + ") " + msg.Raw)
|
log.Debug().Msgf("(%s) %s", cmd, msg.Raw)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,7 +270,7 @@ func (i *Irc) buildMessage(inMsg irc.Msg) msg.Message {
|
||||||
}
|
}
|
||||||
|
|
||||||
channel := inMsg.Args[0]
|
channel := inMsg.Args[0]
|
||||||
if channel == i.config.Nick {
|
if channel == i.config.Get("Nick", "bot") {
|
||||||
channel = inMsg.Args[0]
|
channel = inMsg.Args[0]
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"html"
|
"html"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -21,6 +20,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
"github.com/velour/catbase/bot/user"
|
"github.com/velour/catbase/bot/user"
|
||||||
|
@ -33,6 +33,7 @@ type Slack struct {
|
||||||
|
|
||||||
url string
|
url string
|
||||||
id string
|
id string
|
||||||
|
token string
|
||||||
ws *websocket.Conn
|
ws *websocket.Conn
|
||||||
|
|
||||||
lastRecieved time.Time
|
lastRecieved time.Time
|
||||||
|
@ -43,9 +44,7 @@ type Slack struct {
|
||||||
|
|
||||||
emoji map[string]string
|
emoji map[string]string
|
||||||
|
|
||||||
eventReceived func(msg.Message)
|
event bot.Callback
|
||||||
messageReceived func(msg.Message)
|
|
||||||
replyMessageReceived func(msg.Message, string)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var idCounter uint64
|
var idCounter uint64
|
||||||
|
@ -163,15 +162,44 @@ type rtmStart struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(c *config.Config) *Slack {
|
func New(c *config.Config) *Slack {
|
||||||
|
token := c.Get("slack.token", "NONE")
|
||||||
|
if token == "NONE" {
|
||||||
|
log.Fatal().Msgf("No slack token found. Set SLACKTOKEN env.")
|
||||||
|
}
|
||||||
return &Slack{
|
return &Slack{
|
||||||
config: c,
|
config: c,
|
||||||
|
token: c.Get("slack.token", ""),
|
||||||
lastRecieved: time.Now(),
|
lastRecieved: time.Now(),
|
||||||
users: make(map[string]string),
|
users: make(map[string]string),
|
||||||
emoji: make(map[string]string),
|
emoji: make(map[string]string),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkReturnStatus(response *http.Response) bool {
|
func (s *Slack) Send(kind bot.Kind, args ...interface{}) (string, error) {
|
||||||
|
switch kind {
|
||||||
|
case bot.Message:
|
||||||
|
return s.sendMessage(args[0].(string), args[1].(string))
|
||||||
|
case bot.Action:
|
||||||
|
return s.sendAction(args[0].(string), args[1].(string))
|
||||||
|
case bot.Edit:
|
||||||
|
return s.edit(args[0].(string), args[1].(string), args[2].(string))
|
||||||
|
case bot.Reply:
|
||||||
|
switch args[2].(type) {
|
||||||
|
case msg.Message:
|
||||||
|
return s.replyToMessage(args[0].(string), args[1].(string), args[2].(msg.Message))
|
||||||
|
case string:
|
||||||
|
return s.replyToMessageIdentifier(args[0].(string), args[1].(string), args[2].(string))
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("Invalid types given to Reply")
|
||||||
|
}
|
||||||
|
case bot.Reaction:
|
||||||
|
return s.react(args[0].(string), args[1].(string), args[2].(msg.Message))
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("No handler for message type %d", kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkReturnStatus(response *http.Response) error {
|
||||||
type Response struct {
|
type Response struct {
|
||||||
OK bool `json:"ok"`
|
OK bool `json:"ok"`
|
||||||
}
|
}
|
||||||
|
@ -179,42 +207,34 @@ func checkReturnStatus(response *http.Response) bool {
|
||||||
body, err := ioutil.ReadAll(response.Body)
|
body, err := ioutil.ReadAll(response.Body)
|
||||||
response.Body.Close()
|
response.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error reading Slack API body: %s", err)
|
err := fmt.Errorf("Error reading Slack API body: %s", err)
|
||||||
return false
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var resp Response
|
var resp Response
|
||||||
err = json.Unmarshal(body, &resp)
|
err = json.Unmarshal(body, &resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error parsing message response: %s", err)
|
err := fmt.Errorf("Error parsing message response: %s", err)
|
||||||
return false
|
return err
|
||||||
}
|
}
|
||||||
return resp.OK
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Slack) RegisterEventReceived(f func(msg.Message)) {
|
func (s *Slack) RegisterEvent(f bot.Callback) {
|
||||||
s.eventReceived = f
|
s.event = f
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Slack) RegisterMessageReceived(f func(msg.Message)) {
|
func (s *Slack) sendMessageType(channel, message string, meMessage bool) (string, error) {
|
||||||
s.messageReceived = f
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Slack) RegisterReplyMessageReceived(f func(msg.Message, string)) {
|
|
||||||
s.replyMessageReceived = f
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Slack) SendMessageType(channel, message string, meMessage bool) (string, error) {
|
|
||||||
postUrl := "https://slack.com/api/chat.postMessage"
|
postUrl := "https://slack.com/api/chat.postMessage"
|
||||||
if meMessage {
|
if meMessage {
|
||||||
postUrl = "https://slack.com/api/chat.meMessage"
|
postUrl = "https://slack.com/api/chat.meMessage"
|
||||||
}
|
}
|
||||||
|
|
||||||
nick := s.config.Nick
|
nick := s.config.Get("Nick", "bot")
|
||||||
icon := s.config.IconURL
|
icon := s.config.Get("IconURL", "https://placekitten.com/128/128")
|
||||||
|
|
||||||
resp, err := http.PostForm(postUrl,
|
resp, err := http.PostForm(postUrl,
|
||||||
url.Values{"token": {s.config.Slack.Token},
|
url.Values{"token": {s.token},
|
||||||
"username": {nick},
|
"username": {nick},
|
||||||
"icon_url": {icon},
|
"icon_url": {icon},
|
||||||
"channel": {channel},
|
"channel": {channel},
|
||||||
|
@ -222,16 +242,16 @@ func (s *Slack) SendMessageType(channel, message string, meMessage bool) (string
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error sending Slack message: %s", err)
|
log.Error().Err(err).Msgf("Error sending Slack message")
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error reading Slack API body: %s", err)
|
log.Fatal().Err(err).Msgf("Error reading Slack API body")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println(string(body))
|
log.Debug().Msgf("%+v", body)
|
||||||
|
|
||||||
type MessageResponse struct {
|
type MessageResponse struct {
|
||||||
OK bool `json:"ok"`
|
OK bool `json:"ok"`
|
||||||
|
@ -244,7 +264,7 @@ func (s *Slack) SendMessageType(channel, message string, meMessage bool) (string
|
||||||
var mr MessageResponse
|
var mr MessageResponse
|
||||||
err = json.Unmarshal(body, &mr)
|
err = json.Unmarshal(body, &mr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error parsing message response: %s", err)
|
log.Fatal().Err(err).Msgf("Error parsing message response")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !mr.OK {
|
if !mr.OK {
|
||||||
|
@ -256,24 +276,24 @@ func (s *Slack) SendMessageType(channel, message string, meMessage bool) (string
|
||||||
return mr.Timestamp, err
|
return mr.Timestamp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Slack) SendMessage(channel, message string) string {
|
func (s *Slack) sendMessage(channel, message string) (string, error) {
|
||||||
log.Printf("Sending message to %s: %s", channel, message)
|
log.Debug().Msgf("Sending message to %s: %s", channel, message)
|
||||||
identifier, _ := s.SendMessageType(channel, message, false)
|
identifier, err := s.sendMessageType(channel, message, false)
|
||||||
return identifier
|
return identifier, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Slack) SendAction(channel, message string) string {
|
func (s *Slack) sendAction(channel, message string) (string, error) {
|
||||||
log.Printf("Sending action to %s: %s", channel, message)
|
log.Debug().Msgf("Sending action to %s: %s", channel, message)
|
||||||
identifier, _ := s.SendMessageType(channel, "_"+message+"_", true)
|
identifier, err := s.sendMessageType(channel, "_"+message+"_", true)
|
||||||
return identifier
|
return identifier, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Slack) ReplyToMessageIdentifier(channel, message, identifier string) (string, bool) {
|
func (s *Slack) replyToMessageIdentifier(channel, message, identifier string) (string, error) {
|
||||||
nick := s.config.Nick
|
nick := s.config.Get("Nick", "bot")
|
||||||
icon := s.config.IconURL
|
icon := s.config.Get("IconURL", "https://placekitten.com/128/128")
|
||||||
|
|
||||||
resp, err := http.PostForm("https://slack.com/api/chat.postMessage",
|
resp, err := http.PostForm("https://slack.com/api/chat.postMessage",
|
||||||
url.Values{"token": {s.config.Slack.Token},
|
url.Values{"token": {s.token},
|
||||||
"username": {nick},
|
"username": {nick},
|
||||||
"icon_url": {icon},
|
"icon_url": {icon},
|
||||||
"channel": {channel},
|
"channel": {channel},
|
||||||
|
@ -282,18 +302,18 @@ func (s *Slack) ReplyToMessageIdentifier(channel, message, identifier string) (s
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error sending Slack reply: %s", err)
|
err := fmt.Errorf("Error sending Slack reply: %s", err)
|
||||||
return "", false
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error reading Slack API body: %s", err)
|
err := fmt.Errorf("Error reading Slack API body: %s", err)
|
||||||
return "", false
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println(string(body))
|
log.Debug().Msgf("%s", body)
|
||||||
|
|
||||||
type MessageResponse struct {
|
type MessageResponse struct {
|
||||||
OK bool `json:"ok"`
|
OK bool `json:"ok"`
|
||||||
|
@ -303,47 +323,47 @@ func (s *Slack) ReplyToMessageIdentifier(channel, message, identifier string) (s
|
||||||
var mr MessageResponse
|
var mr MessageResponse
|
||||||
err = json.Unmarshal(body, &mr)
|
err = json.Unmarshal(body, &mr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error parsing message response: %s", err)
|
err := fmt.Errorf("Error parsing message response: %s", err)
|
||||||
return "", false
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !mr.OK {
|
if !mr.OK {
|
||||||
return "", false
|
return "", fmt.Errorf("Got !OK from slack message response")
|
||||||
}
|
}
|
||||||
|
|
||||||
return mr.Timestamp, err == nil
|
return mr.Timestamp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Slack) ReplyToMessage(channel, message string, replyTo msg.Message) (string, bool) {
|
func (s *Slack) replyToMessage(channel, message string, replyTo msg.Message) (string, error) {
|
||||||
return s.ReplyToMessageIdentifier(channel, message, replyTo.AdditionalData["RAW_SLACK_TIMESTAMP"])
|
return s.replyToMessageIdentifier(channel, message, replyTo.AdditionalData["RAW_SLACK_TIMESTAMP"])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Slack) React(channel, reaction string, message msg.Message) bool {
|
func (s *Slack) react(channel, reaction string, message msg.Message) (string, error) {
|
||||||
log.Printf("Reacting in %s: %s", channel, reaction)
|
log.Debug().Msgf("Reacting in %s: %s", channel, reaction)
|
||||||
resp, err := http.PostForm("https://slack.com/api/reactions.add",
|
resp, err := http.PostForm("https://slack.com/api/reactions.add",
|
||||||
url.Values{"token": {s.config.Slack.Token},
|
url.Values{"token": {s.token},
|
||||||
"name": {reaction},
|
"name": {reaction},
|
||||||
"channel": {channel},
|
"channel": {channel},
|
||||||
"timestamp": {message.AdditionalData["RAW_SLACK_TIMESTAMP"]}})
|
"timestamp": {message.AdditionalData["RAW_SLACK_TIMESTAMP"]}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("reaction failed: %s", err)
|
err := fmt.Errorf("reaction failed: %s", err)
|
||||||
return false
|
return "", err
|
||||||
}
|
}
|
||||||
return checkReturnStatus(resp)
|
return "", checkReturnStatus(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Slack) Edit(channel, newMessage, identifier string) bool {
|
func (s *Slack) edit(channel, newMessage, identifier string) (string, error) {
|
||||||
log.Printf("Editing in (%s) %s: %s", identifier, channel, newMessage)
|
log.Debug().Msgf("Editing in (%s) %s: %s", identifier, channel, newMessage)
|
||||||
resp, err := http.PostForm("https://slack.com/api/chat.update",
|
resp, err := http.PostForm("https://slack.com/api/chat.update",
|
||||||
url.Values{"token": {s.config.Slack.Token},
|
url.Values{"token": {s.token},
|
||||||
"channel": {channel},
|
"channel": {channel},
|
||||||
"text": {newMessage},
|
"text": {newMessage},
|
||||||
"ts": {identifier}})
|
"ts": {identifier}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("edit failed: %s", err)
|
err := fmt.Errorf("edit failed: %s", err)
|
||||||
return false
|
return "", err
|
||||||
}
|
}
|
||||||
return checkReturnStatus(resp)
|
return "", checkReturnStatus(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Slack) GetEmojiList() map[string]string {
|
func (s *Slack) GetEmojiList() map[string]string {
|
||||||
|
@ -352,16 +372,16 @@ func (s *Slack) GetEmojiList() map[string]string {
|
||||||
|
|
||||||
func (s *Slack) populateEmojiList() {
|
func (s *Slack) populateEmojiList() {
|
||||||
resp, err := http.PostForm("https://slack.com/api/emoji.list",
|
resp, err := http.PostForm("https://slack.com/api/emoji.list",
|
||||||
url.Values{"token": {s.config.Slack.Token}})
|
url.Values{"token": {s.token}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error retrieving emoji list from Slack: %s", err)
|
log.Debug().Msgf("Error retrieving emoji list from Slack: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error reading Slack API body: %s", err)
|
log.Fatal().Err(err).Msgf("Error reading Slack API body")
|
||||||
}
|
}
|
||||||
|
|
||||||
type EmojiListResponse struct {
|
type EmojiListResponse struct {
|
||||||
|
@ -372,7 +392,7 @@ func (s *Slack) populateEmojiList() {
|
||||||
var list EmojiListResponse
|
var list EmojiListResponse
|
||||||
err = json.Unmarshal(body, &list)
|
err = json.Unmarshal(body, &list)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error parsing emoji list: %s", err)
|
log.Fatal().Err(err).Msgf("Error parsing emoji list")
|
||||||
}
|
}
|
||||||
s.emoji = list.Emoji
|
s.emoji = list.Emoji
|
||||||
}
|
}
|
||||||
|
@ -397,7 +417,7 @@ func (s *Slack) receiveMessage() (slackMessage, error) {
|
||||||
m := slackMessage{}
|
m := slackMessage{}
|
||||||
err := s.ws.Recv(context.TODO(), &m)
|
err := s.ws.Recv(context.TODO(), &m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error decoding WS message")
|
log.Error().Msgf("Error decoding WS message")
|
||||||
panic(fmt.Errorf("%v\n%v", m, err))
|
panic(fmt.Errorf("%v\n%v", m, err))
|
||||||
}
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
|
@ -422,7 +442,7 @@ func (s *Slack) Serve() error {
|
||||||
for {
|
for {
|
||||||
msg, err := s.receiveMessage()
|
msg, err := s.receiveMessage()
|
||||||
if err != nil && err == io.EOF {
|
if err != nil && err == io.EOF {
|
||||||
log.Fatalf("Slack API EOF")
|
log.Fatal().Msg("Slack API EOF")
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return fmt.Errorf("Slack API error: %s", err)
|
return fmt.Errorf("Slack API error: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -432,19 +452,19 @@ func (s *Slack) Serve() error {
|
||||||
if !isItMe && !msg.Hidden && msg.ThreadTs == "" {
|
if !isItMe && !msg.Hidden && msg.ThreadTs == "" {
|
||||||
m := s.buildMessage(msg)
|
m := s.buildMessage(msg)
|
||||||
if m.Time.Before(s.lastRecieved) {
|
if m.Time.Before(s.lastRecieved) {
|
||||||
log.Printf("Ignoring message: %+v\nlastRecieved: %v msg: %v", msg.ID, s.lastRecieved, m.Time)
|
log.Debug().Msgf("Ignoring message: %+v\nlastRecieved: %v msg: %v", msg.ID, s.lastRecieved, m.Time)
|
||||||
} else {
|
} else {
|
||||||
s.lastRecieved = m.Time
|
s.lastRecieved = m.Time
|
||||||
s.messageReceived(m)
|
s.event(s, bot.Message, m)
|
||||||
}
|
}
|
||||||
} else if msg.ThreadTs != "" {
|
} else if msg.ThreadTs != "" {
|
||||||
//we're throwing away some information here by not parsing the correct reply object type, but that's okay
|
//we're throwing away some information here by not parsing the correct reply object type, but that's okay
|
||||||
s.replyMessageReceived(s.buildLightReplyMessage(msg), msg.ThreadTs)
|
s.event(s, bot.Reply, s.buildLightReplyMessage(msg), msg.ThreadTs)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("THAT MESSAGE WAS HIDDEN: %+v", msg.ID)
|
log.Debug().Msgf("THAT MESSAGE WAS HIDDEN: %+v", msg.ID)
|
||||||
}
|
}
|
||||||
case "error":
|
case "error":
|
||||||
log.Printf("Slack error, code: %d, message: %s", msg.Error.Code, msg.Error.Msg)
|
log.Error().Msgf("Slack error, code: %d, message: %s", msg.Error.Code, msg.Error.Msg)
|
||||||
case "": // what even is this?
|
case "": // what even is this?
|
||||||
case "hello":
|
case "hello":
|
||||||
case "presence_change":
|
case "presence_change":
|
||||||
|
@ -455,7 +475,7 @@ func (s *Slack) Serve() error {
|
||||||
// squeltch this stuff
|
// squeltch this stuff
|
||||||
continue
|
continue
|
||||||
default:
|
default:
|
||||||
log.Printf("Unhandled Slack message type: '%s'", msg.Type)
|
log.Debug().Msgf("Unhandled Slack message type: '%s'", msg.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -534,25 +554,24 @@ func (s *Slack) buildLightReplyMessage(m slackMessage) msg.Message {
|
||||||
// markAllChannelsRead gets a list of all channels and marks each as read
|
// markAllChannelsRead gets a list of all channels and marks each as read
|
||||||
func (s *Slack) markAllChannelsRead() {
|
func (s *Slack) markAllChannelsRead() {
|
||||||
chs := s.getAllChannels()
|
chs := s.getAllChannels()
|
||||||
log.Printf("Got list of channels to mark read: %+v", chs)
|
log.Debug().Msgf("Got list of channels to mark read: %+v", chs)
|
||||||
for _, ch := range chs {
|
for _, ch := range chs {
|
||||||
s.markChannelAsRead(ch.ID)
|
s.markChannelAsRead(ch.ID)
|
||||||
}
|
}
|
||||||
log.Printf("Finished marking channels read")
|
log.Debug().Msgf("Finished marking channels read")
|
||||||
}
|
}
|
||||||
|
|
||||||
// getAllChannels returns info for all channels joined
|
// getAllChannels returns info for all channels joined
|
||||||
func (s *Slack) getAllChannels() []slackChannelListItem {
|
func (s *Slack) getAllChannels() []slackChannelListItem {
|
||||||
u := s.url + "channels.list"
|
u := s.url + "channels.list"
|
||||||
resp, err := http.PostForm(u,
|
resp, err := http.PostForm(u,
|
||||||
url.Values{"token": {s.config.Slack.Token}})
|
url.Values{"token": {s.token}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error posting user info request: %s",
|
log.Error().Err(err).Msgf("Error posting user info request")
|
||||||
err)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
log.Printf("Error posting user info request: %d",
|
log.Error().Msgf("Error posting user info request: %d",
|
||||||
resp.StatusCode)
|
resp.StatusCode)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -560,7 +579,7 @@ func (s *Slack) getAllChannels() []slackChannelListItem {
|
||||||
var chanInfo slackChannelListResp
|
var chanInfo slackChannelListResp
|
||||||
err = json.NewDecoder(resp.Body).Decode(&chanInfo)
|
err = json.NewDecoder(resp.Body).Decode(&chanInfo)
|
||||||
if err != nil || !chanInfo.Ok {
|
if err != nil || !chanInfo.Ok {
|
||||||
log.Println("Error decoding response: ", err)
|
log.Error().Err(err).Msgf("Error decoding response")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return chanInfo.Channels
|
return chanInfo.Channels
|
||||||
|
@ -570,66 +589,62 @@ func (s *Slack) getAllChannels() []slackChannelListItem {
|
||||||
func (s *Slack) markChannelAsRead(slackChanId string) error {
|
func (s *Slack) markChannelAsRead(slackChanId string) error {
|
||||||
u := s.url + "channels.info"
|
u := s.url + "channels.info"
|
||||||
resp, err := http.PostForm(u,
|
resp, err := http.PostForm(u,
|
||||||
url.Values{"token": {s.config.Slack.Token}, "channel": {slackChanId}})
|
url.Values{"token": {s.token}, "channel": {slackChanId}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error posting user info request: %s",
|
log.Error().Err(err).Msgf("Error posting user info request")
|
||||||
err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
log.Printf("Error posting user info request: %d",
|
log.Error().Msgf("Error posting user info request: %d",
|
||||||
resp.StatusCode)
|
resp.StatusCode)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
var chanInfo slackChannelInfoResp
|
var chanInfo slackChannelInfoResp
|
||||||
err = json.NewDecoder(resp.Body).Decode(&chanInfo)
|
err = json.NewDecoder(resp.Body).Decode(&chanInfo)
|
||||||
log.Printf("%+v, %+v", err, chanInfo)
|
|
||||||
if err != nil || !chanInfo.Ok {
|
if err != nil || !chanInfo.Ok {
|
||||||
log.Println("Error decoding response: ", err)
|
log.Error().Err(err).Msgf("Error decoding response")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
u = s.url + "channels.mark"
|
u = s.url + "channels.mark"
|
||||||
resp, err = http.PostForm(u,
|
resp, err = http.PostForm(u,
|
||||||
url.Values{"token": {s.config.Slack.Token}, "channel": {slackChanId}, "ts": {chanInfo.Channel.Latest.Ts}})
|
url.Values{"token": {s.token}, "channel": {slackChanId}, "ts": {chanInfo.Channel.Latest.Ts}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error posting user info request: %s",
|
log.Error().Err(err).Msgf("Error posting user info request")
|
||||||
err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
log.Printf("Error posting user info request: %d",
|
log.Error().Msgf("Error posting user info request: %d",
|
||||||
resp.StatusCode)
|
resp.StatusCode)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
var markInfo map[string]interface{}
|
var markInfo map[string]interface{}
|
||||||
err = json.NewDecoder(resp.Body).Decode(&markInfo)
|
err = json.NewDecoder(resp.Body).Decode(&markInfo)
|
||||||
log.Printf("%+v, %+v", err, markInfo)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error decoding response: ", err)
|
log.Error().Err(err).Msgf("Error decoding response")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Marked %s as read", slackChanId)
|
log.Info().Msgf("Marked %s as read", slackChanId)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Slack) connect() {
|
func (s *Slack) connect() {
|
||||||
token := s.config.Slack.Token
|
token := s.token
|
||||||
u := fmt.Sprintf("https://slack.com/api/rtm.connect?token=%s", token)
|
u := fmt.Sprintf("https://slack.com/api/rtm.connect?token=%s", token)
|
||||||
resp, err := http.Get(u)
|
resp, err := http.Get(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
log.Fatalf("Slack API failed. Code: %d", resp.StatusCode)
|
log.Fatal().Msgf("Slack API failed. Code: %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error reading Slack API body: %s", err)
|
log.Fatal().Err(err).Msg("Error reading Slack API body")
|
||||||
}
|
}
|
||||||
var rtm rtmStart
|
var rtm rtmStart
|
||||||
err = json.Unmarshal(body, &rtm)
|
err = json.Unmarshal(body, &rtm)
|
||||||
|
@ -638,7 +653,7 @@ func (s *Slack) connect() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !rtm.Ok {
|
if !rtm.Ok {
|
||||||
log.Fatalf("Slack error: %s", rtm.Error)
|
log.Fatal().Msgf("Slack error: %s", rtm.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.url = "https://slack.com/api/"
|
s.url = "https://slack.com/api/"
|
||||||
|
@ -650,7 +665,7 @@ func (s *Slack) connect() {
|
||||||
rtmURL, _ := url.Parse(rtm.URL)
|
rtmURL, _ := url.Parse(rtm.URL)
|
||||||
s.ws, err = websocket.Dial(context.TODO(), rtmURL)
|
s.ws, err = websocket.Dial(context.TODO(), rtmURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal().Err(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -660,20 +675,20 @@ func (s *Slack) getUser(id string) (string, bool) {
|
||||||
return name, true
|
return name, true
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("User %s not already found, requesting info", id)
|
log.Debug().Msgf("User %s not already found, requesting info", id)
|
||||||
u := s.url + "users.info"
|
u := s.url + "users.info"
|
||||||
resp, err := http.PostForm(u,
|
resp, err := http.PostForm(u,
|
||||||
url.Values{"token": {s.config.Slack.Token}, "user": {id}})
|
url.Values{"token": {s.token}, "user": {id}})
|
||||||
if err != nil || resp.StatusCode != 200 {
|
if err != nil || resp.StatusCode != 200 {
|
||||||
log.Printf("Error posting user info request: %d %s",
|
log.Error().Err(err).Msgf("Error posting user info request: %d",
|
||||||
resp.StatusCode, err)
|
resp.StatusCode)
|
||||||
return "UNKNOWN", false
|
return "UNKNOWN", false
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
var userInfo slackUserInfoResp
|
var userInfo slackUserInfoResp
|
||||||
err = json.NewDecoder(resp.Body).Decode(&userInfo)
|
err = json.NewDecoder(resp.Body).Decode(&userInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error decoding response: ", err)
|
log.Error().Err(err).Msgf("Error decoding response")
|
||||||
return "UNKNOWN", false
|
return "UNKNOWN", false
|
||||||
}
|
}
|
||||||
s.users[id] = userInfo.User.Name
|
s.users[id] = userInfo.User.Name
|
||||||
|
@ -682,17 +697,18 @@ func (s *Slack) getUser(id string) (string, bool) {
|
||||||
|
|
||||||
// Who gets usernames out of a channel
|
// Who gets usernames out of a channel
|
||||||
func (s *Slack) Who(id string) []string {
|
func (s *Slack) Who(id string) []string {
|
||||||
log.Println("Who is queried for ", id)
|
log.Debug().
|
||||||
|
Str("id", id).
|
||||||
|
Msg("Who is queried for ")
|
||||||
u := s.url + "channels.info"
|
u := s.url + "channels.info"
|
||||||
resp, err := http.PostForm(u,
|
resp, err := http.PostForm(u,
|
||||||
url.Values{"token": {s.config.Slack.Token}, "channel": {id}})
|
url.Values{"token": {s.token}, "channel": {id}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error posting user info request: %s",
|
log.Error().Err(err).Msgf("Error posting user info request")
|
||||||
err)
|
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
log.Printf("Error posting user info request: %d",
|
log.Error().Msgf("Error posting user info request: %d",
|
||||||
resp.StatusCode)
|
resp.StatusCode)
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
@ -700,17 +716,17 @@ func (s *Slack) Who(id string) []string {
|
||||||
var chanInfo slackChannelInfoResp
|
var chanInfo slackChannelInfoResp
|
||||||
err = json.NewDecoder(resp.Body).Decode(&chanInfo)
|
err = json.NewDecoder(resp.Body).Decode(&chanInfo)
|
||||||
if err != nil || !chanInfo.Ok {
|
if err != nil || !chanInfo.Ok {
|
||||||
log.Println("Error decoding response: ", err)
|
log.Error().Err(err).Msgf("Error decoding response")
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("%#v", chanInfo.Channel)
|
log.Debug().Msgf("%#v", chanInfo.Channel)
|
||||||
|
|
||||||
handles := []string{}
|
handles := []string{}
|
||||||
for _, member := range chanInfo.Channel.Members {
|
for _, member := range chanInfo.Channel.Members {
|
||||||
u, _ := s.getUser(member)
|
u, _ := s.getUser(member)
|
||||||
handles = append(handles, u)
|
handles = append(handles, u)
|
||||||
}
|
}
|
||||||
log.Printf("Returning %d handles", len(handles))
|
log.Debug().Msgf("Returning %d handles", len(handles))
|
||||||
return handles
|
return handles
|
||||||
}
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
package slackapp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nlopes/slack"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fixText strips all of the Slack-specific annotations from message text,
|
||||||
|
// replacing it with the equivalent display form.
|
||||||
|
// Currently it:
|
||||||
|
// • Replaces user mentions like <@U124356> with @ followed by the user's nick.
|
||||||
|
// This uses the lookupUser function, which must map U1243456 to the nick.
|
||||||
|
// • Replaces user mentions like <U123456|nick> with the user's nick.
|
||||||
|
// • Strips < and > surrounding links.
|
||||||
|
//
|
||||||
|
// This was directly bogarted from velour/chat with emoji conversion removed.
|
||||||
|
func fixText(findUser func(id string) (*slack.User, error), text string) string {
|
||||||
|
var output []rune
|
||||||
|
for len(text) > 0 {
|
||||||
|
r, i := utf8.DecodeRuneInString(text)
|
||||||
|
text = text[i:]
|
||||||
|
switch {
|
||||||
|
case r == '<':
|
||||||
|
var tag []rune
|
||||||
|
for {
|
||||||
|
r, i := utf8.DecodeRuneInString(text)
|
||||||
|
text = text[i:]
|
||||||
|
switch {
|
||||||
|
case r == '>':
|
||||||
|
if t, ok := fixTag(findUser, tag); ok {
|
||||||
|
output = append(output, t...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case len(text) == 0:
|
||||||
|
output = append(output, '<')
|
||||||
|
output = append(output, tag...)
|
||||||
|
output = append(output, r)
|
||||||
|
default:
|
||||||
|
tag = append(tag, r)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
output = append(output, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fixTag(findUser func(string) (*slack.User, error), tag []rune) ([]rune, bool) {
|
||||||
|
switch {
|
||||||
|
case hasPrefix(tag, "@U"):
|
||||||
|
if i := indexRune(tag, '|'); i >= 0 {
|
||||||
|
return tag[i+1:], true
|
||||||
|
}
|
||||||
|
if findUser != nil {
|
||||||
|
if u, err := findUser(string(tag[1:])); err == nil {
|
||||||
|
return []rune(u.Name), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tag, true
|
||||||
|
|
||||||
|
case hasPrefix(tag, "#C"):
|
||||||
|
if i := indexRune(tag, '|'); i >= 0 {
|
||||||
|
return append([]rune{'#'}, tag[i+1:]...), true
|
||||||
|
}
|
||||||
|
|
||||||
|
case hasPrefix(tag, "http"):
|
||||||
|
if i := indexRune(tag, '|'); i >= 0 {
|
||||||
|
tag = tag[:i]
|
||||||
|
}
|
||||||
|
return tag, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasPrefix(text []rune, prefix string) bool {
|
||||||
|
for _, r := range prefix {
|
||||||
|
if len(text) == 0 || text[0] != r {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
text = text[1:]
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func indexRune(text []rune, find rune) int {
|
||||||
|
for i, r := range text {
|
||||||
|
if r == find {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
|
@ -0,0 +1,569 @@
|
||||||
|
package slackapp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"container/ring"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"html"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"github.com/nlopes/slack"
|
||||||
|
"github.com/nlopes/slack/slackevents"
|
||||||
|
|
||||||
|
"github.com/velour/catbase/bot"
|
||||||
|
"github.com/velour/catbase/bot/msg"
|
||||||
|
"github.com/velour/catbase/bot/user"
|
||||||
|
"github.com/velour/catbase/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DefaultRing = 5
|
||||||
|
const defaultLogFormat = "[{{fixDate .Time \"2006-01-02 15:04:05\"}}] {{if .TopicChange}}*** {{.User.Name}}{{else if .Action}}* {{.User.Name}}{{else}}<{{.User.Name}}>{{end}} {{.Body}}\n"
|
||||||
|
|
||||||
|
// 11:10AM DBG connectors/slackapp/slackApp.go:496 > Slack event dir=logs raw={"Action":false,"AdditionalData":
|
||||||
|
// {"RAW_SLACK_TIMESTAMP":"1559920235.001100"},"Body":"aoeu","Channel":"C0S04SMRC","ChannelName":"test",
|
||||||
|
// "Command":false,"Host":"","IsIM":false,"Raw":{"channel":"C0S04SMRC","channel_type":"channel",
|
||||||
|
// "event_ts":1559920235.001100,"files":null,"text":"aoeu","thread_ts":"","ts":"1559920235.001100",
|
||||||
|
// "type":"message","upload":false,"user":"U0RLUDELD"},"Time":"2019-06-07T11:10:35.0000011-04:00",
|
||||||
|
// "User":{"Admin":false,"ID":"U0RLUDELD","Name":"flyngpngn"}}
|
||||||
|
|
||||||
|
type SlackApp struct {
|
||||||
|
bot bot.Bot
|
||||||
|
config *config.Config
|
||||||
|
api *slack.Client
|
||||||
|
|
||||||
|
botToken string
|
||||||
|
userToken string
|
||||||
|
verification string
|
||||||
|
id string
|
||||||
|
|
||||||
|
lastRecieved time.Time
|
||||||
|
|
||||||
|
myBotID string
|
||||||
|
users map[string]*slack.User
|
||||||
|
emoji map[string]string
|
||||||
|
channels map[string]*slack.Channel
|
||||||
|
|
||||||
|
event bot.Callback
|
||||||
|
|
||||||
|
msgIDBuffer *ring.Ring
|
||||||
|
|
||||||
|
logFormat *template.Template
|
||||||
|
}
|
||||||
|
|
||||||
|
func fixDate(input time.Time, format string) string {
|
||||||
|
return input.Format(format)
|
||||||
|
}
|
||||||
|
func New(c *config.Config) *SlackApp {
|
||||||
|
token := c.Get("slack.token", "NONE")
|
||||||
|
if token == "NONE" {
|
||||||
|
log.Fatal().Msg("No slack token found. Set SLACKTOKEN env.")
|
||||||
|
}
|
||||||
|
|
||||||
|
api := slack.New(token, slack.OptionDebug(false))
|
||||||
|
|
||||||
|
idBuf := ring.New(c.GetInt("ringSize", DefaultRing))
|
||||||
|
for i := 0; i < idBuf.Len(); i++ {
|
||||||
|
idBuf.Value = ""
|
||||||
|
idBuf = idBuf.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
tplTxt := c.GetString("slackapp.log.format", defaultLogFormat)
|
||||||
|
funcs := template.FuncMap{
|
||||||
|
"fixDate": fixDate,
|
||||||
|
}
|
||||||
|
tpl := template.Must(template.New("log").Funcs(funcs).Parse(tplTxt))
|
||||||
|
|
||||||
|
return &SlackApp{
|
||||||
|
api: api,
|
||||||
|
config: c,
|
||||||
|
botToken: token,
|
||||||
|
userToken: c.Get("slack.usertoken", "NONE"),
|
||||||
|
verification: c.Get("slack.verification", "NONE"),
|
||||||
|
myBotID: c.Get("slack.botid", ""),
|
||||||
|
lastRecieved: time.Now(),
|
||||||
|
users: make(map[string]*slack.User),
|
||||||
|
emoji: make(map[string]string),
|
||||||
|
channels: make(map[string]*slack.Channel),
|
||||||
|
msgIDBuffer: idBuf,
|
||||||
|
logFormat: tpl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SlackApp) RegisterEvent(f bot.Callback) {
|
||||||
|
s.event = f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SlackApp) Serve() error {
|
||||||
|
s.populateEmojiList()
|
||||||
|
|
||||||
|
http.HandleFunc("/evt", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
buf.ReadFrom(r.Body)
|
||||||
|
body := buf.String()
|
||||||
|
eventsAPIEvent, e := slackevents.ParseEvent(json.RawMessage(body), slackevents.OptionVerifyToken(&slackevents.TokenComparator{VerificationToken: s.verification}))
|
||||||
|
if e != nil {
|
||||||
|
log.Error().Err(e)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
if eventsAPIEvent.Type == slackevents.URLVerification {
|
||||||
|
var r *slackevents.ChallengeResponse
|
||||||
|
err := json.Unmarshal([]byte(body), &r)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text")
|
||||||
|
w.Write([]byte(r.Challenge))
|
||||||
|
} else if eventsAPIEvent.Type == slackevents.CallbackEvent {
|
||||||
|
innerEvent := eventsAPIEvent.InnerEvent
|
||||||
|
switch ev := innerEvent.Data.(type) {
|
||||||
|
case *slackevents.AppMentionEvent:
|
||||||
|
// This is a bit of a problem. AppMentionEvent also needs to
|
||||||
|
// End up in msgReceived
|
||||||
|
//s.msgReceivd(ev)
|
||||||
|
case *slackevents.MessageEvent:
|
||||||
|
s.msgReceivd(ev)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Debug().
|
||||||
|
Str("type", eventsAPIEvent.Type).
|
||||||
|
Interface("event", eventsAPIEvent).
|
||||||
|
Msg("event")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkRingOrAdd returns true if it finds the ts value
|
||||||
|
// or false if the ts isn't yet in the ring (and adds it)
|
||||||
|
func (s *SlackApp) checkRingOrAdd(ts string) bool {
|
||||||
|
found := false
|
||||||
|
s.msgIDBuffer.Do(func(p interface{}) {
|
||||||
|
if p.(string) == ts {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if found {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
s.msgIDBuffer.Value = ts
|
||||||
|
s.msgIDBuffer = s.msgIDBuffer.Next()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SlackApp) msgReceivd(msg *slackevents.MessageEvent) {
|
||||||
|
if msg.TimeStamp == "" {
|
||||||
|
log.Debug().
|
||||||
|
Str("type", msg.SubType).
|
||||||
|
Msg("ignoring an unhandled event type")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().
|
||||||
|
Interface("event", msg).
|
||||||
|
Str("type", msg.SubType).
|
||||||
|
Msg("accepting a message")
|
||||||
|
|
||||||
|
if s.checkRingOrAdd(msg.TimeStamp) {
|
||||||
|
log.Debug().
|
||||||
|
Str("ts", msg.TimeStamp).
|
||||||
|
Msg("Got a duplicate message from server")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isItMe := msg.BotID != "" && msg.BotID == s.myBotID
|
||||||
|
m := s.buildMessage(msg)
|
||||||
|
if m.Time.Before(s.lastRecieved) {
|
||||||
|
log.Debug().
|
||||||
|
Time("ts", m.Time).
|
||||||
|
Interface("lastRecv", s.lastRecieved).
|
||||||
|
Msg("Ignoring message")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := s.log(m); err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error logging message")
|
||||||
|
}
|
||||||
|
if !isItMe && msg.ThreadTimeStamp == "" {
|
||||||
|
s.lastRecieved = m.Time
|
||||||
|
s.event(s, bot.Message, m)
|
||||||
|
} else if msg.ThreadTimeStamp != "" {
|
||||||
|
//we're throwing away some information here by not parsing the correct reply object type, but that's okay
|
||||||
|
s.event(s, bot.Reply, s.buildMessage(msg), msg.ThreadTimeStamp)
|
||||||
|
} else if isItMe {
|
||||||
|
s.event(s, bot.SelfMessage, m)
|
||||||
|
} else {
|
||||||
|
log.Debug().
|
||||||
|
Str("text", msg.Text).
|
||||||
|
Msg("Unknown message is hidden")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SlackApp) Send(kind bot.Kind, args ...interface{}) (string, error) {
|
||||||
|
switch kind {
|
||||||
|
case bot.Message:
|
||||||
|
return s.sendMessage(args[0].(string), args[1].(string), false, args...)
|
||||||
|
case bot.Action:
|
||||||
|
return s.sendMessage(args[0].(string), args[1].(string), true, args...)
|
||||||
|
case bot.Edit:
|
||||||
|
return s.edit(args[0].(string), args[1].(string), args[2].(string))
|
||||||
|
case bot.Reply:
|
||||||
|
switch args[2].(type) {
|
||||||
|
case msg.Message:
|
||||||
|
return s.replyToMessage(args[0].(string), args[1].(string), args[2].(msg.Message))
|
||||||
|
case string:
|
||||||
|
return s.replyToMessageIdentifier(args[0].(string), args[1].(string), args[2].(string))
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("Invalid types given to Reply")
|
||||||
|
}
|
||||||
|
case bot.Reaction:
|
||||||
|
return s.react(args[0].(string), args[1].(string), args[2].(msg.Message))
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("No handler for message type %d", kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SlackApp) sendMessage(channel, message string, meMessage bool, args ...interface{}) (string, error) {
|
||||||
|
ts, err := "", fmt.Errorf("")
|
||||||
|
nick := s.config.Get("Nick", "bot")
|
||||||
|
|
||||||
|
options := []slack.MsgOption{
|
||||||
|
slack.MsgOptionUsername(nick),
|
||||||
|
slack.MsgOptionText(message, false),
|
||||||
|
}
|
||||||
|
if meMessage {
|
||||||
|
options = append(options, slack.MsgOptionMeMessage())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for message attachments
|
||||||
|
attachments := []slack.Attachment{}
|
||||||
|
if len(args) > 0 {
|
||||||
|
for _, a := range args {
|
||||||
|
switch a := a.(type) {
|
||||||
|
case bot.ImageAttachment:
|
||||||
|
attachments = append(attachments, slack.Attachment{
|
||||||
|
ImageURL: a.URL,
|
||||||
|
Text: a.AltTxt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(attachments) > 0 {
|
||||||
|
options = append(options, slack.MsgOptionAttachments(attachments...))
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().
|
||||||
|
Str("channel", channel).
|
||||||
|
Str("message", message).
|
||||||
|
Int("attachment count", len(attachments)).
|
||||||
|
Int("option count", len(options)).
|
||||||
|
Int("arg count", len(args)).
|
||||||
|
Msg("Sending message")
|
||||||
|
|
||||||
|
_, ts, err = s.api.PostMessage(channel, options...)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Error sending message")
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SlackApp) replyToMessageIdentifier(channel, message, identifier string) (string, error) {
|
||||||
|
nick := s.config.Get("Nick", "bot")
|
||||||
|
icon := s.config.Get("IconURL", "https://placekitten.com/128/128")
|
||||||
|
|
||||||
|
resp, err := http.PostForm("https://slack.com/api/chat.postMessage",
|
||||||
|
url.Values{"token": {s.botToken},
|
||||||
|
"username": {nick},
|
||||||
|
"icon_url": {icon},
|
||||||
|
"channel": {channel},
|
||||||
|
"text": {message},
|
||||||
|
"thread_ts": {identifier},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error sending Slack reply: %s", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error reading Slack API body: %s", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().Bytes("body", body)
|
||||||
|
|
||||||
|
type MessageResponse struct {
|
||||||
|
OK bool `json:"ok"`
|
||||||
|
Timestamp string `json:"ts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var mr MessageResponse
|
||||||
|
err = json.Unmarshal(body, &mr)
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("Error parsing message response: %s", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !mr.OK {
|
||||||
|
return "", fmt.Errorf("Got !OK from slack message response")
|
||||||
|
}
|
||||||
|
|
||||||
|
return mr.Timestamp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SlackApp) replyToMessage(channel, message string, replyTo msg.Message) (string, error) {
|
||||||
|
return s.replyToMessageIdentifier(channel, message, replyTo.AdditionalData["RAW_SLACK_TIMESTAMP"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SlackApp) react(channel, reaction string, message msg.Message) (string, error) {
|
||||||
|
log.Debug().
|
||||||
|
Str("channel", channel).
|
||||||
|
Str("reaction", reaction).
|
||||||
|
Msg("reacting")
|
||||||
|
ref := slack.ItemRef{
|
||||||
|
Channel: channel,
|
||||||
|
Timestamp: message.AdditionalData["RAW_SLACK_TIMESTAMP"],
|
||||||
|
}
|
||||||
|
err := s.api.AddReaction(reaction, ref)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SlackApp) edit(channel, newMessage, identifier string) (string, error) {
|
||||||
|
log.Debug().
|
||||||
|
Str("channel", channel).
|
||||||
|
Str("identifier", identifier).
|
||||||
|
Str("newMessage", newMessage).
|
||||||
|
Msg("editing")
|
||||||
|
nick := s.config.Get("Nick", "bot")
|
||||||
|
_, ts, err := s.api.PostMessage(channel,
|
||||||
|
slack.MsgOptionUsername(nick),
|
||||||
|
slack.MsgOptionText(newMessage, false),
|
||||||
|
slack.MsgOptionMeMessage(),
|
||||||
|
slack.MsgOptionUpdate(identifier))
|
||||||
|
return ts, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SlackApp) GetEmojiList() map[string]string {
|
||||||
|
return s.emoji
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SlackApp) populateEmojiList() {
|
||||||
|
if s.userToken == "NONE" {
|
||||||
|
log.Error().Msg("Cannot get emoji list without slack.usertoken")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
api := slack.New(s.userToken, slack.OptionDebug(false))
|
||||||
|
|
||||||
|
em, err := api.GetEmoji()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Error retrieving emoji list from Slack")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.emoji = em
|
||||||
|
}
|
||||||
|
|
||||||
|
// I think it's horseshit that I have to do this
|
||||||
|
func slackTStoTime(t string) time.Time {
|
||||||
|
ts := strings.Split(t, ".")
|
||||||
|
if len(ts) < 2 {
|
||||||
|
log.Fatal().
|
||||||
|
Str("ts", t).
|
||||||
|
Msg("Could not parse Slack timestamp")
|
||||||
|
}
|
||||||
|
sec, _ := strconv.ParseInt(ts[0], 10, 64)
|
||||||
|
nsec, _ := strconv.ParseInt(ts[1], 10, 64)
|
||||||
|
return time.Unix(sec, nsec)
|
||||||
|
}
|
||||||
|
|
||||||
|
var urlDetector = regexp.MustCompile(`<(.+)://([^|^>]+).*>`)
|
||||||
|
|
||||||
|
// Convert a slackMessage to a msg.Message
|
||||||
|
func (s *SlackApp) buildMessage(m *slackevents.MessageEvent) msg.Message {
|
||||||
|
text := html.UnescapeString(m.Text)
|
||||||
|
|
||||||
|
text = fixText(s.getUser, text)
|
||||||
|
|
||||||
|
isCmd, text := bot.IsCmd(s.config, text)
|
||||||
|
|
||||||
|
isAction := m.SubType == "me_message"
|
||||||
|
|
||||||
|
// We have to try a few layers to get a valid name for the user because Slack
|
||||||
|
name := "UNKNOWN"
|
||||||
|
u, _ := s.getUser(m.User)
|
||||||
|
if u != nil {
|
||||||
|
name = u.Profile.DisplayName
|
||||||
|
}
|
||||||
|
if m.Username != "" && u == nil {
|
||||||
|
name = m.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
chName := m.Channel
|
||||||
|
if ch, _ := s.getChannel(m.Channel); ch != nil {
|
||||||
|
chName = ch.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
tstamp := slackTStoTime(m.TimeStamp)
|
||||||
|
|
||||||
|
return msg.Message{
|
||||||
|
User: &user.User{
|
||||||
|
ID: m.User,
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
Body: text,
|
||||||
|
Raw: m,
|
||||||
|
Channel: m.Channel,
|
||||||
|
ChannelName: chName,
|
||||||
|
IsIM: m.ChannelType == "im",
|
||||||
|
Command: isCmd,
|
||||||
|
Action: isAction,
|
||||||
|
Time: tstamp,
|
||||||
|
AdditionalData: map[string]string{
|
||||||
|
"RAW_SLACK_TIMESTAMP": m.TimeStamp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SlackApp) getChannel(id string) (*slack.Channel, error) {
|
||||||
|
if ch, ok := s.channels[id]; ok {
|
||||||
|
return ch, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().
|
||||||
|
Str("id", id).
|
||||||
|
Msg("Channel not known, requesting info")
|
||||||
|
|
||||||
|
ch, err := s.api.GetChannelInfo(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s.channels[id] = ch
|
||||||
|
return s.channels[id], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get username for Slack user ID
|
||||||
|
func (s *SlackApp) getUser(id string) (*slack.User, error) {
|
||||||
|
if name, ok := s.users[id]; ok {
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().
|
||||||
|
Str("id", id).
|
||||||
|
Msg("User not already found, requesting info")
|
||||||
|
u, err := s.api.GetUserInfo(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s.users[id] = u
|
||||||
|
return s.users[id], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Who gets usernames out of a channel
|
||||||
|
func (s *SlackApp) Who(id string) []string {
|
||||||
|
if s.userToken == "NONE" {
|
||||||
|
log.Error().Msg("Cannot get emoji list without slack.usertoken")
|
||||||
|
return []string{s.config.Get("nick", "bot")}
|
||||||
|
}
|
||||||
|
api := slack.New(s.userToken, slack.OptionDebug(false))
|
||||||
|
|
||||||
|
log.Debug().
|
||||||
|
Str("id", id).
|
||||||
|
Msg("Who is queried")
|
||||||
|
// Not super sure this is the correct call
|
||||||
|
params := &slack.GetUsersInConversationParameters{
|
||||||
|
ChannelID: id,
|
||||||
|
Limit: 50,
|
||||||
|
}
|
||||||
|
members, _, err := api.GetUsersInConversation(params)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err)
|
||||||
|
return []string{s.config.Get("nick", "bot")}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := []string{}
|
||||||
|
for _, m := range members {
|
||||||
|
u, err := s.getUser(m)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Str("user", m).
|
||||||
|
Msg("Couldn't get user")
|
||||||
|
continue
|
||||||
|
/**/
|
||||||
|
}
|
||||||
|
ret = append(ret, u.Name)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// log writes to a <slackapp.log.dir>/<channel>.log
|
||||||
|
// Uses slackapp.log.format to write entries
|
||||||
|
func (s *SlackApp) log(raw msg.Message) error {
|
||||||
|
|
||||||
|
// Do some filtering and fixing up front
|
||||||
|
if raw.Body == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data := struct {
|
||||||
|
msg.Message
|
||||||
|
TopicChange bool
|
||||||
|
}{
|
||||||
|
Message: raw,
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(raw.Body, "set the channel topic: ") {
|
||||||
|
topic := strings.SplitN(raw.Body, "set the channel topic: ", 2)
|
||||||
|
data.Body = "changed topic to " + topic[1]
|
||||||
|
data.TopicChange = true
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := path.Join(s.config.Get("slackapp.log.dir", "logs"), raw.ChannelName)
|
||||||
|
now := time.Now()
|
||||||
|
fname := now.Format("20060102") + ".log"
|
||||||
|
path := path.Join(dir, fname)
|
||||||
|
|
||||||
|
log.Debug().
|
||||||
|
Interface("raw", raw).
|
||||||
|
Str("dir", dir).
|
||||||
|
Msg("Slack event")
|
||||||
|
|
||||||
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Msg("Could not create log directory")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().
|
||||||
|
Err(err).
|
||||||
|
Msg("Error opening log file")
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if err := s.logFormat.Execute(f, data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.Sync()
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package slackapp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/ring"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDedupeNoDupes(t *testing.T) {
|
||||||
|
buf := ring.New(3)
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
buf.Value = ""
|
||||||
|
buf = buf.Next()
|
||||||
|
}
|
||||||
|
s := SlackApp{msgIDBuffer: buf}
|
||||||
|
expected := []bool{
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
}
|
||||||
|
|
||||||
|
actuals := []bool{}
|
||||||
|
actuals = append(actuals, s.checkRingOrAdd("a"))
|
||||||
|
actuals = append(actuals, s.checkRingOrAdd("b"))
|
||||||
|
actuals = append(actuals, s.checkRingOrAdd("c"))
|
||||||
|
actuals = append(actuals, s.checkRingOrAdd("d"))
|
||||||
|
actuals = append(actuals, s.checkRingOrAdd("e"))
|
||||||
|
|
||||||
|
assert.ElementsMatch(t, expected, actuals)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDedupeWithDupes(t *testing.T) {
|
||||||
|
buf := ring.New(3)
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
buf.Value = ""
|
||||||
|
buf = buf.Next()
|
||||||
|
}
|
||||||
|
s := SlackApp{msgIDBuffer: buf}
|
||||||
|
expected := []bool{
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
}
|
||||||
|
|
||||||
|
actuals := []bool{}
|
||||||
|
actuals = append(actuals, s.checkRingOrAdd("a"))
|
||||||
|
actuals = append(actuals, s.checkRingOrAdd("b"))
|
||||||
|
actuals = append(actuals, s.checkRingOrAdd("a"))
|
||||||
|
actuals = append(actuals, s.checkRingOrAdd("d"))
|
||||||
|
actuals = append(actuals, s.checkRingOrAdd("d"))
|
||||||
|
|
||||||
|
assert.ElementsMatch(t, expected, actuals)
|
||||||
|
}
|
|
@ -1,123 +0,0 @@
|
||||||
config = {
|
|
||||||
Channels = {
|
|
||||||
"#CatBaseTest"
|
|
||||||
},
|
|
||||||
TwitterConsumerSecret = "<Consumer Secret>",
|
|
||||||
Reminder = {
|
|
||||||
MaxBatchAdd = 10
|
|
||||||
},
|
|
||||||
Nick = "CatBaseTest",
|
|
||||||
IconURL = "http://placekitten.com/g/200/300",
|
|
||||||
LeftPad = {
|
|
||||||
Who = "person",
|
|
||||||
MaxLen = 50
|
|
||||||
},
|
|
||||||
Factoid = {
|
|
||||||
StartupFact = "speed test",
|
|
||||||
QuoteTime = 1,
|
|
||||||
QuoteChance = 0.99,
|
|
||||||
MinLen = 5
|
|
||||||
},
|
|
||||||
CommandChar = {
|
|
||||||
"!",
|
|
||||||
"¡"
|
|
||||||
},
|
|
||||||
FullName = "CatBase",
|
|
||||||
Your = {
|
|
||||||
MaxLength = 140,
|
|
||||||
DuckingChance = 0.5,
|
|
||||||
FuckingChance = 0.15,
|
|
||||||
YourChance = 0.4
|
|
||||||
},
|
|
||||||
Emojify = {
|
|
||||||
Chance = 0.02,
|
|
||||||
Scoreless = {
|
|
||||||
"a",
|
|
||||||
"it"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
DB = {
|
|
||||||
File = "catbase.db",
|
|
||||||
Server = "127.0.0.1"
|
|
||||||
},
|
|
||||||
Plugins = {
|
|
||||||
},
|
|
||||||
Untappd = {
|
|
||||||
Freq = 3600,
|
|
||||||
Channels = {
|
|
||||||
},
|
|
||||||
Token = "<Your Token>"
|
|
||||||
},
|
|
||||||
LogLength = 50,
|
|
||||||
RatePerSec = 10,
|
|
||||||
Reaction = {
|
|
||||||
HarrassChance = 0.05,
|
|
||||||
GeneralChance = 0.01,
|
|
||||||
NegativeHarrassmentMultiplier = 2,
|
|
||||||
HarrassList = {
|
|
||||||
"msherms"
|
|
||||||
},
|
|
||||||
NegativeReactions = {
|
|
||||||
"bullshit",
|
|
||||||
"fake",
|
|
||||||
"tableflip",
|
|
||||||
"vomit"
|
|
||||||
},
|
|
||||||
PositiveReactions = {
|
|
||||||
"+1",
|
|
||||||
"authorized",
|
|
||||||
"aw_yea",
|
|
||||||
"joy"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
TwitterUserKey = "<User Key>",
|
|
||||||
MainChannel = "#CatBaseTest",
|
|
||||||
TwitterUserSecret = "<User Secret>",
|
|
||||||
WelcomeMsgs = {
|
|
||||||
"Real men use screen, %s.",
|
|
||||||
"Joins upset the hivemind's OCD, %s.",
|
|
||||||
"Joins upset the hivemind's CDO, %s.",
|
|
||||||
"%s, I WILL CUT YOU!"
|
|
||||||
},
|
|
||||||
Bad = {
|
|
||||||
Msgs = {
|
|
||||||
},
|
|
||||||
Hosts = {
|
|
||||||
},
|
|
||||||
Nicks = {
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Irc = {
|
|
||||||
Server = "ircserver:6697",
|
|
||||||
Pass = "CatBaseTest:test"
|
|
||||||
},
|
|
||||||
Slack = {
|
|
||||||
Token = "<your slack token>"
|
|
||||||
},
|
|
||||||
TwitterConsumerKey = "<Consumer Key>",
|
|
||||||
Babbler = {
|
|
||||||
DefaultUsers = {
|
|
||||||
"seabass"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Type = "slack",
|
|
||||||
Admins = {
|
|
||||||
"<Admin Nick>"
|
|
||||||
},
|
|
||||||
Stats = {
|
|
||||||
Sightings = {
|
|
||||||
"user"
|
|
||||||
},
|
|
||||||
DBPath = "stats.db"
|
|
||||||
},
|
|
||||||
HttpAddr = "127.0.0.1:1337",
|
|
||||||
Inventory = {
|
|
||||||
Max = 5
|
|
||||||
},
|
|
||||||
Sisyphus = {
|
|
||||||
MinDecrement = 10,
|
|
||||||
MinPush = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
45
go.mod
45
go.mod
|
@ -1,25 +1,46 @@
|
||||||
module github.com/velour/catbase
|
module github.com/velour/catbase
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/PuerkitoBio/goquery v1.5.0 // indirect
|
github.com/PaulRosset/go-hacknews v0.0.0-20170815075127-4aad99273a3c
|
||||||
github.com/boltdb/bolt v1.3.1
|
github.com/PuerkitoBio/goquery v1.5.0
|
||||||
|
github.com/ajstarks/svgo v0.0.0-20181006003313-6ce6a3bcf6cd // indirect
|
||||||
|
github.com/armon/go-radix v1.0.0 // indirect
|
||||||
github.com/chrissexton/leftpad v0.0.0-20181207133115-1e93189d2fff
|
github.com/chrissexton/leftpad v0.0.0-20181207133115-1e93189d2fff
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect
|
||||||
|
github.com/fogleman/gg v1.3.0 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.4.1 // indirect
|
||||||
|
github.com/golang/protobuf v1.3.2 // indirect
|
||||||
|
github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82 // indirect
|
||||||
|
github.com/gonum/internal v0.0.0-20181124074243-f884aa714029 // indirect
|
||||||
github.com/gorilla/websocket v1.4.0 // indirect
|
github.com/gorilla/websocket v1.4.0 // indirect
|
||||||
|
github.com/james-bowman/nlp v0.0.0-20190408090549-143ee6f41889
|
||||||
|
github.com/james-bowman/sparse v0.0.0-20190423065201-80c6877364c7 // indirect
|
||||||
github.com/jmoiron/sqlx v1.2.0
|
github.com/jmoiron/sqlx v1.2.0
|
||||||
github.com/mattn/go-sqlite3 v1.10.0
|
github.com/jung-kurt/gofpdf v1.7.0 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.1.2 // indirect
|
github.com/lib/pq v1.2.0 // indirect
|
||||||
|
github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 // indirect
|
||||||
|
github.com/lusis/slack-test v0.0.0-20190426140909-c40012f20018 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v1.11.0
|
||||||
github.com/mmcdole/gofeed v1.0.0-beta2
|
github.com/mmcdole/gofeed v1.0.0-beta2
|
||||||
github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf // indirect
|
github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/nlopes/slack v0.5.0
|
||||||
|
github.com/olebedev/when v0.0.0-20190311101825-c3b538a97254
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237 // indirect
|
||||||
github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d // indirect
|
github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d // indirect
|
||||||
github.com/stretchr/objx v0.1.1 // indirect
|
github.com/rs/zerolog v1.15.0
|
||||||
github.com/stretchr/testify v1.2.2
|
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||||
|
github.com/stretchr/objx v0.2.0 // indirect
|
||||||
|
github.com/stretchr/testify v1.3.0
|
||||||
github.com/velour/chat v0.0.0-20180713122344-fd1d1606cb89
|
github.com/velour/chat v0.0.0-20180713122344-fd1d1606cb89
|
||||||
github.com/velour/velour v0.0.0-20160303155839-8e090e68d158
|
github.com/velour/velour v0.0.0-20160303155839-8e090e68d158
|
||||||
github.com/yuin/gluamapper v0.0.0-20150323120927-d836955830e7
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 // indirect
|
||||||
github.com/yuin/gopher-lua v0.0.0-20181214045814-db9ae37725ec
|
golang.org/x/mobile v0.0.0-20190806162312-597adff16ade // indirect
|
||||||
golang.org/x/net v0.0.0-20181217023233-e147a9138326 // indirect
|
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 // indirect
|
||||||
golang.org/x/text v0.3.0 // indirect
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a // indirect
|
||||||
|
golang.org/x/tools v0.0.0-20190813142322-97f12d73768f // indirect
|
||||||
|
gonum.org/v1/gonum v0.0.0-20190808205415-ced62fe5104b // indirect
|
||||||
|
gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e // indirect
|
||||||
|
gonum.org/v1/plot v0.0.0-20190615073203-9aa86143727f // indirect
|
||||||
|
google.golang.org/appengine v1.6.1 // indirect
|
||||||
gopkg.in/sourcemap.v1 v1.0.5 // indirect
|
gopkg.in/sourcemap.v1 v1.0.5 // indirect
|
||||||
)
|
)
|
||||||
|
|
146
go.sum
146
go.sum
|
@ -1,50 +1,172 @@
|
||||||
|
github.com/AlekSi/pointer v1.0.0 h1:KWCWzsvFxNLcmM5XmiqHsGTTsuwZMsLFwWF9Y+//bNE=
|
||||||
|
github.com/AlekSi/pointer v1.0.0/go.mod h1:1kjywbfcPFCmncIxtk6fIEub6LKrfMz3gc5QKVOSOA8=
|
||||||
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
|
github.com/PaulRosset/go-hacknews v0.0.0-20170815075127-4aad99273a3c h1:bJ0HbTMaInVjakxM76G+2gsmbKTdHzpTUGyLGYxdMO0=
|
||||||
|
github.com/PaulRosset/go-hacknews v0.0.0-20170815075127-4aad99273a3c/go.mod h1:8+24kIp7vJsYy0GmQDDNnPwAYEWkl3OcaPxJSDAfe1U=
|
||||||
github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk=
|
github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk=
|
||||||
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
|
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
|
||||||
|
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||||
|
github.com/ajstarks/svgo v0.0.0-20181006003313-6ce6a3bcf6cd/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||||
github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o=
|
github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o=
|
||||||
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||||
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
|
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
|
||||||
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
|
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
github.com/chrissexton/leftpad v0.0.0-20181207133115-1e93189d2fff h1:+TEqaP0eO1unI7XHHFeMDhsxhLDIb0x8KYuZbqbAmxA=
|
github.com/chrissexton/leftpad v0.0.0-20181207133115-1e93189d2fff h1:+TEqaP0eO1unI7XHHFeMDhsxhLDIb0x8KYuZbqbAmxA=
|
||||||
github.com/chrissexton/leftpad v0.0.0-20181207133115-1e93189d2fff/go.mod h1:QCRjR0b4qiJiNjuP7RFM89bh4UExGJalcWmYeSvlnRc=
|
github.com/chrissexton/leftpad v0.0.0-20181207133115-1e93189d2fff/go.mod h1:QCRjR0b4qiJiNjuP7RFM89bh4UExGJalcWmYeSvlnRc=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||||
|
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82 h1:EvokxLQsaaQjcWVWSV38221VAK7qc2zhaO17bKys/18=
|
||||||
|
github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82/go.mod h1:PxC8OnwL11+aosOB5+iEPoV3picfs8tUpkVd0pDo+Kg=
|
||||||
|
github.com/gonum/internal v0.0.0-20181124074243-f884aa714029 h1:8jtTdc+Nfj9AR+0soOeia9UZSvYBvETVHZrugUowJ7M=
|
||||||
|
github.com/gonum/internal v0.0.0-20181124074243-f884aa714029/go.mod h1:Pu4dmpkhSyOzRwuXkOgAvijx4o+4YMUJJo9OvPYMkks=
|
||||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
|
github.com/james-bowman/nlp v0.0.0-20190301165020-c5645f996605 h1:MjLvsJmW4uoTjleqqiL0wKRTjxUakKUhDNoSsSlS2hk=
|
||||||
|
github.com/james-bowman/nlp v0.0.0-20190301165020-c5645f996605/go.mod h1:kixuaexEqWB+mHZNysgnb6mqgGIT25WvD1/tFRRt0J0=
|
||||||
|
github.com/james-bowman/nlp v0.0.0-20190408090549-143ee6f41889 h1:VYwE/yKDYXpd5hno5fCCWv2wPcM37DYAX4r3Re1pLz8=
|
||||||
|
github.com/james-bowman/nlp v0.0.0-20190408090549-143ee6f41889/go.mod h1:kixuaexEqWB+mHZNysgnb6mqgGIT25WvD1/tFRRt0J0=
|
||||||
|
github.com/james-bowman/sparse v0.0.0-20190309194602-7d83420cfcbe h1:UFAsFuH6cu/0Lx+qBWfxiO69jrPkvdbG3qwSWI/7yF0=
|
||||||
|
github.com/james-bowman/sparse v0.0.0-20190309194602-7d83420cfcbe/go.mod h1:G6EcQnwZKsWtItoaQHd+FHPPk6bDeYVJSeeSP9Sge+I=
|
||||||
|
github.com/james-bowman/sparse v0.0.0-20190423065201-80c6877364c7 h1:ph/BDQQDL41apnHSN48I5GyNOQXXAlc79HwGqDSXCss=
|
||||||
|
github.com/james-bowman/sparse v0.0.0-20190423065201-80c6877364c7/go.mod h1:G6EcQnwZKsWtItoaQHd+FHPPk6bDeYVJSeeSP9Sge+I=
|
||||||
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
|
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
|
||||||
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||||
|
github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||||
|
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||||
|
github.com/jung-kurt/gofpdf v1.7.0/go.mod h1:s/VXv+TdctEOx2wCEguezYaR7f0OwUAd6H9VGfRkcSs=
|
||||||
|
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 h1:AsEBgzv3DhuYHI/GiQh2HxvTP71HCCE9E/tzGUzGdtU=
|
||||||
|
github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5/go.mod h1:c2mYKRyMb1BPkO5St0c/ps62L4S0W2NAkaTXj9qEI+0=
|
||||||
|
github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6 h1:iOAVXzZyXtW408TMYejlUPo6BIn92HmOacWtIfNyYns=
|
||||||
|
github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6/go.mod h1:sFlOUpQL1YcjhFVXhg1CG8ZASEs/Mf1oVb6H75JL/zg=
|
||||||
|
github.com/lusis/slack-test v0.0.0-20190426140909-c40012f20018/go.mod h1:sFlOUpQL1YcjhFVXhg1CG8ZASEs/Mf1oVb6H75JL/zg=
|
||||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
|
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
|
||||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/mmcdole/gofeed v1.0.0-beta2 h1:CjQ0ADhAwNSb08zknAkGOEYqr8zfZKfrzgk9BxpWP2E=
|
github.com/mmcdole/gofeed v1.0.0-beta2 h1:CjQ0ADhAwNSb08zknAkGOEYqr8zfZKfrzgk9BxpWP2E=
|
||||||
github.com/mmcdole/gofeed v1.0.0-beta2/go.mod h1:/BF9JneEL2/flujm8XHoxUcghdTV6vvb3xx/vKyChFU=
|
github.com/mmcdole/gofeed v1.0.0-beta2/go.mod h1:/BF9JneEL2/flujm8XHoxUcghdTV6vvb3xx/vKyChFU=
|
||||||
github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf h1:sWGE2v+hO0Nd4yFU/S/mDBM5plIU8v/Qhfz41hkDIAI=
|
github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf h1:sWGE2v+hO0Nd4yFU/S/mDBM5plIU8v/Qhfz41hkDIAI=
|
||||||
github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf/go.mod h1:pasqhqstspkosTneA62Nc+2p9SOBBYAPbnmRRWPQ0V8=
|
github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf/go.mod h1:pasqhqstspkosTneA62Nc+2p9SOBBYAPbnmRRWPQ0V8=
|
||||||
|
github.com/nlopes/slack v0.5.0 h1:NbIae8Kd0NpqaEI3iUrsuS0KbcEDhzhc939jLW5fNm0=
|
||||||
|
github.com/nlopes/slack v0.5.0/go.mod h1:jVI4BBK3lSktibKahxBF74txcK2vyvkza1z/+rRnVAM=
|
||||||
|
github.com/olebedev/when v0.0.0-20190131080308-164b69386514 h1:xpZutaUgtGPKT2JFaH72/yby908QS9ORlnrAkkdJ4m0=
|
||||||
|
github.com/olebedev/when v0.0.0-20190131080308-164b69386514/go.mod h1:DPucAeQGDPUzYUt+NaWw6qsF5SFapWWToxEiVDh2aV0=
|
||||||
|
github.com/olebedev/when v0.0.0-20190311101825-c3b538a97254 h1:JYoQR67E1vv1WGoeW8DkdFs7vrIEe/5wP+qJItd5tUE=
|
||||||
|
github.com/olebedev/when v0.0.0-20190311101825-c3b538a97254/go.mod h1:DPucAeQGDPUzYUt+NaWw6qsF5SFapWWToxEiVDh2aV0=
|
||||||
|
github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
|
||||||
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d h1:1VUlQbCfkoSGv7qP7Y+ro3ap1P1pPZxgdGVqiTVy5C4=
|
github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d h1:1VUlQbCfkoSGv7qP7Y+ro3ap1P1pPZxgdGVqiTVy5C4=
|
||||||
github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY=
|
github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY=
|
||||||
|
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||||
|
github.com/rs/zerolog v1.12.0 h1:aqZ1XRadoS8IBknR5IDFvGzbHly1X9ApIqOroooQF/c=
|
||||||
|
github.com/rs/zerolog v1.12.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||||
|
github.com/rs/zerolog v1.15.0 h1:uPRuwkWF4J6fGsJ2R0Gn2jB1EQiav9k3S6CSdygQJXY=
|
||||||
|
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||||
|
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
|
||||||
|
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||||
|
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
|
||||||
|
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/velour/chat v0.0.0-20180713122344-fd1d1606cb89 h1:3D3M900hEBJJAqyKl70QuRHi5weX9+ptlQI1v+FNcQ8=
|
github.com/velour/chat v0.0.0-20180713122344-fd1d1606cb89 h1:3D3M900hEBJJAqyKl70QuRHi5weX9+ptlQI1v+FNcQ8=
|
||||||
github.com/velour/chat v0.0.0-20180713122344-fd1d1606cb89/go.mod h1:ejwOYCjnDMyO5LXFXRARQJGBZ6xQJZ3rgAHE5drSuMM=
|
github.com/velour/chat v0.0.0-20180713122344-fd1d1606cb89/go.mod h1:ejwOYCjnDMyO5LXFXRARQJGBZ6xQJZ3rgAHE5drSuMM=
|
||||||
github.com/velour/velour v0.0.0-20160303155839-8e090e68d158 h1:p3rTUXxzuKsBOsHlkly7+rj9wagFBKeIsCDKkDII9sw=
|
github.com/velour/velour v0.0.0-20160303155839-8e090e68d158 h1:p3rTUXxzuKsBOsHlkly7+rj9wagFBKeIsCDKkDII9sw=
|
||||||
github.com/velour/velour v0.0.0-20160303155839-8e090e68d158/go.mod h1:Ojy3BTOiOTwpHpw7/HNi+TVTFppao191PQs+Qc3sqpE=
|
github.com/velour/velour v0.0.0-20160303155839-8e090e68d158/go.mod h1:Ojy3BTOiOTwpHpw7/HNi+TVTFppao191PQs+Qc3sqpE=
|
||||||
github.com/yuin/gluamapper v0.0.0-20150323120927-d836955830e7 h1:noHsffKZsNfU38DwcXWEPldrTjIZ8FPNKx8mYMGnqjs=
|
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||||
github.com/yuin/gluamapper v0.0.0-20150323120927-d836955830e7/go.mod h1:bbMEM6aU1WDF1ErA5YJ0p91652pGv140gGw4Ww3RGp8=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
github.com/yuin/gopher-lua v0.0.0-20181214045814-db9ae37725ec h1:vpF8Kxql6/3OvGH4y2SKtpN3WsB17mvJ8f8H1o2vucQ=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
github.com/yuin/gopher-lua v0.0.0-20181214045814-db9ae37725ec/go.mod h1:fFiAh+CowNFr0NK5VASokuwKwkbacRmHsVA7Yb1Tqac=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
golang.org/x/exp v0.0.0-20190321205749-f0864edee7f3 h1:Ep4L2ibjtJcW6IP73KbcJAU0cpNKsLNSSP2jE1xlCys=
|
||||||
|
golang.org/x/exp v0.0.0-20190321205749-f0864edee7f3/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU=
|
||||||
|
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
||||||
|
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||||
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
|
golang.org/x/image v0.0.0-20190507092727-e4e5bf290fec/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||||
|
golang.org/x/mobile v0.0.0-20190806162312-597adff16ade/go.mod h1:AlhUtkH4DA4asiFC5RgK7ZKmauvtkAVcy9L0epCzlWo=
|
||||||
|
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181217023233-e147a9138326 h1:iCzOf0xz39Tstp+Tu/WwyGjUXCk34QhQORRxBeXXTA4=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20181217023233-e147a9138326/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
|
golang.org/x/net v0.0.0-20190606173856-1492cefac77f h1:IWHgpgFqnL5AhBUBZSgBdjl2vkQUEzcY+JNKWfcgAU0=
|
||||||
|
golang.org/x/net v0.0.0-20190606173856-1492cefac77f/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA=
|
||||||
|
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190813142322-97f12d73768f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
|
||||||
|
gonum.org/v1/gonum v0.0.0-20190325211145-e42c1265cdd5 h1:tyvqqvbB9Sn6UPjokEzsK6cCE9k4Tx/AHGGaJiLIk7g=
|
||||||
|
gonum.org/v1/gonum v0.0.0-20190325211145-e42c1265cdd5/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
|
||||||
|
gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
|
||||||
|
gonum.org/v1/gonum v0.0.0-20190808205415-ced62fe5104b h1:wlZ2AJblZitrh7dfm5OX2WenXLBZCuWqUeNczop2lPA=
|
||||||
|
gonum.org/v1/gonum v0.0.0-20190808205415-ced62fe5104b/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU=
|
||||||
|
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc=
|
||||||
|
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
|
||||||
|
gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ=
|
||||||
|
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
|
||||||
|
gonum.org/v1/plot v0.0.0-20190615073203-9aa86143727f/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
|
||||||
|
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||||
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
|
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
|
||||||
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
|
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
|
||||||
|
modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
|
||||||
|
modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
|
||||||
|
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
|
||||||
|
modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
|
||||||
|
modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
|
||||||
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
|
|
120
main.go
120
main.go
|
@ -4,19 +4,26 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"log"
|
"github.com/velour/catbase/plugins/cli"
|
||||||
|
"github.com/velour/catbase/plugins/newsbid"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/config"
|
"github.com/velour/catbase/config"
|
||||||
"github.com/velour/catbase/irc"
|
"github.com/velour/catbase/connectors/irc"
|
||||||
|
"github.com/velour/catbase/connectors/slack"
|
||||||
|
"github.com/velour/catbase/connectors/slackapp"
|
||||||
"github.com/velour/catbase/plugins/admin"
|
"github.com/velour/catbase/plugins/admin"
|
||||||
"github.com/velour/catbase/plugins/babbler"
|
"github.com/velour/catbase/plugins/babbler"
|
||||||
"github.com/velour/catbase/plugins/beers"
|
"github.com/velour/catbase/plugins/beers"
|
||||||
"github.com/velour/catbase/plugins/couldashouldawoulda"
|
"github.com/velour/catbase/plugins/couldashouldawoulda"
|
||||||
"github.com/velour/catbase/plugins/counter"
|
"github.com/velour/catbase/plugins/counter"
|
||||||
"github.com/velour/catbase/plugins/db"
|
|
||||||
"github.com/velour/catbase/plugins/dice"
|
"github.com/velour/catbase/plugins/dice"
|
||||||
"github.com/velour/catbase/plugins/emojifyme"
|
"github.com/velour/catbase/plugins/emojifyme"
|
||||||
"github.com/velour/catbase/plugins/fact"
|
"github.com/velour/catbase/plugins/fact"
|
||||||
|
@ -26,68 +33,107 @@ import (
|
||||||
"github.com/velour/catbase/plugins/nerdepedia"
|
"github.com/velour/catbase/plugins/nerdepedia"
|
||||||
"github.com/velour/catbase/plugins/picker"
|
"github.com/velour/catbase/plugins/picker"
|
||||||
"github.com/velour/catbase/plugins/reaction"
|
"github.com/velour/catbase/plugins/reaction"
|
||||||
|
"github.com/velour/catbase/plugins/remember"
|
||||||
"github.com/velour/catbase/plugins/reminder"
|
"github.com/velour/catbase/plugins/reminder"
|
||||||
"github.com/velour/catbase/plugins/rpgORdie"
|
"github.com/velour/catbase/plugins/rpgORdie"
|
||||||
"github.com/velour/catbase/plugins/rss"
|
"github.com/velour/catbase/plugins/rss"
|
||||||
"github.com/velour/catbase/plugins/sisyphus"
|
"github.com/velour/catbase/plugins/sisyphus"
|
||||||
|
"github.com/velour/catbase/plugins/stock"
|
||||||
"github.com/velour/catbase/plugins/talker"
|
"github.com/velour/catbase/plugins/talker"
|
||||||
"github.com/velour/catbase/plugins/tell"
|
"github.com/velour/catbase/plugins/tell"
|
||||||
|
"github.com/velour/catbase/plugins/tldr"
|
||||||
"github.com/velour/catbase/plugins/twitch"
|
"github.com/velour/catbase/plugins/twitch"
|
||||||
"github.com/velour/catbase/plugins/your"
|
"github.com/velour/catbase/plugins/your"
|
||||||
"github.com/velour/catbase/plugins/zork"
|
"github.com/velour/catbase/plugins/zork"
|
||||||
"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")
|
||||||
|
prettyLog = flag.Bool("pretty", false, "Use pretty console logger")
|
||||||
|
debug = flag.Bool("debug", false, "Turn on debug logging")
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
rand.Seed(time.Now().Unix())
|
rand.Seed(time.Now().Unix())
|
||||||
|
|
||||||
var cfile = flag.String("config", "config.lua",
|
var dbpath = flag.String("db", "catbase.db",
|
||||||
"Config file to load. (Defaults to config.lua)")
|
"Database file to load. (Defaults to catbase.db)")
|
||||||
flag.Parse() // parses the logging flags.
|
flag.Parse() // parses the logging flags.
|
||||||
|
|
||||||
c := config.Readconfig(Version, *cfile)
|
log.Logger = log.With().Caller().Stack().Logger()
|
||||||
|
if *prettyLog {
|
||||||
|
log.Logger = log.Logger.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||||
|
}
|
||||||
|
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
||||||
|
if *debug {
|
||||||
|
zerolog.SetGlobalLevel(zerolog.DebugLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := config.ReadConfig(*dbpath)
|
||||||
|
|
||||||
|
if *key != "" && *val != "" {
|
||||||
|
c.Set(*key, *val)
|
||||||
|
log.Info().Msgf("Set config %s: %s", *key, *val)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (*initDB && len(flag.Args()) != 2) || (!*initDB && c.GetInt("init", 0) != 1) {
|
||||||
|
log.Fatal().Msgf(`You must run "catbase -init <channel> <nick>"`)
|
||||||
|
} else if *initDB {
|
||||||
|
c.SetDefaults(flag.Arg(0), flag.Arg(1))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var client bot.Connector
|
var client bot.Connector
|
||||||
|
|
||||||
switch c.Type {
|
switch c.Get("type", "slackapp") {
|
||||||
case "irc":
|
case "irc":
|
||||||
client = irc.New(c)
|
client = irc.New(c)
|
||||||
case "slack":
|
case "slack":
|
||||||
client = slack.New(c)
|
client = slack.New(c)
|
||||||
|
case "slackapp":
|
||||||
|
client = slackapp.New(c)
|
||||||
default:
|
default:
|
||||||
log.Fatalf("Unknown connection type: %s", c.Type)
|
log.Fatal().Msgf("Unknown connection type: %s", c.Get("type", "UNSET"))
|
||||||
}
|
}
|
||||||
|
|
||||||
b := bot.New(c, client)
|
b := bot.New(c, client)
|
||||||
|
|
||||||
b.AddHandler("admin", admin.New(b))
|
b.AddPlugin(admin.New(b))
|
||||||
b.AddHandler("first", first.New(b))
|
b.AddPlugin(emojifyme.New(b))
|
||||||
b.AddHandler("leftpad", leftpad.New(b))
|
b.AddPlugin(first.New(b))
|
||||||
b.AddHandler("talker", talker.New(b))
|
b.AddPlugin(leftpad.New(b))
|
||||||
b.AddHandler("dice", dice.New(b))
|
b.AddPlugin(talker.New(b))
|
||||||
b.AddHandler("picker", picker.New(b))
|
b.AddPlugin(dice.New(b))
|
||||||
b.AddHandler("beers", beers.New(b))
|
b.AddPlugin(picker.New(b))
|
||||||
b.AddHandler("remember", fact.NewRemember(b))
|
b.AddPlugin(beers.New(b))
|
||||||
b.AddHandler("your", your.New(b))
|
b.AddPlugin(remember.New(b))
|
||||||
b.AddHandler("counter", counter.New(b))
|
b.AddPlugin(your.New(b))
|
||||||
b.AddHandler("reminder", reminder.New(b))
|
b.AddPlugin(counter.New(b))
|
||||||
b.AddHandler("babbler", babbler.New(b))
|
b.AddPlugin(reminder.New(b))
|
||||||
b.AddHandler("zork", zork.New(b))
|
b.AddPlugin(babbler.New(b))
|
||||||
b.AddHandler("rss", rss.New(b))
|
b.AddPlugin(zork.New(b))
|
||||||
b.AddHandler("reaction", reaction.New(b))
|
b.AddPlugin(rss.New(b))
|
||||||
b.AddHandler("emojifyme", emojifyme.New(b))
|
b.AddPlugin(reaction.New(b))
|
||||||
b.AddHandler("twitch", twitch.New(b))
|
b.AddPlugin(twitch.New(b))
|
||||||
b.AddHandler("inventory", inventory.New(b))
|
b.AddPlugin(inventory.New(b))
|
||||||
b.AddHandler("rpgORdie", rpgORdie.New(b))
|
b.AddPlugin(rpgORdie.New(b))
|
||||||
b.AddHandler("sisyphus", sisyphus.New(b))
|
b.AddPlugin(sisyphus.New(b))
|
||||||
b.AddHandler("tell", tell.New(b))
|
b.AddPlugin(tell.New(b))
|
||||||
b.AddHandler("couldashouldawoulda", couldashouldawoulda.New(b))
|
b.AddPlugin(couldashouldawoulda.New(b))
|
||||||
b.AddHandler("nedepedia", nerdepedia.New(b))
|
b.AddPlugin(nerdepedia.New(b))
|
||||||
|
b.AddPlugin(tldr.New(b))
|
||||||
|
b.AddPlugin(stock.New(b))
|
||||||
|
b.AddPlugin(newsbid.New(b))
|
||||||
|
b.AddPlugin(cli.New(b))
|
||||||
// catches anything left, will always return true
|
// catches anything left, will always return true
|
||||||
b.AddHandler("factoid", fact.New(b))
|
b.AddPlugin(fact.New(b))
|
||||||
b.AddHandler("db", db.New(b))
|
|
||||||
|
|
||||||
for {
|
if err := client.Serve(); err != nil {
|
||||||
err := client.Serve()
|
log.Fatal().Err(err)
|
||||||
log.Println(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addr := c.Get("HttpAddr", "127.0.0.1:1337")
|
||||||
|
log.Fatal().Err(http.ListenAndServe(addr, nil))
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,55 +3,121 @@
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
|
"github.com/velour/catbase/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This is a admin plugin to serve as an example and quick copy/paste for new plugins.
|
// This is a admin plugin to serve as an example and quick copy/paste for new plugins.
|
||||||
|
|
||||||
type AdminPlugin struct {
|
type AdminPlugin struct {
|
||||||
Bot bot.Bot
|
bot bot.Bot
|
||||||
db *sqlx.DB
|
db *sqlx.DB
|
||||||
|
cfg *config.Config
|
||||||
|
|
||||||
|
quiet bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAdminPlugin creates a new AdminPlugin with the Plugin interface
|
// NewAdminPlugin creates a new AdminPlugin with the Plugin interface
|
||||||
func New(bot bot.Bot) *AdminPlugin {
|
func New(b bot.Bot) *AdminPlugin {
|
||||||
p := &AdminPlugin{
|
p := &AdminPlugin{
|
||||||
Bot: bot,
|
bot: b,
|
||||||
db: bot.DB(),
|
db: b.DB(),
|
||||||
|
cfg: b.Config(),
|
||||||
}
|
}
|
||||||
p.LoadData()
|
b.Register(p, bot.Message, p.message)
|
||||||
|
b.Register(p, bot.Help, p.help)
|
||||||
|
p.registerWeb()
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var forbiddenKeys = map[string]bool{
|
||||||
|
"twitch.authorization": true,
|
||||||
|
"twitch.clientid": true,
|
||||||
|
"untappd.token": true,
|
||||||
|
"slack.token": true,
|
||||||
|
}
|
||||||
|
|
||||||
// Message responds to the bot hook on recieving messages.
|
// Message responds to the bot hook on recieving messages.
|
||||||
// This function returns true if the plugin responds in a meaningful way to the users message.
|
// 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.
|
// Otherwise, the function returns false and the bot continues execution of other plugins.
|
||||||
func (p *AdminPlugin) Message(message msg.Message) bool {
|
func (p *AdminPlugin) message(conn bot.Connector, k bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
body := message.Body
|
body := message.Body
|
||||||
|
|
||||||
|
if p.quiet {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
if len(body) > 0 && body[0] == '$' {
|
if len(body) > 0 && body[0] == '$' {
|
||||||
return p.handleVariables(message)
|
return p.handleVariables(conn, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !message.Command {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.ToLower(body) == "shut up" {
|
||||||
|
dur := time.Duration(p.cfg.GetInt("quietDuration", 5)) * time.Minute
|
||||||
|
log.Info().Msgf("Going to sleep for %v, %v", dur, time.Now().Add(dur))
|
||||||
|
p.bot.Send(conn, bot.Message, message.Channel, "Okay. I'll be back later.")
|
||||||
|
p.quiet = true
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-time.After(dur):
|
||||||
|
p.quiet = false
|
||||||
|
log.Info().Msg("Waking up from nap.")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.ToLower(body) == "password" {
|
||||||
|
p.bot.Send(conn, bot.Message, message.Channel, p.bot.GetPassword())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(body, " ")
|
||||||
|
if parts[0] == "set" && len(parts) > 2 && forbiddenKeys[parts[1]] {
|
||||||
|
p.bot.Send(conn, bot.Message, message.Channel, "You cannot access that key")
|
||||||
|
return true
|
||||||
|
} else if parts[0] == "set" && len(parts) > 2 {
|
||||||
|
p.cfg.Set(parts[1], strings.Join(parts[2:], " "))
|
||||||
|
p.bot.Send(conn, bot.Message, message.Channel, fmt.Sprintf("Set %s", parts[1]))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if parts[0] == "get" && len(parts) == 2 && forbiddenKeys[parts[1]] {
|
||||||
|
p.bot.Send(conn, bot.Message, message.Channel, "You cannot access that key")
|
||||||
|
return true
|
||||||
|
} else if parts[0] == "get" && len(parts) == 2 {
|
||||||
|
v := p.cfg.Get(parts[1], "<unknown>")
|
||||||
|
p.bot.Send(conn, bot.Message, message.Channel, fmt.Sprintf("%s: %s", parts[1], v))
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *AdminPlugin) handleVariables(message msg.Message) bool {
|
func (p *AdminPlugin) handleVariables(conn bot.Connector, message msg.Message) bool {
|
||||||
if parts := strings.SplitN(message.Body, "!=", 2); len(parts) == 2 {
|
if parts := strings.SplitN(message.Body, "!=", 2); len(parts) == 2 {
|
||||||
variable := strings.ToLower(strings.TrimSpace(parts[0]))
|
variable := strings.ToLower(strings.TrimSpace(parts[0]))
|
||||||
value := strings.TrimSpace(parts[1])
|
value := strings.TrimSpace(parts[1])
|
||||||
|
|
||||||
_, err := p.db.Exec(`delete from variables where name=? and value=?`, variable, value)
|
_, err := p.db.Exec(`delete from variables where name=? and value=?`, variable, value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.Bot.SendMessage(message.Channel, "I'm broke and need attention in my variable creation code.")
|
p.bot.Send(conn, bot.Message, message.Channel, "I'm broke and need attention in my variable creation code.")
|
||||||
log.Println("[admin]: ", err)
|
log.Error().Err(err)
|
||||||
} else {
|
} else {
|
||||||
p.Bot.SendMessage(message.Channel, "Removed.")
|
p.bot.Send(conn, bot.Message, message.Channel, "Removed.")
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
@ -69,50 +135,65 @@ func (p *AdminPlugin) handleVariables(message msg.Message) bool {
|
||||||
row := p.db.QueryRow(`select count(*) from variables where value = ?`, variable, value)
|
row := p.db.QueryRow(`select count(*) from variables where value = ?`, variable, value)
|
||||||
err := row.Scan(&count)
|
err := row.Scan(&count)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.Bot.SendMessage(message.Channel, "I'm broke and need attention in my variable creation code.")
|
p.bot.Send(conn, bot.Message, message.Channel, "I'm broke and need attention in my variable creation code.")
|
||||||
log.Println("[admin]: ", err)
|
log.Error().Err(err)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
p.Bot.SendMessage(message.Channel, "I've already got that one.")
|
p.bot.Send(conn, bot.Message, message.Channel, "I've already got that one.")
|
||||||
} else {
|
} else {
|
||||||
_, err := p.db.Exec(`INSERT INTO variables (name, value) VALUES (?, ?)`, variable, value)
|
_, err := p.db.Exec(`INSERT INTO variables (name, value) VALUES (?, ?)`, variable, value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.Bot.SendMessage(message.Channel, "I'm broke and need attention in my variable creation code.")
|
p.bot.Send(conn, bot.Message, message.Channel, "I'm broke and need attention in my variable creation code.")
|
||||||
log.Println("[admin]: ", err)
|
log.Error().Err(err)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
p.Bot.SendMessage(message.Channel, "Added.")
|
p.bot.Send(conn, bot.Message, message.Channel, "Added.")
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 *AdminPlugin) LoadData() {
|
|
||||||
// This bot has no data to load
|
|
||||||
}
|
|
||||||
|
|
||||||
// Help responds to help requests. Every plugin must implement a help function.
|
// Help responds to help requests. Every plugin must implement a help function.
|
||||||
func (p *AdminPlugin) Help(channel string, parts []string) {
|
func (p *AdminPlugin) help(conn bot.Connector, kind bot.Kind, m msg.Message, args ...interface{}) bool {
|
||||||
p.Bot.SendMessage(channel, "This does super secret things that you're not allowed to know about.")
|
p.bot.Send(conn, bot.Message, m.Channel, "This does super secret things that you're not allowed to know about.")
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty event handler because this plugin does not do anything on event recv
|
func (p *AdminPlugin) registerWeb() {
|
||||||
func (p *AdminPlugin) Event(kind string, message msg.Message) bool {
|
http.HandleFunc("/vars/api", p.handleWebAPI)
|
||||||
return false
|
http.HandleFunc("/vars", p.handleWeb)
|
||||||
|
p.bot.RegisterWeb("/vars", "Variables")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler for bot's own messages
|
var tpl = template.Must(template.New("factoidIndex").Parse(varIndex))
|
||||||
func (p *AdminPlugin) BotMessage(message msg.Message) bool {
|
|
||||||
return false
|
func (p *AdminPlugin) handleWeb(w http.ResponseWriter, r *http.Request) {
|
||||||
|
tpl.Execute(w, struct{ Nav []bot.EndPoint }{p.bot.GetWebNavigation()})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register any web URLs desired
|
func (p *AdminPlugin) handleWebAPI(w http.ResponseWriter, r *http.Request) {
|
||||||
func (p *AdminPlugin) RegisterWeb() *string {
|
var configEntries []struct {
|
||||||
return nil
|
Key string `json:"key"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
q := `select key, value from config`
|
||||||
|
err := p.db.Select(&configEntries, q)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Msg("Error getting config entries.")
|
||||||
|
w.WriteHeader(500)
|
||||||
|
fmt.Fprint(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i, e := range configEntries {
|
||||||
|
if strings.Contains(e.Value, ";;") {
|
||||||
|
e.Value = strings.ReplaceAll(e.Value, ";;", ", ")
|
||||||
|
e.Value = fmt.Sprintf("[%s]", e.Value)
|
||||||
|
configEntries[i] = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j, _ := json.Marshal(configEntries)
|
||||||
|
fmt.Fprintf(w, "%s", j)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *AdminPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
|
"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 (
|
||||||
|
a *AdminPlugin
|
||||||
|
mb *bot.MockBot
|
||||||
|
)
|
||||||
|
|
||||||
|
func setup(t *testing.T) (*AdminPlugin, *bot.MockBot) {
|
||||||
|
mb = bot.NewMockBot()
|
||||||
|
a = New(mb)
|
||||||
|
mb.DB().MustExec(`delete from config`)
|
||||||
|
return a, mb
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) {
|
||||||
|
isCmd := strings.HasPrefix(payload, "!")
|
||||||
|
if isCmd {
|
||||||
|
payload = payload[1:]
|
||||||
|
}
|
||||||
|
c := cli.CliPlugin{}
|
||||||
|
return &c, bot.Message, msg.Message{
|
||||||
|
User: &user.User{Name: "tester"},
|
||||||
|
Channel: "test",
|
||||||
|
Body: payload,
|
||||||
|
Command: isCmd,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSet(t *testing.T) {
|
||||||
|
a, mb := setup(t)
|
||||||
|
expected := "test value"
|
||||||
|
a.message(makeMessage("!set test.key " + expected))
|
||||||
|
actual := mb.Config().Get("test.key", "ERR")
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetValue(t *testing.T) {
|
||||||
|
a, mb := setup(t)
|
||||||
|
expected := "value"
|
||||||
|
mb.Config().Set("test.key", "value")
|
||||||
|
a.message(makeMessage("!get test.key"))
|
||||||
|
assert.Len(t, mb.Messages, 1)
|
||||||
|
assert.Contains(t, mb.Messages[0], expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetEmpty(t *testing.T) {
|
||||||
|
a, mb := setup(t)
|
||||||
|
expected := "test.key: <unknown>"
|
||||||
|
a.message(makeMessage("!get test.key"))
|
||||||
|
assert.Len(t, mb.Messages, 1)
|
||||||
|
assert.Equal(t, expected, mb.Messages[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetForbidden(t *testing.T) {
|
||||||
|
a, mb := setup(t)
|
||||||
|
expected := "cannot access"
|
||||||
|
a.message(makeMessage("!get slack.token"))
|
||||||
|
assert.Len(t, mb.Messages, 1)
|
||||||
|
assert.Contains(t, mb.Messages[0], expected)
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
package admin
|
||||||
|
|
||||||
|
var varIndex = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<!-- Load required Bootstrap and BootstrapVue CSS -->
|
||||||
|
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
|
||||||
|
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.css" />
|
||||||
|
|
||||||
|
<!-- Load polyfills to support older browsers -->
|
||||||
|
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CMutationObserver"></script>
|
||||||
|
|
||||||
|
<!-- Load Vue followed by BootstrapVue -->
|
||||||
|
<script src="//unpkg.com/vue@latest/dist/vue.min.js"></script>
|
||||||
|
<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.js"></script>
|
||||||
|
<script src="//unpkg.com/axios/dist/axios.min.js"></script>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Vars</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="app">
|
||||||
|
<b-navbar>
|
||||||
|
<b-navbar-brand>Variables</b-navbar-brand>
|
||||||
|
<b-navbar-nav>
|
||||||
|
<b-nav-item v-for="item in nav" :href="item.URL" :active="item.Name === 'Variables'">{{ "{{ item.Name }}" }}</b-nav-item>
|
||||||
|
</b-navbar-nav>
|
||||||
|
</b-navbar>
|
||||||
|
<b-alert
|
||||||
|
dismissable
|
||||||
|
variant="error"
|
||||||
|
v-if="err"
|
||||||
|
@dismissed="err = ''">
|
||||||
|
{{ "{{ err }}" }}
|
||||||
|
</b-alert>
|
||||||
|
<b-container>
|
||||||
|
<b-table
|
||||||
|
fixed
|
||||||
|
:items="vars"
|
||||||
|
:sort-by.sync="sortBy"
|
||||||
|
:fields="fields"></b-table>
|
||||||
|
</b-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var app = new Vue({
|
||||||
|
el: '#app',
|
||||||
|
data: {
|
||||||
|
err: '',
|
||||||
|
nav: {{ .Nav }},
|
||||||
|
vars: [],
|
||||||
|
sortBy: 'key',
|
||||||
|
fields: [
|
||||||
|
{ key: { sortable: true } },
|
||||||
|
'value'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.getData();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getData: function() {
|
||||||
|
axios.get('/vars/api')
|
||||||
|
.then(resp => {
|
||||||
|
this.vars = resp.data;
|
||||||
|
})
|
||||||
|
.catch(err => this.err = err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
|
@ -6,14 +6,14 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
"github.com/velour/catbase/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -25,7 +25,6 @@ var (
|
||||||
type BabblerPlugin struct {
|
type BabblerPlugin struct {
|
||||||
Bot bot.Bot
|
Bot bot.Bot
|
||||||
db *sqlx.DB
|
db *sqlx.DB
|
||||||
config *config.Config
|
|
||||||
WithGoRoutines bool
|
WithGoRoutines bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,55 +53,55 @@ type BabblerArc struct {
|
||||||
Frequency int64 `db:"frequency"`
|
Frequency int64 `db:"frequency"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(bot bot.Bot) *BabblerPlugin {
|
func New(b bot.Bot) *BabblerPlugin {
|
||||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
if _, err := b.DB().Exec(`create table if not exists babblers (
|
||||||
|
|
||||||
if _, err := bot.DB().Exec(`create table if not exists babblers (
|
|
||||||
id integer primary key,
|
id integer primary key,
|
||||||
babbler string
|
babbler string
|
||||||
);`); err != nil {
|
);`); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal().Err(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := bot.DB().Exec(`create table if not exists babblerWords (
|
if _, err := b.DB().Exec(`create table if not exists babblerWords (
|
||||||
id integer primary key,
|
id integer primary key,
|
||||||
word string
|
word string
|
||||||
);`); err != nil {
|
);`); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal().Err(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := bot.DB().Exec(`create table if not exists babblerNodes (
|
if _, err := b.DB().Exec(`create table if not exists babblerNodes (
|
||||||
id integer primary key,
|
id integer primary key,
|
||||||
babblerId integer,
|
babblerId integer,
|
||||||
wordId integer,
|
wordId integer,
|
||||||
root integer,
|
root integer,
|
||||||
rootFrequency integer
|
rootFrequency integer
|
||||||
);`); err != nil {
|
);`); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal().Err(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := bot.DB().Exec(`create table if not exists babblerArcs (
|
if _, err := b.DB().Exec(`create table if not exists babblerArcs (
|
||||||
id integer primary key,
|
id integer primary key,
|
||||||
fromNodeId integer,
|
fromNodeId integer,
|
||||||
toNodeId interger,
|
toNodeId interger,
|
||||||
frequency integer
|
frequency integer
|
||||||
);`); err != nil {
|
);`); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal().Err(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
plugin := &BabblerPlugin{
|
plugin := &BabblerPlugin{
|
||||||
Bot: bot,
|
Bot: b,
|
||||||
db: bot.DB(),
|
db: b.DB(),
|
||||||
config: bot.Config(),
|
|
||||||
WithGoRoutines: true,
|
WithGoRoutines: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
plugin.createNewWord("")
|
plugin.createNewWord("")
|
||||||
|
|
||||||
|
b.Register(plugin, bot.Message, plugin.message)
|
||||||
|
b.Register(plugin, bot.Help, plugin.help)
|
||||||
|
|
||||||
return plugin
|
return plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *BabblerPlugin) Message(message msg.Message) bool {
|
func (p *BabblerPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
lowercase := strings.ToLower(message.Body)
|
lowercase := strings.ToLower(message.Body)
|
||||||
tokens := strings.Fields(lowercase)
|
tokens := strings.Fields(lowercase)
|
||||||
numTokens := len(tokens)
|
numTokens := len(tokens)
|
||||||
|
@ -144,12 +143,12 @@ func (p *BabblerPlugin) Message(message msg.Message) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
if saidSomething {
|
if saidSomething {
|
||||||
p.Bot.SendMessage(message.Channel, saidWhat)
|
p.Bot.Send(c, bot.Message, message.Channel, saidWhat)
|
||||||
}
|
}
|
||||||
return saidSomething
|
return saidSomething
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *BabblerPlugin) Help(channel string, parts []string) {
|
func (p *BabblerPlugin) help(c bot.Connector, kind bot.Kind, msg msg.Message, args ...interface{}) bool {
|
||||||
commands := []string{
|
commands := []string{
|
||||||
"initialize babbler for seabass",
|
"initialize babbler for seabass",
|
||||||
"merge babbler drseabass into seabass",
|
"merge babbler drseabass into seabass",
|
||||||
|
@ -158,19 +157,8 @@ func (p *BabblerPlugin) Help(channel string, parts []string) {
|
||||||
"seabass says-middle-out ...",
|
"seabass says-middle-out ...",
|
||||||
"seabass says-bridge ... | ...",
|
"seabass says-bridge ... | ...",
|
||||||
}
|
}
|
||||||
p.Bot.SendMessage(channel, strings.Join(commands, "\n\n"))
|
p.Bot.Send(c, bot.Message, msg.Channel, strings.Join(commands, "\n\n"))
|
||||||
}
|
return true
|
||||||
|
|
||||||
func (p *BabblerPlugin) Event(kind string, message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *BabblerPlugin) BotMessage(message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *BabblerPlugin) RegisterWeb() *string {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *BabblerPlugin) makeBabbler(name string) (*Babbler, error) {
|
func (p *BabblerPlugin) makeBabbler(name string) (*Babbler, error) {
|
||||||
|
@ -178,7 +166,7 @@ func (p *BabblerPlugin) makeBabbler(name string) (*Babbler, error) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
id, err := res.LastInsertId()
|
id, err := res.LastInsertId()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Babbler{
|
return &Babbler{
|
||||||
|
@ -194,11 +182,10 @@ func (p *BabblerPlugin) getBabbler(name string) (*Babbler, error) {
|
||||||
err := p.db.QueryRowx(`select * from babblers where babbler = ? LIMIT 1;`, name).StructScan(&bblr)
|
err := p.db.QueryRowx(`select * from babblers where babbler = ? LIMIT 1;`, name).StructScan(&bblr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
log.Printf("failed to find babbler")
|
log.Error().Msg("failed to find babbler")
|
||||||
return nil, NO_BABBLER
|
return nil, NO_BABBLER
|
||||||
}
|
}
|
||||||
log.Printf("encountered problem in babbler lookup")
|
log.Error().Err(err).Msg("encountered problem in babbler lookup")
|
||||||
log.Print(err)
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &bblr, nil
|
return &bblr, nil
|
||||||
|
@ -209,13 +196,13 @@ func (p *BabblerPlugin) getOrCreateBabbler(name string) (*Babbler, error) {
|
||||||
if err == NO_BABBLER {
|
if err == NO_BABBLER {
|
||||||
babbler, err = p.makeBabbler(name)
|
babbler, err = p.makeBabbler(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rows, err := p.db.Queryx(fmt.Sprintf("select tidbit from factoid where fact like '%s quotes';", babbler.Name))
|
rows, err := p.db.Queryx(fmt.Sprintf("select tidbit from factoid where fact like '%s quotes';", babbler.Name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return babbler, nil
|
return babbler, nil
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
@ -225,10 +212,10 @@ func (p *BabblerPlugin) getOrCreateBabbler(name string) (*Babbler, error) {
|
||||||
var tidbit string
|
var tidbit string
|
||||||
err := rows.Scan(&tidbit)
|
err := rows.Scan(&tidbit)
|
||||||
|
|
||||||
log.Print(tidbit)
|
log.Debug().Str("tidbit", tidbit)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return babbler, err
|
return babbler, err
|
||||||
}
|
}
|
||||||
tidbits = append(tidbits, tidbit)
|
tidbits = append(tidbits, tidbit)
|
||||||
|
@ -236,7 +223,7 @@ func (p *BabblerPlugin) getOrCreateBabbler(name string) (*Babbler, error) {
|
||||||
|
|
||||||
for _, tidbit := range tidbits {
|
for _, tidbit := range tidbits {
|
||||||
if err = p.addToMarkovChain(babbler, tidbit); err != nil {
|
if err = p.addToMarkovChain(babbler, tidbit); err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -258,12 +245,12 @@ func (p *BabblerPlugin) getWord(word string) (*BabblerWord, error) {
|
||||||
func (p *BabblerPlugin) createNewWord(word string) (*BabblerWord, error) {
|
func (p *BabblerPlugin) createNewWord(word string) (*BabblerWord, error) {
|
||||||
res, err := p.db.Exec(`insert into babblerWords (word) values (?);`, word)
|
res, err := p.db.Exec(`insert into babblerWords (word) values (?);`, word)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
id, err := res.LastInsertId()
|
id, err := res.LastInsertId()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &BabblerWord{
|
return &BabblerWord{
|
||||||
|
@ -277,7 +264,7 @@ func (p *BabblerPlugin) getOrCreateWord(word string) (*BabblerWord, error) {
|
||||||
return p.createNewWord(word)
|
return p.createNewWord(word)
|
||||||
} else {
|
} else {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
}
|
}
|
||||||
return w, err
|
return w, err
|
||||||
}
|
}
|
||||||
|
@ -303,19 +290,19 @@ func (p *BabblerPlugin) getBabblerNode(babbler *Babbler, word string) (*BabblerN
|
||||||
func (p *BabblerPlugin) createBabblerNode(babbler *Babbler, word string) (*BabblerNode, error) {
|
func (p *BabblerPlugin) createBabblerNode(babbler *Babbler, word string) (*BabblerNode, error) {
|
||||||
w, err := p.getOrCreateWord(word)
|
w, err := p.getOrCreateWord(word)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := p.db.Exec(`insert into babblerNodes (babblerId, wordId, root, rootFrequency) values (?, ?, 0, 0)`, babbler.BabblerId, w.WordId)
|
res, err := p.db.Exec(`insert into babblerNodes (babblerId, wordId, root, rootFrequency) values (?, ?, 0, 0)`, babbler.BabblerId, w.WordId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := res.LastInsertId()
|
id, err := res.LastInsertId()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,12 +325,12 @@ func (p *BabblerPlugin) getOrCreateBabblerNode(babbler *Babbler, word string) (*
|
||||||
func (p *BabblerPlugin) incrementRootWordFrequency(babbler *Babbler, word string) (*BabblerNode, error) {
|
func (p *BabblerPlugin) incrementRootWordFrequency(babbler *Babbler, word string) (*BabblerNode, error) {
|
||||||
node, err := p.getOrCreateBabblerNode(babbler, word)
|
node, err := p.getOrCreateBabblerNode(babbler, word)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
_, err = p.db.Exec(`update babblerNodes set rootFrequency = rootFrequency + 1, root = 1 where id = ?;`, node.NodeId)
|
_, err = p.db.Exec(`update babblerNodes set rootFrequency = rootFrequency + 1, root = 1 where id = ?;`, node.NodeId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
node.RootFrequency += 1
|
node.RootFrequency += 1
|
||||||
|
@ -365,7 +352,7 @@ func (p *BabblerPlugin) getBabblerArc(fromNode, toNode *BabblerNode) (*BabblerAr
|
||||||
func (p *BabblerPlugin) incrementWordArc(fromNode, toNode *BabblerNode) (*BabblerArc, error) {
|
func (p *BabblerPlugin) incrementWordArc(fromNode, toNode *BabblerNode) (*BabblerArc, error) {
|
||||||
res, err := p.db.Exec(`update babblerArcs set frequency = frequency + 1 where fromNodeId = ? and toNodeId = ?;`, fromNode.NodeId, toNode.NodeId)
|
res, err := p.db.Exec(`update babblerArcs set frequency = frequency + 1 where fromNodeId = ? and toNodeId = ?;`, fromNode.NodeId, toNode.NodeId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -377,7 +364,7 @@ func (p *BabblerPlugin) incrementWordArc(fromNode, toNode *BabblerNode) (*Babble
|
||||||
if affectedRows == 0 {
|
if affectedRows == 0 {
|
||||||
res, err = p.db.Exec(`insert into babblerArcs (fromNodeId, toNodeId, frequency) values (?, ?, 1);`, fromNode.NodeId, toNode.NodeId)
|
res, err = p.db.Exec(`insert into babblerArcs (fromNodeId, toNodeId, frequency) values (?, ?, 1);`, fromNode.NodeId, toNode.NodeId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -401,19 +388,19 @@ func (p *BabblerPlugin) addToMarkovChain(babbler *Babbler, phrase string) error
|
||||||
|
|
||||||
curNode, err := p.incrementRootWordFrequency(babbler, words[0])
|
curNode, err := p.incrementRootWordFrequency(babbler, words[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 1; i < len(words); i++ {
|
for i := 1; i < len(words); i++ {
|
||||||
nextNode, err := p.getOrCreateBabblerNode(babbler, words[i])
|
nextNode, err := p.getOrCreateBabblerNode(babbler, words[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = p.incrementWordArc(curNode, nextNode)
|
_, err = p.incrementWordArc(curNode, nextNode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
curNode = nextNode
|
curNode = nextNode
|
||||||
|
@ -426,7 +413,7 @@ func (p *BabblerPlugin) addToMarkovChain(babbler *Babbler, phrase string) error
|
||||||
func (p *BabblerPlugin) getWeightedRootNode(babbler *Babbler) (*BabblerNode, *BabblerWord, error) {
|
func (p *BabblerPlugin) getWeightedRootNode(babbler *Babbler) (*BabblerNode, *BabblerWord, error) {
|
||||||
rows, err := p.db.Queryx(`select * from babblerNodes where babblerId = ? and root = 1;`, babbler.BabblerId)
|
rows, err := p.db.Queryx(`select * from babblerNodes where babblerId = ? and root = 1;`, babbler.BabblerId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
@ -438,7 +425,7 @@ func (p *BabblerPlugin) getWeightedRootNode(babbler *Babbler) (*BabblerNode, *Ba
|
||||||
var node BabblerNode
|
var node BabblerNode
|
||||||
err = rows.StructScan(&node)
|
err = rows.StructScan(&node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
rootNodes = append(rootNodes, &node)
|
rootNodes = append(rootNodes, &node)
|
||||||
|
@ -457,21 +444,21 @@ func (p *BabblerPlugin) getWeightedRootNode(babbler *Babbler) (*BabblerNode, *Ba
|
||||||
var w BabblerWord
|
var w BabblerWord
|
||||||
err := p.db.QueryRowx(`select * from babblerWords where id = ? LIMIT 1;`, node.WordId).StructScan(&w)
|
err := p.db.QueryRowx(`select * from babblerWords where id = ? LIMIT 1;`, node.WordId).StructScan(&w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
return node, &w, nil
|
return node, &w, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
log.Fatalf("shouldn't happen")
|
log.Fatal().Msg("failed to find weighted root word")
|
||||||
return nil, nil, errors.New("failed to find weighted root word")
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *BabblerPlugin) getWeightedNextWord(fromNode *BabblerNode) (*BabblerNode, *BabblerWord, error) {
|
func (p *BabblerPlugin) getWeightedNextWord(fromNode *BabblerNode) (*BabblerNode, *BabblerWord, error) {
|
||||||
rows, err := p.db.Queryx(`select * from babblerArcs where fromNodeId = ?;`, fromNode.NodeId)
|
rows, err := p.db.Queryx(`select * from babblerArcs where fromNodeId = ?;`, fromNode.NodeId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
@ -482,7 +469,7 @@ func (p *BabblerPlugin) getWeightedNextWord(fromNode *BabblerNode) (*BabblerNode
|
||||||
var arc BabblerArc
|
var arc BabblerArc
|
||||||
err = rows.StructScan(&arc)
|
err = rows.StructScan(&arc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
arcs = append(arcs, &arc)
|
arcs = append(arcs, &arc)
|
||||||
|
@ -503,28 +490,28 @@ func (p *BabblerPlugin) getWeightedNextWord(fromNode *BabblerNode) (*BabblerNode
|
||||||
var node BabblerNode
|
var node BabblerNode
|
||||||
err := p.db.QueryRowx(`select * from babblerNodes where id = ? LIMIT 1;`, arc.ToNodeId).StructScan(&node)
|
err := p.db.QueryRowx(`select * from babblerNodes where id = ? LIMIT 1;`, arc.ToNodeId).StructScan(&node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var w BabblerWord
|
var w BabblerWord
|
||||||
err = p.db.QueryRowx(`select * from babblerWords where id = ? LIMIT 1;`, node.WordId).StructScan(&w)
|
err = p.db.QueryRowx(`select * from babblerWords where id = ? LIMIT 1;`, node.WordId).StructScan(&w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
return &node, &w, nil
|
return &node, &w, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
log.Fatalf("shouldn't happen")
|
log.Fatal().Msg("failed to find weighted next word")
|
||||||
return nil, nil, errors.New("failed to find weighted next word")
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *BabblerPlugin) getWeightedPreviousWord(toNode *BabblerNode) (*BabblerNode, *BabblerWord, bool, error) {
|
func (p *BabblerPlugin) getWeightedPreviousWord(toNode *BabblerNode) (*BabblerNode, *BabblerWord, bool, error) {
|
||||||
rows, err := p.db.Queryx(`select * from babblerArcs where toNodeId = ?;`, toNode.NodeId)
|
rows, err := p.db.Queryx(`select * from babblerArcs where toNodeId = ?;`, toNode.NodeId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil, nil, false, err
|
return nil, nil, false, err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
@ -535,7 +522,7 @@ func (p *BabblerPlugin) getWeightedPreviousWord(toNode *BabblerNode) (*BabblerNo
|
||||||
var arc BabblerArc
|
var arc BabblerArc
|
||||||
err = rows.StructScan(&arc)
|
err = rows.StructScan(&arc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil, nil, false, err
|
return nil, nil, false, err
|
||||||
}
|
}
|
||||||
arcs = append(arcs, &arc)
|
arcs = append(arcs, &arc)
|
||||||
|
@ -562,39 +549,39 @@ func (p *BabblerPlugin) getWeightedPreviousWord(toNode *BabblerNode) (*BabblerNo
|
||||||
var node BabblerNode
|
var node BabblerNode
|
||||||
err := p.db.QueryRowx(`select * from babblerNodes where id = ? LIMIT 1;`, arc.FromNodeId).StructScan(&node)
|
err := p.db.QueryRowx(`select * from babblerNodes where id = ? LIMIT 1;`, arc.FromNodeId).StructScan(&node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil, nil, false, err
|
return nil, nil, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var w BabblerWord
|
var w BabblerWord
|
||||||
err = p.db.QueryRowx(`select * from babblerWords where id = ? LIMIT 1;`, node.WordId).StructScan(&w)
|
err = p.db.QueryRowx(`select * from babblerWords where id = ? LIMIT 1;`, node.WordId).StructScan(&w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil, nil, false, err
|
return nil, nil, false, err
|
||||||
}
|
}
|
||||||
return &node, &w, false, nil
|
return &node, &w, false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Fatalf("shouldn't happen")
|
log.Fatal().Msg("failed to find weighted previous word")
|
||||||
return nil, nil, false, errors.New("failed to find weighted previous word")
|
return nil, nil, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *BabblerPlugin) verifyPhrase(babbler *Babbler, phrase []string) (*BabblerNode, *BabblerNode, error) {
|
func (p *BabblerPlugin) verifyPhrase(babbler *Babbler, phrase []string) (*BabblerNode, *BabblerNode, error) {
|
||||||
curNode, err := p.getBabblerNode(babbler, phrase[0])
|
curNode, err := p.getBabblerNode(babbler, phrase[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
firstNode := curNode
|
firstNode := curNode
|
||||||
for i := 1; i < len(phrase); i++ {
|
for i := 1; i < len(phrase); i++ {
|
||||||
nextNode, err := p.getBabblerNode(babbler, phrase[i])
|
nextNode, err := p.getBabblerNode(babbler, phrase[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
_, err = p.getBabblerArc(curNode, nextNode)
|
_, err = p.getBabblerArc(curNode, nextNode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
curNode = nextNode
|
curNode = nextNode
|
||||||
|
@ -610,7 +597,7 @@ func (p *BabblerPlugin) babble(who string) (string, error) {
|
||||||
func (p *BabblerPlugin) babbleSeed(babblerName string, seed []string) (string, error) {
|
func (p *BabblerPlugin) babbleSeed(babblerName string, seed []string) (string, error) {
|
||||||
babbler, err := p.getBabbler(babblerName)
|
babbler, err := p.getBabbler(babblerName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -621,14 +608,14 @@ func (p *BabblerPlugin) babbleSeed(babblerName string, seed []string) (string, e
|
||||||
if len(seed) == 0 {
|
if len(seed) == 0 {
|
||||||
curNode, curWord, err = p.getWeightedRootNode(babbler)
|
curNode, curWord, err = p.getWeightedRootNode(babbler)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
words = append(words, curWord.Word)
|
words = append(words, curWord.Word)
|
||||||
} else {
|
} else {
|
||||||
_, curNode, err = p.verifyPhrase(babbler, seed)
|
_, curNode, err = p.verifyPhrase(babbler, seed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -636,7 +623,7 @@ func (p *BabblerPlugin) babbleSeed(babblerName string, seed []string) (string, e
|
||||||
for {
|
for {
|
||||||
curNode, curWord, err = p.getWeightedNextWord(curNode)
|
curNode, curWord, err = p.getWeightedNextWord(curNode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if curWord.Word == " " {
|
if curWord.Word == " " {
|
||||||
|
@ -655,12 +642,12 @@ func (p *BabblerPlugin) babbleSeed(babblerName string, seed []string) (string, e
|
||||||
func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoName, otherName string) error {
|
func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoName, otherName string) error {
|
||||||
intoNode, err := p.getOrCreateBabblerNode(intoBabbler, "<"+intoName+">")
|
intoNode, err := p.getOrCreateBabblerNode(intoBabbler, "<"+intoName+">")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
otherNode, err := p.getOrCreateBabblerNode(otherBabbler, "<"+otherName+">")
|
otherNode, err := p.getOrCreateBabblerNode(otherBabbler, "<"+otherName+">")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -668,7 +655,7 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa
|
||||||
|
|
||||||
rows, err := p.db.Queryx("select * from babblerNodes where babblerId = ?;", otherBabbler.BabblerId)
|
rows, err := p.db.Queryx("select * from babblerNodes where babblerId = ?;", otherBabbler.BabblerId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
@ -679,7 +666,7 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa
|
||||||
var node BabblerNode
|
var node BabblerNode
|
||||||
err = rows.StructScan(&node)
|
err = rows.StructScan(&node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
nodes = append(nodes, &node)
|
nodes = append(nodes, &node)
|
||||||
|
@ -695,12 +682,12 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa
|
||||||
if node.Root > 0 {
|
if node.Root > 0 {
|
||||||
res, err = p.db.Exec(`update babblerNodes set rootFrequency = rootFrequency + ?, root = 1 where babblerId = ? and wordId = ?;`, node.RootFrequency, intoBabbler.BabblerId, node.WordId)
|
res, err = p.db.Exec(`update babblerNodes set rootFrequency = rootFrequency + ?, root = 1 where babblerId = ? and wordId = ?;`, node.RootFrequency, intoBabbler.BabblerId, node.WordId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
res, err = p.db.Exec(`update babblerNodes set rootFrequency = rootFrequency + ? where babblerId = ? and wordId = ?;`, node.RootFrequency, intoBabbler.BabblerId, node.WordId)
|
res, err = p.db.Exec(`update babblerNodes set rootFrequency = rootFrequency + ? where babblerId = ? and wordId = ?;`, node.RootFrequency, intoBabbler.BabblerId, node.WordId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -712,7 +699,7 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa
|
||||||
if err != nil || rowsAffected == 0 {
|
if err != nil || rowsAffected == 0 {
|
||||||
res, err = p.db.Exec(`insert into babblerNodes (babblerId, wordId, root, rootFrequency) values (?,?,?,?) ;`, intoBabbler.BabblerId, node.WordId, node.Root, node.RootFrequency)
|
res, err = p.db.Exec(`insert into babblerNodes (babblerId, wordId, root, rootFrequency) values (?,?,?,?) ;`, intoBabbler.BabblerId, node.WordId, node.Root, node.RootFrequency)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -720,7 +707,7 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa
|
||||||
var updatedNode BabblerNode
|
var updatedNode BabblerNode
|
||||||
err = p.db.QueryRowx(`select * from babblerNodes where babblerId = ? and wordId = ? LIMIT 1;`, intoBabbler.BabblerId, node.WordId).StructScan(&updatedNode)
|
err = p.db.QueryRowx(`select * from babblerNodes where babblerId = ? and wordId = ? LIMIT 1;`, intoBabbler.BabblerId, node.WordId).StructScan(&updatedNode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -740,7 +727,7 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa
|
||||||
var arc BabblerArc
|
var arc BabblerArc
|
||||||
err = rows.StructScan(&arc)
|
err = rows.StructScan(&arc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
arcs = append(arcs, &arc)
|
arcs = append(arcs, &arc)
|
||||||
|
@ -760,13 +747,13 @@ func (p *BabblerPlugin) mergeBabblers(intoBabbler, otherBabbler *Babbler, intoNa
|
||||||
func (p *BabblerPlugin) babbleSeedSuffix(babblerName string, seed []string) (string, error) {
|
func (p *BabblerPlugin) babbleSeedSuffix(babblerName string, seed []string) (string, error) {
|
||||||
babbler, err := p.getBabbler(babblerName)
|
babbler, err := p.getBabbler(babblerName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
firstNode, curNode, err := p.verifyPhrase(babbler, seed)
|
firstNode, curNode, err := p.verifyPhrase(babbler, seed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -777,7 +764,7 @@ func (p *BabblerPlugin) babbleSeedSuffix(babblerName string, seed []string) (str
|
||||||
for {
|
for {
|
||||||
curNode, curWord, shouldTerminate, err = p.getWeightedPreviousWord(curNode)
|
curNode, curWord, shouldTerminate, err = p.getWeightedPreviousWord(curNode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -806,7 +793,7 @@ func (p *BabblerPlugin) getNextArcs(babblerNodeId int64) ([]*BabblerArc, error)
|
||||||
arcs := []*BabblerArc{}
|
arcs := []*BabblerArc{}
|
||||||
rows, err := p.db.Queryx(`select * from babblerArcs where fromNodeId = ?;`, babblerNodeId)
|
rows, err := p.db.Queryx(`select * from babblerArcs where fromNodeId = ?;`, babblerNodeId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return arcs, err
|
return arcs, err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
@ -815,7 +802,7 @@ func (p *BabblerPlugin) getNextArcs(babblerNodeId int64) ([]*BabblerArc, error)
|
||||||
var arc BabblerArc
|
var arc BabblerArc
|
||||||
err = rows.StructScan(&arc)
|
err = rows.StructScan(&arc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return []*BabblerArc{}, err
|
return []*BabblerArc{}, err
|
||||||
}
|
}
|
||||||
arcs = append(arcs, &arc)
|
arcs = append(arcs, &arc)
|
||||||
|
@ -827,7 +814,7 @@ func (p *BabblerPlugin) getBabblerNodeById(nodeId int64) (*BabblerNode, error) {
|
||||||
var node BabblerNode
|
var node BabblerNode
|
||||||
err := p.db.QueryRowx(`select * from babblerNodes where id = ? LIMIT 1;`, nodeId).StructScan(&node)
|
err := p.db.QueryRowx(`select * from babblerNodes where id = ? LIMIT 1;`, nodeId).StructScan(&node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &node, nil
|
return &node, nil
|
||||||
|
@ -843,19 +830,19 @@ func shuffle(a []*BabblerArc) {
|
||||||
func (p *BabblerPlugin) babbleSeedBookends(babblerName string, start, end []string) (string, error) {
|
func (p *BabblerPlugin) babbleSeedBookends(babblerName string, start, end []string) (string, error) {
|
||||||
babbler, err := p.getBabbler(babblerName)
|
babbler, err := p.getBabbler(babblerName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_, startWordNode, err := p.verifyPhrase(babbler, start)
|
_, startWordNode, err := p.verifyPhrase(babbler, start)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
endWordNode, _, err := p.verifyPhrase(babbler, end)
|
endWordNode, _, err := p.verifyPhrase(babbler, end)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -864,7 +851,7 @@ func (p *BabblerPlugin) babbleSeedBookends(babblerName string, start, end []stri
|
||||||
previous *searchNode
|
previous *searchNode
|
||||||
}
|
}
|
||||||
|
|
||||||
open := []*searchNode{&searchNode{startWordNode.NodeId, nil}}
|
open := []*searchNode{{startWordNode.NodeId, nil}}
|
||||||
closed := map[int64]*searchNode{startWordNode.NodeId: open[0]}
|
closed := map[int64]*searchNode{startWordNode.NodeId: open[0]}
|
||||||
goalNodeId := int64(-1)
|
goalNodeId := int64(-1)
|
||||||
|
|
||||||
|
@ -909,13 +896,13 @@ func (p *BabblerPlugin) babbleSeedBookends(babblerName string, start, end []stri
|
||||||
for {
|
for {
|
||||||
cur, err := p.getBabblerNodeById(curSearchNode.babblerNodeId)
|
cur, err := p.getBabblerNodeById(curSearchNode.babblerNodeId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
var w BabblerWord
|
var w BabblerWord
|
||||||
err = p.db.QueryRowx(`select * from babblerWords where id = ? LIMIT 1;`, cur.WordId).StructScan(&w)
|
err = p.db.QueryRowx(`select * from babblerWords where id = ? LIMIT 1;`, cur.WordId).StructScan(&w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
words = append(words, w.Word)
|
words = append(words, w.Word)
|
||||||
|
@ -937,5 +924,3 @@ func (p *BabblerPlugin) babbleSeedBookends(babblerName string, start, end []stri
|
||||||
|
|
||||||
return strings.Join(words, " "), nil
|
return strings.Join(words, " "), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *BabblerPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
package babbler
|
package babbler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -12,12 +13,13 @@ import (
|
||||||
"github.com/velour/catbase/bot/user"
|
"github.com/velour/catbase/bot/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeMessage(payload string) msg.Message {
|
func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) {
|
||||||
|
c := &cli.CliPlugin{}
|
||||||
isCmd := strings.HasPrefix(payload, "!")
|
isCmd := strings.HasPrefix(payload, "!")
|
||||||
if isCmd {
|
if isCmd {
|
||||||
payload = payload[1:]
|
payload = payload[1:]
|
||||||
}
|
}
|
||||||
return msg.Message{
|
return c, bot.Message, msg.Message{
|
||||||
User: &user.User{Name: "tester"},
|
User: &user.User{Name: "tester"},
|
||||||
Channel: "test",
|
Channel: "test",
|
||||||
Body: payload,
|
Body: payload,
|
||||||
|
@ -28,15 +30,20 @@ func makeMessage(payload string) msg.Message {
|
||||||
func newBabblerPlugin(mb *bot.MockBot) *BabblerPlugin {
|
func newBabblerPlugin(mb *bot.MockBot) *BabblerPlugin {
|
||||||
bp := New(mb)
|
bp := New(mb)
|
||||||
bp.WithGoRoutines = false
|
bp.WithGoRoutines = false
|
||||||
|
mb.DB().MustExec(`
|
||||||
|
delete from babblers;
|
||||||
|
delete from babblerWords;
|
||||||
|
delete from babblerNodes;
|
||||||
|
delete from babblerArcs;
|
||||||
|
`)
|
||||||
return bp
|
return bp
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBabblerNoBabbler(t *testing.T) {
|
func TestBabblerNoBabbler(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
bp := newBabblerPlugin(mb)
|
bp := newBabblerPlugin(mb)
|
||||||
bp.config.Babbler.DefaultUsers = []string{"seabass"}
|
|
||||||
assert.NotNil(t, bp)
|
assert.NotNil(t, bp)
|
||||||
bp.Message(makeMessage("!seabass2 says"))
|
bp.message(makeMessage("!seabass2 says"))
|
||||||
res := assert.Len(t, mb.Messages, 0)
|
res := assert.Len(t, mb.Messages, 0)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
// assert.Contains(t, mb.Messages[0], "seabass2 babbler not found")
|
// assert.Contains(t, mb.Messages[0], "seabass2 babbler not found")
|
||||||
|
@ -45,11 +52,10 @@ func TestBabblerNoBabbler(t *testing.T) {
|
||||||
func TestBabblerNothingSaid(t *testing.T) {
|
func TestBabblerNothingSaid(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
bp := newBabblerPlugin(mb)
|
bp := newBabblerPlugin(mb)
|
||||||
bp.config.Babbler.DefaultUsers = []string{"seabass"}
|
|
||||||
assert.NotNil(t, bp)
|
assert.NotNil(t, bp)
|
||||||
res := bp.Message(makeMessage("initialize babbler for seabass"))
|
res := bp.message(makeMessage("initialize babbler for seabass"))
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
res = bp.Message(makeMessage("!seabass says"))
|
res = bp.message(makeMessage("!seabass says"))
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Len(t, mb.Messages, 2)
|
assert.Len(t, mb.Messages, 2)
|
||||||
assert.Contains(t, mb.Messages[0], "okay.")
|
assert.Contains(t, mb.Messages[0], "okay.")
|
||||||
|
@ -59,16 +65,15 @@ func TestBabblerNothingSaid(t *testing.T) {
|
||||||
func TestBabbler(t *testing.T) {
|
func TestBabbler(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
bp := newBabblerPlugin(mb)
|
bp := newBabblerPlugin(mb)
|
||||||
bp.config.Babbler.DefaultUsers = []string{"seabass"}
|
|
||||||
assert.NotNil(t, bp)
|
assert.NotNil(t, bp)
|
||||||
seabass := makeMessage("This is a message")
|
c, k, seabass := makeMessage("This is a message")
|
||||||
seabass.User = &user.User{Name: "seabass"}
|
seabass.User = &user.User{Name: "seabass"}
|
||||||
res := bp.Message(seabass)
|
res := bp.message(c, k, seabass)
|
||||||
seabass.Body = "This is another message"
|
seabass.Body = "This is another message"
|
||||||
res = bp.Message(seabass)
|
res = bp.message(c, k, seabass)
|
||||||
seabass.Body = "This is a long message"
|
seabass.Body = "This is a long message"
|
||||||
res = bp.Message(seabass)
|
res = bp.message(c, k, seabass)
|
||||||
res = bp.Message(makeMessage("!seabass says"))
|
res = bp.message(makeMessage("!seabass says"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Contains(t, mb.Messages[0], "this is")
|
assert.Contains(t, mb.Messages[0], "this is")
|
||||||
|
@ -78,16 +83,15 @@ func TestBabbler(t *testing.T) {
|
||||||
func TestBabblerSeed(t *testing.T) {
|
func TestBabblerSeed(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
bp := newBabblerPlugin(mb)
|
bp := newBabblerPlugin(mb)
|
||||||
bp.config.Babbler.DefaultUsers = []string{"seabass"}
|
|
||||||
assert.NotNil(t, bp)
|
assert.NotNil(t, bp)
|
||||||
seabass := makeMessage("This is a message")
|
c, k, seabass := makeMessage("This is a message")
|
||||||
seabass.User = &user.User{Name: "seabass"}
|
seabass.User = &user.User{Name: "seabass"}
|
||||||
res := bp.Message(seabass)
|
res := bp.message(c, k, seabass)
|
||||||
seabass.Body = "This is another message"
|
seabass.Body = "This is another message"
|
||||||
res = bp.Message(seabass)
|
res = bp.message(c, k, seabass)
|
||||||
seabass.Body = "This is a long message"
|
seabass.Body = "This is a long message"
|
||||||
res = bp.Message(seabass)
|
res = bp.message(c, k, seabass)
|
||||||
res = bp.Message(makeMessage("!seabass says long"))
|
res = bp.message(makeMessage("!seabass says long"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Contains(t, mb.Messages[0], "long message")
|
assert.Contains(t, mb.Messages[0], "long message")
|
||||||
|
@ -96,16 +100,15 @@ func TestBabblerSeed(t *testing.T) {
|
||||||
func TestBabblerMultiSeed(t *testing.T) {
|
func TestBabblerMultiSeed(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
bp := newBabblerPlugin(mb)
|
bp := newBabblerPlugin(mb)
|
||||||
bp.config.Babbler.DefaultUsers = []string{"seabass"}
|
|
||||||
assert.NotNil(t, bp)
|
assert.NotNil(t, bp)
|
||||||
seabass := makeMessage("This is a message")
|
c, k, seabass := makeMessage("This is a message")
|
||||||
seabass.User = &user.User{Name: "seabass"}
|
seabass.User = &user.User{Name: "seabass"}
|
||||||
res := bp.Message(seabass)
|
res := bp.message(c, k, seabass)
|
||||||
seabass.Body = "This is another message"
|
seabass.Body = "This is another message"
|
||||||
res = bp.Message(seabass)
|
res = bp.message(c, k, seabass)
|
||||||
seabass.Body = "This is a long message"
|
seabass.Body = "This is a long message"
|
||||||
res = bp.Message(seabass)
|
res = bp.message(c, k, seabass)
|
||||||
res = bp.Message(makeMessage("!seabass says This is a long"))
|
res = bp.message(makeMessage("!seabass says This is a long"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Contains(t, mb.Messages[0], "this is a long message")
|
assert.Contains(t, mb.Messages[0], "this is a long message")
|
||||||
|
@ -114,16 +117,15 @@ func TestBabblerMultiSeed(t *testing.T) {
|
||||||
func TestBabblerMultiSeed2(t *testing.T) {
|
func TestBabblerMultiSeed2(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
bp := newBabblerPlugin(mb)
|
bp := newBabblerPlugin(mb)
|
||||||
bp.config.Babbler.DefaultUsers = []string{"seabass"}
|
|
||||||
assert.NotNil(t, bp)
|
assert.NotNil(t, bp)
|
||||||
seabass := makeMessage("This is a message")
|
c, k, seabass := makeMessage("This is a message")
|
||||||
seabass.User = &user.User{Name: "seabass"}
|
seabass.User = &user.User{Name: "seabass"}
|
||||||
res := bp.Message(seabass)
|
res := bp.message(c, k, seabass)
|
||||||
seabass.Body = "This is another message"
|
seabass.Body = "This is another message"
|
||||||
res = bp.Message(seabass)
|
res = bp.message(c, k, seabass)
|
||||||
seabass.Body = "This is a long message"
|
seabass.Body = "This is a long message"
|
||||||
res = bp.Message(seabass)
|
res = bp.message(c, k, seabass)
|
||||||
res = bp.Message(makeMessage("!seabass says is a long"))
|
res = bp.message(makeMessage("!seabass says is a long"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Contains(t, mb.Messages[0], "is a long message")
|
assert.Contains(t, mb.Messages[0], "is a long message")
|
||||||
|
@ -132,16 +134,15 @@ func TestBabblerMultiSeed2(t *testing.T) {
|
||||||
func TestBabblerBadSeed(t *testing.T) {
|
func TestBabblerBadSeed(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
bp := newBabblerPlugin(mb)
|
bp := newBabblerPlugin(mb)
|
||||||
bp.config.Babbler.DefaultUsers = []string{"seabass"}
|
|
||||||
assert.NotNil(t, bp)
|
assert.NotNil(t, bp)
|
||||||
seabass := makeMessage("This is a message")
|
c, k, seabass := makeMessage("This is a message")
|
||||||
seabass.User = &user.User{Name: "seabass"}
|
seabass.User = &user.User{Name: "seabass"}
|
||||||
bp.Message(seabass)
|
bp.message(c, k, seabass)
|
||||||
seabass.Body = "This is another message"
|
seabass.Body = "This is another message"
|
||||||
bp.Message(seabass)
|
bp.message(c, k, seabass)
|
||||||
seabass.Body = "This is a long message"
|
seabass.Body = "This is a long message"
|
||||||
bp.Message(seabass)
|
bp.message(c, k, seabass)
|
||||||
bp.Message(makeMessage("!seabass says noooo this is bad"))
|
bp.message(makeMessage("!seabass says noooo this is bad"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.Contains(t, mb.Messages[0], "seabass never said 'noooo this is bad'")
|
assert.Contains(t, mb.Messages[0], "seabass never said 'noooo this is bad'")
|
||||||
}
|
}
|
||||||
|
@ -149,16 +150,15 @@ func TestBabblerBadSeed(t *testing.T) {
|
||||||
func TestBabblerBadSeed2(t *testing.T) {
|
func TestBabblerBadSeed2(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
bp := newBabblerPlugin(mb)
|
bp := newBabblerPlugin(mb)
|
||||||
bp.config.Babbler.DefaultUsers = []string{"seabass"}
|
|
||||||
assert.NotNil(t, bp)
|
assert.NotNil(t, bp)
|
||||||
seabass := makeMessage("This is a message")
|
c, k, seabass := makeMessage("This is a message")
|
||||||
seabass.User = &user.User{Name: "seabass"}
|
seabass.User = &user.User{Name: "seabass"}
|
||||||
bp.Message(seabass)
|
bp.message(c, k, seabass)
|
||||||
seabass.Body = "This is another message"
|
seabass.Body = "This is another message"
|
||||||
bp.Message(seabass)
|
bp.message(c, k, seabass)
|
||||||
seabass.Body = "This is a long message"
|
seabass.Body = "This is a long message"
|
||||||
bp.Message(seabass)
|
bp.message(c, k, seabass)
|
||||||
bp.Message(makeMessage("!seabass says This is a really"))
|
bp.message(makeMessage("!seabass says This is a really"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.Contains(t, mb.Messages[0], "seabass never said 'this is a really'")
|
assert.Contains(t, mb.Messages[0], "seabass never said 'this is a really'")
|
||||||
}
|
}
|
||||||
|
@ -166,17 +166,16 @@ func TestBabblerBadSeed2(t *testing.T) {
|
||||||
func TestBabblerSuffixSeed(t *testing.T) {
|
func TestBabblerSuffixSeed(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
bp := newBabblerPlugin(mb)
|
bp := newBabblerPlugin(mb)
|
||||||
bp.config.Babbler.DefaultUsers = []string{"seabass"}
|
|
||||||
assert.NotNil(t, bp)
|
assert.NotNil(t, bp)
|
||||||
seabass := makeMessage("This is message one")
|
c, k, seabass := makeMessage("This is message one")
|
||||||
seabass.User = &user.User{Name: "seabass"}
|
seabass.User = &user.User{Name: "seabass"}
|
||||||
res := bp.Message(seabass)
|
res := bp.message(c, k, seabass)
|
||||||
seabass.Body = "It's easier to test with unique messages"
|
seabass.Body = "It's easier to test with unique messages"
|
||||||
res = bp.Message(seabass)
|
res = bp.message(c, k, seabass)
|
||||||
seabass.Body = "hi there"
|
seabass.Body = "hi there"
|
||||||
res = bp.Message(seabass)
|
res = bp.message(c, k, seabass)
|
||||||
res = bp.Message(makeMessage("!seabass says-tail message one"))
|
res = bp.message(makeMessage("!seabass says-tail message one"))
|
||||||
res = bp.Message(makeMessage("!seabass says-tail with unique"))
|
res = bp.message(makeMessage("!seabass says-tail with unique"))
|
||||||
assert.Len(t, mb.Messages, 2)
|
assert.Len(t, mb.Messages, 2)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Contains(t, mb.Messages[0], "this is message one")
|
assert.Contains(t, mb.Messages[0], "this is message one")
|
||||||
|
@ -186,16 +185,15 @@ func TestBabblerSuffixSeed(t *testing.T) {
|
||||||
func TestBabblerBadSuffixSeed(t *testing.T) {
|
func TestBabblerBadSuffixSeed(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
bp := newBabblerPlugin(mb)
|
bp := newBabblerPlugin(mb)
|
||||||
bp.config.Babbler.DefaultUsers = []string{"seabass"}
|
|
||||||
assert.NotNil(t, bp)
|
assert.NotNil(t, bp)
|
||||||
seabass := makeMessage("This is message one")
|
c, k, seabass := makeMessage("This is message one")
|
||||||
seabass.User = &user.User{Name: "seabass"}
|
seabass.User = &user.User{Name: "seabass"}
|
||||||
res := bp.Message(seabass)
|
res := bp.message(c, k, seabass)
|
||||||
seabass.Body = "It's easier to test with unique messages"
|
seabass.Body = "It's easier to test with unique messages"
|
||||||
res = bp.Message(seabass)
|
res = bp.message(c, k, seabass)
|
||||||
seabass.Body = "hi there"
|
seabass.Body = "hi there"
|
||||||
res = bp.Message(seabass)
|
res = bp.message(c, k, seabass)
|
||||||
res = bp.Message(makeMessage("!seabass says-tail anything true"))
|
res = bp.message(makeMessage("!seabass says-tail anything true"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Contains(t, mb.Messages[0], "seabass never said 'anything true'")
|
assert.Contains(t, mb.Messages[0], "seabass never said 'anything true'")
|
||||||
|
@ -204,12 +202,11 @@ func TestBabblerBadSuffixSeed(t *testing.T) {
|
||||||
func TestBabblerBookendSeed(t *testing.T) {
|
func TestBabblerBookendSeed(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
bp := newBabblerPlugin(mb)
|
bp := newBabblerPlugin(mb)
|
||||||
bp.config.Babbler.DefaultUsers = []string{"seabass"}
|
|
||||||
assert.NotNil(t, bp)
|
assert.NotNil(t, bp)
|
||||||
seabass := makeMessage("It's easier to test with unique messages")
|
c, k, seabass := makeMessage("It's easier to test with unique messages")
|
||||||
seabass.User = &user.User{Name: "seabass"}
|
seabass.User = &user.User{Name: "seabass"}
|
||||||
res := bp.Message(seabass)
|
res := bp.message(c, k, seabass)
|
||||||
res = bp.Message(makeMessage("!seabass says-bridge It's easier | unique messages"))
|
res = bp.message(makeMessage("!seabass says-bridge It's easier | unique messages"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Contains(t, mb.Messages[0], "it's easier to test with unique messages")
|
assert.Contains(t, mb.Messages[0], "it's easier to test with unique messages")
|
||||||
|
@ -218,12 +215,11 @@ func TestBabblerBookendSeed(t *testing.T) {
|
||||||
func TestBabblerBookendSeedShort(t *testing.T) {
|
func TestBabblerBookendSeedShort(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
bp := newBabblerPlugin(mb)
|
bp := newBabblerPlugin(mb)
|
||||||
bp.config.Babbler.DefaultUsers = []string{"seabass"}
|
|
||||||
assert.NotNil(t, bp)
|
assert.NotNil(t, bp)
|
||||||
seabass := makeMessage("It's easier to test with unique messages")
|
c, k, seabass := makeMessage("It's easier to test with unique messages")
|
||||||
seabass.User = &user.User{Name: "seabass"}
|
seabass.User = &user.User{Name: "seabass"}
|
||||||
res := bp.Message(seabass)
|
res := bp.message(c, k, seabass)
|
||||||
res = bp.Message(makeMessage("!seabass says-bridge It's easier to test with | unique messages"))
|
res = bp.message(makeMessage("!seabass says-bridge It's easier to test with | unique messages"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Contains(t, mb.Messages[0], "it's easier to test with unique messages")
|
assert.Contains(t, mb.Messages[0], "it's easier to test with unique messages")
|
||||||
|
@ -232,12 +228,11 @@ func TestBabblerBookendSeedShort(t *testing.T) {
|
||||||
func TestBabblerBadBookendSeed(t *testing.T) {
|
func TestBabblerBadBookendSeed(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
bp := newBabblerPlugin(mb)
|
bp := newBabblerPlugin(mb)
|
||||||
bp.config.Babbler.DefaultUsers = []string{"seabass"}
|
|
||||||
assert.NotNil(t, bp)
|
assert.NotNil(t, bp)
|
||||||
seabass := makeMessage("It's easier to test with unique messages")
|
c, k, seabass := makeMessage("It's easier to test with unique messages")
|
||||||
seabass.User = &user.User{Name: "seabass"}
|
seabass.User = &user.User{Name: "seabass"}
|
||||||
res := bp.Message(seabass)
|
res := bp.message(c, k, seabass)
|
||||||
res = bp.Message(makeMessage("!seabass says-bridge It's easier | not unique messages"))
|
res = bp.message(makeMessage("!seabass says-bridge It's easier | not unique messages"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Contains(t, mb.Messages[0], "seabass never said 'it's easier ... not unique messages'")
|
assert.Contains(t, mb.Messages[0], "seabass never said 'it's easier ... not unique messages'")
|
||||||
|
@ -246,12 +241,11 @@ func TestBabblerBadBookendSeed(t *testing.T) {
|
||||||
func TestBabblerMiddleOutSeed(t *testing.T) {
|
func TestBabblerMiddleOutSeed(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
bp := newBabblerPlugin(mb)
|
bp := newBabblerPlugin(mb)
|
||||||
bp.config.Babbler.DefaultUsers = []string{"seabass"}
|
|
||||||
assert.NotNil(t, bp)
|
assert.NotNil(t, bp)
|
||||||
seabass := makeMessage("It's easier to test with unique messages")
|
c, k, seabass := makeMessage("It's easier to test with unique messages")
|
||||||
seabass.User = &user.User{Name: "seabass"}
|
seabass.User = &user.User{Name: "seabass"}
|
||||||
res := bp.Message(seabass)
|
res := bp.message(c, k, seabass)
|
||||||
res = bp.Message(makeMessage("!seabass says-middle-out test with"))
|
res = bp.message(makeMessage("!seabass says-middle-out test with"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Contains(t, mb.Messages[0], "it's easier to test with unique messages")
|
assert.Contains(t, mb.Messages[0], "it's easier to test with unique messages")
|
||||||
|
@ -260,12 +254,11 @@ func TestBabblerMiddleOutSeed(t *testing.T) {
|
||||||
func TestBabblerBadMiddleOutSeed(t *testing.T) {
|
func TestBabblerBadMiddleOutSeed(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
bp := newBabblerPlugin(mb)
|
bp := newBabblerPlugin(mb)
|
||||||
bp.config.Babbler.DefaultUsers = []string{"seabass"}
|
|
||||||
assert.NotNil(t, bp)
|
assert.NotNil(t, bp)
|
||||||
seabass := makeMessage("It's easier to test with unique messages")
|
c, k, seabass := makeMessage("It's easier to test with unique messages")
|
||||||
seabass.User = &user.User{Name: "seabass"}
|
seabass.User = &user.User{Name: "seabass"}
|
||||||
res := bp.Message(seabass)
|
res := bp.message(c, k, seabass)
|
||||||
res = bp.Message(makeMessage("!seabass says-middle-out anything true"))
|
res = bp.message(makeMessage("!seabass says-middle-out anything true"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Equal(t, mb.Messages[0], "seabass never said 'anything true'")
|
assert.Equal(t, mb.Messages[0], "seabass never said 'anything true'")
|
||||||
|
@ -274,12 +267,11 @@ func TestBabblerBadMiddleOutSeed(t *testing.T) {
|
||||||
func TestBabblerBatch(t *testing.T) {
|
func TestBabblerBatch(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
bp := newBabblerPlugin(mb)
|
bp := newBabblerPlugin(mb)
|
||||||
bp.config.Babbler.DefaultUsers = []string{"seabass"}
|
|
||||||
assert.NotNil(t, bp)
|
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?")
|
c, k, 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)
|
res := bp.message(c, k, seabass)
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
res = bp.Message(makeMessage("!seabass says"))
|
res = bp.message(makeMessage("!seabass says"))
|
||||||
assert.Len(t, mb.Messages, 2)
|
assert.Len(t, mb.Messages, 2)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Contains(t, mb.Messages[1], "this is")
|
assert.Contains(t, mb.Messages[1], "this is")
|
||||||
|
@ -289,26 +281,25 @@ func TestBabblerBatch(t *testing.T) {
|
||||||
func TestBabblerMerge(t *testing.T) {
|
func TestBabblerMerge(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
bp := newBabblerPlugin(mb)
|
bp := newBabblerPlugin(mb)
|
||||||
bp.config.Babbler.DefaultUsers = []string{"seabass"}
|
|
||||||
assert.NotNil(t, bp)
|
assert.NotNil(t, bp)
|
||||||
|
|
||||||
seabass := makeMessage("<seabass> This is a message")
|
c, k, seabass := makeMessage("<seabass> This is a message")
|
||||||
seabass.User = &user.User{Name: "seabass"}
|
seabass.User = &user.User{Name: "seabass"}
|
||||||
res := bp.Message(seabass)
|
res := bp.message(c, k, seabass)
|
||||||
assert.Len(t, mb.Messages, 0)
|
assert.Len(t, mb.Messages, 0)
|
||||||
|
|
||||||
seabass.Body = "<seabass> This is another message"
|
seabass.Body = "<seabass> This is another message"
|
||||||
res = bp.Message(seabass)
|
res = bp.message(c, k, seabass)
|
||||||
|
|
||||||
seabass.Body = "<seabass> This is a long message"
|
seabass.Body = "<seabass> This is a long message"
|
||||||
res = bp.Message(seabass)
|
res = bp.message(c, k, seabass)
|
||||||
|
|
||||||
res = bp.Message(makeMessage("!merge babbler seabass into seabass2"))
|
res = bp.message(makeMessage("!merge babbler seabass into seabass2"))
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.Contains(t, mb.Messages[0], "mooooiggged")
|
assert.Contains(t, mb.Messages[0], "mooooiggged")
|
||||||
|
|
||||||
res = bp.Message(makeMessage("!seabass2 says"))
|
res = bp.message(makeMessage("!seabass2 says"))
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Len(t, mb.Messages, 2)
|
assert.Len(t, mb.Messages, 2)
|
||||||
|
|
||||||
|
@ -320,27 +311,7 @@ func TestHelp(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
bp := newBabblerPlugin(mb)
|
bp := newBabblerPlugin(mb)
|
||||||
assert.NotNil(t, bp)
|
assert.NotNil(t, bp)
|
||||||
bp.Help("channel", []string{})
|
c := &cli.CliPlugin{}
|
||||||
|
bp.help(c, bot.Help, msg.Message{Channel: "channel"}, []string{})
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBotMessage(t *testing.T) {
|
|
||||||
mb := bot.NewMockBot()
|
|
||||||
bp := newBabblerPlugin(mb)
|
|
||||||
assert.NotNil(t, bp)
|
|
||||||
assert.False(t, bp.BotMessage(makeMessage("test")))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvent(t *testing.T) {
|
|
||||||
mb := bot.NewMockBot()
|
|
||||||
bp := newBabblerPlugin(mb)
|
|
||||||
assert.NotNil(t, bp)
|
|
||||||
assert.False(t, bp.Event("dummy", makeMessage("test")))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRegisterWeb(t *testing.T) {
|
|
||||||
mb := bot.NewMockBot()
|
|
||||||
bp := newBabblerPlugin(mb)
|
|
||||||
assert.NotNil(t, bp)
|
|
||||||
assert.Nil(t, bp.RegisterWeb())
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -15,6 +14,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
"github.com/velour/catbase/plugins/counter"
|
"github.com/velour/catbase/plugins/counter"
|
||||||
|
@ -37,34 +37,33 @@ type untappdUser struct {
|
||||||
chanNick string
|
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 {
|
func New(b bot.Bot) *BeersPlugin {
|
||||||
if bot.DBVersion() == 1 {
|
if _, err := b.DB().Exec(`create table if not exists untappd (
|
||||||
if _, err := bot.DB().Exec(`create table if not exists untappd (
|
|
||||||
id integer primary key,
|
id integer primary key,
|
||||||
untappdUser string,
|
untappdUser string,
|
||||||
channel string,
|
channel string,
|
||||||
lastCheckin integer,
|
lastCheckin integer,
|
||||||
chanNick string
|
chanNick string
|
||||||
);`); err != nil {
|
);`); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal().Err(err)
|
||||||
}
|
}
|
||||||
|
p := &BeersPlugin{
|
||||||
|
Bot: b,
|
||||||
|
db: b.DB(),
|
||||||
}
|
}
|
||||||
p := BeersPlugin{
|
for _, channel := range b.Config().GetArray("Untappd.Channels", []string{}) {
|
||||||
Bot: bot,
|
go p.untappdLoop(b.DefaultConnector(), channel)
|
||||||
db: bot.DB(),
|
|
||||||
}
|
}
|
||||||
p.LoadData()
|
b.Register(p, bot.Message, p.message)
|
||||||
for _, channel := range bot.Config().Untappd.Channels {
|
b.Register(p, bot.Help, p.help)
|
||||||
go p.untappdLoop(channel)
|
return p
|
||||||
}
|
|
||||||
return &p
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Message responds to the bot hook on recieving messages.
|
// Message responds to the bot hook on recieving messages.
|
||||||
// This function returns true if the plugin responds in a meaningful way to the users message.
|
// 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.
|
// Otherwise, the function returns false and the bot continues execution of other plugins.
|
||||||
func (p *BeersPlugin) Message(message msg.Message) bool {
|
func (p *BeersPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
parts := strings.Fields(message.Body)
|
parts := strings.Fields(message.Body)
|
||||||
|
|
||||||
if len(parts) == 0 {
|
if len(parts) == 0 {
|
||||||
|
@ -84,49 +83,49 @@ func (p *BeersPlugin) Message(message msg.Message) bool {
|
||||||
count, err := strconv.Atoi(parts[2])
|
count, err := strconv.Atoi(parts[2])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// if it's not a number, maybe it's a nick!
|
// if it's not a number, maybe it's a nick!
|
||||||
p.Bot.SendMessage(channel, "Sorry, that didn't make any sense.")
|
p.Bot.Send(c, bot.Message, channel, "Sorry, that didn't make any sense.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if count < 0 {
|
if count < 0 {
|
||||||
// you can't be negative
|
// you can't be negative
|
||||||
msg := fmt.Sprintf("Sorry %s, you can't have negative beers!", nick)
|
msg := fmt.Sprintf("Sorry %s, you can't have negative beers!", nick)
|
||||||
p.Bot.SendMessage(channel, msg)
|
p.Bot.Send(c, bot.Message, channel, msg)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if parts[1] == "+=" {
|
if parts[1] == "+=" {
|
||||||
p.addBeers(nick, count)
|
p.addBeers(nick, count)
|
||||||
p.randomReply(channel)
|
p.randomReply(c, channel)
|
||||||
} else if parts[1] == "=" {
|
} else if parts[1] == "=" {
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
p.puke(nick, channel)
|
p.puke(c, nick, channel)
|
||||||
} else {
|
} else {
|
||||||
p.setBeers(nick, count)
|
p.setBeers(nick, count)
|
||||||
p.randomReply(channel)
|
p.randomReply(c, channel)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
p.Bot.SendMessage(channel, "I don't know your math.")
|
p.Bot.Send(c, bot.Message, channel, "I don't know your math.")
|
||||||
}
|
}
|
||||||
} else if len(parts) == 2 {
|
} else if len(parts) == 2 {
|
||||||
if p.doIKnow(parts[1]) {
|
if p.doIKnow(parts[1]) {
|
||||||
p.reportCount(parts[1], channel, false)
|
p.reportCount(c, parts[1], channel, false)
|
||||||
} else {
|
} else {
|
||||||
msg := fmt.Sprintf("Sorry, I don't know %s.", parts[1])
|
msg := fmt.Sprintf("Sorry, I don't know %s.", parts[1])
|
||||||
p.Bot.SendMessage(channel, msg)
|
p.Bot.Send(c, bot.Message, channel, msg)
|
||||||
}
|
}
|
||||||
} else if len(parts) == 1 {
|
} else if len(parts) == 1 {
|
||||||
p.reportCount(nick, channel, true)
|
p.reportCount(c, nick, channel, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// no matter what, if we're in here, then we've responded
|
// no matter what, if we're in here, then we've responded
|
||||||
return true
|
return true
|
||||||
} else if parts[0] == "puke" {
|
} else if parts[0] == "puke" {
|
||||||
p.puke(nick, channel)
|
p.puke(c, nick, channel)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if message.Command && parts[0] == "imbibe" {
|
if message.Command && parts[0] == "imbibe" {
|
||||||
p.addBeers(nick, 1)
|
p.addBeers(nick, 1)
|
||||||
p.randomReply(channel)
|
p.randomReply(c, channel)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +134,7 @@ func (p *BeersPlugin) Message(message msg.Message) bool {
|
||||||
channel := message.Channel
|
channel := message.Channel
|
||||||
|
|
||||||
if len(parts) < 2 {
|
if len(parts) < 2 {
|
||||||
p.Bot.SendMessage(channel, "You must also provide a user name.")
|
p.Bot.Send(c, bot.Message, channel, "You must also provide a user name.")
|
||||||
} else if len(parts) == 3 {
|
} else if len(parts) == 3 {
|
||||||
chanNick = parts[2]
|
chanNick = parts[2]
|
||||||
} else if len(parts) == 4 {
|
} else if len(parts) == 4 {
|
||||||
|
@ -148,16 +147,19 @@ func (p *BeersPlugin) Message(message msg.Message) bool {
|
||||||
channel: channel,
|
channel: channel,
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Creating Untappd user:", u.untappdUser, "nick:", u.chanNick)
|
log.Info().
|
||||||
|
Str("untappdUser", u.untappdUser).
|
||||||
|
Str("nick", u.chanNick).
|
||||||
|
Msg("Creating Untappd user")
|
||||||
|
|
||||||
var count int
|
var count int
|
||||||
err := p.db.QueryRow(`select count(*) from untappd
|
err := p.db.QueryRow(`select count(*) from untappd
|
||||||
where untappdUser = ?`, u.untappdUser).Scan(&count)
|
where untappdUser = ?`, u.untappdUser).Scan(&count)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error registering untappd: ", err)
|
log.Error().Err(err).Msgf("Error registering untappd")
|
||||||
}
|
}
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
p.Bot.SendMessage(channel, "I'm already watching you.")
|
p.Bot.Send(c, bot.Message, channel, "I'm already watching you.")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
_, err = p.db.Exec(`insert into untappd (
|
_, err = p.db.Exec(`insert into untappd (
|
||||||
|
@ -172,45 +174,36 @@ func (p *BeersPlugin) Message(message msg.Message) bool {
|
||||||
u.chanNick,
|
u.chanNick,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error registering untappd: ", err)
|
log.Error().Err(err).Msgf("Error registering untappd")
|
||||||
p.Bot.SendMessage(channel, "I can't see.")
|
p.Bot.Send(c, bot.Message, channel, "I can't see.")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Bot.SendMessage(channel, "I'll be watching you.")
|
p.Bot.Send(c, bot.Message, channel, "I'll be watching you.")
|
||||||
|
|
||||||
p.checkUntappd(channel)
|
p.checkUntappd(c, channel)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if message.Command && parts[0] == "checkuntappd" {
|
if message.Command && parts[0] == "checkuntappd" {
|
||||||
log.Println("Checking untappd at request of user.")
|
log.Info().
|
||||||
p.checkUntappd(channel)
|
Str("user", message.User.Name).
|
||||||
|
Msgf("Checking untappd at request of user.")
|
||||||
|
p.checkUntappd(c, channel)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty event handler because this plugin does not do anything on event recv
|
|
||||||
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.
|
// Help responds to help requests. Every plugin must implement a help function.
|
||||||
func (p *BeersPlugin) Help(channel string, parts []string) {
|
func (p *BeersPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
msg := "Beers: imbibe by using either beers +=,=,++ or with the !imbibe/drink " +
|
msg := "Beers: imbibe by using either beers +=,=,++ or with the !imbibe/drink " +
|
||||||
"commands. I'll keep a count of how many beers you've had and then if you want " +
|
"commands. I'll keep a count of how many beers you've had and then if you want " +
|
||||||
"to reset, just !puke it all up!"
|
"to reset, just !puke it all up!"
|
||||||
p.Bot.SendMessage(channel, msg)
|
p.Bot.Send(c, bot.Message, message.Channel, msg)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUserBeers(db *sqlx.DB, user string) counter.Item {
|
func getUserBeers(db *sqlx.DB, user string) counter.Item {
|
||||||
|
@ -222,7 +215,7 @@ func (p *BeersPlugin) setBeers(user string, amount int) {
|
||||||
ub := getUserBeers(p.db, user)
|
ub := getUserBeers(p.db, user)
|
||||||
err := ub.Update(amount)
|
err := ub.Update(amount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error saving beers: ", err)
|
log.Error().Err(err).Msgf("Error saving beers")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,7 +223,7 @@ func (p *BeersPlugin) addBeers(user string, delta int) {
|
||||||
ub := getUserBeers(p.db, user)
|
ub := getUserBeers(p.db, user)
|
||||||
err := ub.UpdateDelta(delta)
|
err := ub.UpdateDelta(delta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error saving beers: ", err)
|
log.Error().Err(err).Msgf("Error saving beers")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,7 +232,7 @@ func (p *BeersPlugin) getBeers(nick string) int {
|
||||||
return ub.Count
|
return ub.Count
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *BeersPlugin) reportCount(nick, channel string, himself bool) {
|
func (p *BeersPlugin) reportCount(c bot.Connector, nick, channel string, himself bool) {
|
||||||
beers := p.getBeers(nick)
|
beers := p.getBeers(nick)
|
||||||
msg := fmt.Sprintf("%s has had %d beers so far.", nick, beers)
|
msg := fmt.Sprintf("%s has had %d beers so far.", nick, beers)
|
||||||
if himself {
|
if himself {
|
||||||
|
@ -249,13 +242,13 @@ func (p *BeersPlugin) reportCount(nick, channel string, himself bool) {
|
||||||
msg = fmt.Sprintf("You've had %d beers so far, %s.", beers, nick)
|
msg = fmt.Sprintf("You've had %d beers so far, %s.", beers, nick)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.Bot.SendMessage(channel, msg)
|
p.Bot.Send(c, bot.Message, channel, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *BeersPlugin) puke(user string, channel string) {
|
func (p *BeersPlugin) puke(c bot.Connector, user string, channel string) {
|
||||||
p.setBeers(user, 0)
|
p.setBeers(user, 0)
|
||||||
msg := fmt.Sprintf("Ohhhhhh, and a reversal of fortune for %s!", user)
|
msg := fmt.Sprintf("Ohhhhhh, and a reversal of fortune for %s!", user)
|
||||||
p.Bot.SendMessage(channel, msg)
|
p.Bot.Send(c, bot.Message, channel, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *BeersPlugin) doIKnow(nick string) bool {
|
func (p *BeersPlugin) doIKnow(nick string) bool {
|
||||||
|
@ -268,9 +261,9 @@ func (p *BeersPlugin) doIKnow(nick string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sends random affirmation to the channel. This could be better (with a datastore for sayings)
|
// Sends random affirmation to the channel. This could be better (with a datastore for sayings)
|
||||||
func (p *BeersPlugin) randomReply(channel string) {
|
func (p *BeersPlugin) randomReply(c bot.Connector, channel string) {
|
||||||
replies := []string{"ZIGGY! ZAGGY!", "HIC!", "Stay thirsty, my friend!"}
|
replies := []string{"ZIGGY! ZAGGY!", "HIC!", "Stay thirsty, my friend!"}
|
||||||
p.Bot.SendMessage(channel, replies[rand.Intn(len(replies))])
|
p.Bot.Send(c, bot.Message, channel, replies[rand.Intn(len(replies))])
|
||||||
}
|
}
|
||||||
|
|
||||||
type checkin struct {
|
type checkin struct {
|
||||||
|
@ -316,7 +309,12 @@ type Beers struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *BeersPlugin) pullUntappd() ([]checkin, error) {
|
func (p *BeersPlugin) pullUntappd() ([]checkin, error) {
|
||||||
access_token := "?access_token=" + p.Bot.Config().Untappd.Token
|
token := p.Bot.Config().Get("Untappd.Token", "NONE")
|
||||||
|
if token == "NONE" {
|
||||||
|
return []checkin{}, fmt.Errorf("No untappd token")
|
||||||
|
}
|
||||||
|
|
||||||
|
access_token := "?access_token=" + token
|
||||||
baseUrl := "https://api.untappd.com/v4/checkin/recent/"
|
baseUrl := "https://api.untappd.com/v4/checkin/recent/"
|
||||||
|
|
||||||
url := baseUrl + access_token + "&limit=25"
|
url := baseUrl + access_token + "&limit=25"
|
||||||
|
@ -332,55 +330,54 @@ func (p *BeersPlugin) pullUntappd() ([]checkin, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode == 500 {
|
if resp.StatusCode == 500 {
|
||||||
log.Printf("Error querying untappd: %s, %s", resp.Status, body)
|
log.Error().Msgf("Error querying untappd: %s, %s", resp.Status, body)
|
||||||
return []checkin{}, errors.New(resp.Status)
|
return []checkin{}, errors.New(resp.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
var beers Beers
|
var beers Beers
|
||||||
err = json.Unmarshal(body, &beers)
|
err = json.Unmarshal(body, &beers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Error().Err(err)
|
||||||
return []checkin{}, err
|
return []checkin{}, err
|
||||||
}
|
}
|
||||||
return beers.Response.Checkins.Items, nil
|
return beers.Response.Checkins.Items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *BeersPlugin) checkUntappd(channel string) {
|
func (p *BeersPlugin) checkUntappd(c bot.Connector, channel string) {
|
||||||
token := p.Bot.Config().Untappd.Token
|
token := p.Bot.Config().Get("Untappd.Token", "NONE")
|
||||||
if token == "" || token == "<Your Token>" {
|
if token == "NONE" {
|
||||||
log.Println("No Untappd token, cannot enable plugin.")
|
log.Info().
|
||||||
|
Msg(`Set config value "untappd.token" if you wish to enable untappd`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
userMap := make(map[string]untappdUser)
|
userMap := make(map[string]untappdUser)
|
||||||
rows, err := p.db.Query(`select id, untappdUser, channel, lastCheckin, chanNick from untappd;`)
|
rows, err := p.db.Query(`select id, untappdUser, channel, lastCheckin, chanNick from untappd;`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error getting untappd users: ", err)
|
log.Error().Err(err).Msg("Error getting untappd users")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
u := untappdUser{}
|
u := untappdUser{}
|
||||||
err := rows.Scan(&u.id, &u.untappdUser, &u.channel, &u.lastCheckin, &u.chanNick)
|
err := rows.Scan(&u.id, &u.untappdUser, &u.channel, &u.lastCheckin, &u.chanNick)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal().Err(err)
|
||||||
}
|
}
|
||||||
userMap[u.untappdUser] = u
|
userMap[u.untappdUser] = u
|
||||||
log.Printf("Found untappd user: %#v", u)
|
|
||||||
if u.chanNick == "" {
|
if u.chanNick == "" {
|
||||||
log.Fatal("Empty chanNick for no good reason.")
|
log.Fatal().Msg("Empty chanNick for no good reason.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
chks, err := p.pullUntappd()
|
chks, err := p.pullUntappd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Untappd ERROR: ", err)
|
log.Error().Err(err).Msg("Untappd ERROR")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for i := len(chks); i > 0; i-- {
|
for i := len(chks); i > 0; i-- {
|
||||||
checkin := chks[i-1]
|
checkin := chks[i-1]
|
||||||
|
|
||||||
if checkin.Checkin_id <= userMap[checkin.User.User_name].lastCheckin {
|
if checkin.Checkin_id <= userMap[checkin.User.User_name].lastCheckin {
|
||||||
log.Printf("User %s already check in >%d", checkin.User.User_name, checkin.Checkin_id)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -395,7 +392,8 @@ func (p *BeersPlugin) checkUntappd(channel string) {
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
log.Printf("user.chanNick: %s, user.untappdUser: %s, checkin.User.User_name: %s",
|
log.Debug().
|
||||||
|
Msgf("user.chanNick: %s, user.untappdUser: %s, checkin.User.User_name: %s",
|
||||||
user.chanNick, user.untappdUser, checkin.User.User_name)
|
user.chanNick, user.untappdUser, checkin.User.User_name)
|
||||||
p.addBeers(user.chanNick, 1)
|
p.addBeers(user.chanNick, 1)
|
||||||
drunken := p.getBeers(user.chanNick)
|
drunken := p.getBeers(user.chanNick)
|
||||||
|
@ -410,11 +408,18 @@ func (p *BeersPlugin) checkUntappd(channel string) {
|
||||||
msg, checkin.Checkin_comment)
|
msg, checkin.Checkin_comment)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
args := []interface{}{
|
||||||
|
channel,
|
||||||
|
msg,
|
||||||
|
}
|
||||||
if checkin.Media.Count > 0 {
|
if checkin.Media.Count > 0 {
|
||||||
if strings.Contains(checkin.Media.Items[0].Photo.Photo_img_lg, "photos-processing") {
|
if strings.Contains(checkin.Media.Items[0].Photo.Photo_img_lg, "photos-processing") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
msg += "\nHere's a photo: " + checkin.Media.Items[0].Photo.Photo_img_lg
|
args = append(args, bot.ImageAttachment{
|
||||||
|
URL: checkin.Media.Items[0].Photo.Photo_img_lg,
|
||||||
|
AltTxt: "Here's a photo",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
user.lastCheckin = checkin.Checkin_id
|
user.lastCheckin = checkin.Checkin_id
|
||||||
|
@ -422,33 +427,27 @@ func (p *BeersPlugin) checkUntappd(channel string) {
|
||||||
lastCheckin = ?
|
lastCheckin = ?
|
||||||
where id = ?`, user.lastCheckin, user.id)
|
where id = ?`, user.lastCheckin, user.id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("UPDATE ERROR!:", err)
|
log.Error().Err(err).Msg("UPDATE ERROR!")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("checkin id:", checkin.Checkin_id, "Message:", msg)
|
log.Debug().
|
||||||
p.Bot.SendMessage(channel, msg)
|
Int("checkin_id", checkin.Checkin_id).
|
||||||
|
Str("msg", msg).
|
||||||
|
Msg("checkin")
|
||||||
|
p.Bot.Send(c, bot.Message, args...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *BeersPlugin) untappdLoop(channel string) {
|
func (p *BeersPlugin) untappdLoop(c bot.Connector, channel string) {
|
||||||
frequency := p.Bot.Config().Untappd.Freq
|
frequency := p.Bot.Config().GetInt("Untappd.Freq", 120)
|
||||||
|
if frequency == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
log.Println("Checking every ", frequency, " seconds")
|
log.Info().Msgf("Checking every %v seconds", frequency)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
time.Sleep(time.Duration(frequency) * time.Second)
|
time.Sleep(time.Duration(frequency) * time.Second)
|
||||||
p.checkUntappd(channel)
|
p.checkUntappd(c, channel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler for bot's own messages
|
|
||||||
func (p *BeersPlugin) BotMessage(message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register any web URLs desired
|
|
||||||
func (p *BeersPlugin) RegisterWeb() *string {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *BeersPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
package beers
|
package beers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -13,12 +14,13 @@ import (
|
||||||
"github.com/velour/catbase/plugins/counter"
|
"github.com/velour/catbase/plugins/counter"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeMessage(payload string) msg.Message {
|
func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) {
|
||||||
isCmd := strings.HasPrefix(payload, "!")
|
isCmd := strings.HasPrefix(payload, "!")
|
||||||
if isCmd {
|
if isCmd {
|
||||||
payload = payload[1:]
|
payload = payload[1:]
|
||||||
}
|
}
|
||||||
return msg.Message{
|
c := &cli.CliPlugin{}
|
||||||
|
return c, bot.Message, msg.Message{
|
||||||
User: &user.User{Name: "tester"},
|
User: &user.User{Name: "tester"},
|
||||||
Channel: "test",
|
Channel: "test",
|
||||||
Body: payload,
|
Body: payload,
|
||||||
|
@ -29,18 +31,29 @@ func makeMessage(payload string) msg.Message {
|
||||||
func makeBeersPlugin(t *testing.T) (*BeersPlugin, *bot.MockBot) {
|
func makeBeersPlugin(t *testing.T) (*BeersPlugin, *bot.MockBot) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
counter.New(mb)
|
counter.New(mb)
|
||||||
|
mb.DB().MustExec(`delete from counter; delete from counter_alias;`)
|
||||||
b := New(mb)
|
b := New(mb)
|
||||||
assert.NotNil(t, b)
|
b.message(makeMessage("!mkalias beer :beer:"))
|
||||||
b.Message(makeMessage("!mkalias beer :beer:"))
|
b.message(makeMessage("!mkalias beers :beer:"))
|
||||||
b.Message(makeMessage("!mkalias beers :beer:"))
|
|
||||||
return b, mb
|
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) {
|
func TestImbibe(t *testing.T) {
|
||||||
b, mb := makeBeersPlugin(t)
|
b, mb := makeBeersPlugin(t)
|
||||||
b.Message(makeMessage("!imbibe"))
|
b.message(makeMessage("!imbibe"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
b.Message(makeMessage("!imbibe"))
|
b.message(makeMessage("!imbibe"))
|
||||||
assert.Len(t, mb.Messages, 2)
|
assert.Len(t, mb.Messages, 2)
|
||||||
it, err := counter.GetItem(mb.DB(), "tester", itemName)
|
it, err := counter.GetItem(mb.DB(), "tester", itemName)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
@ -48,7 +61,7 @@ func TestImbibe(t *testing.T) {
|
||||||
}
|
}
|
||||||
func TestEq(t *testing.T) {
|
func TestEq(t *testing.T) {
|
||||||
b, mb := makeBeersPlugin(t)
|
b, mb := makeBeersPlugin(t)
|
||||||
b.Message(makeMessage("!beers = 3"))
|
b.message(makeMessage("!beers = 3"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
it, err := counter.GetItem(mb.DB(), "tester", itemName)
|
it, err := counter.GetItem(mb.DB(), "tester", itemName)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
@ -57,7 +70,7 @@ func TestEq(t *testing.T) {
|
||||||
|
|
||||||
func TestEqNeg(t *testing.T) {
|
func TestEqNeg(t *testing.T) {
|
||||||
b, mb := makeBeersPlugin(t)
|
b, mb := makeBeersPlugin(t)
|
||||||
b.Message(makeMessage("!beers = -3"))
|
b.message(makeMessage("!beers = -3"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
it, err := counter.GetItem(mb.DB(), "tester", itemName)
|
it, err := counter.GetItem(mb.DB(), "tester", itemName)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
@ -66,8 +79,8 @@ func TestEqNeg(t *testing.T) {
|
||||||
|
|
||||||
func TestEqZero(t *testing.T) {
|
func TestEqZero(t *testing.T) {
|
||||||
b, mb := makeBeersPlugin(t)
|
b, mb := makeBeersPlugin(t)
|
||||||
b.Message(makeMessage("beers += 5"))
|
b.message(makeMessage("beers += 5"))
|
||||||
b.Message(makeMessage("!beers = 0"))
|
b.message(makeMessage("!beers = 0"))
|
||||||
assert.Len(t, mb.Messages, 2)
|
assert.Len(t, mb.Messages, 2)
|
||||||
assert.Contains(t, mb.Messages[1], "reversal of fortune")
|
assert.Contains(t, mb.Messages[1], "reversal of fortune")
|
||||||
it, err := counter.GetItem(mb.DB(), "tester", itemName)
|
it, err := counter.GetItem(mb.DB(), "tester", itemName)
|
||||||
|
@ -77,9 +90,9 @@ func TestEqZero(t *testing.T) {
|
||||||
|
|
||||||
func TestBeersPlusEq(t *testing.T) {
|
func TestBeersPlusEq(t *testing.T) {
|
||||||
b, mb := makeBeersPlugin(t)
|
b, mb := makeBeersPlugin(t)
|
||||||
b.Message(makeMessage("beers += 5"))
|
b.message(makeMessage("beers += 5"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
b.Message(makeMessage("beers += 5"))
|
b.message(makeMessage("beers += 5"))
|
||||||
assert.Len(t, mb.Messages, 2)
|
assert.Len(t, mb.Messages, 2)
|
||||||
it, err := counter.GetItem(mb.DB(), "tester", itemName)
|
it, err := counter.GetItem(mb.DB(), "tester", itemName)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
@ -88,11 +101,11 @@ func TestBeersPlusEq(t *testing.T) {
|
||||||
|
|
||||||
func TestPuke(t *testing.T) {
|
func TestPuke(t *testing.T) {
|
||||||
b, mb := makeBeersPlugin(t)
|
b, mb := makeBeersPlugin(t)
|
||||||
b.Message(makeMessage("beers += 5"))
|
b.message(makeMessage("beers += 5"))
|
||||||
it, err := counter.GetItem(mb.DB(), "tester", itemName)
|
it, err := counter.GetItem(mb.DB(), "tester", itemName)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 5, it.Count)
|
assert.Equal(t, 5, it.Count)
|
||||||
b.Message(makeMessage("puke"))
|
b.message(makeMessage("puke"))
|
||||||
it, err = counter.GetItem(mb.DB(), "tester", itemName)
|
it, err = counter.GetItem(mb.DB(), "tester", itemName)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 0, it.Count)
|
assert.Equal(t, 0, it.Count)
|
||||||
|
@ -100,31 +113,16 @@ func TestPuke(t *testing.T) {
|
||||||
|
|
||||||
func TestBeersReport(t *testing.T) {
|
func TestBeersReport(t *testing.T) {
|
||||||
b, mb := makeBeersPlugin(t)
|
b, mb := makeBeersPlugin(t)
|
||||||
b.Message(makeMessage("beers += 5"))
|
b.message(makeMessage("beers += 5"))
|
||||||
it, err := counter.GetItem(mb.DB(), "tester", itemName)
|
it, err := counter.GetItem(mb.DB(), "tester", itemName)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 5, it.Count)
|
assert.Equal(t, 5, it.Count)
|
||||||
b.Message(makeMessage("beers"))
|
b.message(makeMessage("beers"))
|
||||||
assert.Contains(t, mb.Messages[1], "5 beers")
|
assert.Contains(t, mb.Messages[1], "5 beers")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHelp(t *testing.T) {
|
func TestHelp(t *testing.T) {
|
||||||
b, mb := makeBeersPlugin(t)
|
b, mb := makeBeersPlugin(t)
|
||||||
b.Help("channel", []string{})
|
b.help(&cli.CliPlugin{}, bot.Help, msg.Message{Channel: "channel"}, []string{})
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBotMessage(t *testing.T) {
|
|
||||||
b, _ := makeBeersPlugin(t)
|
|
||||||
assert.False(t, b.BotMessage(makeMessage("test")))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvent(t *testing.T) {
|
|
||||||
b, _ := makeBeersPlugin(t)
|
|
||||||
assert.False(t, b.Event("dummy", makeMessage("test")))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRegisterWeb(t *testing.T) {
|
|
||||||
b, _ := makeBeersPlugin(t)
|
|
||||||
assert.Nil(t, b.RegisterWeb())
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
// © 2013 the CatBase Authors under the WTFPL. See AUTHORS for the list of authors.
|
||||||
|
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/velour/catbase/bot"
|
||||||
|
"github.com/velour/catbase/bot/msg"
|
||||||
|
"github.com/velour/catbase/bot/user"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CliPlugin struct {
|
||||||
|
bot bot.Bot
|
||||||
|
db *sqlx.DB
|
||||||
|
cache string
|
||||||
|
counter int
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(b bot.Bot) *CliPlugin {
|
||||||
|
cp := &CliPlugin{
|
||||||
|
bot: b,
|
||||||
|
}
|
||||||
|
cp.registerWeb()
|
||||||
|
return cp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CliPlugin) registerWeb() {
|
||||||
|
http.HandleFunc("/cli/api", p.handleWebAPI)
|
||||||
|
http.HandleFunc("/cli", p.handleWeb)
|
||||||
|
p.bot.RegisterWeb("/cli", "CLI")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CliPlugin) handleWebAPI(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
fmt.Fprintf(w, "Incorrect HTTP method")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
info := struct {
|
||||||
|
User string `json:"user"`
|
||||||
|
Payload string `json:"payload"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}{}
|
||||||
|
decoder := json.NewDecoder(r.Body)
|
||||||
|
err := decoder.Decode(&info)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
fmt.Fprint(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debug().
|
||||||
|
Interface("postbody", info).
|
||||||
|
Msg("Got a POST")
|
||||||
|
if info.Password != p.bot.GetPassword() {
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
j, _ := json.Marshal(struct{ Err string }{Err: "Invalid Password"})
|
||||||
|
w.Write(j)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p.bot.Receive(p, bot.Message, msg.Message{
|
||||||
|
User: &user.User{
|
||||||
|
ID: info.User,
|
||||||
|
Name: info.User,
|
||||||
|
Admin: false,
|
||||||
|
},
|
||||||
|
Channel: "web",
|
||||||
|
Body: info.Payload,
|
||||||
|
Raw: info.Payload,
|
||||||
|
Command: true,
|
||||||
|
Time: time.Now(),
|
||||||
|
})
|
||||||
|
|
||||||
|
info.User = p.bot.WhoAmI()
|
||||||
|
info.Payload = p.cache
|
||||||
|
p.cache = ""
|
||||||
|
|
||||||
|
data, err := json.Marshal(info)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
fmt.Fprint(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
var tpl = template.Must(template.New("factoidIndex").Parse(indexHTML))
|
||||||
|
|
||||||
|
func (p *CliPlugin) handleWeb(w http.ResponseWriter, r *http.Request) {
|
||||||
|
tpl.Execute(w, struct{ Nav []bot.EndPoint }{p.bot.GetWebNavigation()})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Completing the Connector interface, but will not actually be a connector
|
||||||
|
func (p *CliPlugin) RegisterEvent(cb bot.Callback) {}
|
||||||
|
func (p *CliPlugin) Send(kind bot.Kind, args ...interface{}) (string, error) {
|
||||||
|
switch kind {
|
||||||
|
case bot.Message:
|
||||||
|
fallthrough
|
||||||
|
case bot.Action:
|
||||||
|
fallthrough
|
||||||
|
case bot.Reply:
|
||||||
|
fallthrough
|
||||||
|
case bot.Reaction:
|
||||||
|
p.cache += args[1].(string) + "\n"
|
||||||
|
}
|
||||||
|
id := fmt.Sprintf("%d", p.counter)
|
||||||
|
p.counter++
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
func (p *CliPlugin) GetEmojiList() map[string]string { return nil }
|
||||||
|
func (p *CliPlugin) Serve() error { return nil }
|
||||||
|
func (p *CliPlugin) Who(s string) []string { return nil }
|
|
@ -0,0 +1,129 @@
|
||||||
|
package cli
|
||||||
|
|
||||||
|
var indexHTML = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<!-- Load required Bootstrap and BootstrapVue CSS -->
|
||||||
|
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
|
||||||
|
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.css" />
|
||||||
|
|
||||||
|
<!-- Load polyfills to support older browsers -->
|
||||||
|
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CMutationObserver"></script>
|
||||||
|
|
||||||
|
<!-- Load Vue followed by BootstrapVue -->
|
||||||
|
<script src="//unpkg.com/vue@latest/dist/vue.min.js"></script>
|
||||||
|
<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.js"></script>
|
||||||
|
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>CLI</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="app">
|
||||||
|
<b-navbar>
|
||||||
|
<b-navbar-brand>CLI</b-navbar-brand>
|
||||||
|
<b-navbar-nav>
|
||||||
|
<b-nav-item v-for="item in nav" :href="item.URL" :active="item.Name === 'CLI'">{{ "{{ item.Name }}" }}</b-nav-item>
|
||||||
|
</b-navbar-nav>
|
||||||
|
</b-navbar>
|
||||||
|
<b-alert
|
||||||
|
dismissable
|
||||||
|
variant="error"
|
||||||
|
:show="err">
|
||||||
|
{{ "{{ err }}" }}
|
||||||
|
</b-alert>
|
||||||
|
<b-container>
|
||||||
|
<b-row>
|
||||||
|
<b-col cols="5">Password:</b-col>
|
||||||
|
<b-col><b-input v-model="answer"></b-col>
|
||||||
|
</b-row>
|
||||||
|
<b-row>
|
||||||
|
<b-form-textarea
|
||||||
|
v-sticky-scroll
|
||||||
|
disabled
|
||||||
|
id="textarea"
|
||||||
|
v-model="text"
|
||||||
|
placeholder="The bot will respond here..."
|
||||||
|
rows="10"
|
||||||
|
max-rows="10"
|
||||||
|
no-resize
|
||||||
|
></b-form-textarea>
|
||||||
|
</b-row>
|
||||||
|
<b-form
|
||||||
|
@submit="send">
|
||||||
|
<b-row>
|
||||||
|
<b-col>
|
||||||
|
<b-form-input
|
||||||
|
type="text"
|
||||||
|
placeholder="Username"
|
||||||
|
v-model="user"></b-form-input>
|
||||||
|
</b-col>
|
||||||
|
<b-col>
|
||||||
|
<b-form-input
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter something to send to the bot"
|
||||||
|
v-model="input"
|
||||||
|
autocomplete="off"
|
||||||
|
></b-form-input>
|
||||||
|
</b-col>
|
||||||
|
<b-col>
|
||||||
|
<b-button type="submit" :disabled="!authenticated">Send</b-button>
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
</b-form>
|
||||||
|
</b-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var app = new Vue({
|
||||||
|
el: '#app',
|
||||||
|
data: {
|
||||||
|
err: '',
|
||||||
|
nav: {{ .Nav }},
|
||||||
|
answer: '',
|
||||||
|
correct: 0,
|
||||||
|
textarea: [],
|
||||||
|
user: '',
|
||||||
|
input: '',
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
authenticated: function() {
|
||||||
|
if (this.user !== '')
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
text: function() {
|
||||||
|
return this.textarea.join('\n');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
addText(user, text) {
|
||||||
|
this.textarea.push(user + ": " + text);
|
||||||
|
const len = this.textarea.length;
|
||||||
|
if (this.textarea.length > 10)
|
||||||
|
this.textarea = this.textarea.slice(len-10, len);
|
||||||
|
},
|
||||||
|
send(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
evt.stopPropagation()
|
||||||
|
if (!this.authenticated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const payload = {user: this.user, payload: this.input, password: this.answer};
|
||||||
|
this.addText(this.user, this.input);
|
||||||
|
this.input = "";
|
||||||
|
axios.post('/cli/api', payload)
|
||||||
|
.then(resp => {
|
||||||
|
const data = resp.data;
|
||||||
|
this.addText(data.user, data.payload.trim());
|
||||||
|
this.err = '';
|
||||||
|
})
|
||||||
|
.catch(err => (this.err = err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
|
@ -17,13 +17,15 @@ type CSWPlugin struct {
|
||||||
Config *config.Config
|
Config *config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(bot bot.Bot) *CSWPlugin {
|
func New(b bot.Bot) *CSWPlugin {
|
||||||
return &CSWPlugin{
|
csw := &CSWPlugin{
|
||||||
Bot: bot,
|
Bot: b,
|
||||||
}
|
}
|
||||||
|
b.Register(csw, bot.Message, csw.message)
|
||||||
|
return csw
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *CSWPlugin) Message(message msg.Message) bool {
|
func (p *CSWPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
if !message.Command {
|
if !message.Command {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -63,25 +65,9 @@ func (p *CSWPlugin) Message(message msg.Message) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Bot.SendMessage(message.Channel, responses[rand.Intn(len(responses))])
|
p.Bot.Send(c, bot.Message, message.Channel, responses[rand.Intn(len(responses))])
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *CSWPlugin) Help(channel string, parts []string) {}
|
|
||||||
|
|
||||||
func (p *CSWPlugin) Event(kind string, message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *CSWPlugin) BotMessage(message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *CSWPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
|
||||||
|
|
||||||
func (p *CSWPlugin) RegisterWeb() *string {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
package couldashouldawoulda
|
package couldashouldawoulda
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -12,12 +13,12 @@ import (
|
||||||
"github.com/velour/catbase/bot/user"
|
"github.com/velour/catbase/bot/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeMessage(payload string) msg.Message {
|
func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) {
|
||||||
isCmd := strings.HasPrefix(payload, "!")
|
isCmd := strings.HasPrefix(payload, "!")
|
||||||
if isCmd {
|
if isCmd {
|
||||||
payload = payload[1:]
|
payload = payload[1:]
|
||||||
}
|
}
|
||||||
return msg.Message{
|
return &cli.CliPlugin{}, bot.Message, msg.Message{
|
||||||
User: &user.User{Name: "tester"},
|
User: &user.User{Name: "tester"},
|
||||||
Channel: "test",
|
Channel: "test",
|
||||||
Body: payload,
|
Body: payload,
|
||||||
|
@ -29,7 +30,7 @@ func Test0(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
res := c.Message(makeMessage("!should I drink a beer?"))
|
res := c.message(makeMessage("!should I drink a beer?"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
possibilities := []string{"Yes.", "No.", "Maybe.", "For fucks sake, how should I know?"}
|
possibilities := []string{"Yes.", "No.", "Maybe.", "For fucks sake, how should I know?"}
|
||||||
|
@ -47,7 +48,7 @@ func Test1(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
res := c.Message(makeMessage("!should I drink a beer or a bourbon?"))
|
res := c.message(makeMessage("!should I drink a beer or a bourbon?"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
possibilities := []string{"The former.", "The latter.", "Obviously the former.", "Clearly the latter.", "Can't it be both?"}
|
possibilities := []string{"The former.", "The latter.", "Obviously the former.", "Clearly the latter.", "Can't it be both?"}
|
||||||
|
@ -65,7 +66,7 @@ func Test2(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
res := c.Message(makeMessage("!could I drink a beer or a bourbon?"))
|
res := c.message(makeMessage("!could I drink a beer or a bourbon?"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
possibilities := []string{"Yes.", "No.", "Maybe.", "For fucks sake, how should I know?"}
|
possibilities := []string{"Yes.", "No.", "Maybe.", "For fucks sake, how should I know?"}
|
||||||
|
@ -83,7 +84,7 @@ func Test3(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
res := c.Message(makeMessage("!would I die if I drank too much bourbon?"))
|
res := c.message(makeMessage("!would I die if I drank too much bourbon?"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
possibilities := []string{"Yes.", "No.", "Maybe.", "For fucks sake, how should I know?"}
|
possibilities := []string{"Yes.", "No.", "Maybe.", "For fucks sake, how should I know?"}
|
||||||
|
@ -101,7 +102,7 @@ func Test4(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
res := c.Message(makeMessage("!would I die or be sick if I drank all the bourbon?"))
|
res := c.message(makeMessage("!would I die or be sick if I drank all the bourbon?"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
possibilities := []string{"The former.", "The latter.", "Obviously the former.", "Clearly the latter.", "Can't it be both?"}
|
possibilities := []string{"The former.", "The latter.", "Obviously the former.", "Clearly the latter.", "Can't it be both?"}
|
||||||
|
@ -119,7 +120,7 @@ func Test5(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
res := c.Message(makeMessage("!should I have another beer or bourbon or tequila?"))
|
res := c.message(makeMessage("!should I have another beer or bourbon or tequila?"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
possibilities := []string{"I'd say option", "You'd be an idiot not to choose the"}
|
possibilities := []string{"I'd say option", "You'd be an idiot not to choose the"}
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
// © 2013 the CatBase Authors under the WTFPL. See AUTHORS for the list of authors.
|
|
||||||
|
|
||||||
package counter
|
package counter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"html/template"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
|
@ -41,6 +44,20 @@ type alias struct {
|
||||||
PointsTo string `db:"points_to"`
|
PointsTo string `db:"points_to"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetItems returns all counters
|
||||||
|
func GetAllItems(db *sqlx.DB) ([]Item, error) {
|
||||||
|
var items []Item
|
||||||
|
err := db.Select(&items, `select * from counter`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Don't forget to embed the DB into all of that shiz
|
||||||
|
for i := range items {
|
||||||
|
items[i].DB = db
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetItems returns all counters for a subject
|
// GetItems returns all counters for a subject
|
||||||
func GetItems(db *sqlx.DB, nick string) ([]Item, error) {
|
func GetItems(db *sqlx.DB, nick string) ([]Item, error) {
|
||||||
var items []Item
|
var items []Item
|
||||||
|
@ -111,7 +128,7 @@ func GetItem(db *sqlx.DB, nick, itemName string) (Item, error) {
|
||||||
if err := db.Get(&a, `select * from counter_alias where item=?`, itemName); err == nil {
|
if err := db.Get(&a, `select * from counter_alias where item=?`, itemName); err == nil {
|
||||||
itemName = a.PointsTo
|
itemName = a.PointsTo
|
||||||
} else {
|
} else {
|
||||||
log.Println(err, a)
|
log.Error().Err(err).Interface("alias", a)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := db.Get(&item, `select * from counter where nick = ? and item= ?`,
|
err := db.Get(&item, `select * from counter where nick = ? and item= ?`,
|
||||||
|
@ -125,7 +142,11 @@ func GetItem(db *sqlx.DB, nick, itemName string) (Item, error) {
|
||||||
default:
|
default:
|
||||||
return Item{}, err
|
return Item{}, err
|
||||||
}
|
}
|
||||||
log.Printf("Got item %s.%s: %#v", nick, itemName, item)
|
log.Debug().
|
||||||
|
Str("nick", nick).
|
||||||
|
Str("itemName", itemName).
|
||||||
|
Interface("item", item).
|
||||||
|
Msg("got item")
|
||||||
return item, nil
|
return item, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,6 +154,9 @@ func GetItem(db *sqlx.DB, nick, itemName string) (Item, error) {
|
||||||
func (i *Item) Create() error {
|
func (i *Item) Create() error {
|
||||||
res, err := i.Exec(`insert into counter (nick, item, count) values (?, ?, ?);`,
|
res, err := i.Exec(`insert into counter (nick, item, count) values (?, ?, ?);`,
|
||||||
i.Nick, i.Item, i.Count)
|
i.Nick, i.Item, i.Count)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
id, _ := res.LastInsertId()
|
id, _ := res.LastInsertId()
|
||||||
// hackhackhack?
|
// hackhackhack?
|
||||||
i.ID = id
|
i.ID = id
|
||||||
|
@ -149,7 +173,10 @@ func (i *Item) Update(value int) error {
|
||||||
if i.ID == -1 {
|
if i.ID == -1 {
|
||||||
i.Create()
|
i.Create()
|
||||||
}
|
}
|
||||||
log.Printf("Updating item: %#v, value: %d", i, value)
|
log.Debug().
|
||||||
|
Interface("i", i).
|
||||||
|
Int("value", value).
|
||||||
|
Msg("Updating item")
|
||||||
_, err := i.Exec(`update counter set count = ? where id = ?`, i.Count, i.ID)
|
_, err := i.Exec(`update counter set count = ? where id = ?`, i.Count, i.ID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -169,33 +196,35 @@ func (i *Item) Delete() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCounterPlugin creates a new CounterPlugin with the Plugin interface
|
// NewCounterPlugin creates a new CounterPlugin with the Plugin interface
|
||||||
func New(bot bot.Bot) *CounterPlugin {
|
func New(b bot.Bot) *CounterPlugin {
|
||||||
if _, err := bot.DB().Exec(`create table if not exists counter (
|
tx := b.DB().MustBegin()
|
||||||
|
b.DB().MustExec(`create table if not exists counter (
|
||||||
id integer primary key,
|
id integer primary key,
|
||||||
nick string,
|
nick string,
|
||||||
item string,
|
item string,
|
||||||
count integer
|
count integer
|
||||||
);`); err != nil {
|
);`)
|
||||||
log.Fatal(err)
|
b.DB().MustExec(`create table if not exists counter_alias (
|
||||||
}
|
|
||||||
if _, err := bot.DB().Exec(`create table if not exists counter_alias (
|
|
||||||
id integer PRIMARY KEY AUTOINCREMENT,
|
id integer PRIMARY KEY AUTOINCREMENT,
|
||||||
item string NOT NULL UNIQUE,
|
item string NOT NULL UNIQUE,
|
||||||
points_to string NOT NULL
|
points_to string NOT NULL
|
||||||
);`); err != nil {
|
);`)
|
||||||
log.Fatal(err)
|
tx.Commit()
|
||||||
}
|
cp := &CounterPlugin{
|
||||||
return &CounterPlugin{
|
Bot: b,
|
||||||
Bot: bot,
|
DB: b.DB(),
|
||||||
DB: bot.DB(),
|
|
||||||
}
|
}
|
||||||
|
b.Register(cp, bot.Message, cp.message)
|
||||||
|
b.Register(cp, bot.Help, cp.help)
|
||||||
|
cp.registerWeb()
|
||||||
|
return cp
|
||||||
}
|
}
|
||||||
|
|
||||||
// Message responds to the bot hook on recieving messages.
|
// Message responds to the bot hook on recieving messages.
|
||||||
// This function returns true if the plugin responds in a meaningful way to the
|
// 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
|
// users message. Otherwise, the function returns false and the bot continues
|
||||||
// execution of other plugins.
|
// execution of other plugins.
|
||||||
func (p *CounterPlugin) Message(message msg.Message) bool {
|
func (p *CounterPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
// This bot does not reply to anything
|
// This bot does not reply to anything
|
||||||
nick := message.User.Name
|
nick := message.User.Name
|
||||||
channel := message.Channel
|
channel := message.Channel
|
||||||
|
@ -207,10 +236,10 @@ func (p *CounterPlugin) Message(message msg.Message) bool {
|
||||||
|
|
||||||
if len(parts) == 3 && strings.ToLower(parts[0]) == "mkalias" {
|
if len(parts) == 3 && strings.ToLower(parts[0]) == "mkalias" {
|
||||||
if _, err := MkAlias(p.DB, parts[1], parts[2]); err != nil {
|
if _, err := MkAlias(p.DB, parts[1], parts[2]); err != nil {
|
||||||
log.Println(err)
|
log.Error().Err(err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
p.Bot.SendMessage(channel, fmt.Sprintf("Created alias %s -> %s",
|
p.Bot.Send(c, bot.Message, channel, fmt.Sprintf("Created alias %s -> %s",
|
||||||
parts[1], parts[2]))
|
parts[1], parts[2]))
|
||||||
return true
|
return true
|
||||||
} else if strings.ToLower(parts[0]) == "leaderboard" {
|
} else if strings.ToLower(parts[0]) == "leaderboard" {
|
||||||
|
@ -226,7 +255,7 @@ func (p *CounterPlugin) Message(message msg.Message) bool {
|
||||||
|
|
||||||
its, err := cmd()
|
its, err := cmd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Error().Err(err)
|
||||||
return false
|
return false
|
||||||
} else if len(its) == 0 {
|
} else if len(its) == 0 {
|
||||||
return false
|
return false
|
||||||
|
@ -240,23 +269,26 @@ func (p *CounterPlugin) Message(message msg.Message) bool {
|
||||||
it.Item,
|
it.Item,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
p.Bot.SendMessage(channel, out)
|
p.Bot.Send(c, bot.Message, channel, out)
|
||||||
return true
|
return true
|
||||||
} else if match := teaMatcher.MatchString(message.Body); match {
|
} else if match := teaMatcher.MatchString(message.Body); match {
|
||||||
// check for tea match TTT
|
// check for tea match TTT
|
||||||
return p.checkMatch(message)
|
return p.checkMatch(c, message)
|
||||||
} else if message.Command && message.Body == "reset me" {
|
} else if message.Command && message.Body == "reset me" {
|
||||||
items, err := GetItems(p.DB, strings.ToLower(nick))
|
items, err := GetItems(p.DB, strings.ToLower(nick))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error getting items to reset %s: %s", nick, err)
|
log.Error().
|
||||||
p.Bot.SendMessage(channel, "Something is technically wrong with your counters.")
|
Err(err).
|
||||||
|
Str("nick", nick).
|
||||||
|
Msg("Error getting items to reset")
|
||||||
|
p.Bot.Send(c, bot.Message, channel, "Something is technically wrong with your counters.")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
log.Printf("Items: %+v", items)
|
log.Debug().Msgf("Items: %+v", items)
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
item.Delete()
|
item.Delete()
|
||||||
}
|
}
|
||||||
p.Bot.SendMessage(channel, fmt.Sprintf("%s, you are as new, my son.", nick))
|
p.Bot.Send(c, bot.Message, channel, fmt.Sprintf("%s, you are as new, my son.", nick))
|
||||||
return true
|
return true
|
||||||
} else if message.Command && parts[0] == "inspect" && len(parts) == 2 {
|
} else if message.Command && parts[0] == "inspect" && len(parts) == 2 {
|
||||||
var subject string
|
var subject string
|
||||||
|
@ -267,12 +299,17 @@ func (p *CounterPlugin) Message(message msg.Message) bool {
|
||||||
subject = strings.ToLower(parts[1])
|
subject = strings.ToLower(parts[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Getting counter for %s", subject)
|
log.Debug().
|
||||||
|
Str("subject", subject).
|
||||||
|
Msg("Getting counter")
|
||||||
// pull all of the items associated with "subject"
|
// pull all of the items associated with "subject"
|
||||||
items, err := GetItems(p.DB, subject)
|
items, err := GetItems(p.DB, subject)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error retrieving items for %s: %s", subject, err)
|
log.Error().
|
||||||
p.Bot.SendMessage(channel, "Something went wrong finding that counter;")
|
Err(err).
|
||||||
|
Str("subject", subject).
|
||||||
|
Msg("Error retrieving items")
|
||||||
|
p.Bot.Send(c, bot.Message, channel, "Something went wrong finding that counter;")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,11 +329,11 @@ func (p *CounterPlugin) Message(message msg.Message) bool {
|
||||||
resp += "."
|
resp += "."
|
||||||
|
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
p.Bot.SendMessage(channel, fmt.Sprintf("%s has no counters.", subject))
|
p.Bot.Send(c, bot.Message, channel, fmt.Sprintf("%s has no counters.", subject))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Bot.SendMessage(channel, resp)
|
p.Bot.Send(c, bot.Message, channel, resp)
|
||||||
return true
|
return true
|
||||||
} else if message.Command && len(parts) == 2 && parts[0] == "clear" {
|
} else if message.Command && len(parts) == 2 && parts[0] == "clear" {
|
||||||
subject := strings.ToLower(nick)
|
subject := strings.ToLower(nick)
|
||||||
|
@ -304,18 +341,26 @@ func (p *CounterPlugin) Message(message msg.Message) bool {
|
||||||
|
|
||||||
it, err := GetItem(p.DB, subject, itemName)
|
it, err := GetItem(p.DB, subject, itemName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error getting item to remove %s.%s: %s", subject, itemName, err)
|
log.Error().
|
||||||
p.Bot.SendMessage(channel, "Something went wrong removing that counter;")
|
Err(err).
|
||||||
|
Str("subject", subject).
|
||||||
|
Str("itemName", itemName).
|
||||||
|
Msg("Error getting item to remove")
|
||||||
|
p.Bot.Send(c, bot.Message, channel, "Something went wrong removing that counter;")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
err = it.Delete()
|
err = it.Delete()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error removing item %s.%s: %s", subject, itemName, err)
|
log.Error().
|
||||||
p.Bot.SendMessage(channel, "Something went wrong removing that counter;")
|
Err(err).
|
||||||
|
Str("subject", subject).
|
||||||
|
Str("itemName", itemName).
|
||||||
|
Msg("Error removing item")
|
||||||
|
p.Bot.Send(c, bot.Message, channel, "Something went wrong removing that counter;")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Bot.SendAction(channel, fmt.Sprintf("chops a few %s out of his brain",
|
p.Bot.Send(c, bot.Action, channel, fmt.Sprintf("chops a few %s out of his brain",
|
||||||
itemName))
|
itemName))
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
@ -338,16 +383,19 @@ func (p *CounterPlugin) Message(message msg.Message) bool {
|
||||||
item, err := GetItem(p.DB, subject, itemName)
|
item, err := GetItem(p.DB, subject, itemName)
|
||||||
switch {
|
switch {
|
||||||
case err == sql.ErrNoRows:
|
case err == sql.ErrNoRows:
|
||||||
p.Bot.SendMessage(channel, fmt.Sprintf("I don't think %s has any %s.",
|
p.Bot.Send(c, bot.Message, channel, fmt.Sprintf("I don't think %s has any %s.",
|
||||||
subject, itemName))
|
subject, itemName))
|
||||||
return true
|
return true
|
||||||
case err != nil:
|
case err != nil:
|
||||||
log.Printf("Error retrieving item count for %s.%s: %s",
|
log.Error().
|
||||||
subject, itemName, err)
|
Err(err).
|
||||||
|
Str("subject", subject).
|
||||||
|
Str("itemName", itemName).
|
||||||
|
Msg("Error retrieving item count")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Bot.SendMessage(channel, fmt.Sprintf("%s has %d %s.", subject, item.Count,
|
p.Bot.Send(c, bot.Message, channel, fmt.Sprintf("%s has %d %s.", subject, item.Count,
|
||||||
itemName))
|
itemName))
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
@ -372,25 +420,33 @@ func (p *CounterPlugin) Message(message msg.Message) bool {
|
||||||
// ++ those fuckers
|
// ++ those fuckers
|
||||||
item, err := GetItem(p.DB, subject, itemName)
|
item, err := GetItem(p.DB, subject, itemName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error finding item %s.%s: %s.", subject, itemName, err)
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Str("subject", subject).
|
||||||
|
Str("itemName", itemName).
|
||||||
|
Msg("error finding item")
|
||||||
// Item ain't there, I guess
|
// Item ain't there, I guess
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
log.Printf("About to update item: %#v", item)
|
log.Debug().Msgf("About to update item: %#v", item)
|
||||||
item.UpdateDelta(1)
|
item.UpdateDelta(1)
|
||||||
p.Bot.SendMessage(channel, fmt.Sprintf("%s has %d %s.", subject,
|
p.Bot.Send(c, bot.Message, channel, fmt.Sprintf("%s has %d %s.", subject,
|
||||||
item.Count, item.Item))
|
item.Count, item.Item))
|
||||||
return true
|
return true
|
||||||
} else if strings.HasSuffix(parts[0], "--") {
|
} else if strings.HasSuffix(parts[0], "--") {
|
||||||
// -- those fuckers
|
// -- those fuckers
|
||||||
item, err := GetItem(p.DB, subject, itemName)
|
item, err := GetItem(p.DB, subject, itemName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error finding item %s.%s: %s.", subject, itemName, err)
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Str("subject", subject).
|
||||||
|
Str("itemName", itemName).
|
||||||
|
Msg("Error finding item")
|
||||||
// Item ain't there, I guess
|
// Item ain't there, I guess
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
item.UpdateDelta(-1)
|
item.UpdateDelta(-1)
|
||||||
p.Bot.SendMessage(channel, fmt.Sprintf("%s has %d %s.", subject,
|
p.Bot.Send(c, bot.Message, channel, fmt.Sprintf("%s has %d %s.", subject,
|
||||||
item.Count, item.Item))
|
item.Count, item.Item))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -412,28 +468,36 @@ func (p *CounterPlugin) Message(message msg.Message) bool {
|
||||||
// += those fuckers
|
// += those fuckers
|
||||||
item, err := GetItem(p.DB, subject, itemName)
|
item, err := GetItem(p.DB, subject, itemName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error finding item %s.%s: %s.", subject, itemName, err)
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Str("subject", subject).
|
||||||
|
Str("itemName", itemName).
|
||||||
|
Msg("Error finding item")
|
||||||
// Item ain't there, I guess
|
// Item ain't there, I guess
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
n, _ := strconv.Atoi(parts[2])
|
n, _ := strconv.Atoi(parts[2])
|
||||||
log.Printf("About to update item by %d: %#v", n, item)
|
log.Debug().Msgf("About to update item by %d: %#v", n, item)
|
||||||
item.UpdateDelta(n)
|
item.UpdateDelta(n)
|
||||||
p.Bot.SendMessage(channel, fmt.Sprintf("%s has %d %s.", subject,
|
p.Bot.Send(c, bot.Message, channel, fmt.Sprintf("%s has %d %s.", subject,
|
||||||
item.Count, item.Item))
|
item.Count, item.Item))
|
||||||
return true
|
return true
|
||||||
} else if parts[1] == "-=" {
|
} else if parts[1] == "-=" {
|
||||||
// -= those fuckers
|
// -= those fuckers
|
||||||
item, err := GetItem(p.DB, subject, itemName)
|
item, err := GetItem(p.DB, subject, itemName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error finding item %s.%s: %s.", subject, itemName, err)
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Str("subject", subject).
|
||||||
|
Str("itemName", itemName).
|
||||||
|
Msg("Error finding item")
|
||||||
// Item ain't there, I guess
|
// Item ain't there, I guess
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
n, _ := strconv.Atoi(parts[2])
|
n, _ := strconv.Atoi(parts[2])
|
||||||
log.Printf("About to update item by -%d: %#v", n, item)
|
log.Debug().Msgf("About to update item by -%d: %#v", n, item)
|
||||||
item.UpdateDelta(-n)
|
item.UpdateDelta(-n)
|
||||||
p.Bot.SendMessage(channel, fmt.Sprintf("%s has %d %s.", subject,
|
p.Bot.Send(c, bot.Message, channel, fmt.Sprintf("%s has %d %s.", subject,
|
||||||
item.Count, item.Item))
|
item.Count, item.Item))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -443,31 +507,15 @@ func (p *CounterPlugin) Message(message msg.Message) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Help responds to help requests. Every plugin must implement a help function.
|
// Help responds to help requests. Every plugin must implement a help function.
|
||||||
func (p *CounterPlugin) Help(channel string, parts []string) {
|
func (p *CounterPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
p.Bot.SendMessage(channel, "You can set counters incrementally by using "+
|
p.Bot.Send(c, bot.Message, message.Channel, "You can set counters incrementally by using "+
|
||||||
"<noun>++ and <noun>--. You can see all of your counters using "+
|
"<noun>++ and <noun>--. You can see all of your counters using "+
|
||||||
"\"inspect\", erase them with \"clear\", and view single counters with "+
|
"\"inspect\", erase them with \"clear\", and view single counters with "+
|
||||||
"\"count\".")
|
"\"count\".")
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty event handler because this plugin does not do anything on event recv
|
func (p *CounterPlugin) checkMatch(c bot.Connector, message msg.Message) bool {
|
||||||
func (p *CounterPlugin) Event(kind string, message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handler for bot's own messages
|
|
||||||
func (p *CounterPlugin) BotMessage(message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register any web URLs desired
|
|
||||||
func (p *CounterPlugin) RegisterWeb() *string {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *CounterPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
|
||||||
|
|
||||||
func (p *CounterPlugin) checkMatch(message msg.Message) bool {
|
|
||||||
nick := message.User.Name
|
nick := message.User.Name
|
||||||
channel := message.Channel
|
channel := message.Channel
|
||||||
|
|
||||||
|
@ -480,13 +528,98 @@ func (p *CounterPlugin) checkMatch(message msg.Message) bool {
|
||||||
// We will specifically allow :tea: to keep compatability
|
// We will specifically allow :tea: to keep compatability
|
||||||
item, err := GetItem(p.DB, nick, itemName)
|
item, err := GetItem(p.DB, nick, itemName)
|
||||||
if err != nil || (item.Count == 0 && item.Item != ":tea:") {
|
if err != nil || (item.Count == 0 && item.Item != ":tea:") {
|
||||||
log.Printf("Error finding item %s.%s: %s.", nick, itemName, err)
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Str("itemName", itemName).
|
||||||
|
Msg("Error finding item")
|
||||||
// Item ain't there, I guess
|
// Item ain't there, I guess
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
log.Printf("About to update item: %#v", item)
|
log.Debug().Msgf("About to update item: %#v", item)
|
||||||
item.UpdateDelta(1)
|
item.UpdateDelta(1)
|
||||||
p.Bot.SendMessage(channel, fmt.Sprintf("bleep-bloop-blop... %s has %d %s",
|
p.Bot.Send(c, bot.Message, channel, fmt.Sprintf("%s... %s has %d %s",
|
||||||
nick, item.Count, itemName))
|
strings.Join(everyDayImShuffling([]string{"bleep", "bloop", "blop"}), "-"), nick, item.Count, itemName))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func everyDayImShuffling(vals []string) []string {
|
||||||
|
ret := make([]string, len(vals))
|
||||||
|
perm := rand.Perm(len(vals))
|
||||||
|
for i, randIndex := range perm {
|
||||||
|
ret[i] = vals[randIndex]
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CounterPlugin) registerWeb() {
|
||||||
|
http.HandleFunc("/counter/api", p.handleCounterAPI)
|
||||||
|
http.HandleFunc("/counter", p.handleCounter)
|
||||||
|
p.Bot.RegisterWeb("/counter", "Counter")
|
||||||
|
}
|
||||||
|
|
||||||
|
var tpl = template.Must(template.New("factoidIndex").Parse(html))
|
||||||
|
|
||||||
|
func (p *CounterPlugin) handleCounter(w http.ResponseWriter, r *http.Request) {
|
||||||
|
tpl.Execute(w, struct{ Nav []bot.EndPoint }{p.Bot.GetWebNavigation()})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CounterPlugin) handleCounterAPI(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == http.MethodPost {
|
||||||
|
info := struct {
|
||||||
|
User string
|
||||||
|
Thing string
|
||||||
|
Action string
|
||||||
|
Password string
|
||||||
|
}{}
|
||||||
|
decoder := json.NewDecoder(r.Body)
|
||||||
|
err := decoder.Decode(&info)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
fmt.Fprint(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debug().
|
||||||
|
Interface("postbody", info).
|
||||||
|
Msg("Got a POST")
|
||||||
|
if info.Password != p.Bot.GetPassword() {
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
j, _ := json.Marshal(struct{ Err string }{Err: "Invalid Password"})
|
||||||
|
w.Write(j)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
item, err := GetItem(p.DB, info.User, info.Thing)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Str("subject", info.User).
|
||||||
|
Str("itemName", info.Thing).
|
||||||
|
Msg("error finding item")
|
||||||
|
w.WriteHeader(404)
|
||||||
|
fmt.Fprint(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if info.Action == "++" {
|
||||||
|
item.UpdateDelta(1)
|
||||||
|
} else if info.Action == "--" {
|
||||||
|
item.UpdateDelta(-1)
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(400)
|
||||||
|
fmt.Fprint(w, "Invalid increment")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
all, err := GetAllItems(p.DB)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
fmt.Fprint(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(all)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
fmt.Fprint(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprint(w, string(data))
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ package counter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -16,17 +17,18 @@ import (
|
||||||
func setup(t *testing.T) (*bot.MockBot, *CounterPlugin) {
|
func setup(t *testing.T) (*bot.MockBot, *CounterPlugin) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
|
mb.DB().MustExec(`delete from counter; delete from counter_alias;`)
|
||||||
_, err := MkAlias(mb.DB(), "tea", ":tea:")
|
_, err := MkAlias(mb.DB(), "tea", ":tea:")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
return mb, c
|
return mb, c
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeMessage(payload string) msg.Message {
|
func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) {
|
||||||
isCmd := strings.HasPrefix(payload, "!")
|
isCmd := strings.HasPrefix(payload, "!")
|
||||||
if isCmd {
|
if isCmd {
|
||||||
payload = payload[1:]
|
payload = payload[1:]
|
||||||
}
|
}
|
||||||
return msg.Message{
|
return &cli.CliPlugin{}, bot.Message, msg.Message{
|
||||||
User: &user.User{Name: "tester"},
|
User: &user.User{Name: "tester"},
|
||||||
Channel: "test",
|
Channel: "test",
|
||||||
Body: payload,
|
Body: payload,
|
||||||
|
@ -37,8 +39,8 @@ func makeMessage(payload string) msg.Message {
|
||||||
func TestThreeSentencesExists(t *testing.T) {
|
func TestThreeSentencesExists(t *testing.T) {
|
||||||
mb, c := setup(t)
|
mb, c := setup(t)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
c.Message(makeMessage(":beer:++"))
|
c.message(makeMessage(":beer:++"))
|
||||||
c.Message(makeMessage(":beer:. Earl Grey. Hot."))
|
c.message(makeMessage(":beer:. Earl Grey. Hot."))
|
||||||
item, err := GetItem(mb.DB(), "tester", ":beer:")
|
item, err := GetItem(mb.DB(), "tester", ":beer:")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 2, item.Count)
|
assert.Equal(t, 2, item.Count)
|
||||||
|
@ -48,7 +50,7 @@ func TestThreeSentencesNotExists(t *testing.T) {
|
||||||
mb, c := setup(t)
|
mb, c := setup(t)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
item, err := GetItem(mb.DB(), "tester", ":beer:")
|
item, err := GetItem(mb.DB(), "tester", ":beer:")
|
||||||
c.Message(makeMessage(":beer:. Earl Grey. Hot."))
|
c.message(makeMessage(":beer:. Earl Grey. Hot."))
|
||||||
item, err = GetItem(mb.DB(), "tester", ":beer:")
|
item, err = GetItem(mb.DB(), "tester", ":beer:")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 0, item.Count)
|
assert.Equal(t, 0, item.Count)
|
||||||
|
@ -57,8 +59,8 @@ func TestThreeSentencesNotExists(t *testing.T) {
|
||||||
func TestTeaEarlGreyHot(t *testing.T) {
|
func TestTeaEarlGreyHot(t *testing.T) {
|
||||||
mb, c := setup(t)
|
mb, c := setup(t)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
c.Message(makeMessage("Tea. Earl Grey. Hot."))
|
c.message(makeMessage("Tea. Earl Grey. Hot."))
|
||||||
c.Message(makeMessage("Tea. Earl Grey. Hot."))
|
c.message(makeMessage("Tea. Earl Grey. Hot."))
|
||||||
item, err := GetItem(mb.DB(), "tester", ":tea:")
|
item, err := GetItem(mb.DB(), "tester", ":tea:")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 2, item.Count)
|
assert.Equal(t, 2, item.Count)
|
||||||
|
@ -67,8 +69,8 @@ func TestTeaEarlGreyHot(t *testing.T) {
|
||||||
func TestTeaTwoPeriods(t *testing.T) {
|
func TestTeaTwoPeriods(t *testing.T) {
|
||||||
mb, c := setup(t)
|
mb, c := setup(t)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
c.Message(makeMessage("Tea. Earl Grey."))
|
c.message(makeMessage("Tea. Earl Grey."))
|
||||||
c.Message(makeMessage("Tea. Earl Grey."))
|
c.message(makeMessage("Tea. Earl Grey."))
|
||||||
item, err := GetItem(mb.DB(), "tester", ":tea:")
|
item, err := GetItem(mb.DB(), "tester", ":tea:")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 0, item.Count)
|
assert.Equal(t, 0, item.Count)
|
||||||
|
@ -77,8 +79,8 @@ func TestTeaTwoPeriods(t *testing.T) {
|
||||||
func TestTeaMultiplePeriods(t *testing.T) {
|
func TestTeaMultiplePeriods(t *testing.T) {
|
||||||
mb, c := setup(t)
|
mb, c := setup(t)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
c.Message(makeMessage("Tea. Earl Grey. Spiked. Hot."))
|
c.message(makeMessage("Tea. Earl Grey. Spiked. Hot."))
|
||||||
c.Message(makeMessage("Tea. Earl Grey. Spiked. Hot."))
|
c.message(makeMessage("Tea. Earl Grey. Spiked. Hot."))
|
||||||
item, err := GetItem(mb.DB(), "tester", ":tea:")
|
item, err := GetItem(mb.DB(), "tester", ":tea:")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 2, item.Count)
|
assert.Equal(t, 2, item.Count)
|
||||||
|
@ -87,9 +89,9 @@ func TestTeaMultiplePeriods(t *testing.T) {
|
||||||
func TestTeaGreenHot(t *testing.T) {
|
func TestTeaGreenHot(t *testing.T) {
|
||||||
mb, c := setup(t)
|
mb, c := setup(t)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
c.Message(makeMessage("Tea. Green. Hot."))
|
c.message(makeMessage("Tea. Green. Hot."))
|
||||||
c.Message(makeMessage("Tea. Green. Hot"))
|
c.message(makeMessage("Tea. Green. Hot"))
|
||||||
c.Message(makeMessage("Tea. Green. Iced."))
|
c.message(makeMessage("Tea. Green. Iced."))
|
||||||
item, err := GetItem(mb.DB(), "tester", ":tea:")
|
item, err := GetItem(mb.DB(), "tester", ":tea:")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 3, item.Count)
|
assert.Equal(t, 3, item.Count)
|
||||||
|
@ -98,8 +100,8 @@ func TestTeaGreenHot(t *testing.T) {
|
||||||
func TestTeaUnrelated(t *testing.T) {
|
func TestTeaUnrelated(t *testing.T) {
|
||||||
mb, c := setup(t)
|
mb, c := setup(t)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
c.Message(makeMessage("Tea."))
|
c.message(makeMessage("Tea."))
|
||||||
c.Message(makeMessage("Tea. It's great."))
|
c.message(makeMessage("Tea. It's great."))
|
||||||
item, err := GetItem(mb.DB(), "tester", ":tea:")
|
item, err := GetItem(mb.DB(), "tester", ":tea:")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 0, item.Count)
|
assert.Equal(t, 0, item.Count)
|
||||||
|
@ -108,7 +110,7 @@ func TestTeaUnrelated(t *testing.T) {
|
||||||
func TestTeaSkieselQuote(t *testing.T) {
|
func TestTeaSkieselQuote(t *testing.T) {
|
||||||
mb, c := setup(t)
|
mb, c := setup(t)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
c.Message(makeMessage("blah, this is a whole page of explanation where \"we did local search and used a tabu list\" would have sufficed"))
|
c.message(makeMessage("blah, this is a whole page of explanation where \"we did local search and used a tabu list\" would have sufficed"))
|
||||||
item, err := GetItem(mb.DB(), "tester", ":tea:")
|
item, err := GetItem(mb.DB(), "tester", ":tea:")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 0, item.Count)
|
assert.Equal(t, 0, item.Count)
|
||||||
|
@ -116,7 +118,7 @@ func TestTeaSkieselQuote(t *testing.T) {
|
||||||
func TestTeaUnicodeJapanese(t *testing.T) {
|
func TestTeaUnicodeJapanese(t *testing.T) {
|
||||||
mb, c := setup(t)
|
mb, c := setup(t)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
c.Message(makeMessage("Tea. おちや. Hot."))
|
c.message(makeMessage("Tea. おちや. Hot."))
|
||||||
item, err := GetItem(mb.DB(), "tester", ":tea:")
|
item, err := GetItem(mb.DB(), "tester", ":tea:")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 1, item.Count)
|
assert.Equal(t, 1, item.Count)
|
||||||
|
@ -125,8 +127,8 @@ func TestTeaUnicodeJapanese(t *testing.T) {
|
||||||
func TestResetMe(t *testing.T) {
|
func TestResetMe(t *testing.T) {
|
||||||
mb, c := setup(t)
|
mb, c := setup(t)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
c.Message(makeMessage("test++"))
|
c.message(makeMessage("test++"))
|
||||||
c.Message(makeMessage("!reset me"))
|
c.message(makeMessage("!reset me"))
|
||||||
items, err := GetItems(mb.DB(), "tester")
|
items, err := GetItems(mb.DB(), "tester")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Len(t, items, 0)
|
assert.Len(t, items, 0)
|
||||||
|
@ -135,7 +137,7 @@ func TestResetMe(t *testing.T) {
|
||||||
func TestCounterOne(t *testing.T) {
|
func TestCounterOne(t *testing.T) {
|
||||||
mb, c := setup(t)
|
mb, c := setup(t)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
c.Message(makeMessage("test++"))
|
c.message(makeMessage("test++"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.Equal(t, mb.Messages[0], "tester has 1 test.")
|
assert.Equal(t, mb.Messages[0], "tester has 1 test.")
|
||||||
}
|
}
|
||||||
|
@ -143,7 +145,7 @@ func TestCounterOne(t *testing.T) {
|
||||||
func TestCounterOneWithSpace(t *testing.T) {
|
func TestCounterOneWithSpace(t *testing.T) {
|
||||||
mb, c := setup(t)
|
mb, c := setup(t)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
c.Message(makeMessage(":test: ++"))
|
c.message(makeMessage(":test: ++"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.Equal(t, mb.Messages[0], "tester has 1 :test:.")
|
assert.Equal(t, mb.Messages[0], "tester has 1 :test:.")
|
||||||
}
|
}
|
||||||
|
@ -152,7 +154,7 @@ func TestCounterFour(t *testing.T) {
|
||||||
mb, c := setup(t)
|
mb, c := setup(t)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
for i := 0; i < 4; i++ {
|
for i := 0; i < 4; i++ {
|
||||||
c.Message(makeMessage("test++"))
|
c.message(makeMessage("test++"))
|
||||||
}
|
}
|
||||||
assert.Len(t, mb.Messages, 4)
|
assert.Len(t, mb.Messages, 4)
|
||||||
assert.Equal(t, mb.Messages[3], "tester has 4 test.")
|
assert.Equal(t, mb.Messages[3], "tester has 4 test.")
|
||||||
|
@ -162,10 +164,10 @@ func TestCounterDecrement(t *testing.T) {
|
||||||
mb, c := setup(t)
|
mb, c := setup(t)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
for i := 0; i < 4; i++ {
|
for i := 0; i < 4; i++ {
|
||||||
c.Message(makeMessage("test++"))
|
c.message(makeMessage("test++"))
|
||||||
assert.Equal(t, mb.Messages[i], fmt.Sprintf("tester has %d test.", i+1))
|
assert.Equal(t, mb.Messages[i], fmt.Sprintf("tester has %d test.", i+1))
|
||||||
}
|
}
|
||||||
c.Message(makeMessage("test--"))
|
c.message(makeMessage("test--"))
|
||||||
assert.Len(t, mb.Messages, 5)
|
assert.Len(t, mb.Messages, 5)
|
||||||
assert.Equal(t, mb.Messages[4], "tester has 3 test.")
|
assert.Equal(t, mb.Messages[4], "tester has 3 test.")
|
||||||
}
|
}
|
||||||
|
@ -174,10 +176,10 @@ func TestFriendCounterDecrement(t *testing.T) {
|
||||||
mb, c := setup(t)
|
mb, c := setup(t)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
for i := 0; i < 4; i++ {
|
for i := 0; i < 4; i++ {
|
||||||
c.Message(makeMessage("other.test++"))
|
c.message(makeMessage("other.test++"))
|
||||||
assert.Equal(t, mb.Messages[i], fmt.Sprintf("other has %d test.", i+1))
|
assert.Equal(t, mb.Messages[i], fmt.Sprintf("other has %d test.", i+1))
|
||||||
}
|
}
|
||||||
c.Message(makeMessage("other.test--"))
|
c.message(makeMessage("other.test--"))
|
||||||
assert.Len(t, mb.Messages, 5)
|
assert.Len(t, mb.Messages, 5)
|
||||||
assert.Equal(t, mb.Messages[4], "other has 3 test.")
|
assert.Equal(t, mb.Messages[4], "other has 3 test.")
|
||||||
}
|
}
|
||||||
|
@ -186,12 +188,12 @@ func TestDecrementZero(t *testing.T) {
|
||||||
mb, c := setup(t)
|
mb, c := setup(t)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
for i := 0; i < 4; i++ {
|
for i := 0; i < 4; i++ {
|
||||||
c.Message(makeMessage("test++"))
|
c.message(makeMessage("test++"))
|
||||||
assert.Equal(t, mb.Messages[i], fmt.Sprintf("tester has %d test.", i+1))
|
assert.Equal(t, mb.Messages[i], fmt.Sprintf("tester has %d test.", i+1))
|
||||||
}
|
}
|
||||||
j := 4
|
j := 4
|
||||||
for i := 4; i > 0; i-- {
|
for i := 4; i > 0; i-- {
|
||||||
c.Message(makeMessage("test--"))
|
c.message(makeMessage("test--"))
|
||||||
assert.Equal(t, mb.Messages[j], fmt.Sprintf("tester has %d test.", i-1))
|
assert.Equal(t, mb.Messages[j], fmt.Sprintf("tester has %d test.", i-1))
|
||||||
j++
|
j++
|
||||||
}
|
}
|
||||||
|
@ -203,10 +205,10 @@ func TestClear(t *testing.T) {
|
||||||
mb, c := setup(t)
|
mb, c := setup(t)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
for i := 0; i < 4; i++ {
|
for i := 0; i < 4; i++ {
|
||||||
c.Message(makeMessage("test++"))
|
c.message(makeMessage("test++"))
|
||||||
assert.Equal(t, mb.Messages[i], fmt.Sprintf("tester has %d test.", i+1))
|
assert.Equal(t, mb.Messages[i], fmt.Sprintf("tester has %d test.", i+1))
|
||||||
}
|
}
|
||||||
res := c.Message(makeMessage("!clear test"))
|
res := c.message(makeMessage("!clear test"))
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Len(t, mb.Actions, 1)
|
assert.Len(t, mb.Actions, 1)
|
||||||
assert.Equal(t, mb.Actions[0], "chops a few test out of his brain")
|
assert.Equal(t, mb.Actions[0], "chops a few test out of his brain")
|
||||||
|
@ -216,10 +218,10 @@ func TestCount(t *testing.T) {
|
||||||
mb, c := setup(t)
|
mb, c := setup(t)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
for i := 0; i < 4; i++ {
|
for i := 0; i < 4; i++ {
|
||||||
c.Message(makeMessage("test++"))
|
c.message(makeMessage("test++"))
|
||||||
assert.Equal(t, mb.Messages[i], fmt.Sprintf("tester has %d test.", i+1))
|
assert.Equal(t, mb.Messages[i], fmt.Sprintf("tester has %d test.", i+1))
|
||||||
}
|
}
|
||||||
res := c.Message(makeMessage("!count test"))
|
res := c.message(makeMessage("!count test"))
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Len(t, mb.Messages, 5)
|
assert.Len(t, mb.Messages, 5)
|
||||||
assert.Equal(t, mb.Messages[4], "tester has 4 test.")
|
assert.Equal(t, mb.Messages[4], "tester has 4 test.")
|
||||||
|
@ -229,18 +231,18 @@ func TestInspectMe(t *testing.T) {
|
||||||
mb, c := setup(t)
|
mb, c := setup(t)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
for i := 0; i < 4; i++ {
|
for i := 0; i < 4; i++ {
|
||||||
c.Message(makeMessage("test++"))
|
c.message(makeMessage("test++"))
|
||||||
assert.Equal(t, mb.Messages[i], fmt.Sprintf("tester has %d test.", i+1))
|
assert.Equal(t, mb.Messages[i], fmt.Sprintf("tester has %d test.", i+1))
|
||||||
}
|
}
|
||||||
for i := 0; i < 2; i++ {
|
for i := 0; i < 2; i++ {
|
||||||
c.Message(makeMessage("fucks++"))
|
c.message(makeMessage("fucks++"))
|
||||||
assert.Equal(t, mb.Messages[i+4], fmt.Sprintf("tester has %d fucks.", i+1))
|
assert.Equal(t, mb.Messages[i+4], fmt.Sprintf("tester has %d fucks.", i+1))
|
||||||
}
|
}
|
||||||
for i := 0; i < 20; i++ {
|
for i := 0; i < 20; i++ {
|
||||||
c.Message(makeMessage("cheese++"))
|
c.message(makeMessage("cheese++"))
|
||||||
assert.Equal(t, mb.Messages[i+6], fmt.Sprintf("tester has %d cheese.", i+1))
|
assert.Equal(t, mb.Messages[i+6], fmt.Sprintf("tester has %d cheese.", i+1))
|
||||||
}
|
}
|
||||||
res := c.Message(makeMessage("!inspect me"))
|
res := c.message(makeMessage("!inspect me"))
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Len(t, mb.Messages, 27)
|
assert.Len(t, mb.Messages, 27)
|
||||||
assert.Equal(t, mb.Messages[26], "tester has the following counters: test: 4, fucks: 2, cheese: 20.")
|
assert.Equal(t, mb.Messages[26], "tester has the following counters: test: 4, fucks: 2, cheese: 20.")
|
||||||
|
@ -249,24 +251,6 @@ func TestInspectMe(t *testing.T) {
|
||||||
func TestHelp(t *testing.T) {
|
func TestHelp(t *testing.T) {
|
||||||
mb, c := setup(t)
|
mb, c := setup(t)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
c.Help("channel", []string{})
|
c.help(&cli.CliPlugin{}, bot.Help, msg.Message{Channel: "channel"}, []string{})
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBotMessage(t *testing.T) {
|
|
||||||
_, c := setup(t)
|
|
||||||
assert.NotNil(t, c)
|
|
||||||
assert.False(t, c.BotMessage(makeMessage("test")))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvent(t *testing.T) {
|
|
||||||
_, c := setup(t)
|
|
||||||
assert.NotNil(t, c)
|
|
||||||
assert.False(t, c.Event("dummy", makeMessage("test")))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRegisterWeb(t *testing.T) {
|
|
||||||
_, c := setup(t)
|
|
||||||
assert.NotNil(t, c)
|
|
||||||
assert.Nil(t, c.RegisterWeb())
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
package counter
|
||||||
|
|
||||||
|
var html = `
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<!-- Load required Bootstrap and BootstrapVue CSS -->
|
||||||
|
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
|
||||||
|
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.css" />
|
||||||
|
|
||||||
|
<!-- Load polyfills to support older browsers -->
|
||||||
|
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CMutationObserver"></script>
|
||||||
|
|
||||||
|
<!-- Load Vue followed by BootstrapVue -->
|
||||||
|
<script src="//unpkg.com/vue@latest/dist/vue.min.js"></script>
|
||||||
|
<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.js"></script>
|
||||||
|
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
||||||
|
<title>Counters</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="app">
|
||||||
|
<b-navbar>
|
||||||
|
<b-navbar-brand>Counters</b-navbar-brand>
|
||||||
|
<b-navbar-nav>
|
||||||
|
<b-nav-item v-for="item in nav" :href="item.URL" :active="item.Name === 'Counter'">{{ "{{ item.Name }}" }}</b-nav-item>
|
||||||
|
</b-navbar-nav>
|
||||||
|
</b-navbar>
|
||||||
|
<b-alert
|
||||||
|
dismissable
|
||||||
|
:show="err"
|
||||||
|
variant="error">
|
||||||
|
{{ "{{ err }}" }}
|
||||||
|
</b-alert>
|
||||||
|
<b-container>
|
||||||
|
<b-row>
|
||||||
|
<b-col cols="5">Password:</b-col>
|
||||||
|
<b-col><b-input v-model="answer"></b-col>
|
||||||
|
</b-row>
|
||||||
|
<b-row v-for="(counter, user) in counters">
|
||||||
|
{{ "{{ user }}" }}:
|
||||||
|
<b-container>
|
||||||
|
<b-row v-for="(count, thing) in counter">
|
||||||
|
<b-col offset="1">
|
||||||
|
{{ "{{ thing }}" }}:
|
||||||
|
</b-col>
|
||||||
|
<b-col>
|
||||||
|
{{ "{{ count }}" }}
|
||||||
|
</b-col>
|
||||||
|
<b-col cols="2">
|
||||||
|
<button @click="subtract(user,thing,count)">-</button>
|
||||||
|
<button @click="add(user,thing,count)">+</button>
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
</b-container>
|
||||||
|
</b-row>
|
||||||
|
</b-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function convertData(data) {
|
||||||
|
var newData = {};
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
let entry = data[i]
|
||||||
|
if (newData[entry.Nick] === undefined) {
|
||||||
|
newData[entry.Nick] = {}
|
||||||
|
}
|
||||||
|
newData[entry.Nick][entry.Item] = entry.Count;
|
||||||
|
}
|
||||||
|
return newData;
|
||||||
|
}
|
||||||
|
var app = new Vue({
|
||||||
|
el: '#app',
|
||||||
|
data: {
|
||||||
|
err: '',
|
||||||
|
nav: {{ .Nav }},
|
||||||
|
answer: '',
|
||||||
|
correct: 0,
|
||||||
|
counters: {}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
axios.get('/counter/api')
|
||||||
|
.then(resp => (this.counters = convertData(resp.data)))
|
||||||
|
.catch(err => (this.err = err));
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
add(user, thing, count) {
|
||||||
|
axios.post('/counter/api',
|
||||||
|
{user: user, thing: thing, action: '++', password: this.answer})
|
||||||
|
.then(resp => {this.counters = convertData(resp.data); this.err = '';})
|
||||||
|
.catch(err => this.err = err);
|
||||||
|
},
|
||||||
|
subtract(user, thing, count) {
|
||||||
|
axios.post('/counter/api',
|
||||||
|
{user: user, thing: thing, action: '--', password: this.answer})
|
||||||
|
.then(resp => {this.counters = convertData(resp.data); this.err = '';})
|
||||||
|
.catch(err => this.err = err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
|
@ -1,45 +0,0 @@
|
||||||
package db
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/velour/catbase/bot"
|
|
||||||
"github.com/velour/catbase/bot/msg"
|
|
||||||
"github.com/velour/catbase/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DBPlugin struct {
|
|
||||||
bot bot.Bot
|
|
||||||
config *config.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(b bot.Bot) *DBPlugin {
|
|
||||||
return &DBPlugin{b, b.Config()}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DBPlugin) Message(message msg.Message) bool { return false }
|
|
||||||
func (p *DBPlugin) Event(kind string, message msg.Message) bool { return false }
|
|
||||||
func (p *DBPlugin) ReplyMessage(msg.Message, string) bool { return false }
|
|
||||||
func (p *DBPlugin) BotMessage(message msg.Message) bool { return false }
|
|
||||||
func (p *DBPlugin) Help(channel string, parts []string) {}
|
|
||||||
|
|
||||||
func (p *DBPlugin) RegisterWeb() *string {
|
|
||||||
http.HandleFunc("/db/catbase.db", p.serveQuery)
|
|
||||||
tmp := "/db/catbase.db"
|
|
||||||
return &tmp
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DBPlugin) serveQuery(w http.ResponseWriter, r *http.Request) {
|
|
||||||
f, err := os.Open(p.bot.Config().DB.File)
|
|
||||||
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, "catbase.db", time.Now(), f)
|
|
||||||
}
|
|
|
@ -19,10 +19,13 @@ type DicePlugin struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDicePlugin creates a new DicePlugin with the Plugin interface
|
// NewDicePlugin creates a new DicePlugin with the Plugin interface
|
||||||
func New(bot bot.Bot) *DicePlugin {
|
func New(b bot.Bot) *DicePlugin {
|
||||||
return &DicePlugin{
|
dp := &DicePlugin{
|
||||||
Bot: bot,
|
Bot: b,
|
||||||
}
|
}
|
||||||
|
b.Register(dp, bot.Message, dp.message)
|
||||||
|
b.Register(dp, bot.Help, dp.help)
|
||||||
|
return dp
|
||||||
}
|
}
|
||||||
|
|
||||||
func rollDie(sides int) int {
|
func rollDie(sides int) int {
|
||||||
|
@ -32,7 +35,7 @@ func rollDie(sides int) int {
|
||||||
// Message responds to the bot hook on recieving messages.
|
// Message responds to the bot hook on recieving messages.
|
||||||
// This function returns true if the plugin responds in a meaningful way to the users message.
|
// 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.
|
// Otherwise, the function returns false and the bot continues execution of other plugins.
|
||||||
func (p *DicePlugin) Message(message msg.Message) bool {
|
func (p *DicePlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
if !message.Command {
|
if !message.Command {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -46,7 +49,7 @@ func (p *DicePlugin) Message(message msg.Message) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
if sides < 2 || nDice < 1 || nDice > 20 {
|
if sides < 2 || nDice < 1 || nDice > 20 {
|
||||||
p.Bot.SendMessage(channel, "You're a dick.")
|
p.Bot.Send(c, bot.Message, channel, "You're a dick.")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,29 +64,13 @@ func (p *DicePlugin) Message(message msg.Message) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Bot.SendMessage(channel, rolls)
|
p.Bot.Send(c, bot.Message, channel, rolls)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Help responds to help requests. Every plugin must implement a help function.
|
// Help responds to help requests. Every plugin must implement a help function.
|
||||||
func (p *DicePlugin) Help(channel string, parts []string) {
|
func (p *DicePlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
p.Bot.SendMessage(channel, "Roll dice using notation XdY. Try \"3d20\".")
|
p.Bot.Send(c, bot.Message, message.Channel, "Roll dice using notation XdY. Try \"3d20\".")
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty event handler because this plugin does not do anything on event recv
|
|
||||||
func (p *DicePlugin) Event(kind string, message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handler for bot's own messages
|
|
||||||
func (p *DicePlugin) BotMessage(message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register any web URLs desired
|
|
||||||
func (p *DicePlugin) RegisterWeb() *string {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DicePlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
package dice
|
package dice
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -12,12 +13,12 @@ import (
|
||||||
"github.com/velour/catbase/bot/user"
|
"github.com/velour/catbase/bot/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeMessage(payload string) msg.Message {
|
func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) {
|
||||||
isCmd := strings.HasPrefix(payload, "!")
|
isCmd := strings.HasPrefix(payload, "!")
|
||||||
if isCmd {
|
if isCmd {
|
||||||
payload = payload[1:]
|
payload = payload[1:]
|
||||||
}
|
}
|
||||||
return msg.Message{
|
return &cli.CliPlugin{}, bot.Message, msg.Message{
|
||||||
User: &user.User{Name: "tester"},
|
User: &user.User{Name: "tester"},
|
||||||
Channel: "test",
|
Channel: "test",
|
||||||
Body: payload,
|
Body: payload,
|
||||||
|
@ -29,7 +30,7 @@ func TestDie(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
res := c.Message(makeMessage("!1d6"))
|
res := c.message(makeMessage("!1d6"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Contains(t, mb.Messages[0], "tester, you rolled:")
|
assert.Contains(t, mb.Messages[0], "tester, you rolled:")
|
||||||
|
@ -39,7 +40,7 @@ func TestDice(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
res := c.Message(makeMessage("!5d6"))
|
res := c.message(makeMessage("!5d6"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Contains(t, mb.Messages[0], "tester, you rolled:")
|
assert.Contains(t, mb.Messages[0], "tester, you rolled:")
|
||||||
|
@ -49,7 +50,7 @@ func TestNotCommand(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
res := c.Message(makeMessage("1d6"))
|
res := c.message(makeMessage("1d6"))
|
||||||
assert.False(t, res)
|
assert.False(t, res)
|
||||||
assert.Len(t, mb.Messages, 0)
|
assert.Len(t, mb.Messages, 0)
|
||||||
}
|
}
|
||||||
|
@ -58,7 +59,7 @@ func TestBadDice(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
res := c.Message(makeMessage("!aued6"))
|
res := c.message(makeMessage("!aued6"))
|
||||||
assert.False(t, res)
|
assert.False(t, res)
|
||||||
assert.Len(t, mb.Messages, 0)
|
assert.Len(t, mb.Messages, 0)
|
||||||
}
|
}
|
||||||
|
@ -67,7 +68,7 @@ func TestBadSides(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
res := c.Message(makeMessage("!1daoeu"))
|
res := c.message(makeMessage("!1daoeu"))
|
||||||
assert.False(t, res)
|
assert.False(t, res)
|
||||||
assert.Len(t, mb.Messages, 0)
|
assert.Len(t, mb.Messages, 0)
|
||||||
}
|
}
|
||||||
|
@ -76,7 +77,7 @@ func TestLotsOfDice(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
res := c.Message(makeMessage("!100d100"))
|
res := c.message(makeMessage("!100d100"))
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.Contains(t, mb.Messages[0], "You're a dick.")
|
assert.Contains(t, mb.Messages[0], "You're a dick.")
|
||||||
|
@ -86,27 +87,6 @@ func TestHelp(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
c.Help("channel", []string{})
|
c.help(&cli.CliPlugin{}, bot.Help, msg.Message{Channel: "channel"}, []string{})
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBotMessage(t *testing.T) {
|
|
||||||
mb := bot.NewMockBot()
|
|
||||||
c := New(mb)
|
|
||||||
assert.NotNil(t, c)
|
|
||||||
assert.False(t, c.BotMessage(makeMessage("test")))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvent(t *testing.T) {
|
|
||||||
mb := bot.NewMockBot()
|
|
||||||
c := New(mb)
|
|
||||||
assert.NotNil(t, c)
|
|
||||||
assert.False(t, c.Event("dummy", makeMessage("test")))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRegisterWeb(t *testing.T) {
|
|
||||||
mb := bot.NewMockBot()
|
|
||||||
c := New(mb)
|
|
||||||
assert.NotNil(t, c)
|
|
||||||
assert.Nil(t, c.RegisterWeb())
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,235 +0,0 @@
|
||||||
// © 2013 the CatBase Authors under the WTFPL. See AUTHORS for the list of authors.
|
|
||||||
|
|
||||||
package downtime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"github.com/velour/catbase/bot"
|
|
||||||
"github.com/velour/catbase/bot/msg"
|
|
||||||
)
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This is a downtime plugin to monitor how much our users suck
|
|
||||||
|
|
||||||
type DowntimePlugin struct {
|
|
||||||
Bot bot.Bot
|
|
||||||
db *sqlx.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
type idleEntry struct {
|
|
||||||
id sql.NullInt64
|
|
||||||
nick string
|
|
||||||
lastSeen time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry idleEntry) saveIdleEntry(db *sqlx.DB) error {
|
|
||||||
var err error
|
|
||||||
if entry.id.Valid {
|
|
||||||
log.Println("Updating downtime for: ", entry)
|
|
||||||
_, err = db.Exec(`update downtime set
|
|
||||||
nick=?, lastSeen=?
|
|
||||||
where id=?;`, entry.nick, entry.lastSeen.Unix(), entry.id.Int64)
|
|
||||||
} else {
|
|
||||||
log.Println("Inserting downtime for: ", entry)
|
|
||||||
_, err = db.Exec(`insert into downtime (nick, lastSeen)
|
|
||||||
values (?, ?)`, entry.nick, entry.lastSeen.Unix())
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func getIdleEntryByNick(db *sqlx.DB, nick string) (idleEntry, error) {
|
|
||||||
var id sql.NullInt64
|
|
||||||
var lastSeen sql.NullInt64
|
|
||||||
err := db.QueryRow(`select id, max(lastSeen) from downtime
|
|
||||||
where nick = ?`, nick).Scan(&id, &lastSeen)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Error selecting downtime: ", err)
|
|
||||||
return idleEntry{}, err
|
|
||||||
}
|
|
||||||
if !id.Valid {
|
|
||||||
return idleEntry{
|
|
||||||
nick: nick,
|
|
||||||
lastSeen: time.Now(),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
return idleEntry{
|
|
||||||
id: id,
|
|
||||||
nick: nick,
|
|
||||||
lastSeen: time.Unix(lastSeen.Int64, 0),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAllIdleEntries(db *sqlx.DB) (idleEntries, error) {
|
|
||||||
rows, err := db.Query(`select id, nick, max(lastSeen) from downtime
|
|
||||||
group by nick`)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
entries := idleEntries{}
|
|
||||||
for rows.Next() {
|
|
||||||
var e idleEntry
|
|
||||||
err := rows.Scan(&e.id, &e.nick, &e.lastSeen)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
entries = append(entries, &e)
|
|
||||||
}
|
|
||||||
return entries, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type idleEntries []*idleEntry
|
|
||||||
|
|
||||||
func (ie idleEntries) Len() int {
|
|
||||||
return len(ie)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ie idleEntries) Less(i, j int) bool {
|
|
||||||
return ie[i].lastSeen.Before(ie[j].lastSeen)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ie idleEntries) Swap(i, j int) {
|
|
||||||
ie[i], ie[j] = ie[j], ie[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDowntimePlugin creates a new DowntimePlugin with the Plugin interface
|
|
||||||
func New(bot bot.Bot) *DowntimePlugin {
|
|
||||||
p := DowntimePlugin{
|
|
||||||
Bot: bot,
|
|
||||||
db: bot.DB(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if bot.DBVersion() == 1 {
|
|
||||||
_, 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &p
|
|
||||||
}
|
|
||||||
|
|
||||||
// Message responds to the bot hook on recieving messages.
|
|
||||||
// This function returns true if the plugin responds in a meaningful way to the users message.
|
|
||||||
// Otherwise, the function returns false and the bot continues execution of other plugins.
|
|
||||||
func (p *DowntimePlugin) Message(message msg.Message) bool {
|
|
||||||
// If it's a command and the payload is idle <nick>, give it. Log everything.
|
|
||||||
|
|
||||||
parts := strings.Fields(strings.ToLower(message.Body))
|
|
||||||
channel := message.Channel
|
|
||||||
ret := false
|
|
||||||
|
|
||||||
if len(parts) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if parts[0] == "idle" && len(parts) == 2 {
|
|
||||||
nick := parts[1]
|
|
||||||
// parts[1] must be the userid, or we don't know them
|
|
||||||
entry, err := getIdleEntryByNick(p.db, nick)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Error getting idle entry: ", err)
|
|
||||||
}
|
|
||||||
if !entry.id.Valid {
|
|
||||||
// couldn't find em
|
|
||||||
p.Bot.SendMessage(channel, fmt.Sprintf("Sorry, I don't know %s.", nick))
|
|
||||||
} else {
|
|
||||||
p.Bot.SendMessage(channel, fmt.Sprintf("%s has been idle for: %s",
|
|
||||||
nick, time.Now().Sub(entry.lastSeen)))
|
|
||||||
}
|
|
||||||
ret = true
|
|
||||||
} else if parts[0] == "idle" && len(parts) == 1 {
|
|
||||||
// Find all idle times, report them.
|
|
||||||
entries, err := getAllIdleEntries(p.db)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Error retrieving idle entries: ", err)
|
|
||||||
}
|
|
||||||
sort.Sort(entries)
|
|
||||||
tops := "The top entries are: "
|
|
||||||
for _, e := range entries {
|
|
||||||
|
|
||||||
// filter out ZNC entries and ourself
|
|
||||||
if strings.HasPrefix(e.nick, "*") || strings.ToLower(p.Bot.Config().Nick) == e.nick {
|
|
||||||
p.remove(e.nick)
|
|
||||||
} else {
|
|
||||||
tops = fmt.Sprintf("%s%s: %s ", tops, e.nick, time.Now().Sub(e.lastSeen))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.Bot.SendMessage(channel, tops)
|
|
||||||
ret = true
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
p.record(strings.ToLower(message.User.Name))
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DowntimePlugin) record(user string) {
|
|
||||||
entry, err := getIdleEntryByNick(p.db, user)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Error recording downtime: ", err)
|
|
||||||
}
|
|
||||||
entry.lastSeen = time.Now()
|
|
||||||
entry.saveIdleEntry(p.db)
|
|
||||||
log.Println("Inserted downtime for:", user)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DowntimePlugin) remove(user string) error {
|
|
||||||
_, err := p.db.Exec(`delete from downtime where nick = ?`, user)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Error removing downtime for user: ", user, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Println("Removed downtime for:", user)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Help responds to help requests. Every plugin must implement a help function.
|
|
||||||
func (p *DowntimePlugin) Help(channel string, parts []string) {
|
|
||||||
p.Bot.SendMessage(channel, "Ask me how long one of your friends has been idele with, \"idle <nick>\"")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
// user joined, let's nail them for it
|
|
||||||
if kind == "NICK" {
|
|
||||||
p.record(strings.ToLower(message.Channel))
|
|
||||||
p.remove(strings.ToLower(message.User.Name))
|
|
||||||
} else {
|
|
||||||
p.record(strings.ToLower(message.User.Name))
|
|
||||||
}
|
|
||||||
} else if kind == "PART" || kind == "QUIT" {
|
|
||||||
p.remove(strings.ToLower(message.User.Name))
|
|
||||||
} else {
|
|
||||||
log.Println("Unknown event: ", kind, message.User, message)
|
|
||||||
p.record(strings.ToLower(message.User.Name))
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handler for bot's own messages
|
|
||||||
func (p *DowntimePlugin) BotMessage(message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register any web URLs desired
|
|
||||||
func (p *DowntimePlugin) RegisterWeb() *string {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DowntimePlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
|
|
@ -5,11 +5,12 @@ package emojifyme
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
)
|
)
|
||||||
|
@ -20,15 +21,15 @@ type EmojifyMePlugin struct {
|
||||||
Emoji map[string]string
|
Emoji map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(bot bot.Bot) *EmojifyMePlugin {
|
func New(b bot.Bot) *EmojifyMePlugin {
|
||||||
resp, err := http.Get("https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json")
|
resp, err := http.Get("https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error generic emoji list: %s", err)
|
log.Fatal().Err(err).Msg("Error generic emoji list")
|
||||||
}
|
}
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error generic emoji list body: %s", err)
|
log.Fatal().Err(err).Msg("Error generic emoji list body")
|
||||||
}
|
}
|
||||||
|
|
||||||
type Emoji struct {
|
type Emoji struct {
|
||||||
|
@ -38,7 +39,7 @@ func New(bot bot.Bot) *EmojifyMePlugin {
|
||||||
var emoji []Emoji
|
var emoji []Emoji
|
||||||
err = json.Unmarshal(body, &emoji)
|
err = json.Unmarshal(body, &emoji)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error parsing emoji list: %s", err)
|
log.Fatal().Err(err).Msg("Error parsing emoji list")
|
||||||
}
|
}
|
||||||
|
|
||||||
emojiMap := map[string]string{}
|
emojiMap := map[string]string{}
|
||||||
|
@ -48,14 +49,16 @@ func New(bot bot.Bot) *EmojifyMePlugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &EmojifyMePlugin{
|
ep := &EmojifyMePlugin{
|
||||||
Bot: bot,
|
Bot: b,
|
||||||
GotBotEmoji: false,
|
GotBotEmoji: false,
|
||||||
Emoji: emojiMap,
|
Emoji: emojiMap,
|
||||||
}
|
}
|
||||||
|
b.Register(ep, bot.Message, ep.message)
|
||||||
|
return ep
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *EmojifyMePlugin) Message(message msg.Message) bool {
|
func (p *EmojifyMePlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
if !p.GotBotEmoji {
|
if !p.GotBotEmoji {
|
||||||
p.GotBotEmoji = true
|
p.GotBotEmoji = true
|
||||||
emojiMap := p.Bot.GetEmojiList()
|
emojiMap := p.Bot.GetEmojiList()
|
||||||
|
@ -64,61 +67,39 @@ func (p *EmojifyMePlugin) Message(message msg.Message) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inertTokens := p.Bot.Config().Emojify.Scoreless
|
inertTokens := p.Bot.Config().GetArray("Emojify.Scoreless", []string{})
|
||||||
emojied := 0.0
|
emojied := 0.0
|
||||||
tokens := strings.Fields(strings.ToLower(message.Body))
|
emojys := []string{}
|
||||||
for i, token := range tokens {
|
msg := strings.Replace(strings.ToLower(message.Body), "_", " ", -1)
|
||||||
if _, ok := p.Emoji[token]; ok {
|
for k, v := range p.Emoji {
|
||||||
if !stringsContain(inertTokens, token) {
|
k = strings.Replace(k, "_", " ", -1)
|
||||||
emojied++
|
candidates := []string{
|
||||||
|
k,
|
||||||
|
k + "es",
|
||||||
|
k + "s",
|
||||||
}
|
}
|
||||||
tokens[i] = ":" + token + ":"
|
for _, c := range candidates {
|
||||||
} else if strings.HasSuffix(token, "s") {
|
if strings.Contains(msg, " "+c+" ") ||
|
||||||
//Check to see if we can strip the trailing "s" off and get an emoji
|
strings.HasPrefix(msg, c+" ") ||
|
||||||
temp := strings.TrimSuffix(token, "s")
|
strings.HasSuffix(msg, " "+c) ||
|
||||||
if _, ok := p.Emoji[temp]; ok {
|
msg == c {
|
||||||
if !stringsContain(inertTokens, temp) {
|
emojys = append(emojys, v)
|
||||||
emojied++
|
if !stringsContain(inertTokens, k) && len(v) > 2 {
|
||||||
}
|
emojied += 1
|
||||||
tokens[i] = ":" + temp + ":s"
|
|
||||||
} else if strings.HasSuffix(token, "es") {
|
|
||||||
//Check to see if we can strip the trailing "es" off and get an emoji
|
|
||||||
temp := strings.TrimSuffix(token, "es")
|
|
||||||
if _, ok := p.Emoji[temp]; ok {
|
|
||||||
if !stringsContain(inertTokens, temp) {
|
|
||||||
emojied++
|
|
||||||
}
|
|
||||||
tokens[i] = ":" + temp + ":es"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if emojied > 0 && rand.Float64() <= p.Bot.Config().Emojify.Chance*emojied {
|
|
||||||
modified := strings.Join(tokens, " ")
|
if emojied > 0 && rand.Float64() <= p.Bot.Config().GetFloat64("Emojify.Chance", 0.02)*emojied {
|
||||||
p.Bot.SendMessage(message.Channel, modified)
|
for _, e := range emojys {
|
||||||
return true
|
p.Bot.Send(c, bot.Reaction, message.Channel, e, message)
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *EmojifyMePlugin) Help(channel string, parts []string) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *EmojifyMePlugin) Event(kind string, message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *EmojifyMePlugin) BotMessage(message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *EmojifyMePlugin) RegisterWeb() *string {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *EmojifyMePlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
|
||||||
|
|
||||||
func stringsContain(haystack []string, needle string) bool {
|
func stringsContain(haystack []string, needle string) bool {
|
||||||
for _, s := range haystack {
|
for _, s := range haystack {
|
||||||
if s == needle {
|
if s == needle {
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
package fact
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
|
"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 c = &cli.CliPlugin{}
|
||||||
|
|
||||||
|
func makeMessage(nick, payload string) msg.Message {
|
||||||
|
isCmd := strings.HasPrefix(payload, "!")
|
||||||
|
if isCmd {
|
||||||
|
payload = payload[1:]
|
||||||
|
}
|
||||||
|
return msg.Message{
|
||||||
|
User: &user.User{Name: nick},
|
||||||
|
Channel: "test",
|
||||||
|
Body: payload,
|
||||||
|
Command: isCmd,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makePlugin(t *testing.T) (*FactoidPlugin, *bot.MockBot) {
|
||||||
|
mb := bot.NewMockBot()
|
||||||
|
f := New(mb) // for DB table
|
||||||
|
return f, mb
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReact(t *testing.T) {
|
||||||
|
msgs := []msg.Message{
|
||||||
|
makeMessage("user1", "!testing123 <react> jesus"),
|
||||||
|
makeMessage("user2", "testing123"),
|
||||||
|
}
|
||||||
|
p, mb := makePlugin(t)
|
||||||
|
|
||||||
|
for _, m := range msgs {
|
||||||
|
p.message(c, bot.Message, m)
|
||||||
|
}
|
||||||
|
assert.Len(t, mb.Reactions, 1)
|
||||||
|
assert.Contains(t, mb.Reactions[0], "jesus")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReactCantLearnSpaces(t *testing.T) {
|
||||||
|
msgs := []msg.Message{
|
||||||
|
makeMessage("user1", "!test <react> jesus christ"),
|
||||||
|
}
|
||||||
|
p, mb := makePlugin(t)
|
||||||
|
|
||||||
|
for _, m := range msgs {
|
||||||
|
p.message(c, bot.Message, m)
|
||||||
|
}
|
||||||
|
assert.Len(t, mb.Messages, 1)
|
||||||
|
assert.Contains(t, mb.Messages[0], "not a valid")
|
||||||
|
}
|
|
@ -4,15 +4,17 @@ package fact
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"log"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
|
@ -22,14 +24,14 @@ import (
|
||||||
// respond to queries in a way that is unpredictable and fun
|
// respond to queries in a way that is unpredictable and fun
|
||||||
|
|
||||||
// factoid stores info about our factoid for lookup and later interaction
|
// factoid stores info about our factoid for lookup and later interaction
|
||||||
type factoid struct {
|
type Factoid struct {
|
||||||
id sql.NullInt64
|
ID sql.NullInt64
|
||||||
Fact string
|
Fact string
|
||||||
Tidbit string
|
Tidbit string
|
||||||
Verb string
|
Verb string
|
||||||
Owner string
|
Owner string
|
||||||
created time.Time
|
Created time.Time
|
||||||
accessed time.Time
|
Accessed time.Time
|
||||||
Count int
|
Count int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,14 +40,14 @@ type alias struct {
|
||||||
Next string
|
Next string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *alias) resolve(db *sqlx.DB) (*factoid, error) {
|
func (a *alias) resolve(db *sqlx.DB) (*Factoid, error) {
|
||||||
// perform DB query to fill the To field
|
// perform DB query to fill the To field
|
||||||
q := `select fact, next from factoid_alias where fact=?`
|
q := `select fact, next from factoid_alias where fact=?`
|
||||||
var next alias
|
var next alias
|
||||||
err := db.Get(&next, q, a.Next)
|
err := db.Get(&next, q, a.Next)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// we hit the end of the chain, get a factoid named Next
|
// we hit the end of the chain, get a factoid named Next
|
||||||
fact, err := getSingleFact(db, a.Next)
|
fact, err := GetSingleFact(db, a.Next)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Errorf("Error resolvig alias %v: %v", a, err)
|
err := fmt.Errorf("Error resolvig alias %v: %v", a, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -55,7 +57,7 @@ func (a *alias) resolve(db *sqlx.DB) (*factoid, error) {
|
||||||
return next.resolve(db)
|
return next.resolve(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
func findAlias(db *sqlx.DB, fact string) (bool, *factoid) {
|
func findAlias(db *sqlx.DB, fact string) (bool, *Factoid) {
|
||||||
q := `select * from factoid_alias where fact=?`
|
q := `select * from factoid_alias where fact=?`
|
||||||
var a alias
|
var a alias
|
||||||
err := db.Get(&a, q, fact)
|
err := db.Get(&a, q, fact)
|
||||||
|
@ -89,9 +91,9 @@ func aliasFromStrings(from, to string) *alias {
|
||||||
return &alias{from, to}
|
return &alias{from, to}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *factoid) save(db *sqlx.DB) error {
|
func (f *Factoid) Save(db *sqlx.DB) error {
|
||||||
var err error
|
var err error
|
||||||
if f.id.Valid {
|
if f.ID.Valid {
|
||||||
// update
|
// update
|
||||||
_, err = db.Exec(`update factoid set
|
_, err = db.Exec(`update factoid set
|
||||||
fact=?,
|
fact=?,
|
||||||
|
@ -105,12 +107,12 @@ func (f *factoid) save(db *sqlx.DB) error {
|
||||||
f.Tidbit,
|
f.Tidbit,
|
||||||
f.Verb,
|
f.Verb,
|
||||||
f.Owner,
|
f.Owner,
|
||||||
f.accessed.Unix(),
|
f.Accessed.Unix(),
|
||||||
f.Count,
|
f.Count,
|
||||||
f.id.Int64)
|
f.ID.Int64)
|
||||||
} else {
|
} else {
|
||||||
f.created = time.Now()
|
f.Created = time.Now()
|
||||||
f.accessed = time.Now()
|
f.Accessed = time.Now()
|
||||||
// insert
|
// insert
|
||||||
res, err := db.Exec(`insert into factoid (
|
res, err := db.Exec(`insert into factoid (
|
||||||
fact,
|
fact,
|
||||||
|
@ -125,8 +127,8 @@ func (f *factoid) save(db *sqlx.DB) error {
|
||||||
f.Tidbit,
|
f.Tidbit,
|
||||||
f.Verb,
|
f.Verb,
|
||||||
f.Owner,
|
f.Owner,
|
||||||
f.created.Unix(),
|
f.Created.Unix(),
|
||||||
f.accessed.Unix(),
|
f.Accessed.Unix(),
|
||||||
f.Count,
|
f.Count,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -134,23 +136,23 @@ func (f *factoid) save(db *sqlx.DB) error {
|
||||||
}
|
}
|
||||||
id, err := res.LastInsertId()
|
id, err := res.LastInsertId()
|
||||||
// hackhackhack?
|
// hackhackhack?
|
||||||
f.id.Int64 = id
|
f.ID.Int64 = id
|
||||||
f.id.Valid = true
|
f.ID.Valid = true
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *factoid) delete(db *sqlx.DB) error {
|
func (f *Factoid) delete(db *sqlx.DB) error {
|
||||||
var err error
|
var err error
|
||||||
if f.id.Valid {
|
if f.ID.Valid {
|
||||||
_, err = db.Exec(`delete from factoid where id=?`, f.id)
|
_, err = db.Exec(`delete from factoid where id=?`, f.ID)
|
||||||
}
|
}
|
||||||
f.id.Valid = false
|
f.ID.Valid = false
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFacts(db *sqlx.DB, fact string, tidbit string) ([]*factoid, error) {
|
func getFacts(db *sqlx.DB, fact string, tidbit string) ([]*Factoid, error) {
|
||||||
var fs []*factoid
|
var fs []*Factoid
|
||||||
query := `select
|
query := `select
|
||||||
id,
|
id,
|
||||||
fact,
|
fact,
|
||||||
|
@ -166,15 +168,15 @@ func getFacts(db *sqlx.DB, fact string, tidbit string) ([]*factoid, error) {
|
||||||
rows, err := db.Query(query,
|
rows, err := db.Query(query,
|
||||||
"%"+fact+"%", "%"+tidbit+"%")
|
"%"+fact+"%", "%"+tidbit+"%")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error regexping for facts: %s", err)
|
log.Error().Err(err).Msg("Error regexping for facts")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var f factoid
|
var f Factoid
|
||||||
var tmpCreated int64
|
var tmpCreated int64
|
||||||
var tmpAccessed int64
|
var tmpAccessed int64
|
||||||
err := rows.Scan(
|
err := rows.Scan(
|
||||||
&f.id,
|
&f.ID,
|
||||||
&f.Fact,
|
&f.Fact,
|
||||||
&f.Tidbit,
|
&f.Tidbit,
|
||||||
&f.Verb,
|
&f.Verb,
|
||||||
|
@ -186,15 +188,15 @@ func getFacts(db *sqlx.DB, fact string, tidbit string) ([]*factoid, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
f.created = time.Unix(tmpCreated, 0)
|
f.Created = time.Unix(tmpCreated, 0)
|
||||||
f.accessed = time.Unix(tmpAccessed, 0)
|
f.Accessed = time.Unix(tmpAccessed, 0)
|
||||||
fs = append(fs, &f)
|
fs = append(fs, &f)
|
||||||
}
|
}
|
||||||
return fs, err
|
return fs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSingle(db *sqlx.DB) (*factoid, error) {
|
func GetSingle(db *sqlx.DB) (*Factoid, error) {
|
||||||
var f factoid
|
var f Factoid
|
||||||
var tmpCreated int64
|
var tmpCreated int64
|
||||||
var tmpAccessed int64
|
var tmpAccessed int64
|
||||||
err := db.QueryRow(`select
|
err := db.QueryRow(`select
|
||||||
|
@ -208,7 +210,7 @@ func getSingle(db *sqlx.DB) (*factoid, error) {
|
||||||
count
|
count
|
||||||
from factoid
|
from factoid
|
||||||
order by random() limit 1;`).Scan(
|
order by random() limit 1;`).Scan(
|
||||||
&f.id,
|
&f.ID,
|
||||||
&f.Fact,
|
&f.Fact,
|
||||||
&f.Tidbit,
|
&f.Tidbit,
|
||||||
&f.Verb,
|
&f.Verb,
|
||||||
|
@ -217,13 +219,13 @@ func getSingle(db *sqlx.DB) (*factoid, error) {
|
||||||
&tmpAccessed,
|
&tmpAccessed,
|
||||||
&f.Count,
|
&f.Count,
|
||||||
)
|
)
|
||||||
f.created = time.Unix(tmpCreated, 0)
|
f.Created = time.Unix(tmpCreated, 0)
|
||||||
f.accessed = time.Unix(tmpAccessed, 0)
|
f.Accessed = time.Unix(tmpAccessed, 0)
|
||||||
return &f, err
|
return &f, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSingleFact(db *sqlx.DB, fact string) (*factoid, error) {
|
func GetSingleFact(db *sqlx.DB, fact string) (*Factoid, error) {
|
||||||
var f factoid
|
var f Factoid
|
||||||
var tmpCreated int64
|
var tmpCreated int64
|
||||||
var tmpAccessed int64
|
var tmpAccessed int64
|
||||||
err := db.QueryRow(`select
|
err := db.QueryRow(`select
|
||||||
|
@ -239,7 +241,7 @@ func getSingleFact(db *sqlx.DB, fact string) (*factoid, error) {
|
||||||
where fact like ?
|
where fact like ?
|
||||||
order by random() limit 1;`,
|
order by random() limit 1;`,
|
||||||
fact).Scan(
|
fact).Scan(
|
||||||
&f.id,
|
&f.ID,
|
||||||
&f.Fact,
|
&f.Fact,
|
||||||
&f.Tidbit,
|
&f.Tidbit,
|
||||||
&f.Verb,
|
&f.Verb,
|
||||||
|
@ -248,22 +250,22 @@ func getSingleFact(db *sqlx.DB, fact string) (*factoid, error) {
|
||||||
&tmpAccessed,
|
&tmpAccessed,
|
||||||
&f.Count,
|
&f.Count,
|
||||||
)
|
)
|
||||||
f.created = time.Unix(tmpCreated, 0)
|
f.Created = time.Unix(tmpCreated, 0)
|
||||||
f.accessed = time.Unix(tmpAccessed, 0)
|
f.Accessed = time.Unix(tmpAccessed, 0)
|
||||||
return &f, err
|
return &f, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Factoid provides the necessary plugin-wide needs
|
// Factoid provides the necessary plugin-wide needs
|
||||||
type Factoid struct {
|
type FactoidPlugin struct {
|
||||||
Bot bot.Bot
|
Bot bot.Bot
|
||||||
NotFound []string
|
NotFound []string
|
||||||
LastFact *factoid
|
LastFact *Factoid
|
||||||
db *sqlx.DB
|
db *sqlx.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFactoid creates a new Factoid with the Plugin interface
|
// NewFactoid creates a new Factoid with the Plugin interface
|
||||||
func New(botInst bot.Bot) *Factoid {
|
func New(botInst bot.Bot) *FactoidPlugin {
|
||||||
p := &Factoid{
|
p := &FactoidPlugin{
|
||||||
Bot: botInst,
|
Bot: botInst,
|
||||||
NotFound: []string{
|
NotFound: []string{
|
||||||
"I don't know.",
|
"I don't know.",
|
||||||
|
@ -276,6 +278,8 @@ func New(botInst bot.Bot) *Factoid {
|
||||||
db: botInst.DB(),
|
db: botInst.DB(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c := botInst.DefaultConnector()
|
||||||
|
|
||||||
if _, err := p.db.Exec(`create table if not exists factoid (
|
if _, err := p.db.Exec(`create table if not exists factoid (
|
||||||
id integer primary key,
|
id integer primary key,
|
||||||
fact string,
|
fact string,
|
||||||
|
@ -286,7 +290,7 @@ func New(botInst bot.Bot) *Factoid {
|
||||||
accessed integer,
|
accessed integer,
|
||||||
count integer
|
count integer
|
||||||
);`); err != nil {
|
);`); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal().Err(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := p.db.Exec(`create table if not exists factoid_alias (
|
if _, err := p.db.Exec(`create table if not exists factoid_alias (
|
||||||
|
@ -294,17 +298,17 @@ func New(botInst bot.Bot) *Factoid {
|
||||||
next string,
|
next string,
|
||||||
primary key (fact, next)
|
primary key (fact, next)
|
||||||
);`); err != nil {
|
);`); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal().Err(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, channel := range botInst.Config().Channels {
|
for _, channel := range botInst.Config().GetArray("channels", []string{}) {
|
||||||
go p.factTimer(channel)
|
go p.factTimer(c, channel)
|
||||||
|
|
||||||
go func(ch string) {
|
go func(ch string) {
|
||||||
// Some random time to start up
|
// Some random time to start up
|
||||||
time.Sleep(time.Duration(15) * time.Second)
|
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", "speed test")); ok {
|
||||||
p.sayFact(msg.Message{
|
p.sayFact(c, msg.Message{
|
||||||
Channel: ch,
|
Channel: ch,
|
||||||
Body: "speed test", // BUG: This is defined in the config too
|
Body: "speed test", // BUG: This is defined in the config too
|
||||||
Command: true,
|
Command: true,
|
||||||
|
@ -314,6 +318,11 @@ func New(botInst bot.Bot) *Factoid {
|
||||||
}(channel)
|
}(channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
botInst.Register(p, bot.Message, p.message)
|
||||||
|
botInst.Register(p, bot.Help, p.help)
|
||||||
|
|
||||||
|
p.registerWeb()
|
||||||
|
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,45 +347,53 @@ func findAction(message string) string {
|
||||||
|
|
||||||
// learnFact assumes we have a learning situation and inserts a new fact
|
// learnFact assumes we have a learning situation and inserts a new fact
|
||||||
// into the database
|
// into the database
|
||||||
func (p *Factoid) learnFact(message msg.Message, fact, verb, tidbit string) bool {
|
func (p *FactoidPlugin) learnFact(message msg.Message, fact, verb, tidbit string) error {
|
||||||
verb = strings.ToLower(verb)
|
verb = strings.ToLower(verb)
|
||||||
|
if verb == "react" {
|
||||||
|
// This would be a great place to check against the API for valid emojy
|
||||||
|
// I'm too lazy for that
|
||||||
|
tidbit = strings.Replace(tidbit, ":", "", -1)
|
||||||
|
if len(strings.Split(tidbit, " ")) > 1 {
|
||||||
|
return fmt.Errorf("That's not a valid emojy.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var count sql.NullInt64
|
var count sql.NullInt64
|
||||||
err := p.db.QueryRow(`select count(*) from factoid
|
err := p.db.QueryRow(`select count(*) from factoid
|
||||||
where fact=? and verb=? and tidbit=?`,
|
where fact=? and verb=? and tidbit=?`,
|
||||||
fact, verb, tidbit).Scan(&count)
|
fact, verb, tidbit).Scan(&count)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error counting facts: ", err)
|
log.Error().Err(err).Msg("Error counting facts")
|
||||||
return false
|
return fmt.Errorf("What?")
|
||||||
} else if count.Valid && count.Int64 != 0 {
|
} else if count.Valid && count.Int64 != 0 {
|
||||||
log.Println("User tried to relearn a fact.")
|
log.Debug().Msg("User tried to relearn a fact.")
|
||||||
return false
|
return fmt.Errorf("Look, I already know that.")
|
||||||
}
|
}
|
||||||
|
|
||||||
n := factoid{
|
n := Factoid{
|
||||||
Fact: fact,
|
Fact: fact,
|
||||||
Tidbit: tidbit,
|
Tidbit: tidbit,
|
||||||
Verb: verb,
|
Verb: verb,
|
||||||
Owner: message.User.Name,
|
Owner: message.User.Name,
|
||||||
created: time.Now(),
|
Created: time.Now(),
|
||||||
accessed: time.Now(),
|
Accessed: time.Now(),
|
||||||
Count: 0,
|
Count: 0,
|
||||||
}
|
}
|
||||||
p.LastFact = &n
|
p.LastFact = &n
|
||||||
err = n.save(p.db)
|
err = n.Save(p.db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error inserting fact: ", err)
|
log.Error().Err(err).Msg("Error inserting fact")
|
||||||
return false
|
return fmt.Errorf("My brain is overheating.")
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// findTrigger checks to see if a given string is a trigger or not
|
// findTrigger checks to see if a given string is a trigger or not
|
||||||
func (p *Factoid) findTrigger(fact string) (bool, *factoid) {
|
func (p *FactoidPlugin) findTrigger(fact string) (bool, *Factoid) {
|
||||||
fact = strings.ToLower(fact) // TODO: make sure this needs to be lowered here
|
fact = strings.ToLower(fact) // TODO: make sure this needs to be lowered here
|
||||||
|
|
||||||
f, err := getSingleFact(p.db, fact)
|
f, err := GetSingleFact(p.db, fact)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return findAlias(p.db, fact)
|
return findAlias(p.db, fact)
|
||||||
}
|
}
|
||||||
|
@ -385,7 +402,7 @@ func (p *Factoid) findTrigger(fact string) (bool, *factoid) {
|
||||||
|
|
||||||
// sayFact spits out a fact to the channel and updates the fact in the database
|
// sayFact spits out a fact to the channel and updates the fact in the database
|
||||||
// with new time and count information
|
// with new time and count information
|
||||||
func (p *Factoid) sayFact(message msg.Message, fact factoid) {
|
func (p *FactoidPlugin) sayFact(c bot.Connector, message msg.Message, fact Factoid) {
|
||||||
msg := p.Bot.Filter(message, fact.Tidbit)
|
msg := p.Bot.Filter(message, fact.Tidbit)
|
||||||
full := p.Bot.Filter(message, fmt.Sprintf("%s %s %s",
|
full := p.Bot.Filter(message, fmt.Sprintf("%s %s %s",
|
||||||
fact.Fact, fact.Verb, fact.Tidbit,
|
fact.Fact, fact.Verb, fact.Tidbit,
|
||||||
|
@ -397,39 +414,42 @@ func (p *Factoid) sayFact(message msg.Message, fact factoid) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if fact.Verb == "action" {
|
if fact.Verb == "action" {
|
||||||
p.Bot.SendAction(message.Channel, msg)
|
p.Bot.Send(c, bot.Action, message.Channel, msg)
|
||||||
|
} else if fact.Verb == "react" {
|
||||||
|
p.Bot.Send(c, bot.Reaction, message.Channel, msg, message)
|
||||||
} else if fact.Verb == "reply" {
|
} else if fact.Verb == "reply" {
|
||||||
p.Bot.SendMessage(message.Channel, msg)
|
p.Bot.Send(c, bot.Message, message.Channel, msg)
|
||||||
} else {
|
} else {
|
||||||
p.Bot.SendMessage(message.Channel, full)
|
p.Bot.Send(c, bot.Message, message.Channel, full)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// update fact tracking
|
// update fact tracking
|
||||||
fact.accessed = time.Now()
|
fact.Accessed = time.Now()
|
||||||
fact.Count += 1
|
fact.Count += 1
|
||||||
err := fact.save(p.db)
|
err := fact.Save(p.db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Could not update fact.\n")
|
log.Error().
|
||||||
log.Printf("%#v\n", fact)
|
Interface("fact", fact).
|
||||||
log.Println(err)
|
Err(err).
|
||||||
|
Msg("could not update fact")
|
||||||
}
|
}
|
||||||
p.LastFact = &fact
|
p.LastFact = &fact
|
||||||
}
|
}
|
||||||
|
|
||||||
// trigger checks the message for its fitness to be a factoid and then hauls
|
// 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
|
// the message off to sayFact for processing if it is in fact a trigger
|
||||||
func (p *Factoid) trigger(message msg.Message) bool {
|
func (p *FactoidPlugin) trigger(c bot.Connector, message msg.Message) bool {
|
||||||
minLen := p.Bot.Config().Factoid.MinLen
|
minLen := p.Bot.Config().GetInt("Factoid.MinLen", 4)
|
||||||
if len(message.Body) > minLen || message.Command || message.Body == "..." {
|
if len(message.Body) > minLen || message.Command || message.Body == "..." {
|
||||||
if ok, fact := p.findTrigger(message.Body); ok {
|
if ok, fact := p.findTrigger(message.Body); ok {
|
||||||
p.sayFact(message, *fact)
|
p.sayFact(c, message, *fact)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
r := strings.NewReplacer("'", "", "\"", "", ",", "", ".", "", ":", "",
|
r := strings.NewReplacer("'", "", "\"", "", ",", "", ".", "", ":", "",
|
||||||
"?", "", "!", "")
|
"?", "", "!", "")
|
||||||
if ok, fact := p.findTrigger(r.Replace(message.Body)); ok {
|
if ok, fact := p.findTrigger(r.Replace(message.Body)); ok {
|
||||||
p.sayFact(message, *fact)
|
p.sayFact(c, message, *fact)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -438,20 +458,20 @@ func (p *Factoid) trigger(message msg.Message) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// tellThemWhatThatWas is a hilarious name for a function.
|
// tellThemWhatThatWas is a hilarious name for a function.
|
||||||
func (p *Factoid) tellThemWhatThatWas(message msg.Message) bool {
|
func (p *FactoidPlugin) tellThemWhatThatWas(c bot.Connector, message msg.Message) bool {
|
||||||
fact := p.LastFact
|
fact := p.LastFact
|
||||||
var msg string
|
var msg string
|
||||||
if fact == nil {
|
if fact == nil {
|
||||||
msg = "Nope."
|
msg = "Nope."
|
||||||
} else {
|
} else {
|
||||||
msg = fmt.Sprintf("That was (#%d) '%s <%s> %s'",
|
msg = fmt.Sprintf("That was (#%d) '%s <%s> %s'",
|
||||||
fact.id.Int64, fact.Fact, fact.Verb, fact.Tidbit)
|
fact.ID.Int64, fact.Fact, fact.Verb, fact.Tidbit)
|
||||||
}
|
}
|
||||||
p.Bot.SendMessage(message.Channel, msg)
|
p.Bot.Send(c, bot.Message, message.Channel, msg)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Factoid) learnAction(message msg.Message, action string) bool {
|
func (p *FactoidPlugin) learnAction(c bot.Connector, message msg.Message, action string) bool {
|
||||||
body := message.Body
|
body := message.Body
|
||||||
|
|
||||||
parts := strings.SplitN(body, action, 2)
|
parts := strings.SplitN(body, action, 2)
|
||||||
|
@ -465,21 +485,21 @@ func (p *Factoid) learnAction(message msg.Message, action string) bool {
|
||||||
action = strings.TrimSpace(action)
|
action = strings.TrimSpace(action)
|
||||||
|
|
||||||
if len(trigger) == 0 || len(fact) == 0 || len(action) == 0 {
|
if len(trigger) == 0 || len(fact) == 0 || len(action) == 0 {
|
||||||
p.Bot.SendMessage(message.Channel, "I don't want to learn that.")
|
p.Bot.Send(c, bot.Message, message.Channel, "I don't want to learn that.")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(strings.Split(fact, "$and")) > 4 {
|
if len(strings.Split(fact, "$and")) > 4 {
|
||||||
p.Bot.SendMessage(message.Channel, "You can't use more than 4 $and operators.")
|
p.Bot.Send(c, bot.Message, message.Channel, "You can't use more than 4 $and operators.")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
strippedaction := strings.Replace(strings.Replace(action, "<", "", 1), ">", "", 1)
|
strippedaction := strings.Replace(strings.Replace(action, "<", "", 1), ">", "", 1)
|
||||||
|
|
||||||
if p.learnFact(message, trigger, strippedaction, fact) {
|
if err := p.learnFact(message, trigger, strippedaction, fact); err != nil {
|
||||||
p.Bot.SendMessage(message.Channel, fmt.Sprintf("Okay, %s.", message.User.Name))
|
p.Bot.Send(c, bot.Message, message.Channel, err.Error())
|
||||||
} else {
|
} else {
|
||||||
p.Bot.SendMessage(message.Channel, "I already know that.")
|
p.Bot.Send(c, bot.Message, message.Channel, fmt.Sprintf("Okay, %s.", message.User.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
@ -497,26 +517,29 @@ func changeOperator(body string) string {
|
||||||
|
|
||||||
// If the user requesting forget is either the owner of the last learned fact or
|
// If the user requesting forget is either the owner of the last learned fact or
|
||||||
// an admin, it may be deleted
|
// an admin, it may be deleted
|
||||||
func (p *Factoid) forgetLastFact(message msg.Message) bool {
|
func (p *FactoidPlugin) forgetLastFact(c bot.Connector, message msg.Message) bool {
|
||||||
if p.LastFact == nil {
|
if p.LastFact == nil {
|
||||||
p.Bot.SendMessage(message.Channel, "I refuse.")
|
p.Bot.Send(c, bot.Message, message.Channel, "I refuse.")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
err := p.LastFact.delete(p.db)
|
err := p.LastFact.delete(p.db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error removing fact: ", p.LastFact, err)
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Interface("LastFact", p.LastFact).
|
||||||
|
Msg("Error removing fact")
|
||||||
}
|
}
|
||||||
fmt.Printf("Forgot #%d: %s %s %s\n", p.LastFact.id.Int64, p.LastFact.Fact,
|
fmt.Printf("Forgot #%d: %s %s %s\n", p.LastFact.ID.Int64, p.LastFact.Fact,
|
||||||
p.LastFact.Verb, p.LastFact.Tidbit)
|
p.LastFact.Verb, p.LastFact.Tidbit)
|
||||||
p.Bot.SendAction(message.Channel, "hits himself over the head with a skillet")
|
p.Bot.Send(c, bot.Action, message.Channel, "hits himself over the head with a skillet")
|
||||||
p.LastFact = nil
|
p.LastFact = nil
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow users to change facts with a simple regexp
|
// Allow users to change facts with a simple regexp
|
||||||
func (p *Factoid) changeFact(message msg.Message) bool {
|
func (p *FactoidPlugin) changeFact(c bot.Connector, message msg.Message) bool {
|
||||||
oper := changeOperator(message.Body)
|
oper := changeOperator(message.Body)
|
||||||
parts := strings.SplitN(message.Body, oper, 2)
|
parts := strings.SplitN(message.Body, oper, 2)
|
||||||
userexp := strings.TrimSpace(parts[1])
|
userexp := strings.TrimSpace(parts[1])
|
||||||
|
@ -524,12 +547,16 @@ func (p *Factoid) changeFact(message msg.Message) bool {
|
||||||
|
|
||||||
parts = strings.Split(userexp, "/")
|
parts = strings.Split(userexp, "/")
|
||||||
|
|
||||||
log.Printf("changeFact: %s %s %#v", trigger, userexp, parts)
|
log.Debug().
|
||||||
|
Str("trigger", trigger).
|
||||||
|
Str("userexp", userexp).
|
||||||
|
Strs("parts", parts).
|
||||||
|
Msg("changefact")
|
||||||
|
|
||||||
if len(parts) == 4 {
|
if len(parts) == 4 {
|
||||||
// replacement
|
// replacement
|
||||||
if parts[0] != "s" {
|
if parts[0] != "s" {
|
||||||
p.Bot.SendMessage(message.Channel, "Nah.")
|
p.Bot.Send(c, bot.Message, message.Channel, "Nah.")
|
||||||
}
|
}
|
||||||
find := parts[1]
|
find := parts[1]
|
||||||
replace := parts[2]
|
replace := parts[2]
|
||||||
|
@ -537,17 +564,20 @@ func (p *Factoid) changeFact(message msg.Message) bool {
|
||||||
// replacement
|
// replacement
|
||||||
result, err := getFacts(p.db, trigger, parts[1])
|
result, err := getFacts(p.db, trigger, parts[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error getting facts: ", trigger, err)
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Str("trigger", trigger).
|
||||||
|
Msg("Error getting facts")
|
||||||
}
|
}
|
||||||
if userexp[len(userexp)-1] != 'g' {
|
if userexp[len(userexp)-1] != 'g' {
|
||||||
result = result[:1]
|
result = result[:1]
|
||||||
}
|
}
|
||||||
// make the changes
|
// make the changes
|
||||||
msg := fmt.Sprintf("Changing %d facts.", len(result))
|
msg := fmt.Sprintf("Changing %d facts.", len(result))
|
||||||
p.Bot.SendMessage(message.Channel, msg)
|
p.Bot.Send(c, bot.Message, message.Channel, msg)
|
||||||
reg, err := regexp.Compile(find)
|
reg, err := regexp.Compile(find)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.Bot.SendMessage(message.Channel, "I don't really want to.")
|
p.Bot.Send(c, bot.Message, message.Channel, "I don't really want to.")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for _, fact := range result {
|
for _, fact := range result {
|
||||||
|
@ -556,27 +586,30 @@ func (p *Factoid) changeFact(message msg.Message) bool {
|
||||||
fact.Verb = reg.ReplaceAllString(fact.Verb, replace)
|
fact.Verb = reg.ReplaceAllString(fact.Verb, replace)
|
||||||
fact.Tidbit = reg.ReplaceAllString(fact.Tidbit, replace)
|
fact.Tidbit = reg.ReplaceAllString(fact.Tidbit, replace)
|
||||||
fact.Count += 1
|
fact.Count += 1
|
||||||
fact.accessed = time.Now()
|
fact.Accessed = time.Now()
|
||||||
fact.save(p.db)
|
fact.Save(p.db)
|
||||||
}
|
}
|
||||||
} else if len(parts) == 3 {
|
} else if len(parts) == 3 {
|
||||||
// search for a factoid and print it
|
// search for a factoid and print it
|
||||||
result, err := getFacts(p.db, trigger, parts[1])
|
result, err := getFacts(p.db, trigger, parts[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error getting facts: ", trigger, err)
|
log.Error().
|
||||||
p.Bot.SendMessage(message.Channel, "bzzzt")
|
Err(err).
|
||||||
|
Str("trigger", trigger).
|
||||||
|
Msg("Error getting facts")
|
||||||
|
p.Bot.Send(c, bot.Message, message.Channel, "bzzzt")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
count := len(result)
|
count := len(result)
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
p.Bot.SendMessage(message.Channel, "I didn't find any facts like that.")
|
p.Bot.Send(c, bot.Message, message.Channel, "I didn't find any facts like that.")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if parts[2] == "g" && len(result) > 4 {
|
if parts[2] == "g" && len(result) > 4 {
|
||||||
// summarize
|
// summarize
|
||||||
result = result[:4]
|
result = result[:4]
|
||||||
} else {
|
} else {
|
||||||
p.sayFact(message, *result[0])
|
p.sayFact(c, message, *result[0])
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
msg := fmt.Sprintf("%s ", trigger)
|
msg := fmt.Sprintf("%s ", trigger)
|
||||||
|
@ -589,9 +622,9 @@ func (p *Factoid) changeFact(message msg.Message) bool {
|
||||||
if count > 4 {
|
if count > 4 {
|
||||||
msg = fmt.Sprintf("%s | ...and %d others", msg, count)
|
msg = fmt.Sprintf("%s | ...and %d others", msg, count)
|
||||||
}
|
}
|
||||||
p.Bot.SendMessage(message.Channel, msg)
|
p.Bot.Send(c, bot.Message, message.Channel, msg)
|
||||||
} else {
|
} else {
|
||||||
p.Bot.SendMessage(message.Channel, "I don't know what you mean.")
|
p.Bot.Send(c, bot.Message, message.Channel, "I don't know what you mean.")
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -599,79 +632,77 @@ func (p *Factoid) changeFact(message msg.Message) bool {
|
||||||
// Message responds to the bot hook on recieving messages.
|
// Message responds to the bot hook on recieving messages.
|
||||||
// This function returns true if the plugin responds in a meaningful way to the users message.
|
// 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.
|
// Otherwise, the function returns false and the bot continues execution of other plugins.
|
||||||
func (p *Factoid) Message(message msg.Message) bool {
|
func (p *FactoidPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
if strings.ToLower(message.Body) == "what was that?" {
|
if strings.ToLower(message.Body) == "what was that?" {
|
||||||
return p.tellThemWhatThatWas(message)
|
return p.tellThemWhatThatWas(c, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This plugin has no business with normal messages
|
// This plugin has no business with normal messages
|
||||||
if !message.Command {
|
if !message.Command {
|
||||||
// look for any triggers in the db matching this message
|
// look for any triggers in the db matching this message
|
||||||
return p.trigger(message)
|
return p.trigger(c, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(strings.ToLower(message.Body), "alias") {
|
if strings.HasPrefix(strings.ToLower(message.Body), "alias") {
|
||||||
log.Printf("Trying to learn an alias: %s", message.Body)
|
log.Debug().
|
||||||
|
Str("alias", message.Body).
|
||||||
|
Msg("Trying to learn an alias")
|
||||||
m := strings.TrimPrefix(message.Body, "alias ")
|
m := strings.TrimPrefix(message.Body, "alias ")
|
||||||
parts := strings.SplitN(m, "->", 2)
|
parts := strings.SplitN(m, "->", 2)
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
p.Bot.SendMessage(message.Channel, "If you want to alias something, use: `alias this -> that`")
|
p.Bot.Send(c, bot.Message, message.Channel, "If you want to alias something, use: `alias this -> that`")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
a := aliasFromStrings(strings.TrimSpace(parts[1]), strings.TrimSpace(parts[0]))
|
a := aliasFromStrings(strings.TrimSpace(parts[1]), strings.TrimSpace(parts[0]))
|
||||||
if err := a.save(p.db); err != nil {
|
if err := a.save(p.db); err != nil {
|
||||||
p.Bot.SendMessage(message.Channel, err.Error())
|
p.Bot.Send(c, bot.Message, message.Channel, err.Error())
|
||||||
} else {
|
} else {
|
||||||
p.Bot.SendAction(message.Channel, "learns a new synonym")
|
p.Bot.Send(c, bot.Action, message.Channel, "learns a new synonym")
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.ToLower(message.Body) == "factoid" {
|
if strings.ToLower(message.Body) == "factoid" {
|
||||||
if fact := p.randomFact(); fact != nil {
|
if fact := p.randomFact(); fact != nil {
|
||||||
p.sayFact(message, *fact)
|
p.sayFact(c, message, *fact)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
log.Println("Got a nil fact.")
|
log.Debug().Msg("Got a nil fact.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.ToLower(message.Body) == "forget that" {
|
if strings.ToLower(message.Body) == "forget that" {
|
||||||
return p.forgetLastFact(message)
|
return p.forgetLastFact(c, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
if changeOperator(message.Body) != "" {
|
if changeOperator(message.Body) != "" {
|
||||||
return p.changeFact(message)
|
return p.changeFact(c, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
action := findAction(message.Body)
|
action := findAction(message.Body)
|
||||||
if action != "" {
|
if action != "" {
|
||||||
return p.learnAction(message, action)
|
return p.learnAction(c, message, action)
|
||||||
}
|
}
|
||||||
|
|
||||||
// look for any triggers in the db matching this message
|
// look for any triggers in the db matching this message
|
||||||
if p.trigger(message) {
|
if p.trigger(c, message) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// We didn't find anything, panic!
|
// We didn't find anything, panic!
|
||||||
p.Bot.SendMessage(message.Channel, p.NotFound[rand.Intn(len(p.NotFound))])
|
p.Bot.Send(c, bot.Message, message.Channel, p.NotFound[rand.Intn(len(p.NotFound))])
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Help responds to help requests. Every plugin must implement a help function.
|
// Help responds to help requests. Every plugin must implement a help function.
|
||||||
func (p *Factoid) Help(channel string, parts []string) {
|
func (p *FactoidPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
p.Bot.SendMessage(channel, "I can learn facts and spit them back out. You can say \"this is that\" or \"he <has> $5\". Later, trigger the factoid by just saying the trigger word, \"this\" or \"he\" in these examples.")
|
p.Bot.Send(c, bot.Message, message.Channel, "I can learn facts and spit them back out. You can say \"this is that\" or \"he <has> $5\". Later, trigger the factoid by just saying the trigger word, \"this\" or \"he\" in these examples.")
|
||||||
p.Bot.SendMessage(channel, "I can also figure out some variables including: $nonzero, $digit, $nick, and $someone.")
|
p.Bot.Send(c, bot.Message, message.Channel, "I can also figure out some variables including: $nonzero, $digit, $nick, and $someone.")
|
||||||
}
|
return true
|
||||||
|
|
||||||
// Empty event handler because this plugin does not do anything on event recv
|
|
||||||
func (p *Factoid) Event(kind string, message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pull a fact at random from the database
|
// Pull a fact at random from the database
|
||||||
func (p *Factoid) randomFact() *factoid {
|
func (p *FactoidPlugin) randomFact() *Factoid {
|
||||||
f, err := getSingle(p.db)
|
f, err := GetSingle(p.db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error getting a fact: ", err)
|
fmt.Println("Error getting a fact: ", err)
|
||||||
return nil
|
return nil
|
||||||
|
@ -680,8 +711,13 @@ func (p *Factoid) randomFact() *factoid {
|
||||||
}
|
}
|
||||||
|
|
||||||
// factTimer spits out a fact at a given interval and with given probability
|
// factTimer spits out a fact at a given interval and with given probability
|
||||||
func (p *Factoid) factTimer(channel string) {
|
func (p *FactoidPlugin) factTimer(c bot.Connector, channel string) {
|
||||||
duration := time.Duration(p.Bot.Config().Factoid.QuoteTime) * time.Minute
|
quoteTime := p.Bot.Config().GetInt("Factoid.QuoteTime", 30)
|
||||||
|
if quoteTime == 0 {
|
||||||
|
quoteTime = 30
|
||||||
|
p.Bot.Config().Set("Factoid.QuoteTime", "30")
|
||||||
|
}
|
||||||
|
duration := time.Duration(quoteTime) * time.Minute
|
||||||
myLastMsg := time.Now()
|
myLastMsg := time.Now()
|
||||||
for {
|
for {
|
||||||
time.Sleep(time.Duration(5) * time.Second) // why 5?
|
time.Sleep(time.Duration(5) * time.Second) // why 5?
|
||||||
|
@ -695,12 +731,17 @@ func (p *Factoid) factTimer(channel string) {
|
||||||
tdelta := time.Since(lastmsg.Time)
|
tdelta := time.Since(lastmsg.Time)
|
||||||
earlier := time.Since(myLastMsg) > tdelta
|
earlier := time.Since(myLastMsg) > tdelta
|
||||||
chance := rand.Float64()
|
chance := rand.Float64()
|
||||||
success := chance < p.Bot.Config().Factoid.QuoteChance
|
quoteChance := p.Bot.Config().GetFloat64("Factoid.QuoteChance", 0.99)
|
||||||
|
if quoteChance == 0.0 {
|
||||||
|
quoteChance = 0.99
|
||||||
|
p.Bot.Config().Set("Factoid.QuoteChance", "0.99")
|
||||||
|
}
|
||||||
|
success := chance < quoteChance
|
||||||
|
|
||||||
if success && tdelta > duration && earlier {
|
if success && tdelta > duration && earlier {
|
||||||
fact := p.randomFact()
|
fact := p.randomFact()
|
||||||
if fact == nil {
|
if fact == nil {
|
||||||
log.Println("Didn't find a random fact to say")
|
log.Debug().Msg("Didn't find a random fact to say")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -711,23 +752,18 @@ func (p *Factoid) factTimer(channel string) {
|
||||||
User: &users[rand.Intn(len(users))],
|
User: &users[rand.Intn(len(users))],
|
||||||
Channel: channel,
|
Channel: channel,
|
||||||
}
|
}
|
||||||
p.sayFact(message, *fact)
|
p.sayFact(c, message, *fact)
|
||||||
myLastMsg = time.Now()
|
myLastMsg = time.Now()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler for bot's own messages
|
|
||||||
func (p *Factoid) BotMessage(message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register any web URLs desired
|
// Register any web URLs desired
|
||||||
func (p *Factoid) RegisterWeb() *string {
|
func (p *FactoidPlugin) registerWeb() {
|
||||||
|
http.HandleFunc("/factoid/api", p.serveAPI)
|
||||||
http.HandleFunc("/factoid/req", p.serveQuery)
|
http.HandleFunc("/factoid/req", p.serveQuery)
|
||||||
http.HandleFunc("/factoid", p.serveQuery)
|
http.HandleFunc("/factoid", p.serveQuery)
|
||||||
tmp := "/factoid"
|
p.Bot.RegisterWeb("/factoid", "Factoid")
|
||||||
return &tmp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func linkify(text string) template.HTML {
|
func linkify(text string) template.HTML {
|
||||||
|
@ -739,30 +775,40 @@ func linkify(text string) template.HTML {
|
||||||
}
|
}
|
||||||
return template.HTML(strings.Join(parts, " "))
|
return template.HTML(strings.Join(parts, " "))
|
||||||
}
|
}
|
||||||
|
func (p *FactoidPlugin) serveAPI(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
fmt.Fprintf(w, "Incorrect HTTP method")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
info := struct {
|
||||||
|
Query string `json:"query"`
|
||||||
|
}{}
|
||||||
|
decoder := json.NewDecoder(r.Body)
|
||||||
|
err := decoder.Decode(&info)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
fmt.Fprint(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Factoid) serveQuery(w http.ResponseWriter, r *http.Request) {
|
entries, err := getFacts(p.db, info.Query, "")
|
||||||
context := make(map[string]interface{})
|
|
||||||
funcMap := template.FuncMap{
|
|
||||||
// The name "title" is what the function will be called in the template text.
|
|
||||||
"linkify": linkify,
|
|
||||||
}
|
|
||||||
if e := r.FormValue("entry"); e != "" {
|
|
||||||
entries, err := getFacts(p.db, e, "")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Web error searching: ", err)
|
w.WriteHeader(500)
|
||||||
|
fmt.Fprint(w, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
context["Count"] = fmt.Sprintf("%d", len(entries))
|
|
||||||
context["Entries"] = entries
|
data, err := json.Marshal(entries)
|
||||||
context["Search"] = e
|
|
||||||
}
|
|
||||||
t, err := template.New("factoidIndex").Funcs(funcMap).Parse(factoidIndex)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
w.WriteHeader(500)
|
||||||
}
|
fmt.Fprint(w, err)
|
||||||
err = t.Execute(w, context)
|
return
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
}
|
||||||
|
w.Write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Factoid) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
var tpl = template.Must(template.New("factoidIndex").Parse(factoidIndex))
|
||||||
|
|
||||||
|
func (p *FactoidPlugin) serveQuery(w http.ResponseWriter, r *http.Request) {
|
||||||
|
tpl.Execute(w, struct{ Nav []bot.EndPoint }{p.Bot.GetWebNavigation()})
|
||||||
|
}
|
||||||
|
|
|
@ -1,174 +0,0 @@
|
||||||
// © 2013 the CatBase Authors under the WTFPL. See AUTHORS for the list of authors.
|
|
||||||
|
|
||||||
package fact
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"github.com/velour/catbase/bot"
|
|
||||||
"github.com/velour/catbase/bot/msg"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This is a skeleton plugin to serve as an example and quick copy/paste for new
|
|
||||||
// plugins.
|
|
||||||
|
|
||||||
type RememberPlugin struct {
|
|
||||||
Bot bot.Bot
|
|
||||||
Log map[string][]msg.Message
|
|
||||||
db *sqlx.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRememberPlugin creates a new RememberPlugin with the Plugin interface
|
|
||||||
func NewRemember(b bot.Bot) *RememberPlugin {
|
|
||||||
p := RememberPlugin{
|
|
||||||
Bot: b,
|
|
||||||
Log: make(map[string][]msg.Message),
|
|
||||||
db: b.DB(),
|
|
||||||
}
|
|
||||||
return &p
|
|
||||||
}
|
|
||||||
|
|
||||||
// Message responds to the bot hook on recieving messages.
|
|
||||||
// This function returns true if the plugin responds in a meaningful way to the
|
|
||||||
// users message. Otherwise, the function returns false and the bot continues
|
|
||||||
// execution of other plugins.
|
|
||||||
func (p *RememberPlugin) Message(message msg.Message) bool {
|
|
||||||
|
|
||||||
if strings.ToLower(message.Body) == "quote" && message.Command {
|
|
||||||
q := p.randQuote()
|
|
||||||
p.Bot.SendMessage(message.Channel, q)
|
|
||||||
|
|
||||||
// is it evil not to remember that the user said quote?
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
user := message.User
|
|
||||||
parts := strings.Fields(message.Body)
|
|
||||||
if message.Command && len(parts) >= 3 &&
|
|
||||||
strings.ToLower(parts[0]) == "remember" {
|
|
||||||
|
|
||||||
// we have a remember!
|
|
||||||
// look through the logs and find parts[1] as a user, if not,
|
|
||||||
// fuck this hoser
|
|
||||||
nick := parts[1]
|
|
||||||
snip := strings.Join(parts[2:], " ")
|
|
||||||
for i := len(p.Log[message.Channel]) - 1; i >= 0; i-- {
|
|
||||||
entry := p.Log[message.Channel][i]
|
|
||||||
log.Printf("Comparing %s:%s with %s:%s",
|
|
||||||
entry.User.Name, entry.Body, nick, snip)
|
|
||||||
if strings.ToLower(entry.User.Name) == strings.ToLower(nick) &&
|
|
||||||
strings.Contains(
|
|
||||||
strings.ToLower(entry.Body),
|
|
||||||
strings.ToLower(snip),
|
|
||||||
) {
|
|
||||||
log.Printf("Found!")
|
|
||||||
|
|
||||||
var msg string
|
|
||||||
if entry.Action {
|
|
||||||
msg = fmt.Sprintf("*%s* %s", entry.User.Name, entry.Body)
|
|
||||||
} else {
|
|
||||||
msg = fmt.Sprintf("<%s> %s", entry.User.Name, entry.Body)
|
|
||||||
}
|
|
||||||
|
|
||||||
trigger := fmt.Sprintf("%s quotes", entry.User.Name)
|
|
||||||
|
|
||||||
fact := factoid{
|
|
||||||
Fact: strings.ToLower(trigger),
|
|
||||||
Verb: "reply",
|
|
||||||
Tidbit: msg,
|
|
||||||
Owner: user.Name,
|
|
||||||
created: time.Now(),
|
|
||||||
accessed: time.Now(),
|
|
||||||
Count: 0,
|
|
||||||
}
|
|
||||||
if err := fact.save(p.db); err != nil {
|
|
||||||
log.Println("ERROR!!!!:", err)
|
|
||||||
p.Bot.SendMessage(message.Channel, "Tell somebody I'm broke.")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("Remembering factoid:", msg)
|
|
||||||
|
|
||||||
// sorry, not creative with names so we're reusing msg
|
|
||||||
msg = fmt.Sprintf("Okay, %s, remembering '%s'.",
|
|
||||||
message.User.Name, msg)
|
|
||||||
p.Bot.SendMessage(message.Channel, msg)
|
|
||||||
p.recordMsg(message)
|
|
||||||
return true
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p.Bot.SendMessage(message.Channel, "Sorry, I don't know that phrase.")
|
|
||||||
p.recordMsg(message)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
p.recordMsg(message)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Help responds to help requests. Every plugin must implement a help function.
|
|
||||||
func (p *RememberPlugin) Help(channel string, parts []string) {
|
|
||||||
|
|
||||||
msg := "!remember will let you quote your idiot friends. Just type " +
|
|
||||||
"!remember <nick> <snippet> to remember what they said. Snippet can " +
|
|
||||||
"be any part of their message. Later on, you can ask for a random " +
|
|
||||||
"!quote."
|
|
||||||
|
|
||||||
p.Bot.SendMessage(channel, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// deliver a random quote out of the db.
|
|
||||||
// Note: this is the same cache for all channels joined. This plugin needs to be
|
|
||||||
// expanded to have this function execute a quote for a particular channel
|
|
||||||
func (p *RememberPlugin) randQuote() string {
|
|
||||||
|
|
||||||
var f factoid
|
|
||||||
var tmpCreated int64
|
|
||||||
var tmpAccessed int64
|
|
||||||
err := p.db.QueryRow(`select * from factoid where fact like '%quotes'
|
|
||||||
order by random() limit 1;`).Scan(
|
|
||||||
&f.id,
|
|
||||||
&f.Fact,
|
|
||||||
&f.Tidbit,
|
|
||||||
&f.Verb,
|
|
||||||
&f.Owner,
|
|
||||||
&tmpCreated,
|
|
||||||
&tmpAccessed,
|
|
||||||
&f.Count,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Error getting quotes: ", err)
|
|
||||||
return "I had a problem getting your quote."
|
|
||||||
}
|
|
||||||
f.created = time.Unix(tmpCreated, 0)
|
|
||||||
f.accessed = time.Unix(tmpAccessed, 0)
|
|
||||||
|
|
||||||
return f.Tidbit
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty event handler because this plugin does not do anything on event recv
|
|
||||||
func (p *RememberPlugin) Event(kind string, message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record what the bot says in the log
|
|
||||||
func (p *RememberPlugin) BotMessage(message msg.Message) bool {
|
|
||||||
p.recordMsg(message)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register any web URLs desired
|
|
||||||
func (p *RememberPlugin) RegisterWeb() *string {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *RememberPlugin) recordMsg(message msg.Message) {
|
|
||||||
log.Printf("Logging message: %s: %s", message.User.Name, message.Body)
|
|
||||||
p.Log[message.Channel] = append(p.Log[message.Channel], message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *RememberPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
|
|
@ -7,106 +7,109 @@ package fact
|
||||||
|
|
||||||
// 2016-01-15 Later note, why are these in plugins and the server is in bot?
|
// 2016-01-15 Later note, why are these in plugins and the server is in bot?
|
||||||
|
|
||||||
var factoidIndex string = `
|
var factoidIndex = `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
<!-- Load required Bootstrap and BootstrapVue CSS -->
|
||||||
|
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
|
||||||
|
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.css" />
|
||||||
|
|
||||||
|
<!-- Load polyfills to support older browsers -->
|
||||||
|
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CMutationObserver"></script>
|
||||||
|
|
||||||
|
<!-- Load Vue followed by BootstrapVue -->
|
||||||
|
<script src="//unpkg.com/vue@latest/dist/vue.min.js"></script>
|
||||||
|
<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.js"></script>
|
||||||
|
<script src="https://unpkg.com/vue-router"></script>
|
||||||
|
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
||||||
|
<meta charset="UTF-8">
|
||||||
<title>Factoids</title>
|
<title>Factoids</title>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/pure/0.6.0/base-min.css">
|
|
||||||
|
|
||||||
<!-- DataTables CSS -->
|
|
||||||
<link rel="stylesheet" type="text/css" href="https://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/css/jquery.dataTables.css">
|
|
||||||
|
|
||||||
<!-- jQuery -->
|
|
||||||
<script type="text/javascript" charset="utf8" src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.2.min.js"></script>
|
|
||||||
|
|
||||||
<!-- DataTables -->
|
|
||||||
<script type="text/javascript" charset="utf8" src="https://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/jquery.dataTables.min.js"></script>
|
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<div>
|
<body>
|
||||||
<form action="/factoid" method="GET" class="pure-form">
|
|
||||||
<fieldset>
|
|
||||||
<legend>Search for a factoid</legend>
|
|
||||||
<input type="text" name="entry" placeholder="trigger" value="{{.Search}}" />
|
|
||||||
<button type="submit" class="pure-button notice">Find</button>
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div id="app">
|
||||||
<style scoped>
|
<b-navbar>
|
||||||
|
<b-navbar-brand>Factoids</b-navbar-brand>
|
||||||
|
<b-navbar-nav>
|
||||||
|
<b-nav-item v-for="item in nav" :href="item.URL" :active="item.Name === 'Factoid'">{{ "{{ item.Name }}" }}</b-nav-item>
|
||||||
|
</b-navbar-nav>
|
||||||
|
</b-navbar>
|
||||||
|
<b-alert
|
||||||
|
dismissable
|
||||||
|
variant="error"
|
||||||
|
v-if="err"
|
||||||
|
@dismissed="err = ''">
|
||||||
|
{{ "{{ err }}" }}
|
||||||
|
</b-alert>
|
||||||
|
<b-form @submit="runQuery">
|
||||||
|
<b-container>
|
||||||
|
<b-row>
|
||||||
|
<b-col cols="10">
|
||||||
|
<b-input v-model="query"></b-input>
|
||||||
|
</b-col>
|
||||||
|
<b-col cols="2">
|
||||||
|
<b-button>Search</b-button>
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
<b-row>
|
||||||
|
<b-col>
|
||||||
|
<b-table
|
||||||
|
fixed
|
||||||
|
:items="results"
|
||||||
|
:fields="fields"></b-table>
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
</b-container>
|
||||||
|
</b-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
.pure-button-success,
|
<script>
|
||||||
.pure-button-error,
|
var router = new VueRouter({
|
||||||
.pure-button-warning,
|
mode: 'history',
|
||||||
.pure-button-secondary {
|
routes: []
|
||||||
color: white;
|
|
||||||
border-radius: 4px;
|
|
||||||
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
|
|
||||||
padding: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pure-button-success {
|
|
||||||
background: rgb(76, 201, 71); /* this is a green */
|
|
||||||
}
|
|
||||||
|
|
||||||
.pure-button-error {
|
|
||||||
background: rgb(202, 60, 60); /* this is a maroon */
|
|
||||||
}
|
|
||||||
|
|
||||||
.pure-button-warning {
|
|
||||||
background: orange;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pure-button-secondary {
|
|
||||||
background: rgb(95, 198, 218); /* this is a light blue */
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
{{if .Error}}
|
|
||||||
<span id="error" class="pure-button-error">{{.Error}}</span>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{if .Count}}
|
|
||||||
<span id="count" class="pure-button-success">Found {{.Count}} entries.</span>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{if .Entries}}
|
|
||||||
<div style="padding-top: 1em;">
|
|
||||||
<table class="pure-table" id="factTable">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Trigger</th>
|
|
||||||
<th>Full Text</th>
|
|
||||||
<th>Author</th>
|
|
||||||
<th># Hits</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
{{range .Entries}}
|
|
||||||
<tr>
|
|
||||||
<td>{{linkify .Fact}}</td>
|
|
||||||
<td>{{linkify .Tidbit}}</td>
|
|
||||||
<td>{{linkify .Owner}}</td>
|
|
||||||
<td>{{.Count}}</td>
|
|
||||||
</tr>
|
|
||||||
{{end}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
<script>
|
|
||||||
$(document).ready(function(){
|
|
||||||
$('#factTable').dataTable({
|
|
||||||
"bPaginate": false
|
|
||||||
});
|
});
|
||||||
});
|
var app = new Vue({
|
||||||
</script>
|
el: '#app',
|
||||||
|
router,
|
||||||
|
data: {
|
||||||
|
err: '',
|
||||||
|
nav: {{ .Nav }},
|
||||||
|
query: '',
|
||||||
|
results: [],
|
||||||
|
fields: [
|
||||||
|
{ key: 'Fact', sortable: true },
|
||||||
|
{ key: 'Tidbit', sortable: true },
|
||||||
|
{ key: 'Owner', sortable: true },
|
||||||
|
{ key: 'Count' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (this.$route.query.query) {
|
||||||
|
this.query = this.$route.query.query;
|
||||||
|
this.runQuery()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
result0: function() {
|
||||||
|
return this.results[0] || "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
runQuery: function(evt) {
|
||||||
|
if (evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
evt.stopPropagation()
|
||||||
|
}
|
||||||
|
axios.post('/factoid/api', {query: this.query})
|
||||||
|
.then(resp => {
|
||||||
|
this.results = resp.data;
|
||||||
|
})
|
||||||
|
.catch(err => (this.err = err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`
|
`
|
||||||
|
|
|
@ -5,12 +5,12 @@ package first
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
)
|
)
|
||||||
|
@ -18,7 +18,6 @@ import (
|
||||||
// This is a first plugin to serve as an example and quick copy/paste for new plugins.
|
// This is a first plugin to serve as an example and quick copy/paste for new plugins.
|
||||||
|
|
||||||
type FirstPlugin struct {
|
type FirstPlugin struct {
|
||||||
First *FirstEntry
|
|
||||||
Bot bot.Bot
|
Bot bot.Bot
|
||||||
db *sqlx.DB
|
db *sqlx.DB
|
||||||
}
|
}
|
||||||
|
@ -27,6 +26,7 @@ type FirstEntry struct {
|
||||||
id int64
|
id int64
|
||||||
day time.Time
|
day time.Time
|
||||||
time time.Time
|
time time.Time
|
||||||
|
channel string
|
||||||
body string
|
body string
|
||||||
nick string
|
nick string
|
||||||
saved bool
|
saved bool
|
||||||
|
@ -34,10 +34,11 @@ type FirstEntry struct {
|
||||||
|
|
||||||
// Insert or update the first entry
|
// Insert or update the first entry
|
||||||
func (fe *FirstEntry) save(db *sqlx.DB) error {
|
func (fe *FirstEntry) save(db *sqlx.DB) error {
|
||||||
if _, err := db.Exec(`insert into first (day, time, body, nick)
|
if _, err := db.Exec(`insert into first (day, time, channel, body, nick)
|
||||||
values (?, ?, ?, ?)`,
|
values (?, ?, ?, ?, ?)`,
|
||||||
fe.day.Unix(),
|
fe.day.Unix(),
|
||||||
fe.time.Unix(),
|
fe.time.Unix(),
|
||||||
|
fe.channel,
|
||||||
fe.body,
|
fe.body,
|
||||||
fe.nick,
|
fe.nick,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
@ -48,34 +49,33 @@ func (fe *FirstEntry) save(db *sqlx.DB) error {
|
||||||
|
|
||||||
// NewFirstPlugin creates a new FirstPlugin with the Plugin interface
|
// NewFirstPlugin creates a new FirstPlugin with the Plugin interface
|
||||||
func New(b bot.Bot) *FirstPlugin {
|
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,
|
id integer primary key,
|
||||||
day integer,
|
day integer,
|
||||||
time integer,
|
time integer,
|
||||||
|
channel string,
|
||||||
body string,
|
body string,
|
||||||
nick string
|
nick string
|
||||||
);`)
|
);`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Could not create first table: ", err)
|
log.Fatal().
|
||||||
}
|
Err(err).
|
||||||
|
Msg("Could not create first table")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("First plugin initialized with day:", midnight(time.Now()))
|
log.Info().Msgf("First plugin initialized with day: %s",
|
||||||
|
midnight(time.Now()))
|
||||||
|
|
||||||
first, err := getLastFirst(b.DB())
|
fp := &FirstPlugin{
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Could not initialize first plugin: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &FirstPlugin{
|
|
||||||
Bot: b,
|
Bot: b,
|
||||||
db: b.DB(),
|
db: b.DB(),
|
||||||
First: first,
|
|
||||||
}
|
}
|
||||||
|
b.Register(fp, bot.Message, fp.message)
|
||||||
|
b.Register(fp, bot.Help, fp.help)
|
||||||
|
return fp
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLastFirst(db *sqlx.DB) (*FirstEntry, error) {
|
func getLastFirst(db *sqlx.DB, channel string) (*FirstEntry, error) {
|
||||||
// Get last first entry
|
// Get last first entry
|
||||||
var id sql.NullInt64
|
var id sql.NullInt64
|
||||||
var day sql.NullInt64
|
var day sql.NullInt64
|
||||||
|
@ -85,8 +85,9 @@ func getLastFirst(db *sqlx.DB) (*FirstEntry, error) {
|
||||||
|
|
||||||
err := db.QueryRow(`select
|
err := db.QueryRow(`select
|
||||||
id, max(day), time, body, nick from first
|
id, max(day), time, body, nick from first
|
||||||
|
where channel = ?
|
||||||
limit 1;
|
limit 1;
|
||||||
`).Scan(
|
`, channel).Scan(
|
||||||
&id,
|
&id,
|
||||||
&day,
|
&day,
|
||||||
&timeEntered,
|
&timeEntered,
|
||||||
|
@ -95,17 +96,19 @@ func getLastFirst(db *sqlx.DB) (*FirstEntry, error) {
|
||||||
)
|
)
|
||||||
switch {
|
switch {
|
||||||
case err == sql.ErrNoRows || !id.Valid:
|
case err == sql.ErrNoRows || !id.Valid:
|
||||||
log.Println("No previous first entries")
|
log.Info().Msg("No previous first entries")
|
||||||
return nil, nil
|
return nil, nil
|
||||||
case err != nil:
|
case err != nil:
|
||||||
log.Println("Error on first query row: ", err)
|
log.Warn().Err(err).Msg("Error on first query row")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
log.Println(id, day, timeEntered, body, nick)
|
log.Debug().Msgf("id: %v day %v time %v body %v nick %v",
|
||||||
|
id, day, timeEntered, body, nick)
|
||||||
return &FirstEntry{
|
return &FirstEntry{
|
||||||
id: id.Int64,
|
id: id.Int64,
|
||||||
day: time.Unix(day.Int64, 0),
|
day: time.Unix(day.Int64, 0),
|
||||||
time: time.Unix(timeEntered.Int64, 0),
|
time: time.Unix(timeEntered.Int64, 0),
|
||||||
|
channel: channel,
|
||||||
body: body.String,
|
body: body.String,
|
||||||
nick: nick.String,
|
nick: nick.String,
|
||||||
saved: true,
|
saved: true,
|
||||||
|
@ -117,7 +120,11 @@ func midnight(t time.Time) time.Time {
|
||||||
return time.Date(y, m, d, 0, 0, 0, 0, time.UTC)
|
return time.Date(y, m, d, 0, 0, 0, 0, time.UTC)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isToday(t time.Time) bool {
|
func isNotToday(f *FirstEntry) bool {
|
||||||
|
if f == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
t := f.time
|
||||||
t0 := midnight(t)
|
t0 := midnight(t)
|
||||||
return t0.Before(midnight(time.Now()))
|
return t0.Before(midnight(time.Now()))
|
||||||
}
|
}
|
||||||
|
@ -125,26 +132,42 @@ func isToday(t time.Time) bool {
|
||||||
// Message responds to the bot hook on recieving messages.
|
// Message responds to the bot hook on recieving messages.
|
||||||
// This function returns true if the plugin responds in a meaningful way to the users message.
|
// 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.
|
// Otherwise, the function returns false and the bot continues execution of other plugins.
|
||||||
func (p *FirstPlugin) Message(message msg.Message) bool {
|
func (p *FirstPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
// This bot does not reply to anything
|
log.Debug().
|
||||||
|
Interface("msg", message).
|
||||||
|
Msg("First is looking at a message")
|
||||||
|
|
||||||
if p.First == nil && p.allowed(message) {
|
if message.IsIM {
|
||||||
log.Printf("No previous first. Recording new first: %s", message.Body)
|
log.Debug().Msg("Skipping IM")
|
||||||
p.recordFirst(message)
|
|
||||||
return false
|
|
||||||
} else if p.First != nil {
|
|
||||||
if isToday(p.First.time) && p.allowed(message) {
|
|
||||||
log.Printf("Recording first: %s - %v vs %v", message.Body, p.First.time, time.Now())
|
|
||||||
p.recordFirst(message)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
first, err := getLastFirst(p.db, message.Channel)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().
|
||||||
|
Err(err).
|
||||||
|
Msg("Error getting last first")
|
||||||
}
|
}
|
||||||
|
|
||||||
r := strings.NewReplacer("'", "", "\"", "", ",", "", ".", "", ":", "",
|
log.Debug().Bool("first == nil", first == nil).Msg("Is first nil?")
|
||||||
|
log.Debug().Bool("first == nil || isNotToday()", isNotToday(first)).Msg("Is it today?")
|
||||||
|
log.Debug().Bool("p.allowed", p.allowed(message)).Msg("Allowed?")
|
||||||
|
|
||||||
|
if (first == nil || isNotToday(first)) && p.allowed(message) {
|
||||||
|
log.Debug().
|
||||||
|
Str("body", message.Body).
|
||||||
|
Interface("t0", first).
|
||||||
|
Time("t1", time.Now()).
|
||||||
|
Msg("Recording first")
|
||||||
|
p.recordFirst(c, message)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
r := strings.NewReplacer("’", "", "'", "", "\"", "", ",", "", ".", "", ":", "",
|
||||||
"?", "", "!", "")
|
"?", "", "!", "")
|
||||||
msg := strings.ToLower(message.Body)
|
m := strings.ToLower(message.Body)
|
||||||
if r.Replace(msg) == "whos on first" {
|
if r.Replace(m) == "whos on first" && first != nil {
|
||||||
p.announceFirst(message)
|
p.announceFirst(c, first)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,81 +175,70 @@ func (p *FirstPlugin) Message(message msg.Message) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *FirstPlugin) allowed(message msg.Message) bool {
|
func (p *FirstPlugin) allowed(message msg.Message) bool {
|
||||||
for _, msg := range p.Bot.Config().Bad.Msgs {
|
for _, m := range p.Bot.Config().GetArray("Bad.Msgs", []string{}) {
|
||||||
match, err := regexp.MatchString(msg, strings.ToLower(message.Body))
|
match, err := regexp.MatchString(m, strings.ToLower(message.Body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Bad regexp: ", err)
|
log.Error().Err(err).Msg("Bad regexp")
|
||||||
}
|
}
|
||||||
if match {
|
if match {
|
||||||
log.Println("Disallowing first: ", message.User.Name, ":", message.Body)
|
log.Info().
|
||||||
|
Str("user", message.User.Name).
|
||||||
|
Str("body", message.Body).
|
||||||
|
Msg("Disallowing first")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, host := range p.Bot.Config().Bad.Hosts {
|
for _, host := range p.Bot.Config().GetArray("Bad.Hosts", []string{}) {
|
||||||
if host == message.Host {
|
if host == message.Host {
|
||||||
log.Println("Disallowing first: ", message.User.Name, ":", message.Body)
|
log.Info().
|
||||||
|
Str("user", message.User.Name).
|
||||||
|
Str("body", message.Body).
|
||||||
|
Msg("Disallowing first")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, nick := range p.Bot.Config().Bad.Nicks {
|
for _, nick := range p.Bot.Config().GetArray("Bad.Nicks", []string{}) {
|
||||||
if nick == message.User.Name {
|
if nick == message.User.Name {
|
||||||
log.Println("Disallowing first: ", message.User.Name, ":", message.Body)
|
log.Info().
|
||||||
|
Str("user", message.User.Name).
|
||||||
|
Str("body", message.Body).
|
||||||
|
Msg("Disallowing first")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *FirstPlugin) recordFirst(message msg.Message) {
|
func (p *FirstPlugin) recordFirst(c bot.Connector, message msg.Message) {
|
||||||
log.Println("Recording first: ", message.User.Name, ":", message.Body)
|
log.Info().
|
||||||
p.First = &FirstEntry{
|
Str("channel", message.Channel).
|
||||||
|
Str("user", message.User.Name).
|
||||||
|
Str("body", message.Body).
|
||||||
|
Msg("Recording first")
|
||||||
|
first := &FirstEntry{
|
||||||
day: midnight(time.Now()),
|
day: midnight(time.Now()),
|
||||||
time: message.Time,
|
time: message.Time,
|
||||||
|
channel: message.Channel,
|
||||||
body: message.Body,
|
body: message.Body,
|
||||||
nick: message.User.Name,
|
nick: message.User.Name,
|
||||||
}
|
}
|
||||||
log.Printf("recordFirst: %+v", p.First.day)
|
log.Info().Msgf("recordFirst: %+v", first.day)
|
||||||
err := p.First.save(p.db)
|
err := first.save(p.db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error saving first entry: ", err)
|
log.Error().Err(err).Msg("Error saving first entry")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p.announceFirst(message)
|
p.announceFirst(c, first)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *FirstPlugin) announceFirst(message msg.Message) {
|
func (p *FirstPlugin) announceFirst(c bot.Connector, first *FirstEntry) {
|
||||||
c := message.Channel
|
ch := first.channel
|
||||||
if p.First != nil {
|
p.Bot.Send(c, bot.Message, ch, fmt.Sprintf("%s had first at %s with the message: \"%s\"",
|
||||||
p.Bot.SendMessage(c, fmt.Sprintf("%s had first at %s with the message: \"%s\"",
|
first.nick, first.time.Format("15:04"), first.body))
|
||||||
p.First.nick, p.First.time.Format("15:04"), p.First.body))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadData imports any configuration data into the plugin. This is not strictly necessary other
|
|
||||||
// than the fact that the Plugin interface demands it exist. This may be deprecated at a later
|
|
||||||
// date.
|
|
||||||
func (p *FirstPlugin) LoadData() {
|
|
||||||
// This bot has no data to load
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Help responds to help requests. Every plugin must implement a help function.
|
// Help responds to help requests. Every plugin must implement a help function.
|
||||||
func (p *FirstPlugin) Help(channel string, parts []string) {
|
func (p *FirstPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
p.Bot.SendMessage(channel, "Sorry, First does not do a goddamn thing.")
|
p.Bot.Send(c, bot.Message, message.Channel, "You can ask 'who's on first?' to find out.")
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty event handler because this plugin does not do anything on event recv
|
|
||||||
func (p *FirstPlugin) Event(kind string, message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handler for bot's own messages
|
|
||||||
func (p *FirstPlugin) BotMessage(message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register any web URLs desired
|
|
||||||
func (p *FirstPlugin) RegisterWeb() *string {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *FirstPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
|
||||||
|
|
|
@ -6,10 +6,11 @@ package inventory
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
|
@ -24,38 +25,41 @@ type InventoryPlugin struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new InventoryPlugin with the Plugin interface
|
// New creates a new InventoryPlugin with the Plugin interface
|
||||||
func New(bot bot.Bot) *InventoryPlugin {
|
func New(b bot.Bot) *InventoryPlugin {
|
||||||
config := bot.Config()
|
config := b.Config()
|
||||||
|
nick := config.Get("nick", "bot")
|
||||||
r1, err := regexp.Compile("take this (.+)")
|
r1, err := regexp.Compile("take this (.+)")
|
||||||
checkerr(err)
|
checkerr(err)
|
||||||
r2, err := regexp.Compile("have a (.+)")
|
r2, err := regexp.Compile("have a (.+)")
|
||||||
checkerr(err)
|
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)
|
checkerr(err)
|
||||||
r4, err := regexp.Compile(fmt.Sprintf("gives %s (.+)", config.Nick))
|
r4, err := regexp.Compile(fmt.Sprintf("gives %s (.+)", nick))
|
||||||
checkerr(err)
|
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)
|
checkerr(err)
|
||||||
|
|
||||||
p := InventoryPlugin{
|
p := &InventoryPlugin{
|
||||||
DB: bot.DB(),
|
DB: b.DB(),
|
||||||
bot: bot,
|
bot: b,
|
||||||
config: config,
|
config: config,
|
||||||
r1: r1, r2: r2, r3: r3, r4: r4, r5: r5,
|
r1: r1, r2: r2, r3: r3, r4: r4, r5: r5,
|
||||||
}
|
}
|
||||||
|
|
||||||
bot.RegisterFilter("$item", p.itemFilter)
|
b.RegisterFilter("$item", p.itemFilter)
|
||||||
bot.RegisterFilter("$giveitem", p.giveItemFilter)
|
b.RegisterFilter("$giveitem", p.giveItemFilter)
|
||||||
|
|
||||||
_, err = p.DB.Exec(`create table if not exists inventory (
|
_, err = p.DB.Exec(`create table if not exists inventory (
|
||||||
item string primary key
|
item string primary key
|
||||||
);`)
|
);`)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal().Err(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &p
|
b.Register(p, bot.Message, p.message)
|
||||||
|
|
||||||
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *InventoryPlugin) giveItemFilter(input string) string {
|
func (p *InventoryPlugin) giveItemFilter(input string) string {
|
||||||
|
@ -74,49 +78,49 @@ func (p *InventoryPlugin) itemFilter(input string) string {
|
||||||
return input
|
return input
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *InventoryPlugin) Message(message msg.Message) bool {
|
func (p *InventoryPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
m := message.Body
|
m := message.Body
|
||||||
log.Printf("inventory trying to read %+v", message)
|
log.Debug().Msgf("inventory trying to read %+v", message)
|
||||||
if message.Command {
|
if message.Command {
|
||||||
if strings.ToLower(m) == "inventory" {
|
if strings.ToLower(m) == "inventory" {
|
||||||
items := p.getAll()
|
items := p.getAll()
|
||||||
say := "I'm not holding anything"
|
say := "I'm not holding anything"
|
||||||
if len(items) > 0 {
|
if len(items) > 0 {
|
||||||
log.Printf("I think I have more than 0 items: %+v, len(items)=%d", items, len(items))
|
log.Debug().Msgf("I think I have more than 0 items: %+v, len(items)=%d", items, len(items))
|
||||||
say = fmt.Sprintf("I'm currently holding %s", strings.Join(items, ", "))
|
say = fmt.Sprintf("I'm currently holding %s", strings.Join(items, ", "))
|
||||||
}
|
}
|
||||||
p.bot.SendMessage(message.Channel, say)
|
p.bot.Send(c, bot.Message, message.Channel, say)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// <Randall> Bucket[:,] take this (.+)
|
// <Randall> Bucket[:,] take this (.+)
|
||||||
// <Randall> Bucket[:,] have a (.+)
|
// <Randall> Bucket[:,] have a (.+)
|
||||||
if matches := p.r1.FindStringSubmatch(m); len(matches) > 0 {
|
if matches := p.r1.FindStringSubmatch(m); len(matches) > 0 {
|
||||||
log.Printf("Found item to add: %s", matches[1])
|
log.Debug().Msgf("Found item to add: %s", matches[1])
|
||||||
return p.addItem(message, matches[1])
|
return p.addItem(c, message, matches[1])
|
||||||
}
|
}
|
||||||
if matches := p.r2.FindStringSubmatch(m); len(matches) > 0 {
|
if matches := p.r2.FindStringSubmatch(m); len(matches) > 0 {
|
||||||
log.Printf("Found item to add: %s", matches[1])
|
log.Debug().Msgf("Found item to add: %s", matches[1])
|
||||||
return p.addItem(message, matches[1])
|
return p.addItem(c, message, matches[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if message.Action {
|
if message.Action {
|
||||||
log.Println("Inventory found an action")
|
log.Debug().Msg("Inventory found an action")
|
||||||
// * Randall puts (.+) in Bucket([^a-zA-Z].*)?
|
// * Randall puts (.+) in Bucket([^a-zA-Z].*)?
|
||||||
// * Randall gives Bucket (.+)
|
// * Randall gives Bucket (.+)
|
||||||
// * Randall gives (.+) to Bucket([^a-zA-Z].*)?
|
// * Randall gives (.+) to Bucket([^a-zA-Z].*)?
|
||||||
|
|
||||||
if matches := p.r3.FindStringSubmatch(m); len(matches) > 0 {
|
if matches := p.r3.FindStringSubmatch(m); len(matches) > 0 {
|
||||||
log.Printf("Found item to add: %s", matches[1])
|
log.Debug().Msgf("Found item to add: %s", matches[1])
|
||||||
return p.addItem(message, matches[1])
|
return p.addItem(c, message, matches[1])
|
||||||
}
|
}
|
||||||
if matches := p.r4.FindStringSubmatch(m); len(matches) > 0 {
|
if matches := p.r4.FindStringSubmatch(m); len(matches) > 0 {
|
||||||
log.Printf("Found item to add: %s", matches[1])
|
log.Debug().Msgf("Found item to add: %s", matches[1])
|
||||||
return p.addItem(message, matches[1])
|
return p.addItem(c, message, matches[1])
|
||||||
}
|
}
|
||||||
if matches := p.r5.FindStringSubmatch(m); len(matches) > 0 {
|
if matches := p.r5.FindStringSubmatch(m); len(matches) > 0 {
|
||||||
log.Printf("Found item to add: %s", matches[1])
|
log.Debug().Msgf("Found item to add: %s", matches[1])
|
||||||
return p.addItem(message, matches[1])
|
return p.addItem(c, message, matches[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -128,12 +132,12 @@ func (p *InventoryPlugin) removeRandom() string {
|
||||||
&name,
|
&name,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error finding random entry: %s", err)
|
log.Error().Err(err).Msgf("Error finding random entry")
|
||||||
return "IAMERROR"
|
return "IAMERROR"
|
||||||
}
|
}
|
||||||
_, err = p.Exec(`delete from inventory where item=?`, name)
|
_, err = p.Exec(`delete from inventory where item=?`, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error finding random entry: %s", err)
|
log.Error().Err(err).Msgf("Error finding random entry")
|
||||||
return "IAMERROR"
|
return "IAMERROR"
|
||||||
}
|
}
|
||||||
return name
|
return name
|
||||||
|
@ -143,7 +147,7 @@ func (p *InventoryPlugin) count() int {
|
||||||
var output int
|
var output int
|
||||||
err := p.QueryRow(`select count(*) as count from inventory`).Scan(&output)
|
err := p.QueryRow(`select count(*) as count from inventory`).Scan(&output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error checking for item: %s", err)
|
log.Error().Err(err).Msg("Error checking for item")
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
return output
|
return output
|
||||||
|
@ -155,7 +159,7 @@ func (p *InventoryPlugin) random() string {
|
||||||
&name,
|
&name,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error finding random entry: %s", err)
|
log.Error().Err(err).Msg("Error finding random entry")
|
||||||
return "IAMERROR"
|
return "IAMERROR"
|
||||||
}
|
}
|
||||||
return name
|
return name
|
||||||
|
@ -164,7 +168,7 @@ func (p *InventoryPlugin) random() string {
|
||||||
func (p *InventoryPlugin) getAll() []string {
|
func (p *InventoryPlugin) getAll() []string {
|
||||||
rows, err := p.Queryx(`select item from inventory`)
|
rows, err := p.Queryx(`select item from inventory`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error getting all items: %s", err)
|
log.Error().Err(err).Msg("Error getting all items")
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
output := []string{}
|
output := []string{}
|
||||||
|
@ -181,7 +185,7 @@ func (p *InventoryPlugin) exists(i string) bool {
|
||||||
var output int
|
var output int
|
||||||
err := p.QueryRow(`select count(*) as count from inventory where item=?`, i).Scan(&output)
|
err := p.QueryRow(`select count(*) as count from inventory where item=?`, i).Scan(&output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error checking for item: %s", err)
|
log.Error().Err(err).Msg("Error checking for item")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return output > 0
|
return output > 0
|
||||||
|
@ -190,51 +194,34 @@ func (p *InventoryPlugin) exists(i string) bool {
|
||||||
func (p *InventoryPlugin) remove(i string) {
|
func (p *InventoryPlugin) remove(i string) {
|
||||||
_, err := p.Exec(`delete from inventory where item=?`, i)
|
_, err := p.Exec(`delete from inventory where item=?`, i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error inserting new inventory item: %s", err)
|
log.Error().Msg("Error inserting new inventory item")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *InventoryPlugin) addItem(m msg.Message, i string) bool {
|
func (p *InventoryPlugin) addItem(c bot.Connector, m msg.Message, i string) bool {
|
||||||
if p.exists(i) {
|
if p.exists(i) {
|
||||||
p.bot.SendMessage(m.Channel, fmt.Sprintf("I already have %s.", i))
|
p.bot.Send(c, bot.Message, m.Channel, fmt.Sprintf("I already have %s.", i))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
var removed string
|
var removed string
|
||||||
if p.count() > p.config.Inventory.Max {
|
max := p.config.GetInt("inventory.max", 10)
|
||||||
|
if p.count() > max {
|
||||||
removed = p.removeRandom()
|
removed = p.removeRandom()
|
||||||
}
|
}
|
||||||
_, err := p.Exec(`INSERT INTO inventory (item) values (?)`, i)
|
_, err := p.Exec(`INSERT INTO inventory (item) values (?)`, i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error inserting new inventory item: %s", err)
|
log.Error().Err(err).Msg("Error inserting new inventory item")
|
||||||
}
|
}
|
||||||
if removed != "" {
|
if removed != "" {
|
||||||
p.bot.SendAction(m.Channel, fmt.Sprintf("dropped %s and took %s from %s", removed, i, m.User.Name))
|
p.bot.Send(c, bot.Action, m.Channel, fmt.Sprintf("dropped %s and took %s from %s", removed, i, m.User.Name))
|
||||||
} else {
|
} else {
|
||||||
p.bot.SendAction(m.Channel, fmt.Sprintf("takes %s from %s", i, m.User.Name))
|
p.bot.Send(c, bot.Action, m.Channel, fmt.Sprintf("takes %s from %s", i, m.User.Name))
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkerr(e error) {
|
func checkerr(e error) {
|
||||||
if e != nil {
|
if e != nil {
|
||||||
log.Println(e)
|
log.Error().Err(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *InventoryPlugin) Event(e string, message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *InventoryPlugin) BotMessage(message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *InventoryPlugin) Help(e string, m []string) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *InventoryPlugin) RegisterWeb() *string {
|
|
||||||
// nothing to register
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *InventoryPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
|
||||||
|
|
|
@ -20,19 +20,20 @@ type LeftpadPlugin struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new LeftpadPlugin with the Plugin interface
|
// New creates a new LeftpadPlugin with the Plugin interface
|
||||||
func New(bot bot.Bot) *LeftpadPlugin {
|
func New(b bot.Bot) *LeftpadPlugin {
|
||||||
p := LeftpadPlugin{
|
p := &LeftpadPlugin{
|
||||||
bot: bot,
|
bot: b,
|
||||||
config: bot.Config(),
|
config: b.Config(),
|
||||||
}
|
}
|
||||||
return &p
|
b.Register(p, bot.Message, p.message)
|
||||||
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
type leftpadResp struct {
|
type leftpadResp struct {
|
||||||
Str string
|
Str string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *LeftpadPlugin) Message(message msg.Message) bool {
|
func (p *LeftpadPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
if !message.Command {
|
if !message.Command {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -42,39 +43,22 @@ func (p *LeftpadPlugin) Message(message msg.Message) bool {
|
||||||
padchar := parts[1]
|
padchar := parts[1]
|
||||||
length, err := strconv.Atoi(parts[2])
|
length, err := strconv.Atoi(parts[2])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.bot.SendMessage(message.Channel, "Invalid padding number")
|
p.bot.Send(c, bot.Message, message.Channel, "Invalid padding number")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if length > p.config.LeftPad.MaxLen && p.config.LeftPad.MaxLen > 0 {
|
maxLen, who := p.config.GetInt("LeftPad.MaxLen", 50), p.config.Get("LeftPad.Who", "Putin")
|
||||||
msg := fmt.Sprintf("%s would kill me if I did that.", p.config.LeftPad.Who)
|
if length > maxLen && maxLen > 0 {
|
||||||
p.bot.SendMessage(message.Channel, msg)
|
msg := fmt.Sprintf("%s would kill me if I did that.", who)
|
||||||
|
p.bot.Send(c, bot.Message, message.Channel, msg)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
text := strings.Join(parts[3:], " ")
|
text := strings.Join(parts[3:], " ")
|
||||||
|
|
||||||
res := leftpad.LeftPad(text, length, padchar)
|
res := leftpad.LeftPad(text, length, padchar)
|
||||||
|
|
||||||
p.bot.SendMessage(message.Channel, res)
|
p.bot.Send(c, bot.Message, message.Channel, res)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *LeftpadPlugin) Event(e string, message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *LeftpadPlugin) BotMessage(message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *LeftpadPlugin) Help(e string, m []string) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *LeftpadPlugin) RegisterWeb() *string {
|
|
||||||
// nothing to register
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *LeftpadPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
package leftpad
|
package leftpad
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -13,12 +14,12 @@ import (
|
||||||
"github.com/velour/catbase/plugins/counter"
|
"github.com/velour/catbase/plugins/counter"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeMessage(payload string) msg.Message {
|
func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) {
|
||||||
isCmd := strings.HasPrefix(payload, "!")
|
isCmd := strings.HasPrefix(payload, "!")
|
||||||
if isCmd {
|
if isCmd {
|
||||||
payload = payload[1:]
|
payload = payload[1:]
|
||||||
}
|
}
|
||||||
return msg.Message{
|
return &cli.CliPlugin{}, bot.Message, msg.Message{
|
||||||
User: &user.User{Name: "tester"},
|
User: &user.User{Name: "tester"},
|
||||||
Channel: "test",
|
Channel: "test",
|
||||||
Body: payload,
|
Body: payload,
|
||||||
|
@ -31,75 +32,57 @@ func makePlugin(t *testing.T) (*LeftpadPlugin, *bot.MockBot) {
|
||||||
counter.New(mb)
|
counter.New(mb)
|
||||||
p := New(mb)
|
p := New(mb)
|
||||||
assert.NotNil(t, p)
|
assert.NotNil(t, p)
|
||||||
|
p.config.Set("LeftPad.MaxLen", "0")
|
||||||
return p, mb
|
return p, mb
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLeftpad(t *testing.T) {
|
func TestLeftpad(t *testing.T) {
|
||||||
p, mb := makePlugin(t)
|
p, mb := makePlugin(t)
|
||||||
p.Message(makeMessage("!leftpad test 8 test"))
|
p.message(makeMessage("!leftpad test 8 test"))
|
||||||
assert.Contains(t, mb.Messages[0], "testtest")
|
assert.Contains(t, mb.Messages[0], "testtest")
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBadNumber(t *testing.T) {
|
func TestBadNumber(t *testing.T) {
|
||||||
p, mb := makePlugin(t)
|
p, mb := makePlugin(t)
|
||||||
p.Message(makeMessage("!leftpad test fuck test"))
|
p.message(makeMessage("!leftpad test fuck test"))
|
||||||
assert.Contains(t, mb.Messages[0], "Invalid")
|
assert.Contains(t, mb.Messages[0], "Invalid")
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNotCommand(t *testing.T) {
|
func TestNotCommand(t *testing.T) {
|
||||||
p, mb := makePlugin(t)
|
p, mb := makePlugin(t)
|
||||||
p.Message(makeMessage("leftpad test fuck test"))
|
p.message(makeMessage("leftpad test fuck test"))
|
||||||
assert.Len(t, mb.Messages, 0)
|
assert.Len(t, mb.Messages, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNoMaxLen(t *testing.T) {
|
func TestNoMaxLen(t *testing.T) {
|
||||||
p, mb := makePlugin(t)
|
p, mb := makePlugin(t)
|
||||||
p.Message(makeMessage("!leftpad dicks 100 dicks"))
|
p.config.Set("LeftPad.MaxLen", "0")
|
||||||
|
p.message(makeMessage("!leftpad dicks 100 dicks"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.Contains(t, mb.Messages[0], "dicks")
|
assert.Contains(t, mb.Messages[0], "dicks")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test50Padding(t *testing.T) {
|
func Test50Padding(t *testing.T) {
|
||||||
p, mb := makePlugin(t)
|
p, mb := makePlugin(t)
|
||||||
p.config.LeftPad.MaxLen = 50
|
p.config.Set("LeftPad.MaxLen", "50")
|
||||||
p.Message(makeMessage("!leftpad dicks 100 dicks"))
|
assert.Equal(t, 50, p.config.GetInt("LeftPad.MaxLen", 100))
|
||||||
|
p.message(makeMessage("!leftpad dicks 100 dicks"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.Contains(t, mb.Messages[0], "kill me")
|
assert.Contains(t, mb.Messages[0], "kill me")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnder50Padding(t *testing.T) {
|
func TestUnder50Padding(t *testing.T) {
|
||||||
p, mb := makePlugin(t)
|
p, mb := makePlugin(t)
|
||||||
p.config.LeftPad.MaxLen = 50
|
p.config.Set("LeftPad.MaxLen", "50")
|
||||||
p.Message(makeMessage("!leftpad dicks 49 dicks"))
|
p.message(makeMessage("!leftpad dicks 49 dicks"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.Contains(t, mb.Messages[0], "dicks")
|
assert.Contains(t, mb.Messages[0], "dicks")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNotPadding(t *testing.T) {
|
func TestNotPadding(t *testing.T) {
|
||||||
p, mb := makePlugin(t)
|
p, mb := makePlugin(t)
|
||||||
p.Message(makeMessage("!lololol"))
|
p.message(makeMessage("!lololol"))
|
||||||
assert.Len(t, mb.Messages, 0)
|
assert.Len(t, mb.Messages, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHelp(t *testing.T) {
|
|
||||||
p, mb := makePlugin(t)
|
|
||||||
p.Help("channel", []string{})
|
|
||||||
assert.Len(t, mb.Messages, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBotMessage(t *testing.T) {
|
|
||||||
p, _ := makePlugin(t)
|
|
||||||
assert.False(t, p.BotMessage(makeMessage("test")))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvent(t *testing.T) {
|
|
||||||
p, _ := makePlugin(t)
|
|
||||||
assert.False(t, p.Event("dummy", makeMessage("test")))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRegisterWeb(t *testing.T) {
|
|
||||||
p, _ := makePlugin(t)
|
|
||||||
assert.Nil(t, p.RegisterWeb())
|
|
||||||
}
|
|
||||||
|
|
|
@ -27,29 +27,32 @@ type NerdepediaPlugin struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewNerdepediaPlugin creates a new NerdepediaPlugin with the Plugin interface
|
// NewNerdepediaPlugin creates a new NerdepediaPlugin with the Plugin interface
|
||||||
func New(bot bot.Bot) *NerdepediaPlugin {
|
func New(b bot.Bot) *NerdepediaPlugin {
|
||||||
return &NerdepediaPlugin{
|
np := &NerdepediaPlugin{
|
||||||
bot: bot,
|
bot: b,
|
||||||
config: bot.Config(),
|
config: b.Config(),
|
||||||
}
|
}
|
||||||
|
b.Register(np, bot.Message, np.message)
|
||||||
|
b.Register(np, bot.Help, np.help)
|
||||||
|
return np
|
||||||
}
|
}
|
||||||
|
|
||||||
// Message responds to the bot hook on recieving messages.
|
// Message responds to the bot hook on recieving messages.
|
||||||
// This function returns true if the plugin responds in a meaningful way to the users message.
|
// 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.
|
// Otherwise, the function returns false and the bot continues execution of other plugins.
|
||||||
func (p *NerdepediaPlugin) Message(message msg.Message) bool {
|
func (p *NerdepediaPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
lowerCase := strings.ToLower(message.Body)
|
lowerCase := strings.ToLower(message.Body)
|
||||||
query := ""
|
query := ""
|
||||||
if lowerCase == "may the force be with you" || lowerCase == "help me obi-wan" {
|
if lowerCase == "may the force be with you" || lowerCase == "help me obi-wan" {
|
||||||
query = "http://starwars.wikia.com/wiki/Special:Random"
|
query = "http://starwars.wikia.com/wiki/Special:Random"
|
||||||
} else if lowerCase == "beam me up scotty" || lowerCase == "live long and prosper" {
|
} else if lowerCase == "beam me up scotty" || lowerCase == "live long and prosper" {
|
||||||
query = "http://memory-alpha.wikia.com/wiki/Special:Random"
|
query = "http://memory-alpha.wikia.com/wiki/Special:Random"
|
||||||
} else if lowerCase == "bless the maker" || lowerCase == "i must not fear" {
|
} else if lowerCase == "bless the maker" || lowerCase == "i must not fear" || lowerCase == "the spice must flow" {
|
||||||
query = "http://dune.wikia.com/wiki/Special:Random"
|
query = "http://dune.wikia.com/wiki/Special:Random"
|
||||||
} else if lowerCase == "my precious" || lowerCase == "one ring to rule them all" || lowerCase == "one does not simply walk into mordor" {
|
} else if lowerCase == "my precious" || lowerCase == "one ring to rule them all" || lowerCase == "one does not simply walk into mordor" {
|
||||||
query = "http://lotr.wikia.com/wiki/Special:Random"
|
query = "http://lotr.wikia.com/wiki/Special:Random"
|
||||||
} else if lowerCase == "gotta catch em all" {
|
} else if lowerCase == "pikachu i choose you" || lowerCase == "gotta catch em all" {
|
||||||
query = "https://bulbapedia.bulbagarden.net/wiki/Special:Random"
|
query = "http://pokemon.wikia.com/wiki/Special:Random"
|
||||||
}
|
}
|
||||||
|
|
||||||
if query != "" {
|
if query != "" {
|
||||||
|
@ -78,7 +81,7 @@ func (p *NerdepediaPlugin) Message(message msg.Message) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
if description != "" && link != "" {
|
if description != "" && link != "" {
|
||||||
p.bot.SendMessage(message.Channel, fmt.Sprintf("%s (%s)", description, link))
|
p.bot.Send(c, bot.Message, message.Channel, fmt.Sprintf("%s (%s)", description, link))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,23 +90,7 @@ func (p *NerdepediaPlugin) Message(message msg.Message) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Help responds to help requests. Every plugin must implement a help function.
|
// Help responds to help requests. Every plugin must implement a help function.
|
||||||
func (p *NerdepediaPlugin) Help(channel string, parts []string) {
|
func (p *NerdepediaPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
p.bot.SendMessage(channel, "nerd stuff")
|
p.bot.Send(c, bot.Message, message.Channel, "nerd stuff")
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty event handler because this plugin does not do anything on event recv
|
|
||||||
func (p *NerdepediaPlugin) Event(kind string, message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handler for bot's own messages
|
|
||||||
func (p *NerdepediaPlugin) BotMessage(message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register any web URLs desired
|
|
||||||
func (p *NerdepediaPlugin) RegisterWeb() *string {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *NerdepediaPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
package nerdepedia
|
package nerdepedia
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -12,12 +13,12 @@ import (
|
||||||
"github.com/velour/catbase/bot/user"
|
"github.com/velour/catbase/bot/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeMessage(payload string) msg.Message {
|
func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) {
|
||||||
isCmd := strings.HasPrefix(payload, "!")
|
isCmd := strings.HasPrefix(payload, "!")
|
||||||
if isCmd {
|
if isCmd {
|
||||||
payload = payload[1:]
|
payload = payload[1:]
|
||||||
}
|
}
|
||||||
return msg.Message{
|
return &cli.CliPlugin{}, bot.Message, msg.Message{
|
||||||
User: &user.User{Name: "tester"},
|
User: &user.User{Name: "tester"},
|
||||||
Channel: "test",
|
Channel: "test",
|
||||||
Body: payload,
|
Body: payload,
|
||||||
|
@ -25,11 +26,38 @@ func makeMessage(payload string) msg.Message {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestObiWan(t *testing.T) {
|
func TestWars(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
res := c.Message(makeMessage("help me obi-wan"))
|
res := c.message(makeMessage("help me obi-wan"))
|
||||||
|
assert.Len(t, mb.Messages, 1)
|
||||||
|
assert.True(t, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrek(t *testing.T) {
|
||||||
|
mb := bot.NewMockBot()
|
||||||
|
c := New(mb)
|
||||||
|
assert.NotNil(t, c)
|
||||||
|
res := c.message(makeMessage("live long and prosper"))
|
||||||
|
assert.Len(t, mb.Messages, 1)
|
||||||
|
assert.True(t, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDune(t *testing.T) {
|
||||||
|
mb := bot.NewMockBot()
|
||||||
|
c := New(mb)
|
||||||
|
assert.NotNil(t, c)
|
||||||
|
res := c.message(makeMessage("bless the maker"))
|
||||||
|
assert.Len(t, mb.Messages, 1)
|
||||||
|
assert.True(t, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPoke(t *testing.T) {
|
||||||
|
mb := bot.NewMockBot()
|
||||||
|
c := New(mb)
|
||||||
|
assert.NotNil(t, c)
|
||||||
|
res := c.message(makeMessage("gotta catch em all"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
package newsbid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/velour/catbase/bot"
|
||||||
|
"github.com/velour/catbase/bot/msg"
|
||||||
|
"github.com/velour/catbase/plugins/newsbid/webshit"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NewsBid struct {
|
||||||
|
bot bot.Bot
|
||||||
|
db *sqlx.DB
|
||||||
|
ws *webshit.Webshit
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(b bot.Bot) *NewsBid {
|
||||||
|
ws := webshit.New(b.DB())
|
||||||
|
p := &NewsBid{
|
||||||
|
bot: b,
|
||||||
|
db: b.DB(),
|
||||||
|
ws: ws,
|
||||||
|
}
|
||||||
|
p.bot.Register(p, bot.Message, p.message)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *NewsBid) message(conn bot.Connector, k bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
|
body := strings.ToLower(message.Body)
|
||||||
|
ch := message.Channel
|
||||||
|
if message.Command && body == "balance" {
|
||||||
|
bal := p.ws.GetBalance(message.User.Name)
|
||||||
|
p.bot.Send(conn, bot.Message, ch, fmt.Sprintf("%s, your current balance is %d.",
|
||||||
|
message.User.Name, bal))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if message.Command && body == "bids" {
|
||||||
|
bids, err := p.ws.GetAllBids()
|
||||||
|
if err != nil {
|
||||||
|
p.bot.Send(conn, bot.Message, ch, fmt.Sprintf("Error getting bids: %s", err))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if len(bids) == 0 {
|
||||||
|
p.bot.Send(conn, bot.Message, ch, "No bids to report.")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
sort.Slice(bids, func(i, j int) bool { return bids[i].User < bids[j].User })
|
||||||
|
out := "Bids:\n"
|
||||||
|
for _, b := range bids {
|
||||||
|
out += fmt.Sprintf("%s bid %d on %s\n", b.User, b.Bid, b.Title)
|
||||||
|
}
|
||||||
|
p.bot.Send(conn, bot.Message, ch, out)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if message.Command && body == "scores" {
|
||||||
|
bals, err := p.ws.GetAllBalances()
|
||||||
|
if err != nil {
|
||||||
|
p.bot.Send(conn, bot.Message, ch, fmt.Sprintf("Error getting bids: %s", err))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if len(bals) == 0 {
|
||||||
|
p.bot.Send(conn, bot.Message, ch, "No balances to report.")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
out := "NGate balances:\n"
|
||||||
|
for _, b := range bals {
|
||||||
|
out += fmt.Sprintf("%s has a total score of %d with %d left to bid this session\n", b.User, b.Score, b.Balance)
|
||||||
|
}
|
||||||
|
p.bot.Send(conn, bot.Message, ch, out)
|
||||||
|
return true
|
||||||
|
|
||||||
|
}
|
||||||
|
if message.Command && strings.HasPrefix(body, "bid") {
|
||||||
|
parts := strings.Fields(body)
|
||||||
|
if len(parts) != 3 {
|
||||||
|
p.bot.Send(conn, bot.Message, ch, "You must bid with an amount and a URL.")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
amount, _ := strconv.Atoi(parts[1])
|
||||||
|
url := parts[2]
|
||||||
|
if bid, err := p.ws.Bid(message.User.Name, amount, url); err != nil {
|
||||||
|
p.bot.Send(conn, bot.Message, ch, fmt.Sprintf("Error placing bid: %s", err))
|
||||||
|
} else {
|
||||||
|
p.bot.Send(conn, bot.Message, ch, fmt.Sprintf("Your bid has been placed on %s", bid.Title))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if message.Command && body == "check ngate" {
|
||||||
|
p.check(conn, ch)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *NewsBid) check(conn bot.Connector, ch string) {
|
||||||
|
wr, err := p.ws.Check()
|
||||||
|
if err != nil {
|
||||||
|
p.bot.Send(conn, bot.Message, ch, fmt.Sprintf("Error checking ngate: %s", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, res := range wr {
|
||||||
|
msg := fmt.Sprintf("%s won %d for a score of %d",
|
||||||
|
res.User, res.Won, res.Score)
|
||||||
|
if len(res.WinningArticles) > 0 {
|
||||||
|
msg += "\nWinning articles: " + res.WinningArticles.Titles()
|
||||||
|
}
|
||||||
|
if len(res.LosingArticles) > 0 {
|
||||||
|
msg += "\nLosing articles: " + res.LosingArticles.Titles()
|
||||||
|
}
|
||||||
|
p.bot.Send(conn, bot.Message, ch, msg)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,390 @@
|
||||||
|
package webshit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
hacknews "github.com/PaulRosset/go-hacknews"
|
||||||
|
"github.com/PuerkitoBio/goquery"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/mmcdole/gofeed"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
HNFeed string
|
||||||
|
HNLimit int
|
||||||
|
BalanceReferesh int
|
||||||
|
}
|
||||||
|
|
||||||
|
var DefaultConfig = Config{
|
||||||
|
HNFeed: "topstories",
|
||||||
|
HNLimit: 10,
|
||||||
|
BalanceReferesh: 100,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Webshit struct {
|
||||||
|
db *sqlx.DB
|
||||||
|
config Config
|
||||||
|
}
|
||||||
|
|
||||||
|
type Story struct {
|
||||||
|
Title string
|
||||||
|
URL string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Stories []Story
|
||||||
|
|
||||||
|
func (s Stories) Titles() string {
|
||||||
|
out := ""
|
||||||
|
for i, v := range s {
|
||||||
|
if i > 0 {
|
||||||
|
out += ", "
|
||||||
|
}
|
||||||
|
out += v.Title
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bid struct {
|
||||||
|
ID int
|
||||||
|
User string
|
||||||
|
Title string
|
||||||
|
URL string
|
||||||
|
Bid int
|
||||||
|
Placed int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b Bid) PlacedParsed() time.Time {
|
||||||
|
return time.Unix(b.Placed, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Balance struct {
|
||||||
|
User string
|
||||||
|
Balance int
|
||||||
|
Score int
|
||||||
|
}
|
||||||
|
|
||||||
|
type WeeklyResult struct {
|
||||||
|
User string
|
||||||
|
Won int
|
||||||
|
WinningArticles Stories
|
||||||
|
LosingArticles Stories
|
||||||
|
Score int
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(db *sqlx.DB) *Webshit {
|
||||||
|
return NewConfig(db, DefaultConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConfig(db *sqlx.DB, cfg Config) *Webshit {
|
||||||
|
w := &Webshit{db: db, config: cfg}
|
||||||
|
w.setup()
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup will create any necessary SQL tables and populate them with minimal data
|
||||||
|
func (w *Webshit) setup() {
|
||||||
|
w.db.MustExec(`create table if not exists webshit_bids (
|
||||||
|
id integer primary key autoincrement,
|
||||||
|
user string,
|
||||||
|
title string,
|
||||||
|
url string,
|
||||||
|
bid integer,
|
||||||
|
placed integer
|
||||||
|
)`)
|
||||||
|
w.db.MustExec(`create table if not exists webshit_balances (
|
||||||
|
user string primary key,
|
||||||
|
balance int,
|
||||||
|
score int
|
||||||
|
)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Webshit) Check() ([]WeeklyResult, error) {
|
||||||
|
stories, published, err := w.GetWeekly()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var bids []Bid
|
||||||
|
if err = w.db.Select(&bids, `select user,title,url,bid from webshit_bids where placed < ?`,
|
||||||
|
published.Unix()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assuming no bids earlier than the weekly means there hasn't been a new weekly
|
||||||
|
if len(bids) == 0 {
|
||||||
|
return nil, fmt.Errorf("there are no bids against the current ngate post")
|
||||||
|
}
|
||||||
|
|
||||||
|
storyMap := map[string]Story{}
|
||||||
|
for _, s := range stories {
|
||||||
|
u, err := url.Parse(s.URL)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("couldn't parse URL")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
id := u.Query().Get("id")
|
||||||
|
storyMap[id] = s
|
||||||
|
}
|
||||||
|
|
||||||
|
wr := w.checkBids(bids, storyMap)
|
||||||
|
|
||||||
|
// Update all balance scores in a tx
|
||||||
|
if err := w.updateScores(wr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete all those bids
|
||||||
|
if _, err = w.db.Exec(`delete from webshit_bids where placed < ?`,
|
||||||
|
published.Unix()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set all balances to 100
|
||||||
|
if _, err = w.db.Exec(`update webshit_balances set balance=?`,
|
||||||
|
w.config.BalanceReferesh); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return wr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Webshit) checkBids(bids []Bid, storyMap map[string]Story) []WeeklyResult {
|
||||||
|
|
||||||
|
var wins []Bid
|
||||||
|
total, totalWinning := 0.0, 0.0
|
||||||
|
wr := map[string]WeeklyResult{}
|
||||||
|
|
||||||
|
for _, b := range bids {
|
||||||
|
score := w.GetScore(b.User)
|
||||||
|
if _, ok := wr[b.User]; !ok {
|
||||||
|
wr[b.User] = WeeklyResult{
|
||||||
|
User: b.User,
|
||||||
|
Score: score,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rec := wr[b.User]
|
||||||
|
|
||||||
|
u, err := url.Parse(b.URL)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("couldn't parse URL")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
id := u.Query().Get("id")
|
||||||
|
|
||||||
|
if s, ok := storyMap[id]; ok {
|
||||||
|
wins = append(wins, b)
|
||||||
|
rec.WinningArticles = append(rec.WinningArticles, s)
|
||||||
|
totalWinning += float64(b.Bid)
|
||||||
|
} else {
|
||||||
|
rec.LosingArticles = append(rec.LosingArticles, Story{b.Title, b.URL})
|
||||||
|
}
|
||||||
|
total += float64(b.Bid)
|
||||||
|
wr[b.User] = rec
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, b := range wins {
|
||||||
|
payout := float64(b.Bid) / totalWinning * total
|
||||||
|
rec := wr[b.User]
|
||||||
|
rec.Won += int(payout)
|
||||||
|
rec.Score += int(payout)
|
||||||
|
wr[b.User] = rec
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrMapToSlice(wr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHeadlines will return the current possible news headlines for bidding
|
||||||
|
func (w *Webshit) GetHeadlines() ([]Story, error) {
|
||||||
|
news := hacknews.Initializer{Story: w.config.HNFeed, NbPosts: w.config.HNLimit}
|
||||||
|
ids, err := news.GetCodesStory()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
posts, err := news.GetPostStory(ids)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var stories []Story
|
||||||
|
for _, p := range posts {
|
||||||
|
stories = append(stories, Story{
|
||||||
|
Title: p.Title,
|
||||||
|
URL: p.Url,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return stories, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWeekly will return the headlines in the last webshit weekly report
|
||||||
|
func (w *Webshit) GetWeekly() ([]Story, *time.Time, error) {
|
||||||
|
fp := gofeed.NewParser()
|
||||||
|
feed, err := fp.ParseURL("http://n-gate.com/hackernews/index.rss")
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if len(feed.Items) <= 0 {
|
||||||
|
return nil, nil, fmt.Errorf("no webshit weekly found")
|
||||||
|
}
|
||||||
|
|
||||||
|
published := feed.Items[0].PublishedParsed
|
||||||
|
|
||||||
|
buf := bytes.NewBufferString(feed.Items[0].Description)
|
||||||
|
doc, err := goquery.NewDocumentFromReader(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var items []Story
|
||||||
|
doc.Find(".storylink").Each(func(i int, s *goquery.Selection) {
|
||||||
|
story := Story{
|
||||||
|
Title: s.Find("a").Text(),
|
||||||
|
URL: s.SiblingsFiltered(".small").First().Find("a").AttrOr("href", ""),
|
||||||
|
}
|
||||||
|
items = append(items, story)
|
||||||
|
log.Debug().
|
||||||
|
Str("URL", story.URL).
|
||||||
|
Str("Title", story.Title).
|
||||||
|
Msg("Parsed webshit story")
|
||||||
|
})
|
||||||
|
|
||||||
|
return items, published, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBalances returns the current balance for all known users
|
||||||
|
// Any unknown user has a default balance on their first bid
|
||||||
|
func (w *Webshit) GetBalance(user string) int {
|
||||||
|
q := `select balance from webshit_balances where user=?`
|
||||||
|
var balance int
|
||||||
|
err := w.db.Get(&balance, q, user)
|
||||||
|
if err != nil {
|
||||||
|
return 100
|
||||||
|
}
|
||||||
|
return balance
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Webshit) GetScore(user string) int {
|
||||||
|
q := `select score from webshit_balances where user=?`
|
||||||
|
var score int
|
||||||
|
err := w.db.Get(&score, q, user)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return score
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Webshit) GetAllBids() ([]Bid, error) {
|
||||||
|
var bids []Bid
|
||||||
|
err := w.db.Select(&bids, `select * from webshit_bids`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bids, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Webshit) GetAllBalances() ([]Balance, error) {
|
||||||
|
var balances []Balance
|
||||||
|
err := w.db.Select(&balances, `select * from webshit_balances`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return balances, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bid allows a user to place a bid on a particular story
|
||||||
|
func (w *Webshit) Bid(user string, amount int, URL string) (Bid, error) {
|
||||||
|
bal := w.GetBalance(user)
|
||||||
|
if amount < 0 {
|
||||||
|
return Bid{}, fmt.Errorf("cannot bid less than 0")
|
||||||
|
}
|
||||||
|
if bal < amount {
|
||||||
|
return Bid{}, fmt.Errorf("cannot bid more than balance, %d", bal)
|
||||||
|
}
|
||||||
|
story, err := w.getStoryByURL(URL)
|
||||||
|
if err != nil {
|
||||||
|
return Bid{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ts := time.Now().Unix()
|
||||||
|
|
||||||
|
tx := w.db.MustBegin()
|
||||||
|
_, err = tx.Exec(`insert into webshit_bids (user,title,url,bid,placed) values (?,?,?,?,?)`,
|
||||||
|
user, story.Title, story.URL, amount, ts)
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
return Bid{}, err
|
||||||
|
}
|
||||||
|
q := `insert into webshit_balances (user,balance,score) values (?,?,0)
|
||||||
|
on conflict(user) do update set balance=?`
|
||||||
|
_, err = tx.Exec(q, user, bal-amount, bal-amount)
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
return Bid{}, err
|
||||||
|
}
|
||||||
|
tx.Commit()
|
||||||
|
|
||||||
|
return Bid{
|
||||||
|
User: user,
|
||||||
|
Title: story.Title,
|
||||||
|
URL: story.URL,
|
||||||
|
Placed: ts,
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// getStoryByURL scrapes the URL for a title
|
||||||
|
func (w *Webshit) getStoryByURL(URL string) (Story, error) {
|
||||||
|
u, err := url.Parse(URL)
|
||||||
|
if err != nil {
|
||||||
|
return Story{}, err
|
||||||
|
}
|
||||||
|
if u.Host != "news.ycombinator.com" {
|
||||||
|
return Story{}, fmt.Errorf("expected HN link")
|
||||||
|
}
|
||||||
|
res, err := http.Get(URL)
|
||||||
|
if err != nil {
|
||||||
|
return Story{}, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return Story{}, fmt.Errorf("bad response code: %d", res.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the HTML document
|
||||||
|
doc, err := goquery.NewDocumentFromReader(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return Story{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the review items
|
||||||
|
title := doc.Find("title").Text()
|
||||||
|
title = strings.ReplaceAll(title, " | Hacker News", "")
|
||||||
|
return Story{
|
||||||
|
Title: title,
|
||||||
|
URL: URL,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Webshit) updateScores(results []WeeklyResult) error {
|
||||||
|
tx := w.db.MustBegin()
|
||||||
|
for _, res := range results {
|
||||||
|
if _, err := tx.Exec(`update webshit_balances set score=? where user=?`,
|
||||||
|
res.Score, res.User); err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := tx.Commit()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrMapToSlice(wr map[string]WeeklyResult) []WeeklyResult {
|
||||||
|
var out = []WeeklyResult{}
|
||||||
|
for _, r := range wr {
|
||||||
|
out = append(out, r)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package webshit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.Logger = log.Logger.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeWS(t *testing.T) *Webshit {
|
||||||
|
db := sqlx.MustOpen("sqlite3", "file::memory:?mode=memory&cache=shared")
|
||||||
|
w := New(db)
|
||||||
|
assert.Equal(t, w.db, db)
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWebshit_GetWeekly(t *testing.T) {
|
||||||
|
w := makeWS(t)
|
||||||
|
weekly, pub, err := w.GetWeekly()
|
||||||
|
t.Logf("Pub: %v", pub)
|
||||||
|
assert.NotNil(t, pub)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.NotEmpty(t, weekly)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWebshit_GetHeadlines(t *testing.T) {
|
||||||
|
w := makeWS(t)
|
||||||
|
headlines, err := w.GetHeadlines()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.NotEmpty(t, headlines)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWebshit_getStoryByURL(t *testing.T) {
|
||||||
|
w := makeWS(t)
|
||||||
|
expected := "Developer Tropes: “Google Does It”"
|
||||||
|
s, err := w.getStoryByURL("https://news.ycombinator.com/item?id=20432887")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, s.Title, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWebshit_getStoryByURL_BadURL(t *testing.T) {
|
||||||
|
w := makeWS(t)
|
||||||
|
_, err := w.getStoryByURL("https://google.com")
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWebshit_GetBalance(t *testing.T) {
|
||||||
|
w := makeWS(t)
|
||||||
|
expected := 100
|
||||||
|
actual := w.GetBalance("foo")
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWebshit_checkBids(t *testing.T) {
|
||||||
|
w := makeWS(t)
|
||||||
|
bids := []Bid{
|
||||||
|
Bid{User: "foo", Title: "bar", URL: "https://baz/?id=1", Bid: 10},
|
||||||
|
Bid{User: "foo", Title: "bar2", URL: "http://baz/?id=2", Bid: 10},
|
||||||
|
}
|
||||||
|
storyMap := map[string]Story{
|
||||||
|
"1": Story{Title: "bar", URL: "http://baz/?id=1"},
|
||||||
|
}
|
||||||
|
result := w.checkBids(bids, storyMap)
|
||||||
|
assert.Len(t, result, 1)
|
||||||
|
if len(result) > 0 {
|
||||||
|
assert.Len(t, result[0].WinningArticles, 1)
|
||||||
|
assert.Len(t, result[0].LosingArticles, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWebshit_33PcWinner(t *testing.T) {
|
||||||
|
w := makeWS(t)
|
||||||
|
bids := []Bid{
|
||||||
|
Bid{User: "foo", Title: "bar", URL: "https://baz/?id=1", Bid: 10},
|
||||||
|
Bid{User: "foo", Title: "bar2", URL: "http://baz/?id=2", Bid: 10},
|
||||||
|
Bid{User: "bar", Title: "bar", URL: "http://baz/?id=1", Bid: 5},
|
||||||
|
}
|
||||||
|
storyMap := map[string]Story{
|
||||||
|
"1": Story{Title: "bar", URL: "http://baz/?id=1"},
|
||||||
|
}
|
||||||
|
result := w.checkBids(bids, storyMap)
|
||||||
|
assert.Len(t, result, 2)
|
||||||
|
if len(result) > 0 {
|
||||||
|
assert.Len(t, result[0].WinningArticles, 1)
|
||||||
|
assert.Len(t, result[0].LosingArticles, 1)
|
||||||
|
assert.Len(t, result[1].WinningArticles, 1)
|
||||||
|
assert.Len(t, result[1].LosingArticles, 0)
|
||||||
|
assert.Equal(t, result[0].Won, 16)
|
||||||
|
assert.Equal(t, result[1].Won, 8)
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,34 +16,37 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type PickerPlugin struct {
|
type PickerPlugin struct {
|
||||||
Bot bot.Bot
|
bot bot.Bot
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPickerPlugin creates a new PickerPlugin with the Plugin interface
|
// NewPickerPlugin creates a new PickerPlugin with the Plugin interface
|
||||||
func New(bot bot.Bot) *PickerPlugin {
|
func New(b bot.Bot) *PickerPlugin {
|
||||||
return &PickerPlugin{
|
pp := &PickerPlugin{
|
||||||
Bot: bot,
|
bot: b,
|
||||||
}
|
}
|
||||||
|
b.Register(pp, bot.Message, pp.message)
|
||||||
|
b.Register(pp, bot.Help, pp.help)
|
||||||
|
return pp
|
||||||
}
|
}
|
||||||
|
|
||||||
// Message responds to the bot hook on recieving messages.
|
// Message responds to the bot hook on recieving messages.
|
||||||
// This function returns true if the plugin responds in a meaningful way to the users message.
|
// 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.
|
// Otherwise, the function returns false and the bot continues execution of other plugins.
|
||||||
func (p *PickerPlugin) Message(message msg.Message) bool {
|
func (p *PickerPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
if !strings.HasPrefix(message.Body, "pick") {
|
if !strings.HasPrefix(message.Body, "pick") {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
n, items, err := p.parse(message.Body)
|
n, items, err := p.parse(message.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.Bot.SendMessage(message.Channel, err.Error())
|
p.bot.Send(c, bot.Message, message.Channel, err.Error())
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if n == 1 {
|
if n == 1 {
|
||||||
item := items[rand.Intn(len(items))]
|
item := items[rand.Intn(len(items))]
|
||||||
out := fmt.Sprintf("I've chosen %q for you.", strings.TrimSpace(item))
|
out := fmt.Sprintf("I've chosen %q for you.", strings.TrimSpace(item))
|
||||||
p.Bot.SendMessage(message.Channel, out)
|
p.bot.Send(c, bot.Message, message.Channel, out)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +62,7 @@ func (p *PickerPlugin) Message(message msg.Message) bool {
|
||||||
fmt.Fprintf(&b, ", %q", item)
|
fmt.Fprintf(&b, ", %q", item)
|
||||||
}
|
}
|
||||||
b.WriteString(" }")
|
b.WriteString(" }")
|
||||||
p.Bot.SendMessage(message.Channel, b.String())
|
p.bot.Send(c, bot.Message, message.Channel, b.String())
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,23 +111,7 @@ func (p *PickerPlugin) parse(body string) (int, []string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Help responds to help requests. Every plugin must implement a help function.
|
// Help responds to help requests. Every plugin must implement a help function.
|
||||||
func (p *PickerPlugin) Help(channel string, parts []string) {
|
func (p *PickerPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
p.Bot.SendMessage(channel, "Choose from a list of options. Try \"pick {a,b,c}\".")
|
p.bot.Send(c, bot.Message, message.Channel, "Choose from a list of options. Try \"pick {a,b,c}\".")
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty event handler because this plugin does not do anything on event recv
|
|
||||||
func (p *PickerPlugin) Event(kind string, message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handler for bot's own messages
|
|
||||||
func (p *PickerPlugin) BotMessage(message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register any web URLs desired
|
|
||||||
func (p *PickerPlugin) RegisterWeb() *string {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PickerPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
package picker
|
package picker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -12,12 +13,12 @@ import (
|
||||||
"github.com/velour/catbase/bot/user"
|
"github.com/velour/catbase/bot/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeMessage(payload string) msg.Message {
|
func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) {
|
||||||
isCmd := strings.HasPrefix(payload, "!")
|
isCmd := strings.HasPrefix(payload, "!")
|
||||||
if isCmd {
|
if isCmd {
|
||||||
payload = payload[1:]
|
payload = payload[1:]
|
||||||
}
|
}
|
||||||
return msg.Message{
|
return &cli.CliPlugin{}, bot.Message, msg.Message{
|
||||||
User: &user.User{Name: "tester"},
|
User: &user.User{Name: "tester"},
|
||||||
Channel: "test",
|
Channel: "test",
|
||||||
Body: payload,
|
Body: payload,
|
||||||
|
@ -29,7 +30,7 @@ func TestPick2(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
res := c.Message(makeMessage("!pick 2 { a, b,c}"))
|
res := c.message(makeMessage("!pick 2 { a, b,c}"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
if !res {
|
if !res {
|
||||||
t.Fatalf("expected a successful choice, got %q", mb.Messages[0])
|
t.Fatalf("expected a successful choice, got %q", mb.Messages[0])
|
||||||
|
@ -40,7 +41,7 @@ func TestPickDefault(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
_ = c.Message(makeMessage("!pick { a}"))
|
_ = c.message(makeMessage("!pick { a}"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.Equal(t, `I've chosen "a" for you.`, mb.Messages[0])
|
assert.Equal(t, `I've chosen "a" for you.`, mb.Messages[0])
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,3 @@
|
||||||
// © 2013 the CatBase Authors under the WTFPL. See AUTHORS for the list of authors.
|
// © 2013 the CatBase Authors under the WTFPL. See AUTHORS for the list of authors.
|
||||||
|
|
||||||
package plugins
|
package plugins
|
||||||
|
|
||||||
import "github.com/velour/catbase/bot/msg"
|
|
||||||
|
|
||||||
// Plugin interface defines the methods needed to accept a plugin
|
|
||||||
type Plugin interface {
|
|
||||||
Message(message msg.Message) bool
|
|
||||||
Event(kind string, message msg.Message) bool
|
|
||||||
BotMessage(message msg.Message) bool
|
|
||||||
LoadData()
|
|
||||||
Help()
|
|
||||||
RegisterWeb()
|
|
||||||
}
|
|
||||||
|
|
|
@ -11,36 +11,38 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type ReactionPlugin struct {
|
type ReactionPlugin struct {
|
||||||
Bot bot.Bot
|
bot bot.Bot
|
||||||
Config *config.Config
|
config *config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(bot bot.Bot) *ReactionPlugin {
|
func New(b bot.Bot) *ReactionPlugin {
|
||||||
return &ReactionPlugin{
|
rp := &ReactionPlugin{
|
||||||
Bot: bot,
|
bot: b,
|
||||||
Config: bot.Config(),
|
config: b.Config(),
|
||||||
}
|
}
|
||||||
|
b.Register(rp, bot.Message, rp.message)
|
||||||
|
return rp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReactionPlugin) Message(message msg.Message) bool {
|
func (p *ReactionPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
harrass := false
|
harrass := false
|
||||||
for _, nick := range p.Config.Reaction.HarrassList {
|
for _, nick := range p.config.GetArray("Reaction.HarrassList", []string{}) {
|
||||||
if message.User.Name == nick {
|
if message.User.Name == nick {
|
||||||
harrass = true
|
harrass = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
chance := p.Config.Reaction.GeneralChance
|
chance := p.config.GetFloat64("Reaction.GeneralChance", 0.01)
|
||||||
negativeWeight := 1
|
negativeWeight := 1
|
||||||
if harrass {
|
if harrass {
|
||||||
chance = p.Config.Reaction.HarrassChance
|
chance = p.config.GetFloat64("Reaction.HarrassChance", 0.05)
|
||||||
negativeWeight = p.Config.Reaction.NegativeHarrassmentMultiplier
|
negativeWeight = p.config.GetInt("Reaction.NegativeHarrassmentMultiplier", 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rand.Float64() < chance {
|
if rand.Float64() < chance {
|
||||||
numPositiveReactions := len(p.Config.Reaction.PositiveReactions)
|
numPositiveReactions := len(p.config.GetArray("Reaction.PositiveReactions", []string{}))
|
||||||
numNegativeReactions := len(p.Config.Reaction.NegativeReactions)
|
numNegativeReactions := len(p.config.GetArray("Reaction.NegativeReactions", []string{}))
|
||||||
|
|
||||||
maxIndex := numPositiveReactions + numNegativeReactions*negativeWeight
|
maxIndex := numPositiveReactions + numNegativeReactions*negativeWeight
|
||||||
|
|
||||||
|
@ -49,33 +51,15 @@ func (p *ReactionPlugin) Message(message msg.Message) bool {
|
||||||
reaction := ""
|
reaction := ""
|
||||||
|
|
||||||
if index < numPositiveReactions {
|
if index < numPositiveReactions {
|
||||||
reaction = p.Config.Reaction.PositiveReactions[index]
|
reaction = p.config.GetArray("Reaction.PositiveReactions", []string{})[index]
|
||||||
} else {
|
} else {
|
||||||
index -= numPositiveReactions
|
index -= numPositiveReactions
|
||||||
index %= numNegativeReactions
|
index %= numNegativeReactions
|
||||||
reaction = p.Config.Reaction.NegativeReactions[index]
|
reaction = p.config.GetArray("Reaction.NegativeReactions", []string{})[index]
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Bot.React(message.Channel, reaction, message)
|
p.bot.Send(c, bot.Reaction, message.Channel, reaction, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReactionPlugin) Help(channel string, parts []string) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ReactionPlugin) Event(kind string, message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ReactionPlugin) BotMessage(message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ReactionPlugin) RegisterWeb() *string {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ReactionPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
|
||||||
|
|
|
@ -0,0 +1,152 @@
|
||||||
|
package remember
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/velour/catbase/bot"
|
||||||
|
"github.com/velour/catbase/bot/msg"
|
||||||
|
"github.com/velour/catbase/plugins/fact"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RememberPlugin struct {
|
||||||
|
bot bot.Bot
|
||||||
|
log map[string][]msg.Message
|
||||||
|
db *sqlx.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(b bot.Bot) *RememberPlugin {
|
||||||
|
p := &RememberPlugin{
|
||||||
|
bot: b,
|
||||||
|
log: make(map[string][]msg.Message),
|
||||||
|
db: b.DB(),
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Register(p, bot.Message, p.message)
|
||||||
|
b.Register(p, bot.Help, p.help)
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *RememberPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
|
if strings.ToLower(message.Body) == "quote" && message.Command {
|
||||||
|
q := p.randQuote()
|
||||||
|
p.bot.Send(c, bot.Message, message.Channel, q)
|
||||||
|
|
||||||
|
// is it evil not to remember that the user said quote?
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
user := message.User
|
||||||
|
parts := strings.Fields(message.Body)
|
||||||
|
|
||||||
|
if message.Command && len(parts) >= 3 &&
|
||||||
|
strings.ToLower(parts[0]) == "remember" {
|
||||||
|
// we have a remember!
|
||||||
|
// look through the logs and find parts[1] as a user, if not,
|
||||||
|
// fuck this hoser
|
||||||
|
nick := parts[1]
|
||||||
|
snip := strings.Join(parts[2:], " ")
|
||||||
|
for i := len(p.log[message.Channel]) - 1; i >= 0; i-- {
|
||||||
|
entry := p.log[message.Channel][i]
|
||||||
|
log.Debug().Msgf("Comparing %s:%s with %s:%s",
|
||||||
|
entry.User.Name, entry.Body, nick, snip)
|
||||||
|
if strings.ToLower(entry.User.Name) == strings.ToLower(nick) &&
|
||||||
|
strings.Contains(
|
||||||
|
strings.ToLower(entry.Body),
|
||||||
|
strings.ToLower(snip),
|
||||||
|
) {
|
||||||
|
log.Debug().Msg("Found!")
|
||||||
|
|
||||||
|
var msg string
|
||||||
|
if entry.Action {
|
||||||
|
msg = fmt.Sprintf("*%s* %s", entry.User.Name, entry.Body)
|
||||||
|
} else {
|
||||||
|
msg = fmt.Sprintf("<%s> %s", entry.User.Name, entry.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
trigger := fmt.Sprintf("%s quotes", entry.User.Name)
|
||||||
|
|
||||||
|
fact := fact.Factoid{
|
||||||
|
Fact: strings.ToLower(trigger),
|
||||||
|
Verb: "reply",
|
||||||
|
Tidbit: msg,
|
||||||
|
Owner: user.Name,
|
||||||
|
Created: time.Now(),
|
||||||
|
Accessed: time.Now(),
|
||||||
|
Count: 0,
|
||||||
|
}
|
||||||
|
if err := fact.Save(p.db); err != nil {
|
||||||
|
log.Error().Err(err)
|
||||||
|
p.bot.Send(c, bot.Message, message.Channel, "Tell somebody I'm broke.")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info().
|
||||||
|
Str("msg", msg).
|
||||||
|
Msg("Remembering factoid")
|
||||||
|
|
||||||
|
// sorry, not creative with names so we're reusing msg
|
||||||
|
msg = fmt.Sprintf("Okay, %s, remembering '%s'.",
|
||||||
|
message.User.Name, msg)
|
||||||
|
p.bot.Send(c, bot.Message, message.Channel, msg)
|
||||||
|
p.recordMsg(message)
|
||||||
|
return true
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.bot.Send(c, bot.Message, message.Channel, "Sorry, I don't know that phrase.")
|
||||||
|
p.recordMsg(message)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
p.recordMsg(message)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *RememberPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
|
msg := "remember will let you quote your idiot friends. Just type " +
|
||||||
|
"!remember <nick> <snippet> to remember what they said. Snippet can " +
|
||||||
|
"be any part of their message. Later on, you can ask for a random " +
|
||||||
|
"!quote."
|
||||||
|
|
||||||
|
p.bot.Send(c, bot.Message, message.Channel, msg)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// deliver a random quote out of the db.
|
||||||
|
// Note: this is the same cache for all channels joined. This plugin needs to be
|
||||||
|
// expanded to have this function execute a quote for a particular channel
|
||||||
|
func (p *RememberPlugin) randQuote() string {
|
||||||
|
|
||||||
|
var f fact.Factoid
|
||||||
|
var tmpCreated int64
|
||||||
|
var tmpAccessed int64
|
||||||
|
err := p.db.QueryRow(`select * from factoid where fact like '%quotes'
|
||||||
|
order by random() limit 1;`).Scan(
|
||||||
|
&f.ID,
|
||||||
|
&f.Fact,
|
||||||
|
&f.Tidbit,
|
||||||
|
&f.Verb,
|
||||||
|
&f.Owner,
|
||||||
|
&tmpCreated,
|
||||||
|
&tmpAccessed,
|
||||||
|
&f.Count,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Error getting quotes")
|
||||||
|
return "I had a problem getting your quote."
|
||||||
|
}
|
||||||
|
f.Created = time.Unix(tmpCreated, 0)
|
||||||
|
f.Accessed = time.Unix(tmpAccessed, 0)
|
||||||
|
|
||||||
|
return f.Tidbit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *RememberPlugin) recordMsg(message msg.Message) {
|
||||||
|
log.Debug().Msgf("Logging message: %s: %s", message.User.Name, message.Body)
|
||||||
|
p.log[message.Channel] = append(p.log[message.Channel], message)
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package fact
|
package remember
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -8,6 +9,7 @@ import (
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
"github.com/velour/catbase/bot/user"
|
"github.com/velour/catbase/bot/user"
|
||||||
|
"github.com/velour/catbase/plugins/fact"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeMessage(nick, payload string) msg.Message {
|
func makeMessage(nick, payload string) msg.Message {
|
||||||
|
@ -23,10 +25,10 @@ func makeMessage(nick, payload string) msg.Message {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func makePlugin(t *testing.T) (*RememberPlugin, *Factoid, *bot.MockBot) {
|
func makePlugin(t *testing.T) (*RememberPlugin, *fact.FactoidPlugin, *bot.MockBot) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
f := New(mb) // for DB table
|
f := fact.New(mb) // for DB table
|
||||||
p := NewRemember(mb)
|
p := New(mb)
|
||||||
assert.NotNil(t, p)
|
assert.NotNil(t, p)
|
||||||
return p, f, mb
|
return p, f, mb
|
||||||
}
|
}
|
||||||
|
@ -42,11 +44,11 @@ func TestCornerCaseBug(t *testing.T) {
|
||||||
p, _, mb := makePlugin(t)
|
p, _, mb := makePlugin(t)
|
||||||
|
|
||||||
for _, m := range msgs {
|
for _, m := range msgs {
|
||||||
p.Message(m)
|
p.message(&cli.CliPlugin{}, bot.Message, m)
|
||||||
}
|
}
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.Contains(t, mb.Messages[0], "horse dick")
|
assert.Contains(t, mb.Messages[0], "horse dick")
|
||||||
q, err := getSingleFact(mb.DB(), "user1 quotes")
|
q, err := fact.GetSingleFact(mb.DB(), "user1 quotes")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Contains(t, q.Tidbit, "horse dick")
|
assert.Contains(t, q.Tidbit, "horse dick")
|
||||||
}
|
}
|
|
@ -5,13 +5,18 @@ package reminder
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"github.com/olebedev/when"
|
||||||
|
"github.com/olebedev/when/rules/common"
|
||||||
|
"github.com/olebedev/when/rules/en"
|
||||||
|
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
"github.com/velour/catbase/config"
|
"github.com/velour/catbase/config"
|
||||||
|
@ -22,11 +27,12 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type ReminderPlugin struct {
|
type ReminderPlugin struct {
|
||||||
Bot bot.Bot
|
bot bot.Bot
|
||||||
db *sqlx.DB
|
db *sqlx.DB
|
||||||
mutex *sync.Mutex
|
mutex *sync.Mutex
|
||||||
timer *time.Timer
|
timer *time.Timer
|
||||||
config *config.Config
|
config *config.Config
|
||||||
|
when *when.Parser
|
||||||
}
|
}
|
||||||
|
|
||||||
type Reminder struct {
|
type Reminder struct {
|
||||||
|
@ -38,10 +44,8 @@ type Reminder struct {
|
||||||
channel string
|
channel string
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(bot bot.Bot) *ReminderPlugin {
|
func New(b bot.Bot) *ReminderPlugin {
|
||||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
if _, err := b.DB().Exec(`create table if not exists reminders (
|
||||||
if bot.DBVersion() == 1 {
|
|
||||||
if _, err := bot.DB().Exec(`create table if not exists reminders (
|
|
||||||
id integer primary key,
|
id integer primary key,
|
||||||
fromWho string,
|
fromWho string,
|
||||||
toWho string,
|
toWho string,
|
||||||
|
@ -49,33 +53,51 @@ func New(bot bot.Bot) *ReminderPlugin {
|
||||||
remindWhen string,
|
remindWhen string,
|
||||||
channel string
|
channel string
|
||||||
);`); err != nil {
|
);`); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal().Err(err)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dur, _ := time.ParseDuration("1h")
|
dur, _ := time.ParseDuration("1h")
|
||||||
timer := time.NewTimer(dur)
|
timer := time.NewTimer(dur)
|
||||||
timer.Stop()
|
timer.Stop()
|
||||||
|
|
||||||
|
w := when.New(nil)
|
||||||
|
w.Add(en.All...)
|
||||||
|
w.Add(common.All...)
|
||||||
|
|
||||||
plugin := &ReminderPlugin{
|
plugin := &ReminderPlugin{
|
||||||
Bot: bot,
|
bot: b,
|
||||||
db: bot.DB(),
|
db: b.DB(),
|
||||||
mutex: &sync.Mutex{},
|
mutex: &sync.Mutex{},
|
||||||
timer: timer,
|
timer: timer,
|
||||||
config: bot.Config(),
|
config: b.Config(),
|
||||||
|
when: w,
|
||||||
}
|
}
|
||||||
|
|
||||||
plugin.queueUpNextReminder()
|
plugin.queueUpNextReminder()
|
||||||
|
|
||||||
go reminderer(plugin)
|
go reminderer(b.DefaultConnector(), plugin)
|
||||||
|
|
||||||
|
b.Register(plugin, bot.Message, plugin.message)
|
||||||
|
b.Register(plugin, bot.Help, plugin.help)
|
||||||
|
|
||||||
return plugin
|
return plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReminderPlugin) Message(message msg.Message) bool {
|
func (p *ReminderPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
channel := message.Channel
|
channel := message.Channel
|
||||||
from := message.User.Name
|
from := message.User.Name
|
||||||
|
|
||||||
|
var dur, dur2 time.Duration
|
||||||
|
t, err := p.when.Parse(message.Body, time.Now())
|
||||||
|
// Allowing err to fallthrough for other parsing
|
||||||
|
if t != nil && err == nil {
|
||||||
|
t2 := t.Time.Sub(time.Now()).String()
|
||||||
|
message.Body = string(message.Body[0:t.Index]) + t2 + string(message.Body[t.Index+len(t.Text):])
|
||||||
|
log.Debug().
|
||||||
|
Str("body", message.Body).
|
||||||
|
Str("text", t.Text).
|
||||||
|
Msg("Got time request")
|
||||||
|
}
|
||||||
parts := strings.Fields(message.Body)
|
parts := strings.Fields(message.Body)
|
||||||
|
|
||||||
if len(parts) >= 5 {
|
if len(parts) >= 5 {
|
||||||
|
@ -85,17 +107,16 @@ func (p *ReminderPlugin) Message(message msg.Message) bool {
|
||||||
who = from
|
who = from
|
||||||
}
|
}
|
||||||
|
|
||||||
dur, err := time.ParseDuration(parts[3])
|
dur, err = time.ParseDuration(parts[3])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.Bot.SendMessage(channel, "Easy cowboy, not sure I can parse that duration.")
|
p.bot.Send(c, bot.Message, channel, "Easy cowboy, not sure I can parse that duration. Try something like '1.5h' or '2h45m'.")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
operator := strings.ToLower(parts[2])
|
operator := strings.ToLower(parts[2])
|
||||||
|
|
||||||
doConfirm := true
|
doConfirm := true
|
||||||
|
|
||||||
if operator == "in" {
|
if operator == "in" || operator == "at" || operator == "on" {
|
||||||
//one off reminder
|
//one off reminder
|
||||||
//remind who in dur blah
|
//remind who in dur blah
|
||||||
when := time.Now().UTC().Add(dur)
|
when := time.Now().UTC().Add(dur)
|
||||||
|
@ -113,9 +134,10 @@ func (p *ReminderPlugin) Message(message msg.Message) bool {
|
||||||
} else if operator == "every" && strings.ToLower(parts[4]) == "for" {
|
} else if operator == "every" && strings.ToLower(parts[4]) == "for" {
|
||||||
//batch add, especially for reminding msherms to buy a kit
|
//batch add, especially for reminding msherms to buy a kit
|
||||||
//remind who every dur for dur2 blah
|
//remind who every dur for dur2 blah
|
||||||
dur2, err := time.ParseDuration(parts[5])
|
dur2, err = time.ParseDuration(parts[5])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.Bot.SendMessage(channel, "Easy cowboy, not sure I can parse that duration.")
|
log.Error().Err(err)
|
||||||
|
p.bot.Send(c, bot.Message, channel, "Easy cowboy, not sure I can parse that duration. Try something like '1.5h' or '2h45m'.")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,9 +145,10 @@ func (p *ReminderPlugin) Message(message msg.Message) bool {
|
||||||
endTime := time.Now().UTC().Add(dur2)
|
endTime := time.Now().UTC().Add(dur2)
|
||||||
what := strings.Join(parts[6:], " ")
|
what := strings.Join(parts[6:], " ")
|
||||||
|
|
||||||
|
max := p.config.GetInt("Reminder.MaxBatchAdd", 10)
|
||||||
for i := 0; when.Before(endTime); i++ {
|
for i := 0; when.Before(endTime); i++ {
|
||||||
if i >= p.config.Reminder.MaxBatchAdd {
|
if i >= max {
|
||||||
p.Bot.SendMessage(channel, "Easy cowboy, that's a lot of reminders. I'll add some of them.")
|
p.bot.Send(c, bot.Message, channel, "Easy cowboy, that's a lot of reminders. I'll add some of them.")
|
||||||
doConfirm = false
|
doConfirm = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -142,14 +165,14 @@ func (p *ReminderPlugin) Message(message msg.Message) bool {
|
||||||
when = when.Add(dur)
|
when = when.Add(dur)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
p.Bot.SendMessage(channel, "Easy cowboy, not sure I comprehend what you're asking.")
|
p.bot.Send(c, bot.Message, channel, "Easy cowboy, not sure I comprehend what you're asking.")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if doConfirm && from == who {
|
if doConfirm && from == who {
|
||||||
p.Bot.SendMessage(channel, fmt.Sprintf("Okay. I'll remind you."))
|
p.bot.Send(c, bot.Message, channel, fmt.Sprintf("Okay. I'll remind you."))
|
||||||
} else if doConfirm {
|
} else if doConfirm {
|
||||||
p.Bot.SendMessage(channel, fmt.Sprintf("Sure %s, I'll remind %s.", from, who))
|
p.bot.Send(c, bot.Message, channel, fmt.Sprintf("Sure %s, I'll remind %s.", from, who))
|
||||||
}
|
}
|
||||||
|
|
||||||
p.queueUpNextReminder()
|
p.queueUpNextReminder()
|
||||||
|
@ -169,22 +192,22 @@ func (p *ReminderPlugin) Message(message msg.Message) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.Bot.SendMessage(channel, "listing failed.")
|
p.bot.Send(c, bot.Message, channel, "listing failed.")
|
||||||
} else {
|
} else {
|
||||||
p.Bot.SendMessage(channel, response)
|
p.bot.Send(c, bot.Message, channel, response)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
} else if len(parts) == 3 && strings.ToLower(parts[0]) == "cancel" && strings.ToLower(parts[1]) == "reminder" {
|
} else if len(parts) == 3 && strings.ToLower(parts[0]) == "cancel" && strings.ToLower(parts[1]) == "reminder" {
|
||||||
id, err := strconv.ParseInt(parts[2], 10, 64)
|
id, err := strconv.ParseInt(parts[2], 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.Bot.SendMessage(channel, fmt.Sprintf("couldn't parse id: %s", parts[2]))
|
p.bot.Send(c, bot.Message, channel, fmt.Sprintf("couldn't parse id: %s", parts[2]))
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
err := p.deleteReminder(id)
|
err := p.deleteReminder(id)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
p.Bot.SendMessage(channel, fmt.Sprintf("successfully canceled reminder: %s", parts[2]))
|
p.bot.Send(c, bot.Message, channel, fmt.Sprintf("successfully canceled reminder: %s", parts[2]))
|
||||||
} else {
|
} else {
|
||||||
p.Bot.SendMessage(channel, fmt.Sprintf("failed to find and cancel reminder: %s", parts[2]))
|
p.bot.Send(c, bot.Message, channel, fmt.Sprintf("failed to find and cancel reminder: %s", parts[2]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -193,20 +216,9 @@ func (p *ReminderPlugin) Message(message msg.Message) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReminderPlugin) Help(channel string, parts []string) {
|
func (p *ReminderPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
p.Bot.SendMessage(channel, "Pester someone with a reminder. Try \"remind <user> in <duration> message\".\n\nUnsure about duration syntax? Check https://golang.org/pkg/time/#ParseDuration")
|
p.bot.Send(c, bot.Message, message.Channel, "Pester someone with a reminder. Try \"remind <user> in <duration> message\".\n\nUnsure about duration syntax? Check https://golang.org/pkg/time/#ParseDuration")
|
||||||
}
|
return true
|
||||||
|
|
||||||
func (p *ReminderPlugin) Event(kind string, message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ReminderPlugin) BotMessage(message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ReminderPlugin) RegisterWeb() *string {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReminderPlugin) getNextReminder() *Reminder {
|
func (p *ReminderPlugin) getNextReminder() *Reminder {
|
||||||
|
@ -214,7 +226,7 @@ func (p *ReminderPlugin) getNextReminder() *Reminder {
|
||||||
defer p.mutex.Unlock()
|
defer p.mutex.Unlock()
|
||||||
rows, err := p.db.Query("select id, fromWho, toWho, what, remindWhen, channel from reminders order by remindWhen asc limit 1;")
|
rows, err := p.db.Query("select id, fromWho, toWho, what, remindWhen, channel from reminders order by remindWhen asc limit 1;")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
@ -223,19 +235,19 @@ func (p *ReminderPlugin) getNextReminder() *Reminder {
|
||||||
var reminder *Reminder
|
var reminder *Reminder
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
if once {
|
if once {
|
||||||
log.Print("somehow got multiple rows")
|
log.Debug().Msg("somehow got multiple rows")
|
||||||
}
|
}
|
||||||
reminder = &Reminder{}
|
reminder = &Reminder{}
|
||||||
|
|
||||||
var when string
|
var when string
|
||||||
err := rows.Scan(&reminder.id, &reminder.from, &reminder.who, &reminder.what, &when, &reminder.channel)
|
err := rows.Scan(&reminder.id, &reminder.from, &reminder.who, &reminder.what, &when, &reminder.channel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
reminder.when, err = time.Parse(TIMESTAMP, when)
|
reminder.when, err = time.Parse(TIMESTAMP, when)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,7 +264,7 @@ func (p *ReminderPlugin) addReminder(reminder *Reminder) error {
|
||||||
reminder.from, reminder.who, reminder.what, reminder.when.Format(TIMESTAMP), reminder.channel)
|
reminder.from, reminder.who, reminder.what, reminder.when.Format(TIMESTAMP), reminder.channel)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -262,7 +274,7 @@ func (p *ReminderPlugin) deleteReminder(id int64) error {
|
||||||
defer p.mutex.Unlock()
|
defer p.mutex.Unlock()
|
||||||
res, err := p.db.Exec(`delete from reminders where id = ?;`, id)
|
res, err := p.db.Exec(`delete from reminders where id = ?;`, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
} else {
|
} else {
|
||||||
if affected, err := res.RowsAffected(); err != nil {
|
if affected, err := res.RowsAffected(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -273,12 +285,28 @@ func (p *ReminderPlugin) deleteReminder(id int64) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReminderPlugin) getRemindersFormatted(queryString string) (string, error) {
|
func (p *ReminderPlugin) getRemindersFormatted(filter string) (string, error) {
|
||||||
|
max := p.config.GetInt("Reminder.MaxList", 25)
|
||||||
|
queryString := fmt.Sprintf("select id, fromWho, toWho, what, remindWhen from reminders %s order by remindWhen asc limit %d;", filter, max)
|
||||||
|
countString := fmt.Sprintf("select COUNT(*) from reminders %s;", filter)
|
||||||
|
|
||||||
p.mutex.Lock()
|
p.mutex.Lock()
|
||||||
defer p.mutex.Unlock()
|
defer p.mutex.Unlock()
|
||||||
|
|
||||||
|
var total int
|
||||||
|
err := p.db.Get(&total, countString)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err)
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if total == 0 {
|
||||||
|
return "no pending reminders", nil
|
||||||
|
}
|
||||||
|
|
||||||
rows, err := p.db.Query(queryString)
|
rows, err := p.db.Query(queryString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Error().Err(err)
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
@ -294,23 +322,25 @@ func (p *ReminderPlugin) getRemindersFormatted(queryString string) (string, erro
|
||||||
reminders += fmt.Sprintf("%d) %s -> %s :: %s @ %s (%d)\n", counter, reminder.from, reminder.who, reminder.what, when, reminder.id)
|
reminders += fmt.Sprintf("%d) %s -> %s :: %s @ %s (%d)\n", counter, reminder.from, reminder.who, reminder.what, when, reminder.id)
|
||||||
counter++
|
counter++
|
||||||
}
|
}
|
||||||
if counter == 1 {
|
|
||||||
return "no pending reminders", nil
|
remaining := total - max
|
||||||
|
if remaining > 0 {
|
||||||
|
reminders += fmt.Sprintf("...%d more...\n", remaining)
|
||||||
}
|
}
|
||||||
|
|
||||||
return reminders, nil
|
return reminders, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReminderPlugin) getAllRemindersFormatted(channel string) (string, error) {
|
func (p *ReminderPlugin) getAllRemindersFormatted(channel string) (string, error) {
|
||||||
return p.getRemindersFormatted("select id, fromWho, toWho, what, remindWhen from reminders order by remindWhen asc;")
|
return p.getRemindersFormatted("")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReminderPlugin) getAllRemindersFromMeFormatted(channel, me string) (string, error) {
|
func (p *ReminderPlugin) getAllRemindersFromMeFormatted(channel, me string) (string, error) {
|
||||||
return p.getRemindersFormatted(fmt.Sprintf("select id, fromWho, toWho, what, remindWhen from reminders where fromWho = '%s' order by remindWhen asc;", me))
|
return p.getRemindersFormatted(fmt.Sprintf("where fromWho = '%s'", me))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReminderPlugin) getAllRemindersToMeFormatted(channel, me string) (string, error) {
|
func (p *ReminderPlugin) getAllRemindersToMeFormatted(channel, me string) (string, error) {
|
||||||
return p.getRemindersFormatted(fmt.Sprintf("select id, fromWho, toWho, what, remindWhen from reminders where toWho = '%s' order by remindWhen asc;", me))
|
return p.getRemindersFormatted(fmt.Sprintf("where toWho = '%s'", me))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReminderPlugin) queueUpNextReminder() {
|
func (p *ReminderPlugin) queueUpNextReminder() {
|
||||||
|
@ -321,7 +351,7 @@ func (p *ReminderPlugin) queueUpNextReminder() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func reminderer(p *ReminderPlugin) {
|
func reminderer(c bot.Connector, p *ReminderPlugin) {
|
||||||
for {
|
for {
|
||||||
<-p.timer.C
|
<-p.timer.C
|
||||||
|
|
||||||
|
@ -336,17 +366,16 @@ func reminderer(p *ReminderPlugin) {
|
||||||
message = fmt.Sprintf("Hey %s, %s wanted you to be reminded: %s", reminder.who, reminder.from, reminder.what)
|
message = fmt.Sprintf("Hey %s, %s wanted you to be reminded: %s", reminder.who, reminder.from, reminder.what)
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Bot.SendMessage(reminder.channel, message)
|
p.bot.Send(c, bot.Message, reminder.channel, message)
|
||||||
|
|
||||||
if err := p.deleteReminder(reminder.id); err != nil {
|
if err := p.deleteReminder(reminder.id); err != nil {
|
||||||
log.Print(reminder.id)
|
log.Error().
|
||||||
log.Print(err)
|
Int64("id", reminder.id).
|
||||||
log.Fatal("this will cause problems, we need to stop now.")
|
Err(err).
|
||||||
|
Msg("this will cause problems, we need to stop now.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p.queueUpNextReminder()
|
p.queueUpNextReminder()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReminderPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ package reminder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -14,25 +15,16 @@ import (
|
||||||
"github.com/velour/catbase/bot/user"
|
"github.com/velour/catbase/bot/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeMessage(payload string) msg.Message {
|
func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) {
|
||||||
isCmd := strings.HasPrefix(payload, "!")
|
return makeMessageBy(payload, "tester")
|
||||||
if isCmd {
|
|
||||||
payload = payload[1:]
|
|
||||||
}
|
|
||||||
return msg.Message{
|
|
||||||
User: &user.User{Name: "tester"},
|
|
||||||
Channel: "test",
|
|
||||||
Body: payload,
|
|
||||||
Command: isCmd,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeMessageBy(payload, by string) msg.Message {
|
func makeMessageBy(payload, by string) (bot.Connector, bot.Kind, msg.Message) {
|
||||||
isCmd := strings.HasPrefix(payload, "!")
|
isCmd := strings.HasPrefix(payload, "!")
|
||||||
if isCmd {
|
if isCmd {
|
||||||
payload = payload[1:]
|
payload = payload[1:]
|
||||||
}
|
}
|
||||||
return msg.Message{
|
return &cli.CliPlugin{}, bot.Message, msg.Message{
|
||||||
User: &user.User{Name: by},
|
User: &user.User{Name: by},
|
||||||
Channel: "test",
|
Channel: "test",
|
||||||
Body: payload,
|
Body: payload,
|
||||||
|
@ -40,11 +32,16 @@ func makeMessageBy(payload, by string) msg.Message {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMeReminder(t *testing.T) {
|
func setup(t *testing.T) (*ReminderPlugin, *bot.MockBot) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
r := New(mb)
|
||||||
assert.NotNil(t, c)
|
mb.DB().MustExec(`delete from reminders; delete from config;`)
|
||||||
res := c.Message(makeMessage("!remind me in 1s don't fail this test"))
|
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)
|
time.Sleep(2 * time.Second)
|
||||||
assert.Len(t, mb.Messages, 2)
|
assert.Len(t, mb.Messages, 2)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
|
@ -53,10 +50,8 @@ func TestMeReminder(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReminder(t *testing.T) {
|
func TestReminder(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
c, mb := setup(t)
|
||||||
c := New(mb)
|
res := c.message(makeMessage("!remind testuser in 1s don't fail this test"))
|
||||||
assert.NotNil(t, c)
|
|
||||||
res := c.Message(makeMessage("!remind testuser in 1s don't fail this test"))
|
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
assert.Len(t, mb.Messages, 2)
|
assert.Len(t, mb.Messages, 2)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
|
@ -65,12 +60,10 @@ func TestReminder(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReminderReorder(t *testing.T) {
|
func TestReminderReorder(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
c, mb := setup(t)
|
||||||
c := New(mb)
|
res := c.message(makeMessage("!remind testuser in 2s don't fail this test 2"))
|
||||||
assert.NotNil(t, c)
|
|
||||||
res := c.Message(makeMessage("!remind testuser in 2s don't fail this test 2"))
|
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
res = c.Message(makeMessage("!remind testuser in 1s don't fail this test 1"))
|
res = c.message(makeMessage("!remind testuser in 1s don't fail this test 1"))
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
assert.Len(t, mb.Messages, 4)
|
assert.Len(t, mb.Messages, 4)
|
||||||
|
@ -81,34 +74,28 @@ func TestReminderReorder(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReminderParse(t *testing.T) {
|
func TestReminderParse(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
c, mb := setup(t)
|
||||||
c := New(mb)
|
res := c.message(makeMessage("!remind testuser in unparseable don't fail this test"))
|
||||||
assert.NotNil(t, c)
|
|
||||||
res := c.Message(makeMessage("!remind testuser in unparseable don't fail this test"))
|
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Contains(t, mb.Messages[0], "Easy cowboy, not sure I can parse that duration.")
|
assert.Contains(t, mb.Messages[0], "Easy cowboy, not sure I can parse that duration. Try something like '1.5h' or '2h45m'.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEmptyList(t *testing.T) {
|
func TestEmptyList(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
c, mb := setup(t)
|
||||||
c := New(mb)
|
res := c.message(makeMessage("!list reminders"))
|
||||||
assert.NotNil(t, c)
|
|
||||||
res := c.Message(makeMessage("!list reminders"))
|
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Contains(t, mb.Messages[0], "no pending reminders")
|
assert.Contains(t, mb.Messages[0], "no pending reminders")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestList(t *testing.T) {
|
func TestList(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
c, mb := setup(t)
|
||||||
c := New(mb)
|
res := c.message(makeMessage("!remind testuser in 5m don't fail this test 1"))
|
||||||
assert.NotNil(t, c)
|
|
||||||
res := c.Message(makeMessage("!remind testuser in 5m don't fail this test 1"))
|
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
res = c.Message(makeMessage("!remind testuser in 5m don't fail this test 2"))
|
res = c.message(makeMessage("!remind testuser in 5m don't fail this test 2"))
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
res = c.Message(makeMessage("!list reminders"))
|
res = c.message(makeMessage("!list reminders"))
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Len(t, mb.Messages, 3)
|
assert.Len(t, mb.Messages, 3)
|
||||||
assert.Contains(t, mb.Messages[2], "1) tester -> testuser :: don't fail this test 1 @ ")
|
assert.Contains(t, mb.Messages[2], "1) tester -> testuser :: don't fail this test 1 @ ")
|
||||||
|
@ -116,14 +103,12 @@ func TestList(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestListBy(t *testing.T) {
|
func TestListBy(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
c, mb := setup(t)
|
||||||
c := New(mb)
|
res := c.message(makeMessageBy("!remind testuser in 5m don't fail this test 1", "testuser"))
|
||||||
assert.NotNil(t, c)
|
|
||||||
res := c.Message(makeMessageBy("!remind testuser in 5m don't fail this test 1", "testuser"))
|
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
res = c.Message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2"))
|
res = c.message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2"))
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
res = c.Message(makeMessage("!list reminders from testuser"))
|
res = c.message(makeMessage("!list reminders from testuser"))
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Len(t, mb.Messages, 3)
|
assert.Len(t, mb.Messages, 3)
|
||||||
assert.Contains(t, mb.Messages[2], "don't fail this test 1 @ ")
|
assert.Contains(t, mb.Messages[2], "don't fail this test 1 @ ")
|
||||||
|
@ -131,14 +116,12 @@ func TestListBy(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestListTo(t *testing.T) {
|
func TestListTo(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
c, mb := setup(t)
|
||||||
c := New(mb)
|
res := c.message(makeMessageBy("!remind testuser2 in 5m don't fail this test 1", "testuser"))
|
||||||
assert.NotNil(t, c)
|
|
||||||
res := c.Message(makeMessageBy("!remind testuser2 in 5m don't fail this test 1", "testuser"))
|
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
res = c.Message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2"))
|
res = c.message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2"))
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
res = c.Message(makeMessage("!list reminders to testuser"))
|
res = c.message(makeMessage("!list reminders to testuser"))
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Len(t, mb.Messages, 3)
|
assert.Len(t, mb.Messages, 3)
|
||||||
assert.NotContains(t, mb.Messages[2], "don't fail this test 1 @ ")
|
assert.NotContains(t, mb.Messages[2], "don't fail this test 1 @ ")
|
||||||
|
@ -146,55 +129,36 @@ func TestListTo(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToEmptyList(t *testing.T) {
|
func TestToEmptyList(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
c, mb := setup(t)
|
||||||
c := New(mb)
|
res := c.message(makeMessageBy("!remind testuser2 in 5m don't fail this test 1", "testuser"))
|
||||||
assert.NotNil(t, c)
|
|
||||||
res := c.Message(makeMessageBy("!remind testuser2 in 5m don't fail this test 1", "testuser"))
|
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
res = c.Message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2"))
|
res = c.message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2"))
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
res = c.Message(makeMessage("!list reminders to test"))
|
res = c.message(makeMessage("!list reminders to test"))
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Len(t, mb.Messages, 3)
|
assert.Len(t, mb.Messages, 3)
|
||||||
assert.Contains(t, mb.Messages[2], "no pending reminders")
|
assert.Contains(t, mb.Messages[2], "no pending reminders")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFromEmptyList(t *testing.T) {
|
func TestFromEmptyList(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
c, mb := setup(t)
|
||||||
c := New(mb)
|
res := c.message(makeMessageBy("!remind testuser2 in 5m don't fail this test 1", "testuser"))
|
||||||
assert.NotNil(t, c)
|
|
||||||
res := c.Message(makeMessageBy("!remind testuser2 in 5m don't fail this test 1", "testuser"))
|
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
res = c.Message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2"))
|
res = c.message(makeMessageBy("!remind testuser in 5m don't fail this test 2", "testuser2"))
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
res = c.Message(makeMessage("!list reminders from test"))
|
res = c.message(makeMessage("!list reminders from test"))
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Len(t, mb.Messages, 3)
|
assert.Len(t, mb.Messages, 3)
|
||||||
assert.Contains(t, mb.Messages[2], "no pending reminders")
|
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) {
|
func TestBatchMax(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
c, mb := setup(t)
|
||||||
c := New(mb)
|
c.config.Set("Reminder.MaxBatchAdd", "10")
|
||||||
c.config.Reminder.MaxBatchAdd = 10
|
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
res := c.Message(makeMessage("!remind testuser every 1h for 24h yikes"))
|
res := c.message(makeMessage("!remind testuser every 1h for 24h yikes"))
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
res = c.Message(makeMessage("!list reminders"))
|
res = c.message(makeMessage("!list reminders"))
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
time.Sleep(6 * time.Second)
|
time.Sleep(6 * time.Second)
|
||||||
assert.Len(t, mb.Messages, 2)
|
assert.Len(t, mb.Messages, 2)
|
||||||
|
@ -206,14 +170,13 @@ func TestBatchMax(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCancel(t *testing.T) {
|
func TestCancel(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
c, mb := setup(t)
|
||||||
c := New(mb)
|
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
res := c.Message(makeMessage("!remind testuser in 1m don't fail this test"))
|
res := c.message(makeMessage("!remind testuser in 1m don't fail this test"))
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
res = c.Message(makeMessage("!cancel reminder 1"))
|
res = c.message(makeMessage("!cancel reminder 1"))
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
res = c.Message(makeMessage("!list reminders"))
|
res = c.message(makeMessage("!list reminders"))
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Len(t, mb.Messages, 3)
|
assert.Len(t, mb.Messages, 3)
|
||||||
assert.Contains(t, mb.Messages[0], "Sure tester, I'll remind testuser.")
|
assert.Contains(t, mb.Messages[0], "Sure tester, I'll remind testuser.")
|
||||||
|
@ -222,40 +185,45 @@ func TestCancel(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCancelMiss(t *testing.T) {
|
func TestCancelMiss(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
c, mb := setup(t)
|
||||||
c := New(mb)
|
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
res := c.Message(makeMessage("!cancel reminder 1"))
|
res := c.message(makeMessage("!cancel reminder 1"))
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.Contains(t, mb.Messages[0], "failed to find and cancel reminder: 1")
|
assert.Contains(t, mb.Messages[0], "failed to find and cancel reminder: 1")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHelp(t *testing.T) {
|
func TestLimitList(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
c, mb := setup(t)
|
||||||
c := New(mb)
|
c.config.Set("Reminder.MaxBatchAdd", "10")
|
||||||
|
c.config.Set("Reminder.MaxList", "25")
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
c.Help("channel", []string{})
|
|
||||||
|
//Someone can redo this with a single batch add, but I can't locally due to an old version of sqllite (maybe).
|
||||||
|
res := c.message(makeMessage("!remind testuser every 1h for 10h don't fail this test"))
|
||||||
|
assert.True(t, res)
|
||||||
|
res = c.message(makeMessage("!remind testuser every 1h for 10h don't fail this test"))
|
||||||
|
assert.True(t, res)
|
||||||
|
res = c.message(makeMessage("!remind testuser every 1h for 10h don't fail this test"))
|
||||||
|
assert.True(t, res)
|
||||||
|
res = c.message(makeMessage("!list reminders"))
|
||||||
|
assert.True(t, res)
|
||||||
|
assert.Len(t, mb.Messages, 4)
|
||||||
|
assert.Contains(t, mb.Messages[0], "Sure tester, I'll remind testuser.")
|
||||||
|
assert.Contains(t, mb.Messages[1], "Sure tester, I'll remind testuser.")
|
||||||
|
assert.Contains(t, mb.Messages[2], "Sure tester, I'll remind testuser.")
|
||||||
|
|
||||||
|
for i := 0; i < 25; i++ {
|
||||||
|
assert.Contains(t, mb.Messages[3], fmt.Sprintf("%d) tester -> testuser :: don't fail this test", i+1))
|
||||||
|
}
|
||||||
|
assert.Contains(t, mb.Messages[3], "more...")
|
||||||
|
|
||||||
|
assert.NotContains(t, mb.Messages[3], "26) tester -> testuser")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHelp(t *testing.T) {
|
||||||
|
c, mb := setup(t)
|
||||||
|
assert.NotNil(t, c)
|
||||||
|
c.help(&cli.CliPlugin{}, bot.Help, msg.Message{Channel: "channel"}, []string{})
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBotMessage(t *testing.T) {
|
|
||||||
mb := bot.NewMockBot()
|
|
||||||
c := New(mb)
|
|
||||||
assert.NotNil(t, c)
|
|
||||||
assert.False(t, c.BotMessage(makeMessage("test")))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvent(t *testing.T) {
|
|
||||||
mb := bot.NewMockBot()
|
|
||||||
c := New(mb)
|
|
||||||
assert.NotNil(t, c)
|
|
||||||
assert.False(t, c.Event("dummy", makeMessage("test")))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRegisterWeb(t *testing.T) {
|
|
||||||
mb := bot.NewMockBot()
|
|
||||||
c := New(mb)
|
|
||||||
assert.NotNil(t, c)
|
|
||||||
assert.Nil(t, c.RegisterWeb())
|
|
||||||
}
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type RPGPlugin struct {
|
type RPGPlugin struct {
|
||||||
Bot bot.Bot
|
bot bot.Bot
|
||||||
listenFor map[string]*board
|
listenFor map[string]*board
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,45 +98,35 @@ func (b *board) checkAndMove(dx, dy int) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(b bot.Bot) *RPGPlugin {
|
func New(b bot.Bot) *RPGPlugin {
|
||||||
return &RPGPlugin{
|
rpg := &RPGPlugin{
|
||||||
Bot: b,
|
bot: b,
|
||||||
listenFor: map[string]*board{},
|
listenFor: map[string]*board{},
|
||||||
}
|
}
|
||||||
|
b.Register(rpg, bot.Message, rpg.message)
|
||||||
|
b.Register(rpg, bot.Reply, rpg.replyMessage)
|
||||||
|
b.Register(rpg, bot.Help, rpg.help)
|
||||||
|
return rpg
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *RPGPlugin) Message(message msg.Message) bool {
|
func (p *RPGPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
if strings.ToLower(message.Body) == "start rpg" {
|
if strings.ToLower(message.Body) == "start rpg" {
|
||||||
b := NewRandomBoard()
|
b := NewRandomBoard()
|
||||||
ts := p.Bot.SendMessage(message.Channel, b.toMessageString())
|
ts, _ := p.bot.Send(c, bot.Message, message.Channel, b.toMessageString())
|
||||||
p.listenFor[ts] = b
|
p.listenFor[ts] = b
|
||||||
p.Bot.ReplyToMessageIdentifier(message.Channel, "Over here.", ts)
|
p.bot.Send(c, bot.Reply, message.Channel, "Over here.", ts)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *RPGPlugin) LoadData() {
|
func (p *RPGPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
|
p.bot.Send(c, bot.Message, message.Channel, "Go find a walkthrough or something.")
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *RPGPlugin) Help(channel string, parts []string) {
|
func (p *RPGPlugin) replyMessage(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
p.Bot.SendMessage(channel, "Go find a walkthrough or something.")
|
identifier := args[0].(string)
|
||||||
}
|
if strings.ToLower(message.User.Name) != strings.ToLower(p.bot.Config().Get("Nick", "bot")) {
|
||||||
|
|
||||||
func (p *RPGPlugin) Event(kind string, message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *RPGPlugin) BotMessage(message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *RPGPlugin) RegisterWeb() *string {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *RPGPlugin) ReplyMessage(message msg.Message, identifier string) bool {
|
|
||||||
if strings.ToLower(message.User.Name) != strings.ToLower(p.Bot.Config().Nick) {
|
|
||||||
if b, ok := p.listenFor[identifier]; ok {
|
if b, ok := p.listenFor[identifier]; ok {
|
||||||
|
|
||||||
var res int
|
var res int
|
||||||
|
@ -155,12 +145,12 @@ func (p *RPGPlugin) ReplyMessage(message msg.Message, identifier string) bool {
|
||||||
|
|
||||||
switch res {
|
switch res {
|
||||||
case OK:
|
case OK:
|
||||||
p.Bot.Edit(message.Channel, b.toMessageString(), identifier)
|
p.bot.Send(c, bot.Edit, message.Channel, b.toMessageString(), identifier)
|
||||||
case WIN:
|
case WIN:
|
||||||
p.Bot.Edit(message.Channel, b.toMessageString(), identifier)
|
p.bot.Send(c, bot.Edit, message.Channel, b.toMessageString(), identifier)
|
||||||
p.Bot.ReplyToMessageIdentifier(message.Channel, "congratulations, you beat the easiest level imaginable.", identifier)
|
p.bot.Send(c, bot.Reply, message.Channel, "congratulations, you beat the easiest level imaginable.", identifier)
|
||||||
case INVALID:
|
case INVALID:
|
||||||
p.Bot.ReplyToMessageIdentifier(message.Channel, fmt.Sprintf("you can't move %s", message.Body), identifier)
|
p.bot.Send(c, bot.Reply, message.Channel, fmt.Sprintf("you can't move %s", message.Body), identifier)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1 @@
|
||||||
package rpgORdie
|
package rpgORdie
|
||||||
|
|
||||||
import ()
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type RSSPlugin struct {
|
type RSSPlugin struct {
|
||||||
Bot bot.Bot
|
bot bot.Bot
|
||||||
cache map[string]*cacheItem
|
cache map[string]*cacheItem
|
||||||
shelfLife time.Duration
|
shelfLife time.Duration
|
||||||
maxLines int
|
maxLines int
|
||||||
|
@ -49,28 +49,31 @@ func (c *cacheItem) getCurrentPage(maxLines int) string {
|
||||||
return page
|
return page
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(bot bot.Bot) *RSSPlugin {
|
func New(b bot.Bot) *RSSPlugin {
|
||||||
return &RSSPlugin{
|
rss := &RSSPlugin{
|
||||||
Bot: bot,
|
bot: b,
|
||||||
cache: map[string]*cacheItem{},
|
cache: map[string]*cacheItem{},
|
||||||
shelfLife: time.Minute * 20,
|
shelfLife: time.Minute * time.Duration(b.Config().GetInt("rss.shelfLife", 20)),
|
||||||
maxLines: 5,
|
maxLines: b.Config().GetInt("rss.maxLines", 5),
|
||||||
}
|
}
|
||||||
|
b.Register(rss, bot.Message, rss.message)
|
||||||
|
b.Register(rss, bot.Help, rss.help)
|
||||||
|
return rss
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *RSSPlugin) Message(message msg.Message) bool {
|
func (p *RSSPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
tokens := strings.Fields(message.Body)
|
tokens := strings.Fields(message.Body)
|
||||||
numTokens := len(tokens)
|
numTokens := len(tokens)
|
||||||
|
|
||||||
if numTokens == 2 && strings.ToLower(tokens[0]) == "rss" {
|
if numTokens == 2 && strings.ToLower(tokens[0]) == "rss" {
|
||||||
if item, ok := p.cache[strings.ToLower(tokens[1])]; ok && time.Now().Before(item.expiration) {
|
if item, ok := p.cache[strings.ToLower(tokens[1])]; ok && time.Now().Before(item.expiration) {
|
||||||
p.Bot.SendMessage(message.Channel, item.getCurrentPage(p.maxLines))
|
p.bot.Send(c, bot.Message, message.Channel, item.getCurrentPage(p.maxLines))
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
fp := gofeed.NewParser()
|
fp := gofeed.NewParser()
|
||||||
feed, err := fp.ParseURL(tokens[1])
|
feed, err := fp.ParseURL(tokens[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.Bot.SendMessage(message.Channel, fmt.Sprintf("RSS error: %s", err.Error()))
|
p.bot.Send(c, bot.Message, message.Channel, fmt.Sprintf("RSS error: %s", err.Error()))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
item := &cacheItem{
|
item := &cacheItem{
|
||||||
|
@ -86,7 +89,7 @@ func (p *RSSPlugin) Message(message msg.Message) bool {
|
||||||
|
|
||||||
p.cache[strings.ToLower(tokens[1])] = item
|
p.cache[strings.ToLower(tokens[1])] = item
|
||||||
|
|
||||||
p.Bot.SendMessage(message.Channel, item.getCurrentPage(p.maxLines))
|
p.bot.Send(c, bot.Message, message.Channel, item.getCurrentPage(p.maxLines))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,28 +97,8 @@ func (p *RSSPlugin) Message(message msg.Message) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *RSSPlugin) LoadData() {
|
|
||||||
// This bot has no data to load
|
|
||||||
}
|
|
||||||
|
|
||||||
// Help responds to help requests. Every plugin must implement a help function.
|
// Help responds to help requests. Every plugin must implement a help function.
|
||||||
func (p *RSSPlugin) Help(channel string, parts []string) {
|
func (p *RSSPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
p.Bot.SendMessage(channel, "try '!rss http://rss.cnn.com/rss/edition.rss'")
|
p.bot.Send(c, bot.Message, message.Channel, "try '!rss http://rss.cnn.com/rss/edition.rss'")
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty event handler because this plugin does not do anything on event recv
|
|
||||||
func (p *RSSPlugin) Event(kind string, message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handler for bot's own messages
|
|
||||||
func (p *RSSPlugin) BotMessage(message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register any web URLs desired
|
|
||||||
func (p *RSSPlugin) RegisterWeb() *string {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *RSSPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package rss
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -11,12 +12,12 @@ import (
|
||||||
"github.com/velour/catbase/bot/user"
|
"github.com/velour/catbase/bot/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeMessage(payload string) msg.Message {
|
func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) {
|
||||||
isCmd := strings.HasPrefix(payload, "!")
|
isCmd := strings.HasPrefix(payload, "!")
|
||||||
if isCmd {
|
if isCmd {
|
||||||
payload = payload[1:]
|
payload = payload[1:]
|
||||||
}
|
}
|
||||||
return msg.Message{
|
return &cli.CliPlugin{}, bot.Message, msg.Message{
|
||||||
User: &user.User{Name: "tester"},
|
User: &user.User{Name: "tester"},
|
||||||
Channel: "test",
|
Channel: "test",
|
||||||
Body: payload,
|
Body: payload,
|
||||||
|
@ -28,7 +29,7 @@ func TestRSS(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
res := c.Message(makeMessage("!rss http://rss.cnn.com/rss/edition.rss"))
|
res := c.message(makeMessage("!rss http://rss.cnn.com/rss/edition.rss"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
}
|
}
|
||||||
|
@ -38,7 +39,7 @@ func TestRSSPaging(t *testing.T) {
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
for i := 0; i < 20; i++ {
|
for i := 0; i < 20; i++ {
|
||||||
res := c.Message(makeMessage("!rss http://rss.cnn.com/rss/edition.rss"))
|
res := c.message(makeMessage("!rss http://rss.cnn.com/rss/edition.rss"))
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,13 @@ package sisyphus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
)
|
)
|
||||||
|
@ -18,7 +19,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type SisyphusPlugin struct {
|
type SisyphusPlugin struct {
|
||||||
Bot bot.Bot
|
bot bot.Bot
|
||||||
listenFor map[string]*game
|
listenFor map[string]*game
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,54 +38,54 @@ type game struct {
|
||||||
nextAns int
|
nextAns int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRandomGame(bot bot.Bot, channel, who string) *game {
|
func NewRandomGame(c bot.Connector, b bot.Bot, channel, who string) *game {
|
||||||
size := rand.Intn(9) + 2
|
size := rand.Intn(9) + 2
|
||||||
g := game{
|
g := game{
|
||||||
channel: channel,
|
channel: channel,
|
||||||
bot: bot,
|
bot: b,
|
||||||
who: who,
|
who: who,
|
||||||
start: time.Now(),
|
start: time.Now(),
|
||||||
size: size,
|
size: size,
|
||||||
current: size / 2,
|
current: size / 2,
|
||||||
}
|
}
|
||||||
g.id = bot.SendMessage(channel, g.toMessageString())
|
g.id, _ = b.Send(c, bot.Message, channel, g.toMessageString())
|
||||||
|
|
||||||
g.schedulePush()
|
g.schedulePush(c)
|
||||||
g.scheduleDecrement()
|
g.scheduleDecrement(c)
|
||||||
|
|
||||||
return &g
|
return &g
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *game) scheduleDecrement() {
|
func (g *game) scheduleDecrement(c bot.Connector) {
|
||||||
if g.timers[0] != nil {
|
if g.timers[0] != nil {
|
||||||
g.timers[0].Stop()
|
g.timers[0].Stop()
|
||||||
}
|
}
|
||||||
minDec := g.bot.Config().Sisyphus.MinDecrement
|
minDec := g.bot.Config().GetInt("Sisyphus.MinDecrement", 10)
|
||||||
maxDec := g.bot.Config().Sisyphus.MinDecrement
|
maxDec := g.bot.Config().GetInt("Sisyphus.MaxDecrement", 30)
|
||||||
g.nextDec = time.Now().Add(time.Duration((minDec + rand.Intn(maxDec))) * time.Minute)
|
g.nextDec = time.Now().Add(time.Duration(minDec+rand.Intn(maxDec)) * time.Minute)
|
||||||
go func() {
|
go func() {
|
||||||
t := time.NewTimer(g.nextDec.Sub(time.Now()))
|
t := time.NewTimer(g.nextDec.Sub(time.Now()))
|
||||||
g.timers[0] = t
|
g.timers[0] = t
|
||||||
select {
|
select {
|
||||||
case <-t.C:
|
case <-t.C:
|
||||||
g.handleDecrement()
|
g.handleDecrement(c)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *game) schedulePush() {
|
func (g *game) schedulePush(c bot.Connector) {
|
||||||
if g.timers[1] != nil {
|
if g.timers[1] != nil {
|
||||||
g.timers[1].Stop()
|
g.timers[1].Stop()
|
||||||
}
|
}
|
||||||
minPush := g.bot.Config().Sisyphus.MinPush
|
minPush := g.bot.Config().GetInt("Sisyphus.MinPush", 1)
|
||||||
maxPush := g.bot.Config().Sisyphus.MaxPush
|
maxPush := g.bot.Config().GetInt("Sisyphus.MaxPush", 10)
|
||||||
g.nextPush = time.Now().Add(time.Duration(rand.Intn(maxPush)+minPush) * time.Minute)
|
g.nextPush = time.Now().Add(time.Duration(rand.Intn(maxPush)+minPush) * time.Minute)
|
||||||
go func() {
|
go func() {
|
||||||
t := time.NewTimer(g.nextPush.Sub(time.Now()))
|
t := time.NewTimer(g.nextPush.Sub(time.Now()))
|
||||||
g.timers[1] = t
|
g.timers[1] = t
|
||||||
select {
|
select {
|
||||||
case <-t.C:
|
case <-t.C:
|
||||||
g.handleNotify()
|
g.handleNotify(c)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
@ -96,21 +97,21 @@ func (g *game) endGame() {
|
||||||
g.ended = true
|
g.ended = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *game) handleDecrement() {
|
func (g *game) handleDecrement(c bot.Connector) {
|
||||||
g.current++
|
g.current++
|
||||||
g.bot.Edit(g.channel, g.toMessageString(), g.id)
|
g.bot.Send(c, bot.Edit, g.channel, g.toMessageString(), g.id)
|
||||||
if g.current > g.size-2 {
|
if g.current > g.size-2 {
|
||||||
g.bot.ReplyToMessageIdentifier(g.channel, "you lose", g.id)
|
g.bot.Send(c, bot.Reply, g.channel, "you lose", g.id)
|
||||||
msg := fmt.Sprintf("%s just lost the game after %s", g.who, time.Now().Sub(g.start))
|
msg := fmt.Sprintf("%s just lost the game after %s", g.who, time.Now().Sub(g.start))
|
||||||
g.bot.SendMessage(g.channel, msg)
|
g.bot.Send(c, bot.Message, g.channel, msg)
|
||||||
g.endGame()
|
g.endGame()
|
||||||
} else {
|
} else {
|
||||||
g.scheduleDecrement()
|
g.scheduleDecrement(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *game) handleNotify() {
|
func (g *game) handleNotify(c bot.Connector) {
|
||||||
g.bot.ReplyToMessageIdentifier(g.channel, "You can push now.\n"+g.generateQuestion(), g.id)
|
g.bot.Send(c, bot.Reply, g.channel, "You can push now.\n"+g.generateQuestion(), g.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *game) generateQuestion() string {
|
func (g *game) generateQuestion() string {
|
||||||
|
@ -162,43 +163,37 @@ func (g *game) toMessageString() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(b bot.Bot) *SisyphusPlugin {
|
func New(b bot.Bot) *SisyphusPlugin {
|
||||||
return &SisyphusPlugin{
|
sp := &SisyphusPlugin{
|
||||||
Bot: b,
|
bot: b,
|
||||||
listenFor: map[string]*game{},
|
listenFor: map[string]*game{},
|
||||||
}
|
}
|
||||||
|
b.Register(sp, bot.Message, sp.message)
|
||||||
|
b.Register(sp, bot.Reply, sp.replyMessage)
|
||||||
|
b.Register(sp, bot.Help, sp.help)
|
||||||
|
return sp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *SisyphusPlugin) Message(message msg.Message) bool {
|
func (p *SisyphusPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
if strings.ToLower(message.Body) == "start sisyphus" {
|
if strings.ToLower(message.Body) == "start sisyphus" {
|
||||||
b := NewRandomGame(p.Bot, message.Channel, message.User.Name)
|
b := NewRandomGame(c, p.bot, message.Channel, message.User.Name)
|
||||||
p.listenFor[b.id] = b
|
p.listenFor[b.id] = b
|
||||||
p.Bot.ReplyToMessageIdentifier(message.Channel, "Over here.", b.id)
|
p.bot.Send(c, bot.Reply, message.Channel, "Over here.", b.id)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *SisyphusPlugin) Help(channel string, parts []string) {
|
func (p *SisyphusPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
p.Bot.SendMessage(channel, "https://en.wikipedia.org/wiki/Sisyphus")
|
p.bot.Send(c, bot.Message, message.Channel, "https://en.wikipedia.org/wiki/Sisyphus")
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *SisyphusPlugin) Event(kind string, message msg.Message) bool {
|
func (p *SisyphusPlugin) replyMessage(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
return false
|
identifier := args[0].(string)
|
||||||
}
|
if strings.ToLower(message.User.Name) != strings.ToLower(p.bot.Config().Get("Nick", "bot")) {
|
||||||
|
|
||||||
func (p *SisyphusPlugin) BotMessage(message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SisyphusPlugin) RegisterWeb() *string {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SisyphusPlugin) ReplyMessage(message msg.Message, identifier string) bool {
|
|
||||||
if strings.ToLower(message.User.Name) != strings.ToLower(p.Bot.Config().Nick) {
|
|
||||||
if g, ok := p.listenFor[identifier]; ok {
|
if g, ok := p.listenFor[identifier]; ok {
|
||||||
|
|
||||||
log.Printf("got message on %s: %+v", identifier, message)
|
log.Debug().Msgf("got message on %s: %+v", identifier, message)
|
||||||
|
|
||||||
if g.ended {
|
if g.ended {
|
||||||
return false
|
return false
|
||||||
|
@ -211,18 +206,18 @@ func (p *SisyphusPlugin) ReplyMessage(message msg.Message, identifier string) bo
|
||||||
|
|
||||||
if time.Now().After(g.nextPush) {
|
if time.Now().After(g.nextPush) {
|
||||||
if g.checkAnswer(message.Body) {
|
if g.checkAnswer(message.Body) {
|
||||||
p.Bot.Edit(message.Channel, g.toMessageString(), identifier)
|
p.bot.Send(c, bot.Edit, message.Channel, g.toMessageString(), identifier)
|
||||||
g.schedulePush()
|
g.schedulePush(c)
|
||||||
msg := fmt.Sprintf("Ok. You can push again in %s", g.nextPush.Sub(time.Now()))
|
msg := fmt.Sprintf("Ok. You can push again in %s", g.nextPush.Sub(time.Now()))
|
||||||
p.Bot.ReplyToMessageIdentifier(message.Channel, msg, identifier)
|
p.bot.Send(c, bot.Reply, message.Channel, msg, identifier)
|
||||||
} else {
|
} else {
|
||||||
p.Bot.ReplyToMessageIdentifier(message.Channel, "you lose", identifier)
|
p.bot.Send(c, bot.Reply, message.Channel, "you lose", identifier)
|
||||||
msg := fmt.Sprintf("%s just lost the sisyphus game after %s", g.who, time.Now().Sub(g.start))
|
msg := fmt.Sprintf("%s just lost the sisyphus game after %s", g.who, time.Now().Sub(g.start))
|
||||||
p.Bot.SendMessage(message.Channel, msg)
|
p.bot.Send(c, bot.Message, message.Channel, msg)
|
||||||
g.endGame()
|
g.endGame()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
p.Bot.ReplyToMessageIdentifier(message.Channel, "you cannot push yet", identifier)
|
p.bot.Send(c, bot.Reply, message.Channel, "you cannot push yet", identifier)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
package stock
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"github.com/velour/catbase/bot"
|
||||||
|
"github.com/velour/catbase/bot/msg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StockPlugin struct {
|
||||||
|
bot bot.Bot
|
||||||
|
apiKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(b bot.Bot) *StockPlugin {
|
||||||
|
s := &StockPlugin{
|
||||||
|
bot: b,
|
||||||
|
apiKey: b.Config().GetString("Stock.API_KEY", "0E1DP61SJ7GF81IE"),
|
||||||
|
}
|
||||||
|
b.Register(s, bot.Message, s.message)
|
||||||
|
b.Register(s, bot.Help, s.help)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
type GlobalQuote struct {
|
||||||
|
Info StockInfo `json:"GlobalQuote"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StockInfo struct {
|
||||||
|
Symbol string `json:"symbol"`
|
||||||
|
Open string `json:"open"`
|
||||||
|
High string `json:"high"`
|
||||||
|
Low string `json:"low"`
|
||||||
|
Price string `json:"price"`
|
||||||
|
Volume string `json:"volume"`
|
||||||
|
LatestTradingDay string `json:"latesttradingday"`
|
||||||
|
PreviousClose string `json:"previousclose"`
|
||||||
|
Change string `json:"change"`
|
||||||
|
ChangePercent string `json:"changepercent"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *StockPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
|
if !message.Command {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens := strings.Fields(message.Body)
|
||||||
|
numTokens := len(tokens)
|
||||||
|
|
||||||
|
if numTokens == 2 && strings.ToLower(tokens[0]) == "stock-price" {
|
||||||
|
query := fmt.Sprintf("https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=%s&apikey=%s", tokens[1], p.apiKey)
|
||||||
|
|
||||||
|
resp, err := http.Get(query)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Failed to get stock info")
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error stock info body")
|
||||||
|
}
|
||||||
|
|
||||||
|
response := "Failed to retrieve data for stock symbol: " + tokens[1]
|
||||||
|
|
||||||
|
regex := regexp.MustCompile("[0-9][0-9]\\. ")
|
||||||
|
|
||||||
|
cleaned := regex.ReplaceAllString(string(body), "")
|
||||||
|
cleaned = strings.ReplaceAll(cleaned, " ", "")
|
||||||
|
|
||||||
|
var info GlobalQuote
|
||||||
|
err = json.Unmarshal([]byte(cleaned), &info)
|
||||||
|
|
||||||
|
if err == nil && strings.EqualFold(tokens[1], info.Info.Symbol) {
|
||||||
|
response = fmt.Sprintf("%s : $%s (%s)", tokens[1], info.Info.Price, info.Info.ChangePercent)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.bot.Send(c, bot.Message, message.Channel, response)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *StockPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
|
p.bot.Send(c, bot.Message, message.Channel, "try '!stock-price SYMBOL'")
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package stock
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/velour/catbase/bot"
|
||||||
|
"github.com/velour/catbase/bot/msg"
|
||||||
|
"github.com/velour/catbase/bot/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) {
|
||||||
|
isCmd := strings.HasPrefix(payload, "!")
|
||||||
|
if isCmd {
|
||||||
|
payload = payload[1:]
|
||||||
|
}
|
||||||
|
return &cli.CliPlugin{}, bot.Message, msg.Message{
|
||||||
|
User: &user.User{Name: "tester"},
|
||||||
|
Channel: "test",
|
||||||
|
Body: payload,
|
||||||
|
Command: isCmd,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValid(t *testing.T) {
|
||||||
|
mb := bot.NewMockBot()
|
||||||
|
c := New(mb)
|
||||||
|
assert.NotNil(t, c)
|
||||||
|
res := c.message(makeMessage("!stock-price TWTR"))
|
||||||
|
assert.Len(t, mb.Messages, 1)
|
||||||
|
assert.True(t, res)
|
||||||
|
assert.Contains(t, mb.Messages[0], "TWTR : $")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvalid(t *testing.T) {
|
||||||
|
mb := bot.NewMockBot()
|
||||||
|
c := New(mb)
|
||||||
|
assert.NotNil(t, c)
|
||||||
|
res := c.message(makeMessage("!stock-price NOTREAL"))
|
||||||
|
assert.Len(t, mb.Messages, 1)
|
||||||
|
assert.True(t, res)
|
||||||
|
assert.Contains(t, mb.Messages[0], "Failed to retrieve data for stock symbol: NOTREAL")
|
||||||
|
}
|
|
@ -4,14 +4,20 @@ package talker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
|
"github.com/velour/catbase/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
var goatse []string = []string{
|
var goatse = []string{
|
||||||
"```* g o a t s e x * g o a t s e x * g o a t s e x *",
|
"```* g o a t s e x * g o a t s e x * g o a t s e x *",
|
||||||
"g g",
|
"g g",
|
||||||
"o / \\ \\ / \\ o",
|
"o / \\ \\ / \\ o",
|
||||||
|
@ -40,28 +46,48 @@ var goatse []string = []string{
|
||||||
}
|
}
|
||||||
|
|
||||||
type TalkerPlugin struct {
|
type TalkerPlugin struct {
|
||||||
Bot bot.Bot
|
bot bot.Bot
|
||||||
enforceNicks bool
|
config *config.Config
|
||||||
sayings []string
|
sayings []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(bot bot.Bot) *TalkerPlugin {
|
func New(b bot.Bot) *TalkerPlugin {
|
||||||
return &TalkerPlugin{
|
tp := &TalkerPlugin{
|
||||||
Bot: bot,
|
bot: b,
|
||||||
enforceNicks: bot.Config().EnforceNicks,
|
config: b.Config(),
|
||||||
sayings: bot.Config().WelcomeMsgs,
|
|
||||||
}
|
}
|
||||||
|
b.Register(tp, bot.Message, tp.message)
|
||||||
|
b.Register(tp, bot.Help, tp.help)
|
||||||
|
tp.registerWeb(b.DefaultConnector())
|
||||||
|
return tp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *TalkerPlugin) Message(message msg.Message) bool {
|
func (p *TalkerPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
channel := message.Channel
|
channel := message.Channel
|
||||||
body := message.Body
|
body := message.Body
|
||||||
lowermessage := strings.ToLower(body)
|
lowermessage := strings.ToLower(body)
|
||||||
|
|
||||||
|
if message.Command && strings.HasPrefix(lowermessage, "cowsay") {
|
||||||
|
msg, err := p.cowSay(strings.TrimPrefix(message.Body, "cowsay "))
|
||||||
|
if err != nil {
|
||||||
|
p.bot.Send(c, bot.Message, channel, "Error running cowsay: %s", err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
p.bot.Send(c, bot.Message, channel, msg)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if message.Command && strings.HasPrefix(lowermessage, "list cows") {
|
||||||
|
cows := p.allCows()
|
||||||
|
m := fmt.Sprintf("Cows: %s", strings.Join(cows, ", "))
|
||||||
|
p.bot.Send(c, bot.Message, channel, m)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: This ought to be space split afterwards to remove any punctuation
|
// TODO: This ought to be space split afterwards to remove any punctuation
|
||||||
if message.Command && strings.HasPrefix(lowermessage, "say") {
|
if message.Command && strings.HasPrefix(lowermessage, "say") {
|
||||||
msg := strings.TrimSpace(body[3:])
|
msg := strings.TrimSpace(body[3:])
|
||||||
p.Bot.SendMessage(channel, msg)
|
p.bot.Send(c, bot.Message, channel, msg)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,45 +103,85 @@ func (p *TalkerPlugin) Message(message msg.Message) bool {
|
||||||
line = strings.Replace(line, "{nick}", nick, 1)
|
line = strings.Replace(line, "{nick}", nick, 1)
|
||||||
output += line + "\n"
|
output += line + "\n"
|
||||||
}
|
}
|
||||||
p.Bot.SendMessage(channel, output)
|
p.bot.Send(c, bot.Message, channel, output)
|
||||||
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 true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *TalkerPlugin) Help(channel string, parts []string) {
|
func (p *TalkerPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
p.Bot.SendMessage(channel, "Hi, this is talker. I like to talk about FredFelps!")
|
p.bot.Send(c, bot.Message, message.Channel, "Hi, this is talker. I like to talk about FredFelps!")
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty event handler because this plugin does not do anything on event recv
|
func (p *TalkerPlugin) cowSay(text string) (string, error) {
|
||||||
func (p *TalkerPlugin) Event(kind string, message msg.Message) bool {
|
fields := strings.Split(text, " ")
|
||||||
if kind == "JOIN" && strings.ToLower(message.User.Name) != strings.ToLower(p.Bot.Config().Nick) {
|
cow := "default"
|
||||||
if len(p.sayings) == 0 {
|
if len(fields) > 1 && p.hasCow(fields[0]) {
|
||||||
return false
|
cow = fields[0]
|
||||||
|
text = strings.Join(fields[1:], " ")
|
||||||
}
|
}
|
||||||
msg := fmt.Sprintf(p.sayings[rand.Intn(len(p.sayings))], message.User.Name)
|
cmd := exec.Command("cowsay", "-f", cow, text)
|
||||||
p.Bot.SendMessage(message.Channel, msg)
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = cmd.Start(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := ioutil.ReadAll(stdout)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("```%s```", output), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TalkerPlugin) hasCow(cow string) bool {
|
||||||
|
cows := p.allCows()
|
||||||
|
for _, c := range cows {
|
||||||
|
if strings.ToLower(cow) == c {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler for bot's own messages
|
func (p *TalkerPlugin) allCows() []string {
|
||||||
func (p *TalkerPlugin) BotMessage(message msg.Message) bool {
|
f, err := os.Open(p.config.Get("talker.cowpath", "/usr/local/share/cows"))
|
||||||
return false
|
if err != nil {
|
||||||
|
return []string{"default"}
|
||||||
|
}
|
||||||
|
|
||||||
|
files, err := f.Readdir(0)
|
||||||
|
if err != nil {
|
||||||
|
return []string{"default"}
|
||||||
|
}
|
||||||
|
|
||||||
|
cows := []string{}
|
||||||
|
for _, f := range files {
|
||||||
|
if strings.HasSuffix(f.Name(), ".cow") {
|
||||||
|
cows = append(cows, strings.TrimSuffix(f.Name(), ".cow"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cows
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register any web URLs desired
|
func (p *TalkerPlugin) registerWeb(c bot.Connector) {
|
||||||
func (p *TalkerPlugin) RegisterWeb() *string {
|
http.HandleFunc("/slash/cowsay", func(w http.ResponseWriter, r *http.Request) {
|
||||||
return nil
|
r.ParseForm()
|
||||||
|
log.Debug().Msgf("Cowsay:\n%+v", r.PostForm.Get("text"))
|
||||||
|
channel := r.PostForm.Get("channel_id")
|
||||||
|
log.Debug().Msgf("channel: %s", channel)
|
||||||
|
msg, err := p.cowSay(r.PostForm.Get("text"))
|
||||||
|
if err != nil {
|
||||||
|
p.bot.Send(c, bot.Message, channel, fmt.Sprintf("Error running cowsay: %s", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.bot.Send(c, bot.Message, channel, msg)
|
||||||
|
w.WriteHeader(200)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *TalkerPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
package talker
|
package talker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -12,12 +13,12 @@ import (
|
||||||
"github.com/velour/catbase/bot/user"
|
"github.com/velour/catbase/bot/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeMessage(payload string) msg.Message {
|
func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) {
|
||||||
isCmd := strings.HasPrefix(payload, "!")
|
isCmd := strings.HasPrefix(payload, "!")
|
||||||
if isCmd {
|
if isCmd {
|
||||||
payload = payload[1:]
|
payload = payload[1:]
|
||||||
}
|
}
|
||||||
return msg.Message{
|
return &cli.CliPlugin{}, bot.Message, msg.Message{
|
||||||
User: &user.User{Name: "tester"},
|
User: &user.User{Name: "tester"},
|
||||||
Channel: "test",
|
Channel: "test",
|
||||||
Body: payload,
|
Body: payload,
|
||||||
|
@ -29,7 +30,7 @@ func TestGoatse(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
res := c.Message(makeMessage("goatse"))
|
res := c.message(makeMessage("goatse"))
|
||||||
assert.Len(t, mb.Messages, 0)
|
assert.Len(t, mb.Messages, 0)
|
||||||
assert.False(t, res)
|
assert.False(t, res)
|
||||||
}
|
}
|
||||||
|
@ -38,7 +39,7 @@ func TestGoatseCommand(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
res := c.Message(makeMessage("!goatse"))
|
res := c.message(makeMessage("!goatse"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Contains(t, mb.Messages[0], "g o a t s e")
|
assert.Contains(t, mb.Messages[0], "g o a t s e")
|
||||||
|
@ -48,7 +49,7 @@ func TestGoatseWithNickCommand(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
res := c.Message(makeMessage("!goatse seabass"))
|
res := c.message(makeMessage("!goatse seabass"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Contains(t, mb.Messages[0], "g o a t s e")
|
assert.Contains(t, mb.Messages[0], "g o a t s e")
|
||||||
|
@ -59,7 +60,7 @@ func TestSay(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
res := c.Message(makeMessage("say hello"))
|
res := c.message(makeMessage("say hello"))
|
||||||
assert.Len(t, mb.Messages, 0)
|
assert.Len(t, mb.Messages, 0)
|
||||||
assert.False(t, res)
|
assert.False(t, res)
|
||||||
}
|
}
|
||||||
|
@ -68,78 +69,16 @@ func TestSayCommand(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
res := c.Message(makeMessage("!say hello"))
|
res := c.message(makeMessage("!say hello"))
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
assert.Contains(t, mb.Messages[0], "hello")
|
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) {
|
func TestHelp(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
c.Help("channel", []string{})
|
c.help(&cli.CliPlugin{}, bot.Help, msg.Message{Channel: "channel"}, []string{})
|
||||||
assert.Len(t, mb.Messages, 1)
|
assert.Len(t, mb.Messages, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBotMessage(t *testing.T) {
|
|
||||||
mb := bot.NewMockBot()
|
|
||||||
c := New(mb)
|
|
||||||
assert.NotNil(t, c)
|
|
||||||
assert.False(t, c.BotMessage(makeMessage("test")))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvent(t *testing.T) {
|
|
||||||
mb := bot.NewMockBot()
|
|
||||||
c := New(mb)
|
|
||||||
assert.NotNil(t, c)
|
|
||||||
assert.False(t, c.Event("dummy", makeMessage("test")))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRegisterWeb(t *testing.T) {
|
|
||||||
mb := bot.NewMockBot()
|
|
||||||
c := New(mb)
|
|
||||||
assert.NotNil(t, c)
|
|
||||||
assert.Nil(t, c.RegisterWeb())
|
|
||||||
}
|
|
||||||
|
|
|
@ -16,32 +16,28 @@ type TellPlugin struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(b bot.Bot) *TellPlugin {
|
func New(b bot.Bot) *TellPlugin {
|
||||||
return &TellPlugin{b, make(map[string][]string)}
|
tp := &TellPlugin{b, make(map[string][]string)}
|
||||||
|
b.Register(tp, bot.Message, tp.message)
|
||||||
|
return tp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TellPlugin) Message(message msg.Message) bool {
|
func (t *TellPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
if strings.HasPrefix(strings.ToLower(message.Body), "tell") {
|
if strings.HasPrefix(strings.ToLower(message.Body), "tell") {
|
||||||
parts := strings.Split(message.Body, " ")
|
parts := strings.Split(message.Body, " ")
|
||||||
target := strings.ToLower(parts[1])
|
target := strings.ToLower(parts[1])
|
||||||
newMessage := strings.Join(parts[2:], " ")
|
newMessage := strings.Join(parts[2:], " ")
|
||||||
newMessage = fmt.Sprintf("Hey, %s. %s said: %s", target, message.User.Name, newMessage)
|
newMessage = fmt.Sprintf("Hey, %s. %s said: %s", target, message.User.Name, newMessage)
|
||||||
t.users[target] = append(t.users[target], newMessage)
|
t.users[target] = append(t.users[target], newMessage)
|
||||||
t.b.SendMessage(message.Channel, fmt.Sprintf("Okay. I'll tell %s.", target))
|
t.b.Send(c, bot.Message, message.Channel, fmt.Sprintf("Okay. I'll tell %s.", target))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
uname := strings.ToLower(message.User.Name)
|
uname := strings.ToLower(message.User.Name)
|
||||||
if msg, ok := t.users[uname]; ok && len(msg) > 0 {
|
if msg, ok := t.users[uname]; ok && len(msg) > 0 {
|
||||||
for _, m := range msg {
|
for _, m := range msg {
|
||||||
t.b.SendMessage(message.Channel, string(m))
|
t.b.Send(c, bot.Message, message.Channel, string(m))
|
||||||
}
|
}
|
||||||
t.users[uname] = []string{}
|
t.users[uname] = []string{}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TellPlugin) Event(kind string, message msg.Message) bool { return false }
|
|
||||||
func (t *TellPlugin) ReplyMessage(msg.Message, string) bool { return false }
|
|
||||||
func (t *TellPlugin) BotMessage(message msg.Message) bool { return false }
|
|
||||||
func (t *TellPlugin) Help(channel string, parts []string) {}
|
|
||||||
func (t *TellPlugin) RegisterWeb() *string { return nil }
|
|
||||||
|
|
|
@ -0,0 +1,185 @@
|
||||||
|
package tldr
|
||||||
|
|
||||||
|
//nltk stopwords list, from some point...
|
||||||
|
|
||||||
|
var THESE_ARE_NOT_THE_WORDS_YOU_ARE_LOOKING_FOR = []string{
|
||||||
|
"i",
|
||||||
|
"me",
|
||||||
|
"my",
|
||||||
|
"myself",
|
||||||
|
"we",
|
||||||
|
"our",
|
||||||
|
"ours",
|
||||||
|
"ourselves",
|
||||||
|
"you",
|
||||||
|
"you're",
|
||||||
|
"you've",
|
||||||
|
"you'll",
|
||||||
|
"you'd",
|
||||||
|
"your",
|
||||||
|
"yours",
|
||||||
|
"yourself",
|
||||||
|
"yourselves",
|
||||||
|
"he",
|
||||||
|
"him",
|
||||||
|
"his",
|
||||||
|
"himself",
|
||||||
|
"she",
|
||||||
|
"she's",
|
||||||
|
"her",
|
||||||
|
"hers",
|
||||||
|
"herself",
|
||||||
|
"it",
|
||||||
|
"it's",
|
||||||
|
"its",
|
||||||
|
"itself",
|
||||||
|
"they",
|
||||||
|
"them",
|
||||||
|
"their",
|
||||||
|
"theirs",
|
||||||
|
"themselves",
|
||||||
|
"what",
|
||||||
|
"which",
|
||||||
|
"who",
|
||||||
|
"whom",
|
||||||
|
"this",
|
||||||
|
"that",
|
||||||
|
"that'll",
|
||||||
|
"these",
|
||||||
|
"those",
|
||||||
|
"am",
|
||||||
|
"is",
|
||||||
|
"are",
|
||||||
|
"was",
|
||||||
|
"were",
|
||||||
|
"be",
|
||||||
|
"been",
|
||||||
|
"being",
|
||||||
|
"have",
|
||||||
|
"has",
|
||||||
|
"had",
|
||||||
|
"having",
|
||||||
|
"do",
|
||||||
|
"does",
|
||||||
|
"did",
|
||||||
|
"doing",
|
||||||
|
"a",
|
||||||
|
"an",
|
||||||
|
"the",
|
||||||
|
"and",
|
||||||
|
"but",
|
||||||
|
"if",
|
||||||
|
"or",
|
||||||
|
"because",
|
||||||
|
"as",
|
||||||
|
"until",
|
||||||
|
"while",
|
||||||
|
"of",
|
||||||
|
"at",
|
||||||
|
"by",
|
||||||
|
"for",
|
||||||
|
"with",
|
||||||
|
"about",
|
||||||
|
"against",
|
||||||
|
"between",
|
||||||
|
"into",
|
||||||
|
"through",
|
||||||
|
"during",
|
||||||
|
"before",
|
||||||
|
"after",
|
||||||
|
"above",
|
||||||
|
"below",
|
||||||
|
"to",
|
||||||
|
"from",
|
||||||
|
"up",
|
||||||
|
"down",
|
||||||
|
"in",
|
||||||
|
"out",
|
||||||
|
"on",
|
||||||
|
"off",
|
||||||
|
"over",
|
||||||
|
"under",
|
||||||
|
"again",
|
||||||
|
"further",
|
||||||
|
"then",
|
||||||
|
"once",
|
||||||
|
"here",
|
||||||
|
"there",
|
||||||
|
"when",
|
||||||
|
"where",
|
||||||
|
"why",
|
||||||
|
"how",
|
||||||
|
"all",
|
||||||
|
"any",
|
||||||
|
"both",
|
||||||
|
"each",
|
||||||
|
"few",
|
||||||
|
"more",
|
||||||
|
"most",
|
||||||
|
"other",
|
||||||
|
"some",
|
||||||
|
"such",
|
||||||
|
"no",
|
||||||
|
"nor",
|
||||||
|
"not",
|
||||||
|
"only",
|
||||||
|
"own",
|
||||||
|
"same",
|
||||||
|
"so",
|
||||||
|
"than",
|
||||||
|
"too",
|
||||||
|
"very",
|
||||||
|
"s",
|
||||||
|
"t",
|
||||||
|
"can",
|
||||||
|
"will",
|
||||||
|
"just",
|
||||||
|
"don",
|
||||||
|
"don't",
|
||||||
|
"should",
|
||||||
|
"should've",
|
||||||
|
"now",
|
||||||
|
"d",
|
||||||
|
"ll",
|
||||||
|
"m",
|
||||||
|
"o",
|
||||||
|
"re",
|
||||||
|
"ve",
|
||||||
|
"y",
|
||||||
|
"ain",
|
||||||
|
"aren",
|
||||||
|
"aren't",
|
||||||
|
"couldn",
|
||||||
|
"couldn't",
|
||||||
|
"didn",
|
||||||
|
"didn't",
|
||||||
|
"doesn",
|
||||||
|
"doesn't",
|
||||||
|
"hadn",
|
||||||
|
"hadn't",
|
||||||
|
"hasn",
|
||||||
|
"hasn't",
|
||||||
|
"haven",
|
||||||
|
"haven't",
|
||||||
|
"isn",
|
||||||
|
"isn't",
|
||||||
|
"ma",
|
||||||
|
"mightn",
|
||||||
|
"mightn't",
|
||||||
|
"mustn",
|
||||||
|
"mustn't",
|
||||||
|
"needn",
|
||||||
|
"needn't",
|
||||||
|
"shan",
|
||||||
|
"shan't",
|
||||||
|
"shouldn",
|
||||||
|
"shouldn't",
|
||||||
|
"wasn",
|
||||||
|
"wasn't",
|
||||||
|
"weren",
|
||||||
|
"weren't",
|
||||||
|
"won",
|
||||||
|
"won't",
|
||||||
|
"wouldn",
|
||||||
|
"wouldn't",
|
||||||
|
}
|
|
@ -0,0 +1,180 @@
|
||||||
|
package tldr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/velour/catbase/bot"
|
||||||
|
"github.com/velour/catbase/bot/msg"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"github.com/james-bowman/nlp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TLDRPlugin struct {
|
||||||
|
bot bot.Bot
|
||||||
|
history []history
|
||||||
|
index int
|
||||||
|
lastRequest time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type history struct {
|
||||||
|
timestamp time.Time
|
||||||
|
user string
|
||||||
|
body string
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(b bot.Bot) *TLDRPlugin {
|
||||||
|
plugin := &TLDRPlugin{
|
||||||
|
bot: b,
|
||||||
|
history: []history{},
|
||||||
|
index: 0,
|
||||||
|
lastRequest: time.Now().Add(-24 * time.Hour),
|
||||||
|
}
|
||||||
|
b.Register(plugin, bot.Message, plugin.message)
|
||||||
|
b.Register(plugin, bot.Help, plugin.help)
|
||||||
|
return plugin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TLDRPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
|
timeLimit := time.Duration(p.bot.Config().GetInt("TLDR.HourLimit", 1))
|
||||||
|
lowercaseMessage := strings.ToLower(message.Body)
|
||||||
|
if lowercaseMessage == "tl;dr" && p.lastRequest.After(time.Now().Add(-timeLimit*time.Hour)) {
|
||||||
|
p.bot.Send(c, bot.Message, message.Channel, "Slow down, cowboy. Read that tiny backlog.")
|
||||||
|
return true
|
||||||
|
} else if lowercaseMessage == "tl;dr" {
|
||||||
|
p.lastRequest = time.Now()
|
||||||
|
nTopics := p.bot.Config().GetInt("TLDR.Topics", 5)
|
||||||
|
|
||||||
|
stopWordSlice := p.bot.Config().GetArray("TLDR.StopWords", []string{})
|
||||||
|
if len(stopWordSlice) == 0 {
|
||||||
|
stopWordSlice = THESE_ARE_NOT_THE_WORDS_YOU_ARE_LOOKING_FOR
|
||||||
|
p.bot.Config().SetArray("TLDR.StopWords", stopWordSlice)
|
||||||
|
}
|
||||||
|
|
||||||
|
vectoriser := nlp.NewCountVectoriser(stopWordSlice...)
|
||||||
|
lda := nlp.NewLatentDirichletAllocation(nTopics)
|
||||||
|
pipeline := nlp.NewPipeline(vectoriser, lda)
|
||||||
|
docsOverTopics, err := pipeline.FitTransform(p.getTopics()...)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
bestScores := make([][]float64, nTopics)
|
||||||
|
bestDocs := make([][]history, nTopics)
|
||||||
|
|
||||||
|
supportingDocs := p.bot.Config().GetInt("TLDR.Support", 3)
|
||||||
|
for i := 0; i < nTopics; i++ {
|
||||||
|
bestScores[i] = make([]float64, supportingDocs)
|
||||||
|
bestDocs[i] = make([]history, supportingDocs)
|
||||||
|
}
|
||||||
|
|
||||||
|
dr, dc := docsOverTopics.Dims()
|
||||||
|
for topic := 0; topic < dr; topic++ {
|
||||||
|
minScore, minIndex := min(bestScores[topic])
|
||||||
|
|
||||||
|
for doc := 0; doc < dc; doc++ {
|
||||||
|
score := docsOverTopics.At(topic, doc)
|
||||||
|
if score > minScore {
|
||||||
|
bestScores[topic][minIndex] = score
|
||||||
|
bestDocs[topic][minIndex] = p.history[doc]
|
||||||
|
minScore, minIndex = min(bestScores[topic])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
topicsOverWords := lda.Components()
|
||||||
|
tr, tc := topicsOverWords.Dims()
|
||||||
|
|
||||||
|
vocab := make([]string, len(vectoriser.Vocabulary))
|
||||||
|
for k, v := range vectoriser.Vocabulary {
|
||||||
|
vocab[v] = k
|
||||||
|
}
|
||||||
|
|
||||||
|
response := "Here you go captain 'too good to read backlog':\n"
|
||||||
|
|
||||||
|
for topic := 0; topic < tr; topic++ {
|
||||||
|
bestScore := -1.
|
||||||
|
bestTopic := ""
|
||||||
|
for word := 0; word < tc; word++ {
|
||||||
|
score := topicsOverWords.At(topic, word)
|
||||||
|
if score > bestScore {
|
||||||
|
bestScore = score
|
||||||
|
bestTopic = vocab[word]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response += fmt.Sprintf("\n*Topic #%d: %s*\n", topic, bestTopic)
|
||||||
|
for i := range bestDocs[topic] {
|
||||||
|
response += fmt.Sprintf("<%s>%s\n", bestDocs[topic][i].user, bestDocs[topic][i].body)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
p.bot.Send(c, bot.Message, message.Channel, response)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
hist := history{
|
||||||
|
body: lowercaseMessage,
|
||||||
|
user: message.User.Name,
|
||||||
|
timestamp: time.Now(),
|
||||||
|
}
|
||||||
|
p.addHistory(hist)
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TLDRPlugin) addHistory(hist history) {
|
||||||
|
p.history = append(p.history, hist)
|
||||||
|
sz := len(p.history)
|
||||||
|
max := p.bot.Config().GetInt("TLDR.HistorySize", 1000)
|
||||||
|
keepHrs := time.Duration(p.bot.Config().GetInt("TLDR.KeepHours", 24))
|
||||||
|
// Clamp the size of the history
|
||||||
|
if sz > max {
|
||||||
|
p.history = p.history[len(p.history)-max:]
|
||||||
|
}
|
||||||
|
// Remove old entries
|
||||||
|
yesterday := time.Now().Add(-keepHrs * time.Hour)
|
||||||
|
begin := 0
|
||||||
|
for i, m := range p.history {
|
||||||
|
if !m.timestamp.Before(yesterday) {
|
||||||
|
begin = i - 1 // should keep this message
|
||||||
|
if begin < 0 {
|
||||||
|
begin = 0
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.history = p.history[begin:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TLDRPlugin) getTopics() []string {
|
||||||
|
hist := []string{}
|
||||||
|
for _, h := range p.history {
|
||||||
|
hist = append(hist, h.body)
|
||||||
|
}
|
||||||
|
return hist
|
||||||
|
}
|
||||||
|
|
||||||
|
// Help responds to help requests. Every plugin must implement a help function.
|
||||||
|
func (p *TLDRPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
|
p.bot.Send(c, bot.Message, message.Channel, "tl;dr")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func min(slice []float64) (float64, int) {
|
||||||
|
minVal := 1.
|
||||||
|
minIndex := -1
|
||||||
|
for index, val := range slice {
|
||||||
|
if val < minVal {
|
||||||
|
minVal = val
|
||||||
|
minIndex = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return minVal, minIndex
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
package tldr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/velour/catbase/bot"
|
||||||
|
"github.com/velour/catbase/bot/msg"
|
||||||
|
"github.com/velour/catbase/bot/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.Logger = log.Logger.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeMessageBy(payload, by string) (bot.Connector, bot.Kind, msg.Message) {
|
||||||
|
isCmd := strings.HasPrefix(payload, "!")
|
||||||
|
if isCmd {
|
||||||
|
payload = payload[1:]
|
||||||
|
}
|
||||||
|
return &cli.CliPlugin{}, bot.Message, msg.Message{
|
||||||
|
User: &user.User{Name: by},
|
||||||
|
Channel: "test",
|
||||||
|
Body: payload,
|
||||||
|
Command: isCmd,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) {
|
||||||
|
return makeMessageBy(payload, "tester")
|
||||||
|
}
|
||||||
|
|
||||||
|
func setup(t *testing.T) (*TLDRPlugin, *bot.MockBot) {
|
||||||
|
mb := bot.NewMockBot()
|
||||||
|
r := New(mb)
|
||||||
|
return r, mb
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test(t *testing.T) {
|
||||||
|
c, mb := setup(t)
|
||||||
|
res := c.message(makeMessage("The quick brown fox jumped over the lazy dog"))
|
||||||
|
res = c.message(makeMessage("The cow jumped over the moon"))
|
||||||
|
res = c.message(makeMessage("The little dog laughed to see such fun"))
|
||||||
|
res = c.message(makeMessage("tl;dr"))
|
||||||
|
assert.True(t, res)
|
||||||
|
assert.Len(t, mb.Messages, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDoubleUp(t *testing.T) {
|
||||||
|
c, mb := setup(t)
|
||||||
|
res := c.message(makeMessage("The quick brown fox jumped over the lazy dog"))
|
||||||
|
res = c.message(makeMessage("The cow jumped over the moon"))
|
||||||
|
res = c.message(makeMessage("The little dog laughed to see such fun"))
|
||||||
|
res = c.message(makeMessage("tl;dr"))
|
||||||
|
res = c.message(makeMessage("tl;dr"))
|
||||||
|
assert.True(t, res)
|
||||||
|
assert.Len(t, mb.Messages, 2)
|
||||||
|
assert.Contains(t, mb.Messages[1], "Slow down, cowboy.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddHistoryLimitsMessages(t *testing.T) {
|
||||||
|
c, _ := setup(t)
|
||||||
|
max := 1000
|
||||||
|
c.bot.Config().Set("TLDR.HistorySize", strconv.Itoa(max))
|
||||||
|
c.bot.Config().Set("TLDR.KeepHours", "24")
|
||||||
|
t0 := time.Now().Add(-24 * time.Hour)
|
||||||
|
for i := 0; i < max*2; i++ {
|
||||||
|
hist := history{
|
||||||
|
body: "test",
|
||||||
|
user: "tester",
|
||||||
|
timestamp: t0.Add(time.Duration(i) * time.Second),
|
||||||
|
}
|
||||||
|
c.addHistory(hist)
|
||||||
|
}
|
||||||
|
assert.Len(t, c.history, max)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddHistoryLimitsDays(t *testing.T) {
|
||||||
|
c, _ := setup(t)
|
||||||
|
hrs := 24
|
||||||
|
expected := 24
|
||||||
|
c.bot.Config().Set("TLDR.HistorySize", "100")
|
||||||
|
c.bot.Config().Set("TLDR.KeepHours", strconv.Itoa(hrs))
|
||||||
|
t0 := time.Now().Add(-time.Duration(hrs*2) * time.Hour)
|
||||||
|
for i := 0; i < 48; i++ {
|
||||||
|
hist := history{
|
||||||
|
body: "test",
|
||||||
|
user: "tester",
|
||||||
|
timestamp: t0.Add(time.Duration(i) * time.Hour),
|
||||||
|
}
|
||||||
|
c.addHistory(hist)
|
||||||
|
}
|
||||||
|
assert.Len(t, c.history, expected, "%d != %d", len(c.history), expected)
|
||||||
|
}
|
|
@ -1,30 +1,37 @@
|
||||||
package twitch
|
package twitch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
"github.com/velour/catbase/config"
|
"github.com/velour/catbase/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
isStreamingTplFallback = "{{.Name}} is streaming {{.Game}} at {{.URL}}"
|
||||||
|
notStreamingTplFallback = "{{.Name}} is not streaming"
|
||||||
|
stoppedStreamingTplFallback = "{{.Name}} just stopped streaming"
|
||||||
|
)
|
||||||
|
|
||||||
type TwitchPlugin struct {
|
type TwitchPlugin struct {
|
||||||
Bot bot.Bot
|
bot bot.Bot
|
||||||
config *config.Config
|
config *config.Config
|
||||||
twitchList map[string]*Twitcher
|
twitchList map[string]*Twitcher
|
||||||
}
|
}
|
||||||
|
|
||||||
type Twitcher struct {
|
type Twitcher struct {
|
||||||
name string
|
name string
|
||||||
game string
|
gameID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t Twitcher) URL() string {
|
func (t Twitcher) URL() string {
|
||||||
|
@ -51,39 +58,34 @@ type stream struct {
|
||||||
} `json:"pagination"`
|
} `json:"pagination"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(bot bot.Bot) *TwitchPlugin {
|
func New(b bot.Bot) *TwitchPlugin {
|
||||||
p := &TwitchPlugin{
|
p := &TwitchPlugin{
|
||||||
Bot: bot,
|
bot: b,
|
||||||
config: bot.Config(),
|
config: b.Config(),
|
||||||
twitchList: map[string]*Twitcher{},
|
twitchList: map[string]*Twitcher{},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, users := range p.config.Twitch.Users {
|
for _, ch := range p.config.GetArray("Twitch.Channels", []string{}) {
|
||||||
for _, twitcherName := range users {
|
for _, twitcherName := range p.config.GetArray("Twitch."+ch+".Users", []string{}) {
|
||||||
if _, ok := p.twitchList[twitcherName]; !ok {
|
if _, ok := p.twitchList[twitcherName]; !ok {
|
||||||
p.twitchList[twitcherName] = &Twitcher{
|
p.twitchList[twitcherName] = &Twitcher{
|
||||||
name: twitcherName,
|
name: twitcherName,
|
||||||
game: "",
|
gameID: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
go p.twitchLoop(b.DefaultConnector(), ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
for channel := range p.config.Twitch.Users {
|
b.Register(p, bot.Message, p.message)
|
||||||
go p.twitchLoop(channel)
|
b.Register(p, bot.Help, p.help)
|
||||||
}
|
p.registerWeb()
|
||||||
|
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *TwitchPlugin) BotMessage(message msg.Message) bool {
|
func (p *TwitchPlugin) registerWeb() {
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *TwitchPlugin) RegisterWeb() *string {
|
|
||||||
http.HandleFunc("/isstreaming/", p.serveStreaming)
|
http.HandleFunc("/isstreaming/", p.serveStreaming)
|
||||||
tmp := "/isstreaming"
|
|
||||||
return &tmp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *TwitchPlugin) serveStreaming(w http.ResponseWriter, r *http.Request) {
|
func (p *TwitchPlugin) serveStreaming(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -101,61 +103,68 @@ func (p *TwitchPlugin) serveStreaming(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
status := "NO."
|
status := "NO."
|
||||||
if twitcher.game != "" {
|
if twitcher.gameID != "" {
|
||||||
status = "YES."
|
status = "YES."
|
||||||
}
|
}
|
||||||
context := map[string]interface{}{"Name": twitcher.name, "Status": status}
|
context := map[string]interface{}{"Name": twitcher.name, "Status": status}
|
||||||
|
|
||||||
t, err := template.New("streaming").Parse(page)
|
t, err := template.New("streaming").Parse(page)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Could not parse template!", err)
|
log.Error().Err(err).Msg("Could not parse template!")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = t.Execute(w, context)
|
err = t.Execute(w, context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Could not execute template!", err)
|
log.Error().Err(err).Msg("Could not execute template!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *TwitchPlugin) Message(message msg.Message) bool {
|
func (p *TwitchPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
if strings.ToLower(message.Body) == "twitch status" {
|
body := strings.ToLower(message.Body)
|
||||||
|
if body == "twitch status" {
|
||||||
channel := message.Channel
|
channel := message.Channel
|
||||||
if _, ok := p.config.Twitch.Users[channel]; ok {
|
if users := p.config.GetArray("Twitch."+channel+".Users", []string{}); len(users) > 0 {
|
||||||
for _, twitcherName := range p.config.Twitch.Users[channel] {
|
for _, twitcherName := range users {
|
||||||
if _, ok = p.twitchList[twitcherName]; ok {
|
if _, ok := p.twitchList[twitcherName]; ok {
|
||||||
p.checkTwitch(channel, p.twitchList[twitcherName], true)
|
p.checkTwitch(c, channel, p.twitchList[twitcherName], true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
} else if body == "reset twitch" {
|
||||||
|
p.config.Set("twitch.istpl", isStreamingTplFallback)
|
||||||
|
p.config.Set("twitch.nottpl", notStreamingTplFallback)
|
||||||
|
p.config.Set("twitch.stoppedtpl", stoppedStreamingTplFallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *TwitchPlugin) Event(kind string, message msg.Message) bool {
|
func (p *TwitchPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
return false
|
msg := "You can set the templates for streams with\n"
|
||||||
|
msg += fmt.Sprintf("twitch.istpl (default: %s)\n", isStreamingTplFallback)
|
||||||
|
msg += fmt.Sprintf("twitch.nottpl (default: %s)\n", notStreamingTplFallback)
|
||||||
|
msg += fmt.Sprintf("twitch.stoppedtpl (default: %s)\n", stoppedStreamingTplFallback)
|
||||||
|
msg += "You can reset all messages with `!reset twitch`"
|
||||||
|
msg += "And you can ask who is streaming with `!twitch status`"
|
||||||
|
p.bot.Send(c, bot.Message, message.Channel, msg)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *TwitchPlugin) LoadData() {
|
func (p *TwitchPlugin) twitchLoop(c bot.Connector, channel string) {
|
||||||
|
frequency := p.config.GetInt("Twitch.Freq", 60)
|
||||||
|
if p.config.Get("twitch.clientid", "") == "" || p.config.Get("twitch.authorization", "") == "" {
|
||||||
|
log.Info().Msgf("Disabling twitch autochecking.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
}
|
log.Info().Msgf("Checking every %d seconds", frequency)
|
||||||
|
|
||||||
func (p *TwitchPlugin) Help(channel string, parts []string) {
|
|
||||||
msg := "There's no help for you here."
|
|
||||||
p.Bot.SendMessage(channel, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *TwitchPlugin) twitchLoop(channel string) {
|
|
||||||
frequency := p.config.Twitch.Freq
|
|
||||||
|
|
||||||
log.Println("Checking every ", frequency, " seconds")
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
time.Sleep(time.Duration(frequency) * time.Second)
|
time.Sleep(time.Duration(frequency) * time.Second)
|
||||||
|
|
||||||
for _, twitcherName := range p.config.Twitch.Users[channel] {
|
for _, twitcherName := range p.config.GetArray("Twitch."+channel+".Users", []string{}) {
|
||||||
p.checkTwitch(channel, p.twitchList[twitcherName], false)
|
p.checkTwitch(c, channel, p.twitchList[twitcherName], false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,14 +193,14 @@ func getRequest(url, clientID, authorization string) ([]byte, bool) {
|
||||||
return body, true
|
return body, true
|
||||||
|
|
||||||
errCase:
|
errCase:
|
||||||
log.Println(err)
|
log.Error().Err(err)
|
||||||
return []byte{}, false
|
return []byte{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *TwitchPlugin) checkTwitch(channel string, twitcher *Twitcher, alwaysPrintStatus bool) {
|
func (p *TwitchPlugin) checkTwitch(c bot.Connector, channel string, twitcher *Twitcher, alwaysPrintStatus bool) {
|
||||||
baseURL, err := url.Parse("https://api.twitch.tv/helix/streams")
|
baseURL, err := url.Parse("https://api.twitch.tv/helix/streams")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error parsing twitch stream URL")
|
log.Error().Msg("Error parsing twitch stream URL")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,8 +209,12 @@ func (p *TwitchPlugin) checkTwitch(channel string, twitcher *Twitcher, alwaysPri
|
||||||
|
|
||||||
baseURL.RawQuery = query.Encode()
|
baseURL.RawQuery = query.Encode()
|
||||||
|
|
||||||
cid := p.config.Twitch.ClientID
|
cid := p.config.Get("Twitch.ClientID", "")
|
||||||
auth := p.config.Twitch.Authorization
|
auth := p.config.Get("Twitch.Authorization", "")
|
||||||
|
if cid == auth && cid == "" {
|
||||||
|
log.Info().Msgf("Twitch plugin not enabled.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
body, ok := getRequest(baseURL.String(), cid, auth)
|
body, ok := getRequest(baseURL.String(), cid, auth)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -211,32 +224,75 @@ func (p *TwitchPlugin) checkTwitch(channel string, twitcher *Twitcher, alwaysPri
|
||||||
var s stream
|
var s stream
|
||||||
err = json.Unmarshal(body, &s)
|
err = json.Unmarshal(body, &s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Error().Err(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
games := s.Data
|
games := s.Data
|
||||||
game := ""
|
gameID, title := "", ""
|
||||||
if len(games) > 0 {
|
if len(games) > 0 {
|
||||||
game = games[0].Title
|
gameID = games[0].GameID
|
||||||
|
title = games[0].Title
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notStreamingTpl := p.config.Get("Twitch.NotTpl", notStreamingTplFallback)
|
||||||
|
isStreamingTpl := p.config.Get("Twitch.IsTpl", isStreamingTplFallback)
|
||||||
|
stoppedStreamingTpl := p.config.Get("Twitch.StoppedTpl", stoppedStreamingTplFallback)
|
||||||
|
buf := bytes.Buffer{}
|
||||||
|
|
||||||
|
info := struct {
|
||||||
|
Name string
|
||||||
|
Game string
|
||||||
|
URL string
|
||||||
|
}{
|
||||||
|
twitcher.name,
|
||||||
|
title,
|
||||||
|
twitcher.URL(),
|
||||||
|
}
|
||||||
|
|
||||||
if alwaysPrintStatus {
|
if alwaysPrintStatus {
|
||||||
if game == "" {
|
if gameID == "" {
|
||||||
p.Bot.SendMessage(channel, twitcher.name+" is not streaming.")
|
t, err := template.New("notStreaming").Parse(notStreamingTpl)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err)
|
||||||
|
p.bot.Send(c, bot.Message, channel, err)
|
||||||
|
t = template.Must(template.New("notStreaming").Parse(notStreamingTplFallback))
|
||||||
|
}
|
||||||
|
t.Execute(&buf, info)
|
||||||
|
p.bot.Send(c, bot.Message, channel, buf.String())
|
||||||
} else {
|
} else {
|
||||||
p.Bot.SendMessage(channel, twitcher.name+" is streaming "+game+" at "+twitcher.URL())
|
t, err := template.New("isStreaming").Parse(isStreamingTpl)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err)
|
||||||
|
p.bot.Send(c, bot.Message, channel, err)
|
||||||
|
t = template.Must(template.New("isStreaming").Parse(isStreamingTplFallback))
|
||||||
}
|
}
|
||||||
} else if game == "" {
|
t.Execute(&buf, info)
|
||||||
if twitcher.game != "" {
|
p.bot.Send(c, bot.Message, channel, buf.String())
|
||||||
p.Bot.SendMessage(channel, twitcher.name+" just stopped streaming.")
|
|
||||||
}
|
}
|
||||||
twitcher.game = ""
|
} else if gameID == "" {
|
||||||
|
if twitcher.gameID != "" {
|
||||||
|
t, err := template.New("stoppedStreaming").Parse(stoppedStreamingTpl)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err)
|
||||||
|
p.bot.Send(c, bot.Message, channel, err)
|
||||||
|
t = template.Must(template.New("stoppedStreaming").Parse(stoppedStreamingTplFallback))
|
||||||
|
}
|
||||||
|
t.Execute(&buf, info)
|
||||||
|
p.bot.Send(c, bot.Message, channel, buf.String())
|
||||||
|
}
|
||||||
|
twitcher.gameID = ""
|
||||||
} else {
|
} else {
|
||||||
if twitcher.game != game {
|
if twitcher.gameID != gameID {
|
||||||
p.Bot.SendMessage(channel, twitcher.name+" just started streaming "+game+" at "+twitcher.URL())
|
t, err := template.New("isStreaming").Parse(isStreamingTpl)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err)
|
||||||
|
p.bot.Send(c, bot.Message, channel, err)
|
||||||
|
t = template.Must(template.New("isStreaming").Parse(isStreamingTplFallback))
|
||||||
}
|
}
|
||||||
twitcher.game = game
|
t.Execute(&buf, info)
|
||||||
|
p.bot.Send(c, bot.Message, channel, buf.String())
|
||||||
|
}
|
||||||
|
twitcher.gameID = gameID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *TwitchPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
package twitch
|
package twitch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -12,12 +13,12 @@ import (
|
||||||
"github.com/velour/catbase/bot/user"
|
"github.com/velour/catbase/bot/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeMessage(payload string) msg.Message {
|
func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) {
|
||||||
isCmd := strings.HasPrefix(payload, "!")
|
isCmd := strings.HasPrefix(payload, "!")
|
||||||
if isCmd {
|
if isCmd {
|
||||||
payload = payload[1:]
|
payload = payload[1:]
|
||||||
}
|
}
|
||||||
return msg.Message{
|
return &cli.CliPlugin{}, bot.Message, msg.Message{
|
||||||
User: &user.User{Name: "tester"},
|
User: &user.User{Name: "tester"},
|
||||||
Channel: "test",
|
Channel: "test",
|
||||||
Body: payload,
|
Body: payload,
|
||||||
|
@ -28,12 +29,15 @@ func makeMessage(payload string) msg.Message {
|
||||||
func makeTwitchPlugin(t *testing.T) (*TwitchPlugin, *bot.MockBot) {
|
func makeTwitchPlugin(t *testing.T) (*TwitchPlugin, *bot.MockBot) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
c.config.Twitch.Users = map[string][]string{"test": []string{"drseabass"}}
|
mb.Config().Set("twitch.clientid", "fake")
|
||||||
|
mb.Config().Set("twitch.authorization", "fake")
|
||||||
|
c.config.SetArray("Twitch.Channels", []string{"test"})
|
||||||
|
c.config.SetArray("Twitch.test.Users", []string{"drseabass"})
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
|
|
||||||
c.twitchList["drseabass"] = &Twitcher{
|
c.twitchList["drseabass"] = &Twitcher{
|
||||||
name: "drseabass",
|
name: "drseabass",
|
||||||
game: "",
|
gameID: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
return c, mb
|
return c, mb
|
||||||
|
@ -41,6 +45,6 @@ func makeTwitchPlugin(t *testing.T) (*TwitchPlugin, *bot.MockBot) {
|
||||||
|
|
||||||
func TestTwitch(t *testing.T) {
|
func TestTwitch(t *testing.T) {
|
||||||
b, mb := makeTwitchPlugin(t)
|
b, mb := makeTwitchPlugin(t)
|
||||||
b.Message(makeMessage("!twitch status"))
|
b.message(makeMessage("!twitch status"))
|
||||||
assert.NotEmpty(t, mb.Messages)
|
assert.NotEmpty(t, mb.Messages)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,52 +17,43 @@ type YourPlugin struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewYourPlugin creates a new YourPlugin with the Plugin interface
|
// NewYourPlugin creates a new YourPlugin with the Plugin interface
|
||||||
func New(bot bot.Bot) *YourPlugin {
|
func New(b bot.Bot) *YourPlugin {
|
||||||
return &YourPlugin{
|
yp := &YourPlugin{
|
||||||
bot: bot,
|
bot: b,
|
||||||
config: bot.Config(),
|
config: b.Config(),
|
||||||
}
|
}
|
||||||
|
b.Register(yp, bot.Message, yp.message)
|
||||||
|
b.Register(yp, bot.Help, yp.help)
|
||||||
|
return yp
|
||||||
}
|
}
|
||||||
|
|
||||||
// Message responds to the bot hook on recieving messages.
|
// Message responds to the bot hook on recieving messages.
|
||||||
// This function returns true if the plugin responds in a meaningful way to the users message.
|
// 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.
|
// Otherwise, the function returns false and the bot continues execution of other plugins.
|
||||||
func (p *YourPlugin) Message(message msg.Message) bool {
|
func (p *YourPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
if len(message.Body) > p.config.Your.MaxLength {
|
maxLen := p.config.GetInt("your.maxlength", 140)
|
||||||
|
if len(message.Body) > maxLen {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
msg := message.Body
|
msg := message.Body
|
||||||
for _, replacement := range p.config.Your.Replacements {
|
for _, replacement := range p.config.GetArray("Your.Replacements", []string{}) {
|
||||||
if rand.Float64() < replacement.Frequency {
|
freq := p.config.GetFloat64("your.replacements."+replacement+".freq", 0.0)
|
||||||
r := strings.NewReplacer(replacement.This, replacement.That)
|
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)
|
msg = r.Replace(msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if msg != message.Body {
|
if msg != message.Body {
|
||||||
p.bot.SendMessage(message.Channel, msg)
|
p.bot.Send(c, bot.Message, message.Channel, msg)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Help responds to help requests. Every plugin must implement a help function.
|
// Help responds to help requests. Every plugin must implement a help function.
|
||||||
func (p *YourPlugin) Help(channel string, parts []string) {
|
func (p *YourPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
p.bot.SendMessage(channel, "Your corrects people's grammar.")
|
p.bot.Send(c, bot.Message, message.Channel, "Your corrects people's grammar.")
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty event handler because this plugin does not do anything on event recv
|
|
||||||
func (p *YourPlugin) Event(kind string, message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handler for bot's own messages
|
|
||||||
func (p *YourPlugin) BotMessage(message msg.Message) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register any web URLs desired
|
|
||||||
func (p *YourPlugin) RegisterWeb() *string {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *YourPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
package your
|
package your
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -10,15 +11,14 @@ import (
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
"github.com/velour/catbase/bot/user"
|
"github.com/velour/catbase/bot/user"
|
||||||
"github.com/velour/catbase/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeMessage(payload string) msg.Message {
|
func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) {
|
||||||
isCmd := strings.HasPrefix(payload, "!")
|
isCmd := strings.HasPrefix(payload, "!")
|
||||||
if isCmd {
|
if isCmd {
|
||||||
payload = payload[1:]
|
payload = payload[1:]
|
||||||
}
|
}
|
||||||
return msg.Message{
|
return &cli.CliPlugin{}, bot.Message, msg.Message{
|
||||||
User: &user.User{Name: "tester"},
|
User: &user.User{Name: "tester"},
|
||||||
Channel: "test",
|
Channel: "test",
|
||||||
Body: payload,
|
Body: payload,
|
||||||
|
@ -26,46 +26,41 @@ func makeMessage(payload string) msg.Message {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReplacement(t *testing.T) {
|
func setup(t *testing.T) (*YourPlugin, *bot.MockBot) {
|
||||||
mb := bot.NewMockBot()
|
mb := bot.NewMockBot()
|
||||||
c := New(mb)
|
c := New(mb)
|
||||||
assert.NotNil(t, c)
|
mb.DB().MustExec(`delete from config;`)
|
||||||
c.config.Your.MaxLength = 1000
|
return c, mb
|
||||||
c.config.Your.Replacements = []config.Replacement{
|
}
|
||||||
config.Replacement{
|
|
||||||
This: "fuck",
|
func TestReplacement(t *testing.T) {
|
||||||
That: "duck",
|
c, mb := setup(t)
|
||||||
Frequency: 1.0,
|
c.config.Set("Your.MaxLength", "1000")
|
||||||
},
|
c.config.SetArray("your.replacements", []string{"0"})
|
||||||
}
|
c.config.Set("your.replacements.0.freq", "1.0")
|
||||||
res := c.Message(makeMessage("fuck a duck"))
|
c.config.Set("your.replacements.0.this", "fuck")
|
||||||
assert.Len(t, mb.Messages, 1)
|
c.config.Set("your.replacements.0.that", "duck")
|
||||||
|
res := c.message(makeMessage("fuck a duck"))
|
||||||
assert.True(t, res)
|
assert.True(t, res)
|
||||||
|
assert.Len(t, mb.Messages, 1)
|
||||||
assert.Contains(t, mb.Messages[0], "duck a duck")
|
assert.Contains(t, mb.Messages[0], "duck a duck")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNoReplacement(t *testing.T) {
|
func TestNoReplacement(t *testing.T) {
|
||||||
mb := bot.NewMockBot()
|
c, mb := setup(t)
|
||||||
c := New(mb)
|
c.config.Set("Your.MaxLength", "1000")
|
||||||
assert.NotNil(t, c)
|
c.config.SetArray("your.replacements", []string{"0", "1", "2"})
|
||||||
c.config.Your.MaxLength = 1000
|
c.config.Set("your.replacements.0.freq", "1.0")
|
||||||
c.config.Your.Replacements = []config.Replacement{
|
c.config.Set("your.replacements.0.this", "nope")
|
||||||
config.Replacement{
|
c.config.Set("your.replacements.0.that", "duck")
|
||||||
This: "nope",
|
|
||||||
That: "duck",
|
c.config.Set("your.replacements.1.freq", "1.0")
|
||||||
Frequency: 1.0,
|
c.config.Set("your.replacements.1.this", "nope")
|
||||||
},
|
c.config.Set("your.replacements.1.that", "duck")
|
||||||
config.Replacement{
|
|
||||||
This: " fuck",
|
c.config.Set("your.replacements.2.freq", "1.0")
|
||||||
That: "duck",
|
c.config.Set("your.replacements.2.this", "Fuck")
|
||||||
Frequency: 1.0,
|
c.config.Set("your.replacements.2.that", "duck")
|
||||||
},
|
c.message(makeMessage("fuck a duck"))
|
||||||
config.Replacement{
|
|
||||||
This: "Fuck",
|
|
||||||
That: "duck",
|
|
||||||
Frequency: 1.0,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
c.Message(makeMessage("fuck a duck"))
|
|
||||||
assert.Len(t, mb.Messages, 0)
|
assert.Len(t, mb.Messages, 0)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,13 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"go/build"
|
"go/build"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
)
|
)
|
||||||
|
@ -26,14 +27,17 @@ type ZorkPlugin struct {
|
||||||
zorks map[string]io.WriteCloser
|
zorks map[string]io.WriteCloser
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(b bot.Bot) bot.Handler {
|
func New(b bot.Bot) bot.Plugin {
|
||||||
return &ZorkPlugin{
|
z := &ZorkPlugin{
|
||||||
bot: b,
|
bot: b,
|
||||||
zorks: make(map[string]io.WriteCloser),
|
zorks: make(map[string]io.WriteCloser),
|
||||||
}
|
}
|
||||||
|
b.Register(z, bot.Message, z.message)
|
||||||
|
b.Register(z, bot.Help, z.help)
|
||||||
|
return z
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ZorkPlugin) runZork(ch string) error {
|
func (p *ZorkPlugin) runZork(c bot.Connector, ch string) error {
|
||||||
const importString = "github.com/velour/catbase/plugins/zork"
|
const importString = "github.com/velour/catbase/plugins/zork"
|
||||||
pkg, err := build.Import(importString, "", build.FindOnly)
|
pkg, err := build.Import(importString, "", build.FindOnly)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -49,7 +53,7 @@ func (p *ZorkPlugin) runZork(ch string) error {
|
||||||
var w io.WriteCloser
|
var w io.WriteCloser
|
||||||
cmd.Stdin, w = io.Pipe()
|
cmd.Stdin, w = io.Pipe()
|
||||||
|
|
||||||
log.Printf("zork running %v\n", cmd)
|
log.Info().Msgf("zork running %v", cmd)
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
w.Close()
|
w.Close()
|
||||||
return err
|
return err
|
||||||
|
@ -75,25 +79,25 @@ func (p *ZorkPlugin) runZork(ch string) error {
|
||||||
m := strings.Replace(s.Text(), ">", "", -1)
|
m := strings.Replace(s.Text(), ">", "", -1)
|
||||||
m = strings.Replace(m, "\n", "\n>", -1)
|
m = strings.Replace(m, "\n", "\n>", -1)
|
||||||
m = ">" + m + "\n"
|
m = ">" + m + "\n"
|
||||||
p.bot.SendMessage(ch, m)
|
p.bot.Send(c, bot.Message, ch, m)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
go func() {
|
go func() {
|
||||||
if err := cmd.Wait(); err != nil {
|
if err := cmd.Wait(); err != nil {
|
||||||
log.Printf("zork exited: %v\n", err)
|
log.Error().Err(err).Msg("zork exited")
|
||||||
}
|
}
|
||||||
p.Lock()
|
p.Lock()
|
||||||
p.zorks[ch] = nil
|
p.zorks[ch] = nil
|
||||||
p.Unlock()
|
p.Unlock()
|
||||||
}()
|
}()
|
||||||
log.Printf("zork is running in %s\n", ch)
|
log.Info().Msgf("zork is running in %s\n", ch)
|
||||||
p.zorks[ch] = w
|
p.zorks[ch] = w
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ZorkPlugin) Message(message msg.Message) bool {
|
func (p *ZorkPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
m := strings.ToLower(message.Body)
|
m := strings.ToLower(message.Body)
|
||||||
log.Printf("got message [%s]\n", m)
|
log.Debug().Msgf("got message [%s]", m)
|
||||||
if ts := strings.Fields(m); len(ts) < 1 || ts[0] != "zork" {
|
if ts := strings.Fields(m); len(ts) < 1 || ts[0] != "zork" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -103,24 +107,17 @@ func (p *ZorkPlugin) Message(message msg.Message) bool {
|
||||||
p.Lock()
|
p.Lock()
|
||||||
defer p.Unlock()
|
defer p.Unlock()
|
||||||
if p.zorks[ch] == nil {
|
if p.zorks[ch] == nil {
|
||||||
if err := p.runZork(ch); err != nil {
|
if err := p.runZork(c, ch); err != nil {
|
||||||
p.bot.SendMessage(ch, "failed to run zork: "+err.Error())
|
p.bot.Send(c, bot.Message, ch, "failed to run zork: "+err.Error())
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Printf("zorking, [%s]\n", m)
|
log.Debug().Msgf("zorking, [%s]", m)
|
||||||
io.WriteString(p.zorks[ch], m+"\n")
|
io.WriteString(p.zorks[ch], m+"\n")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ZorkPlugin) Event(_ string, _ msg.Message) bool { return false }
|
func (p *ZorkPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
|
p.bot.Send(c, bot.Message, message.Channel, "Play zork using 'zork <zork command>'.")
|
||||||
func (p *ZorkPlugin) BotMessage(_ msg.Message) bool { return false }
|
return true
|
||||||
|
|
||||||
func (p *ZorkPlugin) Help(ch string, _ []string) {
|
|
||||||
p.bot.SendMessage(ch, "Play zork using 'zork <zork command>'.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ZorkPlugin) RegisterWeb() *string { return nil }
|
|
||||||
|
|
||||||
func (p *ZorkPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
|
||||||
|
|
|
@ -5,12 +5,14 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -20,9 +22,10 @@ var (
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||||
|
|
||||||
if *token == "" {
|
if *token == "" {
|
||||||
log.Printf("No token provided.")
|
log.Fatal().Msg("No token provided.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +38,7 @@ func main() {
|
||||||
func getFiles() map[string]string {
|
func getFiles() map[string]string {
|
||||||
files := fileResp{}
|
files := fileResp{}
|
||||||
|
|
||||||
log.Printf("Getting files")
|
log.Debug().Msgf("Getting files")
|
||||||
body := mkReq("https://slack.com/api/emoji.list",
|
body := mkReq("https://slack.com/api/emoji.list",
|
||||||
"token", *token,
|
"token", *token,
|
||||||
)
|
)
|
||||||
|
@ -43,9 +46,9 @@ func getFiles() map[string]string {
|
||||||
err := json.Unmarshal(body, &files)
|
err := json.Unmarshal(body, &files)
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
|
|
||||||
log.Printf("Ok: %v", files.Ok)
|
log.Debug().Msgf("Ok: %v", files.Ok)
|
||||||
if !files.Ok {
|
if !files.Ok {
|
||||||
log.Println(files)
|
log.Debug().Msgf("%+v", files)
|
||||||
}
|
}
|
||||||
|
|
||||||
return files.Files
|
return files.Files
|
||||||
|
@ -55,7 +58,7 @@ func downloadFile(n, f string) {
|
||||||
url := strings.Replace(f, "\\", "", -1) // because fuck slack
|
url := strings.Replace(f, "\\", "", -1) // because fuck slack
|
||||||
|
|
||||||
if strings.HasPrefix(url, "alias:") {
|
if strings.HasPrefix(url, "alias:") {
|
||||||
log.Printf("Skipping alias: %s", url)
|
log.Debug().Msgf("Skipping alias: %s", url)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +69,7 @@ func downloadFile(n, f string) {
|
||||||
|
|
||||||
fname := filepath.Join(*path, n+"."+ext)
|
fname := filepath.Join(*path, n+"."+ext)
|
||||||
|
|
||||||
log.Printf("Downloading from: %s", url)
|
log.Debug().Msgf("Downloading from: %s", url)
|
||||||
|
|
||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
req, err := http.NewRequest("GET", url, nil)
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
@ -82,18 +85,18 @@ func downloadFile(n, f string) {
|
||||||
defer out.Close()
|
defer out.Close()
|
||||||
io.Copy(out, resp.Body)
|
io.Copy(out, resp.Body)
|
||||||
|
|
||||||
log.Printf("Downloaded %s", f)
|
log.Debug().Msgf("Downloaded %s", f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkErr(err error) {
|
func checkErr(err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal().Err(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mkReq(path string, arg ...string) []byte {
|
func mkReq(path string, arg ...string) []byte {
|
||||||
if len(arg)%2 != 0 {
|
if len(arg)%2 != 0 {
|
||||||
log.Fatal("Bad request arg number.")
|
log.Fatal().Msg("Bad request arg number.")
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := url.Parse(path)
|
u, err := url.Parse(path)
|
||||||
|
|
|
@ -5,13 +5,15 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -24,6 +26,7 @@ var (
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||||
|
|
||||||
for {
|
for {
|
||||||
files, count := getFiles()
|
files, count := getFiles()
|
||||||
|
@ -40,7 +43,7 @@ func main() {
|
||||||
func getFiles() ([]slackFile, int) {
|
func getFiles() ([]slackFile, int) {
|
||||||
files := fileResp{}
|
files := fileResp{}
|
||||||
|
|
||||||
log.Printf("Getting files")
|
log.Debug().Msg("Getting files")
|
||||||
body := mkReq("https://slack.com/api/files.list",
|
body := mkReq("https://slack.com/api/files.list",
|
||||||
"token", *token,
|
"token", *token,
|
||||||
"count", strconv.Itoa(*limit),
|
"count", strconv.Itoa(*limit),
|
||||||
|
@ -50,9 +53,11 @@ func getFiles() ([]slackFile, int) {
|
||||||
err := json.Unmarshal(body, &files)
|
err := json.Unmarshal(body, &files)
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
|
|
||||||
log.Printf("Ok: %v, Count: %d", files.Ok, files.Paging.Count)
|
log.Info().
|
||||||
|
Int("count", files.Paging.Count).
|
||||||
|
Bool("ok", files.Ok)
|
||||||
if !files.Ok {
|
if !files.Ok {
|
||||||
log.Println(files)
|
log.Error().Interface("files", files)
|
||||||
}
|
}
|
||||||
|
|
||||||
return files.Files, files.Paging.Pages
|
return files.Files, files.Paging.Pages
|
||||||
|
@ -69,18 +74,24 @@ func deleteFile(f slackFile) {
|
||||||
|
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
if !del.Ok {
|
if !del.Ok {
|
||||||
log.Println(body)
|
log.Fatal().
|
||||||
log.Fatal("Couldn't delete " + f.ID)
|
Bytes("body", body).
|
||||||
|
Str("id", f.ID).
|
||||||
|
Msg("Couldn't delete")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Deleted %s", f.ID)
|
log.Info().
|
||||||
|
Str("id", f.ID).
|
||||||
|
Msg("Deleted")
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloadFile(f slackFile) {
|
func downloadFile(f slackFile) {
|
||||||
url := strings.Replace(f.URLPrivateDownload, "\\", "", -1) // because fuck slack
|
url := strings.Replace(f.URLPrivateDownload, "\\", "", -1) // because fuck slack
|
||||||
fname := filepath.Join(*path, f.ID+f.Name)
|
fname := filepath.Join(*path, f.ID+f.Name)
|
||||||
|
|
||||||
log.Printf("Downloading from: %s", url)
|
log.Info().
|
||||||
|
Str("url", url).
|
||||||
|
Msg("Downloading")
|
||||||
|
|
||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
req, err := http.NewRequest("GET", url, nil)
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
@ -96,18 +107,20 @@ func downloadFile(f slackFile) {
|
||||||
defer out.Close()
|
defer out.Close()
|
||||||
io.Copy(out, resp.Body)
|
io.Copy(out, resp.Body)
|
||||||
|
|
||||||
log.Printf("Downloaded %s", f.ID)
|
log.Info().
|
||||||
|
Str("id", f.ID).
|
||||||
|
Msg("Downloaded")
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkErr(err error) {
|
func checkErr(err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal().Err(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mkReq(path string, arg ...string) []byte {
|
func mkReq(path string, arg ...string) []byte {
|
||||||
if len(arg)%2 != 0 {
|
if len(arg)%2 != 0 {
|
||||||
log.Fatal("Bad request arg number.")
|
log.Fatal().Msg("Bad request arg number.")
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := url.Parse(path)
|
u, err := url.Parse(path)
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
// © 2013 the CatBase Authors under the WTFPL. See AUTHORS for the list of authors.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
const Version = "0.9"
|
|
Loading…
Reference in New Issue