From 402f1b304914a0790fb1ae794537320d35f4e142 Mon Sep 17 00:00:00 2001 From: Fleny Date: Sun, 21 Jul 2024 18:49:07 +0200 Subject: [PATCH] Add message forward (#3764) --- packages/bot/src/transformers.ts | 20 +++++++++++++ packages/bot/src/transformers/message.ts | 15 +++++++++- packages/bot/src/transformers/types.ts | 12 ++++++++ packages/types/src/discord.ts | 37 +++++++++++++++++++++++- packages/types/src/discordeno.ts | 5 +++- 5 files changed, 86 insertions(+), 3 deletions(-) diff --git a/packages/bot/src/transformers.ts b/packages/bot/src/transformers.ts index 10a6700b6..cf5b55dcf 100644 --- a/packages/bot/src/transformers.ts +++ b/packages/bot/src/transformers.ts @@ -36,6 +36,7 @@ import type { DiscordMessage, DiscordMessageCall, DiscordMessageInteractionMetadata, + DiscordMessageSnapshot, DiscordPoll, DiscordPollMedia, DiscordPresenceUpdate, @@ -90,6 +91,7 @@ import { type Message, type MessageCall, type MessageInteractionMetadata, + type MessageSnapshot, type Poll, type PollMedia, type PresenceUpdate, @@ -147,6 +149,7 @@ import { transformMessage, transformMessageCall, transformMessageInteractionMetadata, + transformMessageSnapshot, transformPoll, transformPollMedia, transformPresence, @@ -184,6 +187,7 @@ export interface Transformers { forumTag: (bot: Bot, payload: DiscordForumTag, forumTag: ForumTag) => any interaction: (bot: Bot, payload: { interaction: DiscordInteraction; shardId: number }, interaction: Interaction) => any message: (bot: Bot, payload: DiscordMessage, message: Message) => any + messageSnapshot: (bot: Bot, payload: DiscordMessageSnapshot, messageSnapshot: MessageSnapshot) => any messageInteractionMetadata: (bot: Bot, payload: DiscordMessageInteractionMetadata, metadata: MessageInteractionMetadata) => any messageCall: (bot: Bot, payload: DiscordMessageCall, call: MessageCall) => any user: (bot: Bot, payload: DiscordUser, user: User) => any @@ -267,6 +271,7 @@ export interface Transformers { user: (bot: Bot, payload: DiscordUser) => User member: (bot: Bot, payload: DiscordMember, guildId: BigString, userId: BigString) => Member message: (bot: Bot, payload: DiscordMessage) => Message + messageSnapshot: (bot: Bot, payload: DiscordMessageSnapshot) => MessageSnapshot messageInteractionMetadata: (bot: Bot, payload: DiscordMessageInteractionMetadata) => MessageInteractionMetadata messageCall: (bot: Bot, payload: DiscordMessageCall) => MessageCall role: (bot: Bot, payload: { role: DiscordRole } & { guildId: BigString }) => Role @@ -518,6 +523,8 @@ export interface TransformersDesiredProprieties { mentionedRoleIds: boolean mentions: boolean messageReference: boolean + referencedMessage: boolean + messageSnapshots: boolean nonce: boolean reactions: boolean stickerItems: boolean @@ -527,6 +534,9 @@ export interface TransformersDesiredProprieties { poll: boolean call: boolean } + messageSnapshot: { + message: boolean + } messageInteractionMetadata: { id: boolean type: boolean @@ -756,6 +766,9 @@ export function createTransformers(options: Partial, opts?: Create message(_bot, _payload, message) { return message }, + messageSnapshot(_bot, _payload, messageSnapshot) { + return messageSnapshot + }, messageInteractionMetadata(_bot, _payload, metadata) { return metadata }, @@ -929,6 +942,7 @@ export function createTransformers(options: Partial, opts?: Create invite: options.invite ?? transformInvite, member: options.member ?? transformMember, message: options.message ?? transformMessage, + messageSnapshot: options.messageSnapshot ?? transformMessageSnapshot, messageInteractionMetadata: options.messageInteractionMetadata ?? transformMessageInteractionMetadata, messageCall: options.messageCall ?? transformMessageCall, presence: options.presence ?? transformPresence, @@ -1185,6 +1199,8 @@ export function createDesiredProprietiesObject( mentionedRoleIds: defaultValue, mentions: defaultValue, messageReference: defaultValue, + messageSnapshots: defaultValue, + referencedMessage: defaultValue, nonce: defaultValue, reactions: defaultValue, stickerItems: defaultValue, @@ -1195,6 +1211,10 @@ export function createDesiredProprietiesObject( call: defaultValue, ...desiredProperties.message, }, + messageSnapshot: { + message: defaultValue, + ...desiredProperties.messageSnapshot, + }, messageInteractionMetadata: { id: defaultValue, type: defaultValue, diff --git a/packages/bot/src/transformers/message.ts b/packages/bot/src/transformers/message.ts index 62c2677a9..2e6e7c221 100644 --- a/packages/bot/src/transformers/message.ts +++ b/packages/bot/src/transformers/message.ts @@ -3,10 +3,11 @@ import { type DiscordMessage, type DiscordMessageCall, type DiscordMessageInteractionMetadata, + type DiscordMessageSnapshot, MessageFlags, } from '@discordeno/types' import { CHANNEL_MENTION_REGEX } from '../constants.js' -import { type Bot, type Message, type MessageCall, type MessageInteractionMetadata, snowflakeToTimestamp } from '../index.js' +import { type Bot, type Message, type MessageCall, type MessageInteractionMetadata, type MessageSnapshot, snowflakeToTimestamp } from '../index.js' import { ToggleBitfield } from './toggles/ToggleBitfield.js' const EMPTY_STRING = '' @@ -202,6 +203,9 @@ export function transformMessage(bot: Bot, payload: DiscordMessage): Message { message.messageReference = reference } + if (props.referencedMessage && payload.referenced_message) message.referencedMessage = bot.transformers.message(bot, payload.referenced_message) + if (props.messageSnapshots && payload.message_snapshots) + message.messageSnapshots = payload.message_snapshots.map((snap) => bot.transformers.messageSnapshot(bot, snap)) if (props.nonce && payload.nonce) message.nonce = payload.nonce if (payload.pinned) message.pinned = true if (props.reactions && payload.reactions?.length) { @@ -233,6 +237,15 @@ export function transformMessage(bot: Bot, payload: DiscordMessage): Message { return bot.transformers.customizers.message(bot, payload, message) } +export function transformMessageSnapshot(bot: Bot, payload: DiscordMessageSnapshot): MessageSnapshot { + const props = bot.transformers.desiredProperties.messageSnapshot + const messageSnapshot = {} as MessageSnapshot + + if (props.message && payload.message) messageSnapshot.message = bot.transformers.message(bot, payload.message as DiscordMessage) + + return bot.transformers.customizers.messageSnapshot(bot, payload, messageSnapshot) +} + export function transformMessageInteractionMetadata(bot: Bot, payload: DiscordMessageInteractionMetadata): MessageInteractionMetadata { const props = bot.transformers.desiredProperties.messageInteractionMetadata const metadata = {} as MessageInteractionMetadata diff --git a/packages/bot/src/transformers/types.ts b/packages/bot/src/transformers/types.ts index 3d27da602..f843d1c82 100644 --- a/packages/bot/src/transformers/types.ts +++ b/packages/bot/src/transformers/types.ts @@ -1068,6 +1068,13 @@ export interface Message { mentionedRoleIds?: bigint[] /** Data showing the source of a crossposted channel follow add, pin or reply message */ messageReference?: MessageReference + /** + * The message associated with the `message_reference` + * Note: This field is only returned for messages with a `type` of `19` (REPLY). If the message is a reply but the `referenced_message` field is not present, the backend did not attempt to fetch the message that was being replied to, so its state is unknown. If the field exists but is null, the referenced message was deleted. + */ + referencedMessage?: Message + /** The message associated with the `message_reference`. This is a minimal subset of fields in a message (e.g. `author` is excluded.) */ + messageSnapshots?: MessageSnapshot[] nonce?: string | number /** Reactions on this message. */ reactions?: Reaction[] @@ -1148,6 +1155,11 @@ export interface MessageReference { messageId?: bigint } +export interface MessageSnapshot { + /** Minimal subset of fields in the forwarded message */ + message: Pick +} + export interface MessageInteractionMetadata { /** Id of the interaction */ id: bigint diff --git a/packages/types/src/discord.ts b/packages/types/src/discord.ts index b38a773cd..57eb15ca5 100644 --- a/packages/types/src/discord.ts +++ b/packages/types/src/discord.ts @@ -1330,7 +1330,7 @@ export interface DiscordMessage { application?: Partial /** if the message is an Interaction or application-owned webhook, this is the id of the application */ application_id?: string - /** Data showing the source of a crossposted channel follow add, pin or reply message */ + /** Data showing the source of a crosspost, channel follow add, pin, or reply message */ message_reference?: Omit /** Message flags combined as a bitfield */ flags?: MessageFlags @@ -1344,6 +1344,8 @@ export interface DiscordMessage { * Note: This field is only returned for messages with a `type` of `19` (REPLY). If the message is a reply but the `referenced_message` field is not present, the backend did not attempt to fetch the message that was being replied to, so its state is unknown. If the field exists but is null, the referenced message was deleted. */ referenced_message?: DiscordMessage + /** The message associated with the `message_reference`. This is a minimal subset of fields in a message (e.g. `author` is excluded.) */ + message_snapshots?: DiscordMessageSnapshot[] /** sent if the message is sent as a result of an interaction */ interaction_metadata?: DiscordMessageInteractionMetadata /** @@ -1426,6 +1428,8 @@ export interface DiscordMessageActivity { /** https://discord.com/developers/docs/resources/channel#message-object-message-reference-structure */ export interface DiscordMessageReference { + /** Type of reference */ + type?: DiscordMessageReferenceType /** id of the originating message */ message_id?: string /** @@ -1439,6 +1443,37 @@ export interface DiscordMessageReference { fail_if_not_exists: boolean } +/** https://discord.com/developers/docs/resources/channel#message-reference-object-message-reference-types */ +export enum DiscordMessageReferenceType { + /** + * A standard reference used by replies. + * + * @remarks + * When the type is set to this value, the field {@link DiscordMessage.referenced_message} will be present + */ + Default, + /** + * Reference used to point to a message at a point in time. + * + * @remarks + * When the type is set to this value, the field {@link DiscordMessage.message_snapshot} will be present in the + * + * This value can only be used for basic messages; + * i.e. messages which do not have strong bindings to a non global entity. + * Thus we support only messages with `DEFAULT` or `REPLY` types, but disallowed if there are any polls, calls, or components. + */ + Forward, +} + +/** https://discord.com/developers/docs/resources/channel#message-snapshot-object-message-snapshot-structure */ +export interface DiscordMessageSnapshot { + /** Minimal subset of fields in the forwarded message */ + message: Pick< + DiscordMessage, + 'type' | 'content' | 'embeds' | 'attachments' | 'timestamp' | 'edited_timestamp' | 'flags' | 'mentions' | 'mention_roles' + > +} + /** https://discord.com/developers/docs/resources/poll#poll-object */ export interface DiscordPoll { /** The question of the poll. Only `text` is supported. */ diff --git a/packages/types/src/discordeno.ts b/packages/types/src/discordeno.ts index c0d9abcc1..94a99bbeb 100644 --- a/packages/types/src/discordeno.ts +++ b/packages/types/src/discordeno.ts @@ -15,6 +15,7 @@ import type { DiscordGuildOnboardingPrompt, DiscordInstallParams, DiscordInteractionContextType, + DiscordMessageReferenceType, DiscordPollAnswer, DiscordPollLayoutType, DiscordPollMedia, @@ -63,8 +64,10 @@ export interface CreateMessageOptions { embeds?: Camelize[] /** Allowed mentions for the message */ allowedMentions?: AllowedMentions - /** Include to make your message a reply */ + /** Include to make your message a reply or a forward */ messageReference?: { + /** Type of reference */ + type?: DiscordMessageReferenceType /** id of the originating message */ messageId?: BigString /**