discordrb 3.3.0 → 3.4.3
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 +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
|