discordrb 3.3.0 → 3.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +152 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
- data/.github/pull_request_template.md +37 -0
- data/.github/workflows/codeql.yml +65 -0
- data/.markdownlint.json +4 -0
- data/.rubocop.yml +39 -36
- data/CHANGELOG.md +874 -552
- data/Gemfile +2 -0
- data/LICENSE.txt +1 -1
- data/README.md +80 -86
- data/Rakefile +2 -0
- data/bin/console +1 -0
- data/discordrb-webhooks.gemspec +9 -6
- data/discordrb.gemspec +21 -18
- data/lib/discordrb/allowed_mentions.rb +36 -0
- data/lib/discordrb/api/application.rb +202 -0
- data/lib/discordrb/api/channel.rb +236 -47
- data/lib/discordrb/api/interaction.rb +54 -0
- data/lib/discordrb/api/invite.rb +5 -5
- data/lib/discordrb/api/server.rb +94 -66
- data/lib/discordrb/api/user.rb +17 -11
- data/lib/discordrb/api/webhook.rb +63 -6
- data/lib/discordrb/api.rb +55 -16
- data/lib/discordrb/await.rb +0 -1
- data/lib/discordrb/bot.rb +480 -93
- data/lib/discordrb/cache.rb +31 -24
- data/lib/discordrb/colour_rgb.rb +43 -0
- data/lib/discordrb/commands/command_bot.rb +35 -12
- data/lib/discordrb/commands/container.rb +21 -24
- data/lib/discordrb/commands/parser.rb +20 -20
- data/lib/discordrb/commands/rate_limiter.rb +4 -3
- data/lib/discordrb/container.rb +209 -20
- data/lib/discordrb/data/activity.rb +271 -0
- data/lib/discordrb/data/application.rb +50 -0
- data/lib/discordrb/data/attachment.rb +71 -0
- data/lib/discordrb/data/audit_logs.rb +345 -0
- data/lib/discordrb/data/channel.rb +993 -0
- data/lib/discordrb/data/component.rb +229 -0
- data/lib/discordrb/data/embed.rb +251 -0
- data/lib/discordrb/data/emoji.rb +82 -0
- data/lib/discordrb/data/integration.rb +122 -0
- data/lib/discordrb/data/interaction.rb +800 -0
- data/lib/discordrb/data/invite.rb +137 -0
- data/lib/discordrb/data/member.rb +372 -0
- data/lib/discordrb/data/message.rb +414 -0
- data/lib/discordrb/data/overwrite.rb +108 -0
- data/lib/discordrb/data/profile.rb +91 -0
- data/lib/discordrb/data/reaction.rb +33 -0
- data/lib/discordrb/data/recipient.rb +34 -0
- data/lib/discordrb/data/role.rb +248 -0
- data/lib/discordrb/data/server.rb +1004 -0
- data/lib/discordrb/data/user.rb +264 -0
- data/lib/discordrb/data/voice_region.rb +45 -0
- data/lib/discordrb/data/voice_state.rb +41 -0
- data/lib/discordrb/data/webhook.rb +238 -0
- data/lib/discordrb/data.rb +28 -4180
- data/lib/discordrb/errors.rb +46 -4
- data/lib/discordrb/events/bans.rb +7 -5
- data/lib/discordrb/events/channels.rb +3 -1
- data/lib/discordrb/events/guilds.rb +16 -9
- data/lib/discordrb/events/interactions.rb +482 -0
- data/lib/discordrb/events/invites.rb +125 -0
- data/lib/discordrb/events/members.rb +6 -2
- data/lib/discordrb/events/message.rb +72 -27
- data/lib/discordrb/events/presence.rb +35 -18
- data/lib/discordrb/events/raw.rb +1 -3
- data/lib/discordrb/events/reactions.rb +49 -4
- data/lib/discordrb/events/threads.rb +96 -0
- data/lib/discordrb/events/typing.rb +6 -4
- data/lib/discordrb/events/voice_server_update.rb +47 -0
- data/lib/discordrb/events/voice_state_update.rb +15 -10
- data/lib/discordrb/events/webhooks.rb +9 -6
- data/lib/discordrb/gateway.rb +99 -71
- data/lib/discordrb/id_object.rb +39 -0
- data/lib/discordrb/light/integrations.rb +1 -1
- data/lib/discordrb/light/light_bot.rb +1 -1
- data/lib/discordrb/logger.rb +4 -4
- data/lib/discordrb/paginator.rb +57 -0
- data/lib/discordrb/permissions.rb +159 -39
- data/lib/discordrb/version.rb +1 -1
- data/lib/discordrb/voice/encoder.rb +16 -7
- data/lib/discordrb/voice/network.rb +99 -47
- data/lib/discordrb/voice/sodium.rb +98 -0
- data/lib/discordrb/voice/voice_bot.rb +33 -25
- data/lib/discordrb/webhooks.rb +2 -0
- data/lib/discordrb.rb +107 -1
- metadata +126 -54
- data/.codeclimate.yml +0 -16
- data/.travis.yml +0 -33
- data/bin/travis_build_docs.sh +0 -17
- /data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +0 -0
data/lib/discordrb/cache.rb
CHANGED
@@ -21,8 +21,7 @@ module Discordrb
|
|
21
21
|
|
22
22
|
@channels = {}
|
23
23
|
@pm_channels = {}
|
24
|
-
|
25
|
-
@restricted_channels = []
|
24
|
+
@thread_members = {}
|
26
25
|
end
|
27
26
|
|
28
27
|
# Returns or caches the available voice regions
|
@@ -42,28 +41,21 @@ module Discordrb
|
|
42
41
|
# @param id [Integer] The channel ID for which to search for.
|
43
42
|
# @param server [Server] The server for which to search the channel for. If this isn't specified, it will be
|
44
43
|
# inferred using the API
|
45
|
-
# @return [Channel] The channel identified by the ID.
|
44
|
+
# @return [Channel, nil] The channel identified by the ID.
|
45
|
+
# @raise Discordrb::Errors::NoPermission
|
46
46
|
def channel(id, server = nil)
|
47
47
|
id = id.resolve_id
|
48
48
|
|
49
|
-
raise Discordrb::Errors::NoPermission if @restricted_channels.include? id
|
50
|
-
|
51
49
|
debug("Obtaining data for channel with id #{id}")
|
52
50
|
return @channels[id] if @channels[id]
|
53
51
|
|
54
52
|
begin
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
return nil
|
59
|
-
end
|
60
|
-
channel = Channel.new(JSON.parse(response), self, server)
|
61
|
-
@channels[id] = channel
|
62
|
-
rescue Discordrb::Errors::NoPermission
|
63
|
-
debug "Tried to get access to restricted channel #{id}, blacklisting it"
|
64
|
-
@restricted_channels << id
|
65
|
-
raise
|
53
|
+
response = API::Channel.resolve(token, id)
|
54
|
+
rescue Discordrb::Errors::UnknownChannel
|
55
|
+
return nil
|
66
56
|
end
|
57
|
+
channel = Channel.new(JSON.parse(response), self, server)
|
58
|
+
@channels[id] = channel
|
67
59
|
end
|
68
60
|
|
69
61
|
alias_method :group_channel, :channel
|
@@ -79,7 +71,7 @@ module Discordrb
|
|
79
71
|
LOGGER.out("Resolving user #{id}")
|
80
72
|
begin
|
81
73
|
response = API::User.resolve(token, id)
|
82
|
-
rescue
|
74
|
+
rescue Discordrb::Errors::UnknownUser
|
83
75
|
return nil
|
84
76
|
end
|
85
77
|
user = User.new(JSON.parse(response), self)
|
@@ -111,7 +103,6 @@ module Discordrb
|
|
111
103
|
def member(server_or_id, user_id)
|
112
104
|
server_id = server_or_id.resolve_id
|
113
105
|
user_id = user_id.resolve_id
|
114
|
-
|
115
106
|
server = server_or_id.is_a?(Server) ? server_or_id : self.server(server_id)
|
116
107
|
|
117
108
|
return server.member(user_id) if server.member_cached?(user_id)
|
@@ -119,7 +110,7 @@ module Discordrb
|
|
119
110
|
LOGGER.out("Resolving member #{server_id} on server #{user_id}")
|
120
111
|
begin
|
121
112
|
response = API::Server.resolve_member(token, server_id, user_id)
|
122
|
-
rescue
|
113
|
+
rescue Discordrb::Errors::UnknownUser, Discordrb::Errors::UnknownMember
|
123
114
|
return nil
|
124
115
|
end
|
125
116
|
member = Member.new(JSON.parse(response), server, self)
|
@@ -134,6 +125,7 @@ module Discordrb
|
|
134
125
|
def pm_channel(id)
|
135
126
|
id = id.resolve_id
|
136
127
|
return @pm_channels[id] if @pm_channels[id]
|
128
|
+
|
137
129
|
debug("Creating pm channel with user id #{id}")
|
138
130
|
response = API::User.create_pm(token, id)
|
139
131
|
channel = Channel.new(JSON.parse(response), self)
|
@@ -155,10 +147,14 @@ module Discordrb
|
|
155
147
|
|
156
148
|
# Ensures a given server object is cached and if not, cache it from the given data hash.
|
157
149
|
# @param data [Hash] A data hash representing a server.
|
150
|
+
# @param force_cache [true, false] Whether the object in cache should be updated with the given
|
151
|
+
# data if it already exists.
|
158
152
|
# @return [Server] the server represented by the data hash.
|
159
|
-
def ensure_server(data)
|
153
|
+
def ensure_server(data, force_cache = false)
|
160
154
|
if @servers.include?(data['id'].to_i)
|
161
|
-
@servers[data['id'].to_i]
|
155
|
+
server = @servers[data['id'].to_i]
|
156
|
+
server.update_data(data) if force_cache
|
157
|
+
server
|
162
158
|
else
|
163
159
|
@servers[data['id'].to_i] = Server.new(data, self)
|
164
160
|
end
|
@@ -176,6 +172,16 @@ module Discordrb
|
|
176
172
|
end
|
177
173
|
end
|
178
174
|
|
175
|
+
# Ensures a given thread member object is cached.
|
176
|
+
# @param data [Hash] Thread member data.
|
177
|
+
def ensure_thread_member(data)
|
178
|
+
thread_id = data['id'].to_i
|
179
|
+
user_id = data['user_id'].to_i
|
180
|
+
|
181
|
+
@thread_members[thread_id] ||= {}
|
182
|
+
@thread_members[thread_id][user_id] = data.slice('join_timestamp', 'flags')
|
183
|
+
end
|
184
|
+
|
179
185
|
# Requests member chunks for a given server ID.
|
180
186
|
# @param id [Integer] The server ID to request chunks for.
|
181
187
|
def request_chunks(id)
|
@@ -187,13 +193,13 @@ module Discordrb
|
|
187
193
|
#
|
188
194
|
# * An {Invite} object
|
189
195
|
# * The code for an invite
|
190
|
-
# * A fully qualified invite URL (e.g. `https://
|
196
|
+
# * A fully qualified invite URL (e.g. `https://discord.com/invite/0A37aN7fasF7n83q`)
|
191
197
|
# * A short invite URL with protocol (e.g. `https://discord.gg/0A37aN7fasF7n83q`)
|
192
198
|
# * A short invite URL without protocol (e.g. `discord.gg/0A37aN7fasF7n83q`)
|
193
199
|
# @return [String] Only the code for the invite.
|
194
200
|
def resolve_invite_code(invite)
|
195
201
|
invite = invite.code if invite.is_a? Discordrb::Invite
|
196
|
-
invite = invite[invite.rindex('/') + 1
|
202
|
+
invite = invite[invite.rindex('/') + 1..] if invite.start_with?('http', 'discord.gg')
|
197
203
|
invite
|
198
204
|
end
|
199
205
|
|
@@ -219,7 +225,7 @@ module Discordrb
|
|
219
225
|
return [channel(id)]
|
220
226
|
end
|
221
227
|
|
222
|
-
@servers.
|
228
|
+
@servers.each_value do |server|
|
223
229
|
server.channels.each do |channel|
|
224
230
|
results << channel if channel.name == channel_name && (server_name || server.name) == server.name && (!type || (channel.type == type))
|
225
231
|
end
|
@@ -248,6 +254,7 @@ module Discordrb
|
|
248
254
|
def find_user(username, discrim = nil)
|
249
255
|
users = @users.values.find_all { |e| e.username == username }
|
250
256
|
return users.find { |u| u.discrim == discrim } if discrim
|
257
|
+
|
251
258
|
users
|
252
259
|
end
|
253
260
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Discordrb
|
4
|
+
# A colour (red, green and blue values). Used for role colours. If you prefer the American spelling, the alias
|
5
|
+
# {ColorRGB} is also available.
|
6
|
+
class ColourRGB
|
7
|
+
# @return [Integer] the red part of this colour (0-255).
|
8
|
+
attr_reader :red
|
9
|
+
|
10
|
+
# @return [Integer] the green part of this colour (0-255).
|
11
|
+
attr_reader :green
|
12
|
+
|
13
|
+
# @return [Integer] the blue part of this colour (0-255).
|
14
|
+
attr_reader :blue
|
15
|
+
|
16
|
+
# @return [Integer] the colour's RGB values combined into one integer.
|
17
|
+
attr_reader :combined
|
18
|
+
alias_method :to_i, :combined
|
19
|
+
|
20
|
+
# Make a new colour from the combined value.
|
21
|
+
# @param combined [String, Integer] The colour's RGB values combined into one integer or a hexadecimal string
|
22
|
+
# @example Initialize a with a base 10 integer
|
23
|
+
# ColourRGB.new(7506394) #=> ColourRGB
|
24
|
+
# ColourRGB.new(0x7289da) #=> ColourRGB
|
25
|
+
# @example Initialize a with a hexadecimal string
|
26
|
+
# ColourRGB.new('7289da') #=> ColourRGB
|
27
|
+
def initialize(combined)
|
28
|
+
@combined = combined.is_a?(String) ? combined.to_i(16) : combined
|
29
|
+
@red = (@combined >> 16) & 0xFF
|
30
|
+
@green = (@combined >> 8) & 0xFF
|
31
|
+
@blue = @combined & 0xFF
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [String] the colour as a hexadecimal.
|
35
|
+
def hex
|
36
|
+
@combined.to_s(16)
|
37
|
+
end
|
38
|
+
alias_method :hexadecimal, :hex
|
39
|
+
end
|
40
|
+
|
41
|
+
# Alias for the class {ColourRGB}
|
42
|
+
ColorRGB = ColourRGB
|
43
|
+
end
|
@@ -24,7 +24,7 @@ module Discordrb::Commands
|
|
24
24
|
|
25
25
|
# Creates a new CommandBot and logs in to Discord.
|
26
26
|
# @param attributes [Hash] The attributes to initialize the CommandBot with.
|
27
|
-
# @see
|
27
|
+
# @see Discordrb::Bot#initialize Discordrb::Bot#initialize for other attributes that should be used to create the underlying regular bot.
|
28
28
|
# @option attributes [String, Array<String>, #call] :prefix The prefix that should trigger this bot's commands. It
|
29
29
|
# can be:
|
30
30
|
#
|
@@ -39,14 +39,15 @@ module Discordrb::Commands
|
|
39
39
|
# complicated dynamic prefixes (e. g. based on server), or even something else entirely (suffixes, or most
|
40
40
|
# adventurous, infixes).
|
41
41
|
# @option attributes [true, false] :advanced_functionality Whether to enable advanced functionality (very powerful
|
42
|
-
# way to nest commands into chains, see https://github.com/
|
42
|
+
# way to nest commands into chains, see https://github.com/shardlab/discordrb/wiki/Commands#command-chain-syntax
|
43
43
|
# for info. Default is false.
|
44
44
|
# @option attributes [Symbol, Array<Symbol>, false] :help_command The name of the command that displays info for
|
45
45
|
# other commands. Use an array if you want to have aliases. Default is "help". If none should be created, use
|
46
46
|
# `false` as the value.
|
47
|
-
# @option attributes [String] :command_doesnt_exist_message The message that should be displayed if a user attempts
|
47
|
+
# @option attributes [String, #call] :command_doesnt_exist_message The message that should be displayed if a user attempts
|
48
48
|
# to use a command that does not exist. If none is specified, no message will be displayed. In the message, you
|
49
|
-
# can use the string '%command%' that will be replaced with the name of the command.
|
49
|
+
# can use the string '%command%' that will be replaced with the name of the command. Anything responding to call
|
50
|
+
# such as a proc will be called with the event, and is expected to return a String or nil.
|
50
51
|
# @option attributes [String] :no_permission_message The message to be displayed when `NoPermission` error is raised.
|
51
52
|
# @option attributes [true, false] :spaces_allowed Whether spaces are allowed to occur between the prefix and the
|
52
53
|
# command. Default is false.
|
@@ -69,7 +70,9 @@ module Discordrb::Commands
|
|
69
70
|
# @option attributes [String] :quote_end Character that should end a quoted string (see
|
70
71
|
# :advanced_functionality). Default is '"' or the same as :quote_start. Set to an empty string to disable.
|
71
72
|
# @option attributes [true, false] :ignore_bots Whether the bot should ignore bot accounts or not. Default is false.
|
72
|
-
def initialize(attributes
|
73
|
+
def initialize(**attributes)
|
74
|
+
# TODO: This needs to be revisited. undefined attributes are treated
|
75
|
+
# as explicitly passed nils.
|
73
76
|
super(
|
74
77
|
log_mode: attributes[:log_mode],
|
75
78
|
token: attributes[:token],
|
@@ -83,7 +86,9 @@ module Discordrb::Commands
|
|
83
86
|
num_shards: attributes[:num_shards],
|
84
87
|
redact_token: attributes.key?(:redact_token) ? attributes[:redact_token] : true,
|
85
88
|
ignore_bots: attributes[:ignore_bots],
|
86
|
-
compress_mode: attributes[:compress_mode]
|
89
|
+
compress_mode: attributes[:compress_mode],
|
90
|
+
intents: attributes[:intents] || :all
|
91
|
+
)
|
87
92
|
|
88
93
|
@prefix = attributes[:prefix]
|
89
94
|
@attributes = {
|
@@ -140,6 +145,7 @@ module Discordrb::Commands
|
|
140
145
|
}
|
141
146
|
|
142
147
|
return unless @attributes[:help_command]
|
148
|
+
|
143
149
|
command(@attributes[:help_command], max_args: 1, description: 'Shows a list of all the commands available or displays help for a specific command.', usage: 'help [command name]') do |event, command_name|
|
144
150
|
if command_name
|
145
151
|
command = @commands[command_name.to_sym]
|
@@ -148,6 +154,7 @@ module Discordrb::Commands
|
|
148
154
|
command_name = command.name
|
149
155
|
end
|
150
156
|
return "The command `#{command_name}` does not exist!" unless command
|
157
|
+
|
151
158
|
desc = command.attributes[:description] || '*No description available*'
|
152
159
|
usage = command.attributes[:usage]
|
153
160
|
parameters = command.attributes[:parameters]
|
@@ -206,15 +213,22 @@ module Discordrb::Commands
|
|
206
213
|
def execute_command(name, event, arguments, chained = false, check_permissions = true)
|
207
214
|
debug("Executing command #{name} with arguments #{arguments}")
|
208
215
|
return unless @commands
|
216
|
+
|
209
217
|
command = @commands[name]
|
210
218
|
command = command.aliased_command if command.is_a?(CommandAlias)
|
211
219
|
return unless !check_permissions || channels?(event.channel, @attributes[:channels]) ||
|
212
220
|
(command && !command.attributes[:channels].nil?)
|
221
|
+
|
213
222
|
unless command
|
214
|
-
|
223
|
+
if @attributes[:command_doesnt_exist_message]
|
224
|
+
message = @attributes[:command_doesnt_exist_message]
|
225
|
+
message = message.call(event) if message.respond_to?(:call)
|
226
|
+
event.respond message.gsub('%command%', name.to_s) if message
|
227
|
+
end
|
215
228
|
return
|
216
229
|
end
|
217
230
|
return unless !check_permissions || channels?(event.channel, command.attributes[:channels])
|
231
|
+
|
218
232
|
arguments = arg_check(arguments, command.attributes[:arg_types], event.server) if check_permissions
|
219
233
|
if (check_permissions &&
|
220
234
|
permission?(event.author, command.attributes[:permission_level], event.server) &&
|
@@ -238,11 +252,13 @@ module Discordrb::Commands
|
|
238
252
|
# For example, `['1', '10..14']` with types `[Integer, Range]` would turn into `[1, 10..14]`.
|
239
253
|
def arg_check(args, types = nil, server = nil)
|
240
254
|
return args unless types
|
255
|
+
|
241
256
|
args.each_with_index.map do |arg, i|
|
242
257
|
next arg if types[i].nil? || types[i] == String
|
258
|
+
|
243
259
|
if types[i] == Integer
|
244
260
|
begin
|
245
|
-
Integer(arg)
|
261
|
+
Integer(arg, 10)
|
246
262
|
rescue ArgumentError
|
247
263
|
nil
|
248
264
|
end
|
@@ -304,7 +320,7 @@ module Discordrb::Commands
|
|
304
320
|
elsif types[i].respond_to?(:from_argument)
|
305
321
|
begin
|
306
322
|
types[i].from_argument arg
|
307
|
-
rescue
|
323
|
+
rescue StandardError
|
308
324
|
nil
|
309
325
|
end
|
310
326
|
else
|
@@ -319,8 +335,9 @@ module Discordrb::Commands
|
|
319
335
|
# @return [String, nil] the command's result, if there is any.
|
320
336
|
def simple_execute(chain, event)
|
321
337
|
return nil if chain.empty?
|
338
|
+
|
322
339
|
args = chain.split(' ')
|
323
|
-
execute_command(args[0].to_sym, event, args[1
|
340
|
+
execute_command(args[0].to_sym, event, args[1..])
|
324
341
|
end
|
325
342
|
|
326
343
|
# Sets the permission level of a user
|
@@ -369,6 +386,7 @@ module Discordrb::Commands
|
|
369
386
|
# @param channel [String, Integer, Channel] The channel name, integer ID, or `Channel` object to be added
|
370
387
|
def add_channel(channel)
|
371
388
|
return if @attributes[:channels].find { |c| channel.resolve_id == c.resolve_id }
|
389
|
+
|
372
390
|
@attributes[:channels] << channel
|
373
391
|
end
|
374
392
|
|
@@ -426,7 +444,8 @@ module Discordrb::Commands
|
|
426
444
|
|
427
445
|
def standard_prefix_trigger(message, prefix)
|
428
446
|
return nil unless message.start_with? prefix
|
429
|
-
|
447
|
+
|
448
|
+
message[prefix.length..]
|
430
449
|
end
|
431
450
|
|
432
451
|
def required_permissions?(member, required, channel = nil)
|
@@ -437,11 +456,13 @@ module Discordrb::Commands
|
|
437
456
|
|
438
457
|
def required_roles?(member, required)
|
439
458
|
return true if member.webhook? || member.is_a?(Discordrb::Recipient) || required.nil? || required.empty?
|
459
|
+
|
440
460
|
required.is_a?(Array) ? check_multiple_roles(member, required) : member.role?(role)
|
441
461
|
end
|
442
462
|
|
443
463
|
def allowed_roles?(member, required)
|
444
464
|
return true if member.webhook? || member.is_a?(Discordrb::Recipient) || required.nil? || required.empty?
|
465
|
+
|
445
466
|
required.is_a?(Array) ? check_multiple_roles(member, required, false) : member.role?(role)
|
446
467
|
end
|
447
468
|
|
@@ -459,9 +480,11 @@ module Discordrb::Commands
|
|
459
480
|
|
460
481
|
def channels?(channel, channels)
|
461
482
|
return true if channels.nil? || channels.empty?
|
483
|
+
|
462
484
|
channels.any? do |c|
|
463
485
|
# if c is string, make sure to remove the "#" from channel names in case it was specified
|
464
486
|
return true if c.is_a?(String) && c.delete('#') == channel.name
|
487
|
+
|
465
488
|
c.resolve_id == channel.resolve_id
|
466
489
|
end
|
467
490
|
end
|
@@ -480,7 +503,7 @@ module Discordrb::Commands
|
|
480
503
|
else
|
481
504
|
event.respond result unless result.nil? || result.empty?
|
482
505
|
end
|
483
|
-
rescue => e
|
506
|
+
rescue StandardError => e
|
484
507
|
log_exception(e)
|
485
508
|
ensure
|
486
509
|
@event_threads.delete(t)
|
@@ -9,22 +9,25 @@ module Discordrb::Commands
|
|
9
9
|
module CommandContainer
|
10
10
|
include RateLimiter
|
11
11
|
|
12
|
-
# @return [Hash<Symbol, Command>] hash of command names and commands this container has.
|
12
|
+
# @return [Hash<Symbol, Command, CommandAlias>] hash of command names and commands this container has.
|
13
13
|
attr_reader :commands
|
14
14
|
|
15
15
|
# Adds a new command to the container.
|
16
|
-
# @param name [Symbol
|
16
|
+
# @param name [Symbol] The name of the command to add.
|
17
17
|
# @param attributes [Hash] The attributes to initialize the command with.
|
18
|
+
# @option attributes [Array<Symbol>] :aliases A list of additional names for this command. This in effect
|
19
|
+
# creates {CommandAlias} objects in the container ({#commands}) that refer to the newly created command.
|
20
|
+
# Additionally, the default help command will identify these command names as an alias where applicable.
|
18
21
|
# @option attributes [Integer] :permission_level The minimum permission level that can use this command, inclusive.
|
19
22
|
# See {CommandBot#set_user_permission} and {CommandBot#set_role_permission}.
|
20
23
|
# @option attributes [String, false] :permission_message Message to display when a user does not have sufficient
|
21
24
|
# permissions to execute a command. %name% in the message will be replaced with the name of the command. Disable
|
22
25
|
# the message by setting this option to false.
|
23
26
|
# @option attributes [Array<Symbol>] :required_permissions Discord action permissions (e.g. `:kick_members`) that
|
24
|
-
# should be required to use this command. See {Discordrb::Permissions::
|
25
|
-
# @option attributes [Array<Role>, Array
|
27
|
+
# should be required to use this command. See {Discordrb::Permissions::FLAGS} for a list.
|
28
|
+
# @option attributes [Array<Role>, Array<String, Integer>] :required_roles Roles, or their IDs, that user must have to use this command
|
26
29
|
# (user must have all of them).
|
27
|
-
# @option attributes [Array<Role>, Array
|
30
|
+
# @option attributes [Array<Role>, Array<String, Integer>] :allowed_roles Roles, or their IDs, that user should have to use this command
|
28
31
|
# (user should have at least one of them).
|
29
32
|
# @option attributes [Array<String, Integer, Channel>] :channels The channels that this command can be used on. An
|
30
33
|
# empty array indicates it can be used on any channel. Supersedes the command bot attribute.
|
@@ -55,26 +58,22 @@ module Discordrb::Commands
|
|
55
58
|
# @note `LocalJumpError`s are rescued from internally, giving bots the opportunity to use `return` or `break` in
|
56
59
|
# their blocks without propagating an exception.
|
57
60
|
# @return [Command] The command that was added.
|
58
|
-
# @deprecated The command name argument will no longer support arrays in the next release.
|
59
|
-
# Use the `aliases` attribute instead.
|
60
61
|
def command(name, attributes = {}, &block)
|
61
62
|
@commands ||= {}
|
62
|
-
if name.is_a? Array
|
63
|
-
new_command = nil
|
64
63
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
64
|
+
# TODO: Remove in 4.0
|
65
|
+
if name.is_a?(Array)
|
66
|
+
name, *aliases = name
|
67
|
+
attributes[:aliases] = aliases if attributes[:aliases].nil?
|
68
|
+
Discordrb::LOGGER.warn("While registering command #{name.inspect}")
|
69
|
+
Discordrb::LOGGER.warn('Arrays for command aliases is removed. Please use `aliases` argument instead.')
|
70
|
+
end
|
69
71
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
new_command.attributes[:aliases].each do |aliased_name|
|
74
|
-
@commands[aliased_name] = CommandAlias.new(aliased_name, new_command)
|
75
|
-
end
|
76
|
-
@commands[name] = new_command
|
72
|
+
new_command = Command.new(name, attributes, &block)
|
73
|
+
new_command.attributes[:aliases].each do |aliased_name|
|
74
|
+
@commands[aliased_name] = CommandAlias.new(aliased_name, new_command)
|
77
75
|
end
|
76
|
+
@commands[name] = new_command
|
78
77
|
end
|
79
78
|
|
80
79
|
# Removes a specific command from this container.
|
@@ -87,7 +86,7 @@ module Discordrb::Commands
|
|
87
86
|
# Adds all commands from another container into this one. Existing commands will be overwritten.
|
88
87
|
# @param container [Module] A module that `extend`s {CommandContainer} from which the commands will be added.
|
89
88
|
def include_commands(container)
|
90
|
-
handlers = container.instance_variable_get
|
89
|
+
handlers = container.instance_variable_get :@commands
|
91
90
|
return unless handlers
|
92
91
|
|
93
92
|
@commands ||= {}
|
@@ -100,9 +99,7 @@ module Discordrb::Commands
|
|
100
99
|
container_modules = container.singleton_class.included_modules
|
101
100
|
|
102
101
|
# If the container is an EventContainer and we can include it, then do that
|
103
|
-
if container_modules.include?(Discordrb::EventContainer) && respond_to?(:include_events)
|
104
|
-
include_events(container)
|
105
|
-
end
|
102
|
+
include_events(container) if container_modules.include?(Discordrb::EventContainer) && respond_to?(:include_events)
|
106
103
|
|
107
104
|
if container_modules.include? Discordrb::Commands::CommandContainer
|
108
105
|
include_commands(container)
|
@@ -81,45 +81,43 @@ module Discordrb::Commands
|
|
81
81
|
# @return [String] the result of the execution.
|
82
82
|
def call(event, arguments, chained = false, check_permissions = true)
|
83
83
|
if arguments.length < @attributes[:min_args]
|
84
|
-
|
85
|
-
|
84
|
+
response = "Too few arguments for command `#{name}`!"
|
85
|
+
response += "\nUsage: `#{@attributes[:usage]}`" if @attributes[:usage]
|
86
|
+
event.respond(response)
|
86
87
|
return
|
87
88
|
end
|
88
89
|
if @attributes[:max_args] >= 0 && arguments.length > @attributes[:max_args]
|
89
|
-
|
90
|
-
|
90
|
+
response = "Too many arguments for command `#{name}`!"
|
91
|
+
response += "\nUsage: `#{@attributes[:usage]}`" if @attributes[:usage]
|
92
|
+
event.respond(response)
|
91
93
|
return
|
92
94
|
end
|
93
|
-
unless @attributes[:chain_usable]
|
94
|
-
|
95
|
-
|
96
|
-
return
|
97
|
-
end
|
95
|
+
unless @attributes[:chain_usable] && !chained
|
96
|
+
event.respond "Command `#{name}` cannot be used in a command chain!"
|
97
|
+
return
|
98
98
|
end
|
99
99
|
|
100
100
|
if check_permissions
|
101
101
|
rate_limited = event.bot.rate_limited?(@attributes[:bucket], event.author)
|
102
102
|
if @attributes[:bucket] && rate_limited
|
103
|
-
if @attributes[:rate_limit_message]
|
104
|
-
event.respond @attributes[:rate_limit_message].gsub('%time%', rate_limited.round(2).to_s)
|
105
|
-
end
|
103
|
+
event.respond @attributes[:rate_limit_message].gsub('%time%', rate_limited.round(2).to_s) if @attributes[:rate_limit_message]
|
106
104
|
return
|
107
105
|
end
|
108
106
|
end
|
109
107
|
|
110
108
|
result = @block.call(event, *arguments)
|
111
109
|
event.drain_into(result)
|
112
|
-
rescue LocalJumpError =>
|
113
|
-
result =
|
110
|
+
rescue LocalJumpError => e # occurs when breaking
|
111
|
+
result = e.exit_value
|
114
112
|
event.drain_into(result)
|
115
|
-
rescue =>
|
113
|
+
rescue StandardError => e # Something went wrong inside our @block!
|
116
114
|
rescue_value = @attributes[:rescue] || event.bot.attributes[:rescue]
|
117
115
|
if rescue_value
|
118
|
-
event.respond(rescue_value.gsub('%exception%',
|
119
|
-
rescue_value.call(event,
|
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)
|
120
118
|
end
|
121
119
|
|
122
|
-
raise
|
120
|
+
raise e
|
123
121
|
end
|
124
122
|
end
|
125
123
|
|
@@ -209,8 +207,10 @@ module Discordrb::Commands
|
|
209
207
|
result += char if b_level <= 0
|
210
208
|
|
211
209
|
next unless char == @attributes[:sub_chain_end] && !quoted
|
210
|
+
|
212
211
|
b_level -= 1
|
213
212
|
next unless b_level.zero?
|
213
|
+
|
214
214
|
nested = @chain[b_start + 1..index - 1]
|
215
215
|
subchain = CommandChain.new(nested, @bot, true)
|
216
216
|
result += subchain.execute(event)
|
@@ -246,7 +246,7 @@ module Discordrb::Commands
|
|
246
246
|
|
247
247
|
first_space = command.index ' '
|
248
248
|
command_name = first_space ? command[0..first_space - 1] : command
|
249
|
-
arguments = first_space ? command[first_space + 1
|
249
|
+
arguments = first_space ? command[first_space + 1..] : ''
|
250
250
|
|
251
251
|
# Append a previous sign if none is present
|
252
252
|
arguments += @attributes[:previous] unless arguments.include? @attributes[:previous]
|
@@ -318,7 +318,7 @@ module Discordrb::Commands
|
|
318
318
|
arg.split ' '
|
319
319
|
end
|
320
320
|
|
321
|
-
chain = chain[chain_args_index + 1
|
321
|
+
chain = chain[chain_args_index + 1..]
|
322
322
|
end
|
323
323
|
|
324
324
|
[chain_args, chain]
|
@@ -35,7 +35,7 @@ module Discordrb::Commands
|
|
35
35
|
end
|
36
36
|
|
37
37
|
# Performs a rate limiting request
|
38
|
-
# @param thing [
|
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
39
|
# @param rate_limit_time [Time] The time to base the rate limiting on, only useful for testing.
|
40
40
|
# @param increment [Integer] How much to increment the rate-limit counter. Default is 1.
|
41
41
|
# @return [Integer, false] the waiting time until the next request, in seconds, or false if the request succeeded
|
@@ -83,6 +83,7 @@ module Discordrb::Commands
|
|
83
83
|
def resolve_key(thing)
|
84
84
|
return thing.resolve_id if thing.respond_to?(:resolve_id) && !thing.is_a?(String)
|
85
85
|
return thing if thing.is_a?(Integer) || thing.is_a?(Symbol)
|
86
|
+
|
86
87
|
raise ArgumentError, "Cannot use a #{thing.class} as a rate limiting key!"
|
87
88
|
end
|
88
89
|
end
|
@@ -104,7 +105,7 @@ module Discordrb::Commands
|
|
104
105
|
|
105
106
|
# Performs a rate limit request.
|
106
107
|
# @param key [Symbol] Which bucket to perform the request for.
|
107
|
-
# @param thing [
|
108
|
+
# @param thing [String, Integer, Symbol] What should be rate-limited.
|
108
109
|
# @param increment (see Bucket#rate_limited?)
|
109
110
|
# @see Bucket#rate_limited?
|
110
111
|
# @return [Integer, false] How much time to wait or false if the request succeeded.
|
@@ -124,7 +125,7 @@ module Discordrb::Commands
|
|
124
125
|
# Adds all the buckets from another RateLimiter onto this one.
|
125
126
|
# @param limiter [Module] Another {RateLimiter} module
|
126
127
|
def include_buckets(limiter)
|
127
|
-
buckets = limiter.instance_variable_get(
|
128
|
+
buckets = limiter.instance_variable_get(:@buckets) || {}
|
128
129
|
@buckets ||= {}
|
129
130
|
@buckets.merge! buckets
|
130
131
|
end
|