update from upstream

This commit is contained in:
vel 2021-08-16 22:05:33 -07:00
parent f743f1efbd
commit 3e3f761b01
Signed by: velvox
GPG Key ID: 1C8200C1D689CEF5
7 changed files with 267 additions and 190 deletions

View File

@ -45,6 +45,7 @@ var (
SubCmd ArgTypeGuards = "subcmd" SubCmd ArgTypeGuards = "subcmd"
SubCmdGrp ArgTypeGuards = "subcmdgrp" SubCmdGrp ArgTypeGuards = "subcmdgrp"
ArrString ArgTypeGuards = "arrString" ArrString ArgTypeGuards = "arrString"
Time ArgTypeGuards = "time"
) )
// ArgInfo // ArgInfo
@ -75,7 +76,7 @@ type Arguments map[string]CommandArg
// CreateCommandInfo // CreateCommandInfo
// Creates a pointer to a CommandInfo // Creates a pointer to a CommandInfo
func CreateCommandInfo(trigger string, description string, public bool, group string) *CommandInfo { func CreateCommandInfo(trigger string, description string, public bool, group Group) *CommandInfo {
cI := &CommandInfo{ cI := &CommandInfo{
Aliases: nil, Aliases: nil,
Arguments: orderedmap.New(), Arguments: orderedmap.New(),
@ -88,6 +89,13 @@ func CreateCommandInfo(trigger string, description string, public bool, group st
return cI return cI
} }
// CreateRawCmdInfo
// Creates a pointer to a CommandInfo
func CreateRawCmdInfo(cI *CommandInfo) *CommandInfo {
cI.Arguments = orderedmap.New()
return cI
}
// SetParent // SetParent
// Sets the parent properties // Sets the parent properties
func (cI *CommandInfo) SetParent(isParent bool, parentID string) { func (cI *CommandInfo) SetParent(isParent bool, parentID string) {
@ -248,34 +256,7 @@ func findAllOptionArgs(argString []string, keys []string, infoArgs *orderedmap.O
modifiedArgString := "" modifiedArgString := ""
var modKeys []string var modKeys []string
var indexes []int var indexes []int
// If the length of the argString is equal to the length of keys
// We can just set each value in argString to a CommandArg
// If a match field is equal to the content we will return early.
// This is a rare occurrence. But this allows for a faster result
if len(argString) == len(keys) {
for i, v := range argString {
// error handling
iA, ok := infoArgs.Get(keys[i])
if !ok {
err := errors.New(fmt.Sprintf("Unable to find map relating to key: %s", keys[i]))
SendErrorReport("", "", "", "Argument Parsing error", err)
continue
}
vv := iA.(*ArgInfo)
// ArgContent type should always be the last item in the slice
// Should be safe to return early
if vv.Match == ArgContent {
modifiedArgString = strings.Join(argString[i:], " ")
modKeys = RemoveItems(keys, indexes)
return *args, true, createSplitString(modifiedArgString), modKeys
}
if checkTypeGuard(v, vv.TypeGuard) {
(*args)[keys[i]] = handleArgOption(v, *vv)
indexes = append(indexes, i)
}
}
return *args, false, createSplitString(modifiedArgString), modKeys
}
// (semi) Brute force method // (semi) Brute force method
// First lets find all required args // First lets find all required args
currentPos := 0 currentPos := 0
@ -314,8 +295,13 @@ func findAllOptionArgs(argString []string, keys []string, infoArgs *orderedmap.O
argString = argString[currentPos:] argString = argString[currentPos:]
indexes = nil indexes = nil
currentPos = 0 currentPos = 0
// Return early if the argument parser has found all args
if argString == nil || len(argString) == 0 || len(modKeys) == 0 || modKeys == nil {
return *args, false, argString, modKeys
}
// Now lets find the not required args // Now lets find the not required args
for _, v := range modKeys { for i, v := range modKeys {
// error handling // error handling
iA, ok := infoArgs.Get(v) iA, ok := infoArgs.Get(v)
if !ok { if !ok {
@ -331,8 +317,8 @@ func findAllOptionArgs(argString []string, keys []string, infoArgs *orderedmap.O
break break
} }
if vv.Match == ArgContent { if vv.Match == ArgContent {
modifiedArgString = strings.Join(argString, " ") modKeys = RemoveItems(modKeys, indexes)
return *args, true, createSplitString(modifiedArgString), modKeys return *args, true, argString, modKeys
} }
// Break early if current pos is the length of the array // Break early if current pos is the length of the array
if currentPos == len(argString) { if currentPos == len(argString) {
@ -342,9 +328,11 @@ func findAllOptionArgs(argString []string, keys []string, infoArgs *orderedmap.O
var value string var value string
value, argString = findTypeGuard(strings.Join(argString, " "), argString, vv.TypeGuard) value, argString = findTypeGuard(strings.Join(argString, " "), argString, vv.TypeGuard)
(*args)[v] = handleArgOption(value, *vv) (*args)[v] = handleArgOption(value, *vv)
indexes = append(indexes, i)
} else if checkTypeGuard(argString[currentPos], vv.TypeGuard) { } else if checkTypeGuard(argString[currentPos], vv.TypeGuard) {
(*args)[v] = handleArgOption(argString[currentPos], *vv) (*args)[v] = handleArgOption(argString[currentPos], *vv)
currentPos++ currentPos++
indexes = append(indexes, i)
} else { } else {
} }
@ -356,7 +344,12 @@ func findAllOptionArgs(argString []string, keys []string, infoArgs *orderedmap.O
func findTypeGuard(input string, array []string, typeguard ArgTypeGuards) (string, []string) { func findTypeGuard(input string, array []string, typeguard ArgTypeGuards) (string, []string) {
switch typeguard { switch typeguard {
case Int: case Int:
if match, isMatch := Misc["int"].FindStringMatch(input); isMatch == nil && match != nil { if match, isMatch := TypeGuard["int"].FindStringMatch(input); isMatch == nil && match != nil {
return match.String(), RemoveItem(array, match.String())
}
return "", array
case Boolean:
if match, isMatch := TypeGuard["boolean"].FindStringMatch(input); isMatch == nil && match != nil {
return match.String(), RemoveItem(array, match.String()) return match.String(), RemoveItem(array, match.String())
} }
return "", array return "", array
@ -391,8 +384,18 @@ func findTypeGuard(input string, array []string, typeguard ArgTypeGuards) (strin
return match.String(), RemoveItem(array, match.String()) return match.String(), RemoveItem(array, match.String())
} }
return "", array return "", array
case Time:
match := strings.Join(FindAllString(TimeRegexes["all"], input), "")
//if match, isMatch := TimeRegexes["all"].Mat(input); isMatch == nil && match != nil {
// return match.String(), RemoveItem(array, match.String())
//}
if match != "" {
return match, RemoveItem(array, match)
} }
return "", array return "", array
default:
return "", array
}
} }
func findAllFlags(argString string, keys []string, infoArgs *orderedmap.OrderedMap, args *Arguments) ([]string, Arguments, []string) { func findAllFlags(argString string, keys []string, infoArgs *orderedmap.OrderedMap, args *Arguments) ([]string, Arguments, []string) {

View File

@ -2,19 +2,22 @@ package framework
import ( import (
"github.com/QPixel/orderedmap" "github.com/QPixel/orderedmap"
"runtime"
"strings"
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
"runtime"
"runtime/debug"
"strings"
"time"
) )
// commands.go // commands.go
// This file contains everything required to add core commands to the bot, and parse commands from a message // This file contains everything required to add core commands to the bot, and parse commands from a message
// GroupTypes // GroupTypes
const ( type Group string
Moderation = "moderation"
Utility = "utility" var (
Moderation Group = "moderation"
Utility Group = "utility"
) )
// CommandInfo // CommandInfo
@ -23,7 +26,7 @@ type CommandInfo struct {
Aliases []string // Aliases for the normal trigger Aliases []string // Aliases for the normal trigger
Arguments *orderedmap.OrderedMap // Arguments for the command Arguments *orderedmap.OrderedMap // Arguments for the command
Description string // A short description of what the command does Description string // A short description of what the command does
Group string // The group this command belongs to Group Group // The group this command belongs to
ParentID string // The ID of the parent command ParentID string // The ID of the parent command
Public bool // Whether non-admins and non-mods can use this command Public bool // Whether non-admins and non-mods can use this command
IsTyping bool // Whether the command will show a typing thing when ran. IsTyping bool // Whether the command will show a typing thing when ran.
@ -86,6 +89,9 @@ var commandAliases = make(map[string]string)
// This is also private so other commands cannot modify it // This is also private so other commands cannot modify it
var slashCommands = make(map[string]discordgo.ApplicationCommand) var slashCommands = make(map[string]discordgo.ApplicationCommand)
// commandsGC
var commandsGC = 0
// AddCommand // AddCommand
// Add a command to the bot // Add a command to the bot
func AddCommand(info *CommandInfo, function BotFunction) { func AddCommand(info *CommandInfo, function BotFunction) {
@ -227,13 +233,6 @@ func commandHandler(session *discordgo.Session, message *discordgo.MessageCreate
} }
} }
// 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)
}
}
if !isCustom { if !isCustom {
//Get the command to run //Get the command to run
// Error Checking // Error Checking
@ -253,6 +252,14 @@ func commandHandler(session *discordgo.Session, message *discordgo.MessageCreate
if command.Info.IsTyping && g.Info.ResponseChannelId == "" { if command.Info.IsTyping && g.Info.ResponseChannelId == "" {
_ = Session.ChannelTyping(message.ChannelID) _ = 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) defer handleCommandError(g.ID, channel.ID, message.Author.ID)
if command.Info.IsParent { if command.Info.IsParent {
handleChildCommand(*argString, command, message.Message, g) handleChildCommand(*argString, command, message.Message, g)
@ -264,6 +271,13 @@ func commandHandler(session *discordgo.Session, message *discordgo.MessageCreate
Args: *ParseArguments(*argString, command.Info.Arguments), Args: *ParseArguments(*argString, command.Info.Arguments),
Message: message.Message, Message: message.Message,
}) })
// Makes sure that variables ran in ParseArguments are gone.
if commandsGC == 25 && commandsGC > 25 {
debug.FreeOSMemory()
commandsGC = 0
} else {
commandsGC++
}
return return
} }
} }
@ -272,24 +286,9 @@ func commandHandler(session *discordgo.Session, message *discordgo.MessageCreate
// -- Helper Methods // -- Helper Methods
func handleChildCommand(argString string, command Command, message *discordgo.Message, g *Guild) { func handleChildCommand(argString string, command Command, message *discordgo.Message, g *Guild) {
split := strings.SplitN(argString, " ", 2) split := strings.SplitN(argString, " ", 2)
// First lets see if this subcmd even exists
v, ok := command.Info.Arguments.Get("subcmdgrp")
// the command doesn't even have a subcmdgrp arg, return
if !ok {
return
}
choices := v.(*ArgInfo).Choices childCmd, ok := childCommands[command.Info.Trigger][split[0]]
subCmdExist := false if !ok {
for _, choice := range choices {
if split[0] != choice {
continue
} else {
subCmdExist = true
break
}
}
if !subCmdExist {
command.Function(&Context{ command.Function(&Context{
Guild: g, Guild: g,
Cmd: command.Info, Cmd: command.Info,
@ -298,12 +297,11 @@ func handleChildCommand(argString string, command Command, message *discordgo.Me
}) })
return return
} }
childCmd, ok := childCommands[command.Info.Trigger][split[0]] if len(split) < 2 {
if !ok || len(split) < 2 { childCmd.Function(&Context{
command.Function(&Context{
Guild: g, Guild: g,
Cmd: command.Info, Cmd: childCmd.Info,
Args: nil, Args: *ParseArguments("", childCmd.Info.Arguments),
Message: message, Message: message,
}) })
return return
@ -326,6 +324,7 @@ func handleCommandError(gID string, cId string, uId string) {
if err != nil { if err != nil {
log.Errorf("err sending message %s", err) log.Errorf("err sending message %s", err)
} }
time.Sleep(5 * time.Second)
_ = Session.ChannelMessageDelete(cId, message.ID) _ = Session.ChannelMessageDelete(cId, message.ID)
return return
} }

View File

@ -11,20 +11,19 @@ var (
"hours": regexp2.MustCompile("^[0-9]+h$", 0), "hours": regexp2.MustCompile("^[0-9]+h$", 0),
"days": regexp2.MustCompile("^[0-9]+d$", 0), "days": regexp2.MustCompile("^[0-9]+d$", 0),
"weeks": regexp2.MustCompile("^[0-9]+w$", 0), "weeks": regexp2.MustCompile("^[0-9]+w$", 0),
"years": regexp2.MustCompile("^[0-9]+y$", 0), "years": regexp2.MustCompile("[0-9]+y", 0),
"all": regexp2.MustCompile("(([0-9]+)(s|m|h|d|w|y))", 0),
} }
MentionStringRegexes = regex{ MentionStringRegexes = regex{
"all": regexp2.MustCompile("<((@!?\\d+)|(#?\\d+)|(@&?\\d+))>", 0), "all": regexp2.MustCompile("<((@!?\\d+)|(#?\\d+)|(@&?\\d+))>", 0),
"role": regexp2.MustCompile("<((@&?\\d+))>", 0), "role": regexp2.MustCompile("<((@&?\\d+))>", 0),
"user": regexp2.MustCompile("<((@!?\\d+))>", 0), "user": regexp2.MustCompile("<((@!?\\d+))>", 0),
"channel": regexp2.MustCompile("<((#?\\d+))>", 0), "channel": regexp2.MustCompile("<((#?\\d+))>", 0),
"id": regexp2.MustCompile("^[0-9]{18}$", 0), "id": regexp2.MustCompile("^[0-9]{18}", 0),
} }
TypeGuard = regex{ TypeGuard = regex{
"message_url": regexp2.MustCompile("((https:\\/\\/canary.discord.com\\/channels\\/)+([0-9]{18})\\/+([0-9]{18})\\/+([0-9]{18})$)", regexp2.IgnoreCase|regexp2.Multiline), "message_url": regexp2.MustCompile("((https:\\/\\/canary.discord.com\\/channels\\/)+([0-9]{18})\\/+([0-9]{18})\\/+([0-9]{18})$)", regexp2.IgnoreCase|regexp2.Multiline),
}
Misc = regex{
"quoted_string": regexp2.MustCompile("^\"[a-zA-Z0-9]+\"$", 0),
"int": regexp2.MustCompile("\\b(0*(?:[0-9]{1,8}))\\b", 0), "int": regexp2.MustCompile("\\b(0*(?:[0-9]{1,8}))\\b", 0),
"boolean": regexp2.MustCompile("\\b((?:true|false))\\b", 0),
} }
) )

68
core.go
View File

@ -1,11 +1,12 @@
package framework package framework
import ( import (
"fmt"
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
tlog "github.com/ubergeek77/tinylog" tlog "github.com/ubergeek77/tinylog"
"os" "os"
"os/signal" "os/signal"
"runtime"
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
@ -15,13 +16,17 @@ import (
// This file contains the main code responsible for driving core bot functionality // This file contains the main code responsible for driving core bot functionality
// messageState // messageState
// Tells discordgo the amount of mesesages to cache // Tells discordgo the amount of messages to cache
var messageState = 20 var messageState = 500
// log // log
// The logger for the core bot // The logger for the core bot
var log = tlog.NewTaggedLogger("BotCore", tlog.NewColor("38;5;111")) var log = tlog.NewTaggedLogger("BotCore", tlog.NewColor("38;5;111"))
// dlog
// The logger for discordgo
var dlog = tlog.NewTaggedLogger("DG", tlog.NewColor("38;5;111"))
// Session // Session
// The Discord session, made public so commands can use it // The Discord session, made public so commands can use it
var Session *discordgo.Session var Session *discordgo.Session
@ -33,9 +38,6 @@ var Session *discordgo.Session
// This is a boolean map, because checking its values is dead simple this way // This is a boolean map, because checking its values is dead simple this way
var botAdmins = make(map[string]bool) var botAdmins = make(map[string]bool)
// BotPresence
var botPresence discordgo.GatewayStatusUpdate
// BotToken // BotToken
// A string of the current bot token, usually set by the main method // A string of the current bot token, usually set by the main method
// Similar to BotAdmins, this isn't saved to .json and is added programmatically // Similar to BotAdmins, this isn't saved to .json and is added programmatically
@ -77,15 +79,32 @@ func IsCommand(trigger string) bool {
return false return false
} }
// SetPresence // dgoLog
// Sets the presence struct after a session has been created // Allows for discordgo to call tinylog
func SetPresence(presence discordgo.GatewayStatusUpdate) { func dgoLog(msgL, caller int, format string, a ...interface{}) {
botPresence = presence pc, file, line, _ := runtime.Caller(caller)
return files := strings.Split(file, "/")
file = files[len(files)-1]
name := runtime.FuncForPC(pc).Name()
fns := strings.Split(name, ".")
name = fns[len(fns)-1]
msg := fmt.Sprintf(format, a...)
switch msgL {
case discordgo.LogError:
dlog.Errorf("%s:%d:%s() %s", file, line, name, msg)
case discordgo.LogWarning:
dlog.Warningf("%s:%d:%s() %s", file, line, name, msg)
case discordgo.LogInformational:
dlog.Infof("%s:%d:%s() %s", file, line, name, msg)
case discordgo.LogDebug:
dlog.Debugf("%s:%d:%s() %s", file, line, name, msg)
}
} }
// Start the bot! // Start uberbot!
func Start() { func Start() {
discordgo.Logger = dgoLog
// Load all the guilds // Load all the guilds
loadGuilds() loadGuilds()
@ -103,8 +122,19 @@ func Start() {
} }
// Setup State specific variables // Setup State specific variables
Session.State.MaxMessageCount = messageState Session.State.MaxMessageCount = messageState
Session.LogLevel = discordgo.LogWarning
Session.SyncEvents = false
Session.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsAll) Session.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsAll)
Session.Identify.Presence = botPresence // Set the bots status
Session.Identify.Presence = discordgo.GatewayStatusUpdate{
Game: discordgo.Activity{
Name: "Mega Man Battle Network",
Type: 3,
},
Status: "dnd",
AFK: true,
Since: 91879201,
}
// Open the session // Open the session
log.Info("Connecting to Discord...") log.Info("Connecting to Discord...")
err = Session.Open() err = Session.Open()
@ -117,6 +147,7 @@ func Start() {
// Add the slash command handler to the list of user-defined handlers // Add the slash command handler to the list of user-defined handlers
AddHandler(handleInteraction) AddHandler(handleInteraction)
// Add the handlers to the session // Add the handlers to the session
addHandlers() addHandlers()
@ -141,18 +172,19 @@ func Start() {
log.Warning("You have not added any bot admins! Only moderators will be able to run commands, and permissions cannot be changed!") log.Warning("You have not added any bot admins! Only moderators will be able to run commands, and permissions cannot be changed!")
} }
// Register slash commands // Register slash commands
slashChannel := make(chan string) //slashChannel := make(chan string)
log.Info("Registering slash commands") //log.Info("Registering slash commands")
go AddSlashCommands("833901685054242846", slashChannel) //go AddSlashCommands("833901685054242846", slashChannel)
// Bot ready // Bot ready
log.Info("Initialization complete! The bot is now ready.") log.Info("Initialization complete! The bot is now ready.")
// Info about slash commands // Info about slash commands
log.Info(<-slashChannel) //log.Info(<-slashChannel)
// -- GRACEFUL TERMINATION -- // // -- GRACEFUL TERMINATION -- //
// Set up a sigterm channel so we can detect when the application receives a TERM signal // Set up a sigterm channel, so we can detect when the application receives a TERM signal
sigChannel := make(chan os.Signal, 1) sigChannel := make(chan os.Signal, 1)
signal.Notify(sigChannel, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL, os.Interrupt, os.Kill) signal.Notify(sigChannel, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL, os.Interrupt, os.Kill)

View File

@ -4,8 +4,8 @@ package framework
// Everything required for commands to pass their own handlers to discordgo // Everything required for commands to pass their own handlers to discordgo
// handlers // handlers
// This list stores all of the handlers that can be added to the bot // This list stores all the handlers that can be added to the bot
// It's basically a passthrough for discordgo.AddHandler, but having a list // It's basically a passthroughs for discordgo.AddHandler, but having a list
// allows them to be collected ahead of time and then added all at once // allows them to be collected ahead of time and then added all at once
var handlers []interface{} var handlers []interface{}

View File

@ -1,7 +1,6 @@
package framework package framework
import ( import (
"fmt"
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
"runtime" "runtime"
) )
@ -22,9 +21,6 @@ var slashCommandTypes = map[ArgTypeGuards]discordgo.ApplicationCommandOptionType
//SubCmdGrp: discordgo.ApplicationCommandOptionSubCommandGroup, //SubCmdGrp: discordgo.ApplicationCommandOptionSubCommandGroup,
} }
//var componentHandlers
//
// getSlashCommandStruct // getSlashCommandStruct
// Creates a slash command struct // Creates a slash command struct
// todo work on sub command stuff // todo work on sub command stuff
@ -44,8 +40,14 @@ func createSlashCommandStruct(info *CommandInfo) (st *discordgo.ApplicationComma
for i, k := range info.Arguments.Keys() { for i, k := range info.Arguments.Keys() {
v, _ := info.Arguments.Get(k) v, _ := info.Arguments.Get(k)
vv := v.(*ArgInfo) vv := v.(*ArgInfo)
var sType discordgo.ApplicationCommandOptionType
if val, ok := slashCommandTypes[vv.TypeGuard]; ok {
sType = val
} else {
sType = slashCommandTypes["String"]
}
optionStruct := discordgo.ApplicationCommandOption{ optionStruct := discordgo.ApplicationCommandOption{
Type: slashCommandTypes[vv.TypeGuard], Type: sType,
Name: k, Name: k,
Description: vv.Description, Description: vv.Description,
Required: vv.Required, Required: vv.Required,
@ -105,47 +107,8 @@ func handleInteraction(s *discordgo.Session, i *discordgo.InteractionCreate) {
// handleInteractionCommand // handleInteractionCommand
// Handles a slash command // Handles a slash command
func handleInteractionCommand(s *discordgo.Session, i *discordgo.InteractionCreate) { func handleInteractionCommand(s *discordgo.Session, i *discordgo.InteractionCreate) {
if i.ApplicationCommandData().Name == "rickroll-em" {
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "Operation rickroll has begun",
Flags: 1 << 6,
},
})
if err != nil {
panic(err)
}
ch, err := s.UserChannelCreate(
i.ApplicationCommandData().TargetID,
)
if err != nil {
_, err = s.FollowupMessageCreate(Session.State.User.ID, i.Interaction, true, &discordgo.WebhookParams{
Content: fmt.Sprintf("Mission failed. Cannot send a message to this user: %q", err.Error()),
Flags: 1 << 6,
})
if err != nil {
panic(err)
}
}
_, err = s.ChannelMessageSend(
ch.ID,
fmt.Sprintf("%s sent you this: https://youtu.be/dQw4w9WgXcQ", i.Member.Mention()),
)
if err != nil {
panic(err)
}
return
}
g := getGuild(i.GuildID) g := getGuild(i.GuildID)
if g.Info.DeletePolicy {
err := Session.ChannelMessageDelete(i.ChannelID, i.ID)
if err != nil {
SendErrorReport(i.GuildID, i.ChannelID, i.Member.User.ID, "Failed to delete message: "+i.ID, err)
}
}
trigger := i.ApplicationCommandData().Name trigger := i.ApplicationCommandData().Name
if !IsAdmin(i.Member.User.ID) { if !IsAdmin(i.Member.User.ID) {
// Ignore the command if it is globally disabled // Ignore the command if it is globally disabled
@ -175,6 +138,7 @@ func handleInteractionCommand(s *discordgo.Session, i *discordgo.InteractionCrea
if IsAdmin(i.Member.User.ID) || command.Info.Public || g.IsMod(i.Member.User.ID) { if IsAdmin(i.Member.User.ID) || command.Info.Public || g.IsMod(i.Member.User.ID) {
// Check if the command is public, or if the current user is a bot moderator // Check if the command is public, or if the current user is a bot moderator
// Bot admins supercede both checks // Bot admins supercede both checks
defer handleSlashCommandError(*i.Interaction) defer handleSlashCommandError(*i.Interaction)
command.Function(&Context{ command.Function(&Context{
Guild: g, Guild: g,
@ -198,7 +162,7 @@ func handleMessageComponents(s *discordgo.Session, i *discordgo.InteractionCreat
i.Message.Embeds[0].Description = content i.Message.Embeds[0].Description = content
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
// Buttons also may update the message which they was attached to. // Buttons also may update the message which they was attached to.
// Or may just acknowledge (InteractionResponseDredeferMessageUpdate) that the event was received and not update the message. // Or may just acknowledge (InteractionResponseDeferredMessageUpdate) that the event was received and not update the message.
// To update it later you need to use interaction response edit endpoint. // To update it later you need to use interaction response edit endpoint.
Type: discordgo.InteractionResponseUpdateMessage, Type: discordgo.InteractionResponseUpdateMessage,
Data: &discordgo.InteractionResponseData{ Data: &discordgo.InteractionResponseData{

168
util.go
View File

@ -2,7 +2,9 @@ package framework
import ( import (
"errors" "errors"
"fmt"
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
"github.com/dlclark/regexp2"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
@ -27,11 +29,20 @@ func RemoveItem(slice []string, delete string) []string {
// Removes items from a slice by index // Removes items from a slice by index
func RemoveItems(slice []string, indexes []int) []string { func RemoveItems(slice []string, indexes []int) []string {
newSlice := make([]string, len(slice)) newSlice := make([]string, len(slice))
if len(indexes) >= len(slice) {
return newSlice
}
copy(newSlice, slice) copy(newSlice, slice)
for _, v := range indexes { for _, v := range indexes {
newSlice[v] = newSlice[len(newSlice)-1] if len(newSlice) > v+1 && v != 0 {
newSlice[len(newSlice)-1] = "" v = v - 1
newSlice = newSlice[:len(newSlice)-1] }
//newSlice[v] = newSlice[len(newSlice)-1]
//newSlice[len(newSlice)-1] = ""
//newSlice = newSlice[:len(newSlice)-1]
copy(newSlice[v:], newSlice[v+1:]) // Shift a[i+1:] left one index.
newSlice[len(newSlice)-1] = "" // Erase last element (write zero value).
newSlice = newSlice[:len(newSlice)-1] // Truncate slice.
} }
return newSlice return newSlice
} }
@ -107,22 +118,6 @@ func ExtractCommand(guild *GuildInfo, message string) (*string, *string) {
return &trigger, &fullArgs return &trigger, &fullArgs
} else { } else {
if strings.Contains(message, "uber") {
// Same process as above prefix method, but split on a bot mention instead
split := strings.SplitN(message, "uber", 2)
content := strings.TrimPrefix(split[1], " ")
// If content is null someone just sent the prefix
if content == "" {
return nil, nil
}
// Attempt to pull the trigger out of the command content by splitting on spaces
trigger := strings.Fields(content)[0]
fullArgs := strings.SplitN(content, trigger, 2)[1]
fullArgs = strings.TrimPrefix(fullArgs, " ")
// Avoids issues with strings that are case sensitive
trigger = strings.ToLower(trigger)
return &trigger, &fullArgs
}
// The bot can only be mentioned with a space // The bot can only be mentioned with a space
botMention := Session.State.User.Mention() + " " botMention := Session.State.User.Mention() + " "
@ -241,37 +236,122 @@ func ParseTime(content string) (int, string) {
return 0, "error lol" return 0, "error lol"
} }
duration := 0 duration := 0
displayDuration := "Indefinite"
multiplier := 1 multiplier := 1
for k, v := range TimeRegexes {
if isMatch, _ := v.MatchString(content); isMatch { matches := FindAllString(TimeRegexes["all"], content)
multiplier, _ = strconv.Atoi(EnsureNumbers(v.String())) if len(matches) <= 0 {
switch k { return 0, "error lol"
case "seconds": }
for _, v := range matches {
// Grab only the letters out of the duration, to detect the unit
muteUnit := strings.ToLower(EnsureLetters(v))
// Grab the number out of the duration
// Errors shouldn't be possible due to EnsureNumbers
multiplier, _ = strconv.Atoi(EnsureNumbers(v))
// Use the string next to the number to check how long the mute should be for
switch muteUnit {
case "s":
duration = multiplier + duration duration = multiplier + duration
displayDuration = "Second" case "m":
case "minutes":
duration = multiplier*60 + duration duration = multiplier*60 + duration
displayDuration = "Minute" case "h":
case "hours":
duration = multiplier*60*60 + duration duration = multiplier*60*60 + duration
displayDuration = "Hour" case "d":
case "days":
duration = multiplier*60*60*24 + duration duration = multiplier*60*60*24 + duration
displayDuration = "Day" case "w":
case "weeks":
duration = multiplier*60*60*24*7 + duration duration = multiplier*60*60*24*7 + duration
displayDuration = "Week" case "y":
case "years": duration = multiplier*60*60*24*7*52 + duration
duration = multiplier*60*60*24*7*365 + duration default:
displayDuration = "Year" break
} }
} }
}
// Plurals matter! return duration, createDisplayDurationString(content)
if multiplier != 1 { }
displayDuration += "s"
} func createDisplayDurationString(content string) (str string) {
displayDuration = strconv.Itoa(multiplier) + " " + displayDuration // First tokenize
return duration, displayDuration str = ""
matches := FindAllString(TimeRegexes["all"], content)
if matches == nil || len(matches) == 0 {
str = "Indefinite"
return
}
for i, v := range matches {
prefixChar := ""
if i+1 == len(matches) && len(matches) > 1 {
prefixChar = " & "
} else if i != 0 {
prefixChar = ", "
}
// Grab only the letters out of the duration, to detect the unit
muteUnit := strings.ToLower(EnsureLetters(v))
// Grab the number out of the duration
// Errors shouldn't be possible due to EnsureNumbers
multiplier, _ := strconv.Atoi(EnsureNumbers(v))
// clean this up
switch muteUnit {
case "s":
if multiplier > 1 {
str += prefixChar + fmt.Sprintf("%d Seconds", multiplier)
break
}
str += prefixChar + "Second"
break
case "m":
if multiplier > 1 {
str += prefixChar + fmt.Sprintf("%d Minutes", multiplier)
break
}
str += prefixChar + fmt.Sprintf("%d Minute", multiplier)
break
case "h":
if multiplier > 1 {
str += prefixChar + fmt.Sprintf("%d Hours", multiplier)
break
}
str += prefixChar + fmt.Sprintf("%d Hours", multiplier)
break
case "d":
if multiplier > 1 {
str += prefixChar + fmt.Sprintf("%d Days", multiplier)
break
}
str += prefixChar + fmt.Sprintf("%d Day", multiplier)
break
case "w":
if multiplier > 1 {
str += prefixChar + fmt.Sprintf("%d Weeks", multiplier)
break
}
str += prefixChar + fmt.Sprintf("%d Week", multiplier)
break
case "y":
if multiplier > 1 {
str += prefixChar + fmt.Sprintf("%d Years", multiplier)
break
}
str += prefixChar + fmt.Sprintf("%d Year", multiplier)
break
default:
break
}
}
return
}
func FindAllString(re *regexp2.Regexp, s string) []string {
var matches []string
m, _ := re.FindStringMatch(s)
for m != nil {
matches = append(matches, m.String())
m, _ = re.FindNextMatch(m)
}
return matches
} }