rubycord 1.0.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 (88) hide show
  1. checksums.yaml +7 -0
  2. data/lib/rubycord/allowed_mentions.rb +34 -0
  3. data/lib/rubycord/api/application.rb +200 -0
  4. data/lib/rubycord/api/channel.rb +597 -0
  5. data/lib/rubycord/api/interaction.rb +52 -0
  6. data/lib/rubycord/api/invite.rb +42 -0
  7. data/lib/rubycord/api/server.rb +557 -0
  8. data/lib/rubycord/api/user.rb +153 -0
  9. data/lib/rubycord/api/webhook.rb +138 -0
  10. data/lib/rubycord/api.rb +356 -0
  11. data/lib/rubycord/await.rb +49 -0
  12. data/lib/rubycord/bot.rb +1757 -0
  13. data/lib/rubycord/cache.rb +259 -0
  14. data/lib/rubycord/colour_rgb.rb +41 -0
  15. data/lib/rubycord/commands/command_bot.rb +519 -0
  16. data/lib/rubycord/commands/container.rb +110 -0
  17. data/lib/rubycord/commands/events.rb +9 -0
  18. data/lib/rubycord/commands/parser.rb +325 -0
  19. data/lib/rubycord/commands/rate_limiter.rb +142 -0
  20. data/lib/rubycord/container.rb +753 -0
  21. data/lib/rubycord/data/activity.rb +269 -0
  22. data/lib/rubycord/data/application.rb +48 -0
  23. data/lib/rubycord/data/attachment.rb +109 -0
  24. data/lib/rubycord/data/audit_logs.rb +343 -0
  25. data/lib/rubycord/data/channel.rb +996 -0
  26. data/lib/rubycord/data/component.rb +227 -0
  27. data/lib/rubycord/data/embed.rb +249 -0
  28. data/lib/rubycord/data/emoji.rb +80 -0
  29. data/lib/rubycord/data/integration.rb +120 -0
  30. data/lib/rubycord/data/interaction.rb +798 -0
  31. data/lib/rubycord/data/invite.rb +135 -0
  32. data/lib/rubycord/data/member.rb +370 -0
  33. data/lib/rubycord/data/message.rb +412 -0
  34. data/lib/rubycord/data/overwrite.rb +106 -0
  35. data/lib/rubycord/data/profile.rb +89 -0
  36. data/lib/rubycord/data/reaction.rb +31 -0
  37. data/lib/rubycord/data/recipient.rb +32 -0
  38. data/lib/rubycord/data/role.rb +246 -0
  39. data/lib/rubycord/data/server.rb +1002 -0
  40. data/lib/rubycord/data/user.rb +261 -0
  41. data/lib/rubycord/data/voice_region.rb +43 -0
  42. data/lib/rubycord/data/voice_state.rb +39 -0
  43. data/lib/rubycord/data/webhook.rb +232 -0
  44. data/lib/rubycord/data.rb +40 -0
  45. data/lib/rubycord/errors.rb +737 -0
  46. data/lib/rubycord/events/await.rb +46 -0
  47. data/lib/rubycord/events/bans.rb +58 -0
  48. data/lib/rubycord/events/channels.rb +186 -0
  49. data/lib/rubycord/events/generic.rb +126 -0
  50. data/lib/rubycord/events/guilds.rb +191 -0
  51. data/lib/rubycord/events/interactions.rb +480 -0
  52. data/lib/rubycord/events/invites.rb +123 -0
  53. data/lib/rubycord/events/lifetime.rb +29 -0
  54. data/lib/rubycord/events/members.rb +91 -0
  55. data/lib/rubycord/events/message.rb +337 -0
  56. data/lib/rubycord/events/presence.rb +127 -0
  57. data/lib/rubycord/events/raw.rb +45 -0
  58. data/lib/rubycord/events/reactions.rb +156 -0
  59. data/lib/rubycord/events/roles.rb +86 -0
  60. data/lib/rubycord/events/threads.rb +94 -0
  61. data/lib/rubycord/events/typing.rb +70 -0
  62. data/lib/rubycord/events/voice_server_update.rb +45 -0
  63. data/lib/rubycord/events/voice_state_update.rb +103 -0
  64. data/lib/rubycord/events/webhooks.rb +62 -0
  65. data/lib/rubycord/gateway.rb +867 -0
  66. data/lib/rubycord/id_object.rb +37 -0
  67. data/lib/rubycord/light/data.rb +60 -0
  68. data/lib/rubycord/light/integrations.rb +71 -0
  69. data/lib/rubycord/light/light_bot.rb +56 -0
  70. data/lib/rubycord/light.rb +6 -0
  71. data/lib/rubycord/logger.rb +118 -0
  72. data/lib/rubycord/paginator.rb +55 -0
  73. data/lib/rubycord/permissions.rb +251 -0
  74. data/lib/rubycord/version.rb +5 -0
  75. data/lib/rubycord/voice/encoder.rb +113 -0
  76. data/lib/rubycord/voice/network.rb +366 -0
  77. data/lib/rubycord/voice/sodium.rb +96 -0
  78. data/lib/rubycord/voice/voice_bot.rb +408 -0
  79. data/lib/rubycord/webhooks/builder.rb +100 -0
  80. data/lib/rubycord/webhooks/client.rb +132 -0
  81. data/lib/rubycord/webhooks/embeds.rb +248 -0
  82. data/lib/rubycord/webhooks/modal.rb +78 -0
  83. data/lib/rubycord/webhooks/version.rb +7 -0
  84. data/lib/rubycord/webhooks/view.rb +192 -0
  85. data/lib/rubycord/webhooks.rb +12 -0
  86. data/lib/rubycord/websocket.rb +70 -0
  87. data/lib/rubycord.rb +140 -0
  88. 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