From 5fc932f473c053b43e6f54875fce4bef40a956eb Mon Sep 17 00:00:00 2001 From: Fleny Date: Thu, 30 Jul 2026 09:09:32 +0200 Subject: [PATCH] feat!: Document more attachment flags and fields (#5234) * feat: Document more attachment flags and fields * Update packages/types/src/discord/message.ts Co-authored-by: stickz <38146668+AwesomeStickz@users.noreply.github.com> * fix!: Add missing optionals, remove EmbedThumbnail --- packages/bot/src/desiredProperties.ts | 8 ++ packages/bot/src/transformers/attachment.ts | 6 ++ packages/bot/src/transformers/component.ts | 4 + packages/bot/src/transformers/embed.ts | 17 ++++ .../bot/src/transformers/reverse/component.ts | 3 + .../bot/src/transformers/reverse/embed.ts | 15 ++++ packages/bot/src/transformers/types.ts | 61 +++++++++++--- packages/types/src/discord/components.ts | 19 +++++ packages/types/src/discord/message.ts | 84 +++++++++++++++---- packages/utils/src/builders/embeds.ts | 5 +- 10 files changed, 195 insertions(+), 27 deletions(-) diff --git a/packages/bot/src/desiredProperties.ts b/packages/bot/src/desiredProperties.ts index 7070e6b64..ffc028c49 100644 --- a/packages/bot/src/desiredProperties.ts +++ b/packages/bot/src/desiredProperties.ts @@ -283,6 +283,11 @@ export function createDesiredPropertiesObject bot.transformers.user(bot, user)); + if (props.clipCreatedAt && payload.clip_created_at) attachment.clipCreatedAt = payload.clip_created_at; + if (props.application && payload.application) attachment.application = bot.transformers.application(bot, payload.application); return bot.transformers.customizers.attachment(bot, payload, attachment); } diff --git a/packages/bot/src/transformers/component.ts b/packages/bot/src/transformers/component.ts index 85b1a9fbb..8420b65a2 100644 --- a/packages/bot/src/transformers/component.ts +++ b/packages/bot/src/transformers/component.ts @@ -39,6 +39,7 @@ import { } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { ToggleBitfield } from './toggles/ToggleBitfield.js'; import type { Component, MediaGalleryItem, UnfurledMediaItem } from './types.js'; export function transformComponent(bot: Bot, payload: DiscordMessageComponent | DiscordMessageComponentFromModalInteractionResponse): Component { @@ -119,7 +120,10 @@ export function transformUnfurledMediaItem(bot: Bot, payload: DiscordUnfurledMed if (props.proxyUrl && payload.proxy_url) mediaItem.proxyUrl = payload.proxy_url; if (props.height && payload.height) mediaItem.height = payload.height; if (props.width && payload.width) mediaItem.width = payload.width; + if (props.placeholder && payload.placeholder) mediaItem.placeholder = payload.placeholder; + if (props.placeholderVersion && payload.placeholder_version) mediaItem.placeholderVersion = payload.placeholder_version; if (props.contentType && payload.content_type) mediaItem.contentType = payload.content_type; + if (props.flags && payload.flags) mediaItem.flags = new ToggleBitfield(payload.flags); if (props.attachmentId && payload.attachment_id) mediaItem.attachmentId = bot.transformers.snowflake(payload.attachment_id); return bot.transformers.customizers.unfurledMediaItem(bot, payload, mediaItem); diff --git a/packages/bot/src/transformers/embed.ts b/packages/bot/src/transformers/embed.ts index ad7bd171d..8bf94410d 100644 --- a/packages/bot/src/transformers/embed.ts +++ b/packages/bot/src/transformers/embed.ts @@ -1,5 +1,6 @@ import type { DiscordEmbed } from '@discordeno/types'; import type { Bot } from '../bot.js'; +import { ToggleBitfield } from './toggles/ToggleBitfield.js'; import type { Embed } from './types.js'; export function transformEmbed(bot: Bot, payload: DiscordEmbed): Embed { @@ -23,6 +24,11 @@ export function transformEmbed(bot: Bot, payload: DiscordEmbed): Embed { proxyUrl: payload.image.proxy_url, height: payload.image.height, width: payload.image.width, + contentType: payload.image.content_type, + placeholder: payload.image.placeholder, + placeholderVersion: payload.image.placeholder_version, + description: payload.image.description, + flags: payload.image.flags ? new ToggleBitfield(payload.image.flags) : undefined, } : undefined, thumbnail: payload.thumbnail @@ -31,6 +37,11 @@ export function transformEmbed(bot: Bot, payload: DiscordEmbed): Embed { proxyUrl: payload.thumbnail.proxy_url, height: payload.thumbnail.height, width: payload.thumbnail.width, + contentType: payload.thumbnail.content_type, + placeholder: payload.thumbnail.placeholder, + placeholderVersion: payload.thumbnail.placeholder_version, + description: payload.thumbnail.description, + flags: payload.thumbnail.flags ? new ToggleBitfield(payload.thumbnail.flags) : undefined, } : undefined, video: payload.video @@ -39,6 +50,11 @@ export function transformEmbed(bot: Bot, payload: DiscordEmbed): Embed { proxyUrl: payload.video.proxy_url, height: payload.video.height, width: payload.video.width, + contentType: payload.video.content_type, + placeholder: payload.video.placeholder, + placeholderVersion: payload.video.placeholder_version, + description: payload.video.description, + flags: payload.video.flags ? new ToggleBitfield(payload.video.flags) : undefined, } : undefined, provider: payload.provider, @@ -51,6 +67,7 @@ export function transformEmbed(bot: Bot, payload: DiscordEmbed): Embed { } : undefined, fields: payload.fields, + flags: payload.flags ? new ToggleBitfield(payload.flags) : undefined, } as Embed; return bot.transformers.customizers.embed(bot, payload, embed); diff --git a/packages/bot/src/transformers/reverse/component.ts b/packages/bot/src/transformers/reverse/component.ts index cac59aae9..2f08de838 100644 --- a/packages/bot/src/transformers/reverse/component.ts +++ b/packages/bot/src/transformers/reverse/component.ts @@ -91,7 +91,10 @@ export function transformUnfurledMediaItemToDiscordUnfurledMediaItem(bot: Bot, p proxy_url: payload.proxyUrl, height: payload.height, width: payload.width, + placeholder: payload.placeholder, + placeholder_version: payload.placeholderVersion, content_type: payload.contentType, + flags: payload.flags?.bitfield, attachment_id: payload.attachmentId ? bot.transformers.reverse.snowflake(payload.attachmentId) : undefined, }; } diff --git a/packages/bot/src/transformers/reverse/embed.ts b/packages/bot/src/transformers/reverse/embed.ts index c58598517..baf54b00a 100644 --- a/packages/bot/src/transformers/reverse/embed.ts +++ b/packages/bot/src/transformers/reverse/embed.ts @@ -23,6 +23,11 @@ export function transformEmbedToDiscordEmbed(_bot: Bot, payload: Embed): Discord proxy_url: payload.image.proxyUrl, height: payload.image.height, width: payload.image.width, + content_type: payload.image.contentType, + placeholder: payload.image.placeholder, + placeholder_version: payload.image.placeholderVersion, + description: payload.image.description, + flags: payload.image.flags?.bitfield, } : undefined, thumbnail: payload.thumbnail @@ -31,6 +36,11 @@ export function transformEmbedToDiscordEmbed(_bot: Bot, payload: Embed): Discord proxy_url: payload.thumbnail.proxyUrl, height: payload.thumbnail.height, width: payload.thumbnail.width, + content_type: payload.thumbnail.contentType, + placeholder: payload.thumbnail.placeholder, + placeholder_version: payload.thumbnail.placeholderVersion, + description: payload.thumbnail.description, + flags: payload.thumbnail.flags?.bitfield, } : undefined, video: payload.video @@ -39,6 +49,11 @@ export function transformEmbedToDiscordEmbed(_bot: Bot, payload: Embed): Discord proxy_url: payload.video.proxyUrl, height: payload.video.height, width: payload.video.width, + content_type: payload.video.contentType, + placeholder: payload.video.placeholder, + placeholder_version: payload.video.placeholderVersion, + description: payload.video.description, + flags: payload.video.flags?.bitfield, } : undefined, provider: payload.provider, diff --git a/packages/bot/src/transformers/types.ts b/packages/bot/src/transformers/types.ts index ed5cdf809..76969e593 100644 --- a/packages/bot/src/transformers/types.ts +++ b/packages/bot/src/transformers/types.ts @@ -25,6 +25,8 @@ import type { DiscordAuditLogChange, DiscordAutoModerationRuleTriggerMetadataPresets, DiscordBaseTheme, + DiscordEmbedFlags, + DiscordEmbedMediaFlags, DiscordEntitlementType, DiscordGuildOnboardingMode, DiscordGuildOnboardingPromptType, @@ -40,6 +42,7 @@ import type { DiscordSubscriptionStatus, DiscordTeamMemberRole, DiscordTemplateSerializedSourceGuild, + DiscordUnfurledMediaItemFlags, DiscordWebhookEventType, EmbedTypes, ExplicitContentFilterLevels, @@ -316,10 +319,14 @@ export interface Attachment { id: bigint; /** description for the file (max 1024 characters) */ description?: string; - /** Height of file (if image) */ + /** Height of file (if image or video) */ height?: number; - /** Width of file (if image) */ + /** Width of file (if image or video) */ width?: number; + /** Thumbhash placeholder (if image or video) */ + placeholder?: string; + /** Version of the placeholder (if image or video) */ + placeholderVersion?: number; /** whether this attachment is ephemeral. Ephemeral attachments will automatically be removed after a set period of time. Ephemeral attachments on messages are guaranteed to be available as long as the message itself exists. */ ephemeral?: boolean; /** The duration of the audio file for a voice message */ @@ -328,6 +335,12 @@ export interface Attachment { waveform?: string; /** Attachment flags combined as a bitfield */ flags?: AttachmentFlags; + /** for Clips, array of users who were in the stream */ + clipParticipants?: User[]; + /** for Clips, when the clip was created. ISO8601 timestamp */ + clipCreatedAt?: string; + /** for Clips, the application in the stream, if recognized */ + application?: Application; } export interface AuditLogEntry { @@ -687,8 +700,21 @@ export interface UnfurledMediaItem { height?: number | null; /** The width of the media item. This field is ignored and provided by the API as part of the response */ width?: number | null; + /** Thumbhash placeholder if image or video. This field is ignored and provided by the API as part of the response */ + placeholder?: string; + /** Version of the placeholder (if image or video). This field is ignored and provided by the API as part of the response */ + placeholderVersion?: number; /** The media type of the content. This field is ignored and provided by the API as part of the response */ contentType?: string; + /** + * Unfurled media item flags combined as a bitfield + * + * @remarks + * This field is ignored and provided by the API as part of the response + * + * @see {@link DiscordUnfurledMediaItemFlags} + */ + flags?: ToggleBitfield; /** The id of the uploaded attachment. Only present if the media was uploaded as an attachment. This field is ignored and provided by the API as part of the response */ attachmentId?: bigint; } @@ -719,10 +745,12 @@ export interface Embed { timestamp?: number; color?: number; footer?: EmbedFooter; - thumbnail?: EmbedThumbnail; + thumbnail?: EmbedImage; provider?: EmbedProvider; author?: EmbedAuthor; fields?: EmbedField[]; + /** @see {@link DiscordEmbedFlags} */ + flags?: ToggleBitfield; } export interface EmbedImage { @@ -730,6 +758,16 @@ export interface EmbedImage { height?: number; width?: number; url: string; + contentType?: string; + placeholder?: string; + placeholderVersion?: number; + description?: string; + /** + * Embed media flags combined as a bitfield + * + * @see {@link DiscordEmbedMediaFlags} + */ + flags?: ToggleBitfield; } export interface EmbedVideo { @@ -737,6 +775,16 @@ export interface EmbedVideo { proxyUrl?: string; height?: number; width?: number; + contentType?: string; + placeholder?: string; + placeholderVersion?: number; + description?: string; + /** + * Embed media flags combined as a bitfield + * + * @see {@link DiscordEmbedMediaFlags} + */ + flags?: ToggleBitfield; } export interface EmbedFooter { @@ -745,13 +793,6 @@ export interface EmbedFooter { text: string; } -export interface EmbedThumbnail { - proxyUrl?: string; - height?: number; - width?: number; - url: string; -} - export interface EmbedProvider { name?: string; url?: string; diff --git a/packages/types/src/discord/components.ts b/packages/types/src/discord/components.ts index 1c8949522..c16d10bc5 100644 --- a/packages/types/src/discord/components.ts +++ b/packages/types/src/discord/components.ts @@ -943,8 +943,27 @@ export interface DiscordUnfurledMediaItem { height?: number | null; /** The width of the media item. This field is ignored and provided by the API as part of the response */ width?: number | null; + /** Thumbhash placeholder if image or video. This field is ignored and provided by the API as part of the response */ + placeholder?: string; + /** Version of the placeholder (if image or video). This field is ignored and provided by the API as part of the response */ + placeholder_version?: number; /** The media type of the content. This field is ignored and provided by the API as part of the response */ content_type?: string; + /** + * Unfurled media item flags combined as a bitfield + * + * @remarks + * This field is ignored and provided by the API as part of the response + * + * @see {@link DiscordUnfurledMediaItemFlags} + */ + flags?: number; /** The id of the uploaded attachment. Only present if the media was uploaded as an attachment. This field is ignored and provided by the API as part of the response */ attachment_id?: string | null; } + +/** https://docs.discord.com/developers/components/reference#unfurled-media-item-unfurled-media-item-flags */ +export enum DiscordUnfurledMediaItemFlags { + /** This image is animated */ + IsAnimated = 1 << 0, +} diff --git a/packages/types/src/discord/message.ts b/packages/types/src/discord/message.ts index b9d335b50..4c7e35b78 100644 --- a/packages/types/src/discord/message.ts +++ b/packages/types/src/discord/message.ts @@ -373,7 +373,7 @@ export interface DiscordEmbed { /** Image information */ image?: DiscordEmbedImage; /** Thumbnail information */ - thumbnail?: DiscordEmbedThumbnail; + thumbnail?: DiscordEmbedImage; /** Video information */ video?: DiscordEmbedVideo; /** Provider information */ @@ -382,21 +382,21 @@ export interface DiscordEmbed { author?: DiscordEmbedAuthor; /** Fields information */ fields?: DiscordEmbedField[]; + /** + * Embed flags combined as a bitfield + * + * @see {@link DiscordEmbedFlags} + */ + flags?: number; } /** https://docs.discord.com/developers/resources/message#embed-object-embed-types */ export type EmbedTypes = 'rich' | 'image' | 'video' | 'gifv' | 'article' | 'link' | 'poll_result'; -/** https://docs.discord.com/developers/resources/message#embed-object-embed-thumbnail-structure */ -export interface DiscordEmbedThumbnail { - /** Source url of thumbnail (only supports http(s) and attachments) */ - url: string; - /** A proxied url of the thumbnail */ - proxy_url?: string; - /** Height of thumbnail */ - height?: number; - /** Width of thumbnail */ - width?: number; +/** https://docs.discord.com/developers/resources/message#embed-object-embed-flags */ +export enum DiscordEmbedFlags { + /** This embed is a fallback for a reply to an activity card */ + IsContentInventoryEntry = 1 << 5, } /** https://docs.discord.com/developers/resources/message#embed-object-embed-video-structure */ @@ -409,6 +409,20 @@ export interface DiscordEmbedVideo { height?: number; /** Width of video */ width?: number; + /** The video's media type */ + content_type?: string; + /** Thumbhash placeholder of the video */ + placeholder?: string; + /** Version of the placeholder */ + placeholder_version?: number; + /** Description (alt text) of the video */ + description?: string; + /** + * Embed media flags combined as a bitfield + * + * @see {@link DiscordEmbedMediaFlags} + */ + flags?: number; } /** https://docs.discord.com/developers/resources/message#embed-object-embed-image-structure */ @@ -421,6 +435,26 @@ export interface DiscordEmbedImage { height?: number; /** Width of image */ width?: number; + /** The image's media type */ + content_type?: string; + /** Thumbhash placeholder of the image */ + placeholder?: string; + /** Version of the placeholder */ + placeholder_version?: number; + /** Description (alt text) of the image */ + description?: string; + /** + * Embed media flags combined as a bitfield + * + * @see {@link DiscordEmbedMediaFlags} + */ + flags?: number; +} + +/** https://docs.discord.com/developers/resources/message#embed-object-embed-media-flags */ +export enum DiscordEmbedMediaFlags { + /** This image is animated */ + IsAnimated = 1 << 5, } /** https://docs.discord.com/developers/resources/message#embed-object-embed-provider-structure */ @@ -481,10 +515,14 @@ export interface DiscordAttachment { url: string; /** A proxied url of file */ proxy_url: string; - /** Height of file (if image) */ + /** Height of file (if image or video) */ height?: number | null; - /** Width of file (if image) */ + /** Width of file (if image or video) */ width?: number | null; + /** Thumbhash placeholder (if image or video) */ + placeholder?: string; + /** Version of the placeholder (if image or video) */ + placeholder_version?: number; /** whether this attachment is ephemeral. Ephemeral attachments will automatically be removed after a set period of time. Ephemeral attachments on messages are guaranteed to be available as long as the message itself exists. */ ephemeral?: boolean; /** The duration of the audio or video file */ @@ -493,13 +531,31 @@ export interface DiscordAttachment { waveform?: string; /** Attachment flags combined as a bitfield */ flags?: AttachmentFlags; + /** for Clips, array of users who were in the stream */ + clip_participants?: DiscordUser[]; + /** for Clips, when the clip was created. ISO8601 timestamp */ + clip_created_at?: string; + /** for Clips, the application in the stream, if recognized */ + application?: DiscordApplication | null; } /** https://docs.discord.com/developers/resources/message#attachment-object-attachment-flags */ export enum AttachmentFlags { None, - /** This attachment has been edited using the remix feature on mobile */ + /** This attachment is a Clip from a stream */ + IsClip = 1 << 0, + /** This attachment is the thumbnail of a thread in a media channel, displayed in the grid but not on the message */ + IsThumbnail = 1 << 1, + /** + * This attachment has been edited using the remix feature on mobile + * + * @deprecated + */ IsRemix = 1 << 2, + /** This attachment was marked as a spoiler and is blurred until clicked */ + IsSpoiler = 1 << 3, + /** This attachment is an animated image */ + IsAnimated = 1 << 5, } /** https://docs.discord.com/developers/resources/message#channel-mention-object-channel-mention-structure */ diff --git a/packages/utils/src/builders/embeds.ts b/packages/utils/src/builders/embeds.ts index 4924999b8..bf05675ca 100644 --- a/packages/utils/src/builders/embeds.ts +++ b/packages/utils/src/builders/embeds.ts @@ -4,7 +4,6 @@ import type { DiscordEmbedField, DiscordEmbedFooter, DiscordEmbedImage, - DiscordEmbedThumbnail, DiscordEmbedVideo, } from '@discordeno/types'; @@ -253,10 +252,10 @@ export class EmbedsBuilder extends Array { * Set the thumbnail of the current embed. * * @param {string} url - URL of the image - * @param {?Omit} [options] + * @param {?Omit} [options] * @returns {EmbedsBuilder} */ - setThumbnail(url: string, options?: Omit): this { + setThumbnail(url: string, options?: Omit): this { this.#currentEmbed.thumbnail = { ...this.#currentEmbed.thumbnail, ...options,