discordrb 3.3.0 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of discordrb might be problematic. Click here for more details.

Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +126 -0
  3. data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +0 -0
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +39 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +25 -0
  6. data/.github/pull_request_template.md +37 -0
  7. data/.rubocop.yml +34 -37
  8. data/.travis.yml +5 -6
  9. data/CHANGELOG.md +472 -347
  10. data/Gemfile +2 -0
  11. data/LICENSE.txt +1 -1
  12. data/README.md +61 -79
  13. data/Rakefile +2 -0
  14. data/bin/console +1 -0
  15. data/discordrb-webhooks.gemspec +6 -6
  16. data/discordrb.gemspec +17 -17
  17. data/lib/discordrb.rb +73 -0
  18. data/lib/discordrb/allowed_mentions.rb +36 -0
  19. data/lib/discordrb/api.rb +40 -15
  20. data/lib/discordrb/api/channel.rb +57 -39
  21. data/lib/discordrb/api/invite.rb +3 -3
  22. data/lib/discordrb/api/server.rb +55 -50
  23. data/lib/discordrb/api/user.rb +8 -8
  24. data/lib/discordrb/api/webhook.rb +6 -6
  25. data/lib/discordrb/await.rb +0 -1
  26. data/lib/discordrb/bot.rb +164 -72
  27. data/lib/discordrb/cache.rb +4 -2
  28. data/lib/discordrb/colour_rgb.rb +43 -0
  29. data/lib/discordrb/commands/command_bot.rb +22 -6
  30. data/lib/discordrb/commands/container.rb +20 -23
  31. data/lib/discordrb/commands/parser.rb +18 -18
  32. data/lib/discordrb/commands/rate_limiter.rb +3 -2
  33. data/lib/discordrb/container.rb +77 -17
  34. data/lib/discordrb/data.rb +25 -4180
  35. data/lib/discordrb/data/activity.rb +264 -0
  36. data/lib/discordrb/data/application.rb +50 -0
  37. data/lib/discordrb/data/attachment.rb +56 -0
  38. data/lib/discordrb/data/audit_logs.rb +345 -0
  39. data/lib/discordrb/data/channel.rb +849 -0
  40. data/lib/discordrb/data/embed.rb +251 -0
  41. data/lib/discordrb/data/emoji.rb +82 -0
  42. data/lib/discordrb/data/integration.rb +83 -0
  43. data/lib/discordrb/data/invite.rb +137 -0
  44. data/lib/discordrb/data/member.rb +297 -0
  45. data/lib/discordrb/data/message.rb +334 -0
  46. data/lib/discordrb/data/overwrite.rb +102 -0
  47. data/lib/discordrb/data/profile.rb +91 -0
  48. data/lib/discordrb/data/reaction.rb +33 -0
  49. data/lib/discordrb/data/recipient.rb +34 -0
  50. data/lib/discordrb/data/role.rb +191 -0
  51. data/lib/discordrb/data/server.rb +1002 -0
  52. data/lib/discordrb/data/user.rb +204 -0
  53. data/lib/discordrb/data/voice_region.rb +45 -0
  54. data/lib/discordrb/data/voice_state.rb +41 -0
  55. data/lib/discordrb/data/webhook.rb +145 -0
  56. data/lib/discordrb/errors.rb +2 -1
  57. data/lib/discordrb/events/bans.rb +7 -5
  58. data/lib/discordrb/events/channels.rb +2 -0
  59. data/lib/discordrb/events/guilds.rb +16 -9
  60. data/lib/discordrb/events/invites.rb +125 -0
  61. data/lib/discordrb/events/members.rb +6 -2
  62. data/lib/discordrb/events/message.rb +69 -27
  63. data/lib/discordrb/events/presence.rb +14 -4
  64. data/lib/discordrb/events/raw.rb +1 -3
  65. data/lib/discordrb/events/reactions.rb +49 -3
  66. data/lib/discordrb/events/typing.rb +6 -4
  67. data/lib/discordrb/events/voice_server_update.rb +47 -0
  68. data/lib/discordrb/events/voice_state_update.rb +15 -10
  69. data/lib/discordrb/events/webhooks.rb +9 -6
  70. data/lib/discordrb/gateway.rb +72 -57
  71. data/lib/discordrb/id_object.rb +39 -0
  72. data/lib/discordrb/light/integrations.rb +1 -1
  73. data/lib/discordrb/light/light_bot.rb +1 -1
  74. data/lib/discordrb/logger.rb +4 -4
  75. data/lib/discordrb/paginator.rb +57 -0
  76. data/lib/discordrb/permissions.rb +103 -8
  77. data/lib/discordrb/version.rb +1 -1
  78. data/lib/discordrb/voice/encoder.rb +3 -3
  79. data/lib/discordrb/voice/network.rb +84 -43
  80. data/lib/discordrb/voice/sodium.rb +96 -0
  81. data/lib/discordrb/voice/voice_bot.rb +34 -26
  82. metadata +93 -55
@@ -0,0 +1,297 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discordrb
4
+ # Mixin for the attributes members and private members should have
5
+ module MemberAttributes
6
+ # @return [Time] when this member joined the server.
7
+ attr_reader :joined_at
8
+
9
+ # @return [Time, nil] when this member boosted this server, `nil` if they haven't.
10
+ attr_reader :boosting_since
11
+
12
+ # @return [String, nil] the nickname this member has, or `nil` if it has none.
13
+ attr_reader :nick
14
+ alias_method :nickname, :nick
15
+
16
+ # @return [Array<Role>] the roles this member has.
17
+ attr_reader :roles
18
+
19
+ # @return [Server] the server this member is on.
20
+ attr_reader :server
21
+ end
22
+
23
+ # A member is a user on a server. It differs from regular users in that it has roles, voice statuses and things like
24
+ # that.
25
+ class Member < DelegateClass(User)
26
+ # @return [true, false] whether this member is muted server-wide.
27
+ def mute
28
+ voice_state_attribute(:mute)
29
+ end
30
+
31
+ # @return [true, false] whether this member is deafened server-wide.
32
+ def deaf
33
+ voice_state_attribute(:deaf)
34
+ end
35
+
36
+ # @return [true, false] whether this member has muted themselves.
37
+ def self_mute
38
+ voice_state_attribute(:self_mute)
39
+ end
40
+
41
+ # @return [true, false] whether this member has deafened themselves.
42
+ def self_deaf
43
+ voice_state_attribute(:self_deaf)
44
+ end
45
+
46
+ # @return [Channel] the voice channel this member is in.
47
+ def voice_channel
48
+ voice_state_attribute(:voice_channel)
49
+ end
50
+
51
+ alias_method :muted?, :mute
52
+ alias_method :deafened?, :deaf
53
+ alias_method :self_muted?, :self_mute
54
+ alias_method :self_deafened?, :self_deaf
55
+
56
+ include MemberAttributes
57
+
58
+ # @!visibility private
59
+ def initialize(data, server, bot)
60
+ @bot = bot
61
+
62
+ @user = bot.ensure_user(data['user'])
63
+ super @user # Initialize the delegate class
64
+
65
+ # Somehow, Discord doesn't send the server ID in the standard member format...
66
+ raise ArgumentError, 'Cannot create a member without any information about the server!' if server.nil? && data['guild_id'].nil?
67
+
68
+ @server = server || bot.server(data['guild_id'].to_i)
69
+
70
+ # Initialize the roles by getting the roles from the server one-by-one
71
+ update_roles(data['roles'])
72
+
73
+ @nick = data['nick']
74
+ @joined_at = data['joined_at'] ? Time.parse(data['joined_at']) : nil
75
+ @boosting_since = data['premium_since'] ? Time.parse(data['premium_since']) : nil
76
+ end
77
+
78
+ # @return [true, false] if this user is a Nitro Booster of this server.
79
+ def boosting?
80
+ !@boosting_since.nil?
81
+ end
82
+
83
+ # @return [true, false] whether this member is the server owner.
84
+ def owner?
85
+ @server.owner == self
86
+ end
87
+
88
+ # @param role [Role, String, Integer] the role to check or its ID.
89
+ # @return [true, false] whether this member has the specified role.
90
+ def role?(role)
91
+ role = role.resolve_id
92
+ @roles.any? { |e| e.id == role }
93
+ end
94
+
95
+ # @see Member#set_roles
96
+ def roles=(role)
97
+ set_roles(role)
98
+ end
99
+
100
+ # Bulk sets a member's roles.
101
+ # @param role [Role, Array<Role>] The role(s) to set.
102
+ # @param reason [String] The reason the user's roles are being changed.
103
+ def set_roles(role, reason = nil)
104
+ role_ids = role_id_array(role)
105
+ API::Server.update_member(@bot.token, @server.id, @user.id, roles: role_ids, reason: reason)
106
+ end
107
+
108
+ # Adds and removes roles from a member.
109
+ # @param add [Role, Array<Role>] The role(s) to add.
110
+ # @param remove [Role, Array<Role>] The role(s) to remove.
111
+ # @param reason [String] The reason the user's roles are being changed.
112
+ # @example Remove the 'Member' role from a user, and add the 'Muted' role to them.
113
+ # to_add = server.roles.find {|role| role.name == 'Muted'}
114
+ # to_remove = server.roles.find {|role| role.name == 'Member'}
115
+ # member.modify_roles(to_add, to_remove)
116
+ def modify_roles(add, remove, reason = nil)
117
+ add_role_ids = role_id_array(add)
118
+ remove_role_ids = role_id_array(remove)
119
+ old_role_ids = @roles.map(&:id)
120
+ new_role_ids = (old_role_ids - remove_role_ids + add_role_ids).uniq
121
+
122
+ API::Server.update_member(@bot.token, @server.id, @user.id, roles: new_role_ids, reason: reason)
123
+ end
124
+
125
+ # Adds one or more roles to this member.
126
+ # @param role [Role, Array<Role, String, Integer>, String, Integer] The role(s), or their ID(s), to add.
127
+ # @param reason [String] The reason the user's roles are being changed.
128
+ def add_role(role, reason = nil)
129
+ role_ids = role_id_array(role)
130
+
131
+ if role_ids.count == 1
132
+ API::Server.add_member_role(@bot.token, @server.id, @user.id, role_ids[0], reason)
133
+ else
134
+ old_role_ids = @roles.map(&:id)
135
+ new_role_ids = (old_role_ids + role_ids).uniq
136
+ API::Server.update_member(@bot.token, @server.id, @user.id, roles: new_role_ids, reason: reason)
137
+ end
138
+ end
139
+
140
+ # Removes one or more roles from this member.
141
+ # @param role [Role, Array<Role>] The role(s) to remove.
142
+ # @param reason [String] The reason the user's roles are being changed.
143
+ def remove_role(role, reason = nil)
144
+ role_ids = role_id_array(role)
145
+
146
+ if role_ids.count == 1
147
+ API::Server.remove_member_role(@bot.token, @server.id, @user.id, role_ids[0], reason)
148
+ else
149
+ old_role_ids = @roles.map(&:id)
150
+ new_role_ids = old_role_ids.reject { |i| role_ids.include?(i) }
151
+ API::Server.update_member(@bot.token, @server.id, @user.id, roles: new_role_ids, reason: reason)
152
+ end
153
+ end
154
+
155
+ # @return [Role] the highest role this member has.
156
+ def highest_role
157
+ @roles.max_by(&:position)
158
+ end
159
+
160
+ # @return [Role, nil] the role this member is being hoisted with.
161
+ def hoist_role
162
+ hoisted_roles = @roles.select(&:hoist)
163
+ return nil if hoisted_roles.empty?
164
+
165
+ hoisted_roles.max_by(&:position)
166
+ end
167
+
168
+ # @return [Role, nil] the role this member is basing their colour on.
169
+ def colour_role
170
+ coloured_roles = @roles.select { |v| v.colour.combined.nonzero? }
171
+ return nil if coloured_roles.empty?
172
+
173
+ coloured_roles.max_by(&:position)
174
+ end
175
+ alias_method :color_role, :colour_role
176
+
177
+ # @return [ColourRGB, nil] the colour this member has.
178
+ def colour
179
+ return nil unless colour_role
180
+
181
+ colour_role.color
182
+ end
183
+ alias_method :color, :colour
184
+
185
+ # Server deafens this member.
186
+ def server_deafen
187
+ API::Server.update_member(@bot.token, @server.id, @user.id, deaf: true)
188
+ end
189
+
190
+ # Server undeafens this member.
191
+ def server_undeafen
192
+ API::Server.update_member(@bot.token, @server.id, @user.id, deaf: false)
193
+ end
194
+
195
+ # Server mutes this member.
196
+ def server_mute
197
+ API::Server.update_member(@bot.token, @server.id, @user.id, mute: true)
198
+ end
199
+
200
+ # Server unmutes this member.
201
+ def server_unmute
202
+ API::Server.update_member(@bot.token, @server.id, @user.id, mute: false)
203
+ end
204
+
205
+ # @see Member#set_nick
206
+ def nick=(nick)
207
+ set_nick(nick)
208
+ end
209
+
210
+ alias_method :nickname=, :nick=
211
+
212
+ # Sets or resets this member's nickname. Requires the Change Nickname permission for the bot itself and Manage
213
+ # Nicknames for other users.
214
+ # @param nick [String, nil] The string to set the nickname to, or nil if it should be reset.
215
+ # @param reason [String] The reason the user's nickname is being changed.
216
+ def set_nick(nick, reason = nil)
217
+ # Discord uses the empty string to signify 'no nickname' so we convert nil into that
218
+ nick ||= ''
219
+
220
+ if @user.current_bot?
221
+ API::User.change_own_nickname(@bot.token, @server.id, nick, reason)
222
+ else
223
+ API::Server.update_member(@bot.token, @server.id, @user.id, nick: nick, reason: nil)
224
+ end
225
+ end
226
+
227
+ alias_method :set_nickname, :set_nick
228
+
229
+ # @return [String] the name the user displays as (nickname if they have one, username otherwise)
230
+ def display_name
231
+ nickname || username
232
+ end
233
+
234
+ # Update this member's roles
235
+ # @note For internal use only.
236
+ # @!visibility private
237
+ def update_roles(role_ids)
238
+ @roles = [@server.role(@server.id)]
239
+ role_ids.each do |id|
240
+ # It is possible for members to have roles that do not exist
241
+ # on the server any longer. See https://github.com/shardlab/discordrb/issues/371
242
+ role = @server.role(id)
243
+ @roles << role if role
244
+ end
245
+ end
246
+
247
+ # Update this member's nick
248
+ # @note For internal use only.
249
+ # @!visibility private
250
+ def update_nick(nick)
251
+ @nick = nick
252
+ end
253
+
254
+ # Update this member's boosting timestamp
255
+ # @note For internal user only.
256
+ # @!visibility private
257
+ def update_boosting_since(time)
258
+ @boosting_since = time
259
+ end
260
+
261
+ # Update this member
262
+ # @note For internal use only.
263
+ # @!visibility private
264
+ def update_data(data)
265
+ update_roles(data['roles']) if data['roles']
266
+ update_nick(data['nick']) if data.key?('nick')
267
+ @mute = data['mute'] if data.key?('mute')
268
+ @deaf = data['deaf'] if data.key?('deaf')
269
+
270
+ @joined_at = Time.parse(data['joined_at']) if data['joined_at']
271
+ end
272
+
273
+ include PermissionCalculator
274
+
275
+ # Overwriting inspect for debug purposes
276
+ def inspect
277
+ "<Member user=#{@user.inspect} server=#{@server.inspect} joined_at=#{@joined_at} roles=#{@roles.inspect} voice_channel=#{@voice_channel.inspect} mute=#{@mute} deaf=#{@deaf} self_mute=#{@self_mute} self_deaf=#{@self_deaf}>"
278
+ end
279
+
280
+ private
281
+
282
+ # Utility method to get a list of role IDs from one role or an array of roles
283
+ def role_id_array(role)
284
+ if role.is_a? Array
285
+ role.map(&:resolve_id)
286
+ else
287
+ [role.resolve_id]
288
+ end
289
+ end
290
+
291
+ # Utility method to get data out of this member's voice state
292
+ def voice_state_attribute(name)
293
+ voice_state = @server.voice_states[@user.id]
294
+ voice_state&.send name
295
+ end
296
+ end
297
+ end
@@ -0,0 +1,334 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discordrb
4
+ # A message on Discord that was sent to a text channel
5
+ class Message
6
+ include IDObject
7
+
8
+ # @return [String] the content of this message.
9
+ attr_reader :content
10
+ alias_method :text, :content
11
+ alias_method :to_s, :content
12
+
13
+ # @return [Member, User] the user that sent this message. (Will be a {Member} most of the time, it should only be a
14
+ # {User} for old messages when the author has left the server since then)
15
+ attr_reader :author
16
+ alias_method :user, :author
17
+ alias_method :writer, :author
18
+
19
+ # @return [Channel] the channel in which this message was sent.
20
+ attr_reader :channel
21
+
22
+ # @return [Time] the timestamp at which this message was sent.
23
+ attr_reader :timestamp
24
+
25
+ # @return [Time] the timestamp at which this message was edited. `nil` if the message was never edited.
26
+ attr_reader :edited_timestamp
27
+ alias_method :edit_timestamp, :edited_timestamp
28
+
29
+ # @return [Array<User>] the users that were mentioned in this message.
30
+ attr_reader :mentions
31
+
32
+ # @return [Array<Role>] the roles that were mentioned in this message.
33
+ attr_reader :role_mentions
34
+
35
+ # @return [Array<Attachment>] the files attached to this message.
36
+ attr_reader :attachments
37
+
38
+ # @return [Array<Embed>] the embed objects contained in this message.
39
+ attr_reader :embeds
40
+
41
+ # @return [Array<Reaction>] the reaction objects contained in this message.
42
+ attr_reader :reactions
43
+
44
+ # @return [true, false] whether the message used Text-To-Speech (TTS) or not.
45
+ attr_reader :tts
46
+ alias_method :tts?, :tts
47
+
48
+ # @return [String] used for validating a message was sent.
49
+ attr_reader :nonce
50
+
51
+ # @return [true, false] whether the message was edited or not.
52
+ attr_reader :edited
53
+ alias_method :edited?, :edited
54
+
55
+ # @return [true, false] whether the message mentioned everyone or not.
56
+ attr_reader :mention_everyone
57
+ alias_method :mention_everyone?, :mention_everyone
58
+ alias_method :mentions_everyone?, :mention_everyone
59
+
60
+ # @return [true, false] whether the message is pinned or not.
61
+ attr_reader :pinned
62
+ alias_method :pinned?, :pinned
63
+
64
+ # @return [Server, nil] the server in which this message was sent.
65
+ attr_reader :server
66
+
67
+ # @return [Integer, nil] the webhook ID that sent this message, or `nil` if it wasn't sent through a webhook.
68
+ attr_reader :webhook_id
69
+
70
+ # The discriminator that webhook user accounts have.
71
+ ZERO_DISCRIM = '0000'
72
+
73
+ # @!visibility private
74
+ def initialize(data, bot)
75
+ @bot = bot
76
+ @content = data['content']
77
+ @channel = bot.channel(data['channel_id'].to_i)
78
+ @pinned = data['pinned']
79
+ @tts = data['tts']
80
+ @nonce = data['nonce']
81
+ @mention_everyone = data['mention_everyone']
82
+
83
+ @referenced_message = Message.new(data['referenced_message'], bot) if data['referenced_message']
84
+ @message_reference = data['message_reference']
85
+
86
+ @server = bot.server(data['guild_id'].to_i) if data['guild_id']
87
+
88
+ @author = if data['author']
89
+ if data['author']['discriminator'] == ZERO_DISCRIM
90
+ # This is a webhook user! It would be pointless to try to resolve a member here, so we just create
91
+ # a User and return that instead.
92
+ Discordrb::LOGGER.debug("Webhook user: #{data['author']['id']}")
93
+ User.new(data['author'], @bot)
94
+ elsif @channel.private?
95
+ # Turn the message user into a recipient - we can't use the channel recipient
96
+ # directly because the bot may also send messages to the channel
97
+ Recipient.new(bot.user(data['author']['id'].to_i), @channel, bot)
98
+ else
99
+ member = @channel.server.member(data['author']['id'].to_i)
100
+
101
+ if member
102
+ member.update_data(data['member']) if data['member']
103
+ else
104
+ Discordrb::LOGGER.debug("Member with ID #{data['author']['id']} not cached (possibly left the server).")
105
+ member = if data['member']
106
+ member_data = data['author'].merge(data['member'])
107
+ Member.new(member_data, bot)
108
+ else
109
+ @bot.ensure_user(data['author'])
110
+ end
111
+ end
112
+
113
+ member
114
+ end
115
+ end
116
+
117
+ @webhook_id = data['webhook_id'].to_i if data['webhook_id']
118
+
119
+ @timestamp = Time.parse(data['timestamp']) if data['timestamp']
120
+ @edited_timestamp = data['edited_timestamp'].nil? ? nil : Time.parse(data['edited_timestamp'])
121
+ @edited = !@edited_timestamp.nil?
122
+ @id = data['id'].to_i
123
+
124
+ @emoji = []
125
+
126
+ @reactions = []
127
+
128
+ data['reactions']&.each do |element|
129
+ @reactions << Reaction.new(element)
130
+ end
131
+
132
+ @mentions = []
133
+
134
+ data['mentions']&.each do |element|
135
+ @mentions << bot.ensure_user(element)
136
+ end
137
+
138
+ @role_mentions = []
139
+
140
+ # Role mentions can only happen on public servers so make sure we only parse them there
141
+ if @channel.text?
142
+ data['mention_roles']&.each do |element|
143
+ @role_mentions << @channel.server.role(element.to_i)
144
+ end
145
+ end
146
+
147
+ @attachments = []
148
+ @attachments = data['attachments'].map { |e| Attachment.new(e, self, @bot) } if data['attachments']
149
+
150
+ @embeds = []
151
+ @embeds = data['embeds'].map { |e| Embed.new(e, self) } if data['embeds']
152
+ end
153
+
154
+ # Replies to this message with the specified content.
155
+ # @deprecated Please use {#respond}.
156
+ # @see Channel#send_message
157
+ def reply(content)
158
+ @channel.send_message(content)
159
+ end
160
+
161
+ # Sends a message to this channel.
162
+ # @param content [String] The content to send. Should not be longer than 2000 characters or it will result in an error.
163
+ # @param tts [true, false] Whether or not this message should be sent using Discord text-to-speech.
164
+ # @param embed [Hash, Discordrb::Webhooks::Embed, nil] The rich embed to append to this message.
165
+ # @param attachments [Array<File>] Files that can be referenced in embeds via `attachment://file.png`
166
+ # @param allowed_mentions [Hash, Discordrb::AllowedMentions, false, nil] Mentions that are allowed to ping on this message. `false` disables all pings
167
+ # @param mention_user [true, false] Whether the user that is being replied to should be pinged by the reply.
168
+ # @return [Message] the message that was sent.
169
+ def reply!(content, tts: false, embed: nil, attachments: nil, allowed_mentions: {}, mention_user: false)
170
+ allowed_mentions = { parse: [] } if allowed_mentions == false
171
+ allowed_mentions = allowed_mentions.to_hash.transform_keys(&:to_sym)
172
+ allowed_mentions[:replied_user] = mention_user
173
+
174
+ respond(content, tts, embed, attachments, allowed_mentions, self)
175
+ end
176
+
177
+ # (see Channel#send_message)
178
+ def respond(content, tts = false, embed = nil, attachments = nil, allowed_mentions = nil, message_reference = nil)
179
+ @channel.send_message(content, tts, embed, attachments, allowed_mentions, message_reference)
180
+ end
181
+
182
+ # Edits this message to have the specified content instead.
183
+ # You can only edit your own messages.
184
+ # @param new_content [String] the new content the message should have.
185
+ # @param new_embed [Hash, Discordrb::Webhooks::Embed, nil] The new embed the message should have. If `nil` the message will be changed to have no embed.
186
+ # @return [Message] the resulting message.
187
+ def edit(new_content, new_embed = nil)
188
+ response = API::Channel.edit_message(@bot.token, @channel.id, @id, new_content, [], new_embed ? new_embed.to_hash : nil)
189
+ Message.new(JSON.parse(response), @bot)
190
+ end
191
+
192
+ # Deletes this message.
193
+ def delete(reason = nil)
194
+ API::Channel.delete_message(@bot.token, @channel.id, @id, reason)
195
+ nil
196
+ end
197
+
198
+ # Pins this message
199
+ def pin(reason = nil)
200
+ API::Channel.pin_message(@bot.token, @channel.id, @id, reason)
201
+ @pinned = true
202
+ nil
203
+ end
204
+
205
+ # Unpins this message
206
+ def unpin(reason = nil)
207
+ API::Channel.unpin_message(@bot.token, @channel.id, @id, reason)
208
+ @pinned = false
209
+ nil
210
+ end
211
+
212
+ # Add an {Await} for a message with the same user and channel.
213
+ # @see Bot#add_await
214
+ # @deprecated Will be changed to blocking behavior in v4.0. Use {#await!} instead.
215
+ def await(key, attributes = {}, &block)
216
+ @bot.add_await(key, Discordrb::Events::MessageEvent, { from: @author.id, in: @channel.id }.merge(attributes), &block)
217
+ end
218
+
219
+ # Add a blocking {Await} for a message with the same user and channel.
220
+ # @see Bot#add_await!
221
+ def await!(attributes = {}, &block)
222
+ @bot.add_await!(Discordrb::Events::MessageEvent, { from: @author.id, in: @channel.id }.merge(attributes), &block)
223
+ end
224
+
225
+ # @return [true, false] whether this message was sent by the current {Bot}.
226
+ def from_bot?
227
+ @author&.current_bot?
228
+ end
229
+
230
+ # @return [true, false] whether this message has been sent over a webhook.
231
+ def webhook?
232
+ !@webhook_id.nil?
233
+ end
234
+
235
+ # @return [Array<Emoji>] the emotes that were used/mentioned in this message.
236
+ def emoji
237
+ return if @content.nil?
238
+ return @emoji unless @emoji.empty?
239
+
240
+ @emoji = @bot.parse_mentions(@content).select { |el| el.is_a? Discordrb::Emoji }
241
+ end
242
+
243
+ # Check if any emoji were used in this message.
244
+ # @return [true, false] whether or not any emoji were used
245
+ def emoji?
246
+ emoji&.empty?
247
+ end
248
+
249
+ # Check if any reactions were used in this message.
250
+ # @return [true, false] whether or not this message has reactions
251
+ def reactions?
252
+ !@reactions.empty?
253
+ end
254
+
255
+ # Returns the reactions made by the current bot or user.
256
+ # @return [Array<Reaction>] the reactions
257
+ def my_reactions
258
+ @reactions.select(&:me)
259
+ end
260
+
261
+ # Reacts to a message.
262
+ # @param reaction [String, #to_reaction] the unicode emoji or {Emoji}
263
+ def create_reaction(reaction)
264
+ reaction = reaction.to_reaction if reaction.respond_to?(:to_reaction)
265
+ API::Channel.create_reaction(@bot.token, @channel.id, @id, reaction)
266
+ nil
267
+ end
268
+
269
+ alias_method :react, :create_reaction
270
+
271
+ # Returns the list of users who reacted with a certain reaction.
272
+ # @param reaction [String, #to_reaction] the unicode emoji or {Emoji}
273
+ # @param limit [Integer] the limit of how many users to retrieve. `nil` will return all users
274
+ # @example Get all the users that reacted with a thumbs up.
275
+ # thumbs_up_reactions = message.reacted_with("\u{1F44D}")
276
+ # @return [Array<User>] the users who used this reaction
277
+ def reacted_with(reaction, limit: 100)
278
+ reaction = reaction.to_reaction if reaction.respond_to?(:to_reaction)
279
+ paginator = Paginator.new(limit, :down) do |last_page|
280
+ after_id = last_page.last.id if last_page
281
+ last_page = JSON.parse(API::Channel.get_reactions(@bot.token, @channel.id, @id, reaction, nil, after_id, limit))
282
+ last_page.map { |d| User.new(d, @bot) }
283
+ end
284
+ paginator.to_a
285
+ end
286
+
287
+ # Deletes a reaction made by a user on this message.
288
+ # @param user [User, String, Integer] the user or user ID who used this reaction
289
+ # @param reaction [String, #to_reaction] the reaction to remove
290
+ def delete_reaction(user, reaction)
291
+ reaction = reaction.to_reaction if reaction.respond_to?(:to_reaction)
292
+ API::Channel.delete_user_reaction(@bot.token, @channel.id, @id, reaction, user.resolve_id)
293
+ end
294
+
295
+ # Deletes this client's reaction on this message.
296
+ # @param reaction [String, #to_reaction] the reaction to remove
297
+ def delete_own_reaction(reaction)
298
+ reaction = reaction.to_reaction if reaction.respond_to?(:to_reaction)
299
+ API::Channel.delete_own_reaction(@bot.token, @channel.id, @id, reaction)
300
+ end
301
+
302
+ # Removes all reactions from this message.
303
+ def delete_all_reactions
304
+ API::Channel.delete_all_reactions(@bot.token, @channel.id, @id)
305
+ end
306
+
307
+ # The inspect method is overwritten to give more useful output
308
+ def inspect
309
+ "<Message content=\"#{@content}\" id=#{@id} timestamp=#{@timestamp} author=#{@author} channel=#{@channel}>"
310
+ end
311
+
312
+ # @return [String] a URL that a user can use to navigate to this message in the client
313
+ def link
314
+ "https://discord.com/channels/#{@server&.id || '@me'}/#{@channel.id}/#{@id}"
315
+ end
316
+
317
+ alias_method :jump_link, :link
318
+
319
+ # Whether or not this message was sent in reply to another message
320
+ # @return [true, false]
321
+ def reply?
322
+ !@referenced_message.nil?
323
+ end
324
+
325
+ # @return [Message, nil] the Message this Message was sent in reply to.
326
+ def referenced_message
327
+ return @referenced_message if @referenced_message
328
+ return nil unless @message_reference
329
+
330
+ referenced_channel = @bot.channel(@message_reference['channel_id'])
331
+ @referenced_message = referenced_channel.message(@message_reference['message_id'])
332
+ end
333
+ end
334
+ end