Compare commits

...

46 Commits
v3.1.1 ... main

Author SHA1 Message Date
6d5b162a2d
chore: bump version to 3.2.4 2025-06-23 10:13:49 -04:00
a15eb2035f
Merge branch 'main' of https://github.com/tiramisulabs/seyfert 2025-06-23 10:04:21 -04:00
c4d1c1ce90
fix(api): handle 429 in shared scopes 2025-06-23 10:04:07 -04:00
eadf63450b chore: bump version to 3.2.3 2025-06-22 23:55:03 -04:00
0720d0c170 fix: accept null as content value 2025-06-22 00:20:52 -04:00
798f648955
feat: waitFor modals (#346)
* feat: modal#waitFor

* refactor: update waitFor method to improve promise handling and timeout logic

* feat: enhance modal handling with options for wait time and improved promise resolution

---------

Co-authored-by: MARCROCK22 <marcos22dev@gmail.com>
2025-06-19 23:12:42 -04:00
6e037ae964 chore: bump version to 3.2.2 2025-06-17 16:48:01 -04:00
0608c7ad85 fix: i dont know why it was like that 2025-06-17 16:47:46 -04:00
db38f49ca9 chore: bump 3.2.1 2025-06-17 16:19:49 -04:00
325a39f1bf ci: add npm token verification step in dev workflow 2025-06-14 19:42:42 -04:00
e810b6eb52 refactor: update customId matching logic and rename asyncRun to waitFor 2025-06-14 19:24:53 -04:00
Free 公園
b84d462ce2
feat: use regex in customId component command (#345) 2025-06-13 22:08:47 -04:00
61413181bb Merge branch 'main' of https://github.com/tiramisulabs/seyfert 2025-06-13 20:34:56 -04:00
30a066e68d feat: awaitable collector 2025-06-13 20:34:43 -04:00
95619a8a89 refactor: change listActiveThreads method to remove async keyword 2025-06-13 13:38:26 -04:00
84806f3c54 Merge branch 'main' of https://github.com/tiramisulabs/seyfert 2025-06-13 13:30:49 -04:00
422cfb2a80 feat: list active threads 2025-06-13 04:11:07 -04:00
David
3f6c6dc4d4
fix: add error handling for undefined locale (#344) 2025-06-13 01:11:56 -04:00
d6a405469d fix: revert, rerun later 2025-06-12 22:32:09 -04:00
1903257e46 fix: streamline publish workflow by removing unnecessary steps 2025-06-12 22:27:50 -04:00
0b00e2d19b fix: add id-token write permission to publish workflow 2025-06-12 22:23:18 -04:00
970ed980cf chore: update version 3.2.0 2025-06-12 22:14:20 -04:00
935cce99f6 fix: mentionableMenu accept channels 2025-06-12 18:12:53 -04:00
5de23ffe58
fix: Error with ThreadChannel#webhooks for bad patchClass interaction (#341) 2025-05-23 14:53:50 -04:00
e4f715515c fix: the fix 2025-05-23 06:43:57 -04:00
7998577b07 fix: workers correctly assing ws properties 2025-05-23 06:15:08 -04:00
05cdc20d7f fix: remove unused ObjectToLower type declarations from index.ts 2025-05-22 17:02:06 -04:00
e095edd20f fix: update ObjectToLower and ObjectToSnake types to preserve numeric keys 2025-05-22 17:01:12 -04:00
d9aef4335d fix: xd 2025-05-20 20:20:11 -04:00
3350570334 fix: make pendingGuilds optional and update related logic for guild handling 2025-05-19 18:16:47 -04:00
c20f2fd0a3 feat: implement heartbeater for managing worker heartbeat messages 2025-05-17 12:58:39 -04:00
0d8ad177b7 fix: replace ws.removeListener with ws.removeEventListener for 'pong' event 2025-05-16 08:46:25 -04:00
089cfab6da fix: replace ws.on with ws.addEventListener for 'pong' event 2025-05-16 08:18:52 -04:00
fb9c59a51b feat: add resetTimeouts method to ComponentHandler 2025-05-12 18:12:10 -04:00
92ab65be7b feat: make deferReplyResponse and reply awaitable 2025-05-08 21:09:15 -04:00
23c9c2a710 feat: workerClient.calculateShardId 2025-05-07 18:41:46 -04:00
b4324f9487 fix: type error 2025-05-05 20:01:16 -04:00
f74b75fcef fix: change return types from 'never' to 'undefined' for various API methods 2025-04-29 08:30:01 -04:00
9cbde76ad0 fix: update promise return types to undefined for delete and post methods 2025-04-29 08:25:05 -04:00
66b5ca34a9 fix: dx 2025-04-28 04:04:35 -04:00
4574ca018f
fix: xd 2025-04-28 03:44:17 -04:00
34ca3e293e
fix(roles): delete don't return role anymore 2025-04-28 03:42:46 -04:00
a358bb0a04 chore: this thing 2025-04-27 21:30:28 -04:00
6cf75e9ad2 chore: 3.1.2 2025-04-27 21:24:24 -04:00
60108b4c42 fix: empty data 2025-04-27 16:25:30 -04:00
e8c686a1df chore: update dependencies 2025-04-27 14:27:21 -04:00
101 changed files with 1158 additions and 1283 deletions

View File

@ -12,8 +12,10 @@ jobs:
permissions:
id-token: write
if: github.repository == 'tiramisulabs/seyfert'
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
steps:
- name: check out code
- name: Check out code
uses: actions/checkout@v4
- name: Install Node
@ -29,6 +31,9 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Verify npm token
run: npm whoami
- name: Publish dev tag
id: publish
run: |
@ -36,5 +41,3 @@ jobs:
echo "New version: $new_version"
npm config set //registry.npmjs.org/:_authToken ${NODE_AUTH_TOKEN}
npm publish --provenance --tag=dev
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@ -1,80 +0,0 @@
name: Create Release
on:
push:
branches:
- build
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: check out code
uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
- uses: pnpm/action-setup@v4
with:
version: 9
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build project
run: pnpm run build
- name: Get version from package.json
id: get_version
run: echo "::set-output name=version::$(node -p "require('./package.json').version")"
- name: Set repository variable
run: echo "REPO=${{ github.repository }}" >> $GITHUB_ENV
- name: Get and format commits
run: |
last_tag=$(git tag --sort=-v:refname | head -n 1)
if [ -z "$last_tag" ]; then
git log --pretty=format:"%h - %s" > commits.txt
else
git log ${last_tag}..HEAD --pretty=format:"%h - %s" > commits.txt
fi
sed -e "s|^\([a-f0-9]\+\) - \(.*\)|- [\1](https://github.com/$REPO/commit/\1) - \2|" commits.txt > formatted_commits.txt
echo "Changes in this release:" > release_body.txt
cat formatted_commits.txt >> release_body.txt
- name: Create tag
run: |
git tag v${{ steps.get_version.outputs.version }}
git push origin v${{ steps.get_version.outputs.version }}
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: v${{ steps.get_version.outputs.version }}
release_name: Release v${{ steps.get_version.outputs.version }}
body_path: release_body.txt
draft: false
prerelease: false
- name: Zip dist folder
run: zip -r dist.zip src
- name: Upload Release Asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./dist.zip
asset_name: dist.zip
asset_content_type: application/zip

View File

@ -1 +1 @@
npx lint-staged
npx biome check --write

View File

@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
@ -8,7 +8,16 @@
},
"files": {
"ignoreUnknown": true,
"ignore": ["node_modules/", "build", "lib", "__test__", "package.json", "tsconfig.json", ".vscode"]
"includes": [
"**/src/**",
"!/node_modules/",
"!/build",
"!/lib",
"!/__test__",
"!/package.json",
"!/tsconfig.json",
"!/.vscode"
]
},
"formatter": {
"enabled": true,
@ -18,14 +27,17 @@
"lineEnding": "crlf",
"formatWithErrors": true
},
"organizeImports": {
"enabled": true
"assist": {
"actions": {
"source": {
"organizeImports": "on"
}
}
},
"linter": {
"enabled": true,
"rules": {
"recommended": false,
"all": true,
"security": {
"noGlobalEval": "off"
},
@ -41,9 +53,7 @@
},
"correctness": {
"noNodejsModules": "off",
"useImportExtensions": "off",
"noUnusedFunctionParameters": "off",
"noUnusedVariables": "off"
"useImportExtensions": "off"
},
"style": {
"noDefaultExport": "off",
@ -56,8 +66,7 @@
"noParameterAssign": "off",
"useFilenamingConvention": "off",
"useEnumInitializers": "off",
"useExplicitLengthCheck": "off",
"noNamespaceImport": "off"
"useExplicitLengthCheck": "off"
},
"complexity": {
"noForEach": "off",
@ -69,7 +78,8 @@
"noBarrelFile": "off",
"noDelete": "off",
"noReExportAll": "off",
"useTopLevelRegex": "off"
"useTopLevelRegex": "off",
"noNamespaceImport": "off"
}
}
},

View File

@ -1,6 +1,6 @@
{
"name": "seyfert",
"version": "3.1.1",
"version": "3.2.4",
"description": "The most advanced framework for discord bots",
"main": "./lib/index.js",
"module": "./lib/index.js",
@ -21,15 +21,14 @@
"author": "MARCROCK22",
"license": "MIT",
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@changesets/cli": "^2.28.1",
"@commitlint/cli": "^19.8.0",
"@commitlint/config-conventional": "^19.8.0",
"@types/node": "^22.14.0",
"@biomejs/biome": "2.0.0",
"@changesets/cli": "^2.29.4",
"@commitlint/cli": "^19.8.1",
"@commitlint/config-conventional": "^19.8.1",
"@types/node": "^24.0.3",
"husky": "^9.1.7",
"lint-staged": "^15.5.0",
"typescript": "^5.8.2",
"vitest": "^3.1.1"
"typescript": "^5.8.3",
"vitest": "^3.2.4"
},
"homepage": "https://seyfert.dev",
"repository": {
@ -65,11 +64,6 @@
"url": "https://github.com/Drylozu"
}
],
"lint-staged": {
"*.ts": [
"biome check --write"
]
},
"pnpm": {
"onlyBuiltDependencies": [
"@biomejs/biome",

1311
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -71,7 +71,7 @@ export interface ApplicationRoutes {
patch(
args: RestArguments<RESTPatchAPIApplicationGuildCommandJSONBody>,
): Promise<RESTPatchAPIApplicationGuildCommandResult>;
delete(args?: RestArgumentsNoBody): Promise<never>;
delete(args?: RestArgumentsNoBody): Promise<undefined>;
permissions: {
get(args?: RestArgumentsNoBody): Promise<RESTGetAPIGuildApplicationCommandsPermissionsResult>;
put(
@ -92,7 +92,7 @@ export interface ApplicationRoutes {
patch(
args: RestArguments<RESTPatchAPIApplicationCommandJSONBody>,
): Promise<RESTPatchAPIApplicationCommandResult>;
delete(args?: RestArgumentsNoBody): Promise<never>;
delete(args?: RestArgumentsNoBody): Promise<undefined>;
};
};
'role-connections': {
@ -122,10 +122,10 @@ export interface ApplicationRoutes {
(
id: string,
): {
get(args?: RestArgumentsNoBody<never>): Promise<RESTGetAPIEntitlementResult>;
delete(args?: RestArgumentsNoBody): Promise<never>;
get(args?: RestArgumentsNoBody): Promise<RESTGetAPIEntitlementResult>;
delete(args?: RestArgumentsNoBody): Promise<undefined>;
consume: {
post(args?: RestArgumentsNoBody): Promise<never>;
post(args?: RestArgumentsNoBody): Promise<undefined>;
};
};
};

View File

@ -190,7 +190,7 @@ export interface ChannelRoutes {
post(args: RestArguments<RESTPostAPIChannelWebhookJSONBody>): Promise<RESTPostAPIChannelWebhookResult>;
};
'voice-status': {
put(args: RestArguments<{ status: string | null }>): Promise<never>;
put(args: RestArguments<{ status: string | null }>): Promise<undefined>;
};
polls(messageId: string): {
answers(id: ValidAnswerId): {
@ -201,7 +201,7 @@ export interface ChannelRoutes {
};
};
'send-soundboard-sound': {
post(args: RestArguments<RESTPostAPISendSoundboardSound>): Promise<never>;
post(args: RestArguments<RESTPostAPISendSoundboardSound>): Promise<undefined>;
};
};
}

View File

@ -39,10 +39,10 @@ import type {
RESTGetAPIGuildRolesResult,
RESTGetAPIGuildScheduledEventQuery,
RESTGetAPIGuildScheduledEventResult,
RESTGetAPIGuildScheduledEventUsersQuery,
RESTGetAPIGuildScheduledEventUsersResult,
RESTGetAPIGuildScheduledEventsQuery,
RESTGetAPIGuildScheduledEventsResult,
RESTGetAPIGuildScheduledEventUsersQuery,
RESTGetAPIGuildScheduledEventUsersResult,
RESTGetAPIGuildSoundboardSoundsResult,
RESTGetAPIGuildStickerResult,
RESTGetAPIGuildStickersResult,
@ -107,12 +107,12 @@ import type {
RESTPostAPIGuildSoundboardSoundResult,
RESTPostAPIGuildStickerFormDataBody,
RESTPostAPIGuildStickerResult,
RESTPostAPIGuildTemplatesJSONBody,
RESTPostAPIGuildTemplatesResult,
RESTPostAPIGuildsJSONBody,
RESTPostAPIGuildsMFAJSONBody,
RESTPostAPIGuildsMFAResult,
RESTPostAPIGuildsResult,
RESTPostAPIGuildTemplatesJSONBody,
RESTPostAPIGuildTemplatesResult,
RESTPostAPITemplateCreateGuildJSONBody,
RESTPostAPITemplateCreateGuildResult,
RESTPutAPIGuildBanJSONBody,
@ -361,7 +361,7 @@ export interface GuildRoutes {
patch(
args?: RestArguments<RESTPatchAPIGuildSoundboardSound>,
): Promise<RESTPatchAPIGuildSoundboardSoundResult>;
delete(args?: RestArgumentsNoBody): Promise<never>;
delete(args?: RestArgumentsNoBody): Promise<undefined>;
};
};
};

View File

@ -1,11 +1,11 @@
import { type UUID, randomUUID } from 'node:crypto';
import { type Awaitable, BASE_HOST, Logger, delay, lazyLoadPackage, snowflakeToTimestamp } from '../common';
import { randomUUID, type UUID } from 'node:crypto';
import { type Awaitable, BASE_HOST, delay, Logger, lazyLoadPackage, snowflakeToTimestamp } from '../common';
import { toArrayBuffer, toBuffer } from '../common/it/utils';
import type { WorkerData } from '../websocket';
import type { WorkerSendApiRequest } from '../websocket/discord/worker';
import { Bucket } from './bucket';
import { CDNRouter, Router } from './Router';
import type { APIRoutes } from './Routes';
import { Bucket } from './bucket';
import {
type ApiHandlerInternalOptions,
type ApiHandlerOptions,
@ -291,17 +291,19 @@ export class ApiHandler {
await this.onRatelimit?.(response, request);
const content = `${JSON.stringify(request)} `;
let retryAfter =
let retryAfter: number | undefined;
const data = JSON.parse(result);
if (data.retry_after) retryAfter = Math.ceil(data.retry_after * 1000);
retryAfter ??=
Number(response.headers.get('x-ratelimit-reset-after') || response.headers.get('retry-after')) * 1000;
if (Number.isNaN(retryAfter)) {
try {
retryAfter = JSON.parse(result).retry_after * 1000;
} catch (err) {
this.debugger?.warn(`Unexpected error: ${err}`);
reject(err);
return false;
}
this.debugger?.warn(`${route} Could not extract retry_after from 429 response. ${result}`);
next();
reject(new Error('Could not extract retry_after from 429 response.'));
return false;
}
this.debugger?.info(
@ -384,11 +386,7 @@ export class ApiHandler {
}
}
parseRequest(options: {
url: string;
headers: RequestHeaders;
request: ApiRequestOptions;
}) {
parseRequest(options: { url: string; headers: RequestHeaders; request: ApiRequestOptions }) {
let finalUrl = options.url;
let data: string | FormData | undefined;
if (options.request.auth) {

View File

@ -1,4 +1,4 @@
export * from './api';
export * from './Router';
export * from './Routes';
export * from './api';
export * from './shared';

View File

@ -1,6 +1,6 @@
import { type ActionRow, fromComponent } from '.';
import { type ColorResolvable, type RestOrArray, resolveColor } from '../common';
import { type APIContainerComponent, ComponentType } from '../types';
import { type ActionRow, fromComponent } from '.';
import { BaseComponentBuilder } from './Base';
import type { File } from './File';
import type { MediaGallery } from './MediaGallery';

View File

@ -1,6 +1,6 @@
import { type Button, fromComponent } from '.';
import type { RestOrArray } from '../common';
import { type APISectionComponent, ComponentType } from '../types';
import { type Button, fromComponent } from '.';
import { BaseComponentBuilder } from './Base';
import type { TextDisplay } from './TextDisplay';
import type { Thumbnail } from './Thumbnail';
@ -9,7 +9,7 @@ export class Section<
Ac extends Button | Thumbnail = Button | Thumbnail,
> extends BaseComponentBuilder<APISectionComponent> {
components: TextDisplay[];
accessory!: Ac;
accessory?: Ac;
constructor({ components, accessory, ...data }: Partial<APISectionComponent> = {}) {
super({ type: ComponentType.Section, ...data });
this.components = (components?.map(component => fromComponent(component)) ?? []) as TextDisplay[];
@ -46,6 +46,7 @@ export class Section<
* @returns The JSON representation of this section
*/
toJSON() {
if (!this.accessory) throw new Error('Cannot convert to JSON without an accessory.');
return {
...this.data,
components: this.components.map(component => component.toJSON()),

View File

@ -161,7 +161,7 @@ export class RoleSelectMenu extends SelectMenu<APIRoleSelectComponent> {
}
}
export type MentionableDefaultElement = { id: string; type: keyof Omit<typeof SelectMenuDefaultValueType, 'Channel'> };
export type MentionableDefaultElement = { id: string; type: keyof typeof SelectMenuDefaultValueType };
/**
* Represents a Select Menu for selecting mentionable entities.

View File

@ -94,7 +94,11 @@ export class LimitedMemoryAdapter<T> implements Adapter {
}
private __set(key: string, data: any) {
const __guildId = Array.isArray(data) ? data[0].guild_id : data.guild_id;
const isArray = Array.isArray(data);
if (isArray && data.length === 0) {
return;
}
const __guildId = isArray ? data[0].guild_id : data.guild_id;
const namespace = `${key.split('.')[0]}${__guildId ? `.${__guildId}` : ''}`;
const self = this;
if (!this.storage.has(namespace)) {

12
src/cache/index.ts vendored
View File

@ -1,11 +1,5 @@
import { type If, Logger } from '../common';
import type { Adapter } from './adapters';
import { Guilds } from './resources/guilds';
import { Users } from './resources/users';
import type { InternalOptions, UsingClient } from '../commands';
import { type If, Logger } from '../common';
import {
type APIChannel,
type APIEmoji,
@ -20,9 +14,11 @@ import {
GuildMemberFlags,
OverwriteType,
} from '../types';
import type { Adapter } from './adapters';
import { Bans } from './resources/bans';
import { Channels } from './resources/channels';
import { Emojis } from './resources/emojis';
import { Guilds } from './resources/guilds';
import { Members } from './resources/members';
import { Messages } from './resources/messages';
import { Overwrites } from './resources/overwrites';
@ -30,7 +26,9 @@ import { Presences } from './resources/presence';
import { Roles } from './resources/roles';
import { StageInstances } from './resources/stage-instances';
import { Stickers } from './resources/stickers';
import { Users } from './resources/users';
import { VoiceStates } from './resources/voice-states';
export { BaseResource } from './resources/default/base';
export { GuildBasedResource } from './resources/default/guild-based';
export { GuildRelatedResource } from './resources/default/guild-related';

View File

@ -1,7 +1,7 @@
import type { Cache, CacheFrom, ReturnCache } from '..';
import { type GuildStructure, Transformers } from '../../client/transformers';
import { fakePromise } from '../../common';
import type { APIGuild, GatewayGuildCreateDispatchData } from '../../types';
import type { Cache, CacheFrom, ReturnCache } from '..';
import { BaseResource } from './default/base';
export class Guilds extends BaseResource<any, APIGuild | GatewayGuildCreateDispatchData> {

View File

@ -1,5 +1,7 @@
import { promises } from 'node:fs';
import { join } from 'node:path';
import { ApiHandler } from '../api';
import { isBufferLike } from '../api/utils/utils';
import type { Adapter, DisabledCache } from '../cache';
import { Cache, MemoryAdapter } from '../cache';
import type {
@ -16,34 +18,31 @@ import type {
UsingClient,
} from '../commands';
import { IgnoreCommand, type InferWithPrefix, type MiddlewareContext } from '../commands/applications/shared';
import { HandleCommand } from '../commands/handle';
import { CommandHandler } from '../commands/handler';
import {
ApplicationShorter,
assertString,
ChannelShorter,
EmojiShorter,
filterSplit,
GuildShorter,
InteractionShorter,
InvitesShorter,
LogLevels,
Logger,
LogLevels,
type MakeRequired,
MemberShorter,
MergeOptions,
MessageShorter,
magicImport,
ReactionShorter,
RoleShorter,
TemplateShorter,
ThreadShorter,
UsersShorter,
WebhookShorter,
assertString,
filterSplit,
magicImport,
} from '../common';
import { promises } from 'node:fs';
import { isBufferLike } from '../api/utils/utils';
import { HandleCommand } from '../commands/handle';
import { BanShorter } from '../common/shorters/bans';
import { SoundboardShorter } from '../common/shorters/soundboard';
import { VoiceStateShorter } from '../common/shorters/voiceStates';

View File

@ -1,17 +1,17 @@
import type { CommandContext, Message } from '..';
import {
type Awaitable,
assertString,
type DeepPartial,
type If,
lazyLoadPackage,
type PickPartial,
type WatcherPayload,
type WatcherSendToShard,
assertString,
lazyLoadPackage,
} from '../common';
import { EventHandler } from '../events';
import type { GatewayDispatchPayload, GatewayPresenceUpdateData } from '../types';
import { ShardManager, type ShardManagerOptions, properties } from '../websocket';
import { properties, ShardManager, type ShardManagerOptions } from '../websocket';
import { MemberUpdateHandler } from '../websocket/discord/events/memberUpdate';
import { PresenceUpdateHandler } from '../websocket/discord/events/presenceUpdate';
import type { BaseClientOptions, InternalRuntimeConfig, ServicesOptions, StartOptions } from './base';
@ -219,8 +219,8 @@ export interface ClientOptions extends BaseClientOptions {
};
commands?: BaseClientOptions['commands'] & {
prefix?: (message: MessageStructure) => Awaitable<string[]>;
deferReplyResponse?: (ctx: CommandContext) => Parameters<Message['write']>[0];
reply?: (ctx: CommandContext) => boolean;
deferReplyResponse?: (ctx: CommandContext) => Awaitable<Parameters<Message['write']>[0]>;
reply?: (ctx: CommandContext) => Awaitable<boolean>;
};
handlePayload?: ShardManagerOptions['handlePayload'];
resharding?: PickPartial<NonNullable<ShardManagerOptions['resharding']>, 'getInfo'>;

View File

@ -1,4 +1,4 @@
import { type UUID, randomUUID } from 'node:crypto';
import { randomUUID, type UUID } from 'node:crypto';
import type { UsingClient } from '../commands';
import type { Awaitable, CamelCase } from '../common';
import type { CallbackEventHandler, CustomEventsKeys, GatewayEvents } from '../events';

View File

@ -1,5 +1,5 @@
export type { RuntimeConfig, RuntimeConfigHTTP } from './base';
export * from './client';
export * from './httpclient';
export * from './workerclient';
export * from './transformers';
export * from './workerclient';

View File

@ -8,8 +8,8 @@ import {
BaseGuildChannel,
CategoryChannel,
ClientUser,
DMChannel,
DirectoryChannel,
DMChannel,
Emoji,
Entitlement,
ForumChannel,

View File

@ -1,11 +1,24 @@
import { type UUID, randomUUID } from 'node:crypto';
import { randomUUID, type UUID } from 'node:crypto';
import { ApiHandler, Logger } from '..';
import { WorkerAdapter } from '../cache';
import { type DeepPartial, LogLevels, type MakeRequired, type When, lazyLoadPackage } from '../common';
import {
type Awaitable,
calculateShardId,
type DeepPartial,
LogLevels,
lazyLoadPackage,
type MakeRequired,
type When,
} from '../common';
import { EventHandler } from '../events';
import type { GatewayDispatchPayload, GatewaySendPayload } from '../types';
import { Shard, type ShardManagerOptions, ShardSocketCloseCodes, type WorkerData, properties } from '../websocket';
import { properties, Shard, type ShardManagerOptions, ShardSocketCloseCodes, type WorkerData } from '../websocket';
import { MemberUpdateHandler } from '../websocket/discord/events/memberUpdate';
import { PresenceUpdateHandler } from '../websocket/discord/events/presenceUpdate';
import type { WorkerHeartbeaterMessages } from '../websocket/discord/heartbeater';
import type { ShardData } from '../websocket/discord/shared';
import type {
ClientHeartbeaterMessages,
WorkerDisconnectedAllShardsResharding,
WorkerMessages,
WorkerReady,
@ -27,10 +40,6 @@ import type { ManagerMessages, ManagerSpawnShards } from '../websocket/discord/w
import type { BaseClientOptions, ServicesOptions, StartOptions } from './base';
import { BaseClient } from './base';
import type { Client, ClientOptions } from './client';
import { MemberUpdateHandler } from '../websocket/discord/events/memberUpdate';
import { PresenceUpdateHandler } from '../websocket/discord/events/presenceUpdate';
import type { ShardData } from '../websocket/discord/shared';
import { Collectors } from './collectors';
import { type ClientUserStructure, Transformers } from './transformers';
@ -166,13 +175,19 @@ export class WorkerClient<Ready extends boolean = boolean> extends BaseClient {
}
}
postMessage(body: WorkerMessages): unknown {
postMessage(body: WorkerMessages | ClientHeartbeaterMessages): unknown {
if (manager) return manager.postMessage(body);
return process.send!(body);
}
async handleManagerMessages(data: ManagerMessages) {
async handleManagerMessages(data: ManagerMessages | WorkerHeartbeaterMessages) {
switch (data.type) {
case 'HEARTBEAT':
this.postMessage({
type: 'ACK_HEARTBEAT',
workerId: workerData.workerId,
});
break;
case 'CACHE_RESULT':
if (this.cache.adapter instanceof WorkerAdapter && this.cache.adapter.promises.has(data.nonce)) {
const cacheData = this.cache.adapter.promises.get(data.nonce)!;
@ -377,12 +392,18 @@ export class WorkerClient<Ready extends boolean = boolean> extends BaseClient {
return this.onPacket(packet, shardId);
};
}
workerData.totalShards = data.totalShards;
workerData.shards = [...this.shards.keys()];
this.resharding.clear();
}
break;
}
}
calculateShardId(guildId: string) {
return calculateShardId(guildId, this.workerData.totalShards);
}
private generateNonce(): UUID {
const uuid = randomUUID();
if (this.promises.has(uuid)) return this.generateNonce();
@ -420,7 +441,7 @@ export class WorkerClient<Ready extends boolean = boolean> extends BaseClient {
return Promise.all(promises);
}
createShard(id: number, data: Pick<ManagerSpawnShards, 'info' | 'compress'>) {
createShard(id: number, data: Pick<ManagerSpawnShards, 'info' | 'compress' | 'properties'>) {
const onPacket = this.onPacket.bind(this);
const handlePayload = this.options?.handlePayload?.bind(this);
const self = this;
@ -432,6 +453,7 @@ export class WorkerClient<Ready extends boolean = boolean> extends BaseClient {
debugger: this.debugger,
properties: {
...properties,
...data.properties,
...this.options.gateway?.properties,
},
async handlePayload(shardId, payload) {
@ -553,12 +575,12 @@ export function generateShardInfo(shard: Shard): WorkerShardInfo {
};
}
interface WorkerClientOptions extends BaseClientOptions {
export interface WorkerClientOptions extends BaseClientOptions {
commands?: NonNullable<Client['options']>['commands'];
handlePayload?: ShardManagerOptions['handlePayload'];
gateway?: ClientOptions['gateway'];
postMessage?: (body: unknown) => unknown;
postMessage?: (body: unknown) => Awaitable<unknown>;
/** can have perfomance issues in big bots if the client sends every event, specially in startup (false by default) */
sendPayloadToParent?: boolean;
handleManagerMessages?(message: ManagerMessages): any;
handleManagerMessages?(message: ManagerMessages | WorkerHeartbeaterMessages): Awaitable<unknown>;
}

View File

@ -79,7 +79,7 @@ export class CommandContext<
if (this.interaction) return this.interaction.write(body, withResponse);
const options = (this.client as Client | WorkerClient).options?.commands;
return (this.messageResponse = await (this.message! as Message)[
!this.messageResponse && options?.reply?.(this) ? 'reply' : 'write'
!this.messageResponse && (await options?.reply?.(this)) ? 'reply' : 'write'
](body)) as never;
}
@ -97,8 +97,8 @@ export class CommandContext<
return this.interaction.deferReply(ephemeral ? MessageFlags.Ephemeral : undefined, withResponse);
this.__deferred = true;
const options = (this.client as Client | WorkerClient).options?.commands;
return (this.messageResponse = await (this.message! as Message)[options?.reply?.(this) ? 'reply' : 'write'](
options?.deferReplyResponse?.(this) ?? { content: 'Thinking...' },
return (this.messageResponse = await (this.message! as Message)[(await options?.reply?.(this)) ? 'reply' : 'write'](
(await options?.deferReplyResponse?.(this)) ?? { content: 'Thinking...' },
)) as never;
}

View File

@ -1,4 +1,4 @@
import { type PermissionStrings, magicImport } from '../../common';
import { magicImport, type PermissionStrings } from '../../common';
import {
ApplicationCommandType,
type ApplicationIntegrationType,

View File

@ -12,10 +12,11 @@ import type {
MakeRequired,
MessageWebhookCreateBodyRequest,
ModalCreateBodyRequest,
ModalCreateOptions,
UnionToTuple,
When,
} from '../../common';
import type { AllChannels, EntryPointInteraction } from '../../structures';
import type { AllChannels, EntryPointInteraction, ModalSubmitInteraction } from '../../structures';
import { MessageFlags, type RESTGetAPIGuildQuery } from '../../types';
import { BaseContext } from '../basecontext';
import type { RegisteredMiddlewares } from '../decorators';
@ -52,8 +53,11 @@ export class EntryPointContext<M extends keyof RegisteredMiddlewares = never> ex
return this.interaction.write<WR>(body, withResponse);
}
modal(body: ModalCreateBodyRequest) {
return this.interaction.modal(body);
modal(body: ModalCreateBodyRequest, options?: undefined): Promise<undefined>;
modal(body: ModalCreateBodyRequest, options: ModalCreateOptions): Promise<ModalSubmitInteraction | null>;
modal(body: ModalCreateBodyRequest, options?: ModalCreateOptions | undefined) {
if (options === undefined) return this.interaction.modal(body);
return this.interaction.modal(body, options);
}
deferReply<WR extends boolean = false>(

View File

@ -1,4 +1,4 @@
import { type PermissionStrings, magicImport } from '../../common';
import { magicImport, type PermissionStrings } from '../../common';
import type {
ApplicationCommandType,
ApplicationIntegrationType,

View File

@ -8,16 +8,22 @@ import {
type WebhookMessageStructure,
} from '../../client/transformers';
import {
type InteractionCreateBodyRequest,
type InteractionMessageUpdateBodyRequest,
type MakeRequired,
type MessageWebhookCreateBodyRequest,
type ModalCreateBodyRequest,
type UnionToTuple,
type When,
InteractionCreateBodyRequest,
InteractionMessageUpdateBodyRequest,
MakeRequired,
MessageWebhookCreateBodyRequest,
ModalCreateBodyRequest,
ModalCreateOptions,
toSnakeCase,
UnionToTuple,
When,
} from '../../common';
import type { AllChannels, MessageCommandInteraction, UserCommandInteraction } from '../../structures';
import {
AllChannels,
MessageCommandInteraction,
ModalSubmitInteraction,
UserCommandInteraction,
} from '../../structures';
import { type APIMessage, ApplicationCommandType, MessageFlags, type RESTGetAPIGuildQuery } from '../../types';
import { BaseContext } from '../basecontext';
import type { RegisteredMiddlewares } from '../decorators';
@ -47,7 +53,6 @@ export class MenuCommandContext<
metadata: CommandMetadata<UnionToTuple<M>> = {} as never;
globalMetadata: GlobalMetadata = {};
// biome-ignore lint/suspicious/useGetterReturn: default don't exist.
get target(): InteractionTarget<T> {
switch (this.interaction.data.type) {
case ApplicationCommandType.Message: {
@ -76,8 +81,11 @@ export class MenuCommandContext<
return this.interaction.write<WR>(body, withResponse);
}
modal(body: ModalCreateBodyRequest) {
return this.interaction.modal(body);
modal(body: ModalCreateBodyRequest, options?: undefined): Promise<undefined>;
modal(body: ModalCreateBodyRequest, options: ModalCreateOptions): Promise<ModalSubmitInteraction | null>;
modal(body: ModalCreateBodyRequest, options?: ModalCreateOptions | undefined) {
if (options === undefined) return this.interaction.modal(body);
return this.interaction.modal(body, options);
}
deferReply<WR extends boolean = false>(

View File

@ -1,10 +1,3 @@
import type {
AutocompleteCallback,
EntryPointContext,
MenuCommandContext,
OnAutocompleteErrorCallback,
ReturnOptionsTypes,
} from '..';
import type { Awaitable, FlatObjectKeys } from '../../common';
import type { ModalContext } from '../../components';
import type { ComponentContext } from '../../components/componentcontext';
@ -15,6 +8,13 @@ import {
ApplicationCommandOptionType,
} from '../../types';
import type { LocalizationMap } from '../../types/payloads';
import type {
AutocompleteCallback,
EntryPointContext,
MenuCommandContext,
OnAutocompleteErrorCallback,
ReturnOptionsTypes,
} from '..';
import type { CommandContext } from './chatcontext';
import type { DefaultLocale, MiddlewareContext, OKFunction, SeyfertChannelMap, StopFunction } from './shared';

View File

@ -1,7 +1,7 @@
import type {
CategoryChannelStructure,
DMChannelStructure,
DirectoryChannelStructure,
DMChannelStructure,
ForumChannelStructure,
MediaChannelStructure,
NewsChannelStructure,

View File

@ -1,3 +1,32 @@
import type { Client, WorkerClient } from '../client';
import { type MessageStructure, type OptionResolverStructure, Transformers } from '../client/transformers';
import type { MakeRequired } from '../common';
import { INTEGER_OPTION_VALUE_LIMIT } from '../common/it/constants';
import { ComponentContext, ModalContext } from '../components';
import {
type __InternalReplyFunction,
AutocompleteInteraction,
BaseInteraction,
type ChatInputCommandInteraction,
type ComponentInteraction,
type EntryPointInteraction,
type MessageCommandInteraction,
type ModalSubmitInteraction,
type UserCommandInteraction,
} from '../structures';
import type { PermissionsBitField } from '../structures/extra/Permissions';
import {
type APIApplicationCommandInteraction,
type APIApplicationCommandInteractionDataOption,
type APIInteraction,
type APIInteractionDataResolvedChannel,
ApplicationCommandOptionType,
ApplicationCommandType,
ChannelType,
type GatewayMessageCreateDispatchData,
InteractionContextType,
InteractionType,
} from '../types';
import {
BaseCommand,
Command,
@ -19,35 +48,6 @@ import {
SubCommand,
type UsingClient,
} from '.';
import type { Client, WorkerClient } from '../client';
import { type MessageStructure, type OptionResolverStructure, Transformers } from '../client/transformers';
import type { MakeRequired } from '../common';
import { INTEGER_OPTION_VALUE_LIMIT } from '../common/it/constants';
import { ComponentContext, ModalContext } from '../components';
import {
AutocompleteInteraction,
BaseInteraction,
type ChatInputCommandInteraction,
type ComponentInteraction,
type EntryPointInteraction,
type MessageCommandInteraction,
type ModalSubmitInteraction,
type UserCommandInteraction,
type __InternalReplyFunction,
} from '../structures';
import type { PermissionsBitField } from '../structures/extra/Permissions';
import {
type APIApplicationCommandInteraction,
type APIApplicationCommandInteractionDataOption,
type APIInteraction,
type APIInteractionDataResolvedChannel,
ApplicationCommandOptionType,
ApplicationCommandType,
ChannelType,
type GatewayMessageCreateDispatchData,
InteractionContextType,
InteractionType,
} from '../types';
export type CommandOptionWithType = CommandOption & {
type: ApplicationCommandOptionType;

View File

@ -1,6 +1,5 @@
import { promises } from 'node:fs';
import { basename, dirname } from 'node:path';
import type { EntryPointCommand } from '.';
import type { Logger, NulleableCoalising, OmitInsert } from '../common';
import { BaseHandler, isCloudfareWorker } from '../common';
import { PermissionsBitField } from '../structures/extra/Permissions';
@ -17,6 +16,7 @@ import {
type LocaleString,
type LocalizationMap,
} from '../types';
import type { EntryPointCommand } from '.';
import { Command, type CommandOption, SubCommand } from './applications/chat';
import { ContextMenuCommand } from './applications/menu';
import { IgnoreCommand, type UsingClient } from './applications/shared';

View File

@ -1,11 +1,11 @@
export * from './applications/shared';
//
export * from './applications/chat';
export * from './applications/chatcontext';
export * from './applications/entrycontext';
export * from './applications/entryPoint';
export * from './applications/menu';
export * from './applications/menucontext';
export * from './applications/options';
export * from './applications/entryPoint';
export * from './applications/entrycontext';
export * from './applications/shared';
export * from './decorators';
export * from './optionresolver';

View File

@ -1,28 +1,28 @@
export * from './it/constants';
export * from './it/utils';
//
export * from './bot/watcher';
export * from './it/colors';
export { CustomizeLoggerCallback, AssignFilenameCallback, LogLevels, Logger, LoggerOptions } from './it/logger';
export * from './it/constants';
export * from './it/formatter';
// circular lol
export * from './shorters/invites';
export { AssignFilenameCallback, CustomizeLoggerCallback, Logger, LoggerOptions, LogLevels } from './it/logger';
export * from './it/utils';
export * from './shorters/application';
//
export * from './shorters/channels';
export * from './shorters/emojis';
export * from './shorters/guilds';
export * from './shorters/interaction';
// circular lol
export * from './shorters/invites';
export * from './shorters/members';
export * from './shorters/messages';
export * from './shorters/reactions';
export * from './shorters/roles';
export * from './shorters/templates';
export * from './shorters/users';
export * from './shorters/threads';
export * from './shorters/users';
export * from './shorters/webhook';
export * from './shorters/interaction';
export * from './shorters/application';
//
export * from './types/options';
export * from './types/resolvables';
export * from './types/util';
export * from './types/write';
//
export * from './bot/watcher';

View File

@ -1,4 +1,4 @@
import { type WriteStream, createWriteStream, existsSync, mkdirSync, promises } from 'node:fs';
import { createWriteStream, existsSync, mkdirSync, promises, type WriteStream } from 'node:fs';
import { join } from 'node:path';
import { bgBrightWhite, black, bold, brightBlack, cyan, gray, italic, red, stripColor, yellow } from './colors';
import { MergeOptions } from './utils';

View File

@ -1,5 +1,7 @@
import { promises } from 'node:fs';
import { basename, join } from 'node:path';
import type { Cache } from '../../cache';
import { type APIPartialEmoji, FormattingPatterns, GatewayIntentBits } from '../../types';
import {
type ColorResolvable,
DiscordEpoch,
@ -10,8 +12,6 @@ import {
type ObjectToSnake,
type TypeArray,
} from '..';
import type { Cache } from '../../cache';
import { type APIPartialEmoji, FormattingPatterns, GatewayIntentBits } from '../../types';
/**
* Calculates the shard ID for a guild based on its ID.
@ -304,8 +304,6 @@ export function lazyLoadPackage<T>(mod: string): T | undefined {
try {
return require(mod);
} catch (e) {
// biome-ignore lint/suspicious/noConsoleLog:
// biome-ignore lint/suspicious/noConsole:
console.log(`Cannot import ${mod}`, e);
return;
}

View File

@ -1,7 +1,7 @@
import { CacheFrom } from '../../cache';
import type { Overwrites } from '../../cache/resources/overwrites';
import { type MessageStructure, type ThreadChannelStructure, Transformers } from '../../client/transformers';
import { type AllChannels, BaseChannel, type GuildMember, type GuildRole, channelFrom } from '../../structures';
import { type AllChannels, BaseChannel, channelFrom, type GuildMember, type GuildRole } from '../../structures';
import { PermissionsBitField } from '../../structures/extra/Permissions';
import type {
APIChannel,

View File

@ -14,8 +14,8 @@ import {
type AllChannels,
BaseChannel,
type CreateStickerBodyRequest,
type GuildChannelTypes,
channelFrom,
type GuildChannelTypes,
} from '../../structures';
import type {
APIChannel,

View File

@ -1,5 +1,6 @@
import { BaseInteraction, Modal, type ReplyInteractionBody, resolveFiles } from '../..';
import { Transformers, type WebhookMessageStructure } from '../../client/transformers';
import type { RESTPostAPIWebhookWithTokenWaitResult } from '../../types';
import type { InteractionMessageUpdateBodyRequest, MessageWebhookCreateBodyRequest } from '../types/write';
import { BaseShorter } from './base';
@ -69,12 +70,12 @@ export class InteractionShorter extends BaseShorter {
async followup(token: string, { files, ...body }: MessageWebhookCreateBodyRequest): Promise<WebhookMessageStructure> {
const parsedFiles = files ? await resolveFiles(files) : undefined;
const apiMessage = await this.client.proxy
const apiMessage = (await this.client.proxy
.webhooks(this.client.applicationId)(token)
.post({
body: BaseInteraction.transformBody(body, parsedFiles, this.client),
files: parsedFiles,
});
})) as RESTPostAPIWebhookWithTokenWaitResult;
return Transformers.WebhookMessage(this.client, apiMessage, this.client.applicationId, token);
}
}

View File

@ -1,4 +1,7 @@
import type { ValidAnswerId } from '../../api/Routes/channels';
import { resolveFiles } from '../../builders';
import { CacheFrom } from '../../cache';
import { type MessageStructure, type ThreadChannelStructure, Transformers, type UserStructure } from '../../client';
import { MessagesMethods } from '../../structures';
import type {
RESTGetAPIChannelMessagesQuery,
@ -6,10 +9,6 @@ import type {
RESTPostAPIChannelMessageJSONBody,
RESTPostAPIChannelMessagesThreadsJSONBody,
} from '../../types';
import type { ValidAnswerId } from '../../api/Routes/channels';
import { CacheFrom } from '../../cache';
import { type MessageStructure, type ThreadChannelStructure, Transformers, type UserStructure } from '../../client';
import type { MessageCreateBodyRequest, MessageUpdateBodyRequest } from '../types/write';
import { BaseShorter } from './base';

View File

@ -92,10 +92,9 @@ export class RoleShorter extends BaseShorter {
* @param reason The reason for deleting the role.
* @returns A Promise that resolves when the role is deleted.
*/
async delete(guildId: string, roleId: string, reason?: string): Promise<GuildRoleStructure> {
const res = await this.client.proxy.guilds(guildId).roles(roleId).delete({ reason });
async delete(guildId: string, roleId: string, reason?: string) {
await this.client.proxy.guilds(guildId).roles(roleId).delete({ reason });
this.client.cache.roles?.removeIfNI('Guilds', roleId, guildId);
return Transformers.GuildRole(this.client, res, guildId);
}
/**

View File

@ -1,15 +1,16 @@
import { CacheFrom } from '../..';
import type { ThreadChannelStructure } from '../../client/transformers';
import { channelFrom } from '../../structures';
import type {
APIThreadChannel,
APIThreadMember,
RESTGetAPIChannelThreadMembersQuery,
RESTGetAPIChannelThreadsArchivedQuery,
RESTPatchAPIChannelJSONBody,
RESTPostAPIChannelMessagesThreadsJSONBody,
RESTPostAPIChannelThreadsJSONBody,
RESTPostAPIGuildForumThreadsJSONBody,
import {
type APIThreadChannel,
type APIThreadMember,
ChannelType,
type RESTGetAPIChannelThreadMembersQuery,
type RESTGetAPIChannelThreadsArchivedQuery,
type RESTPatchAPIChannelJSONBody,
type RESTPostAPIChannelMessagesThreadsJSONBody,
type RESTPostAPIChannelThreadsJSONBody,
type RESTPostAPIGuildForumThreadsJSONBody,
} from '../../types';
import type { MakeRequired, When } from '../types/util';
import { BaseShorter } from './base';
@ -44,27 +45,22 @@ export class ThreadShorter extends BaseShorter {
);
}
fromMessage(
async fromMessage(
channelId: string,
messageId: string,
options: RESTPostAPIChannelMessagesThreadsJSONBody & { reason?: string },
): Promise<ThreadChannelStructure> {
const { reason, ...body } = options;
return this.client.proxy
.channels(channelId)
.messages(messageId)
.threads.post({ body, reason })
.then(async thread => {
await this.client.cache.channels?.setIfNI(
CacheFrom.Rest,
'Guilds',
thread.id,
(thread as APIThreadChannel).guild_id!,
thread,
);
return channelFrom(thread, this.client) as ThreadChannelStructure;
});
const thread = await this.client.proxy.channels(channelId).messages(messageId).threads.post({ body, reason });
await this.client.cache.channels?.setIfNI(
CacheFrom.Rest,
'Guilds',
thread.id,
(thread as APIThreadChannel).guild_id!,
thread,
);
return await (channelFrom(thread, this.client) as ThreadChannelStructure);
}
join(threadId: string) {
@ -75,8 +71,9 @@ export class ThreadShorter extends BaseShorter {
return this.client.proxy.channels(threadId)['thread-members']('@me').delete();
}
lock(threadId: string, locked = true, reason?: string): Promise<ThreadChannelStructure> {
return this.edit(threadId, { locked }, reason).then(x => channelFrom(x, this.client) as ThreadChannelStructure);
async lock(threadId: string, locked = true, reason?: string): Promise<ThreadChannelStructure> {
const x = await this.edit(threadId, { locked }, reason);
return channelFrom(x, this.client) as ThreadChannelStructure;
}
async edit(threadId: string, body: RESTPatchAPIChannelJSONBody, reason?: string): Promise<ThreadChannelStructure> {
@ -110,7 +107,7 @@ export class ThreadShorter extends BaseShorter {
return this.client.proxy.channels(threadId)['thread-members'].get({ query }) as never;
}
async listArchivedThreads(
async listArchived(
channelId: string,
type: 'public' | 'private',
query?: RESTGetAPIChannelThreadsArchivedQuery,
@ -128,6 +125,25 @@ export class ThreadShorter extends BaseShorter {
};
}
async listGuildActive(guildId: string, force = false): Promise<ThreadChannelStructure[]> {
if (!force) {
const cached = await this.client.cache.channels?.valuesRaw(guildId);
if (cached)
return cached
.filter(x =>
[ChannelType.PublicThread, ChannelType.PrivateThread, ChannelType.AnnouncementThread].includes(x.type),
)
.map(x => channelFrom(x, this.client) as ThreadChannelStructure);
}
const data = await this.client.proxy.guilds(guildId).threads.active.get();
return Promise.all(
data.threads.map(async thread => {
await this.client.cache.channels?.setIfNI(CacheFrom.Rest, 'Guilds', thread.id, guildId, thread);
return channelFrom(thread, this.client) as ThreadChannelStructure;
}),
);
}
async listJoinedArchivedPrivate(
channelId: string,
query?: RESTGetAPIChannelThreadsArchivedQuery,

View File

@ -1,9 +1,9 @@
import { resolveFiles } from '../../builders';
import { Transformers, type WebhookMessageStructure, type WebhookStructure } from '../../client/transformers';
import {
MessagesMethods,
type MessageWebhookMethodEditParams,
type MessageWebhookMethodWriteParams,
MessagesMethods,
} from '../../structures';
import type {
APIWebhook,

View File

@ -1,6 +1,6 @@
import type { Identify } from '..';
import type { CDNUrlOptions } from '../../api';
import type { UsingClient } from '../../commands';
import type { Identify } from '..';
export type ImageOptions = CDNUrlOptions;

View File

@ -1,7 +1,7 @@
import type { EmbedColors, OmitInsert } from '..';
import type { Attachment, AttachmentDataType, AttachmentResolvable } from '../../builders';
import type { GuildMember } from '../../structures';
import type { APIGuildMember, APIPartialEmoji, RESTPostAPIApplicationEmojiJSONBody } from '../../types';
import type { EmbedColors, OmitInsert } from '..';
export type EmojiResolvable = string | Partial<APIPartialEmoji> | `<${string | undefined}:${string}:${string}>`;
export type GuildMemberResolvable = string | Partial<GuildMember> | APIGuildMember;

View File

@ -107,7 +107,7 @@ export type SnakeCase<S extends string> = S extends `${infer A}${infer Rest}`
export type ObjectToLower<T> = T extends unknown[]
? ObjectToLower<T[0]>[]
: Identify<{
[K in keyof T as CamelCase<Exclude<K, symbol | number>>]: T[K] extends unknown[]
[K in keyof T as K extends number ? K : CamelCase<Exclude<K, symbol | number>>]: T[K] extends unknown[]
? Identify<ObjectToLower<T[K][0]>[]>
: T[K] extends object
? Identify<ObjectToLower<T[K]>>
@ -119,7 +119,7 @@ export type ObjectToLower<T> = T extends unknown[]
export type ObjectToLowerUndefined<T> = T extends unknown[]
? ObjectToLower<T[0]>[]
: Identify<{
[K in keyof T as CamelCase<Exclude<K, symbol | number>>]: T[K] extends unknown[]
[K in keyof T as K extends number ? K : CamelCase<Exclude<K, symbol | number>>]: T[K] extends unknown[]
? ObjectToLower<T[K][0]>[]
: T[K] extends object
? ObjectToLower<T[K]>
@ -127,7 +127,7 @@ export type ObjectToLowerUndefined<T> = T extends unknown[]
}>;
export type ObjectToSnake<T> = Identify<{
[K in keyof T as SnakeCase<Exclude<K, symbol | number>>]: T[K] extends unknown[]
[K in keyof T as K extends number ? K : SnakeCase<Exclude<K, symbol | number>>]: T[K] extends unknown[]
? Identify<ObjectToSnake<T[K][0]>[]>
: T[K] extends object
? Identify<ObjectToSnake<T[K]>>
@ -139,7 +139,7 @@ export type ObjectToSnake<T> = Identify<{
export type ObjectToSnakeUndefined<T> = T extends unknown[]
? ObjectToSnake<T[0]>[]
: Identify<{
[K in keyof T as SnakeCase<Exclude<K, symbol | number>>]: T[K] extends unknown[]
[K in keyof T as K extends number ? K : SnakeCase<Exclude<K, symbol | number>>]: T[K] extends unknown[]
? ObjectToSnake<T[K][0]>[]
: T[K] extends object
? ObjectToSnake<T[K]>
@ -171,14 +171,10 @@ type OptionalizeAux<T extends object> = Identify<
* it is recursive
*/
export type Optionalize<T> = T extends object
? // biome-ignore lint/style/useShorthandArrayType: typescript things
// biome-ignore lint/style/useConsistentArrayType: <explanation>
T extends Array<unknown>
? T extends Array<unknown>
? number extends T['length']
? T[number] extends object
? // biome-ignore lint/style/useShorthandArrayType: <explanation>
// biome-ignore lint/style/useConsistentArrayType: <explanation>
Array<OptionalizeAux<T[number]>>
? Array<OptionalizeAux<T[number]>>
: T
: Partial<T>
: OptionalizeAux<T>

View File

@ -16,6 +16,7 @@ import type {
import type { OmitInsert } from './util';
export interface ResolverProps {
content?: string | undefined | null;
embeds?: Embed[] | APIEmbed[] | undefined;
components?: TopLevelBuilders[] | ReturnType<TopLevelBuilders['toJSON']>[];
files?: AttachmentBuilder[] | Attachment[] | RawFile[] | undefined;
@ -27,31 +28,31 @@ export interface SendResolverProps extends ResolverProps {
export type MessageCreateBodyRequest = OmitInsert<
RESTPostAPIChannelMessageJSONBody,
'components' | 'embeds' | 'poll',
'components' | 'embeds' | 'poll' | 'content',
SendResolverProps
>;
export type MessageUpdateBodyRequest = OmitInsert<
RESTPatchAPIChannelMessageJSONBody,
'components' | 'embeds',
'components' | 'embeds' | 'content',
ResolverProps
>;
export type MessageWebhookCreateBodyRequest = OmitInsert<
RESTPostAPIWebhookWithTokenJSONBody,
'components' | 'embeds' | 'poll',
'components' | 'embeds' | 'poll' | 'content',
SendResolverProps
>;
export type MessageWebhookUpdateBodyRequest = OmitInsert<
RESTPatchAPIWebhookWithTokenMessageJSONBody,
'components' | 'embeds' | 'poll',
'components' | 'embeds' | 'poll' | 'content',
ResolverProps
>;
export type InteractionMessageUpdateBodyRequest = OmitInsert<
RESTPatchAPIWebhookWithTokenMessageJSONBody,
'components' | 'embeds' | 'poll',
'components' | 'embeds' | 'poll' | 'content',
SendResolverProps
> & {
flags?: MessageFlags;
@ -59,14 +60,18 @@ export type InteractionMessageUpdateBodyRequest = OmitInsert<
export type ComponentInteractionMessageUpdate = OmitInsert<
APIInteractionResponseCallbackData,
'components' | 'embeds',
'components' | 'embeds' | 'content',
ResolverProps
>;
export type InteractionCreateBodyRequest = OmitInsert<
APIInteractionResponseChannelMessageWithSource['data'],
'components' | 'embeds' | 'poll',
'components' | 'embeds' | 'poll' | 'content',
SendResolverProps
>;
export type ModalCreateBodyRequest = APIModalInteractionResponse['data'] | Modal;
export interface ModalCreateOptions {
waitFor?: number;
}

View File

@ -4,6 +4,7 @@ import {
type ChannelSelectMenu,
type Container,
type File,
fromComponent,
type MediaGallery,
type MentionableSelectMenu,
type RoleSelectMenu,
@ -14,7 +15,6 @@ import {
type TextInput,
type Thumbnail,
type UserSelectMenu,
fromComponent,
} from '../builders';
import {
type APIActionRowComponent,

View File

@ -1,5 +1,5 @@
import { type ContainerComponents, componentFactory } from '.';
import type { APIContainerComponent, ComponentType } from '../types';
import { type ContainerComponents, componentFactory } from '.';
import { BaseComponent } from './BaseComponent';
export class ContainerComponent extends BaseComponent<ComponentType.Container> {

View File

@ -1,5 +1,5 @@
import { componentFactory } from '.';
import type { APISectionComponent, ComponentType } from '../types';
import { componentFactory } from '.';
import { BaseComponent } from './BaseComponent';
import type { ButtonComponent } from './ButtonComponent';
import type { TextDisplayComponent } from './TextDisplay';

View File

@ -14,13 +14,17 @@ export interface ComponentCommand {
export abstract class ComponentCommand {
type = InteractionCommandType.COMPONENT;
abstract componentType: keyof ContextComponentCommandInteractionMap;
customId?: string;
customId?: string | RegExp;
filter?(context: ComponentContext<typeof this.componentType>): Promise<boolean> | boolean;
abstract run(context: ComponentContext<typeof this.componentType>): any;
/** @internal */
_filter(context: ComponentContext) {
if (this.customId && this.customId !== context.customId) return false;
if (this.customId) {
const matches =
typeof this.customId === 'string' ? this.customId === context.customId : context.customId.match(this.customId);
if (!matches) return false;
}
if (this.filter) return this.filter(context);
return true;
}

View File

@ -18,16 +18,18 @@ import type {
} from '../client/transformers';
import type { CommandMetadata, ExtendContext, GlobalMetadata, RegisteredMiddlewares, UsingClient } from '../commands';
import { BaseContext } from '../commands/basecontext';
import type {
import {
ComponentInteractionMessageUpdate,
InteractionCreateBodyRequest,
InteractionMessageUpdateBodyRequest,
MakeRequired,
MessageWebhookCreateBodyRequest,
ModalCreateBodyRequest,
ModalCreateOptions,
UnionToTuple,
When,
} from '../common';
import { ModalSubmitInteraction } from '../structures';
import { ComponentType, MessageFlags, type RESTGetAPIGuildQuery } from '../types';
export interface ComponentContext<
@ -150,8 +152,11 @@ export class ComponentContext<
return this.interaction.deleteResponse();
}
modal(body: ModalCreateBodyRequest) {
return this.interaction.modal(body);
modal(body: ModalCreateBodyRequest, options?: undefined): Promise<undefined>;
modal(body: ModalCreateBodyRequest, options: ModalCreateOptions): Promise<ModalSubmitInteraction | null>;
modal(body: ModalCreateBodyRequest, options?: ModalCreateOptions | undefined) {
if (options === undefined) return this.interaction.modal(body);
return this.interaction.modal(body, options);
}
/**

View File

@ -9,7 +9,7 @@ import type {
import { LimitedCollection } from '../collection';
import { BaseCommand, type RegisteredMiddlewares, type UsingClient } from '../commands';
import type { FileLoaded } from '../commands/handler';
import { BaseHandler, type Logger, type OnFailCallback, isCloudfareWorker, magicImport } from '../common';
import { BaseHandler, isCloudfareWorker, type Logger, magicImport, type OnFailCallback } from '../common';
import type { ComponentInteraction, ModalSubmitInteraction, StringSelectMenuInteraction } from '../structures';
import { ComponentCommand, InteractionCommandType } from './componentcommand';
import type { ComponentContext } from './componentcontext';
@ -38,6 +38,11 @@ export interface CreateComponentCollectorResult {
callback: ComponentCallback<T>,
): void;
stop(reason?: string): void;
waitFor<T extends CollectorInteraction = CollectorInteraction>(
customId: UserMatches,
timeout?: number,
): Promise<T | null>;
resetTimeouts(): void;
}
export class ComponentHandler extends BaseHandler {
@ -115,6 +120,28 @@ export class ComponentHandler extends BaseHandler {
this.createComponentCollector(messageId, channelId, guildId, options, old.components);
});
},
waitFor: (customId, timeout) =>
new Promise(resolve => {
const collector = this.values.get(messageId);
if (!collector) return resolve(null);
let nodeTimeout: NodeJS.Timeout | undefined;
this.values.get(messageId)!.__run(customId, interaction => {
clearTimeout(nodeTimeout);
//@ts-expect-error generic
resolve(interaction);
});
if (timeout && timeout > 0)
nodeTimeout = setTimeout(() => {
resolve(null);
// by default 15 seconds in case user don't do anything
}, timeout);
}),
resetTimeouts: () => {
this.resetTimeouts(messageId);
},
};
}

View File

@ -1,4 +1,4 @@
import type { AllChannels, Interaction, ModalCommand, ModalSubmitInteraction, ReturnCache } from '..';
import type { AllChannels, ModalCommand, ModalSubmitInteraction, ReturnCache } from '..';
import type {
GuildMemberStructure,
GuildStructure,
@ -8,12 +8,13 @@ import type {
} from '../client/transformers';
import type { CommandMetadata, ExtendContext, GlobalMetadata, RegisteredMiddlewares, UsingClient } from '../commands';
import { BaseContext } from '../commands/basecontext';
import type {
import {
InteractionCreateBodyRequest,
InteractionMessageUpdateBodyRequest,
MakeRequired,
MessageWebhookCreateBodyRequest,
ModalCreateBodyRequest,
ModalCreateOptions,
UnionToTuple,
When,
} from '../common';
@ -119,9 +120,11 @@ export class ModalContext<M extends keyof RegisteredMiddlewares = never> extends
return this.interaction.deleteResponse();
}
modal(body: ModalCreateBodyRequest): ReturnType<Interaction['modal']> {
//@ts-expect-error
return this.interaction.modal(body);
modal(body: ModalCreateBodyRequest, options?: undefined): Promise<undefined>;
modal(body: ModalCreateBodyRequest, options: ModalCreateOptions): Promise<ModalSubmitInteraction | null>;
modal(body: ModalCreateBodyRequest, options?: ModalCreateOptions | undefined) {
// @ts-expect-error
return this.interaction.modal(body, options);
}
/**

View File

@ -4,11 +4,11 @@ import type { FileLoaded } from '../commands/handler';
import {
BaseHandler,
type CamelCase,
isCloudfareWorker,
type MakeRequired,
magicImport,
ReplaceRegex,
type SnakeCase,
isCloudfareWorker,
magicImport,
} from '../common';
import type { ClientEvents } from '../events/hooks';
import * as RawEvents from '../events/hooks';

View File

@ -1,3 +1,6 @@
import type { UsingClient } from '../../commands';
import { toCamelCase } from '../../common';
import { type AllChannels, channelFrom } from '../../structures';
import type {
GatewayChannelCreateDispatchData,
GatewayChannelDeleteDispatchData,
@ -5,10 +8,6 @@ import type {
GatewayChannelUpdateDispatchData,
} from '../../types';
import type { UsingClient } from '../../commands';
import { toCamelCase } from '../../common';
import { type AllChannels, channelFrom } from '../../structures';
export const CHANNEL_CREATE = (self: UsingClient, data: GatewayChannelCreateDispatchData): AllChannels => {
return channelFrom(data, self);
};

View File

@ -20,8 +20,8 @@ import type {
GatewayGuildIntegrationsUpdateDispatchData,
GatewayGuildMemberAddDispatchData,
GatewayGuildMemberRemoveDispatchData,
GatewayGuildMemberUpdateDispatchData,
GatewayGuildMembersChunkDispatchData,
GatewayGuildMemberUpdateDispatchData,
GatewayGuildRoleCreateDispatchData,
GatewayGuildRoleDeleteDispatchData,
GatewayGuildRoleUpdateDispatchData,

View File

@ -10,13 +10,13 @@ export * from './interactions';
export * from './invite';
export * from './message';
export * from './presence';
export * from './soundboard';
export * from './stage';
export * from './thread';
export * from './typing';
export * from './user';
export * from './voice';
export * from './webhook';
export * from './soundboard';
import type { CamelCase } from '../../common';
import type * as RawEvents from './index';

View File

@ -1,6 +1,6 @@
import { type MessageStructure, Transformers } from '../../client/transformers';
import type { UsingClient } from '../../commands';
import { type ObjectToLower, type OmitInsert, fakePromise, toCamelCase } from '../../common';
import { fakePromise, type ObjectToLower, type OmitInsert, toCamelCase } from '../../common';
import type {
GatewayMessageCreateDispatchData,
GatewayMessageDeleteBulkDispatchData,

View File

@ -5,8 +5,8 @@ import type {
APISoundBoard,
GatewayGuildSoundboardSoundCreateDispatchData,
GatewayGuildSoundboardSoundDeleteDispatchData,
GatewayGuildSoundboardSoundUpdateDispatchData,
GatewayGuildSoundboardSoundsUpdateDispatchData,
GatewayGuildSoundboardSoundUpdateDispatchData,
GatewaySoundboardSoundsDispatchData,
} from '../../types';

View File

@ -5,8 +5,8 @@ import type {
GatewayThreadCreateDispatchData,
GatewayThreadDeleteDispatchData,
GatewayThreadListSyncDispatchData,
GatewayThreadMemberUpdateDispatchData,
GatewayThreadMembersUpdateDispatchData,
GatewayThreadMemberUpdateDispatchData,
GatewayThreadUpdateDispatchData,
} from '../../types';

View File

@ -1,4 +1,5 @@
export * from './client';
import {
BaseClient,
type BaseClientOptions,
@ -10,21 +11,22 @@ import {
import { isCloudfareWorker } from './common';
import type { ClientNameEvents, CustomEventsKeys, ResolveEventParams } from './events';
import { GatewayIntentBits } from './types';
export { Logger, PermissionStrings, Formatter } from './common';
//
export { Collection, LimitedCollection } from './collection';
//
export * from './api';
export * from './builders';
export * from './cache';
//
export { Collection, LimitedCollection } from './collection';
export * from './commands';
export { Formatter, Logger, PermissionStrings } from './common';
export * from './components';
export * from './events';
export * from './langs';
//
export { ShardManager, WorkerManager } from './websocket/discord';
//
export * from './structures';
//
export { ShardManager, WorkerManager } from './websocket/discord';
/**
* Creates an event with the specified data and run function.

View File

@ -14,6 +14,7 @@ export const LangRouter = (userLocale: string, defaultLang: string, langs: Parti
function getValue(locale?: string) {
if (typeof locale === 'undefined') throw new Error('Undefined locale');
let value = langs[locale] as Record<string, any>;
if (typeof value === 'undefined') throw new Error(`Locale "${locale}" not found`);
for (const i of route) value = value[i];
return value;
}
@ -51,4 +52,4 @@ export type __InternalParseLocale<T extends Record<string, any>> = {
};
export type ParseLocales<T extends Record<string, any>> = T;
/**Idea inspiration from: FreeAoi */
/**Idea inspiration from: FreeAoi | Fixed by: Drylozu */

View File

@ -1,18 +1,18 @@
import type { GuildMemberStructure, GuildStructure } from '../client';
import type { GuildMemberStructure, GuildStructure, ThreadChannelStructure } from '../client';
import type { UsingClient } from '../commands';
import type { CreateInviteFromChannel } from '../common';
import type { ObjectToLower, StructPropState, StructStates, ToClass } from '../common/types/util';
import type { APIGuild, APIPartialGuild, GatewayGuildCreateDispatchData, RESTPatchAPIGuildJSONBody } from '../types';
import { AutoModerationRule } from './AutoModerationRule';
import { BaseChannel, WebhookGuildMethods } from './channels';
import { GuildEmoji } from './Emoji';
import { BaseGuild } from './extra/BaseGuild';
import type { DiscordBase } from './extra/DiscordBase';
import { GuildBan } from './GuildBan';
import { GuildMember } from './GuildMember';
import { GuildRole } from './GuildRole';
import { GuildTemplate } from './GuildTemplate';
import { Sticker } from './Sticker';
import { BaseChannel, WebhookGuildMethods } from './channels';
import { BaseGuild } from './extra/BaseGuild';
import type { DiscordBase } from './extra/DiscordBase';
export interface Guild extends ObjectToLower<Omit<APIGuild, 'stickers' | 'emojis' | 'roles'>>, DiscordBase {}
export class Guild<State extends StructStates = 'api'> extends (BaseGuild as unknown as ToClass<
@ -71,6 +71,10 @@ export class Guild<State extends StructStates = 'api'> extends (BaseGuild as unk
return this.members.fetch(this.ownerId, force);
}
listActiveThreads(force = false): Promise<ThreadChannelStructure[]> {
return this.client.threads.listGuildActive(this.id, force);
}
templates = GuildTemplate.methods({ client: this.client, guildId: this.id });
stickers = Sticker.methods({ client: this.client, guildId: this.id });
members = GuildMember.methods({ client: this.client, guildId: this.id });

View File

@ -3,7 +3,7 @@ import type { GuildBanStructure, GuildStructure } from '../client';
import type { UsingClient } from '../commands';
import { Formatter, type MethodContext, type ObjectToLower } from '../common';
import type { BanShorter } from '../common/shorters/bans';
import type { APIBan, ActuallyBan, RESTGetAPIGuildBansQuery } from '../types';
import type { ActuallyBan, APIBan, RESTGetAPIGuildBansQuery } from '../types';
import { DiscordBase } from './extra/DiscordBase';
export interface GuildBan extends DiscordBase, ObjectToLower<Omit<APIBan, 'id'>> {}

View File

@ -46,7 +46,7 @@ export class GuildRole extends DiscordBase {
return this.client.roles.edit(this.guildId, this.id, body);
}
delete(reason?: string): Promise<GuildRoleStructure> {
delete(reason?: string) {
return this.client.roles.delete(this.guildId, this.id, reason);
}
@ -61,8 +61,7 @@ export class GuildRole extends DiscordBase {
list: (force = false): Promise<GuildRoleStructure[]> => ctx.client.roles.list(ctx.guildId, force),
edit: (roleId: string, body: RESTPatchAPIGuildRoleJSONBody, reason?: string): Promise<GuildRoleStructure> =>
ctx.client.roles.edit(ctx.guildId, roleId, body, reason),
delete: (roleId: string, reason?: string): Promise<GuildRoleStructure> =>
ctx.client.roles.delete(ctx.guildId, roleId, reason),
delete: (roleId: string, reason?: string) => ctx.client.roles.delete(ctx.guildId, roleId, reason),
editPositions: (body: RESTPatchAPIGuildRolePositionsJSONBody): Promise<GuildRoleStructure[]> =>
ctx.client.roles.editPositions(ctx.guildId, body),
};

View File

@ -1,3 +1,34 @@
import type { RawFile } from '../api';
import { ActionRow, Embed, Modal, PollBuilder, resolveAttachment, resolveFiles } from '../builders';
import type { ReturnCache } from '../cache';
import {
type EntitlementStructure,
type GuildRoleStructure,
type GuildStructure,
type InteractionGuildMemberStructure,
type MessageStructure,
type OptionResolverStructure,
Transformers,
type UserStructure,
type WebhookMessageStructure,
} from '../client/transformers';
import type { ContextOptionsResolved, UsingClient } from '../commands';
import {
type ComponentInteractionMessageUpdate,
type InteractionCreateBodyRequest,
type InteractionMessageUpdateBodyRequest,
type MessageCreateBodyRequest,
type MessageUpdateBodyRequest,
type MessageWebhookCreateBodyRequest,
type ModalCreateBodyRequest,
ModalCreateOptions,
type ObjectToLower,
type OmitInsert,
type ToClass,
toCamelCase,
type When,
} from '../common';
import { mix } from '../deps/mixer';
import {
type APIActionRowComponent,
type APIApplicationCommandAutocompleteInteraction,
@ -41,37 +72,6 @@ import {
type RESTPostAPIInteractionCallbackJSONBody,
type RESTPostAPIInteractionCallbackResult,
} from '../types';
import type { RawFile } from '../api';
import { ActionRow, Embed, Modal, PollBuilder, resolveAttachment, resolveFiles } from '../builders';
import type { ReturnCache } from '../cache';
import {
type EntitlementStructure,
type GuildRoleStructure,
type GuildStructure,
type InteractionGuildMemberStructure,
type MessageStructure,
type OptionResolverStructure,
Transformers,
type UserStructure,
type WebhookMessageStructure,
} from '../client/transformers';
import type { ContextOptionsResolved, UsingClient } from '../commands';
import {
type ComponentInteractionMessageUpdate,
type InteractionCreateBodyRequest,
type InteractionMessageUpdateBodyRequest,
type MessageCreateBodyRequest,
type MessageUpdateBodyRequest,
type MessageWebhookCreateBodyRequest,
type ModalCreateBodyRequest,
type ObjectToLower,
type OmitInsert,
type ToClass,
type When,
toCamelCase,
} from '../common';
import { mix } from '../deps/mixer';
import { type AllChannels, channelFrom } from './';
import { DiscordBase } from './extra/DiscordBase';
import { PermissionsBitField } from './extra/Permissions';
@ -315,7 +315,6 @@ export class BaseInteraction<
switch (gateway.type) {
case InteractionType.ApplicationCommandAutocomplete:
return new AutocompleteInteraction(client, gateway, undefined, __reply);
// biome-ignore lint/suspicious/noFallthroughSwitchClause: bad interaction between biome and ts-server
case InteractionType.ApplicationCommand:
switch (gateway.data.type) {
case ApplicationCommandType.ChatInput:
@ -474,11 +473,37 @@ export class Interaction<
) as never;
}
modal(body: ModalCreateBodyRequest) {
return this.reply({
modal(body: ModalCreateBodyRequest, options?: undefined): Promise<undefined>;
modal(body: ModalCreateBodyRequest, options: ModalCreateOptions): Promise<ModalSubmitInteraction | null>;
async modal(body: ModalCreateBodyRequest, options?: ModalCreateOptions | undefined) {
if (options !== undefined && !(body instanceof Modal)) {
body = new Modal(body);
}
if (options === undefined)
return this.reply({
type: InteractionResponseType.Modal,
data: body,
});
const promise = new Promise<ModalSubmitInteraction | null>(res => {
let nodeTimeout: NodeJS.Timeout | undefined;
// body is always a modal here, so we can safely cast it
(body as Modal).__exec = (interaction: ModalSubmitInteraction) => {
res(interaction);
clearTimeout(nodeTimeout);
};
if (options?.waitFor && options?.waitFor > 0) {
nodeTimeout = setTimeout(() => {
res(null);
}, options.waitFor);
}
});
await this.reply({
type: InteractionResponseType.Modal,
data: body,
});
return promise;
}
async editOrReply<FR extends boolean = false>(

View File

@ -1,4 +1,4 @@
import { type AllChannels, Embed, type ReturnCache, componentFactory } from '..';
import { type AllChannels, componentFactory, Embed, type ReturnCache } from '..';
import type { ListenerOptions } from '../builders';
import {
type GuildMemberStructure,
@ -11,8 +11,7 @@ import {
type WebhookStructure,
} from '../client/transformers';
import type { UsingClient } from '../commands';
import { type ObjectToLower, toCamelCase } from '../common';
import { Formatter } from '../common';
import { Formatter, type ObjectToLower, toCamelCase } from '../common';
import type { EmojiResolvable } from '../common/types/resolvables';
import type { MessageCreateBodyRequest, MessageUpdateBodyRequest } from '../common/types/write';
import type { TopLevelComponents } from '../components';
@ -24,8 +23,8 @@ import type {
APIUser,
GatewayMessageCreateDispatchData,
} from '../types';
import type { MessageWebhookMethodEditParams, MessageWebhookMethodWriteParams } from './Webhook';
import { DiscordBase } from './extra/DiscordBase';
import type { MessageWebhookMethodEditParams, MessageWebhookMethodWriteParams } from './Webhook';
export type MessageData = APIMessage | GatewayMessageCreateDispatchData;

View File

@ -5,8 +5,8 @@ import {
type BaseChannelStructure,
type BaseGuildChannelStructure,
type CategoryChannelStructure,
type DMChannelStructure,
type DirectoryChannelStructure,
type DMChannelStructure,
type ForumChannelStructure,
type GuildMemberStructure,
type GuildStructure,
@ -26,13 +26,13 @@ import type { SeyfertChannelMap, UsingClient } from '../commands';
import {
type CreateInviteFromChannel,
type EmojiResolvable,
fakePromise,
type MessageCreateBodyRequest,
type MessageUpdateBodyRequest,
type MethodContext,
type ObjectToLower,
type StringToNumber,
type ToClass,
fakePromise,
} from '../common';
import { mix } from '../deps/mixer';
import {
@ -63,9 +63,9 @@ import {
type ThreadAutoArchiveDuration,
VideoQualityMode,
} from '../types';
import { DiscordBase } from './extra/DiscordBase';
import type { GuildMember } from './GuildMember';
import type { GuildRole } from './GuildRole';
import { DiscordBase } from './extra/DiscordBase';
export class BaseNoEditableChannel<T extends ChannelType> extends DiscordBase<APIChannelBase<ChannelType>> {
declare type: T;
@ -581,13 +581,15 @@ export class ForumChannel extends BaseGuildChannel {
export interface ThreadChannel
extends ObjectToLower<Omit<APIThreadChannel, 'permission_overwrites' | 'guild_id'>>,
Omit<TextBaseGuildChannel, 'edit' | 'parentId'> {}
Omit<TextBaseGuildChannel, 'edit' | 'parentId'> {
parentId: string;
}
@mix(TextBaseGuildChannel)
export class ThreadChannel extends BaseChannel<
ChannelType.PublicThread | ChannelType.AnnouncementThread | ChannelType.PrivateThread
> {
parentId!: string;
declare type: ChannelType.PublicThread | ChannelType.AnnouncementThread | ChannelType.PrivateThread;
webhooks = WebhookChannelMethods.channel({
client: this.client,
channelId: this.parentId,

View File

@ -1,6 +1,6 @@
import type { ReturnCache, WorkerClient } from '../..';
import type { GuildStructure } from '../../client';
import { type ObjectToLower, calculateShardId } from '../../common';
import { calculateShardId, type ObjectToLower } from '../../common';
import type { ImageOptions } from '../../common/types/options';
import { type APIPartialGuild, GuildFeature } from '../../types';
import type { ShardManager } from '../../websocket';

View File

@ -1,19 +1,19 @@
export * from './AnonymousGuild';
export * from './AutoModerationRule';
export * from './ClientUser';
export * from './Guild';
export * from './channels';
export * from './Emoji';
export * from './Entitlement';
export * from './Guild';
export * from './GuildBan';
export * from './GuildMember';
export * from './GuildPreview';
export * from './GuildRole';
export * from './GuildTemplate';
export * from './Interaction';
export * from './Message';
export * from './Poll';
export * from './Sticker';
export * from './User';
export * from './VoiceState';
export * from './Webhook';
export * from './channels';
export * from './Poll';
export * from './GuildBan';
export * from './Entitlement';

View File

@ -23,9 +23,9 @@
*/
export * from './gateway';
export * from './rest';
export * from './payloads';
export * from './rest';
export * from './rest';
export * from './utils';
/**

View File

@ -1,7 +1,6 @@
import type { ApplicationIntegrationType, InteractionContextType } from '..';
import type { ChannelType, Permissions, Snowflake } from '../..';
import type { LocaleString } from '../../rest';
import type { ApplicationIntegrationType, InteractionContextType } from '..';
import type {
APIAttachment,
APIChannel,

View File

@ -2,9 +2,9 @@
* Types extracted from https://discord.com/developers/docs/resources/application
*/
import type { APIEmoji, LocalizationMap } from '.';
import type { Permissions, Snowflake } from '..';
import type { MakeRequired } from '../../common';
import type { Permissions, Snowflake } from '..';
import type { APIEmoji, LocalizationMap } from '.';
import type { APIPartialGuild } from './guild';
import type { ApplicationIntegrationType } from './interactions';
import type { OAuth2Scopes } from './oauth2';

View File

@ -1,5 +1,5 @@
import type { APIAttachment, Snowflake } from '..';
import type { Identify, MakeRequired } from '../../common';
import type { APIAttachment, Snowflake } from '..';
import type { ChannelType } from '../utils';
/**
@ -260,7 +260,7 @@ export type APIRoleSelectComponent = APIBaseAutoPopulatedSelectMenuComponent<
*/
export type APIMentionableSelectComponent = APIBaseAutoPopulatedSelectMenuComponent<
ComponentType.MentionableSelect,
SelectMenuDefaultValueType.Role | SelectMenuDefaultValueType.User
SelectMenuDefaultValueType
>;
/**

View File

@ -1,6 +1,7 @@
import type { Snowflake } from '..';
import type { APIGuildMember } from './guild';
import type { APIUser } from './user';
interface APIGuildScheduledEventBase<Type extends GuildScheduledEventEntityType> {
/**
* The id of the guild event

View File

@ -2,15 +2,18 @@ export * from './application';
export * from './auditLog';
export * from './autoModeration';
export * from './channel';
export * from './components';
export * from './emoji';
export * from './gateway';
export * from './guild';
export * from './guildScheduledEvent';
export * from './interactions';
export * from './invite';
export * from './monetization';
export * from './oauth2';
export * from './poll';
export * from './permissions';
export * from './poll';
export * from './soundboard';
export * from './stageInstance';
export * from './sticker';
export * from './teams';
@ -18,9 +21,6 @@ export * from './template';
export * from './user';
export * from './voice';
export * from './webhook';
export * from './monetization';
export * from './soundboard';
export * from './components';
import type { LocaleString } from '../rest';

View File

@ -85,7 +85,7 @@ export type RESTPatchAPIApplicationEmojiResult = APIApplicationEmoji;
/**
* https://discord.com/developers/docs/resources/emoji#delete-application-emoji
*/
export type RESTDeleteAPIApplicationEmojiResult = never;
export type RESTDeleteAPIApplicationEmojiResult = undefined;
/**
* https://discord.com/developers/docs/resources/application#get-application-activity-instance

View File

@ -80,4 +80,4 @@ export type RESTPatchAPIAutoModerationRuleResult = APIAutoModerationRule;
/**
* https://discord.com/developers/docs/resources/auto-moderation#delete-auto-moderation-rule
*/
export type RESTDeleteAPIAutoModerationRuleResult = never;
export type RESTDeleteAPIAutoModerationRuleResult = undefined;

View File

@ -344,17 +344,17 @@ export type RESTPostAPIChannelMessageCrosspostResult = APIMessage;
/**
* https://discord.com/developers/docs/resources/channel#create-reaction
*/
export type RESTPutAPIChannelMessageReactionResult = never;
export type RESTPutAPIChannelMessageReactionResult = undefined;
/**
* https://discord.com/developers/docs/resources/channel#delete-own-reaction
*/
export type RESTDeleteAPIChannelMessageOwnReaction = never;
export type RESTDeleteAPIChannelMessageOwnReaction = undefined;
/**
* https://discord.com/developers/docs/resources/channel#delete-user-reaction
*/
export type RESTDeleteAPIChannelMessageUserReactionResult = never;
export type RESTDeleteAPIChannelMessageUserReactionResult = undefined;
/**
* https://discord.com/developers/docs/resources/channel#get-reactions
@ -392,12 +392,12 @@ export type RESTGetAPIChannelMessageReactionUsersResult = APIUser[];
/**
* https://discord.com/developers/docs/resources/channel#delete-all-reactions
*/
export type RESTDeleteAPIChannelAllMessageReactionsResult = never;
export type RESTDeleteAPIChannelAllMessageReactionsResult = undefined;
/**
* https://discord.com/developers/docs/resources/channel#delete-all-reactions-for-emoji
*/
export type RESTDeleteAPIChannelMessageReactionResult = never;
export type RESTDeleteAPIChannelMessageReactionResult = undefined;
/**
* https://discord.com/developers/docs/resources/channel#edit-message
@ -464,7 +464,7 @@ export type RESTPatchAPIChannelMessageResult = APIMessage;
/**
* https://discord.com/developers/docs/resources/channel#delete-message
*/
export type RESTDeleteAPIChannelMessageResult = never;
export type RESTDeleteAPIChannelMessageResult = undefined;
/**
* https://discord.com/developers/docs/resources/channel#bulk-delete-messages
@ -479,7 +479,7 @@ export interface RESTPostAPIChannelMessagesBulkDeleteJSONBody {
/**
* https://discord.com/developers/docs/resources/channel#bulk-delete-messages
*/
export type RESTPostAPIChannelMessagesBulkDeleteResult = never;
export type RESTPostAPIChannelMessagesBulkDeleteResult = undefined;
/**
* https://discord.com/developers/docs/resources/channel#edit-channel-permissions
@ -510,7 +510,7 @@ export interface RESTPutAPIChannelPermissionJSONBody {
/**
* https://discord.com/developers/docs/resources/channel#edit-channel-permissions
*/
export type RESTPutAPIChannelPermissionResult = never;
export type RESTPutAPIChannelPermissionResult = undefined;
/**
* https://discord.com/developers/docs/resources/channel#get-channel-invites
@ -574,7 +574,7 @@ export type RESTPostAPIChannelInviteResult = APIExtendedInvite;
/**
* https://discord.com/developers/docs/resources/channel#delete-channel-permission
*/
export type RESTDeleteAPIChannelPermissionResult = never;
export type RESTDeleteAPIChannelPermissionResult = undefined;
/**
* https://discord.com/developers/docs/resources/channel#follow-news-channel
@ -594,7 +594,7 @@ export type RESTPostAPIChannelFollowersResult = APIFollowedChannel;
/**
* https://discord.com/developers/docs/resources/channel#trigger-typing-indicator
*/
export type RESTPostAPIChannelTypingResult = never;
export type RESTPostAPIChannelTypingResult = undefined;
/**
* https://discord.com/developers/docs/resources/channel#get-pinned-messages
@ -604,12 +604,12 @@ export type RESTGetAPIChannelPinsResult = APIMessage[];
/**
* https://discord.com/developers/docs/resources/channel#pin-message
*/
export type RESTPutAPIChannelPinResult = never;
export type RESTPutAPIChannelPinResult = undefined;
/**
* https://discord.com/developers/docs/resources/channel#unpin-message
*/
export type RESTDeleteAPIChannelPinResult = never;
export type RESTDeleteAPIChannelPinResult = undefined;
/**
* https://discord.com/developers/docs/resources/channel#group-dm-add-recipient
@ -711,12 +711,12 @@ export type RESTPostAPIChannelThreadsResult = APIChannel;
/**
* https://discord.com/developers/docs/resources/channel#join-thread
*/
export type RESTPutAPIChannelThreadMembersResult = never;
export type RESTPutAPIChannelThreadMembersResult = undefined;
/**
* https://discord.com/developers/docs/resources/channel#leave-thread
*/
export type RESTDeleteAPIChannelThreadMembersResult = never;
export type RESTDeleteAPIChannelThreadMembersResult = undefined;
/**
* https://discord.com/developers/docs/resources/channel#get-thread-member

View File

@ -58,4 +58,4 @@ export type RESTPatchAPIGuildEmojiResult = APIEmoji;
/**
* https://discord.com/developers/docs/resources/emoji#delete-guild-emoji
*/
export type RESTDeleteAPIGuildEmojiResult = never;
export type RESTDeleteAPIGuildEmojiResult = undefined;

View File

@ -322,7 +322,7 @@ export type RESTPatchAPIGuildResult = APIGuild;
/**
* https://discord.com/developers/docs/resources/guild#delete-guild
*/
export type RESTDeleteAPIGuildResult = never;
export type RESTDeleteAPIGuildResult = undefined;
/**
* https://discord.com/developers/docs/resources/guild#get-guild-channels
@ -364,7 +364,7 @@ export type RESTPatchAPIGuildChannelPositionsJSONBody = {
/**
* https://discord.com/developers/docs/resources/guild#modify-guild-channel-positions
*/
export type RESTPatchAPIGuildChannelPositionsResult = never;
export type RESTPatchAPIGuildChannelPositionsResult = undefined;
/**
* https://discord.com/developers/docs/resources/guild#list-active-guild-threads
@ -513,17 +513,17 @@ export interface RESTPatchAPICurrentGuildMemberJSONBody {
/**
* https://discord.com/developers/docs/resources/guild#add-guild-member-role
*/
export type RESTPutAPIGuildMemberRoleResult = never;
export type RESTPutAPIGuildMemberRoleResult = undefined;
/**
* https://discord.com/developers/docs/resources/guild#remove-guild-member-role
*/
export type RESTDeleteAPIGuildMemberRoleResult = never;
export type RESTDeleteAPIGuildMemberRoleResult = undefined;
/**
* https://discord.com/developers/docs/resources/guild#remove-guild-member
*/
export type RESTDeleteAPIGuildMemberResult = never;
export type RESTDeleteAPIGuildMemberResult = undefined;
/**
* https://discord.com/developers/docs/resources/guild#get-guild-bans
@ -568,12 +568,12 @@ export interface RESTPutAPIGuildBanJSONBody {
/**
* https://discord.com/developers/docs/resources/guild#create-guild-ban
*/
export type RESTPutAPIGuildBanResult = never;
export type RESTPutAPIGuildBanResult = undefined;
/**
* https://discord.com/developers/docs/resources/guild#remove-guild-ban
*/
export type RESTDeleteAPIGuildBanResult = never;
export type RESTDeleteAPIGuildBanResult = undefined;
/**
* https://discord.com/developers/docs/resources/guild#bulk-guild-ban
@ -723,7 +723,7 @@ export type RESTPatchAPIGuildRoleResult = APIRole;
/**
* https://discord.com/developers/docs/resources/guild#delete-guild-role
*/
export type RESTDeleteAPIGuildRoleResult = never;
export type RESTDeleteAPIGuildRoleResult = undefined;
/**
* https://discord.com/developers/docs/resources/guild#get-guild-prune-count
@ -800,7 +800,7 @@ export type RESTGetAPIGuildIntegrationsResult = APIGuildIntegration[];
/**
* https://discord.com/developers/docs/resources/guild#delete-guild-integration
*/
export type RESTDeleteAPIGuildIntegrationResult = never;
export type RESTDeleteAPIGuildIntegrationResult = undefined;
/**
* https://discord.com/developers/docs/resources/guild#get-guild-widget-settings
@ -888,7 +888,7 @@ export interface RESTPatchAPIGuildVoiceStateCurrentMemberJSONBody {
/**
* https://discord.com/developers/docs/resources/guild#modify-current-user-voice-state
*/
export type RESTPatchAPIGuildVoiceStateCurrentMemberResult = never;
export type RESTPatchAPIGuildVoiceStateCurrentMemberResult = undefined;
/**
* https://discord.com/developers/docs/resources/guild#modify-user-voice-state
@ -907,7 +907,7 @@ export interface RESTPatchAPIGuildVoiceStateUserJSONBody {
/**
* https://discord.com/developers/docs/resources/guild#modify-user-voice-state
*/
export type RESTPatchAPIGuildVoiceStateUserResult = never;
export type RESTPatchAPIGuildVoiceStateUserResult = undefined;
/**
* https://discord.com/developers/docs/resources/guild#get-guild-welcome-screen

View File

@ -106,7 +106,7 @@ export type RESTPatchAPIGuildScheduledEventResult = APIGuildScheduledEvent;
/**
* https://discord.com/developers/docs/resources/guild-scheduled-event#delete-guild-scheduled-event
*/
export type RESTDeleteAPIGuildScheduledEventResult = never;
export type RESTDeleteAPIGuildScheduledEventResult = undefined;
/**
* https://discord.com/developers/docs/resources/guild-scheduled-event#get-guild-scheduled-event-users

View File

@ -10,16 +10,16 @@ export * from './guild';
export * from './guildScheduledEvent';
export * from './interactions';
export * from './invite';
export * from './monetization';
export * from './oauth2';
export * from './poll';
export * from './soundboard';
export * from './stageInstance';
export * from './sticker';
export * from './template';
export * from './user';
export * from './voice';
export * from './webhook';
export * from './monetization';
export * from './soundboard';
export type DefaultUserAvatarAssets = 0 | 1 | 2 | 3 | 4 | 5;

View File

@ -86,7 +86,7 @@ export enum EntitlementOwnerType {
/**
* https://discord.com/developers/docs/monetization/entitlements#delete-test-entitlement
*/
export type RESTDeleteAPIEntitlementResult = never;
export type RESTDeleteAPIEntitlementResult = undefined;
/**
* https://discord.com/developers/docs/monetization/skus#list-skus
@ -96,7 +96,7 @@ export type RESTGetAPISKUsResult = APISKU[];
/**
* https://discord.com/developers/docs/monetization/entitlements#consume-an-entitlement
*/
export type RESTPostAPIEntitlementConsumeResult = never;
export type RESTPostAPIEntitlementConsumeResult = undefined;
/**
* https://canary.discord.com/developers/docs/resources/subscription#query-string-params

View File

@ -67,4 +67,4 @@ export type RESTPatchAPIGuildSoundboardSoundResult = APISoundBoard;
* This endpoint supports the X-Audit-Log-Reason header.
* @fires GuildSoundboardSoundDelete
*/
export type RESTDeleteAPIGuildSoundboardSoundResult = never;
export type RESTDeleteAPIGuildSoundboardSoundResult = undefined;

View File

@ -61,4 +61,4 @@ export type RESTPatchAPIStageInstanceResult = APIStageInstance;
/**
* https://discord.com/developers/docs/resources/stage-instance#delete-stage-instance
*/
export type RESTDeleteAPIStageInstanceResult = never;
export type RESTDeleteAPIStageInstanceResult = undefined;

View File

@ -82,4 +82,4 @@ export type RESTPatchAPIGuildStickerResult = APISticker;
/**
* https://discord.com/developers/docs/resources/sticker#delete-guild-sticker
*/
export type RESTDeleteAPIGuildStickerResult = never;
export type RESTDeleteAPIGuildStickerResult = undefined;

View File

@ -91,7 +91,7 @@ export type RESTGetAPICurrentUserGuildsResult = RESTAPIPartialCurrentUserGuild[]
/**
* https://discord.com/developers/docs/resources/user#leave-guild
*/
export type RESTDeleteAPICurrentUserGuildResult = never;
export type RESTDeleteAPICurrentUserGuildResult = undefined;
/**
* https://discord.com/developers/docs/resources/user#create-dm

View File

@ -89,12 +89,12 @@ export type RESTPatchAPIWebhookWithTokenResult = RESTGetAPIWebhookWithTokenResul
/**
* https://discord.com/developers/docs/resources/webhook#delete-webhook
*/
export type RESTDeleteAPIWebhookResult = never;
export type RESTDeleteAPIWebhookResult = undefined;
/**
* https://discord.com/developers/docs/resources/webhook#delete-webhook-with-token
*/
export type RESTDeleteAPIWebhookWithTokenResult = never;
export type RESTDeleteAPIWebhookWithTokenResult = undefined;
/**
* https://discord.com/developers/docs/resources/webhook#execute-webhook
@ -202,7 +202,7 @@ export interface RESTPostAPIWebhookWithTokenQuery {
/**
* https://discord.com/developers/docs/resources/webhook#execute-webhook
*/
export type RESTPostAPIWebhookWithTokenResult = never;
export type RESTPostAPIWebhookWithTokenResult = undefined;
/**
* Received when a call to https://discord.com/developers/docs/resources/webhook#execute-webhook receives
@ -220,7 +220,7 @@ export type RESTPostAPIWebhookWithTokenSlackQuery = Omit<RESTPostAPIWebhookWithT
/**
* https://discord.com/developers/docs/resources/webhook#execute-slackcompatible-webhook
*/
export type RESTPostAPIWebhookWithTokenSlackResult = never;
export type RESTPostAPIWebhookWithTokenSlackResult = undefined;
/**
* Received when a call to https://discord.com/developers/docs/resources/webhook#execute-webhook receives
@ -238,7 +238,7 @@ export type RESTPostAPIWebhookWithTokenGitHubQuery = Omit<RESTPostAPIWebhookWith
/**
* https://discord.com/developers/docs/resources/webhook#execute-githubcompatible-webhook
*/
export type RESTPostAPIWebhookWithTokenGitHubResult = never;
export type RESTPostAPIWebhookWithTokenGitHubResult = undefined;
/**
* Received when a call to https://discord.com/developers/docs/resources/webhook#execute-webhook receives
@ -297,4 +297,4 @@ export type RESTPatchAPIWebhookWithTokenMessageResult = APIMessage;
/**
* https://discord.com/developers/docs/resources/webhook#delete-webhook-message
*/
export type RESTDeleteAPIWebhookWithTokenMessageResult = never;
export type RESTDeleteAPIWebhookWithTokenMessageResult = undefined;

View File

@ -23,8 +23,8 @@ import type {
GatewayGuildIntegrationsUpdateDispatchData,
GatewayGuildMemberAddDispatchData,
GatewayGuildMemberRemoveDispatchData,
GatewayGuildMemberUpdateDispatchData,
GatewayGuildMembersChunkDispatchData,
GatewayGuildMemberUpdateDispatchData,
GatewayGuildRoleCreateDispatchData,
GatewayGuildRoleDeleteDispatchData,
GatewayGuildRoleUpdateDispatchData,
@ -55,8 +55,8 @@ import type {
GatewayThreadCreateDispatchData,
GatewayThreadDeleteDispatchData,
GatewayThreadListSyncDispatchData,
GatewayThreadMemberUpdateDispatchData,
GatewayThreadMembersUpdateDispatchData,
GatewayThreadMemberUpdateDispatchData,
GatewayTypingStartDispatchData,
GatewayUserUpdateDispatchData,
GatewayVoiceChannelEffectSendDispachData,

View File

@ -25,14 +25,14 @@ export class BaseSocket {
return new Promise<number>(res => {
const nonce = randomUUID();
const start = performance.now();
const listener = (data: Buffer) => {
const listener = ({ data }: MessageEvent) => {
if (data.toString() !== nonce) return;
//@ts-expect-error
ws.removeListener('pong', listener);
ws.removeEventListener('pong', listener);
res(performance.now() - start);
};
//@ts-expect-error
ws.on('pong', listener);
ws.addEventListener('pong', listener);
//@ts-expect-error
ws.ping(nonce);
});

View File

@ -0,0 +1,43 @@
import type { Awaitable } from '../../common';
export type WorkerHeartbeaterMessages = SendHeartbeat;
export type CreateHeartbeaterMessage<T extends string, D extends object = object> = { type: T } & D;
export type SendHeartbeat = CreateHeartbeaterMessage<'HEARTBEAT'>;
export class Heartbeater {
store = new Map<
number,
{
ack: boolean;
interval: NodeJS.Timeout;
}
>();
constructor(
public sendMethod: (workerId: number, data: WorkerHeartbeaterMessages) => Awaitable<void>,
public interval: number,
) {}
register(workerId: number, recreate: (workerId: number) => Awaitable<void>) {
if (this.interval <= 0) return;
this.store.set(workerId, {
ack: true,
interval: setInterval(() => {
const heartbeat = this.store.get(workerId)!;
if (!heartbeat.ack) {
heartbeat.ack = true;
return recreate(workerId);
}
heartbeat.ack = false;
this.sendMethod(workerId, { type: 'HEARTBEAT' });
}, this.interval),
});
}
acknowledge(workerId: number) {
const heartbeat = this.store.get(workerId);
if (!heartbeat) return;
heartbeat.ack = true;
}
}

View File

@ -1,5 +1,5 @@
import { inflateSync } from 'node:zlib';
import { LogLevels, Logger, type MakeRequired, MergeOptions, delay, hasIntent } from '../../common';
import { delay, hasIntent, Logger, LogLevels, type MakeRequired, MergeOptions } from '../../common';
import {
type APIGuildMember,
GatewayCloseCodes,
@ -45,7 +45,7 @@ export class Shard {
bucket: DynamicBucket;
offlineSendQueue: ((_?: unknown) => void)[] = [];
pendingGuilds = new Set<string>();
pendingGuilds?: Set<string>;
options: MakeRequired<ShardOptions, 'properties' | 'ratelimitOptions' | 'reconnectTimeout' | 'connectionTimeout'>;
isReady = false;
@ -56,10 +56,7 @@ export class Shard {
{
members: APIGuildMember[];
presences: GatewayGuildMembersChunkPresence[];
resolve: (value: {
members: APIGuildMember[];
presences: GatewayGuildMembersChunkPresence[];
}) => void;
resolve: (value: { members: APIGuildMember[]; presences: GatewayGuildMembersChunkPresence[] }) => void;
reject: (reason?: any) => void;
}
>();
@ -138,7 +135,6 @@ export class Shard {
);
// @ts-expect-error Use native websocket when using Bun
// biome-ignore lint/correctness/noUndeclaredVariables: /\
this.websocket = new BaseSocket(typeof Bun === 'undefined' ? 'ws' : 'bun', this.currentGatewayURL);
this.websocket.onmessage = ({ data }: { data: string | Buffer }) => {
@ -301,16 +297,14 @@ export class Shard {
clearTimeout(this.connectionTimeout);
this.connectionTimeout = undefined;
if (hasIntent(this.options.intents, 'Guilds')) {
for (let i = 0; i < packet.d.guilds.length; i++) {
this.pendingGuilds.add(packet.d.guilds.at(i)!.id);
}
this.pendingGuilds = new Set(packet.d.guilds.map(guild => guild.id));
}
this.data.resume_gateway_url = packet.d.resume_gateway_url;
this.data.session_id = packet.d.session_id;
this.offlineSendQueue.map(resolve => resolve());
this.options.handlePayload(this.id, packet);
if (this.pendingGuilds.size === 0) {
if (this.pendingGuilds?.size === 0) {
this.isReady = true;
this.options.handlePayload(this.id, {
t: GatewayDispatchEvents.GuildsReady,
@ -322,7 +316,7 @@ export class Shard {
}
case GatewayDispatchEvents.GuildCreate:
case GatewayDispatchEvents.GuildDelete:
if (this.pendingGuilds.delete(packet.d.id)) {
if (this.pendingGuilds?.delete(packet.d.id)) {
(packet as any).t = `RAW_${packet.t}`;
this.options.handlePayload(this.id, packet);
if (this.pendingGuilds.size === 0) {
@ -376,10 +370,7 @@ export class Shard {
) {
const nonce = Date.now().toString() + Math.random().toString(36);
let resolve: (value: {
members: APIGuildMember[];
presences: GatewayGuildMembersChunkPresence[];
}) => void = () => {
let resolve: (value: { members: APIGuildMember[]; presences: GatewayGuildMembersChunkPresence[] }) => void = () => {
//
};
let reject: (reason?: any) => void = () => {

View File

@ -1,11 +1,11 @@
import {
LogLevels,
calculateShardId,
Logger,
LogLevels,
lazyLoadPackage,
type MakeRequired,
MergeOptions,
type WatcherSendToShard,
calculateShardId,
lazyLoadPackage,
} from '../../common';
import type { DeepPartial, MakeDeepPartial } from '../../common/types/util';
import {

View File

@ -69,6 +69,9 @@ export interface WorkerManagerOptions extends Omit<ShardManagerOptions, 'handleP
workerProxy?: boolean;
/** @default 15000 */
heartbeaterInterval?: number;
path: string;
handlePayload?(shardId: number, workerId: number, packet: GatewayDispatchPayload): any;

View File

@ -1,4 +1,4 @@
import { type UUID, createHash, randomBytes, randomUUID } from 'node:crypto';
import { createHash, randomBytes, randomUUID, type UUID } from 'node:crypto';
import { request } from 'node:https';
import type { Socket } from 'node:net';

View File

@ -114,7 +114,12 @@ export type CustomWorkerClientMessages = {
>;
};
export type ClientHeartbeaterMessages = ACKHeartbeat;
export type ACKHeartbeat = CreateWorkerMessage<'ACK_HEARTBEAT'>;
export type WorkerMessages =
| ClientHeartbeaterMessages
| {
[K in BaseWorkerMessage['type']]: Identify<Extract<BaseWorkerMessage, { type: K }>>;
}[BaseWorkerMessage['type']]

View File

@ -1,14 +1,15 @@
import cluster, { type Worker as ClusterWorker } from 'node:cluster';
import { type UUID, randomUUID } from 'node:crypto';
import { randomUUID, type UUID } from 'node:crypto';
import type { Worker as WorkerThreadsWorker } from 'node:worker_threads';
import { ApiHandler, type CustomWorkerManagerEvents, Logger, type UsingClient, type WorkerClient } from '../..';
import { type Adapter, MemoryAdapter } from '../../cache';
import { BaseClient, type InternalRuntimeConfig } from '../../client/base';
import { BASE_HOST, type Identify, MergeOptions, type PickPartial, lazyLoadPackage } from '../../common';
import { BASE_HOST, type Identify, lazyLoadPackage, MergeOptions, type PickPartial } from '../../common';
import type { GatewayPresenceUpdateData, GatewaySendPayload, RESTGetAPIGatewayBotResult } from '../../types';
import { WorkerManagerDefaults, properties } from '../constants';
import { properties, WorkerManagerDefaults } from '../constants';
import { DynamicBucket } from '../structures';
import { ConnectQueue } from '../structures/timeout';
import { Heartbeater, type WorkerHeartbeaterMessages } from './heartbeater';
import type { ShardOptions, WorkerData, WorkerManagerOptions } from './shared';
import type { WorkerInfo, WorkerMessages, WorkerShardInfo } from './worker';
@ -55,6 +56,7 @@ export class WorkerManager extends Map<
rest!: ApiHandler;
reshardingWorkerQueue: (() => void)[] = [];
private _info?: RESTGetAPIGatewayBotResult;
heartbeater: Heartbeater;
constructor(
options: Omit<
@ -75,6 +77,8 @@ export class WorkerManager extends Map<
return oldFn(message);
};
}
this.heartbeater = new Heartbeater(this.postMessage.bind(this), options.heartbeaterInterval ?? 15e3);
}
setCache(adapter: Adapter) {
@ -144,12 +148,12 @@ export class WorkerManager extends Map<
return workerId;
}
postMessage(id: number, body: ManagerMessages) {
postMessage(id: number, body: ManagerMessages | WorkerHeartbeaterMessages) {
const worker = this.get(id);
if (!worker) return this.debugger?.error(`Worker ${id} does not exists.`);
switch (this.options.mode) {
case 'clusters':
(worker as ClusterWorker).send(body);
if ((worker as ClusterWorker).isConnected()) (worker as ClusterWorker).send(body);
break;
case 'threads':
(worker as import('worker_threads').Worker).postMessage(body);
@ -160,33 +164,40 @@ export class WorkerManager extends Map<
}
}
prepareWorkers(shards: number[][], resharding = false) {
prepareWorkers(shards: number[][], rawResharding = false) {
const worker_threads = lazyLoadPackage<typeof import('node:worker_threads')>('node:worker_threads');
if (!worker_threads) throw new Error('Cannot prepare workers without worker_threads.');
for (let i = 0; i < shards.length; i++) {
const registerWorker = (resharding: boolean) => {
const worker = this.createWorker({
path: this.options.path,
debug: this.options.debug,
token: this.options.token,
shards: shards[i],
intents: this.options.intents,
workerId: i,
workerProxy: this.options.workerProxy,
totalShards: resharding ? this._info!.shards : this.totalShards,
mode: this.options.mode,
resharding,
totalWorkers: shards.length,
info: {
...this.options.info,
shards: this.totalShards,
},
compress: this.options.compress,
});
this.set(i, worker);
};
const workerExists = this.has(i);
if (resharding || !workerExists) {
this[resharding ? 'reshardingWorkerQueue' : 'workerQueue'].push(() => {
const worker = this.createWorker({
path: this.options.path,
debug: this.options.debug,
token: this.options.token,
shards: shards[i],
intents: this.options.intents,
workerId: i,
workerProxy: this.options.workerProxy,
totalShards: resharding ? this._info!.shards : this.totalShards,
mode: this.options.mode,
resharding,
totalWorkers: shards.length,
info: {
...this.options.info,
shards: this.totalShards,
},
compress: this.options.compress,
if (rawResharding || !workerExists) {
this[rawResharding ? 'reshardingWorkerQueue' : 'workerQueue'].push(() => {
registerWorker(rawResharding);
this.heartbeater.register(i, () => {
this.delete(i);
registerWorker(false);
});
this.set(i, worker);
});
}
}
@ -218,6 +229,9 @@ export class WorkerManager extends Map<
env,
});
worker.on('message', data => this.handleWorkerMessage(data));
worker.on('error', err => {
this.debugger?.error(`[Worker #${workerData.workerId}]`, err);
});
return worker;
}
case 'clusters': {
@ -254,6 +268,9 @@ export class WorkerManager extends Map<
async handleWorkerMessage(message: WorkerMessages) {
switch (message.type) {
case 'ACK_HEARTBEAT':
this.heartbeater.acknowledge(message.workerId);
break;
case 'WORKER_READY_RESHARDING':
{
this.get(message.workerId)!.resharded = true;
@ -288,6 +305,7 @@ export class WorkerManager extends Map<
for (const [id] of this.entries()) {
this.postMessage(id, {
type: 'CONNECT_ALL_SHARDS_RESHARDING',
totalShards: this.options.totalShards,
} satisfies ConnnectAllShardsResharding);
}
this.forEach(w => {
@ -656,7 +674,12 @@ export type ManagerSpawnShardsResharding = CreateManagerMessage<
Pick<ShardOptions, 'info' | 'properties' | 'compress'>
>;
export type DisconnectAllShardsResharding = CreateManagerMessage<'DISCONNECT_ALL_SHARDS_RESHARDING'>;
export type ConnnectAllShardsResharding = CreateManagerMessage<'CONNECT_ALL_SHARDS_RESHARDING'>;
export type ConnnectAllShardsResharding = CreateManagerMessage<
'CONNECT_ALL_SHARDS_RESHARDING',
{
totalShards: number;
}
>;
export type ManagerSendPayload = CreateManagerMessage<
'SEND_PAYLOAD',
GatewaySendPayload & { shardId: number; nonce: string }

View File

@ -1,3 +1,3 @@
export * from './SharedTypes';
export * from './constants';
export * from './discord';
export * from './SharedTypes';

Some files were not shown because too many files have changed in this diff Show More