discordrb 3.4.0 → 3.5.0
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 +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"
|