From 3376c69e8699a77cceeba4bfe002ae27e4aa8d74 Mon Sep 17 00:00:00 2001 From: Riley Date: Sat, 21 Aug 2021 17:39:42 -0700 Subject: [PATCH] tons of clean up, removed stuff from the upstream codebase --- arguments_test.go | 7 - commands.go | 118 +++++-------- fs-win.go | 176 +++++++++++++++++++ fs.go | 7 +- guilds.go | 423 ++++++++-------------------------------------- interaction.go | 4 +- response.go | 70 -------- util.go | 4 +- 8 files changed, 300 insertions(+), 509 deletions(-) delete mode 100644 arguments_test.go create mode 100644 fs-win.go diff --git a/arguments_test.go b/arguments_test.go deleted file mode 100644 index ca7d62f..0000000 --- a/arguments_test.go +++ /dev/null @@ -1,7 +0,0 @@ -package framework - -import "testing" - -func TestParseArguments(t *testing.T) { - -} diff --git a/commands.go b/commands.go index 05cdaf8..01798aa 100644 --- a/commands.go +++ b/commands.go @@ -12,7 +12,8 @@ import ( // commands.go // This file contains everything required to add core commands to the bot, and parse commands from a message -// GroupTypes +// Group +// Defines different "groups" of commands for ordering in a help command type Group string var ( @@ -62,14 +63,6 @@ type Command struct { // Defines how child commands are stored type ChildCommand map[string]map[string]Command -// CustomCommand -// A type that defines a custom command -type CustomCommand struct { - Content string // The content of the custom command. Custom commands are just special strings after all - InvokeCount int64 // How many times the command has been invoked; int64 for easier use with json - Public bool // Whether non-admins and non-mods can use this command -} - // commands // All the registered core commands (not custom commands) // This is private so that other commands cannot modify it @@ -174,12 +167,6 @@ func GetCommands() map[string]CommandInfo { return list } -// customCommandHandler -// Given a custom command, interpret and run it -func customCommandHandler(command CustomCommand, args []string, message *discordgo.Message) { - //TODO -} - // commandHandler // This handler will be added to a *discordgo.Session, and will scan an incoming messages for commands to run func commandHandler(session *discordgo.Session, message *discordgo.MessageCreate) { @@ -202,14 +189,6 @@ func commandHandler(session *discordgo.Session, message *discordgo.MessageCreate if trigger == nil { return } - isCustom := false - if _, ok := commands[commandAliases[*trigger]]; !ok { - if !g.IsCustomCommand(*trigger) { - return - } else { - isCustom = true - } - } // Only do further checks if the user is not a bot admin if !IsAdmin(message.Author.ID) { // Ignore the command if it is globally disabled @@ -217,8 +196,8 @@ func commandHandler(session *discordgo.Session, message *discordgo.MessageCreate return } - // Ignore the command if this channel has blocked the trigger - if g.TriggerIsDisabledInChannel(*trigger, message.ChannelID) { + // Ignore the command if this channel has blocked the command + if g.CommandIsDisabledInChannel(*trigger, message.ChannelID) { return } @@ -233,54 +212,49 @@ func commandHandler(session *discordgo.Session, message *discordgo.MessageCreate } } - if !isCustom { - //Get the command to run - // Error Checking - command, ok := commands[commandAliases[*trigger]] - if !ok { - log.Errorf("Command was not found") - if IsAdmin(message.Author.ID) { - Session.MessageReactionAdd(message.ChannelID, message.ID, "<:redtick:861413502991073281>") - Session.ChannelMessageSendReply(message.ChannelID, "<:redtick:861413502991073281> Error! Command not found!", message.MessageReference) - } - return - } - // Check if the command is public, or if the current user is a bot moderator - // Bot admins supercede both checks - if IsAdmin(message.Author.ID) || command.Info.Public || g.IsMod(message.Author.ID) { - // Run the command with the necessary context - if command.Info.IsTyping && g.Info.ResponseChannelId == "" { - _ = Session.ChannelTyping(message.ChannelID) - } - // The command is valid, so now we need to delete the invoking message if that is configured - if g.Info.DeletePolicy { - err := Session.ChannelMessageDelete(message.ChannelID, message.ID) - if err != nil { - SendErrorReport(message.GuildID, message.ChannelID, message.Author.ID, "Failed to delete message: "+message.ID, err) - } - } - - defer handleCommandError(g.ID, channel.ID, message.Author.ID) - if command.Info.IsParent { - handleChildCommand(*argString, command, message.Message, g) - return - } - command.Function(&Context{ - Guild: g, - Cmd: command.Info, - Args: *ParseArguments(*argString, command.Info.Arguments), - Message: message.Message, - }) - // Makes sure that variables ran in ParseArguments are gone. - if commandsGC == 25 && commandsGC > 25 { - debug.FreeOSMemory() - commandsGC = 0 - } else { - commandsGC++ - } - return - } + //Get the command to run + // Error Checking + command, ok := commands[commandAliases[*trigger]] + if !ok { + log.Errorf("Command was not found") + return } + // Check if the command is public, or if the current user is a bot moderator + // Bot admins supercede both checks + if IsAdmin(message.Author.ID) || command.Info.Public || g.IsMod(message.Author.ID) { + // Run the command with the necessary context + if command.Info.IsTyping && g.Info.ResponseChannelId == "" { + _ = Session.ChannelTyping(message.ChannelID) + } + // The command is valid, so now we need to delete the invoking message if that is configured + if g.Info.DeletePolicy { + err := Session.ChannelMessageDelete(message.ChannelID, message.ID) + if err != nil { + SendErrorReport(message.GuildID, message.ChannelID, message.Author.ID, "Failed to delete message: "+message.ID, err) + } + } + + defer handleCommandError(g.ID, channel.ID, message.Author.ID) + if command.Info.IsParent { + handleChildCommand(*argString, command, message.Message, g) + return + } + command.Function(&Context{ + Guild: g, + Cmd: command.Info, + Args: *ParseArguments(*argString, command.Info.Arguments), + Message: message.Message, + }) + // Makes sure that variables ran in ParseArguments are gone. + if commandsGC == 25 && commandsGC > 25 { + debug.FreeOSMemory() + commandsGC = 0 + } else { + commandsGC++ + } + return + } + } // -- Helper Methods diff --git a/fs-win.go b/fs-win.go new file mode 100644 index 0000000..7a21dee --- /dev/null +++ b/fs-win.go @@ -0,0 +1,176 @@ +//go:build windows +// +build windows + +package framework + +import ( + "encoding/json" + "golang.org/x/sys/windows" + "io/ioutil" + "os" + "path" + "strings" + "sync" +) + +// fs.go +// This file contains functions that pertain to interacting with the filesystem, including mutex locking of files + +// GuildsDir +// The directory to use for reading and writing guild .json files. Defaults to ./guilds +// todo abstract this into a database module (being completed in feature/database) +var GuildsDir = "" + +// saveLock +// A map that stores mutexes for each guild, which will be locked every time that guild's data is written +// This ensures files are written to synchronously, avoiding file race conditions +var saveLock = make(map[string]*sync.Mutex) + +// loadGuilds +// Load all known guilds from the filesystem, from inside GuildsDir +func loadGuilds() { + // Check if the configured guild directory exists, and create it if otherwise + if _, existErr := os.Stat(GuildsDir); os.IsNotExist(existErr) { + mkErr := os.MkdirAll(GuildsDir, 0755) + if mkErr != nil { + log.Fatalf("Failed to create guild directory: %s", mkErr) + } + log.Warningf("There are no Guilds to load; data for new Guilds will be saved to: %s", GuildsDir) + + // There are no guilds to load, so we can return early + return + } + + // Get a list of files in the directory + files, rdErr := ioutil.ReadDir(GuildsDir) + if rdErr != nil { + log.Fatalf("Failed to read guild directory: %s", rdErr) + } + + // Iterate over each file + for _, file := range files { + // Ignore directories + if file.IsDir() { + continue + } + + // Get the file name, convert to lowercase so ".JSON" is also valid + fName := strings.ToLower(file.Name()) + + // File name must end in .json + if !strings.HasSuffix(fName, ".json") { + continue + } + + // Split ".json" from the string name, and check that the remaining characters: + // - Add up to at least 17 characters (it must be a Discord snowflake) + // - Are all numbers + guildId := strings.Split(fName, ".json")[0] + if len(guildId) < 17 || guildId != EnsureNumbers(guildId) { + continue + } + + // Even though we are reading files, we need to make sure we can write to this file later + fPath := path.Join(GuildsDir, fName) + fd, err := windows.Open(fPath, windows.O_RDWR, 0) + if err != nil { + log.Errorf("File \"%s\" is not writable; guild %s WILL NOT be loaded! (%s)", fPath, guildId, err) + windows.Close(fd) + continue + } + // Close file handle since we are not writing to it. + windows.Close(fd) + // Try reading the file + jsonBytes, err := ioutil.ReadFile(fPath) + if err != nil { + log.Errorf("Failed to read \"%s\"; guild %s WILL NOT be loaded! (%s)", fPath, guildId, err) + continue + } + + // Unmarshal the json + var gInfo GuildInfo + err = json.Unmarshal(jsonBytes, &gInfo) + if err != nil { + log.Errorf("Failed to unmarshal \"%s\"; guild %s WILL NOT be loaded! (%s)", fPath, guildId, err) + continue + } + + // Add the loaded guild to the map + Guilds[guildId] = &Guild{ + ID: guildId, + Info: gInfo, + } + } + + if len(Guilds) == 0 { + log.Warningf("There are no guilds to load; data for new guilds will be saved to \"%s\"", GuildsDir) + return + } + + // :) + plural := "" + if len(Guilds) != 1 { + plural = "s" + } + + log.Infof("Loaded %d guild%s", len(Guilds), plural) +} + +// save +// Save a given guild object to .json +func (g *Guild) save() { + // See if a mutex exists for this guild, and create if not + if _, ok := saveLock[g.ID]; !ok { + saveLock[g.ID] = &sync.Mutex{} + } + + // Unlock writing when done + defer saveLock[g.ID].Unlock() + + // Mark this guild as locked before saving + saveLock[g.ID].Lock() + + // Create the output directory if it doesn't exist + // This is a fatal error, since no other guilds would be savable if this fails + if _, err := os.Stat(GuildsDir); os.IsNotExist(err) { + mkErr := os.Mkdir(GuildsDir, 0755) + if mkErr != nil { + log.Fatalf("Failed to create guild output directory: %s", mkErr) + } + } + + // Convert the guild object to text + jsonBytes, err := json.MarshalIndent(g.Info, "", " ") + if err != nil { + log.Fatalf("Failed marshalling JSON data for guild %s: %s", g.ID, err) + } + + // Write the contents to a file + outPath := path.Join(GuildsDir, g.ID+".json") + err = ioutil.WriteFile(outPath, jsonBytes, 0644) + if err != nil { + log.Fatalf("Write failed to %s: %s", outPath, err) + } +} + +// ReadDefaults +// TODO: WRITE DOCUMENTATION FOR THIS LMAO +func ReadDefaults(filePath string) (result []string) { + fPath := path.Clean(filePath) + if _, existErr := os.Stat(fPath); os.IsNotExist(existErr) { + log.Errorf("Failed to find \"%s\"; File WILL NOT be loaded! (%s)", fPath, existErr) + return + } + + jsonBytes, err := ioutil.ReadFile(fPath) + if err != nil { + log.Errorf("Failed to read \"%s\"; File WILL NOT be loaded! (%s)", fPath, err) + return + } + + err = json.Unmarshal(jsonBytes, &result) + if err != nil { + log.Errorf("Failed to unmarshal \"%s\"; File WILL NOT be loaded! (%s)", fPath, err) + } + return +} diff --git a/fs.go b/fs.go index a233d0f..5df8cc9 100644 --- a/fs.go +++ b/fs.go @@ -1,13 +1,16 @@ +//go:build darwin || linux +// +build darwin linux + package framework import ( "encoding/json" + "golang.org/x/sys/unix" "io/ioutil" "os" "path" "strings" "sync" - "syscall" ) // fs.go @@ -69,7 +72,7 @@ func loadGuilds() { // Even though we are reading files, we need to make sure we can write to this file later fPath := path.Join(GuildsDir, fName) - err := syscall.Access(fPath, syscall.O_RDWR) + err := unix.Access(fPath, unix.O_RDWR) if err != nil { log.Errorf("File \"%s\" is not writable; guild %s WILL NOT be loaded! (%s)", fPath, guildId, err) continue diff --git a/guilds.go b/guilds.go index af84682..7959672 100644 --- a/guilds.go +++ b/guilds.go @@ -3,38 +3,29 @@ package framework import ( "errors" "strings" - "sync" "time" "github.com/bwmarrin/discordgo" ) // guilds.go -// This file contains the structure of a guild, and all of the functions used to store and retrieve guild information +// This file contains the structure of a guild, and all the functions used to store and retrieve guild information // GuildInfo -// This is all of the settings and data that needs to be stored about a single guild +// This is all the settings and data that needs to be stored about a single guild type GuildInfo struct { - AddedDate int64 `json:"addedDate"` // The date the bot was added to the server - Prefix string `json:"prefix"` // The bot prefix - GuildLanguage string `json:"guildLanguage"` // Guilds default language todo make language per user - ModeratorIds []string `json:"moderatorIds"` // The list of user/role IDs allowed to run mod-only commands - WhitelistIds []string `json:"whitelistIds"` // List of user/role Ids that a user MUST have one of in order to run any commands, including public ones - IgnoredIds []string `json:"ignoredIds"` // List of user/role IDs that can never run commands, even public ones - WhitelistedChannels []string `json:"whitelistedChannels"` // List of channel IDs of whitelisted channels. If this list is non-empty, then only channels in this list can be used to invoke commands (unless the invoker is a bot moderator) - IgnoredChannels []string `json:"ignoredChannels"` // A list of channel IDs where commands will always be ignored, unless the user is a bot admin - BannedWordDetector bool `json:"banned_word_detector"` // Whether or not to detect banned words - GuildBannedWords []string `json:"guild_banned_words"` // List of banned words and phrases in this guild. Can use a command to update list. - BannedWordDetectorRoles []string `json:"banned_word_detector_roles"` // List of roles that the bot will not ignore - BannedWordDetectorChannels []string `json:"banned_word_detector_channels"` // List of channels that the bot will detect - GlobalDisabledTriggers []string `json:"globalDisabledTriggers"` // List of BotCommand triggers that can't be used anywhere in this guild - ChannelDisabledTriggers map[string][]string `json:"channelDisabledTriggers"` // List of channel IDs and the list of triggers that can't be used in it - CustomCommands map[string]CustomCommand `json:"customCommands"` // The list of triggers and their corresponding outputs for custom commands - DeletePolicy bool `json:"deletePolicy"` // Whether or not to delete BotCommand messages after a user sends them - ResponseChannelId string `json:"responseChannelId"` // The channelID of the channel to use for responses by default - MuteRoleId string `json:"muteRoleId"` // The role ID of the Mute role - MutedUsers map[string]int64 `json:"mutedUsers"` // The list of muted users, and the Unix timestamp of when their mute expired - Storage map[string]interface{} `json:"storage"` // Generic storage available to store anything not specific to the core bot + AddedDate int64 `json:"added_date"` + ChannelDisabledCommands map[string][]string `json:"channel_disabled_commands"` + DeletePolicy bool `json:"delete_policy"` + GlobalDisabledCommands []string `json:"global_disabled_commands"` + IgnoredChannels []string `json:"ignored_channels"` + IgnoredIds []string `json:"ignored_ids"` + ModeratorIds []string `json:"moderator_ids"` + Prefix string `json:"prefix,"` + ResponseChannelId string `json:"response_channel_id"` + Storage map[string]interface{} `json:"storage"` + WhitelistedChannels []string `json:"whitelisted_channels"` + WhitelistIds []string `json:"whitelist_ids"` } // Guild @@ -50,10 +41,6 @@ type Guild struct { // Otherwise, there will be information desync var Guilds = make(map[string]*Guild) -// muteLock -// A map to store mutexes for handling mutes for a server synchronously -var muteLock = make(map[string]*sync.Mutex) - // getGuild // Return a Guild object corresponding to the given guildId // If the guild doesn't exist, initialize a new guild and save it before returning @@ -64,23 +51,18 @@ func getGuild(guildId string) *Guild { return &Guild{ ID: "", Info: GuildInfo{ - AddedDate: time.Now().Unix(), - Prefix: "!", - DeletePolicy: false, - GuildLanguage: "en", - ResponseChannelId: "", - MuteRoleId: "", - GlobalDisabledTriggers: nil, - ChannelDisabledTriggers: make(map[string][]string), - CustomCommands: make(map[string]CustomCommand), - ModeratorIds: nil, - IgnoredIds: nil, - BannedWordDetector: false, - GuildBannedWords: nil, - BannedWordDetectorRoles: nil, - BannedWordDetectorChannels: nil, - MutedUsers: make(map[string]int64), - Storage: make(map[string]interface{}), + AddedDate: time.Now().Unix(), + ChannelDisabledCommands: nil, + DeletePolicy: false, + GlobalDisabledCommands: nil, + IgnoredChannels: nil, + IgnoredIds: nil, + ModeratorIds: nil, + Prefix: "!", + ResponseChannelId: "", + Storage: make(map[string]interface{}), + WhitelistedChannels: nil, + WhitelistIds: nil, }, } } @@ -91,23 +73,18 @@ func getGuild(guildId string) *Guild { newGuild := Guild{ ID: guildId, Info: GuildInfo{ - AddedDate: time.Now().Unix(), - Prefix: "!", - DeletePolicy: false, - GuildLanguage: "en", - ResponseChannelId: "", - MuteRoleId: "", - GlobalDisabledTriggers: nil, - ChannelDisabledTriggers: make(map[string][]string), - CustomCommands: make(map[string]CustomCommand), - ModeratorIds: nil, - IgnoredIds: nil, - BannedWordDetector: false, - GuildBannedWords: nil, - BannedWordDetectorRoles: nil, - BannedWordDetectorChannels: nil, - MutedUsers: make(map[string]int64), - Storage: make(map[string]interface{}), + AddedDate: time.Now().Unix(), + ChannelDisabledCommands: nil, + DeletePolicy: false, + GlobalDisabledCommands: nil, + IgnoredChannels: nil, + IgnoredIds: nil, + ModeratorIds: nil, + Prefix: "!", + ResponseChannelId: "", + Storage: make(map[string]interface{}), + WhitelistedChannels: nil, + WhitelistIds: nil, }, } // Add the new guild to the map of guilds @@ -280,13 +257,6 @@ func (g *Guild) SetPrefix(newPrefix string) { g.save() } -// SetLang -// Set the prefix, then save the guild data -func (g *Guild) SetLang(lang string) { - g.Info.GuildLanguage = lang - g.save() -} - // IsMod // Check if a given ID is a moderator or not func (g *Guild) IsMod(checkId string) bool { @@ -611,9 +581,9 @@ func (g *Guild) RemoveChannelFromIgnored(channelId string) error { } // IsGloballyDisabled -// Check if a given trigger is globally disabled +// Check if a given command is globally disabled func (g *Guild) IsGloballyDisabled(trigger string) bool { - for _, disabled := range g.Info.GlobalDisabledTriggers { + for _, disabled := range g.Info.GlobalDisabledCommands { if strings.ToLower(disabled) == strings.ToLower(trigger) { return true } @@ -622,33 +592,33 @@ func (g *Guild) IsGloballyDisabled(trigger string) bool { return false } -// EnableTriggerGlobally -// Remove a trigger from the list of *globally disabled* triggers -func (g *Guild) EnableTriggerGlobally(trigger string) error { +// EnableCommandGlobally +// Remove a command from the list of *globally disabled* triggers +func (g *Guild) EnableCommandGlobally(trigger string) error { if !g.IsGloballyDisabled(trigger) { return errors.New("trigger is not disabled; nothing to enable") } - g.Info.GlobalDisabledTriggers = RemoveItem(g.Info.GlobalDisabledTriggers, trigger) + g.Info.GlobalDisabledCommands = RemoveItem(g.Info.GlobalDisabledCommands, trigger) g.save() return nil } -// DisableTriggerGlobally -// Add a trigger to the list of *globally disabled* triggers -func (g *Guild) DisableTriggerGlobally(trigger string) error { - if g.IsGloballyDisabled(trigger) { - return errors.New("trigger is not enabled; nothing to disable") +// DisableCommandGlobally +// Add a command to the list of *globally disabled* commands +func (g *Guild) DisableCommandGlobally(command string) error { + if g.IsGloballyDisabled(command) { + return errors.New("command is not enabled; nothing to disable") } - g.Info.GlobalDisabledTriggers = append(g.Info.GlobalDisabledTriggers, trigger) + g.Info.GlobalDisabledCommands = append(g.Info.GlobalDisabledCommands, command) g.save() return nil } -// TriggerIsDisabledInChannel -// Check if a given trigger is disabled in the given channel -func (g *Guild) TriggerIsDisabledInChannel(trigger string, channelId string) bool { +// CommandIsDisabledInChannel +// Check if a given command is disabled in the given channel +func (g *Guild) CommandIsDisabledInChannel(command string, channelId string) bool { cleanedId := CleanId(channelId) if cleanedId == "" { return true @@ -659,16 +629,16 @@ func (g *Guild) TriggerIsDisabledInChannel(trigger string, channelId string) boo } // Iterate over every channel ID (the map key) and their internal list of disabled triggers - for channel, triggers := range g.Info.ChannelDisabledTriggers { + for channel, commands := range g.Info.ChannelDisabledCommands { // If the channel matches our current channel, continue if channel == cleanedId { // For every disabled trigger in the list... - for _, disabled := range triggers { + for _, disabled := range commands { // If the current trigger matches a disabled one, return true - if disabled == trigger { + if disabled == command { return true } } @@ -678,83 +648,43 @@ func (g *Guild) TriggerIsDisabledInChannel(trigger string, channelId string) boo return false } -// EnableTriggerInChannel -// Given a trigger and channel ID, remove that trigger from that channel's list of blocked triggers -func (g *Guild) EnableTriggerInChannel(trigger string, channelId string) error { +// EnableCommandInChannel +// Given a command and channel ID, remove that command from that channel's list of blocked comamnds +func (g *Guild) EnableCommandInChannel(command string, channelId string) error { cleanedId := CleanId(channelId) if cleanedId == "" { return errors.New("provided channel ID is invalid") } - if !g.TriggerIsDisabledInChannel(trigger, cleanedId) { - return errors.New("that trigger is not disabled in this channel; nothing to enable") + if !g.CommandIsDisabledInChannel(command, cleanedId) { + return errors.New("that command is not disabled in this channel; nothing to enable") } // Remove the trigger from THIS channel's list - g.Info.ChannelDisabledTriggers[cleanedId] = RemoveItem(g.Info.ChannelDisabledTriggers[cleanedId], trigger) + g.Info.ChannelDisabledCommands[cleanedId] = RemoveItem(g.Info.ChannelDisabledCommands[cleanedId], command) // If there are no more items, delete the entire channel list, otherwise it will appear as null in the json - if len(g.Info.ChannelDisabledTriggers[cleanedId]) == 0 { - delete(g.Info.ChannelDisabledTriggers, cleanedId) + if len(g.Info.ChannelDisabledCommands[cleanedId]) == 0 { + delete(g.Info.ChannelDisabledCommands, cleanedId) } g.save() return nil } -// DisableTriggerInChannel -// Given a trigger and channel ID, add that trigger to that channel's list of blocked triggers -func (g *Guild) DisableTriggerInChannel(trigger string, channelId string) error { +// DisableCommandInChannel +// Given a command and channel ID, add that command to that channel's list of blocked commands +func (g *Guild) DisableTriggerInChannel(command string, channelId string) error { cleanedId := CleanId(channelId) if cleanedId == "" { return errors.New("provided channel ID is invalid") } - if g.TriggerIsDisabledInChannel(trigger, cleanedId) { + if g.CommandIsDisabledInChannel(command, cleanedId) { return errors.New("that trigger is already disabled in this channel; nothing to disable") } - g.Info.ChannelDisabledTriggers[cleanedId] = append(g.Info.ChannelDisabledTriggers[cleanedId], trigger) - g.save() - return nil -} - -// IsCustomCommand -// Check if a given trigger is a custom command in this guild -func (g *Guild) IsCustomCommand(trigger string) bool { - if _, ok := g.Info.CustomCommands[strings.ToLower(trigger)]; ok { - return true - } - return false -} - -// AddCustomCommand -// Add a custom command to this guild -func (g *Guild) AddCustomCommand(trigger string, content string, public bool) error { - if g.IsCustomCommand(trigger) { - return errors.New("the provided trigger is already a custom command") - } - - if _, ok := commands[trigger]; ok { - return errors.New("custom command would have overridden a core command") - } - - g.Info.CustomCommands[trigger] = CustomCommand{ - Content: content, - InvokeCount: 0, - Public: public, - } - g.save() - return nil -} - -// RemoveCustomCommand -// Remove a custom command from this guild -func (g *Guild) RemoveCustomCommand(trigger string) error { - if !g.IsCustomCommand(trigger) { - return errors.New("the provided trigger is not a custom command") - } - delete(g.Info.CustomCommands, trigger) + g.Info.ChannelDisabledCommands[cleanedId] = append(g.Info.ChannelDisabledCommands[cleanedId], command) g.save() return nil } @@ -785,116 +715,6 @@ func (g *Guild) SetResponseChannel(channelId string) error { return nil } -// SetMuteRole -// Set the role ID to use for issuing mutes, then save the guild data -func (g *Guild) SetMuteRole(roleId string) error { - // Try grabbing the role first (we don't use IsRole since we need the real ID) - role, err := g.GetRole(roleId) - if err != nil { - return err - } - g.Info.MuteRoleId = role.ID - g.save() - return nil -} - -// HasMuteRecord -// Check if a member with a given ID has a mute record -// To check if they are actually muted, use g.HasRole -func (g *Guild) HasMuteRecord(userId string) bool { - // Check if the member exists - member, err := g.GetMember(userId) - if err != nil { - return false - } - - // Check if the member is in the list of mutes - if _, ok := g.Info.MutedUsers[member.User.ID]; ok { - return true - } - - return false -} - -// Mute -// Mute a user for the specified duration, apply the mute role, and write a mute record to the guild info -func (g *Guild) Mute(userId string, duration int64) error { - // Make sure the mute role exists - muteRole, err := g.GetRole(g.Info.MuteRoleId) - if err != nil { - return err - } - - // Make sure the member exists - member, err := g.GetMember(userId) - if err != nil { - return err - } - - // Create a mute mutex for this guild if it does not exist - if _, ok := muteLock[g.ID]; !ok { - muteLock[g.ID] = &sync.Mutex{} - } - - // Lock this guild's mute activity so there is no desync - defer muteLock[g.ID].Unlock() - muteLock[g.ID].Lock() - - // Try muting the member - err = Session.GuildMemberRoleAdd(g.ID, member.User.ID, muteRole.ID) - if err != nil { - return err - } - - // If the duration is not 0 (indefinite mute), add the current time to the duration - if duration != 0 { - duration += time.Now().Unix() - } - - // Record this mute record - g.Info.MutedUsers[member.User.ID] = duration - g.save() - - return nil -} - -// UnMute -// Unmute a user; expiry checks will not be done here, this is a direct unmute -func (g *Guild) UnMute(userId string) error { - // Make sure the mute role exists - muteRole, err := g.GetRole(g.Info.MuteRoleId) - if err != nil { - return err - } - - // Make sure the member exists - member, err := g.GetMember(userId) - if err != nil { - return err - } - - // Create a mute mutex for this guild if it does not exist - if _, ok := muteLock[g.ID]; !ok { - muteLock[g.ID] = &sync.Mutex{} - } - - // Lock this guild's mute activity so there is no desync - defer muteLock[g.ID].Unlock() - muteLock[g.ID].Lock() - - // Delete the mute record if it exists - delete(g.Info.MutedUsers, member.User.ID) - g.save() - - // Try unmuting the user - err = Session.GuildMemberRoleRemove(g.ID, member.User.ID, muteRole.ID) - if err != nil { - return err - } - - return nil -} - // Kick // Kick a member func (g *Guild) Kick(userId string, reason string) error { @@ -1133,108 +953,3 @@ func (g *Guild) GetCommandUsage(cmd CommandInfo) string { } return "```\n" + output + "\n```" } - -// IsSniperEnabled -// Checks to see if the sniper module is enabled -func (g *Guild) IsSniperEnabled() bool { - return g.Info.BannedWordDetector -} - -// IsSnipeable -// Checks to see if the sniper module can snipe this role -func (g *Guild) IsSnipeable(authorID string) bool { - if Session.State.Ready.User != nil && authorID == Session.State.Ready.User.ID { - return false - } - if g.MemberOrRoleInList(authorID, g.Info.BannedWordDetectorRoles) { - return false - } - return true -} - -// IsSniperChannel -// Checks to see if the channel is in the channel list -func (g *Guild) IsSniperChannel(channelID string) bool { - for _, id := range g.Info.BannedWordDetectorChannels { - if id == channelID { - return true - } - } - return false -} - -// SetSniper -// Sets the state of the sniper -func (g *Guild) SetSniper(value bool) bool { - g.Info.BannedWordDetector = value - g.save() - return value -} - -// BulkAddWords -// Allows you to bulk add words to the banned word detector -func (g *Guild) BulkAddWords(words []string) []string { - g.Info.GuildBannedWords = append(g.Info.GuildBannedWords, words...) - g.save() - return g.Info.GuildBannedWords -} - -// AddWord -// Allows you to add a word to the banned word detector -func (g *Guild) AddWord(word string) []string { - g.Info.GuildBannedWords = append(g.Info.GuildBannedWords, word) - g.save() - return g.Info.GuildBannedWords -} - -// RemoveWord -// Allows you to remove a word from the banned word detector -func (g *Guild) RemoveWord(word string) []string { - g.Info.GuildBannedWords = RemoveItem(g.Info.GuildBannedWords, word) - g.save() - return g.Info.GuildBannedWords -} - -// SetSniperRole -// Allows you to add a role to the sniper -func (g *Guild) SetSniperRole(roleID string) []string { - if g.IsRole(roleID) { - g.Info.BannedWordDetectorRoles = append(g.Info.BannedWordDetectorRoles, roleID) - g.save() - return g.Info.BannedWordDetectorRoles - } - return g.Info.BannedWordDetectorRoles -} - -// SetSniperChannel -// Allows you to add a channel to the sniper -func (g *Guild) SetSniperChannel(channelID string) []string { - if g.IsChannel(channelID) { - g.Info.BannedWordDetectorChannels = append(g.Info.BannedWordDetectorChannels, channelID) - g.save() - return g.Info.BannedWordDetectorChannels - } - return g.Info.BannedWordDetectorChannels -} - -// UnsetSniperRole -// Allows you to remove a role from the sniper -func (g *Guild) UnsetSniperRole(roleID string) []string { - if g.IsRole(roleID) { - g.Info.BannedWordDetectorRoles = RemoveItem(g.Info.BannedWordDetectorRoles, roleID) - g.save() - return g.Info.BannedWordDetectorRoles - } - return g.Info.BannedWordDetectorRoles -} - -// UnsetSniperChannel -// Allows you to remove a channel from the sniper -func (g *Guild) UnsetSniperChannel(channelID string) []string { - if g.IsChannel(channelID) { - g.Info.BannedWordDetectorChannels = RemoveItem(g.Info.BannedWordDetectorChannels, channelID) - g.save() - return g.Info.BannedWordDetectorChannels - } - return g.Info.BannedWordDetectorChannels -} diff --git a/interaction.go b/interaction.go index 918fa94..71b5b7b 100644 --- a/interaction.go +++ b/interaction.go @@ -117,8 +117,8 @@ func handleInteractionCommand(s *discordgo.Session, i *discordgo.InteractionCrea return } - // Ignore the command if this channel has blocked the trigger - if g.TriggerIsDisabledInChannel(trigger, i.ChannelID) { + // Ignore the command if this channel has blocked the command + if g.CommandIsDisabledInChannel(trigger, i.ChannelID) { ErrorResponse(i.Interaction, "Command is disabled in this channel!", trigger) return } diff --git a/response.go b/response.go index ab686e3..ce2b22f 100644 --- a/response.go +++ b/response.go @@ -1,7 +1,6 @@ package framework import ( - "fmt" "time" "github.com/bwmarrin/discordgo" @@ -94,72 +93,6 @@ func NewResponse(ctx *Context, messageComponents bool, ephemeral bool) *Response }) } } - // If the command context is not empty, append the command - if ctx.Cmd.Trigger != "" { - // Get the command used as a string, and all interpreted arguments, so it can be a part of the output - commandUsed := "" - if r.Ctx.Cmd.IsChild { - commandUsed = fmt.Sprintf("%s%s %s", r.Ctx.Guild.Info.Prefix, r.Ctx.Cmd.ParentID, r.Ctx.Cmd.Trigger) - } else { - commandUsed = r.Ctx.Guild.Info.Prefix + r.Ctx.Cmd.Trigger - } - // Just makes the thing prettier - if ctx.Interaction != nil { - commandUsed = "/" + r.Ctx.Cmd.Trigger - } - for _, k := range r.Ctx.Cmd.Arguments.Keys() { - arg := ctx.Args[k] - if arg.StringValue() == "" { - continue - } - vv, ok := r.Ctx.Cmd.Arguments.Get(k) - - if ok { - argInfo := vv.(*ArgInfo) - switch argInfo.TypeGuard { - case Int: - fallthrough - case Boolean: - fallthrough - case String: - commandUsed += " " + arg.StringValue() - break - case User: - user, err := arg.UserValue(Session) - if err != nil { - commandUsed += " " + arg.StringValue() - } else { - commandUsed += " " + user.Mention() - } - case Role: - role, err := arg.RoleValue(Session, r.Ctx.Guild.ID) - if err != nil { - commandUsed += " " + arg.StringValue() - } else { - commandUsed += " " + role.Mention() - } - case Channel: - channel, err := arg.ChannelValue(Session) - if err != nil { - commandUsed += " " + arg.StringValue() - } else { - commandUsed += " " + channel.Mention() - } - } - } else { - commandUsed += " " + arg.StringValue() - } - } - - commandUsed = "```\n" + commandUsed + "\n```" - - r.AppendField("Command used:", commandUsed, false) - } - - // If the message is not nil, append an invoker - if ctx.Message != nil { - r.AppendField("Invoked by:", r.Ctx.Message.Author.Mention(), false) - } return r } @@ -441,9 +374,6 @@ func ErrorResponse(i *discordgo.Interaction, errorMsg string, trigger string) { func (r *Response) AcknowledgeInteraction() { Session.InteractionRespond(r.Ctx.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "<:loadingdots:759625992166965288>", - }, }) r.Loading = true } diff --git a/util.go b/util.go index 77047cf..889479d 100644 --- a/util.go +++ b/util.go @@ -89,10 +89,10 @@ func CleanId(in string) string { } // ExtractCommand -// Given a message, attempt to extract a command trigger and command arguments out of it +// Given a message, attempt to extract a commands trigger and command arguments out of it // If there is no prefix, try using a bot mention as the prefix func ExtractCommand(guild *GuildInfo, message string) (*string, *string) { - // Check if the message starts with the bot trigger + // Check if the message starts with the bot prefix if strings.HasPrefix(message, guild.Prefix) { // Split the message on the prefix, but ensure only 2 fields are returned // This ensures messages containing multiple instances of the prefix don't split multiple times