bot: add ability to disable plugins per channel

admin: use 'enable/disable plugin <name>'
This commit is contained in:
Chris Sexton 2020-06-09 13:48:56 -04:00 committed by Chris Sexton
parent c68ca70964
commit 12ca34006f
5 changed files with 124 additions and 12 deletions

View File

@ -26,6 +26,9 @@ type bot struct {
plugins map[string]Plugin plugins map[string]Plugin
pluginOrdering []string pluginOrdering []string
// channel -> plugin
pluginBlacklist map[string]bool
// Users holds information about all of our friends // Users holds information about all of our friends
users []user.User users []user.User
// Represents the bot // Represents the bot
@ -77,21 +80,24 @@ func New(config *config.Config, connector Connector) Bot {
} }
bot := &bot{ bot := &bot{
config: config, config: config,
plugins: make(map[string]Plugin), plugins: make(map[string]Plugin),
pluginOrdering: make([]string, 0), pluginOrdering: make([]string, 0),
conn: connector, pluginBlacklist: make(map[string]bool),
users: users, conn: connector,
me: users[0], users: users,
logIn: logIn, me: users[0],
logOut: logOut, logIn: logIn,
httpEndPoints: make([]EndPoint, 0), logOut: logOut,
filters: make(map[string]func(string) string), httpEndPoints: make([]EndPoint, 0),
callbacks: make(CallbackMap), filters: make(map[string]func(string) string),
callbacks: make(CallbackMap),
} }
bot.migrateDB() bot.migrateDB()
bot.RefreshPluginBlacklist()
http.HandleFunc("/", bot.serveRoot) http.HandleFunc("/", bot.serveRoot)
connector.RegisterEvent(bot.Receive) connector.RegisterEvent(bot.Receive)
@ -127,6 +133,13 @@ func (b *bot) migrateDB() {
);`); err != nil { );`); err != nil {
log.Fatal().Err(err).Msgf("Initial DB migration create variables table") log.Fatal().Err(err).Msgf("Initial DB migration create variables table")
} }
if _, err := b.DB().Exec(`create table if not exists pluginBlacklist (
channel string,
name string,
primary key (channel, name)
);`); err != nil {
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
@ -265,3 +278,35 @@ func (b *bot) GetPassword() string {
func (b *bot) SetQuiet(status bool) { func (b *bot) SetQuiet(status bool) {
b.quiet = status b.quiet = status
} }
func (b *bot) RefreshPluginBlacklist() error {
blacklistItems := []struct {
Channel string
Name string
}{}
if err := b.DB().Select(&blacklistItems, `select channel, name from pluginBlacklist`); err != nil {
return fmt.Errorf("%w", err)
}
b.pluginBlacklist = make(map[string]bool)
for _, i := range blacklistItems {
b.pluginBlacklist[i.Channel+i.Name] = true
}
log.Debug().Interface("blacklist", b.pluginBlacklist).Msgf("Refreshed plugin blacklist")
return nil
}
func (b *bot) GetPluginNames() []string {
names := []string{}
for _, name := range b.pluginOrdering {
names = append(names, pluginNameStem(name))
}
return names
}
func (b *bot) onBlacklist(channel, plugin string) bool {
return b.pluginBlacklist[channel+plugin]
}
func pluginNameStem(name string) string {
return strings.Split(strings.TrimPrefix(name, "*"), ".")[0]
}

View File

@ -31,7 +31,11 @@ func (b *bot) Receive(conn Connector, kind Kind, msg msg.Message, args ...interf
goto RET goto RET
} }
log.Debug().Msgf("checking blacklist %v", b.pluginBlacklist)
for _, name := range b.pluginOrdering { for _, name := range b.pluginOrdering {
if b.onBlacklist(msg.Channel, pluginNameStem(name)) {
continue
}
if b.runCallback(conn, b.plugins[name], kind, msg, args...) { if b.runCallback(conn, b.plugins[name], kind, msg, args...) {
goto RET goto RET
} }
@ -70,7 +74,7 @@ func (b *bot) checkHelp(conn Connector, channel string, parts []string) {
// 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] name = pluginNameStem(name)
topics = fmt.Sprintf("%s, %s", topics, name) topics = fmt.Sprintf("%s, %s", topics, name)
} }
b.Send(conn, Message, channel, topics) b.Send(conn, Message, channel, topics)

View File

@ -4,6 +4,7 @@ package bot
import ( import (
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"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" "github.com/velour/catbase/config"
@ -69,6 +70,8 @@ type Bot interface {
GetWebNavigation() []EndPoint GetWebNavigation() []EndPoint
GetPassword() string GetPassword() string
SetQuiet(bool) SetQuiet(bool)
GetPluginNames() []string
RefreshPluginBlacklist() error
} }
// Connector represents a server connection to a chat service // Connector represents a server connection to a chat service

View File

@ -112,3 +112,6 @@ func NewMockBot() *MockBot {
http.DefaultServeMux = new(http.ServeMux) http.DefaultServeMux = new(http.ServeMux)
return &b return &b
} }
func (mb *MockBot) GetPluginNames() []string { return nil }
func (mb *MockBot) RefreshPluginBlacklist() error { return nil }

View File

@ -8,6 +8,7 @@ import (
"html/template" "html/template"
"net/http" "net/http"
"os" "os"
"regexp"
"strings" "strings"
"time" "time"
@ -51,6 +52,9 @@ var forbiddenKeys = map[string]bool{
"meme.memes": true, "meme.memes": true,
} }
var addBlacklist = regexp.MustCompile(`(?i)disable plugin (.*)`)
var rmBlacklist = regexp.MustCompile(`(?i)enable plugin (.*)`)
// 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.
@ -103,6 +107,30 @@ func (p *AdminPlugin) message(conn bot.Connector, k bot.Kind, message msg.Messag
return true return true
} }
if addBlacklist.MatchString(body) {
submatches := addBlacklist.FindStringSubmatch(message.Body)
plugin := submatches[1]
if err := p.addBlacklist(message.Channel, plugin); err != nil {
p.bot.Send(conn, bot.Message, message.Channel, fmt.Sprintf("I couldn't add that item: %s", err))
log.Error().Err(err).Msgf("error adding blacklist item")
return true
}
p.bot.Send(conn, bot.Message, message.Channel, fmt.Sprintf("%s disabled. Use `!enable plugin %s` to re-enable it.", plugin, plugin))
return true
}
if rmBlacklist.MatchString(body) {
submatches := rmBlacklist.FindStringSubmatch(message.Body)
plugin := submatches[1]
if err := p.rmBlacklist(message.Channel, plugin); err != nil {
p.bot.Send(conn, bot.Message, message.Channel, fmt.Sprintf("I couldn't remove that item: %s", err))
log.Error().Err(err).Msgf("error removing blacklist item")
return true
}
p.bot.Send(conn, bot.Message, message.Channel, fmt.Sprintf("%s enabled. Use `!disable plugin %s` to disable it.", plugin, plugin))
return true
}
if strings.ToLower(body) == "password" { if strings.ToLower(body) == "password" {
p.bot.Send(conn, bot.Message, message.Channel, p.bot.GetPassword()) p.bot.Send(conn, bot.Message, message.Channel, p.bot.GetPassword())
return true return true
@ -254,3 +282,32 @@ func (p *AdminPlugin) handleWebAPI(w http.ResponseWriter, r *http.Request) {
j, _ := json.Marshal(configEntries) j, _ := json.Marshal(configEntries)
fmt.Fprintf(w, "%s", j) fmt.Fprintf(w, "%s", j)
} }
func (p *AdminPlugin) addBlacklist(channel, plugin string) error {
if plugin == "admin" {
return fmt.Errorf("you cannot disable the admin plugin")
}
return p.modBlacklist(`insert or replace into pluginBlacklist values (?, ?)`, channel, plugin)
}
func (p *AdminPlugin) rmBlacklist(channel, plugin string) error {
return p.modBlacklist(`delete from pluginBlacklist where channel=? and name=?`, channel, plugin)
}
func (p *AdminPlugin) modBlacklist(query, channel, plugin string) error {
plugins := p.bot.GetPluginNames()
for _, pp := range plugins {
if pp == plugin {
if _, err := p.db.Exec(query, channel, plugin); err != nil {
return fmt.Errorf("%w", err)
}
err := p.bot.RefreshPluginBlacklist()
if err != nil {
return fmt.Errorf("%w", err)
}
return nil
}
}
err := fmt.Errorf("unknown plugin named '%s'", plugin)
return err
}