diff --git a/src/api/api.ts b/src/api/api.ts index 7ecd770..f64eac4 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -1,8 +1,6 @@ import { filetypeinfo } from 'magic-bytes.js'; import { randomUUID } from 'node:crypto'; -import { setTimeout as delay } from 'node:timers/promises'; -import { parentPort, workerData } from 'node:worker_threads'; -import { Logger } from '../common'; +import { Logger, delay, lazyLoadPackage } from '../common'; import { snowflakeToTimestamp } from '../structures/extra/functions'; import type { WorkerData } from '../websocket'; import type { WorkerSendApiRequest } from '../websocket/discord/worker'; @@ -20,6 +18,9 @@ import { } from './shared'; import { isBufferLike } from './utils/utils'; +let parentPort: import('node:worker_threads').MessagePort; +let workerData: WorkerData; + export class ApiHandler { options: ApiHandlerInternalOptions; globalBlock = false; @@ -43,8 +44,15 @@ export class ApiHandler { }); } - if (options.workerProxy && !parentPort) throw new Error('Cannot use workerProxy without a parent.'); + const worker_threads = lazyLoadPackage('node:worker_threads'); + + if (options.workerProxy && !worker_threads?.parentPort) throw new Error('Cannot use workerProxy without a parent.'); if (options.workerProxy) this.workerPromises = new Map(); + + if (worker_threads) { + workerData = worker_threads.workerData; + if (worker_threads.parentPort) parentPort = worker_threads.parentPort; + } } globalUnblock() { diff --git a/src/builders/Attachment.ts b/src/builders/Attachment.ts index 8132eff..9de9e80 100644 --- a/src/builders/Attachment.ts +++ b/src/builders/Attachment.ts @@ -1,9 +1,8 @@ import type { APIAttachment, RESTAPIAttachment } from 'discord-api-types/v10'; import { randomBytes } from 'node:crypto'; -import { readFile, stat } from 'node:fs/promises'; import path from 'node:path'; import { type UsingClient, throwError, type RawFile } from '..'; -import type { ImageResolvable, ObjectToLower } from '../common'; +import { promisesReadFile, promisesStat, type ImageResolvable, type ObjectToLower } from '../common'; import { Base } from '../structures/extra/Base'; export interface AttachmentResolvableMap { @@ -196,12 +195,12 @@ export async function resolveAttachmentData( } case 'path': { const file = path.resolve(data as string); - const stats = await stat(file); + const stats = await promisesStat(file); if (!stats.isFile()) return throwError( `The attachment type has been expressed as ${type.toUpperCase()} but cannot be resolved as one.`, ); - return { data: await readFile(file) }; + return { data: await promisesReadFile(file) }; } case 'buffer': { if (Buffer.isBuffer(data)) return { data }; diff --git a/src/cache/adapters/workeradapter.ts b/src/cache/adapters/workeradapter.ts index 9646dc6..04d9262 100644 --- a/src/cache/adapters/workeradapter.ts +++ b/src/cache/adapters/workeradapter.ts @@ -1,14 +1,19 @@ import { randomUUID } from 'node:crypto'; -import { parentPort } from 'node:worker_threads'; import type { WorkerData } from '../../websocket'; import type { WorkerSendCacheRequest } from '../../websocket/discord/worker'; import type { Adapter } from './types'; +import { lazyLoadPackage } from '../../common'; + +let parentPort: import('node:worker_threads').MessagePort; export class WorkerAdapter implements Adapter { isAsync = true; promises = new Map void; timeout: NodeJS.Timeout }>(); - constructor(public workerData: WorkerData) {} + constructor(public workerData: WorkerData) { + const worker_threads = lazyLoadPackage('node:worker_threads'); + if (worker_threads?.parentPort) parentPort = worker_threads.parentPort; + } postMessage(body: any) { if (parentPort) return parentPort.postMessage(body); diff --git a/src/client/base.ts b/src/client/base.ts index e5ece87..c733a27 100644 --- a/src/client/base.ts +++ b/src/client/base.ts @@ -81,6 +81,8 @@ export class BaseClient { options: BaseClientOptions | undefined; + static seyfertConfig?: InternalRuntimeConfigHTTP | InternalRuntimeConfig; + constructor(options?: BaseClientOptions) { this.options = MergeOptions( { @@ -300,11 +302,12 @@ export class BaseClient { async getRC< T extends InternalRuntimeConfigHTTP | InternalRuntimeConfig = InternalRuntimeConfigHTTP | InternalRuntimeConfig, >() { - const { locations, debug, ...env } = (await magicImport(join(process.cwd(), 'seyfert.config.js')).then( - x => x.default ?? x, - )) as T; + const seyfertConfig = (BaseClient.seyfertConfig || + (await magicImport(join(process.cwd(), 'seyfert.config.js')).then(x => x.default ?? x))) as T; - return { + const { locations, debug, ...env } = seyfertConfig; + + const obj = { debug: !!debug, ...env, templates: locations.templates ? join(process.cwd(), locations.base, locations.templates) : undefined, @@ -316,6 +319,10 @@ export class BaseClient { base: join(process.cwd(), locations.base), output: join(process.cwd(), locations.output), }; + + BaseClient.seyfertConfig = seyfertConfig; + + return obj; } } diff --git a/src/client/client.ts b/src/client/client.ts index 4bf6e8f..1c7e79f 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -1,7 +1,6 @@ import { GatewayIntentBits, type GatewayDispatchPayload, type GatewayPresenceUpdateData } from 'discord-api-types/v10'; -import { parentPort, workerData } from 'node:worker_threads'; import type { Command, CommandContext, Message, SubCommand } from '..'; -import type { DeepPartial, If, WatcherPayload, WatcherSendToShard } from '../common'; +import { lazyLoadPackage, type DeepPartial, type If, type WatcherPayload, type WatcherSendToShard } from '../common'; import { EventHandler } from '../events'; import { ClientUser } from '../structures'; import { ShardManager, properties, type ShardManagerOptions } from '../websocket'; @@ -12,6 +11,8 @@ import { BaseClient } from './base'; import { onInteractionCreate } from './oninteractioncreate'; import { onMessageCreate } from './onmessagecreate'; +let parentPort: import('node:worker_threads').MessagePort; + export class Client extends BaseClient { private __handleGuilds?: Set = new Set(); gateway!: ShardManager; @@ -68,7 +69,14 @@ export class Client extends BaseClient { protected async execute(options: { token?: string; intents?: number } = {}) { await super.execute(options); - if (!workerData?.__USING_WATCHER__) { + + const worker_threads = lazyLoadPackage('node:worker_threads'); + + if (worker_threads?.parentPort) { + parentPort = worker_threads.parentPort; + } + + if (!worker_threads?.workerData.__USING_WATCHER__) { await this.gateway.spawnShards(); } else { parentPort?.on('message', (data: WatcherPayload | WatcherSendToShard) => { diff --git a/src/client/httpclient.ts b/src/client/httpclient.ts index b412cf4..d565995 100644 --- a/src/client/httpclient.ts +++ b/src/client/httpclient.ts @@ -35,9 +35,9 @@ export class HttpClient extends BaseClient { constructor(options?: BaseClientOptions) { super(options); - if (!UWS) { - throw new Error('No uws installed.'); - } + // if (!UWS) { + // throw new Error('No uws installed.'); + // } if (!nacl) { throw new Error('No tweetnacl installed.'); } @@ -89,20 +89,40 @@ export class HttpClient extends BaseClient { this.publicKey = publicKey; this.publicKeyHex = Buffer.from(this.publicKey, 'hex'); - this.app = UWS!.App(); - this.app.post('/interactions', (res, req) => { - return this.onPacket(res, req); - }); - this.app.listen(port, () => { - this.logger.info(`Listening to port ${port}`); - }); + if (UWS) { + this.app = UWS.App(); + this.app.post('/interactions', (res, req) => { + return this.onPacket(res, req); + }); + this.app.listen(port, () => { + this.logger.info(`Listening to port ${port}`); + }); + } else { + this.logger.warn('No UWS installed.'); + } } - async start(options: DeepPartial> = {}) { + async start(options: DeepPartial> = {}) { await super.start(options); return this.execute(options.httpConnection); } + protected async verifySignatureGenericRequest(req: Request) { + const timestamp = req.headers.get('x-signature-timestamp'); + const ed25519 = req.headers.get('x-signature-ed25519') ?? ''; + const body = (await req.json()) as APIInteraction; + if ( + nacl!.sign.detached.verify( + Buffer.from(timestamp + JSON.stringify(body)), + Buffer.from(ed25519, 'hex'), + this.publicKeyHex, + ) + ) { + return body; + } + return; + } + // https://discord.com/developers/docs/interactions/receiving-and-responding#security-and-authorization protected async verifySignature(res: HttpResponse, req: HttpRequest) { const timestamp = req.getHeader('x-signature-timestamp'); @@ -120,7 +140,71 @@ export class HttpClient extends BaseClient { return; } - async onPacket(res: HttpResponse, req: HttpRequest) { + async fetch(req: Request): Promise { + const rawBody = await this.verifySignatureGenericRequest(req); + if (!rawBody) { + this.debugger?.debug('Invalid request/No info, returning 418 status.'); + // I'm a teapot + return new Response('', { status: 418 }); + } + switch (rawBody.type) { + case InteractionType.Ping: + this.debugger?.debug('Ping interaction received, responding.'); + return Response.json( + { type: InteractionResponseType.Pong }, + { + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + default: + return new Promise(r => { + onInteractionCreate(this, rawBody, -1, async ({ body, files }) => { + let response: FormData | APIInteractionResponse; + const headers: { 'Content-Type'?: string } = {}; + + if (files) { + response = new FormData(); + for (const [index, file] of files.entries()) { + const fileKey = file.key ?? `files[${index}]`; + if (isBufferLike(file.data)) { + let contentType = file.contentType; + + if (!contentType) { + const [parsedType] = filetypeinfo(file.data); + + if (parsedType) { + contentType = + OverwrittenMimeTypes[parsedType.mime as keyof typeof OverwrittenMimeTypes] ?? + parsedType.mime ?? + 'application/octet-stream'; + } + } + response.append(fileKey, new Blob([file.data], { type: contentType }), file.name); + } else { + response.append(fileKey, new Blob([`${file.data}`], { type: file.contentType }), file.name); + } + } + if (body) { + response.append('payload_json', JSON.stringify(body)); + } + } else { + response = body ?? {}; + headers['Content-Type'] = 'application/json'; + } + + r( + Response.json(response, { + headers, + }), + ); + }); + }); + } + } + + protected async onPacket(res: HttpResponse, req: HttpRequest) { const rawBody = await this.verifySignature(res, req); if (!rawBody) { this.debugger?.debug('Invalid request/No info, returning 418 status.'); diff --git a/src/client/workerclient.ts b/src/client/workerclient.ts index 6ac0275..47b0218 100644 --- a/src/client/workerclient.ts +++ b/src/client/workerclient.ts @@ -1,10 +1,9 @@ import { GatewayIntentBits, type GatewayDispatchPayload, type GatewaySendPayload } from 'discord-api-types/v10'; import { randomUUID } from 'node:crypto'; -import { parentPort as manager } from 'node:worker_threads'; import { ApiHandler, Logger } from '..'; import type { Cache } from '../cache'; import { WorkerAdapter } from '../cache'; -import { LogLevels, type DeepPartial, type When } from '../common'; +import { LogLevels, lazyLoadPackage, type DeepPartial, type When } from '../common'; import { EventHandler } from '../events'; import { ClientUser } from '../structures'; import { Shard, type ShardManagerOptions, type WorkerData } from '../websocket'; @@ -28,6 +27,7 @@ import { onInteractionCreate } from './oninteractioncreate'; import { onMessageCreate } from './onmessagecreate'; let workerData: WorkerData; +let manager: import('node:worker_threads').MessagePort; try { workerData = { debug: process.env.SEYFERT_WORKER_DEBUG === 'true', @@ -51,6 +51,7 @@ export class WorkerClient extends BaseClient { promises = new Map void; timeout: NodeJS.Timeout }>(); shards = new Map(); + declare options: WorkerClientOptions | undefined; constructor(options?: WorkerClientOptions) { @@ -62,7 +63,13 @@ export class WorkerClient extends BaseClient { type: 'WORKER_START', workerId: workerData.workerId, } satisfies WorkerStart); + + const worker_threads = lazyLoadPackage('node:worker_threads'); + if (worker_threads?.parentPort) { + manager = worker_threads?.parentPort; + } (manager ?? process).on('message', (data: ManagerMessages) => this.handleManagerMessages(data)); + this.setServices({ cache: { adapter: new WorkerAdapter(workerData), diff --git a/src/commands/applications/chatcontext.ts b/src/commands/applications/chatcontext.ts index 24b80fe..4e07ee9 100644 --- a/src/commands/applications/chatcontext.ts +++ b/src/commands/applications/chatcontext.ts @@ -9,7 +9,7 @@ import { type GuildMember, type InteractionGuildMember, } from '../../structures'; -import { BaseContext } from '../basecontex'; +import { BaseContext } from '../basecontext'; import type { RegisteredMiddlewares } from '../decorators'; import type { OptionResolver } from '../optionresolver'; import type { Command, ContextOptions, OptionsRecord, SubCommand } from './chat'; diff --git a/src/commands/applications/menucontext.ts b/src/commands/applications/menucontext.ts index 697a9e4..f508265 100644 --- a/src/commands/applications/menucontext.ts +++ b/src/commands/applications/menucontext.ts @@ -17,7 +17,7 @@ import { type MessageCommandInteraction, type UserCommandInteraction, } from '../../structures'; -import { BaseContext } from '../basecontex'; +import { BaseContext } from '../basecontext'; import type { RegisteredMiddlewares } from '../decorators'; import type { CommandMetadata, ExtendContext, GlobalMetadata, UsingClient } from './shared'; diff --git a/src/commands/basecontex.ts b/src/commands/basecontext.ts similarity index 100% rename from src/commands/basecontex.ts rename to src/commands/basecontext.ts diff --git a/src/commands/handler.ts b/src/commands/handler.ts index 29a2a1d..eebe653 100644 --- a/src/commands/handler.ts +++ b/src/commands/handler.ts @@ -8,6 +8,7 @@ import type { UsingClient } from './applications/shared'; export class CommandHandler extends BaseHandler { values: (Command | ContextMenuCommand)[] = []; + protected filter = (path: string) => path.endsWith('.js') || (!path.endsWith('.d.ts') && path.endsWith('.ts')); constructor( @@ -36,10 +37,15 @@ export class CommandHandler extends BaseHandler { } } - async load(commandsDir: string, client: UsingClient) { - const result = ( - await this.loadFilesK<{ new (): Command | SubCommand | ContextMenuCommand }>(await this.getFiles(commandsDir)) - ).filter(x => x.file); + async load(commandsDir: string, client: UsingClient, instances?: { new (): Command | ContextMenuCommand }[]) { + const result = + instances?.map(x => { + const i = new x(); + return { name: i.name, file: x, path: i.__filePath ?? '*' }; + }) ?? + ( + await this.loadFilesK<{ new (): Command | SubCommand | ContextMenuCommand }>(await this.getFiles(commandsDir)) + ).filter(x => x.file); this.values = []; for (const command of result) { diff --git a/src/common/bot/watcher.ts b/src/common/bot/watcher.ts index 87ccf8b..3da217f 100644 --- a/src/common/bot/watcher.ts +++ b/src/common/bot/watcher.ts @@ -1,18 +1,17 @@ -import { watch } from 'chokidar'; import type { GatewayDispatchPayload, GatewaySendPayload } from 'discord-api-types/v10'; import { execSync } from 'node:child_process'; -import { Worker } from 'node:worker_threads'; import { ApiHandler, Router } from '../../api'; import { BaseClient, type InternalRuntimeConfig } from '../../client/base'; import { ShardManager, type ShardManagerOptions } from '../../websocket'; import { Logger } from '../it/logger'; import type { MakeRequired } from '../types/util'; +import { lazyLoadPackage } from '../it/utils'; /** * Represents a watcher class that extends the ShardManager. */ export class Watcher extends ShardManager { - worker?: Worker; + worker?: import('node:worker_threads').Worker; logger = new Logger({ name: '[Watcher]', }); @@ -47,11 +46,13 @@ export class Watcher extends ShardManager { * Resets the worker instance. */ resetWorker() { + const worker_threads = lazyLoadPackage('node:worker_threads'); + if (!worker_threads) throw new Error('Cannot use worker_threads'); if (this.worker) { this.worker.terminate(); } this.build(); - this.worker = new Worker(this.options.filePath, { + this.worker = new worker_threads.Worker(this.options.filePath, { argv: this.options.argv, workerData: { __USING_WATCHER__: true, @@ -95,7 +96,12 @@ export class Watcher extends ShardManager { this.connectQueue.concurrency = this.options.info.session_start_limit.max_concurrency; await super.spawnShards(); - const watcher = watch(this.options.srcPath).on('ready', () => { + + const chokidar = lazyLoadPackage('chokidar'); + + if (!chokidar?.watch) return this.logger.warn('No chokidar installed.'); + + const watcher = chokidar.watch(this.options.srcPath).on('ready', () => { this.logger.debug(`Watching ${this.options.srcPath}`); watcher.on('all', event => { this.logger.debug(`${event} event detected, building`); diff --git a/src/common/it/logger.ts b/src/common/it/logger.ts index 8371e74..c220439 100644 --- a/src/common/it/logger.ts +++ b/src/common/it/logger.ts @@ -1,8 +1,7 @@ import { createWriteStream, existsSync, mkdirSync, type WriteStream } from 'node:fs'; -import { readdir, unlink } from 'node:fs/promises'; import { join } from 'node:path'; import { bgBrightWhite, black, bold, brightBlack, cyan, gray, italic, red, stripColor, yellow } from './colors'; -import { MergeOptions } from './utils'; +import { MergeOptions, promisesReaddir, promisesUnlink } from './utils'; export enum LogLevels { Debug = 0, Info = 1, @@ -52,10 +51,10 @@ export class Logger { } static async clearLogs() { - for (const i of await readdir(join(process.cwd(), Logger.dirname))) { - if (this.streams[i]) await new Promise(res => this.streams[i]!.close(res)); - await unlink(join(process.cwd(), Logger.dirname, i)).catch(() => {}); - delete this.streams[i]; + for (const i of await promisesReaddir(join(process.cwd(), Logger.dirname))) { + if (this.streams[i.name]) await new Promise(res => this.streams[i.name]!.close(res)); + await promisesUnlink(join(process.cwd(), Logger.dirname, i.name)).catch(() => {}); + delete this.streams[i.name]; } } @@ -131,10 +130,10 @@ export class Logger { if (!Logger.__callback) { const color = Logger.colorFunctions.get(level) ?? Logger.noColor; - const memoryData = process.memoryUsage(); + const memoryData = process.memoryUsage?.(); const date = new Date(); log = [ - brightBlack(formatMemoryUsage(memoryData.rss)), + brightBlack(formatMemoryUsage(memoryData?.rss ?? 0)), bgBrightWhite(black(`[${date.toLocaleDateString()} ${date.toLocaleTimeString()}]`)), color(Logger.prefixes.get(level) ?? 'DEBUG'), this.name ? `${this.name} >` : '>', diff --git a/src/common/it/utils.ts b/src/common/it/utils.ts index ab526f0..2652e35 100644 --- a/src/common/it/utils.ts +++ b/src/common/it/utils.ts @@ -1,6 +1,6 @@ -import { readdir } from 'node:fs/promises'; +import type fs from 'node:fs'; +import { type Dirent, readFile, readdir, stat, unlink } from 'node:fs'; import { basename, join } from 'node:path'; -import { setTimeout } from 'node:timers/promises'; import { EmbedColors, type ColorResolvable, type Logger, type ObjectToLower, type ObjectToSnake } from '..'; /** @@ -33,7 +33,7 @@ export function resolveColor(color: ColorResolvable): number { * @returns A Promise that resolves after the specified time with the provided result. */ export function delay(time: number, result?: T): Promise { - return setTimeout(time, result); + return new Promise(r => setTimeout(r, time, result)); } /** @@ -115,7 +115,7 @@ export class BaseHandler { protected async getFiles(dir: string) { const files: string[] = []; - for (const i of await readdir(dir, { withFileTypes: true })) { + for (const i of await promisesReaddir(dir)) { if (i.isDirectory()) { files.push(...(await this.getFiles(join(dir, i.name)))); } else { @@ -268,3 +268,54 @@ export function fakePromise>( then: callback => callback(value as Awaited), }; } + +export function lazyLoadPackage(mod: string): T | undefined { + try { + return require(mod); + } catch (e) { + console.log(`Cannot import ${mod}`); + return; + } +} + +export function isCloudfareWorker() { + //@ts-expect-error + return process.platform === 'browser'; +} + +//cloudfare support +export function promisesReadFile(file: string) { + return new Promise((res, rej) => + readFile(file, (err, result) => { + if (err) return rej(err); + res(result); + }), + ); +} + +export function promisesUnlink(file: string) { + return new Promise((res, rej) => + unlink(file, err => { + if (err) return rej(err); + res(); + }), + ); +} + +export function promisesStat(file: string) { + return new Promise((res, rej) => + stat(file, (err, result) => { + if (err) return rej(err); + res(result); + }), + ); +} + +export function promisesReaddir(file: string) { + return new Promise((res, rej) => + readdir(file, { withFileTypes: true }, (err, result) => { + if (err) return rej(err); + res(result); + }), + ); +} diff --git a/src/components/componentcontext.ts b/src/components/componentcontext.ts index 39a6f1c..656d3b5 100644 --- a/src/components/componentcontext.ts +++ b/src/components/componentcontext.ts @@ -15,7 +15,7 @@ import type { WebhookMessage, } from '..'; import type { ExtendContext, UsingClient } from '../commands'; -import { BaseContext } from '../commands/basecontex'; +import { BaseContext } from '../commands/basecontext'; import type { ComponentInteractionMessageUpdate, InteractionCreateBodyRequest, diff --git a/src/components/handler.ts b/src/components/handler.ts index 8ad0adf..6b83d91 100644 --- a/src/components/handler.ts +++ b/src/components/handler.ts @@ -145,10 +145,12 @@ export class ComponentHandler extends BaseHandler { this.deleteValue(id, 'messageDelete'); } - async load(componentsDir: string) { - const paths = await this.loadFilesK<{ new (): ModalCommand | ComponentCommand }>( - await this.getFiles(componentsDir), - ); + async load(componentsDir: string, instances?: { new (): ModalCommand | ComponentCommand }[]) { + const paths = + instances?.map(x => { + const i = new x(); + return { file: x, path: i.__filePath ?? '*' }; + }) ?? (await this.loadFilesK<{ new (): ModalCommand | ComponentCommand }>(await this.getFiles(componentsDir))); for (let i = 0; i < paths.length; i++) { let component; diff --git a/src/events/handler.ts b/src/events/handler.ts index 6db88a2..ed00f3b 100644 --- a/src/events/handler.ts +++ b/src/events/handler.ts @@ -20,8 +20,8 @@ export class EventHandler extends BaseHandler { values: Partial> = {}; - async load(eventsDir: string) { - for (const i of await this.loadFilesK(await this.getFiles(eventsDir))) { + async load(eventsDir: string, instances?: { file: ClientEvent; path: string }[]) { + for (const i of instances ?? (await this.loadFilesK(await this.getFiles(eventsDir)))) { const instance = this.callback(i.file); if (!instance) continue; if (typeof instance?.run !== 'function') { diff --git a/src/index.ts b/src/index.ts index 15e08c7..fdbc1b3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,13 +1,15 @@ import { GatewayIntentBits } from 'discord-api-types/gateway/v10'; -import type { - BaseClientOptions, - InternalRuntimeConfig, - InternalRuntimeConfigHTTP, - RuntimeConfig, - RuntimeConfigHTTP, +import { + BaseClient, + type BaseClientOptions, + type InternalRuntimeConfig, + type InternalRuntimeConfigHTTP, + type RuntimeConfig, + type RuntimeConfigHTTP, } from './client/base'; import type { ClientNameEvents, EventContext } from './events'; -export { Logger, PermissionStrings, Watcher } from './common'; +import { isCloudfareWorker } from './common'; +export { Logger, PermissionStrings } from './common'; // export { Collection, LimitedCollection } from './collection'; // @@ -80,10 +82,12 @@ export const config = { * @returns The internal runtime configuration for HTTP. */ http(data: RuntimeConfigHTTP) { - return { + const obj = { port: 8080, ...data, } as InternalRuntimeConfigHTTP; + if (isCloudfareWorker()) BaseClient.seyfertConfig = obj; + return obj; }, }; diff --git a/src/langs/handler.ts b/src/langs/handler.ts index 72c0d88..450aba8 100644 --- a/src/langs/handler.ts +++ b/src/langs/handler.ts @@ -36,8 +36,8 @@ export class LangsHandler extends BaseHandler { return LangRouter(locale, this.defaultLang ?? locale, this.values)(); } - async load(dir: string) { - const files = await this.loadFilesK>(await this.getFiles(dir)); + async load(dir: string, instances?: { name: string; file: Record }[]) { + const files = instances ?? (await this.loadFilesK>(await this.getFiles(dir))); for (const i of files) { const locale = i.name.split('.').slice(0, -1).join('.'); const result = this.callback(locale, i.file); diff --git a/src/websocket/discord/sharder.ts b/src/websocket/discord/sharder.ts index 7d5f0ba..94ad4ed 100644 --- a/src/websocket/discord/sharder.ts +++ b/src/websocket/discord/sharder.ts @@ -4,11 +4,11 @@ import { type GatewayUpdatePresence, type GatewayVoiceStateUpdate, } from 'discord-api-types/v10'; -import { parentPort, workerData } from 'node:worker_threads'; import { LogLevels, Logger, MergeOptions, + lazyLoadPackage, toSnakeCase, type ObjectToLower, type WatcherSendToShard, @@ -17,7 +17,10 @@ import { ShardManagerDefaults } from '../constants'; import { DynamicBucket } from '../structures'; import { ConnectQueue } from '../structures/timeout'; import { Shard } from './shard.js'; -import type { ShardManagerOptions } from './shared'; +import type { ShardManagerOptions, WorkerData } from './shared'; + +let parentPort: import('node:worker_threads').MessagePort; +let workerData: WorkerData; export class ShardManager extends Map { connectQueue: ConnectQueue; @@ -36,6 +39,13 @@ export class ShardManager extends Map { logLevel: LogLevels.Debug, }); } + + const worker_threads = lazyLoadPackage('node:worker_threads'); + + if (worker_threads) { + workerData = worker_threads.workerData; + if (worker_threads.parentPort) parentPort = worker_threads.parentPort; + } } get totalShards() { diff --git a/src/websocket/discord/shared.ts b/src/websocket/discord/shared.ts index 3aa915f..f3b1a00 100644 --- a/src/websocket/discord/shared.ts +++ b/src/websocket/discord/shared.ts @@ -120,4 +120,5 @@ export interface WorkerData { workerId: number; debug: boolean; workerProxy: boolean; + __USING_WATCHER__?: boolean; } diff --git a/src/websocket/discord/workermanager.ts b/src/websocket/discord/workermanager.ts index 70bb39a..23b1ec0 100644 --- a/src/websocket/discord/workermanager.ts +++ b/src/websocket/discord/workermanager.ts @@ -1,11 +1,10 @@ import type { GatewayPresenceUpdateData, GatewaySendPayload } from 'discord-api-types/v10'; import cluster, { type Worker as ClusterWorker } from 'node:cluster'; import { randomUUID } from 'node:crypto'; -import { Worker as ThreadWorker } from 'node:worker_threads'; import { ApiHandler, Logger, Router } from '../..'; import { MemoryAdapter, type Adapter } from '../../cache'; import { BaseClient, type InternalRuntimeConfig } from '../../client/base'; -import { MergeOptions, type MakePartial } from '../../common'; +import { MergeOptions, lazyLoadPackage, type MakePartial } from '../../common'; import { WorkerManagerDefaults } from '../constants'; import { DynamicBucket } from '../structures'; import { ConnectQueue } from '../structures/timeout'; @@ -14,7 +13,10 @@ import { PresenceUpdateHandler } from './events/presenceUpdate'; import type { ShardOptions, WorkerData, WorkerManagerOptions } from './shared'; import type { WorkerInfo, WorkerMessage, WorkerShardInfo, WorkerStart } from './worker'; -export class WorkerManager extends Map { +export class WorkerManager extends Map< + number, + (ClusterWorker | import('node:worker_threads').Worker) & { ready?: boolean } +> { options!: Required; debugger?: Logger; connectQueue!: ConnectQueue; @@ -124,7 +126,7 @@ export class WorkerManager extends Map('node:worker_threads'); + if (!worker_threads) throw new Error('Cannot create worker without worker_threads.'); const env: Record = { SEYFERT_SPAWNING: 'true', }; @@ -170,7 +174,7 @@ export class WorkerManager extends Map this.handleWorkerMessage(data));