discordrb 3.3.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +152 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
  5. data/.github/pull_request_template.md +37 -0
  6. data/.github/workflows/codeql.yml +65 -0
  7. data/.markdownlint.json +4 -0
  8. data/.rubocop.yml +39 -36
  9. data/CHANGELOG.md +874 -552
  10. data/Gemfile +2 -0
  11. data/LICENSE.txt +1 -1
  12. data/README.md +80 -86
  13. data/Rakefile +2 -0
  14. data/bin/console +1 -0
  15. data/discordrb-webhooks.gemspec +9 -6
  16. data/discordrb.gemspec +21 -18
  17. data/lib/discordrb/allowed_mentions.rb +36 -0
  18. data/lib/discordrb/api/application.rb +202 -0
  19. data/lib/discordrb/api/channel.rb +236 -47
  20. data/lib/discordrb/api/interaction.rb +54 -0
  21. data/lib/discordrb/api/invite.rb +5 -5
  22. data/lib/discordrb/api/server.rb +94 -66
  23. data/lib/discordrb/api/user.rb +17 -11
  24. data/lib/discordrb/api/webhook.rb +63 -6
  25. data/lib/discordrb/api.rb +55 -16
  26. data/lib/discordrb/await.rb +0 -1
  27. data/lib/discordrb/bot.rb +480 -93
  28. data/lib/discordrb/cache.rb +31 -24
  29. data/lib/discordrb/colour_rgb.rb +43 -0
  30. data/lib/discordrb/commands/command_bot.rb +35 -12
  31. data/lib/discordrb/commands/container.rb +21 -24
  32. data/lib/discordrb/commands/parser.rb +20 -20
  33. data/lib/discordrb/commands/rate_limiter.rb +4 -3
  34. data/lib/discordrb/container.rb +209 -20
  35. data/lib/discordrb/data/activity.rb +271 -0
  36. data/lib/discordrb/data/application.rb +50 -0
  37. data/lib/discordrb/data/attachment.rb +71 -0
  38. data/lib/discordrb/data/audit_logs.rb +345 -0
  39. data/lib/discordrb/data/channel.rb +993 -0
  40. data/lib/discordrb/data/component.rb +229 -0
  41. data/lib/discordrb/data/embed.rb +251 -0
  42. data/lib/discordrb/data/emoji.rb +82 -0
  43. data/lib/discordrb/data/integration.rb +122 -0
  44. data/lib/discordrb/data/interaction.rb +800 -0
  45. data/lib/discordrb/data/invite.rb +137 -0
  46. data/lib/discordrb/data/member.rb +372 -0
  47. data/lib/discordrb/data/message.rb +414 -0
  48. data/lib/discordrb/data/overwrite.rb +108 -0
  49. data/lib/discordrb/data/profile.rb +91 -0
  50. data/lib/discordrb/data/reaction.rb +33 -0
  51. data/lib/discordrb/data/recipient.rb +34 -0
  52. data/lib/discordrb/data/role.rb +248 -0
  53. data/lib/discordrb/data/server.rb +1004 -0
  54. data/lib/discordrb/data/user.rb +264 -0
  55. data/lib/discordrb/data/voice_region.rb +45 -0
  56. data/lib/discordrb/data/voice_state.rb +41 -0
  57. data/lib/discordrb/data/webhook.rb +238 -0
  58. data/lib/discordrb/data.rb +28 -4180
  59. data/lib/discordrb/errors.rb +46 -4
  60. data/lib/discordrb/events/bans.rb +7 -5
  61. data/lib/discordrb/events/channels.rb +3 -1
  62. data/lib/discordrb/events/guilds.rb +16 -9
  63. data/lib/discordrb/events/interactions.rb +482 -0
  64. data/lib/discordrb/events/invites.rb +125 -0
  65. data/lib/discordrb/events/members.rb +6 -2
  66. data/lib/discordrb/events/message.rb +72 -27
  67. data/lib/discordrb/events/presence.rb +35 -18
  68. data/lib/discordrb/events/raw.rb +1 -3
  69. data/lib/discordrb/events/reactions.rb +49 -4
  70. data/lib/discordrb/events/threads.rb +96 -0
  71. data/lib/discordrb/events/typing.rb +6 -4
  72. data/lib/discordrb/events/voice_server_update.rb +47 -0
  73. data/lib/discordrb/events/voice_state_update.rb +15 -10
  74. data/lib/discordrb/events/webhooks.rb +9 -6
  75. data/lib/discordrb/gateway.rb +99 -71
  76. data/lib/discordrb/id_object.rb +39 -0
  77. data/lib/discordrb/light/integrations.rb +1 -1
  78. data/lib/discordrb/light/light_bot.rb +1 -1
  79. data/lib/discordrb/logger.rb +4 -4
  80. data/lib/discordrb/paginator.rb +57 -0
  81. data/lib/discordrb/permissions.rb +159 -39
  82. data/lib/discordrb/version.rb +1 -1
  83. data/lib/discordrb/voice/encoder.rb +16 -7
  84. data/lib/discordrb/voice/network.rb +99 -47
  85. data/lib/discordrb/voice/sodium.rb +98 -0
  86. data/lib/discordrb/voice/voice_bot.rb +33 -25
  87. data/lib/discordrb/webhooks.rb +2 -0
  88. data/lib/discordrb.rb +107 -1
  89. metadata +126 -54
  90. data/.codeclimate.yml +0 -16
  91. data/.travis.yml +0 -33
  92. data/bin/travis_build_docs.sh +0 -17
  93. /data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +0 -0
@@ -0,0 +1,264 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discordrb
4
+ # Mixin for the attributes users should have
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
+
26
+ # @return [String] this user's username
27
+ attr_reader :username
28
+ alias_method :name, :username
29
+
30
+ # @return [String, nil] this user's global name
31
+ attr_reader :global_name
32
+
33
+ # @return [String] this user's discriminator which is used internally to identify users with identical usernames.
34
+ attr_reader :discriminator
35
+ alias_method :discrim, :discriminator
36
+ alias_method :tag, :discriminator
37
+ alias_method :discord_tag, :discriminator
38
+
39
+ # @return [true, false] whether this user is a Discord bot account
40
+ attr_reader :bot_account
41
+ alias_method :bot_account?, :bot_account
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
+
48
+ # @return [String] the ID of this user's current avatar, can be used to generate an avatar URL.
49
+ # @see #avatar_url
50
+ attr_accessor :avatar_id
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
+
58
+ # Utility function to mention users in messages
59
+ # @return [String] the mention code in the form of <@id>
60
+ def mention
61
+ "<@#{@id}>"
62
+ end
63
+
64
+ # Utility function to get Discord's distinct representation of a user, i.e. username + discriminator
65
+ # @return [String] distinct representation of user
66
+ # TODO: Maybe change this method again after discriminator removal ?
67
+ def distinct
68
+ if @discriminator && @discriminator != '0'
69
+ "#{@username}##{@discriminator}"
70
+ else
71
+ @username.to_s
72
+ end
73
+ end
74
+
75
+ # Utility function to get a user's avatar URL.
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.
77
+ # @return [String] the URL to the avatar image.
78
+ # TODO: Maybe change this method again after discriminator removal ?
79
+ def avatar_url(format = nil)
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
85
+
86
+ API::User.avatar_url(@id, @avatar_id, format)
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
97
+ end
98
+
99
+ # User on Discord, including internal data like discriminators
100
+ class User
101
+ include IDObject
102
+ include UserAttributes
103
+
104
+ # @return [Symbol] the current online status of the user (`:online`, `:offline` or `:idle`)
105
+ attr_reader :status
106
+
107
+ # @return [ActivitySet] the activities of the user
108
+ attr_reader :activities
109
+
110
+ # @return [Hash<Symbol, Symbol>] the current online status (`:online`, `:idle` or `:dnd`) of the user
111
+ # on various device types (`:desktop`, `:mobile`, or `:web`). The value will be `nil` if the user is offline or invisible.
112
+ attr_reader :client_status
113
+
114
+ # @!visibility private
115
+ def initialize(data, bot)
116
+ @bot = bot
117
+
118
+ @username = data['username']
119
+ @global_name = data['global_name']
120
+ @id = data['id'].to_i
121
+ @discriminator = data['discriminator']
122
+ @avatar_id = data['avatar']
123
+ @roles = {}
124
+ @activities = Discordrb::ActivitySet.new
125
+ @public_flags = data['public_flags'] || 0
126
+
127
+ @bot_account = false
128
+ @bot_account = true if data['bot']
129
+
130
+ @webhook_account = false
131
+ @webhook_account = true if data['_webhook']
132
+
133
+ @status = :offline
134
+ @client_status = process_client_status(data['client_status'])
135
+ end
136
+
137
+ # Get a user's PM channel or send them a PM
138
+ # @overload pm
139
+ # Creates a private message channel for this user or returns an existing one if it already exists
140
+ # @return [Channel] the PM channel to this user.
141
+ # @overload pm(content)
142
+ # Sends a private to this user.
143
+ # @param content [String] The content to send.
144
+ # @return [Message] the message sent to this user.
145
+ def pm(content = nil)
146
+ if content
147
+ # Recursively call pm to get the channel, then send a message to it
148
+ channel = pm
149
+ channel.send_message(content)
150
+ else
151
+ # If no message was specified, return the PM channel
152
+ @bot.pm_channel(@id)
153
+ end
154
+ end
155
+
156
+ alias_method :dm, :pm
157
+
158
+ # Send the user a file.
159
+ # @param file [File] The file to send to the user
160
+ # @param caption [String] The caption of the file being sent
161
+ # @param filename [String] Overrides the filename of the uploaded file
162
+ # @param spoiler [true, false] Whether or not this file should appear as a spoiler.
163
+ # @return [Message] the message sent to this user.
164
+ # @example Send a file from disk
165
+ # user.send_file(File.open('rubytaco.png', 'r'))
166
+ def send_file(file, caption = nil, filename: nil, spoiler: nil)
167
+ pm.send_file(file, caption: caption, filename: filename, spoiler: spoiler)
168
+ end
169
+
170
+ # Set the user's username
171
+ # @note for internal use only
172
+ # @!visibility private
173
+ def update_username(username)
174
+ @username = username
175
+ end
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
+
184
+ # Set the user's presence data
185
+ # @note for internal use only
186
+ # @!visibility private
187
+ def update_presence(data)
188
+ @status = data['status'].to_sym
189
+ @client_status = process_client_status(data['client_status'])
190
+
191
+ @activities = Discordrb::ActivitySet.new(data['activities'].map { |act| Activity.new(act, @bot) })
192
+ end
193
+
194
+ # Add an await for a message from this user. Specifically, this adds a global await for a MessageEvent with this
195
+ # user's ID as a :from attribute.
196
+ # @see Bot#add_await
197
+ def await(key, attributes = {}, &block)
198
+ @bot.add_await(key, Discordrb::Events::MessageEvent, { from: @id }.merge(attributes), &block)
199
+ end
200
+
201
+ # Add a blocking await for a message from this user. Specifically, this adds a global await for a MessageEvent with this
202
+ # user's ID as a :from attribute.
203
+ # @see Bot#add_await!
204
+ def await!(attributes = {}, &block)
205
+ @bot.add_await!(Discordrb::Events::MessageEvent, { from: @id }.merge(attributes), &block)
206
+ end
207
+
208
+ # Gets the member this user is on a server
209
+ # @param server [Server] The server to get the member for
210
+ # @return [Member] this user as a member on a particular server
211
+ def on(server)
212
+ id = server.resolve_id
213
+ @bot.server(id).member(@id)
214
+ end
215
+
216
+ # Is the user the bot?
217
+ # @return [true, false] whether this user is the bot
218
+ def current_bot?
219
+ @bot.profile.id == @id
220
+ end
221
+
222
+ # @!visibility private
223
+ def process_client_status(client_status)
224
+ (client_status || {}).to_h { |k, v| [k.to_sym, v.to_sym] }
225
+ end
226
+
227
+ # @!method offline?
228
+ # @return [true, false] whether this user is offline.
229
+ # @!method idle?
230
+ # @return [true, false] whether this user is idle.
231
+ # @!method online?
232
+ # @return [true, false] whether this user is online.
233
+ # @!method dnd?
234
+ # @return [true, false] whether this user is set to do not disturb.
235
+ %i[offline idle online dnd].each do |e|
236
+ define_method("#{e}?") do
237
+ @status.to_sym == e
238
+ end
239
+ end
240
+
241
+ # @return [String, nil] the game the user is currently playing, or `nil` if nothing is being played.
242
+ # @deprecated Please use {ActivitySet#games} for information about the user's game activity
243
+ def game
244
+ @activities.games.first&.name
245
+ end
246
+
247
+ # @return [Integer] returns 1 for twitch streams, or 0 for no stream.
248
+ # @deprecated Please use {ActivitySet#streaming} for information about the user's stream activity
249
+ def stream_type
250
+ @activities.streaming ? 1 : 0
251
+ end
252
+
253
+ # @return [String, nil] the URL to the stream, if the user is currently streaming something
254
+ # @deprecated Please use {ActivitySet#streaming} for information about the user's stream activity
255
+ def stream_url
256
+ @activities.streaming.first&.url
257
+ end
258
+
259
+ # The inspect method is overwritten to give more useful output
260
+ def inspect
261
+ "<User username=#{@username} id=#{@id} discriminator=#{@discriminator}>"
262
+ end
263
+ end
264
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discordrb
4
+ # Voice regions are the locations of servers that handle voice communication in Discord
5
+ class VoiceRegion
6
+ # @return [String] unique ID for the region
7
+ attr_reader :id
8
+ alias_method :to_s, :id
9
+
10
+ # @return [String] name of the region
11
+ attr_reader :name
12
+
13
+ # @return [String] an example hostname for the region
14
+ attr_reader :sample_hostname
15
+
16
+ # @return [Integer] an example port for the region
17
+ attr_reader :sample_port
18
+
19
+ # @return [true, false] if this is a VIP-only server
20
+ attr_reader :vip
21
+
22
+ # @return [true, false] if this voice server is the closest to the client
23
+ attr_reader :optimal
24
+
25
+ # @return [true, false] whether this is a deprecated voice region (avoid switching to these)
26
+ attr_reader :deprecated
27
+
28
+ # @return [true, false] whether this is a custom voice region (used for events/etc)
29
+ attr_reader :custom
30
+
31
+ def initialize(data)
32
+ @id = data['id']
33
+
34
+ @name = data['name']
35
+
36
+ @sample_hostname = data['sample_hostname']
37
+ @sample_port = data['sample_port']
38
+
39
+ @vip = data['vip']
40
+ @optimal = data['optimal']
41
+ @deprecated = data['deprecated']
42
+ @custom = data['custom']
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discordrb
4
+ # A voice state represents the state of a member's connection to a voice channel. It includes data like the voice
5
+ # channel the member is connected to and mute/deaf flags.
6
+ class VoiceState
7
+ # @return [Integer] the ID of the user whose voice state is represented by this object.
8
+ attr_reader :user_id
9
+
10
+ # @return [true, false] whether this voice state's member is muted server-wide.
11
+ attr_reader :mute
12
+
13
+ # @return [true, false] whether this voice state's member is deafened server-wide.
14
+ attr_reader :deaf
15
+
16
+ # @return [true, false] whether this voice state's member has muted themselves.
17
+ attr_reader :self_mute
18
+
19
+ # @return [true, false] whether this voice state's member has deafened themselves.
20
+ attr_reader :self_deaf
21
+
22
+ # @return [Channel] the voice channel this voice state's member is in.
23
+ attr_reader :voice_channel
24
+
25
+ # @!visibility private
26
+ def initialize(user_id)
27
+ @user_id = user_id
28
+ end
29
+
30
+ # Update this voice state with new data from Discord
31
+ # @note For internal use only.
32
+ # @!visibility private
33
+ def update(channel, mute, deaf, self_mute, self_deaf)
34
+ @voice_channel = channel
35
+ @mute = mute
36
+ @deaf = deaf
37
+ @self_mute = self_mute
38
+ @self_deaf = self_deaf
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,238 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'discordrb/webhooks/builder'
4
+ require 'discordrb/webhooks/view'
5
+
6
+ module Discordrb
7
+ # A webhook on a server channel
8
+ class Webhook
9
+ include IDObject
10
+
11
+ # @return [String] the webhook name.
12
+ attr_reader :name
13
+
14
+ # @return [Channel] the channel that the webhook is currently connected to.
15
+ attr_reader :channel
16
+
17
+ # @return [Server] the server that the webhook is currently connected to.
18
+ attr_reader :server
19
+
20
+ # @return [String, nil] the webhook's token, if this is an Incoming Webhook.
21
+ attr_reader :token
22
+
23
+ # @return [String] the webhook's avatar id.
24
+ attr_reader :avatar
25
+
26
+ # @return [Integer] the webhook's type (1: Incoming, 2: Channel Follower)
27
+ attr_reader :type
28
+
29
+ # Gets the user object of the creator of the webhook. May be limited to username, discriminator,
30
+ # ID and avatar if the bot cannot reach the owner
31
+ # @return [Member, User, nil] the user object of the owner or nil if the webhook was requested using the token.
32
+ attr_reader :owner
33
+
34
+ def initialize(data, bot)
35
+ @bot = bot
36
+
37
+ @name = data['name']
38
+ @id = data['id'].to_i
39
+ @channel = bot.channel(data['channel_id'])
40
+ @server = @channel.server
41
+ @token = data['token']
42
+ @avatar = data['avatar']
43
+ @type = data['type']
44
+
45
+ # Will not exist if the data was requested through a webhook token
46
+ return unless data['user']
47
+
48
+ @owner = @server.member(data['user']['id'].to_i)
49
+ return if @owner
50
+
51
+ Discordrb::LOGGER.debug("Member with ID #{data['user']['id']} not cached (possibly left the server).")
52
+ @owner = @bot.ensure_user(data['user'])
53
+ end
54
+
55
+ # Sets the webhook's avatar.
56
+ # @param avatar [String, #read] The new avatar, in base64-encoded JPG format.
57
+ def avatar=(avatar)
58
+ update_webhook(avatar: avatarise(avatar))
59
+ end
60
+
61
+ # Deletes the webhook's avatar.
62
+ def delete_avatar
63
+ update_webhook(avatar: nil)
64
+ end
65
+
66
+ # Sets the webhook's channel
67
+ # @param channel [Channel, String, Integer] The channel the webhook should use.
68
+ def channel=(channel)
69
+ update_webhook(channel_id: channel.resolve_id)
70
+ end
71
+
72
+ # Sets the webhook's name.
73
+ # @param name [String] The webhook's new name.
74
+ def name=(name)
75
+ update_webhook(name: name)
76
+ end
77
+
78
+ # Updates the webhook if you need to edit more than 1 attribute.
79
+ # @param data [Hash] the data to update.
80
+ # @option data [String, #read, nil] :avatar The new avatar, in base64-encoded JPG format, or nil to delete the avatar.
81
+ # @option data [Channel, String, Integer] :channel The channel the webhook should use.
82
+ # @option data [String] :name The webhook's new name.
83
+ # @option data [String] :reason The reason for the webhook changes.
84
+ def update(data)
85
+ # Only pass a value for avatar if the key is defined as sending nil will delete the
86
+ data[:avatar] = avatarise(data[:avatar]) if data.key?(:avatar)
87
+ data[:channel_id] = data[:channel]&.resolve_id
88
+ data.delete(:channel)
89
+ update_webhook(**data)
90
+ end
91
+
92
+ # Deletes the webhook.
93
+ # @param reason [String] The reason the webhook is being deleted.
94
+ def delete(reason = nil)
95
+ if token?
96
+ API::Webhook.token_delete_webhook(@token, @id, reason)
97
+ else
98
+ API::Webhook.delete_webhook(@bot.token, @id, reason)
99
+ end
100
+ end
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
+
192
+ # Utility function to get a webhook's avatar URL.
193
+ # @return [String] the URL to the avatar image
194
+ def avatar_url
195
+ return API::User.default_avatar(@id) unless @avatar
196
+
197
+ API::User.avatar_url(@id, @avatar)
198
+ end
199
+
200
+ # The `inspect` method is overwritten to give more useful output.
201
+ def inspect
202
+ "<Webhook name=#{@name} id=#{@id}>"
203
+ end
204
+
205
+ # Utility function to know if the webhook was requested through a webhook token, rather than auth.
206
+ # @return [true, false] whether the webhook was requested by token or not.
207
+ def token?
208
+ @owner.nil?
209
+ end
210
+
211
+ private
212
+
213
+ def avatarise(avatar)
214
+ if avatar.respond_to? :read
215
+ "data:image/jpg;base64,#{Base64.strict_encode64(avatar.read)}"
216
+ else
217
+ avatar
218
+ end
219
+ end
220
+
221
+ def update_internal(data)
222
+ @name = data['name']
223
+ @avatar_id = data['avatar']
224
+ @channel = @bot.channel(data['channel_id'])
225
+ end
226
+
227
+ def update_webhook(new_data)
228
+ reason = new_data.delete(:reason)
229
+ data = JSON.parse(if token?
230
+ API::Webhook.token_update_webhook(@token, @id, new_data, reason)
231
+ else
232
+ API::Webhook.update_webhook(@bot.token, @id, new_data, reason)
233
+ end)
234
+ # Only update cache if API call worked
235
+ update_internal(data) if data['name']
236
+ end
237
+ end
238
+ end