feat(BitField): add BitField base class (#3759)

* feat(BitField): add BitField base class

* fix(Permissions): properly deprecate the getters/setters
This commit is contained in:
SpaceEEC
2020-02-12 22:23:48 +01:00
committed by GitHub
parent b7ccf9a53e
commit 46e8bc44fc
7 changed files with 239 additions and 129 deletions

View File

@@ -418,7 +418,7 @@ class Client extends EventEmitter {
* .catch(console.error);
*/
generateInvite(permissions) {
permissions = typeof permissions === 'undefined' ? 0 : Permissions.resolve(permissions);
permissions = Permissions.resolve(permissions);
return this.fetchApplication().then(application =>
`https://discordapp.com/oauth2/authorize?client_id=${application.id}&permissions=${permissions}&scope=bot`
);

View File

@@ -9,6 +9,7 @@ module.exports = {
WebhookClient: require('./client/WebhookClient'),
// Utilities
BitField: require('./util/BitField'),
Collection: require('./util/Collection'),
Constants: require('./util/Constants'),
DiscordAPIError: require('./client/rest/DiscordAPIError'),

View File

@@ -192,6 +192,10 @@ class Game {
return new Date(this.createdTimestamp);
}
/**
* Flags that describe the activity
* @type {ActivityFlags[]}
*/
get flags() {
const flags = [];
for (const [name, flag] of Object.entries(ActivityFlags)) {

160
src/util/BitField.js Normal file
View File

@@ -0,0 +1,160 @@
/**
* Data structure that makes it easy to interact with a bitfield.
*/
class BitField {
/**
* @param {BitFieldResolvable} [bits=0] Bits(s) to read from
*/
constructor(bits) {
/**
* Bitfield of the packed bits
* @type {number}
*/
this.bitfield = this.constructor.resolve(bits);
}
/**
* Checks whether the bitfield has a bit, or any of multiple bits.
* @param {BitFieldResolvable} bit Bit(s) to check for
* @returns {boolean}
*/
any(bit) {
return (this.bitfield & this.constructor.resolve(bit)) !== 0;
}
/**
* Checks if this bitfield equals another
* @param {BitFieldResolvable} bit Bit(s) to check for
* @returns {boolean}
*/
equals(bit) {
return this.bitfield === this.constructor.resolve(bit);
}
/**
* Checks whether the bitfield has a bit, or multiple bits.
* @param {BitFieldResolvable} bit Bit(s) to check for
* @returns {boolean}
*/
has(bit) {
if (Array.isArray(bit)) return bit.every(p => this.has(p));
bit = this.constructor.resolve(bit);
return (this.bitfield & bit) === bit;
}
/**
* Gets all given bits that are missing from the bitfield.
* @param {BitFieldResolvable} bits Bits(s) to check for
* @param {...*} hasParams Additional parameters for the has method, if any
* @returns {string[]}
*/
missing(bits, ...hasParams) {
if (!Array.isArray(bits)) bits = new this.constructor(bits).toArray(false);
return bits.filter(p => !this.has(p, ...hasParams));
}
/**
* Freezes these bits, making them immutable.
* @returns {Readonly<BitField>} These bits
*/
freeze() {
return Object.freeze(this);
}
/**
* Adds bits to these ones.
* @param {...BitFieldResolvable} [bits] Bits to add
* @returns {BitField} These bits or new BitField if the instance is frozen.
*/
add(...bits) {
let total = 0;
for (const bit of bits) {
total |= this.constructor.resolve(bit);
}
if (Object.isFrozen(this)) return new this.constructor(this.bitfield | total);
this.bitfield |= total;
return this;
}
/**
* Removes bits from these.
* @param {...BitFieldResolvable} [bits] Bits to remove
* @returns {BitField} These bits or new BitField if the instance is frozen.
*/
remove(...bits) {
let total = 0;
for (const bit of bits) {
total |= this.constructor.resolve(bit);
}
if (Object.isFrozen(this)) return new this.constructor(this.bitfield & ~total);
this.bitfield &= ~total;
return this;
}
/**
* Gets an object mapping field names to a {@link boolean} indicating whether the
* bit is available.
* @param {...*} hasParams Additional parameters for the has method, if any
* @returns {Object}
*/
serialize(...hasParams) {
const serialized = {};
for (const flag of Object.keys(this.constructor.FLAGS)) {
serialized[flag] = this.has(this.constructor.FLAGS[flag], ...hasParams);
}
return serialized;
}
/**
* Gets an {@link Array} of bitfield names based on the bits available.
* @param {...*} hasParams Additional parameters for the has method, if any
* @returns {string[]}
*/
toArray(...hasParams) {
return Object.keys(this.constructor.FLAGS).filter(bit => this.has(bit, ...hasParams));
}
toJSON() {
return this.bitfield;
}
valueOf() {
return this.bitfield;
}
*[Symbol.iterator]() {
yield* this.toArray();
}
/**
* Data that can be resolved to give a bitfield. This can be:
* * A string (see {@link BitField.FLAGS})
* * A bit number
* * An instance of BitField
* * An Array of BitFieldResolvable
* @typedef {string|number|BitField|BitFieldResolvable[]} BitFieldResolvable
*/
/**
* Resolves bitfields to their numeric form.
* @param {BitFieldResolvable} [bit=0] - bit(s) to resolve
* @returns {number}
*/
static resolve(bit = 0) {
if (typeof bit === 'number' && bit >= 0) return bit;
if (bit instanceof BitField) return bit.bitfield;
if (Array.isArray(bit)) return bit.map(p => this.resolve(p)).reduce((prev, p) => prev | p, 0);
if (typeof bit === 'string' && typeof this.FLAGS[bit] !== 'undefined') return this.FLAGS[bit];
throw new RangeError('Invalid bitfield flag or number.');
}
}
/**
* Numeric bitfield flags.
* <info>Defined in extension classes</info>
* @type {Object}
* @abstract
*/
BitField.FLAGS = {};
module.exports = BitField;

View File

@@ -386,6 +386,17 @@ exports.ActivityTypes = [
'CUSTOM_STATUS',
];
/**
* Numeric activity flags. All available properties:
* * `INSTANCE`
* * `JOIN`
* * `SPECTATE`
* * `JOIN_REQUEST`
* * `SYNC`
* * `PLAY`
* @typedef {string} ActivityFlag
* @see {@link https://discordapp.com/developers/docs/topics/gateway#activity-object-activity-flags}
*/
exports.ActivityFlags = {
INSTANCE: 1 << 0,
JOIN: 1 << 1,

View File

@@ -1,33 +1,31 @@
const Constants = require('../util/Constants');
const BitField = require('./BitField');
const util = require('util');
/**
* Data structure that makes it easy to interact with a permission bitfield. All {@link GuildMember}s have a set of
* permissions in their guild, and each channel in the guild may also have {@link PermissionOverwrites} for the member
* that override their default permissions.
* @extends {BitField}
*/
class Permissions {
class Permissions extends BitField {
/**
* @param {GuildMember} [member] Member the permissions are for **(deprecated)**
* @param {number|PermissionResolvable} permissions Permissions or bitfield to read from
*/
constructor(member, permissions) {
permissions = typeof member === 'object' && !(member instanceof Array) ? permissions : member;
super(typeof member === 'object' && !(member instanceof Array) ? permissions : member);
/**
Object.defineProperty(this, '_member', {
writable: true,
value: typeof member === 'object' && !(member instanceof Array) ? member : null,
});
}
/**
* Member the permissions are for
* @type {GuildMember}
* @deprecated
*/
this._member = typeof member === 'object' ? member : null;
/**
* Bitfield of the packed permissions
* @type {number}
*/
this.bitfield = typeof permissions === 'number' ? permissions : this.constructor.resolve(permissions);
}
get member() {
return this._member;
}
@@ -51,6 +49,16 @@ class Permissions {
this.bitfield = raw;
}
/**
* Checks whether the bitfield has a permission, or any of multiple permissions.
* @param {PermissionResolvable} permission Permission(s) to check for
* @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override
* @returns {boolean}
*/
any(permission, checkAdmin = true) {
return (checkAdmin && super.has(this.constructor.FLAGS.ADMINISTRATOR)) || super.any(permission);
}
/**
* Checks whether the bitfield has a permission, or multiple permissions.
* @param {PermissionResolvable} permission Permission(s) to check for
@@ -58,76 +66,7 @@ class Permissions {
* @returns {boolean}
*/
has(permission, checkAdmin = true) {
if (permission instanceof Array) return permission.every(p => this.has(p, checkAdmin));
permission = this.constructor.resolve(permission);
if (checkAdmin && (this.bitfield & this.constructor.FLAGS.ADMINISTRATOR) > 0) return true;
return (this.bitfield & permission) === permission;
}
/**
* Checks whether the bitfield has a permission, or any of multiple permissions.
* @param {PermissionResolvable} permissions Permission(s) to check for
* @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override
* @returns {boolean}
*/
any(permissions, checkAdmin = true) {
return (checkAdmin && this.has(this.constructor.FLAGS.ADMINISTRATOR)) ||
(this.bitfield & this.constructor.resolve(permissions)) !== 0;
}
/**
* Gets all given permissions that are missing from the bitfield.
* @param {PermissionResolvable} permissions Permissions to check for
* @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override
* @returns {PermissionResolvable}
*/
missing(permissions, checkAdmin = true) {
if (!(permissions instanceof Array)) permissions = [permissions];
return permissions.filter(p => !this.has(p, checkAdmin));
}
/**
* Adds permissions to this one, creating a new instance to represent the new bitfield.
* @param {...PermissionResolvable} permissions Permissions to add
* @returns {Permissions}
*/
add(...permissions) {
let total = 0;
for (let p = permissions.length - 1; p >= 0; p--) {
const perm = this.constructor.resolve(permissions[p]);
total |= perm;
}
if (Object.isFrozen(this)) return new this.constructor(this.bitfield | total);
this.bitfield |= total;
return this;
}
/**
* Removes permissions to this one, creating a new instance to represent the new bitfield.
* @param {...PermissionResolvable} permissions Permissions to remove
* @returns {Permissions}
*/
remove(...permissions) {
let total = 0;
for (let p = permissions.length - 1; p >= 0; p--) {
const perm = this.constructor.resolve(permissions[p]);
total |= perm;
}
if (Object.isFrozen(this)) return new this.constructor(this.bitfield & ~total);
this.bitfield &= ~total;
return this;
}
/**
* Gets an object mapping permission name (like `VIEW_CHANNEL`) to a {@link boolean} indicating whether the
* permission is available.
* @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override
* @returns {Object}
*/
serialize(checkAdmin = true) {
const serialized = {};
for (const perm of Object.keys(this.constructor.FLAGS)) serialized[perm] = this.has(perm, checkAdmin);
return serialized;
return (checkAdmin && super.has(this.constructor.FLAGS.ADMINISTRATOR)) || super.has(permission);
}
/**
@@ -166,46 +105,12 @@ class Permissions {
return this.missing(permissions, !explicit);
}
/**
* 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));
}
/**
* Freezes these permissions, making them immutable.
* @returns {Permissions} These permissions
*/
freeze() {
return Object.freeze(this);
}
valueOf() {
return this.bitfield;
}
/**
* Data that can be resolved to give a permission number. This can be:
* * A string (see {@link Permissions.FLAGS})
* * A permission number
* @typedef {string|number|Permissions|PermissionResolvable[]} PermissionResolvable
*/
/**
* Resolves permissions to their numeric form.
* @param {PermissionResolvable} permission - Permission(s) to resolve
* @returns {number}
*/
static resolve(permission) {
if (permission instanceof Array) return permission.map(p => this.resolve(p)).reduce((prev, p) => prev | p, 0);
if (permission instanceof Permissions) return permission.bitfield;
if (typeof permission === 'string') permission = this.FLAGS[permission];
if (typeof permission !== 'number' || permission < 0) throw new RangeError(Constants.Errors.NOT_A_PERMISSION);
return permission;
}
}
/**
@@ -310,10 +215,20 @@ Permissions.prototype.hasPermissions = util.deprecate(Permissions.prototype.hasP
'EvaluatedPermissions#hasPermissions is deprecated, use Permissions#has instead');
Permissions.prototype.missingPermissions = util.deprecate(Permissions.prototype.missingPermissions,
'EvaluatedPermissions#missingPermissions is deprecated, use Permissions#missing instead');
Object.defineProperty(Permissions.prototype, 'raw', {
get: util
.deprecate(Object.getOwnPropertyDescriptor(Permissions.prototype, 'raw').get,
'EvaluatedPermissions#raw is deprecated use Permissions#bitfield instead'),
set: util.deprecate(Object.getOwnPropertyDescriptor(Permissions.prototype, 'raw').set,
'EvaluatedPermissions#raw is deprecated use Permissions#bitfield instead'),
});
Object.defineProperty(Permissions.prototype, 'member', {
get: util
.deprecate(Object.getOwnPropertyDescriptor(Permissions.prototype, 'member').get,
'EvaluatedPermissions#member is deprecated'),
set: util
.deprecate(Object.getOwnPropertyDescriptor(Permissions.prototype, 'member').set,
'EvaluatedPermissions#member is deprecated'),
});
module.exports = Permissions;