discordrb 3.3.0 → 3.4.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +126 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +39 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.md +25 -0
  5. data/.github/pull_request_template.md +37 -0
  6. data/.rubocop.yml +34 -37
  7. data/.travis.yml +5 -6
  8. data/CHANGELOG.md +504 -347
  9. data/Gemfile +2 -0
  10. data/LICENSE.txt +1 -1
  11. data/README.md +61 -79
  12. data/Rakefile +2 -0
  13. data/bin/console +1 -0
  14. data/discordrb-webhooks.gemspec +6 -6
  15. data/discordrb.gemspec +18 -18
  16. data/lib/discordrb/allowed_mentions.rb +36 -0
  17. data/lib/discordrb/api/channel.rb +62 -39
  18. data/lib/discordrb/api/invite.rb +3 -3
  19. data/lib/discordrb/api/server.rb +57 -50
  20. data/lib/discordrb/api/user.rb +9 -8
  21. data/lib/discordrb/api/webhook.rb +6 -6
  22. data/lib/discordrb/api.rb +40 -15
  23. data/lib/discordrb/await.rb +0 -1
  24. data/lib/discordrb/bot.rb +175 -73
  25. data/lib/discordrb/cache.rb +4 -2
  26. data/lib/discordrb/colour_rgb.rb +43 -0
  27. data/lib/discordrb/commands/command_bot.rb +30 -9
  28. data/lib/discordrb/commands/container.rb +20 -23
  29. data/lib/discordrb/commands/parser.rb +18 -18
  30. data/lib/discordrb/commands/rate_limiter.rb +3 -2
  31. data/lib/discordrb/container.rb +77 -17
  32. data/lib/discordrb/data/activity.rb +271 -0
  33. data/lib/discordrb/data/application.rb +50 -0
  34. data/lib/discordrb/data/attachment.rb +56 -0
  35. data/lib/discordrb/data/audit_logs.rb +345 -0
  36. data/lib/discordrb/data/channel.rb +849 -0
  37. data/lib/discordrb/data/embed.rb +251 -0
  38. data/lib/discordrb/data/emoji.rb +82 -0
  39. data/lib/discordrb/data/integration.rb +83 -0
  40. data/lib/discordrb/data/invite.rb +137 -0
  41. data/lib/discordrb/data/member.rb +297 -0
  42. data/lib/discordrb/data/message.rb +334 -0
  43. data/lib/discordrb/data/overwrite.rb +102 -0
  44. data/lib/discordrb/data/profile.rb +91 -0
  45. data/lib/discordrb/data/reaction.rb +33 -0
  46. data/lib/discordrb/data/recipient.rb +34 -0
  47. data/lib/discordrb/data/role.rb +191 -0
  48. data/lib/discordrb/data/server.rb +1002 -0
  49. data/lib/discordrb/data/user.rb +204 -0
  50. data/lib/discordrb/data/voice_region.rb +45 -0
  51. data/lib/discordrb/data/voice_state.rb +41 -0
  52. data/lib/discordrb/data/webhook.rb +145 -0
  53. data/lib/discordrb/data.rb +25 -4180
  54. data/lib/discordrb/errors.rb +2 -1
  55. data/lib/discordrb/events/bans.rb +7 -5
  56. data/lib/discordrb/events/channels.rb +2 -0
  57. data/lib/discordrb/events/guilds.rb +16 -9
  58. data/lib/discordrb/events/invites.rb +125 -0
  59. data/lib/discordrb/events/members.rb +6 -2
  60. data/lib/discordrb/events/message.rb +69 -27
  61. data/lib/discordrb/events/presence.rb +14 -4
  62. data/lib/discordrb/events/raw.rb +1 -3
  63. data/lib/discordrb/events/reactions.rb +49 -3
  64. data/lib/discordrb/events/typing.rb +6 -4
  65. data/lib/discordrb/events/voice_server_update.rb +47 -0
  66. data/lib/discordrb/events/voice_state_update.rb +15 -10
  67. data/lib/discordrb/events/webhooks.rb +9 -6
  68. data/lib/discordrb/gateway.rb +72 -57
  69. data/lib/discordrb/id_object.rb +39 -0
  70. data/lib/discordrb/light/integrations.rb +1 -1
  71. data/lib/discordrb/light/light_bot.rb +1 -1
  72. data/lib/discordrb/logger.rb +4 -4
  73. data/lib/discordrb/paginator.rb +57 -0
  74. data/lib/discordrb/permissions.rb +103 -8
  75. data/lib/discordrb/version.rb +1 -1
  76. data/lib/discordrb/voice/encoder.rb +16 -7
  77. data/lib/discordrb/voice/network.rb +84 -43
  78. data/lib/discordrb/voice/sodium.rb +96 -0
  79. data/lib/discordrb/voice/voice_bot.rb +34 -26
  80. data/lib/discordrb.rb +73 -0
  81. metadata +98 -60
  82. /data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +0 -0
@@ -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 = @channel.server
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