diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 69e3725fe..9f84b1073 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -14,4 +14,4 @@ To get ready to work on the codebase, please do the following:
3. If you're working on voice, also run `npm install node-opus` or `npm install opusscript`
4. Code your heart out!
5. Run `npm test` to run ESLint and ensure any JSDoc changes are valid
-6. [Submit a pull request](https://github.com/hydrabolt/discord.js/compare)
+6. [Submit a pull request](https://github.com/discordjs/discord.js/compare)
diff --git a/.gitmodules b/.gitmodules
index d5aa0ecce..44fff6d5f 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,3 @@
[submodule "typings"]
path = typings
- url = https://github.com/zajrik/discord.js-typings
+ url = https://github.com/discordjs/discord.js-typings
diff --git a/README.md b/README.md
index 989d2d652..3d0f665d2 100644
--- a/README.md
+++ b/README.md
@@ -8,8 +8,8 @@
-
-
+
+
@@ -40,7 +40,7 @@ Using opusscript is only recommended for development environments where node-opu
For production bots, using node-opus should be considered a necessity, especially if they're going to be running on multiple servers.
### Optional packages
-- [zlib-sync](https://www.npmjs.com/package/zlib-sync) for significantly faster WebSocket data inflation (`npm i zlib-sync`)
+- [zlib-sync](https://www.npmjs.com/package/zlib-sync) for significantly faster WebSocket data inflation (`npm i zlib-sync`)
- [erlpack](https://github.com/discordapp/erlpack) for significantly faster WebSocket data (de)serialisation (`npm i discordapp/erlpack`)
- One of the following packages can be installed for faster voice packet encryption and decryption:
- [sodium](https://www.npmjs.com/package/sodium) (`npm i sodium`)
@@ -67,21 +67,21 @@ client.login('your token');
```
## Links
-* [Website](https://discord.js.org/) ([source](https://github.com/hydrabolt/discord.js-site))
+* [Website](https://discord.js.org/) ([source](https://github.com/discordjs/website))
* [Documentation](https://discord.js.org/#/docs)
* [Discord.js Discord server](https://discord.gg/bRCvFy9)
* [Discord API Discord server](https://discord.gg/discord-api)
-* [GitHub](https://github.com/hydrabolt/discord.js)
+* [GitHub](https://github.com/discordjs/discord.js)
* [NPM](https://www.npmjs.com/package/discord.js)
* [Related libraries](https://discordapi.com/unofficial/libs.html)
### Extensions
-* [discord-rpc](https://www.npmjs.com/package/discord-rpc) ([github](https://github.com/devsnek/discord-rpc))
+* [discord-rpc](https://www.npmjs.com/package/discord-rpc) ([github](https://github.com/discordjs/RPC))
## Contributing
Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the
[documentation](https://discord.js.org/#/docs).
-See [the contribution guide](https://github.com/hydrabolt/discord.js/blob/master/.github/CONTRIBUTING.md) if you'd like to submit a PR.
+See [the contribution guide](https://github.com/discordjs/discord.js/blob/master/.github/CONTRIBUTING.md) if you'd like to submit a PR.
## Help
If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle
diff --git a/docs/general/updating.md b/docs/general/updating.md
index cc2c7399f..0eab3f4b9 100644
--- a/docs/general/updating.md
+++ b/docs/general/updating.md
@@ -1,11 +1,11 @@
# Version 11.1.0
v11.1.0 features improved voice and gateway stability, as well as support for new features such as audit logs and searching for messages.
-See [the changelog](https://github.com/hydrabolt/discord.js/releases/tag/11.1.0) for a full list of changes, including
+See [the changelog](https://github.com/discordjs/discord.js/releases/tag/11.1.0) for a full list of changes, including
information about deprecations.
# Version 11
Version 11 contains loads of new and improved features, optimisations, and bug fixes.
-See [the changelog](https://github.com/hydrabolt/discord.js/releases/tag/11.0.0) for a full list of changes.
+See [the changelog](https://github.com/discordjs/discord.js/releases/tag/11.0.0) for a full list of changes.
## Significant additions
* Message Reactions and Embeds (rich text)
diff --git a/docs/general/welcome.md b/docs/general/welcome.md
index 84b06ed33..803837b72 100644
--- a/docs/general/welcome.md
+++ b/docs/general/welcome.md
@@ -8,8 +8,8 @@
-
-
+
+
@@ -68,18 +68,18 @@ client.login('your token');
```
## Links
-* [Website](https://discord.js.org/) ([source](https://github.com/hydrabolt/discord.js-site))
+* [Website](https://discord.js.org/) ([source](https://github.com/discordjs/website))
* [Documentation](https://discord.js.org/#/docs)
* [Discord.js server](https://discord.gg/bRCvFy9)
* [Discord API server](https://discord.gg/rV4BwdK)
-* [GitHub](https://github.com/hydrabolt/discord.js)
+* [GitHub](https://github.com/discordjs/discord.js)
* [NPM](https://www.npmjs.com/package/discord.js)
* [Related libraries](https://discordapi.com/unofficial/libs.html) (see also [discord-rpc](https://www.npmjs.com/package/discord-rpc))
## Contributing
Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the
[documentation](https://discord.js.org/#/docs).
-See [the contribution guide](https://github.com/hydrabolt/discord.js/blob/master/.github/CONTRIBUTING.md) if you'd like to submit a PR.
+See [the contribution guide](https://github.com/discordjs/discord.js/blob/master/.github/CONTRIBUTING.md) if you'd like to submit a PR.
## Help
If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle
diff --git a/docs/topics/web.md b/docs/topics/web.md
index 660651bb7..863051212 100644
--- a/docs/topics/web.md
+++ b/docs/topics/web.md
@@ -17,7 +17,7 @@ const Discord = require('discord.js/browser');
```
### Webpack File
-You can obtain your desired version of discord.js' web build from the [webpack branch](https://github.com/hydrabolt/discord.js/tree/webpack) of the GitHub repository.
+You can obtain your desired version of discord.js' web build from the [webpack branch](https://github.com/discordjs/discord.js/tree/webpack) of the GitHub repository.
There is a file for each branch and version of the library, and the ones ending in `.min.js` are minified to substantially reduce the size of the source code.
Include the file on the page just as you would any other JS library, like so:
diff --git a/package.json b/package.json
index f99a4ebdd..cd7d010cc 100644
--- a/package.json
+++ b/package.json
@@ -14,7 +14,7 @@
},
"repository": {
"type": "git",
- "url": "git+https://github.com/hydrabolt/discord.js.git"
+ "url": "git+https://github.com/discordjs/discord.js.git"
},
"keywords": [
"discord",
@@ -27,9 +27,9 @@
"author": "Amish Shah ",
"license": "Apache-2.0",
"bugs": {
- "url": "https://github.com/hydrabolt/discord.js/issues"
+ "url": "https://github.com/discordjs/discord.js/issues"
},
- "homepage": "https://github.com/hydrabolt/discord.js#readme",
+ "homepage": "https://github.com/discordjs/discord.js#readme",
"runkitExampleFilename": "./docs/examples/ping.js",
"dependencies": {
"pako": "^1.0.0",
@@ -48,7 +48,7 @@
},
"devDependencies": {
"@types/node": "^8.0.0",
- "discord.js-docgen": "hydrabolt/discord.js-docgen",
+ "discord.js-docgen": "discordjs/docgen",
"eslint": "^4.11.0",
"jsdoc-strip-async-await": "^0.1.0",
"json-filter-loader": "^1.0.0",
diff --git a/src/client/Client.js b/src/client/Client.js
index 77f0682c2..db0fb151a 100644
--- a/src/client/Client.js
+++ b/src/client/Client.js
@@ -15,7 +15,7 @@ const UserStore = require('../stores/UserStore');
const ChannelStore = require('../stores/ChannelStore');
const GuildStore = require('../stores/GuildStore');
const ClientPresenceStore = require('../stores/ClientPresenceStore');
-const EmojiStore = require('../stores/EmojiStore');
+const GuildEmojiStore = require('../stores/GuildEmojiStore');
const { Events, browser } = require('../util/Constants');
const DataResolver = require('../util/DataResolver');
const { Error, TypeError, RangeError } = require('../errors');
@@ -208,11 +208,11 @@ class Client extends BaseClient {
/**
* All custom emojis that the client has access to, mapped by their IDs
- * @type {EmojiStore}
+ * @type {GuildEmojiStore}
* @readonly
*/
get emojis() {
- const emojis = new EmojiStore({ client: this });
+ const emojis = new GuildEmojiStore({ client: this });
for (const guild of this.guilds.values()) {
if (guild.available) for (const emoji of guild.emojis.values()) emojis.set(emoji.id, emoji);
}
@@ -287,6 +287,11 @@ class Client extends BaseClient {
* Obtains an invite from Discord.
* @param {InviteResolvable} invite Invite code or URL
* @returns {Promise}
+ * @example
+ * client.fetchInvite('https://discord.gg/bRCvFy9')
+ * .then(invite => {
+ * console.log(`Obtained invite with code: ${invite.code}`);
+ * }).catch(console.error);
*/
fetchInvite(invite) {
const code = DataResolver.resolveInviteCode(invite);
@@ -299,6 +304,11 @@ class Client extends BaseClient {
* @param {Snowflake} id ID of the webhook
* @param {string} [token] Token for the webhook
* @returns {Promise}
+ * @example
+ * client.fetchWebhook('id', 'token')
+ * .then(webhook => {
+ * console.log(`Obtained webhook with name: ${webhook.name}`);
+ * }).catch(console.error);
*/
fetchWebhook(id, token) {
return this.api.webhooks(id, token).get().then(data => new Webhook(this, data));
@@ -307,6 +317,11 @@ class Client extends BaseClient {
/**
* Obtains the available voice regions from Discord.
* @returns {Collection}
+ * @example
+ * client.fetchVoiceRegions()
+ * .then(regions => {
+ * console.log(`Available regions are: ${regions.map(region => region.name).join(', ')}`);
+ * }).catch(console.error);
*/
fetchVoiceRegions() {
return this.api.voice.regions.get().then(res => {
@@ -323,6 +338,10 @@ class Client extends BaseClient {
* will be removed from the caches. The default is based on {@link ClientOptions#messageCacheLifetime}
* @returns {number} Amount of messages that were removed from the caches,
* or -1 if the message cache lifetime is unlimited
+ * @example
+ * // Remove all messages older than 1800 seconds from the messages cache
+ * const amount = client.sweepMessages(1800);
+ * console.log(`Successfully removed ${amount} messages from the cache.`);
*/
sweepMessages(lifetime = this.options.messageCacheLifetime) {
if (typeof lifetime !== 'number' || isNaN(lifetime)) {
@@ -359,6 +378,11 @@ class Client extends BaseClient {
* Obtains the OAuth Application of the bot from Discord.
* @param {Snowflake} [id='@me'] ID of application to fetch
* @returns {Promise}
+ * @example
+ * client.fetchApplication('id')
+ * .then(application => {
+ * console.log(`Obtained application with name: ${application.name}`);
+ * }).catch(console.error);
*/
fetchApplication(id = '@me') {
return this.api.oauth2.applications(id).get()
@@ -374,7 +398,7 @@ class Client extends BaseClient {
* client.generateInvite(['SEND_MESSAGES', 'MANAGE_GUILD', 'MENTION_EVERYONE'])
* .then(link => {
* console.log(`Generated bot invite link: ${link}`);
- * });
+ * }).catch(console.error);
*/
generateInvite(permissions) {
if (permissions) {
diff --git a/src/client/actions/GuildEmojiCreate.js b/src/client/actions/GuildEmojiCreate.js
index 4b5b913c8..7fc955a0f 100644
--- a/src/client/actions/GuildEmojiCreate.js
+++ b/src/client/actions/GuildEmojiCreate.js
@@ -12,7 +12,7 @@ class GuildEmojiCreateAction extends Action {
/**
* Emitted whenever a custom emoji is created in a guild.
* @event Client#emojiCreate
- * @param {Emoji} emoji The emoji that was created
+ * @param {GuildEmoji} emoji The emoji that was created
*/
module.exports = GuildEmojiCreateAction;
diff --git a/src/client/actions/GuildEmojiDelete.js b/src/client/actions/GuildEmojiDelete.js
index 36a674b33..d8a83fc3e 100644
--- a/src/client/actions/GuildEmojiDelete.js
+++ b/src/client/actions/GuildEmojiDelete.js
@@ -10,9 +10,9 @@ class GuildEmojiDeleteAction extends Action {
}
/**
- * Emitted whenever a custom guild emoji is deleted.
+ * Emitted whenever a custom emoji is deleted in a guild.
* @event Client#emojiDelete
- * @param {Emoji} emoji The emoji that was deleted
+ * @param {GuildEmoji} emoji The emoji that was deleted
*/
module.exports = GuildEmojiDeleteAction;
diff --git a/src/client/actions/GuildEmojiUpdate.js b/src/client/actions/GuildEmojiUpdate.js
index b3ebb4b63..e6accf2c5 100644
--- a/src/client/actions/GuildEmojiUpdate.js
+++ b/src/client/actions/GuildEmojiUpdate.js
@@ -10,10 +10,10 @@ class GuildEmojiUpdateAction extends Action {
}
/**
- * Emitted whenever a custom guild emoji is updated.
+ * Emitted whenever a custom emoji is updated in a guild.
* @event Client#emojiUpdate
- * @param {Emoji} oldEmoji The old emoji
- * @param {Emoji} newEmoji The new emoji
+ * @param {GuildEmoji} oldEmoji The old emoji
+ * @param {GuildEmoji} newEmoji The new emoji
*/
module.exports = GuildEmojiUpdateAction;
diff --git a/src/client/actions/MessageReactionAdd.js b/src/client/actions/MessageReactionAdd.js
index 073ba05a7..9d307ceee 100644
--- a/src/client/actions/MessageReactionAdd.js
+++ b/src/client/actions/MessageReactionAdd.js
@@ -33,7 +33,7 @@ class MessageReactionAdd extends Action {
* Emitted whenever a reaction is added to a message.
* @event Client#messageReactionAdd
* @param {MessageReaction} messageReaction The reaction object
- * @param {User} user The user that applied the emoji or reaction emoji
+ * @param {User} user The user that applied the guild or reaction emoji
*/
module.exports = MessageReactionAdd;
diff --git a/src/errors/Messages.js b/src/errors/Messages.js
index 545452703..70ee8d47e 100644
--- a/src/errors/Messages.js
+++ b/src/errors/Messages.js
@@ -93,7 +93,7 @@ const Messages = {
WEBHOOK_MESSAGE: 'The message was not sent by a webhook.',
- EMOJI_TYPE: 'Emoji must be a string or Emoji/ReactionEmoji',
+ EMOJI_TYPE: 'Emoji must be a string or GuildEmoji/ReactionEmoji',
REACTION_RESOLVE_USER: 'Couldn\'t resolve the user ID to remove from the reaction.',
};
diff --git a/src/index.js b/src/index.js
index 42de34564..1cd14b654 100644
--- a/src/index.js
+++ b/src/index.js
@@ -26,8 +26,8 @@ module.exports = {
// Stores
ChannelStore: require('./stores/ChannelStore'),
ClientPresenceStore: require('./stores/ClientPresenceStore'),
- EmojiStore: require('./stores/EmojiStore'),
GuildChannelStore: require('./stores/GuildChannelStore'),
+ GuildEmojiStore: require('./stores/GuildEmojiStore'),
GuildMemberStore: require('./stores/GuildMemberStore'),
GuildStore: require('./stores/GuildStore'),
ReactionUserStore: require('./stores/ReactionUserStore'),
@@ -64,6 +64,7 @@ module.exports = {
Guild: require('./structures/Guild'),
GuildAuditLogs: require('./structures/GuildAuditLogs'),
GuildChannel: require('./structures/GuildChannel'),
+ GuildEmoji: require('./structures/GuildEmoji'),
GuildMember: require('./structures/GuildMember'),
Invite: require('./structures/Invite'),
Message: require('./structures/Message'),
diff --git a/src/stores/EmojiStore.js b/src/stores/GuildEmojiStore.js
similarity index 87%
rename from src/stores/EmojiStore.js
rename to src/stores/GuildEmojiStore.js
index 1e50c07f4..0fc4cc60b 100644
--- a/src/stores/EmojiStore.js
+++ b/src/stores/GuildEmojiStore.js
@@ -1,17 +1,17 @@
const Collection = require('../util/Collection');
const DataStore = require('./DataStore');
-const Emoji = require('../structures/Emoji');
+const GuildEmoji = require('../structures/GuildEmoji');
const ReactionEmoji = require('../structures/ReactionEmoji');
const DataResolver = require('../util/DataResolver');
/**
- * Stores emojis.
+ * Stores guild emojis.
* @private
* @extends {DataStore}
*/
-class EmojiStore extends DataStore {
+class GuildEmojiStore extends DataStore {
constructor(guild, iterable) {
- super(guild.client, iterable, Emoji);
+ super(guild.client, iterable, GuildEmoji);
this.guild = guild;
}
@@ -61,17 +61,17 @@ class EmojiStore extends DataStore {
}
/**
- * Data that can be resolved into an Emoji object. This can be:
+ * Data that can be resolved into an GuildEmoji object. This can be:
* * A custom emoji ID
- * * An Emoji object
+ * * A GuildEmoji object
* * A ReactionEmoji object
- * @typedef {Snowflake|Emoji|ReactionEmoji} EmojiResolvable
+ * @typedef {Snowflake|GuildEmoji|ReactionEmoji} EmojiResolvable
*/
/**
- * Resolves a EmojiResolvable to a Emoji object.
+ * Resolves an EmojiResolvable to an Emoji object.
* @param {EmojiResolvable} emoji The Emoji resolvable to identify
- * @returns {?Emoji}
+ * @returns {?GuildEmoji}
*/
resolve(emoji) {
if (emoji instanceof ReactionEmoji) return super.resolve(emoji.id);
@@ -79,7 +79,7 @@ class EmojiStore extends DataStore {
}
/**
- * Resolves a EmojiResolvable to a Emoji ID string.
+ * Resolves an EmojiResolvable to an Emoji ID string.
* @param {EmojiResolvable} emoji The Emoji resolvable to identify
* @returns {?Snowflake}
*/
@@ -111,4 +111,4 @@ class EmojiStore extends DataStore {
}
}
-module.exports = EmojiStore;
+module.exports = GuildEmojiStore;
diff --git a/src/structures/Emoji.js b/src/structures/Emoji.js
index 302ebfccd..f5682046f 100644
--- a/src/structures/Emoji.js
+++ b/src/structures/Emoji.js
@@ -1,97 +1,29 @@
-const Collection = require('../util/Collection');
-const Snowflake = require('../util/Snowflake');
const Base = require('./Base');
-const { TypeError } = require('../errors');
/**
- * Represents a custom emoji.
+ * Represents an emoji, see {@link GuildEmoji} and {@link ReactionEmoji}.
* @extends {Base}
*/
class Emoji extends Base {
- constructor(client, data, guild) {
+ constructor(client, emoji) {
super(client);
-
- /**
- * The guild this emoji is part of
- * @type {Guild}
- */
- this.guild = guild;
-
- this._patch(data);
- }
-
- _patch(data) {
- /**
- * The ID of the emoji
- * @type {Snowflake}
- */
- this.id = data.id;
-
- /**
- * The name of the emoji
- * @type {string}
- */
- this.name = data.name;
-
- /**
- * Whether or not this emoji requires colons surrounding it
- * @type {boolean}
- */
- this.requiresColons = data.require_colons;
-
- /**
- * Whether this emoji is managed by an external service
- * @type {boolean}
- */
- this.managed = data.managed;
-
/**
* Whether this emoji is animated
* @type {boolean}
*/
- this.animated = data.animated;
+ this.animated = emoji.animated;
- this._roles = data.roles;
- }
+ /**
+ * The name of this emoji
+ * @type {string}
+ */
+ this.name = emoji.name;
- /**
- * The timestamp the emoji was created at
- * @type {number}
- * @readonly
- */
- get createdTimestamp() {
- return Snowflake.deconstruct(this.id).timestamp;
- }
-
- /**
- * The time the emoji was created at
- * @type {Date}
- * @readonly
- */
- get createdAt() {
- return new Date(this.createdTimestamp);
- }
-
- /**
- * A collection of roles this emoji is active for (empty if all), mapped by role ID
- * @type {Collection}
- * @readonly
- */
- get roles() {
- const roles = new Collection();
- for (const role of this._roles) {
- if (this.guild.roles.has(role)) roles.set(role, this.guild.roles.get(role));
- }
- return roles;
- }
-
- /**
- * The URL to the emoji file
- * @type {string}
- * @readonly
- */
- get url() {
- return this.client.rest.cdn.Emoji(this.id, this.animated ? 'gif' : 'png');
+ /**
+ * The ID of this emoji
+ * @type {?Snowflake}
+ */
+ this.id = emoji.id;
}
/**
@@ -100,148 +32,34 @@ class Emoji extends Base {
* @readonly
*/
get identifier() {
- if (this.id) return `${this.name}:${this.id}`;
+ if (this.id) return `${this.animated ? 'a:' : ''}${this.name}:${this.id}`;
return encodeURIComponent(this.name);
}
/**
- * Data for editing an emoji.
- * @typedef {Object} EmojiEditData
- * @property {string} [name] The name of the emoji
- * @property {Collection|RoleResolvable[]} [roles] Roles to restrict emoji to
+ * The URL to the emoji file if its a custom emoji
+ * @type {?string}
+ * @readonly
*/
-
- /**
- * Edits the emoji.
- * @param {EmojiEditData} data The new data for the emoji
- * @param {string} [reason] Reason for editing this emoji
- * @returns {Promise}
- * @example
- * // Edit an emoji
- * emoji.edit({name: 'newemoji'})
- * .then(e => console.log(`Edited emoji ${e}`))
- * .catch(console.error);
- */
- edit(data, reason) {
- return this.client.api.guilds(this.guild.id).emojis(this.id)
- .patch({ data: {
- name: data.name,
- roles: data.roles ? data.roles.map(r => r.id ? r.id : r) : undefined,
- }, reason })
- .then(() => this);
+ get url() {
+ if (!this.id) return null;
+ return this.client.rest.cdn.Emoji(this.id, this.animated ? 'gif' : 'png');
}
/**
- * Sets the name of the emoji.
- * @param {string} name The new name for the emoji
- * @param {string} [reason] Reason for changing the emoji's name
- * @returns {Promise}
- */
- setName(name, reason) {
- return this.edit({ name }, reason);
- }
-
- /**
- * Adds a role to the list of roles that can use this emoji.
- * @param {Role} role The role to add
- * @returns {Promise}
- */
- addRestrictedRole(role) {
- return this.addRestrictedRoles([role]);
- }
-
- /**
- * Adds multiple roles to the list of roles that can use this emoji.
- * @param {Collection|RoleResolvable[]} roles Roles to add
- * @returns {Promise}
- */
- addRestrictedRoles(roles) {
- const newRoles = new Collection(this.roles);
- for (let role of roles instanceof Collection ? roles.values() : roles) {
- role = this.guild.roles.resolve(role);
- if (!role) {
- return Promise.reject(new TypeError('INVALID_TYPE', 'roles',
- 'Array or Collection of Roles or Snowflakes', true));
- }
- newRoles.set(role.id, role);
- }
- return this.edit({ roles: newRoles });
- }
-
- /**
- * Removes a role from the list of roles that can use this emoji.
- * @param {Role} role The role to remove
- * @returns {Promise}
- */
- removeRestrictedRole(role) {
- return this.removeRestrictedRoles([role]);
- }
-
- /**
- * Removes multiple roles from the list of roles that can use this emoji.
- * @param {Collection|RoleResolvable[]} roles Roles to remove
- * @returns {Promise}
- */
- removeRestrictedRoles(roles) {
- const newRoles = new Collection(this.roles);
- for (let role of roles instanceof Collection ? roles.values() : roles) {
- role = this.guild.roles.resolve(role);
- if (!role) {
- return Promise.reject(new TypeError('INVALID_TYPE', 'roles',
- 'Array or Collection of Roles or Snowflakes', true));
- }
- if (newRoles.has(role.id)) newRoles.delete(role.id);
- }
- return this.edit({ roles: newRoles });
- }
-
- /**
- * When concatenated with a string, this automatically concatenates the emoji's mention instead of the Emoji object.
+ * When concatenated with a string, this automatically returns the text required to form a graphical emoji on Discord
+ * instead of the Emoji object.
* @returns {string}
* @example
- * // Send an emoji:
+ * // Send a custom emoji from a guild:
* const emoji = guild.emojis.first();
* msg.reply(`Hello! ${emoji}`);
+ * @example
+ * // Send the emoji used in a reaction to the channel the reaction is part of
+ * reaction.message.channel.send(`The emoji used was: ${reaction.emoji}`);
*/
toString() {
- if (!this.id || !this.requiresColons) {
- return this.name;
- }
-
- return `<${this.animated ? 'a' : ''}:${this.name}:${this.id}>`;
- }
-
- /**
- * Deletes the emoji.
- * @param {string} [reason] Reason for deleting the emoji
- * @returns {Promise}
- */
- delete(reason) {
- return this.client.api.guilds(this.guild.id).emojis(this.id).delete({ reason })
- .then(() => this);
- }
-
- /**
- * Whether this emoji is the same as another one.
- * @param {Emoji|Object} other The emoji to compare it to
- * @returns {boolean} Whether the emoji is equal to the given emoji or not
- */
- equals(other) {
- if (other instanceof Emoji) {
- return (
- other.id === this.id &&
- other.name === this.name &&
- other.managed === this.managed &&
- other.requiresColons === this.requiresColons &&
- other._roles === this._roles
- );
- } else {
- return (
- other.id === this.id &&
- other.name === this.name &&
- other._roles === this._roles
- );
- }
+ return this.id ? `<${this.animated ? 'a' : ''}:${this.name}:${this.id}>` : this.name;
}
}
diff --git a/src/structures/Guild.js b/src/structures/Guild.js
index a75829313..51ea81d32 100644
--- a/src/structures/Guild.js
+++ b/src/structures/Guild.js
@@ -10,7 +10,7 @@ const Snowflake = require('../util/Snowflake');
const Shared = require('./shared');
const GuildMemberStore = require('../stores/GuildMemberStore');
const RoleStore = require('../stores/RoleStore');
-const EmojiStore = require('../stores/EmojiStore');
+const GuildEmojiStore = require('../stores/GuildEmojiStore');
const GuildChannelStore = require('../stores/GuildChannelStore');
const PresenceStore = require('../stores/PresenceStore');
const Base = require('./Base');
@@ -218,9 +218,9 @@ class Guild extends Base {
if (!this.emojis) {
/**
* A collection of emojis that are in this guild. The key is the emoji's ID, the value is the emoji.
- * @type {EmojiStore}
+ * @type {GuildEmojiStore}
*/
- this.emojis = new EmojiStore(this);
+ this.emojis = new GuildEmojiStore(this);
if (data.emojis) for (const emoji of data.emojis) this.emojis.add(emoji);
} else {
this.client.actions.GuildEmojisUpdate.handle({
diff --git a/src/structures/GuildAuditLogs.js b/src/structures/GuildAuditLogs.js
index 148e199a9..6df77707e 100644
--- a/src/structures/GuildAuditLogs.js
+++ b/src/structures/GuildAuditLogs.js
@@ -148,7 +148,7 @@ class GuildAuditLogs {
* * An invite
* * A webhook
* * An object where the keys represent either the new value or the old value
- * @typedef {?Object|Guild|User|Role|Emoji|Invite|Webhook} AuditLogEntryTarget
+ * @typedef {?Object|Guild|User|Role|GuildEmoji|Invite|Webhook} AuditLogEntryTarget
*/
/**
diff --git a/src/structures/GuildChannel.js b/src/structures/GuildChannel.js
index 198adcd2c..c4d4c7bbf 100644
--- a/src/structures/GuildChannel.js
+++ b/src/structures/GuildChannel.js
@@ -92,31 +92,16 @@ class GuildChannel extends Channel {
}
/**
- * Gets the overall set of permissions for a user in this channel, taking into account roles and permission
- * overwrites.
- * @param {GuildMemberResolvable} member The user that you want to obtain the overall permissions for
+ * Gets the overall set of permissions for a member or role in this channel, taking into account channel overwrites.
+ * @param {GuildMemberResolvable|RoleResolvable} memberOrRole The member or role to obtain the overall permissions for
* @returns {?Permissions}
*/
- permissionsFor(member) {
- member = this.guild.members.resolve(member);
- if (!member) return null;
- if (member.id === this.guild.ownerID) return new Permissions(Permissions.ALL).freeze();
-
- const roles = member.roles;
- const permissions = new Permissions(roles.map(role => role.permissions));
-
- if (permissions.has(Permissions.FLAGS.ADMINISTRATOR)) return new Permissions(Permissions.ALL).freeze();
-
- const overwrites = this.overwritesFor(member, true, roles);
-
- return permissions
- .remove(overwrites.everyone ? overwrites.everyone.denied : 0)
- .add(overwrites.everyone ? overwrites.everyone.allowed : 0)
- .remove(overwrites.roles.length > 0 ? overwrites.roles.map(role => role.denied) : 0)
- .add(overwrites.roles.length > 0 ? overwrites.roles.map(role => role.allowed) : 0)
- .remove(overwrites.member ? overwrites.member.denied : 0)
- .add(overwrites.member ? overwrites.member.allowed : 0)
- .freeze();
+ permissionsFor(memberOrRole) {
+ const member = this.guild.members.resolve(memberOrRole);
+ if (member) return this.memberPermissions(member);
+ const role = this.guild.roles.resolve(memberOrRole);
+ if (role) return this.rolePermissions(role);
+ return null;
}
overwritesFor(member, verified = false, roles = null) {
@@ -145,6 +130,52 @@ class GuildChannel extends Channel {
};
}
+ /**
+ * Gets the overall set of permissions for a member in this channel, taking into account channel overwrites.
+ * @param {GuildMember} member The member to obtain the overall permissions for
+ * @returns {Permissions}
+ * @private
+ */
+ memberPermissions(member) {
+ if (member.id === this.guild.ownerID) return new Permissions(Permissions.ALL).freeze();
+
+ const roles = member.roles;
+ const permissions = new Permissions(roles.map(role => role.permissions));
+
+ if (permissions.has(Permissions.FLAGS.ADMINISTRATOR)) return new Permissions(Permissions.ALL).freeze();
+
+ const overwrites = this.overwritesFor(member, true, roles);
+
+ return permissions
+ .remove(overwrites.everyone ? overwrites.everyone.denied : 0)
+ .add(overwrites.everyone ? overwrites.everyone.allowed : 0)
+ .remove(overwrites.roles.length > 0 ? overwrites.roles.map(role => role.denied) : 0)
+ .add(overwrites.roles.length > 0 ? overwrites.roles.map(role => role.allowed) : 0)
+ .remove(overwrites.member ? overwrites.member.denied : 0)
+ .add(overwrites.member ? overwrites.member.allowed : 0)
+ .freeze();
+ }
+
+ /**
+ * Gets the overall set of permissions for a role in this channel, taking into account channel overwrites.
+ * @param {Role} role The role to obtain the overall permissions for
+ * @returns {Permissions}
+ * @private
+ */
+ rolePermissions(role) {
+ if (role.permissions.has(Permissions.FLAGS.ADMINISTRATOR)) return new Permissions(Permissions.ALL).freeze();
+
+ const everyoneOverwrites = this.permissionOverwrites.get(this.guild.id);
+ const roleOverwrites = this.permissionOverwrites.get(role.id);
+
+ return role.permissions
+ .remove(everyoneOverwrites ? everyoneOverwrites.denied : 0)
+ .add(everyoneOverwrites ? everyoneOverwrites.allowed : 0)
+ .remove(roleOverwrites ? roleOverwrites.denied : 0)
+ .add(roleOverwrites ? roleOverwrites.allowed : 0)
+ .freeze();
+ }
+
/**
* An object mapping permission flags to `true` (enabled), `null` (default) or `false` (disabled).
* ```js
diff --git a/src/structures/GuildEmoji.js b/src/structures/GuildEmoji.js
new file mode 100644
index 000000000..6bf63152d
--- /dev/null
+++ b/src/structures/GuildEmoji.js
@@ -0,0 +1,197 @@
+const Collection = require('../util/Collection');
+const Snowflake = require('../util/Snowflake');
+const Emoji = require('./Emoji');
+const { TypeError } = require('../errors');
+
+/**
+ * Represents a custom emoji.
+ * @extends {Emoji}
+ */
+class GuildEmoji extends Emoji {
+ constructor(client, data, guild) {
+ super(client, data);
+
+ /**
+ * The guild this emoji is part of
+ * @type {Guild}
+ */
+ this.guild = guild;
+
+ this._patch(data);
+ }
+
+ _patch(data) {
+ this.name = data.name;
+
+ /**
+ * Whether or not this emoji requires colons surrounding it
+ * @type {boolean}
+ */
+ this.requiresColons = data.require_colons;
+
+ /**
+ * Whether this emoji is managed by an external service
+ * @type {boolean}
+ */
+ this.managed = data.managed;
+
+ this._roles = data.roles;
+ }
+
+ /**
+ * The timestamp the emoji was created at
+ * @type {number}
+ * @readonly
+ */
+ get createdTimestamp() {
+ return Snowflake.deconstruct(this.id).timestamp;
+ }
+
+ /**
+ * The time the emoji was created at
+ * @type {Date}
+ * @readonly
+ */
+ get createdAt() {
+ return new Date(this.createdTimestamp);
+ }
+
+ /**
+ * A collection of roles this emoji is active for (empty if all), mapped by role ID
+ * @type {Collection}
+ * @readonly
+ */
+ get roles() {
+ const roles = new Collection();
+ for (const role of this._roles) {
+ if (this.guild.roles.has(role)) roles.set(role, this.guild.roles.get(role));
+ }
+ return roles;
+ }
+
+ /**
+ * Data for editing an emoji.
+ * @typedef {Object} GuildEmojiEditData
+ * @property {string} [name] The name of the emoji
+ * @property {Collection|RoleResolvable[]} [roles] Roles to restrict emoji to
+ */
+
+ /**
+ * Edits the emoji.
+ * @param {Guild} data The new data for the emoji
+ * @param {string} [reason] Reason for editing this emoji
+ * @returns {Promise}
+ * @example
+ * // Edit an emoji
+ * emoji.edit({name: 'newemoji'})
+ * .then(e => console.log(`Edited emoji ${e}`))
+ * .catch(console.error);
+ */
+ edit(data, reason) {
+ return this.client.api.guilds(this.guild.id).emojis(this.id)
+ .patch({ data: {
+ name: data.name,
+ roles: data.roles ? data.roles.map(r => r.id ? r.id : r) : undefined,
+ }, reason })
+ .then(() => this);
+ }
+
+ /**
+ * Sets the name of the emoji.
+ * @param {string} name The new name for the emoji
+ * @param {string} [reason] Reason for changing the emoji's name
+ * @returns {Promise}
+ */
+ setName(name, reason) {
+ return this.edit({ name }, reason);
+ }
+
+ /**
+ * Adds a role to the list of roles that can use this emoji.
+ * @param {Role} role The role to add
+ * @returns {Promise}
+ */
+ addRestrictedRole(role) {
+ return this.addRestrictedRoles([role]);
+ }
+
+ /**
+ * Adds multiple roles to the list of roles that can use this emoji.
+ * @param {Collection|RoleResolvable[]} roles Roles to add
+ * @returns {Promise}
+ */
+ addRestrictedRoles(roles) {
+ const newRoles = new Collection(this.roles);
+ for (let role of roles instanceof Collection ? roles.values() : roles) {
+ role = this.guild.roles.resolve(role);
+ if (!role) {
+ return Promise.reject(new TypeError('INVALID_TYPE', 'roles',
+ 'Array or Collection of Roles or Snowflakes', true));
+ }
+ newRoles.set(role.id, role);
+ }
+ return this.edit({ roles: newRoles });
+ }
+
+ /**
+ * Removes a role from the list of roles that can use this emoji.
+ * @param {Role} role The role to remove
+ * @returns {Promise}
+ */
+ removeRestrictedRole(role) {
+ return this.removeRestrictedRoles([role]);
+ }
+
+ /**
+ * Removes multiple roles from the list of roles that can use this emoji.
+ * @param {Collection|RoleResolvable[]} roles Roles to remove
+ * @returns {Promise}
+ */
+ removeRestrictedRoles(roles) {
+ const newRoles = new Collection(this.roles);
+ for (let role of roles instanceof Collection ? roles.values() : roles) {
+ role = this.guild.roles.resolve(role);
+ if (!role) {
+ return Promise.reject(new TypeError('INVALID_TYPE', 'roles',
+ 'Array or Collection of Roles or Snowflakes', true));
+ }
+ if (newRoles.has(role.id)) newRoles.delete(role.id);
+ }
+ return this.edit({ roles: newRoles });
+ }
+
+ /**
+ * Deletes the emoji.
+ * @param {string} [reason] Reason for deleting the emoji
+ * @returns {Promise}
+ */
+ delete(reason) {
+ return this.client.api.guilds(this.guild.id).emojis(this.id).delete({ reason })
+ .then(() => this);
+ }
+
+ /**
+ * Whether this emoji is the same as another one.
+ * @param {GuildEmoji|Object} other The emoji to compare it to
+ * @returns {boolean} Whether the emoji is equal to the given emoji or not
+ */
+ equals(other) {
+ if (other instanceof GuildEmoji) {
+ return (
+ other.id === this.id &&
+ other.name === this.name &&
+ other.managed === this.managed &&
+ other.requiresColons === this.requiresColons &&
+ other._roles === this._roles
+ );
+ } else {
+ return (
+ other.id === this.id &&
+ other.name === this.name &&
+ other._roles === this._roles
+ );
+ }
+ }
+}
+
+module.exports = GuildEmoji;
diff --git a/src/structures/GuildMember.js b/src/structures/GuildMember.js
index c7384d875..08561c757 100644
--- a/src/structures/GuildMember.js
+++ b/src/structures/GuildMember.js
@@ -295,9 +295,9 @@ class GuildMember extends Base {
* @returns {?Permissions}
*/
permissionsIn(channel) {
- channel = this.client.channels.resolve(channel);
- if (!channel || !channel.guild) throw new Error('GUILD_CHANNEL_RESOLVE');
- return channel.permissionsFor(this);
+ channel = this.guild.channels.resolve(channel);
+ if (!channel) throw new Error('GUILD_CHANNEL_RESOLVE');
+ return channel.memberPermissions(this);
}
/**
diff --git a/src/structures/MessageEmbed.js b/src/structures/MessageEmbed.js
index 9c8271372..de3ceddf7 100644
--- a/src/structures/MessageEmbed.js
+++ b/src/structures/MessageEmbed.js
@@ -193,7 +193,7 @@ class MessageEmbed {
/**
* Sets the file to upload alongside the embed. This file can be accessed via `attachment://fileName.extension` when
- * setting an embed image or author/footer icons. Only one file may be attached.
+ * setting an embed image or author/footer icons. Multiple files can be attached.
* @param {Array} files Files to attach
* @returns {MessageEmbed}
*/
diff --git a/src/structures/MessageReaction.js b/src/structures/MessageReaction.js
index b65721134..660967f0b 100644
--- a/src/structures/MessageReaction.js
+++ b/src/structures/MessageReaction.js
@@ -1,4 +1,4 @@
-const Emoji = require('./Emoji');
+const GuildEmoji = require('./GuildEmoji');
const ReactionEmoji = require('./ReactionEmoji');
const ReactionUserStore = require('../stores/ReactionUserStore');
@@ -31,18 +31,18 @@ class MessageReaction {
*/
this.users = new ReactionUserStore(client, undefined, this);
- this._emoji = new ReactionEmoji(this, data.emoji.name, data.emoji.id);
+ this._emoji = new ReactionEmoji(this, data.emoji);
}
/**
- * The emoji of this reaction, either an Emoji object for known custom emojis, or a ReactionEmoji
+ * The emoji of this reaction, either an GuildEmoji object for known custom emojis, or a ReactionEmoji
* object which has fewer properties. Whatever the prototype of the emoji, it will still have
* `name`, `id`, `identifier` and `toString()`
- * @type {Emoji|ReactionEmoji}
+ * @type {GuildEmoji|ReactionEmoji}
* @readonly
*/
get emoji() {
- if (this._emoji instanceof Emoji) return this._emoji;
+ if (this._emoji instanceof GuildEmoji) return this._emoji;
// Check to see if the emoji has become known to the client
if (this._emoji.id) {
const emojis = this.message.client.emojis;
diff --git a/src/structures/ReactionEmoji.js b/src/structures/ReactionEmoji.js
index 94ea38930..9bb23c120 100644
--- a/src/structures/ReactionEmoji.js
+++ b/src/structures/ReactionEmoji.js
@@ -1,49 +1,19 @@
+const Emoji = require('./Emoji');
+
/**
* Represents a limited emoji set used for both custom and unicode emojis. Custom emojis
* will use this class opposed to the Emoji class when the client doesn't know enough
* information about them.
+ * @extends {Emoji}
*/
-class ReactionEmoji {
- constructor(reaction, name, id) {
+class ReactionEmoji extends Emoji {
+ constructor(reaction, emoji) {
+ super(reaction.message.client, emoji);
/**
* The message reaction this emoji refers to
* @type {MessageReaction}
*/
this.reaction = reaction;
-
- /**
- * The name of this reaction emoji
- * @type {string}
- */
- this.name = name;
-
- /**
- * The ID of this reaction emoji
- * @type {?Snowflake}
- */
- this.id = id;
- }
-
- /**
- * The identifier of this emoji, used for message reactions
- * @type {string}
- * @readonly
- */
- get identifier() {
- if (this.id) return `${this.name}:${this.id}`;
- return encodeURIComponent(this.name);
- }
-
- /**
- * When concatenated with a string, this automatically returns the text required to form a graphical emoji on Discord
- * instead of the ReactionEmoji object.
- * @returns {string}
- * @example
- * // Send the emoji used in a reaction to the channel the reaction is part of
- * reaction.message.channel.send(`The emoji used was: ${reaction.emoji}`);
- */
- toString() {
- return this.id ? `<:${this.name}:${this.id}>` : this.name;
}
}
diff --git a/src/structures/Role.js b/src/structures/Role.js
index 39c28e11b..1d82f63ba 100644
--- a/src/structures/Role.js
+++ b/src/structures/Role.js
@@ -195,6 +195,18 @@ class Role extends Base {
});
}
+ /**
+ * Returns `channel.permissionsFor(role)`. Returns permissions for a role in a guild channel,
+ * taking into account permission overwrites.
+ * @param {ChannelResolvable} channel The guild channel to use as context
+ * @returns {?Permissions}
+ */
+ permissionsIn(channel) {
+ channel = this.guild.channels.resolve(channel);
+ if (!channel) throw new Error('GUILD_CHANNEL_RESOLVE');
+ return channel.rolePermissions(this);
+ }
+
/**
* Sets a new name for the role.
* @param {string} name The new name of the role
diff --git a/src/util/Permissions.js b/src/util/Permissions.js
index 7ccd9009c..e9ef9d1c5 100644
--- a/src/util/Permissions.js
+++ b/src/util/Permissions.js
@@ -7,19 +7,19 @@ const { RangeError } = require('../errors');
*/
class Permissions {
/**
- * @param {number|PermissionResolvable[]} permissions Permissions or bitfield to read from
+ * @param {PermissionResolvable} permissions Permission(s) to read from
*/
constructor(permissions) {
/**
* Bitfield of the packed permissions
* @type {number}
*/
- this.bitfield = typeof permissions === 'number' ? permissions : this.constructor.resolve(permissions);
+ this.bitfield = this.constructor.resolve(permissions);
}
/**
* Checks whether the bitfield has a permission, or multiple permissions.
- * @param {PermissionResolvable|PermissionResolvable[]} permission Permission(s) to check for
+ * @param {PermissionResolvable} permission Permission(s) to check for
* @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override
* @returns {boolean}
*/
@@ -32,11 +32,12 @@ class Permissions {
/**
* Gets all given permissions that are missing from the bitfield.
- * @param {PermissionResolvable[]} permissions Permissions to check for
+ * @param {PermissionResolvable} permissions Permission(s) to check for
* @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override
- * @returns {PermissionResolvable[]}
+ * @returns {string[]}
*/
missing(permissions, checkAdmin = true) {
+ if (!(permissions instanceof Array)) permissions = new this.constructor(permissions).toArray(false);
return permissions.filter(p => !this.has(p, checkAdmin));
}
@@ -92,17 +93,32 @@ class Permissions {
return serialized;
}
+ /**
+ * Gets an {@link Array} of permission names (such as `VIEW_CHANNEL`) based on the permissions available.
+ * @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override
+ * @returns {string[]}
+ */
+ toArray(checkAdmin = true) {
+ return Object.keys(this.constructor.FLAGS).filter(perm => this.has(perm, checkAdmin));
+ }
+
+ *[Symbol.iterator]() {
+ const keys = this.toArray();
+ while (keys.length) yield keys.shift();
+ }
+
/**
* Data that can be resolved to give a permission number. This can be:
* * A string (see {@link Permissions.FLAGS})
* * A permission number
* * An instance of Permissions
- * @typedef {string|number|Permissions} PermissionResolvable
+ * * An Array of PermissionResolvable
+ * @typedef {string|number|Permissions|PermissionResolvable[]} PermissionResolvable
*/
/**
* Resolves permissions to their numeric form.
- * @param {PermissionResolvable|PermissionResolvable[]} permission - Permission(s) to resolve
+ * @param {PermissionResolvable} permission - Permission(s) to resolve
* @returns {number}
*/
static resolve(permission) {
diff --git a/src/util/Structures.js b/src/util/Structures.js
index a1cb7e156..e7f615c79 100644
--- a/src/util/Structures.js
+++ b/src/util/Structures.js
@@ -61,7 +61,7 @@ class Structures {
}
const structures = {
- Emoji: require('../structures/Emoji'),
+ GuildEmoji: require('../structures/GuildEmoji'),
DMChannel: require('../structures/DMChannel'),
GroupDMChannel: require('../structures/GroupDMChannel'),
TextChannel: require('../structures/TextChannel'),
diff --git a/typings b/typings
index 0b5b13f4a..895af7f3d 160000
--- a/typings
+++ b/typings
@@ -1 +1 @@
-Subproject commit 0b5b13f4a521cba0fc42aa0f9b2c4a1abca2de3d
+Subproject commit 895af7f3dad233139b8246fe0e44079867e6cc95