diff --git a/src/api/api.ts b/src/api/api.ts index 7dce284..17df664 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -1,5 +1,5 @@ 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 { WorkerSendApiRequest } from '../websocket/discord/worker'; import { CDNRouter, Router } from './Router'; @@ -25,6 +25,8 @@ export interface ApiHandler { debugger?: Logger; } +export type OnRatelimitCallback = (response: Response, request: ApiRequestOptions) => Awaitable; + export class ApiHandler { options: ApiHandlerInternalOptions; globalBlock = false; @@ -32,6 +34,7 @@ export class ApiHandler { readyQueue: (() => void)[] = []; cdn = CDNRouter.createProxy(); workerPromises?: Map any; reject: (error: any) => any }>; + onRatelimit?: OnRatelimitCallback; constructor(options: ApiHandlerOptions) { this.options = { @@ -246,6 +249,8 @@ export class ApiHandler { reject: (err: unknown) => void, now: number, ) { + await this.onRatelimit?.(response, request); + const content = `${JSON.stringify(request)} `; let retryAfter = Number(response.headers.get('x-ratelimit-reset-after') || response.headers.get('retry-after')) * 1000; diff --git a/src/commands/applications/chatcontext.ts b/src/commands/applications/chatcontext.ts index 04c358e..7daa06f 100644 --- a/src/commands/applications/chatcontext.ts +++ b/src/commands/applications/chatcontext.ts @@ -8,7 +8,7 @@ import type { OptionResolverStructure, WebhookMessageStructure, } 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 { ChatInputCommandInteraction } from '../../structures'; import { MessageFlags } from '../../types'; @@ -219,3 +219,9 @@ export class CommandContext< return true; } } + +export interface GuildCommandContext + extends Omit, 'guildId'>, 'guild'> { + guild(mode?: 'rest' | 'flow'): Promise>; + guild(mode?: 'cache'): ReturnCache | undefined>; +} diff --git a/src/commands/applications/entrycontext.ts b/src/commands/applications/entrycontext.ts index 7d4eb44..aebeb76 100644 --- a/src/commands/applications/entrycontext.ts +++ b/src/commands/applications/entrycontext.ts @@ -8,6 +8,7 @@ import type { import type { InteractionCreateBodyRequest, InteractionMessageUpdateBodyRequest, + MakeRequired, ModalCreateBodyRequest, UnionToTuple, When, @@ -128,3 +129,9 @@ export class EntryPointContext ex return this.interaction.member; } } + +export interface GuildEntryPointContext + extends Omit, 'guildId'>, 'guild'> { + guild(mode?: 'rest' | 'flow'): Promise>; + guild(mode?: 'cache'): ReturnCache | undefined>; +} diff --git a/src/commands/applications/menucontext.ts b/src/commands/applications/menucontext.ts index 53c33e7..f753e81 100644 --- a/src/commands/applications/menucontext.ts +++ b/src/commands/applications/menucontext.ts @@ -10,6 +10,7 @@ import { import { type InteractionCreateBodyRequest, type InteractionMessageUpdateBodyRequest, + type MakeRequired, type ModalCreateBodyRequest, type UnionToTuple, type When, @@ -165,3 +166,11 @@ export class MenuCommandContext< return this.interaction.data.type === ApplicationCommandType.Message; } } + +export interface GuildMenuCommandContext< + T extends MessageCommandInteraction | UserCommandInteraction, + M extends keyof RegisteredMiddlewares = never, +> extends Omit, 'guildId'>, 'guild'> { + guild(mode?: 'rest' | 'flow'): Promise>; + guild(mode?: 'cache'): ReturnCache | undefined>; +} diff --git a/src/commands/handler.ts b/src/commands/handler.ts index b9833e7..825643d 100644 --- a/src/commands/handler.ts +++ b/src/commands/handler.ts @@ -484,13 +484,13 @@ export class CommandHandler extends BaseHandler { option.onPermissionsFail?.bind(option) ?? commandInstance.onPermissionsFail?.bind(commandInstance) ?? this.client.options.commands?.defaults?.onPermissionsFail; - option.botPermissions = new PermissionsBitField().add( + option.botPermissions = PermissionsBitField.resolve( option.botPermissions ?? PermissionsBitField.None, - commandInstance.botPermissions, + commandInstance.botPermissions ?? PermissionsBitField.None, ); - option.defaultMemberPermissions ??= new PermissionsBitField().add( + option.defaultMemberPermissions ??= PermissionsBitField.resolve( option.defaultMemberPermissions ?? PermissionsBitField.None, - commandInstance.defaultMemberPermissions, + commandInstance.defaultMemberPermissions ?? PermissionsBitField.None, ); option.contexts ??= commandInstance.contexts; option.integrationTypes ??= commandInstance.integrationTypes; diff --git a/src/components/componentcontext.ts b/src/components/componentcontext.ts index 3a2f477..a4e1693 100644 --- a/src/components/componentcontext.ts +++ b/src/components/componentcontext.ts @@ -21,6 +21,7 @@ import type { ComponentInteractionMessageUpdate, InteractionCreateBodyRequest, InteractionMessageUpdateBodyRequest, + MakeRequired, ModalCreateBodyRequest, UnionToTuple, When, @@ -243,3 +244,9 @@ export interface ContextComponentCommandInteractionMap { MentionableSelect: MentionableSelectMenuInteraction; ChannelSelect: ChannelSelectMenuInteraction; } + +export interface GuildComponentContext + extends Omit, 'guildId'>, 'guild'> { + guild(mode?: 'rest' | 'flow'): Promise>; + guild(mode?: 'cache'): ReturnCache | undefined>; +} diff --git a/src/components/modalcontext.ts b/src/components/modalcontext.ts index 3af39f7..3f61830 100644 --- a/src/components/modalcontext.ts +++ b/src/components/modalcontext.ts @@ -10,6 +10,7 @@ import { BaseContext } from '../commands/basecontext'; import type { InteractionCreateBodyRequest, InteractionMessageUpdateBodyRequest, + MakeRequired, ModalCreateBodyRequest, UnionToTuple, When, @@ -187,3 +188,9 @@ export class ModalContext extends return true; } } + +export interface GuildModalContext + extends Omit, 'guildId'>, 'guild'> { + guild(mode?: 'rest' | 'flow'): Promise>; + guild(mode?: 'cache'): ReturnCache | undefined>; +} diff --git a/src/structures/extra/BitField.ts b/src/structures/extra/BitField.ts index 15de184..13a1a04 100644 --- a/src/structures/extra/BitField.ts +++ b/src/structures/extra/BitField.ts @@ -69,24 +69,32 @@ export class BitField { return this.bits; } - resolve(bits: BitFieldResolvable): bigint { - switch (typeof bits) { - case 'string': - return this.resolve(this.Flags[bits]); - case 'number': - return BigInt(bits); - case 'bigint': - return bits; - case 'object': { - if (!Array.isArray(bits)) { - throw new TypeError(`Cannot resolve permission: ${bits}`); + resolve(...bits: BitFieldResolvable[]): bigint { + let bitsResult = 0n; + + for (const bit of bits) { + switch (typeof bit) { + case 'string': + bitsResult |= this.resolve(this.Flags[bit]); + break; + case 'number': + bitsResult |= BigInt(bit); + break; + case 'bigint': + bitsResult |= bit; + break; + case 'object': { + if (!Array.isArray(bit)) { + throw new TypeError(`Cannot resolve permission: ${bit}`); + } + bitsResult |= bits.reduce((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; } } diff --git a/src/structures/extra/Permissions.ts b/src/structures/extra/Permissions.ts index 09519b8..bd914d9 100644 --- a/src/structures/extra/Permissions.ts +++ b/src/structures/extra/Permissions.ts @@ -21,26 +21,36 @@ export class PermissionsBitField extends BitField { return super.has(...bits); } - resolve(bits: BitFieldResolvable): bigint { - return PermissionsBitField.resolve(bits); + resolve(...bits: BitFieldResolvable[]): bigint { + return bits.reduce((acc, cur) => acc | PermissionsBitField.resolve(cur), BitField.None); } - static resolve(bits: BitFieldResolvable): bigint { - switch (typeof bits) { - case 'string': - return PermissionsBitField.resolve(PermissionFlagsBits[bits as keyof typeof PermissionFlagsBits]); - case 'number': - return BigInt(bits); - case 'bigint': - return bits; - case 'object': { - if (!Array.isArray(bits)) { - throw new TypeError(`Cannot resolve permission: ${bits}`); + static resolve(...bits: BitFieldResolvable[]): bigint { + let bitsResult = 0n; + + for (const bit of bits) { + switch (typeof bit) { + case 'string': + bitsResult |= PermissionsBitField.resolve(PermissionFlagsBits[bit as keyof typeof PermissionFlagsBits]); + break; + case 'number': + bitsResult |= BigInt(bit); + break; + case 'bigint': + bitsResult |= bit; + break; + case 'object': { + if (!Array.isArray(bit)) { + throw new TypeError(`Cannot resolve permission: ${bit}`); + } + bitsResult |= bit.reduce((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; } } diff --git a/tests/bitfield.test.mts b/tests/bitfield.test.mts index c07a55d..c444a36 100644 --- a/tests/bitfield.test.mts +++ b/tests/bitfield.test.mts @@ -1,65 +1,65 @@ -import { assert, describe, test } from 'vitest'; -import { BitField } from '../lib/structures/extra/BitField'; -import { PermissionsBitField } from '../lib/structures/extra/Permissions'; -import { PermissionFlagsBits } from '../lib/types'; - -describe('PermissionsBitField', () => { - test('constructor', () => { - const p = new PermissionsBitField(['CreateEvents']); - assert.equal(p.bits, PermissionFlagsBits.CreateEvents); - }); - - test('add', () => { - const p = new PermissionsBitField(['CreateEvents']); - p.add(['AttachFiles']); - p.add('ChangeNickname'); - assert.equal( - p.bits, - PermissionFlagsBits.CreateEvents | PermissionFlagsBits.AttachFiles | PermissionFlagsBits.ChangeNickname, - ); - }); - - test('remove', () => { - const p = new PermissionsBitField(['CreateEvents']); - p.remove('CreateEvents'); - assert.equal(p.bits, BitField.None); - }); - - test('keys', () => { - const p = new PermissionsBitField(['CreateEvents', 'Administrator']); - p.add(['AttachFiles']); - p.add('ChangeNickname'); - - const keys = ['CreateEvents', 'Administrator', 'AttachFiles', 'ChangeNickname']; - assert.equal( - true, - p.keys().every(x => keys.includes(x)), - ); - assert.equal(p.keys().length, 4); - }); - - test('values', () => { - const p = new PermissionsBitField(['CreateEvents']); - p.add('Administrator'); - p.add(['ChangeNickname']); - assert.deepEqual(p.values(), [ - PermissionFlagsBits.Administrator, - PermissionFlagsBits.ChangeNickname, - PermissionFlagsBits.CreateEvents, - ]); - }); - - test('missings', () => { - const p = new PermissionsBitField(['CreateEvents']); - p.add('Administrator'); - p.add(['ChangeNickname']); - assert.deepEqual(p.missings('Connect'), [PermissionFlagsBits.Connect]); - }); - - test('equals', () => { - const p = new PermissionsBitField(['CreateEvents']); - p.add(['ChangeNickname']); - assert.deepEqual(p.equals(['ChangeNickname', 'CreateEvents']), true); - assert.deepEqual(p.equals('Connect'), false); - }); -}); +import { assert, describe, test } from 'vitest'; +import { BitField } from '../lib/structures/extra/BitField'; +import { PermissionsBitField } from '../lib/structures/extra/Permissions'; +import { PermissionFlagsBits } from '../lib/types'; + +describe('PermissionsBitField', () => { + test('constructor', () => { + const p = new PermissionsBitField(['CreateEvents']); + assert.equal(p.bits, PermissionFlagsBits.CreateEvents); + }); + + test('add', () => { + const p = new PermissionsBitField(['CreateEvents']); + p.add(['AttachFiles']); + p.add('ChangeNickname'); + assert.equal( + p.bits, + PermissionFlagsBits.CreateEvents | PermissionFlagsBits.AttachFiles | PermissionFlagsBits.ChangeNickname, + ); + }); + + test('remove', () => { + const p = new PermissionsBitField(['CreateEvents']); + p.remove('CreateEvents'); + assert.equal(p.bits, BitField.None); + }); + + test('keys', () => { + const p = new PermissionsBitField(['CreateEvents', 'Administrator']); + p.add(['AttachFiles']); + p.add('ChangeNickname'); + + const keys = ['CreateEvents', 'Administrator', 'AttachFiles', 'ChangeNickname']; + assert.equal( + true, + p.keys().every(x => keys.includes(x)), + ); + assert.equal(p.keys().length, 4); + }); + + test('values', () => { + const p = new PermissionsBitField(['CreateEvents']); + p.add('Administrator'); + p.add(['ChangeNickname']); + assert.deepEqual(p.values(), [ + PermissionFlagsBits.Administrator, + PermissionFlagsBits.ChangeNickname, + PermissionFlagsBits.CreateEvents, + ]); + }); + + test('missings', () => { + const p = new PermissionsBitField(['CreateEvents']); + p.add('Administrator'); + p.add(['ChangeNickname']); + assert.deepEqual(p.missings('Connect'), [PermissionFlagsBits.Connect]); + }); + + test('equals', () => { + const p = new PermissionsBitField(['CreateEvents']); + p.add(['ChangeNickname']); + assert.deepEqual(p.equals(['ChangeNickname', 'CreateEvents']), true); + assert.deepEqual(p.equals('Connect'), false); + }); +});