From 707c4397a4460340182518704cc98dad89bea6d4 Mon Sep 17 00:00:00 2001 From: Elysia <71698422+aiko-chan-ai@users.noreply.github.com> Date: Sat, 5 Oct 2024 16:59:09 +0700 Subject: [PATCH] feat: Implement forward feature with Message.forward and Message.snapshot --- src/structures/Message.js | 48 ++++++++++++++++++++++++-- src/structures/MessagePayload.js | 3 ++ src/util/Constants.js | 12 +++++++ typings/enums.d.ts | 17 ++++++---- typings/index.d.ts | 58 +++++++++++++++++--------------- 5 files changed, 103 insertions(+), 35 deletions(-) diff --git a/src/structures/Message.js b/src/structures/Message.js index 59df173..b521ef2 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -14,7 +14,13 @@ const { Sticker } = require('./Sticker'); const Application = require('./interfaces/Application'); const { Error } = require('../errors'); 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 Permissions = require('../util/Permissions'); const SnowflakeUtil = require('../util/SnowflakeUtil'); @@ -73,7 +79,7 @@ class Message extends Base { * The timestamp the message was sent at * @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) { /** @@ -327,6 +333,7 @@ class Message extends Base { * @property {Snowflake} channelId The channel'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 {?MessageReferenceType} type The type of the message reference */ if ('message_reference' in data) { @@ -338,6 +345,7 @@ class Message extends Base { channelId: data.message_reference.channel_id, guildId: data.message_reference.guild_id, messageId: data.message_reference.message_id, + type: MessageReferenceTypes[data.message_reference.type ?? 0], }; } else { this.reference ??= null; @@ -395,6 +403,19 @@ class Message extends Base { } else { this.call ??= null; } + + if ('message_snapshots' in data) { + /** + * A collection of message snapshots + * @type {?Array>} + */ + 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); } + /** + * Forwards this message to a channel. + * @param {TextBasedChannelResolvable} channel The channel to forward the message to + * @returns {Promise} + */ + 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 * archived. This can be: diff --git a/src/structures/MessagePayload.js b/src/structures/MessagePayload.js index 0d4acac..257d3d9 100644 --- a/src/structures/MessagePayload.js +++ b/src/structures/MessagePayload.js @@ -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) => ({ id: index.toString(), diff --git a/src/util/Constants.js b/src/util/Constants.js index e086c85..84d0669 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -789,6 +789,18 @@ exports.MessageTypes = [ '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 * * `applicationCommands` - both global and guild commands diff --git a/typings/enums.d.ts b/typings/enums.d.ts index 2d4eaa9..ee54112 100644 --- a/typings/enums.d.ts +++ b/typings/enums.d.ts @@ -77,18 +77,18 @@ export const enum ChannelTypes { GUILD_MEDIA = 16, } -export const enum SortOrderType { +export const enum SortOrderTypes { LATEST_ACTIVITY = 1, CREATION_DATE = 2, } -export const enum ForumLayoutType { +export const enum ForumLayoutTypes { NOT_SET = 0, LIST_VIEW = 1, GALLERY_VIEW = 2, } -export const enum MessagePollLayoutType { +export const enum MessagePollLayoutTypes { DEFAULT = 1, IMAGE_ONLY_ANSWERS, } @@ -140,6 +140,11 @@ export const enum MessageTypes { NITRO_NOTIFICATION, } +export const enum MessageReferenceTypes { + DEFAULT = 0, + FORWARD, +} + export const enum DefaultMessageNotificationLevels { ALL_MESSAGES = 0, ONLY_MENTIONS = 1, @@ -186,14 +191,14 @@ export const enum InteractionTypes { MODAL_SUBMIT = 5, } -export const enum InviteTargetType { +export const enum InviteTargetTypes { STREAM = 1, EMBEDDED_APPLICATION, ROLE_SUBSCRIPTIONS, CREATOR_PAGE, } -export const enum InviteType { +export const enum InviteTypes { GUILD, GROUP_DM, FRIEND, @@ -312,7 +317,7 @@ export enum ApplicationRoleConnectionMetadataTypes { BOOLEAN_NOT_EQUAL, } -export const enum RelationshipType { +export const enum RelationshipTypes { NONE = 0, FRIEND = 1, BLOCKED = 2, diff --git a/typings/index.d.ts b/typings/index.d.ts index ed9bc8e..815de9e 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -77,7 +77,7 @@ import { ExplicitContentFilterLevels, InteractionResponseTypes, InteractionTypes, - InviteTargetType, + InviteTargetTypes, MembershipStates, MessageButtonStyles, MessageComponentTypes, @@ -96,14 +96,15 @@ import { GuildScheduledEventStatuses, GuildScheduledEventPrivacyLevels, VideoQualityModes, - SortOrderType, - ForumLayoutType, + SortOrderTypes, + ForumLayoutTypes, ApplicationRoleConnectionMetadataTypes, - RelationshipType, + RelationshipTypes, SelectMenuComponentTypes, - InviteType, - MessagePollLayoutType, + InviteTypes, + MessagePollLayoutTypes, ReactionTypes, + MessageReferenceTypes, } from './enums'; import { APIApplicationRoleConnectionMetadata, @@ -1941,10 +1942,10 @@ export class Invite extends Base { public maxUses: number | null; public memberCount: number; public presenceCount: number; - public type: InviteType | null; + public type: InviteTypes | null; public targetApplication: IntegrationApplication | null; public targetUser: User | null; - public targetType: InviteTargetType | null; + public targetType: InviteTargetTypes | null; public temporary: boolean | null; public readonly url: string; public uses: number | null; @@ -2084,6 +2085,7 @@ export class Message extends Base { public call: MessageCall | null; public flags: Readonly; public reference: MessageReference | null; + public snapshots: Array>> | null; public position: number | null; public awaitReactions(options?: AwaitReactionsOptions): Promise>; public createReactionCollector(options?: ReactionCollectorOptions): ReactionCollector; @@ -2098,6 +2100,7 @@ export class Message extends Base { public react(emoji: EmojiIdentifierResolvable, burst?: boolean): Promise; public removeAttachments(): Promise; public reply(options: string | MessagePayload | ReplyMessageOptions): Promise; + public forward(channel: TextBasedChannelResolvable): Promise; public resolveComponent(customId: string): MessageActionRowComponent | null; public startThread(options: StartThreadOptions): Promise; public suppressEmbeds(suppress?: boolean): Promise; @@ -2540,7 +2543,7 @@ export class MessagePoll { public constructor(data: MessagePoll | object); public question: MessagePollMedia | null; public answers: Collection; - public layoutType: MessagePollLayoutType | null; + public layoutType: MessagePollLayoutTypes | null; public allowMultiSelect: boolean; public expiry: Date | null; public results: MessagePollResult | null; @@ -3245,7 +3248,7 @@ export abstract class ThreadOnlyChannel extends TextBasedChannelMixin(GuildChann public defaultAutoArchiveDuration: ThreadAutoArchiveDuration | null; public nsfw: boolean; public topic: string | null; - public defaultSortOrder: SortOrderType | null; + public defaultSortOrder: SortOrderTypes | null; public setAvailableTags(tags: GuildForumTagData[], reason?: string): Promise; public setDefaultReactionEmoji(emojiId: DefaultReactionEmoji | null, reason?: string): Promise; public setDefaultThreadRateLimitPerUser(rateLimit: number, reason?: string): Promise; @@ -3256,13 +3259,13 @@ export abstract class ThreadOnlyChannel extends TextBasedChannelMixin(GuildChann reason?: string, ): Promise; public setTopic(topic: string | null, reason?: string): Promise; - public setDefaultSortOrder(defaultSortOrder: SortOrderType | null, reason?: string): Promise; + public setDefaultSortOrder(defaultSortOrder: SortOrderTypes | null, reason?: string): Promise; } export class ForumChannel extends ThreadOnlyChannel { public type: 'GUILD_FORUM'; - public defaultForumLayout: ForumLayoutType; - public setDefaultForumLayout(defaultForumLayout: ForumLayoutType, reason?: string): Promise; + public defaultForumLayout: ForumLayoutTypes; + public setDefaultForumLayout(defaultForumLayout: ForumLayoutTypes, reason?: string): Promise; } export class MediaChannel extends ThreadOnlyChannel { @@ -3419,7 +3422,7 @@ export class User extends PartialTextBasedChannel(Base) { public username: string; public readonly note: string | undefined; public readonly voice?: VoiceState; - public readonly relationship: RelationshipType; + public readonly relationship: RelationshipTypes; public readonly friendNickname: string | null | undefined; public clan: UserClan | null; public avatarURL(options?: ImageURLOptions): string | null; @@ -3872,7 +3875,7 @@ export const Constants: { GuildScheduledEventStatuses: EnumHolder; IntegrationExpireBehaviors: IntegrationExpireBehaviors[]; SelectMenuComponentTypes: EnumHolder; - RelationshipTypes: EnumHolder; + RelationshipTypes: EnumHolder; MembershipStates: EnumHolder; MessageButtonStyles: EnumHolder; MessageComponentTypes: EnumHolder; @@ -4073,22 +4076,22 @@ export class RelationshipManager extends BaseManager { client: Client, data: { user: RawUserData; - type: RelationshipType; + type: RelationshipTypes; since?: string; nickname: string | null | undefined; id: Snowflake; }[], ); - public cache: Collection; + public cache: Collection; public friendNicknames: Collection; public sinceCache: Collection; public readonly friendCache: Collection; public readonly blockedCache: Collection; public readonly incomingCache: Collection; public readonly outgoingCache: Collection; - 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 fetch(user?: UserResolvable, options?: BaseFetchOptions): Promise; + public fetch(user?: UserResolvable, options?: BaseFetchOptions): Promise; public deleteRelationship(user: UserResolvable): Promise; public sendFriendRequest(options: FriendRequestOptions): Promise; public addFriend(user: UserResolvable): Promise; @@ -5424,8 +5427,8 @@ export interface CategoryCreateChannelOptions { videoQualityMode?: VideoQualityMode; availableTags?: GuildForumTagData[]; defaultReactionEmoji?: DefaultReactionEmoji; - defaultSortOrder?: SortOrderType; - defaultForumLayout?: ForumLayoutType; + defaultSortOrder?: SortOrderTypes; + defaultForumLayout?: ForumLayoutTypes; defaultThreadRateLimitPerUser?: number; reason?: string; } @@ -5454,8 +5457,8 @@ export interface ChannelData { availableTags?: GuildForumTagData[]; defaultReactionEmoji?: DefaultReactionEmoji; defaultThreadRateLimitPerUser?: number; - defaultSortOrder?: SortOrderType | null; - defaultForumLayout?: ForumLayoutType; + defaultSortOrder?: SortOrderTypes | null; + defaultForumLayout?: ForumLayoutTypes; flags?: ChannelFlagsResolvable; } @@ -5604,18 +5607,18 @@ export interface ClientEvents extends BaseClientEvents { guildAuditLogEntryCreate: [auditLogEntry: GuildAuditLogsEntry, guild: Guild]; unhandledPacket: [packet: { t?: string; d: any }, shard: number]; relationshipAdd: [userId: Snowflake, shouldNotify: boolean]; - relationshipRemove: [userId: Snowflake, type: RelationshipType, nickname: string | null]; + relationshipRemove: [userId: Snowflake, type: RelationshipTypes, nickname: string | null]; relationshipUpdate: [ userId: Snowflake, oldData: { nickname: string | null; since: Date; - type: RelationshipType; + type: RelationshipTypes; }, newData: { nickname: string | null; since: Date; - type: RelationshipType; + type: RelationshipTypes; }, ]; channelRecipientAdd: [channel: GroupDMChannel, user: User]; @@ -6763,7 +6766,7 @@ export interface CreateInviteOptions { reason?: string; targetApplication?: ApplicationResolvable; targetUser?: UserResolvable; - targetType?: InviteTargetType; + targetType?: InviteTargetTypes; } export type IntegrationExpireBehaviors = 'REMOVE_ROLE' | 'KICK'; @@ -7016,6 +7019,7 @@ export interface MessageReference { channelId: Snowflake; guildId: Snowflake | undefined; messageId: Snowflake | undefined; + type: MessageReferenceTypes } export type MessageResolvable = Message | Snowflake;