Merge branch 'discord'

master
Tyler Sommer 2022-03-05 18:28:26 -07:00
commit f46393ee5b
Signed by: tyler-sommer
GPG Key ID: C09C010500DBD008
7 changed files with 240 additions and 3 deletions

View File

@ -124,6 +124,8 @@ as shared libraries and loaded at runtime using the Go plugin API.
- `squircy2_compat` provides a compatibility layer with
[squircy2](https://squircy.com).
- `script` loads javascript files from a configured folder at app startup.
- `discord` provides integration with
[discordgo](https://github.com/bwmarrin/discordgo).
#### Linking extra plugins at compile-time

View File

@ -14,6 +14,9 @@ extra_plugins=[
# script is a plugin that loads scripts from a directory and executes them during application
# startup.
"script.so",
# discord is a plugin that enables discord interaction, ie. discord bot functionality.
"discord.so",
]
[irc]
@ -48,3 +51,8 @@ data_path="data"
# set enable_exec to true to allow scripts to spawn child processes.
enable_exec=false
[discord]
# bot authorization token
#token=""
#owner=""
#activity=""

1
go.mod
View File

@ -4,6 +4,7 @@ go 1.12
require (
github.com/BurntSushi/toml v0.3.1
github.com/bwmarrin/discordgo v0.24.0
github.com/dop251/goja v0.0.0-20220214123719-b09a6bfa842f
github.com/fatih/structtag v1.2.0
github.com/gobuffalo/logger v1.0.4 // indirect

12
go.sum
View File

@ -7,6 +7,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bwmarrin/discordgo v0.24.0 h1:Gw4MYxqHdvhO99A3nXnSLy97z5pmIKHZVJ1JY5ZDPqY=
github.com/bwmarrin/discordgo v0.24.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
@ -23,8 +25,6 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E=
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dop251/goja v0.0.0-20210630164231-8f81471d5d0b h1:rnfefjbv6blIje+eurnj8yffNiNAeuF5XbplLwgYNxk=
github.com/dop251/goja v0.0.0-20210630164231-8f81471d5d0b/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
github.com/dop251/goja v0.0.0-20220214123719-b09a6bfa842f h1:ztRywKO1rqqS8li0TDcnwi9AGsqAH0ky9NaND69/Ccc=
github.com/dop251/goja v0.0.0-20220214123719-b09a6bfa842f/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
@ -55,6 +55,8 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
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/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
@ -149,6 +151,8 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@ -160,6 +164,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/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-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -179,12 +184,13 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=

132
plugins/discord/discord.go Normal file
View File

@ -0,0 +1,132 @@
package discord
import (
"sync"
"code.dopame.me/veonik/squircy3/event"
"github.com/bwmarrin/discordgo"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
var ErrNotConnected = errors.New("not connected")
type Config struct {
Token string `toml:"token"`
ActivityName string `toml:"activity"`
OwnerID string `toml:"owner"`
enabled bool
}
type Manager struct {
conf Config
ev *event.Dispatcher
session *discordgo.Session
channels map[string]*discordgo.Channel
mu sync.Mutex
}
func NewManager(ev *event.Dispatcher) *Manager {
return &Manager{ev: ev, channels: make(map[string]*discordgo.Channel)}
}
func (m *Manager) Configure(c Config) error {
c.enabled = len(c.Token) > 0
m.mu.Lock()
defer m.mu.Unlock()
m.conf = c
return nil
}
func (m *Manager) Connect() error {
m.mu.Lock()
defer m.mu.Unlock()
if m.session != nil {
return errors.New("already connected")
}
if !m.conf.enabled {
return errors.New("not enabled")
}
s, err := discordgo.New("Bot " + m.conf.Token)
if err != nil {
return err
}
m.session = s
m.session.AddHandler(m.onMessageCreate)
m.session.Identify.Intents = discordgo.IntentsGuildMessages | discordgo.IntentGuildMessageTyping | discordgo.IntentsDirectMessages
m.session.Identify.Presence.Game = discordgo.Activity{Name: m.conf.ActivityName}
return m.session.Open()
}
func (m *Manager) Disconnect() error {
m.mu.Lock()
defer m.mu.Unlock()
if m.session == nil {
return ErrNotConnected
}
s := m.session
m.session = nil
return s.Close()
}
func userToMap(user *discordgo.User) map[string]interface{} {
return map[string]interface{}{
"ID": user.ID,
"Username": user.Username,
}
}
func (m *Manager) onMessageCreate(s *discordgo.Session, e *discordgo.MessageCreate) {
ch, err := m.getChannel(e.ChannelID)
isDM := false
if err != nil {
logrus.Warnf("%s: failed to get channel for %s: %s", PluginName, e.ChannelID, err)
} else {
isDM = ch.Type == discordgo.ChannelTypeDM
}
m.ev.Emit("discord.MESSAGE", map[string]interface{}{
"ID": e.ID,
"Content": e.Content,
"ChannelID": e.ChannelID,
"GuildID": e.GuildID,
"Author": userToMap(e.Author),
"FromSelf": e.Author.ID == s.State.User.ID,
"IsDM": isDM,
})
}
func (m *Manager) MessageChannel(channelID, message string) error {
_, err := m.session.ChannelMessageSend(channelID, message)
return err
}
func (m *Manager) MessageChannelTTS(channelID, message string) error {
_, err := m.session.ChannelMessageSendTTS(channelID, message)
return err
}
func (m *Manager) CurrentUsername() (string, error) {
return m.session.State.User.Username, nil
}
func (m *Manager) OwnerID() string {
return m.conf.OwnerID
}
func (m *Manager) getChannel(id string) (*discordgo.Channel, error) {
m.mu.Lock()
defer m.mu.Unlock()
if ch, ok := m.channels[id]; ok {
return ch, nil
}
ch, err := m.session.Channel(id)
if err != nil {
return nil, err
}
m.channels[id] = ch
return ch, nil
}

74
plugins/discord/plugin.go Normal file
View File

@ -0,0 +1,74 @@
package discord
import (
"code.dopame.me/veonik/squircy3/config"
"code.dopame.me/veonik/squircy3/event"
"code.dopame.me/veonik/squircy3/plugin"
"github.com/dop251/goja"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
const PluginName = "discord"
func Initialize(m *plugin.Manager) (plugin.Plugin, error) {
ev, err := event.FromPlugins(m)
if err != nil {
return nil, errors.Wrapf(err, "%s: required dependency missing (event)", PluginName)
}
return &discordPlugin{NewManager(ev)}, nil
}
type discordPlugin struct {
manager *Manager
}
func (p *discordPlugin) Configure(c config.Config) error {
if gcv, ok := c.Self().(*Config); ok {
return p.manager.Configure(*gcv)
}
cf := Config{}
cf.Token, _ = c.String("token")
cf.OwnerID, _ = c.String("owner")
cf.ActivityName, _ = c.String("activity")
return p.manager.Configure(cf)
}
func (p *discordPlugin) Options() []config.SetupOption {
return []config.SetupOption{config.WithInitValue(&Config{})}
}
func (p *discordPlugin) Name() string {
return PluginName
}
// must logs the given error as a warning
func must(what string, err error) {
if err != nil {
logrus.Warnf("%s: error %s: %s", PluginName, what, err)
}
}
func (p *discordPlugin) HandleRuntimeInit(gr *goja.Runtime) {
v := gr.NewObject()
must("setting connect", v.Set("connect", p.manager.Connect))
must("setting messageChannel", v.Set("messageChannel", p.manager.MessageChannel))
must("setting messageChannelTTS", v.Set("messageChannelTTS", p.manager.MessageChannelTTS))
must("setting getCurrentUsername", v.Set("getCurrentUsername", p.manager.CurrentUsername))
must("setting getOwnerID", v.Set("getOwnerID", p.manager.OwnerID))
if err := gr.Set("discord", v); err != nil {
logrus.Warnf("%s: error initializing runtime: %s", PluginName, err)
}
}
func (p *discordPlugin) HandleShutdown() {
if p.manager == nil {
logrus.Warnf("%s: shutting down uninitialized plugin", PluginName)
return
}
if err := p.manager.Disconnect(); err != nil {
if err != ErrNotConnected {
logrus.Warnf("%s: failed to disconnect before shutting down: %s", PluginName, err)
}
}
}

View File

@ -0,0 +1,14 @@
package main
import (
"code.dopame.me/veonik/squircy3/plugin"
"code.dopame.me/veonik/squircy3/plugins/discord"
)
func main() {
plugin.Main(discord.PluginName)
}
func Initialize(m *plugin.Manager) (plugin.Plugin, error) {
return discord.Initialize(m)
}