discordrb 3.3.0 → 3.4.3
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.
- checksums.yaml +4 -4
- data/.circleci/config.yml +126 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +39 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +25 -0
- data/.github/pull_request_template.md +37 -0
- data/.rubocop.yml +34 -37
- data/.travis.yml +5 -6
- data/CHANGELOG.md +504 -347
- data/Gemfile +2 -0
- data/LICENSE.txt +1 -1
- data/README.md +61 -79
- data/Rakefile +2 -0
- data/bin/console +1 -0
- data/discordrb-webhooks.gemspec +6 -6
- data/discordrb.gemspec +18 -18
- data/lib/discordrb/allowed_mentions.rb +36 -0
- data/lib/discordrb/api/channel.rb +62 -39
- data/lib/discordrb/api/invite.rb +3 -3
- data/lib/discordrb/api/server.rb +57 -50
- data/lib/discordrb/api/user.rb +9 -8
- data/lib/discordrb/api/webhook.rb +6 -6
- data/lib/discordrb/api.rb +40 -15
- data/lib/discordrb/await.rb +0 -1
- data/lib/discordrb/bot.rb +175 -73
- data/lib/discordrb/cache.rb +4 -2
- data/lib/discordrb/colour_rgb.rb +43 -0
- data/lib/discordrb/commands/command_bot.rb +30 -9
- data/lib/discordrb/commands/container.rb +20 -23
- data/lib/discordrb/commands/parser.rb +18 -18
- data/lib/discordrb/commands/rate_limiter.rb +3 -2
- data/lib/discordrb/container.rb +77 -17
- data/lib/discordrb/data/activity.rb +271 -0
- data/lib/discordrb/data/application.rb +50 -0
- data/lib/discordrb/data/attachment.rb +56 -0
- data/lib/discordrb/data/audit_logs.rb +345 -0
- data/lib/discordrb/data/channel.rb +849 -0
- data/lib/discordrb/data/embed.rb +251 -0
- data/lib/discordrb/data/emoji.rb +82 -0
- data/lib/discordrb/data/integration.rb +83 -0
- data/lib/discordrb/data/invite.rb +137 -0
- data/lib/discordrb/data/member.rb +297 -0
- data/lib/discordrb/data/message.rb +334 -0
- data/lib/discordrb/data/overwrite.rb +102 -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 +191 -0
- data/lib/discordrb/data/server.rb +1002 -0
- data/lib/discordrb/data/user.rb +204 -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 +145 -0
- data/lib/discordrb/data.rb +25 -4180
- data/lib/discordrb/errors.rb +2 -1
- data/lib/discordrb/events/bans.rb +7 -5
- data/lib/discordrb/events/channels.rb +2 -0
- data/lib/discordrb/events/guilds.rb +16 -9
- data/lib/discordrb/events/invites.rb +125 -0
- data/lib/discordrb/events/members.rb +6 -2
- data/lib/discordrb/events/message.rb +69 -27
- data/lib/discordrb/events/presence.rb +14 -4
- data/lib/discordrb/events/raw.rb +1 -3
- data/lib/discordrb/events/reactions.rb +49 -3
- 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 +72 -57
- 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 +103 -8
- data/lib/discordrb/version.rb +1 -1
- data/lib/discordrb/voice/encoder.rb +16 -7
- data/lib/discordrb/voice/network.rb +84 -43
- data/lib/discordrb/voice/sodium.rb +96 -0
- data/lib/discordrb/voice/voice_bot.rb +34 -26
- data/lib/discordrb.rb +73 -0
- metadata +98 -60
- /data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +0 -0
data/lib/discordrb/bot.rb
CHANGED
@@ -18,6 +18,7 @@ require 'discordrb/events/bans'
|
|
18
18
|
require 'discordrb/events/raw'
|
19
19
|
require 'discordrb/events/reactions'
|
20
20
|
require 'discordrb/events/webhooks'
|
21
|
+
require 'discordrb/events/invites'
|
21
22
|
|
22
23
|
require 'discordrb/api'
|
23
24
|
require 'discordrb/api/channel'
|
@@ -101,12 +102,14 @@ module Discordrb
|
|
101
102
|
# to Discord's gateway. `:none` will request that no payloads are received compressed (not recommended for
|
102
103
|
# production bots). `:large` will request that large payloads are received compressed. `:stream` will request
|
103
104
|
# that all data be received in a continuous compressed stream.
|
105
|
+
# @param intents [:all, Array<Symbol>, nil] Intents that this bot requires. See {Discordrb::INTENTS}. If `nil`, no intents
|
106
|
+
# field will be passed.
|
104
107
|
def initialize(
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
108
|
+
log_mode: :normal,
|
109
|
+
token: nil, client_id: nil,
|
110
|
+
type: nil, name: '', fancy_log: false, suppress_ready: false, parse_self: false,
|
111
|
+
shard_id: nil, num_shards: nil, redact_token: true, ignore_bots: false,
|
112
|
+
compress_mode: :large, intents: nil
|
110
113
|
)
|
111
114
|
LOGGER.mode = log_mode
|
112
115
|
LOGGER.token = token if redact_token
|
@@ -125,8 +128,12 @@ module Discordrb
|
|
125
128
|
|
126
129
|
@compress_mode = compress_mode
|
127
130
|
|
131
|
+
raise 'Token string is empty or nil' if token.nil? || token.empty?
|
132
|
+
|
133
|
+
@intents = intents == :all ? INTENTS.values.reduce(&:|) : calculate_intents(intents) if intents
|
134
|
+
|
128
135
|
@token = process_token(@type, token)
|
129
|
-
@gateway = Gateway.new(self, @token, @shard_key, @compress_mode)
|
136
|
+
@gateway = Gateway.new(self, @token, @shard_key, @compress_mode, @intents)
|
130
137
|
|
131
138
|
init_cache
|
132
139
|
|
@@ -160,16 +167,13 @@ module Discordrb
|
|
160
167
|
|
161
168
|
# @overload emoji(id)
|
162
169
|
# Return an emoji by its ID
|
163
|
-
# @param id [
|
170
|
+
# @param id [String, Integer] The emoji's ID.
|
164
171
|
# @return [Emoji, nil] the emoji object. `nil` if the emoji was not found.
|
165
172
|
# @overload emoji
|
166
173
|
# The list of emoji the bot can use.
|
167
174
|
# @return [Array<Emoji>] the emoji available.
|
168
175
|
def emoji(id = nil)
|
169
|
-
|
170
|
-
unavailable_servers_check
|
171
|
-
|
172
|
-
emoji_hash = @servers.values.map(&:emoji).reduce(&:merge)
|
176
|
+
emoji_hash = servers.values.map(&:emoji).reduce(&:merge)
|
173
177
|
if id
|
174
178
|
id = id.resolve_id
|
175
179
|
emoji_hash[id]
|
@@ -203,6 +207,7 @@ module Discordrb
|
|
203
207
|
# @return [Application, nil] The bot's application info. Returns `nil` if bot is not a bot account.
|
204
208
|
def bot_application
|
205
209
|
return unless @type == :bot
|
210
|
+
|
206
211
|
response = API.oauth_application(token)
|
207
212
|
Application.new(JSON.parse(response), self)
|
208
213
|
end
|
@@ -225,7 +230,7 @@ module Discordrb
|
|
225
230
|
|
226
231
|
# Runs the bot, which logs into Discord and connects the WebSocket. This
|
227
232
|
# prevents all further execution unless it is executed with
|
228
|
-
# `
|
233
|
+
# `background` = `true`.
|
229
234
|
# @param background [true, false] If it is `true`, then the bot will run in
|
230
235
|
# another thread to allow further execution. If it is `false`, this method
|
231
236
|
# will block until {#stop} is called. If the bot is run with `true`, make
|
@@ -254,9 +259,9 @@ module Discordrb
|
|
254
259
|
|
255
260
|
# Stops the bot gracefully, disconnecting the websocket without immediately killing the thread. This means that
|
256
261
|
# Discord is immediately aware of the closed connection and makes the bot appear offline instantly.
|
257
|
-
# @
|
258
|
-
def stop(
|
259
|
-
@gateway.stop
|
262
|
+
# @note This method no longer takes an argument as of 3.4.0
|
263
|
+
def stop(_no_sync = nil)
|
264
|
+
@gateway.stop
|
260
265
|
end
|
261
266
|
|
262
267
|
# @return [true, false] whether or not the bot is currently connected to Discord.
|
@@ -273,14 +278,14 @@ module Discordrb
|
|
273
278
|
|
274
279
|
# Creates an OAuth invite URL that can be used to invite this bot to a particular server.
|
275
280
|
# @param server [Server, nil] The server the bot should be invited to, or nil if a general invite should be created.
|
276
|
-
# @param permission_bits [
|
281
|
+
# @param permission_bits [String, Integer] Permission bits that should be appended to invite url.
|
277
282
|
# @return [String] the OAuth invite URL.
|
278
283
|
def invite_url(server: nil, permission_bits: nil)
|
279
284
|
@client_id ||= bot_application.id
|
280
285
|
|
281
286
|
server_id_str = server ? "&guild_id=#{server.id}" : ''
|
282
287
|
permission_bits_str = permission_bits ? "&permissions=#{permission_bits}" : ''
|
283
|
-
"https://
|
288
|
+
"https://discord.com/oauth2/authorize?&client_id=#{@client_id}#{server_id_str}#{permission_bits_str}&scope=bot"
|
284
289
|
end
|
285
290
|
|
286
291
|
# @return [Hash<Integer => VoiceBot>] the voice connections this bot currently has, by the server ID to which they are connected.
|
@@ -299,22 +304,21 @@ module Discordrb
|
|
299
304
|
|
300
305
|
server_id = channel.server.id
|
301
306
|
return @voices[server_id] if @voices[server_id]
|
302
|
-
|
303
|
-
nil
|
304
307
|
end
|
305
308
|
|
306
309
|
# Connects to a voice channel, initializes network connections and returns the {Voice::VoiceBot} over which audio
|
307
310
|
# data can then be sent. After connecting, the bot can also be accessed using {#voice}. If the bot is already
|
308
311
|
# connected to voice, the existing connection will be terminated - you don't have to call
|
309
312
|
# {Discordrb::Voice::VoiceBot#destroy} before calling this method.
|
310
|
-
# @param chan [Channel,
|
311
|
-
# @param encrypted [true, false] Whether voice communication should be encrypted using
|
313
|
+
# @param chan [Channel, String, Integer] The voice channel, or its ID, to connect to.
|
314
|
+
# @param encrypted [true, false] Whether voice communication should be encrypted using
|
312
315
|
# (uses an XSalsa20 stream cipher for encryption and Poly1305 for authentication)
|
313
316
|
# @return [Voice::VoiceBot] the initialized bot over which audio data can then be sent.
|
314
317
|
def voice_connect(chan, encrypted = true)
|
318
|
+
raise ArgumentError, 'Unencrypted voice connections are no longer supported.' unless encrypted
|
319
|
+
|
315
320
|
chan = channel(chan.resolve_id)
|
316
321
|
server_id = chan.server.id
|
317
|
-
@should_encrypt_voice = encrypted
|
318
322
|
|
319
323
|
if @voices[chan.id]
|
320
324
|
debug('Voice bot exists already! Destroying it')
|
@@ -336,7 +340,7 @@ module Discordrb
|
|
336
340
|
|
337
341
|
# Disconnects the client from a specific voice connection given the server ID. Usually it's more convenient to use
|
338
342
|
# {Discordrb::Voice::VoiceBot#destroy} rather than this.
|
339
|
-
# @param server [Server,
|
343
|
+
# @param server [Server, String, Integer] The server, or server ID, the voice connection is on.
|
340
344
|
# @param destroy_vws [true, false] Whether or not the VWS should also be destroyed. If you're calling this method
|
341
345
|
# directly, you should leave it as true.
|
342
346
|
def voice_destroy(server, destroy_vws = true)
|
@@ -355,31 +359,38 @@ module Discordrb
|
|
355
359
|
end
|
356
360
|
|
357
361
|
# Sends a text message to a channel given its ID and the message's content.
|
358
|
-
# @param channel [Channel,
|
362
|
+
# @param channel [Channel, String, Integer] The channel, or its ID, to send something to.
|
359
363
|
# @param content [String] The text that should be sent as a message. It is limited to 2000 characters (Discord imposed).
|
360
364
|
# @param tts [true, false] Whether or not this message should be sent using Discord text-to-speech.
|
361
365
|
# @param embed [Hash, Discordrb::Webhooks::Embed, nil] The rich embed to append to this message.
|
366
|
+
# @param allowed_mentions [Hash, Discordrb::AllowedMentions, false, nil] Mentions that are allowed to ping on this message. `false` disables all pings
|
367
|
+
# @param message_reference [Message, String, Integer, nil] The message, or message ID, to reply to if any.
|
362
368
|
# @return [Message] The message that was sent.
|
363
|
-
def send_message(channel, content, tts = false, embed = nil)
|
369
|
+
def send_message(channel, content, tts = false, embed = nil, attachments = nil, allowed_mentions = nil, message_reference = nil)
|
364
370
|
channel = channel.resolve_id
|
365
371
|
debug("Sending message to #{channel} with content '#{content}'")
|
372
|
+
allowed_mentions = { parse: [] } if allowed_mentions == false
|
373
|
+
message_reference = { message_id: message_reference.id } if message_reference
|
366
374
|
|
367
|
-
response = API::Channel.create_message(token, channel, content, tts, embed
|
375
|
+
response = API::Channel.create_message(token, channel, content, tts, embed&.to_hash, nil, attachments, allowed_mentions&.to_hash, message_reference)
|
368
376
|
Message.new(JSON.parse(response), self)
|
369
377
|
end
|
370
378
|
|
371
379
|
# Sends a text message to a channel given its ID and the message's content,
|
372
380
|
# then deletes it after the specified timeout in seconds.
|
373
|
-
# @param channel [Channel,
|
381
|
+
# @param channel [Channel, String, Integer] The channel, or its ID, to send something to.
|
374
382
|
# @param content [String] The text that should be sent as a message. It is limited to 2000 characters (Discord imposed).
|
375
383
|
# @param timeout [Float] The amount of time in seconds after which the message sent will be deleted.
|
376
384
|
# @param tts [true, false] Whether or not this message should be sent using Discord text-to-speech.
|
377
385
|
# @param embed [Hash, Discordrb::Webhooks::Embed, nil] The rich embed to append to this message.
|
378
|
-
|
386
|
+
# @param attachments [Array<File>] Files that can be referenced in embeds via `attachment://file.png`
|
387
|
+
# @param allowed_mentions [Hash, Discordrb::AllowedMentions, false, nil] Mentions that are allowed to ping on this message. `false` disables all pings
|
388
|
+
# @param message_reference [Message, String, Integer, nil] The message, or message ID, to reply to if any.
|
389
|
+
def send_temporary_message(channel, content, timeout, tts = false, embed = nil, attachments = nil, allowed_mentions = nil, message_reference = nil)
|
379
390
|
Thread.new do
|
380
391
|
Thread.current[:discordrb_name] = "#{@current_thread}-temp-msg"
|
381
392
|
|
382
|
-
message = send_message(channel, content, tts, embed)
|
393
|
+
message = send_message(channel, content, tts, embed, attachments, allowed_mentions, message_reference)
|
383
394
|
sleep(timeout)
|
384
395
|
message.delete
|
385
396
|
end
|
@@ -389,13 +400,24 @@ module Discordrb
|
|
389
400
|
|
390
401
|
# Sends a file to a channel. If it is an image, it will automatically be embedded.
|
391
402
|
# @note This executes in a blocking way, so if you're sending long files, be wary of delays.
|
392
|
-
# @param channel [Channel,
|
403
|
+
# @param channel [Channel, String, Integer] The channel, or its ID, to send something to.
|
393
404
|
# @param file [File] The file that should be sent.
|
394
405
|
# @param caption [string] The caption for the file.
|
395
406
|
# @param tts [true, false] Whether or not this file's caption should be sent using Discord text-to-speech.
|
407
|
+
# @param filename [String] Overrides the filename of the uploaded file
|
408
|
+
# @param spoiler [true, false] Whether or not this file should appear as a spoiler.
|
396
409
|
# @example Send a file from disk
|
397
410
|
# bot.send_file(83281822225530880, File.open('rubytaco.png', 'r'))
|
398
|
-
def send_file(channel, file, caption: nil, tts: false)
|
411
|
+
def send_file(channel, file, caption: nil, tts: false, filename: nil, spoiler: nil)
|
412
|
+
if file.respond_to?(:read)
|
413
|
+
if spoiler
|
414
|
+
filename ||= File.basename(file.path)
|
415
|
+
filename = "SPOILER_#{filename}" unless filename.start_with? 'SPOILER_'
|
416
|
+
end
|
417
|
+
# https://github.com/rest-client/rest-client/blob/v2.0.2/lib/restclient/payload.rb#L160
|
418
|
+
file.define_singleton_method(:original_filename) { filename } if filename
|
419
|
+
end
|
420
|
+
|
399
421
|
channel = channel.resolve_id
|
400
422
|
response = API::Channel.upload_file(token, channel, file, caption: caption, tts: tts)
|
401
423
|
Message.new(JSON.parse(response), self)
|
@@ -410,14 +432,13 @@ module Discordrb
|
|
410
432
|
def create_server(name, region = :'eu-central')
|
411
433
|
response = API::Server.create(token, name, region)
|
412
434
|
id = JSON.parse(response)['id'].to_i
|
413
|
-
sleep 0.1 until @servers[id]
|
414
|
-
server = @servers[id]
|
435
|
+
sleep 0.1 until (server = @servers[id])
|
415
436
|
debug "Successfully created server #{server.id} with name #{server.name}"
|
416
437
|
server
|
417
438
|
end
|
418
439
|
|
419
440
|
# Creates a new application to do OAuth authorization with. This allows you to use OAuth to authorize users using
|
420
|
-
# Discord. For information how to use this, see the docs: https://
|
441
|
+
# Discord. For information how to use this, see the docs: https://discord.com/developers/docs/topics/oauth2
|
421
442
|
# @param name [String] What your application should be called.
|
422
443
|
# @param redirect_uris [Array<String>] URIs that Discord should redirect your users to after authorizing.
|
423
444
|
# @return [Array(String, String)] your applications' client ID and client secret to be used in OAuth authorization.
|
@@ -436,28 +457,46 @@ module Discordrb
|
|
436
457
|
API.update_oauth_application(@token, name, redirect_uris, description, icon)
|
437
458
|
end
|
438
459
|
|
439
|
-
# Gets the
|
460
|
+
# Gets the users, channels, roles and emoji from a string.
|
461
|
+
# @param mentions [String] The mentions, which should look like `<@12314873129>`, `<#123456789>`, `<@&123456789>` or `<:name:126328:>`.
|
462
|
+
# @param server [Server, nil] The server of the associated mentions. (recommended for role parsing, to speed things up)
|
463
|
+
# @return [Array<User, Channel, Role, Emoji>] The array of users, channels, roles and emoji identified by the mentions, or `nil` if none exists.
|
464
|
+
def parse_mentions(mentions, server = nil)
|
465
|
+
array_to_return = []
|
466
|
+
# While possible mentions may be in message
|
467
|
+
while mentions.include?('<') && mentions.include?('>')
|
468
|
+
# Removing all content before the next possible mention
|
469
|
+
mentions = mentions.split('<', 2)[1]
|
470
|
+
# Locate the first valid mention enclosed in `<...>`, otherwise advance to the next open `<`
|
471
|
+
next unless mentions.split('>', 2).first.length < mentions.split('<', 2).first.length
|
472
|
+
|
473
|
+
# Store the possible mention value to be validated with RegEx
|
474
|
+
mention = mentions.split('>', 2).first
|
475
|
+
if /@!?(?<id>\d+)/ =~ mention
|
476
|
+
array_to_return << user(id) unless user(id).nil?
|
477
|
+
elsif /#(?<id>\d+)/ =~ mention
|
478
|
+
array_to_return << channel(id, server) unless channel(id, server).nil?
|
479
|
+
elsif /@&(?<id>\d+)/ =~ mention
|
480
|
+
if server
|
481
|
+
array_to_return << server.role(id) unless server.role(id).nil?
|
482
|
+
else
|
483
|
+
@servers.each_value do |element|
|
484
|
+
array_to_return << element.role(id) unless element.role(id).nil?
|
485
|
+
end
|
486
|
+
end
|
487
|
+
elsif /(?<animated>^a|^${0}):(?<name>\w+):(?<id>\d+)/ =~ mention
|
488
|
+
array_to_return << (emoji(id) || Emoji.new({ 'animated' => !animated.nil?, 'name' => name, 'id' => id }, self, nil))
|
489
|
+
end
|
490
|
+
end
|
491
|
+
array_to_return
|
492
|
+
end
|
493
|
+
|
494
|
+
# Gets the user, channel, role or emoji from a string.
|
440
495
|
# @param mention [String] The mention, which should look like `<@12314873129>`, `<#123456789>`, `<@&123456789>` or `<:name:126328:>`.
|
441
496
|
# @param server [Server, nil] The server of the associated mention. (recommended for role parsing, to speed things up)
|
442
497
|
# @return [User, Channel, Role, Emoji] The user, channel, role or emoji identified by the mention, or `nil` if none exists.
|
443
498
|
def parse_mention(mention, server = nil)
|
444
|
-
|
445
|
-
if /<@!?(?<id>\d+)>/ =~ mention
|
446
|
-
user(id)
|
447
|
-
elsif /<#(?<id>\d+)>/ =~ mention
|
448
|
-
channel(id, server)
|
449
|
-
elsif /<@&(?<id>\d+)>/ =~ mention
|
450
|
-
return server.role(id) if server
|
451
|
-
@servers.values.each do |element|
|
452
|
-
role = element.role(id)
|
453
|
-
return role unless role.nil?
|
454
|
-
end
|
455
|
-
|
456
|
-
# Return nil if no role is found
|
457
|
-
nil
|
458
|
-
elsif /<(?<animated>a)?:(?<name>\w+):(?<id>\d+)>/ =~ mention
|
459
|
-
emoji(id) || Emoji.new({ 'animated' => !animated.nil?, 'name' => name, 'id' => id }, self, nil)
|
460
|
-
end
|
499
|
+
parse_mentions(mention, server).first
|
461
500
|
end
|
462
501
|
|
463
502
|
# Updates presence status.
|
@@ -466,7 +505,8 @@ module Discordrb
|
|
466
505
|
# @param url [String, nil] The Twitch URL to display as a stream. nil for no stream.
|
467
506
|
# @param since [Integer] When this status was set.
|
468
507
|
# @param afk [true, false] Whether the bot is AFK.
|
469
|
-
# @param activity_type [Integer] The type of activity status to display.
|
508
|
+
# @param activity_type [Integer] The type of activity status to display.
|
509
|
+
# Can be 0 (Playing), 1 (Streaming), 2 (Listening), 3 (Watching), or 5 (Competing).
|
470
510
|
# @see Gateway#send_status_update
|
471
511
|
def update_status(status, activity, url, since = 0, afk = false, activity_type = 0)
|
472
512
|
gateway_check
|
@@ -480,7 +520,7 @@ module Discordrb
|
|
480
520
|
@gateway.send_status_update(status, since, activity_obj, afk)
|
481
521
|
|
482
522
|
# Update the status in the cache
|
483
|
-
profile.update_presence('status' => status.to_s, '
|
523
|
+
profile.update_presence('status' => status.to_s, 'activities' => [activity_obj].compact)
|
484
524
|
end
|
485
525
|
|
486
526
|
# Sets the currently playing game to the specified game.
|
@@ -489,7 +529,6 @@ module Discordrb
|
|
489
529
|
def game=(name)
|
490
530
|
gateway_check
|
491
531
|
update_status(@status, name, nil)
|
492
|
-
name
|
493
532
|
end
|
494
533
|
|
495
534
|
alias_method :playing=, :game=
|
@@ -500,7 +539,6 @@ module Discordrb
|
|
500
539
|
def listening=(name)
|
501
540
|
gateway_check
|
502
541
|
update_status(@status, name, nil, nil, nil, 2)
|
503
|
-
name
|
504
542
|
end
|
505
543
|
|
506
544
|
# Sets the current watching status to the specified name.
|
@@ -509,7 +547,6 @@ module Discordrb
|
|
509
547
|
def watching=(name)
|
510
548
|
gateway_check
|
511
549
|
update_status(@status, name, nil, nil, nil, 3)
|
512
|
-
name
|
513
550
|
end
|
514
551
|
|
515
552
|
# Sets the currently online stream to the specified name and Twitch URL.
|
@@ -522,6 +559,14 @@ module Discordrb
|
|
522
559
|
name
|
523
560
|
end
|
524
561
|
|
562
|
+
# Sets the currently competing status to the specified name.
|
563
|
+
# @param name [String] The name of the game to be competing in.
|
564
|
+
# @return [String] The game that is being competed in now.
|
565
|
+
def competing=(name)
|
566
|
+
gateway_check
|
567
|
+
update_status(@status, name, nil, nil, nil, 5)
|
568
|
+
end
|
569
|
+
|
525
570
|
# Sets status to online.
|
526
571
|
def online
|
527
572
|
gateway_check
|
@@ -576,6 +621,7 @@ module Discordrb
|
|
576
621
|
# @deprecated Will be changed to blocking behavior in v4.0. Use {#add_await!} instead.
|
577
622
|
def add_await(key, type, attributes = {}, &block)
|
578
623
|
raise "You can't await an AwaitEvent!" if type == Discordrb::Events::AwaitEvent
|
624
|
+
|
579
625
|
await = Await.new(self, key, type, attributes, block)
|
580
626
|
@awaits ||= {}
|
581
627
|
@awaits[key] = await
|
@@ -583,14 +629,17 @@ module Discordrb
|
|
583
629
|
|
584
630
|
# Awaits an event, blocking the current thread until a response is received.
|
585
631
|
# @param type [Class] The event class that should be listened for.
|
586
|
-
# @option attributes [Numeric] :timeout the amount of time to wait for a response before returning `nil`. Waits forever if omitted.
|
632
|
+
# @option attributes [Numeric] :timeout the amount of time (in seconds) to wait for a response before returning `nil`. Waits forever if omitted.
|
633
|
+
# @yield Executed when a matching event is received.
|
634
|
+
# @yieldparam event [Event] The event object that was triggered.
|
635
|
+
# @yieldreturn [true, false] Whether the event matches extra await criteria described by the block
|
587
636
|
# @return [Event, nil] The event object that was triggered, or `nil` if a `timeout` was set and no event was raised in time.
|
588
637
|
# @raise [ArgumentError] if `timeout` is given and is not a positive numeric value
|
589
638
|
def add_await!(type, attributes = {})
|
590
639
|
raise "You can't await an AwaitEvent!" if type == Discordrb::Events::AwaitEvent
|
591
640
|
|
592
641
|
timeout = attributes[:timeout]
|
593
|
-
raise ArgumentError, 'Timeout must be a number > 0' if timeout
|
642
|
+
raise ArgumentError, 'Timeout must be a number > 0' if timeout.is_a?(Numeric) && !timeout.positive?
|
594
643
|
|
595
644
|
mutex = Mutex.new
|
596
645
|
cv = ConditionVariable.new
|
@@ -598,7 +647,12 @@ module Discordrb
|
|
598
647
|
block = lambda do |event|
|
599
648
|
mutex.synchronize do
|
600
649
|
response = event
|
601
|
-
|
650
|
+
if block_given?
|
651
|
+
result = yield(event)
|
652
|
+
cv.signal if result.is_a?(TrueClass)
|
653
|
+
else
|
654
|
+
cv.signal
|
655
|
+
end
|
602
656
|
end
|
603
657
|
end
|
604
658
|
|
@@ -615,25 +669,26 @@ module Discordrb
|
|
615
669
|
|
616
670
|
remove_handler(handler)
|
617
671
|
raise 'ConditionVariable was signaled without returning an event!' if response.nil? && timeout.nil?
|
672
|
+
|
618
673
|
response
|
619
674
|
end
|
620
675
|
|
621
676
|
# Add a user to the list of ignored users. Those users will be ignored in message events at event processing level.
|
622
677
|
# @note Ignoring a user only prevents any message events (including mentions, commands etc.) from them! Typing and
|
623
678
|
# presence and any other events will still be received.
|
624
|
-
# @param user [User,
|
679
|
+
# @param user [User, String, Integer] The user, or its ID, to be ignored.
|
625
680
|
def ignore_user(user)
|
626
681
|
@ignored_ids << user.resolve_id
|
627
682
|
end
|
628
683
|
|
629
684
|
# Remove a user from the ignore list.
|
630
|
-
# @param user [User,
|
685
|
+
# @param user [User, String, Integer] The user, or its ID, to be unignored.
|
631
686
|
def unignore_user(user)
|
632
687
|
@ignored_ids.delete(user.resolve_id)
|
633
688
|
end
|
634
689
|
|
635
690
|
# Checks whether a user is being ignored.
|
636
|
-
# @param user [User,
|
691
|
+
# @param user [User, String, Integer] The user, or its ID, to check.
|
637
692
|
# @return [true, false] whether or not the user is ignored.
|
638
693
|
def ignored?(user)
|
639
694
|
@ignored_ids.include?(user.resolve_id)
|
@@ -677,7 +732,8 @@ module Discordrb
|
|
677
732
|
# e.g. due to a Discord outage or because the servers are large and taking a while to load.
|
678
733
|
def unavailable_servers_check
|
679
734
|
# Return unless there are servers that are unavailable.
|
680
|
-
return unless @unavailable_servers
|
735
|
+
return unless @unavailable_servers&.positive?
|
736
|
+
|
681
737
|
LOGGER.warn("#{@unavailable_servers} servers haven't been cached yet.")
|
682
738
|
LOGGER.warn('Servers may be unavailable due to an outage, or your bot is on very large servers that are taking a while to load.')
|
683
739
|
end
|
@@ -737,10 +793,21 @@ module Discordrb
|
|
737
793
|
|
738
794
|
user_id = data['user_id'].to_i
|
739
795
|
old_voice_state = server.voice_states[user_id]
|
740
|
-
old_channel_id = old_voice_state.voice_channel
|
796
|
+
old_channel_id = old_voice_state.voice_channel&.id if old_voice_state
|
741
797
|
|
742
798
|
server.update_voice_state(data)
|
743
799
|
|
800
|
+
existing_voice = @voices[server_id]
|
801
|
+
if user_id == @profile.id && existing_voice
|
802
|
+
new_channel_id = data['channel_id']
|
803
|
+
if new_channel_id
|
804
|
+
new_channel = channel(new_channel_id)
|
805
|
+
existing_voice.channel = new_channel
|
806
|
+
else
|
807
|
+
voice_destroy(server_id)
|
808
|
+
end
|
809
|
+
end
|
810
|
+
|
744
811
|
old_channel_id
|
745
812
|
end
|
746
813
|
|
@@ -751,6 +818,7 @@ module Discordrb
|
|
751
818
|
|
752
819
|
debug("Voice server update received! chan: #{channel.inspect}")
|
753
820
|
return unless channel
|
821
|
+
|
754
822
|
@should_connect_to_voice.delete(server_id)
|
755
823
|
debug('Updating voice server!')
|
756
824
|
|
@@ -763,7 +831,7 @@ module Discordrb
|
|
763
831
|
end
|
764
832
|
|
765
833
|
debug('Got data, now creating the bot.')
|
766
|
-
@voices[server_id] = Discordrb::Voice::VoiceBot.new(channel, self, token, @session_id, endpoint
|
834
|
+
@voices[server_id] = Discordrb::Voice::VoiceBot.new(channel, self, token, @session_id, endpoint)
|
767
835
|
end
|
768
836
|
|
769
837
|
# Internal handler for CHANNEL_CREATE
|
@@ -787,6 +855,7 @@ module Discordrb
|
|
787
855
|
channel = Channel.new(data, self)
|
788
856
|
old_channel = @channels[channel.id]
|
789
857
|
return unless old_channel
|
858
|
+
|
790
859
|
old_channel.update_from(channel)
|
791
860
|
end
|
792
861
|
|
@@ -843,12 +912,14 @@ module Discordrb
|
|
843
912
|
member = server.member(data['user']['id'].to_i)
|
844
913
|
member.update_roles(data['roles'])
|
845
914
|
member.update_nick(data['nick'])
|
915
|
+
member.update_boosting_since(data['premium_since'])
|
846
916
|
end
|
847
917
|
|
848
918
|
# Internal handler for GUILD_MEMBER_DELETE
|
849
919
|
def delete_guild_member(data)
|
850
920
|
server_id = data['guild_id'].to_i
|
851
921
|
server = self.server(server_id)
|
922
|
+
return unless server
|
852
923
|
|
853
924
|
user_id = data['user']['id'].to_i
|
854
925
|
server.delete_member(user_id)
|
@@ -951,13 +1022,13 @@ module Discordrb
|
|
951
1022
|
# Remove the "Bot " prefix if it exists
|
952
1023
|
token = token[4..-1] if token.start_with? 'Bot '
|
953
1024
|
|
954
|
-
token =
|
1025
|
+
token = "Bot #{token}" unless type == :user
|
955
1026
|
token
|
956
1027
|
end
|
957
1028
|
|
958
1029
|
def handle_dispatch(type, data)
|
959
1030
|
# Check whether there are still unavailable servers and there have been more than 10 seconds since READY
|
960
|
-
if @unavailable_servers &&
|
1031
|
+
if @unavailable_servers&.positive? && (Time.now - @unavailable_timeout_time) > 10 && !((@intents || 0) & INTENTS[:servers]).zero?
|
961
1032
|
# The server streaming timed out!
|
962
1033
|
LOGGER.debug("Server streaming timed out with #{@unavailable_servers} servers remaining")
|
963
1034
|
LOGGER.debug('Calling ready now because server loading is taking a long time. Servers may be unavailable due to an outage, or your bot is on very large servers.')
|
@@ -1019,6 +1090,11 @@ module Discordrb
|
|
1019
1090
|
id = data['guild_id'].to_i
|
1020
1091
|
server = server(id)
|
1021
1092
|
server.process_chunk(data['members'])
|
1093
|
+
when :INVITE_CREATE
|
1094
|
+
invite = Invite.new(data, self)
|
1095
|
+
raise_event(InviteCreateEvent.new(data, invite, self))
|
1096
|
+
when :INVITE_DELETE
|
1097
|
+
raise_event(InviteDeleteEvent.new(data, self))
|
1022
1098
|
when :MESSAGE_CREATE
|
1023
1099
|
if ignored?(data['author']['id'].to_i)
|
1024
1100
|
debug("Ignored author with ID #{data['author']['id']}")
|
@@ -1053,6 +1129,10 @@ module Discordrb
|
|
1053
1129
|
update_message(data)
|
1054
1130
|
|
1055
1131
|
message = Message.new(data, self)
|
1132
|
+
|
1133
|
+
event = MessageUpdateEvent.new(message, self)
|
1134
|
+
raise_event(event)
|
1135
|
+
|
1056
1136
|
return if message.from_bot? && !should_parse_self
|
1057
1137
|
|
1058
1138
|
unless message.author
|
@@ -1120,10 +1200,10 @@ module Discordrb
|
|
1120
1200
|
played_before = presence_user.nil? ? nil : presence_user.game
|
1121
1201
|
update_presence(data)
|
1122
1202
|
|
1123
|
-
event = if now_playing
|
1124
|
-
PlayingEvent.new(data, self)
|
1125
|
-
else
|
1203
|
+
event = if now_playing == played_before
|
1126
1204
|
PresenceEvent.new(data, self)
|
1205
|
+
else
|
1206
|
+
PlayingEvent.new(data, self)
|
1127
1207
|
end
|
1128
1208
|
|
1129
1209
|
raise_event(event)
|
@@ -1135,7 +1215,8 @@ module Discordrb
|
|
1135
1215
|
when :VOICE_SERVER_UPDATE
|
1136
1216
|
update_voice_server(data)
|
1137
1217
|
|
1138
|
-
|
1218
|
+
event = VoiceServerUpdateEvent.new(data, self)
|
1219
|
+
raise_event(event)
|
1139
1220
|
when :CHANNEL_CREATE
|
1140
1221
|
create_channel(data)
|
1141
1222
|
|
@@ -1300,6 +1381,7 @@ module Discordrb
|
|
1300
1381
|
@event_handlers ||= {}
|
1301
1382
|
handlers = @event_handlers[event.class]
|
1302
1383
|
return unless handlers
|
1384
|
+
|
1303
1385
|
handlers.dup.each do |handler|
|
1304
1386
|
call_event(handler, event) if handler.matches?(event)
|
1305
1387
|
end
|
@@ -1315,7 +1397,7 @@ module Discordrb
|
|
1315
1397
|
begin
|
1316
1398
|
handler.call(event)
|
1317
1399
|
handler.after_call(event)
|
1318
|
-
rescue => e
|
1400
|
+
rescue StandardError => e
|
1319
1401
|
log_exception(e)
|
1320
1402
|
ensure
|
1321
1403
|
@event_threads.delete(t)
|
@@ -1328,6 +1410,7 @@ module Discordrb
|
|
1328
1410
|
@awaits.each do |_, await|
|
1329
1411
|
key, should_delete = await.match(event)
|
1330
1412
|
next unless key
|
1413
|
+
|
1331
1414
|
debug("should_delete: #{should_delete}")
|
1332
1415
|
@awaits.delete(await.key) if should_delete
|
1333
1416
|
|
@@ -1335,5 +1418,24 @@ module Discordrb
|
|
1335
1418
|
raise_event(await_event)
|
1336
1419
|
end
|
1337
1420
|
end
|
1421
|
+
|
1422
|
+
def calculate_intents(intents)
|
1423
|
+
intents.reduce(0) do |sum, intent|
|
1424
|
+
case intent
|
1425
|
+
when Symbol
|
1426
|
+
if INTENTS[intent]
|
1427
|
+
sum | INTENTS[intent]
|
1428
|
+
else
|
1429
|
+
LOGGER.warn("Unknown intent: #{intent}")
|
1430
|
+
sum
|
1431
|
+
end
|
1432
|
+
when Integer
|
1433
|
+
sum | intent
|
1434
|
+
else
|
1435
|
+
LOGGER.warn("Invalid intent: #{intent}")
|
1436
|
+
sum
|
1437
|
+
end
|
1438
|
+
end
|
1439
|
+
end
|
1338
1440
|
end
|
1339
1441
|
end
|
data/lib/discordrb/cache.rb
CHANGED
@@ -134,6 +134,7 @@ module Discordrb
|
|
134
134
|
def pm_channel(id)
|
135
135
|
id = id.resolve_id
|
136
136
|
return @pm_channels[id] if @pm_channels[id]
|
137
|
+
|
137
138
|
debug("Creating pm channel with user id #{id}")
|
138
139
|
response = API::User.create_pm(token, id)
|
139
140
|
channel = Channel.new(JSON.parse(response), self)
|
@@ -187,7 +188,7 @@ module Discordrb
|
|
187
188
|
#
|
188
189
|
# * An {Invite} object
|
189
190
|
# * The code for an invite
|
190
|
-
# * A fully qualified invite URL (e.g. `https://
|
191
|
+
# * A fully qualified invite URL (e.g. `https://discord.com/invite/0A37aN7fasF7n83q`)
|
191
192
|
# * A short invite URL with protocol (e.g. `https://discord.gg/0A37aN7fasF7n83q`)
|
192
193
|
# * A short invite URL without protocol (e.g. `discord.gg/0A37aN7fasF7n83q`)
|
193
194
|
# @return [String] Only the code for the invite.
|
@@ -219,7 +220,7 @@ module Discordrb
|
|
219
220
|
return [channel(id)]
|
220
221
|
end
|
221
222
|
|
222
|
-
@servers.
|
223
|
+
@servers.each_value do |server|
|
223
224
|
server.channels.each do |channel|
|
224
225
|
results << channel if channel.name == channel_name && (server_name || server.name) == server.name && (!type || (channel.type == type))
|
225
226
|
end
|
@@ -248,6 +249,7 @@ module Discordrb
|
|
248
249
|
def find_user(username, discrim = nil)
|
249
250
|
users = @users.values.find_all { |e| e.username == username }
|
250
251
|
return users.find { |u| u.discrim == discrim } if discrim
|
252
|
+
|
251
253
|
users
|
252
254
|
end
|
253
255
|
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
|