discordrb 3.4.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 +44 -18
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -1
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -1
- data/.github/workflows/codeql.yml +65 -0
- data/.markdownlint.json +4 -0
- data/.rubocop.yml +8 -2
- data/CHANGELOG.md +419 -222
- data/LICENSE.txt +1 -1
- data/README.md +37 -25
- data/discordrb-webhooks.gemspec +4 -1
- data/discordrb.gemspec +9 -6
- data/lib/discordrb/api/application.rb +202 -0
- data/lib/discordrb/api/channel.rb +182 -11
- data/lib/discordrb/api/interaction.rb +54 -0
- data/lib/discordrb/api/invite.rb +2 -2
- data/lib/discordrb/api/server.rb +42 -19
- data/lib/discordrb/api/user.rb +9 -3
- data/lib/discordrb/api/webhook.rb +57 -0
- data/lib/discordrb/api.rb +19 -5
- data/lib/discordrb/bot.rb +328 -33
- data/lib/discordrb/cache.rb +27 -22
- data/lib/discordrb/commands/command_bot.rb +14 -7
- data/lib/discordrb/commands/container.rb +1 -1
- data/lib/discordrb/commands/parser.rb +2 -2
- data/lib/discordrb/commands/rate_limiter.rb +1 -1
- data/lib/discordrb/container.rb +132 -3
- data/lib/discordrb/data/activity.rb +8 -1
- data/lib/discordrb/data/attachment.rb +15 -0
- data/lib/discordrb/data/audit_logs.rb +3 -3
- data/lib/discordrb/data/channel.rb +167 -23
- data/lib/discordrb/data/component.rb +229 -0
- data/lib/discordrb/data/integration.rb +42 -3
- data/lib/discordrb/data/interaction.rb +800 -0
- data/lib/discordrb/data/invite.rb +2 -2
- data/lib/discordrb/data/member.rb +108 -33
- data/lib/discordrb/data/message.rb +100 -20
- data/lib/discordrb/data/overwrite.rb +13 -7
- data/lib/discordrb/data/role.rb +58 -1
- data/lib/discordrb/data/server.rb +82 -80
- data/lib/discordrb/data/user.rb +69 -9
- data/lib/discordrb/data/webhook.rb +97 -4
- data/lib/discordrb/data.rb +3 -0
- data/lib/discordrb/errors.rb +44 -3
- data/lib/discordrb/events/channels.rb +1 -1
- data/lib/discordrb/events/interactions.rb +482 -0
- data/lib/discordrb/events/message.rb +9 -6
- data/lib/discordrb/events/presence.rb +21 -14
- data/lib/discordrb/events/reactions.rb +0 -1
- data/lib/discordrb/events/threads.rb +96 -0
- data/lib/discordrb/gateway.rb +30 -17
- data/lib/discordrb/permissions.rb +59 -34
- data/lib/discordrb/version.rb +1 -1
- data/lib/discordrb/voice/encoder.rb +13 -4
- data/lib/discordrb/voice/network.rb +18 -7
- data/lib/discordrb/voice/sodium.rb +3 -1
- data/lib/discordrb/voice/voice_bot.rb +3 -3
- data/lib/discordrb/webhooks.rb +2 -0
- data/lib/discordrb.rb +37 -4
- metadata +53 -19
- data/.codeclimate.yml +0 -16
- data/.travis.yml +0 -32
- data/bin/travis_build_docs.sh +0 -17
data/lib/discordrb/api.rb
CHANGED
@@ -9,7 +9,7 @@ require 'discordrb/errors'
|
|
9
9
|
# List of methods representing endpoints in Discord's API
|
10
10
|
module Discordrb::API
|
11
11
|
# The base URL of the Discord REST API.
|
12
|
-
APIBASE = 'https://discord.com/api/
|
12
|
+
APIBASE = 'https://discord.com/api/v9'
|
13
13
|
|
14
14
|
# The URL of Discord's CDN
|
15
15
|
CDN_URL = 'https://cdn.discordapp.com'
|
@@ -94,9 +94,6 @@ module Discordrb::API
|
|
94
94
|
# Add a custom user agent
|
95
95
|
attributes.last[:user_agent] = user_agent if attributes.last.is_a? Hash
|
96
96
|
|
97
|
-
# Specify RateLimit precision
|
98
|
-
attributes.last[:x_ratelimit_precision] = 'millisecond'
|
99
|
-
|
100
97
|
# The most recent Discord rate limit requirements require the support of major parameters, where a particular route
|
101
98
|
# and major parameter combination (*not* the HTTP method) uniquely identifies a RL bucket.
|
102
99
|
key = [key, major_parameter].freeze
|
@@ -115,6 +112,15 @@ module Discordrb::API
|
|
115
112
|
response = raw_request(type, attributes)
|
116
113
|
rescue RestClient::Exception => e
|
117
114
|
response = e.response
|
115
|
+
|
116
|
+
if response.body && !e.is_a?(RestClient::TooManyRequests)
|
117
|
+
data = JSON.parse(response.body)
|
118
|
+
err_klass = Discordrb::Errors.error_class_for(data['code'] || 0)
|
119
|
+
e = err_klass.new(data['message'], data['errors'])
|
120
|
+
|
121
|
+
Discordrb::LOGGER.error(e.full_message)
|
122
|
+
end
|
123
|
+
|
118
124
|
raise e
|
119
125
|
rescue Discordrb::Errors::NoPermission => e
|
120
126
|
if e.respond_to?(:_rc_response)
|
@@ -137,7 +143,7 @@ module Discordrb::API
|
|
137
143
|
|
138
144
|
unless mutex.locked?
|
139
145
|
response = JSON.parse(e.response)
|
140
|
-
wait_seconds = response['retry_after'].
|
146
|
+
wait_seconds = response['retry_after'] ? response['retry_after'].to_f : e.response.headers[:retry_after].to_i
|
141
147
|
Discordrb::LOGGER.ratelimit("Locking RL mutex (key: #{key}) for #{wait_seconds} seconds due to Discord rate limiting")
|
142
148
|
trace("429 #{key.join(' ')}")
|
143
149
|
|
@@ -217,6 +223,14 @@ module Discordrb::API
|
|
217
223
|
"#{cdn_url}/app-assets/#{application_id}/achievements/#{achievement_id}/icons/#{icon_hash}.#{format}"
|
218
224
|
end
|
219
225
|
|
226
|
+
# @param role_id [String, Integer]
|
227
|
+
# @param icon_hash [String]
|
228
|
+
# @param format ['webp', 'png', 'jpeg']
|
229
|
+
# @return [String]
|
230
|
+
def role_icon_url(role_id, icon_hash, format = 'webp')
|
231
|
+
"#{cdn_url}/role-icons/#{role_id}/#{icon_hash}.#{format}"
|
232
|
+
end
|
233
|
+
|
220
234
|
# Login to the server
|
221
235
|
def login(email, password)
|
222
236
|
request(
|
data/lib/discordrb/bot.rb
CHANGED
@@ -19,11 +19,16 @@ require 'discordrb/events/raw'
|
|
19
19
|
require 'discordrb/events/reactions'
|
20
20
|
require 'discordrb/events/webhooks'
|
21
21
|
require 'discordrb/events/invites'
|
22
|
+
require 'discordrb/events/interactions'
|
23
|
+
require 'discordrb/events/threads'
|
22
24
|
|
23
25
|
require 'discordrb/api'
|
24
26
|
require 'discordrb/api/channel'
|
25
27
|
require 'discordrb/api/server'
|
26
28
|
require 'discordrb/api/invite'
|
29
|
+
require 'discordrb/api/interaction'
|
30
|
+
require 'discordrb/api/application'
|
31
|
+
|
27
32
|
require 'discordrb/errors'
|
28
33
|
require 'discordrb/data'
|
29
34
|
require 'discordrb/await'
|
@@ -93,23 +98,25 @@ module Discordrb
|
|
93
98
|
# @param parse_self [true, false] Whether the bot should react on its own messages. It's best to turn this off
|
94
99
|
# unless you really need this so you don't inadvertently create infinite loops.
|
95
100
|
# @param shard_id [Integer] The number of the shard this bot should handle. See
|
96
|
-
# https://github.com/
|
101
|
+
# https://github.com/discord/discord-api-docs/issues/17 for how to do sharding.
|
97
102
|
# @param num_shards [Integer] The total number of shards that should be running. See
|
98
|
-
# https://github.com/
|
103
|
+
# https://github.com/discord/discord-api-docs/issues/17 for how to do sharding.
|
99
104
|
# @param redact_token [true, false] Whether the bot should redact the token in logs. Default is true.
|
100
105
|
# @param ignore_bots [true, false] Whether the bot should ignore bot accounts or not. Default is false.
|
101
106
|
# @param compress_mode [:none, :large, :stream] Sets which compression mode should be used when connecting
|
102
107
|
# to Discord's gateway. `:none` will request that no payloads are received compressed (not recommended for
|
103
108
|
# production bots). `:large` will request that large payloads are received compressed. `:stream` will request
|
104
109
|
# that all data be received in a continuous compressed stream.
|
105
|
-
# @param intents [:all, Array<Symbol>,
|
106
|
-
#
|
110
|
+
# @param intents [:all, :unprivileged, Array<Symbol>, :none] Gateway intents that this bot requires. `:all` will
|
111
|
+
# request all intents. `:unprivileged` will request only intents that are not defined as "Privileged". `:none`
|
112
|
+
# will request no intents. An array of symbols will request only those intents specified.
|
113
|
+
# @see Discordrb::INTENTS
|
107
114
|
def initialize(
|
108
115
|
log_mode: :normal,
|
109
116
|
token: nil, client_id: nil,
|
110
117
|
type: nil, name: '', fancy_log: false, suppress_ready: false, parse_self: false,
|
111
118
|
shard_id: nil, num_shards: nil, redact_token: true, ignore_bots: false,
|
112
|
-
compress_mode: :large, intents:
|
119
|
+
compress_mode: :large, intents: :all
|
113
120
|
)
|
114
121
|
LOGGER.mode = log_mode
|
115
122
|
LOGGER.token = token if redact_token
|
@@ -130,7 +137,16 @@ module Discordrb
|
|
130
137
|
|
131
138
|
raise 'Token string is empty or nil' if token.nil? || token.empty?
|
132
139
|
|
133
|
-
@intents =
|
140
|
+
@intents = case intents
|
141
|
+
when :all
|
142
|
+
ALL_INTENTS
|
143
|
+
when :unprivileged
|
144
|
+
UNPRIVILEGED_INTENTS
|
145
|
+
when :none
|
146
|
+
NO_INTENTS
|
147
|
+
else
|
148
|
+
calculate_intents(intents)
|
149
|
+
end
|
134
150
|
|
135
151
|
@token = process_token(@type, token)
|
136
152
|
@gateway = Gateway.new(self, @token, @shard_key, @compress_mode, @intents)
|
@@ -147,6 +163,8 @@ module Discordrb
|
|
147
163
|
@current_thread = 0
|
148
164
|
|
149
165
|
@status = :online
|
166
|
+
|
167
|
+
@application_commands = {}
|
150
168
|
end
|
151
169
|
|
152
170
|
# The list of users the bot shares a server with.
|
@@ -165,6 +183,14 @@ module Discordrb
|
|
165
183
|
@servers
|
166
184
|
end
|
167
185
|
|
186
|
+
# The list of members in threads the bot can see.
|
187
|
+
# @return [Hash<Integer => Hash<Integer => Hash<String => Object>>]
|
188
|
+
def thread_members
|
189
|
+
gateway_check
|
190
|
+
unavailable_servers_check
|
191
|
+
@thread_members
|
192
|
+
end
|
193
|
+
|
168
194
|
# @overload emoji(id)
|
169
195
|
# Return an emoji by its ID
|
170
196
|
# @param id [String, Integer] The emoji's ID.
|
@@ -197,8 +223,10 @@ module Discordrb
|
|
197
223
|
# to edit user data like the current username (see {Profile#username=}).
|
198
224
|
# @return [Profile] The bot's profile that can be used to edit data.
|
199
225
|
def profile
|
200
|
-
|
201
|
-
|
226
|
+
return @profile if @profile
|
227
|
+
|
228
|
+
response = Discordrb::API::User.profile(@token)
|
229
|
+
@profile = Profile.new(JSON.parse(response), self)
|
202
230
|
end
|
203
231
|
|
204
232
|
alias_method :bot_user, :profile
|
@@ -362,17 +390,19 @@ module Discordrb
|
|
362
390
|
# @param channel [Channel, String, Integer] The channel, or its ID, to send something to.
|
363
391
|
# @param content [String] The text that should be sent as a message. It is limited to 2000 characters (Discord imposed).
|
364
392
|
# @param tts [true, false] Whether or not this message should be sent using Discord text-to-speech.
|
365
|
-
# @param
|
393
|
+
# @param embeds [Hash, Discordrb::Webhooks::Embed, Array<Hash>, Array<Discordrb::Webhooks::Embed> nil] The rich embed(s) to append to this message.
|
366
394
|
# @param allowed_mentions [Hash, Discordrb::AllowedMentions, false, nil] Mentions that are allowed to ping on this message. `false` disables all pings
|
367
395
|
# @param message_reference [Message, String, Integer, nil] The message, or message ID, to reply to if any.
|
396
|
+
# @param components [View, Array<Hash>] Interaction components to associate with this message.
|
368
397
|
# @return [Message] The message that was sent.
|
369
|
-
def send_message(channel, content, tts = false,
|
398
|
+
def send_message(channel, content, tts = false, embeds = nil, attachments = nil, allowed_mentions = nil, message_reference = nil, components = nil)
|
370
399
|
channel = channel.resolve_id
|
371
400
|
debug("Sending message to #{channel} with content '#{content}'")
|
372
401
|
allowed_mentions = { parse: [] } if allowed_mentions == false
|
373
|
-
message_reference = { message_id: message_reference.id } if message_reference
|
402
|
+
message_reference = { message_id: message_reference.id } if message_reference.respond_to?(:id)
|
403
|
+
embeds = (embeds.instance_of?(Array) ? embeds.map(&:to_hash) : [embeds&.to_hash]).compact
|
374
404
|
|
375
|
-
response = API::Channel.create_message(token, channel, content, tts,
|
405
|
+
response = API::Channel.create_message(token, channel, content, tts, embeds, nil, attachments, allowed_mentions&.to_hash, message_reference, components)
|
376
406
|
Message.new(JSON.parse(response), self)
|
377
407
|
end
|
378
408
|
|
@@ -382,14 +412,16 @@ module Discordrb
|
|
382
412
|
# @param content [String] The text that should be sent as a message. It is limited to 2000 characters (Discord imposed).
|
383
413
|
# @param timeout [Float] The amount of time in seconds after which the message sent will be deleted.
|
384
414
|
# @param tts [true, false] Whether or not this message should be sent using Discord text-to-speech.
|
385
|
-
# @param
|
415
|
+
# @param embeds [Hash, Discordrb::Webhooks::Embed, Array<Hash>, Array<Discordrb::Webhooks::Embed> nil] The rich embed(s) to append to this message.
|
386
416
|
# @param attachments [Array<File>] Files that can be referenced in embeds via `attachment://file.png`
|
387
417
|
# @param allowed_mentions [Hash, Discordrb::AllowedMentions, false, nil] Mentions that are allowed to ping on this message. `false` disables all pings
|
388
|
-
|
418
|
+
# @param message_reference [Message, String, Integer, nil] The message, or message ID, to reply to if any.
|
419
|
+
# @param components [View, Array<Hash>] Interaction components to associate with this message.
|
420
|
+
def send_temporary_message(channel, content, timeout, tts = false, embeds = nil, attachments = nil, allowed_mentions = nil, message_reference = nil, components = nil)
|
389
421
|
Thread.new do
|
390
422
|
Thread.current[:discordrb_name] = "#{@current_thread}-temp-msg"
|
391
423
|
|
392
|
-
message = send_message(channel, content, tts,
|
424
|
+
message = send_message(channel, content, tts, embeds, attachments, allowed_mentions, message_reference, components)
|
393
425
|
sleep(timeout)
|
394
426
|
message.delete
|
395
427
|
end
|
@@ -415,6 +447,7 @@ module Discordrb
|
|
415
447
|
end
|
416
448
|
# https://github.com/rest-client/rest-client/blob/v2.0.2/lib/restclient/payload.rb#L160
|
417
449
|
file.define_singleton_method(:original_filename) { filename } if filename
|
450
|
+
file.define_singleton_method(:path) { filename } if filename
|
418
451
|
end
|
419
452
|
|
420
453
|
channel = channel.resolve_id
|
@@ -504,7 +537,8 @@ module Discordrb
|
|
504
537
|
# @param url [String, nil] The Twitch URL to display as a stream. nil for no stream.
|
505
538
|
# @param since [Integer] When this status was set.
|
506
539
|
# @param afk [true, false] Whether the bot is AFK.
|
507
|
-
# @param activity_type [Integer] The type of activity status to display.
|
540
|
+
# @param activity_type [Integer] The type of activity status to display.
|
541
|
+
# Can be 0 (Playing), 1 (Streaming), 2 (Listening), 3 (Watching), or 5 (Competing).
|
508
542
|
# @see Gateway#send_status_update
|
509
543
|
def update_status(status, activity, url, since = 0, afk = false, activity_type = 0)
|
510
544
|
gateway_check
|
@@ -557,6 +591,14 @@ module Discordrb
|
|
557
591
|
name
|
558
592
|
end
|
559
593
|
|
594
|
+
# Sets the currently competing status to the specified name.
|
595
|
+
# @param name [String] The name of the game to be competing in.
|
596
|
+
# @return [String] The game that is being competed in now.
|
597
|
+
def competing=(name)
|
598
|
+
gateway_check
|
599
|
+
update_status(@status, name, nil, nil, nil, 5)
|
600
|
+
end
|
601
|
+
|
560
602
|
# Sets status to online.
|
561
603
|
def online
|
562
604
|
gateway_check
|
@@ -585,6 +627,36 @@ module Discordrb
|
|
585
627
|
update_status(:invisible, @activity, nil)
|
586
628
|
end
|
587
629
|
|
630
|
+
# Join a thread
|
631
|
+
# @param channel [Channel, Integer, String]
|
632
|
+
def join_thread(channel)
|
633
|
+
API::Channel.join_thread(@token, channel.resolve_id)
|
634
|
+
nil
|
635
|
+
end
|
636
|
+
|
637
|
+
# Leave a thread
|
638
|
+
# @param channel [Channel, Integer, String]
|
639
|
+
def leave_thread(channel)
|
640
|
+
API::Channel.leave_thread(@token, channel.resolve_id)
|
641
|
+
nil
|
642
|
+
end
|
643
|
+
|
644
|
+
# Add a member to a thread
|
645
|
+
# @param channel [Channel, Integer, String]
|
646
|
+
# @param member [Member, Integer, String]
|
647
|
+
def add_thread_member(channel, member)
|
648
|
+
API::Channel.add_thread_member(@token, channel.resolve_id, member.resolve_id)
|
649
|
+
nil
|
650
|
+
end
|
651
|
+
|
652
|
+
# Remove a member from a thread
|
653
|
+
# @param channel [Channel, Integer, String]
|
654
|
+
# @param member [Member, Integer, String]
|
655
|
+
def remove_thread_member(channel, member)
|
656
|
+
API::Channel.remove_thread_member(@token, channel.resolve_id, member.resolve_id)
|
657
|
+
nil
|
658
|
+
end
|
659
|
+
|
588
660
|
# Sets debug mode. If debug mode is on, many things will be outputted to STDOUT.
|
589
661
|
def debug=(new_debug)
|
590
662
|
LOGGER.debug = new_debug
|
@@ -711,6 +783,118 @@ module Discordrb
|
|
711
783
|
end
|
712
784
|
end
|
713
785
|
|
786
|
+
# Get all application commands.
|
787
|
+
# @param server_id [String, Integer, nil] The ID of the server to get the commands from. Global if `nil`.
|
788
|
+
# @return [Array<ApplicationCommand>]
|
789
|
+
def get_application_commands(server_id: nil)
|
790
|
+
resp = if server_id
|
791
|
+
API::Application.get_guild_commands(@token, profile.id, server_id)
|
792
|
+
else
|
793
|
+
API::Application.get_global_commands(@token, profile.id)
|
794
|
+
end
|
795
|
+
|
796
|
+
JSON.parse(resp).map do |command_data|
|
797
|
+
ApplicationCommand.new(command_data, self, server_id)
|
798
|
+
end
|
799
|
+
end
|
800
|
+
|
801
|
+
# Get an application command by ID.
|
802
|
+
# @param command_id [String, Integer]
|
803
|
+
# @param server_id [String, Integer, nil] The ID of the server to get the command from. Global if `nil`.
|
804
|
+
def get_application_command(command_id, server_id: nil)
|
805
|
+
resp = if server_id
|
806
|
+
API::Application.get_guild_command(@token, profile.id, server_id, command_id)
|
807
|
+
else
|
808
|
+
API::Application.get_global_command(@token, profile.id, command_id)
|
809
|
+
end
|
810
|
+
ApplicationCommand.new(JSON.parse(resp), self, server_id)
|
811
|
+
end
|
812
|
+
|
813
|
+
# @yieldparam [OptionBuilder]
|
814
|
+
# @yieldparam [PermissionBuilder]
|
815
|
+
# @example
|
816
|
+
# bot.register_application_command(:reddit, 'Reddit Commands') do |cmd|
|
817
|
+
# cmd.subcommand_group(:subreddit, 'Subreddit Commands') do |group|
|
818
|
+
# group.subcommand(:hot, "What's trending") do |sub|
|
819
|
+
# sub.string(:subreddit, 'Subreddit to search')
|
820
|
+
# end
|
821
|
+
# group.subcommand(:new, "What's new") do |sub|
|
822
|
+
# sub.string(:since, 'How long ago', choices: ['this hour', 'today', 'this week', 'this month', 'this year', 'all time'])
|
823
|
+
# sub.string(:subreddit, 'Subreddit to search')
|
824
|
+
# end
|
825
|
+
# end
|
826
|
+
# end
|
827
|
+
def register_application_command(name, description, server_id: nil, default_permission: nil, type: :chat_input)
|
828
|
+
type = ApplicationCommand::TYPES[type] || type
|
829
|
+
|
830
|
+
builder = Interactions::OptionBuilder.new
|
831
|
+
permission_builder = Interactions::PermissionBuilder.new
|
832
|
+
yield(builder, permission_builder) if block_given?
|
833
|
+
|
834
|
+
resp = if server_id
|
835
|
+
API::Application.create_guild_command(@token, profile.id, server_id, name, description, builder.to_a, default_permission, type)
|
836
|
+
else
|
837
|
+
API::Application.create_global_command(@token, profile.id, name, description, builder.to_a, default_permission, type)
|
838
|
+
end
|
839
|
+
cmd = ApplicationCommand.new(JSON.parse(resp), self, server_id)
|
840
|
+
|
841
|
+
if permission_builder.to_a.any?
|
842
|
+
raise ArgumentError, 'Permissions can only be set for guild commands' unless server_id
|
843
|
+
|
844
|
+
edit_application_command_permissions(cmd.id, server_id, permission_builder.to_a)
|
845
|
+
end
|
846
|
+
|
847
|
+
cmd
|
848
|
+
end
|
849
|
+
|
850
|
+
# @yieldparam [OptionBuilder]
|
851
|
+
# @yieldparam [PermissionBuilder]
|
852
|
+
def edit_application_command(command_id, server_id: nil, name: nil, description: nil, default_permission: nil, type: :chat_input)
|
853
|
+
type = ApplicationCommand::TYPES[type] || type
|
854
|
+
|
855
|
+
builder = Interactions::OptionBuilder.new
|
856
|
+
permission_builder = Interactions::PermissionBuilder.new
|
857
|
+
|
858
|
+
yield(builder, permission_builder) if block_given?
|
859
|
+
|
860
|
+
resp = if server_id
|
861
|
+
API::Application.edit_guild_command(@token, profile.id, server_id, command_id, name, description, builder.to_a, default_permission, type)
|
862
|
+
else
|
863
|
+
API::Application.edit_guild_command(@token, profile.id, command_id, name, description, builder.to_a, default_permission.type)
|
864
|
+
end
|
865
|
+
cmd = ApplicationCommand.new(JSON.parse(resp), self, server_id)
|
866
|
+
|
867
|
+
if permission_builder.to_a.any?
|
868
|
+
raise ArgumentError, 'Permissions can only be set for guild commands' unless server_id
|
869
|
+
|
870
|
+
edit_application_command_permissions(cmd.id, server_id, permission_builder.to_a)
|
871
|
+
end
|
872
|
+
|
873
|
+
cmd
|
874
|
+
end
|
875
|
+
|
876
|
+
# Remove an application command from the commands registered with discord.
|
877
|
+
# @param command_id [String, Integer] The ID of the command to remove.
|
878
|
+
# @param server_id [String, Integer] The ID of the server to delete this command from, global if `nil`.
|
879
|
+
def delete_application_command(command_id, server_id: nil)
|
880
|
+
if server_id
|
881
|
+
API::Application.delete_guild_command(@token, profile.id, server_id, command_id)
|
882
|
+
else
|
883
|
+
API::Application.delete_global_command(@token, profile.id, command_id)
|
884
|
+
end
|
885
|
+
end
|
886
|
+
|
887
|
+
# @param command_id [Integer, String]
|
888
|
+
# @param server_id [Integer, String]
|
889
|
+
# @param permissions [Array<Hash>] An array of objects formatted as `{ id: ENTITY_ID, type: 1 or 2, permission: true or false }`
|
890
|
+
def edit_application_command_permissions(command_id, server_id, permissions = [])
|
891
|
+
builder = Interactions::PermissionBuilder.new
|
892
|
+
yield builder if block_given?
|
893
|
+
|
894
|
+
permissions += builder.to_a
|
895
|
+
API::Application.edit_guild_command_permissions(@token, profile.id, server_id, command_id, permissions)
|
896
|
+
end
|
897
|
+
|
714
898
|
private
|
715
899
|
|
716
900
|
# Throws a useful exception if there's currently no gateway connection.
|
@@ -762,10 +946,16 @@ module Discordrb
|
|
762
946
|
|
763
947
|
username = data['user']['username']
|
764
948
|
if username && !member_is_new # Don't set the username for newly-cached members
|
765
|
-
debug "Implicitly updating presence-obtained information for member #{user_id}"
|
949
|
+
debug "Implicitly updating presence-obtained information username for member #{user_id}"
|
766
950
|
member.update_username(username)
|
767
951
|
end
|
768
952
|
|
953
|
+
global_name = data['user']['global_name']
|
954
|
+
if global_name && !member_is_new # Don't set the global_name for newly-cached members
|
955
|
+
debug "Implicitly updating presence-obtained information global_name for member #{user_id}"
|
956
|
+
member.update_global_name(global_name)
|
957
|
+
end
|
958
|
+
|
769
959
|
member.update_presence(data)
|
770
960
|
|
771
961
|
member.avatar_id = data['user']['avatar'] if data['user']['avatar']
|
@@ -826,14 +1016,14 @@ module Discordrb
|
|
826
1016
|
|
827
1017
|
# Internal handler for CHANNEL_CREATE
|
828
1018
|
def create_channel(data)
|
829
|
-
channel = Channel.new(data, self)
|
1019
|
+
channel = data.is_a?(Discordrb::Channel) ? data : Channel.new(data, self)
|
830
1020
|
server = channel.server
|
831
1021
|
|
832
1022
|
# Handle normal and private channels separately
|
833
1023
|
if server
|
834
1024
|
server.add_channel(channel)
|
835
1025
|
@channels[channel.id] = channel
|
836
|
-
elsif channel.
|
1026
|
+
elsif channel.private?
|
837
1027
|
@pm_channels[channel.recipient.id] = channel
|
838
1028
|
elsif channel.group?
|
839
1029
|
@channels[channel.id] = channel
|
@@ -863,6 +1053,8 @@ module Discordrb
|
|
863
1053
|
elsif channel.group?
|
864
1054
|
@channels.delete(channel.id)
|
865
1055
|
end
|
1056
|
+
|
1057
|
+
@thread_members.delete(channel.id) if channel.thread?
|
866
1058
|
end
|
867
1059
|
|
868
1060
|
# Internal handler for CHANNEL_RECIPIENT_ADD
|
@@ -902,7 +1094,9 @@ module Discordrb
|
|
902
1094
|
member = server.member(data['user']['id'].to_i)
|
903
1095
|
member.update_roles(data['roles'])
|
904
1096
|
member.update_nick(data['nick'])
|
1097
|
+
member.update_global_name(data['user']['global_name']) if data['user']['global_name']
|
905
1098
|
member.update_boosting_since(data['premium_since'])
|
1099
|
+
member.update_communication_disabled_until(data['communication_disabled_until'])
|
906
1100
|
end
|
907
1101
|
|
908
1102
|
# Internal handler for GUILD_MEMBER_DELETE
|
@@ -919,7 +1113,7 @@ module Discordrb
|
|
919
1113
|
|
920
1114
|
# Internal handler for GUILD_CREATE
|
921
1115
|
def create_guild(data)
|
922
|
-
ensure_server(data)
|
1116
|
+
ensure_server(data, true)
|
923
1117
|
end
|
924
1118
|
|
925
1119
|
# Internal handler for GUILD_UPDATE
|
@@ -1010,7 +1204,7 @@ module Discordrb
|
|
1010
1204
|
|
1011
1205
|
def process_token(type, token)
|
1012
1206
|
# Remove the "Bot " prefix if it exists
|
1013
|
-
token = token[4
|
1207
|
+
token = token[4..] if token.start_with? 'Bot '
|
1014
1208
|
|
1015
1209
|
token = "Bot #{token}" unless type == :user
|
1016
1210
|
token
|
@@ -1047,14 +1241,14 @@ module Discordrb
|
|
1047
1241
|
data['guilds'].each do |element|
|
1048
1242
|
# Check for true specifically because unavailable=false indicates that a previously unavailable server has
|
1049
1243
|
# come online
|
1050
|
-
if element['unavailable']
|
1244
|
+
if element['unavailable']
|
1051
1245
|
@unavailable_servers += 1
|
1052
1246
|
|
1053
1247
|
# Ignore any unavailable servers
|
1054
1248
|
next
|
1055
1249
|
end
|
1056
1250
|
|
1057
|
-
ensure_server(element)
|
1251
|
+
ensure_server(element, true)
|
1058
1252
|
end
|
1059
1253
|
|
1060
1254
|
# Add PM and group channels
|
@@ -1079,14 +1273,14 @@ module Discordrb
|
|
1079
1273
|
when :GUILD_MEMBERS_CHUNK
|
1080
1274
|
id = data['guild_id'].to_i
|
1081
1275
|
server = server(id)
|
1082
|
-
server.process_chunk(data['members'])
|
1276
|
+
server.process_chunk(data['members'], data['chunk_index'], data['chunk_count'])
|
1083
1277
|
when :INVITE_CREATE
|
1084
1278
|
invite = Invite.new(data, self)
|
1085
1279
|
raise_event(InviteCreateEvent.new(data, invite, self))
|
1086
1280
|
when :INVITE_DELETE
|
1087
1281
|
raise_event(InviteDeleteEvent.new(data, self))
|
1088
1282
|
when :MESSAGE_CREATE
|
1089
|
-
if ignored?(data['author']['id']
|
1283
|
+
if ignored?(data['author']['id'])
|
1090
1284
|
debug("Ignored author with ID #{data['author']['id']}")
|
1091
1285
|
return
|
1092
1286
|
end
|
@@ -1103,6 +1297,13 @@ module Discordrb
|
|
1103
1297
|
|
1104
1298
|
return if message.from_bot? && !should_parse_self
|
1105
1299
|
|
1300
|
+
# Dispatch a ChannelCreateEvent for channels we don't have cached
|
1301
|
+
if message.channel.private? && @pm_channels[message.channel.recipient.id].nil?
|
1302
|
+
create_channel(message.channel)
|
1303
|
+
|
1304
|
+
raise_event(ChannelCreateEvent.new(message.channel, self))
|
1305
|
+
end
|
1306
|
+
|
1106
1307
|
event = MessageEvent.new(message, self)
|
1107
1308
|
raise_event(event)
|
1108
1309
|
|
@@ -1185,18 +1386,28 @@ module Discordrb
|
|
1185
1386
|
# Ignore friends list presences
|
1186
1387
|
return unless data['guild_id']
|
1187
1388
|
|
1188
|
-
|
1389
|
+
new_activities = (data['activities'] || []).map { |act_data| Activity.new(act_data, self) }
|
1189
1390
|
presence_user = @users[data['user']['id'].to_i]
|
1190
|
-
|
1391
|
+
old_activities = (presence_user&.activities || [])
|
1191
1392
|
update_presence(data)
|
1192
1393
|
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
|
1197
|
-
end
|
1394
|
+
# Starting a new game
|
1395
|
+
playing_change = new_activities.reject do |act|
|
1396
|
+
old_activities.find { |old| old.name == act.name }
|
1397
|
+
end
|
1198
1398
|
|
1199
|
-
|
1399
|
+
# Exiting an existing game
|
1400
|
+
playing_change += old_activities.reject do |old|
|
1401
|
+
new_activities.find { |act| act.name == old.name }
|
1402
|
+
end
|
1403
|
+
|
1404
|
+
if playing_change.any?
|
1405
|
+
playing_change.each do |act|
|
1406
|
+
raise_event(PlayingEvent.new(data, act, self))
|
1407
|
+
end
|
1408
|
+
else
|
1409
|
+
raise_event(PresenceEvent.new(data, self))
|
1410
|
+
end
|
1200
1411
|
when :VOICE_STATE_UPDATE
|
1201
1412
|
old_channel_id = update_voice_state(data)
|
1202
1413
|
|
@@ -1333,9 +1544,93 @@ module Discordrb
|
|
1333
1544
|
event = ServerEmojiUpdateEvent.new(server, old_emoji_data[e], new_emoji_data[e], self)
|
1334
1545
|
raise_event(event)
|
1335
1546
|
end
|
1547
|
+
when :INTERACTION_CREATE
|
1548
|
+
event = InteractionCreateEvent.new(data, self)
|
1549
|
+
raise_event(event)
|
1550
|
+
|
1551
|
+
case data['type']
|
1552
|
+
when Interaction::TYPES[:command]
|
1553
|
+
event = ApplicationCommandEvent.new(data, self)
|
1554
|
+
|
1555
|
+
Thread.new do
|
1556
|
+
Thread.current[:discordrb_name] = "it-#{event.interaction.id}"
|
1557
|
+
|
1558
|
+
begin
|
1559
|
+
debug("Executing application command #{event.command_name}:#{event.command_id}")
|
1560
|
+
|
1561
|
+
@application_commands[event.command_name]&.call(event)
|
1562
|
+
rescue StandardError => e
|
1563
|
+
log_exception(e)
|
1564
|
+
end
|
1565
|
+
end
|
1566
|
+
when Interaction::TYPES[:component]
|
1567
|
+
case data['data']['component_type']
|
1568
|
+
when Webhooks::View::COMPONENT_TYPES[:button]
|
1569
|
+
event = ButtonEvent.new(data, self)
|
1570
|
+
|
1571
|
+
raise_event(event)
|
1572
|
+
when Webhooks::View::COMPONENT_TYPES[:string_select]
|
1573
|
+
event = StringSelectEvent.new(data, self)
|
1574
|
+
|
1575
|
+
raise_event(event)
|
1576
|
+
when Webhooks::View::COMPONENT_TYPES[:user_select]
|
1577
|
+
event = UserSelectEvent.new(data, self)
|
1578
|
+
|
1579
|
+
raise_event(event)
|
1580
|
+
when Webhooks::View::COMPONENT_TYPES[:role_select]
|
1581
|
+
event = RoleSelectEvent.new(data, self)
|
1582
|
+
|
1583
|
+
raise_event(event)
|
1584
|
+
when Webhooks::View::COMPONENT_TYPES[:mentionable_select]
|
1585
|
+
event = MentionableSelectEvent.new(data, self)
|
1586
|
+
|
1587
|
+
raise_event(event)
|
1588
|
+
when Webhooks::View::COMPONENT_TYPES[:channel_select]
|
1589
|
+
event = ChannelSelectEvent.new(data, self)
|
1590
|
+
|
1591
|
+
raise_event(event)
|
1592
|
+
end
|
1593
|
+
when Interaction::TYPES[:modal_submit]
|
1594
|
+
|
1595
|
+
event = ModalSubmitEvent.new(data, self)
|
1596
|
+
raise_event(event)
|
1597
|
+
end
|
1336
1598
|
when :WEBHOOKS_UPDATE
|
1337
1599
|
event = WebhookUpdateEvent.new(data, self)
|
1338
1600
|
raise_event(event)
|
1601
|
+
when :THREAD_CREATE
|
1602
|
+
create_channel(data)
|
1603
|
+
|
1604
|
+
event = ThreadCreateEvent.new(data, self)
|
1605
|
+
raise_event(event)
|
1606
|
+
when :THREAD_UPDATE
|
1607
|
+
update_channel(data)
|
1608
|
+
|
1609
|
+
event = ThreadUpdateEvent.new(data, self)
|
1610
|
+
raise_event(event)
|
1611
|
+
when :THREAD_DELETE
|
1612
|
+
delete_channel(data)
|
1613
|
+
@thread_members.delete(data['id']&.resolve_id)
|
1614
|
+
|
1615
|
+
# raise ThreadDeleteEvent
|
1616
|
+
when :THREAD_LIST_SYNC
|
1617
|
+
data['members'].map { |member| ensure_thread_member(member) }
|
1618
|
+
data['threads'].map { |channel| ensure_channel(channel, data['guild_id']) }
|
1619
|
+
|
1620
|
+
# raise ThreadListSyncEvent?
|
1621
|
+
when :THREAD_MEMBER_UPDATE
|
1622
|
+
ensure_thread_member(data)
|
1623
|
+
when :THREAD_MEMBERS_UPDATE
|
1624
|
+
data['added_members']&.each do |added_member|
|
1625
|
+
ensure_thread_member(added_member) if added_member['user_id']
|
1626
|
+
end
|
1627
|
+
|
1628
|
+
data['removed_member_ids']&.each do |member_id|
|
1629
|
+
@thread_members[data['id']&.resolve_id]&.delete(member_id&.resolve_id)
|
1630
|
+
end
|
1631
|
+
|
1632
|
+
event = ThreadMembersUpdateEvent.new(data, self)
|
1633
|
+
raise_event(event)
|
1339
1634
|
else
|
1340
1635
|
# another event that we don't support yet
|
1341
1636
|
debug "Event #{type} has been received but is unsupported. Raising UnknownEvent"
|