mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-11 17:13:31 +01:00
feat(Sticker): updates, sticker packs, and guild stickers (#5867)
Co-authored-by: SpaceEEC <spaceeec@yahoo.com> Co-authored-by: Antonio Román <kyradiscord@gmail.com> Co-authored-by: Tiemen <ThaTiemsz@users.noreply.github.com> Co-authored-by: Vlad Frangu <kingdgrizzle@gmail.com> Co-authored-by: BannerBomb <BannerBomb55@gmail.com> Co-authored-by: Noel <icrawltogo@gmail.com> Co-authored-by: Sugden <28943913+NotSugden@users.noreply.github.com>
This commit is contained in:
@@ -14,6 +14,7 @@ const GuildChannelManager = require('../managers/GuildChannelManager');
|
||||
const GuildEmojiManager = require('../managers/GuildEmojiManager');
|
||||
const GuildInviteManager = require('../managers/GuildInviteManager');
|
||||
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');
|
||||
@@ -404,6 +405,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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -153,15 +153,12 @@ class Message extends Base {
|
||||
}
|
||||
|
||||
/**
|
||||
* A collection of stickers in the message
|
||||
* A collection of (partial) stickers in the message
|
||||
* @type {Collection<Snowflake, Sticker>}
|
||||
*/
|
||||
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 ?? data.stickers)?.map(s => [s.id, new Sticker(this.client, s)]),
|
||||
);
|
||||
|
||||
/**
|
||||
* The timestamp the message was sent at
|
||||
|
||||
@@ -191,6 +191,7 @@ class MessagePayload {
|
||||
flags,
|
||||
message_reference,
|
||||
attachments: this.options.attachments,
|
||||
sticker_ids: this.options.stickers?.map(sticker => sticker.id ?? sticker),
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
/**
|
||||
@@ -9,8 +9,17 @@ const SnowflakeUtil = require('../util/SnowflakeUtil');
|
||||
* @extends {Base}
|
||||
*/
|
||||
class Sticker extends Base {
|
||||
/**
|
||||
* @param {Client} client The instantiating client
|
||||
* @param {APISticker | APIStickerItem} sticker The data for the sticker
|
||||
*/
|
||||
constructor(client, sticker) {
|
||||
super(client);
|
||||
|
||||
this._patch(sticker);
|
||||
}
|
||||
|
||||
_patch(sticker) {
|
||||
/**
|
||||
* The sticker's id
|
||||
* @type {Snowflake}
|
||||
@@ -18,16 +27,16 @@ class Sticker extends Base {
|
||||
this.id = sticker.id;
|
||||
|
||||
/**
|
||||
* The sticker image's id
|
||||
* @type {string}
|
||||
* The description of the sticker
|
||||
* @type {?string}
|
||||
*/
|
||||
this.asset = sticker.asset;
|
||||
this.description = sticker.description ?? null;
|
||||
|
||||
/**
|
||||
* The description of the sticker
|
||||
* @type {string}
|
||||
* The type of the sticker
|
||||
* @type {?StickerType}
|
||||
*/
|
||||
this.description = sticker.description;
|
||||
this.type = StickerTypes[sticker.type] ?? null;
|
||||
|
||||
/**
|
||||
* The format of the sticker
|
||||
@@ -42,16 +51,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[]}
|
||||
* 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
|
||||
* @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,17 +105,141 @@ class Sticker extends Base {
|
||||
return new Date(this.createdTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this sticker is partial
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get partial() {
|
||||
return !this.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* The guild that owns this sticker
|
||||
* @type {?Guild}
|
||||
* @readonly
|
||||
*/
|
||||
get guild() {
|
||||
return this.client.guilds.resolve(this.guildId);
|
||||
}
|
||||
|
||||
/**
|
||||
* A link to the sticker
|
||||
* <info>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.</info>
|
||||
* <info>If the sticker's format is LOTTIE, it returns the URL of the Lottie json file.</info>
|
||||
* @type {string}
|
||||
*/
|
||||
get url() {
|
||||
return `${this.client.options.http.cdn}/stickers/${this.id}/${this.asset}.${
|
||||
this.format === 'LOTTIE' ? 'json' : 'png'
|
||||
}`;
|
||||
return this.client.rest.cdn.Sticker(this.id, this.format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches this sticker.
|
||||
* @returns {Promise<Sticker>}
|
||||
*/
|
||||
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<?StickerPack>}
|
||||
*/
|
||||
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<?User>}
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<Sticker>}
|
||||
* @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<Sticker>}
|
||||
* @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 &&
|
||||
other.tags === this.tags.join(', ')
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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}
|
||||
*/
|
||||
|
||||
104
src/structures/StickerPack.js
Normal file
104
src/structures/StickerPack.js
Normal file
@@ -0,0 +1,104 @@
|
||||
'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 {APIStickerPack} pack The data for the sticker pack
|
||||
*/
|
||||
constructor(client, pack) {
|
||||
super(client);
|
||||
/**
|
||||
* The Sticker pack's id
|
||||
* @type {Snowflake}
|
||||
*/
|
||||
this.id = pack.id;
|
||||
|
||||
/**
|
||||
* The stickers in the pack
|
||||
* @type {Collection<Snowflake, Sticker>}
|
||||
*/
|
||||
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 ?? null;
|
||||
|
||||
/**
|
||||
* 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.coverStickerId && 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.StickerPackBanner(this.bannerId, format, size);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = StickerPack;
|
||||
|
||||
/**
|
||||
* @external APIStickerPack
|
||||
* @see {@link https://discord.com/developers/docs/resources/sticker#sticker-pack-object}
|
||||
*/
|
||||
@@ -65,6 +65,7 @@ class TextBasedChannel {
|
||||
* @property {FileOptions[]|BufferResolvable[]|MessageAttachment[]} [files] Files to send with the message
|
||||
* @property {MessageActionRow[]|MessageActionRowOptions[]} [components]
|
||||
* Action rows containing interactive components for the message (buttons, select menus)
|
||||
* @property {StickerResolvable[]} [stickers=[]] Stickers to send in the message
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user