fix: resolve bitfield correctly

This commit is contained in:
MARCROCK22 2025-01-05 15:23:41 -04:00
parent cb52d6fe6b
commit 3ed0c8f293
7 changed files with 52 additions and 61 deletions

View File

@ -520,9 +520,9 @@ export class HandleCommand {
} }
checkPermissions(app: PermissionsBitField, bot: bigint) { checkPermissions(app: PermissionsBitField, bot: bigint) {
if (app.has('Administrator')) return; if (app.has(['Administrator'])) return;
const permissions = app.missings(...app.values([bot])); const permissions = app.missings(app.values([bot]));
if (permissions.length) { if (permissions.length) {
return app.keys(permissions); return app.keys(permissions);
} }

View File

@ -523,14 +523,14 @@ export class CommandHandler extends BaseHandler {
option.onPermissionsFail?.bind(option) ?? option.onPermissionsFail?.bind(option) ??
commandInstance.onPermissionsFail?.bind(commandInstance) ?? commandInstance.onPermissionsFail?.bind(commandInstance) ??
this.client.options.commands?.defaults?.onPermissionsFail; this.client.options.commands?.defaults?.onPermissionsFail;
option.botPermissions = PermissionsBitField.resolve( option.botPermissions = PermissionsBitField.resolve([
option.botPermissions ?? PermissionsBitField.None, option.botPermissions ?? PermissionsBitField.None,
commandInstance.botPermissions ?? PermissionsBitField.None, commandInstance.botPermissions ?? PermissionsBitField.None,
); ]);
option.defaultMemberPermissions ??= PermissionsBitField.resolve( option.defaultMemberPermissions ??= PermissionsBitField.resolve([
option.defaultMemberPermissions ?? PermissionsBitField.None, option.defaultMemberPermissions ?? PermissionsBitField.None,
commandInstance.defaultMemberPermissions ?? PermissionsBitField.None, commandInstance.defaultMemberPermissions ?? PermissionsBitField.None,
); ]);
option.contexts ??= commandInstance.contexts; option.contexts ??= commandInstance.contexts;
option.integrationTypes ??= commandInstance.integrationTypes; option.integrationTypes ??= commandInstance.integrationTypes;
option.props ??= commandInstance.props; option.props ??= commandInstance.props;

View File

@ -154,19 +154,19 @@ export class ChannelShorter extends BaseShorter {
async memberPermissions(channelId: string, member: GuildMember, checkAdmin = true): Promise<PermissionsBitField> { async memberPermissions(channelId: string, member: GuildMember, checkAdmin = true): Promise<PermissionsBitField> {
const memberPermissions = await member.fetchPermissions(); const memberPermissions = await member.fetchPermissions();
if (checkAdmin && memberPermissions.has(PermissionFlagsBits.Administrator)) { if (checkAdmin && memberPermissions.has([PermissionFlagsBits.Administrator])) {
return new PermissionsBitField(PermissionsBitField.All); return new PermissionsBitField(PermissionsBitField.All);
} }
const overwrites = await this.overwritesFor(channelId, member); const overwrites = await this.overwritesFor(channelId, member);
const permissions = new PermissionsBitField(memberPermissions.bits); const permissions = new PermissionsBitField(memberPermissions.bits);
permissions.remove(overwrites.everyone?.deny.bits ?? 0n); permissions.remove([overwrites.everyone?.deny.bits ?? 0n]);
permissions.add(overwrites.everyone?.allow.bits ?? 0n); permissions.add([overwrites.everyone?.allow.bits ?? 0n]);
permissions.remove(overwrites.roles.length > 0 ? overwrites.roles.map(role => role.deny.bits) : 0n); permissions.remove(overwrites.roles.length > 0 ? overwrites.roles.map(role => role.deny.bits) : [0n]);
permissions.add(overwrites.roles.length > 0 ? overwrites.roles.map(role => role.allow.bits) : 0n); permissions.add(overwrites.roles.length > 0 ? overwrites.roles.map(role => role.allow.bits) : [0n]);
permissions.remove(overwrites.member?.deny.bits ?? 0n); permissions.remove([overwrites.member?.deny.bits ?? 0n]);
permissions.add(overwrites.member?.allow.bits ?? 0n); permissions.add([overwrites.member?.allow.bits ?? 0n]);
return permissions; return permissions;
} }
@ -195,7 +195,7 @@ export class ChannelShorter extends BaseShorter {
} }
async rolePermissions(channelId: string, role: GuildRole, checkAdmin = true): Promise<PermissionsBitField> { async rolePermissions(channelId: string, role: GuildRole, checkAdmin = true): Promise<PermissionsBitField> {
if (checkAdmin && role.permissions.has(PermissionFlagsBits.Administrator)) { if (checkAdmin && role.permissions.has([PermissionFlagsBits.Administrator])) {
return new PermissionsBitField(PermissionsBitField.All); return new PermissionsBitField(PermissionsBitField.All);
} }
const channelOverwrites = (await this.client.cache.overwrites?.get(channelId)) ?? []; const channelOverwrites = (await this.client.cache.overwrites?.get(channelId)) ?? [];
@ -204,10 +204,10 @@ export class ChannelShorter extends BaseShorter {
const roleOverwrites = channelOverwrites.find(x => x.id === role.id); const roleOverwrites = channelOverwrites.find(x => x.id === role.id);
const permissions = new PermissionsBitField(role.permissions.bits); const permissions = new PermissionsBitField(role.permissions.bits);
permissions.remove(everyoneOverwrites?.deny.bits ?? 0n); permissions.remove([everyoneOverwrites?.deny.bits ?? 0n]);
permissions.add(everyoneOverwrites?.allow.bits ?? 0n); permissions.add([everyoneOverwrites?.allow.bits ?? 0n]);
permissions.remove(roleOverwrites?.deny.bits ?? 0n); permissions.remove([roleOverwrites?.deny.bits ?? 0n]);
permissions.add(roleOverwrites?.allow.bits ?? 0n); permissions.add([roleOverwrites?.allow.bits ?? 0n]);
return permissions; return permissions;
} }

View File

@ -242,18 +242,18 @@ export class GuildMember extends BaseGuildMember {
} }
async bannable(force = false) { async bannable(force = false) {
return (await this.manageable(force)) && (await this.__me!.fetchPermissions(force)).has('BanMembers'); return (await this.manageable(force)) && (await this.__me!.fetchPermissions(force)).has(['BanMembers']);
} }
async kickable(force = false) { async kickable(force = false) {
return (await this.manageable(force)) && (await this.__me!.fetchPermissions(force)).has('KickMembers'); return (await this.manageable(force)) && (await this.__me!.fetchPermissions(force)).has(['KickMembers']);
} }
async moderatable(force = false) { async moderatable(force = false) {
return ( return (
!(await this.roles.permissions(force)).has('Administrator') && !(await this.roles.permissions(force)).has(['Administrator']) &&
(await this.manageable(force)) && (await this.manageable(force)) &&
(await this.__me!.fetchPermissions(force)).has('KickMembers') (await this.__me!.fetchPermissions(force)).has(['KickMembers'])
); );
} }
} }

View File

@ -1,4 +1,4 @@
export type BitFieldResolvable<T extends object> = keyof T | number | bigint | (keyof T | number | bigint)[]; export type BitFieldResolvable<T extends object> = keyof T | number | bigint;
export class BitField<T extends object> { export class BitField<T extends object> {
static None = 0n; static None = 0n;
@ -18,17 +18,17 @@ export class BitField<T extends object> {
return this.bit; return this.bit;
} }
has(...bits: BitFieldResolvable<T>[]) { has(bits: BitFieldResolvable<T>[]) {
const bitsResolved = bits.map(bit => this.resolve(bit)); const bitsResolved = bits.map(bit => this.resolve(bit));
return bitsResolved.every(bit => (this.bits & bit) === bit); return bitsResolved.every(bit => (this.bits & bit) === bit);
} }
missings(...bits: BitFieldResolvable<T>[]) { missings(bits: BitFieldResolvable<T>[]) {
const bitsResolved = bits.map(bit => this.resolve(bit)); const bitsResolved = bits.map(bit => this.resolve(bit));
return bitsResolved.filter(bit => (this.bits & bit) !== bit); return bitsResolved.filter(bit => (this.bits & bit) !== bit);
} }
equals(other: BitFieldResolvable<T>) { equals(other: BitFieldResolvable<T>[]) {
return this.bits === this.resolve(other); return this.bits === this.resolve(other);
} }
@ -54,7 +54,7 @@ export class BitField<T extends object> {
}, [] as bigint[]); }, [] as bigint[]);
} }
add(...bits: (BitFieldResolvable<T> | undefined)[]) { add(bits: BitFieldResolvable<T>[]) {
for (const bit of bits) { for (const bit of bits) {
if (!bit) continue; if (!bit) continue;
this.bits |= this.resolve(bit); this.bits |= this.resolve(bit);
@ -62,20 +62,20 @@ export class BitField<T extends object> {
return this.bits; return this.bits;
} }
remove(...bits: BitFieldResolvable<T>[]): bigint { remove(bits: BitFieldResolvable<T>[]): bigint {
for (const bit of bits) { for (const bit of bits) {
this.bits &= ~this.resolve(bit); this.bits &= ~this.resolve(bit);
} }
return this.bits; return this.bits;
} }
resolve(...bits: BitFieldResolvable<T>[]): bigint { resolve(bits: BitFieldResolvable<T> | BitFieldResolvable<T>[]): bigint {
let bitsResult = 0n; let bitsResult = 0n;
for (const bit of bits) { for (const bit of Array.isArray(bits) ? bits : [bits]) {
switch (typeof bit) { switch (typeof bit) {
case 'string': case 'string':
bitsResult |= this.resolve(this.Flags[bit]); bitsResult |= this.resolve([this.Flags[bit]]);
break; break;
case 'number': case 'number':
bitsResult |= BigInt(bit); bitsResult |= BigInt(bit);
@ -83,13 +83,6 @@ export class BitField<T extends object> {
case 'bigint': case 'bigint':
bitsResult |= bit; bitsResult |= bit;
break; break;
case 'object': {
if (!Array.isArray(bit)) {
throw new TypeError(`Cannot resolve permission: ${bit}`);
}
bitsResult |= bits.reduce<bigint>((acc, val) => this.resolve(val) | acc, BitField.None);
break;
}
default: default:
throw new TypeError(`Cannot resolve permission: ${typeof bit === 'symbol' ? String(bit) : (bit as unknown)}`); throw new TypeError(`Cannot resolve permission: ${typeof bit === 'symbol' ? String(bit) : (bit as unknown)}`);
} }

View File

@ -6,29 +6,34 @@ export class PermissionsBitField extends BitField<typeof PermissionFlagsBits> {
Flags = PermissionFlagsBits; Flags = PermissionFlagsBits;
static All = Object.values(PermissionFlagsBits).reduce((acc, value) => acc | value, 0n); static All = Object.values(PermissionFlagsBits).reduce((acc, value) => acc | value, 0n);
constructor(bitfields?: BitFieldResolvable<typeof PermissionFlagsBits>) { constructor(
bitfields?: BitFieldResolvable<typeof PermissionFlagsBits> | BitFieldResolvable<typeof PermissionFlagsBits>[],
) {
super(); super();
if (bitfields) this.bit = this.resolve(bitfields); if (bitfields) this.bit = this.resolve(bitfields);
} }
declare keys: (bits?: BitFieldResolvable<typeof PermissionFlagsBits>[]) => PermissionStrings; declare keys: (bits?: BitFieldResolvable<typeof PermissionFlagsBits>[]) => PermissionStrings;
has(...bits: BitFieldResolvable<typeof PermissionFlagsBits>[]) { has(bits: BitFieldResolvable<typeof PermissionFlagsBits>[]) {
return super.has(...bits) || super.has('Administrator'); return super.has(bits) || super.has(['Administrator']);
} }
strictHas(...bits: BitFieldResolvable<typeof PermissionFlagsBits>[]) { strictHas(bits: BitFieldResolvable<typeof PermissionFlagsBits>[]) {
return super.has(...bits); return super.has(bits);
} }
resolve<T extends typeof PermissionFlagsBits>(...bits: BitFieldResolvable<T>[]): bigint { resolve<T extends typeof PermissionFlagsBits>(bits: BitFieldResolvable<T> | BitFieldResolvable<T>[]): bigint {
return bits.reduce<bigint>((acc, cur) => acc | PermissionsBitField.resolve(cur), BitField.None); return (Array.isArray(bits) ? bits : [bits]).reduce<bigint>(
(acc, cur) => acc | PermissionsBitField.resolve([cur]),
BitField.None,
);
} }
static resolve<T extends typeof PermissionFlagsBits>(...bits: BitFieldResolvable<T>[]): bigint { static resolve<T extends typeof PermissionFlagsBits>(bits: BitFieldResolvable<T> | BitFieldResolvable<T>[]): bigint {
let bitsResult = 0n; let bitsResult = 0n;
for (const bit of bits) { for (const bit of Array.isArray(bits) ? bits : [bits]) {
switch (typeof bit) { switch (typeof bit) {
case 'string': case 'string':
bitsResult |= PermissionsBitField.resolve(PermissionFlagsBits[bit as keyof typeof PermissionFlagsBits]); bitsResult |= PermissionsBitField.resolve(PermissionFlagsBits[bit as keyof typeof PermissionFlagsBits]);
@ -39,13 +44,6 @@ export class PermissionsBitField extends BitField<typeof PermissionFlagsBits> {
case 'bigint': case 'bigint':
bitsResult |= bit; bitsResult |= bit;
break; break;
case 'object': {
if (!Array.isArray(bit)) {
throw new TypeError(`Cannot resolve permission: ${bit}`);
}
bitsResult |= bit.reduce<bigint>((acc, val) => PermissionsBitField.resolve(val) | acc, BitField.None);
break;
}
default: default:
throw new TypeError(`Cannot resolve permission: ${typeof bit === 'symbol' ? String(bit) : (bit as any)}`); throw new TypeError(`Cannot resolve permission: ${typeof bit === 'symbol' ? String(bit) : (bit as any)}`);
} }

View File

@ -12,7 +12,7 @@ describe('PermissionsBitField', () => {
test('add', () => { test('add', () => {
const p = new PermissionsBitField(['CreateEvents']); const p = new PermissionsBitField(['CreateEvents']);
p.add(['AttachFiles']); p.add(['AttachFiles']);
p.add('ChangeNickname'); p.add(['ChangeNickname']);
assert.equal( assert.equal(
p.bits, p.bits,
PermissionFlagsBits.CreateEvents | PermissionFlagsBits.AttachFiles | PermissionFlagsBits.ChangeNickname, PermissionFlagsBits.CreateEvents | PermissionFlagsBits.AttachFiles | PermissionFlagsBits.ChangeNickname,
@ -21,14 +21,14 @@ describe('PermissionsBitField', () => {
test('remove', () => { test('remove', () => {
const p = new PermissionsBitField(['CreateEvents']); const p = new PermissionsBitField(['CreateEvents']);
p.remove('CreateEvents'); p.remove(['CreateEvents']);
assert.equal(p.bits, BitField.None); assert.equal(p.bits, BitField.None);
}); });
test('keys', () => { test('keys', () => {
const p = new PermissionsBitField(['CreateEvents', 'Administrator']); const p = new PermissionsBitField(['CreateEvents', 'Administrator']);
p.add(['AttachFiles']); p.add(['AttachFiles']);
p.add('ChangeNickname'); p.add(['ChangeNickname']);
const keys = ['CreateEvents', 'Administrator', 'AttachFiles', 'ChangeNickname']; const keys = ['CreateEvents', 'Administrator', 'AttachFiles', 'ChangeNickname'];
assert.equal( assert.equal(
@ -40,7 +40,7 @@ describe('PermissionsBitField', () => {
test('values', () => { test('values', () => {
const p = new PermissionsBitField(['CreateEvents']); const p = new PermissionsBitField(['CreateEvents']);
p.add('Administrator'); p.add(['Administrator']);
p.add(['ChangeNickname']); p.add(['ChangeNickname']);
assert.deepEqual(p.values(), [ assert.deepEqual(p.values(), [
PermissionFlagsBits.Administrator, PermissionFlagsBits.Administrator,
@ -51,15 +51,15 @@ describe('PermissionsBitField', () => {
test('missings', () => { test('missings', () => {
const p = new PermissionsBitField(['CreateEvents']); const p = new PermissionsBitField(['CreateEvents']);
p.add('Administrator'); p.add(['Administrator']);
p.add(['ChangeNickname']); p.add(['ChangeNickname']);
assert.deepEqual(p.missings('Connect'), [PermissionFlagsBits.Connect]); assert.deepEqual(p.missings(['Connect']), [PermissionFlagsBits.Connect]);
}); });
test('equals', () => { test('equals', () => {
const p = new PermissionsBitField(['CreateEvents']); const p = new PermissionsBitField(['CreateEvents']);
p.add(['ChangeNickname']); p.add(['ChangeNickname']);
assert.deepEqual(p.equals(['ChangeNickname', 'CreateEvents']), true); assert.deepEqual(p.equals(['ChangeNickname', 'CreateEvents']), true);
assert.deepEqual(p.equals('Connect'), false); assert.deepEqual(p.equals(['Connect']), false);
}); });
}); });