discorb 0.19.0 → 0.20.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +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