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;

41
typings/index.d.ts vendored
View File

@@ -41,6 +41,25 @@ declare module 'discord.js' {
public options: object;
}
export class BitField<S extends string> {
constructor(bits?: BitFieldResolvable<S>);
public bitfield: number;
public add(...bits: BitFieldResolvable<S>[]): BitField<S>;
public any(bit: BitFieldResolvable<S>): boolean;
public equals(bit: BitFieldResolvable<S>): boolean;
public freeze(): Readonly<BitField<S>>;
public has(bit: BitFieldResolvable<S>): boolean;
public missing(bits: BitFieldResolvable<S>, ...hasParam: readonly unknown[]): S[];
public remove(...bits: BitFieldResolvable<S>[]): BitField<S>;
public serialize(...hasParam: readonly unknown[]): Record<S, boolean>;
public toArray(...hasParam: readonly unknown[]): S[];
public toJSON(): number;
public valueOf(): number;
public [Symbol.iterator](): IterableIterator<S>;
public static FLAGS: object;
public static resolve(bit?: BitFieldResolvable<any>): number;
}
export class CategoryChannel extends GuildChannel {
public readonly children: Collection<Snowflake, GuildChannel>;
}
@@ -436,6 +455,9 @@ declare module 'discord.js' {
export class Game {
constructor(data: object, presence: Presence);
private _flags: string[];
private syncID: string;
public applicationID: string;
public assets: RichPresenceAssets;
public details: string;
@@ -456,8 +478,6 @@ declare module 'discord.js' {
public url: string;
public equals(game: Game): boolean;
public toString(): string;
private _flags: string[];
private syncID: string;
}
export class GroupDMChannel extends TextBasedChannel(Channel) {
@@ -1006,30 +1026,27 @@ declare module 'discord.js' {
public delete(reason?: string): Promise<PermissionOverwrites>;
}
export class Permissions {
export class Permissions extends BitField<PermissionString> {
constructor(permissions: PermissionResolvable);
constructor(member: GuildMember, permissions: PermissionResolvable);
private readonly raw: number;
public bitfield: number;
public member: GuildMember;
public add(...permissions: PermissionResolvable[]): this;
public readonly raw: number;
public any(permissions: PermissionResolvable, checkAdmin?: boolean): boolean;
public freeze(): this;
public has(permission: PermissionResolvable, checkAdmin?: boolean): boolean;
public hasPermission(permission: PermissionResolvable, explicit?: boolean): boolean;
public hasPermissions(permissions: PermissionResolvable, explicit?: boolean): boolean;
public missing(permissions: PermissionResolvable, checkAdmin?: boolean): PermissionResolvable;
public missing(permissions: PermissionResolvable, checkAdmin?: boolean): PermissionString[];
public missingPermissions(permissions: PermissionResolvable, checkAdmin?: boolean): PermissionResolvable;
public remove(...permissions: PermissionResolvable[]): this;
public serialize(checkAdmin?: boolean): PermissionObject;
public serialize(checkAdmin?: boolean): Required<PermissionObject>;
public toArray(checkAdmin?: boolean): PermissionString[];
public valueOf(): number;
public static ALL: number;
public static DEFAULT: number;
public static FLAGS: PermissionFlags;
public static resolve(permission: PermissionResolvable): number;
public static resolve(permission?: PermissionResolvable): number;
}
export class Presence {
@@ -1730,6 +1747,8 @@ declare module 'discord.js' {
type Base64String = string;
type BitFieldResolvable<T extends string> = RecursiveArray<T | number | Readonly<BitField<T>>> | T | number | Readonly<BitField<T>>;
type BufferResolvable = Buffer | string;
type ChannelCreationOverwrites = {
@@ -2181,7 +2200,7 @@ declare module 'discord.js' {
interface RecursiveArray<T> extends Array<T | RecursiveArray<T>> { }
type PermissionResolvable = RecursiveArray<Permissions | PermissionString | number> | Permissions | PermissionString | number;
type PermissionResolvable = BitFieldResolvable<PermissionString>
type PremiumTier = number;