import { DiscordSnowflake } from '@sapphire/snowflake'; import type { APIMessage, MessageFlags } from 'discord-api-types/v10'; import { Structure } from '../Structure.js'; import { MessageFlagsBitField } from '../bitfields/MessageFlagsBitField.js'; import { dateToDiscordISOTimestamp } from '../utils/optimization.js'; import { kData, kEditedTimestamp } from '../utils/symbols.js'; import { isFieldSet, isIdSet } from '../utils/type-guards.js'; import type { Partialize } from '../utils/types.js'; // TODO: missing substructures: application /** * Represents a message on Discord. * * @typeParam Omitted - Specify the properties that will not be stored in the raw data field as a union, implement via `DataTemplate` * @remarks has substructures `Message`, `Channel`, `MessageActivity`, `MessageCall`, `MessageReference`, `Attachment`, `Application`, `ChannelMention`, `Reaction`, `Poll`, `ResolvedInteractionData`, `RoleSubscriptionData`, `Sticker`, all the different `Component`s, ... which need to be instantiated and stored by an extending class using it */ export class Message extends Structure< APIMessage, Omitted > { /** * The template used for removing data from the raw data stored for each Message */ public static override DataTemplate: Partial = { set timestamp(_: string) {}, set edited_timestamp(_: string) {}, }; protected [kEditedTimestamp]: number | null = null; /** * @param data - The raw data received from the API for the message */ public constructor(data: Partialize) { super(data); this.optimizeData(data); } /** * {@inheritDoc Structure.optimizeData} * * @internal */ protected override optimizeData(data: Partial) { if (data.edited_timestamp) { this[kEditedTimestamp] = Date.parse(data.edited_timestamp); } } /** * The message's id */ public get id() { return this[kData].id; } /** * The id of the interaction's application, if this message is a reply to an interaction */ public get applicationId() { return this[kData].application_id; } /** * The channel's id this message was sent in */ public get channelId() { return this[kData].channel_id; } /** * The timestamp this message was created at */ public get createdTimestamp() { return isIdSet(this.id) ? DiscordSnowflake.timestampFrom(this.id) : null; } /** * The time the message was created at */ public get createdAt() { const createdTimestamp = this.createdTimestamp; return createdTimestamp ? new Date(createdTimestamp) : null; } /** * The content of the message */ public get content() { return this[kData].content; } /** * The timestamp this message was last edited at, or `null` if it never was edited */ public get editedTimestamp() { return this[kEditedTimestamp]; } /** * The time the message was last edited at, or `null` if it never was edited */ public get editedAt() { const editedTimestamp = this.editedTimestamp; return editedTimestamp ? new Date(editedTimestamp) : null; } /** * The flags of this message as a bit field */ public get flags() { return isFieldSet(this[kData], 'flags', 'number') ? new MessageFlagsBitField(this[kData].flags as MessageFlags) : null; } /** * The nonce used when sending this message. * * @remarks This is only present in MESSAGE_CREATE event, if a nonce was provided when sending */ public get nonce() { return this[kData].nonce; } /** * Whether this message is pinned in its channel */ public get pinned() { return this[kData].pinned; } /** * A generally increasing integer (there may be gaps or duplicates) that represents the approximate position of the message in a thread * It can be used to estimate the relative position of the message in a thread in company with `totalMessageSent` on parent thread */ public get position() { return this[kData].position; } /** * Whether this message was a TTS message */ public get tts() { return this[kData].tts; } /** * The type of message */ public get type() { return this[kData].type; } /** * If the message is generated by a webhook, this is the webhook's id */ public get webhookId() { return this[kData].webhook_id; } /** * {@inheritDoc Structure.toJSON} */ public override toJSON() { const clone = super.toJSON(); if (this[kEditedTimestamp]) { clone.edited_timestamp = dateToDiscordISOTimestamp(new Date(this[kEditedTimestamp])); } const createdAt = this.createdAt; if (createdAt) { clone.timestamp = dateToDiscordISOTimestamp(createdAt); } return clone; } }