Skip to content

Commit

Permalink
Implement discord slash commands
Browse files Browse the repository at this point in the history
  • Loading branch information
HotaruBlaze committed Feb 25, 2024
1 parent 56b8a3a commit ce95bef
Show file tree
Hide file tree
Showing 13 changed files with 582 additions and 192 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ require (
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/mapstructure v1.5.0
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
Expand Down
155 changes: 155 additions & 0 deletions src/discord.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package main

import (
"bytes"
"encoding/json"
"math/big"
"strconv"
"strings"
"time"

"github.com/bwmarrin/discordgo"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"
)
Expand All @@ -18,6 +22,7 @@ type discordRole struct {

// DiscordSession : Global Discord Session
var DiscordSession *discordgo.Session
var DiscordGuildID string

// InitDiscord initializes the discordgo session
func InitDiscord() {
Expand All @@ -36,6 +41,7 @@ func InitDiscord() {
discord.AddHandler(messageCreate)
discord.AddHandler(ready)
discord.AddHandler(UpdateDiscordStatus)
discord.AddHandler(handleDiscordCommands)

// Open the connection to Discord
if err := discord.Open(); err != nil {
Expand All @@ -49,8 +55,147 @@ func ready(s *discordgo.Session, event *discordgo.Ready) {
if err != nil {
log.Println(err)
} else {
// Discord module is ready!
log.Println(tes3mpLogMessage, "Discord Module is now running")
// Get the first guildID
DiscordGuildID = event.Guilds[0].ID
// Load Commands
commandResponses, err = LoadDiscordCommandData()
if err != nil {
log.Errorln("Error loading Discord command data:", err)
}
}
}

func handleDiscordCommands(s *discordgo.Session, i *discordgo.InteractionCreate) {
// Check if the interaction is an application command
if i.Type == discordgo.InteractionApplicationCommand {
// Get the name of the command
commandName := strings.ToLower(i.ApplicationCommandData().Name)

// Convert discord options to json we can handle easier
commandArgs, err := discordOptionsToJSON(i.ApplicationCommandData().Options)
if err != nil {
log.Errorln("Error converting Discord options to JSON:", err)
}

commandArgs = string(commandArgs)

// Find and execute the corresponding functionality based on the command name
if _, ok := commandResponses.Commands[commandName]; ok {
// Build a DiscordCommand packet for TES3MP
discordCommand := baseresponse{
JobID: uuid.New().String(),
ServerID: viper.GetString("tes3mp.serverid"),
Method: "Command",
Source: "DiscordCommand",
Target: "TES3MP",
Data: map[string]string{
"command": commandName,
"args": commandArgs,
"discordInteractiveToken": string(i.Interaction.Token),
},
}
jsonresponse, err := json.Marshal(discordCommand)
checkError("DiscordChat", err)
sendresponse := bytes.NewBuffer(jsonresponse).String()
IRCSendMessage(viper.GetString("irc.systemchannel"), sendresponse)

// Temp response for now
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Processing...",
},
})
} else {
// Respond with unknown command message
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Unknown command. Type `/help` to see available commands.",
},
})
}
}
}

// discordOptionsToJSON converts Discord options to JSON format.
func discordOptionsToJSON(options []*discordgo.ApplicationCommandInteractionDataOption) (string, error) {
// Create a map to store the data
data := make(map[string]interface{})

// Iterate through each option and convert it to the appropriate type
for _, option := range options {
name := option.Name
var value interface{}
switch option.Type {
case discordgo.ApplicationCommandOptionString:
value = option.StringValue()
case discordgo.ApplicationCommandOptionInteger:
value = option.IntValue()
case discordgo.ApplicationCommandOptionBoolean:
value = option.BoolValue()
default:
value = option.StringValue()
}

// Add the converted value to the data map
data[name] = value
}

// Marshal the data map into JSON
jsonData, err := json.Marshal(data)
if err != nil {
return "", err
}

return string(jsonData), nil
}

// createSlashCommand creates a new slash command for the given command name in the Discord guild.
// It maps each command argument to a discordgo.ApplicationCommandOption and sets the options for the command.
func createSlashCommand(command string) error {
// Retrieve the command details from the commandResponses map
tes3mpCommand := commandResponses.Commands[command]

// Create a slice to hold the options
var options []*discordgo.ApplicationCommandOption

// Map each command argument to a discordgo.ApplicationCommandOption
for _, arg := range tes3mpCommand.CommandArgs {
// Determine the type of the argument based on your requirements
optionType := discordgo.ApplicationCommandOptionString // For example, assuming all args are strings

// Create the option
option := &discordgo.ApplicationCommandOption{
Type: optionType,
Name: arg.Name,
Description: arg.Description,
Required: arg.Required,
}

// Add the option to the slice
options = append(options, option)
}

// Define the data for the slash command
commandData := &discordgo.ApplicationCommand{
Name: tes3mpCommand.Command,
Description: tes3mpCommand.Description,
Type: discordgo.ChatApplicationCommand,
Options: options, // Set the options for the command
}

// Create the slash command in a specific guild
_, err := DiscordSession.ApplicationCommandCreate(DiscordSession.State.User.ID, DiscordGuildID, commandData)
if err != nil {
return err
}

// Print confirmation message
log.Println("Created discord slash command:", command)
return nil
}

// allowColorHexUsage checks if the usage of color hex codes is allowed.
Expand Down Expand Up @@ -181,3 +326,13 @@ func isStaffMember(UserID string, GuildID string) bool {
// Return false if the user is not a staff member
return false
}

func SendDiscordInteractiveMessage(interactionToken, newContent string) {
// Construct the interaction response data for editing
responseEdit := &discordgo.WebhookEdit{
Content: &newContent, // New content for the interaction response
}

// Update the interaction response
DiscordSession.WebhookMessageEdit(DiscordSession.State.User.ID, interactionToken, "@original", responseEdit)
}
111 changes: 111 additions & 0 deletions src/discordCommandData.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package main

import (
"encoding/json"
"fmt"
"os"

log "github.com/sirupsen/logrus"
)

type CommandResponses struct {
Commands map[string]CommandData `json:"commands"`
}

var commandResponses CommandResponses

// AddDiscordCommand is responsible for adding a new Discord slash command to the system. It saves the command and registers it with the Discord platform.
func AddDiscordCommand(data *CommandResponses, command string, description string, args ...string) {
// If the data has no Commands map, return without doing anything
if data.Commands == nil {
return
}

// Create CommandArg objects for each argument
commandArgs := make([]*CommandArg, len(args))
for i, arg := range args {
commandArgs[i] = &CommandArg{
Required: true,
Name: arg,
Description: description,
}
}

// Add the new CommandData to the Commands map in the data
data.Commands[command] = CommandData{
Command: command,
Description: description,
CommandArgs: commandArgs,
}

// Create a new slash command
createSlashCommand(command)

// Save the updated Discord command data to a file
if err := SaveDiscordCommandData(*data, "discordCommands.json"); err != nil {
// Print an error message if there was an error saving the data
log.Errorln("Error saving Discord command data:", err)
}
}

// RemoveDiscordCommand removes the specified command from the CommandResponses map.
//
// data *CommandResponses - the map of command responses
// command string - the command to be removed
func RemoveDiscordCommand(data *CommandResponses, command string) {
delete(data.Commands, command)
}

// SaveDiscordCommandData saves the given CommandResponses data to a file specified by the filename parameter.
// It takes a CommandResponses data and a filename string as parameters and returns an error.
func SaveDiscordCommandData(data CommandResponses, filename string) error {
jsonData, err := json.Marshal(data)
if err != nil {
return fmt.Errorf("error marshalling JSON: %v", err)
}
if err := os.WriteFile(filename, jsonData, 0644); err != nil {
return fmt.Errorf("error writing JSON to file: %v", err)
}
return nil
}

// LoadDiscordCommandData loads the Discord command data from a JSON file.
//
// It returns a CommandResponses and an error.
func LoadDiscordCommandData() (CommandResponses, error) {
fileData, err := os.ReadFile("discordCommands.json")
if err != nil {
return CommandResponses{}, fmt.Errorf("error reading file: %v", err)
}
var loadedData CommandResponses
err = json.Unmarshal(fileData, &loadedData)
if err != nil {
return CommandResponses{}, fmt.Errorf("error unmarshalling JSON: %v", err)
}

// Count the number of registered commands
numCommands := len(loadedData.Commands)
log.Println("[Discord]", "Loaded", numCommands, "Discord commands!")

return loadedData, nil
}

func purgeDiscordCommands() error {
commands, err := DiscordSession.ApplicationCommands(DiscordSession.State.User.ID, DiscordGuildID)
if err != nil {
return err
}

for _, command := range commands {
err := DiscordSession.ApplicationCommandDelete(DiscordSession.State.User.ID, DiscordGuildID, command.ID)
if err != nil {
return err
}
RemoveDiscordCommand(&commandResponses, command.Name)
log.Println("[Discord]", "Purged Discord command:", command.Name)
}

log.Println("[Discord]", "Purged all Discord commands!")
SaveDiscordCommandData(commandResponses, "discordCommands.json")
return nil
}
51 changes: 0 additions & 51 deletions src/discordCommands.go

This file was deleted.

Loading

0 comments on commit ce95bef

Please sign in to comment.