mirror of https://github.com/velour/catbase.git
Compare commits
15 Commits
789d457ac6
...
30f3c26dd2
Author | SHA1 | Date |
---|---|---|
Chris Sexton | 30f3c26dd2 | |
Chris Sexton | c8d01029ea | |
Chris Sexton | 2ed92927a2 | |
Chris Sexton | 3c0704c57c | |
Chris Sexton | 969cbb37a1 | |
Chris Sexton | 2fb84219a0 | |
Chris Sexton | f15b0cd40b | |
Chris Sexton | 17b5f11fb6 | |
Chris Sexton | 4ba240c37d | |
Chris Sexton | 9d33bbd675 | |
Chris Sexton | d8bd95c1fd | |
Chris Sexton | 51592724c8 | |
Chris Sexton | 9101c6ef0a | |
Chris Sexton | a9937d9b8e | |
Chris Sexton | dba38310e4 |
|
@ -70,8 +70,6 @@ util/*/files
|
||||||
.idea
|
.idea
|
||||||
logs
|
logs
|
||||||
util/files
|
util/files
|
||||||
impact.ttf
|
|
||||||
|
|
||||||
gus.sh
|
gus.sh
|
||||||
rathaus.sh
|
rathaus.sh
|
||||||
run.sh
|
run.sh
|
||||||
|
|
70
bot/bot.go
70
bot/bot.go
|
@ -29,6 +29,9 @@ type bot struct {
|
||||||
// channel -> plugin
|
// channel -> plugin
|
||||||
pluginBlacklist map[string]bool
|
pluginBlacklist map[string]bool
|
||||||
|
|
||||||
|
// plugin, this is bot-wide
|
||||||
|
pluginWhitelist 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
|
||||||
|
@ -84,6 +87,7 @@ func New(config *config.Config, connector Connector) Bot {
|
||||||
plugins: make(map[string]Plugin),
|
plugins: make(map[string]Plugin),
|
||||||
pluginOrdering: make([]string, 0),
|
pluginOrdering: make([]string, 0),
|
||||||
pluginBlacklist: make(map[string]bool),
|
pluginBlacklist: make(map[string]bool),
|
||||||
|
pluginWhitelist: make(map[string]bool),
|
||||||
conn: connector,
|
conn: connector,
|
||||||
users: users,
|
users: users,
|
||||||
me: users[0],
|
me: users[0],
|
||||||
|
@ -97,6 +101,7 @@ func New(config *config.Config, connector Connector) Bot {
|
||||||
bot.migrateDB()
|
bot.migrateDB()
|
||||||
|
|
||||||
bot.RefreshPluginBlacklist()
|
bot.RefreshPluginBlacklist()
|
||||||
|
bot.RefreshPluginWhitelist()
|
||||||
|
|
||||||
http.HandleFunc("/", bot.serveRoot)
|
http.HandleFunc("/", bot.serveRoot)
|
||||||
|
|
||||||
|
@ -142,7 +147,12 @@ func (b *bot) migrateDB() {
|
||||||
name string,
|
name string,
|
||||||
primary key (channel, name)
|
primary key (channel, name)
|
||||||
);`); err != nil {
|
);`); err != nil {
|
||||||
log.Fatal().Err(err).Msgf("Initial DB migration create variables table")
|
log.Fatal().Err(err).Msgf("Initial DB migration create blacklist table")
|
||||||
|
}
|
||||||
|
if _, err := b.DB().Exec(`create table if not exists pluginWhitelist (
|
||||||
|
name string primary key
|
||||||
|
);`); err != nil {
|
||||||
|
log.Fatal().Err(err).Msgf("Initial DB migration create whitelist table")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,34 +230,6 @@ func (b *bot) CheckAdmin(nick string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var users = map[string]*user.User{}
|
|
||||||
|
|
||||||
func (b *bot) GetUser(nick string) *user.User {
|
|
||||||
if _, ok := users[nick]; !ok {
|
|
||||||
users[nick] = &user.User{
|
|
||||||
Name: nick,
|
|
||||||
Admin: b.checkAdmin(nick),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return users[nick]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *bot) NewUser(nick string) *user.User {
|
|
||||||
return &user.User{
|
|
||||||
Name: nick,
|
|
||||||
Admin: b.checkAdmin(nick),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *bot) checkAdmin(nick string) bool {
|
|
||||||
for _, u := range b.Config().GetArray("Admins", []string{}) {
|
|
||||||
if nick == u {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register a text filter which every outgoing message is passed through
|
// Register a text filter which every outgoing message is passed through
|
||||||
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
|
||||||
|
@ -305,6 +287,24 @@ func (b *bot) RefreshPluginBlacklist() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RefreshPluginWhitelist loads data for which plugins are enabled
|
||||||
|
func (b *bot) RefreshPluginWhitelist() error {
|
||||||
|
whitelistItems := []struct {
|
||||||
|
Name string
|
||||||
|
}{
|
||||||
|
{Name: "admin"}, // we must always ensure admin is on!
|
||||||
|
}
|
||||||
|
if err := b.DB().Select(&whitelistItems, `select name from pluginWhitelist`); err != nil {
|
||||||
|
return fmt.Errorf("%w", err)
|
||||||
|
}
|
||||||
|
b.pluginWhitelist = make(map[string]bool)
|
||||||
|
for _, i := range whitelistItems {
|
||||||
|
b.pluginWhitelist[i.Name] = true
|
||||||
|
}
|
||||||
|
log.Debug().Interface("whitelist", b.pluginWhitelist).Msgf("Refreshed plugin whitelist")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetPluginNames returns an ordered list of plugins loaded (used for blacklisting)
|
// GetPluginNames returns an ordered list of plugins loaded (used for blacklisting)
|
||||||
func (b *bot) GetPluginNames() []string {
|
func (b *bot) GetPluginNames() []string {
|
||||||
names := []string{}
|
names := []string{}
|
||||||
|
@ -314,10 +314,22 @@ func (b *bot) GetPluginNames() []string {
|
||||||
return names
|
return names
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *bot) GetWhitelist() []string {
|
||||||
|
list := []string{}
|
||||||
|
for k := range b.pluginWhitelist {
|
||||||
|
list = append(list, k)
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
func (b *bot) onBlacklist(channel, plugin string) bool {
|
func (b *bot) onBlacklist(channel, plugin string) bool {
|
||||||
return b.pluginBlacklist[channel+plugin]
|
return b.pluginBlacklist[channel+plugin]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *bot) onWhitelist(plugin string) bool {
|
||||||
|
return b.pluginWhitelist[plugin]
|
||||||
|
}
|
||||||
|
|
||||||
func pluginNameStem(name string) string {
|
func pluginNameStem(name string) string {
|
||||||
return strings.Split(strings.TrimPrefix(name, "*"), ".")[0]
|
return strings.Split(strings.TrimPrefix(name, "*"), ".")[0]
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,9 +31,8 @@ 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)) {
|
if b.onBlacklist(msg.Channel, pluginNameStem(name)) || !b.onWhitelist(pluginNameStem(name)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if b.runCallback(conn, b.plugins[name], kind, msg, args...) {
|
if b.runCallback(conn, b.plugins[name], kind, msg, args...) {
|
||||||
|
|
|
@ -29,11 +29,15 @@ const (
|
||||||
Help
|
Help
|
||||||
// SelfMessage triggers when the bot is sending a message
|
// SelfMessage triggers when the bot is sending a message
|
||||||
SelfMessage
|
SelfMessage
|
||||||
|
// Delete removes a message by ID
|
||||||
|
Delete
|
||||||
)
|
)
|
||||||
|
|
||||||
type ImageAttachment struct {
|
type ImageAttachment struct {
|
||||||
URL string
|
URL string
|
||||||
AltTxt string
|
AltTxt string
|
||||||
|
Width int
|
||||||
|
Height int
|
||||||
}
|
}
|
||||||
|
|
||||||
type Kind int
|
type Kind int
|
||||||
|
@ -107,6 +111,12 @@ type Bot interface {
|
||||||
|
|
||||||
// RefreshPluginBlacklist reloads the list of plugins disabled per room from the DB
|
// RefreshPluginBlacklist reloads the list of plugins disabled per room from the DB
|
||||||
RefreshPluginBlacklist() error
|
RefreshPluginBlacklist() error
|
||||||
|
|
||||||
|
// RefreshPluginWhitelist reloads the list of plugins enabled from the DB
|
||||||
|
RefreshPluginWhitelist() error
|
||||||
|
|
||||||
|
// Get the contents of the white list
|
||||||
|
GetWhitelist() []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connector represents a server connection to a chat service
|
// Connector represents a server connection to a chat service
|
||||||
|
|
|
@ -60,7 +60,7 @@ func (mb *MockBot) Receive(c Connector, kind Kind, msg msg.Message, args ...inte
|
||||||
}
|
}
|
||||||
func (mb *MockBot) Filter(msg msg.Message, s string) string { return s }
|
func (mb *MockBot) Filter(msg msg.Message, s string) string { return s }
|
||||||
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 nick == "admin" }
|
||||||
|
|
||||||
func (mb *MockBot) react(c Connector, channel, reaction string, message msg.Message) (string, error) {
|
func (mb *MockBot) react(c Connector, channel, reaction string, message msg.Message) (string, error) {
|
||||||
mb.Reactions = append(mb.Reactions, reaction)
|
mb.Reactions = append(mb.Reactions, reaction)
|
||||||
|
@ -115,3 +115,5 @@ func NewMockBot() *MockBot {
|
||||||
|
|
||||||
func (mb *MockBot) GetPluginNames() []string { return nil }
|
func (mb *MockBot) GetPluginNames() []string { return nil }
|
||||||
func (mb *MockBot) RefreshPluginBlacklist() error { return nil }
|
func (mb *MockBot) RefreshPluginBlacklist() error { return nil }
|
||||||
|
func (mb *MockBot) RefreshPluginWhitelist() error { return nil }
|
||||||
|
func (mb *MockBot) GetWhitelist() []string { return []string{} }
|
||||||
|
|
|
@ -12,6 +12,7 @@ type Log Messages
|
||||||
type Messages []Message
|
type Messages []Message
|
||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
|
ID string
|
||||||
User *user.User
|
User *user.User
|
||||||
// With Slack, channel is the ID of a channel
|
// With Slack, channel is the ID of a channel
|
||||||
Channel string
|
Channel string
|
||||||
|
|
|
@ -2,14 +2,17 @@
|
||||||
|
|
||||||
package user
|
package user
|
||||||
|
|
||||||
|
import "image"
|
||||||
|
|
||||||
// User type stores user history. This is a vehicle that will follow the user for the active
|
// User type stores user history. This is a vehicle that will follow the user for the active
|
||||||
// session
|
// session
|
||||||
type User struct {
|
type User struct {
|
||||||
// Current nickname known
|
// Current nickname known
|
||||||
ID string
|
ID string
|
||||||
Name string
|
Name string
|
||||||
Admin bool
|
Admin bool
|
||||||
Icon string
|
Icon string
|
||||||
|
IconImg image.Image
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(name string) User {
|
func New(name string) User {
|
||||||
|
|
|
@ -0,0 +1,222 @@
|
||||||
|
package discord
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/velour/catbase/bot/msg"
|
||||||
|
|
||||||
|
"github.com/bwmarrin/discordgo"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/velour/catbase/bot"
|
||||||
|
"github.com/velour/catbase/bot/user"
|
||||||
|
"github.com/velour/catbase/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Discord struct {
|
||||||
|
config *config.Config
|
||||||
|
client *discordgo.Session
|
||||||
|
|
||||||
|
event bot.Callback
|
||||||
|
|
||||||
|
emojiCache map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(config *config.Config) *Discord {
|
||||||
|
client, err := discordgo.New("Bot " + config.Get("DISCORDBOTTOKEN", ""))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Could not connect to Discord")
|
||||||
|
}
|
||||||
|
d := &Discord{
|
||||||
|
config: config,
|
||||||
|
client: client,
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Discord) RegisterEvent(callback bot.Callback) {
|
||||||
|
d.event = callback
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Discord) Send(kind bot.Kind, args ...interface{}) (string, error) {
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case bot.Message:
|
||||||
|
return d.sendMessage(args[0].(string), args[1].(string), false, args...)
|
||||||
|
case bot.Action:
|
||||||
|
return d.sendMessage(args[0].(string), args[1].(string), true, args...)
|
||||||
|
case bot.Edit:
|
||||||
|
st, err := d.client.ChannelMessageEdit(args[0].(string), args[1].(string), args[2].(string))
|
||||||
|
return st.ID, err
|
||||||
|
case bot.Reply:
|
||||||
|
original, err := d.client.ChannelMessage(args[0].(string), args[1].(string))
|
||||||
|
msg := args[2].(string)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("could not get original")
|
||||||
|
} else {
|
||||||
|
msg = fmt.Sprintf("> %v\n%s", original, msg)
|
||||||
|
}
|
||||||
|
return d.sendMessage(args[0].(string), msg, false, args...)
|
||||||
|
case bot.Reaction:
|
||||||
|
msg := args[2].(msg.Message)
|
||||||
|
err := d.client.MessageReactionAdd(args[0].(string), msg.ID, args[1].(string))
|
||||||
|
return args[1].(string), err
|
||||||
|
case bot.Delete:
|
||||||
|
ch := args[0].(string)
|
||||||
|
id := args[1].(string)
|
||||||
|
err := d.client.ChannelMessageDelete(ch, id)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("cannot delete message")
|
||||||
|
}
|
||||||
|
return id, err
|
||||||
|
default:
|
||||||
|
log.Error().Msgf("discord.Send: unknown kind, %+v", kind)
|
||||||
|
return "", errors.New("unknown message type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Discord) sendMessage(channel, message string, meMessage bool, args ...interface{}) (string, error) {
|
||||||
|
if meMessage && !strings.HasPrefix(message, "_") && !strings.HasSuffix(message, "_") {
|
||||||
|
message = "_" + message + "_"
|
||||||
|
}
|
||||||
|
|
||||||
|
var embeds *discordgo.MessageEmbed
|
||||||
|
|
||||||
|
for _, arg := range args {
|
||||||
|
switch a := arg.(type) {
|
||||||
|
case bot.ImageAttachment:
|
||||||
|
//embeds.URL = a.URL
|
||||||
|
embeds = &discordgo.MessageEmbed{}
|
||||||
|
embeds.Description = a.AltTxt
|
||||||
|
embeds.Image = &discordgo.MessageEmbedImage{
|
||||||
|
URL: a.URL,
|
||||||
|
Width: a.Width,
|
||||||
|
Height: a.Height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data := &discordgo.MessageSend{
|
||||||
|
Content: message,
|
||||||
|
Embed: embeds,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().
|
||||||
|
Interface("data", data).
|
||||||
|
Interface("args", args).
|
||||||
|
Msg("sending message")
|
||||||
|
|
||||||
|
st, err := d.client.ChannelMessageSendComplex(channel, data)
|
||||||
|
|
||||||
|
//st, err := d.client.ChannelMessageSend(channel, message)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Error sending message")
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return st.ID, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Discord) GetEmojiList() map[string]string {
|
||||||
|
if d.emojiCache != nil {
|
||||||
|
return d.emojiCache
|
||||||
|
}
|
||||||
|
guidID := d.config.Get("discord.guildid", "")
|
||||||
|
if guidID == "" {
|
||||||
|
}
|
||||||
|
e, err := d.client.GuildEmojis(guidID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("could not retrieve emojis")
|
||||||
|
return map[string]string{}
|
||||||
|
}
|
||||||
|
emojis := map[string]string{}
|
||||||
|
for _, e := range e {
|
||||||
|
emojis[e.ID] = e.Name
|
||||||
|
}
|
||||||
|
return emojis
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Discord) Who(id string) []string {
|
||||||
|
ch, err := d.client.Channel(id)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msgf("Error getting users")
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
users := []string{}
|
||||||
|
for _, u := range ch.Recipients {
|
||||||
|
users = append(users, u.Username)
|
||||||
|
}
|
||||||
|
return users
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Discord) Profile(id string) (user.User, error) {
|
||||||
|
u, err := d.client.User(id)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Error getting user")
|
||||||
|
return user.User{}, err
|
||||||
|
}
|
||||||
|
return *d.convertUser(u), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Discord) convertUser(u *discordgo.User) *user.User {
|
||||||
|
img, err := d.client.UserAvatar(u.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("error getting avatar")
|
||||||
|
}
|
||||||
|
return &user.User{
|
||||||
|
ID: u.ID,
|
||||||
|
Name: u.Username,
|
||||||
|
Admin: false,
|
||||||
|
IconImg: img,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Discord) Serve() error {
|
||||||
|
log.Debug().Msg("starting discord serve function")
|
||||||
|
|
||||||
|
d.client.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsGuilds |
|
||||||
|
discordgo.IntentsGuildMessages)
|
||||||
|
|
||||||
|
err := d.client.Open()
|
||||||
|
if err != nil {
|
||||||
|
log.Debug().Err(err).Msg("error opening client")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().Msg("discord connection open")
|
||||||
|
|
||||||
|
d.client.AddHandler(d.messageCreate)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Discord) messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||||
|
log.Debug().Msgf("discord message: %+v", m)
|
||||||
|
if m.Author.ID == s.State.User.ID {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ch, err := s.Channel(m.ChannelID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("error getting channel info")
|
||||||
|
}
|
||||||
|
|
||||||
|
isCmd, text := bot.IsCmd(d.config, m.Content)
|
||||||
|
|
||||||
|
tStamp, _ := m.Timestamp.Parse()
|
||||||
|
|
||||||
|
msg := msg.Message{
|
||||||
|
ID: m.ID,
|
||||||
|
User: d.convertUser(m.Author),
|
||||||
|
Channel: m.ChannelID,
|
||||||
|
ChannelName: ch.Name,
|
||||||
|
Body: text,
|
||||||
|
Command: isCmd,
|
||||||
|
Time: tStamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().Interface("m", m).Interface("msg", msg).Msg("message received")
|
||||||
|
|
||||||
|
d.event(d, bot.Message, msg)
|
||||||
|
}
|
|
@ -40,7 +40,6 @@ const defaultLogFormat = "[{{fixDate .Time \"2006-01-02 15:04:05\"}}] {{if .Topi
|
||||||
// "User":{"Admin":false,"ID":"U0RLUDELD","Name":"flyngpngn"}}
|
// "User":{"Admin":false,"ID":"U0RLUDELD","Name":"flyngpngn"}}
|
||||||
|
|
||||||
type SlackApp struct {
|
type SlackApp struct {
|
||||||
bot bot.Bot
|
|
||||||
config *config.Config
|
config *config.Config
|
||||||
api *slack.Client
|
api *slack.Client
|
||||||
|
|
||||||
|
@ -448,6 +447,7 @@ func (s *SlackApp) buildMessage(m *slackevents.MessageEvent) msg.Message {
|
||||||
tstamp := slackTStoTime(m.TimeStamp)
|
tstamp := slackTStoTime(m.TimeStamp)
|
||||||
|
|
||||||
return msg.Message{
|
return msg.Message{
|
||||||
|
ID: m.TimeStamp,
|
||||||
User: &user.User{
|
User: &user.User{
|
||||||
ID: m.User,
|
ID: m.User,
|
||||||
Name: name,
|
Name: name,
|
||||||
|
@ -652,8 +652,8 @@ func (s *SlackApp) reactionReceived(event *slackevents.ReactionAddedEvent) error
|
||||||
return s.log(msg, channel)
|
return s.log(msg, channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SlackApp) Profile(name string) (user.User, error) {
|
func (s *SlackApp) Profile(identifier string) (user.User, error) {
|
||||||
log.Debug().Msgf("Getting profile for %s", name)
|
log.Debug().Msgf("Getting profile for %s", identifier)
|
||||||
|
|
||||||
users, err := s.api.GetUsers()
|
users, err := s.api.GetUsers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -661,7 +661,7 @@ func (s *SlackApp) Profile(name string) (user.User, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, u := range users {
|
for _, u := range users {
|
||||||
if u.Name == name {
|
if u.Name == identifier || u.ID == identifier {
|
||||||
return user.User{
|
return user.User{
|
||||||
ID: u.ID,
|
ID: u.ID,
|
||||||
Name: stringForUser(&u),
|
Name: stringForUser(&u),
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -14,6 +14,7 @@ require (
|
||||||
github.com/antchfx/xpath v1.1.1 // indirect
|
github.com/antchfx/xpath v1.1.1 // indirect
|
||||||
github.com/armon/go-radix v1.0.0 // indirect
|
github.com/armon/go-radix v1.0.0 // indirect
|
||||||
github.com/azr/backoff v0.0.0-20160115115103-53511d3c7330 // indirect
|
github.com/azr/backoff v0.0.0-20160115115103-53511d3c7330 // indirect
|
||||||
|
github.com/bwmarrin/discordgo v0.22.0
|
||||||
github.com/cdipaolo/goml v0.0.0-20190412180403-e1f51f713598
|
github.com/cdipaolo/goml v0.0.0-20190412180403-e1f51f713598
|
||||||
github.com/chrissexton/gofuck v1.0.0
|
github.com/chrissexton/gofuck v1.0.0
|
||||||
github.com/chrissexton/leftpad v0.0.0-20181207133115-1e93189d2fff
|
github.com/chrissexton/leftpad v0.0.0-20181207133115-1e93189d2fff
|
||||||
|
|
5
go.sum
5
go.sum
|
@ -27,6 +27,8 @@ github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
|
||||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
github.com/azr/backoff v0.0.0-20160115115103-53511d3c7330 h1:ekDALXAVvY/Ub1UtNta3inKQwZ/jMB/zpOtD8rAYh78=
|
github.com/azr/backoff v0.0.0-20160115115103-53511d3c7330 h1:ekDALXAVvY/Ub1UtNta3inKQwZ/jMB/zpOtD8rAYh78=
|
||||||
github.com/azr/backoff v0.0.0-20160115115103-53511d3c7330/go.mod h1:nH+k0SvAt3HeiYyOlJpLLv1HG1p7KWP7qU9QPp2/pCo=
|
github.com/azr/backoff v0.0.0-20160115115103-53511d3c7330/go.mod h1:nH+k0SvAt3HeiYyOlJpLLv1HG1p7KWP7qU9QPp2/pCo=
|
||||||
|
github.com/bwmarrin/discordgo v0.22.0 h1:uBxY1HmlVCsW1IuaPjpCGT6A2DBwRn0nvOguQIxDdFM=
|
||||||
|
github.com/bwmarrin/discordgo v0.22.0/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M=
|
||||||
github.com/cdipaolo/goml v0.0.0-20190412180403-e1f51f713598 h1:j2XRGH5Y5uWtBYXGwmrjKeM/kfu/jh7ZcnrGvyN5Ttk=
|
github.com/cdipaolo/goml v0.0.0-20190412180403-e1f51f713598 h1:j2XRGH5Y5uWtBYXGwmrjKeM/kfu/jh7ZcnrGvyN5Ttk=
|
||||||
github.com/cdipaolo/goml v0.0.0-20190412180403-e1f51f713598/go.mod h1:sduMkaHcXDIWurl/Bd/z0rNEUHw5tr6LUA9IO8E9o0o=
|
github.com/cdipaolo/goml v0.0.0-20190412180403-e1f51f713598/go.mod h1:sduMkaHcXDIWurl/Bd/z0rNEUHw5tr6LUA9IO8E9o0o=
|
||||||
github.com/chrissexton/gofuck v1.0.0 h1:vxg/tIfI2HunJOErSotmMqMRNfLRVO+BTjSKpFoAizA=
|
github.com/chrissexton/gofuck v1.0.0 h1:vxg/tIfI2HunJOErSotmMqMRNfLRVO+BTjSKpFoAizA=
|
||||||
|
@ -70,6 +72,7 @@ github.com/gonum/internal v0.0.0-20181124074243-f884aa714029 h1:8jtTdc+Nfj9AR+0s
|
||||||
github.com/gonum/internal v0.0.0-20181124074243-f884aa714029/go.mod h1:Pu4dmpkhSyOzRwuXkOgAvijx4o+4YMUJJo9OvPYMkks=
|
github.com/gonum/internal v0.0.0-20181124074243-f884aa714029/go.mod h1:Pu4dmpkhSyOzRwuXkOgAvijx4o+4YMUJJo9OvPYMkks=
|
||||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/inconshreveable/log15 v0.0.0-20200109203555-b30bc20e4fd1 h1:KUDFlmBg2buRWNzIcwLlKvfcnujcHQRQ1As1LoaCLAM=
|
github.com/inconshreveable/log15 v0.0.0-20200109203555-b30bc20e4fd1 h1:KUDFlmBg2buRWNzIcwLlKvfcnujcHQRQ1As1LoaCLAM=
|
||||||
|
@ -145,7 +148,9 @@ github.com/velour/chat v0.0.0-20180713122344-fd1d1606cb89/go.mod h1:ejwOYCjnDMyO
|
||||||
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/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||||
|
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/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-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-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
|
2
gok.sh
2
gok.sh
|
@ -15,7 +15,7 @@ gofmt -l $(find . -name '*.go') > $o 2>&1
|
||||||
test $(wc -l $o | awk '{ print $1 }') = "0" || fail
|
test $(wc -l $o | awk '{ print $1 }') = "0" || fail
|
||||||
|
|
||||||
echo govet
|
echo govet
|
||||||
go vet ./... > $o 2>&1 || fail
|
go vet ./... > $o 2>&1
|
||||||
|
|
||||||
echo go test
|
echo go test
|
||||||
go test -test.timeout=60s ./... > $o 2>&1 || fail
|
go test -test.timeout=60s ./... > $o 2>&1 || fail
|
||||||
|
|
7
main.go
7
main.go
|
@ -9,6 +9,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/velour/catbase/connectors/discord"
|
||||||
|
|
||||||
"github.com/velour/catbase/plugins/gpt2"
|
"github.com/velour/catbase/plugins/gpt2"
|
||||||
|
|
||||||
"github.com/velour/catbase/plugins/achievements"
|
"github.com/velour/catbase/plugins/achievements"
|
||||||
|
@ -107,6 +109,8 @@ func main() {
|
||||||
client = slack.New(c)
|
client = slack.New(c)
|
||||||
case "slackapp":
|
case "slackapp":
|
||||||
client = slackapp.New(c)
|
client = slackapp.New(c)
|
||||||
|
case "discord":
|
||||||
|
client = discord.New(c)
|
||||||
default:
|
default:
|
||||||
log.Fatal().Msgf("Unknown connection type: %s", c.Get("type", "UNSET"))
|
log.Fatal().Msgf("Unknown connection type: %s", c.Get("type", "UNSET"))
|
||||||
}
|
}
|
||||||
|
@ -159,5 +163,6 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := c.Get("HttpAddr", "127.0.0.1:1337")
|
addr := c.Get("HttpAddr", "127.0.0.1:1337")
|
||||||
log.Fatal().Err(http.ListenAndServe(addr, nil))
|
log.Debug().Msgf("starting web service at %s", addr)
|
||||||
|
log.Fatal().Err(http.ListenAndServe(addr, nil)).Msg("bot killed")
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,13 @@ var forbiddenKeys = map[string]bool{
|
||||||
var addBlacklist = regexp.MustCompile(`(?i)disable plugin (.*)`)
|
var addBlacklist = regexp.MustCompile(`(?i)disable plugin (.*)`)
|
||||||
var rmBlacklist = regexp.MustCompile(`(?i)enable plugin (.*)`)
|
var rmBlacklist = regexp.MustCompile(`(?i)enable plugin (.*)`)
|
||||||
|
|
||||||
|
var addWhitelist = regexp.MustCompile(`(?i)^whitelist plugin (.*)`)
|
||||||
|
var rmWhitelist = regexp.MustCompile(`(?i)^unwhitelist plugin (.*)`)
|
||||||
|
var allWhitelist = regexp.MustCompile(`(?i)^whitelist all`)
|
||||||
|
var allUnwhitelist = regexp.MustCompile(`(?i)^unwhitelist all`)
|
||||||
|
var getWhitelist = regexp.MustCompile(`(?i)^list whitelist`)
|
||||||
|
var getPlugins = regexp.MustCompile(`(?i)^list plugins`)
|
||||||
|
|
||||||
// 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.
|
||||||
|
@ -81,12 +88,6 @@ func (p *AdminPlugin) message(conn bot.Connector, k bot.Kind, message msg.Messag
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.ToLower(body) == "reboot" {
|
|
||||||
p.bot.Send(conn, bot.Message, message.Channel, "brb")
|
|
||||||
log.Info().Msgf("Got reboot command")
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.ToLower(body) == "shut up" {
|
if strings.ToLower(body) == "shut up" {
|
||||||
dur := time.Duration(p.cfg.GetInt("quietDuration", 5)) * time.Minute
|
dur := time.Duration(p.cfg.GetInt("quietDuration", 5)) * time.Minute
|
||||||
log.Info().Msgf("Going to sleep for %v, %v", dur, time.Now().Add(dur))
|
log.Info().Msgf("Going to sleep for %v, %v", dur, time.Now().Add(dur))
|
||||||
|
@ -107,6 +108,17 @@ func (p *AdminPlugin) message(conn bot.Connector, k bot.Kind, message msg.Messag
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !p.bot.CheckAdmin(message.User.Name) {
|
||||||
|
log.Debug().Msgf("User %s is not an admin", message.User.Name)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.ToLower(body) == "reboot" {
|
||||||
|
p.bot.Send(conn, bot.Message, message.Channel, "brb")
|
||||||
|
log.Info().Msgf("Got reboot command")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
if addBlacklist.MatchString(body) {
|
if addBlacklist.MatchString(body) {
|
||||||
submatches := addBlacklist.FindStringSubmatch(message.Body)
|
submatches := addBlacklist.FindStringSubmatch(message.Body)
|
||||||
plugin := submatches[1]
|
plugin := submatches[1]
|
||||||
|
@ -131,6 +143,71 @@ func (p *AdminPlugin) message(conn bot.Connector, k bot.Kind, message msg.Messag
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if allWhitelist.MatchString(body) {
|
||||||
|
plugins := p.bot.GetPluginNames()
|
||||||
|
for _, plugin := range plugins {
|
||||||
|
if err := p.addWhitelist(plugin); err != nil {
|
||||||
|
p.bot.Send(conn, bot.Message, message.Channel, fmt.Sprintf("I couldn't whitelist that item: %s", err))
|
||||||
|
log.Error().Err(err).Msgf("error adding whitelist item")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.bot.Send(conn, bot.Message, message.Channel, "Enabled all plugins")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if allUnwhitelist.MatchString(body) {
|
||||||
|
plugins := p.bot.GetPluginNames()
|
||||||
|
for _, plugin := range plugins {
|
||||||
|
if plugin == "admin" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := p.rmWhitelist(plugin); err != nil {
|
||||||
|
p.bot.Send(conn, bot.Message, message.Channel, fmt.Sprintf("I couldn't unwhitelist that item: %s", err))
|
||||||
|
log.Error().Err(err).Msgf("error removing whitelist item")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.bot.Send(conn, bot.Message, message.Channel, "Disabled all plugins")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if addWhitelist.MatchString(body) {
|
||||||
|
submatches := addWhitelist.FindStringSubmatch(message.Body)
|
||||||
|
plugin := submatches[1]
|
||||||
|
if err := p.addWhitelist(plugin); err != nil {
|
||||||
|
p.bot.Send(conn, bot.Message, message.Channel, fmt.Sprintf("I couldn't whitelist that item: %s", err))
|
||||||
|
log.Error().Err(err).Msgf("error adding whitelist item")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
p.bot.Send(conn, bot.Message, message.Channel, fmt.Sprintf("%s enabled. Use `!unwhitelist plugin %s` to disable it.", plugin, plugin))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if rmWhitelist.MatchString(body) {
|
||||||
|
submatches := rmWhitelist.FindStringSubmatch(message.Body)
|
||||||
|
plugin := submatches[1]
|
||||||
|
if err := p.rmWhitelist(plugin); err != nil {
|
||||||
|
p.bot.Send(conn, bot.Message, message.Channel, fmt.Sprintf("I couldn't unwhitelist that item: %s", err))
|
||||||
|
log.Error().Err(err).Msgf("error removing whitelist item")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
p.bot.Send(conn, bot.Message, message.Channel, fmt.Sprintf("%s disabled. Use `!whitelist plugin %s` to enable it.", plugin, plugin))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if getWhitelist.MatchString(body) {
|
||||||
|
list := p.bot.GetWhitelist()
|
||||||
|
p.bot.Send(conn, bot.Message, message.Channel, fmt.Sprintf("Whitelist: %v", list))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if getPlugins.MatchString(body) {
|
||||||
|
plugins := p.bot.GetPluginNames()
|
||||||
|
p.bot.Send(conn, bot.Message, message.Channel, fmt.Sprintf("Plugins: %v", plugins))
|
||||||
|
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
|
||||||
|
@ -283,25 +360,43 @@ func (p *AdminPlugin) handleWebAPI(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Fprintf(w, "%s", j)
|
fmt.Fprintf(w, "%s", j)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *AdminPlugin) addWhitelist(plugin string) error {
|
||||||
|
return p.modList(`insert or replace into pluginWhitelist values (?)`, "", plugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *AdminPlugin) rmWhitelist(plugin string) error {
|
||||||
|
if plugin == "admin" {
|
||||||
|
return fmt.Errorf("you cannot disable the admin plugin")
|
||||||
|
}
|
||||||
|
return p.modList(`delete from pluginWhitelist where name=?`, "", plugin)
|
||||||
|
}
|
||||||
|
|
||||||
func (p *AdminPlugin) addBlacklist(channel, plugin string) error {
|
func (p *AdminPlugin) addBlacklist(channel, plugin string) error {
|
||||||
if plugin == "admin" {
|
if plugin == "admin" {
|
||||||
return fmt.Errorf("you cannot disable the admin plugin")
|
return fmt.Errorf("you cannot disable the admin plugin")
|
||||||
}
|
}
|
||||||
return p.modBlacklist(`insert or replace into pluginBlacklist values (?, ?)`, channel, plugin)
|
return p.modList(`insert or replace into pluginBlacklist values (?, ?)`, channel, plugin)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *AdminPlugin) rmBlacklist(channel, plugin string) error {
|
func (p *AdminPlugin) rmBlacklist(channel, plugin string) error {
|
||||||
return p.modBlacklist(`delete from pluginBlacklist where channel=? and name=?`, channel, plugin)
|
return p.modList(`delete from pluginBlacklist where channel=? and name=?`, channel, plugin)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *AdminPlugin) modBlacklist(query, channel, plugin string) error {
|
func (p *AdminPlugin) modList(query, channel, plugin string) error {
|
||||||
|
if channel == "" && plugin != "" {
|
||||||
|
channel = plugin // hack
|
||||||
|
}
|
||||||
plugins := p.bot.GetPluginNames()
|
plugins := p.bot.GetPluginNames()
|
||||||
for _, pp := range plugins {
|
for _, pp := range plugins {
|
||||||
if pp == plugin {
|
if pp == plugin {
|
||||||
if _, err := p.db.Exec(query, channel, plugin); err != nil {
|
if _, err := p.db.Exec(query, channel, plugin); err != nil {
|
||||||
return fmt.Errorf("%w", err)
|
return fmt.Errorf("%w", err)
|
||||||
}
|
}
|
||||||
err := p.bot.RefreshPluginBlacklist()
|
err := p.bot.RefreshPluginWhitelist()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w", err)
|
||||||
|
}
|
||||||
|
err = p.bot.RefreshPluginBlacklist()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%w", err)
|
return fmt.Errorf("%w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/velour/catbase/plugins/cli"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/velour/catbase/plugins/cli"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
"github.com/velour/catbase/bot/msg"
|
"github.com/velour/catbase/bot/msg"
|
||||||
|
@ -20,6 +21,10 @@ func setup(t *testing.T) (*AdminPlugin, *bot.MockBot) {
|
||||||
mb = bot.NewMockBot()
|
mb = bot.NewMockBot()
|
||||||
a = New(mb)
|
a = New(mb)
|
||||||
mb.DB().MustExec(`delete from config`)
|
mb.DB().MustExec(`delete from config`)
|
||||||
|
err := mb.Config().Set("admins", "tester")
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
return a, mb
|
return a, mb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +35,7 @@ func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) {
|
||||||
}
|
}
|
||||||
c := cli.CliPlugin{}
|
c := cli.CliPlugin{}
|
||||||
return &c, bot.Message, msg.Message{
|
return &c, bot.Message, msg.Message{
|
||||||
User: &user.User{Name: "tester"},
|
User: &user.User{Name: "admin"},
|
||||||
Channel: "test",
|
Channel: "test",
|
||||||
Body: payload,
|
Body: payload,
|
||||||
Command: isCmd,
|
Command: isCmd,
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -67,7 +68,19 @@ func New(b bot.Bot) *MemePlugin {
|
||||||
return mp
|
return mp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cmdMatch = regexp.MustCompile(`(?i)meme (.+)`)
|
||||||
|
|
||||||
func (p *MemePlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
func (p *MemePlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
|
||||||
|
if message.Command && cmdMatch.MatchString(message.Body) {
|
||||||
|
subs := cmdMatch.FindStringSubmatch(message.Body)
|
||||||
|
if len(subs) != 2 {
|
||||||
|
p.bot.Send(c, bot.Message, message.Channel, "Invalid meme request.")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
minusMeme := subs[1]
|
||||||
|
p.sendMeme(c, message.Channel, message.ChannelName, message.ID, message.User, minusMeme)
|
||||||
|
return true
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,6 +215,107 @@ func (p *MemePlugin) img(w http.ResponseWriter, r *http.Request) {
|
||||||
p.images.cleanup()
|
p.images.cleanup()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *MemePlugin) bully(c bot.Connector, format, id string) image.Image {
|
||||||
|
bullyIcon := ""
|
||||||
|
|
||||||
|
for _, bully := range p.c.GetArray("meme.bully", []string{}) {
|
||||||
|
if format == bully {
|
||||||
|
if u, err := c.Profile(bully); err == nil {
|
||||||
|
bullyIcon = u.Icon
|
||||||
|
} else {
|
||||||
|
log.Debug().Err(err).Msgf("could not get profile for %s", format)
|
||||||
|
}
|
||||||
|
formats := p.c.GetMap("meme.memes", defaultFormats)
|
||||||
|
format = randEntry(formats)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if u, err := c.Profile(id); bullyIcon == "" && err == nil {
|
||||||
|
if u.IconImg != nil {
|
||||||
|
return u.IconImg
|
||||||
|
}
|
||||||
|
bullyIcon = u.Icon
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(bullyIcon)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("error with bully URL")
|
||||||
|
}
|
||||||
|
bullyImg, err := DownloadTemplate(u)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("error downloading bully icon")
|
||||||
|
}
|
||||||
|
return bullyImg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *MemePlugin) sendMeme(c bot.Connector, channel, channelName, msgID string, from *user.User, text string) {
|
||||||
|
parts := strings.SplitN(text, " ", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
log.Debug().Msgf("Bad meme request: %v, %v", from, text)
|
||||||
|
p.bot.Send(c, bot.Message, channel, fmt.Sprintf("%v tried to send me a bad meme request.", from.Name))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isCmd, message := bot.IsCmd(p.c, parts[1])
|
||||||
|
format := parts[0]
|
||||||
|
|
||||||
|
log.Debug().Strs("parts", parts).Msgf("Meme:\n%+v", text)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
top, bottom := "", message
|
||||||
|
parts = strings.Split(message, "\n")
|
||||||
|
if len(parts) > 1 {
|
||||||
|
top, bottom = parts[0], parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if top == "_" {
|
||||||
|
message = bottom
|
||||||
|
} else if bottom == "_" {
|
||||||
|
message = top
|
||||||
|
}
|
||||||
|
|
||||||
|
bullyImg := p.bully(c, format, from.ID)
|
||||||
|
|
||||||
|
id, w, h, err := p.genMeme(format, top, bottom, bullyImg)
|
||||||
|
if err != nil {
|
||||||
|
msg := fmt.Sprintf("Hey %v, I couldn't download that image you asked for.", from.Name)
|
||||||
|
p.bot.Send(c, bot.Message, channel, msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
baseURL := p.c.Get("BaseURL", ``)
|
||||||
|
u, _ := url.Parse(baseURL)
|
||||||
|
u.Path = path.Join(u.Path, "meme", "img", id)
|
||||||
|
|
||||||
|
log.Debug().Msgf("image is at %s", u.String())
|
||||||
|
_, err = p.bot.Send(c, bot.Message, channel, "", bot.ImageAttachment{
|
||||||
|
URL: u.String(),
|
||||||
|
AltTxt: fmt.Sprintf("%s: %s", from.Name, message),
|
||||||
|
Width: w,
|
||||||
|
Height: h,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err == nil && msgID != "" {
|
||||||
|
p.bot.Send(c, bot.Delete, channel, msgID)
|
||||||
|
}
|
||||||
|
|
||||||
|
m := msg.Message{
|
||||||
|
User: &user.User{
|
||||||
|
ID: from.ID,
|
||||||
|
Name: from.Name,
|
||||||
|
Admin: false,
|
||||||
|
},
|
||||||
|
Channel: channel,
|
||||||
|
ChannelName: channelName,
|
||||||
|
Body: message,
|
||||||
|
Command: isCmd,
|
||||||
|
Time: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
p.bot.Receive(c, bot.Message, m)
|
||||||
|
}()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func (p *MemePlugin) slashMeme(c bot.Connector) http.HandlerFunc {
|
func (p *MemePlugin) slashMeme(c bot.Connector) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
|
@ -212,81 +326,15 @@ func (p *MemePlugin) slashMeme(c bot.Connector) http.HandlerFunc {
|
||||||
text := r.PostForm.Get("text")
|
text := r.PostForm.Get("text")
|
||||||
log.Debug().Msgf("channel: %s", channel)
|
log.Debug().Msgf("channel: %s", channel)
|
||||||
|
|
||||||
parts := strings.SplitN(text, " ", 2)
|
user := &user.User{
|
||||||
if len(parts) != 2 {
|
ID: from, // HACK but should work fine
|
||||||
log.Debug().Msgf("Bad meme request: %s, %s", from, text)
|
Name: from,
|
||||||
p.bot.Send(c, bot.Message, channel, fmt.Sprintf("%s tried to send me a bad meme request.", from))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
isCmd, message := bot.IsCmd(p.c, parts[1])
|
|
||||||
format := parts[0]
|
|
||||||
|
|
||||||
bullyIcon := ""
|
|
||||||
|
|
||||||
for _, bully := range p.c.GetArray("meme.bully", []string{}) {
|
|
||||||
if format == bully {
|
|
||||||
if u, err := c.Profile(bully); err == nil {
|
|
||||||
bullyIcon = u.Icon
|
|
||||||
} else {
|
|
||||||
log.Debug().Err(err).Msgf("could not get profile for %s", format)
|
|
||||||
}
|
|
||||||
formats := p.c.GetMap("meme.memes", defaultFormats)
|
|
||||||
format = randEntry(formats)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if u, err := c.Profile(from); bullyIcon == "" && err == nil {
|
p.sendMeme(c, channel, channelName, "", user, text)
|
||||||
bullyIcon = u.Icon
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug().Strs("parts", parts).Msgf("Meme:\n%+v", text)
|
|
||||||
w.WriteHeader(200)
|
w.WriteHeader(200)
|
||||||
w.Write(nil)
|
w.Write(nil)
|
||||||
|
|
||||||
go func() {
|
|
||||||
top, bottom := "", message
|
|
||||||
parts = strings.Split(message, "\n")
|
|
||||||
if len(parts) > 1 {
|
|
||||||
top, bottom = parts[0], parts[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
if top == "_" {
|
|
||||||
message = bottom
|
|
||||||
} else if bottom == "_" {
|
|
||||||
message = top
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := p.genMeme(format, top, bottom, bullyIcon)
|
|
||||||
if err != nil {
|
|
||||||
msg := fmt.Sprintf("Hey %s, I couldn't download that image you asked for.", from)
|
|
||||||
p.bot.Send(c, bot.Message, channel, msg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
baseURL := p.c.Get("BaseURL", `https://catbase.velour.ninja`)
|
|
||||||
u, _ := url.Parse(baseURL)
|
|
||||||
u.Path = path.Join(u.Path, "meme", "img", id)
|
|
||||||
|
|
||||||
log.Debug().Msgf("image is at %s", u.String())
|
|
||||||
p.bot.Send(c, bot.Message, channel, "", bot.ImageAttachment{
|
|
||||||
URL: u.String(),
|
|
||||||
AltTxt: fmt.Sprintf("%s: %s", from, message),
|
|
||||||
})
|
|
||||||
m := msg.Message{
|
|
||||||
User: &user.User{
|
|
||||||
ID: from,
|
|
||||||
Name: from,
|
|
||||||
Admin: false,
|
|
||||||
},
|
|
||||||
Channel: channel,
|
|
||||||
ChannelName: channelName,
|
|
||||||
Body: message,
|
|
||||||
Command: isCmd,
|
|
||||||
Time: time.Now(),
|
|
||||||
}
|
|
||||||
|
|
||||||
p.bot.Receive(c, bot.Message, m)
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,7 +370,7 @@ var defaultFormats = map[string]string{
|
||||||
"raptor": "Philosoraptor.jpg",
|
"raptor": "Philosoraptor.jpg",
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *MemePlugin) genMeme(meme, top, bottom, bully string) (string, error) {
|
func (p *MemePlugin) genMeme(meme, top, bottom string, bully image.Image) (string, int, int, error) {
|
||||||
fontSizes := []float64{48, 36, 24, 16, 12}
|
fontSizes := []float64{48, 36, 24, 16, 12}
|
||||||
fontSize := fontSizes[0]
|
fontSize := fontSizes[0]
|
||||||
|
|
||||||
|
@ -346,7 +394,7 @@ func (p *MemePlugin) genMeme(meme, top, bottom, bully string) (string, error) {
|
||||||
img, err := DownloadTemplate(u)
|
img, err := DownloadTemplate(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug().Msgf("failed to download image: %s", err)
|
log.Debug().Msgf("failed to download image: %s", err)
|
||||||
return "", err
|
return "", 0, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
r := img.Bounds()
|
r := img.Bounds()
|
||||||
|
@ -372,7 +420,7 @@ func (p *MemePlugin) genMeme(meme, top, bottom, bully string) (string, error) {
|
||||||
h = r.Dy()
|
h = r.Dy()
|
||||||
log.Debug().Msgf("resized to %v, %v", w, h)
|
log.Debug().Msgf("resized to %v, %v", w, h)
|
||||||
|
|
||||||
if bully != "" {
|
if bully != nil {
|
||||||
img = p.applyBully(img, bully)
|
img = p.applyBully(img, bully)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -427,20 +475,11 @@ func (p *MemePlugin) genMeme(meme, top, bottom, bully string) (string, error) {
|
||||||
|
|
||||||
log.Debug().Msgf("Saved to %s\n", path)
|
log.Debug().Msgf("Saved to %s\n", path)
|
||||||
|
|
||||||
return path, nil
|
return path, w, h, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *MemePlugin) applyBully(img image.Image, bully string) image.Image {
|
func (p *MemePlugin) applyBully(img, bullyImg image.Image) image.Image {
|
||||||
log.Debug().Msgf("applying bully: %s", bully)
|
|
||||||
dst := image.NewRGBA(img.Bounds())
|
dst := image.NewRGBA(img.Bounds())
|
||||||
u, err := url.Parse(bully)
|
|
||||||
if err != nil {
|
|
||||||
return img
|
|
||||||
}
|
|
||||||
bullyImg, err := DownloadTemplate(u)
|
|
||||||
if err != nil {
|
|
||||||
return img
|
|
||||||
}
|
|
||||||
|
|
||||||
scaleFactor := p.c.GetFloat64("meme.bullyScale", 0.1)
|
scaleFactor := p.c.GetFloat64("meme.bullyScale", 0.1)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue