524 lines
12 KiB
Go
524 lines
12 KiB
Go
package core
|
|
|
|
import (
|
|
"errors"
|
|
"github.com/QPixel/orderedmap"
|
|
"github.com/bwmarrin/discordgo"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// Arguments.go
|
|
// File for all argument based functions which includes: parsing, creating, and more
|
|
// Woo
|
|
|
|
// pixel wrote this
|
|
|
|
// -- TypeDefs --
|
|
|
|
// ArgTypes
|
|
// A way to get type safety in AddArg
|
|
type ArgTypes string
|
|
|
|
var (
|
|
ArgOption ArgTypes = "option"
|
|
ArgContent ArgTypes = "content"
|
|
ArgFlag ArgTypes = "flag"
|
|
)
|
|
|
|
// ArgTypeGuards
|
|
// A way to get type safety in AddArg
|
|
type ArgTypeGuards string
|
|
|
|
var (
|
|
Int ArgTypeGuards = "int"
|
|
String ArgTypeGuards = "string"
|
|
Channel ArgTypeGuards = "channel"
|
|
User ArgTypeGuards = "user"
|
|
Role ArgTypeGuards = "role"
|
|
Boolean ArgTypeGuards = "bool"
|
|
SubCmd ArgTypeGuards = "subcmd"
|
|
SubCmdGrp ArgTypeGuards = "subcmdgrp"
|
|
ArrString ArgTypeGuards = "arrString"
|
|
)
|
|
|
|
// ArgInfo
|
|
// Describes a CommandInfo argument
|
|
type ArgInfo struct {
|
|
Match ArgTypes
|
|
TypeGuard ArgTypeGuards
|
|
Description string
|
|
Required bool
|
|
Flag bool
|
|
DefaultOption string
|
|
Choices []string
|
|
Regex string
|
|
}
|
|
|
|
// CommandArg
|
|
// Describes what a cmd ctx will receive
|
|
type CommandArg struct {
|
|
info ArgInfo
|
|
Value interface{}
|
|
}
|
|
|
|
// Arguments
|
|
// Type of the arguments field in the command ctx
|
|
type Arguments map[string]CommandArg
|
|
|
|
// -- Command Configuration --
|
|
|
|
// CreateCommandInfo
|
|
// Creates a pointer to a CommandInfo
|
|
func CreateCommandInfo(trigger string, description string, public bool, group string) *CommandInfo {
|
|
cI := &CommandInfo{
|
|
Aliases: nil,
|
|
Arguments: orderedmap.New(),
|
|
Description: description,
|
|
Group: group,
|
|
Public: public,
|
|
IsTyping: false,
|
|
Trigger: trigger,
|
|
}
|
|
return cI
|
|
}
|
|
|
|
// SetParent
|
|
// Sets the parent properties
|
|
func (cI *CommandInfo) SetParent(isParent bool, parentID string) {
|
|
if !isParent {
|
|
cI.IsChild = true
|
|
}
|
|
cI.IsParent = isParent
|
|
cI.ParentID = parentID
|
|
}
|
|
|
|
//AddCmdAlias
|
|
// Adds a list of strings as aliases for the command
|
|
func (cI *CommandInfo) AddCmdAlias(aliases []string) *CommandInfo {
|
|
if len(aliases) < 1 {
|
|
return cI
|
|
}
|
|
cI.Aliases = aliases
|
|
return cI
|
|
}
|
|
|
|
// AddArg
|
|
// Adds an arg to the CommandInfo
|
|
func (cI *CommandInfo) AddArg(argument string, typeGuard ArgTypeGuards, match ArgTypes, description string, required bool, defaultOption string) *CommandInfo {
|
|
cI.Arguments.Set(argument, &ArgInfo{
|
|
TypeGuard: typeGuard,
|
|
Description: description,
|
|
Required: required,
|
|
Match: match,
|
|
DefaultOption: defaultOption,
|
|
Choices: nil,
|
|
Regex: "",
|
|
})
|
|
return cI
|
|
}
|
|
|
|
// AddFlagArg
|
|
// Adds a flag arg, which is a special type of argument
|
|
func (cI *CommandInfo) AddFlagArg(flag string, typeGuard ArgTypeGuards, match ArgTypes, description string, required bool, defaultOption string) *CommandInfo {
|
|
cI.Arguments.Set(flag, &ArgInfo{
|
|
Description: description,
|
|
Required: required,
|
|
Flag: true,
|
|
Match: match,
|
|
TypeGuard: typeGuard,
|
|
DefaultOption: defaultOption,
|
|
Regex: "",
|
|
})
|
|
return cI
|
|
}
|
|
|
|
// AddChoices
|
|
// Adds SubCmd choices
|
|
func (cI *CommandInfo) AddChoices(arg string, choices []string) *CommandInfo {
|
|
v, ok := cI.Arguments.Get(arg)
|
|
if ok {
|
|
vv := v.(*ArgInfo)
|
|
vv.Choices = choices
|
|
cI.Arguments.Set(arg, vv)
|
|
} else {
|
|
log.Errorf("Unable to get argument %s in AddChoices", arg)
|
|
return cI
|
|
}
|
|
return cI
|
|
}
|
|
|
|
func (cI *CommandInfo) SetTyping(isTyping bool) *CommandInfo {
|
|
cI.IsTyping = isTyping
|
|
return cI
|
|
}
|
|
|
|
// -- Argument Parser --
|
|
|
|
// ParseArguments
|
|
// Parses the arguments into a pointer to an Arguments struct
|
|
func ParseArguments(args string, infoArgs *orderedmap.OrderedMap) *Arguments {
|
|
ar := make(Arguments)
|
|
if args == "" || len(infoArgs.Keys()) < 1 {
|
|
return &ar
|
|
}
|
|
// Split string on spaces to get every "phrase"
|
|
splitString := strings.Split(args, " ")
|
|
|
|
// Current Position in the infoArgs map
|
|
currentPos := 0
|
|
|
|
// Keys of infoArgs
|
|
k := infoArgs.Keys()
|
|
|
|
for i := 0; i < len(splitString); i++ {
|
|
for n := currentPos; n <= len(k); n++ {
|
|
if n > len(k)+1 || currentPos+1 > len(k) {
|
|
break
|
|
}
|
|
v, _ := infoArgs.Get(k[currentPos])
|
|
vv := v.(*ArgInfo)
|
|
switch vv.Match {
|
|
case ArgOption:
|
|
// Lets first check the typeguard to see if the str matches the arg
|
|
if checkTypeGuard(splitString[i], vv.TypeGuard) {
|
|
// todo abstract this into handleArgOption
|
|
// Handle quoted ArgOptions separately
|
|
if strings.Contains(splitString[i], "\"") {
|
|
st := CommandArg{}
|
|
st, i = handleQuotedString(splitString, *vv, i)
|
|
ar[k[currentPos]] = st
|
|
currentPos++
|
|
break
|
|
}
|
|
// Handle ArgOption
|
|
ar[k[currentPos]] = handleArgOption(splitString[i], *vv)
|
|
currentPos++
|
|
break
|
|
}
|
|
if n+1 > len(splitString) {
|
|
break
|
|
}
|
|
// If the TypeGuard does not match check to see if the Arg is required or not
|
|
if vv.Required {
|
|
// Set the CommandArg to the default option, which is usually ""
|
|
ar[k[currentPos]] = CommandArg{
|
|
info: *vv,
|
|
Value: vv.DefaultOption,
|
|
}
|
|
currentPos++
|
|
break
|
|
} else {
|
|
// If it's not required, we set the CommandArg to ""
|
|
ar[k[currentPos]] = CommandArg{
|
|
info: *vv,
|
|
Value: "",
|
|
}
|
|
currentPos++
|
|
break
|
|
}
|
|
case ArgContent:
|
|
// Takes the splitString and currentPos to find how many more elements in the slice
|
|
// need to join together
|
|
contentString := ""
|
|
contentString, i = createContentString(splitString, i)
|
|
ar[k[currentPos]] = CommandArg{
|
|
info: *vv,
|
|
Value: contentString,
|
|
}
|
|
break
|
|
default:
|
|
break
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
return &ar
|
|
}
|
|
|
|
/* Argument Parsing Helpers */
|
|
|
|
func createContentString(splitString []string, currentPos int) (string, int) {
|
|
str := ""
|
|
for i := currentPos; i < len(splitString); i++ {
|
|
str += splitString[i] + " "
|
|
currentPos = i
|
|
}
|
|
return strings.TrimSuffix(str, " "), currentPos
|
|
}
|
|
|
|
func handleQuotedString(splitString []string, argInfo ArgInfo, currentPos int) (CommandArg, int) {
|
|
str := ""
|
|
splitString[currentPos] = strings.TrimPrefix(splitString[currentPos], "\"")
|
|
for i := currentPos; i < len(splitString); i++ {
|
|
if !strings.HasSuffix(splitString[i], "\"") {
|
|
str += splitString[i] + " "
|
|
} else {
|
|
str += strings.TrimSuffix(splitString[i], "\"")
|
|
currentPos = i
|
|
break
|
|
}
|
|
}
|
|
return CommandArg{
|
|
info: argInfo,
|
|
Value: str,
|
|
}, currentPos
|
|
}
|
|
|
|
func handleArgOption(str string, info ArgInfo) CommandArg {
|
|
return CommandArg{
|
|
info: info,
|
|
Value: str,
|
|
}
|
|
}
|
|
|
|
func checkTypeGuard(str string, typeguard ArgTypeGuards) bool {
|
|
switch typeguard {
|
|
case String:
|
|
return true
|
|
case Int:
|
|
if _, err := strconv.Atoi(str); err == nil {
|
|
return true
|
|
}
|
|
return false
|
|
case Boolean:
|
|
if _, err := strconv.ParseBool(str); err == nil {
|
|
return true
|
|
}
|
|
case Channel:
|
|
if isMatch, _ := MentionStringRegexes["channel"].MatchString(str); isMatch {
|
|
return true
|
|
} else if isMatch, _ := MentionStringRegexes["id"].MatchString(str); isMatch {
|
|
return true
|
|
}
|
|
case Role:
|
|
if isMatch, _ := MentionStringRegexes["role"].MatchString(str); isMatch {
|
|
return true
|
|
} else if isMatch, _ := MentionStringRegexes["id"].MatchString(str); isMatch {
|
|
return true
|
|
}
|
|
case User:
|
|
if isMatch, _ := MentionStringRegexes["user"].MatchString(str); isMatch {
|
|
return true
|
|
} else if isMatch, _ := MentionStringRegexes["id"].MatchString(str); isMatch {
|
|
return true
|
|
}
|
|
return false
|
|
case ArrString:
|
|
if isMatch, _ := TypeGuard["arrString"].MatchString(str); isMatch {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
/* Argument Casting s*/
|
|
|
|
// StringValue
|
|
// Returns the string value of the arg
|
|
func (ag CommandArg) StringValue() string {
|
|
if ag.Value == nil {
|
|
return ""
|
|
}
|
|
if v, ok := ag.Value.(string); ok {
|
|
return v
|
|
} else if v := strconv.FormatFloat(ag.Value.(float64), 'f', 2, 64); v != "" {
|
|
return v
|
|
} else if v = strconv.FormatBool(ag.Value.(bool)); v != "" {
|
|
return v
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// Int64Value
|
|
// Returns the int64 value of the arg
|
|
func (ag CommandArg) Int64Value() int64 {
|
|
if ag.Value == nil {
|
|
return 0
|
|
}
|
|
if v, ok := ag.Value.(float64); ok {
|
|
return int64(v)
|
|
} else if v, err := strconv.ParseInt(ag.StringValue(), 10, 64); err == nil {
|
|
return v
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// IntValue
|
|
// Returns the int value of the arg
|
|
func (ag CommandArg) IntValue() int {
|
|
if ag.Value == nil {
|
|
return 0
|
|
}
|
|
if v, ok := ag.Value.(float64); ok {
|
|
return int(v)
|
|
} else if v, err := strconv.Atoi(ag.StringValue()); err == nil {
|
|
return v
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// FloatValue
|
|
// Returns the int value of the arg
|
|
func (ag CommandArg) FloatValue() float64 {
|
|
if ag.Value == nil {
|
|
return 0.0
|
|
}
|
|
if v, ok := ag.Value.(float64); ok {
|
|
return v
|
|
} else if v, err := strconv.ParseFloat(ag.StringValue(), 64); err == nil {
|
|
return v
|
|
}
|
|
return 0.0
|
|
}
|
|
|
|
// BoolValue
|
|
// Returns the int value of the arg
|
|
func (ag CommandArg) BoolValue() bool {
|
|
if ag.Value == nil {
|
|
return false
|
|
}
|
|
stringValue := ag.StringValue()
|
|
if v, err := strconv.ParseBool(stringValue); err == nil {
|
|
return v
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ChannelValue is a utility function for casting value to a channel struct
|
|
// Returns a channel struct, partial channel struct, or a nil value
|
|
func (ag CommandArg) ChannelValue(s *discordgo.Session) (*discordgo.Channel, error) {
|
|
chanID := ag.StringValue()
|
|
if chanID == "" {
|
|
return &discordgo.Channel{ID: chanID}, errors.New("no channel id")
|
|
}
|
|
|
|
if s == nil {
|
|
return &discordgo.Channel{ID: chanID}, errors.New("no session")
|
|
}
|
|
cleanedId := CleanId(chanID)
|
|
|
|
if cleanedId == "" {
|
|
return &discordgo.Channel{ID: chanID}, errors.New("not an id")
|
|
}
|
|
ch, err := s.State.Channel(cleanedId)
|
|
|
|
if err != nil {
|
|
ch, err = s.Channel(cleanedId)
|
|
if err != nil {
|
|
return &discordgo.Channel{ID: chanID}, errors.New("could not find channel")
|
|
}
|
|
}
|
|
return ch, nil
|
|
}
|
|
|
|
// MemberValue is a utility function for casting value to a member struct
|
|
// Returns a user struct, partial user struct, or a nil value
|
|
func (ag CommandArg) MemberValue(s *discordgo.Session, g string) (*discordgo.Member, error) {
|
|
userID := ag.StringValue()
|
|
if userID == "" {
|
|
return &discordgo.Member{
|
|
GuildID: g,
|
|
User: &discordgo.User{
|
|
ID: userID,
|
|
},
|
|
}, errors.New("no userid")
|
|
}
|
|
cleanedId := CleanId(userID)
|
|
if cleanedId == "" {
|
|
return &discordgo.Member{
|
|
GuildID: g,
|
|
User: &discordgo.User{
|
|
ID: userID,
|
|
},
|
|
}, errors.New("invalid userid")
|
|
}
|
|
if s == nil {
|
|
return &discordgo.Member{
|
|
GuildID: g,
|
|
User: &discordgo.User{
|
|
ID: cleanedId,
|
|
},
|
|
}, errors.New("session is nil")
|
|
}
|
|
u, err := s.State.Member(g, cleanedId)
|
|
|
|
if err != nil {
|
|
u, err = s.GuildMember(g, cleanedId)
|
|
if err != nil {
|
|
return &discordgo.Member{
|
|
GuildID: g,
|
|
User: &discordgo.User{
|
|
ID: userID,
|
|
},
|
|
}, errors.New("cant find user")
|
|
}
|
|
}
|
|
return u, nil
|
|
}
|
|
|
|
// UserValue is a utility function for casting value to a member struct
|
|
// Returns a user struct, partial user struct, or a nil value
|
|
func (ag CommandArg) UserValue(s *discordgo.Session) (*discordgo.User, error) {
|
|
userID := ag.StringValue()
|
|
if userID == "" {
|
|
return &discordgo.User{
|
|
ID: userID,
|
|
}, errors.New("no userid")
|
|
}
|
|
cleanedId := CleanId(userID)
|
|
if cleanedId == "" {
|
|
return &discordgo.User{
|
|
ID: userID,
|
|
}, errors.New("invalid userid")
|
|
}
|
|
if s == nil {
|
|
return &discordgo.User{
|
|
ID: userID,
|
|
}, errors.New("session is nil")
|
|
}
|
|
u, err := s.User(cleanedId)
|
|
|
|
if err != nil {
|
|
return &discordgo.User{
|
|
|
|
ID: cleanedId,
|
|
}, errors.New("cant find user")
|
|
}
|
|
return u, nil
|
|
}
|
|
|
|
// RoleValue is a utility function for casting value to a user struct
|
|
// Returns a user struct, partial user struct, or a nil value
|
|
func (ag CommandArg) RoleValue(s *discordgo.Session, gID string) (*discordgo.Role, error) {
|
|
roleID := ag.StringValue()
|
|
if roleID == "" {
|
|
return nil, errors.New("unable to find roleid")
|
|
}
|
|
cleanedId := CleanId(roleID)
|
|
if cleanedId == "" {
|
|
return &discordgo.Role{
|
|
ID: roleID,
|
|
}, errors.New("invalid roleid")
|
|
}
|
|
if s == nil || gID == "" {
|
|
return &discordgo.Role{ID: cleanedId}, errors.New("no session (and/or) guild id")
|
|
}
|
|
r, err := s.State.Role(cleanedId, gID)
|
|
|
|
if err != nil {
|
|
roles, err := s.GuildRoles(gID)
|
|
if err == nil {
|
|
for _, r = range roles {
|
|
if r.ID == cleanedId {
|
|
return r, nil
|
|
}
|
|
}
|
|
}
|
|
return &discordgo.Role{ID: roleID}, errors.New("could not find role")
|
|
}
|
|
return r, nil
|
|
}
|