update from upstream
This commit is contained in:
parent
f743f1efbd
commit
3e3f761b01
69
arguments.go
69
arguments.go
|
|
@ -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) {
|
||||||
|
|
|
||||||
71
commands.go
71
commands.go
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
68
core.go
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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{}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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{
|
||||||
|
|
|
||||||
164
util.go
164
util.go
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return duration, createDisplayDurationString(content)
|
||||||
}
|
}
|
||||||
// Plurals matter!
|
|
||||||
if multiplier != 1 {
|
func createDisplayDurationString(content string) (str string) {
|
||||||
displayDuration += "s"
|
// First tokenize
|
||||||
|
str = ""
|
||||||
|
matches := FindAllString(TimeRegexes["all"], content)
|
||||||
|
if matches == nil || len(matches) == 0 {
|
||||||
|
str = "Indefinite"
|
||||||
|
return
|
||||||
}
|
}
|
||||||
displayDuration = strconv.Itoa(multiplier) + " " + displayDuration
|
for i, v := range matches {
|
||||||
return duration, displayDuration
|
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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue