From c86ad1c07eabfce72020efaad2283c66866d783f Mon Sep 17 00:00:00 2001 From: Levatax <levatax@randomchars.net> Date: Sun, 8 Aug 2021 11:21:15 +0300 Subject: [PATCH] implement scoped rate limiting, wrap around instance actions, implement channel name changing command --- command.go | 108 +++++++++++++++++++++++++++++++++++++++++++--------- instance.go | 43 ++++++++++++++++++--- 2 files changed, 126 insertions(+), 25 deletions(-) diff --git a/command.go b/command.go index fccb674..8496a0d 100644 --- a/command.go +++ b/command.go @@ -5,6 +5,7 @@ import ( "git.randomchars.net/freenitori/multiplexer" "github.com/bwmarrin/discordgo" "strconv" + "strings" ) func init() { @@ -40,41 +41,110 @@ func init() { return } - // Lock the instance for write - instance.Lock() - defer instance.Unlock() + // Perform action + if !instance.actionPerform("ceiling", + func() bool { + // Check if payload is the same as current state + if instance.Channel.UserLimit == int(limit) { + context.SendMessage("User limit was not changed.") + return false + } - // Check if action is free - if !instance.actionFree() { - context.SendMessage("Not so fast! Please try again later.") + // Perform change + if channel, err := context.Session.ChannelEditComplex(instance.Channel.ID, + &discordgo.ChannelEdit{UserLimit: int(limit), Position: instance.Channel.Position}); !context.HandleError(err) { + return false + } else { + instance.Channel = channel + } + return true + }, context.SendMessage) { return } - // Check if payload is the same as current state - if instance.Channel.UserLimit == int(limit) { - context.SendMessage("User limit was not changed.") + log.Infof("Set user limit of volatile channel #%s (%s) to %v on behalf of %s#%s (%s).", + instance.Channel.Name, + instance.Channel.ID, + limit, + instance.Initiator.User.Username, + instance.Initiator.User.Discriminator, + instance.Initiator.User.ID) + context.SendMessage("Action completed successfully.") + }, + }) + + m.Route(&multiplexer.Route{ + Pattern: "name", + AliasPatterns: []string{"n", "rename"}, + Description: "Configure name of a channel.", + Category: system, + Handler: func(context *multiplexer.Context) { + var name string + + if len(context.Fields) < 2 { + // Return if unexpected amount of arguments. + context.SendMessage("Expecting new channel name.") return + } else { + // Trim out first field since it's normally the command prefix or bot mention + name = strings.TrimPrefix( + strings.TrimPrefix( + strings.TrimPrefix(context.Message.Content, + context.Multiplexer.Prefix), + context.Fields[0]), + " ") + + // Check length + if len(name) > 32 || len(name) < 2 { + context.SendMessage("Name length out of range.") + return + } + + // Check content + if !nameRegex.MatchString(name) { + println(context.Fields[0]) + println(context.Fields[1]) + context.SendMessage("Name is invalid.") + return + } } - // Perform change - if channel, err := context.Session.ChannelEditComplex(instance.Channel.ID, - &discordgo.ChannelEdit{UserLimit: int(limit), Position: instance.Channel.Position}); !context.HandleError(err) { + // Get instance + instance, ok := instancesUser[context.User.ID] + if !ok { + context.SendMessage("You do not own any volatile channel.") return - } else { - instance.Channel = channel } - // Register action - instance.actionRegister() + // Perform action + if !instance.actionPerform("name", + func() bool { + // Check if payload is the same as current state + if instance.Channel.Name == name { + context.SendMessage("Channel name was not changed.") + return false + } - log.Infof("Set user limit of volatile channel #%s (%s) to %v on behalf of %s#%s (%s).", + // Perform change + if channel, err := context.Session.ChannelEditComplex(instance.Channel.ID, + &discordgo.ChannelEdit{Name: name, Position: instance.Channel.Position}); !context.HandleError(err) { + return false + } else { + instance.Channel = channel + } + return true + }, context.SendMessage) { + return + } + + log.Infof("Set name of volatile channel #%s (%s) to %v on behalf of %s#%s (%s).", instance.Channel.Name, instance.Channel.ID, - limit, + name, instance.Initiator.User.Username, instance.Initiator.User.Discriminator, instance.Initiator.User.ID) - context.SendMessage("Action completed successfully.") + context.SendMessage("Action completed.") }, }) } diff --git a/instance.go b/instance.go index 137fe9b..3e8b346 100644 --- a/instance.go +++ b/instance.go @@ -3,10 +3,13 @@ package main import ( "git.randomchars.net/freenitori/log" "github.com/bwmarrin/discordgo" + "regexp" "sync" "time" ) +var nameRegex = regexp.MustCompile(`^[A-Za-z0-9()_-]*$`) + var ( categoryMap map[string]string guildMap map[string]string @@ -16,7 +19,7 @@ var ( type chatInstance struct { Initiator *discordgo.Member Channel *discordgo.Channel - PreviousAction time.Time + PreviousAction map[string]time.Time Members map[string]bool sync.RWMutex } @@ -39,13 +42,18 @@ func (c *chatInstance) destroy() { } // actionFree checks if actions are free in chatInstance. -func (c *chatInstance) actionFree() bool { - return c.PreviousAction.Add(timeout).Before(time.Now()) +func (c *chatInstance) actionFree(variant string) bool { + p, ok := c.PreviousAction[variant] + if !ok { + return true + } + + return p.Add(timeout).Before(time.Now()) } // actionRegister registers an action in chatInstance. -func (c *chatInstance) actionRegister() { - c.PreviousAction = time.Now() +func (c *chatInstance) actionRegister(variant string) { + c.PreviousAction[variant] = time.Now() } // newInstance creates a new chatInstance on behalf of a discordgo.Member. @@ -90,7 +98,30 @@ func setupInstance(member *discordgo.Member) *chatInstance { return &chatInstance{ Initiator: member, Channel: channel, - PreviousAction: time.Unix(0, 0), + PreviousAction: make(map[string]time.Time), Members: make(map[string]bool), } } + +// actionPerform performs an action. +func (c *chatInstance) actionPerform(variant string, action func() bool, sendMessage func(message string) *discordgo.Message) bool { + // Lock the instance for write + c.Lock() + defer c.Unlock() + + // Check if action is free + if !c.actionFree(variant) { + sendMessage("Not so fast! Please try again later.") + return false + } + + // Perform action + ok := action() + + // Register action + if ok { + c.actionRegister(variant) + } + + return ok +} -- GitLab