Merge branch 'master' into voice-rewrite

This commit is contained in:
Schuyler Cebulskie
2018-01-18 19:49:23 -05:00
28 changed files with 404 additions and 335 deletions

View File

@@ -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` 3. If you're working on voice, also run `npm install node-opus` or `npm install opusscript`
4. Code your heart out! 4. Code your heart out!
5. Run `npm test` to run ESLint and ensure any JSDoc changes are valid 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)

2
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "typings"] [submodule "typings"]
path = typings path = typings
url = https://github.com/zajrik/discord.js-typings url = https://github.com/discordjs/discord.js-typings

View File

@@ -8,8 +8,8 @@
<a href="https://discord.gg/bRCvFy9"><img src="https://discordapp.com/api/guilds/222078108977594368/embed.png" alt="Discord server" /></a> <a href="https://discord.gg/bRCvFy9"><img src="https://discordapp.com/api/guilds/222078108977594368/embed.png" alt="Discord server" /></a>
<a href="https://www.npmjs.com/package/discord.js"><img src="https://img.shields.io/npm/v/discord.js.svg?maxAge=3600" alt="NPM version" /></a> <a href="https://www.npmjs.com/package/discord.js"><img src="https://img.shields.io/npm/v/discord.js.svg?maxAge=3600" alt="NPM version" /></a>
<a href="https://www.npmjs.com/package/discord.js"><img src="https://img.shields.io/npm/dt/discord.js.svg?maxAge=3600" alt="NPM downloads" /></a> <a href="https://www.npmjs.com/package/discord.js"><img src="https://img.shields.io/npm/dt/discord.js.svg?maxAge=3600" alt="NPM downloads" /></a>
<a href="https://travis-ci.org/hydrabolt/discord.js"><img src="https://travis-ci.org/hydrabolt/discord.js.svg" alt="Build status" /></a> <a href="https://travis-ci.org/discordjs/discord.js"><img src="https://travis-ci.org/discordjs/discord.js.svg" alt="Build status" /></a>
<a href="https://david-dm.org/hydrabolt/discord.js"><img src="https://img.shields.io/david/hydrabolt/discord.js.svg?maxAge=3600" alt="Dependencies" /></a> <a href="https://david-dm.org/discordjs/discord.js"><img src="https://img.shields.io/david/discordjs/discord.js.svg?maxAge=3600" alt="Dependencies" /></a>
<a href="https://www.patreon.com/discordjs"><img src="https://img.shields.io/badge/donate-patreon-F96854.svg" alt="Patreon" /></a> <a href="https://www.patreon.com/discordjs"><img src="https://img.shields.io/badge/donate-patreon-F96854.svg" alt="Patreon" /></a>
</p> </p>
<p> <p>
@@ -67,21 +67,21 @@ client.login('your token');
``` ```
## Links ## 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) * [Documentation](https://discord.js.org/#/docs)
* [Discord.js Discord server](https://discord.gg/bRCvFy9) * [Discord.js Discord server](https://discord.gg/bRCvFy9)
* [Discord API Discord server](https://discord.gg/discord-api) * [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) * [NPM](https://www.npmjs.com/package/discord.js)
* [Related libraries](https://discordapi.com/unofficial/libs.html) * [Related libraries](https://discordapi.com/unofficial/libs.html)
### Extensions ### 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 ## Contributing
Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the
[documentation](https://discord.js.org/#/docs). [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 ## Help
If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle

View File

@@ -1,11 +1,11 @@
# Version 11.1.0 # 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. 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. information about deprecations.
# Version 11 # Version 11
Version 11 contains loads of new and improved features, optimisations, and bug fixes. 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 ## Significant additions
* Message Reactions and Embeds (rich text) * Message Reactions and Embeds (rich text)

View File

@@ -8,8 +8,8 @@
<a href="https://discord.gg/bRCvFy9"><img src="https://discordapp.com/api/guilds/222078108977594368/embed.png" alt="Discord server" /></a> <a href="https://discord.gg/bRCvFy9"><img src="https://discordapp.com/api/guilds/222078108977594368/embed.png" alt="Discord server" /></a>
<a href="https://www.npmjs.com/package/discord.js"><img src="https://img.shields.io/npm/v/discord.js.svg?maxAge=3600" alt="NPM version" /></a> <a href="https://www.npmjs.com/package/discord.js"><img src="https://img.shields.io/npm/v/discord.js.svg?maxAge=3600" alt="NPM version" /></a>
<a href="https://www.npmjs.com/package/discord.js"><img src="https://img.shields.io/npm/dt/discord.js.svg?maxAge=3600" alt="NPM downloads" /></a> <a href="https://www.npmjs.com/package/discord.js"><img src="https://img.shields.io/npm/dt/discord.js.svg?maxAge=3600" alt="NPM downloads" /></a>
<a href="https://travis-ci.org/hydrabolt/discord.js"><img src="https://travis-ci.org/hydrabolt/discord.js.svg" alt="Build status" /></a> <a href="https://travis-ci.org/discordjs/discord.js"><img src="https://travis-ci.org/discordjs/discord.js.svg" alt="Build status" /></a>
<a href="https://david-dm.org/hydrabolt/discord.js"><img src="https://img.shields.io/david/hydrabolt/discord.js.svg?maxAge=3600" alt="Dependencies" /></a> <a href="https://david-dm.org/discordjs/discord.js"><img src="https://img.shields.io/david/discordjs/discord.js.svg?maxAge=3600" alt="Dependencies" /></a>
</p> </p>
<p> <p>
<a href="https://nodei.co/npm/discord.js/"><img src="https://nodei.co/npm/discord.js.png?downloads=true&stars=true" alt="NPM info" /></a> <a href="https://nodei.co/npm/discord.js/"><img src="https://nodei.co/npm/discord.js.png?downloads=true&stars=true" alt="NPM info" /></a>
@@ -68,18 +68,18 @@ client.login('your token');
``` ```
## Links ## 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) * [Documentation](https://discord.js.org/#/docs)
* [Discord.js server](https://discord.gg/bRCvFy9) * [Discord.js server](https://discord.gg/bRCvFy9)
* [Discord API server](https://discord.gg/rV4BwdK) * [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) * [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)) * [Related libraries](https://discordapi.com/unofficial/libs.html) (see also [discord-rpc](https://www.npmjs.com/package/discord-rpc))
## Contributing ## Contributing
Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the
[documentation](https://discord.js.org/#/docs). [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 ## Help
If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle

View File

@@ -17,7 +17,7 @@ const Discord = require('discord.js/browser');
``` ```
### Webpack File ### 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. 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: Include the file on the page just as you would any other JS library, like so:

View File

@@ -14,7 +14,7 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/hydrabolt/discord.js.git" "url": "git+https://github.com/discordjs/discord.js.git"
}, },
"keywords": [ "keywords": [
"discord", "discord",
@@ -27,9 +27,9 @@
"author": "Amish Shah <amishshah.2k@gmail.com>", "author": "Amish Shah <amishshah.2k@gmail.com>",
"license": "Apache-2.0", "license": "Apache-2.0",
"bugs": { "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", "runkitExampleFilename": "./docs/examples/ping.js",
"dependencies": { "dependencies": {
"pako": "^1.0.0", "pako": "^1.0.0",
@@ -48,7 +48,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^8.0.0", "@types/node": "^8.0.0",
"discord.js-docgen": "hydrabolt/discord.js-docgen", "discord.js-docgen": "discordjs/docgen",
"eslint": "^4.11.0", "eslint": "^4.11.0",
"jsdoc-strip-async-await": "^0.1.0", "jsdoc-strip-async-await": "^0.1.0",
"json-filter-loader": "^1.0.0", "json-filter-loader": "^1.0.0",

View File

@@ -15,7 +15,7 @@ const UserStore = require('../stores/UserStore');
const ChannelStore = require('../stores/ChannelStore'); const ChannelStore = require('../stores/ChannelStore');
const GuildStore = require('../stores/GuildStore'); const GuildStore = require('../stores/GuildStore');
const ClientPresenceStore = require('../stores/ClientPresenceStore'); const ClientPresenceStore = require('../stores/ClientPresenceStore');
const EmojiStore = require('../stores/EmojiStore'); const GuildEmojiStore = require('../stores/GuildEmojiStore');
const { Events, browser } = require('../util/Constants'); const { Events, browser } = require('../util/Constants');
const DataResolver = require('../util/DataResolver'); const DataResolver = require('../util/DataResolver');
const { Error, TypeError, RangeError } = require('../errors'); 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 * All custom emojis that the client has access to, mapped by their IDs
* @type {EmojiStore<Snowflake, Emoji>} * @type {GuildEmojiStore<Snowflake, GuildEmoji>}
* @readonly * @readonly
*/ */
get emojis() { get emojis() {
const emojis = new EmojiStore({ client: this }); const emojis = new GuildEmojiStore({ client: this });
for (const guild of this.guilds.values()) { for (const guild of this.guilds.values()) {
if (guild.available) for (const emoji of guild.emojis.values()) emojis.set(emoji.id, emoji); 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. * Obtains an invite from Discord.
* @param {InviteResolvable} invite Invite code or URL * @param {InviteResolvable} invite Invite code or URL
* @returns {Promise<Invite>} * @returns {Promise<Invite>}
* @example
* client.fetchInvite('https://discord.gg/bRCvFy9')
* .then(invite => {
* console.log(`Obtained invite with code: ${invite.code}`);
* }).catch(console.error);
*/ */
fetchInvite(invite) { fetchInvite(invite) {
const code = DataResolver.resolveInviteCode(invite); const code = DataResolver.resolveInviteCode(invite);
@@ -299,6 +304,11 @@ class Client extends BaseClient {
* @param {Snowflake} id ID of the webhook * @param {Snowflake} id ID of the webhook
* @param {string} [token] Token for the webhook * @param {string} [token] Token for the webhook
* @returns {Promise<Webhook>} * @returns {Promise<Webhook>}
* @example
* client.fetchWebhook('id', 'token')
* .then(webhook => {
* console.log(`Obtained webhook with name: ${webhook.name}`);
* }).catch(console.error);
*/ */
fetchWebhook(id, token) { fetchWebhook(id, token) {
return this.api.webhooks(id, token).get().then(data => new Webhook(this, data)); 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. * Obtains the available voice regions from Discord.
* @returns {Collection<string, VoiceRegion>} * @returns {Collection<string, VoiceRegion>}
* @example
* client.fetchVoiceRegions()
* .then(regions => {
* console.log(`Available regions are: ${regions.map(region => region.name).join(', ')}`);
* }).catch(console.error);
*/ */
fetchVoiceRegions() { fetchVoiceRegions() {
return this.api.voice.regions.get().then(res => { 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} * 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, * @returns {number} Amount of messages that were removed from the caches,
* or -1 if the message cache lifetime is unlimited * 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) { sweepMessages(lifetime = this.options.messageCacheLifetime) {
if (typeof lifetime !== 'number' || isNaN(lifetime)) { if (typeof lifetime !== 'number' || isNaN(lifetime)) {
@@ -359,6 +378,11 @@ class Client extends BaseClient {
* Obtains the OAuth Application of the bot from Discord. * Obtains the OAuth Application of the bot from Discord.
* @param {Snowflake} [id='@me'] ID of application to fetch * @param {Snowflake} [id='@me'] ID of application to fetch
* @returns {Promise<ClientApplication>} * @returns {Promise<ClientApplication>}
* @example
* client.fetchApplication('id')
* .then(application => {
* console.log(`Obtained application with name: ${application.name}`);
* }).catch(console.error);
*/ */
fetchApplication(id = '@me') { fetchApplication(id = '@me') {
return this.api.oauth2.applications(id).get() return this.api.oauth2.applications(id).get()
@@ -374,7 +398,7 @@ class Client extends BaseClient {
* client.generateInvite(['SEND_MESSAGES', 'MANAGE_GUILD', 'MENTION_EVERYONE']) * client.generateInvite(['SEND_MESSAGES', 'MANAGE_GUILD', 'MENTION_EVERYONE'])
* .then(link => { * .then(link => {
* console.log(`Generated bot invite link: ${link}`); * console.log(`Generated bot invite link: ${link}`);
* }); * }).catch(console.error);
*/ */
generateInvite(permissions) { generateInvite(permissions) {
if (permissions) { if (permissions) {

View File

@@ -12,7 +12,7 @@ class GuildEmojiCreateAction extends Action {
/** /**
* Emitted whenever a custom emoji is created in a guild. * Emitted whenever a custom emoji is created in a guild.
* @event Client#emojiCreate * @event Client#emojiCreate
* @param {Emoji} emoji The emoji that was created * @param {GuildEmoji} emoji The emoji that was created
*/ */
module.exports = GuildEmojiCreateAction; module.exports = GuildEmojiCreateAction;

View File

@@ -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 * @event Client#emojiDelete
* @param {Emoji} emoji The emoji that was deleted * @param {GuildEmoji} emoji The emoji that was deleted
*/ */
module.exports = GuildEmojiDeleteAction; module.exports = GuildEmojiDeleteAction;

View File

@@ -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 * @event Client#emojiUpdate
* @param {Emoji} oldEmoji The old emoji * @param {GuildEmoji} oldEmoji The old emoji
* @param {Emoji} newEmoji The new emoji * @param {GuildEmoji} newEmoji The new emoji
*/ */
module.exports = GuildEmojiUpdateAction; module.exports = GuildEmojiUpdateAction;

View File

@@ -33,7 +33,7 @@ class MessageReactionAdd extends Action {
* Emitted whenever a reaction is added to a message. * Emitted whenever a reaction is added to a message.
* @event Client#messageReactionAdd * @event Client#messageReactionAdd
* @param {MessageReaction} messageReaction The reaction object * @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; module.exports = MessageReactionAdd;

View File

@@ -93,7 +93,7 @@ const Messages = {
WEBHOOK_MESSAGE: 'The message was not sent by a webhook.', 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.', REACTION_RESOLVE_USER: 'Couldn\'t resolve the user ID to remove from the reaction.',
}; };

View File

@@ -26,8 +26,8 @@ module.exports = {
// Stores // Stores
ChannelStore: require('./stores/ChannelStore'), ChannelStore: require('./stores/ChannelStore'),
ClientPresenceStore: require('./stores/ClientPresenceStore'), ClientPresenceStore: require('./stores/ClientPresenceStore'),
EmojiStore: require('./stores/EmojiStore'),
GuildChannelStore: require('./stores/GuildChannelStore'), GuildChannelStore: require('./stores/GuildChannelStore'),
GuildEmojiStore: require('./stores/GuildEmojiStore'),
GuildMemberStore: require('./stores/GuildMemberStore'), GuildMemberStore: require('./stores/GuildMemberStore'),
GuildStore: require('./stores/GuildStore'), GuildStore: require('./stores/GuildStore'),
ReactionUserStore: require('./stores/ReactionUserStore'), ReactionUserStore: require('./stores/ReactionUserStore'),
@@ -64,6 +64,7 @@ module.exports = {
Guild: require('./structures/Guild'), Guild: require('./structures/Guild'),
GuildAuditLogs: require('./structures/GuildAuditLogs'), GuildAuditLogs: require('./structures/GuildAuditLogs'),
GuildChannel: require('./structures/GuildChannel'), GuildChannel: require('./structures/GuildChannel'),
GuildEmoji: require('./structures/GuildEmoji'),
GuildMember: require('./structures/GuildMember'), GuildMember: require('./structures/GuildMember'),
Invite: require('./structures/Invite'), Invite: require('./structures/Invite'),
Message: require('./structures/Message'), Message: require('./structures/Message'),

View File

@@ -1,17 +1,17 @@
const Collection = require('../util/Collection'); const Collection = require('../util/Collection');
const DataStore = require('./DataStore'); const DataStore = require('./DataStore');
const Emoji = require('../structures/Emoji'); const GuildEmoji = require('../structures/GuildEmoji');
const ReactionEmoji = require('../structures/ReactionEmoji'); const ReactionEmoji = require('../structures/ReactionEmoji');
const DataResolver = require('../util/DataResolver'); const DataResolver = require('../util/DataResolver');
/** /**
* Stores emojis. * Stores guild emojis.
* @private * @private
* @extends {DataStore} * @extends {DataStore}
*/ */
class EmojiStore extends DataStore { class GuildEmojiStore extends DataStore {
constructor(guild, iterable) { constructor(guild, iterable) {
super(guild.client, iterable, Emoji); super(guild.client, iterable, GuildEmoji);
this.guild = guild; 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 * * A custom emoji ID
* * An Emoji object * * A GuildEmoji object
* * A ReactionEmoji 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 * @param {EmojiResolvable} emoji The Emoji resolvable to identify
* @returns {?Emoji} * @returns {?GuildEmoji}
*/ */
resolve(emoji) { resolve(emoji) {
if (emoji instanceof ReactionEmoji) return super.resolve(emoji.id); 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 * @param {EmojiResolvable} emoji The Emoji resolvable to identify
* @returns {?Snowflake} * @returns {?Snowflake}
*/ */
@@ -111,4 +111,4 @@ class EmojiStore extends DataStore {
} }
} }
module.exports = EmojiStore; module.exports = GuildEmojiStore;

View File

@@ -1,97 +1,29 @@
const Collection = require('../util/Collection');
const Snowflake = require('../util/Snowflake');
const Base = require('./Base'); const Base = require('./Base');
const { TypeError } = require('../errors');
/** /**
* Represents a custom emoji. * Represents an emoji, see {@link GuildEmoji} and {@link ReactionEmoji}.
* @extends {Base} * @extends {Base}
*/ */
class Emoji extends Base { class Emoji extends Base {
constructor(client, data, guild) { constructor(client, emoji) {
super(client); 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 * Whether this emoji is animated
* @type {boolean} * @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 * The ID of this emoji
* @type {number} * @type {?Snowflake}
* @readonly */
*/ this.id = emoji.id;
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<Snowflake, Role>}
* @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');
} }
/** /**
@@ -100,148 +32,34 @@ class Emoji extends Base {
* @readonly * @readonly
*/ */
get identifier() { 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); return encodeURIComponent(this.name);
} }
/** /**
* Data for editing an emoji. * The URL to the emoji file if its a custom emoji
* @typedef {Object} EmojiEditData * @type {?string}
* @property {string} [name] The name of the emoji * @readonly
* @property {Collection<Snowflake, Role>|RoleResolvable[]} [roles] Roles to restrict emoji to
*/ */
get url() {
/** if (!this.id) return null;
* Edits the emoji. return this.client.rest.cdn.Emoji(this.id, this.animated ? 'gif' : 'png');
* @param {EmojiEditData} data The new data for the emoji
* @param {string} [reason] Reason for editing this emoji
* @returns {Promise<Emoji>}
* @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. * When concatenated with a string, this automatically returns the text required to form a graphical emoji on Discord
* @param {string} name The new name for the emoji * instead of the Emoji object.
* @param {string} [reason] Reason for changing the emoji's name
* @returns {Promise<Emoji>}
*/
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<Emoji>}
*/
addRestrictedRole(role) {
return this.addRestrictedRoles([role]);
}
/**
* Adds multiple roles to the list of roles that can use this emoji.
* @param {Collection<Snowflake, Role>|RoleResolvable[]} roles Roles to add
* @returns {Promise<Emoji>}
*/
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<Emoji>}
*/
removeRestrictedRole(role) {
return this.removeRestrictedRoles([role]);
}
/**
* Removes multiple roles from the list of roles that can use this emoji.
* @param {Collection<Snowflake, Role>|RoleResolvable[]} roles Roles to remove
* @returns {Promise<Emoji>}
*/
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.
* @returns {string} * @returns {string}
* @example * @example
* // Send an emoji: * // Send a custom emoji from a guild:
* const emoji = guild.emojis.first(); * const emoji = guild.emojis.first();
* msg.reply(`Hello! ${emoji}`); * 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() { toString() {
if (!this.id || !this.requiresColons) { return this.id ? `<${this.animated ? 'a' : ''}:${this.name}:${this.id}>` : this.name;
return this.name;
}
return `<${this.animated ? 'a' : ''}:${this.name}:${this.id}>`;
}
/**
* Deletes the emoji.
* @param {string} [reason] Reason for deleting the emoji
* @returns {Promise<Emoji>}
*/
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
);
}
} }
} }

View File

@@ -10,7 +10,7 @@ const Snowflake = require('../util/Snowflake');
const Shared = require('./shared'); const Shared = require('./shared');
const GuildMemberStore = require('../stores/GuildMemberStore'); const GuildMemberStore = require('../stores/GuildMemberStore');
const RoleStore = require('../stores/RoleStore'); const RoleStore = require('../stores/RoleStore');
const EmojiStore = require('../stores/EmojiStore'); const GuildEmojiStore = require('../stores/GuildEmojiStore');
const GuildChannelStore = require('../stores/GuildChannelStore'); const GuildChannelStore = require('../stores/GuildChannelStore');
const PresenceStore = require('../stores/PresenceStore'); const PresenceStore = require('../stores/PresenceStore');
const Base = require('./Base'); const Base = require('./Base');
@@ -218,9 +218,9 @@ class Guild extends Base {
if (!this.emojis) { if (!this.emojis) {
/** /**
* A collection of emojis that are in this guild. The key is the emoji's ID, the value is the emoji. * A collection of emojis that are in this guild. The key is the emoji's ID, the value is the emoji.
* @type {EmojiStore<Snowflake, Emoji>} * @type {GuildEmojiStore<Snowflake, GuildEmoji>}
*/ */
this.emojis = new EmojiStore(this); this.emojis = new GuildEmojiStore(this);
if (data.emojis) for (const emoji of data.emojis) this.emojis.add(emoji); if (data.emojis) for (const emoji of data.emojis) this.emojis.add(emoji);
} else { } else {
this.client.actions.GuildEmojisUpdate.handle({ this.client.actions.GuildEmojisUpdate.handle({

View File

@@ -148,7 +148,7 @@ class GuildAuditLogs {
* * An invite * * An invite
* * A webhook * * A webhook
* * An object where the keys represent either the new value or the old value * * 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
*/ */
/** /**

View File

@@ -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 * Gets the overall set of permissions for a member or role in this channel, taking into account channel overwrites.
* overwrites. * @param {GuildMemberResolvable|RoleResolvable} memberOrRole The member or role to obtain the overall permissions for
* @param {GuildMemberResolvable} member The user that you want to obtain the overall permissions for
* @returns {?Permissions} * @returns {?Permissions}
*/ */
permissionsFor(member) { permissionsFor(memberOrRole) {
member = this.guild.members.resolve(member); const member = this.guild.members.resolve(memberOrRole);
if (!member) return null; if (member) return this.memberPermissions(member);
if (member.id === this.guild.ownerID) return new Permissions(Permissions.ALL).freeze(); const role = this.guild.roles.resolve(memberOrRole);
if (role) return this.rolePermissions(role);
const roles = member.roles; return null;
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();
} }
overwritesFor(member, verified = false, roles = 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). * An object mapping permission flags to `true` (enabled), `null` (default) or `false` (disabled).
* ```js * ```js

View File

@@ -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<Snowflake, Role>}
* @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<Snowflake, Role>|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<GuildEmoji>}
* @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<GuildEmoji>}
*/
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<GuildEmoji>}
*/
addRestrictedRole(role) {
return this.addRestrictedRoles([role]);
}
/**
* Adds multiple roles to the list of roles that can use this emoji.
* @param {Collection<Snowflake, Role>|RoleResolvable[]} roles Roles to add
* @returns {Promise<GuildEmoji>}
*/
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<GuildEmoji>}
*/
removeRestrictedRole(role) {
return this.removeRestrictedRoles([role]);
}
/**
* Removes multiple roles from the list of roles that can use this emoji.
* @param {Collection<Snowflake, Role>|RoleResolvable[]} roles Roles to remove
* @returns {Promise<GuildEmoji>}
*/
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<GuildEmoji>}
*/
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;

View File

@@ -295,9 +295,9 @@ class GuildMember extends Base {
* @returns {?Permissions} * @returns {?Permissions}
*/ */
permissionsIn(channel) { permissionsIn(channel) {
channel = this.client.channels.resolve(channel); channel = this.guild.channels.resolve(channel);
if (!channel || !channel.guild) throw new Error('GUILD_CHANNEL_RESOLVE'); if (!channel) throw new Error('GUILD_CHANNEL_RESOLVE');
return channel.permissionsFor(this); return channel.memberPermissions(this);
} }
/** /**

View File

@@ -193,7 +193,7 @@ class MessageEmbed {
/** /**
* Sets the file to upload alongside the embed. This file can be accessed via `attachment://fileName.extension` when * 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<FileOptions|string|MessageAttachment>} files Files to attach * @param {Array<FileOptions|string|MessageAttachment>} files Files to attach
* @returns {MessageEmbed} * @returns {MessageEmbed}
*/ */

View File

@@ -1,4 +1,4 @@
const Emoji = require('./Emoji'); const GuildEmoji = require('./GuildEmoji');
const ReactionEmoji = require('./ReactionEmoji'); const ReactionEmoji = require('./ReactionEmoji');
const ReactionUserStore = require('../stores/ReactionUserStore'); const ReactionUserStore = require('../stores/ReactionUserStore');
@@ -31,18 +31,18 @@ class MessageReaction {
*/ */
this.users = new ReactionUserStore(client, undefined, this); 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 * object which has fewer properties. Whatever the prototype of the emoji, it will still have
* `name`, `id`, `identifier` and `toString()` * `name`, `id`, `identifier` and `toString()`
* @type {Emoji|ReactionEmoji} * @type {GuildEmoji|ReactionEmoji}
* @readonly * @readonly
*/ */
get emoji() { 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 // Check to see if the emoji has become known to the client
if (this._emoji.id) { if (this._emoji.id) {
const emojis = this.message.client.emojis; const emojis = this.message.client.emojis;

View File

@@ -1,49 +1,19 @@
const Emoji = require('./Emoji');
/** /**
* Represents a limited emoji set used for both custom and unicode emojis. Custom emojis * 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 * will use this class opposed to the Emoji class when the client doesn't know enough
* information about them. * information about them.
* @extends {Emoji}
*/ */
class ReactionEmoji { class ReactionEmoji extends Emoji {
constructor(reaction, name, id) { constructor(reaction, emoji) {
super(reaction.message.client, emoji);
/** /**
* The message reaction this emoji refers to * The message reaction this emoji refers to
* @type {MessageReaction} * @type {MessageReaction}
*/ */
this.reaction = reaction; 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;
} }
} }

View File

@@ -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. * Sets a new name for the role.
* @param {string} name The new name of the role * @param {string} name The new name of the role

View File

@@ -7,19 +7,19 @@ const { RangeError } = require('../errors');
*/ */
class Permissions { class Permissions {
/** /**
* @param {number|PermissionResolvable[]} permissions Permissions or bitfield to read from * @param {PermissionResolvable} permissions Permission(s) to read from
*/ */
constructor(permissions) { constructor(permissions) {
/** /**
* Bitfield of the packed permissions * Bitfield of the packed permissions
* @type {number} * @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. * 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 * @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override
* @returns {boolean} * @returns {boolean}
*/ */
@@ -32,11 +32,12 @@ class Permissions {
/** /**
* Gets all given permissions that are missing from the bitfield. * 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 * @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override
* @returns {PermissionResolvable[]} * @returns {string[]}
*/ */
missing(permissions, checkAdmin = true) { missing(permissions, checkAdmin = true) {
if (!(permissions instanceof Array)) permissions = new this.constructor(permissions).toArray(false);
return permissions.filter(p => !this.has(p, checkAdmin)); return permissions.filter(p => !this.has(p, checkAdmin));
} }
@@ -92,17 +93,32 @@ class Permissions {
return serialized; 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: * Data that can be resolved to give a permission number. This can be:
* * A string (see {@link Permissions.FLAGS}) * * A string (see {@link Permissions.FLAGS})
* * A permission number * * A permission number
* * An instance of Permissions * * 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. * Resolves permissions to their numeric form.
* @param {PermissionResolvable|PermissionResolvable[]} permission - Permission(s) to resolve * @param {PermissionResolvable} permission - Permission(s) to resolve
* @returns {number} * @returns {number}
*/ */
static resolve(permission) { static resolve(permission) {

View File

@@ -61,7 +61,7 @@ class Structures {
} }
const structures = { const structures = {
Emoji: require('../structures/Emoji'), GuildEmoji: require('../structures/GuildEmoji'),
DMChannel: require('../structures/DMChannel'), DMChannel: require('../structures/DMChannel'),
GroupDMChannel: require('../structures/GroupDMChannel'), GroupDMChannel: require('../structures/GroupDMChannel'),
TextChannel: require('../structures/TextChannel'), TextChannel: require('../structures/TextChannel'),

Submodule typings updated: 0b5b13f4a5...895af7f3da