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"
SubCmdGrp ArgTypeGuards = "subcmdgrp"
ArrString ArgTypeGuards = "arrString"
Time ArgTypeGuards = "time"
)
// ArgInfo
@ -75,7 +76,7 @@ type Arguments map[string]CommandArg
// CreateCommandInfo
// 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{
Aliases: nil,
Arguments: orderedmap.New(),
@ -88,6 +89,13 @@ func CreateCommandInfo(trigger string, description string, public bool, group st
return cI
}
// CreateRawCmdInfo
// Creates a pointer to a CommandInfo
func CreateRawCmdInfo(cI *CommandInfo) *CommandInfo {
cI.Arguments = orderedmap.New()
return cI
}
// SetParent
// Sets the parent properties
func (cI *CommandInfo) SetParent(isParent bool, parentID string) {
@ -248,34 +256,7 @@ func findAllOptionArgs(argString []string, keys []string, infoArgs *orderedmap.O
modifiedArgString := ""
var modKeys []string
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
// First lets find all required args
currentPos := 0
@ -314,8 +295,13 @@ func findAllOptionArgs(argString []string, keys []string, infoArgs *orderedmap.O
argString = argString[currentPos:]
indexes = nil
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
for _, v := range modKeys {
for i, v := range modKeys {
// error handling
iA, ok := infoArgs.Get(v)
if !ok {
@ -331,8 +317,8 @@ func findAllOptionArgs(argString []string, keys []string, infoArgs *orderedmap.O
break
}
if vv.Match == ArgContent {
modifiedArgString = strings.Join(argString, " ")
return *args, true, createSplitString(modifiedArgString), modKeys
modKeys = RemoveItems(modKeys, indexes)
return *args, true, argString, modKeys
}
// Break early if current pos is the length of the array
if currentPos == len(argString) {
@ -342,9 +328,11 @@ func findAllOptionArgs(argString []string, keys []string, infoArgs *orderedmap.O
var value string
value, argString = findTypeGuard(strings.Join(argString, " "), argString, vv.TypeGuard)
(*args)[v] = handleArgOption(value, *vv)
indexes = append(indexes, i)
} else if checkTypeGuard(argString[currentPos], vv.TypeGuard) {
(*args)[v] = handleArgOption(argString[currentPos], *vv)
currentPos++
indexes = append(indexes, i)
} else {
}
@ -356,7 +344,12 @@ func findAllOptionArgs(argString []string, keys []string, infoArgs *orderedmap.O
func findTypeGuard(input string, array []string, typeguard ArgTypeGuards) (string, []string) {
switch typeguard {
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 "", array
@ -391,8 +384,18 @@ func findTypeGuard(input string, array []string, typeguard ArgTypeGuards) (strin
return match.String(), RemoveItem(array, match.String())
}
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
default:
return "", array
}
return "", array
}
func findAllFlags(argString string, keys []string, infoArgs *orderedmap.OrderedMap, args *Arguments) ([]string, Arguments, []string) {

View File

@ -2,19 +2,22 @@ package framework
import (
"github.com/QPixel/orderedmap"
"runtime"
"strings"
"github.com/bwmarrin/discordgo"
"runtime"
"runtime/debug"
"strings"
"time"
)
// commands.go
// This file contains everything required to add core commands to the bot, and parse commands from a message
// GroupTypes
const (
Moderation = "moderation"
Utility = "utility"
type Group string
var (
Moderation Group = "moderation"
Utility Group = "utility"
)
// CommandInfo
@ -23,7 +26,7 @@ type CommandInfo struct {
Aliases []string // Aliases for the normal trigger
Arguments *orderedmap.OrderedMap // Arguments for the command
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
Public bool // Whether non-admins and non-mods can use this command
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
var slashCommands = make(map[string]discordgo.ApplicationCommand)
// commandsGC
var commandsGC = 0
// AddCommand
// Add a command to the bot
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 {
//Get the command to run
// Error Checking
@ -253,6 +252,14 @@ func commandHandler(session *discordgo.Session, message *discordgo.MessageCreate
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)
@ -264,6 +271,13 @@ func commandHandler(session *discordgo.Session, message *discordgo.MessageCreate
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
}
}
@ -272,24 +286,9 @@ func commandHandler(session *discordgo.Session, message *discordgo.MessageCreate
// -- Helper Methods
func handleChildCommand(argString string, command Command, message *discordgo.Message, g *Guild) {
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
subCmdExist := false
for _, choice := range choices {
if split[0] != choice {
continue
} else {
subCmdExist = true
break
}
}
if !subCmdExist {
childCmd, ok := childCommands[command.Info.Trigger][split[0]]
if !ok {
command.Function(&Context{
Guild: g,
Cmd: command.Info,
@ -298,12 +297,11 @@ func handleChildCommand(argString string, command Command, message *discordgo.Me
})
return
}
childCmd, ok := childCommands[command.Info.Trigger][split[0]]
if !ok || len(split) < 2 {
command.Function(&Context{
if len(split) < 2 {
childCmd.Function(&Context{
Guild: g,
Cmd: command.Info,
Args: nil,
Cmd: childCmd.Info,
Args: *ParseArguments("", childCmd.Info.Arguments),
Message: message,
})
return
@ -326,6 +324,7 @@ func handleCommandError(gID string, cId string, uId string) {
if err != nil {
log.Errorf("err sending message %s", err)
}
time.Sleep(5 * time.Second)
_ = Session.ChannelMessageDelete(cId, message.ID)
return
}

View File

@ -11,20 +11,19 @@ var (
"hours": regexp2.MustCompile("^[0-9]+h$", 0),
"days": regexp2.MustCompile("^[0-9]+d$", 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{
"all": regexp2.MustCompile("<((@!?\\d+)|(#?\\d+)|(@&?\\d+))>", 0),
"role": regexp2.MustCompile("<((@&?\\d+))>", 0),
"user": 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{
"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
import (
"fmt"
"github.com/bwmarrin/discordgo"
tlog "github.com/ubergeek77/tinylog"
"os"
"os/signal"
"runtime"
"strconv"
"strings"
"syscall"
@ -15,13 +16,17 @@ import (
// This file contains the main code responsible for driving core bot functionality
// messageState
// Tells discordgo the amount of mesesages to cache
var messageState = 20
// Tells discordgo the amount of messages to cache
var messageState = 500
// log
// The logger for the core bot
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
// The Discord session, made public so commands can use it
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
var botAdmins = make(map[string]bool)
// BotPresence
var botPresence discordgo.GatewayStatusUpdate
// BotToken
// 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
@ -77,15 +79,32 @@ func IsCommand(trigger string) bool {
return false
}
// SetPresence
// Sets the presence struct after a session has been created
func SetPresence(presence discordgo.GatewayStatusUpdate) {
botPresence = presence
return
// dgoLog
// Allows for discordgo to call tinylog
func dgoLog(msgL, caller int, format string, a ...interface{}) {
pc, file, line, _ := runtime.Caller(caller)
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() {
discordgo.Logger = dgoLog
// Load all the guilds
loadGuilds()
@ -103,8 +122,19 @@ func Start() {
}
// Setup State specific variables
Session.State.MaxMessageCount = messageState
Session.LogLevel = discordgo.LogWarning
Session.SyncEvents = false
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
log.Info("Connecting to Discord...")
err = Session.Open()
@ -117,6 +147,7 @@ func Start() {
// Add the slash command handler to the list of user-defined handlers
AddHandler(handleInteraction)
// Add the handlers to the session
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!")
}
// Register slash commands
slashChannel := make(chan string)
log.Info("Registering slash commands")
go AddSlashCommands("833901685054242846", slashChannel)
//slashChannel := make(chan string)
//log.Info("Registering slash commands")
//go AddSlashCommands("833901685054242846", slashChannel)
// Bot ready
log.Info("Initialization complete! The bot is now ready.")
// Info about slash commands
log.Info(<-slashChannel)
//log.Info(<-slashChannel)
// -- 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)
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
// handlers
// This list stores all of the handlers that can be added to the bot
// It's basically a passthrough for discordgo.AddHandler, but having a list
// This list stores all the handlers that can be added to the bot
// 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
var handlers []interface{}

View File

@ -1,7 +1,6 @@
package framework
import (
"fmt"
"github.com/bwmarrin/discordgo"
"runtime"
)
@ -22,9 +21,6 @@ var slashCommandTypes = map[ArgTypeGuards]discordgo.ApplicationCommandOptionType
//SubCmdGrp: discordgo.ApplicationCommandOptionSubCommandGroup,
}
//var componentHandlers
//
// getSlashCommandStruct
// Creates a slash command struct
// todo work on sub command stuff
@ -44,8 +40,14 @@ func createSlashCommandStruct(info *CommandInfo) (st *discordgo.ApplicationComma
for i, k := range info.Arguments.Keys() {
v, _ := info.Arguments.Get(k)
vv := v.(*ArgInfo)
var sType discordgo.ApplicationCommandOptionType
if val, ok := slashCommandTypes[vv.TypeGuard]; ok {
sType = val
} else {
sType = slashCommandTypes["String"]
}
optionStruct := discordgo.ApplicationCommandOption{
Type: slashCommandTypes[vv.TypeGuard],
Type: sType,
Name: k,
Description: vv.Description,
Required: vv.Required,
@ -105,47 +107,8 @@ func handleInteraction(s *discordgo.Session, i *discordgo.InteractionCreate) {
// handleInteractionCommand
// Handles a slash command
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)
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
if !IsAdmin(i.Member.User.ID) {
// 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) {
// Check if the command is public, or if the current user is a bot moderator
// Bot admins supercede both checks
defer handleSlashCommandError(*i.Interaction)
command.Function(&Context{
Guild: g,
@ -198,7 +162,7 @@ func handleMessageComponents(s *discordgo.Session, i *discordgo.InteractionCreat
i.Message.Embeds[0].Description = content
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
// 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.
Type: discordgo.InteractionResponseUpdateMessage,
Data: &discordgo.InteractionResponseData{

178
util.go
View File

@ -2,7 +2,9 @@ package framework
import (
"errors"
"fmt"
"github.com/bwmarrin/discordgo"
"github.com/dlclark/regexp2"
"regexp"
"strconv"
"strings"
@ -27,11 +29,20 @@ func RemoveItem(slice []string, delete string) []string {
// Removes items from a slice by index
func RemoveItems(slice []string, indexes []int) []string {
newSlice := make([]string, len(slice))
if len(indexes) >= len(slice) {
return newSlice
}
copy(newSlice, slice)
for _, v := range indexes {
newSlice[v] = newSlice[len(newSlice)-1]
newSlice[len(newSlice)-1] = ""
newSlice = newSlice[:len(newSlice)-1]
if len(newSlice) > v+1 && v != 0 {
v = v - 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
}
@ -107,22 +118,6 @@ func ExtractCommand(guild *GuildInfo, message string) (*string, *string) {
return &trigger, &fullArgs
} 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
botMention := Session.State.User.Mention() + " "
@ -241,37 +236,122 @@ func ParseTime(content string) (int, string) {
return 0, "error lol"
}
duration := 0
displayDuration := "Indefinite"
multiplier := 1
for k, v := range TimeRegexes {
if isMatch, _ := v.MatchString(content); isMatch {
multiplier, _ = strconv.Atoi(EnsureNumbers(v.String()))
switch k {
case "seconds":
duration = multiplier + duration
displayDuration = "Second"
case "minutes":
duration = multiplier*60 + duration
displayDuration = "Minute"
case "hours":
duration = multiplier*60*60 + duration
displayDuration = "Hour"
case "days":
duration = multiplier*60*60*24 + duration
displayDuration = "Day"
case "weeks":
duration = multiplier*60*60*24*7 + duration
displayDuration = "Week"
case "years":
duration = multiplier*60*60*24*7*365 + duration
displayDuration = "Year"
}
matches := FindAllString(TimeRegexes["all"], content)
if len(matches) <= 0 {
return 0, "error lol"
}
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
case "m":
duration = multiplier*60 + duration
case "h":
duration = multiplier*60*60 + duration
case "d":
duration = multiplier*60*60*24 + duration
case "w":
duration = multiplier*60*60*24*7 + duration
case "y":
duration = multiplier*60*60*24*7*52 + duration
default:
break
}
}
// Plurals matter!
if multiplier != 1 {
displayDuration += "s"
}
displayDuration = strconv.Itoa(multiplier) + " " + displayDuration
return duration, displayDuration
return duration, createDisplayDurationString(content)
}
func createDisplayDurationString(content string) (str string) {
// First tokenize
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
}