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