discordrb 3.3.0 → 3.5.0

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