Skip to content

Commit

Permalink
feat: application emojis (#10399)
Browse files Browse the repository at this point in the history
* feat: application emojis

* chore: requested changes

---------

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
sdanialraza and kodiakhq[bot] committed Aug 20, 2024
1 parent 45f7e1a commit 5d92525
Show file tree
Hide file tree
Showing 9 changed files with 402 additions and 2 deletions.
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

0 comments on commit 5d92525

Please sign in to comment.