Skip to content

Commit

Permalink
feat: application emojis
Browse files Browse the repository at this point in the history
  • Loading branch information
sdanialraza committed Jul 21, 2024
1 parent efa16a6 commit 7488507
Show file tree
Hide file tree
Showing 10 changed files with 546 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,15 +2,101 @@

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 {
public constructor(private readonly rest: REST) {}

/**
* 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 });
}

/**
* Fetches the application associated with the requesting bot user.
*
Expand Down
141 changes: 141 additions & 0 deletions packages/discord.js/src/managers/ApplicationEmojiManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
'use strict';

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

/**
* Manages API methods for ApplicationEmojis and stores their cache.
* @extends {BaseApplicationEmojiManager}
*/
class ApplicationEmojiManager extends BaseApplicationEmojiManager {
constructor(application, iterable) {
super(application.client, 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 an 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 in 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) {
const clone = existing._clone();
clone._patch(newData);
return clone;
}
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 emojiId = typeof emoji === 'string' ? emoji : emoji.id;

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

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

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

const CachedManager = require('./CachedManager');
const ApplicationEmoji = require('../structures/ApplicationEmoji');
const ReactionEmoji = require('../structures/ReactionEmoji');
const { parseEmoji } = require('../util/Util');

/**
* Holds methods to resolve ApplicationEmojis and stores their cache.
* @extends {CachedManager}
*/
class BaseApplicationEmojiManager extends CachedManager {
constructor(client, iterable) {
super(client, ApplicationEmoji, iterable);
}

/**
* The cache of ApplicationEmojis
* @type {Collection<Snowflake, ApplicationEmoji>}
* @name BaseApplicationEmojiManager#cache
*/

/**
* Data that can be resolved into a ApplicationEmoji object. This can be:
* * A Snowflake
* * An ApplicationEmoji object
* * A ReactionEmoji object
* @typedef {Snowflake|ApplicationEmoji|ReactionEmoji} EmojiResolvable
*/

/**
* Resolves an EmojiResolvable to an Emoji object.
* @param {EmojiResolvable} emoji The Emoji resolvable to identify
* @returns {?ApplicationEmoji}
*/
resolve(emoji) {
if (emoji instanceof ReactionEmoji) return super.resolve(emoji.id);
return super.resolve(emoji);
}

/**
* Resolves an EmojiResolvable to an Emoji id string.
* @param {EmojiResolvable} emoji The Emoji resolvable to identify
* @returns {?Snowflake}
*/
resolveId(emoji) {
if (emoji instanceof ReactionEmoji) return emoji.id;
return super.resolveId(emoji);
}

/**
* Data that can be resolved to give an emoji identifier. This can be:
* * An EmojiResolvable
* * The `<a:name:id>`, `<:name:id>`, `a:name:id` or `name:id` emoji identifier string of an emoji
* * The Unicode representation of an emoji
* @typedef {string|EmojiResolvable} EmojiIdentifierResolvable
*/

/**
* Resolves an EmojiResolvable to an emoji identifier.
* @param {EmojiIdentifierResolvable} emoji The emoji resolvable to resolve
* @returns {?string}
*/
resolveIdentifier(emoji) {
const emojiResolvable = this.resolve(emoji);
if (emojiResolvable) return emojiResolvable.identifier;
if (emoji instanceof ReactionEmoji) return emoji.identifier;
if (typeof emoji === 'string') {
const res = parseEmoji(emoji);
if (res?.name.length) {
emoji = `${res.animated ? 'a:' : ''}${res.name}${res.id ? `:${res.id}` : ''}`;
}
if (!emoji.includes('%')) return encodeURIComponent(emoji);
return emoji;
}
return null;
}
}

module.exports = BaseApplicationEmojiManager;
Loading

0 comments on commit 7488507

Please sign in to comment.