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,414 @@
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 [Integer] what the type of the message is
65
+ attr_reader :type
66
+
67
+ # @return [Server, nil] the server in which this message was sent.
68
+ attr_reader :server
69
+
70
+ # @return [Integer, nil] the webhook ID that sent this message, or `nil` if it wasn't sent through a webhook.
71
+ attr_reader :webhook_id
72
+
73
+ # @return [Array<Component>]
74
+ attr_reader :components
75
+
76
+ # @!visibility private
77
+ def initialize(data, bot)
78
+ @bot = bot
79
+ @content = data['content']
80
+ @channel = bot.channel(data['channel_id'].to_i)
81
+ @pinned = data['pinned']
82
+ @type = data['type']
83
+ @tts = data['tts']
84
+ @nonce = data['nonce']
85
+ @mention_everyone = data['mention_everyone']
86
+
87
+ @referenced_message = Message.new(data['referenced_message'], bot) if data['referenced_message']
88
+ @message_reference = data['message_reference']
89
+
90
+ @server = @channel.server
91
+
92
+ @webhook_id = data['webhook_id']&.to_i
93
+
94
+ @author = if data['author']
95
+ if @webhook_id
96
+ # This is a webhook user! It would be pointless to try to resolve a member here, so we just create
97
+ # a User and return that instead.
98
+ Discordrb::LOGGER.debug("Webhook user: #{data['author']['id']}")
99
+ User.new(data['author'].merge({ '_webhook' => true }), @bot)
100
+ elsif @channel.private?
101
+ # Turn the message user into a recipient - we can't use the channel recipient
102
+ # directly because the bot may also send messages to the channel
103
+ Recipient.new(bot.user(data['author']['id'].to_i), @channel, bot)
104
+ else
105
+ member = @channel.server.member(data['author']['id'].to_i)
106
+
107
+ if member
108
+ member.update_data(data['member']) if data['member']
109
+ member.update_global_name(data['author']['global_name']) if data['author']['global_name']
110
+ else
111
+ Discordrb::LOGGER.debug("Member with ID #{data['author']['id']} not cached (possibly left the server).")
112
+ member = if data['member']
113
+ member_data = data['author'].merge(data['member'])
114
+ Member.new(member_data, @server, bot)
115
+ else
116
+ @bot.ensure_user(data['author'])
117
+ end
118
+ end
119
+
120
+ member
121
+ end
122
+ end
123
+
124
+ @timestamp = Time.parse(data['timestamp']) if data['timestamp']
125
+ @edited_timestamp = data['edited_timestamp'].nil? ? nil : Time.parse(data['edited_timestamp'])
126
+ @edited = !@edited_timestamp.nil?
127
+ @id = data['id'].to_i
128
+
129
+ @emoji = []
130
+
131
+ @reactions = []
132
+
133
+ data['reactions']&.each do |element|
134
+ @reactions << Reaction.new(element)
135
+ end
136
+
137
+ @mentions = []
138
+
139
+ data['mentions']&.each do |element|
140
+ @mentions << bot.ensure_user(element)
141
+ end
142
+
143
+ @role_mentions = []
144
+
145
+ # Role mentions can only happen on public servers so make sure we only parse them there
146
+ if @channel.text?
147
+ data['mention_roles']&.each do |element|
148
+ @role_mentions << @channel.server.role(element.to_i)
149
+ end
150
+ end
151
+
152
+ @attachments = []
153
+ @attachments = data['attachments'].map { |e| Attachment.new(e, self, @bot) } if data['attachments']
154
+
155
+ @embeds = []
156
+ @embeds = data['embeds'].map { |e| Embed.new(e, self) } if data['embeds']
157
+
158
+ @components = []
159
+ @components = data['components'].map { |component_data| Components.from_data(component_data, @bot) } if data['components']
160
+ end
161
+
162
+ # Replies to this message with the specified content.
163
+ # @deprecated Please use {#respond}.
164
+ # @param content [String] The content to send. Should not be longer than 2000 characters or it will result in an error.
165
+ # @return (see #respond)
166
+ # @see Channel#send_message
167
+ def reply(content)
168
+ @channel.send_message(content)
169
+ end
170
+
171
+ # Responds to this message as an inline reply.
172
+ # @param content [String] The content to send. Should not be longer than 2000 characters or it will result in an error.
173
+ # @param tts [true, false] Whether or not this message should be sent using Discord text-to-speech.
174
+ # @param embed [Hash, Discordrb::Webhooks::Embed, nil] The rich embed to append to this message.
175
+ # @param attachments [Array<File>] Files that can be referenced in embeds via `attachment://file.png`
176
+ # @param allowed_mentions [Hash, Discordrb::AllowedMentions, false, nil] Mentions that are allowed to ping on this message. `false` disables all pings
177
+ # @param mention_user [true, false] Whether the user that is being replied to should be pinged by the reply.
178
+ # @param components [View, Array<Hash>] Interaction components to associate with this message.
179
+ # @return (see #respond)
180
+ def reply!(content, tts: false, embed: nil, attachments: nil, allowed_mentions: {}, mention_user: false, components: nil)
181
+ allowed_mentions = { parse: [] } if allowed_mentions == false
182
+ allowed_mentions = allowed_mentions.to_hash.transform_keys(&:to_sym)
183
+ allowed_mentions[:replied_user] = mention_user
184
+
185
+ respond(content, tts, embed, attachments, allowed_mentions, self, components)
186
+ end
187
+
188
+ # (see Channel#send_message)
189
+ def respond(content, tts = false, embed = nil, attachments = nil, allowed_mentions = nil, message_reference = nil, components = nil)
190
+ @channel.send_message(content, tts, embed, attachments, allowed_mentions, message_reference, components)
191
+ end
192
+
193
+ # Edits this message to have the specified content instead.
194
+ # You can only edit your own messages.
195
+ # @param new_content [String] the new content the message should have.
196
+ # @param new_embeds [Hash, Discordrb::Webhooks::Embed, Array<Hash>, Array<Discordrb::Webhooks::Embed>, nil] The new embeds the message should have. If `nil` the message will be changed to have no embeds.
197
+ # @param new_components [View, Array<Hash>] The new components the message should have. If `nil` the message will be changed to have no components.
198
+ # @return [Message] the resulting message.
199
+ def edit(new_content, new_embeds = nil, new_components = nil)
200
+ new_embeds = (new_embeds.instance_of?(Array) ? new_embeds.map(&:to_hash) : [new_embeds&.to_hash]).compact
201
+ new_components = new_components&.to_a || []
202
+
203
+ response = API::Channel.edit_message(@bot.token, @channel.id, @id, new_content, [], new_embeds, new_components)
204
+ Message.new(JSON.parse(response), @bot)
205
+ end
206
+
207
+ # Deletes this message.
208
+ def delete(reason = nil)
209
+ API::Channel.delete_message(@bot.token, @channel.id, @id, reason)
210
+ nil
211
+ end
212
+
213
+ # Pins this message
214
+ def pin(reason = nil)
215
+ API::Channel.pin_message(@bot.token, @channel.id, @id, reason)
216
+ @pinned = true
217
+ nil
218
+ end
219
+
220
+ # Unpins this message
221
+ def unpin(reason = nil)
222
+ API::Channel.unpin_message(@bot.token, @channel.id, @id, reason)
223
+ @pinned = false
224
+ nil
225
+ end
226
+
227
+ # Add an {Await} for a message with the same user and channel.
228
+ # @see Bot#add_await
229
+ # @deprecated Will be changed to blocking behavior in v4.0. Use {#await!} instead.
230
+ def await(key, attributes = {}, &block)
231
+ @bot.add_await(key, Discordrb::Events::MessageEvent, { from: @author.id, in: @channel.id }.merge(attributes), &block)
232
+ end
233
+
234
+ # Add a blocking {Await} for a message with the same user and channel.
235
+ # @see Bot#add_await!
236
+ def await!(attributes = {}, &block)
237
+ @bot.add_await!(Discordrb::Events::MessageEvent, { from: @author.id, in: @channel.id }.merge(attributes), &block)
238
+ end
239
+
240
+ # Add an {Await} for a reaction to be added on this message.
241
+ # @see Bot#add_await
242
+ # @deprecated Will be changed to blocking behavior in v4.0. Use {#await_reaction!} instead.
243
+ def await_reaction(key, attributes = {}, &block)
244
+ @bot.add_await(key, Discordrb::Events::ReactionAddEvent, { message: @id }.merge(attributes), &block)
245
+ end
246
+
247
+ # Add a blocking {Await} for a reaction to be added on this message.
248
+ # @see Bot#add_await!
249
+ def await_reaction!(attributes = {}, &block)
250
+ @bot.add_await!(Discordrb::Events::ReactionAddEvent, { message: @id }.merge(attributes), &block)
251
+ end
252
+
253
+ # @return [true, false] whether this message was sent by the current {Bot}.
254
+ def from_bot?
255
+ @author&.current_bot?
256
+ end
257
+
258
+ # @return [true, false] whether this message has been sent over a webhook.
259
+ def webhook?
260
+ !@webhook_id.nil?
261
+ end
262
+
263
+ # @return [Array<Emoji>] the emotes that were used/mentioned in this message.
264
+ def emoji
265
+ return if @content.nil?
266
+ return @emoji unless @emoji.empty?
267
+
268
+ @emoji = @bot.parse_mentions(@content).select { |el| el.is_a? Discordrb::Emoji }
269
+ end
270
+
271
+ # Check if any emoji were used in this message.
272
+ # @return [true, false] whether or not any emoji were used
273
+ def emoji?
274
+ emoji&.empty?
275
+ end
276
+
277
+ # Check if any reactions were used in this message.
278
+ # @return [true, false] whether or not this message has reactions
279
+ def reactions?
280
+ !@reactions.empty?
281
+ end
282
+
283
+ # Returns the reactions made by the current bot or user.
284
+ # @return [Array<Reaction>] the reactions
285
+ def my_reactions
286
+ @reactions.select(&:me)
287
+ end
288
+
289
+ # Reacts to a message.
290
+ # @param reaction [String, #to_reaction] the unicode emoji or {Emoji}
291
+ def create_reaction(reaction)
292
+ reaction = reaction.to_reaction if reaction.respond_to?(:to_reaction)
293
+ API::Channel.create_reaction(@bot.token, @channel.id, @id, reaction)
294
+ nil
295
+ end
296
+
297
+ alias_method :react, :create_reaction
298
+
299
+ # Returns the list of users who reacted with a certain reaction.
300
+ # @param reaction [String, #to_reaction] the unicode emoji or {Emoji}
301
+ # @param limit [Integer] the limit of how many users to retrieve. `nil` will return all users
302
+ # @example Get all the users that reacted with a thumbs up.
303
+ # thumbs_up_reactions = message.reacted_with("\u{1F44D}")
304
+ # @return [Array<User>] the users who used this reaction
305
+ def reacted_with(reaction, limit: 100)
306
+ reaction = reaction.to_reaction if reaction.respond_to?(:to_reaction)
307
+ reaction = reaction.to_s if reaction.respond_to?(:to_s)
308
+
309
+ get_reactions = proc do |fetch_limit, after_id = nil|
310
+ resp = API::Channel.get_reactions(@bot.token, @channel.id, @id, reaction, nil, after_id, fetch_limit)
311
+ return JSON.parse(resp).map { |d| User.new(d, @bot) }
312
+ end
313
+
314
+ # Can be done without pagination
315
+ return get_reactions.call(limit) if limit && limit <= 100
316
+
317
+ paginator = Paginator.new(limit, :down) do |last_page|
318
+ if last_page && last_page.count < 100
319
+ []
320
+ else
321
+ get_reactions.call(100, last_page&.last&.id)
322
+ end
323
+ end
324
+
325
+ paginator.to_a
326
+ end
327
+
328
+ # Returns a hash of all reactions to a message as keys and the users that reacted to it as values.
329
+ # @param limit [Integer] the limit of how many users to retrieve per distinct reaction emoji. `nil` will return all users
330
+ # @example Get all the users that reacted to a message for a giveaway.
331
+ # giveaway_participants = message.all_reaction_users
332
+ # @return [Hash<String => Array<User>>] A hash mapping the string representation of a
333
+ # reaction to an array of users.
334
+ def all_reaction_users(limit: 100)
335
+ all_reactions = @reactions.map { |r| { r.to_s => reacted_with(r, limit: limit) } }
336
+ all_reactions.reduce({}, :merge)
337
+ end
338
+
339
+ # Deletes a reaction made by a user on this message.
340
+ # @param user [User, String, Integer] the user or user ID who used this reaction
341
+ # @param reaction [String, #to_reaction] the reaction to remove
342
+ def delete_reaction(user, reaction)
343
+ reaction = reaction.to_reaction if reaction.respond_to?(:to_reaction)
344
+ API::Channel.delete_user_reaction(@bot.token, @channel.id, @id, reaction, user.resolve_id)
345
+ end
346
+
347
+ # Deletes this client's reaction on this message.
348
+ # @param reaction [String, #to_reaction] the reaction to remove
349
+ def delete_own_reaction(reaction)
350
+ reaction = reaction.to_reaction if reaction.respond_to?(:to_reaction)
351
+ API::Channel.delete_own_reaction(@bot.token, @channel.id, @id, reaction)
352
+ end
353
+
354
+ # Removes all reactions from this message.
355
+ def delete_all_reactions
356
+ API::Channel.delete_all_reactions(@bot.token, @channel.id, @id)
357
+ end
358
+
359
+ # The inspect method is overwritten to give more useful output
360
+ def inspect
361
+ "<Message content=\"#{@content}\" id=#{@id} timestamp=#{@timestamp} author=#{@author} channel=#{@channel}>"
362
+ end
363
+
364
+ # @return [String] a URL that a user can use to navigate to this message in the client
365
+ def link
366
+ "https://discord.com/channels/#{@server&.id || '@me'}/#{@channel.id}/#{@id}"
367
+ end
368
+
369
+ alias_method :jump_link, :link
370
+
371
+ # Whether or not this message was sent in reply to another message
372
+ # @return [true, false]
373
+ def reply?
374
+ !@referenced_message.nil?
375
+ end
376
+
377
+ # Whether or not this message was of type "CHAT_INPUT_COMMAND"
378
+ # @return [true, false]
379
+ def chat_input_command?
380
+ @type == 20
381
+ end
382
+
383
+ # @return [Message, nil] the Message this Message was sent in reply to.
384
+ def referenced_message
385
+ return @referenced_message if @referenced_message
386
+ return nil unless @message_reference
387
+
388
+ referenced_channel = @bot.channel(@message_reference['channel_id'])
389
+ @referenced_message = referenced_channel.message(@message_reference['message_id'])
390
+ end
391
+
392
+ # @return [Array<Components::Button>]
393
+ def buttons
394
+ results = @components.collect do |component|
395
+ case component
396
+ when Components::Button
397
+ component
398
+ when Components::ActionRow
399
+ component.buttons
400
+ end
401
+ end
402
+
403
+ results.flatten.compact
404
+ end
405
+
406
+ # to_message -> self or message
407
+ # @return [Discordrb::Message]
408
+ def to_message
409
+ self
410
+ end
411
+
412
+ alias_method :message, :to_message
413
+ end
414
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discordrb
4
+ # A permissions overwrite, when applied to channels describes additional
5
+ # permissions a member needs to perform certain actions in context.
6
+ class Overwrite
7
+ # Types of overwrites mapped to their API value.
8
+ TYPES = {
9
+ role: 0,
10
+ member: 1
11
+ }.freeze
12
+
13
+ # @return [Integer] ID of the thing associated with this overwrite type
14
+ attr_accessor :id
15
+
16
+ # @return [Symbol] either :role or :member
17
+ attr_accessor :type
18
+
19
+ # @return [Permissions] allowed permissions for this overwrite type
20
+ attr_accessor :allow
21
+
22
+ # @return [Permissions] denied permissions for this overwrite type
23
+ attr_accessor :deny
24
+
25
+ # Creates a new Overwrite object
26
+ # @example Create an overwrite for a role that can mention everyone, send TTS messages, but can't create instant invites
27
+ # allow = Discordrb::Permissions.new
28
+ # allow.can_mention_everyone = true
29
+ # allow.can_send_tts_messages = true
30
+ #
31
+ # deny = Discordrb::Permissions.new
32
+ # deny.can_create_instant_invite = true
33
+ #
34
+ # # Find some role by name
35
+ # role = server.roles.find { |r| r.name == 'some role' }
36
+ #
37
+ # Overwrite.new(role, allow: allow, deny: deny)
38
+ # @example Create an overwrite by ID and permissions bits
39
+ # Overwrite.new(120571255635181568, type: 'member', allow: 1024, deny: 0)
40
+ # @param object [Integer, #id] the ID or object this overwrite is for
41
+ # @param type [String, Symbol, Integer] the type of object this overwrite is for (only required if object is an Integer)
42
+ # @param allow [String, Integer, Permissions] allowed permissions for this overwrite, by bits or a Permissions object
43
+ # @param deny [String, Integer, Permissions] denied permissions for this overwrite, by bits or a Permissions object
44
+ # @raise [ArgumentError] if type is not :member or :role
45
+ def initialize(object = nil, type: nil, allow: 0, deny: 0)
46
+ if type
47
+ type = TYPES.value?(type) ? TYPES.key(type) : type.to_sym
48
+ raise ArgumentError, 'Overwrite type must be :member or :role' unless type
49
+ end
50
+
51
+ @id = object.respond_to?(:id) ? object.id : object
52
+
53
+ @type = case object
54
+ when User, Member, Recipient, Profile
55
+ :member
56
+ when Role
57
+ :role
58
+ else
59
+ type
60
+ end
61
+
62
+ @allow = allow.is_a?(Permissions) ? allow : Permissions.new(allow)
63
+ @deny = deny.is_a?(Permissions) ? deny : Permissions.new(deny)
64
+ end
65
+
66
+ # Comparison by attributes [:id, :type, :allow, :deny]
67
+ def ==(other)
68
+ false unless other.is_a? Discordrb::Overwrite
69
+ id == other.id &&
70
+ type == other.type &&
71
+ allow == other.allow &&
72
+ deny == other.deny
73
+ end
74
+
75
+ # @return [Overwrite] create an overwrite from a hash payload
76
+ # @!visibility private
77
+ def self.from_hash(data)
78
+ new(
79
+ data['id'].to_i,
80
+ type: TYPES.key(data['type']),
81
+ allow: Permissions.new(data['allow']),
82
+ deny: Permissions.new(data['deny'])
83
+ )
84
+ end
85
+
86
+ # @return [Overwrite] copies an overwrite from another Overwrite
87
+ # @!visibility private
88
+ def self.from_other(other)
89
+ new(
90
+ other.id,
91
+ type: other.type,
92
+ allow: Permissions.new(other.allow.bits),
93
+ deny: Permissions.new(other.deny.bits)
94
+ )
95
+ end
96
+
97
+ # @return [Hash] hash representation of an overwrite
98
+ # @!visibility private
99
+ def to_hash
100
+ {
101
+ id: id,
102
+ type: TYPES[type],
103
+ allow: allow.bits,
104
+ deny: deny.bits
105
+ }
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discordrb
4
+ # This class is a special variant of User that represents the bot's user profile (things like own username and the avatar).
5
+ # It can be accessed using {Bot#profile}.
6
+ class Profile < User
7
+ # Whether or not the user is the bot. The Profile can only ever be the bot user, so this always returns true.
8
+ # @return [true]
9
+ def current_bot?
10
+ true
11
+ end
12
+
13
+ # Sets the bot's username.
14
+ # @param username [String] The new username.
15
+ def username=(username)
16
+ update_profile_data(username: username)
17
+ end
18
+
19
+ alias_method :name=, :username=
20
+
21
+ # Changes the bot's avatar.
22
+ # @param avatar [String, #read] A JPG file to be used as the avatar, either
23
+ # something readable (e.g. File Object) or as a data URL.
24
+ def avatar=(avatar)
25
+ if avatar.respond_to? :read
26
+ # Set the file to binary mode if supported, so we don't get problems with Windows
27
+ avatar.binmode if avatar.respond_to?(:binmode)
28
+
29
+ avatar_string = 'data:image/jpg;base64,'
30
+ avatar_string += Base64.strict_encode64(avatar.read)
31
+ update_profile_data(avatar: avatar_string)
32
+ else
33
+ update_profile_data(avatar: avatar)
34
+ end
35
+ end
36
+
37
+ # Updates the cached profile data with the new one.
38
+ # @note For internal use only.
39
+ # @!visibility private
40
+ def update_data(new_data)
41
+ @username = new_data[:username] || @username
42
+ @avatar_id = new_data[:avatar_id] || @avatar_id
43
+ end
44
+
45
+ # Sets the user status setting to Online.
46
+ # @note Only usable on User accounts.
47
+ def online
48
+ update_profile_status_setting('online')
49
+ end
50
+
51
+ # Sets the user status setting to Idle.
52
+ # @note Only usable on User accounts.
53
+ def idle
54
+ update_profile_status_setting('idle')
55
+ end
56
+
57
+ # Sets the user status setting to Do Not Disturb.
58
+ # @note Only usable on User accounts.
59
+ def dnd
60
+ update_profile_status_setting('dnd')
61
+ end
62
+
63
+ alias_method(:busy, :dnd)
64
+
65
+ # Sets the user status setting to Invisible.
66
+ # @note Only usable on User accounts.
67
+ def invisible
68
+ update_profile_status_setting('invisible')
69
+ end
70
+
71
+ # The inspect method is overwritten to give more useful output
72
+ def inspect
73
+ "<Profile user=#{super}>"
74
+ end
75
+
76
+ private
77
+
78
+ # Internal handler for updating the user's status setting
79
+ def update_profile_status_setting(status)
80
+ API::User.change_status_setting(@bot.token, status)
81
+ end
82
+
83
+ def update_profile_data(new_data)
84
+ API::User.update_profile(@bot.token,
85
+ nil, nil,
86
+ new_data[:username] || @username,
87
+ new_data.key?(:avatar) ? new_data[:avatar] : @avatar_id)
88
+ update_data(new_data)
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discordrb
4
+ # A reaction to a message.
5
+ class Reaction
6
+ # @return [Integer] the amount of users who have reacted with this reaction
7
+ attr_reader :count
8
+
9
+ # @return [true, false] whether the current bot or user used this reaction
10
+ attr_reader :me
11
+ alias_method :me?, :me
12
+
13
+ # @return [Integer] the ID of the emoji, if it was custom
14
+ attr_reader :id
15
+
16
+ # @return [String] the name or unicode representation of the emoji
17
+ attr_reader :name
18
+
19
+ def initialize(data)
20
+ @count = data['count']
21
+ @me = data['me']
22
+ @id = data['emoji']['id'].nil? ? nil : data['emoji']['id'].to_i
23
+ @name = data['emoji']['name']
24
+ end
25
+
26
+ # Converts this Reaction into a string that can be sent back to Discord in other reaction endpoints.
27
+ # If ID is present, it will be rendered into the form of `name:id`.
28
+ # @return [String] the name of this reaction, including the ID if it is a custom emoji
29
+ def to_s
30
+ id.nil? ? name : "#{name}:#{id}"
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discordrb
4
+ # Recipients are members on private channels - they exist for completeness purposes, but all
5
+ # the attributes will be empty.
6
+ class Recipient < DelegateClass(User)
7
+ include MemberAttributes
8
+
9
+ # @return [Channel] the private channel this recipient is the recipient of.
10
+ attr_reader :channel
11
+
12
+ # @!visibility private
13
+ def initialize(user, channel, bot)
14
+ @bot = bot
15
+ @channel = channel
16
+ raise ArgumentError, 'Tried to create a recipient for a public channel!' unless @channel.private?
17
+
18
+ @user = user
19
+ super @user
20
+
21
+ # Member attributes
22
+ @mute = @deaf = @self_mute = @self_deaf = false
23
+ @voice_channel = nil
24
+ @server = nil
25
+ @roles = []
26
+ @joined_at = @channel.creation_time
27
+ end
28
+
29
+ # Overwriting inspect for debug purposes
30
+ def inspect
31
+ "<Recipient user=#{@user.inspect} channel=#{@channel.inspect}>"
32
+ end
33
+ end
34
+ end