Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: forwarding messages #10559

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions packages/discord.js/src/managers/MessageManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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<Message>}
*/
async forward(reference) {
if (!reference) throw new DiscordjsError(ErrorCodes.MessageReferenceMissing);
const message_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
Expand Down
24 changes: 21 additions & 3 deletions packages/discord.js/src/structures/Message.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const {
ChannelType,
MessageType,
MessageFlags,
MessageReferenceType,
PermissionFlagsBits,
} = require('discord-api-types/v10');
const Attachment = require('./Attachment');
Expand All @@ -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');
Expand Down Expand Up @@ -794,6 +795,20 @@ class Message extends Base {
return message;
}

/**
* Forwards this message.
* @param {ChannelResolvable} channel The channel to forward this message to
* @returns {Promise<Message>}
*/
async forward(channel) {
const resolvedChannel = this.client.channels.resolve(channel);

if (!resolvedChannel) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'channel', 'ChannelResolvable');

const message = await resolvedChannel.messages.forward(this);
return message;
}

/**
* Whether the message is crosspostable by the client user
* @type {boolean}
Expand Down Expand Up @@ -947,8 +962,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,
},
});
Expand Down
14 changes: 10 additions & 4 deletions packages/discord.js/src/structures/MessagePayload.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,13 +171,19 @@ class MessagePayload {
}

let message_reference;
if (typeof this.options.reply === 'object') {
const reference = this.options.reply.messageReference;
const message_id = this.isMessage ? (reference.id ?? reference) : this.target.messages.resolveId(reference);
if (this.options.messageReference) {
const reference = this.options.messageReference;
const message_id = this.target.messages.resolveId(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,
fail_if_not_exists: this.options.reply.failIfNotExists ?? this.target.client.options.failIfNotExists,
channel_id,
guild_id,
type: reference.type,
fail_if_not_exists: reference.failIfNotExists ?? this.target.client.options.failIfNotExists,
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ class TextBasedChannel {
/**
* The options for sending a message.
* @typedef {BaseMessageCreateOptions} MessageCreateOptions
* @property {ReplyOptions} [reply] The options for replying to a message
* @property {MessageReference|MessageResolvable} [messageReference] The options for a reference to a message
*/

/**
Expand Down
14 changes: 6 additions & 8 deletions packages/discord.js/typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2191,6 +2191,7 @@ export class Message<InGuild extends boolean = boolean> extends Base {
public equals(message: Message, rawData: unknown): boolean;
public fetchReference(): Promise<OmitPartialGroupDMChannel<Message<InGuild>>>;
public fetchWebhook(): Promise<Webhook>;
public forward(channel: TextBasedChannelResolvable): Promise<Message>;
public crosspost(): Promise<OmitPartialGroupDMChannel<Message<InGuild>>>;
public fetch(force?: boolean): Promise<OmitPartialGroupDMChannel<Message<InGuild>>>;
public pin(reason?: string): Promise<OmitPartialGroupDMChannel<Message<InGuild>>>;
Expand Down Expand Up @@ -4309,6 +4310,7 @@ export abstract class MessageManager<InGuild extends boolean = boolean> extends
public fetch(options: MessageResolvable | FetchMessageOptions): Promise<Message<InGuild>>;
public fetch(options?: FetchMessagesOptions): Promise<Collection<Snowflake, Message<InGuild>>>;
public fetchPinned(cache?: boolean): Promise<Collection<Snowflake, Message<InGuild>>>;
public forward(reference: Omit<MessageReference, 'type'>): Promise<Message<InGuild>>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels out of place in the manager...

  • we don't have create
  • we don't have reply

So we should either move create/reply into this manager, or drop this from here altogether. And I'm leaning more towards the former.. thoughts @discordjs/core?

Copy link
Member

@Jiralite Jiralite Nov 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we were to move anything at all, it'd probably just be create() (replying and forwarding can happen within this method).

public react(message: MessageResolvable, emoji: EmojiIdentifierResolvable): Promise<void>;
public pin(message: MessageResolvable, reason?: string): Promise<void>;
public unpin(message: MessageResolvable, reason?: string): Promise<void>;
Expand Down Expand Up @@ -6273,7 +6275,7 @@ export interface MessageCreateOptions extends BaseMessageOptionsWithPoll {
tts?: boolean;
nonce?: string | number;
enforceNonce?: boolean;
reply?: ReplyOptions;
messageReference?: MessageReference & { failIfNotExists?: boolean };
Jiralite marked this conversation as resolved.
Show resolved Hide resolved
stickers?: readonly StickerResolvable[];
flags?: BitFieldResolvable<
Extract<MessageFlagsString, 'SuppressEmbeds' | 'SuppressNotifications'>,
Expand Down Expand Up @@ -6519,12 +6521,7 @@ export interface ReactionCollectorOptions extends CollectorOptions<[MessageReact
maxUsers?: number;
}

export interface ReplyOptions {
messageReference: MessageResolvable;
failIfNotExists?: boolean;
}

export interface MessageReplyOptions extends Omit<MessageCreateOptions, 'reply'> {
export interface MessageReplyOptions extends Omit<MessageCreateOptions, 'messageReference'> {
failIfNotExists?: boolean;
}

Expand Down Expand Up @@ -6799,7 +6796,8 @@ export interface WebhookFetchMessageOptions {
threadId?: Snowflake;
}

export interface WebhookMessageCreateOptions extends Omit<MessageCreateOptions, 'nonce' | 'reply' | 'stickers'> {
export interface WebhookMessageCreateOptions
extends Omit<MessageCreateOptions, 'nonce' | 'messageReference' | 'stickers'> {
username?: string;
avatarURL?: string;
threadId?: Snowflake;
Expand Down