Confessions module
This commit adds a confessions feature that allows users to mark a "confessional" channel and also to post anonymously to it. The changes that this comprises of are as follows: - New discord "slash" commands for both marking a confessional and posting to it - a bunch of stuff in the discord module to register and deregister "slash" commands - New event type to track marked confessionals - confession module that processes new confession channel links and also posts confessions to corresponding confessionals Not included in this commit: - a way to cleanup obsolete or reconfigured confession channel links - access control for the confessional slash commands Signed-off-by: Ava Affine <ava@sunnypup.io>
This commit is contained in:
parent
720b80679a
commit
430c0afaa6
6 changed files with 253 additions and 0 deletions
58
internal/confession/confession.go
Normal file
58
internal/confession/confession.go
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
package confession
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"gitlab.com/whom/bingobot/internal/logging"
|
||||
"gitlab.com/whom/bingobot/internal/state"
|
||||
)
|
||||
|
||||
/* Activity module
|
||||
* This module posts anonymous confessions according to a linked channel map
|
||||
*/
|
||||
|
||||
const (
|
||||
ActivityModuleStartFail = "failed to start activity module"
|
||||
)
|
||||
|
||||
var (
|
||||
// guild ID to channel ID
|
||||
linkLock sync.RWMutex
|
||||
confessionChannelLinks map[string]state.ConfessionsChannelLinkEvent
|
||||
)
|
||||
|
||||
func Start() error {
|
||||
ch, err := state.ConfessionsChannelLink.Subscribe()
|
||||
if err != nil {
|
||||
return errors.Join(
|
||||
errors.New(ActivityModuleStartFail),
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
// process incoming events loop
|
||||
go func() {
|
||||
for {
|
||||
ev := <- ch
|
||||
e := ev.(state.ConfessionsChannelLinkEvent)
|
||||
linkLock.Lock()
|
||||
confessionChannelLinks[e.GuildID] = e
|
||||
linkLock.Unlock()
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func MakeConfession(s *discordgo.Session, guildID string, content string) {
|
||||
linkLock.RLock()
|
||||
link, ok := confessionChannelLinks[guildID]
|
||||
linkLock.RUnlock()
|
||||
if !ok {
|
||||
logging.Error("Failed to send confession in guild %s: no link exists in map", guildID)
|
||||
return
|
||||
}
|
||||
s.ChannelMessageSend(link.ChannelID, content)
|
||||
}
|
||||
112
internal/discord/commands.go
Normal file
112
internal/discord/commands.go
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
package discord
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
|
||||
"gitlab.com/whom/bingobot/internal/confession"
|
||||
"gitlab.com/whom/bingobot/internal/logging"
|
||||
"gitlab.com/whom/bingobot/internal/state"
|
||||
)
|
||||
|
||||
var (
|
||||
// map of guildID to registeredCommands
|
||||
registeredCommands map[string][]*discordgo.ApplicationCommand
|
||||
|
||||
// all commands
|
||||
commandList = []*discordgo.ApplicationCommand{
|
||||
// TODO: Limit usage somehow?
|
||||
// maybe delete this and use the vote module instead
|
||||
&discordgo.ApplicationCommand{
|
||||
Name: "confessional",
|
||||
Description: "mark a channel as a designated confessional for a guild",
|
||||
},
|
||||
|
||||
&discordgo.ApplicationCommand{
|
||||
Name: "confess",
|
||||
Description: "anonymously post a confession in configured channel",
|
||||
Options: []*discordgo.ApplicationCommandOption{
|
||||
&discordgo.ApplicationCommandOption{
|
||||
Name: "confession",
|
||||
Description: "A confession to be posted anonymously",
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
commandHandlers = map[string]func(
|
||||
s *discordgo.Session,
|
||||
i *discordgo.InteractionCreate,
|
||||
) {
|
||||
"confessional": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
state.PublishEvent(state.ConfessionsChannelLinkEvent{
|
||||
GuildID: i.GuildID,
|
||||
ChannelID: i.ChannelID,
|
||||
Created: time.Now(),
|
||||
})
|
||||
},
|
||||
|
||||
// handle a confession
|
||||
"confess": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
for _, v := range i.ApplicationCommandData().Options {
|
||||
if v.Name == "confession" {
|
||||
confession.MakeConfession(s, i.GuildID, v.StringValue())
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func handleCommand(s *discordgo.Session, e *discordgo.InteractionCreate) {
|
||||
name := e.ApplicationCommandData().Name
|
||||
// TODO: audit log
|
||||
if h, ok := commandHandlers[name]; ok {
|
||||
h(s, e)
|
||||
} else {
|
||||
logging.Debug("no handler for command: %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
func registerCommands(s *discordgo.Session) {
|
||||
for _, guild := range s.State.Guilds {
|
||||
cmds, err := s.ApplicationCommandBulkOverwrite(
|
||||
s.State.Application.ID,
|
||||
guild.ID,
|
||||
commandList,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
logging.Error(
|
||||
"Failed to register commands for guild %s: %s",
|
||||
guild.ID, err.Error(),
|
||||
)
|
||||
} else {
|
||||
logging.Info("Registered commands for guild %s", guild.ID)
|
||||
registeredCommands[guild.ID] = cmds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func deregisterCommands(s *discordgo.Session) {
|
||||
for guild, commands := range registeredCommands {
|
||||
for _, cmd := range commands {
|
||||
if err := s.ApplicationCommandDelete(
|
||||
s.State.Application.ID,
|
||||
guild,
|
||||
cmd.ID,
|
||||
); err != nil {
|
||||
logging.Error(
|
||||
"Failed to delete %s command (id: %s) from guild %s",
|
||||
cmd.Name, cmd.ID, guild,
|
||||
)
|
||||
} else {
|
||||
logging.Info(
|
||||
"Deregistered command %s (id: %s) from guild %s",
|
||||
cmd.Name, cmd.ID, guild,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -36,10 +36,13 @@ func Connect(token string) error {
|
|||
return fmt.Errorf("failed to open discord session: %s", err)
|
||||
}
|
||||
|
||||
registerCommands(session.s)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Close() {
|
||||
deregisterCommands(session.s)
|
||||
err := session.s.Close()
|
||||
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ func addHandlers() {
|
|||
session.s.AddHandler(handleConnect)
|
||||
session.s.AddHandler(handleDisconnect)
|
||||
session.s.AddHandler(handleVoiceStateUpdate)
|
||||
session.s.AddHandler(handleCommand) // handles InteractionCreate
|
||||
}
|
||||
|
||||
func handleConnect(s *discordgo.Session, e *discordgo.Connect) {
|
||||
|
|
|
|||
|
|
@ -226,3 +226,47 @@ func (te TestEvent) Validate() error {
|
|||
func (te TestEvent) Disposable() bool {
|
||||
return te.Dispose
|
||||
}
|
||||
|
||||
const (
|
||||
ConfessionsLinkEventGuildKey = "guild_id"
|
||||
ConfessionsLinkEventChannelKey = "channel_id"
|
||||
ConfessionsLinkEventCreatedKey = "created"
|
||||
ConfessionsLinkEventObsoleteKey = "obsolete"
|
||||
|
||||
BadConfessionsLinkEventError = "link event doesnt have required fields"
|
||||
)
|
||||
|
||||
type ConfessionsChannelLinkEvent struct {
|
||||
GuildID string
|
||||
ChannelID string
|
||||
Created time.Time
|
||||
}
|
||||
|
||||
func (e ConfessionsChannelLinkEvent) Type() EventType {
|
||||
return ConfessionsChannelLink
|
||||
}
|
||||
|
||||
func (e ConfessionsChannelLinkEvent) Time() time.Time {
|
||||
return e.Time()
|
||||
}
|
||||
|
||||
func (e ConfessionsChannelLinkEvent) Data() map[string]string {
|
||||
return map[string]string{
|
||||
ConfessionsLinkEventGuildKey: e.GuildID,
|
||||
ConfessionsLinkEventChannelKey: e.ChannelID,
|
||||
ConfessionsLinkEventCreatedKey: e.Created.Format(time.RFC3339),
|
||||
}
|
||||
}
|
||||
|
||||
func (e ConfessionsChannelLinkEvent) Validate() error {
|
||||
if len(e.ChannelID) > 1 || len(e.GuildID) > 1 {
|
||||
return errors.New(BadConfessionsLinkEventError)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e ConfessionsChannelLinkEvent) Disposable() bool {
|
||||
// TODO
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -183,6 +183,7 @@ const (
|
|||
Restoration
|
||||
UserActive
|
||||
Test
|
||||
ConfessionsChannelLink
|
||||
// ...
|
||||
|
||||
// leave this last
|
||||
|
|
@ -202,6 +203,8 @@ func EventTypeFromString(doc string) EventType {
|
|||
return UserActive
|
||||
case "Test":
|
||||
return Test
|
||||
case "ConfessionsChannelLink":
|
||||
return ConfessionsChannelLink
|
||||
default:
|
||||
// error case
|
||||
return NumEventTypes
|
||||
|
|
@ -215,6 +218,7 @@ func (et EventType) String() string {
|
|||
"Restoration",
|
||||
"UserActive",
|
||||
"Test",
|
||||
"ConfessionsChannelLink",
|
||||
}
|
||||
|
||||
if et < 0 || et >= NumEventTypes {
|
||||
|
|
@ -266,24 +270,28 @@ func (et EventType) MakeEvent(data map[string]string) (Event, error) {
|
|||
switch et {
|
||||
case Vote:
|
||||
return VoteEvent(data), nil
|
||||
|
||||
case Challenge:
|
||||
e, err := MakeUserEvent(data)
|
||||
if err != nil {
|
||||
return nil, errors.Join(errors.New(BadChallengeEvent), err)
|
||||
}
|
||||
return ChallengeEvent{*e}, nil
|
||||
|
||||
case Restoration:
|
||||
e, err := MakeUserEvent(data)
|
||||
if err != nil {
|
||||
return nil, errors.Join(errors.New(BadRestorationEvent), err)
|
||||
}
|
||||
return RestorationEvent{*e}, nil
|
||||
|
||||
case UserActive:
|
||||
e, err := MakeUserEvent(data)
|
||||
if err != nil {
|
||||
return nil, errors.Join(errors.New(BadUserActiveEvent), err)
|
||||
}
|
||||
return UserActiveEvent{*e}, nil
|
||||
|
||||
case Test:
|
||||
disp := false
|
||||
if v, ok := data[TestEventDisposeKey]; ok && v == "t" {
|
||||
|
|
@ -300,6 +308,33 @@ func (et EventType) MakeEvent(data map[string]string) (Event, error) {
|
|||
Dispose: disp,
|
||||
ID: id,
|
||||
}, nil
|
||||
|
||||
case ConfessionsChannelLink:
|
||||
gid, ok := data[ConfessionsLinkEventGuildKey]
|
||||
if !ok {
|
||||
return nil, errors.New(BadConfessionsLinkEventError)
|
||||
}
|
||||
|
||||
cid, ok := data[ConfessionsLinkEventChannelKey]
|
||||
if !ok {
|
||||
return nil, errors.New(BadConfessionsLinkEventError)
|
||||
}
|
||||
|
||||
ti, ok := data[ConfessionsLinkEventCreatedKey]
|
||||
if !ok {
|
||||
return nil, errors.New(BadConfessionsLinkEventError)
|
||||
}
|
||||
t, err := time.Parse(time.RFC3339, ti)
|
||||
if err != nil {
|
||||
return nil, errors.Join(errors.New(BadConfessionsLinkEventError), err)
|
||||
}
|
||||
|
||||
return ConfessionsChannelLinkEvent{
|
||||
GuildID: gid,
|
||||
ChannelID: cid,
|
||||
Created: t,
|
||||
}, nil
|
||||
|
||||
default:
|
||||
return nil, errors.New(BadEventTypeError)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue