rubycord 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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