mirror of https://github.com/veonik/squircy3
Merge branch 'discord'
commit
f46393ee5b
|
@ -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
|
||||
|
||||
|
|
|
@ -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
1
go.mod
|
@ -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
12
go.sum
|
@ -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=
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
Loading…
Reference in New Issue