discordrb 3.3.0 → 3.5.0

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