From 89c076c89e90e8f5912786e8899ced9e8eea6003 Mon Sep 17 00:00:00 2001 From: Naiyar <137700126+imnaiyar@users.noreply.github.com> Date: Sat, 8 Feb 2025 03:42:30 +0600 Subject: [PATCH] feat: message forwards (#10733) * feat: message forwards * fix: spelling * feat: add guildId option for forward * refactor: type * refactor: do not use ID suffix for resolvables * Update TextBasedChannel.js --------- Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com> --- packages/discord.js/src/structures/Message.js | 25 ++++++++++++++++++- .../src/structures/MessagePayload.js | 18 ++++++++++++- .../structures/interfaces/TextBasedChannel.js | 8 ++++++ packages/discord.js/typings/index.d.ts | 21 ++++++++++++++-- 4 files changed, 68 insertions(+), 4 deletions(-) diff --git a/packages/discord.js/src/structures/Message.js b/packages/discord.js/src/structures/Message.js index 41fa9b8106ef..02c0c601431c 100644 --- a/packages/discord.js/src/structures/Message.js +++ b/packages/discord.js/src/structures/Message.js @@ -9,6 +9,7 @@ const { MessageType, MessageFlags, PermissionFlagsBits, + MessageReferenceType, } = require('discord-api-types/v10'); const Attachment = require('./Attachment'); const Base = require('./Base'); @@ -704,7 +705,11 @@ class Message extends Base { * @readonly */ get editable() { - const precheck = Boolean(this.author.id === this.client.user.id && (!this.guild || this.channel?.viewable)); + const precheck = Boolean( + this.author.id === this.client.user.id && + (!this.guild || this.channel?.viewable) && + this.reference?.type !== MessageReferenceType.Forward, + ); // Regardless of permissions thread messages cannot be edited if // the thread is archived or the thread is locked and the bot does not have permission to manage threads. @@ -956,6 +961,24 @@ class Message extends Base { return this.channel.send(data); } + /** + * Forwards this message + * + * @param {TextBasedChannelResolvable} channel The channel to forward this message to. + * @returns {Promise} + */ + forward(channel) { + const resolvedChannel = this.client.channels.resolve(channel); + if (!resolvedChannel) throw new DiscordjsError(ErrorCodes.InvalidType, 'channel', 'TextBasedChannelResolvable'); + return resolvedChannel.send({ + forward: { + message: this.id, + channel: this.channelId, + guild: this.guildId, + }, + }); + } + /** * Options for starting a thread on a message. * @typedef {Object} StartThreadOptions diff --git a/packages/discord.js/src/structures/MessagePayload.js b/packages/discord.js/src/structures/MessagePayload.js index ea3672a86802..a24ddaab6724 100644 --- a/packages/discord.js/src/structures/MessagePayload.js +++ b/packages/discord.js/src/structures/MessagePayload.js @@ -3,7 +3,7 @@ const { Buffer } = require('node:buffer'); const { lazy, isJSONEncodable } = require('@discordjs/util'); const { DiscordSnowflake } = require('@sapphire/snowflake'); -const { MessageFlags } = require('discord-api-types/v10'); +const { MessageFlags, MessageReferenceType } = require('discord-api-types/v10'); const ActionRowBuilder = require('./ActionRowBuilder'); const { DiscordjsError, DiscordjsRangeError, ErrorCodes } = require('../errors'); const { resolveFile } = require('../util/DataResolver'); @@ -199,6 +199,22 @@ class MessagePayload { } } + if (typeof this.options.forward === 'object') { + const reference = this.options.forward.message; + const channel_id = reference.channelId ?? this.target.client.channels.resolveId(this.options.forward.channel); + const guild_id = reference.guildId ?? this.target.client.guilds.resolveId(this.options.forward.guild); + const message_id = this.target.messages.resolveId(reference); + if (message_id) { + if (!channel_id) throw new DiscordjsError(ErrorCodes.InvalidType, 'channelId', 'TextBasedChannelResolvable'); + message_reference = { + type: MessageReferenceType.Forward, + message_id, + channel_id, + guild_id: guild_id ?? undefined, + }; + } + } + 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 bac708143d20..5bfec44a85e9 100644 --- a/packages/discord.js/src/structures/interfaces/TextBasedChannel.js +++ b/packages/discord.js/src/structures/interfaces/TextBasedChannel.js @@ -110,10 +110,18 @@ class TextBasedChannel { * Only `MessageFlags.SuppressEmbeds` and `MessageFlags.SuppressNotifications` can be set. */ + /** + * @typedef {Object} ForwardOptions + * @property {MessageResolvable} message The originating message + * @property {TextBasedChannelResolvable} [channel] The channel of the originating message + * @property {GuildResolvable} [guild] The guild of the originating message + */ + /** * The options for sending a message. * @typedef {BaseMessageCreateOptions} MessageCreateOptions * @property {ReplyOptions} [reply] The options for replying to a message + * @property {ForwardOptions} [forward] The options for forwarding a message */ /** diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 2e4c483600b9..11e7e7b9b805 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -2310,6 +2310,7 @@ export class Message extends Base { public reply( options: string | MessagePayload | MessageReplyOptions, ): Promise>>; + public forward(channel: Exclude): Promise; public resolveComponent(customId: string): MessageActionRowComponent | null; public startThread(options: StartThreadOptions): Promise>; public suppressEmbeds(suppress?: boolean): Promise>>; @@ -6759,6 +6760,7 @@ export interface MessageCreateOptions extends BaseMessageOptionsWithPoll { nonce?: string | number; enforceNonce?: boolean; reply?: ReplyOptions; + forward?: ForwardOptions; stickers?: readonly StickerResolvable[]; flags?: | BitFieldResolvable< @@ -7011,7 +7013,21 @@ export interface ReplyOptions { failIfNotExists?: boolean; } -export interface MessageReplyOptions extends Omit { +export interface BaseForwardOptions { + message: MessageResolvable; + channel?: Exclude; + guild?: GuildResolvable; +} + +export type ForwardOptionsWithMandatoryChannel = BaseForwardOptions & Required>; + +export interface ForwardOptionsWithOptionalChannel extends BaseForwardOptions { + message: Exclude; +} + +export type ForwardOptions = ForwardOptionsWithMandatoryChannel | ForwardOptionsWithOptionalChannel; + +export interface MessageReplyOptions extends Omit { failIfNotExists?: boolean; } @@ -7290,7 +7306,8 @@ export interface WebhookFetchMessageOptions { threadId?: Snowflake; } -export interface WebhookMessageCreateOptions extends Omit { +export interface WebhookMessageCreateOptions + extends Omit { username?: string; avatarURL?: string; threadId?: Snowflake;