tons of clean up, removed stuff from the upstream codebase
This commit is contained in:
parent
8e2b31059a
commit
3376c69e86
|
|
@ -1,7 +0,0 @@
|
|||
package framework
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestParseArguments(t *testing.T) {
|
||||
|
||||
}
|
||||
118
commands.go
118
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
7
fs.go
7
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
|
||||
|
|
|
|||
423
guilds.go
423
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
70
response.go
70
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
|
||||
}
|
||||
|
|
|
|||
4
util.go
4
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
|
||||
|
|
|
|||
Loading…
Reference in New Issue