feat: Implement forward feature with Message.forward and Message.snapshot

This commit is contained in:
Elysia
2024-10-05 16:59:09 +07:00
parent d16bd89f4a
commit 707c4397a4
5 changed files with 103 additions and 35 deletions

View File

@@ -14,7 +14,13 @@ const { Sticker } = require('./Sticker');
const Application = require('./interfaces/Application'); const Application = require('./interfaces/Application');
const { Error } = require('../errors'); const { Error } = require('../errors');
const ReactionManager = require('../managers/ReactionManager'); const ReactionManager = require('../managers/ReactionManager');
const { InteractionTypes, MessageTypes, SystemMessageTypes, MessageComponentTypes } = require('../util/Constants'); const {
InteractionTypes,
MessageTypes,
SystemMessageTypes,
MessageComponentTypes,
MessageReferenceTypes,
} = require('../util/Constants');
const MessageFlags = require('../util/MessageFlags'); const MessageFlags = require('../util/MessageFlags');
const Permissions = require('../util/Permissions'); const Permissions = require('../util/Permissions');
const SnowflakeUtil = require('../util/SnowflakeUtil'); const SnowflakeUtil = require('../util/SnowflakeUtil');
@@ -73,7 +79,7 @@ class Message extends Base {
* The timestamp the message was sent at * The timestamp the message was sent at
* @type {number} * @type {number}
*/ */
this.createdTimestamp = SnowflakeUtil.timestampFrom(this.id); this.createdTimestamp = this.id ? SnowflakeUtil.timestampFrom(this.id) : new Date(data.timestamp).getTime();
if ('type' in data) { if ('type' in data) {
/** /**
@@ -327,6 +333,7 @@ class Message extends Base {
* @property {Snowflake} channelId The channel's id the message was referenced * @property {Snowflake} channelId The channel's id the message was referenced
* @property {?Snowflake} guildId The guild's id the message was referenced * @property {?Snowflake} guildId The guild's id the message was referenced
* @property {?Snowflake} messageId The message's id that was referenced * @property {?Snowflake} messageId The message's id that was referenced
* @property {?MessageReferenceType} type The type of the message reference
*/ */
if ('message_reference' in data) { if ('message_reference' in data) {
@@ -338,6 +345,7 @@ class Message extends Base {
channelId: data.message_reference.channel_id, channelId: data.message_reference.channel_id,
guildId: data.message_reference.guild_id, guildId: data.message_reference.guild_id,
messageId: data.message_reference.message_id, messageId: data.message_reference.message_id,
type: MessageReferenceTypes[data.message_reference.type ?? 0],
}; };
} else { } else {
this.reference ??= null; this.reference ??= null;
@@ -395,6 +403,19 @@ class Message extends Base {
} else { } else {
this.call ??= null; this.call ??= null;
} }
if ('message_snapshots' in data) {
/**
* A collection of message snapshots
* @type {?Array<Partial<Message>>}
*/
this.snapshots = [];
for (const snapshot of data.message_snapshots) {
this.snapshots.push(new Message(this.client, snapshot.message));
}
} else {
this.snapshots = null;
}
} }
/** /**
@@ -839,6 +860,29 @@ class Message extends Base {
return this.channel.send(data); return this.channel.send(data);
} }
/**
* Forwards this message to a channel.
* @param {TextBasedChannelResolvable} channel The channel to forward the message to
* @returns {Promise<Message>}
*/
forward(channel) {
channel = this.client.channels.resolve(channel);
if (!channel || !this.channelId) return Promise.reject(new Error('CHANNEL_NOT_CACHED'));
const data = MessagePayload.create(
this,
{},
{
forward: {
channel_id: this.channelId,
guild_id: this.guildId,
message_id: this.id,
type: 1,
},
},
);
return channel.send(data);
}
/** /**
* A number that is allowed to be the duration (in minutes) of inactivity after which a thread is automatically * A number that is allowed to be the duration (in minutes) of inactivity after which a thread is automatically
* archived. This can be: * archived. This can be:

View File

@@ -189,6 +189,9 @@ class MessagePayload {
}; };
} }
} }
if (typeof this.options.forward === 'object') {
message_reference = this.options.forward;
}
const attachments = this.options.files?.map((file, index) => ({ const attachments = this.options.files?.map((file, index) => ({
id: index.toString(), id: index.toString(),

View File

@@ -789,6 +789,18 @@ exports.MessageTypes = [
'NITRO_NOTIFICATION', 'NITRO_NOTIFICATION',
]; ];
/**
* The type of a message reference, e.g. `DEFAULT`. Here are the available types:
* * DEFAULT
* * FORWARD
* @typedef {string} MessageReferenceType
* @see {@link https://discord.com/developers/docs/resources/message#message-reference-types}
*/
exports.MessageReferenceTypes = [
'DEFAULT', // 0
'FORWARD',
];
/** /**
* The name of an item to be swept in Sweepers * The name of an item to be swept in Sweepers
* * `applicationCommands` - both global and guild commands * * `applicationCommands` - both global and guild commands

17
typings/enums.d.ts vendored
View File

@@ -77,18 +77,18 @@ export const enum ChannelTypes {
GUILD_MEDIA = 16, GUILD_MEDIA = 16,
} }
export const enum SortOrderType { export const enum SortOrderTypes {
LATEST_ACTIVITY = 1, LATEST_ACTIVITY = 1,
CREATION_DATE = 2, CREATION_DATE = 2,
} }
export const enum ForumLayoutType { export const enum ForumLayoutTypes {
NOT_SET = 0, NOT_SET = 0,
LIST_VIEW = 1, LIST_VIEW = 1,
GALLERY_VIEW = 2, GALLERY_VIEW = 2,
} }
export const enum MessagePollLayoutType { export const enum MessagePollLayoutTypes {
DEFAULT = 1, DEFAULT = 1,
IMAGE_ONLY_ANSWERS, IMAGE_ONLY_ANSWERS,
} }
@@ -140,6 +140,11 @@ export const enum MessageTypes {
NITRO_NOTIFICATION, NITRO_NOTIFICATION,
} }
export const enum MessageReferenceTypes {
DEFAULT = 0,
FORWARD,
}
export const enum DefaultMessageNotificationLevels { export const enum DefaultMessageNotificationLevels {
ALL_MESSAGES = 0, ALL_MESSAGES = 0,
ONLY_MENTIONS = 1, ONLY_MENTIONS = 1,
@@ -186,14 +191,14 @@ export const enum InteractionTypes {
MODAL_SUBMIT = 5, MODAL_SUBMIT = 5,
} }
export const enum InviteTargetType { export const enum InviteTargetTypes {
STREAM = 1, STREAM = 1,
EMBEDDED_APPLICATION, EMBEDDED_APPLICATION,
ROLE_SUBSCRIPTIONS, ROLE_SUBSCRIPTIONS,
CREATOR_PAGE, CREATOR_PAGE,
} }
export const enum InviteType { export const enum InviteTypes {
GUILD, GUILD,
GROUP_DM, GROUP_DM,
FRIEND, FRIEND,
@@ -312,7 +317,7 @@ export enum ApplicationRoleConnectionMetadataTypes {
BOOLEAN_NOT_EQUAL, BOOLEAN_NOT_EQUAL,
} }
export const enum RelationshipType { export const enum RelationshipTypes {
NONE = 0, NONE = 0,
FRIEND = 1, FRIEND = 1,
BLOCKED = 2, BLOCKED = 2,

58
typings/index.d.ts vendored
View File

@@ -77,7 +77,7 @@ import {
ExplicitContentFilterLevels, ExplicitContentFilterLevels,
InteractionResponseTypes, InteractionResponseTypes,
InteractionTypes, InteractionTypes,
InviteTargetType, InviteTargetTypes,
MembershipStates, MembershipStates,
MessageButtonStyles, MessageButtonStyles,
MessageComponentTypes, MessageComponentTypes,
@@ -96,14 +96,15 @@ import {
GuildScheduledEventStatuses, GuildScheduledEventStatuses,
GuildScheduledEventPrivacyLevels, GuildScheduledEventPrivacyLevels,
VideoQualityModes, VideoQualityModes,
SortOrderType, SortOrderTypes,
ForumLayoutType, ForumLayoutTypes,
ApplicationRoleConnectionMetadataTypes, ApplicationRoleConnectionMetadataTypes,
RelationshipType, RelationshipTypes,
SelectMenuComponentTypes, SelectMenuComponentTypes,
InviteType, InviteTypes,
MessagePollLayoutType, MessagePollLayoutTypes,
ReactionTypes, ReactionTypes,
MessageReferenceTypes,
} from './enums'; } from './enums';
import { import {
APIApplicationRoleConnectionMetadata, APIApplicationRoleConnectionMetadata,
@@ -1941,10 +1942,10 @@ export class Invite extends Base {
public maxUses: number | null; public maxUses: number | null;
public memberCount: number; public memberCount: number;
public presenceCount: number; public presenceCount: number;
public type: InviteType | null; public type: InviteTypes | null;
public targetApplication: IntegrationApplication | null; public targetApplication: IntegrationApplication | null;
public targetUser: User | null; public targetUser: User | null;
public targetType: InviteTargetType | null; public targetType: InviteTargetTypes | null;
public temporary: boolean | null; public temporary: boolean | null;
public readonly url: string; public readonly url: string;
public uses: number | null; public uses: number | null;
@@ -2084,6 +2085,7 @@ export class Message<Cached extends boolean = boolean> extends Base {
public call: MessageCall | null; public call: MessageCall | null;
public flags: Readonly<MessageFlags>; public flags: Readonly<MessageFlags>;
public reference: MessageReference | null; public reference: MessageReference | null;
public snapshots: Array<Partial<Message<false>>> | null;
public position: number | null; public position: number | null;
public awaitReactions(options?: AwaitReactionsOptions): Promise<Collection<Snowflake | string, MessageReaction>>; public awaitReactions(options?: AwaitReactionsOptions): Promise<Collection<Snowflake | string, MessageReaction>>;
public createReactionCollector(options?: ReactionCollectorOptions): ReactionCollector; public createReactionCollector(options?: ReactionCollectorOptions): ReactionCollector;
@@ -2098,6 +2100,7 @@ export class Message<Cached extends boolean = boolean> extends Base {
public react(emoji: EmojiIdentifierResolvable, burst?: boolean): Promise<MessageReaction>; public react(emoji: EmojiIdentifierResolvable, burst?: boolean): Promise<MessageReaction>;
public removeAttachments(): Promise<Message>; public removeAttachments(): Promise<Message>;
public reply(options: string | MessagePayload | ReplyMessageOptions): Promise<Message>; public reply(options: string | MessagePayload | ReplyMessageOptions): Promise<Message>;
public forward(channel: TextBasedChannelResolvable): Promise<Message>;
public resolveComponent(customId: string): MessageActionRowComponent | null; public resolveComponent(customId: string): MessageActionRowComponent | null;
public startThread(options: StartThreadOptions): Promise<ThreadChannel>; public startThread(options: StartThreadOptions): Promise<ThreadChannel>;
public suppressEmbeds(suppress?: boolean): Promise<Message>; public suppressEmbeds(suppress?: boolean): Promise<Message>;
@@ -2540,7 +2543,7 @@ export class MessagePoll {
public constructor(data: MessagePoll | object); public constructor(data: MessagePoll | object);
public question: MessagePollMedia | null; public question: MessagePollMedia | null;
public answers: Collection<number, MessagePollMedia>; public answers: Collection<number, MessagePollMedia>;
public layoutType: MessagePollLayoutType | null; public layoutType: MessagePollLayoutTypes | null;
public allowMultiSelect: boolean; public allowMultiSelect: boolean;
public expiry: Date | null; public expiry: Date | null;
public results: MessagePollResult | null; public results: MessagePollResult | null;
@@ -3245,7 +3248,7 @@ export abstract class ThreadOnlyChannel extends TextBasedChannelMixin(GuildChann
public defaultAutoArchiveDuration: ThreadAutoArchiveDuration | null; public defaultAutoArchiveDuration: ThreadAutoArchiveDuration | null;
public nsfw: boolean; public nsfw: boolean;
public topic: string | null; public topic: string | null;
public defaultSortOrder: SortOrderType | null; public defaultSortOrder: SortOrderTypes | null;
public setAvailableTags(tags: GuildForumTagData[], reason?: string): Promise<this>; public setAvailableTags(tags: GuildForumTagData[], reason?: string): Promise<this>;
public setDefaultReactionEmoji(emojiId: DefaultReactionEmoji | null, reason?: string): Promise<this>; public setDefaultReactionEmoji(emojiId: DefaultReactionEmoji | null, reason?: string): Promise<this>;
public setDefaultThreadRateLimitPerUser(rateLimit: number, reason?: string): Promise<this>; public setDefaultThreadRateLimitPerUser(rateLimit: number, reason?: string): Promise<this>;
@@ -3256,13 +3259,13 @@ export abstract class ThreadOnlyChannel extends TextBasedChannelMixin(GuildChann
reason?: string, reason?: string,
): Promise<this>; ): Promise<this>;
public setTopic(topic: string | null, reason?: string): Promise<this>; public setTopic(topic: string | null, reason?: string): Promise<this>;
public setDefaultSortOrder(defaultSortOrder: SortOrderType | null, reason?: string): Promise<this>; public setDefaultSortOrder(defaultSortOrder: SortOrderTypes | null, reason?: string): Promise<this>;
} }
export class ForumChannel extends ThreadOnlyChannel { export class ForumChannel extends ThreadOnlyChannel {
public type: 'GUILD_FORUM'; public type: 'GUILD_FORUM';
public defaultForumLayout: ForumLayoutType; public defaultForumLayout: ForumLayoutTypes;
public setDefaultForumLayout(defaultForumLayout: ForumLayoutType, reason?: string): Promise<this>; public setDefaultForumLayout(defaultForumLayout: ForumLayoutTypes, reason?: string): Promise<this>;
} }
export class MediaChannel extends ThreadOnlyChannel { export class MediaChannel extends ThreadOnlyChannel {
@@ -3419,7 +3422,7 @@ export class User extends PartialTextBasedChannel(Base) {
public username: string; public username: string;
public readonly note: string | undefined; public readonly note: string | undefined;
public readonly voice?: VoiceState; public readonly voice?: VoiceState;
public readonly relationship: RelationshipType; public readonly relationship: RelationshipTypes;
public readonly friendNickname: string | null | undefined; public readonly friendNickname: string | null | undefined;
public clan: UserClan | null; public clan: UserClan | null;
public avatarURL(options?: ImageURLOptions): string | null; public avatarURL(options?: ImageURLOptions): string | null;
@@ -3872,7 +3875,7 @@ export const Constants: {
GuildScheduledEventStatuses: EnumHolder<typeof GuildScheduledEventStatuses>; GuildScheduledEventStatuses: EnumHolder<typeof GuildScheduledEventStatuses>;
IntegrationExpireBehaviors: IntegrationExpireBehaviors[]; IntegrationExpireBehaviors: IntegrationExpireBehaviors[];
SelectMenuComponentTypes: EnumHolder<typeof SelectMenuComponentTypes>; SelectMenuComponentTypes: EnumHolder<typeof SelectMenuComponentTypes>;
RelationshipTypes: EnumHolder<typeof RelationshipType>; RelationshipTypes: EnumHolder<typeof RelationshipTypes>;
MembershipStates: EnumHolder<typeof MembershipStates>; MembershipStates: EnumHolder<typeof MembershipStates>;
MessageButtonStyles: EnumHolder<typeof MessageButtonStyles>; MessageButtonStyles: EnumHolder<typeof MessageButtonStyles>;
MessageComponentTypes: EnumHolder<typeof MessageComponentTypes>; MessageComponentTypes: EnumHolder<typeof MessageComponentTypes>;
@@ -4073,22 +4076,22 @@ export class RelationshipManager extends BaseManager {
client: Client, client: Client,
data: { data: {
user: RawUserData; user: RawUserData;
type: RelationshipType; type: RelationshipTypes;
since?: string; since?: string;
nickname: string | null | undefined; nickname: string | null | undefined;
id: Snowflake; id: Snowflake;
}[], }[],
); );
public cache: Collection<Snowflake, RelationshipType>; public cache: Collection<Snowflake, RelationshipTypes>;
public friendNicknames: Collection<Snowflake, string | null>; public friendNicknames: Collection<Snowflake, string | null>;
public sinceCache: Collection<Snowflake, Date>; public sinceCache: Collection<Snowflake, Date>;
public readonly friendCache: Collection<Snowflake, User>; public readonly friendCache: Collection<Snowflake, User>;
public readonly blockedCache: Collection<Snowflake, User>; public readonly blockedCache: Collection<Snowflake, User>;
public readonly incomingCache: Collection<Snowflake, User>; public readonly incomingCache: Collection<Snowflake, User>;
public readonly outgoingCache: Collection<Snowflake, User>; public readonly outgoingCache: Collection<Snowflake, User>;
public toJSON(): { type: RelationshipType; since: string; nickname: string | null | undefined; id: Snowflake }[]; public toJSON(): { type: RelationshipTypes; since: string; nickname: string | null | undefined; id: Snowflake }[];
public resolveId(user: UserResolvable): Snowflake | undefined; public resolveId(user: UserResolvable): Snowflake | undefined;
public fetch(user?: UserResolvable, options?: BaseFetchOptions): Promise<RelationshipType | RelationshipManager>; public fetch(user?: UserResolvable, options?: BaseFetchOptions): Promise<RelationshipTypes | RelationshipManager>;
public deleteRelationship(user: UserResolvable): Promise<boolean>; public deleteRelationship(user: UserResolvable): Promise<boolean>;
public sendFriendRequest(options: FriendRequestOptions): Promise<boolean>; public sendFriendRequest(options: FriendRequestOptions): Promise<boolean>;
public addFriend(user: UserResolvable): Promise<boolean>; public addFriend(user: UserResolvable): Promise<boolean>;
@@ -5424,8 +5427,8 @@ export interface CategoryCreateChannelOptions {
videoQualityMode?: VideoQualityMode; videoQualityMode?: VideoQualityMode;
availableTags?: GuildForumTagData[]; availableTags?: GuildForumTagData[];
defaultReactionEmoji?: DefaultReactionEmoji; defaultReactionEmoji?: DefaultReactionEmoji;
defaultSortOrder?: SortOrderType; defaultSortOrder?: SortOrderTypes;
defaultForumLayout?: ForumLayoutType; defaultForumLayout?: ForumLayoutTypes;
defaultThreadRateLimitPerUser?: number; defaultThreadRateLimitPerUser?: number;
reason?: string; reason?: string;
} }
@@ -5454,8 +5457,8 @@ export interface ChannelData {
availableTags?: GuildForumTagData[]; availableTags?: GuildForumTagData[];
defaultReactionEmoji?: DefaultReactionEmoji; defaultReactionEmoji?: DefaultReactionEmoji;
defaultThreadRateLimitPerUser?: number; defaultThreadRateLimitPerUser?: number;
defaultSortOrder?: SortOrderType | null; defaultSortOrder?: SortOrderTypes | null;
defaultForumLayout?: ForumLayoutType; defaultForumLayout?: ForumLayoutTypes;
flags?: ChannelFlagsResolvable; flags?: ChannelFlagsResolvable;
} }
@@ -5604,18 +5607,18 @@ export interface ClientEvents extends BaseClientEvents {
guildAuditLogEntryCreate: [auditLogEntry: GuildAuditLogsEntry, guild: Guild]; guildAuditLogEntryCreate: [auditLogEntry: GuildAuditLogsEntry, guild: Guild];
unhandledPacket: [packet: { t?: string; d: any }, shard: number]; unhandledPacket: [packet: { t?: string; d: any }, shard: number];
relationshipAdd: [userId: Snowflake, shouldNotify: boolean]; relationshipAdd: [userId: Snowflake, shouldNotify: boolean];
relationshipRemove: [userId: Snowflake, type: RelationshipType, nickname: string | null]; relationshipRemove: [userId: Snowflake, type: RelationshipTypes, nickname: string | null];
relationshipUpdate: [ relationshipUpdate: [
userId: Snowflake, userId: Snowflake,
oldData: { oldData: {
nickname: string | null; nickname: string | null;
since: Date; since: Date;
type: RelationshipType; type: RelationshipTypes;
}, },
newData: { newData: {
nickname: string | null; nickname: string | null;
since: Date; since: Date;
type: RelationshipType; type: RelationshipTypes;
}, },
]; ];
channelRecipientAdd: [channel: GroupDMChannel, user: User]; channelRecipientAdd: [channel: GroupDMChannel, user: User];
@@ -6763,7 +6766,7 @@ export interface CreateInviteOptions {
reason?: string; reason?: string;
targetApplication?: ApplicationResolvable; targetApplication?: ApplicationResolvable;
targetUser?: UserResolvable; targetUser?: UserResolvable;
targetType?: InviteTargetType; targetType?: InviteTargetTypes;
} }
export type IntegrationExpireBehaviors = 'REMOVE_ROLE' | 'KICK'; export type IntegrationExpireBehaviors = 'REMOVE_ROLE' | 'KICK';
@@ -7016,6 +7019,7 @@ export interface MessageReference {
channelId: Snowflake; channelId: Snowflake;
guildId: Snowflake | undefined; guildId: Snowflake | undefined;
messageId: Snowflake | undefined; messageId: Snowflake | undefined;
type: MessageReferenceTypes
} }
export type MessageResolvable = Message | Snowflake; export type MessageResolvable = Message | Snowflake;