discordrb 3.4.3 → 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 +390 -225
- 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 +177 -11
- data/lib/discordrb/api/interaction.rb +54 -0
- data/lib/discordrb/api/invite.rb +2 -2
- data/lib/discordrb/api/server.rb +40 -19
- data/lib/discordrb/api/user.rb +8 -3
- data/lib/discordrb/api/webhook.rb +57 -0
- data/lib/discordrb/api.rb +19 -5
- data/lib/discordrb/bot.rb +317 -32
- data/lib/discordrb/cache.rb +27 -22
- data/lib/discordrb/commands/command_bot.rb +6 -4
- 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/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 +1 -1
- data/lib/discordrb/data/member.rb +108 -33
- data/lib/discordrb/data/message.rb +99 -19
- 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 +2 -2
- 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 +48 -14
- data/.codeclimate.yml +0 -16
- data/.travis.yml +0 -32
- data/bin/travis_build_docs.sh +0 -17
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,15 +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.
|
389
|
-
|
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)
|
390
421
|
Thread.new do
|
391
422
|
Thread.current[:discordrb_name] = "#{@current_thread}-temp-msg"
|
392
423
|
|
393
|
-
message = send_message(channel, content, tts,
|
424
|
+
message = send_message(channel, content, tts, embeds, attachments, allowed_mentions, message_reference, components)
|
394
425
|
sleep(timeout)
|
395
426
|
message.delete
|
396
427
|
end
|
@@ -416,6 +447,7 @@ module Discordrb
|
|
416
447
|
end
|
417
448
|
# https://github.com/rest-client/rest-client/blob/v2.0.2/lib/restclient/payload.rb#L160
|
418
449
|
file.define_singleton_method(:original_filename) { filename } if filename
|
450
|
+
file.define_singleton_method(:path) { filename } if filename
|
419
451
|
end
|
420
452
|
|
421
453
|
channel = channel.resolve_id
|
@@ -595,6 +627,36 @@ module Discordrb
|
|
595
627
|
update_status(:invisible, @activity, nil)
|
596
628
|
end
|
597
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
|
+
|
598
660
|
# Sets debug mode. If debug mode is on, many things will be outputted to STDOUT.
|
599
661
|
def debug=(new_debug)
|
600
662
|
LOGGER.debug = new_debug
|
@@ -721,6 +783,118 @@ module Discordrb
|
|
721
783
|
end
|
722
784
|
end
|
723
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
|
+
|
724
898
|
private
|
725
899
|
|
726
900
|
# Throws a useful exception if there's currently no gateway connection.
|
@@ -772,10 +946,16 @@ module Discordrb
|
|
772
946
|
|
773
947
|
username = data['user']['username']
|
774
948
|
if username && !member_is_new # Don't set the username for newly-cached members
|
775
|
-
debug "Implicitly updating presence-obtained information for member #{user_id}"
|
949
|
+
debug "Implicitly updating presence-obtained information username for member #{user_id}"
|
776
950
|
member.update_username(username)
|
777
951
|
end
|
778
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
|
+
|
779
959
|
member.update_presence(data)
|
780
960
|
|
781
961
|
member.avatar_id = data['user']['avatar'] if data['user']['avatar']
|
@@ -836,14 +1016,14 @@ module Discordrb
|
|
836
1016
|
|
837
1017
|
# Internal handler for CHANNEL_CREATE
|
838
1018
|
def create_channel(data)
|
839
|
-
channel = Channel.new(data, self)
|
1019
|
+
channel = data.is_a?(Discordrb::Channel) ? data : Channel.new(data, self)
|
840
1020
|
server = channel.server
|
841
1021
|
|
842
1022
|
# Handle normal and private channels separately
|
843
1023
|
if server
|
844
1024
|
server.add_channel(channel)
|
845
1025
|
@channels[channel.id] = channel
|
846
|
-
elsif channel.
|
1026
|
+
elsif channel.private?
|
847
1027
|
@pm_channels[channel.recipient.id] = channel
|
848
1028
|
elsif channel.group?
|
849
1029
|
@channels[channel.id] = channel
|
@@ -873,6 +1053,8 @@ module Discordrb
|
|
873
1053
|
elsif channel.group?
|
874
1054
|
@channels.delete(channel.id)
|
875
1055
|
end
|
1056
|
+
|
1057
|
+
@thread_members.delete(channel.id) if channel.thread?
|
876
1058
|
end
|
877
1059
|
|
878
1060
|
# Internal handler for CHANNEL_RECIPIENT_ADD
|
@@ -912,7 +1094,9 @@ module Discordrb
|
|
912
1094
|
member = server.member(data['user']['id'].to_i)
|
913
1095
|
member.update_roles(data['roles'])
|
914
1096
|
member.update_nick(data['nick'])
|
1097
|
+
member.update_global_name(data['user']['global_name']) if data['user']['global_name']
|
915
1098
|
member.update_boosting_since(data['premium_since'])
|
1099
|
+
member.update_communication_disabled_until(data['communication_disabled_until'])
|
916
1100
|
end
|
917
1101
|
|
918
1102
|
# Internal handler for GUILD_MEMBER_DELETE
|
@@ -929,7 +1113,7 @@ module Discordrb
|
|
929
1113
|
|
930
1114
|
# Internal handler for GUILD_CREATE
|
931
1115
|
def create_guild(data)
|
932
|
-
ensure_server(data)
|
1116
|
+
ensure_server(data, true)
|
933
1117
|
end
|
934
1118
|
|
935
1119
|
# Internal handler for GUILD_UPDATE
|
@@ -1020,7 +1204,7 @@ module Discordrb
|
|
1020
1204
|
|
1021
1205
|
def process_token(type, token)
|
1022
1206
|
# Remove the "Bot " prefix if it exists
|
1023
|
-
token = token[4
|
1207
|
+
token = token[4..] if token.start_with? 'Bot '
|
1024
1208
|
|
1025
1209
|
token = "Bot #{token}" unless type == :user
|
1026
1210
|
token
|
@@ -1057,14 +1241,14 @@ module Discordrb
|
|
1057
1241
|
data['guilds'].each do |element|
|
1058
1242
|
# Check for true specifically because unavailable=false indicates that a previously unavailable server has
|
1059
1243
|
# come online
|
1060
|
-
if element['unavailable']
|
1244
|
+
if element['unavailable']
|
1061
1245
|
@unavailable_servers += 1
|
1062
1246
|
|
1063
1247
|
# Ignore any unavailable servers
|
1064
1248
|
next
|
1065
1249
|
end
|
1066
1250
|
|
1067
|
-
ensure_server(element)
|
1251
|
+
ensure_server(element, true)
|
1068
1252
|
end
|
1069
1253
|
|
1070
1254
|
# Add PM and group channels
|
@@ -1089,14 +1273,14 @@ module Discordrb
|
|
1089
1273
|
when :GUILD_MEMBERS_CHUNK
|
1090
1274
|
id = data['guild_id'].to_i
|
1091
1275
|
server = server(id)
|
1092
|
-
server.process_chunk(data['members'])
|
1276
|
+
server.process_chunk(data['members'], data['chunk_index'], data['chunk_count'])
|
1093
1277
|
when :INVITE_CREATE
|
1094
1278
|
invite = Invite.new(data, self)
|
1095
1279
|
raise_event(InviteCreateEvent.new(data, invite, self))
|
1096
1280
|
when :INVITE_DELETE
|
1097
1281
|
raise_event(InviteDeleteEvent.new(data, self))
|
1098
1282
|
when :MESSAGE_CREATE
|
1099
|
-
if ignored?(data['author']['id']
|
1283
|
+
if ignored?(data['author']['id'])
|
1100
1284
|
debug("Ignored author with ID #{data['author']['id']}")
|
1101
1285
|
return
|
1102
1286
|
end
|
@@ -1113,6 +1297,13 @@ module Discordrb
|
|
1113
1297
|
|
1114
1298
|
return if message.from_bot? && !should_parse_self
|
1115
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
|
+
|
1116
1307
|
event = MessageEvent.new(message, self)
|
1117
1308
|
raise_event(event)
|
1118
1309
|
|
@@ -1195,18 +1386,28 @@ module Discordrb
|
|
1195
1386
|
# Ignore friends list presences
|
1196
1387
|
return unless data['guild_id']
|
1197
1388
|
|
1198
|
-
|
1389
|
+
new_activities = (data['activities'] || []).map { |act_data| Activity.new(act_data, self) }
|
1199
1390
|
presence_user = @users[data['user']['id'].to_i]
|
1200
|
-
|
1391
|
+
old_activities = (presence_user&.activities || [])
|
1201
1392
|
update_presence(data)
|
1202
1393
|
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
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
|
1208
1398
|
|
1209
|
-
|
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
|
1210
1411
|
when :VOICE_STATE_UPDATE
|
1211
1412
|
old_channel_id = update_voice_state(data)
|
1212
1413
|
|
@@ -1343,9 +1544,93 @@ module Discordrb
|
|
1343
1544
|
event = ServerEmojiUpdateEvent.new(server, old_emoji_data[e], new_emoji_data[e], self)
|
1344
1545
|
raise_event(event)
|
1345
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
|
1346
1598
|
when :WEBHOOKS_UPDATE
|
1347
1599
|
event = WebhookUpdateEvent.new(data, self)
|
1348
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)
|
1349
1634
|
else
|
1350
1635
|
# another event that we don't support yet
|
1351
1636
|
debug "Event #{type} has been received but is unsupported. Raising UnknownEvent"
|
data/lib/discordrb/cache.rb
CHANGED
@@ -21,8 +21,7 @@ module Discordrb
|
|
21
21
|
|
22
22
|
@channels = {}
|
23
23
|
@pm_channels = {}
|
24
|
-
|
25
|
-
@restricted_channels = []
|
24
|
+
@thread_members = {}
|
26
25
|
end
|
27
26
|
|
28
27
|
# Returns or caches the available voice regions
|
@@ -42,28 +41,21 @@ module Discordrb
|
|
42
41
|
# @param id [Integer] The channel ID for which to search for.
|
43
42
|
# @param server [Server] The server for which to search the channel for. If this isn't specified, it will be
|
44
43
|
# inferred using the API
|
45
|
-
# @return [Channel] The channel identified by the ID.
|
44
|
+
# @return [Channel, nil] The channel identified by the ID.
|
45
|
+
# @raise Discordrb::Errors::NoPermission
|
46
46
|
def channel(id, server = nil)
|
47
47
|
id = id.resolve_id
|
48
48
|
|
49
|
-
raise Discordrb::Errors::NoPermission if @restricted_channels.include? id
|
50
|
-
|
51
49
|
debug("Obtaining data for channel with id #{id}")
|
52
50
|
return @channels[id] if @channels[id]
|
53
51
|
|
54
52
|
begin
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
return nil
|
59
|
-
end
|
60
|
-
channel = Channel.new(JSON.parse(response), self, server)
|
61
|
-
@channels[id] = channel
|
62
|
-
rescue Discordrb::Errors::NoPermission
|
63
|
-
debug "Tried to get access to restricted channel #{id}, blacklisting it"
|
64
|
-
@restricted_channels << id
|
65
|
-
raise
|
53
|
+
response = API::Channel.resolve(token, id)
|
54
|
+
rescue Discordrb::Errors::UnknownChannel
|
55
|
+
return nil
|
66
56
|
end
|
57
|
+
channel = Channel.new(JSON.parse(response), self, server)
|
58
|
+
@channels[id] = channel
|
67
59
|
end
|
68
60
|
|
69
61
|
alias_method :group_channel, :channel
|
@@ -79,7 +71,7 @@ module Discordrb
|
|
79
71
|
LOGGER.out("Resolving user #{id}")
|
80
72
|
begin
|
81
73
|
response = API::User.resolve(token, id)
|
82
|
-
rescue
|
74
|
+
rescue Discordrb::Errors::UnknownUser
|
83
75
|
return nil
|
84
76
|
end
|
85
77
|
user = User.new(JSON.parse(response), self)
|
@@ -111,7 +103,6 @@ module Discordrb
|
|
111
103
|
def member(server_or_id, user_id)
|
112
104
|
server_id = server_or_id.resolve_id
|
113
105
|
user_id = user_id.resolve_id
|
114
|
-
|
115
106
|
server = server_or_id.is_a?(Server) ? server_or_id : self.server(server_id)
|
116
107
|
|
117
108
|
return server.member(user_id) if server.member_cached?(user_id)
|
@@ -119,7 +110,7 @@ module Discordrb
|
|
119
110
|
LOGGER.out("Resolving member #{server_id} on server #{user_id}")
|
120
111
|
begin
|
121
112
|
response = API::Server.resolve_member(token, server_id, user_id)
|
122
|
-
rescue
|
113
|
+
rescue Discordrb::Errors::UnknownUser, Discordrb::Errors::UnknownMember
|
123
114
|
return nil
|
124
115
|
end
|
125
116
|
member = Member.new(JSON.parse(response), server, self)
|
@@ -156,10 +147,14 @@ module Discordrb
|
|
156
147
|
|
157
148
|
# Ensures a given server object is cached and if not, cache it from the given data hash.
|
158
149
|
# @param data [Hash] A data hash representing a server.
|
150
|
+
# @param force_cache [true, false] Whether the object in cache should be updated with the given
|
151
|
+
# data if it already exists.
|
159
152
|
# @return [Server] the server represented by the data hash.
|
160
|
-
def ensure_server(data)
|
153
|
+
def ensure_server(data, force_cache = false)
|
161
154
|
if @servers.include?(data['id'].to_i)
|
162
|
-
@servers[data['id'].to_i]
|
155
|
+
server = @servers[data['id'].to_i]
|
156
|
+
server.update_data(data) if force_cache
|
157
|
+
server
|
163
158
|
else
|
164
159
|
@servers[data['id'].to_i] = Server.new(data, self)
|
165
160
|
end
|
@@ -177,6 +172,16 @@ module Discordrb
|
|
177
172
|
end
|
178
173
|
end
|
179
174
|
|
175
|
+
# Ensures a given thread member object is cached.
|
176
|
+
# @param data [Hash] Thread member data.
|
177
|
+
def ensure_thread_member(data)
|
178
|
+
thread_id = data['id'].to_i
|
179
|
+
user_id = data['user_id'].to_i
|
180
|
+
|
181
|
+
@thread_members[thread_id] ||= {}
|
182
|
+
@thread_members[thread_id][user_id] = data.slice('join_timestamp', 'flags')
|
183
|
+
end
|
184
|
+
|
180
185
|
# Requests member chunks for a given server ID.
|
181
186
|
# @param id [Integer] The server ID to request chunks for.
|
182
187
|
def request_chunks(id)
|
@@ -194,7 +199,7 @@ module Discordrb
|
|
194
199
|
# @return [String] Only the code for the invite.
|
195
200
|
def resolve_invite_code(invite)
|
196
201
|
invite = invite.code if invite.is_a? Discordrb::Invite
|
197
|
-
invite = invite[invite.rindex('/') + 1
|
202
|
+
invite = invite[invite.rindex('/') + 1..] if invite.start_with?('http', 'discord.gg')
|
198
203
|
invite
|
199
204
|
end
|
200
205
|
|