135 lines
3.2 KiB
Go
135 lines
3.2 KiB
Go
package plugins
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/bwmarrin/discordgo"
|
|
)
|
|
|
|
// HandlerFunc is an event handler function.
|
|
type HandlerFunc func(session *discordgo.Session, data any)
|
|
|
|
// Handler represents a plugin event handler.
|
|
type Handler struct {
|
|
PluginName string
|
|
Func HandlerFunc
|
|
}
|
|
|
|
var (
|
|
handlersMtx = sync.Mutex{}
|
|
handlerMap = map[string][]Handler{}
|
|
)
|
|
|
|
// handlePluginEvent handles any discord event we receive and
|
|
// routes it to the appropriate plugin handler(s).
|
|
func handlePluginEvent(s *discordgo.Session, data any) {
|
|
name := reflect.TypeOf(data).Elem().Name()
|
|
handlers, ok := handlerMap[name]
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
for _, h := range handlers {
|
|
if !pluginEnabled(eventGuildID(data), h.PluginName) {
|
|
continue
|
|
}
|
|
|
|
h.Func(s, data)
|
|
}
|
|
}
|
|
|
|
// eventGuildID uses reflection to get the guild ID from an event
|
|
func eventGuildID(event any) string {
|
|
evt := reflect.ValueOf(event)
|
|
|
|
for evt.Kind() == reflect.Pointer {
|
|
evt = evt.Elem()
|
|
}
|
|
|
|
if evt.Kind() != reflect.Struct {
|
|
return ""
|
|
}
|
|
|
|
if id := evt.FieldByName("GuildID"); id.IsValid() {
|
|
return id.String()
|
|
} else if guild := evt.FieldByName("Guild"); guild.IsValid() {
|
|
if id := guild.FieldByName("ID"); id.IsValid() {
|
|
return id.String()
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// handleAutocomplete handles autocomplete events for the /plugin run command.
|
|
func handleAutocomplete(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|
if i.Type != discordgo.InteractionApplicationCommandAutocomplete {
|
|
return
|
|
}
|
|
|
|
data := i.ApplicationCommandData()
|
|
if data.Name != "plugin" {
|
|
return
|
|
}
|
|
|
|
cmdStr := data.Options[0].Options[0].StringValue()
|
|
|
|
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
|
Type: discordgo.InteractionApplicationCommandAutocompleteResult,
|
|
Data: &discordgo.InteractionResponseData{
|
|
Choices: getAllChoices(i.GuildID, cmdStr, i.Member),
|
|
},
|
|
})
|
|
}
|
|
|
|
// getAllChoices gets possible command strings for each plugin and converts them
|
|
// to Discord command options.
|
|
func getAllChoices(guildID, partial string, member *discordgo.Member) (out []*discordgo.ApplicationCommandOptionChoice) {
|
|
for _, plugin := range Plugins {
|
|
if !pluginEnabled(guildID, plugin.Info.Name) {
|
|
continue
|
|
}
|
|
out = append(out, getChoiceStrs(partial, "", plugin.Commands, member)...)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// getChoiceStrs recursively looks through every command in cmds,
|
|
// and generates a list of strings to use as autocomplete options.
|
|
func getChoiceStrs(partial, prefix string, cmds []Command, member *discordgo.Member) []*discordgo.ApplicationCommandOptionChoice {
|
|
if len(cmds) == 0 {
|
|
return nil
|
|
}
|
|
|
|
partial = strings.TrimSpace(partial)
|
|
var out []*discordgo.ApplicationCommandOptionChoice
|
|
|
|
for _, cmd := range cmds {
|
|
for _, perm := range cmd.Permissions {
|
|
if member.Permissions&perm == 0 {
|
|
continue
|
|
}
|
|
}
|
|
|
|
sub := getChoiceStrs(strings.TrimPrefix(partial, cmd.Name), cmd.Name+" ", cmd.Subcommands, member)
|
|
out = append(out, sub...)
|
|
|
|
if cmd.OnExec == nil {
|
|
continue
|
|
}
|
|
|
|
qualifiedCmd := prefix + cmd.Name
|
|
|
|
if strings.Contains(qualifiedCmd, partial) {
|
|
out = append(out, &discordgo.ApplicationCommandOptionChoice{
|
|
Name: qualifiedCmd,
|
|
Value: qualifiedCmd,
|
|
})
|
|
}
|
|
}
|
|
|
|
return out
|
|
}
|