rubycord 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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