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,849 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Discordrb
4
+ # A Discord channel, including data like the topic
5
+ class Channel
6
+ include IDObject
7
+
8
+ # Map of channel types
9
+ TYPES = {
10
+ text: 0,
11
+ dm: 1,
12
+ voice: 2,
13
+ group: 3,
14
+ category: 4,
15
+ news: 5,
16
+ store: 6
17
+ }.freeze
18
+
19
+ # @return [String] this channel's name.
20
+ attr_reader :name
21
+
22
+ # @return [Server, nil] the server this channel is on. If this channel is a PM channel, it will be nil.
23
+ attr_reader :server
24
+
25
+ # @return [Integer, nil] the ID of the parent channel, if this channel is inside a category
26
+ attr_reader :parent_id
27
+
28
+ # @return [Integer] the type of this channel
29
+ # @see TYPES
30
+ attr_reader :type
31
+
32
+ # @return [Integer, nil] the ID of the owner of the group channel or nil if this is not a group channel.
33
+ attr_reader :owner_id
34
+
35
+ # @return [Array<Recipient>, nil] the array of recipients of the private messages, or nil if this is not a Private channel
36
+ attr_reader :recipients
37
+
38
+ # @return [String] the channel's topic
39
+ attr_reader :topic
40
+
41
+ # @return [Integer] the bitrate (in bps) of the channel
42
+ attr_reader :bitrate
43
+
44
+ # @return [Integer] the amount of users that can be in the channel. `0` means it is unlimited.
45
+ attr_reader :user_limit
46
+ alias_method :limit, :user_limit
47
+
48
+ # @return [Integer] the channel's position on the channel list
49
+ attr_reader :position
50
+
51
+ # @return [true, false] if this channel is marked as nsfw
52
+ attr_reader :nsfw
53
+ alias_method :nsfw?, :nsfw
54
+
55
+ # @return [Integer] the amount of time (in seconds) users need to wait to send in between messages.
56
+ attr_reader :rate_limit_per_user
57
+ alias_method :slowmode_rate, :rate_limit_per_user
58
+
59
+ # @return [true, false] whether or not this channel is a PM or group channel.
60
+ def private?
61
+ pm? || group?
62
+ end
63
+
64
+ # @return [String] a string that will mention the channel as a clickable link on Discord.
65
+ def mention
66
+ "<##{@id}>"
67
+ end
68
+
69
+ # @return [Recipient, nil] the recipient of the private messages, or nil if this is not a PM channel
70
+ def recipient
71
+ @recipients.first if pm?
72
+ end
73
+
74
+ # @!visibility private
75
+ def initialize(data, bot, server = nil)
76
+ @bot = bot
77
+ # data is sometimes a Hash and other times an array of Hashes, you only want the last one if it's an array
78
+ data = data[-1] if data.is_a?(Array)
79
+
80
+ @id = data['id'].to_i
81
+ @type = data['type'] || 0
82
+ @topic = data['topic']
83
+ @bitrate = data['bitrate']
84
+ @user_limit = data['user_limit']
85
+ @position = data['position']
86
+ @parent_id = data['parent_id'].to_i if data['parent_id']
87
+
88
+ if private?
89
+ @recipients = []
90
+ data['recipients']&.each do |recipient|
91
+ recipient_user = bot.ensure_user(recipient)
92
+ @recipients << Recipient.new(recipient_user, self, bot)
93
+ end
94
+ if pm?
95
+ @name = @recipients.first.username
96
+ else
97
+ @name = data['name']
98
+ @owner_id = data['owner_id']
99
+ end
100
+ else
101
+ @name = data['name']
102
+ @server = server || bot.server(data['guild_id'].to_i)
103
+ end
104
+
105
+ @nsfw = data['nsfw'] || false
106
+ @rate_limit_per_user = data['rate_limit_per_user'] || 0
107
+
108
+ process_permission_overwrites(data['permission_overwrites'])
109
+ end
110
+
111
+ # @return [true, false] whether or not this channel is a text channel
112
+ def text?
113
+ @type.zero?
114
+ end
115
+
116
+ # @return [true, false] whether or not this channel is a PM channel.
117
+ def pm?
118
+ @type == 1
119
+ end
120
+
121
+ # @return [true, false] whether or not this channel is a voice channel.
122
+ def voice?
123
+ @type == 2
124
+ end
125
+
126
+ # @return [true, false] whether or not this channel is a group channel.
127
+ def group?
128
+ @type == 3
129
+ end
130
+
131
+ # @return [true, false] whether or not this channel is a category channel.
132
+ def category?
133
+ @type == 4
134
+ end
135
+
136
+ # @return [true, false] whether or not this channel is a news channel.
137
+ def news?
138
+ @type == 5
139
+ end
140
+
141
+ # @return [true, false] whether or not this channel is a store channel.
142
+ def store?
143
+ @type == 6
144
+ end
145
+
146
+ # @return [Channel, nil] the category channel, if this channel is in a category
147
+ def category
148
+ @bot.channel(@parent_id) if @parent_id
149
+ end
150
+
151
+ alias_method :parent, :category
152
+
153
+ # Sets this channels parent category
154
+ # @param channel [Channel, String, Integer] the target category channel, or its ID
155
+ # @raise [ArgumentError] if the target channel isn't a category
156
+ def category=(channel)
157
+ channel = @bot.channel(channel)
158
+ raise ArgumentError, 'Cannot set parent category to a channel that isn\'t a category' unless channel.category?
159
+
160
+ update_channel_data(parent_id: channel.id)
161
+ end
162
+
163
+ alias_method :parent=, :category=
164
+
165
+ # Sorts this channel's position to follow another channel.
166
+ # @param other [Channel, String, Integer, nil] The channel, or its ID, below which this channel should be sorted. If the given
167
+ # channel is a category, this channel will be sorted at the top of that category. If it is `nil`, the channel will
168
+ # be sorted at the top of the channel list.
169
+ # @param lock_permissions [true, false] Whether the channel's permissions should be synced to the category's
170
+ def sort_after(other = nil, lock_permissions = false)
171
+ raise TypeError, 'other must be one of Channel, NilClass, String, or Integer' unless other.is_a?(Channel) || other.nil? || other.respond_to?(:resolve_id)
172
+
173
+ other = @bot.channel(other.resolve_id) if other
174
+
175
+ # Container for the API request payload
176
+ move_argument = []
177
+
178
+ if other
179
+ raise ArgumentError, 'Can only sort a channel after a channel of the same type!' unless other.category? || (@type == other.type)
180
+
181
+ raise ArgumentError, 'Can only sort a channel after a channel in the same server!' unless other.server == server
182
+
183
+ # Store `others` parent (or if `other` is a category itself)
184
+ parent = if category? && other.category?
185
+ # If we're sorting two categories, there is no new parent
186
+ nil
187
+ elsif other.category?
188
+ # `other` is the category this channel will be moved into
189
+ other
190
+ else
191
+ # `other`'s parent is the category this channel will be
192
+ # moved into (if it exists)
193
+ other.parent
194
+ end
195
+ end
196
+
197
+ # Collect and sort the IDs within the context (category or not) that we
198
+ # need to form our payload with
199
+ ids = if parent
200
+ parent.children
201
+ else
202
+ @server.channels.reject(&:parent_id).select { |c| c.type == @type }
203
+ end.sort_by(&:position).map(&:id)
204
+
205
+ # Move our channel ID after the target ID by deleting it,
206
+ # getting the index of `other`, and inserting it after.
207
+ ids.delete(@id) if ids.include?(@id)
208
+ index = other ? (ids.index { |c| c == other.id } || -1) + 1 : 0
209
+ ids.insert(index, @id)
210
+
211
+ # Generate `move_argument`, making the positions in order from how
212
+ # we have sorted them in the above logic
213
+ ids.each_with_index do |id, pos|
214
+ # These keys are present in each element
215
+ hash = { id: id, position: pos }
216
+
217
+ # Conditionally add `lock_permissions` and `parent_id` if we're
218
+ # iterating past ourselves
219
+ if id == @id
220
+ hash[:lock_permissions] = true if lock_permissions
221
+ hash[:parent_id] = parent.nil? ? nil : parent.id
222
+ end
223
+
224
+ # Add it to the stack
225
+ move_argument << hash
226
+ end
227
+
228
+ API::Server.update_channel_positions(@bot.token, @server.id, move_argument)
229
+ end
230
+
231
+ # Sets whether this channel is NSFW
232
+ # @param nsfw [true, false]
233
+ # @raise [ArgumentError] if value isn't one of true, false
234
+ def nsfw=(nsfw)
235
+ raise ArgumentError, 'nsfw value must be true or false' unless nsfw.is_a?(TrueClass) || nsfw.is_a?(FalseClass)
236
+
237
+ update_channel_data(nsfw: nsfw)
238
+ end
239
+
240
+ # This channel's permission overwrites
241
+ # @overload permission_overwrites
242
+ # The overwrites represented as a hash of role/user ID
243
+ # to an Overwrite object
244
+ # @return [Hash<Integer => Overwrite>] the channel's permission overwrites
245
+ # @overload permission_overwrites(type)
246
+ # Return an array of a certain type of overwrite
247
+ # @param type [Symbol] the kind of overwrite to return
248
+ # @return [Array<Overwrite>]
249
+ def permission_overwrites(type = nil)
250
+ return @permission_overwrites unless type
251
+
252
+ @permission_overwrites.values.select { |e| e.type == type }
253
+ end
254
+
255
+ alias_method :overwrites, :permission_overwrites
256
+
257
+ # Bulk sets this channels permission overwrites
258
+ # @param overwrites [Array<Overwrite>]
259
+ def permission_overwrites=(overwrites)
260
+ update_channel_data(permission_overwrites: overwrites)
261
+ end
262
+
263
+ # Sets the amount of time (in seconds) users have to wait in between sending messages.
264
+ # @param rate [Integer]
265
+ # @raise [ArgumentError] if value isn't between 0 and 120
266
+ def rate_limit_per_user=(rate)
267
+ raise ArgumentError, 'rate_limit_per_user must be between 0 and 120' unless rate.between?(0, 120)
268
+
269
+ update_channel_data(rate_limit_per_user: rate)
270
+ end
271
+
272
+ alias_method :slowmode_rate=, :rate_limit_per_user=
273
+
274
+ # Syncs this channels overwrites with its parent category
275
+ # @raise [RuntimeError] if this channel is not in a category
276
+ def sync_overwrites
277
+ raise 'Cannot sync overwrites on a channel with no parent category' unless parent
278
+
279
+ self.permission_overwrites = parent.permission_overwrites
280
+ end
281
+
282
+ alias_method :sync, :sync_overwrites
283
+
284
+ # @return [true, false, nil] whether this channels permissions match the permission overwrites of the category that it's in, or nil if it is not in a category
285
+ def synchronized?
286
+ return unless parent
287
+
288
+ permission_overwrites == parent.permission_overwrites
289
+ end
290
+
291
+ alias_method :synced?, :synchronized?
292
+
293
+ # Returns the children of this channel, if it is a category. Otherwise returns an empty array.
294
+ # @return [Array<Channel>]
295
+ def children
296
+ return [] unless category?
297
+
298
+ server.channels.select { |c| c.parent_id == id }
299
+ end
300
+
301
+ alias_method :channels, :children
302
+
303
+ # Returns the text channels in this category, if it is a category channel. Otherwise returns an empty array.
304
+ # @return [Array<Channel>]
305
+ def text_channels
306
+ children.select(&:text?)
307
+ end
308
+
309
+ # Returns the voice channels in this category, if it is a category channel. Otherwise returns an empty array.
310
+ # @return [Array<Channel>]
311
+ def voice_channels
312
+ children.select(&:voice?)
313
+ end
314
+
315
+ # @return [Overwrite] any member-type permission overwrites on this channel
316
+ def member_overwrites
317
+ permission_overwrites :member
318
+ end
319
+
320
+ # @return [Overwrite] any role-type permission overwrites on this channel
321
+ def role_overwrites
322
+ permission_overwrites :role
323
+ end
324
+
325
+ # @return [true, false] whether or not this channel is the default channel
326
+ def default_channel?
327
+ server.default_channel == self
328
+ end
329
+
330
+ alias_method :default?, :default_channel?
331
+
332
+ # @return [true, false] whether or not this channel has slowmode enabled
333
+ def slowmode?
334
+ @rate_limit_per_user != 0
335
+ end
336
+
337
+ # Sends a message to this channel.
338
+ # @param content [String] The content to send. Should not be longer than 2000 characters or it will result in an error.
339
+ # @param tts [true, false] Whether or not this message should be sent using Discord text-to-speech.
340
+ # @param embed [Hash, Discordrb::Webhooks::Embed, nil] The rich embed to append to this message.
341
+ # @param attachments [Array<File>] Files that can be referenced in embeds via `attachment://file.png`
342
+ # @param allowed_mentions [Hash, Discordrb::AllowedMentions, false, nil] Mentions that are allowed to ping on this message. `false` disables all pings
343
+ # @param message_reference [Message, String, Integer, nil] The message, or message ID, to reply to if any.
344
+ # @return [Message] the message that was sent.
345
+ def send_message(content, tts = false, embed = nil, attachments = nil, allowed_mentions = nil, message_reference = nil)
346
+ @bot.send_message(@id, content, tts, embed, attachments, allowed_mentions, message_reference)
347
+ end
348
+
349
+ alias_method :send, :send_message
350
+
351
+ # Sends a temporary message to this channel.
352
+ # @param content [String] The content to send. Should not be longer than 2000 characters or it will result in an error.
353
+ # @param timeout [Float] The amount of time in seconds after which the message sent will be deleted.
354
+ # @param tts [true, false] Whether or not this message should be sent using Discord text-to-speech.
355
+ # @param embed [Hash, Discordrb::Webhooks::Embed, nil] The rich embed to append to this message.
356
+ # @param attachments [Array<File>] Files that can be referenced in embeds via `attachment://file.png`
357
+ # @param allowed_mentions [Hash, Discordrb::AllowedMentions, false, nil] Mentions that are allowed to ping on this message. `false` disables all pings
358
+ # @param message_reference [Message, String, Integer, nil] The message, or message ID, to reply to if any.
359
+ def send_temporary_message(content, timeout, tts = false, embed = nil, attachments = nil, allowed_mentions = nil, message_reference = nil)
360
+ @bot.send_temporary_message(@id, content, timeout, tts, embed, attachments, allowed_mentions, message_reference)
361
+ end
362
+
363
+ # Convenience method to send a message with an embed.
364
+ # @example Send a message with an embed
365
+ # channel.send_embed do |embed|
366
+ # embed.title = 'The Ruby logo'
367
+ # embed.image = Discordrb::Webhooks::EmbedImage.new(url: 'https://www.ruby-lang.org/images/header-ruby-logo.png')
368
+ # end
369
+ # @param message [String] The message that should be sent along with the embed. If this is the empty string, only the embed will be shown.
370
+ # @param embed [Discordrb::Webhooks::Embed, nil] The embed to start the building process with, or nil if one should be created anew.
371
+ # @param attachments [Array<File>] Files that can be referenced in embeds via `attachment://file.png`
372
+ # @param tts [true, false] Whether or not this message should be sent using Discord text-to-speech.
373
+ # @param allowed_mentions [Hash, Discordrb::AllowedMentions, false, nil] Mentions that are allowed to ping on this message. `false` disables all pings
374
+ # @param message_reference [Message, String, Integer, nil] The message, or message ID, to reply to if any.
375
+ # @yield [embed] Yields the embed to allow for easy building inside a block.
376
+ # @yieldparam embed [Discordrb::Webhooks::Embed] The embed from the parameters, or a new one.
377
+ # @return [Message] The resulting message.
378
+ def send_embed(message = '', embed = nil, attachments = nil, tts = false, allowed_mentions = nil, message_reference = nil)
379
+ embed ||= Discordrb::Webhooks::Embed.new
380
+ yield(embed) if block_given?
381
+ send_message(message, tts, embed, attachments, allowed_mentions, message_reference)
382
+ end
383
+
384
+ # Sends multiple messages to a channel
385
+ # @param content [Array<String>] The messages to send.
386
+ def send_multiple(content)
387
+ content.each { |e| send_message(e) }
388
+ end
389
+
390
+ # Splits a message into chunks whose length is at most the Discord character limit, then sends them individually.
391
+ # Useful for sending long messages, but be wary of rate limits!
392
+ def split_send(content)
393
+ send_multiple(Discordrb.split_message(content))
394
+ nil
395
+ end
396
+
397
+ # Sends a file to this channel. If it is an image, it will be embedded.
398
+ # @param file [File] The file to send. There's no clear size limit for this, you'll have to attempt it for yourself (most non-image files are fine, large images may fail to embed)
399
+ # @param caption [string] The caption for the file.
400
+ # @param tts [true, false] Whether or not this file's caption should be sent using Discord text-to-speech.
401
+ # @param filename [String] Overrides the filename of the uploaded file
402
+ # @param spoiler [true, false] Whether or not this file should appear as a spoiler.
403
+ # @example Send a file from disk
404
+ # channel.send_file(File.open('rubytaco.png', 'r'))
405
+ def send_file(file, caption: nil, tts: false, filename: nil, spoiler: nil)
406
+ @bot.send_file(@id, file, caption: caption, tts: tts, filename: filename, spoiler: spoiler)
407
+ end
408
+
409
+ # Deletes a message on this channel. Mostly useful in case a message needs to be deleted when only the ID is known
410
+ # @param message [Message, String, Integer, String, Integer] The message, or its ID, that should be deleted.
411
+ def delete_message(message)
412
+ API::Channel.delete_message(@bot.token, @id, message.resolve_id)
413
+ end
414
+
415
+ # Permanently deletes this channel
416
+ # @param reason [String] The reason the for the channel deletion.
417
+ def delete(reason = nil)
418
+ API::Channel.delete(@bot.token, @id, reason)
419
+ end
420
+
421
+ # Sets this channel's name. The name must be alphanumeric with dashes, unless this is a voice channel (then there are no limitations)
422
+ # @param name [String] The new name.
423
+ def name=(name)
424
+ update_channel_data(name: name)
425
+ end
426
+
427
+ # Sets this channel's topic.
428
+ # @param topic [String] The new topic.
429
+ def topic=(topic)
430
+ raise 'Tried to set topic on voice channel' if voice?
431
+
432
+ update_channel_data(topic: topic)
433
+ end
434
+
435
+ # Sets this channel's bitrate.
436
+ # @param bitrate [Integer] The new bitrate (in bps). Number has to be between 8000-96000 (128000 for VIP servers)
437
+ def bitrate=(bitrate)
438
+ raise 'Tried to set bitrate on text channel' if text?
439
+
440
+ update_channel_data(bitrate: bitrate)
441
+ end
442
+
443
+ # Sets this channel's user limit.
444
+ # @param limit [Integer] The new user limit. `0` for unlimited, has to be a number between 0-99
445
+ def user_limit=(limit)
446
+ raise 'Tried to set user_limit on text channel' if text?
447
+
448
+ update_channel_data(user_limit: limit)
449
+ end
450
+
451
+ alias_method :limit=, :user_limit=
452
+
453
+ # Sets this channel's position in the list.
454
+ # @param position [Integer] The new position.
455
+ def position=(position)
456
+ update_channel_data(position: position)
457
+ end
458
+
459
+ # Defines a permission overwrite for this channel that sets the specified thing to the specified allow and deny
460
+ # permission sets, or change an existing one.
461
+ # @overload define_overwrite(overwrite)
462
+ # @param thing [Overwrite] an Overwrite object to apply to this channel
463
+ # @param reason [String] The reason the for defining the overwrite.
464
+ # @overload define_overwrite(thing, allow, deny)
465
+ # @param thing [User, Role] What to define an overwrite for.
466
+ # @param allow [#bits, Permissions, Integer] The permission sets that should receive an `allow` override (i.e. a
467
+ # green checkmark on Discord)
468
+ # @param deny [#bits, Permissions, Integer] The permission sets that should receive a `deny` override (i.e. a red
469
+ # cross on Discord)
470
+ # @param reason [String] The reason the for defining the overwrite.
471
+ # @example Define a permission overwrite for a user that can then mention everyone and use TTS, but not create any invites
472
+ # allow = Discordrb::Permissions.new
473
+ # allow.can_mention_everyone = true
474
+ # allow.can_send_tts_messages = true
475
+ #
476
+ # deny = Discordrb::Permissions.new
477
+ # deny.can_create_instant_invite = true
478
+ #
479
+ # channel.define_overwrite(user, allow, deny)
480
+ def define_overwrite(thing, allow = 0, deny = 0, reason: nil)
481
+ unless thing.is_a? Overwrite
482
+ allow_bits = allow.respond_to?(:bits) ? allow.bits : allow
483
+ deny_bits = deny.respond_to?(:bits) ? deny.bits : deny
484
+
485
+ thing = Overwrite.new thing, allow: allow_bits, deny: deny_bits
486
+ end
487
+
488
+ API::Channel.update_permission(@bot.token, @id, thing.id, thing.allow.bits, thing.deny.bits, thing.type, reason)
489
+ end
490
+
491
+ # Deletes a permission overwrite for this channel
492
+ # @param target [Member, User, Role, Profile, Recipient, String, Integer] What permission overwrite to delete
493
+ # @param reason [String] The reason the for the overwrite deletion.
494
+ def delete_overwrite(target, reason = nil)
495
+ raise 'Tried deleting a overwrite for an invalid target' unless target.is_a?(Member) || target.is_a?(User) || target.is_a?(Role) || target.is_a?(Profile) || target.is_a?(Recipient) || target.respond_to?(:resolve_id)
496
+
497
+ API::Channel.delete_permission(@bot.token, @id, target.resolve_id, reason)
498
+ end
499
+
500
+ # Updates the cached data from another channel.
501
+ # @note For internal use only
502
+ # @!visibility private
503
+ def update_from(other)
504
+ @name = other.name
505
+ @position = other.position
506
+ @topic = other.topic
507
+ @recipients = other.recipients
508
+ @bitrate = other.bitrate
509
+ @user_limit = other.user_limit
510
+ @permission_overwrites = other.permission_overwrites
511
+ @nsfw = other.nsfw
512
+ @parent_id = other.parent_id
513
+ @rate_limit_per_user = other.rate_limit_per_user
514
+ end
515
+
516
+ # The list of users currently in this channel. For a voice channel, it will return all the members currently
517
+ # in that channel. For a text channel, it will return all online members that have permission to read it.
518
+ # @return [Array<Member>] the users in this channel
519
+ def users
520
+ if text?
521
+ @server.online_members(include_idle: true).select { |u| u.can_read_messages? self }
522
+ elsif voice?
523
+ @server.voice_states.map { |id, voice_state| @server.member(id) if !voice_state.voice_channel.nil? && voice_state.voice_channel.id == @id }.compact
524
+ end
525
+ end
526
+
527
+ # Retrieves some of this channel's message history.
528
+ # @param amount [Integer] How many messages to retrieve. This must be less than or equal to 100, if it is higher
529
+ # than 100 it will be treated as 100 on Discord's side.
530
+ # @param before_id [Integer] The ID of the most recent message the retrieval should start at, or nil if it should
531
+ # start at the current message.
532
+ # @param after_id [Integer] The ID of the oldest message the retrieval should start at, or nil if it should start
533
+ # as soon as possible with the specified amount.
534
+ # @param around_id [Integer] The ID of the message retrieval should start from, reading in both directions
535
+ # @example Count the number of messages in the last 50 messages that contain the letter 'e'.
536
+ # message_count = channel.history(50).count {|message| message.content.include? "e"}
537
+ # @example Get the last 10 messages before the provided message.
538
+ # last_ten_messages = channel.history(10, message.id)
539
+ # @return [Array<Message>] the retrieved messages.
540
+ def history(amount, before_id = nil, after_id = nil, around_id = nil)
541
+ logs = API::Channel.messages(@bot.token, @id, amount, before_id, after_id, around_id)
542
+ JSON.parse(logs).map { |message| Message.new(message, @bot) }
543
+ end
544
+
545
+ # Retrieves message history, but only message IDs for use with prune.
546
+ # @note For internal use only
547
+ # @!visibility private
548
+ def history_ids(amount, before_id = nil, after_id = nil, around_id = nil)
549
+ logs = API::Channel.messages(@bot.token, @id, amount, before_id, after_id, around_id)
550
+ JSON.parse(logs).map { |message| message['id'].to_i }
551
+ end
552
+
553
+ # Returns a single message from this channel's history by ID.
554
+ # @param message_id [Integer] The ID of the message to retrieve.
555
+ # @return [Message, nil] the retrieved message, or `nil` if it couldn't be found.
556
+ def load_message(message_id)
557
+ response = API::Channel.message(@bot.token, @id, message_id)
558
+ Message.new(JSON.parse(response), @bot)
559
+ rescue RestClient::ResourceNotFound
560
+ nil
561
+ end
562
+
563
+ alias_method :message, :load_message
564
+
565
+ # Requests all pinned messages in a channel.
566
+ # @return [Array<Message>] the received messages.
567
+ def pins
568
+ msgs = API::Channel.pinned_messages(@bot.token, @id)
569
+ JSON.parse(msgs).map { |msg| Message.new(msg, @bot) }
570
+ end
571
+
572
+ # Delete the last N messages on this channel.
573
+ # @param amount [Integer] The amount of message history to consider for pruning. Must be a value between 2 and 100 (Discord limitation)
574
+ # @param strict [true, false] Whether an error should be raised when a message is reached that is too old to be bulk
575
+ # deleted. If this is false only a warning message will be output to the console.
576
+ # @param reason [String, nil] The reason for pruning
577
+ # @raise [ArgumentError] if the amount of messages is not a value between 2 and 100
578
+ # @yield [message] Yields each message in this channels history for filtering the messages to delete
579
+ # @example Pruning messages from a specific user ID
580
+ # channel.prune(100) { |m| m.author.id == 83283213010599936 }
581
+ # @return [Integer] The amount of messages that were successfully deleted
582
+ def prune(amount, strict = false, reason = nil, &block)
583
+ raise ArgumentError, 'Can only delete between 1 and 100 messages!' unless amount.between?(1, 100)
584
+
585
+ messages =
586
+ if block
587
+ history(amount).select(&block).map(&:id)
588
+ else
589
+ history_ids(amount)
590
+ end
591
+
592
+ case messages.size
593
+ when 0
594
+ 0
595
+ when 1
596
+ API::Channel.delete_message(@bot.token, @id, messages.first, reason)
597
+ 1
598
+ else
599
+ bulk_delete(messages, strict, reason)
600
+ end
601
+ end
602
+
603
+ # Deletes a collection of messages
604
+ # @param messages [Array<Message, String, Integer>] the messages (or message IDs) to delete. Total must be an amount between 2 and 100 (Discord limitation)
605
+ # @param strict [true, false] Whether an error should be raised when a message is reached that is too old to be bulk
606
+ # deleted. If this is false only a warning message will be output to the console.
607
+ # @param reason [String, nil] The reason for deleting the messages
608
+ # @raise [ArgumentError] if the amount of messages is not a value between 2 and 100
609
+ # @return [Integer] The amount of messages that were successfully deleted
610
+ def delete_messages(messages, strict = false, reason = nil)
611
+ raise ArgumentError, 'Can only delete between 2 and 100 messages!' unless messages.count.between?(2, 100)
612
+
613
+ messages.map!(&:resolve_id)
614
+ bulk_delete(messages, strict, reason)
615
+ end
616
+
617
+ # Updates the cached permission overwrites
618
+ # @note For internal use only
619
+ # @!visibility private
620
+ def update_overwrites(overwrites)
621
+ @permission_overwrites = overwrites
622
+ end
623
+
624
+ # Add an {Await} for a message in this channel. This is identical in functionality to adding a
625
+ # {Discordrb::Events::MessageEvent} await with the `in` attribute as this channel.
626
+ # @see Bot#add_await
627
+ # @deprecated Will be changed to blocking behavior in v4.0. Use {#await!} instead.
628
+ def await(key, attributes = {}, &block)
629
+ @bot.add_await(key, Discordrb::Events::MessageEvent, { in: @id }.merge(attributes), &block)
630
+ end
631
+
632
+ # Add a blocking {Await} for a message in this channel. This is identical in functionality to adding a
633
+ # {Discordrb::Events::MessageEvent} await with the `in` attribute as this channel.
634
+ # @see Bot#add_await!
635
+ def await!(attributes = {}, &block)
636
+ @bot.add_await!(Discordrb::Events::MessageEvent, { in: @id }.merge(attributes), &block)
637
+ end
638
+
639
+ # Creates a new invite to this channel.
640
+ # @param max_age [Integer] How many seconds this invite should last.
641
+ # @param max_uses [Integer] How many times this invite should be able to be used.
642
+ # @param temporary [true, false] Whether membership should be temporary (kicked after going offline).
643
+ # @param unique [true, false] If true, Discord will always send a unique invite instead of possibly re-using a similar one
644
+ # @param reason [String] The reason the for the creation of this invite.
645
+ # @return [Invite] the created invite.
646
+ def make_invite(max_age = 0, max_uses = 0, temporary = false, unique = false, reason = nil)
647
+ response = API::Channel.create_invite(@bot.token, @id, max_age, max_uses, temporary, unique, reason)
648
+ Invite.new(JSON.parse(response), @bot)
649
+ end
650
+
651
+ alias_method :invite, :make_invite
652
+
653
+ # Starts typing, which displays the typing indicator on the client for five seconds.
654
+ # If you want to keep typing you'll have to resend this every five seconds. (An abstraction
655
+ # for this will eventually be coming)
656
+ # @example Send a typing indicator for the bot in a given channel.
657
+ # channel.start_typing()
658
+ def start_typing
659
+ API::Channel.start_typing(@bot.token, @id)
660
+ end
661
+
662
+ # Creates a Group channel
663
+ # @param user_ids [Array<Integer>] Array of user IDs to add to the new group channel (Excluding
664
+ # the recipient of the PM channel).
665
+ # @return [Channel] the created channel.
666
+ def create_group(user_ids)
667
+ raise 'Attempted to create group channel on a non-pm channel!' unless pm?
668
+
669
+ response = API::Channel.create_group(@bot.token, @id, user_ids.shift)
670
+ channel = Channel.new(JSON.parse(response), @bot)
671
+ channel.add_group_users(user_ids)
672
+ end
673
+
674
+ # Adds a user to a group channel.
675
+ # @param user_ids [Array<String, Integer>, String, Integer] User ID or array of user IDs to add to the group channel.
676
+ # @return [Channel] the group channel.
677
+ def add_group_users(user_ids)
678
+ raise 'Attempted to add a user to a non-group channel!' unless group?
679
+
680
+ user_ids = [user_ids] unless user_ids.is_a? Array
681
+ user_ids.each do |user_id|
682
+ API::Channel.add_group_user(@bot.token, @id, user_id.resolve_id)
683
+ end
684
+ self
685
+ end
686
+
687
+ alias_method :add_group_user, :add_group_users
688
+
689
+ # Removes a user from a group channel.
690
+ # @param user_ids [Array<String, Integer>, String, Integer] User ID or array of user IDs to remove from the group channel.
691
+ # @return [Channel] the group channel.
692
+ def remove_group_users(user_ids)
693
+ raise 'Attempted to remove a user from a non-group channel!' unless group?
694
+
695
+ user_ids = [user_ids] unless user_ids.is_a? Array
696
+ user_ids.each do |user_id|
697
+ API::Channel.remove_group_user(@bot.token, @id, user_id.resolve_id)
698
+ end
699
+ self
700
+ end
701
+
702
+ alias_method :remove_group_user, :remove_group_users
703
+
704
+ # Leaves the group.
705
+ def leave_group
706
+ raise 'Attempted to leave a non-group channel!' unless group?
707
+
708
+ API::Channel.leave_group(@bot.token, @id)
709
+ end
710
+
711
+ alias_method :leave, :leave_group
712
+
713
+ # Creates a webhook in this channel
714
+ # @param name [String] the default name of this webhook.
715
+ # @param avatar [String] the default avatar URL to give this webhook.
716
+ # @param reason [String] the reason for the webhook creation.
717
+ # @raise [ArgumentError] if the channel isn't a text channel in a server.
718
+ # @return [Webhook] the created webhook.
719
+ def create_webhook(name, avatar = nil, reason = nil)
720
+ raise ArgumentError, 'Tried to create a webhook in a non-server channel' unless server
721
+ raise ArgumentError, 'Tried to create a webhook in a non-text channel' unless text?
722
+
723
+ response = API::Channel.create_webhook(@bot.token, @id, name, avatar, reason)
724
+ Webhook.new(JSON.parse(response), @bot)
725
+ end
726
+
727
+ # Requests a list of Webhooks on the channel.
728
+ # @return [Array<Webhook>] webhooks on the channel.
729
+ def webhooks
730
+ raise 'Tried to request webhooks from a non-server channel' unless server
731
+
732
+ webhooks = JSON.parse(API::Channel.webhooks(@bot.token, @id))
733
+ webhooks.map { |webhook_data| Webhook.new(webhook_data, @bot) }
734
+ end
735
+
736
+ # Requests a list of Invites to the channel.
737
+ # @return [Array<Invite>] invites to the channel.
738
+ def invites
739
+ raise 'Tried to request invites from a non-server channel' unless server
740
+
741
+ invites = JSON.parse(API::Channel.invites(@bot.token, @id))
742
+ invites.map { |invite_data| Invite.new(invite_data, @bot) }
743
+ end
744
+
745
+ # The default `inspect` method is overwritten to give more useful output.
746
+ def inspect
747
+ "<Channel name=#{@name} id=#{@id} topic=\"#{@topic}\" type=#{@type} position=#{@position} server=#{@server}>"
748
+ end
749
+
750
+ # Adds a recipient to a group channel.
751
+ # @param recipient [Recipient] the recipient to add to the group
752
+ # @raise [ArgumentError] if tried to add a non-recipient
753
+ # @note For internal use only
754
+ # @!visibility private
755
+ def add_recipient(recipient)
756
+ raise 'Tried to add recipient to a non-group channel' unless group?
757
+ raise ArgumentError, 'Tried to add a non-recipient to a group' unless recipient.is_a?(Recipient)
758
+
759
+ @recipients << recipient
760
+ end
761
+
762
+ # Removes a recipient from a group channel.
763
+ # @param recipient [Recipient] the recipient to remove from the group
764
+ # @raise [ArgumentError] if tried to remove a non-recipient
765
+ # @note For internal use only
766
+ # @!visibility private
767
+ def remove_recipient(recipient)
768
+ raise 'Tried to remove recipient from a non-group channel' unless group?
769
+ raise ArgumentError, 'Tried to remove a non-recipient from a group' unless recipient.is_a?(Recipient)
770
+
771
+ @recipients.delete(recipient)
772
+ end
773
+
774
+ # Updates the cached data with new data
775
+ # @note For internal use only
776
+ # @!visibility private
777
+ def update_data(new_data = nil)
778
+ new_data ||= JSON.parse(API::Channel.resolve(@bot.token, @id))
779
+ @name = new_data[:name] || new_data['name'] || @name
780
+ @topic = new_data[:topic] || new_data['topic'] || @topic
781
+ @position = new_data[:position] || new_data['position'] || @position
782
+ @bitrate = new_data[:bitrate] || new_data['bitrate'] || @bitrate
783
+ @user_limit = new_data[:user_limit] || new_data['user_limit'] || @user_limit
784
+ new_nsfw = new_data.key?(:nsfw) ? new_data[:nsfw] : new_data['nsfw']
785
+ @nsfw = new_nsfw.nil? ? @nsfw : new_nsfw
786
+ @parent_id = new_data[:parent_id] || new_data['parent_id'] || @parent_id
787
+ process_permission_overwrites(new_data[:permission_overwrites] || new_data['permission_overwrites'])
788
+ @rate_limit_per_user = new_data[:rate_limit_per_user] || new_data['rate_limit_per_user'] || @rate_limit_per_user
789
+ end
790
+
791
+ # @return [String] a URL that a user can use to navigate to this channel in the client
792
+ def link
793
+ "https://discord.com/channels/#{@server&.id || '@me'}/#{@channel.id}"
794
+ end
795
+
796
+ alias_method :jump_link, :link
797
+
798
+ private
799
+
800
+ # For bulk_delete checking
801
+ TWO_WEEKS = 86_400 * 14
802
+
803
+ # Deletes a list of messages on this channel using bulk delete.
804
+ def bulk_delete(ids, strict = false, reason = nil)
805
+ min_snowflake = IDObject.synthesise(Time.now - TWO_WEEKS)
806
+
807
+ ids.reject! do |e|
808
+ next unless e < min_snowflake
809
+
810
+ message = "Attempted to bulk_delete message #{e} which is too old (min = #{min_snowflake})"
811
+ raise ArgumentError, message if strict
812
+
813
+ Discordrb::LOGGER.warn(message)
814
+ true
815
+ end
816
+
817
+ API::Channel.bulk_delete_messages(@bot.token, @id, ids, reason)
818
+ ids.size
819
+ end
820
+
821
+ def update_channel_data(new_data)
822
+ new_nsfw = new_data[:nsfw].is_a?(TrueClass) || new_data[:nsfw].is_a?(FalseClass) ? new_data[:nsfw] : @nsfw
823
+ # send permission_overwrite only when explicitly set
824
+ overwrites = new_data[:permission_overwrites] ? new_data[:permission_overwrites].map { |_, v| v.to_hash } : nil
825
+ response = JSON.parse(API::Channel.update(@bot.token, @id,
826
+ new_data[:name] || @name,
827
+ new_data[:topic] || @topic,
828
+ new_data[:position] || @position,
829
+ new_data[:bitrate] || @bitrate,
830
+ new_data[:user_limit] || @user_limit,
831
+ new_nsfw,
832
+ overwrites,
833
+ new_data[:parent_id] || @parent_id,
834
+ new_data[:rate_limit_per_user] || @rate_limit_per_user))
835
+ update_data(response)
836
+ end
837
+
838
+ def process_permission_overwrites(overwrites)
839
+ # Populate permission overwrites
840
+ @permission_overwrites = {}
841
+ return unless overwrites
842
+
843
+ overwrites.each do |element|
844
+ id = element['id'].to_i
845
+ @permission_overwrites[id] = Overwrite.from_hash(element)
846
+ end
847
+ end
848
+ end
849
+ end