diff --git a/packages/discord.js/src/managers/MessageManager.js b/packages/discord.js/src/managers/MessageManager.js index dcddf2857383..76194c4ecdad 100644 --- a/packages/discord.js/src/managers/MessageManager.js +++ b/packages/discord.js/src/managers/MessageManager.js @@ -2,9 +2,9 @@ const { Collection } = require('@discordjs/collection'); const { makeURLSearchParams } = require('@discordjs/rest'); -const { Routes } = require('discord-api-types/v10'); +const { MessageReferenceType, Routes } = require('discord-api-types/v10'); const CachedManager = require('./CachedManager'); -const { DiscordjsTypeError, ErrorCodes } = require('../errors'); +const { DiscordjsError, DiscordjsTypeError, ErrorCodes } = require('../errors'); const { Message } = require('../structures/Message'); const MessagePayload = require('../structures/MessagePayload'); const { MakeCacheOverrideSymbol } = require('../util/Symbols'); @@ -209,6 +209,33 @@ class MessageManager extends CachedManager { return this.cache.get(data.id) ?? this._add(data); } + /** + * Forwards a message to this manager's channel. + * @param {Message|MessageReference} reference The message to forward + * @returns {Promise} + */ + async forward(reference) { + if (!reference) throw new DiscordjsError(ErrorCodes.MessageReferenceMissing); + const message_id = reference.id ?? this.resolveId(reference.messageId); + if (!message_id) throw new DiscordjsError(ErrorCodes.MessageReferenceMissing); + const channel_id = this.client.channels.resolveId(reference.channelId); + if (!channel_id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'channel', 'ChannelResolvable'); + const guild_id = this.client.guilds.resolveId(reference.guildId); + + const data = await this.client.rest.post(Routes.channelMessages(this.channel.id), { + body: { + message_reference: { + message_id, + channel_id, + guild_id, + type: MessageReferenceType.Forward, + }, + }, + }); + + return this.cache.get(data.id) ?? this._add(data); + } + /** * Pins a message to the channel's pinned messages, even if it's not cached. * @param {MessageResolvable} message The message to pin diff --git a/packages/discord.js/src/structures/Message.js b/packages/discord.js/src/structures/Message.js index 642524f0b6b7..2b18b3e88bf3 100644 --- a/packages/discord.js/src/structures/Message.js +++ b/packages/discord.js/src/structures/Message.js @@ -8,6 +8,7 @@ const { ChannelType, MessageType, MessageFlags, + MessageReferenceType, PermissionFlagsBits, } = require('discord-api-types/v10'); const Attachment = require('./Attachment'); @@ -20,7 +21,7 @@ const MessagePayload = require('./MessagePayload'); const { Poll } = require('./Poll.js'); const ReactionCollector = require('./ReactionCollector'); const { Sticker } = require('./Sticker'); -const { DiscordjsError, ErrorCodes } = require('../errors'); +const { DiscordjsError, DiscordjsTypeError, ErrorCodes } = require('../errors'); const ReactionManager = require('../managers/ReactionManager'); const { createComponent } = require('../util/Components'); const { NonSystemMessageTypes, MaxBulkDeletableMessageAge, UndeletableMessageTypes } = require('../util/Constants'); @@ -794,6 +795,19 @@ class Message extends Base { return message; } + /** + * Forwards this message. + * @param {ChannelResolvable} channel The channel to forward this message to + * @returns {Promise} + */ + forward(channel) { + const resolvedChannel = this.client.channels.resolve(channel); + + if (!resolvedChannel) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'channel', 'ChannelResolvable'); + + return resolvedChannel.messages.forward(this); + } + /** * Whether the message is crosspostable by the client user * @type {boolean} @@ -947,8 +961,11 @@ class Message extends Base { data = options; } else { data = MessagePayload.create(this, options, { - reply: { - messageReference: this, + messageReference: { + messageId: this.id, + channelId: this.channelId, + guildId: this.guildId, + type: MessageReferenceType.Default, failIfNotExists: options?.failIfNotExists ?? this.client.options.failIfNotExists, }, }); diff --git a/packages/discord.js/src/structures/MessagePayload.js b/packages/discord.js/src/structures/MessagePayload.js index dad8ffedb026..86e66c53a136 100644 --- a/packages/discord.js/src/structures/MessagePayload.js +++ b/packages/discord.js/src/structures/MessagePayload.js @@ -201,6 +201,37 @@ class MessagePayload { } } + if (this.options.messageReference) { + const reference = this.options.messageReference; + + if (typeof reference === 'object') { + const message_id = this.isMessage + ? (reference.id ?? reference) + : this.target.messages.resolveId(reference.id ?? reference.messageId); + const channel_id = this.target.client.channels.resolveId(reference.channelId); + const guild_id = this.target.client.guilds.resolveId(reference.guildId); + + if (message_id) { + message_reference = { + message_id, + channel_id, + guild_id, + type: reference.type, + fail_if_not_exists: reference.failIfNotExists ?? this.target.client.options.failIfNotExists, + }; + } + } else { + const message_id = this.isMessage ? reference : this.target.messages.resolveId(reference); + + if (message_id) { + message_reference = { + message_id, + fail_if_not_exists: reference.failIfNotExists ?? this.target.client.options.failIfNotExists, + }; + } + } + } + const attachments = this.options.files?.map((file, index) => ({ id: index.toString(), description: file.description, diff --git a/packages/discord.js/src/structures/interfaces/TextBasedChannel.js b/packages/discord.js/src/structures/interfaces/TextBasedChannel.js index d2e408580253..6c7ae7f0df18 100644 --- a/packages/discord.js/src/structures/interfaces/TextBasedChannel.js +++ b/packages/discord.js/src/structures/interfaces/TextBasedChannel.js @@ -114,6 +114,8 @@ class TextBasedChannel { * The options for sending a message. * @typedef {BaseMessageCreateOptions} MessageCreateOptions * @property {ReplyOptions} [reply] The options for replying to a message + * This property is deprecated. Use `messageReference` instead. + * @property {MessageReference|MessageResolvable} [messageReference] The options for a reference to a message */ /** diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 2ac75da47a11..6f94757191bf 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -2191,6 +2191,7 @@ export class Message extends Base { public equals(message: Message, rawData: unknown): boolean; public fetchReference(): Promise>>; public fetchWebhook(): Promise; + public forward(channel: TextBasedChannelResolvable): Promise; public crosspost(): Promise>>; public fetch(force?: boolean): Promise>>; public pin(reason?: string): Promise>>; @@ -4310,6 +4311,7 @@ export abstract class MessageManager extends public fetch(options: MessageResolvable | FetchMessageOptions): Promise>; public fetch(options?: FetchMessagesOptions): Promise>>; public fetchPinned(cache?: boolean): Promise>>; + public forward(reference: Message | Omit): Promise>; public react(message: MessageResolvable, emoji: EmojiIdentifierResolvable): Promise; public pin(message: MessageResolvable, reason?: string): Promise; public unpin(message: MessageResolvable, reason?: string): Promise; @@ -6270,7 +6272,9 @@ export interface MessageCreateOptions extends BaseMessageOptionsWithPoll { tts?: boolean; nonce?: string | number; enforceNonce?: boolean; + /** @deprecated Use {@link MessageCreateOptions.messageReference} instead */ reply?: ReplyOptions; + messageReference?: MessageResolvable | (MessageReference & { failIfNotExists?: boolean }); stickers?: readonly StickerResolvable[]; flags?: BitFieldResolvable< Extract, @@ -6516,6 +6520,7 @@ export interface ReactionCollectorOptions extends CollectorOptions<[MessageReact maxUsers?: number; } +/** @deprecated Use {@link MessageReference} instead. */ export interface ReplyOptions { messageReference: MessageResolvable; failIfNotExists?: boolean;