rubycord 1.0.0

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