onyxcord 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. checksums.yaml +7 -0
  2. data/.devcontainer/Dockerfile +13 -0
  3. data/.devcontainer/devcontainer.json +29 -0
  4. data/.devcontainer/postcreate.sh +4 -0
  5. data/.github/CONTRIBUTING.md +13 -0
  6. data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  7. data/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
  8. data/.github/pull_request_template.md +37 -0
  9. data/.github/workflows/ci.yml +78 -0
  10. data/.github/workflows/codeql.yml +65 -0
  11. data/.github/workflows/deploy.yml +54 -0
  12. data/.github/workflows/release.yml +51 -0
  13. data/.gitignore +16 -0
  14. data/.markdownlint.json +4 -0
  15. data/.overcommit.yml +7 -0
  16. data/.rspec +2 -0
  17. data/.rubocop.yml +129 -0
  18. data/.yardopts +1 -0
  19. data/CHANGELOG.md +0 -0
  20. data/Gemfile +7 -0
  21. data/LICENSE.txt +21 -0
  22. data/README.md +305 -0
  23. data/Rakefile +17 -0
  24. data/bin/console +15 -0
  25. data/bin/setup +7 -0
  26. data/lib/onyxcord/allowed_mentions.rb +43 -0
  27. data/lib/onyxcord/api/application.rb +316 -0
  28. data/lib/onyxcord/api/channel.rb +700 -0
  29. data/lib/onyxcord/api/interaction.rb +67 -0
  30. data/lib/onyxcord/api/invite.rb +44 -0
  31. data/lib/onyxcord/api/server.rb +775 -0
  32. data/lib/onyxcord/api/user.rb +158 -0
  33. data/lib/onyxcord/api/webhook.rb +163 -0
  34. data/lib/onyxcord/api.rb +335 -0
  35. data/lib/onyxcord/await.rb +51 -0
  36. data/lib/onyxcord/bot.rb +1971 -0
  37. data/lib/onyxcord/cache.rb +326 -0
  38. data/lib/onyxcord/colour_rgb.rb +43 -0
  39. data/lib/onyxcord/commands/command_bot.rb +511 -0
  40. data/lib/onyxcord/commands/container.rb +112 -0
  41. data/lib/onyxcord/commands/events.rb +11 -0
  42. data/lib/onyxcord/commands/parser.rb +327 -0
  43. data/lib/onyxcord/commands/rate_limiter.rb +144 -0
  44. data/lib/onyxcord/configuration.rb +125 -0
  45. data/lib/onyxcord/container.rb +988 -0
  46. data/lib/onyxcord/data/activity.rb +271 -0
  47. data/lib/onyxcord/data/application.rb +341 -0
  48. data/lib/onyxcord/data/attachment.rb +91 -0
  49. data/lib/onyxcord/data/audit_logs.rb +438 -0
  50. data/lib/onyxcord/data/avatar_decoration.rb +26 -0
  51. data/lib/onyxcord/data/call.rb +22 -0
  52. data/lib/onyxcord/data/channel.rb +1355 -0
  53. data/lib/onyxcord/data/channel_tag.rb +69 -0
  54. data/lib/onyxcord/data/collectibles.rb +47 -0
  55. data/lib/onyxcord/data/component.rb +583 -0
  56. data/lib/onyxcord/data/embed.rb +258 -0
  57. data/lib/onyxcord/data/emoji.rb +123 -0
  58. data/lib/onyxcord/data/install_params.rb +24 -0
  59. data/lib/onyxcord/data/integration.rb +144 -0
  60. data/lib/onyxcord/data/interaction.rb +1141 -0
  61. data/lib/onyxcord/data/invite.rb +137 -0
  62. data/lib/onyxcord/data/member.rb +528 -0
  63. data/lib/onyxcord/data/message.rb +612 -0
  64. data/lib/onyxcord/data/message_activity.rb +41 -0
  65. data/lib/onyxcord/data/overwrite.rb +109 -0
  66. data/lib/onyxcord/data/poll.rb +365 -0
  67. data/lib/onyxcord/data/primary_server.rb +60 -0
  68. data/lib/onyxcord/data/profile.rb +79 -0
  69. data/lib/onyxcord/data/reaction.rb +64 -0
  70. data/lib/onyxcord/data/recipient.rb +34 -0
  71. data/lib/onyxcord/data/role.rb +449 -0
  72. data/lib/onyxcord/data/role_connection_data.rb +69 -0
  73. data/lib/onyxcord/data/role_subscription.rb +41 -0
  74. data/lib/onyxcord/data/scheduled_event.rb +513 -0
  75. data/lib/onyxcord/data/server.rb +1614 -0
  76. data/lib/onyxcord/data/server_preview.rb +68 -0
  77. data/lib/onyxcord/data/snapshot.rb +112 -0
  78. data/lib/onyxcord/data/team.rb +98 -0
  79. data/lib/onyxcord/data/timestamp.rb +69 -0
  80. data/lib/onyxcord/data/user.rb +324 -0
  81. data/lib/onyxcord/data/voice_region.rb +46 -0
  82. data/lib/onyxcord/data/voice_state.rb +41 -0
  83. data/lib/onyxcord/data/webhook.rb +238 -0
  84. data/lib/onyxcord/data.rb +57 -0
  85. data/lib/onyxcord/errors.rb +246 -0
  86. data/lib/onyxcord/event_executor.rb +80 -0
  87. data/lib/onyxcord/events/await.rb +48 -0
  88. data/lib/onyxcord/events/bans.rb +60 -0
  89. data/lib/onyxcord/events/channels.rb +225 -0
  90. data/lib/onyxcord/events/generic.rb +129 -0
  91. data/lib/onyxcord/events/guilds.rb +269 -0
  92. data/lib/onyxcord/events/integrations.rb +100 -0
  93. data/lib/onyxcord/events/interactions.rb +624 -0
  94. data/lib/onyxcord/events/invites.rb +127 -0
  95. data/lib/onyxcord/events/lifetime.rb +31 -0
  96. data/lib/onyxcord/events/members.rb +110 -0
  97. data/lib/onyxcord/events/message.rb +399 -0
  98. data/lib/onyxcord/events/polls.rb +118 -0
  99. data/lib/onyxcord/events/presence.rb +131 -0
  100. data/lib/onyxcord/events/raw.rb +74 -0
  101. data/lib/onyxcord/events/reactions.rb +218 -0
  102. data/lib/onyxcord/events/roles.rb +87 -0
  103. data/lib/onyxcord/events/scheduled_events.rb +171 -0
  104. data/lib/onyxcord/events/threads.rb +100 -0
  105. data/lib/onyxcord/events/typing.rb +73 -0
  106. data/lib/onyxcord/events/voice_server_update.rb +48 -0
  107. data/lib/onyxcord/events/voice_state_update.rb +106 -0
  108. data/lib/onyxcord/events/webhooks.rb +65 -0
  109. data/lib/onyxcord/gateway.rb +890 -0
  110. data/lib/onyxcord/id_object.rb +39 -0
  111. data/lib/onyxcord/light/data.rb +62 -0
  112. data/lib/onyxcord/light/integrations.rb +73 -0
  113. data/lib/onyxcord/light/light_bot.rb +58 -0
  114. data/lib/onyxcord/light.rb +8 -0
  115. data/lib/onyxcord/logger.rb +120 -0
  116. data/lib/onyxcord/message_components.rb +70 -0
  117. data/lib/onyxcord/paginator.rb +60 -0
  118. data/lib/onyxcord/permissions.rb +255 -0
  119. data/lib/onyxcord/rate_limiter/gateway.rb +42 -0
  120. data/lib/onyxcord/rate_limiter/rest.rb +89 -0
  121. data/lib/onyxcord/version.rb +7 -0
  122. data/lib/onyxcord/voice/encoder.rb +115 -0
  123. data/lib/onyxcord/voice/network.rb +380 -0
  124. data/lib/onyxcord/voice/opcodes.rb +29 -0
  125. data/lib/onyxcord/voice/sodium.rb +157 -0
  126. data/lib/onyxcord/voice/timer.rb +19 -0
  127. data/lib/onyxcord/voice/voice_bot.rb +386 -0
  128. data/lib/onyxcord/webhooks.rb +14 -0
  129. data/lib/onyxcord/websocket.rb +62 -0
  130. data/lib/onyxcord.rb +180 -0
  131. data/onyxcord-webhooks.gemspec +30 -0
  132. data/onyxcord.gemspec +50 -0
  133. metadata +421 -0
@@ -0,0 +1,327 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OnyxCord::Commands
4
+ # Command that can be called in a chain
5
+ class Command
6
+ # @return [Hash] the attributes the command was initialized with
7
+ attr_reader :attributes
8
+
9
+ # @return [Symbol] the name of this command
10
+ attr_reader :name
11
+
12
+ # @!visibility private
13
+ def initialize(name, attributes = {}, &block)
14
+ @name = name
15
+ @attributes = {
16
+ # The lowest permission level that can use the command
17
+ permission_level: attributes[:permission_level] || 0,
18
+
19
+ # Message to display when a user does not have sufficient permissions to execute a command
20
+ permission_message: attributes[:permission_message].is_a?(FalseClass) ? nil : (attributes[:permission_message] || "You don't have permission to execute command %name%!"),
21
+
22
+ # Discord action permissions required to use this command
23
+ required_permissions: attributes[:required_permissions] || [],
24
+
25
+ # Roles required to use this command (all? comparison)
26
+ required_roles: attributes[:required_roles] || [],
27
+
28
+ # Roles allowed to use this command (any? comparison)
29
+ allowed_roles: attributes[:allowed_roles] || [],
30
+
31
+ # Channels this command can be used on
32
+ channels: attributes[:channels] || nil,
33
+
34
+ # Whether this command is usable in a command chain
35
+ chain_usable: attributes[:chain_usable].nil? || attributes[:chain_usable],
36
+
37
+ # Whether this command should show up in the help command
38
+ help_available: attributes[:help_available].nil? || attributes[:help_available],
39
+
40
+ # Description (for help command)
41
+ description: attributes[:description] || nil,
42
+
43
+ # Usage description (for help command and error messages)
44
+ usage: attributes[:usage] || nil,
45
+
46
+ # Array of arguments (for type-checking)
47
+ arg_types: attributes[:arg_types] || nil,
48
+
49
+ # Parameter list (for help command and error messages)
50
+ parameters: attributes[:parameters] || nil,
51
+
52
+ # Minimum number of arguments
53
+ min_args: attributes[:min_args] || 0,
54
+
55
+ # Maximum number of arguments (-1 for no limit)
56
+ max_args: attributes[:max_args] || -1,
57
+
58
+ # Message to display upon rate limiting (%time% in the message for the remaining time until the next possible
59
+ # request, nil for no message)
60
+ rate_limit_message: attributes[:rate_limit_message],
61
+
62
+ # Rate limiting bucket (nil for no rate limiting)
63
+ bucket: attributes[:bucket],
64
+
65
+ # Block for handling internal exceptions, or a string to respond with
66
+ rescue: attributes[:rescue],
67
+
68
+ # A list of aliases that reference this command
69
+ aliases: attributes[:aliases] || []
70
+ }
71
+
72
+ @block = block
73
+ end
74
+
75
+ # Calls this command and executes the code inside.
76
+ # @param event [CommandEvent] The event to call the command with.
77
+ # @param arguments [Array<String>] The attributes for the command.
78
+ # @param chained [true, false] Whether or not this command is part of a command chain.
79
+ # @param check_permissions [true, false] Whether the user's permission to execute the command (i.e. rate limits)
80
+ # should be checked.
81
+ # @return [String] the result of the execution.
82
+ def call(event, arguments, chained = false, check_permissions = true)
83
+ if arguments.length < @attributes[:min_args]
84
+ response = "Too few arguments for command `#{name}`!"
85
+ response += "\nUsage: `#{@attributes[:usage]}`" if @attributes[:usage]
86
+ event.respond(response)
87
+ return
88
+ end
89
+ if @attributes[:max_args] >= 0 && arguments.length > @attributes[:max_args]
90
+ response = "Too many arguments for command `#{name}`!"
91
+ response += "\nUsage: `#{@attributes[:usage]}`" if @attributes[:usage]
92
+ event.respond(response)
93
+ return
94
+ end
95
+ unless @attributes[:chain_usable] && !chained
96
+ event.respond "Command `#{name}` cannot be used in a command chain!"
97
+ return
98
+ end
99
+
100
+ if check_permissions
101
+ rate_limited = event.bot.rate_limited?(@attributes[:bucket], event.author)
102
+ if @attributes[:bucket] && rate_limited
103
+ event.respond @attributes[:rate_limit_message].gsub('%time%', rate_limited.round(2).to_s) if @attributes[:rate_limit_message]
104
+ return
105
+ end
106
+ end
107
+
108
+ result = @block.call(event, *arguments)
109
+ event.drain_into(result)
110
+ rescue LocalJumpError => e # occurs when breaking
111
+ result = e.exit_value
112
+ event.drain_into(result)
113
+ rescue StandardError => e # Something went wrong inside our @block!
114
+ rescue_value = @attributes[:rescue] || event.bot.attributes[:rescue]
115
+ if rescue_value
116
+ event.respond(rescue_value.gsub('%exception%', e.message)) if rescue_value.is_a?(String)
117
+ rescue_value.call(event, e) if rescue_value.respond_to?(:call)
118
+ end
119
+
120
+ raise e
121
+ end
122
+ end
123
+
124
+ # A command that references another command
125
+ class CommandAlias
126
+ # @return [Symbol] the name of this alias
127
+ attr_reader :name
128
+
129
+ # @return [Command] the command this alias points to
130
+ attr_reader :aliased_command
131
+
132
+ def initialize(name, aliased_command)
133
+ @name = name
134
+ @aliased_command = aliased_command
135
+ end
136
+ end
137
+
138
+ # Command chain, may have multiple commands, nested and commands
139
+ class CommandChain
140
+ # @param chain [String] The string the chain should be parsed from.
141
+ # @param bot [CommandBot] The bot that executes this command chain.
142
+ # @param subchain [true, false] Whether this chain is a sub chain of another chain.
143
+ def initialize(chain, bot, subchain = false)
144
+ @attributes = bot.attributes
145
+ @chain = chain
146
+ @bot = bot
147
+ @subchain = subchain
148
+ end
149
+
150
+ # Parses the command chain itself, including sub-chains, and executes it. Executes only the command chain, without
151
+ # its chain arguments.
152
+ # @param event [CommandEvent] The event to execute the chain with.
153
+ # @return [String] the result of the execution.
154
+ def execute_bare(event)
155
+ b_start = -1
156
+ b_level = 0
157
+ result = ''
158
+ quoted = false
159
+ escaped = false
160
+ hacky_delim, hacky_space, hacky_prev, hacky_newline = [0xe001, 0xe002, 0xe003, 0xe004].pack('U*').chars
161
+
162
+ @chain.each_char.with_index do |char, index|
163
+ # Escape character
164
+ if char == '\\' && !escaped
165
+ escaped = true
166
+ next
167
+ elsif escaped && b_level <= 0
168
+ result += char
169
+ escaped = false
170
+ next
171
+ end
172
+
173
+ if quoted
174
+ # Quote end
175
+ if char == @attributes[:quote_end]
176
+ quoted = false
177
+ next
178
+ end
179
+
180
+ if b_level <= 0
181
+ case char
182
+ when @attributes[:chain_delimiter]
183
+ result += hacky_delim
184
+ next
185
+ when @attributes[:previous]
186
+ result += hacky_prev
187
+ next
188
+ when ' '
189
+ result += hacky_space
190
+ next
191
+ when "\n"
192
+ result += hacky_newline
193
+ next
194
+ end
195
+ end
196
+ else
197
+ case char
198
+ when @attributes[:quote_start] # Quote begin
199
+ quoted = true
200
+ next
201
+ when @attributes[:sub_chain_start]
202
+ b_start = index if b_level.zero?
203
+ b_level += 1
204
+ end
205
+ end
206
+
207
+ result += char if b_level <= 0
208
+
209
+ next unless char == @attributes[:sub_chain_end] && !quoted
210
+
211
+ b_level -= 1
212
+ next unless b_level.zero?
213
+
214
+ nested = @chain[(b_start + 1)..(index - 1)]
215
+ subchain = CommandChain.new(nested, @bot, true)
216
+ result += subchain.execute(event)
217
+ end
218
+
219
+ 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?
220
+
221
+ @chain = result
222
+
223
+ @chain_args, @chain = divide_chain(@chain)
224
+
225
+ prev = ''
226
+
227
+ chain_to_split = @chain
228
+
229
+ # Don't break if a command is called the same thing as the chain delimiter
230
+ chain_to_split = chain_to_split.slice(1..-1) if !@attributes[:chain_delimiter].empty? && chain_to_split.start_with?(@attributes[:chain_delimiter])
231
+
232
+ first = true
233
+ split_chain = if @attributes[:chain_delimiter].empty?
234
+ [chain_to_split]
235
+ else
236
+ chain_to_split.split(@attributes[:chain_delimiter])
237
+ end
238
+ split_chain.each do |command|
239
+ command = @attributes[:chain_delimiter] + command if first && @chain.start_with?(@attributes[:chain_delimiter])
240
+ first = false
241
+
242
+ command = command.strip
243
+
244
+ # Replace the hacky delimiter that was used inside quotes with actual delimiters
245
+ command = command.gsub hacky_delim, @attributes[:chain_delimiter]
246
+
247
+ first_space = command.index ' '
248
+ command_name = first_space ? command[0..(first_space - 1)] : command
249
+ arguments = first_space ? command[(first_space + 1)..] : ''
250
+
251
+ # Append a previous sign if none is present
252
+ arguments += @attributes[:previous] unless arguments.include? @attributes[:previous]
253
+ arguments = arguments.gsub @attributes[:previous], prev
254
+
255
+ # Replace hacky previous signs with actual ones
256
+ arguments = arguments.gsub hacky_prev, @attributes[:previous]
257
+
258
+ arguments = arguments.split ' '
259
+
260
+ # Replace the hacky spaces/newlines with actual ones
261
+ arguments.map! do |elem|
262
+ elem.gsub(hacky_space, ' ').gsub(hacky_newline, "\n")
263
+ end
264
+
265
+ # Finally execute the command
266
+ prev = @bot.execute_command(command_name.to_sym, event, arguments, split_chain.length > 1 || @subchain)
267
+
268
+ # Stop chain if execute_command failed (maybe the command doesn't exist, or permissions failed, etc.)
269
+ break unless prev
270
+ end
271
+
272
+ prev
273
+ end
274
+
275
+ # Divides the command chain into chain arguments and command chain, then executes them both.
276
+ # @param event [CommandEvent] The event to execute the command with.
277
+ # @return [String] the result of the command chain execution.
278
+ def execute(event)
279
+ old_chain = @chain
280
+ @bot.debug 'Executing bare chain'
281
+ result = execute_bare(event)
282
+
283
+ @chain_args ||= []
284
+
285
+ @bot.debug "Found chain args #{@chain_args}, preliminary result #{result}"
286
+
287
+ @chain_args.each do |arg|
288
+ case arg.first
289
+ when 'repeat'
290
+ new_result = ''
291
+ executed_chain = divide_chain(old_chain).last
292
+
293
+ arg[1].to_i.times do
294
+ chain_result = CommandChain.new(executed_chain, @bot).execute(event)
295
+ new_result += chain_result if chain_result
296
+ end
297
+
298
+ result = new_result
299
+ # TODO: more chain arguments
300
+ end
301
+ end
302
+
303
+ result
304
+ end
305
+
306
+ private
307
+
308
+ def divide_chain(chain)
309
+ chain_args_index = chain.index(@attributes[:chain_args_delim]) unless @attributes[:chain_args_delim].empty?
310
+ chain_args = []
311
+
312
+ if chain_args_index
313
+ chain_args = chain[0..chain_args_index].split ','
314
+
315
+ # Split up the arguments
316
+
317
+ chain_args.map! do |arg|
318
+ arg.split ' '
319
+ end
320
+
321
+ chain = chain[(chain_args_index + 1)..]
322
+ end
323
+
324
+ [chain_args, chain]
325
+ end
326
+ end
327
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OnyxCord::Commands
4
+ # This class represents a bucket for rate limiting - it keeps track of how many requests have been made and when
5
+ # exactly the user should be rate limited.
6
+ class Bucket
7
+ # Makes a new bucket
8
+ # @param limit [Integer, nil] How many requests the user may perform in the given time_span, or nil if there should be no limit.
9
+ # @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)
10
+ # @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.
11
+ def initialize(limit, time_span, delay)
12
+ raise ArgumentError, '`limit` and `time_span` have to either both be set or both be nil!' if !limit != !time_span
13
+
14
+ @limit = limit
15
+ @time_span = time_span
16
+ @delay = delay
17
+
18
+ @bucket = {}
19
+ end
20
+
21
+ # Cleans the bucket, removing all elements that aren't necessary anymore
22
+ # @param rate_limit_time [Time] The time to base the cleaning on, only useful for testing.
23
+ def clean(rate_limit_time = nil)
24
+ rate_limit_time ||= Time.now
25
+
26
+ @bucket.delete_if do |_, limit_hash|
27
+ # Time limit has not run out
28
+ return false if @time_span && rate_limit_time < (limit_hash[:set_time] + @time_span)
29
+
30
+ # Delay has not run out
31
+ return false if @delay && rate_limit_time < (limit_hash[:last_time] + @delay)
32
+
33
+ true
34
+ end
35
+ end
36
+
37
+ # Performs a rate limiting request
38
+ # @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)
39
+ # @param rate_limit_time [Time] The time to base the rate limiting on, only useful for testing.
40
+ # @param increment [Integer] How much to increment the rate-limit counter. Default is 1.
41
+ # @return [Integer, false] the waiting time until the next request, in seconds, or false if the request succeeded
42
+ def rate_limited?(thing, rate_limit_time = nil, increment: 1)
43
+ key = resolve_key thing
44
+ limit_hash = @bucket[key]
45
+
46
+ # First case: limit_hash doesn't exist yet
47
+ unless limit_hash
48
+ @bucket[key] = {
49
+ last_time: Time.now,
50
+ set_time: Time.now,
51
+ count: increment
52
+ }
53
+
54
+ return false
55
+ end
56
+
57
+ # Define the time at which we're being rate limited once so it doesn't get inaccurate
58
+ rate_limit_time ||= Time.now
59
+
60
+ if @limit && (limit_hash[:count] + increment) > @limit
61
+ # Second case: Count is over the limit and the time has not run out yet
62
+ return (limit_hash[:set_time] + @time_span) - rate_limit_time if @time_span && rate_limit_time < (limit_hash[:set_time] + @time_span)
63
+
64
+ # Third case: Count is over the limit but the time has run out
65
+ # Don't return anything here because there may still be delay-based limiting
66
+ limit_hash[:set_time] = rate_limit_time
67
+ limit_hash[:count] = 0
68
+ end
69
+
70
+ if @delay && rate_limit_time < (limit_hash[:last_time] + @delay)
71
+ # Fourth case: we're being delayed
72
+ (limit_hash[:last_time] + @delay) - rate_limit_time
73
+ else
74
+ # Fifth case: no rate limiting at all! Increment the count, set the last_time, and return false
75
+ limit_hash[:last_time] = rate_limit_time
76
+ limit_hash[:count] += increment
77
+ false
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ def resolve_key(thing)
84
+ return thing.resolve_id if thing.respond_to?(:resolve_id) && !thing.is_a?(String)
85
+ return thing if thing.is_a?(Integer) || thing.is_a?(Symbol)
86
+
87
+ raise ArgumentError, "Cannot use a #{thing.class} as a rate limiting key!"
88
+ end
89
+ end
90
+
91
+ # Represents a collection of {Bucket}s.
92
+ module RateLimiter
93
+ # Defines a new bucket for this rate limiter.
94
+ # @param key [Symbol] The name for this new bucket.
95
+ # @param attributes [Hash] The attributes to initialize the bucket with.
96
+ # @option attributes [Integer] :limit The limit of requests to perform in the given time span.
97
+ # @option attributes [Integer] :time_span How many seconds until the limit should be reset.
98
+ # @option attributes [Integer] :delay How many seconds the user has to wait after each request.
99
+ # @see Bucket#initialize
100
+ # @return [Bucket] the created bucket.
101
+ def bucket(key, attributes)
102
+ @buckets ||= {}
103
+ @buckets[key] = Bucket.new(attributes[:limit], attributes[:time_span], attributes[:delay])
104
+ end
105
+
106
+ # Performs a rate limit request.
107
+ # @param key [Symbol] Which bucket to perform the request for.
108
+ # @param thing [String, Integer, Symbol] What should be rate-limited.
109
+ # @param increment (see Bucket#rate_limited?)
110
+ # @see Bucket#rate_limited?
111
+ # @return [Integer, false] How much time to wait or false if the request succeeded.
112
+ def rate_limited?(key, thing, increment: 1)
113
+ # Check whether the bucket actually exists
114
+ return false unless @buckets && @buckets[key]
115
+
116
+ @buckets[key].rate_limited?(thing, increment: increment)
117
+ end
118
+
119
+ # Cleans all buckets
120
+ # @see Bucket#clean
121
+ def clean
122
+ @buckets.each(&:clean)
123
+ end
124
+
125
+ # Adds all the buckets from another RateLimiter onto this one.
126
+ # @param limiter [Module] Another {RateLimiter} module
127
+ def include_buckets(limiter)
128
+ buckets = limiter.instance_variable_get(:@buckets) || {}
129
+ @buckets ||= {}
130
+ @buckets.merge! buckets
131
+ end
132
+ end
133
+
134
+ # This class provides a convenient way to do rate-limiting on non-command events.
135
+ # @see RateLimiter
136
+ class SimpleRateLimiter
137
+ include RateLimiter
138
+
139
+ # Makes a new rate limiter
140
+ def initialize
141
+ @buckets = {}
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Top-level namespace for the OnyxCord Discord framework.
4
+ module OnyxCord
5
+ # Runtime defaults for OnyxCord bots.
6
+ class Configuration
7
+ MODES = %i[raw hybrid object].freeze
8
+ EXECUTORS = %i[pool inline].freeze
9
+
10
+ CACHE_PRESETS = {
11
+ none: {
12
+ users: false,
13
+ voice_regions: false,
14
+ servers: false,
15
+ channels: false,
16
+ pm_channels: false,
17
+ thread_members: false,
18
+ server_previews: false,
19
+ members: false,
20
+ messages: false
21
+ },
22
+ minimal: {
23
+ users: false,
24
+ voice_regions: false,
25
+ servers: true,
26
+ channels: true,
27
+ pm_channels: false,
28
+ thread_members: false,
29
+ server_previews: false,
30
+ members: false,
31
+ messages: false
32
+ },
33
+ full: {
34
+ users: true,
35
+ voice_regions: true,
36
+ servers: true,
37
+ channels: true,
38
+ pm_channels: true,
39
+ thread_members: true,
40
+ server_previews: true,
41
+ members: true,
42
+ messages: true
43
+ }
44
+ }.freeze
45
+
46
+ attr_accessor :mode, :cache, :event_executor, :event_workers
47
+
48
+ def initialize
49
+ @mode = :raw
50
+ @cache = :none
51
+ @event_executor = :pool
52
+ @event_workers = 4
53
+ end
54
+
55
+ def dup
56
+ copy = self.class.new
57
+ copy.mode = @mode
58
+ copy.cache = @cache.is_a?(Hash) ? @cache.dup : @cache
59
+ copy.event_executor = @event_executor
60
+ copy.event_workers = @event_workers
61
+ copy
62
+ end
63
+
64
+ def normalize_mode(value = @mode)
65
+ mode = (value || @mode).to_sym
66
+ return mode if MODES.include?(mode)
67
+
68
+ raise ArgumentError, "Unknown OnyxCord mode: #{value.inspect}"
69
+ end
70
+
71
+ def normalize_event_executor(value = @event_executor)
72
+ executor = (value || @event_executor).to_sym
73
+ return executor if EXECUTORS.include?(executor)
74
+
75
+ raise ArgumentError, "Unknown event executor: #{value.inspect}"
76
+ end
77
+
78
+ def normalize_event_workers(value = @event_workers)
79
+ workers = Integer(value || @event_workers)
80
+ raise ArgumentError, 'event_workers must be greater than zero' unless workers.positive?
81
+
82
+ workers
83
+ end
84
+
85
+ def normalize_cache(value = @cache)
86
+ cache = value.nil? ? @cache : value
87
+
88
+ case cache
89
+ when Hash
90
+ CACHE_PRESETS[:none].merge(cache.transform_keys(&:to_sym))
91
+ else
92
+ preset = cache.to_sym
93
+ raise ArgumentError, "Unknown cache preset: #{cache.inspect}" unless CACHE_PRESETS[preset]
94
+
95
+ CACHE_PRESETS[preset].dup
96
+ end
97
+ end
98
+
99
+ class << self
100
+ def current
101
+ @current ||= new
102
+ end
103
+
104
+ def configure
105
+ yield current
106
+ end
107
+
108
+ def reset!
109
+ @current = new
110
+ end
111
+ end
112
+ end
113
+
114
+ def self.configuration
115
+ Configuration.current
116
+ end
117
+
118
+ def self.configure(&block)
119
+ Configuration.configure(&block)
120
+ end
121
+
122
+ def self.reset_configuration!
123
+ Configuration.reset!
124
+ end
125
+ end