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
@@ -66,28 +66,15 @@ module Discordrb
|
|
66
66
|
@bot = bot
|
67
67
|
@owner_id = data['owner_id'].to_i
|
68
68
|
@id = data['id'].to_i
|
69
|
-
|
70
|
-
process_channels(data['channels'])
|
71
|
-
update_data(data)
|
72
|
-
|
73
|
-
@large = data['large']
|
74
|
-
@member_count = data['member_count']
|
75
|
-
@splash_id = nil
|
76
|
-
@banner_id = nil
|
77
|
-
@features = data['features'].map { |element| element.downcase.to_sym }
|
78
69
|
@members = {}
|
79
70
|
@voice_states = {}
|
80
71
|
@emoji = {}
|
81
72
|
|
82
|
-
|
83
|
-
|
84
|
-
process_members(data['members'])
|
85
|
-
process_presences(data['presences'])
|
86
|
-
process_voice_states(data['voice_states'])
|
73
|
+
process_channels(data['channels'])
|
74
|
+
update_data(data)
|
87
75
|
|
88
76
|
# Whether this server's members have been chunked (resolved using op 8 and GUILD_MEMBERS_CHUNK) yet
|
89
77
|
@chunked = false
|
90
|
-
@processed_chunk_members = 0
|
91
78
|
|
92
79
|
@booster_count = data['premium_subscription_count'] || 0
|
93
80
|
@boost_level = data['premium_tier']
|
@@ -143,10 +130,15 @@ module Discordrb
|
|
143
130
|
end
|
144
131
|
|
145
132
|
# @return [Array<Member>] an array of all the members on this server.
|
133
|
+
# @raise [RuntimeError] if the bot was not started with the :server_member intent
|
146
134
|
def members
|
147
135
|
return @members.values if @chunked
|
148
136
|
|
149
137
|
@bot.debug("Members for server #{@id} not chunked yet - initiating")
|
138
|
+
|
139
|
+
# If the SERVER_MEMBERS intent flag isn't set, the gateway won't respond when we ask for members.
|
140
|
+
raise 'The :server_members intent is required to get server members' if (@bot.gateway.intents & INTENTS[:server_members]).zero?
|
141
|
+
|
150
142
|
@bot.request_chunks(@id)
|
151
143
|
sleep 0.05 until @chunked
|
152
144
|
@members.values
|
@@ -189,78 +181,73 @@ module Discordrb
|
|
189
181
|
AuditLogs.new(self, @bot, JSON.parse(API::Server.audit_logs(@bot.token, @id, limit, user, action, before)))
|
190
182
|
end
|
191
183
|
|
192
|
-
# Cache @
|
184
|
+
# Cache @widget
|
193
185
|
# @note For internal use only
|
194
186
|
# @!visibility private
|
195
|
-
def
|
196
|
-
data = JSON.parse(API::Server.
|
197
|
-
@
|
198
|
-
@
|
187
|
+
def cache_widget_data
|
188
|
+
data = JSON.parse(API::Server.widget(@bot.token, @id))
|
189
|
+
@widget_enabled = data['enabled']
|
190
|
+
@widget_channel_id = data['channel_id']
|
199
191
|
end
|
200
192
|
|
201
193
|
# @return [true, false] whether or not the server has widget enabled
|
202
|
-
def
|
203
|
-
|
204
|
-
@
|
194
|
+
def widget_enabled?
|
195
|
+
cache_widget_data if @widget_enabled.nil?
|
196
|
+
@widget_enabled
|
205
197
|
end
|
206
|
-
alias_method :
|
207
|
-
alias_method :
|
208
|
-
alias_method :embed?, :
|
198
|
+
alias_method :widget?, :widget_enabled?
|
199
|
+
alias_method :embed_enabled, :widget_enabled?
|
200
|
+
alias_method :embed?, :widget_enabled?
|
209
201
|
|
210
|
-
# @return [Channel, nil] the channel the server
|
211
|
-
def
|
212
|
-
|
213
|
-
@bot.channel(@
|
202
|
+
# @return [Channel, nil] the channel the server widget will make an invite for.
|
203
|
+
def widget_channel
|
204
|
+
cache_widget_data if @widget_enabled.nil?
|
205
|
+
@bot.channel(@widget_channel_id) if @widget_channel_id
|
214
206
|
end
|
215
|
-
alias_method :
|
207
|
+
alias_method :embed_channel, :widget_channel
|
216
208
|
|
217
|
-
# Sets whether this server's
|
209
|
+
# Sets whether this server's widget is enabled
|
218
210
|
# @param value [true, false]
|
219
|
-
def
|
220
|
-
|
211
|
+
def widget_enabled=(value)
|
212
|
+
modify_widget(value, widget_channel)
|
221
213
|
end
|
214
|
+
alias_method :embed_enabled=, :widget_enabled=
|
222
215
|
|
223
|
-
|
224
|
-
|
225
|
-
# Sets whether this server's embed (widget) is enabled
|
216
|
+
# Sets whether this server's widget is enabled
|
226
217
|
# @param value [true, false]
|
227
218
|
# @param reason [String, nil] the reason to be shown in the audit log for this action
|
228
|
-
def
|
229
|
-
|
219
|
+
def set_widget_enabled(value, reason = nil)
|
220
|
+
modify_widget(value, widget_channel, reason)
|
230
221
|
end
|
222
|
+
alias_method :set_embed_enabled, :set_widget_enabled
|
231
223
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
def embed_channel=(channel)
|
237
|
-
modify_embed(embed?, channel)
|
224
|
+
# Changes the channel on the server's widget
|
225
|
+
# @param channel [Channel, String, Integer] the channel, or its ID, to be referenced by the widget
|
226
|
+
def widget_channel=(channel)
|
227
|
+
modify_widget(widget?, channel)
|
238
228
|
end
|
229
|
+
alias_method :embed_channel=, :widget_channel=
|
239
230
|
|
240
|
-
|
241
|
-
|
242
|
-
# Changes the channel on the server's embed (widget)
|
243
|
-
# @param channel [Channel, String, Integer] the channel, or its ID, to be referenced by the embed
|
231
|
+
# Changes the channel on the server's widget
|
232
|
+
# @param channel [Channel, String, Integer] the channel, or its ID, to be referenced by the widget
|
244
233
|
# @param reason [String, nil] the reason to be shown in the audit log for this action
|
245
|
-
def
|
246
|
-
|
234
|
+
def set_widget_channel(channel, reason = nil)
|
235
|
+
modify_widget(widget?, channel, reason)
|
247
236
|
end
|
237
|
+
alias_method :set_embed_channel, :set_widget_channel
|
248
238
|
|
249
|
-
|
250
|
-
|
251
|
-
#
|
252
|
-
# @param enabled [true, false] whether the embed (widget) is enabled
|
253
|
-
# @param channel [Channel, String, Integer] the channel, or its ID, to be referenced by the embed
|
239
|
+
# Changes the channel on the server's widget, and sets whether it is enabled.
|
240
|
+
# @param enabled [true, false] whether the widget is enabled
|
241
|
+
# @param channel [Channel, String, Integer] the channel, or its ID, to be referenced by the widget
|
254
242
|
# @param reason [String, nil] the reason to be shown in the audit log for this action
|
255
|
-
def
|
256
|
-
|
257
|
-
channel_id = channel ? channel.resolve_id : @
|
258
|
-
response = JSON.parse(API::Server.
|
259
|
-
@
|
260
|
-
@
|
243
|
+
def modify_widget(enabled, channel, reason = nil)
|
244
|
+
cache_widget_data if @widget_enabled.nil?
|
245
|
+
channel_id = channel ? channel.resolve_id : @widget_channel_id
|
246
|
+
response = JSON.parse(API::Server.modify_widget(@bot.token, @id, enabled, channel_id, reason))
|
247
|
+
@widget_enabled = response['enabled']
|
248
|
+
@widget_channel_id = response['channel_id']
|
261
249
|
end
|
262
|
-
|
263
|
-
alias_method :modify_widget, :modify_embed
|
250
|
+
alias_method :modify_embed, :modify_widget
|
264
251
|
|
265
252
|
# @param include_idle [true, false] Whether to count idle members as online.
|
266
253
|
# @param include_bots [true, false] Whether to include bot accounts in the count.
|
@@ -444,7 +431,7 @@ module Discordrb
|
|
444
431
|
# @!visibility private
|
445
432
|
def delete_member(user_id)
|
446
433
|
@members.delete(user_id)
|
447
|
-
@member_count -= 1
|
434
|
+
@member_count -= 1 unless @member_count <= 0
|
448
435
|
end
|
449
436
|
|
450
437
|
# Checks whether a member is cached
|
@@ -586,7 +573,7 @@ module Discordrb
|
|
586
573
|
# The amount of emoji the server can have, based on its current Nitro Boost Level.
|
587
574
|
# @return [Integer] the max amount of emoji
|
588
575
|
def max_emoji
|
589
|
-
case @
|
576
|
+
case @boost_level
|
590
577
|
when 1
|
591
578
|
100
|
592
579
|
when 2
|
@@ -598,9 +585,13 @@ module Discordrb
|
|
598
585
|
end
|
599
586
|
end
|
600
587
|
|
588
|
+
# Retrieve banned users from this server.
|
589
|
+
# @param limit [Integer] Number of users to return (up to maximum 1000, default 1000).
|
590
|
+
# @param before_id [Integer] Consider only users before given user id.
|
591
|
+
# @param after_id [Integer] Consider only users after given user id.
|
601
592
|
# @return [Array<ServerBan>] a list of banned users on this server and the reason they were banned.
|
602
|
-
def bans
|
603
|
-
response = JSON.parse(API::Server.bans(@bot.token, @id))
|
593
|
+
def bans(limit: nil, before_id: nil, after_id: nil)
|
594
|
+
response = JSON.parse(API::Server.bans(@bot.token, @id, limit, before_id, after_id))
|
604
595
|
response.map do |e|
|
605
596
|
ServerBan.new(self, User.new(e['user'], @bot), e['reason'])
|
606
597
|
end
|
@@ -628,11 +619,12 @@ module Discordrb
|
|
628
619
|
API::Server.remove_member(@bot.token, @id, user.resolve_id, reason)
|
629
620
|
end
|
630
621
|
|
631
|
-
# Forcibly moves a user into a different voice channel.
|
622
|
+
# Forcibly moves a user into a different voice channel.
|
623
|
+
# Only works if the bot has the permission needed and if the user is already connected to some voice channel on this server.
|
632
624
|
# @param user [User, String, Integer] The user to move.
|
633
|
-
# @param channel [Channel, String, Integer] The voice channel to move into.
|
625
|
+
# @param channel [Channel, String, Integer, nil] The voice channel to move into. (If nil, the user is disconnected from the voice channel)
|
634
626
|
def move(user, channel)
|
635
|
-
API::Server.update_member(@bot.token, @id, user.resolve_id, channel_id: channel
|
627
|
+
API::Server.update_member(@bot.token, @id, user.resolve_id, channel_id: channel&.resolve_id)
|
636
628
|
end
|
637
629
|
|
638
630
|
# Deletes this server. Be aware that this is permanent and impossible to undo, so be careful!
|
@@ -805,19 +797,16 @@ module Discordrb
|
|
805
797
|
# Processes a GUILD_MEMBERS_CHUNK packet, specifically the members field
|
806
798
|
# @note For internal use only
|
807
799
|
# @!visibility private
|
808
|
-
def process_chunk(members)
|
800
|
+
def process_chunk(members, chunk_index, chunk_count)
|
809
801
|
process_members(members)
|
810
|
-
@
|
811
|
-
LOGGER.debug("Processed one chunk on server #{@id} - length #{members.length}")
|
802
|
+
LOGGER.debug("Processed chunk #{chunk_index + 1}/#{chunk_count} server #{@id} - index #{chunk_index} - length #{members.length}")
|
812
803
|
|
813
|
-
|
814
|
-
return unless @processed_chunk_members == @member_count
|
804
|
+
return if chunk_index + 1 < chunk_count
|
815
805
|
|
816
806
|
LOGGER.debug("Finished chunking server #{@id}")
|
817
807
|
|
818
808
|
# Reset everything to normal
|
819
809
|
@chunked = true
|
820
|
-
@processed_chunk_members = 0
|
821
810
|
end
|
822
811
|
|
823
812
|
# @return [Channel, nil] the AFK voice channel of this server, or `nil` if none is set.
|
@@ -842,17 +831,30 @@ module Discordrb
|
|
842
831
|
|
843
832
|
afk_channel_id = new_data[:afk_channel_id] || new_data['afk_channel_id'] || @afk_channel
|
844
833
|
@afk_channel_id = afk_channel_id.nil? ? nil : afk_channel_id.resolve_id
|
845
|
-
|
846
|
-
@
|
834
|
+
widget_channel_id = new_data[:widget_channel_id] || new_data['widget_channel_id'] || @widget_channel
|
835
|
+
@widget_channel_id = widget_channel_id.nil? ? nil : widget_channel_id.resolve_id
|
847
836
|
system_channel_id = new_data[:system_channel_id] || new_data['system_channel_id'] || @system_channel
|
848
837
|
@system_channel_id = system_channel_id.nil? ? nil : system_channel_id.resolve_id
|
849
838
|
|
850
|
-
@
|
839
|
+
@widget_enabled = new_data[:widget_enabled] || new_data['widget_enabled']
|
851
840
|
@splash = new_data[:splash_id] || new_data['splash_id'] || @splash_id
|
852
841
|
|
853
842
|
@verification_level = new_data[:verification_level] || new_data['verification_level'] || @verification_level
|
854
843
|
@explicit_content_filter = new_data[:explicit_content_filter] || new_data['explicit_content_filter'] || @explicit_content_filter
|
855
844
|
@default_message_notifications = new_data[:default_message_notifications] || new_data['default_message_notifications'] || @default_message_notifications
|
845
|
+
|
846
|
+
@large = new_data.key?('large') ? new_data['large'] : @large
|
847
|
+
@member_count = new_data['member_count'] || @member_count || 0
|
848
|
+
@splash_id = new_data['splash'] || @splash_id
|
849
|
+
@banner_id = new_data['banner'] || @banner_id
|
850
|
+
@features = new_data['features'] ? new_data['features'].map { |element| element.downcase.to_sym } : @features || []
|
851
|
+
|
852
|
+
process_channels(new_data['channels']) if new_data['channels']
|
853
|
+
process_roles(new_data['roles']) if new_data['roles']
|
854
|
+
process_emoji(new_data['emojis']) if new_data['emojis']
|
855
|
+
process_members(new_data['members']) if new_data['members']
|
856
|
+
process_presences(new_data['presences']) if new_data['presences']
|
857
|
+
process_voice_states(new_data['voice_states']) if new_data['voice_states']
|
856
858
|
end
|
857
859
|
|
858
860
|
# Adds a channel to this server's cache
|
data/lib/discordrb/data/user.rb
CHANGED
@@ -3,10 +3,33 @@
|
|
3
3
|
module Discordrb
|
4
4
|
# Mixin for the attributes users should have
|
5
5
|
module UserAttributes
|
6
|
+
# rubocop:disable Naming/VariableNumber
|
7
|
+
FLAGS = {
|
8
|
+
staff: 1 << 0,
|
9
|
+
partner: 1 << 1,
|
10
|
+
hypesquad: 1 << 2,
|
11
|
+
bug_hunter_level_1: 1 << 3,
|
12
|
+
hypesquad_online_house_1: 1 << 6,
|
13
|
+
hypesquad_online_house_2: 1 << 7,
|
14
|
+
hypesquad_online_house_3: 1 << 8,
|
15
|
+
premium_early_supporter: 1 << 9,
|
16
|
+
team_pseudo_user: 1 << 10,
|
17
|
+
bug_hunter_level_2: 1 << 14,
|
18
|
+
verified_bot: 1 << 16,
|
19
|
+
verified_developer: 1 << 17,
|
20
|
+
certified_moderator: 1 << 18,
|
21
|
+
bot_http_interactions: 1 << 19,
|
22
|
+
active_developer: 1 << 22
|
23
|
+
}.freeze
|
24
|
+
# rubocop:enable Naming/VariableNumber
|
25
|
+
|
6
26
|
# @return [String] this user's username
|
7
27
|
attr_reader :username
|
8
28
|
alias_method :name, :username
|
9
29
|
|
30
|
+
# @return [String, nil] this user's global name
|
31
|
+
attr_reader :global_name
|
32
|
+
|
10
33
|
# @return [String] this user's discriminator which is used internally to identify users with identical usernames.
|
11
34
|
attr_reader :discriminator
|
12
35
|
alias_method :discrim, :discriminator
|
@@ -17,10 +40,21 @@ module Discordrb
|
|
17
40
|
attr_reader :bot_account
|
18
41
|
alias_method :bot_account?, :bot_account
|
19
42
|
|
43
|
+
# @return [true, false] whether this is fake user for a webhook message
|
44
|
+
attr_reader :webhook_account
|
45
|
+
alias_method :webhook_account?, :webhook_account
|
46
|
+
alias_method :webhook?, :webhook_account
|
47
|
+
|
20
48
|
# @return [String] the ID of this user's current avatar, can be used to generate an avatar URL.
|
21
49
|
# @see #avatar_url
|
22
50
|
attr_accessor :avatar_id
|
23
51
|
|
52
|
+
# Utility function to get Discord's display name of a user not in server
|
53
|
+
# @return [String] the name the user displays as (global_name if they have one, username otherwise)
|
54
|
+
def display_name
|
55
|
+
global_name || username
|
56
|
+
end
|
57
|
+
|
24
58
|
# Utility function to mention users in messages
|
25
59
|
# @return [String] the mention code in the form of <@id>
|
26
60
|
def mention
|
@@ -29,18 +63,37 @@ module Discordrb
|
|
29
63
|
|
30
64
|
# Utility function to get Discord's distinct representation of a user, i.e. username + discriminator
|
31
65
|
# @return [String] distinct representation of user
|
66
|
+
# TODO: Maybe change this method again after discriminator removal ?
|
32
67
|
def distinct
|
33
|
-
|
68
|
+
if @discriminator && @discriminator != '0'
|
69
|
+
"#{@username}##{@discriminator}"
|
70
|
+
else
|
71
|
+
@username.to_s
|
72
|
+
end
|
34
73
|
end
|
35
74
|
|
36
75
|
# Utility function to get a user's avatar URL.
|
37
76
|
# @param format [String, nil] If `nil`, the URL will default to `webp` for static avatars, and will detect if the user has a `gif` avatar. You can otherwise specify one of `webp`, `jpg`, `png`, or `gif` to override this. Will always be PNG for default avatars.
|
38
77
|
# @return [String] the URL to the avatar image.
|
78
|
+
# TODO: Maybe change this method again after discriminator removal ?
|
39
79
|
def avatar_url(format = nil)
|
40
|
-
|
80
|
+
unless @avatar_id
|
81
|
+
return API::User.default_avatar(@discriminator, legacy: true) if @discriminator && @discriminator != '0'
|
82
|
+
|
83
|
+
return API::User.default_avatar(@id)
|
84
|
+
end
|
41
85
|
|
42
86
|
API::User.avatar_url(@id, @avatar_id, format)
|
43
87
|
end
|
88
|
+
|
89
|
+
# @return [Integer] the public flags on a user's account
|
90
|
+
attr_reader :public_flags
|
91
|
+
|
92
|
+
FLAGS.each do |name, value|
|
93
|
+
define_method("#{name}?") do
|
94
|
+
(@public_flags & value).positive?
|
95
|
+
end
|
96
|
+
end
|
44
97
|
end
|
45
98
|
|
46
99
|
# User on Discord, including internal data like discriminators
|
@@ -63,15 +116,20 @@ module Discordrb
|
|
63
116
|
@bot = bot
|
64
117
|
|
65
118
|
@username = data['username']
|
119
|
+
@global_name = data['global_name']
|
66
120
|
@id = data['id'].to_i
|
67
121
|
@discriminator = data['discriminator']
|
68
122
|
@avatar_id = data['avatar']
|
69
123
|
@roles = {}
|
70
124
|
@activities = Discordrb::ActivitySet.new
|
125
|
+
@public_flags = data['public_flags'] || 0
|
71
126
|
|
72
127
|
@bot_account = false
|
73
128
|
@bot_account = true if data['bot']
|
74
129
|
|
130
|
+
@webhook_account = false
|
131
|
+
@webhook_account = true if data['_webhook']
|
132
|
+
|
75
133
|
@status = :offline
|
76
134
|
@client_status = process_client_status(data['client_status'])
|
77
135
|
end
|
@@ -109,13 +167,20 @@ module Discordrb
|
|
109
167
|
pm.send_file(file, caption: caption, filename: filename, spoiler: spoiler)
|
110
168
|
end
|
111
169
|
|
112
|
-
# Set the user's
|
170
|
+
# Set the user's username
|
113
171
|
# @note for internal use only
|
114
172
|
# @!visibility private
|
115
173
|
def update_username(username)
|
116
174
|
@username = username
|
117
175
|
end
|
118
176
|
|
177
|
+
# Set the user's global_name
|
178
|
+
# @note For internal use only.
|
179
|
+
# @!visibility private
|
180
|
+
def update_global_name(global_name)
|
181
|
+
@global_name = global_name
|
182
|
+
end
|
183
|
+
|
119
184
|
# Set the user's presence data
|
120
185
|
# @note for internal use only
|
121
186
|
# @!visibility private
|
@@ -154,14 +219,9 @@ module Discordrb
|
|
154
219
|
@bot.profile.id == @id
|
155
220
|
end
|
156
221
|
|
157
|
-
# @return [true, false] whether this user is a fake user for a webhook message
|
158
|
-
def webhook?
|
159
|
-
@discriminator == Message::ZERO_DISCRIM
|
160
|
-
end
|
161
|
-
|
162
222
|
# @!visibility private
|
163
223
|
def process_client_status(client_status)
|
164
|
-
(client_status || {}).
|
224
|
+
(client_status || {}).to_h { |k, v| [k.to_sym, v.to_sym] }
|
165
225
|
end
|
166
226
|
|
167
227
|
# @!method offline?
|
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'discordrb/webhooks/builder'
|
4
|
+
require 'discordrb/webhooks/view'
|
5
|
+
|
3
6
|
module Discordrb
|
4
7
|
# A webhook on a server channel
|
5
8
|
class Webhook
|
@@ -81,13 +84,13 @@ module Discordrb
|
|
81
84
|
def update(data)
|
82
85
|
# Only pass a value for avatar if the key is defined as sending nil will delete the
|
83
86
|
data[:avatar] = avatarise(data[:avatar]) if data.key?(:avatar)
|
84
|
-
data[:channel_id] = data[:channel]
|
87
|
+
data[:channel_id] = data[:channel]&.resolve_id
|
85
88
|
data.delete(:channel)
|
86
|
-
update_webhook(data)
|
89
|
+
update_webhook(**data)
|
87
90
|
end
|
88
91
|
|
89
92
|
# Deletes the webhook.
|
90
|
-
# @param reason [String] The reason the
|
93
|
+
# @param reason [String] The reason the webhook is being deleted.
|
91
94
|
def delete(reason = nil)
|
92
95
|
if token?
|
93
96
|
API::Webhook.token_delete_webhook(@token, @id, reason)
|
@@ -96,10 +99,100 @@ module Discordrb
|
|
96
99
|
end
|
97
100
|
end
|
98
101
|
|
102
|
+
# Execute a webhook.
|
103
|
+
# @param content [String] The content of the message. May be 2000 characters long at most.
|
104
|
+
# @param username [String] The username the webhook will display as. If this is not set, the default username set in the webhook's settings.
|
105
|
+
# @param avatar_url [String] The URL of an image file to be used as an avatar. If this is not set, the default avatar from the webhook's
|
106
|
+
# @param tts [true, false] Whether this message should use TTS or not. By default, it doesn't.
|
107
|
+
# @param file [File] File to be sent together with the message. Mutually exclusive with embeds; a webhook message can contain
|
108
|
+
# either a file to be sent or embeds.
|
109
|
+
# @param embeds [Array<Webhooks::Embed, Hash>] Embeds to attach to this message.
|
110
|
+
# @param allowed_mentions [AllowedMentions, Hash] Mentions that are allowed to ping in the `content`.
|
111
|
+
# @param wait [true, false] Whether Discord should wait for the message to be successfully received by clients, or
|
112
|
+
# whether it should return immediately after sending the message. If `true` a {Message} object will be returned.
|
113
|
+
# @yield [builder] Gives the builder to the block to add additional steps, or to do the entire building process.
|
114
|
+
# @yieldparam builder [Builder] The builder given as a parameter which is used as the initial step to start from.
|
115
|
+
# @example Execute the webhook with kwargs
|
116
|
+
# client.execute(
|
117
|
+
# content: 'Testing',
|
118
|
+
# username: 'discordrb',
|
119
|
+
# embeds: [
|
120
|
+
# { timestamp: Time.now.iso8601, title: 'testing', image: { url: 'https://i.imgur.com/PcMltU7.jpg' } }
|
121
|
+
# ])
|
122
|
+
# @example Execute the webhook with an already existing builder
|
123
|
+
# builder = Discordrb::Webhooks::Builder.new # ...
|
124
|
+
# client.execute(builder)
|
125
|
+
# @example Execute the webhook by building a new message
|
126
|
+
# client.execute do |builder|
|
127
|
+
# builder.content = 'Testing'
|
128
|
+
# builder.username = 'discordrb'
|
129
|
+
# builder.add_embed do |embed|
|
130
|
+
# embed.timestamp = Time.now
|
131
|
+
# embed.title = 'Testing'
|
132
|
+
# embed.image = Discordrb::Webhooks::EmbedImage.new(url: 'https://i.imgur.com/PcMltU7.jpg')
|
133
|
+
# end
|
134
|
+
# end
|
135
|
+
# @return [Message, nil] If `wait` is `true`, a {Message} will be returned. Otherwise this method will return `nil`.
|
136
|
+
# @note This is only available to webhooks with publically exposed tokens. This excludes channel follow webhooks and webhooks retrieved
|
137
|
+
# via the audit log.
|
138
|
+
def execute(content: nil, username: nil, avatar_url: nil, tts: nil, file: nil, embeds: nil, allowed_mentions: nil, wait: true, builder: nil, components: nil)
|
139
|
+
raise Discordrb::Errors::UnauthorizedWebhook unless @token
|
140
|
+
|
141
|
+
params = { content: content, username: username, avatar_url: avatar_url, tts: tts, file: file, embeds: embeds, allowed_mentions: allowed_mentions }
|
142
|
+
|
143
|
+
builder ||= Webhooks::Builder.new
|
144
|
+
view = Webhooks::View.new
|
145
|
+
|
146
|
+
yield(builder, view) if block_given?
|
147
|
+
|
148
|
+
data = builder.to_json_hash.merge(params.compact)
|
149
|
+
components ||= view
|
150
|
+
|
151
|
+
resp = API::Webhook.token_execute_webhook(@token, @id, wait, data[:content], data[:username], data[:avatar_url], data[:tts], data[:file], data[:embeds], data[:allowed_mentions], nil, components.to_a)
|
152
|
+
|
153
|
+
Message.new(JSON.parse(resp), @bot) if wait
|
154
|
+
end
|
155
|
+
|
156
|
+
# Delete a message created by this webhook.
|
157
|
+
# @param message [Message, String, Integer] The ID of the message to delete.
|
158
|
+
def delete_message(message)
|
159
|
+
raise Discordrb::Errors::UnauthorizedWebhook unless @token
|
160
|
+
|
161
|
+
API::Webhook.token_delete_message(@token, @id, message.resolve_id)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Edit a message created by this webhook.
|
165
|
+
# @param message [Message, String, Integer] The ID of the message to edit.
|
166
|
+
# @param content [String] The content of the message. May be 2000 characters long at most.
|
167
|
+
# @param embeds [Array<Webhooks::Embed, Hash>] Embeds to be attached to the message.
|
168
|
+
# @param allowed_mentions [AllowedMentions, Hash] Mentions that are allowed to ping in the `content`.
|
169
|
+
# @param builder [Builder, nil] The builder to start out with, or nil if one should be created anew.
|
170
|
+
# @yield [builder] Gives the builder to the block to add additional steps, or to do the entire building process.
|
171
|
+
# @yieldparam builder [Webhooks::Builder] The builder given as a parameter which is used as the initial step to start from.
|
172
|
+
# @return [Message] The updated message.
|
173
|
+
# @param components [View, Array<Hash>] Interaction components to associate with this message.
|
174
|
+
# @note When editing `allowed_mentions`, it will update visually in the client but not alert the user with a notification.
|
175
|
+
def edit_message(message, content: nil, embeds: nil, allowed_mentions: nil, builder: nil, components: nil)
|
176
|
+
raise Discordrb::Errors::UnauthorizedWebhook unless @token
|
177
|
+
|
178
|
+
params = { content: content, embeds: embeds, allowed_mentions: allowed_mentions }.compact
|
179
|
+
|
180
|
+
builder ||= Webhooks::Builder.new
|
181
|
+
view ||= Webhooks::View.new
|
182
|
+
|
183
|
+
yield(builder, view) if block_given?
|
184
|
+
|
185
|
+
data = builder.to_json_hash.merge(params.compact)
|
186
|
+
components ||= view
|
187
|
+
|
188
|
+
resp = API::Webhook.token_edit_message(@token, @id, message.resolve_id, data[:content], data[:embeds], data[:allowed_mentions], components.to_a)
|
189
|
+
Message.new(JSON.parse(resp), @bot)
|
190
|
+
end
|
191
|
+
|
99
192
|
# Utility function to get a webhook's avatar URL.
|
100
193
|
# @return [String] the URL to the avatar image
|
101
194
|
def avatar_url
|
102
|
-
return API::User.default_avatar unless @avatar
|
195
|
+
return API::User.default_avatar(@id) unless @avatar
|
103
196
|
|
104
197
|
API::User.avatar_url(@id, @avatar)
|
105
198
|
end
|
data/lib/discordrb/data.rb
CHANGED
@@ -12,6 +12,7 @@ require 'discordrb/api/invite'
|
|
12
12
|
require 'discordrb/api/user'
|
13
13
|
require 'discordrb/api/webhook'
|
14
14
|
require 'discordrb/webhooks/embeds'
|
15
|
+
require 'discordrb/webhooks/view'
|
15
16
|
require 'discordrb/paginator'
|
16
17
|
require 'time'
|
17
18
|
require 'base64'
|
@@ -37,3 +38,5 @@ require 'discordrb/data/integration'
|
|
37
38
|
require 'discordrb/data/server'
|
38
39
|
require 'discordrb/data/webhook'
|
39
40
|
require 'discordrb/data/audit_logs'
|
41
|
+
require 'discordrb/data/interaction'
|
42
|
+
require 'discordrb/data/component'
|
data/lib/discordrb/errors.rb
CHANGED
@@ -20,6 +20,9 @@ module Discordrb
|
|
20
20
|
# Raised when the bot gets a HTTP 502 error, which is usually caused by Cloudflare.
|
21
21
|
class CloudflareError < RuntimeError; end
|
22
22
|
|
23
|
+
# Raised when using a webhook method without an associated token.
|
24
|
+
class UnauthorizedWebhook < RuntimeError; end
|
25
|
+
|
23
26
|
# Generic class for errors denoted by API error codes
|
24
27
|
class CodeError < RuntimeError
|
25
28
|
class << self
|
@@ -29,8 +32,11 @@ module Discordrb
|
|
29
32
|
|
30
33
|
# Create a new error with a particular message (the code should be defined by the class instance variable)
|
31
34
|
# @param message [String] the message to use
|
32
|
-
|
35
|
+
# @param errors [Hash] API errors
|
36
|
+
def initialize(message, errors = nil)
|
33
37
|
@message = message
|
38
|
+
|
39
|
+
@errors = errors ? flatten_errors(errors) : []
|
34
40
|
end
|
35
41
|
|
36
42
|
# @return [Integer] The error code represented by this error.
|
@@ -38,15 +44,50 @@ module Discordrb
|
|
38
44
|
self.class.code
|
39
45
|
end
|
40
46
|
|
47
|
+
# @return [String] A message including the message and flattened errors.
|
48
|
+
def full_message(*)
|
49
|
+
error_list = @errors.collect { |err| "\t- #{err}" }
|
50
|
+
|
51
|
+
"#{@message}\n#{error_list.join("\n")}"
|
52
|
+
end
|
53
|
+
|
41
54
|
# @return [String] This error's represented message
|
42
55
|
attr_reader :message
|
56
|
+
|
57
|
+
# @return [Hash] More precise errors
|
58
|
+
attr_reader :errors
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# @!visibility hidden
|
63
|
+
# Flattens errors into a more easily read format.
|
64
|
+
# @example Flattening errors of a bad field
|
65
|
+
# flatten_errors(data['errors'])
|
66
|
+
# # => ["embed.fields[0].name: This field is required", "embed.fields[0].value: This field is required"]
|
67
|
+
def flatten_errors(err, prev_key = nil)
|
68
|
+
err.collect do |key, sub_err|
|
69
|
+
if prev_key
|
70
|
+
key = /\A\d+\Z/.match?(key) ? "#{prev_key}[#{key}]" : "#{prev_key}.#{key}"
|
71
|
+
end
|
72
|
+
|
73
|
+
if (errs = sub_err['_errors'])
|
74
|
+
"#{key}: #{errs.map { |e| e['message'] }.join(' ')}"
|
75
|
+
elsif sub_err['message'] || sub_err['code']
|
76
|
+
"#{sub_err['code'] ? "#{sub_err['code']}: " : nil}#{err_msg}"
|
77
|
+
elsif sub_err.is_a? String
|
78
|
+
sub_err
|
79
|
+
else
|
80
|
+
flatten_errors(sub_err, key)
|
81
|
+
end
|
82
|
+
end.flatten
|
83
|
+
end
|
43
84
|
end
|
44
85
|
|
45
86
|
# Create a new code error class
|
46
87
|
# rubocop:disable Naming/MethodName
|
47
88
|
def self.Code(code)
|
48
89
|
classy = Class.new(CodeError)
|
49
|
-
classy.instance_variable_set(
|
90
|
+
classy.instance_variable_set(:@code, code)
|
50
91
|
|
51
92
|
@code_classes ||= {}
|
52
93
|
@code_classes[code] = classy
|
@@ -58,7 +99,7 @@ module Discordrb
|
|
58
99
|
# @param code [Integer] The code to check
|
59
100
|
# @return [Class] the error class for the given code
|
60
101
|
def self.error_class_for(code)
|
61
|
-
@code_classes[code]
|
102
|
+
@code_classes[code] || UnknownError
|
62
103
|
end
|
63
104
|
|
64
105
|
# Used when Discord doesn't provide a more specific code
|