Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: application emojis #10399

Merged
merged 4 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 87 additions & 1 deletion packages/core/src/api/applications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@

import type { RequestData, REST } from '@discordjs/rest';
import {
Routes,
type RESTGetAPIApplicationEmojiResult,
type RESTGetAPIApplicationEmojisResult,
type RESTGetCurrentApplicationResult,
type RESTPatchAPIApplicationEmojiJSONBody,
type RESTPatchAPIApplicationEmojiResult,
type RESTPatchCurrentApplicationJSONBody,
type RESTPatchCurrentApplicationResult,
Routes,
type RESTPostAPIApplicationEmojiJSONBody,
type RESTPostAPIApplicationEmojiResult,
type Snowflake,
} from 'discord-api-types/v10';

export class ApplicationsAPI {
Expand Down Expand Up @@ -34,4 +41,83 @@ export class ApplicationsAPI {
signal,
}) as Promise<RESTPatchCurrentApplicationResult>;
}

/**
* Fetches all emojis of an application
*
* @see {@link https://discord.com/developers/docs/resources/emoji#list-application-emojis}
* @param applicationId - The id of the application to fetch the emojis of
* @param options - The options for fetching the emojis
*/
public async getEmojis(applicationId: Snowflake, { signal }: Pick<RequestData, 'signal'> = {}) {
return this.rest.get(Routes.applicationEmojis(applicationId), {
signal,
}) as Promise<RESTGetAPIApplicationEmojisResult>;
}

/**
* Fetches an emoji of an application
*
* @see {@link https://discord.com/developers/docs/resources/emoji#get-application-emoji}
* @param applicationId - The id of the application to fetch the emoji of
* @param emojiId - The id of the emoji to fetch
* @param options - The options for fetching the emoji
*/
public async getEmoji(applicationId: Snowflake, emojiId: Snowflake, { signal }: Pick<RequestData, 'signal'> = {}) {
return this.rest.get(Routes.applicationEmoji(applicationId, emojiId), {
signal,
}) as Promise<RESTGetAPIApplicationEmojiResult>;
}

/**
* Creates a new emoji of an application
*
* @see {@link https://discord.com/developers/docs/resources/emoji#create-application-emoji}
* @param applicationId - The id of the application to create the emoji of
* @param body - The data for creating the emoji
* @param options - The options for creating the emoji
*/
public async createEmoji(
applicationId: Snowflake,
body: RESTPostAPIApplicationEmojiJSONBody,
{ signal }: Pick<RequestData, 'signal'> = {},
) {
return this.rest.post(Routes.applicationEmojis(applicationId), {
body,
signal,
}) as Promise<RESTPostAPIApplicationEmojiResult>;
}

/**
* Edits an emoji of an application
*
* @see {@link https://discord.com/developers/docs/resources/emoji#modify-application-emoji}
* @param applicationId - The id of the application to edit the emoji of
* @param emojiId - The id of the emoji to edit
* @param body - The data for editing the emoji
* @param options - The options for editing the emoji
*/
public async editEmoji(
applicationId: Snowflake,
emojiId: Snowflake,
body: RESTPatchAPIApplicationEmojiJSONBody,
{ signal }: Pick<RequestData, 'signal'> = {},
) {
return this.rest.patch(Routes.applicationEmoji(applicationId, emojiId), {
body,
signal,
}) as Promise<RESTPatchAPIApplicationEmojiResult>;
}

/**
* Deletes an emoji of an application
*
* @see {@link https://discord.com/developers/docs/resources/emoji#delete-application-emoji}
* @param applicationId - The id of the application to delete the emoji of
* @param emojiId - The id of the emoji to delete
* @param options - The options for deleting the emoji
*/
public async deleteEmoji(applicationId: Snowflake, emojiId: Snowflake, { signal }: Pick<RequestData, 'signal'> = {}) {
await this.rest.delete(Routes.applicationEmoji(applicationId, emojiId), { signal });
}
}
2 changes: 2 additions & 0 deletions packages/discord.js/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ exports.version = require('../package.json').version;

// Managers
exports.ApplicationCommandManager = require('./managers/ApplicationCommandManager');
exports.ApplicationEmojiManager = require('./managers/ApplicationEmojiManager');
exports.ApplicationCommandPermissionsManager = require('./managers/ApplicationCommandPermissionsManager');
exports.AutoModerationRuleManager = require('./managers/AutoModerationRuleManager');
exports.BaseGuildEmojiManager = require('./managers/BaseGuildEmojiManager');
Expand Down Expand Up @@ -98,6 +99,7 @@ exports.Activity = require('./structures/Presence').Activity;
exports.AnonymousGuild = require('./structures/AnonymousGuild');
exports.Application = require('./structures/interfaces/Application');
exports.ApplicationCommand = require('./structures/ApplicationCommand');
exports.ApplicationEmoji = require('./structures/ApplicationEmoji');
exports.ApplicationRoleConnectionMetadata =
require('./structures/ApplicationRoleConnectionMetadata').ApplicationRoleConnectionMetadata;
exports.AutocompleteInteraction = require('./structures/AutocompleteInteraction');
Expand Down
142 changes: 142 additions & 0 deletions packages/discord.js/src/managers/ApplicationEmojiManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
'use strict';

const { Collection } = require('@discordjs/collection');
const { Routes } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager');
const { DiscordjsTypeError, ErrorCodes } = require('../errors');
const ApplicationEmoji = require('../structures/ApplicationEmoji');
const { resolveImage } = require('../util/DataResolver');

/**
* Manages API methods for ApplicationEmojis and stores their cache.
* @extends {CachedManager}
*/
class ApplicationEmojiManager extends CachedManager {
constructor(application, iterable) {
super(application.client, ApplicationEmoji, iterable);

/**
* The application this manager belongs to
* @type {ClientApplication}
*/
this.application = application;
}

_add(data, cache) {
return super._add(data, cache, { extras: [this.application] });
}

/**
* Options used for creating an emoji of the application
* @typedef {Object} ApplicationEmojiCreateOptions
* @property {BufferResolvable|Base64Resolvable} attachment The image for the emoji
* @property {string} name The name for the emoji
*/

/**
* Creates a new custom emoji of the application.
* @param {ApplicationEmojiCreateOptions} options Options for creating the emoji
* @returns {Promise<Emoji>} The created emoji
* @example
* // Create a new emoji from a URL
* application.emojis.create({ attachment: 'https://i.imgur.com/w3duR07.png', name: 'rip' })
* .then(emoji => console.log(`Created new emoji with name ${emoji.name}!`))
* .catch(console.error);
* @example
* // Create a new emoji from a file on your computer
* application.emojis.create({ attachment: './memes/banana.png', name: 'banana' })
* .then(emoji => console.log(`Created new emoji with name ${emoji.name}!`))
* .catch(console.error);
*/
async create({ attachment, name }) {
attachment = await resolveImage(attachment);
if (!attachment) throw new DiscordjsTypeError(ErrorCodes.ReqResourceType);

const body = { image: attachment, name };

const emoji = await this.client.rest.post(Routes.applicationEmojis(this.application.id), { body });
return this._add(emoji);
}

/**
* Obtains one or more emojis from Discord, or the emoji cache if they're already available.
* @param {Snowflake} [id] The emoji's id
* @param {BaseFetchOptions} [options] Additional options for this fetch
* @returns {Promise<ApplicationEmoji|Collection<Snowflake, ApplicationEmoji>>}
* @example
* // Fetch all emojis from the application
* message.application.emojis.fetch()
* .then(emojis => console.log(`There are ${emojis.size} emojis.`))
* .catch(console.error);
* @example
* // Fetch a single emoji
* message.application.emojis.fetch('222078108977594368')
* .then(emoji => console.log(`The emoji name is: ${emoji.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 emoji = await this.client.rest.get(Routes.applicationEmoji(this.application.id, id));
return this._add(emoji, cache);
}

const { items: data } = await this.client.rest.get(Routes.applicationEmojis(this.application.id));
const emojis = new Collection();
for (const emoji of data) emojis.set(emoji.id, this._add(emoji, cache));
return emojis;
}

/**
* Deletes an emoji.
* @param {EmojiResolvable} emoji The Emoji resolvable to delete
* @returns {Promise<void>}
*/
async delete(emoji) {
const id = this.resolveId(emoji);
if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'emoji', 'EmojiResolvable', true);
await this.client.rest.delete(Routes.applicationEmoji(this.application.id, id));
}

/**
* Edits an emoji.
* @param {EmojiResolvable} emoji The Emoji resolvable to edit
* @param {ApplicationEmojiEditOptions} options The options to provide
* @returns {Promise<ApplicationEmoji>}
*/
async edit(emoji, options) {
const id = this.resolveId(emoji);
if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'emoji', 'EmojiResolvable', true);

const newData = await this.client.rest.patch(Routes.applicationEmoji(this.application.id, id), {
body: {
name: options.name,
},
});
const existing = this.cache.get(id);
if (existing) {
existing._patch(newData);
return existing;
}
return this._add(newData);
}

/**
* Fetches the author for this emoji
* @param {EmojiResolvable} emoji The emoji to fetch the author of
* @returns {Promise<User>}
*/
async fetchAuthor(emoji) {
const id = this.resolveId(emoji);
if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'emoji', 'EmojiResolvable', true);

const data = await this.client.rest.get(Routes.applicationEmoji(this.application.id, id));

return this._add(data).author;
}
}

module.exports = ApplicationEmojiManager;
118 changes: 118 additions & 0 deletions packages/discord.js/src/structures/ApplicationEmoji.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
'use strict';

const { Emoji } = require('./Emoji');

/**
* Represents a custom emoji.
* @extends {Emoji}
*/
class ApplicationEmoji extends Emoji {
constructor(client, data, application) {
super(client, data);

/**
* The application this emoji originates from
* @type {ClientApplication}
*/
this.application = application;

/**
* The user who created this emoji
* @type {?User}
*/
this.author = null;

this.managed = null;
this.requiresColons = null;

this._patch(data);
}

_patch(data) {
if ('name' in data) this.name = data.name;
if (data.user) this.author = this.client.users._add(data.user);

if ('managed' in data) {
/**
* Whether this emoji is managed by an external service
* @type {?boolean}
*/
this.managed = data.managed;
}

if ('require_colons' in data) {
/**
* Whether or not this emoji requires colons surrounding it
* @type {?boolean}
*/
this.requiresColons = data.require_colons;
}
}

/**
* Fetches the author for this emoji
* @returns {Promise<User>}
*/
fetchAuthor() {
return this.application.emojis.fetchAuthor(this);
}

/**
* Data for editing an emoji.
* @typedef {Object} ApplicationEmojiEditOptions
* @property {string} [name] The name of the emoji
*/

/**
* Edits the emoji.
* @param {ApplicationEmojiEditOptions} options The options to provide
* @returns {Promise<ApplicationEmoji>}
* @example
* // Edit an emoji
* emoji.edit({ name: 'newemoji' })
* .then(emoji => console.log(`Edited emoji ${emoji}`))
* .catch(console.error);
*/
edit(options) {
return this.application.emojis.edit(this.id, options);
}

/**
* Sets the name of the emoji.
* @param {string} name The new name for the emoji
* @returns {Promise<ApplicationEmoji>}
*/
setName(name) {
return this.edit({ name });
}

/**
* Deletes the emoji.
* @returns {Promise<ApplicationEmoji>}
*/
async delete() {
await this.application.emojis.delete(this.id);
return this;
}

/**
* Whether this emoji is the same as another one.
* @param {ApplicationEmoji|APIEmoji} other The emoji to compare it to
* @returns {boolean}
*/
equals(other) {
if (other instanceof ApplicationEmoji) {
return (
other.animated === this.animated &&
other.id === this.id &&
other.name === this.name &&
other.managed === this.managed &&
other.requiresColons === this.requiresColons
);
}

return other.id === this.id && other.name === this.name;
}
}

module.exports = ApplicationEmoji;
Loading
Loading