From 23ce3c3146c3453f7902ac37c434231ce5d7a40d Mon Sep 17 00:00:00 2001 From: Advaith Date: Tue, 15 Jun 2021 23:47:04 -0700 Subject: [PATCH 01/40] feat(Sticker): update sticker properties --- src/structures/Sticker.js | 58 +++++++++++++++++++++++++++++---------- src/util/Constants.js | 8 ++++++ typings/index.d.ts | 17 ++++++++++-- 3 files changed, 67 insertions(+), 16 deletions(-) diff --git a/src/structures/Sticker.js b/src/structures/Sticker.js index 941e28cbb1d0..ef900bc7f962 100644 --- a/src/structures/Sticker.js +++ b/src/structures/Sticker.js @@ -1,7 +1,7 @@ 'use strict'; const Base = require('./Base'); -const { StickerFormatTypes } = require('../util/Constants'); +const { StickerFormatTypes, StickerTypes } = require('../util/Constants'); const SnowflakeUtil = require('../util/SnowflakeUtil'); /** @@ -18,16 +18,16 @@ class Sticker extends Base { this.id = sticker.id; /** - * The ID of the sticker's image + * The description of the sticker * @type {string} */ - this.asset = sticker.asset; + this.description = sticker.description; /** - * The description of the sticker - * @type {string} + * The type of the sticker + * @type {StickerType} */ - this.description = sticker.description; + this.type = StickerTypes[sticker.type]; /** * The format of the sticker @@ -42,16 +42,40 @@ class Sticker extends Base { this.name = sticker.name; /** - * The ID of the pack the sticker is from - * @type {Snowflake} + * The ID of the pack the sticker is from, for standard stickers + * @type {?Snowflake} */ - this.packID = sticker.pack_id; + this.packID = sticker.pack_id ?? null; /** * An array of tags for the sticker, if any * @type {string[]} */ this.tags = sticker.tags?.split(', ') ?? []; + + /** + * Whether or not the guild sticker is available + * @type {?boolean} + */ + this.available = sticker.available ?? null; + + /** + * The ID of the guild that owns this sticker + * @type {?Snowflake} + */ + this.guildID = sticker.guild_id ?? null; + + /** + * The user that uploaded the guild sticker + * @type {?User} + */ + this.user = sticker.user ? this.client.users.add(sticker.user) : null; + + /** + * The standard sticker's sort order within its pack + * @type {?number} + */ + this.sortValue = sticker.sort_value ?? null; } /** @@ -72,16 +96,22 @@ class Sticker extends Base { return new Date(this.createdTimestamp); } + /** + * The guild that owns this sticker + * @type {?Guild} + * @readonly + */ + get guild() { + return (this.guildID && this.client.guilds.cache.get(this.guildID)) ?? null; + } + /** * 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. + * If the sticker's format is LOTTIE, it returns the URL of the Lottie json file. * @type {string} */ get url() { - return `${this.client.options.http.cdn}/stickers/${this.id}/${this.asset}.${ - this.format === 'LOTTIE' ? 'json' : 'png' - }`; + return `${this.client.options.http.cdn}/stickers/${this.id}.${this.format === 'LOTTIE' ? 'json' : 'png'}`; } } diff --git a/src/util/Constants.js b/src/util/Constants.js index a75ef8616d96..d1a4d3547d3d 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -742,6 +742,14 @@ exports.WebhookTypes = createEnum([null, 'Incoming', 'Channel Follower']); /** * The value set for a sticker's type: + * * STANDARD + * * GUILD + * @typedef {string} StickerFormatType + */ +exports.StickerTypes = createEnum([null, 'STANDARD', 'GUILD']); + +/** + * The value set for a sticker's format type: * * PNG * * APNG * * LOTTIE diff --git a/typings/index.d.ts b/typings/index.d.ts index 256fe713196b..7d6b9c7e4807 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -100,6 +100,11 @@ declare enum PrivacyLevels { GUILD_ONLY = 2, } +declare enum StickerTypes { + STANDARD = 1, + GUILD = 2, +} + declare enum StickerFormatTypes { PNG = 1, APNG = 2, @@ -701,6 +706,7 @@ declare module 'discord.js' { MessageTypes: MessageType[]; SystemMessageTypes: SystemMessageType[]; ActivityTypes: typeof ActivityTypes; + StickerTypes: typeof StickerTypes; StickerFormatTypes: typeof StickerFormatTypes; OverwriteTypes: typeof OverwriteTypes; ExplicitContentFilterLevels: typeof ExplicitContentFilterLevels; @@ -3840,18 +3846,25 @@ declare module 'discord.js' { export class Sticker extends Base { constructor(client: Client, data: unknown); - public asset: string; public readonly createdTimestamp: number; public readonly createdAt: Date; + public available: boolean | null; public description: string; public format: StickerFormatType; + public readonly guild: Guild | null; + public guildID: Snowflake | null; public id: Snowflake; public name: string; - public packID: Snowflake; + public packID: Snowflake | null; + public sortValue: number | null; public tags: string[]; + public type: StickerType; + public user: User | null; public readonly url: string; } + type StickerType = keyof typeof StickerTypes; + type StickerFormatType = keyof typeof StickerFormatTypes; type SystemChannelFlagsString = From 61c09c72aa373010cb5d7f33ec4e93f711c3fbe8 Mon Sep 17 00:00:00 2001 From: Advaith Date: Wed, 16 Jun 2021 22:09:28 -0700 Subject: [PATCH 02/40] feat: add StickerPack --- src/client/Client.js | 15 ++++++ src/structures/Sticker.js | 4 ++ src/structures/StickerPack.js | 99 +++++++++++++++++++++++++++++++++++ src/util/Constants.js | 2 + typings/index.d.ts | 57 +++++++++++++------- 5 files changed, 157 insertions(+), 20 deletions(-) create mode 100644 src/structures/StickerPack.js diff --git a/src/client/Client.js b/src/client/Client.js index f6848ab2b9c7..6b832bb4823a 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -13,6 +13,7 @@ const ShardClientUtil = require('../sharding/ShardClientUtil'); const GuildPreview = require('../structures/GuildPreview'); const GuildTemplate = require('../structures/GuildTemplate'); const Invite = require('../structures/Invite'); +const StickerPack = require('../structures/StickerPack'); const VoiceRegion = require('../structures/VoiceRegion'); const Webhook = require('../structures/Webhook'); const Widget = require('../structures/Widget'); @@ -310,6 +311,20 @@ class Client extends BaseClient { }); } + /** + * Obtains the list of sticker packs available to Nitro subscribers from Discord. + * @returns {Promise>} + * @example + * client.fetchNitroStickerPacks() + * .then(packs => console.log(`Available sticker packs are: ${packs.map(pack => pack.name).join(', ')}`)) + * .catch(console.error); + */ + fetchNitroStickerPacks() { + return this.api('sticker-packs') + .get() + .then(res => new Collection(res.sticker_packs.map(p => [p.id, new StickerPack(this, p)]))); + } + /** * Sweeps all text-based channels' messages and removes the ones older than the max message lifetime. * If the message has been edited, the time of the edit is used rather than the time of the original message. diff --git a/src/structures/Sticker.js b/src/structures/Sticker.js index ef900bc7f962..f56968d84c5f 100644 --- a/src/structures/Sticker.js +++ b/src/structures/Sticker.js @@ -9,6 +9,10 @@ const SnowflakeUtil = require('../util/SnowflakeUtil'); * @extends {Base} */ class Sticker extends Base { + /** + * @param {Client} client The instantiating client + * @param {Object} sticker The data for the sticker + */ constructor(client, sticker) { super(client); /** diff --git a/src/structures/StickerPack.js b/src/structures/StickerPack.js new file mode 100644 index 000000000000..13e3459e3a3b --- /dev/null +++ b/src/structures/StickerPack.js @@ -0,0 +1,99 @@ +'use strict'; + +const Base = require('./Base'); +const Sticker = require('./Sticker'); +const Collection = require('../util/Collection'); +const SnowflakeUtil = require('../util/SnowflakeUtil'); + +/** + * Represents a pack of standard stickers. + * @extends {Base} + */ +class StickerPack extends Base { + /** + * @param {Client} client The instantiating client + * @param {Object} pack The data for the sticker pack + */ + constructor(client, pack) { + super(client); + /** + * The ID of the sticker pack + * @type {Snowflake} + */ + this.id = pack.id; + + /** + * The stickers in the pack + * @type {Collection} + */ + this.stickers = new Collection(pack.stickers.map(s => [s.id, new Sticker(client, s)])); + + /** + * The name of the sticker pack + * @type {string} + */ + this.name = pack.name; + + /** + * The ID of the pack's SKU + * @type {Snowflake} + */ + this.skuID = pack.sku_id; + + /** + * The ID of a sticker in the pack which is shown as the pack's icon + * @type {Snowflake} + */ + this.coverStickerID = pack.cover_sticker_id; + + /** + * The description of the sticker pack + * @type {string} + */ + this.description = pack.description; + + /** + * The ID of the sticker pack's banner image + * @type {?Snowflake} + */ + this.bannerID = pack.banner_asset_id; + } + + /** + * The timestamp the sticker was created at + * @type {number} + * @readonly + */ + get createdTimestamp() { + return SnowflakeUtil.deconstruct(this.id).timestamp; + } + + /** + * The time the sticker was created at + * @type {Date} + * @readonly + */ + get createdAt() { + return new Date(this.createdTimestamp); + } + + /** + * The sticker which is shown as the pack's icon + * @type {Sticker} + * @readonly + */ + get coverSticker() { + return this.stickers.get(this.coverStickerID); + } + + /** + * The URL to this sticker pack's banner. + * @param {StaticImageURLOptions} [options={}] Options for the Image URL + * @returns {?string} + */ + bannerURL({ format, size } = {}) { + return this.client.rest.cdn.StickerBanner(this.bannerID, format, size); + } +} + +module.exports = StickerPack; diff --git a/src/util/Constants.js b/src/util/Constants.js index d1a4d3547d3d..e1402ac0091a 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -169,6 +169,8 @@ exports.Endpoints = { makeImageUrl(`${root}/app-icons/${clientID}/${hash}`, { size, format }), AppAsset: (clientID, hash, { format = 'webp', size } = {}) => makeImageUrl(`${root}/app-assets/${clientID}/${hash}`, { size, format }), + StickerBanner: (bannerID, format = 'webp', size) => + makeImageUrl(`${root}/app-assets/710982414301790216/store/${bannerID}`, { size, format }), GDMIcon: (channelID, hash, format = 'webp', size) => makeImageUrl(`${root}/channel-icons/${channelID}/${hash}`, { size, format }), Splash: (guildID, hash, format = 'webp', size) => diff --git a/typings/index.d.ts b/typings/index.d.ts index 7d6b9c7e4807..60db02c87778 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -392,6 +392,7 @@ declare module 'discord.js' { public fetchInvite(invite: InviteResolvable): Promise; public fetchGuildTemplate(template: GuildTemplateResolvable): Promise; public fetchVoiceRegions(): Promise>; + public fetchNitroStickerPacks(): Promise>; public fetchWebhook(id: Snowflake, token?: string): Promise; public fetchWidget(id: Snowflake): Promise; public generateInvite(options?: InviteGenerationOptions): string; @@ -560,6 +561,7 @@ declare module 'discord.js' { format: AllowedImageFormat, size: number, ) => string; + StickerBanner: (bannerID: Snowflake, format: AllowedImageFormat, size: number) => string; TeamIcon: (teamID: Snowflake | number, hash: string, format: AllowedImageFormat, size: number) => string; }; }; @@ -1761,6 +1763,40 @@ declare module 'discord.js' { public readonly createdAt: Date; } + export class Sticker extends Base { + constructor(client: Client, data: unknown); + public readonly createdTimestamp: number; + public readonly createdAt: Date; + public available: boolean | null; + public description: string; + public format: StickerFormatType; + public readonly guild: Guild | null; + public guildID: Snowflake | null; + public id: Snowflake; + public name: string; + public packID: Snowflake | null; + public sortValue: number | null; + public tags: string[]; + public type: StickerType; + public user: User | null; + public readonly url: string; + } + + export class StickerPack extends Base { + constructor(client: Client, data: unknown); + public readonly createdTimestamp: number; + public readonly createdAt: Date; + public bannerID: Snowflake; + public readonly coverSticker: Sticker; + public coverStickerID: Snowflake; + public description: string; + public id: Snowflake; + public name: string; + public skuID: Snowflake; + public stickers: Collection; + public bannerURL(options?: StaticImageURLOptions): string; + } + export class StoreChannel extends GuildChannel { constructor(guild: Guild, data?: unknown); public nsfw: boolean; @@ -3844,29 +3880,10 @@ declare module 'discord.js' { type Status = number; - export class Sticker extends Base { - constructor(client: Client, data: unknown); - public readonly createdTimestamp: number; - public readonly createdAt: Date; - public available: boolean | null; - public description: string; - public format: StickerFormatType; - public readonly guild: Guild | null; - public guildID: Snowflake | null; - public id: Snowflake; - public name: string; - public packID: Snowflake | null; - public sortValue: number | null; - public tags: string[]; - public type: StickerType; - public user: User | null; - public readonly url: string; - } + type StickerFormatType = keyof typeof StickerFormatTypes; type StickerType = keyof typeof StickerTypes; - type StickerFormatType = keyof typeof StickerFormatTypes; - type SystemChannelFlagsString = | 'SUPPRESS_JOIN_NOTIFICATIONS' | 'SUPPRESS_PREMIUM_SUBSCRIPTIONS' From 9cc3e8774dcc746cec48db2c75e449b88ac400a1 Mon Sep 17 00:00:00 2001 From: Advaith Date: Wed, 16 Jun 2021 23:06:20 -0700 Subject: [PATCH 03/40] feat(Sticker): add fetchPack() --- src/structures/Sticker.js | 8 ++++++++ typings/index.d.ts | 13 +++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/structures/Sticker.js b/src/structures/Sticker.js index f56968d84c5f..1fbcd37375fa 100644 --- a/src/structures/Sticker.js +++ b/src/structures/Sticker.js @@ -117,6 +117,14 @@ class Sticker extends Base { get url() { return `${this.client.options.http.cdn}/stickers/${this.id}.${this.format === 'LOTTIE' ? 'json' : 'png'}`; } + + /** + * Fetches the pack this sticker is part of from Discord, if this is a Nitro sticker. + * @returns {Promise} + */ + async fetchPack() { + return (this.packID && (await this.client.fetchNitroStickerPacks()).get(this.packID)) ?? null; + } } module.exports = Sticker; diff --git a/typings/index.d.ts b/typings/index.d.ts index 60db02c87778..edcfcb643345 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -1767,19 +1767,20 @@ declare module 'discord.js' { constructor(client: Client, data: unknown); public readonly createdTimestamp: number; public readonly createdAt: Date; - public available: boolean | null; + public available: ?boolean; public description: string; public format: StickerFormatType; - public readonly guild: Guild | null; - public guildID: Snowflake | null; + public readonly guild: ?Guild; + public guildID: ?Snowflake; public id: Snowflake; public name: string; - public packID: Snowflake | null; - public sortValue: number | null; + public packID: ?Snowflake; + public sortValue: ?number; public tags: string[]; public type: StickerType; - public user: User | null; + public user: ?User; public readonly url: string; + public fetchPack(): Promise; } export class StickerPack extends Base { From dd810cff9e40349410cada5bccd368b70bd9d99b Mon Sep 17 00:00:00 2001 From: Advaith Date: Wed, 16 Jun 2021 23:09:54 -0700 Subject: [PATCH 04/40] types: fix order --- typings/index.d.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/typings/index.d.ts b/typings/index.d.ts index edcfcb643345..ee5ea73805f2 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -100,17 +100,17 @@ declare enum PrivacyLevels { GUILD_ONLY = 2, } -declare enum StickerTypes { - STANDARD = 1, - GUILD = 2, -} - declare enum StickerFormatTypes { PNG = 1, APNG = 2, LOTTIE = 3, } +declare enum StickerTypes { + STANDARD = 1, + GUILD = 2, +} + declare enum VerificationLevels { NONE = 0, LOW = 1, From 059964cd888fec46df5244de9dcf4edc97072dcc Mon Sep 17 00:00:00 2001 From: Advaith Date: Thu, 17 Jun 2021 01:51:50 -0700 Subject: [PATCH 05/40] use client.guilds.resolve Co-authored-by: SpaceEEC --- src/structures/Sticker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/Sticker.js b/src/structures/Sticker.js index 1fbcd37375fa..cf794783892f 100644 --- a/src/structures/Sticker.js +++ b/src/structures/Sticker.js @@ -106,7 +106,7 @@ class Sticker extends Base { * @readonly */ get guild() { - return (this.guildID && this.client.guilds.cache.get(this.guildID)) ?? null; + return this.client.guilds.resolve(this.guildID); } /** From ac897e5682e7e5ebd42b168777d3d4f133d2b5d8 Mon Sep 17 00:00:00 2001 From: Advaith Date: Thu, 17 Jun 2021 01:54:56 -0700 Subject: [PATCH 06/40] fix: rename StickerBanner to StickerPackBanner --- src/structures/StickerPack.js | 2 +- src/util/Constants.js | 2 +- typings/index.d.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/structures/StickerPack.js b/src/structures/StickerPack.js index 13e3459e3a3b..b44a9016a39c 100644 --- a/src/structures/StickerPack.js +++ b/src/structures/StickerPack.js @@ -92,7 +92,7 @@ class StickerPack extends Base { * @returns {?string} */ bannerURL({ format, size } = {}) { - return this.client.rest.cdn.StickerBanner(this.bannerID, format, size); + return this.client.rest.cdn.StickerPackBanner(this.bannerID, format, size); } } diff --git a/src/util/Constants.js b/src/util/Constants.js index e1402ac0091a..b47e10290753 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -169,7 +169,7 @@ exports.Endpoints = { makeImageUrl(`${root}/app-icons/${clientID}/${hash}`, { size, format }), AppAsset: (clientID, hash, { format = 'webp', size } = {}) => makeImageUrl(`${root}/app-assets/${clientID}/${hash}`, { size, format }), - StickerBanner: (bannerID, format = 'webp', size) => + StickerPackBanner: (bannerID, format = 'webp', size) => makeImageUrl(`${root}/app-assets/710982414301790216/store/${bannerID}`, { size, format }), GDMIcon: (channelID, hash, format = 'webp', size) => makeImageUrl(`${root}/channel-icons/${channelID}/${hash}`, { size, format }), diff --git a/typings/index.d.ts b/typings/index.d.ts index ee5ea73805f2..8c5dbe766b71 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -561,7 +561,7 @@ declare module 'discord.js' { format: AllowedImageFormat, size: number, ) => string; - StickerBanner: (bannerID: Snowflake, format: AllowedImageFormat, size: number) => string; + StickerPackBanner: (bannerID: Snowflake, format: AllowedImageFormat, size: number) => string; TeamIcon: (teamID: Snowflake | number, hash: string, format: AllowedImageFormat, size: number) => string; }; }; From afa882b0552e24a51418ff310fe8dae7e61c4f37 Mon Sep 17 00:00:00 2001 From: Advaith Date: Thu, 17 Jun 2021 17:38:23 -0700 Subject: [PATCH 07/40] feat(Client): add fetchSticker(id) --- src/client/Client.js | 17 +++++++++++++++++ typings/index.d.ts | 1 + 2 files changed, 18 insertions(+) diff --git a/src/client/Client.js b/src/client/Client.js index 6b832bb4823a..fe518ffa8900 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -13,6 +13,7 @@ const ShardClientUtil = require('../sharding/ShardClientUtil'); const GuildPreview = require('../structures/GuildPreview'); const GuildTemplate = require('../structures/GuildTemplate'); const Invite = require('../structures/Invite'); +const Sticker = require('../structures/Sticker'); const StickerPack = require('../structures/StickerPack'); const VoiceRegion = require('../structures/VoiceRegion'); const Webhook = require('../structures/Webhook'); @@ -311,6 +312,22 @@ class Client extends BaseClient { }); } + /** + * Obtains a sticker from Discord. + * @param {Snowflake} id ID of the sticker + * @returns {Promise} + * @example + * client.fetchSticker('id') + * .then(sticker => console.log(`Obtained sticker with name: ${sticker.name}`)) + * .catch(console.error); + */ + fetchSticker(id) { + return this.api + .stickers(id) + .get() + .then(data => new Sticker(this, data)); + } + /** * Obtains the list of sticker packs available to Nitro subscribers from Discord. * @returns {Promise>} diff --git a/typings/index.d.ts b/typings/index.d.ts index 8c5dbe766b71..1386a89adcf4 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -392,6 +392,7 @@ declare module 'discord.js' { public fetchInvite(invite: InviteResolvable): Promise; public fetchGuildTemplate(template: GuildTemplateResolvable): Promise; public fetchVoiceRegions(): Promise>; + public fetchSticker(id: Snowflake): Promise; public fetchNitroStickerPacks(): Promise>; public fetchWebhook(id: Snowflake, token?: string): Promise; public fetchWidget(id: Snowflake): Promise; From ee953d992a92400afbe0b83deee718efc2ed0d05 Mon Sep 17 00:00:00 2001 From: Advaith Date: Sat, 19 Jun 2021 03:17:03 -0700 Subject: [PATCH 08/40] Edit fetchSticker desc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Antonio Román --- src/client/Client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/Client.js b/src/client/Client.js index fe518ffa8900..23d4ca66bbfb 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -314,7 +314,7 @@ class Client extends BaseClient { /** * Obtains a sticker from Discord. - * @param {Snowflake} id ID of the sticker + * @param {Snowflake} id The sticker's ID * @returns {Promise} * @example * client.fetchSticker('id') From 3545ca2836f7e444d62293e6b250ba17c8a879e3 Mon Sep 17 00:00:00 2001 From: Advaith Date: Sat, 19 Jun 2021 03:27:01 -0700 Subject: [PATCH 09/40] style: switch from .then to async/await --- src/client/Client.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/client/Client.js b/src/client/Client.js index 23d4ca66bbfb..e9e1f434eae4 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -321,11 +321,8 @@ class Client extends BaseClient { * .then(sticker => console.log(`Obtained sticker with name: ${sticker.name}`)) * .catch(console.error); */ - fetchSticker(id) { - return this.api - .stickers(id) - .get() - .then(data => new Sticker(this, data)); + async fetchSticker(id) { + return new Sticker(this, await this.api.stickers(id).get()); } /** @@ -336,10 +333,10 @@ class Client extends BaseClient { * .then(packs => console.log(`Available sticker packs are: ${packs.map(pack => pack.name).join(', ')}`)) * .catch(console.error); */ - fetchNitroStickerPacks() { - return this.api('sticker-packs') - .get() - .then(res => new Collection(res.sticker_packs.map(p => [p.id, new StickerPack(this, p)]))); + async fetchNitroStickerPacks() { + return new Collection( + (await this.api('sticker-packs').get()).sticker_packs.map(p => [p.id, new StickerPack(this, p)]), + ); } /** From 2e84a12bc1ca0281c1e22520e039773dd614d75c Mon Sep 17 00:00:00 2001 From: Advaith Date: Sat, 19 Jun 2021 15:35:34 -0700 Subject: [PATCH 10/40] style: use data variables instead of inline API reqs --- src/client/Client.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client/Client.js b/src/client/Client.js index e9e1f434eae4..3e0d0cb96e12 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -322,7 +322,8 @@ class Client extends BaseClient { * .catch(console.error); */ async fetchSticker(id) { - return new Sticker(this, await this.api.stickers(id).get()); + const data = await this.api.stickers(id).get(); + return new Sticker(this, data); } /** @@ -334,9 +335,8 @@ class Client extends BaseClient { * .catch(console.error); */ async fetchNitroStickerPacks() { - return new Collection( - (await this.api('sticker-packs').get()).sticker_packs.map(p => [p.id, new StickerPack(this, p)]), - ); + const data = await this.api('sticker-packs').get(); + return new Collection(data.sticker_packs.map(p => [p.id, new StickerPack(this, p)])); } /** From 8f3c562de2120edba9a5778955269b0bf0ef1fc8 Mon Sep 17 00:00:00 2001 From: Advaith Date: Sun, 20 Jun 2021 16:28:22 -0700 Subject: [PATCH 11/40] refactor: move sticker url to CDN constant --- src/structures/Sticker.js | 2 +- src/util/Constants.js | 2 ++ typings/index.d.ts | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/structures/Sticker.js b/src/structures/Sticker.js index cf794783892f..20891ac33c0c 100644 --- a/src/structures/Sticker.js +++ b/src/structures/Sticker.js @@ -115,7 +115,7 @@ class Sticker extends Base { * @type {string} */ get url() { - return `${this.client.options.http.cdn}/stickers/${this.id}.${this.format === 'LOTTIE' ? 'json' : 'png'}`; + return this.client.rest.cdn.Sticker(this.id, this.format); } /** diff --git a/src/util/Constants.js b/src/util/Constants.js index b47e10290753..bf4072155eb7 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -179,6 +179,8 @@ exports.Endpoints = { makeImageUrl(`${root}/discovery-splashes/${guildID}/${hash}`, { size, format }), TeamIcon: (teamID, hash, { format = 'webp', size } = {}) => makeImageUrl(`${root}/team-icons/${teamID}/${hash}`, { size, format }), + Sticker: (stickerID, stickerFormat) => + `${root}/stickers/${stickerID}.${stickerFormat === 'LOTTIE' ? 'json' : 'png'}`, }; }, invite: (root, code) => `${root}/${code}`, diff --git a/typings/index.d.ts b/typings/index.d.ts index 1386a89adcf4..e987cb332daa 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -554,6 +554,7 @@ declare module 'discord.js' { ) => string; AppIcon: (userID: Snowflake | number, hash: string, format: AllowedImageFormat, size: number) => string; AppAsset: (userID: Snowflake | number, hash: string, format: AllowedImageFormat, size: number) => string; + StickerPackBanner: (bannerID: Snowflake, format: AllowedImageFormat, size: number) => string; GDMIcon: (userID: Snowflake | number, hash: string, format: AllowedImageFormat, size: number) => string; Splash: (guildID: Snowflake | number, hash: string, format: AllowedImageFormat, size: number) => string; DiscoverySplash: ( @@ -562,8 +563,8 @@ declare module 'discord.js' { format: AllowedImageFormat, size: number, ) => string; - StickerPackBanner: (bannerID: Snowflake, format: AllowedImageFormat, size: number) => string; TeamIcon: (teamID: Snowflake | number, hash: string, format: AllowedImageFormat, size: number) => string; + Sticker: (stickerID: Snowflake, stickerFormat: StickerFormatType) => string; }; }; WSCodes: { From 58c95e3c6d31dea67c0cc67b3d1beeb8238bce42 Mon Sep 17 00:00:00 2001 From: Advaith Date: Sun, 20 Jun 2021 16:30:44 -0700 Subject: [PATCH 12/40] refactor: rename fetchNitroStickerPacks to fetchPremiumStickerPacks Co-Authored-By: Tiemen --- src/client/Client.js | 4 ++-- src/structures/Sticker.js | 2 +- typings/index.d.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/client/Client.js b/src/client/Client.js index 3e0d0cb96e12..240a931f0177 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -330,11 +330,11 @@ class Client extends BaseClient { * Obtains the list of sticker packs available to Nitro subscribers from Discord. * @returns {Promise>} * @example - * client.fetchNitroStickerPacks() + * client.fetchPremiumStickerPacks() * .then(packs => console.log(`Available sticker packs are: ${packs.map(pack => pack.name).join(', ')}`)) * .catch(console.error); */ - async fetchNitroStickerPacks() { + async fetchPremiumStickerPacks() { const data = await this.api('sticker-packs').get(); return new Collection(data.sticker_packs.map(p => [p.id, new StickerPack(this, p)])); } diff --git a/src/structures/Sticker.js b/src/structures/Sticker.js index 20891ac33c0c..0c4a7a5c63c8 100644 --- a/src/structures/Sticker.js +++ b/src/structures/Sticker.js @@ -123,7 +123,7 @@ class Sticker extends Base { * @returns {Promise} */ async fetchPack() { - return (this.packID && (await this.client.fetchNitroStickerPacks()).get(this.packID)) ?? null; + return (this.packID && (await this.client.fetchPremiumStickerPacks()).get(this.packID)) ?? null; } } diff --git a/typings/index.d.ts b/typings/index.d.ts index e987cb332daa..8ac7e609e3cf 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -393,7 +393,7 @@ declare module 'discord.js' { public fetchGuildTemplate(template: GuildTemplateResolvable): Promise; public fetchVoiceRegions(): Promise>; public fetchSticker(id: Snowflake): Promise; - public fetchNitroStickerPacks(): Promise>; + public fetchPremiumStickerPacks(): Promise>; public fetchWebhook(id: Snowflake, token?: string): Promise; public fetchWidget(id: Snowflake): Promise; public generateInvite(options?: InviteGenerationOptions): string; From b7f0fc8dae509a340a7e8a852f66327cb428de70 Mon Sep 17 00:00:00 2001 From: Advaith Date: Thu, 24 Jun 2021 21:48:17 -0700 Subject: [PATCH 13/40] refactor(Permissions): `MANAGE_EMOJIS_AND_STICKERS` --- src/errors/Messages.js | 4 ++-- src/structures/GuildEmoji.js | 6 +++--- src/util/Permissions.js | 4 ++-- typings/index.d.ts | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/errors/Messages.js b/src/errors/Messages.js index c9d7983fea8a..e88186638763 100644 --- a/src/errors/Messages.js +++ b/src/errors/Messages.js @@ -98,8 +98,8 @@ const Messages = { EMOJI_TYPE: 'Emoji must be a string or GuildEmoji/ReactionEmoji', EMOJI_MANAGED: 'Emoji is managed and has no Author.', - MISSING_MANAGE_EMOJIS_PERMISSION: guild => - `Client must have Manage Emoji permission in guild ${guild} to see emoji authors.`, + MISSING_MANAGE_EMOJIS_AND_STICKERS_PERMISSION: guild => + `Client must have Manage Emojis and Stickers permission in guild ${guild} to see emoji authors.`, REACTION_RESOLVE_USER: "Couldn't resolve the user ID to remove from the reaction.", diff --git a/src/structures/GuildEmoji.js b/src/structures/GuildEmoji.js index 43d7117a331d..488d816c8bc5 100644 --- a/src/structures/GuildEmoji.js +++ b/src/structures/GuildEmoji.js @@ -59,7 +59,7 @@ class GuildEmoji extends BaseGuildEmoji { */ get deletable() { if (!this.guild.me) throw new Error('GUILD_UNCACHED_ME'); - return !this.managed && this.guild.me.permissions.has(Permissions.FLAGS.MANAGE_EMOJIS); + return !this.managed && this.guild.me.permissions.has(Permissions.FLAGS.MANAGE_EMOJIS_AND_STICKERS); } /** @@ -80,8 +80,8 @@ class GuildEmoji extends BaseGuildEmoji { throw new Error('EMOJI_MANAGED'); } else { if (!this.guild.me) throw new Error('GUILD_UNCACHED_ME'); - if (!this.guild.me.permissions.has(Permissions.FLAGS.MANAGE_EMOJIS)) { - throw new Error('MISSING_MANAGE_EMOJIS_PERMISSION', this.guild); + if (!this.guild.me.permissions.has(Permissions.FLAGS.MANAGE_EMOJIS_AND_STICKERS)) { + throw new Error('MISSING_MANAGE_EMOJIS_AND_STICKERS_PERMISSION', this.guild); } } const data = await this.client.api.guilds(this.guild.id).emojis(this.id).get(); diff --git a/src/util/Permissions.js b/src/util/Permissions.js index bc6ef20aedea..17c7ac917a13 100644 --- a/src/util/Permissions.js +++ b/src/util/Permissions.js @@ -77,7 +77,7 @@ class Permissions extends BitField { * * `MANAGE_NICKNAMES` (change other members' nicknames) * * `MANAGE_ROLES` * * `MANAGE_WEBHOOKS` - * * `MANAGE_EMOJIS` + * * `MANAGE_EMOJIS_AND_STICKERS` * * `USE_APPLICATION_COMMANDS` * * `REQUEST_TO_SPEAK` * @type {Object} @@ -114,7 +114,7 @@ Permissions.FLAGS = { MANAGE_NICKNAMES: 1n << 27n, MANAGE_ROLES: 1n << 28n, MANAGE_WEBHOOKS: 1n << 29n, - MANAGE_EMOJIS: 1n << 30n, + MANAGE_EMOJIS_AND_STICKERS: 1n << 30n, USE_APPLICATION_COMMANDS: 1n << 31n, REQUEST_TO_SPEAK: 1n << 32n, }; diff --git a/typings/index.d.ts b/typings/index.d.ts index 8ac7e609e3cf..b6c20e223b66 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -3641,7 +3641,7 @@ declare module 'discord.js' { | 'MANAGE_NICKNAMES' | 'MANAGE_ROLES' | 'MANAGE_WEBHOOKS' - | 'MANAGE_EMOJIS' + | 'MANAGE_EMOJIS_AND_STICKERS' | 'USE_APPLICATION_COMMANDS' | 'REQUEST_TO_SPEAK'; From f1c8f19ece0f0a07ba3358a4fd9434617101f5f6 Mon Sep 17 00:00:00 2001 From: Advaith Date: Thu, 24 Jun 2021 21:52:28 -0700 Subject: [PATCH 14/40] fix(StickerPack): `cover_sticker_id` is optional --- src/structures/StickerPack.js | 8 ++++---- typings/index.d.ts | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/structures/StickerPack.js b/src/structures/StickerPack.js index b44a9016a39c..333b926dcc9b 100644 --- a/src/structures/StickerPack.js +++ b/src/structures/StickerPack.js @@ -42,9 +42,9 @@ class StickerPack extends Base { /** * The ID of a sticker in the pack which is shown as the pack's icon - * @type {Snowflake} + * @type {?Snowflake} */ - this.coverStickerID = pack.cover_sticker_id; + this.coverStickerID = pack.cover_sticker_id ?? null; /** * The description of the sticker pack @@ -79,11 +79,11 @@ class StickerPack extends Base { /** * The sticker which is shown as the pack's icon - * @type {Sticker} + * @type {?Sticker} * @readonly */ get coverSticker() { - return this.stickers.get(this.coverStickerID); + return this.coverStickerID && this.stickers.get(this.coverStickerID); } /** diff --git a/typings/index.d.ts b/typings/index.d.ts index b6c20e223b66..c268e7ca1b17 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -1790,8 +1790,8 @@ declare module 'discord.js' { public readonly createdTimestamp: number; public readonly createdAt: Date; public bannerID: Snowflake; - public readonly coverSticker: Sticker; - public coverStickerID: Snowflake; + public readonly coverSticker: ?Sticker; + public coverStickerID: ?Snowflake; public description: string; public id: Snowflake; public name: string; From 3814ef3d577b502d070f3afb0d93f2a72694241a Mon Sep 17 00:00:00 2001 From: Advaith Date: Fri, 25 Jun 2021 15:02:38 -0700 Subject: [PATCH 15/40] fix: banner fields are not nullable --- src/structures/StickerPack.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/structures/StickerPack.js b/src/structures/StickerPack.js index 333b926dcc9b..7378643bf997 100644 --- a/src/structures/StickerPack.js +++ b/src/structures/StickerPack.js @@ -54,7 +54,7 @@ class StickerPack extends Base { /** * The ID of the sticker pack's banner image - * @type {?Snowflake} + * @type {Snowflake} */ this.bannerID = pack.banner_asset_id; } @@ -89,7 +89,7 @@ class StickerPack extends Base { /** * The URL to this sticker pack's banner. * @param {StaticImageURLOptions} [options={}] Options for the Image URL - * @returns {?string} + * @returns {string} */ bannerURL({ format, size } = {}) { return this.client.rest.cdn.StickerPackBanner(this.bannerID, format, size); From 7dd8a600addcdd809e43c3f0796c23841687f37f Mon Sep 17 00:00:00 2001 From: Advaith Date: Tue, 29 Jun 2021 00:33:52 -0700 Subject: [PATCH 16/40] refactor: switch to msg.sticker_items, add fetch() and fetchUser() --- src/errors/Messages.js | 1 + src/structures/Message.js | 7 +----- src/structures/Sticker.js | 49 ++++++++++++++++++++++++++++++++++----- typings/index.d.ts | 8 ++++--- 4 files changed, 50 insertions(+), 15 deletions(-) diff --git a/src/errors/Messages.js b/src/errors/Messages.js index e88186638763..292865a1014c 100644 --- a/src/errors/Messages.js +++ b/src/errors/Messages.js @@ -100,6 +100,7 @@ const Messages = { EMOJI_MANAGED: 'Emoji is managed and has no Author.', MISSING_MANAGE_EMOJIS_AND_STICKERS_PERMISSION: guild => `Client must have Manage Emojis and Stickers permission in guild ${guild} to see emoji authors.`, + NOT_GUILD_STICKER: 'Sticker is a standard (non-guild) sticker and has no User.', REACTION_RESOLVE_USER: "Couldn't resolve the user ID to remove from the reaction.", diff --git a/src/structures/Message.js b/src/structures/Message.js index da4621b2b45c..21e32f58bb94 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -146,12 +146,7 @@ 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)); - } - } + this.stickers = new Collection(data.sticker_items?.map(s => [s.id, new Sticker(this.client, s)])); /** * The timestamp the message was sent at diff --git a/src/structures/Sticker.js b/src/structures/Sticker.js index 0c4a7a5c63c8..6c49684d85d1 100644 --- a/src/structures/Sticker.js +++ b/src/structures/Sticker.js @@ -15,6 +15,11 @@ class Sticker extends Base { */ constructor(client, sticker) { super(client); + + this._patch(sticker); + } + + _patch(sticker) { /** * The ID of the sticker * @type {Snowflake} @@ -23,15 +28,15 @@ class Sticker extends Base { /** * The description of the sticker - * @type {string} + * @type {?string} */ - this.description = sticker.description; + this.description = sticker.description ?? null; /** * The type of the sticker - * @type {StickerType} + * @type {?StickerType} */ - this.type = StickerTypes[sticker.type]; + this.type = StickerTypes[sticker.type] ?? null; /** * The format of the sticker @@ -52,10 +57,10 @@ class Sticker extends Base { this.packID = sticker.pack_id ?? null; /** - * An array of tags for the sticker, if any + * An array of tags for the sticker * @type {string[]} */ - this.tags = sticker.tags?.split(', ') ?? []; + this.tags = sticker.tags?.split(', ') ?? null; /** * Whether or not the guild sticker is available @@ -100,6 +105,15 @@ class Sticker extends Base { return new Date(this.createdTimestamp); } + /** + * Whether this sticker is partial + * @type {boolean} + * @readonly + */ + get partial() { + return 'username' in this; + } + /** * The guild that owns this sticker * @type {?Guild} @@ -118,6 +132,16 @@ class Sticker extends Base { return this.client.rest.cdn.Sticker(this.id, this.format); } + /** + * Fetches this sticker. + * @returns {Promise} + */ + async fetch() { + const data = await this.client.api.stickers(this.id).get(); + this._patch(data); + return this; + } + /** * Fetches the pack this sticker is part of from Discord, if this is a Nitro sticker. * @returns {Promise} @@ -125,6 +149,19 @@ class Sticker extends Base { async fetchPack() { return (this.packID && (await this.client.fetchPremiumStickerPacks()).get(this.packID)) ?? null; } + + /** + * Fetches the user who uploaded this sticker, if this is a guild sticker. + * @returns {Promise} + */ + async fetchUser() { + if (this.partial) await this.fetch(); + if (!this.guildID) throw new Error('NOT_GUILD_STICKER'); + + const data = await this.client.api.guilds(this.guildID).stickers(this.id).get(); + this._patch(data); + return this.user; + } } module.exports = Sticker; diff --git a/typings/index.d.ts b/typings/index.d.ts index c268e7ca1b17..ea596dc954bb 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -1770,7 +1770,7 @@ declare module 'discord.js' { public readonly createdTimestamp: number; public readonly createdAt: Date; public available: ?boolean; - public description: string; + public description: ?string; public format: StickerFormatType; public readonly guild: ?Guild; public guildID: ?Snowflake; @@ -1778,11 +1778,13 @@ declare module 'discord.js' { public name: string; public packID: ?Snowflake; public sortValue: ?number; - public tags: string[]; - public type: StickerType; + public tags: ?string[]; + public type: ?StickerType; public user: ?User; public readonly url: string; + public fetch(): Promise; public fetchPack(): Promise; + public fetchUser(): Promise; } export class StickerPack extends Base { From 16c21cbe05ec957be2e5c2f050e9e22cc5c76bf3 Mon Sep 17 00:00:00 2001 From: Advaith Date: Wed, 30 Jun 2021 07:49:19 -0700 Subject: [PATCH 17/40] Apply suggestions from code review Co-authored-by: Vlad Frangu --- src/errors/Messages.js | 2 +- src/structures/Sticker.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/errors/Messages.js b/src/errors/Messages.js index 292865a1014c..79fdb672d5b1 100644 --- a/src/errors/Messages.js +++ b/src/errors/Messages.js @@ -100,7 +100,7 @@ const Messages = { EMOJI_MANAGED: 'Emoji is managed and has no Author.', MISSING_MANAGE_EMOJIS_AND_STICKERS_PERMISSION: guild => `Client must have Manage Emojis and Stickers permission in guild ${guild} to see emoji authors.`, - NOT_GUILD_STICKER: 'Sticker is a standard (non-guild) sticker and has no User.', + NOT_GUILD_STICKER: 'Sticker is a standard (non-guild) sticker and has no author.', REACTION_RESOLVE_USER: "Couldn't resolve the user ID to remove from the reaction.", diff --git a/src/structures/Sticker.js b/src/structures/Sticker.js index 6c49684d85d1..a70740a86e50 100644 --- a/src/structures/Sticker.js +++ b/src/structures/Sticker.js @@ -134,7 +134,7 @@ class Sticker extends Base { /** * Fetches this sticker. - * @returns {Promise} + * @returns {Promise} */ async fetch() { const data = await this.client.api.stickers(this.id).get(); From c65a377a911aa0f8a42c6aeb7a7b0aff37c55449 Mon Sep 17 00:00:00 2001 From: Advaith Date: Thu, 1 Jul 2021 12:14:40 -0700 Subject: [PATCH 18/40] types: switch to old nullable syntax --- typings/index.d.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/typings/index.d.ts b/typings/index.d.ts index ea596dc954bb..82e23f2a0fc1 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -1769,22 +1769,22 @@ declare module 'discord.js' { constructor(client: Client, data: unknown); public readonly createdTimestamp: number; public readonly createdAt: Date; - public available: ?boolean; - public description: ?string; + public available: boolean | null; + public description: string | null; public format: StickerFormatType; - public readonly guild: ?Guild; - public guildID: ?Snowflake; + public readonly guild: Guild | null; + public guildID: Snowflake | null; public id: Snowflake; public name: string; - public packID: ?Snowflake; - public sortValue: ?number; - public tags: ?string[]; - public type: ?StickerType; - public user: ?User; + public packID: Snowflake | null; + public sortValue: number | null; + public tags: string[] | null; + public type: StickerType | null; + public user: User | null; public readonly url: string; - public fetch(): Promise; - public fetchPack(): Promise; - public fetchUser(): Promise; + public fetch(): Promise; + public fetchPack(): Promise; + public fetchUser(): Promise; } export class StickerPack extends Base { @@ -1792,8 +1792,8 @@ declare module 'discord.js' { public readonly createdTimestamp: number; public readonly createdAt: Date; public bannerID: Snowflake; - public readonly coverSticker: ?Sticker; - public coverStickerID: ?Snowflake; + public readonly coverSticker: Sticker | null; + public coverStickerID: Snowflake | null; public description: string; public id: Snowflake; public name: string; From 55abf978204db648f482903b1c32d96606333d89 Mon Sep 17 00:00:00 2001 From: Advaith Date: Thu, 1 Jul 2021 14:59:32 -0700 Subject: [PATCH 19/40] fix partial check --- src/structures/Sticker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/Sticker.js b/src/structures/Sticker.js index a70740a86e50..d302efe8efe5 100644 --- a/src/structures/Sticker.js +++ b/src/structures/Sticker.js @@ -111,7 +111,7 @@ class Sticker extends Base { * @readonly */ get partial() { - return 'username' in this; + return 'type' in this; } /** From 6c9f2be6354507b9cb794c5548875e18837e094e Mon Sep 17 00:00:00 2001 From: Advaith Date: Fri, 2 Jul 2021 22:02:20 -0700 Subject: [PATCH 20/40] Apply suggestions from code review Co-authored-by: BannerBomb --- src/structures/Sticker.js | 2 +- typings/index.d.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/structures/Sticker.js b/src/structures/Sticker.js index d302efe8efe5..8d7f0b0ee6a4 100644 --- a/src/structures/Sticker.js +++ b/src/structures/Sticker.js @@ -58,7 +58,7 @@ class Sticker extends Base { /** * An array of tags for the sticker - * @type {string[]} + * @type {?string[]} */ this.tags = sticker.tags?.split(', ') ?? null; diff --git a/typings/index.d.ts b/typings/index.d.ts index 82e23f2a0fc1..9f720edb4560 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -1785,6 +1785,7 @@ declare module 'discord.js' { public fetch(): Promise; public fetchPack(): Promise; public fetchUser(): Promise; + public readonly partial: boolean; } export class StickerPack extends Base { From a7616595fd6302ccc0074a44e5e7bf0f0510318a Mon Sep 17 00:00:00 2001 From: Advaith Date: Sat, 3 Jul 2021 01:42:48 -0700 Subject: [PATCH 21/40] fix: partial check but for real this time --- src/structures/Sticker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/Sticker.js b/src/structures/Sticker.js index 8d7f0b0ee6a4..365901b6973d 100644 --- a/src/structures/Sticker.js +++ b/src/structures/Sticker.js @@ -111,7 +111,7 @@ class Sticker extends Base { * @readonly */ get partial() { - return 'type' in this; + return !this.type; } /** From a08b4906a3ca161269e8fff293864e83bc0cdf0c Mon Sep 17 00:00:00 2001 From: Advaith Date: Sat, 3 Jul 2021 02:29:58 -0700 Subject: [PATCH 22/40] feat: sending stickers --- src/structures/MessagePayload.js | 1 + src/structures/Sticker.js | 7 +++++++ src/structures/interfaces/TextBasedChannel.js | 1 + typings/index.d.ts | 3 +++ 4 files changed, 12 insertions(+) diff --git a/src/structures/MessagePayload.js b/src/structures/MessagePayload.js index d53fb4f46dd1..df139d18a3bb 100644 --- a/src/structures/MessagePayload.js +++ b/src/structures/MessagePayload.js @@ -196,6 +196,7 @@ class MessagePayload { flags, message_reference, attachments: this.options.attachments, + sticker_ids: this.options.stickers?.map(sticker => sticker.id ?? sticker), }; return this; } diff --git a/src/structures/Sticker.js b/src/structures/Sticker.js index 365901b6973d..480cf8d7518c 100644 --- a/src/structures/Sticker.js +++ b/src/structures/Sticker.js @@ -19,6 +19,13 @@ class Sticker extends Base { this._patch(sticker); } + /** + * Data that resolves to give a Sticker object. This can be: + * * An Sticker object + * * A Snowflake + * @typedef {Sticker|Snowflake} StickerResolvable + */ + _patch(sticker) { /** * The ID of the sticker diff --git a/src/structures/interfaces/TextBasedChannel.js b/src/structures/interfaces/TextBasedChannel.js index bc68c2457115..6e61345a73a4 100644 --- a/src/structures/interfaces/TextBasedChannel.js +++ b/src/structures/interfaces/TextBasedChannel.js @@ -64,6 +64,7 @@ class TextBasedChannel { * @property {FileOptions[]|BufferResolvable[]|MessageAttachment[]} [files] Files to send with the message * @property {MessageActionRow[]|MessageActionRowOptions[]|MessageActionRowComponentResolvable[][]} [components] * Action rows containing interactive components for the message (buttons, select menus) + * @property {StickerResolvable[]} [stickers=[]] Stickers to send in the message */ /** diff --git a/typings/index.d.ts b/typings/index.d.ts index 4ca1a38c160a..cad251dba448 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -3932,6 +3932,7 @@ declare module 'discord.js' { allowedMentions?: MessageMentionOptions; files?: (FileOptions | BufferResolvable | Stream | MessageAttachment)[]; reply?: ReplyOptions; + stickers?: StickerResolvable[]; } type MessageReactionResolvable = @@ -4328,6 +4329,8 @@ declare module 'discord.js' { type StickerFormatType = keyof typeof StickerFormatTypes; + type StickerResolvable = Sticker | Snowflake; + type StickerType = keyof typeof StickerTypes; type SystemChannelFlagsString = From 594ba641d9b4f6ffdb3e37da6dad44af87d9c73c Mon Sep 17 00:00:00 2001 From: Advaith Date: Sun, 4 Jul 2021 05:33:10 -0700 Subject: [PATCH 23/40] feat: add GuildStickerManager and events --- src/client/actions/ActionsManager.js | 4 + src/client/actions/GuildStickerCreate.js | 20 +++ src/client/actions/GuildStickerDelete.js | 20 +++ src/client/actions/GuildStickerUpdate.js | 20 +++ src/client/actions/GuildStickersUpdate.js | 34 ++++ .../handlers/GUILD_STICKERS_UPDATE.js | 5 + src/index.js | 1 + src/managers/GuildStickerManager.js | 160 ++++++++++++++++++ src/rest/APIRequest.js | 12 +- src/structures/Guild.js | 15 ++ src/structures/Sticker.js | 70 +++++++- src/util/Constants.js | 5 + src/util/Intents.js | 4 +- typings/index.d.ts | 2 +- 14 files changed, 360 insertions(+), 12 deletions(-) create mode 100644 src/client/actions/GuildStickerCreate.js create mode 100644 src/client/actions/GuildStickerDelete.js create mode 100644 src/client/actions/GuildStickerUpdate.js create mode 100644 src/client/actions/GuildStickersUpdate.js create mode 100644 src/client/websocket/handlers/GUILD_STICKERS_UPDATE.js create mode 100644 src/managers/GuildStickerManager.js diff --git a/src/client/actions/ActionsManager.js b/src/client/actions/ActionsManager.js index 3851e9ff26c5..bc2190f9deed 100644 --- a/src/client/actions/ActionsManager.js +++ b/src/client/actions/ActionsManager.js @@ -47,6 +47,10 @@ class ActionsManager { this.register(require('./StageInstanceCreate')); this.register(require('./StageInstanceUpdate')); this.register(require('./StageInstanceDelete')); + this.register(require('./GuildStickerCreate')); + this.register(require('./GuildStickerDelete')); + this.register(require('./GuildStickerUpdate')); + this.register(require('./GuildStickersUpdate')); } register(Action) { diff --git a/src/client/actions/GuildStickerCreate.js b/src/client/actions/GuildStickerCreate.js new file mode 100644 index 000000000000..9de0d51336b7 --- /dev/null +++ b/src/client/actions/GuildStickerCreate.js @@ -0,0 +1,20 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +class GuildStickerCreateAction extends Action { + handle(guild, createdSticker) { + const already = guild.stickers.cache.has(createdSticker.id); + const sticker = guild.stickers.add(createdSticker); + /** + * Emitted whenever a custom sticker is created in a guild. + * @event Client#stickerCreate + * @param {Sticker} sticker The sticker that was created + */ + if (!already) this.client.emit(Events.GUILD_STICKER_CREATE, sticker); + return { sticker }; + } +} + +module.exports = GuildStickerCreateAction; diff --git a/src/client/actions/GuildStickerDelete.js b/src/client/actions/GuildStickerDelete.js new file mode 100644 index 000000000000..2d845eb8f8b0 --- /dev/null +++ b/src/client/actions/GuildStickerDelete.js @@ -0,0 +1,20 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +class GuildStickerDeleteAction extends Action { + handle(sticker) { + sticker.guild.stickers.cache.delete(sticker.id); + sticker.deleted = true; + /** + * Emitted whenever a custom sticker is deleted in a guild. + * @event Client#stickerDelete + * @param {Sticker} sticker The sticker that was deleted + */ + this.client.emit(Events.GUILD_STICKER_DELETE, sticker); + return { sticker }; + } +} + +module.exports = GuildStickerDeleteAction; diff --git a/src/client/actions/GuildStickerUpdate.js b/src/client/actions/GuildStickerUpdate.js new file mode 100644 index 000000000000..0c3edc76d833 --- /dev/null +++ b/src/client/actions/GuildStickerUpdate.js @@ -0,0 +1,20 @@ +'use strict'; + +const Action = require('./Action'); +const { Events } = require('../../util/Constants'); + +class GuildStickerUpdateAction extends Action { + handle(current, data) { + const old = current._update(data); + /** + * Emitted whenever a custom sticker is updated in a guild. + * @event Client#stickerUpdate + * @param {Sticker} oldSticker The old sticker + * @param {Sticker} newSticker The new sticker + */ + this.client.emit(Events.GUILD_STICKER_UPDATE, old, current); + return { sticker: current }; + } +} + +module.exports = GuildStickerUpdateAction; diff --git a/src/client/actions/GuildStickersUpdate.js b/src/client/actions/GuildStickersUpdate.js new file mode 100644 index 000000000000..ccf1d639c813 --- /dev/null +++ b/src/client/actions/GuildStickersUpdate.js @@ -0,0 +1,34 @@ +'use strict'; + +const Action = require('./Action'); + +class GuildStickersUpdateAction extends Action { + handle(data) { + const guild = this.client.guilds.cache.get(data.guild_id); + if (!guild?.stickers) return; + + const deletions = new Map(guild.stickers.cache); + + for (const sticker of data.stickers) { + // Determine type of sticker event + const cachedSticker = guild.stickers.cache.get(sticker.id); + if (cachedSticker) { + deletions.delete(sticker.id); + if (!cachedSticker.equals(sticker)) { + // Sticker updated + this.client.actions.GuildStickerUpdate.handle(cachedSticker, sticker); + } + } else { + // Sticker added + this.client.actions.GuildStickerCreate.handle(guild, sticker); + } + } + + for (const sticker of deletions.values()) { + // Sticker deleted + this.client.actions.GuildStickerDelete.handle(sticker); + } + } +} + +module.exports = GuildStickersUpdateAction; diff --git a/src/client/websocket/handlers/GUILD_STICKERS_UPDATE.js b/src/client/websocket/handlers/GUILD_STICKERS_UPDATE.js new file mode 100644 index 000000000000..e3aba61e54c8 --- /dev/null +++ b/src/client/websocket/handlers/GUILD_STICKERS_UPDATE.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = (client, packet) => { + client.actions.GuildStickersUpdate.handle(packet.d); +}; diff --git a/src/index.js b/src/index.js index 4734ea1b50ac..bb7e6cb28e5a 100644 --- a/src/index.js +++ b/src/index.js @@ -44,6 +44,7 @@ module.exports = { GuildMemberManager: require('./managers/GuildMemberManager'), GuildMemberRoleManager: require('./managers/GuildMemberRoleManager'), GuildManager: require('./managers/GuildManager'), + GuildStickerManager: require('./managers/GuildStickerManager'), ReactionManager: require('./managers/ReactionManager'), ReactionUserManager: require('./managers/ReactionUserManager'), MessageManager: require('./managers/MessageManager'), diff --git a/src/managers/GuildStickerManager.js b/src/managers/GuildStickerManager.js new file mode 100644 index 000000000000..60f6db100be4 --- /dev/null +++ b/src/managers/GuildStickerManager.js @@ -0,0 +1,160 @@ +'use strict'; + +const BaseManager = require('./BaseManager'); +const { TypeError } = require('../errors'); +const MessagePayload = require('../structures/MessagePayload'); +const Sticker = require('../structures/Sticker'); +const Collection = require('../util/Collection'); + +/** + * Manages API methods for Guild Stickers and stores their cache. + * @extends {BaseManager} + */ +class GuildStickerManager extends BaseManager { + constructor(guild, iterable) { + super(guild.client, iterable, Sticker); + /** + * The guild this manager belongs to + * @type {Guild} + */ + this.guild = guild; + } + + /** + * The cache of Guild Stickers + * @type {Collection} + * @name GuildStickerManager#cache + */ + + add(data, cache) { + return super.add(data, cache, { extras: [this.guild] }); + } + + /** + * Creates a new custom sticker in the guild. + * @param {BufferResolvable|Stream|FileOptions|MessageAttachment} file The file for the sticker + * @param {string} name The name for the sticker + * @param {string} tags The Discord name of a unicode emoji representing the sticker's expression + * @param {Object} [options] Options + * @param {string} [options.description] The description for the sticker + * @param {string} [options.reason] Reason for creating the sticker + * @returns {Promise} The created sticker + * @example + * // Create a new sticker from a url + * guild.stickers.create('https://i.imgur.com/w3duR07.png', 'rip') + * .then(sticker => console.log(`Created new sticker with name ${sticker.name}!`)) + * .catch(console.error); + * @example + * // Create a new sticker from a file on your computer + * guild.stickers.create('./memes/banana.png', 'banana') + * .then(sticker => console.log(`Created new sticker with name ${sticker.name}!`)) + * .catch(console.error); + */ + async create(file, name, tags, { description, reason } = {}) { + file = { ...(await MessagePayload.resolveFile(file)), key: 'file' }; + if (!file) throw new TypeError('REQ_RESOURCE_TYPE'); + + const data = { name, tags, description: description ?? '' }; + + return this.client.api + .guilds(this.guild.id) + .stickers.post({ data, files: [file], reason, dontUsePayloadJSON: true }) + .then(sticker => this.client.actions.GuildStickerCreate.handle(this.guild, sticker).sticker); + } + + /** + * Data that resolves to give a Sticker object. This can be: + * * An Sticker object + * * A Snowflake + * @typedef {Sticker|Snowflake} StickerResolvable + */ + + /** + * Resolves an StickerResolvable to a Sticker object. + * @method resolve + * @memberof GuildStickerManager + * @instance + * @param {StickerResolvable} sticker The Sticker resolvable to identify + * @returns {?Sticker} + */ + + /** + * Resolves an StickerResolvable to an Sticker ID string. + * @method resolveID + * @memberof GuildStickerManager + * @instance + * @param {StickerResolvable} sticker The Sticker resolvable to identify + * @returns {?Snowflake} + */ + + /** + * Edits a sticker. + * @param {StickerResolvable} sticker The sticker to edit + * @param {GuildStickerEditData} [data] The new data for the sticker + * @param {string} [reason] Reason for editing this sticker + * @returns {Promise} + */ + async edit(sticker, data, reason) { + const stickerID = this.resolveID(sticker); + if (!stickerID) throw new TypeError('INVALID_TYPE', 'sticker', 'StickerResolvable'); + + const d = await this.client.api.guilds(this.guild.id).stickers(stickerID).patch({ + data, + reason, + }); + + const existing = this.cache.get(stickerID); + if (existing) { + const clone = existing._clone(); + clone._patch(d); + return clone; + } + return this.add(d); + } + + /** + * Deletes a sticker. + * @param {StickerResolvable} sticker The sticker to delete + * @param {string} [reason] Reason for deleting this sticker + * @returns {Promise} + */ + async delete(sticker, reason) { + sticker = this.resolveID(sticker); + if (!sticker) throw new TypeError('INVALID_TYPE', 'sticker', 'StickerResolvable'); + + await this.client.api.guilds(this.guild.id).stickers(sticker).delete({ reason }); + } + + /** + * Obtains one or more stickers from Discord, or the sticker cache if they're already available. + * @param {Snowflake} [id] ID of the sticker + * @param {BaseFetchOptions} [options] Additional options for this fetch + * @returns {Promise>} + * @example + * // Fetch all stickers from the guild + * message.guild.stickers.fetch() + * .then(stickers => console.log(`There are ${stickers.size} stickers.`)) + * .catch(console.error); + * @example + * // Fetch a single sticker + * message.guild.stickers.fetch('222078108977594368') + * .then(sticker => console.log(`The sticker name is: ${sticker.name}`)) + * .catch(console.error); + */ + async fetch(id, { cache = true, force = false } = {}) { + if (id) { + if (!force) { + const existing = this.cache.get(id); + if (existing) return existing; + } + const sticker = await this.client.api.guilds(this.guild.id).stickers(id).get(); + return this.add(sticker, cache); + } + + const data = await this.client.api.guilds(this.guild.id).stickers.get(); + const stickers = new Collection(data.map(sticker => [sticker.id, this.add(sticker, cache)])); + return stickers; + } +} + +module.exports = GuildStickerManager; diff --git a/src/rest/APIRequest.js b/src/rest/APIRequest.js index c4487bf2a708..3f160543a647 100644 --- a/src/rest/APIRequest.js +++ b/src/rest/APIRequest.js @@ -43,8 +43,16 @@ class APIRequest { let body; if (this.options.files && this.options.files.length) { body = new FormData(); - for (const file of this.options.files) if (file && file.file) body.append(file.name, file.file, file.name); - if (typeof this.options.data !== 'undefined') body.append('payload_json', JSON.stringify(this.options.data)); + for (const file of this.options.files) { + if (file && file.file) body.append(file.key ?? file.name, file.file, file.name); + } + if (typeof this.options.data !== 'undefined') { + if (this.options.dontUsePayloadJSON) { + for (const key in this.options.data) body.append(key, this.options.data[key]); + } else { + body.append('payload_json', JSON.stringify(this.options.data)); + } + } headers = Object.assign(headers, body.getHeaders()); // eslint-disable-next-line eqeqeq } else if (this.options.data != null) { diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 0f6b60d654e7..4c2659e16ce3 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -14,6 +14,7 @@ const GuildBanManager = require('../managers/GuildBanManager'); const GuildChannelManager = require('../managers/GuildChannelManager'); const GuildEmojiManager = require('../managers/GuildEmojiManager'); const GuildMemberManager = require('../managers/GuildMemberManager'); +const GuildStickerManager = require('../managers/GuildStickerManager'); const PresenceManager = require('../managers/PresenceManager'); const RoleManager = require('../managers/RoleManager'); const StageInstanceManager = require('../managers/StageInstanceManager'); @@ -398,6 +399,20 @@ class Guild extends AnonymousGuild { emojis: data.emojis, }); } + + if (!this.stickers) { + /** + * A manager of the stickers belonging to this guild + * @type {GuildStickerManager} + */ + this.stickers = new GuildStickerManager(this); + if (data.stickers) for (const sticker of data.stickers) this.stickers.add(sticker); + } else if (data.stickers) { + this.client.actions.GuildStickersUpdate.handle({ + guild_id: this.id, + stickers: data.stickers, + }); + } } /** diff --git a/src/structures/Sticker.js b/src/structures/Sticker.js index 480cf8d7518c..e464c7792d72 100644 --- a/src/structures/Sticker.js +++ b/src/structures/Sticker.js @@ -19,13 +19,6 @@ class Sticker extends Base { this._patch(sticker); } - /** - * Data that resolves to give a Sticker object. This can be: - * * An Sticker object - * * A Snowflake - * @typedef {Sticker|Snowflake} StickerResolvable - */ - _patch(sticker) { /** * The ID of the sticker @@ -169,6 +162,69 @@ class Sticker extends Base { this._patch(data); return this.user; } + + /** + * Data for editing a sticker. + * @typedef {Object} GuildStickerEditData + * @property {string} [name] The name of the sticker + * @property {string} [description] The description of the sticker + * @property {string} [tags] The Discord name of a unicode emoji representing the sticker's expression + */ + + /** + * Edits the sticker. + * @param {GuildStickerEditData} [data] The new data for the sticker + * @param {string} [reason] Reason for editing this sticker + * @returns {Promise} + * @example + * // Update the name of a sticker + * sticker.edit({ name: 'new name' }) + * .then(s => console.log(`Updated the name of the sticker to ${s.name}`)) + * .catch(console.error); + */ + edit(data, reason) { + return this.guild.stickers.edit(this, data, reason); + } + + /** + * Deletes the sticker. + * @returns {Promise} + * @param {string} [reason] Reason for deleting this sticker + * @example + * // Delete a message + * sticker.delete() + * .then(s => console.log(`Deleted sticker ${s.name}`)) + * .catch(console.error); + */ + async delete(reason) { + await this.guild.stickers.delete(this, reason); + return this; + } + + /** + * Whether this sticker is the same as another one. + * @param {Sticker|APISticker} other The sticker to compare it to + * @returns {boolean} Whether the sticker is equal to the given sticker or not + */ + equals(other) { + if (other instanceof Sticker) { + return ( + other.id === this.id && + other.description === this.description && + other.type === this.type && + other.format === this.format && + other.name === this.name && + other.packID === this.packID && + other.tags.length === this.tags.length && + other.tags.every(tag => this.tags.includes(tag)) && + other.available === this.available && + other.guildID === this.guildID && + other.sortValue === this.sortValue + ); + } else { + return other.id === this.id && other.description === this.description && other.name === this.name; + } + } } module.exports = Sticker; diff --git a/src/util/Constants.js b/src/util/Constants.js index ae26472f8bdd..620a2b76ab83 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -293,6 +293,9 @@ exports.Events = { STAGE_INSTANCE_CREATE: 'stageInstanceCreate', STAGE_INSTANCE_UPDATE: 'stageInstanceUpdate', STAGE_INSTANCE_DELETE: 'stageInstanceDelete', + GUILD_STICKER_CREATE: 'stickerCreate', + GUILD_STICKER_DELETE: 'stickerDelete', + GUILD_STICKER_UPDATE: 'stickerUpdate', }; exports.ShardEvents = { @@ -368,6 +371,7 @@ exports.PartialTypes = keyMirror(['USER', 'CHANNEL', 'GUILD_MEMBER', 'MESSAGE', * * STAGE_INSTANCE_CREATE * * STAGE_INSTANCE_UPDATE * * STAGE_INSTANCE_DELETE + * * GUILD_STICKERS_UPDATE * @typedef {string} WSEventType */ exports.WSEvents = keyMirror([ @@ -420,6 +424,7 @@ exports.WSEvents = keyMirror([ 'STAGE_INSTANCE_CREATE', 'STAGE_INSTANCE_UPDATE', 'STAGE_INSTANCE_DELETE', + 'GUILD_STICKERS_UPDATE', ]); /** diff --git a/src/util/Intents.js b/src/util/Intents.js index e627c8bcc170..47a48b1033c5 100644 --- a/src/util/Intents.js +++ b/src/util/Intents.js @@ -28,7 +28,7 @@ class Intents extends BitField {} * * `GUILDS` * * `GUILD_MEMBERS` * * `GUILD_BANS` - * * `GUILD_EMOJIS` + * * `GUILD_EMOJIS_AND_STICKERS` * * `GUILD_INTEGRATIONS` * * `GUILD_WEBHOOKS` * * `GUILD_INVITES` @@ -47,7 +47,7 @@ Intents.FLAGS = { GUILDS: 1 << 0, GUILD_MEMBERS: 1 << 1, GUILD_BANS: 1 << 2, - GUILD_EMOJIS: 1 << 3, + GUILD_EMOJIS_AND_STICKERS: 1 << 3, GUILD_INTEGRATIONS: 1 << 4, GUILD_WEBHOOKS: 1 << 5, GUILD_INVITES: 1 << 6, diff --git a/typings/index.d.ts b/typings/index.d.ts index cad251dba448..313afe52dc28 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -3713,7 +3713,7 @@ declare module 'discord.js' { | 'GUILDS' | 'GUILD_MEMBERS' | 'GUILD_BANS' - | 'GUILD_EMOJIS' + | 'GUILD_EMOJIS_AND_STICKERS' | 'GUILD_INTEGRATIONS' | 'GUILD_WEBHOOKS' | 'GUILD_INVITES' From ddc9b5459235be10e1235a08f7052aee349cdae3 Mon Sep 17 00:00:00 2001 From: Advaith Date: Sun, 4 Jul 2021 17:05:34 -0700 Subject: [PATCH 24/40] Apply suggestions from code review Co-authored-by: Noel --- src/client/Client.js | 2 +- src/managers/GuildStickerManager.js | 15 ++++++++------- src/structures/Sticker.js | 20 ++++++++++---------- src/structures/StickerPack.js | 18 +++++++++--------- src/util/Constants.js | 8 ++++---- typings/index.d.ts | 14 +++++++------- 6 files changed, 39 insertions(+), 38 deletions(-) diff --git a/src/client/Client.js b/src/client/Client.js index f815bbe79dce..a8c42a495cdd 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -307,7 +307,7 @@ class Client extends BaseClient { /** * Obtains a sticker from Discord. - * @param {Snowflake} id The sticker's ID + * @param {Snowflake} id The sticker's id * @returns {Promise} * @example * client.fetchSticker('id') diff --git a/src/managers/GuildStickerManager.js b/src/managers/GuildStickerManager.js index 60f6db100be4..d6f5579bbd7e 100644 --- a/src/managers/GuildStickerManager.js +++ b/src/managers/GuildStickerManager.js @@ -13,6 +13,7 @@ const Collection = require('../util/Collection'); class GuildStickerManager extends BaseManager { constructor(guild, iterable) { super(guild.client, iterable, Sticker); + /** * The guild this manager belongs to * @type {Guild} @@ -79,8 +80,8 @@ class GuildStickerManager extends BaseManager { */ /** - * Resolves an StickerResolvable to an Sticker ID string. - * @method resolveID + * Resolves an StickerResolvable to an Sticker id string. + * @method resolveId * @memberof GuildStickerManager * @instance * @param {StickerResolvable} sticker The Sticker resolvable to identify @@ -95,15 +96,15 @@ class GuildStickerManager extends BaseManager { * @returns {Promise} */ async edit(sticker, data, reason) { - const stickerID = this.resolveID(sticker); + const stickerId = this.resolveId(sticker); if (!stickerID) throw new TypeError('INVALID_TYPE', 'sticker', 'StickerResolvable'); - const d = await this.client.api.guilds(this.guild.id).stickers(stickerID).patch({ + const d = await this.client.api.guilds(this.guild.id).stickers(stickerId).patch({ data, reason, }); - const existing = this.cache.get(stickerID); + const existing = this.cache.get(stickerId); if (existing) { const clone = existing._clone(); clone._patch(d); @@ -119,7 +120,7 @@ class GuildStickerManager extends BaseManager { * @returns {Promise} */ async delete(sticker, reason) { - sticker = this.resolveID(sticker); + sticker = this.resolveId(sticker); if (!sticker) throw new TypeError('INVALID_TYPE', 'sticker', 'StickerResolvable'); await this.client.api.guilds(this.guild.id).stickers(sticker).delete({ reason }); @@ -127,7 +128,7 @@ class GuildStickerManager extends BaseManager { /** * Obtains one or more stickers from Discord, or the sticker cache if they're already available. - * @param {Snowflake} [id] ID of the sticker + * @param {Snowflake} [id] The Sticker's id * @param {BaseFetchOptions} [options] Additional options for this fetch * @returns {Promise>} * @example diff --git a/src/structures/Sticker.js b/src/structures/Sticker.js index e464c7792d72..aa7934cd84ae 100644 --- a/src/structures/Sticker.js +++ b/src/structures/Sticker.js @@ -21,7 +21,7 @@ class Sticker extends Base { _patch(sticker) { /** - * The ID of the sticker + * The Sticker's id * @type {Snowflake} */ this.id = sticker.id; @@ -51,10 +51,10 @@ class Sticker extends Base { this.name = sticker.name; /** - * The ID of the pack the sticker is from, for standard stickers + * The id of the pack the sticker is from, for standard stickers * @type {?Snowflake} */ - this.packID = sticker.pack_id ?? null; + this.packId = sticker.pack_id ?? null; /** * An array of tags for the sticker @@ -69,10 +69,10 @@ class Sticker extends Base { this.available = sticker.available ?? null; /** - * The ID of the guild that owns this sticker + * The id of the guild that owns this sticker * @type {?Snowflake} */ - this.guildID = sticker.guild_id ?? null; + this.guildId = sticker.guild_id ?? null; /** * The user that uploaded the guild sticker @@ -120,7 +120,7 @@ class Sticker extends Base { * @readonly */ get guild() { - return this.client.guilds.resolve(this.guildID); + return this.client.guilds.resolve(this.guildId); } /** @@ -147,7 +147,7 @@ class Sticker extends Base { * @returns {Promise} */ async fetchPack() { - return (this.packID && (await this.client.fetchPremiumStickerPacks()).get(this.packID)) ?? null; + return (this.packId && (await this.client.fetchPremiumStickerPacks()).get(this.packId)) ?? null; } /** @@ -158,7 +158,7 @@ class Sticker extends Base { if (this.partial) await this.fetch(); if (!this.guildID) throw new Error('NOT_GUILD_STICKER'); - const data = await this.client.api.guilds(this.guildID).stickers(this.id).get(); + const data = await this.client.api.guilds(this.guildId).stickers(this.id).get(); this._patch(data); return this.user; } @@ -214,11 +214,11 @@ class Sticker extends Base { other.type === this.type && other.format === this.format && other.name === this.name && - other.packID === this.packID && + other.packId === this.packId && other.tags.length === this.tags.length && other.tags.every(tag => this.tags.includes(tag)) && other.available === this.available && - other.guildID === this.guildID && + other.guildId === this.guildId && other.sortValue === this.sortValue ); } else { diff --git a/src/structures/StickerPack.js b/src/structures/StickerPack.js index 7378643bf997..ad53c2ff779c 100644 --- a/src/structures/StickerPack.js +++ b/src/structures/StickerPack.js @@ -17,7 +17,7 @@ class StickerPack extends Base { constructor(client, pack) { super(client); /** - * The ID of the sticker pack + * The Sticker pack's id * @type {Snowflake} */ this.id = pack.id; @@ -35,16 +35,16 @@ class StickerPack extends Base { this.name = pack.name; /** - * The ID of the pack's SKU + * The id of the pack's SKU * @type {Snowflake} */ - this.skuID = pack.sku_id; + this.skuId = pack.sku_id; /** - * The ID of a sticker in the pack which is shown as the pack's icon + * The id of a sticker in the pack which is shown as the pack's icon * @type {?Snowflake} */ - this.coverStickerID = pack.cover_sticker_id ?? null; + this.coverStickerId = pack.cover_sticker_id ?? null; /** * The description of the sticker pack @@ -53,10 +53,10 @@ class StickerPack extends Base { this.description = pack.description; /** - * The ID of the sticker pack's banner image + * The id of the sticker pack's banner image * @type {Snowflake} */ - this.bannerID = pack.banner_asset_id; + this.bannerId = pack.banner_asset_id; } /** @@ -83,7 +83,7 @@ class StickerPack extends Base { * @readonly */ get coverSticker() { - return this.coverStickerID && this.stickers.get(this.coverStickerID); + return this.coverStickerId && this.stickers.get(this.coverStickerId); } /** @@ -92,7 +92,7 @@ class StickerPack extends Base { * @returns {string} */ bannerURL({ format, size } = {}) { - return this.client.rest.cdn.StickerPackBanner(this.bannerID, format, size); + return this.client.rest.cdn.StickerPackBanner(this.bannerId, format, size); } } diff --git a/src/util/Constants.js b/src/util/Constants.js index 620a2b76ab83..817a695318ff 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -170,8 +170,8 @@ exports.Endpoints = { makeImageUrl(`${root}/app-icons/${clientID}/${hash}`, { size, format }), AppAsset: (clientID, hash, { format = 'webp', size } = {}) => makeImageUrl(`${root}/app-assets/${clientID}/${hash}`, { size, format }), - StickerPackBanner: (bannerID, format = 'webp', size) => - makeImageUrl(`${root}/app-assets/710982414301790216/store/${bannerID}`, { size, format }), + StickerPackBanner: (bannerId, format = 'webp', size) => + makeImageUrl(`${root}/app-assets/710982414301790216/store/${bannerId}`, { size, format }), GDMIcon: (channelID, hash, format = 'webp', size) => makeImageUrl(`${root}/channel-icons/${channelID}/${hash}`, { size, format }), Splash: (guildID, hash, format = 'webp', size) => @@ -180,8 +180,8 @@ exports.Endpoints = { makeImageUrl(`${root}/discovery-splashes/${guildID}/${hash}`, { size, format }), TeamIcon: (teamID, hash, { format = 'webp', size } = {}) => makeImageUrl(`${root}/team-icons/${teamID}/${hash}`, { size, format }), - Sticker: (stickerID, stickerFormat) => - `${root}/stickers/${stickerID}.${stickerFormat === 'LOTTIE' ? 'json' : 'png'}`, + Sticker: (stickerId, stickerFormat) => + `${root}/stickers/${stickerId}.${stickerFormat === 'LOTTIE' ? 'json' : 'png'}`, }; }, invite: (root, code) => `${root}/${code}`, diff --git a/typings/index.d.ts b/typings/index.d.ts index 313afe52dc28..25a4aea6d972 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -637,7 +637,7 @@ declare module 'discord.js' { ) => string; AppIcon: (userID: Snowflake | number, hash: string, format: AllowedImageFormat, size: number) => string; AppAsset: (userID: Snowflake | number, hash: string, format: AllowedImageFormat, size: number) => string; - StickerPackBanner: (bannerID: Snowflake, format: AllowedImageFormat, size: number) => string; + StickerPackBanner: (bannerId: Snowflake, format: AllowedImageFormat, size: number) => string; GDMIcon: (userID: Snowflake | number, hash: string, format: AllowedImageFormat, size: number) => string; Splash: (guildID: Snowflake | number, hash: string, format: AllowedImageFormat, size: number) => string; DiscoverySplash: ( @@ -647,7 +647,7 @@ declare module 'discord.js' { size: number, ) => string; TeamIcon: (teamID: Snowflake | number, hash: string, format: AllowedImageFormat, size: number) => string; - Sticker: (stickerID: Snowflake, stickerFormat: StickerFormatType) => string; + Sticker: (stickerId: Snowflake, stickerFormat: StickerFormatType) => string; }; }; WSCodes: { @@ -1916,10 +1916,10 @@ declare module 'discord.js' { public description: string | null; public format: StickerFormatType; public readonly guild: Guild | null; - public guildID: Snowflake | null; + public guildId: Snowflake | null; public id: Snowflake; public name: string; - public packID: Snowflake | null; + public packId: Snowflake | null; public sortValue: number | null; public tags: string[] | null; public type: StickerType | null; @@ -1935,13 +1935,13 @@ declare module 'discord.js' { constructor(client: Client, data: unknown); public readonly createdTimestamp: number; public readonly createdAt: Date; - public bannerID: Snowflake; + public bannerId: Snowflake; public readonly coverSticker: Sticker | null; - public coverStickerID: Snowflake | null; + public coverStickerId: Snowflake | null; public description: string; public id: Snowflake; public name: string; - public skuID: Snowflake; + public skuId: Snowflake; public stickers: Collection; public bannerURL(options?: StaticImageURLOptions): string; } From a7bb38eb6ae2e57e8afba883a3dde8d5b07502d9 Mon Sep 17 00:00:00 2001 From: Advaith Date: Sun, 4 Jul 2021 17:18:42 -0700 Subject: [PATCH 25/40] fix(GuildStickerManager): crawl missed an `ID` --- src/managers/GuildStickerManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/managers/GuildStickerManager.js b/src/managers/GuildStickerManager.js index d6f5579bbd7e..5e0682bbaa8a 100644 --- a/src/managers/GuildStickerManager.js +++ b/src/managers/GuildStickerManager.js @@ -97,7 +97,7 @@ class GuildStickerManager extends BaseManager { */ async edit(sticker, data, reason) { const stickerId = this.resolveId(sticker); - if (!stickerID) throw new TypeError('INVALID_TYPE', 'sticker', 'StickerResolvable'); + if (!stickerId) throw new TypeError('INVALID_TYPE', 'sticker', 'StickerResolvable'); const d = await this.client.api.guilds(this.guild.id).stickers(stickerId).patch({ data, From dbce4776675d7d216b0d2f1f679aab8e648e47d9 Mon Sep 17 00:00:00 2001 From: Advaith Date: Sun, 4 Jul 2021 17:56:41 -0700 Subject: [PATCH 26/40] fix(Sticker): check tags in API equals --- src/structures/Sticker.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/structures/Sticker.js b/src/structures/Sticker.js index db0b10b1bf12..ed45f8083071 100644 --- a/src/structures/Sticker.js +++ b/src/structures/Sticker.js @@ -222,7 +222,12 @@ class Sticker extends Base { other.sortValue === this.sortValue ); } else { - return other.id === this.id && other.description === this.description && other.name === this.name; + return ( + other.id === this.id && + other.description === this.description && + other.name === this.name && + other.tags === this.tags.join(', ') + ); } } } From 61fc74e4214a03b085821516c48ebbca542e3094 Mon Sep 17 00:00:00 2001 From: Advaith Date: Sun, 4 Jul 2021 18:08:02 -0700 Subject: [PATCH 27/40] types: add typings for GSM and events --- src/managers/GuildStickerManager.js | 2 +- src/structures/Sticker.js | 2 +- typings/index.d.ts | 39 ++++++++++++++++++++++++++++- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/managers/GuildStickerManager.js b/src/managers/GuildStickerManager.js index 5e0682bbaa8a..356c3e903e8f 100644 --- a/src/managers/GuildStickerManager.js +++ b/src/managers/GuildStickerManager.js @@ -37,7 +37,7 @@ class GuildStickerManager extends BaseManager { * @param {string} name The name for the sticker * @param {string} tags The Discord name of a unicode emoji representing the sticker's expression * @param {Object} [options] Options - * @param {string} [options.description] The description for the sticker + * @param {?string} [options.description] The description for the sticker * @param {string} [options.reason] Reason for creating the sticker * @returns {Promise} The created sticker * @example diff --git a/src/structures/Sticker.js b/src/structures/Sticker.js index ed45f8083071..1f81736383e5 100644 --- a/src/structures/Sticker.js +++ b/src/structures/Sticker.js @@ -167,7 +167,7 @@ class Sticker extends Base { * Data for editing a sticker. * @typedef {Object} GuildStickerEditData * @property {string} [name] The name of the sticker - * @property {string} [description] The description of the sticker + * @property {?string} [description] The description of the sticker * @property {string} [tags] The Discord name of a unicode emoji representing the sticker's expression */ diff --git a/typings/index.d.ts b/typings/index.d.ts index a79de05bd8da..29ac9dba7fb1 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -712,6 +712,9 @@ declare module 'discord.js' { STAGE_INSTANCE_CREATE: 'stageInstanceCreate'; STAGE_INSTANCE_UPDATE: 'stageInstanceUpdate'; STAGE_INSTANCE_DELETE: 'stageInstanceDelete'; + GUILD_STICKER_CREATE: 'stickerCreate'; + GUILD_STICKER_DELETE: 'stickerDelete'; + GUILD_STICKER_UPDATE: 'stickerUpdate'; }; ShardEvents: { CLOSE: 'close'; @@ -899,6 +902,7 @@ declare module 'discord.js' { public readonly shard: WebSocketShard; public shardId: number; public stageInstances: StageInstanceManager; + public stickers: GuildStickerManager; public readonly systemChannel: TextChannel | null; public systemChannelFlags: Readonly; public systemChannelId: Snowflake | null; @@ -1895,6 +1899,9 @@ declare module 'discord.js' { public fetch(): Promise; public fetchPack(): Promise; public fetchUser(): Promise; + public edit(data?: GuildStickerEditData, reason?: string): Promise; + public delete(reason?: string): Promise; + public equals(other: Sticker | unknown): boolean; public readonly partial: boolean; } @@ -2590,6 +2597,21 @@ declare module 'discord.js' { public delete(invite: InviteResolvable, reason?: string): Promise; } + export class GuildStickerManager extends CachedManager { + constructor(guild: Guild, iterable?: Iterable); + public guild: Guild; + public create( + file: BufferResolvable | Stream | FileOptions | MessageAttachment, + name: string, + tags: string, + options?: GuildStickerCreateOptions, + ): Promise; + public edit(sticker: StickerResolvable, data?: GuildStickerEditData, reason?: string): Promise; + public delete(sticker: StickerResolvable, reason?: string): Promise; + public fetch(id: Snowflake, options?: BaseFetchOptions): Promise; + public fetch(id?: Snowflake, options?: BaseFetchOptions): Promise>; + } + export class GuildMemberRoleManager extends DataManager { constructor(member: GuildMember); public readonly hoist: Role | null; @@ -3176,6 +3198,9 @@ declare module 'discord.js' { stageInstanceCreate: [stageInstance: StageInstance]; stageInstanceUpdate: [oldStageInstance: StageInstance | null, newStageInstance: StageInstance]; stageInstanceDelete: [stageInstance: StageInstance]; + stickerCreate: [sticker: Sticker]; + stickerDelete: [sticker: Sticker]; + stickerUpdate: [oldSticker: Sticker, newSticker: Sticker]; } interface ClientOptions { @@ -3593,6 +3618,17 @@ declare module 'discord.js' { roles?: Collection | RoleResolvable[]; } + interface GuildStickerCreateOptions { + description?: string | null; + reason?: string; + } + + interface GuildStickerEditData { + name?: string; + description?: string | null; + tags?: string; + } + type GuildFeatures = | 'ANIMATED_ICON' | 'BANNER' @@ -4521,7 +4557,8 @@ declare module 'discord.js' { | 'INTERACTION_CREATE' | 'STAGE_INSTANCE_CREATE' | 'STAGE_INSTANCE_UPDATE' - | 'STAGE_INSTANCE_DELETE'; + | 'STAGE_INSTANCE_DELETE' + | 'GUILD_STICKERS_UPDATE'; type Serialized = T extends symbol | bigint | (() => unknown) ? never From c202904338c0a64b351b7ca2870b71b8370499b4 Mon Sep 17 00:00:00 2001 From: Advaith Date: Sun, 4 Jul 2021 18:22:14 -0700 Subject: [PATCH 28/40] fix(GuildStickerManager): extend CachedManager --- src/managers/GuildStickerManager.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/managers/GuildStickerManager.js b/src/managers/GuildStickerManager.js index 356c3e903e8f..a07c91ad8715 100644 --- a/src/managers/GuildStickerManager.js +++ b/src/managers/GuildStickerManager.js @@ -1,6 +1,6 @@ 'use strict'; -const BaseManager = require('./BaseManager'); +const CachedManager = require('./CachedManager'); const { TypeError } = require('../errors'); const MessagePayload = require('../structures/MessagePayload'); const Sticker = require('../structures/Sticker'); @@ -8,11 +8,11 @@ const Collection = require('../util/Collection'); /** * Manages API methods for Guild Stickers and stores their cache. - * @extends {BaseManager} + * @extends {CachedManager} */ -class GuildStickerManager extends BaseManager { +class GuildStickerManager extends CachedManager { constructor(guild, iterable) { - super(guild.client, iterable, Sticker); + super(guild.client, Sticker, iterable); /** * The guild this manager belongs to From ced9bf3cdbb41d43e99f01b39903ca05e716a3fb Mon Sep 17 00:00:00 2001 From: Advaith Date: Tue, 6 Jul 2021 23:42:21 -0700 Subject: [PATCH 29/40] feat(ApiErrors): add sticker error codes --- src/util/Constants.js | 6 +++++- typings/index.d.ts | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/util/Constants.js b/src/util/Constants.js index 2020fe81781e..ac129db64bd2 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -732,6 +732,8 @@ exports.APIErrors = { INVALID_FORM_BODY: 50035, INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT: 50036, INVALID_API_VERSION: 50041, + FILE_UPLOADED_EXCEEDS_MAXIMUM_SIZE: 50045, + INVALID_FILE_UPLOADED: 50046, CANNOT_SELF_REDEEM_GIFT: 50054, PAYMENT_SOURCE_REQUIRED: 50070, CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL: 50074, @@ -747,7 +749,9 @@ exports.APIErrors = { MESSAGE_ALREADY_HAS_THREAD: 160004, THREAD_LOCKED: 160005, MAXIMUM_ACTIVE_THREADS: 160006, - MAXIMUM_ACTIVE_ANNOUCEMENT_THREAD: 160007, + MAXIMUM_ACTIVE_ANNOUNCEMENT_THREADS: 160007, + INVALID_JSON_FOR_UPLOADED_LOTTIE_FILE: 170001, + LOTTIE_ANIMATION_MAXIMUM_DIMENSIONS_EXCEEDED: 170005, }; /** diff --git a/typings/index.d.ts b/typings/index.d.ts index 1dcb949483b6..aad04886ded4 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -2774,6 +2774,8 @@ export interface APIErrors { INVALID_FORM_BODY: 50035; INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT: 50036; INVALID_API_VERSION: 50041; + FILE_UPLOADED_EXCEEDS_MAXIMUM_SIZE: 50045; + INVALID_FILE_UPLOADED: 50046; CANNOT_SELF_REDEEM_GIFT: 50054; PAYMENT_SOURCE_REQUIRED: 50070; CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL: 50074; @@ -2789,7 +2791,9 @@ export interface APIErrors { MESSAGE_ALREADY_HAS_THREAD: 160004; THREAD_LOCKED: 160005; MAXIMUM_ACTIVE_THREADS: 160006; - MAXIMUM_ACTIVE_ANNOUCEMENT_THREAD: 160007; + MAXIMUM_ACTIVE_ANNOUNCEMENT_THREADS: 160007; + INVALID_JSON_FOR_UPLOADED_LOTTIE_FILE: 170001; + LOTTIE_ANIMATION_MAXIMUM_DIMENSIONS_EXCEEDED: 170005; } export interface ApplicationAsset { From 41fb4bb8a997c857145c8529dba8813edcb90ae3 Mon Sep 17 00:00:00 2001 From: Advaith Date: Wed, 7 Jul 2021 02:19:51 -0700 Subject: [PATCH 30/40] feat(GuildAuditLogs): add sticker audit logs --- src/structures/GuildAuditLogs.js | 29 ++++++++++++++++++++++++++++- typings/index.d.ts | 5 +++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/structures/GuildAuditLogs.js b/src/structures/GuildAuditLogs.js index 6680af2ce488..3de53bca8e41 100644 --- a/src/structures/GuildAuditLogs.js +++ b/src/structures/GuildAuditLogs.js @@ -2,6 +2,7 @@ const Integration = require('./Integration'); const StageInstance = require('./StageInstance'); +const Sticker = require('./Sticker'); const Webhook = require('./Webhook'); const Collection = require('../util/Collection'); const { OverwriteTypes, PartialTypes } = require('../util/Constants'); @@ -21,6 +22,7 @@ const Util = require('../util/Util'); * * MESSAGE * * INTEGRATION * * STAGE_INSTANCE + * * STICKER * @typedef {string} AuditLogTargetType */ @@ -41,6 +43,7 @@ const Targets = { MESSAGE: 'MESSAGE', INTEGRATION: 'INTEGRATION', STAGE_INSTANCE: 'STAGE_INSTANCE', + STICKER: 'STICKER', UNKNOWN: 'UNKNOWN', }; @@ -85,6 +88,9 @@ const Targets = { * * STAGE_INSTANCE_CREATE: 83 * * STAGE_INSTANCE_UPDATE: 84 * * STAGE_INSTANCE_DELETE: 85 + * * STICKER_CREATE: 90 + * * STICKER_UPDATE: 91 + * * STICKER_DELETE: 92 * @typedef {?(number|string)} AuditLogAction */ @@ -133,6 +139,9 @@ const Actions = { STAGE_INSTANCE_CREATE: 83, STAGE_INSTANCE_UPDATE: 84, STAGE_INSTANCE_DELETE: 85, + STICKER_CREATE: 90, + STICKER_UPDATE: 91, + STICKER_DELETE: 92, }; /** @@ -197,9 +206,10 @@ class GuildAuditLogs { * * A message * * An integration * * A stage instance + * * A sticker * * An object with an id key if target was deleted * * An object where the keys represent either the new value or the old value - * @typedef {?(Object|Guild|Channel|User|Role|Invite|Webhook|GuildEmoji|Message|Integration|StageInstance)} + * @typedef {?(Object|Guild|Channel|User|Role|Invite|Webhook|GuildEmoji|Message|Integration|StageInstance|Sticker)} * AuditLogEntryTarget */ @@ -219,6 +229,7 @@ class GuildAuditLogs { if (target < 80) return Targets.MESSAGE; if (target < 83) return Targets.INTEGRATION; if (target < 86) return Targets.STAGE_INSTANCE; + if (target < 100) return Targets.STICKER; return Targets.UNKNOWN; } @@ -250,6 +261,7 @@ class GuildAuditLogs { Actions.MESSAGE_PIN, Actions.INTEGRATION_CREATE, Actions.STAGE_INSTANCE_CREATE, + Actions.STICKER_CREATE, ].includes(action) ) { return 'CREATE'; @@ -272,6 +284,7 @@ class GuildAuditLogs { Actions.MESSAGE_UNPIN, Actions.INTEGRATION_DELETE, Actions.STAGE_INSTANCE_DELETE, + Actions.STICKER_DELETE, ].includes(action) ) { return 'DELETE'; @@ -291,6 +304,7 @@ class GuildAuditLogs { Actions.EMOJI_UPDATE, Actions.INTEGRATION_UPDATE, Actions.STAGE_INSTANCE_UPDATE, + Actions.STICKER_UPDATE, ].includes(action) ) { return 'UPDATE'; @@ -533,6 +547,19 @@ class GuildAuditLogsEntry { }, ), ); + } else if (targetType === Targets.STICKER) { + this.target = + guild.stickers.cache.get(data.target_id) ?? + new Sticker( + guild.client, + this.changes.reduce( + (o, c) => { + o[c.key] = c.new ?? c.old; + return o; + }, + { id: data.target_id }, + ), + ); } else if (data.target_id) { this.target = guild[`${targetType.toLowerCase()}s`]?.cache.get(data.target_id) ?? { id: data.target_id }; } diff --git a/typings/index.d.ts b/typings/index.d.ts index aad04886ded4..570ef48e94c4 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -611,6 +611,7 @@ export class GuildAuditLogsEntry { | Message | Integration | StageInstance + | Sticker | { id: Snowflake } | null; public targetType: GuildAuditLogsTarget; @@ -3329,6 +3330,9 @@ export interface GuildAuditLogsActions { STAGE_INSTANCE_CREATE?: number; STAGE_INSTANCE_UPDATE?: number; STAGE_INSTANCE_DELETE?: number; + STICKER_CREATE?: number; + STICKER_UPDATE?: number; + STICKER_DELETE?: number; } export type GuildAuditLogsActionType = 'CREATE' | 'DELETE' | 'UPDATE' | 'ALL'; @@ -3354,6 +3358,7 @@ export interface GuildAuditLogsTargets { MESSAGE?: string; INTEGRATION?: string; STAGE_INSTANCE?: string; + STICKER?: string; UNKNOWN?: string; } From 54133d332e9b5b27620aefec8e01a4658330425a Mon Sep 17 00:00:00 2001 From: Advaith Date: Wed, 7 Jul 2021 12:14:01 -0700 Subject: [PATCH 31/40] style: use for-of and object.entries --- src/rest/APIRequest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rest/APIRequest.js b/src/rest/APIRequest.js index 3f160543a647..ea0fb8e612a3 100644 --- a/src/rest/APIRequest.js +++ b/src/rest/APIRequest.js @@ -48,7 +48,7 @@ class APIRequest { } if (typeof this.options.data !== 'undefined') { if (this.options.dontUsePayloadJSON) { - for (const key in this.options.data) body.append(key, this.options.data[key]); + for (const [key, value] of Object.entries(this.options.data)) body.append(key, value); } else { body.append('payload_json', JSON.stringify(this.options.data)); } From 60ae9ff8feafec0cefaa52d353cfdbe3efab7eb3 Mon Sep 17 00:00:00 2001 From: Advaith Date: Tue, 13 Jul 2021 20:09:48 -0700 Subject: [PATCH 32/40] style: use optional chaining Co-authored-by: Sugden <28943913+NotSugden@users.noreply.github.com> --- src/rest/APIRequest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rest/APIRequest.js b/src/rest/APIRequest.js index ea0fb8e612a3..dc300b7aca91 100644 --- a/src/rest/APIRequest.js +++ b/src/rest/APIRequest.js @@ -44,7 +44,7 @@ class APIRequest { if (this.options.files && this.options.files.length) { body = new FormData(); for (const file of this.options.files) { - if (file && file.file) body.append(file.key ?? file.name, file.file, file.name); + if (file?.file) body.append(file.key ?? file.name, file.file, file.name); } if (typeof this.options.data !== 'undefined') { if (this.options.dontUsePayloadJSON) { From 31ca245797eae87d3f0003b39b9ab7b06ce08962 Mon Sep 17 00:00:00 2001 From: Advaith Date: Tue, 13 Jul 2021 20:13:20 -0700 Subject: [PATCH 33/40] style: return new collection directly Co-Authored-By: Sugden <28943913+NotSugden@users.noreply.github.com> --- src/managers/GuildStickerManager.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/managers/GuildStickerManager.js b/src/managers/GuildStickerManager.js index a07c91ad8715..f090439a2029 100644 --- a/src/managers/GuildStickerManager.js +++ b/src/managers/GuildStickerManager.js @@ -153,8 +153,7 @@ class GuildStickerManager extends CachedManager { } const data = await this.client.api.guilds(this.guild.id).stickers.get(); - const stickers = new Collection(data.map(sticker => [sticker.id, this.add(sticker, cache)])); - return stickers; + return new Collection(data.map(sticker => [sticker.id, this.add(sticker, cache)])); } } From 8193e0f1c478bb7cf4cc677c978bee398ff5ba4a Mon Sep 17 00:00:00 2001 From: Advaith Date: Fri, 16 Jul 2021 17:43:35 -0700 Subject: [PATCH 34/40] feat(Permissions): add USE_EXTERNAL_STICKERS --- src/util/Permissions.js | 2 ++ typings/index.d.ts | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/util/Permissions.js b/src/util/Permissions.js index de82237210f0..fceb0527591d 100644 --- a/src/util/Permissions.js +++ b/src/util/Permissions.js @@ -93,6 +93,7 @@ class Permissions extends BitField { * * `MANAGE_THREADS` * * `USE_PUBLIC_THREADS` * * `USE_PRIVATE_THREADS` + * * `USE_EXTERNAL_STICKERS` (use stickers from different guilds) * @type {Object} * @see {@link https://discord.com/developers/docs/topics/permissions} */ @@ -133,6 +134,7 @@ Permissions.FLAGS = { MANAGE_THREADS: 1n << 34n, USE_PUBLIC_THREADS: 1n << 35n, USE_PRIVATE_THREADS: 1n << 36n, + USE_EXTERNAL_STICKERS: 1n << 37n, }; /** diff --git a/typings/index.d.ts b/typings/index.d.ts index 8e23a8ddfbcd..85f96b8b7a0d 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -3842,7 +3842,8 @@ export type PermissionString = | 'REQUEST_TO_SPEAK' | 'MANAGE_THREADS' | 'USE_PUBLIC_THREADS' - | 'USE_PRIVATE_THREADS'; + | 'USE_PRIVATE_THREADS' + | 'USE_EXTERNAL_STICKERS'; export type RecursiveArray = ReadonlyArray>; From a3b9a524f339a904164cc1e417bc94c0be1932c9 Mon Sep 17 00:00:00 2001 From: Advaith Date: Fri, 16 Jul 2021 17:45:32 -0700 Subject: [PATCH 35/40] feat(ApiErrors): add 170007 --- src/util/Constants.js | 1 + typings/index.d.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/util/Constants.js b/src/util/Constants.js index 69972bc964ab..6ad340fc954e 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -752,6 +752,7 @@ exports.APIErrors = { MAXIMUM_ACTIVE_ANNOUNCEMENT_THREADS: 160007, INVALID_JSON_FOR_UPLOADED_LOTTIE_FILE: 170001, LOTTIE_ANIMATION_MAXIMUM_DIMENSIONS_EXCEEDED: 170005, + STICKER_ANIMATION_DURATION_EXCEEDS_5_SECOND_MAX: 170007, }; /** diff --git a/typings/index.d.ts b/typings/index.d.ts index 85f96b8b7a0d..ce6a00d6a43c 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -2666,6 +2666,7 @@ export interface APIErrors { MAXIMUM_ACTIVE_ANNOUNCEMENT_THREADS: 160007; INVALID_JSON_FOR_UPLOADED_LOTTIE_FILE: 170001; LOTTIE_ANIMATION_MAXIMUM_DIMENSIONS_EXCEEDED: 170005; + STICKER_ANIMATION_DURATION_EXCEEDS_5_SECOND_MAX: 170007; } export interface ApplicationAsset { From e26591bc9939738afc75c038c00ebb8b25722d41 Mon Sep 17 00:00:00 2001 From: Advaith Date: Sat, 17 Jul 2021 23:08:27 -0700 Subject: [PATCH 36/40] refactor: rename add to _add --- src/client/actions/GuildStickerCreate.js | 2 +- src/managers/GuildStickerManager.js | 8 ++++---- src/structures/Guild.js | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/client/actions/GuildStickerCreate.js b/src/client/actions/GuildStickerCreate.js index 9de0d51336b7..c02cafa941ee 100644 --- a/src/client/actions/GuildStickerCreate.js +++ b/src/client/actions/GuildStickerCreate.js @@ -6,7 +6,7 @@ const { Events } = require('../../util/Constants'); class GuildStickerCreateAction extends Action { handle(guild, createdSticker) { const already = guild.stickers.cache.has(createdSticker.id); - const sticker = guild.stickers.add(createdSticker); + const sticker = guild.stickers._add(createdSticker); /** * Emitted whenever a custom sticker is created in a guild. * @event Client#stickerCreate diff --git a/src/managers/GuildStickerManager.js b/src/managers/GuildStickerManager.js index f090439a2029..7dca41bc76f9 100644 --- a/src/managers/GuildStickerManager.js +++ b/src/managers/GuildStickerManager.js @@ -28,7 +28,7 @@ class GuildStickerManager extends CachedManager { */ add(data, cache) { - return super.add(data, cache, { extras: [this.guild] }); + return super._add(data, cache, { extras: [this.guild] }); } /** @@ -110,7 +110,7 @@ class GuildStickerManager extends CachedManager { clone._patch(d); return clone; } - return this.add(d); + return this._add(d); } /** @@ -149,11 +149,11 @@ class GuildStickerManager extends CachedManager { if (existing) return existing; } const sticker = await this.client.api.guilds(this.guild.id).stickers(id).get(); - return this.add(sticker, cache); + return this._add(sticker, cache); } const data = await this.client.api.guilds(this.guild.id).stickers.get(); - return new Collection(data.map(sticker => [sticker.id, this.add(sticker, cache)])); + return new Collection(data.map(sticker => [sticker.id, this._add(sticker, cache)])); } } diff --git a/src/structures/Guild.js b/src/structures/Guild.js index 8391f534b0eb..f217f8f51e7b 100644 --- a/src/structures/Guild.js +++ b/src/structures/Guild.js @@ -412,7 +412,7 @@ class Guild extends AnonymousGuild { * @type {GuildStickerManager} */ this.stickers = new GuildStickerManager(this); - if (data.stickers) for (const sticker of data.stickers) this.stickers.add(sticker); + if (data.stickers) for (const sticker of data.stickers) this.stickers._add(sticker); } else if (data.stickers) { this.client.actions.GuildStickersUpdate.handle({ guild_id: this.id, From 8a6cb781cf00189971f8fbf379e2b2bcefd5aeda Mon Sep 17 00:00:00 2001 From: Advaith Date: Mon, 19 Jul 2021 15:37:06 -0700 Subject: [PATCH 37/40] feat: support old messages and edit Message#stickers desc --- src/structures/Message.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/structures/Message.js b/src/structures/Message.js index 053e21dc5f7d..1b958dfa3a90 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -153,10 +153,12 @@ class Message extends Base { } /** - * A collection of stickers in the message + * A collection of (partial) stickers in the message * @type {Collection} */ - this.stickers = new Collection(data.sticker_items?.map(s => [s.id, new Sticker(this.client, s)])); + this.stickers = new Collection( + (data.sticker_items ?? data.stickers)?.map(s => [s.id, new Sticker(this.client, s)]), + ); /** * The timestamp the message was sent at From d17fe5dec1f2e91dcdc643a74c52ea8125d21af3 Mon Sep 17 00:00:00 2001 From: Advaith Date: Mon, 19 Jul 2021 15:52:37 -0700 Subject: [PATCH 38/40] feat: add more error codes --- src/util/Constants.js | 6 +++++- typings/index.d.ts | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/util/Constants.js b/src/util/Constants.js index 89c8b3883673..8d02fbc83ffe 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -793,8 +793,12 @@ exports.APIErrors = { MAXIMUM_ACTIVE_THREADS: 160006, MAXIMUM_ACTIVE_ANNOUNCEMENT_THREADS: 160007, INVALID_JSON_FOR_UPLOADED_LOTTIE_FILE: 170001, + UPLOADED_LOTTIES_CANNOT_CONTAIN_RASTERIZED_IMAGES: 170002, + STICKER_MAXIMUM_FRAMERATE_EXCEEDED: 170003, + STICKER_FRAME_COUNT_EXCEEDS_MAXIMUM_OF_1000_FRAMES: 170004, LOTTIE_ANIMATION_MAXIMUM_DIMENSIONS_EXCEEDED: 170005, - STICKER_ANIMATION_DURATION_EXCEEDS_5_SECOND_MAX: 170007, + STICKER_FRAME_RATE_IS_TOO_SMALL_OR_TOO_LARGE: 170006, + STICKER_ANIMATION_DURATION_EXCEEDS_MAXIMUM_OF_5_SECONDS: 170007, }; /** diff --git a/typings/index.d.ts b/typings/index.d.ts index ea4e79f38dad..0195f2e984ac 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -2717,8 +2717,12 @@ export interface APIErrors { MAXIMUM_ACTIVE_THREADS: 160006; MAXIMUM_ACTIVE_ANNOUNCEMENT_THREADS: 160007; INVALID_JSON_FOR_UPLOADED_LOTTIE_FILE: 170001; + UPLOADED_LOTTIES_CANNOT_CONTAIN_RASTERIZED_IMAGES: 170002; + STICKER_MAXIMUM_FRAMERATE_EXCEEDED: 170003; + STICKER_FRAME_COUNT_EXCEEDS_MAXIMUM_OF_1000_FRAMES: 170004; LOTTIE_ANIMATION_MAXIMUM_DIMENSIONS_EXCEEDED: 170005; - STICKER_ANIMATION_DURATION_EXCEEDS_5_SECOND_MAX: 170007; + STICKER_FRAME_RATE_IS_TOO_SMALL_OR_TOO_LARGE: 170006; + STICKER_ANIMATION_DURATION_EXCEEDS_MAXIMUM_OF_5_SECONDS: 170007; } export interface ApplicationAsset { From c98dda1551875c7a7a0b2b8bab1c51bdeb379d80 Mon Sep 17 00:00:00 2001 From: Advaith Date: Mon, 19 Jul 2021 15:54:49 -0700 Subject: [PATCH 39/40] fix: missed one `add` --- src/managers/GuildStickerManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/managers/GuildStickerManager.js b/src/managers/GuildStickerManager.js index 7dca41bc76f9..d3576594e513 100644 --- a/src/managers/GuildStickerManager.js +++ b/src/managers/GuildStickerManager.js @@ -27,7 +27,7 @@ class GuildStickerManager extends CachedManager { * @name GuildStickerManager#cache */ - add(data, cache) { + _add(data, cache) { return super._add(data, cache, { extras: [this.guild] }); } From 5ab68fc51830cc8c74c9b8c384032c0b398eb2b3 Mon Sep 17 00:00:00 2001 From: Advaith Date: Mon, 19 Jul 2021 16:10:15 -0700 Subject: [PATCH 40/40] docs: sticker object API docs links and API types --- package.json | 2 +- src/structures/Sticker.js | 12 +++++++++++- src/structures/StickerPack.js | 7 ++++++- typings/index.d.ts | 9 ++++++--- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index deb89b857073..d07277fc4b70 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "@sapphire/async-queue": "^1.1.4", "@types/ws": "^7.4.5", "abort-controller": "^3.0.0", - "discord-api-types": "^0.19.0-next.f393ba520d7d6d2aacaca7b3ca5d355fab614f6e", + "discord-api-types": "^0.19.0", "node-fetch": "^2.6.1", "ws": "^7.5.1" }, diff --git a/src/structures/Sticker.js b/src/structures/Sticker.js index 1f81736383e5..b72bad8975eb 100644 --- a/src/structures/Sticker.js +++ b/src/structures/Sticker.js @@ -11,7 +11,7 @@ const SnowflakeUtil = require('../util/SnowflakeUtil'); class Sticker extends Base { /** * @param {Client} client The instantiating client - * @param {Object} sticker The data for the sticker + * @param {APISticker | APIStickerItem} sticker The data for the sticker */ constructor(client, sticker) { super(client); @@ -233,3 +233,13 @@ class Sticker extends Base { } module.exports = Sticker; + +/** + * @external APISticker + * @see {@link https://discord.com/developers/docs/resources/sticker#sticker-object} + */ + +/** + * @external APIStickerItem + * @see {@link https://discord.com/developers/docs/resources/sticker#sticker-item-object} + */ diff --git a/src/structures/StickerPack.js b/src/structures/StickerPack.js index ad53c2ff779c..08e669899979 100644 --- a/src/structures/StickerPack.js +++ b/src/structures/StickerPack.js @@ -12,7 +12,7 @@ const SnowflakeUtil = require('../util/SnowflakeUtil'); class StickerPack extends Base { /** * @param {Client} client The instantiating client - * @param {Object} pack The data for the sticker pack + * @param {APIStickerPack} pack The data for the sticker pack */ constructor(client, pack) { super(client); @@ -97,3 +97,8 @@ class StickerPack extends Base { } module.exports = StickerPack; + +/** + * @external APIStickerPack + * @see {@link https://discord.com/developers/docs/resources/sticker#sticker-pack-object} + */ diff --git a/typings/index.d.ts b/typings/index.d.ts index 0195f2e984ac..c3ed88b41804 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -25,11 +25,14 @@ import { APIOverwrite, APIPartialEmoji, APIRole, + APISticker, + APIStickerItem, + APIStickerPack, APIUser, GatewayVoiceServerUpdateDispatchData, GatewayVoiceStateUpdateDispatchData, Snowflake, -} from 'discord-api-types/v8'; +} from 'discord-api-types/v9'; import { EventEmitter } from 'events'; import { Stream } from 'stream'; import * as WebSocket from 'ws'; @@ -1555,7 +1558,7 @@ export class StageInstance extends Base { } export class Sticker extends Base { - public constructor(client: Client, data: unknown); + public constructor(client: Client, data: APISticker | APIStickerItem); public readonly createdTimestamp: number; public readonly createdAt: Date; public available: boolean | null; @@ -1581,7 +1584,7 @@ export class Sticker extends Base { } export class StickerPack extends Base { - public constructor(client: Client, data: unknown); + public constructor(client: Client, data: APIStickerPack); public readonly createdTimestamp: number; public readonly createdAt: Date; public bannerId: Snowflake;