onRatelimit callback & GuildCommandContext types(unnreaal update) (#279)

* feat: type GuildCommandContext, onRatelimit callback

* fix: test

* fix: use reduce
This commit is contained in:
MARCROCK22 2024-10-13 21:48:20 -04:00 committed by GitHub
parent 424970494b
commit 56246ed5cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 162 additions and 103 deletions

View File

@ -1,5 +1,5 @@
import { type UUID, randomUUID } from 'node:crypto'; import { type UUID, randomUUID } from 'node:crypto';
import { Logger, delay, lazyLoadPackage, snowflakeToTimestamp } from '../common'; import { type Awaitable, Logger, delay, lazyLoadPackage, snowflakeToTimestamp } from '../common';
import type { WorkerData } from '../websocket'; import type { WorkerData } from '../websocket';
import type { WorkerSendApiRequest } from '../websocket/discord/worker'; import type { WorkerSendApiRequest } from '../websocket/discord/worker';
import { CDNRouter, Router } from './Router'; import { CDNRouter, Router } from './Router';
@ -25,6 +25,8 @@ export interface ApiHandler {
debugger?: Logger; debugger?: Logger;
} }
export type OnRatelimitCallback = (response: Response, request: ApiRequestOptions) => Awaitable<any>;
export class ApiHandler { export class ApiHandler {
options: ApiHandlerInternalOptions; options: ApiHandlerInternalOptions;
globalBlock = false; globalBlock = false;
@ -32,6 +34,7 @@ export class ApiHandler {
readyQueue: (() => void)[] = []; readyQueue: (() => void)[] = [];
cdn = CDNRouter.createProxy(); cdn = CDNRouter.createProxy();
workerPromises?: Map<string, { resolve: (value: any) => any; reject: (error: any) => any }>; workerPromises?: Map<string, { resolve: (value: any) => any; reject: (error: any) => any }>;
onRatelimit?: OnRatelimitCallback;
constructor(options: ApiHandlerOptions) { constructor(options: ApiHandlerOptions) {
this.options = { this.options = {
@ -246,6 +249,8 @@ export class ApiHandler {
reject: (err: unknown) => void, reject: (err: unknown) => void,
now: number, now: number,
) { ) {
await this.onRatelimit?.(response, request);
const content = `${JSON.stringify(request)} `; const content = `${JSON.stringify(request)} `;
let retryAfter = let retryAfter =
Number(response.headers.get('x-ratelimit-reset-after') || response.headers.get('retry-after')) * 1000; Number(response.headers.get('x-ratelimit-reset-after') || response.headers.get('retry-after')) * 1000;

View File

@ -8,7 +8,7 @@ import type {
OptionResolverStructure, OptionResolverStructure,
WebhookMessageStructure, WebhookMessageStructure,
} from '../../client/transformers'; } from '../../client/transformers';
import type { If, UnionToTuple, When } from '../../common'; import type { If, MakeRequired, UnionToTuple, When } from '../../common';
import type { InteractionCreateBodyRequest, InteractionMessageUpdateBodyRequest } from '../../common/types/write'; import type { InteractionCreateBodyRequest, InteractionMessageUpdateBodyRequest } from '../../common/types/write';
import { ChatInputCommandInteraction } from '../../structures'; import { ChatInputCommandInteraction } from '../../structures';
import { MessageFlags } from '../../types'; import { MessageFlags } from '../../types';
@ -219,3 +219,9 @@ export class CommandContext<
return true; return true;
} }
} }
export interface GuildCommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>
extends Omit<MakeRequired<CommandContext<T, M>, 'guildId'>, 'guild'> {
guild(mode?: 'rest' | 'flow'): Promise<GuildStructure<'cached' | 'api'>>;
guild(mode?: 'cache'): ReturnCache<GuildStructure<'cached'> | undefined>;
}

View File

@ -8,6 +8,7 @@ import type {
import type { import type {
InteractionCreateBodyRequest, InteractionCreateBodyRequest,
InteractionMessageUpdateBodyRequest, InteractionMessageUpdateBodyRequest,
MakeRequired,
ModalCreateBodyRequest, ModalCreateBodyRequest,
UnionToTuple, UnionToTuple,
When, When,
@ -128,3 +129,9 @@ export class EntryPointContext<M extends keyof RegisteredMiddlewares = never> ex
return this.interaction.member; return this.interaction.member;
} }
} }
export interface GuildEntryPointContext<M extends keyof RegisteredMiddlewares = never>
extends Omit<MakeRequired<EntryPointContext<M>, 'guildId'>, 'guild'> {
guild(mode?: 'rest' | 'flow'): Promise<GuildStructure<'cached' | 'api'>>;
guild(mode?: 'cache'): ReturnCache<GuildStructure<'cached'> | undefined>;
}

View File

@ -10,6 +10,7 @@ import {
import { import {
type InteractionCreateBodyRequest, type InteractionCreateBodyRequest,
type InteractionMessageUpdateBodyRequest, type InteractionMessageUpdateBodyRequest,
type MakeRequired,
type ModalCreateBodyRequest, type ModalCreateBodyRequest,
type UnionToTuple, type UnionToTuple,
type When, type When,
@ -165,3 +166,11 @@ export class MenuCommandContext<
return this.interaction.data.type === ApplicationCommandType.Message; return this.interaction.data.type === ApplicationCommandType.Message;
} }
} }
export interface GuildMenuCommandContext<
T extends MessageCommandInteraction | UserCommandInteraction,
M extends keyof RegisteredMiddlewares = never,
> extends Omit<MakeRequired<MenuCommandContext<T, M>, 'guildId'>, 'guild'> {
guild(mode?: 'rest' | 'flow'): Promise<GuildStructure<'cached' | 'api'>>;
guild(mode?: 'cache'): ReturnCache<GuildStructure<'cached'> | undefined>;
}

View File

@ -484,13 +484,13 @@ 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 = new PermissionsBitField().add( option.botPermissions = PermissionsBitField.resolve(
option.botPermissions ?? PermissionsBitField.None, option.botPermissions ?? PermissionsBitField.None,
commandInstance.botPermissions, commandInstance.botPermissions ?? PermissionsBitField.None,
); );
option.defaultMemberPermissions ??= new PermissionsBitField().add( option.defaultMemberPermissions ??= PermissionsBitField.resolve(
option.defaultMemberPermissions ?? PermissionsBitField.None, option.defaultMemberPermissions ?? PermissionsBitField.None,
commandInstance.defaultMemberPermissions, commandInstance.defaultMemberPermissions ?? PermissionsBitField.None,
); );
option.contexts ??= commandInstance.contexts; option.contexts ??= commandInstance.contexts;
option.integrationTypes ??= commandInstance.integrationTypes; option.integrationTypes ??= commandInstance.integrationTypes;

View File

@ -21,6 +21,7 @@ import type {
ComponentInteractionMessageUpdate, ComponentInteractionMessageUpdate,
InteractionCreateBodyRequest, InteractionCreateBodyRequest,
InteractionMessageUpdateBodyRequest, InteractionMessageUpdateBodyRequest,
MakeRequired,
ModalCreateBodyRequest, ModalCreateBodyRequest,
UnionToTuple, UnionToTuple,
When, When,
@ -243,3 +244,9 @@ export interface ContextComponentCommandInteractionMap {
MentionableSelect: MentionableSelectMenuInteraction; MentionableSelect: MentionableSelectMenuInteraction;
ChannelSelect: ChannelSelectMenuInteraction; ChannelSelect: ChannelSelectMenuInteraction;
} }
export interface GuildComponentContext<M extends keyof RegisteredMiddlewares = never>
extends Omit<MakeRequired<ComponentContext<M>, 'guildId'>, 'guild'> {
guild(mode?: 'rest' | 'flow'): Promise<GuildStructure<'cached' | 'api'>>;
guild(mode?: 'cache'): ReturnCache<GuildStructure<'cached'> | undefined>;
}

View File

@ -10,6 +10,7 @@ import { BaseContext } from '../commands/basecontext';
import type { import type {
InteractionCreateBodyRequest, InteractionCreateBodyRequest,
InteractionMessageUpdateBodyRequest, InteractionMessageUpdateBodyRequest,
MakeRequired,
ModalCreateBodyRequest, ModalCreateBodyRequest,
UnionToTuple, UnionToTuple,
When, When,
@ -187,3 +188,9 @@ export class ModalContext<M extends keyof RegisteredMiddlewares = never> extends
return true; return true;
} }
} }
export interface GuildModalContext<M extends keyof RegisteredMiddlewares = never>
extends Omit<MakeRequired<ModalContext<M>, 'guildId'>, 'guild'> {
guild(mode?: 'rest' | 'flow'): Promise<GuildStructure<'cached' | 'api'>>;
guild(mode?: 'cache'): ReturnCache<GuildStructure<'cached'> | undefined>;
}

View File

@ -69,24 +69,32 @@ export class BitField<T extends object> {
return this.bits; return this.bits;
} }
resolve(bits: BitFieldResolvable<T>): bigint { resolve(...bits: BitFieldResolvable<T>[]): bigint {
switch (typeof bits) { let bitsResult = 0n;
case 'string':
return this.resolve(this.Flags[bits]); for (const bit of bits) {
case 'number': switch (typeof bit) {
return BigInt(bits); case 'string':
case 'bigint': bitsResult |= this.resolve(this.Flags[bit]);
return bits; break;
case 'object': { case 'number':
if (!Array.isArray(bits)) { bitsResult |= BigInt(bit);
throw new TypeError(`Cannot resolve permission: ${bits}`); break;
case 'bigint':
bitsResult |= bit;
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;
} }
return bits.map(x => this.resolve(x)).reduce((acc, cur) => acc | cur, BitField.None); default:
throw new TypeError(`Cannot resolve permission: ${typeof bit === 'symbol' ? String(bit) : (bit as unknown)}`);
} }
default:
throw new TypeError(
`Cannot resolve permission: ${typeof bits === 'symbol' ? String(bits) : (bits as unknown)}`,
);
} }
return bitsResult;
} }
} }

View File

@ -21,26 +21,36 @@ export class PermissionsBitField extends BitField<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>[]): bigint {
return PermissionsBitField.resolve(bits); return 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>[]): bigint {
switch (typeof bits) { let bitsResult = 0n;
case 'string':
return PermissionsBitField.resolve(PermissionFlagsBits[bits as keyof typeof PermissionFlagsBits]); for (const bit of bits) {
case 'number': switch (typeof bit) {
return BigInt(bits); case 'string':
case 'bigint': bitsResult |= PermissionsBitField.resolve(PermissionFlagsBits[bit as keyof typeof PermissionFlagsBits]);
return bits; break;
case 'object': { case 'number':
if (!Array.isArray(bits)) { bitsResult |= BigInt(bit);
throw new TypeError(`Cannot resolve permission: ${bits}`); break;
case 'bigint':
bitsResult |= bit;
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;
} }
return bits.map(x => PermissionsBitField.resolve(x)).reduce((acc, cur) => acc | cur, BitField.None); default:
throw new TypeError(`Cannot resolve permission: ${typeof bit === 'symbol' ? String(bit) : (bit as any)}`);
} }
default:
throw new TypeError(`Cannot resolve permission: ${typeof bits === 'symbol' ? String(bits) : (bits as any)}`);
} }
return bitsResult;
} }
} }

View File

@ -1,65 +1,65 @@
import { assert, describe, test } from 'vitest'; import { assert, describe, test } from 'vitest';
import { BitField } from '../lib/structures/extra/BitField'; import { BitField } from '../lib/structures/extra/BitField';
import { PermissionsBitField } from '../lib/structures/extra/Permissions'; import { PermissionsBitField } from '../lib/structures/extra/Permissions';
import { PermissionFlagsBits } from '../lib/types'; import { PermissionFlagsBits } from '../lib/types';
describe('PermissionsBitField', () => { describe('PermissionsBitField', () => {
test('constructor', () => { test('constructor', () => {
const p = new PermissionsBitField(['CreateEvents']); const p = new PermissionsBitField(['CreateEvents']);
assert.equal(p.bits, PermissionFlagsBits.CreateEvents); assert.equal(p.bits, PermissionFlagsBits.CreateEvents);
}); });
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,
); );
}); });
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(
true, true,
p.keys().every(x => keys.includes(x)), p.keys().every(x => keys.includes(x)),
); );
assert.equal(p.keys().length, 4); assert.equal(p.keys().length, 4);
}); });
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,
PermissionFlagsBits.ChangeNickname, PermissionFlagsBits.ChangeNickname,
PermissionFlagsBits.CreateEvents, PermissionFlagsBits.CreateEvents,
]); ]);
}); });
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);
}); });
}); });