discordrb 3.3.0 → 3.5.0

Sign up to get free protection for your applications and to get access to all the features.
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