diff --git a/esm/discord.mjs b/esm/discord.mjs index b8bee9fd7..6c420bf0a 100644 --- a/esm/discord.mjs +++ b/esm/discord.mjs @@ -85,6 +85,7 @@ export const { ReactionEmoji, RichPresenceAssets, Role, + Sticker, StoreChannel, StageChannel, Team, diff --git a/src/index.js b/src/index.js index 51ff9e6f6..5ca8de77d 100644 --- a/src/index.js +++ b/src/index.js @@ -97,6 +97,7 @@ module.exports = { ReactionEmoji: require('./structures/ReactionEmoji'), RichPresenceAssets: require('./structures/Presence').RichPresenceAssets, Role: require('./structures/Role'), + Sticker: require('./structures/Sticker'), StoreChannel: require('./structures/StoreChannel'), StageChannel: require('./structures/StageChannel'), Team: require('./structures/Team'), diff --git a/src/structures/Message.js b/src/structures/Message.js index 954ddab39..a59b1cc22 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -7,6 +7,7 @@ const MessageAttachment = require('./MessageAttachment'); const Embed = require('./MessageEmbed'); const Mentions = require('./MessageMentions'); const ReactionCollector = require('./ReactionCollector'); +const Sticker = require('./Sticker'); const { Error, TypeError } = require('../errors'); const ReactionManager = require('../managers/ReactionManager'); const Collection = require('../util/Collection'); @@ -133,6 +134,17 @@ class Message extends Base { } } + /** + * A collection of stickers in the message + * @type {Collection} + */ + this.stickers = new Collection(); + if (data.stickers) { + for (const sticker of data.stickers) { + this.stickers.set(sticker.id, new Sticker(this.client, sticker)); + } + } + /** * The timestamp the message was sent at * @type {number} diff --git a/src/structures/Sticker.js b/src/structures/Sticker.js new file mode 100644 index 000000000..1e3dda8b5 --- /dev/null +++ b/src/structures/Sticker.js @@ -0,0 +1,88 @@ +'use strict'; + +const Base = require('./Base'); +const { StickerFormatTypes } = require('../util/Constants'); +const Snowflake = require('../util/Snowflake'); + +/** + * Represents a Sticker. + * @extends {Base} + */ +class Sticker extends Base { + constructor(client, sticker) { + super(client); + /** + * The ID of the sticker + * @type {Snowflake} + */ + this.id = sticker.id; + + /** + * The ID of the sticker's image + * @type {string} + */ + this.asset = sticker.asset; + + /** + * The description of the sticker + * @type {string} + */ + this.description = sticker.description; + + /** + * The format of the sticker + * @type {StickerFormatTypes} + */ + this.format = StickerFormatTypes[sticker.format_type]; + + /** + * The name of the sticker + * @type {string} + */ + this.name = sticker.name; + + /** + * The ID of the pack the sticker is from + * @type {Snowflake} + */ + this.packID = sticker.pack_id; + + /** + * An array of tags for the sticker, if any + * @type {string[]} + */ + this.tags = sticker.tags?.split(', ') ?? []; + } + + /** + * The timestamp the sticker was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return Snowflake.deconstruct(this.id).timestamp; + } + + /** + * The time the sticker was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * A link to the sticker + * If the sticker's format is LOTTIE, it returns the URL of the Lottie json file. + * Lottie json files must be converted in order to be displayed in Discord. + * @type {string} + */ + get url() { + return `${this.client.options.http.cdn}/stickers/${this.id}/${this.asset}.${ + this.format === 'LOTTIE' ? 'json' : 'png' + }`; + } +} + +module.exports = Sticker; diff --git a/src/util/Constants.js b/src/util/Constants.js index 904063b2b..7516b78ff 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -110,6 +110,7 @@ function makeImageUrl(root, { format = 'webp', size } = {}) { if (size && !AllowedImageSizes.includes(size)) throw new RangeError('IMAGE_SIZE', size); return `${root}.${format}${size ? `?size=${size}` : ''}`; } + /** * Options for Image URLs. * @typedef {Object} ImageURLOptions @@ -685,6 +686,7 @@ exports.APIErrors = { INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT: 50036, INVALID_API_VERSION: 50041, CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL: 50074, + INVALID_STICKER_SENT: 50081, REACTION_BLOCKED: 90001, RESOURCE_OVERLOADED: 130000, }; @@ -723,6 +725,15 @@ exports.WebhookTypes = [ 'Channel Follower', ]; +/** + * The value set for a sticker's type: + * * PNG + * * APNG + * * LOTTIE + * @typedef {string} StickerFormatTypes + */ +exports.StickerFormatTypes = createEnum([null, 'PNG', 'APNG', 'LOTTIE']); + /** * An overwrite type: * * role diff --git a/typings/index.d.ts b/typings/index.d.ts index 5bb029034..c217855c8 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -21,6 +21,12 @@ declare enum ChannelTypes { STAGE = 13, } +declare enum StickerFormatTypes { + PNG = 1, + APNG = 2, + LOTTIE = 3, +} + declare enum OverwriteTypes { role = 0, member = 1, @@ -557,7 +563,8 @@ declare module 'discord.js' { MessageTypes: MessageType[]; SystemMessageTypes: SystemMessageType[]; ActivityTypes: ActivityType[]; - OverwriteTypes: OverwriteTypes; + StickerFormatTypes: typeof StickerFormatTypes; + OverwriteTypes: typeof OverwriteTypes; ExplicitContentFilterLevels: ExplicitContentFilterLevel[]; DefaultMessageNotifications: DefaultMessageNotifications[]; VerificationLevels: VerificationLevel[]; @@ -1015,6 +1022,7 @@ declare module 'discord.js' { public readonly pinnable: boolean; public pinned: boolean; public reactions: ReactionManager; + public stickers: Collection; public system: boolean; public tts: boolean; public type: MessageType; @@ -2278,6 +2286,7 @@ declare module 'discord.js' { INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT: 50036; INVALID_API_VERSION: 50041; CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL: 50074; + INVALID_STICKER_SENT: 50081; REACTION_BLOCKED: 90001; RESOURCE_OVERLOADED: 130000; } @@ -3263,6 +3272,20 @@ declare module 'discord.js' { type Status = number; + export class Sticker extends Base { + constructor(client: Client, data: object); + public asset: string; + public readonly createdTimestamp: number; + public readonly createdAt: Date; + public description: string; + public format: StickerFormatTypes; + public id: Snowflake; + public name: string; + public packID: Snowflake; + public tags: string[]; + public readonly url: string; + } + interface StreamOptions { type?: StreamType; seek?: number;