Skip to content

Commit 5d92525

Browse files
feat: application emojis (#10399)
* feat: application emojis * chore: requested changes --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
1 parent 45f7e1a commit 5d92525

File tree

9 files changed

+402
-2
lines changed

9 files changed

+402
-2
lines changed

packages/core/src/api/applications.ts

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,17 @@
22

33
import type { RequestData, REST } from '@discordjs/rest';
44
import {
5+
Routes,
6+
type RESTGetAPIApplicationEmojiResult,
7+
type RESTGetAPIApplicationEmojisResult,
58
type RESTGetCurrentApplicationResult,
9+
type RESTPatchAPIApplicationEmojiJSONBody,
10+
type RESTPatchAPIApplicationEmojiResult,
611
type RESTPatchCurrentApplicationJSONBody,
712
type RESTPatchCurrentApplicationResult,
8-
Routes,
13+
type RESTPostAPIApplicationEmojiJSONBody,
14+
type RESTPostAPIApplicationEmojiResult,
15+
type Snowflake,
916
} from 'discord-api-types/v10';
1017

1118
export class ApplicationsAPI {
@@ -34,4 +41,83 @@ export class ApplicationsAPI {
3441
signal,
3542
}) as Promise<RESTPatchCurrentApplicationResult>;
3643
}
44+
45+
/**
46+
* Fetches all emojis of an application
47+
*
48+
* @see {@link https://discord.com/developers/docs/resources/emoji#list-application-emojis}
49+
* @param applicationId - The id of the application to fetch the emojis of
50+
* @param options - The options for fetching the emojis
51+
*/
52+
public async getEmojis(applicationId: Snowflake, { signal }: Pick<RequestData, 'signal'> = {}) {
53+
return this.rest.get(Routes.applicationEmojis(applicationId), {
54+
signal,
55+
}) as Promise<RESTGetAPIApplicationEmojisResult>;
56+
}
57+
58+
/**
59+
* Fetches an emoji of an application
60+
*
61+
* @see {@link https://discord.com/developers/docs/resources/emoji#get-application-emoji}
62+
* @param applicationId - The id of the application to fetch the emoji of
63+
* @param emojiId - The id of the emoji to fetch
64+
* @param options - The options for fetching the emoji
65+
*/
66+
public async getEmoji(applicationId: Snowflake, emojiId: Snowflake, { signal }: Pick<RequestData, 'signal'> = {}) {
67+
return this.rest.get(Routes.applicationEmoji(applicationId, emojiId), {
68+
signal,
69+
}) as Promise<RESTGetAPIApplicationEmojiResult>;
70+
}
71+
72+
/**
73+
* Creates a new emoji of an application
74+
*
75+
* @see {@link https://discord.com/developers/docs/resources/emoji#create-application-emoji}
76+
* @param applicationId - The id of the application to create the emoji of
77+
* @param body - The data for creating the emoji
78+
* @param options - The options for creating the emoji
79+
*/
80+
public async createEmoji(
81+
applicationId: Snowflake,
82+
body: RESTPostAPIApplicationEmojiJSONBody,
83+
{ signal }: Pick<RequestData, 'signal'> = {},
84+
) {
85+
return this.rest.post(Routes.applicationEmojis(applicationId), {
86+
body,
87+
signal,
88+
}) as Promise<RESTPostAPIApplicationEmojiResult>;
89+
}
90+
91+
/**
92+
* Edits an emoji of an application
93+
*
94+
* @see {@link https://discord.com/developers/docs/resources/emoji#modify-application-emoji}
95+
* @param applicationId - The id of the application to edit the emoji of
96+
* @param emojiId - The id of the emoji to edit
97+
* @param body - The data for editing the emoji
98+
* @param options - The options for editing the emoji
99+
*/
100+
public async editEmoji(
101+
applicationId: Snowflake,
102+
emojiId: Snowflake,
103+
body: RESTPatchAPIApplicationEmojiJSONBody,
104+
{ signal }: Pick<RequestData, 'signal'> = {},
105+
) {
106+
return this.rest.patch(Routes.applicationEmoji(applicationId, emojiId), {
107+
body,
108+
signal,
109+
}) as Promise<RESTPatchAPIApplicationEmojiResult>;
110+
}
111+
112+
/**
113+
* Deletes an emoji of an application
114+
*
115+
* @see {@link https://discord.com/developers/docs/resources/emoji#delete-application-emoji}
116+
* @param applicationId - The id of the application to delete the emoji of
117+
* @param emojiId - The id of the emoji to delete
118+
* @param options - The options for deleting the emoji
119+
*/
120+
public async deleteEmoji(applicationId: Snowflake, emojiId: Snowflake, { signal }: Pick<RequestData, 'signal'> = {}) {
121+
await this.rest.delete(Routes.applicationEmoji(applicationId, emojiId), { signal });
122+
}
37123
}

packages/discord.js/src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ exports.version = require('../package.json').version;
5454

5555
// Managers
5656
exports.ApplicationCommandManager = require('./managers/ApplicationCommandManager');
57+
exports.ApplicationEmojiManager = require('./managers/ApplicationEmojiManager');
5758
exports.ApplicationCommandPermissionsManager = require('./managers/ApplicationCommandPermissionsManager');
5859
exports.AutoModerationRuleManager = require('./managers/AutoModerationRuleManager');
5960
exports.BaseGuildEmojiManager = require('./managers/BaseGuildEmojiManager');
@@ -98,6 +99,7 @@ exports.Activity = require('./structures/Presence').Activity;
9899
exports.AnonymousGuild = require('./structures/AnonymousGuild');
99100
exports.Application = require('./structures/interfaces/Application');
100101
exports.ApplicationCommand = require('./structures/ApplicationCommand');
102+
exports.ApplicationEmoji = require('./structures/ApplicationEmoji');
101103
exports.ApplicationRoleConnectionMetadata =
102104
require('./structures/ApplicationRoleConnectionMetadata').ApplicationRoleConnectionMetadata;
103105
exports.AutocompleteInteraction = require('./structures/AutocompleteInteraction');
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
'use strict';
2+
3+
const { Collection } = require('@discordjs/collection');
4+
const { Routes } = require('discord-api-types/v10');
5+
const CachedManager = require('./CachedManager');
6+
const { DiscordjsTypeError, ErrorCodes } = require('../errors');
7+
const ApplicationEmoji = require('../structures/ApplicationEmoji');
8+
const { resolveImage } = require('../util/DataResolver');
9+
10+
/**
11+
* Manages API methods for ApplicationEmojis and stores their cache.
12+
* @extends {CachedManager}
13+
*/
14+
class ApplicationEmojiManager extends CachedManager {
15+
constructor(application, iterable) {
16+
super(application.client, ApplicationEmoji, iterable);
17+
18+
/**
19+
* The application this manager belongs to
20+
* @type {ClientApplication}
21+
*/
22+
this.application = application;
23+
}
24+
25+
_add(data, cache) {
26+
return super._add(data, cache, { extras: [this.application] });
27+
}
28+
29+
/**
30+
* Options used for creating an emoji of the application
31+
* @typedef {Object} ApplicationEmojiCreateOptions
32+
* @property {BufferResolvable|Base64Resolvable} attachment The image for the emoji
33+
* @property {string} name The name for the emoji
34+
*/
35+
36+
/**
37+
* Creates a new custom emoji of the application.
38+
* @param {ApplicationEmojiCreateOptions} options Options for creating the emoji
39+
* @returns {Promise<Emoji>} The created emoji
40+
* @example
41+
* // Create a new emoji from a URL
42+
* application.emojis.create({ attachment: 'https://i.imgur.com/w3duR07.png', name: 'rip' })
43+
* .then(emoji => console.log(`Created new emoji with name ${emoji.name}!`))
44+
* .catch(console.error);
45+
* @example
46+
* // Create a new emoji from a file on your computer
47+
* application.emojis.create({ attachment: './memes/banana.png', name: 'banana' })
48+
* .then(emoji => console.log(`Created new emoji with name ${emoji.name}!`))
49+
* .catch(console.error);
50+
*/
51+
async create({ attachment, name }) {
52+
attachment = await resolveImage(attachment);
53+
if (!attachment) throw new DiscordjsTypeError(ErrorCodes.ReqResourceType);
54+
55+
const body = { image: attachment, name };
56+
57+
const emoji = await this.client.rest.post(Routes.applicationEmojis(this.application.id), { body });
58+
return this._add(emoji);
59+
}
60+
61+
/**
62+
* Obtains one or more emojis from Discord, or the emoji cache if they're already available.
63+
* @param {Snowflake} [id] The emoji's id
64+
* @param {BaseFetchOptions} [options] Additional options for this fetch
65+
* @returns {Promise<ApplicationEmoji|Collection<Snowflake, ApplicationEmoji>>}
66+
* @example
67+
* // Fetch all emojis from the application
68+
* message.application.emojis.fetch()
69+
* .then(emojis => console.log(`There are ${emojis.size} emojis.`))
70+
* .catch(console.error);
71+
* @example
72+
* // Fetch a single emoji
73+
* message.application.emojis.fetch('222078108977594368')
74+
* .then(emoji => console.log(`The emoji name is: ${emoji.name}`))
75+
* .catch(console.error);
76+
*/
77+
async fetch(id, { cache = true, force = false } = {}) {
78+
if (id) {
79+
if (!force) {
80+
const existing = this.cache.get(id);
81+
if (existing) return existing;
82+
}
83+
const emoji = await this.client.rest.get(Routes.applicationEmoji(this.application.id, id));
84+
return this._add(emoji, cache);
85+
}
86+
87+
const { items: data } = await this.client.rest.get(Routes.applicationEmojis(this.application.id));
88+
const emojis = new Collection();
89+
for (const emoji of data) emojis.set(emoji.id, this._add(emoji, cache));
90+
return emojis;
91+
}
92+
93+
/**
94+
* Deletes an emoji.
95+
* @param {EmojiResolvable} emoji The Emoji resolvable to delete
96+
* @returns {Promise<void>}
97+
*/
98+
async delete(emoji) {
99+
const id = this.resolveId(emoji);
100+
if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'emoji', 'EmojiResolvable', true);
101+
await this.client.rest.delete(Routes.applicationEmoji(this.application.id, id));
102+
}
103+
104+
/**
105+
* Edits an emoji.
106+
* @param {EmojiResolvable} emoji The Emoji resolvable to edit
107+
* @param {ApplicationEmojiEditOptions} options The options to provide
108+
* @returns {Promise<ApplicationEmoji>}
109+
*/
110+
async edit(emoji, options) {
111+
const id = this.resolveId(emoji);
112+
if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'emoji', 'EmojiResolvable', true);
113+
114+
const newData = await this.client.rest.patch(Routes.applicationEmoji(this.application.id, id), {
115+
body: {
116+
name: options.name,
117+
},
118+
});
119+
const existing = this.cache.get(id);
120+
if (existing) {
121+
existing._patch(newData);
122+
return existing;
123+
}
124+
return this._add(newData);
125+
}
126+
127+
/**
128+
* Fetches the author for this emoji
129+
* @param {EmojiResolvable} emoji The emoji to fetch the author of
130+
* @returns {Promise<User>}
131+
*/
132+
async fetchAuthor(emoji) {
133+
const id = this.resolveId(emoji);
134+
if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'emoji', 'EmojiResolvable', true);
135+
136+
const data = await this.client.rest.get(Routes.applicationEmoji(this.application.id, id));
137+
138+
return this._add(data).author;
139+
}
140+
}
141+
142+
module.exports = ApplicationEmojiManager;
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
'use strict';
2+
3+
const { Emoji } = require('./Emoji');
4+
5+
/**
6+
* Represents a custom emoji.
7+
* @extends {Emoji}
8+
*/
9+
class ApplicationEmoji extends Emoji {
10+
constructor(client, data, application) {
11+
super(client, data);
12+
13+
/**
14+
* The application this emoji originates from
15+
* @type {ClientApplication}
16+
*/
17+
this.application = application;
18+
19+
/**
20+
* The user who created this emoji
21+
* @type {?User}
22+
*/
23+
this.author = null;
24+
25+
this.managed = null;
26+
this.requiresColons = null;
27+
28+
this._patch(data);
29+
}
30+
31+
_patch(data) {
32+
if ('name' in data) this.name = data.name;
33+
if (data.user) this.author = this.client.users._add(data.user);
34+
35+
if ('managed' in data) {
36+
/**
37+
* Whether this emoji is managed by an external service
38+
* @type {?boolean}
39+
*/
40+
this.managed = data.managed;
41+
}
42+
43+
if ('require_colons' in data) {
44+
/**
45+
* Whether or not this emoji requires colons surrounding it
46+
* @type {?boolean}
47+
*/
48+
this.requiresColons = data.require_colons;
49+
}
50+
}
51+
52+
/**
53+
* Fetches the author for this emoji
54+
* @returns {Promise<User>}
55+
*/
56+
fetchAuthor() {
57+
return this.application.emojis.fetchAuthor(this);
58+
}
59+
60+
/**
61+
* Data for editing an emoji.
62+
* @typedef {Object} ApplicationEmojiEditOptions
63+
* @property {string} [name] The name of the emoji
64+
*/
65+
66+
/**
67+
* Edits the emoji.
68+
* @param {ApplicationEmojiEditOptions} options The options to provide
69+
* @returns {Promise<ApplicationEmoji>}
70+
* @example
71+
* // Edit an emoji
72+
* emoji.edit({ name: 'newemoji' })
73+
* .then(emoji => console.log(`Edited emoji ${emoji}`))
74+
* .catch(console.error);
75+
*/
76+
edit(options) {
77+
return this.application.emojis.edit(this.id, options);
78+
}
79+
80+
/**
81+
* Sets the name of the emoji.
82+
* @param {string} name The new name for the emoji
83+
* @returns {Promise<ApplicationEmoji>}
84+
*/
85+
setName(name) {
86+
return this.edit({ name });
87+
}
88+
89+
/**
90+
* Deletes the emoji.
91+
* @returns {Promise<ApplicationEmoji>}
92+
*/
93+
async delete() {
94+
await this.application.emojis.delete(this.id);
95+
return this;
96+
}
97+
98+
/**
99+
* Whether this emoji is the same as another one.
100+
* @param {ApplicationEmoji|APIEmoji} other The emoji to compare it to
101+
* @returns {boolean}
102+
*/
103+
equals(other) {
104+
if (other instanceof ApplicationEmoji) {
105+
return (
106+
other.animated === this.animated &&
107+
other.id === this.id &&
108+
other.name === this.name &&
109+
other.managed === this.managed &&
110+
other.requiresColons === this.requiresColons
111+
);
112+
}
113+
114+
return other.id === this.id && other.name === this.name;
115+
}
116+
}
117+
118+
module.exports = ApplicationEmoji;

0 commit comments

Comments
 (0)