discorb 0.19.0 → 0.20.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 +4 -4
  2. data/.github/workflows/build_version.yml +2 -2
  3. data/.rubocop.yml +12 -75
  4. data/Changelog.md +10 -0
  5. data/Rakefile +482 -454
  6. data/lib/discorb/allowed_mentions.rb +68 -72
  7. data/lib/discorb/app_command/command.rb +466 -398
  8. data/lib/discorb/app_command/common.rb +65 -25
  9. data/lib/discorb/app_command/handler.rb +304 -266
  10. data/lib/discorb/app_command.rb +5 -5
  11. data/lib/discorb/application.rb +198 -197
  12. data/lib/discorb/asset.rb +101 -101
  13. data/lib/discorb/attachment.rb +134 -119
  14. data/lib/discorb/audit_logs.rb +412 -385
  15. data/lib/discorb/automod.rb +279 -269
  16. data/lib/discorb/channel/base.rb +107 -108
  17. data/lib/discorb/channel/category.rb +32 -32
  18. data/lib/discorb/channel/container.rb +44 -44
  19. data/lib/discorb/channel/dm.rb +26 -28
  20. data/lib/discorb/channel/guild.rb +311 -246
  21. data/lib/discorb/channel/stage.rb +156 -140
  22. data/lib/discorb/channel/text.rb +430 -336
  23. data/lib/discorb/channel/thread.rb +374 -325
  24. data/lib/discorb/channel/voice.rb +85 -79
  25. data/lib/discorb/channel.rb +5 -5
  26. data/lib/discorb/client.rb +635 -621
  27. data/lib/discorb/color.rb +178 -182
  28. data/lib/discorb/common.rb +168 -164
  29. data/lib/discorb/components/button.rb +107 -106
  30. data/lib/discorb/components/select_menu.rb +157 -145
  31. data/lib/discorb/components/text_input.rb +103 -106
  32. data/lib/discorb/components.rb +68 -66
  33. data/lib/discorb/dictionary.rb +135 -135
  34. data/lib/discorb/embed.rb +404 -398
  35. data/lib/discorb/emoji.rb +309 -302
  36. data/lib/discorb/emoji_table.rb +16099 -8857
  37. data/lib/discorb/error.rb +131 -131
  38. data/lib/discorb/event.rb +360 -314
  39. data/lib/discorb/event_handler.rb +39 -39
  40. data/lib/discorb/exe/about.rb +17 -17
  41. data/lib/discorb/exe/irb.rb +72 -67
  42. data/lib/discorb/exe/new.rb +323 -315
  43. data/lib/discorb/exe/run.rb +69 -68
  44. data/lib/discorb/exe/setup.rb +57 -55
  45. data/lib/discorb/exe/show.rb +12 -12
  46. data/lib/discorb/extend.rb +25 -45
  47. data/lib/discorb/extension.rb +89 -83
  48. data/lib/discorb/flag.rb +126 -128
  49. data/lib/discorb/gateway.rb +984 -804
  50. data/lib/discorb/gateway_events.rb +670 -638
  51. data/lib/discorb/gateway_requests.rb +45 -48
  52. data/lib/discorb/guild.rb +2115 -1626
  53. data/lib/discorb/guild_template.rb +280 -241
  54. data/lib/discorb/http.rb +247 -232
  55. data/lib/discorb/image.rb +42 -42
  56. data/lib/discorb/integration.rb +169 -161
  57. data/lib/discorb/intents.rb +161 -163
  58. data/lib/discorb/interaction/autocomplete.rb +76 -62
  59. data/lib/discorb/interaction/command.rb +279 -224
  60. data/lib/discorb/interaction/components.rb +114 -104
  61. data/lib/discorb/interaction/modal.rb +36 -32
  62. data/lib/discorb/interaction/response.rb +379 -336
  63. data/lib/discorb/interaction/root.rb +271 -257
  64. data/lib/discorb/interaction.rb +5 -5
  65. data/lib/discorb/invite.rb +154 -153
  66. data/lib/discorb/member.rb +344 -311
  67. data/lib/discorb/message.rb +615 -544
  68. data/lib/discorb/message_meta.rb +197 -186
  69. data/lib/discorb/modules.rb +371 -290
  70. data/lib/discorb/permission.rb +305 -291
  71. data/lib/discorb/presence.rb +352 -346
  72. data/lib/discorb/rate_limit.rb +81 -76
  73. data/lib/discorb/reaction.rb +55 -54
  74. data/lib/discorb/role.rb +272 -240
  75. data/lib/discorb/shard.rb +76 -74
  76. data/lib/discorb/sticker.rb +193 -171
  77. data/lib/discorb/user.rb +205 -188
  78. data/lib/discorb/utils/colored_puts.rb +16 -16
  79. data/lib/discorb/utils.rb +12 -16
  80. data/lib/discorb/voice_state.rb +305 -281
  81. data/lib/discorb/webhook.rb +537 -507
  82. data/lib/discorb.rb +62 -56
  83. data/sig/discorb/application.rbs +2 -0
  84. data/sig/discorb/automod.rbs +10 -1
  85. data/sig/discorb/guild.rbs +2 -0
  86. data/sig/discorb/message.rbs +2 -0
  87. data/sig/discorb/user.rbs +22 -20
  88. metadata +2 -2
@@ -1,544 +1,615 @@
1
- # frozen_string_literal: true
2
-
3
- module Discorb
4
- #
5
- # Represents a message.
6
- #
7
- class Message < DiscordModel
8
- # @return [Discorb::Snowflake] The ID of the message.
9
- attr_reader :id
10
- # @return [Discorb::User, Discorb::Member, Webhook::Message::Author] The user that sent the message.
11
- attr_reader :author
12
- # @return [String] The content of the message.
13
- attr_reader :content
14
- alias to_s content
15
- # @return [Time] The time the message was created.
16
- attr_reader :created_at
17
- alias timestamp created_at
18
- alias sent_at created_at
19
- # @return [Time] The time the message was edited.
20
- # @return [nil] If the message was not edited.
21
- attr_reader :updated_at
22
- alias edited_at updated_at
23
- alias edited_timestamp updated_at
24
- # @return [Array<Discorb::Attachment>] The attachments of the message.
25
- attr_reader :attachments
26
- # @return [Array<Discorb::Embed>] The embeds of the message.
27
- attr_reader :embeds
28
- # @return [Array<Discorb::Reaction>] The reactions of the message.
29
- attr_reader :reactions
30
- # @return [Discorb::Snowflake] The ID of the channel the message was sent in.
31
- attr_reader :webhook_id
32
- # @return [Symbol] The type of the message.
33
- # Currently, this will be one of:
34
- #
35
- # * `:default`
36
- # * `:recipient_add`
37
- # * `:recipient_remove`
38
- # * `:call`
39
- # * `:channel_name_change`
40
- # * `:channel_icon_change`
41
- # * `:channel_pinned_message`
42
- # * `:guild_member_join`
43
- # * `:user_premium_guild_subscription`
44
- # * `:user_premium_guild_subscription_tier_1`
45
- # * `:user_premium_guild_subscription_tier_2`
46
- # * `:user_premium_guild_subscription_tier_3`
47
- # * `:channel_follow_add`
48
- # * `:guild_discovery_disqualified`
49
- # * `:guild_discovery_requalified`
50
- # * `:guild_discovery_grace_period_initial_warning`
51
- # * `:guild_discovery_grace_period_final_warning`
52
- # * `:thread_created`
53
- # * `:reply`
54
- # * `:chat_input_command`
55
- # * `:thread_starter_message`
56
- # * `:guild_invite_reminder`
57
- # * `:context_menu_command`
58
- attr_reader :type
59
- # @return [Discorb::Message::Activity] The activity of the message.
60
- attr_reader :activity
61
- # @return [Discorb::Application] The application of the message.
62
- attr_reader :application_id
63
- # @return [Discorb::Message::Reference] The reference of the message.
64
- attr_reader :message_reference
65
- # @return [Discorb::Message::Flag] The flag of the message.
66
- # @see Discorb::Message::Flag
67
- attr_reader :flag
68
- # @return [Discorb::Message::Sticker] The sticker of the message.
69
- attr_reader :stickers
70
- # @return [Discorb::Message::Interaction] The interaction of the message.
71
- attr_reader :interaction
72
- # @return [Discorb::ThreadChannel] The thread channel of the message.
73
- attr_reader :thread
74
- # @return [Array<Array<Discorb::Component>>] The components of the message.
75
- attr_reader :components
76
- # @return [Boolean] Whether the message is deleted.
77
- attr_reader :deleted
78
- alias deleted? deleted
79
- # @return [Boolean] Whether the message is tts.
80
- attr_reader :tts
81
- alias tts? tts
82
- # @return [Boolean] Whether the message mentions everyone.
83
- attr_reader :mention_everyone
84
- alias mention_everyone? mention_everyone
85
- # @return [Boolean] Whether the message is pinned.
86
- attr_reader :pinned
87
- alias pinned? pinned
88
- # @private
89
- # @return [{Integer => Symbol}] The mapping of message type.
90
- MESSAGE_TYPE = {
91
- 0 => :default,
92
- 1 => :recipient_add,
93
- 2 => :recipient_remove,
94
- 3 => :call,
95
- 4 => :channel_name_change,
96
- 5 => :channel_icon_change,
97
- 6 => :channel_pinned_message,
98
- 7 => :guild_member_join,
99
- 8 => :user_premium_guild_subscription,
100
- 9 => :user_premium_guild_subscription_tier_1,
101
- 10 => :user_premium_guild_subscription_tier_2,
102
- 11 => :user_premium_guild_subscription_tier_3,
103
- 12 => :channel_follow_add,
104
- 14 => :guild_discovery_disqualified,
105
- 15 => :guild_discovery_requalified,
106
- 16 => :guild_discovery_grace_period_initial_warning,
107
- 17 => :guild_discovery_grace_period_final_warning,
108
- 18 => :thread_created,
109
- 19 => :reply,
110
- 20 => :chat_input_command,
111
- 21 => :thread_starter_message,
112
- 22 => :guild_invite_reminder,
113
- 23 => :context_menu_command,
114
- }.freeze
115
-
116
- # @!attribute [r] channel
117
- # @macro client_cache
118
- # @return [Discorb::Channel] The channel the message was sent in.
119
- # @!attribute [r] guild
120
- # @macro client_cache
121
- # @return [Discorb::Guild] The guild the message was sent in.
122
- # @return [nil] If the message was not sent in a guild.
123
- # @!attribute [r] webhook?
124
- # @return [Boolean] Whether the message was sent by a webhook.
125
- # @!attribute [r] edited?
126
- # @return [Boolean] Whether the message was edited.
127
- # @!attribute [r] jump_url
128
- # @return [String] The URL to jump to the message.
129
- # @!attribute [r] embed
130
- # @return [Discorb::Embed] The embed of the message.
131
- # @return [nil] If the message has no embed.
132
- # @!attribute [r] embed?
133
- # @return [Boolean] Whether the message has an embed.
134
- # @!attribute [r] reply?
135
- # @return [Boolean] Whether the message is a reply.
136
- # @!attribute [r] dm?
137
- # @return [Boolean] Whether the message was sent in a DM.
138
- # @!attribute [r] guild?
139
- # @return [Boolean] Whether the message was sent in a guild.
140
-
141
- def embed?
142
- @embeds.any?
143
- end
144
-
145
- def reply?
146
- !@message_reference.nil?
147
- end
148
-
149
- def dm?
150
- @guild_id.nil?
151
- end
152
-
153
- def guild?
154
- !@guild_id.nil?
155
- end
156
-
157
- #
158
- # Initialize a new message.
159
- # @private
160
- #
161
- # @param [Discorb::Client] client The client.
162
- # @param [Hash] data The data of the welcome screen.
163
- # @param [Boolean] no_cache Whether to disable caching.
164
- #
165
- def initialize(client, data, no_cache: false)
166
- @client = client
167
- @data = {}
168
- @no_cache = no_cache
169
- _set_data(data)
170
- @client.messages[@id] = self unless @no_cache
171
- end
172
-
173
- def channel
174
- @dm || @client.channels[@channel_id]
175
- end
176
-
177
- def guild
178
- @client.guilds[@guild_id]
179
- end
180
-
181
- def webhook?
182
- @webhook_id != nil
183
- end
184
-
185
- def jump_url
186
- "https://discord.com/channels/#{@guild_id || "@me"}/#{@channel_id}/#{@id}"
187
- end
188
-
189
- def edited?
190
- !@updated_at.nil?
191
- end
192
-
193
- #
194
- # Removes the mentions from the message.
195
- #
196
- # @param [Boolean] user Whether to clean user mentions.
197
- # @param [Boolean] channel Whether to clean channel mentions.
198
- # @param [Boolean] role Whether to clean role mentions.
199
- # @param [Boolean] emoji Whether to clean emoji.
200
- # @param [Boolean] everyone Whether to clean `@everyone` and `@here`.
201
- # @param [Boolean] codeblock Whether to clean codeblocks.
202
- #
203
- # @return [String] The cleaned content of the message.
204
- #
205
- def clean_content(user: true, channel: true, role: true, emoji: true, everyone: true, codeblock: false)
206
- ret = @content.dup
207
- if user
208
- ret.gsub!(/<@!?(\d+)>/) do |_match|
209
- member = guild&.members&.[]($1)
210
- member ||= @client.users[$1]
211
- member ? "@#{member.name}" : "@Unknown User"
212
- end
213
- end
214
- ret.gsub!(/<#(\d+)>/) do |_match|
215
- channel = @client.channels[$1]
216
- channel ? "<##{channel.id}>" : "#Unknown Channel"
217
- end
218
- if role
219
- ret.gsub!(/<@&(\d+)>/) do |_match|
220
- r = guild&.roles&.[]($1)
221
- r ? "@#{r.name}" : "@Unknown Role"
222
- end
223
- end
224
- if emoji
225
- ret.gsub!(/<a?:([a-zA-Z0-9_]+):\d+>/) do |_match|
226
- $1
227
- end
228
- end
229
- ret.gsub!(/@(everyone|here)/, "@\u200b\\1") if everyone
230
- if codeblock
231
- ret
232
- else
233
- codeblocks = ret.split("```", -1)
234
- original_codeblocks = @content.scan(/```(.+?)```/m)
235
- res = []
236
- max = codeblocks.length
237
- codeblocks.each_with_index do |single_codeblock, i|
238
- res << if max.even? && i == max - 1 || i.even?
239
- single_codeblock
240
- else
241
- original_codeblocks[i / 2]
242
- end
243
- end
244
- res.join("```")
245
- end
246
- end
247
-
248
- #
249
- # Edit the message.
250
- # @async
251
- #
252
- # @param [String] content The message content.
253
- # @param [Discorb::Embed] embed The embed to send.
254
- # @param [Array<Discorb::Embed>] embeds The embeds to send.
255
- # @param [Discorb::AllowedMentions] allowed_mentions The allowed mentions.
256
- # @param [Array<Discorb::Attachment>] attachments The new attachments.
257
- # @param [Array<Discorb::Component>, Array<Array<Discorb::Component>>] components The components to send.
258
- # @param [Boolean] supress Whether to supress embeds.
259
- #
260
- # @return [Async::Task<void>] The task.
261
- #
262
- def edit(
263
- content = Discorb::Unset,
264
- embed: Discorb::Unset,
265
- embeds: Discorb::Unset,
266
- allowed_mentions: Discorb::Unset,
267
- attachments: Discorb::Unset,
268
- components: Discorb::Unset,
269
- supress: Discorb::Unset
270
- )
271
- Async do
272
- channel.edit_message(@id, content, embed: embed, embeds: embeds, allowed_mentions: allowed_mentions,
273
- attachments: attachments, components: components, supress: supress).wait
274
- end
275
- end
276
-
277
- #
278
- # Delete the message.
279
- # @async
280
- #
281
- # @param [String] reason The reason for deleting the message.
282
- #
283
- # @return [Async::Task<void>] The task.
284
- #
285
- def delete(reason: nil)
286
- Async do
287
- channel.delete_message(@id, reason: reason).wait
288
- end
289
- end
290
-
291
- #
292
- # Convert the message to reference object.
293
- #
294
- # @param [Boolean] fail_if_not_exists Whether to raise an error if the message does not exist.
295
- #
296
- # @return [Discorb::Message::Reference] The reference object.
297
- #
298
- def to_reference(fail_if_not_exists: true)
299
- Reference.from_hash(
300
- {
301
- message_id: @id,
302
- channel_id: @channel_id,
303
- guild_id: @guild_id,
304
- fail_if_not_exists: fail_if_not_exists,
305
- }
306
- )
307
- end
308
-
309
- def embed
310
- @embeds[0]
311
- end
312
-
313
- # Reply to the message.
314
- # @async
315
- # @param (see #post)
316
- # @return [Async::Task<Discorb::Message>] The message.
317
- def reply(*args, **kwargs)
318
- Async do
319
- channel.post(*args, reference: self, **kwargs).wait
320
- end
321
- end
322
-
323
- #
324
- # Publish the message.
325
- # @async
326
- #
327
- # @return [Async::Task<void>] The task.
328
- #
329
- def publish
330
- Async do
331
- channel.post("/channels/#{@channel_id}/messages/#{@id}/crosspost", nil).wait
332
- end
333
- end
334
-
335
- #
336
- # Add a reaction to the message.
337
- # @async
338
- #
339
- # @param [Discorb::Emoji] emoji The emoji to react with.
340
- #
341
- # @return [Async::Task<void>] The task.
342
- #
343
- def add_reaction(emoji)
344
- Async do
345
- @client.http.request(
346
- Route.new("/channels/#{@channel_id}/messages/#{@id}/reactions/#{emoji.to_uri}/@me",
347
- "//channels/:channel_id/messages/:message_id/reactions/:emoji/@me", :put), nil
348
- ).wait
349
- end
350
- end
351
-
352
- alias react_with add_reaction
353
-
354
- #
355
- # Remove a reaction from the message.
356
- # @async
357
- #
358
- # @param [Discorb::Emoji] emoji The emoji to remove.
359
- #
360
- # @return [Async::Task<void>] The task.
361
- #
362
- def remove_reaction(emoji)
363
- Async do
364
- @client.http.request(
365
- Route.new("/channels/#{@channel_id}/messages/#{@id}/reactions/#{emoji.to_uri}/@me",
366
- "//channels/:channel_id/messages/:message_id/reactions/:emoji/@me",
367
- :delete)
368
- ).wait
369
- end
370
- end
371
-
372
- alias delete_reaction remove_reaction
373
-
374
- #
375
- # Remove other member's reaction from the message.
376
- # @async
377
- #
378
- # @param [Discorb::Emoji] emoji The emoji to remove.
379
- # @param [Discorb::Member] member The member to remove the reaction from.
380
- #
381
- # @return [Async::Task<void>] The task.
382
- #
383
- def remove_reaction_of(emoji, member)
384
- Async do
385
- @client.http.request(
386
- Route.new(
387
- "/channels/#{@channel_id}/messages/#{@id}/reactions/#{emoji.to_uri}/#{if member.is_a?(Member)
388
- member.id
389
- else
390
- member
391
- end}",
392
- "//channels/:channel_id/messages/:message_id/reactions/:emoji/:user_id",
393
- :delete
394
- )
395
- ).wait
396
- end
397
- end
398
-
399
- alias delete_reaction_of remove_reaction_of
400
-
401
- #
402
- # Fetch reacted users of reaction.
403
- # @async
404
- #
405
- # @param [Discorb::Emoji, Discorb::PartialEmoji] emoji The emoji to fetch.
406
- # @param [Integer, nil] limit The maximum number of users to fetch. `nil` for no limit.
407
- # @param [Discorb::Snowflake, nil] after The ID of the user to start fetching from.
408
- #
409
- # @return [Async::Task<Array<Discorb::User>>] The users.
410
- #
411
- def fetch_reacted_users(emoji, limit: nil, after: Discorb::Snowflake.new("0"))
412
- Async do
413
- if limit.nil? || !limit.positive?
414
- after = Discorb::Snowflake.new("0")
415
- users = []
416
- loop do
417
- _resp, data = @client.http.request(
418
- Route.new(
419
- "/channels/#{@channel_id}/messages/#{@id}/reactions/#{emoji.to_uri}?limit=100&after=#{after}",
420
- "//channels/:channel_id/messages/:message_id/reactions/:emoji",
421
- :get
422
- )
423
- ).wait
424
- break if data.empty?
425
-
426
- users += data.map { |r| guild&.members&.[](r[:id]) || @client.users[r[:id]] || User.new(@client, r) }
427
-
428
- break if data.length < 100
429
-
430
- after = data[-1][:id]
431
- end
432
- next users
433
- else
434
- _resp, data = @client.http.request(
435
- Route.new(
436
- "/channels/#{@channel_id}/messages/#{@id}/reactions/#{emoji.to_uri}?limit=#{limit}&after=#{after}",
437
- "//channels/:channel_id/messages/:message_id/reactions/:emoji",
438
- :get
439
- )
440
- ).wait
441
- next data.map { |r| guild&.members&.[](r[:id]) || @client.users[r[:id]] || User.new(@client, r) }
442
- end
443
- end
444
- end
445
-
446
- #
447
- # Pin the message.
448
- # @async
449
- #
450
- # @param [String] reason The reason for pinning the message.
451
- #
452
- # @return [Async::Task<void>] The task.
453
- #
454
- def pin(reason: nil)
455
- Async do
456
- channel.pin_message(self, reason: reason).wait
457
- end
458
- end
459
-
460
- #
461
- # Unpin the message.
462
- # @async
463
- #
464
- # @param [String] reason The reason for unpinning the message.
465
- #
466
- # @return [Async::Task<void>] The task.
467
- #
468
- def unpin(reason: nil)
469
- Async do
470
- channel.unpin_message(self, reason: reason).wait
471
- end
472
- end
473
-
474
- #
475
- # Start thread from the message.
476
- # @async
477
- #
478
- # @param (see Discorb::Channel#start_thread)
479
- #
480
- # @return [Async::Task<Discorb::ThreadChannel>] <description>
481
- #
482
- def start_thread(*args, **kwargs)
483
- Async do
484
- channel.start_thread(*args, message: self, **kwargs).wait
485
- end
486
- end
487
-
488
- alias create_thread start_thread
489
-
490
- # Meta
491
-
492
- def inspect
493
- "#<#{self.class} #{@content.inspect} id=#{@id}>"
494
- end
495
-
496
- private
497
-
498
- def _set_data(data)
499
- @id = Snowflake.new(data[:id])
500
- @channel_id = data[:channel_id]
501
-
502
- if data[:guild_id]
503
- @guild_id = data[:guild_id]
504
- @dm = nil
505
- else
506
- @dm = Discorb::DMChannel.new(@client, data[:channel_id])
507
- @guild_id = nil
508
- end
509
-
510
- if data[:member].nil? && data[:webhook_id]
511
- @webhook_id = Snowflake.new(data[:webhook_id])
512
- @author = Webhook::Message::Author.new(data[:author])
513
- elsif data[:guild_id].nil? || data[:guild_id].empty? || data[:member].nil?
514
- @author = @client.users[data[:author][:id]] || User.new(@client, data[:author])
515
- else
516
- @author = guild&.members&.get(data[:author][:id]) || Member.new(@client,
517
- @guild_id, data[:author], data[:member])
518
- end
519
- @content = data[:content]
520
- @created_at = Time.iso8601(data[:timestamp])
521
- @updated_at = data[:edited_timestamp].nil? ? nil : Time.iso8601(data[:edited_timestamp])
522
-
523
- @tts = data[:tts]
524
- @mention_everyone = data[:mention_everyone]
525
- @mention_roles = data[:mention_roles].map { |r| guild.roles[r] }
526
- @attachments = data[:attachments].map { |a| Attachment.from_hash(a) }
527
- @embeds = data[:embeds] ? data[:embeds].map { |e| Embed.from_hash(e) } : []
528
- @reactions = data[:reactions] ? data[:reactions].map { |r| Reaction.new(self, r) } : []
529
- @pinned = data[:pinned]
530
- @type = MESSAGE_TYPE[data[:type]]
531
- @activity = data[:activity] && Activity.new(data[:activity])
532
- @application_id = data[:application_id]
533
- @message_reference = data[:message_reference] && Reference.from_hash(data[:message_reference])
534
- @flag = Flag.new(0b111 - data[:flags])
535
- @sticker_items = data[:sticker_items] ? data[:sticker_items].map { |s| Message::Sticker.new(s) } : []
536
- # @referenced_message = data[:referenced_message] && Message.new(@client, data[:referenced_message])
537
- @interaction = data[:interaction] && Message::Interaction.new(@client, data[:interaction])
538
- @thread = data[:thread] && Channel.make_channel(@client, data[:thread])
539
- @components = data[:components].map { |c| c[:components].map { |co| Component.from_hash(co) } }
540
- @data.update(data)
541
- @deleted = false
542
- end
543
- end
544
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Discorb
4
+ #
5
+ # Represents a message.
6
+ #
7
+ class Message < DiscordModel
8
+ # @return [Discorb::Snowflake] The ID of the message.
9
+ attr_reader :id
10
+ # @return [Discorb::User, Discorb::Member, Webhook::Message::Author] The user that sent the message.
11
+ attr_reader :author
12
+ # @return [String] The content of the message.
13
+ attr_reader :content
14
+ alias to_s content
15
+ # @return [Time] The time the message was created.
16
+ attr_reader :created_at
17
+ alias timestamp created_at
18
+ alias sent_at created_at
19
+ # @return [Time] The time the message was edited.
20
+ # @return [nil] If the message was not edited.
21
+ attr_reader :updated_at
22
+ alias edited_at updated_at
23
+ alias edited_timestamp updated_at
24
+ # @return [Array<Discorb::Attachment>] The attachments of the message.
25
+ attr_reader :attachments
26
+ # @return [Array<Discorb::Embed>] The embeds of the message.
27
+ attr_reader :embeds
28
+ # @return [Array<Discorb::Reaction>] The reactions of the message.
29
+ attr_reader :reactions
30
+ # @return [Discorb::Snowflake] The ID of the channel the message was sent in.
31
+ attr_reader :webhook_id
32
+ # @return [Symbol] The type of the message.
33
+ # Currently, this will be one of:
34
+ #
35
+ # * `:default`
36
+ # * `:recipient_add`
37
+ # * `:recipient_remove`
38
+ # * `:call`
39
+ # * `:channel_name_change`
40
+ # * `:channel_icon_change`
41
+ # * `:channel_pinned_message`
42
+ # * `:guild_member_join`
43
+ # * `:user_premium_guild_subscription`
44
+ # * `:user_premium_guild_subscription_tier_1`
45
+ # * `:user_premium_guild_subscription_tier_2`
46
+ # * `:user_premium_guild_subscription_tier_3`
47
+ # * `:channel_follow_add`
48
+ # * `:guild_discovery_disqualified`
49
+ # * `:guild_discovery_requalified`
50
+ # * `:guild_discovery_grace_period_initial_warning`
51
+ # * `:guild_discovery_grace_period_final_warning`
52
+ # * `:thread_created`
53
+ # * `:reply`
54
+ # * `:chat_input_command`
55
+ # * `:thread_starter_message`
56
+ # * `:guild_invite_reminder`
57
+ # * `:context_menu_command`
58
+ attr_reader :type
59
+ # @return [Discorb::Message::Activity] The activity of the message.
60
+ attr_reader :activity
61
+ # @return [Discorb::Application] The application of the message.
62
+ attr_reader :application_id
63
+ # @return [Discorb::Message::Reference] The reference of the message.
64
+ attr_reader :message_reference
65
+ # @return [Discorb::Message::Flag] The flag of the message.
66
+ # @see Discorb::Message::Flag
67
+ attr_reader :flag
68
+ # @return [Discorb::Message::Sticker] The sticker of the message.
69
+ attr_reader :stickers
70
+ # @return [Discorb::Message::Interaction] The interaction of the message.
71
+ attr_reader :interaction
72
+ # @return [Discorb::ThreadChannel] The thread channel of the message.
73
+ attr_reader :thread
74
+ # @return [Array<Array<Discorb::Component>>] The components of the message.
75
+ attr_reader :components
76
+ # @return [Boolean] Whether the message is deleted.
77
+ attr_reader :deleted
78
+ alias deleted? deleted
79
+ # @return [Boolean] Whether the message is tts.
80
+ attr_reader :tts
81
+ alias tts? tts
82
+ # @return [Boolean] Whether the message mentions everyone.
83
+ attr_reader :mention_everyone
84
+ alias mention_everyone? mention_everyone
85
+ # @return [Boolean] Whether the message is pinned.
86
+ attr_reader :pinned
87
+ alias pinned? pinned
88
+ # @private
89
+ # @return [{Integer => Symbol}] The mapping of message type.
90
+ MESSAGE_TYPE = {
91
+ 0 => :default,
92
+ 1 => :recipient_add,
93
+ 2 => :recipient_remove,
94
+ 3 => :call,
95
+ 4 => :channel_name_change,
96
+ 5 => :channel_icon_change,
97
+ 6 => :channel_pinned_message,
98
+ 7 => :guild_member_join,
99
+ 8 => :user_premium_guild_subscription,
100
+ 9 => :user_premium_guild_subscription_tier_1,
101
+ 10 => :user_premium_guild_subscription_tier_2,
102
+ 11 => :user_premium_guild_subscription_tier_3,
103
+ 12 => :channel_follow_add,
104
+ 14 => :guild_discovery_disqualified,
105
+ 15 => :guild_discovery_requalified,
106
+ 16 => :guild_discovery_grace_period_initial_warning,
107
+ 17 => :guild_discovery_grace_period_final_warning,
108
+ 18 => :thread_created,
109
+ 19 => :reply,
110
+ 20 => :chat_input_command,
111
+ 21 => :thread_starter_message,
112
+ 22 => :guild_invite_reminder,
113
+ 23 => :context_menu_command
114
+ }.freeze
115
+
116
+ # @!attribute [r] channel
117
+ # @macro client_cache
118
+ # @return [Discorb::Channel] The channel the message was sent in.
119
+ # @!attribute [r] guild
120
+ # @macro client_cache
121
+ # @return [Discorb::Guild] The guild the message was sent in.
122
+ # @return [nil] If the message was not sent in a guild.
123
+ # @!attribute [r] webhook?
124
+ # @return [Boolean] Whether the message was sent by a webhook.
125
+ # @!attribute [r] edited?
126
+ # @return [Boolean] Whether the message was edited.
127
+ # @!attribute [r] jump_url
128
+ # @return [String] The URL to jump to the message.
129
+ # @!attribute [r] embed
130
+ # @return [Discorb::Embed] The embed of the message.
131
+ # @return [nil] If the message has no embed.
132
+ # @!attribute [r] embed?
133
+ # @return [Boolean] Whether the message has an embed.
134
+ # @!attribute [r] reply?
135
+ # @return [Boolean] Whether the message is a reply.
136
+ # @!attribute [r] dm?
137
+ # @return [Boolean] Whether the message was sent in a DM.
138
+ # @!attribute [r] guild?
139
+ # @return [Boolean] Whether the message was sent in a guild.
140
+
141
+ def embed?
142
+ @embeds.any?
143
+ end
144
+
145
+ def reply?
146
+ !@message_reference.nil?
147
+ end
148
+
149
+ def dm?
150
+ @guild_id.nil?
151
+ end
152
+
153
+ def guild?
154
+ !@guild_id.nil?
155
+ end
156
+
157
+ #
158
+ # Initialize a new message.
159
+ # @private
160
+ #
161
+ # @param [Discorb::Client] client The client.
162
+ # @param [Hash] data The data of the welcome screen.
163
+ # @param [Boolean] no_cache Whether to disable caching.
164
+ #
165
+ def initialize(client, data, no_cache: false)
166
+ @client = client
167
+ @data = {}
168
+ @no_cache = no_cache
169
+ _set_data(data)
170
+ @client.messages[@id] = self unless @no_cache
171
+ end
172
+
173
+ def channel
174
+ @dm || @client.channels[@channel_id]
175
+ end
176
+
177
+ def guild
178
+ @client.guilds[@guild_id]
179
+ end
180
+
181
+ def webhook?
182
+ @webhook_id != nil
183
+ end
184
+
185
+ def jump_url
186
+ "https://discord.com/channels/#{@guild_id || "@me"}/#{@channel_id}/#{@id}"
187
+ end
188
+
189
+ def edited?
190
+ !@updated_at.nil?
191
+ end
192
+
193
+ #
194
+ # Removes the mentions from the message.
195
+ #
196
+ # @param [Boolean] user Whether to clean user mentions.
197
+ # @param [Boolean] channel Whether to clean channel mentions.
198
+ # @param [Boolean] role Whether to clean role mentions.
199
+ # @param [Boolean] emoji Whether to clean emoji.
200
+ # @param [Boolean] everyone Whether to clean `@everyone` and `@here`.
201
+ # @param [Boolean] codeblock Whether to clean codeblocks.
202
+ #
203
+ # @return [String] The cleaned content of the message.
204
+ #
205
+ def clean_content(
206
+ user: true,
207
+ channel: true,
208
+ role: true,
209
+ emoji: true,
210
+ everyone: true,
211
+ codeblock: false
212
+ )
213
+ ret = @content.dup
214
+ if user
215
+ ret.gsub!(/<@!?(\d+)>/) do |_match|
216
+ member = guild&.members&.[](Regexp.last_match(1))
217
+ member ||= @client.users[Regexp.last_match(1)]
218
+ member ? "@#{member.name}" : "@Unknown User"
219
+ end
220
+ end
221
+ ret.gsub!(/<#(\d+)>/) do |_match|
222
+ channel = @client.channels[Regexp.last_match(1)]
223
+ channel ? "<##{channel.id}>" : "#Unknown Channel"
224
+ end
225
+ if role
226
+ ret.gsub!(/<@&(\d+)>/) do |_match|
227
+ r = guild&.roles&.[](Regexp.last_match(1))
228
+ r ? "@#{r.name}" : "@Unknown Role"
229
+ end
230
+ end
231
+ if emoji
232
+ ret.gsub!(/<a?:([a-zA-Z0-9_]+):\d+>/) { |_match| Regexp.last_match(1) }
233
+ end
234
+ ret.gsub!(/@(everyone|here)/, "@\u200b\\1") if everyone
235
+ if codeblock
236
+ ret
237
+ else
238
+ codeblocks = ret.split("```", -1)
239
+ original_codeblocks = @content.scan(/```(.+?)```/m)
240
+ res = []
241
+ max = codeblocks.length
242
+ codeblocks.each_with_index do |single_codeblock, i|
243
+ res << if (max.even? && i == max - 1) || i.even?
244
+ single_codeblock
245
+ else
246
+ original_codeblocks[i / 2]
247
+ end
248
+ end
249
+ res.join("```")
250
+ end
251
+ end
252
+
253
+ #
254
+ # Edit the message.
255
+ # @async
256
+ #
257
+ # @param [String] content The message content.
258
+ # @param [Discorb::Embed] embed The embed to send.
259
+ # @param [Array<Discorb::Embed>] embeds The embeds to send.
260
+ # @param [Discorb::AllowedMentions] allowed_mentions The allowed mentions.
261
+ # @param [Array<Discorb::Attachment>] attachments The new attachments.
262
+ # @param [Array<Discorb::Component>, Array<Array<Discorb::Component>>] components The components to send.
263
+ # @param [Boolean] supress Whether to supress embeds.
264
+ #
265
+ # @return [Async::Task<void>] The task.
266
+ #
267
+ def edit(
268
+ content = Discorb::Unset,
269
+ embed: Discorb::Unset,
270
+ embeds: Discorb::Unset,
271
+ allowed_mentions: Discorb::Unset,
272
+ attachments: Discorb::Unset,
273
+ components: Discorb::Unset,
274
+ supress: Discorb::Unset
275
+ )
276
+ Async do
277
+ channel.edit_message(
278
+ @id,
279
+ content,
280
+ embed: embed,
281
+ embeds: embeds,
282
+ allowed_mentions: allowed_mentions,
283
+ attachments: attachments,
284
+ components: components,
285
+ supress: supress
286
+ ).wait
287
+ end
288
+ end
289
+
290
+ #
291
+ # Delete the message.
292
+ # @async
293
+ #
294
+ # @param [String] reason The reason for deleting the message.
295
+ #
296
+ # @return [Async::Task<void>] The task.
297
+ #
298
+ def delete(reason: nil)
299
+ Async { channel.delete_message(@id, reason: reason).wait }
300
+ end
301
+
302
+ #
303
+ # Convert the message to reference object.
304
+ #
305
+ # @param [Boolean] fail_if_not_exists Whether to raise an error if the message does not exist.
306
+ #
307
+ # @return [Discorb::Message::Reference] The reference object.
308
+ #
309
+ def to_reference(fail_if_not_exists: true)
310
+ Reference.from_hash(
311
+ {
312
+ message_id: @id,
313
+ channel_id: @channel_id,
314
+ guild_id: @guild_id,
315
+ fail_if_not_exists: fail_if_not_exists
316
+ }
317
+ )
318
+ end
319
+
320
+ def embed
321
+ @embeds[0]
322
+ end
323
+
324
+ # Reply to the message.
325
+ # @async
326
+ # @param (see #post)
327
+ # @return [Async::Task<Discorb::Message>] The message.
328
+ def reply(*args, **kwargs)
329
+ Async { channel.post(*args, reference: self, **kwargs).wait }
330
+ end
331
+
332
+ #
333
+ # Publish the message.
334
+ # @async
335
+ #
336
+ # @return [Async::Task<void>] The task.
337
+ #
338
+ def publish
339
+ Async do
340
+ channel.post(
341
+ "/channels/#{@channel_id}/messages/#{@id}/crosspost",
342
+ nil
343
+ ).wait
344
+ end
345
+ end
346
+
347
+ #
348
+ # Add a reaction to the message.
349
+ # @async
350
+ #
351
+ # @param [Discorb::Emoji] emoji The emoji to react with.
352
+ #
353
+ # @return [Async::Task<void>] The task.
354
+ #
355
+ def add_reaction(emoji)
356
+ Async do
357
+ @client
358
+ .http
359
+ .request(
360
+ Route.new(
361
+ "/channels/#{@channel_id}/messages/#{@id}/reactions/#{emoji.to_uri}/@me",
362
+ "//channels/:channel_id/messages/:message_id/reactions/:emoji/@me",
363
+ :put
364
+ ),
365
+ nil
366
+ )
367
+ .wait
368
+ end
369
+ end
370
+
371
+ alias react_with add_reaction
372
+
373
+ #
374
+ # Remove a reaction from the message.
375
+ # @async
376
+ #
377
+ # @param [Discorb::Emoji] emoji The emoji to remove.
378
+ #
379
+ # @return [Async::Task<void>] The task.
380
+ #
381
+ def remove_reaction(emoji)
382
+ Async do
383
+ @client
384
+ .http
385
+ .request(
386
+ Route.new(
387
+ "/channels/#{@channel_id}/messages/#{@id}/reactions/#{emoji.to_uri}/@me",
388
+ "//channels/:channel_id/messages/:message_id/reactions/:emoji/@me",
389
+ :delete
390
+ )
391
+ )
392
+ .wait
393
+ end
394
+ end
395
+
396
+ alias delete_reaction remove_reaction
397
+
398
+ #
399
+ # Remove other member's reaction from the message.
400
+ # @async
401
+ #
402
+ # @param [Discorb::Emoji] emoji The emoji to remove.
403
+ # @param [Discorb::Member] member The member to remove the reaction from.
404
+ #
405
+ # @return [Async::Task<void>] The task.
406
+ #
407
+ def remove_reaction_of(emoji, member)
408
+ Async do
409
+ @client
410
+ .http
411
+ .request(
412
+ Route.new(
413
+ "/channels/#{@channel_id}/messages/#{@id}/reactions/#{emoji.to_uri}/#{
414
+ member.is_a?(Member) ? member.id : member
415
+ }",
416
+ "//channels/:channel_id/messages/:message_id/reactions/:emoji/:user_id",
417
+ :delete
418
+ )
419
+ )
420
+ .wait
421
+ end
422
+ end
423
+
424
+ alias delete_reaction_of remove_reaction_of
425
+
426
+ #
427
+ # Fetch reacted users of reaction.
428
+ # @async
429
+ #
430
+ # @param [Discorb::Emoji, Discorb::PartialEmoji] emoji The emoji to fetch.
431
+ # @param [Integer, nil] limit The maximum number of users to fetch. `nil` for no limit.
432
+ # @param [Discorb::Snowflake, nil] after The ID of the user to start fetching from.
433
+ #
434
+ # @return [Async::Task<Array<Discorb::User>>] The users.
435
+ #
436
+ def fetch_reacted_users(
437
+ emoji,
438
+ limit: nil,
439
+ after: Discorb::Snowflake.new("0")
440
+ )
441
+ Async do
442
+ if limit.nil? || !limit.positive?
443
+ after = Discorb::Snowflake.new("0")
444
+ users = []
445
+ loop do
446
+ _resp, data =
447
+ @client
448
+ .http
449
+ .request(
450
+ Route.new(
451
+ "/channels/#{@channel_id}/messages/#{@id}/reactions/#{emoji.to_uri}?limit=100&after=#{after}",
452
+ "//channels/:channel_id/messages/:message_id/reactions/:emoji",
453
+ :get
454
+ )
455
+ )
456
+ .wait
457
+ break if data.empty?
458
+
459
+ users +=
460
+ data.map do |r|
461
+ guild&.members&.[](r[:id]) || @client.users[r[:id]] ||
462
+ User.new(@client, r)
463
+ end
464
+
465
+ break if data.length < 100
466
+
467
+ after = data[-1][:id]
468
+ end
469
+ next users
470
+ else
471
+ _resp, data =
472
+ @client
473
+ .http
474
+ .request(
475
+ Route.new(
476
+ "/channels/#{@channel_id}/messages/#{@id}/reactions/#{emoji.to_uri}?limit=#{limit}&after=#{after}",
477
+ "//channels/:channel_id/messages/:message_id/reactions/:emoji",
478
+ :get
479
+ )
480
+ )
481
+ .wait
482
+ next(
483
+ data.map do |r|
484
+ guild&.members&.[](r[:id]) || @client.users[r[:id]] ||
485
+ User.new(@client, r)
486
+ end
487
+ )
488
+ end
489
+ end
490
+ end
491
+
492
+ #
493
+ # Pin the message.
494
+ # @async
495
+ #
496
+ # @param [String] reason The reason for pinning the message.
497
+ #
498
+ # @return [Async::Task<void>] The task.
499
+ #
500
+ def pin(reason: nil)
501
+ Async { channel.pin_message(self, reason: reason).wait }
502
+ end
503
+
504
+ #
505
+ # Unpin the message.
506
+ # @async
507
+ #
508
+ # @param [String] reason The reason for unpinning the message.
509
+ #
510
+ # @return [Async::Task<void>] The task.
511
+ #
512
+ def unpin(reason: nil)
513
+ Async { channel.unpin_message(self, reason: reason).wait }
514
+ end
515
+
516
+ #
517
+ # Start thread from the message.
518
+ # @async
519
+ #
520
+ # @param (see Discorb::Channel#start_thread)
521
+ #
522
+ # @return [Async::Task<Discorb::ThreadChannel>] <description>
523
+ #
524
+ def start_thread(*args, **kwargs)
525
+ Async { channel.start_thread(*args, message: self, **kwargs).wait }
526
+ end
527
+
528
+ alias create_thread start_thread
529
+
530
+ # Meta
531
+
532
+ def inspect
533
+ "#<#{self.class} #{@content.inspect} id=#{@id}>"
534
+ end
535
+
536
+ private
537
+
538
+ def _set_data(data)
539
+ @id = Snowflake.new(data[:id])
540
+ @channel_id = data[:channel_id]
541
+
542
+ if data[:guild_id]
543
+ @guild_id = data[:guild_id]
544
+ @dm = nil
545
+ else
546
+ @dm = Discorb::DMChannel.new(@client, data[:channel_id])
547
+ @guild_id = nil
548
+ end
549
+
550
+ if data[:member].nil? && data[:webhook_id]
551
+ @webhook_id = Snowflake.new(data[:webhook_id])
552
+ @author = Webhook::Message::Author.new(data[:author])
553
+ elsif data[:guild_id].nil? || data[:guild_id].empty? || data[:member].nil?
554
+ @author =
555
+ @client.users[data[:author][:id]] || User.new(@client, data[:author])
556
+ else
557
+ @author =
558
+ guild&.members&.get(data[:author][:id]) ||
559
+ Member.new(@client, @guild_id, data[:author], data[:member])
560
+ end
561
+ @content = data[:content]
562
+ @created_at = Time.iso8601(data[:timestamp])
563
+ @updated_at =
564
+ (
565
+ if data[:edited_timestamp].nil?
566
+ nil
567
+ else
568
+ Time.iso8601(data[:edited_timestamp])
569
+ end
570
+ )
571
+
572
+ @tts = data[:tts]
573
+ @mention_everyone = data[:mention_everyone]
574
+ @mention_roles = data[:mention_roles].map { |r| guild.roles[r] }
575
+ @attachments = data[:attachments].map { |a| Attachment.from_hash(a) }
576
+ @embeds =
577
+ data[:embeds] ? data[:embeds].map { |e| Embed.from_hash(e) } : []
578
+ @reactions =
579
+ (
580
+ if data[:reactions]
581
+ data[:reactions].map { |r| Reaction.new(self, r) }
582
+ else
583
+ []
584
+ end
585
+ )
586
+ @pinned = data[:pinned]
587
+ @type = MESSAGE_TYPE[data[:type]]
588
+ @activity = data[:activity] && Activity.new(data[:activity])
589
+ @application_id = data[:application_id]
590
+ @message_reference =
591
+ data[:message_reference] &&
592
+ Reference.from_hash(data[:message_reference])
593
+ @flag = Flag.new(0b111 - data[:flags])
594
+ @sticker_items =
595
+ (
596
+ if data[:sticker_items]
597
+ data[:sticker_items].map { |s| Message::Sticker.new(s) }
598
+ else
599
+ []
600
+ end
601
+ )
602
+ # @referenced_message = data[:referenced_message] && Message.new(@client, data[:referenced_message])
603
+ @interaction =
604
+ data[:interaction] &&
605
+ Message::Interaction.new(@client, data[:interaction])
606
+ @thread = data[:thread] && Channel.make_channel(@client, data[:thread])
607
+ @components =
608
+ data[:components].map do |c|
609
+ c[:components].map { |co| Component.from_hash(co) }
610
+ end
611
+ @data.update(data)
612
+ @deleted = false
613
+ end
614
+ end
615
+ end