rubycord 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/rubycord/allowed_mentions.rb +34 -0
- data/lib/rubycord/api/application.rb +200 -0
- data/lib/rubycord/api/channel.rb +597 -0
- data/lib/rubycord/api/interaction.rb +52 -0
- data/lib/rubycord/api/invite.rb +42 -0
- data/lib/rubycord/api/server.rb +557 -0
- data/lib/rubycord/api/user.rb +153 -0
- data/lib/rubycord/api/webhook.rb +138 -0
- data/lib/rubycord/api.rb +356 -0
- data/lib/rubycord/await.rb +49 -0
- data/lib/rubycord/bot.rb +1757 -0
- data/lib/rubycord/cache.rb +259 -0
- data/lib/rubycord/colour_rgb.rb +41 -0
- data/lib/rubycord/commands/command_bot.rb +519 -0
- data/lib/rubycord/commands/container.rb +110 -0
- data/lib/rubycord/commands/events.rb +9 -0
- data/lib/rubycord/commands/parser.rb +325 -0
- data/lib/rubycord/commands/rate_limiter.rb +142 -0
- data/lib/rubycord/container.rb +753 -0
- data/lib/rubycord/data/activity.rb +269 -0
- data/lib/rubycord/data/application.rb +48 -0
- data/lib/rubycord/data/attachment.rb +109 -0
- data/lib/rubycord/data/audit_logs.rb +343 -0
- data/lib/rubycord/data/channel.rb +996 -0
- data/lib/rubycord/data/component.rb +227 -0
- data/lib/rubycord/data/embed.rb +249 -0
- data/lib/rubycord/data/emoji.rb +80 -0
- data/lib/rubycord/data/integration.rb +120 -0
- data/lib/rubycord/data/interaction.rb +798 -0
- data/lib/rubycord/data/invite.rb +135 -0
- data/lib/rubycord/data/member.rb +370 -0
- data/lib/rubycord/data/message.rb +412 -0
- data/lib/rubycord/data/overwrite.rb +106 -0
- data/lib/rubycord/data/profile.rb +89 -0
- data/lib/rubycord/data/reaction.rb +31 -0
- data/lib/rubycord/data/recipient.rb +32 -0
- data/lib/rubycord/data/role.rb +246 -0
- data/lib/rubycord/data/server.rb +1002 -0
- data/lib/rubycord/data/user.rb +261 -0
- data/lib/rubycord/data/voice_region.rb +43 -0
- data/lib/rubycord/data/voice_state.rb +39 -0
- data/lib/rubycord/data/webhook.rb +232 -0
- data/lib/rubycord/data.rb +40 -0
- data/lib/rubycord/errors.rb +737 -0
- data/lib/rubycord/events/await.rb +46 -0
- data/lib/rubycord/events/bans.rb +58 -0
- data/lib/rubycord/events/channels.rb +186 -0
- data/lib/rubycord/events/generic.rb +126 -0
- data/lib/rubycord/events/guilds.rb +191 -0
- data/lib/rubycord/events/interactions.rb +480 -0
- data/lib/rubycord/events/invites.rb +123 -0
- data/lib/rubycord/events/lifetime.rb +29 -0
- data/lib/rubycord/events/members.rb +91 -0
- data/lib/rubycord/events/message.rb +337 -0
- data/lib/rubycord/events/presence.rb +127 -0
- data/lib/rubycord/events/raw.rb +45 -0
- data/lib/rubycord/events/reactions.rb +156 -0
- data/lib/rubycord/events/roles.rb +86 -0
- data/lib/rubycord/events/threads.rb +94 -0
- data/lib/rubycord/events/typing.rb +70 -0
- data/lib/rubycord/events/voice_server_update.rb +45 -0
- data/lib/rubycord/events/voice_state_update.rb +103 -0
- data/lib/rubycord/events/webhooks.rb +62 -0
- data/lib/rubycord/gateway.rb +867 -0
- data/lib/rubycord/id_object.rb +37 -0
- data/lib/rubycord/light/data.rb +60 -0
- data/lib/rubycord/light/integrations.rb +71 -0
- data/lib/rubycord/light/light_bot.rb +56 -0
- data/lib/rubycord/light.rb +6 -0
- data/lib/rubycord/logger.rb +118 -0
- data/lib/rubycord/paginator.rb +55 -0
- data/lib/rubycord/permissions.rb +251 -0
- data/lib/rubycord/version.rb +5 -0
- data/lib/rubycord/voice/encoder.rb +113 -0
- data/lib/rubycord/voice/network.rb +366 -0
- data/lib/rubycord/voice/sodium.rb +96 -0
- data/lib/rubycord/voice/voice_bot.rb +408 -0
- data/lib/rubycord/webhooks/builder.rb +100 -0
- data/lib/rubycord/webhooks/client.rb +132 -0
- data/lib/rubycord/webhooks/embeds.rb +248 -0
- data/lib/rubycord/webhooks/modal.rb +78 -0
- data/lib/rubycord/webhooks/version.rb +7 -0
- data/lib/rubycord/webhooks/view.rb +192 -0
- data/lib/rubycord/webhooks.rb +12 -0
- data/lib/rubycord/websocket.rb +70 -0
- data/lib/rubycord.rb +140 -0
- metadata +231 -0
@@ -0,0 +1,325 @@
|
|
1
|
+
module Rubycord::Commands
|
2
|
+
# Command that can be called in a chain
|
3
|
+
class Command
|
4
|
+
# @return [Hash] the attributes the command was initialized with
|
5
|
+
attr_reader :attributes
|
6
|
+
|
7
|
+
# @return [Symbol] the name of this command
|
8
|
+
attr_reader :name
|
9
|
+
|
10
|
+
# @!visibility private
|
11
|
+
def initialize(name, attributes = {}, &block)
|
12
|
+
@name = name
|
13
|
+
@attributes = {
|
14
|
+
# The lowest permission level that can use the command
|
15
|
+
permission_level: attributes[:permission_level] || 0,
|
16
|
+
|
17
|
+
# Message to display when a user does not have sufficient permissions to execute a command
|
18
|
+
permission_message: attributes[:permission_message].is_a?(FalseClass) ? nil : (attributes[:permission_message] || "You don't have permission to execute command %name%!"),
|
19
|
+
|
20
|
+
# Discord action permissions required to use this command
|
21
|
+
required_permissions: attributes[:required_permissions] || [],
|
22
|
+
|
23
|
+
# Roles required to use this command (all? comparison)
|
24
|
+
required_roles: attributes[:required_roles] || [],
|
25
|
+
|
26
|
+
# Roles allowed to use this command (any? comparison)
|
27
|
+
allowed_roles: attributes[:allowed_roles] || [],
|
28
|
+
|
29
|
+
# Channels this command can be used on
|
30
|
+
channels: attributes[:channels] || nil,
|
31
|
+
|
32
|
+
# Whether this command is usable in a command chain
|
33
|
+
chain_usable: attributes[:chain_usable].nil? ? true : attributes[:chain_usable],
|
34
|
+
|
35
|
+
# Whether this command should show up in the help command
|
36
|
+
help_available: attributes[:help_available].nil? ? true : attributes[:help_available],
|
37
|
+
|
38
|
+
# Description (for help command)
|
39
|
+
description: attributes[:description] || nil,
|
40
|
+
|
41
|
+
# Usage description (for help command and error messages)
|
42
|
+
usage: attributes[:usage] || nil,
|
43
|
+
|
44
|
+
# Array of arguments (for type-checking)
|
45
|
+
arg_types: attributes[:arg_types] || nil,
|
46
|
+
|
47
|
+
# Parameter list (for help command and error messages)
|
48
|
+
parameters: attributes[:parameters] || nil,
|
49
|
+
|
50
|
+
# Minimum number of arguments
|
51
|
+
min_args: attributes[:min_args] || 0,
|
52
|
+
|
53
|
+
# Maximum number of arguments (-1 for no limit)
|
54
|
+
max_args: attributes[:max_args] || -1,
|
55
|
+
|
56
|
+
# Message to display upon rate limiting (%time% in the message for the remaining time until the next possible
|
57
|
+
# request, nil for no message)
|
58
|
+
rate_limit_message: attributes[:rate_limit_message],
|
59
|
+
|
60
|
+
# Rate limiting bucket (nil for no rate limiting)
|
61
|
+
bucket: attributes[:bucket],
|
62
|
+
|
63
|
+
# Block for handling internal exceptions, or a string to respond with
|
64
|
+
rescue: attributes[:rescue],
|
65
|
+
|
66
|
+
# A list of aliases that reference this command
|
67
|
+
aliases: attributes[:aliases] || []
|
68
|
+
}
|
69
|
+
|
70
|
+
@block = block
|
71
|
+
end
|
72
|
+
|
73
|
+
# Calls this command and executes the code inside.
|
74
|
+
# @param event [CommandEvent] The event to call the command with.
|
75
|
+
# @param arguments [Array<String>] The attributes for the command.
|
76
|
+
# @param chained [true, false] Whether or not this command is part of a command chain.
|
77
|
+
# @param check_permissions [true, false] Whether the user's permission to execute the command (i.e. rate limits)
|
78
|
+
# should be checked.
|
79
|
+
# @return [String] the result of the execution.
|
80
|
+
def call(event, arguments, chained = false, check_permissions = true)
|
81
|
+
if arguments.length < @attributes[:min_args]
|
82
|
+
response = "Too few arguments for command `#{name}`!"
|
83
|
+
response += "\nUsage: `#{@attributes[:usage]}`" if @attributes[:usage]
|
84
|
+
event.respond(response)
|
85
|
+
return
|
86
|
+
end
|
87
|
+
if @attributes[:max_args] >= 0 && arguments.length > @attributes[:max_args]
|
88
|
+
response = "Too many arguments for command `#{name}`!"
|
89
|
+
response += "\nUsage: `#{@attributes[:usage]}`" if @attributes[:usage]
|
90
|
+
event.respond(response)
|
91
|
+
return
|
92
|
+
end
|
93
|
+
unless @attributes[:chain_usable] && !chained
|
94
|
+
event.respond "Command `#{name}` cannot be used in a command chain!"
|
95
|
+
return
|
96
|
+
end
|
97
|
+
|
98
|
+
if check_permissions
|
99
|
+
rate_limited = event.bot.rate_limited?(@attributes[:bucket], event.author)
|
100
|
+
if @attributes[:bucket] && rate_limited
|
101
|
+
event.respond @attributes[:rate_limit_message].gsub("%time%", rate_limited.round(2).to_s) if @attributes[:rate_limit_message]
|
102
|
+
return
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
result = @block.call(event, *arguments)
|
107
|
+
event.drain_into(result)
|
108
|
+
rescue LocalJumpError => e # occurs when breaking
|
109
|
+
result = e.exit_value
|
110
|
+
event.drain_into(result)
|
111
|
+
rescue => e # Something went wrong inside our @block!
|
112
|
+
rescue_value = @attributes[:rescue] || event.bot.attributes[:rescue]
|
113
|
+
if rescue_value
|
114
|
+
event.respond(rescue_value.gsub("%exception%", e.message)) if rescue_value.is_a?(String)
|
115
|
+
rescue_value.call(event, e) if rescue_value.respond_to?(:call)
|
116
|
+
end
|
117
|
+
|
118
|
+
raise e
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# A command that references another command
|
123
|
+
class CommandAlias
|
124
|
+
# @return [Symbol] the name of this alias
|
125
|
+
attr_reader :name
|
126
|
+
|
127
|
+
# @return [Command] the command this alias points to
|
128
|
+
attr_reader :aliased_command
|
129
|
+
|
130
|
+
def initialize(name, aliased_command)
|
131
|
+
@name = name
|
132
|
+
@aliased_command = aliased_command
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Command chain, may have multiple commands, nested and commands
|
137
|
+
class CommandChain
|
138
|
+
# @param chain [String] The string the chain should be parsed from.
|
139
|
+
# @param bot [CommandBot] The bot that executes this command chain.
|
140
|
+
# @param subchain [true, false] Whether this chain is a sub chain of another chain.
|
141
|
+
def initialize(chain, bot, subchain = false)
|
142
|
+
@attributes = bot.attributes
|
143
|
+
@chain = chain
|
144
|
+
@bot = bot
|
145
|
+
@subchain = subchain
|
146
|
+
end
|
147
|
+
|
148
|
+
# Parses the command chain itself, including sub-chains, and executes it. Executes only the command chain, without
|
149
|
+
# its chain arguments.
|
150
|
+
# @param event [CommandEvent] The event to execute the chain with.
|
151
|
+
# @return [String] the result of the execution.
|
152
|
+
def execute_bare(event)
|
153
|
+
b_start = -1
|
154
|
+
b_level = 0
|
155
|
+
result = ""
|
156
|
+
quoted = false
|
157
|
+
escaped = false
|
158
|
+
hacky_delim, hacky_space, hacky_prev, hacky_newline = [0xe001, 0xe002, 0xe003, 0xe004].pack("U*").chars
|
159
|
+
|
160
|
+
@chain.each_char.with_index do |char, index|
|
161
|
+
# Escape character
|
162
|
+
if char == "\\" && !escaped
|
163
|
+
escaped = true
|
164
|
+
next
|
165
|
+
elsif escaped && b_level <= 0
|
166
|
+
result += char
|
167
|
+
escaped = false
|
168
|
+
next
|
169
|
+
end
|
170
|
+
|
171
|
+
if quoted
|
172
|
+
# Quote end
|
173
|
+
if char == @attributes[:quote_end]
|
174
|
+
quoted = false
|
175
|
+
next
|
176
|
+
end
|
177
|
+
|
178
|
+
if b_level <= 0
|
179
|
+
case char
|
180
|
+
when @attributes[:chain_delimiter]
|
181
|
+
result += hacky_delim
|
182
|
+
next
|
183
|
+
when @attributes[:previous]
|
184
|
+
result += hacky_prev
|
185
|
+
next
|
186
|
+
when " "
|
187
|
+
result += hacky_space
|
188
|
+
next
|
189
|
+
when "\n"
|
190
|
+
result += hacky_newline
|
191
|
+
next
|
192
|
+
end
|
193
|
+
end
|
194
|
+
else
|
195
|
+
case char
|
196
|
+
when @attributes[:quote_start] # Quote begin
|
197
|
+
quoted = true
|
198
|
+
next
|
199
|
+
when @attributes[:sub_chain_start]
|
200
|
+
b_start = index if b_level.zero?
|
201
|
+
b_level += 1
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
result += char if b_level <= 0
|
206
|
+
|
207
|
+
next unless char == @attributes[:sub_chain_end] && !quoted
|
208
|
+
|
209
|
+
b_level -= 1
|
210
|
+
next unless b_level.zero?
|
211
|
+
|
212
|
+
nested = @chain[b_start + 1..index - 1]
|
213
|
+
subchain = CommandChain.new(nested, @bot, true)
|
214
|
+
result += subchain.execute(event)
|
215
|
+
end
|
216
|
+
|
217
|
+
event.respond("Your subchains are mismatched! Make sure you don't have any extra #{@attributes[:sub_chain_start]}'s or #{@attributes[:sub_chain_end]}'s") unless b_level.zero?
|
218
|
+
|
219
|
+
@chain = result
|
220
|
+
|
221
|
+
@chain_args, @chain = divide_chain(@chain)
|
222
|
+
|
223
|
+
prev = ""
|
224
|
+
|
225
|
+
chain_to_split = @chain
|
226
|
+
|
227
|
+
# Don't break if a command is called the same thing as the chain delimiter
|
228
|
+
chain_to_split = chain_to_split.slice(1..-1) if !@attributes[:chain_delimiter].empty? && chain_to_split.start_with?(@attributes[:chain_delimiter])
|
229
|
+
|
230
|
+
first = true
|
231
|
+
split_chain = if @attributes[:chain_delimiter].empty?
|
232
|
+
[chain_to_split]
|
233
|
+
else
|
234
|
+
chain_to_split.split(@attributes[:chain_delimiter])
|
235
|
+
end
|
236
|
+
split_chain.each do |command|
|
237
|
+
command = @attributes[:chain_delimiter] + command if first && @chain.start_with?(@attributes[:chain_delimiter])
|
238
|
+
first = false
|
239
|
+
|
240
|
+
command = command.strip
|
241
|
+
|
242
|
+
# Replace the hacky delimiter that was used inside quotes with actual delimiters
|
243
|
+
command = command.gsub hacky_delim, @attributes[:chain_delimiter]
|
244
|
+
|
245
|
+
first_space = command.index " "
|
246
|
+
command_name = first_space ? command[0..first_space - 1] : command
|
247
|
+
arguments = first_space ? command[first_space + 1..] : ""
|
248
|
+
|
249
|
+
# Append a previous sign if none is present
|
250
|
+
arguments += @attributes[:previous] unless arguments.include? @attributes[:previous]
|
251
|
+
arguments = arguments.gsub @attributes[:previous], prev
|
252
|
+
|
253
|
+
# Replace hacky previous signs with actual ones
|
254
|
+
arguments = arguments.gsub hacky_prev, @attributes[:previous]
|
255
|
+
|
256
|
+
arguments = arguments.split " "
|
257
|
+
|
258
|
+
# Replace the hacky spaces/newlines with actual ones
|
259
|
+
arguments.map! do |elem|
|
260
|
+
elem.gsub(hacky_space, " ").gsub(hacky_newline, "\n")
|
261
|
+
end
|
262
|
+
|
263
|
+
# Finally execute the command
|
264
|
+
prev = @bot.execute_command(command_name.to_sym, event, arguments, split_chain.length > 1 || @subchain)
|
265
|
+
|
266
|
+
# Stop chain if execute_command failed (maybe the command doesn't exist, or permissions failed, etc.)
|
267
|
+
break unless prev
|
268
|
+
end
|
269
|
+
|
270
|
+
prev
|
271
|
+
end
|
272
|
+
|
273
|
+
# Divides the command chain into chain arguments and command chain, then executes them both.
|
274
|
+
# @param event [CommandEvent] The event to execute the command with.
|
275
|
+
# @return [String] the result of the command chain execution.
|
276
|
+
def execute(event)
|
277
|
+
old_chain = @chain
|
278
|
+
@bot.debug "Executing bare chain"
|
279
|
+
result = execute_bare(event)
|
280
|
+
|
281
|
+
@chain_args ||= []
|
282
|
+
|
283
|
+
@bot.debug "Found chain args #{@chain_args}, preliminary result #{result}"
|
284
|
+
|
285
|
+
@chain_args.each do |arg|
|
286
|
+
case arg.first
|
287
|
+
when "repeat"
|
288
|
+
new_result = ""
|
289
|
+
executed_chain = divide_chain(old_chain).last
|
290
|
+
|
291
|
+
arg[1].to_i.times do
|
292
|
+
chain_result = CommandChain.new(executed_chain, @bot).execute(event)
|
293
|
+
new_result += chain_result if chain_result
|
294
|
+
end
|
295
|
+
|
296
|
+
result = new_result
|
297
|
+
# TODO: more chain arguments
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
result
|
302
|
+
end
|
303
|
+
|
304
|
+
private
|
305
|
+
|
306
|
+
def divide_chain(chain)
|
307
|
+
chain_args_index = chain.index(@attributes[:chain_args_delim]) unless @attributes[:chain_args_delim].empty?
|
308
|
+
chain_args = []
|
309
|
+
|
310
|
+
if chain_args_index
|
311
|
+
chain_args = chain[0..chain_args_index].split ","
|
312
|
+
|
313
|
+
# Split up the arguments
|
314
|
+
|
315
|
+
chain_args.map! do |arg|
|
316
|
+
arg.split " "
|
317
|
+
end
|
318
|
+
|
319
|
+
chain = chain[chain_args_index + 1..]
|
320
|
+
end
|
321
|
+
|
322
|
+
[chain_args, chain]
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module Rubycord::Commands
|
2
|
+
# This class represents a bucket for rate limiting - it keeps track of how many requests have been made and when
|
3
|
+
# exactly the user should be rate limited.
|
4
|
+
class Bucket
|
5
|
+
# Makes a new bucket
|
6
|
+
# @param limit [Integer, nil] How many requests the user may perform in the given time_span, or nil if there should be no limit.
|
7
|
+
# @param time_span [Integer, nil] The time span after which the request count is reset, in seconds, or nil if the bucket should never be reset. (If this is nil, limit should be nil too)
|
8
|
+
# @param delay [Integer, nil] The delay for which the user has to wait after performing a request, in seconds, or nil if the user shouldn't have to wait.
|
9
|
+
def initialize(limit, time_span, delay)
|
10
|
+
raise ArgumentError, "`limit` and `time_span` have to either both be set or both be nil!" if !limit != !time_span
|
11
|
+
|
12
|
+
@limit = limit
|
13
|
+
@time_span = time_span
|
14
|
+
@delay = delay
|
15
|
+
|
16
|
+
@bucket = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
# Cleans the bucket, removing all elements that aren't necessary anymore
|
20
|
+
# @param rate_limit_time [Time] The time to base the cleaning on, only useful for testing.
|
21
|
+
def clean(rate_limit_time = nil)
|
22
|
+
rate_limit_time ||= Time.now
|
23
|
+
|
24
|
+
@bucket.delete_if do |_, limit_hash|
|
25
|
+
# Time limit has not run out
|
26
|
+
return false if @time_span && rate_limit_time < (limit_hash[:set_time] + @time_span)
|
27
|
+
|
28
|
+
# Delay has not run out
|
29
|
+
return false if @delay && rate_limit_time < (limit_hash[:last_time] + @delay)
|
30
|
+
|
31
|
+
true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Performs a rate limiting request
|
36
|
+
# @param thing [String, Integer, Symbol] The particular thing that should be rate-limited (usually a user/channel, but you can also choose arbitrary integers or symbols)
|
37
|
+
# @param rate_limit_time [Time] The time to base the rate limiting on, only useful for testing.
|
38
|
+
# @param increment [Integer] How much to increment the rate-limit counter. Default is 1.
|
39
|
+
# @return [Integer, false] the waiting time until the next request, in seconds, or false if the request succeeded
|
40
|
+
def rate_limited?(thing, rate_limit_time = nil, increment: 1)
|
41
|
+
key = resolve_key thing
|
42
|
+
limit_hash = @bucket[key]
|
43
|
+
|
44
|
+
# First case: limit_hash doesn't exist yet
|
45
|
+
unless limit_hash
|
46
|
+
@bucket[key] = {
|
47
|
+
last_time: Time.now,
|
48
|
+
set_time: Time.now,
|
49
|
+
count: increment
|
50
|
+
}
|
51
|
+
|
52
|
+
return false
|
53
|
+
end
|
54
|
+
|
55
|
+
# Define the time at which we're being rate limited once so it doesn't get inaccurate
|
56
|
+
rate_limit_time ||= Time.now
|
57
|
+
|
58
|
+
if @limit && (limit_hash[:count] + increment) > @limit
|
59
|
+
# Second case: Count is over the limit and the time has not run out yet
|
60
|
+
return (limit_hash[:set_time] + @time_span) - rate_limit_time if @time_span && rate_limit_time < (limit_hash[:set_time] + @time_span)
|
61
|
+
|
62
|
+
# Third case: Count is over the limit but the time has run out
|
63
|
+
# Don't return anything here because there may still be delay-based limiting
|
64
|
+
limit_hash[:set_time] = rate_limit_time
|
65
|
+
limit_hash[:count] = 0
|
66
|
+
end
|
67
|
+
|
68
|
+
if @delay && rate_limit_time < (limit_hash[:last_time] + @delay)
|
69
|
+
# Fourth case: we're being delayed
|
70
|
+
(limit_hash[:last_time] + @delay) - rate_limit_time
|
71
|
+
else
|
72
|
+
# Fifth case: no rate limiting at all! Increment the count, set the last_time, and return false
|
73
|
+
limit_hash[:last_time] = rate_limit_time
|
74
|
+
limit_hash[:count] += increment
|
75
|
+
false
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def resolve_key(thing)
|
82
|
+
return thing.resolve_id if thing.respond_to?(:resolve_id) && !thing.is_a?(String)
|
83
|
+
return thing if thing.is_a?(Integer) || thing.is_a?(Symbol)
|
84
|
+
|
85
|
+
raise ArgumentError, "Cannot use a #{thing.class} as a rate limiting key!"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Represents a collection of {Bucket}s.
|
90
|
+
module RateLimiter
|
91
|
+
# Defines a new bucket for this rate limiter.
|
92
|
+
# @param key [Symbol] The name for this new bucket.
|
93
|
+
# @param attributes [Hash] The attributes to initialize the bucket with.
|
94
|
+
# @option attributes [Integer] :limit The limit of requests to perform in the given time span.
|
95
|
+
# @option attributes [Integer] :time_span How many seconds until the limit should be reset.
|
96
|
+
# @option attributes [Integer] :delay How many seconds the user has to wait after each request.
|
97
|
+
# @see Bucket#initialize
|
98
|
+
# @return [Bucket] the created bucket.
|
99
|
+
def bucket(key, attributes)
|
100
|
+
@buckets ||= {}
|
101
|
+
@buckets[key] = Bucket.new(attributes[:limit], attributes[:time_span], attributes[:delay])
|
102
|
+
end
|
103
|
+
|
104
|
+
# Performs a rate limit request.
|
105
|
+
# @param key [Symbol] Which bucket to perform the request for.
|
106
|
+
# @param thing [String, Integer, Symbol] What should be rate-limited.
|
107
|
+
# @param increment (see Bucket#rate_limited?)
|
108
|
+
# @see Bucket#rate_limited?
|
109
|
+
# @return [Integer, false] How much time to wait or false if the request succeeded.
|
110
|
+
def rate_limited?(key, thing, increment: 1)
|
111
|
+
# Check whether the bucket actually exists
|
112
|
+
return false unless @buckets && @buckets[key]
|
113
|
+
|
114
|
+
@buckets[key].rate_limited?(thing, increment: increment)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Cleans all buckets
|
118
|
+
# @see Bucket#clean
|
119
|
+
def clean
|
120
|
+
@buckets.each(&:clean)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Adds all the buckets from another RateLimiter onto this one.
|
124
|
+
# @param limiter [Module] Another {RateLimiter} module
|
125
|
+
def include_buckets(limiter)
|
126
|
+
buckets = limiter.instance_variable_get(:@buckets) || {}
|
127
|
+
@buckets ||= {}
|
128
|
+
@buckets.merge! buckets
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# This class provides a convenient way to do rate-limiting on non-command events.
|
133
|
+
# @see RateLimiter
|
134
|
+
class SimpleRateLimiter
|
135
|
+
include RateLimiter
|
136
|
+
|
137
|
+
# Makes a new rate limiter
|
138
|
+
def initialize
|
139
|
+
@buckets = {}
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|