mirror of
https://github.com/tiramisulabs/seyfert.git
synced 2025-07-04 05:56:09 +00:00
feat: custom events
This commit is contained in:
parent
625e400c20
commit
349628a587
@ -1,22 +1,25 @@
|
|||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
import type { Awaitable, CamelCase, SnakeCase } from '../common';
|
import type { Awaitable, CamelCase } from '../common';
|
||||||
import type { ClientNameEvents, GatewayEvents } from '../events';
|
import type { CallbackEventHandler, CustomEventsKeys, GatewayEvents } from '../events';
|
||||||
import type { ClientEvents } from '../events/hooks';
|
|
||||||
import { error } from 'node:console';
|
import { error } from 'node:console';
|
||||||
|
|
||||||
type SnakeCaseClientNameEvents = Uppercase<SnakeCase<ClientNameEvents>>;
|
export type AllClientEvents = CustomEventsKeys | GatewayEvents;
|
||||||
|
export type ParseClientEventName<T extends AllClientEvents> = T extends CustomEventsKeys ? T : CamelCase<T>;
|
||||||
|
|
||||||
type RunData<T extends SnakeCaseClientNameEvents> = {
|
type RunData<T extends AllClientEvents> = {
|
||||||
options: {
|
options: {
|
||||||
event: T;
|
event: T;
|
||||||
idle?: number;
|
idle?: number;
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
onStop?: (reason: string) => unknown;
|
onStop?: (reason: string) => unknown;
|
||||||
onStopError?: (reason: string, error: unknown) => unknown;
|
onStopError?: (reason: string, error: unknown) => unknown;
|
||||||
filter: (arg: Awaited<ClientEvents[CamelCase<Lowercase<T>>]>) => Awaitable<boolean>;
|
filter: (arg: Awaited<Parameters<CallbackEventHandler[ParseClientEventName<T>]>[0]>) => Awaitable<boolean>;
|
||||||
run: (arg: Awaited<ClientEvents[CamelCase<Lowercase<T>>]>, stop: (reason?: string) => void) => unknown;
|
run: (
|
||||||
|
arg: Awaited<Parameters<CallbackEventHandler[ParseClientEventName<T>]>[0]>,
|
||||||
|
stop: (reason?: string) => void,
|
||||||
|
) => unknown;
|
||||||
onRunError?: (
|
onRunError?: (
|
||||||
arg: Awaited<ClientEvents[CamelCase<Lowercase<T>>]>,
|
arg: Awaited<Parameters<CallbackEventHandler[ParseClientEventName<T>]>[0]>,
|
||||||
error: unknown,
|
error: unknown,
|
||||||
stop: (reason?: string) => void,
|
stop: (reason?: string) => void,
|
||||||
) => unknown;
|
) => unknown;
|
||||||
@ -27,9 +30,9 @@ type RunData<T extends SnakeCaseClientNameEvents> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class Collectors {
|
export class Collectors {
|
||||||
readonly values = new Map<SnakeCaseClientNameEvents, RunData<any>[]>();
|
readonly values = new Map<AllClientEvents, RunData<any>[]>();
|
||||||
|
|
||||||
private generateRandomUUID(name: SnakeCaseClientNameEvents) {
|
private generateRandomUUID(name: AllClientEvents) {
|
||||||
const collectors = this.values.get(name);
|
const collectors = this.values.get(name);
|
||||||
if (!collectors) return '*';
|
if (!collectors) return '*';
|
||||||
|
|
||||||
@ -42,7 +45,7 @@ export class Collectors {
|
|||||||
return nonce;
|
return nonce;
|
||||||
}
|
}
|
||||||
|
|
||||||
create<T extends SnakeCaseClientNameEvents>(options: RunData<T>['options']) {
|
create<T extends AllClientEvents>(options: RunData<T>['options']) {
|
||||||
const nonce = this.generateRandomUUID(options.event);
|
const nonce = this.generateRandomUUID(options.event);
|
||||||
|
|
||||||
if (!this.values.has(options.event)) {
|
if (!this.values.has(options.event)) {
|
||||||
@ -71,7 +74,7 @@ export class Collectors {
|
|||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async delete(name: SnakeCaseClientNameEvents, nonce: string, reason = 'unknown') {
|
private async delete(name: AllClientEvents, nonce: string, reason = 'unknown') {
|
||||||
const collectors = this.values.get(name);
|
const collectors = this.values.get(name);
|
||||||
|
|
||||||
if (!collectors?.length) {
|
if (!collectors?.length) {
|
||||||
@ -93,20 +96,23 @@ export class Collectors {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**@internal */
|
/**@internal */
|
||||||
async run<T extends GatewayEvents>(name: T, data: Awaited<ClientEvents[CamelCase<Lowercase<T>>]>) {
|
async run<T extends AllClientEvents>(
|
||||||
|
name: T,
|
||||||
|
data: Awaited<Parameters<CallbackEventHandler[ParseClientEventName<T>]>[0]>,
|
||||||
|
) {
|
||||||
const collectors = this.values.get(name);
|
const collectors = this.values.get(name);
|
||||||
if (!collectors) return;
|
if (!collectors) return;
|
||||||
|
|
||||||
for (const i of collectors) {
|
for (const i of collectors) {
|
||||||
if (await i.options.filter(data)) {
|
if (await i.options.filter(data as never)) {
|
||||||
i.idle?.refresh();
|
i.idle?.refresh();
|
||||||
const stop = (reason = 'unknown') => {
|
const stop = (reason = 'unknown') => {
|
||||||
return this.delete(i.options.event, i.nonce, reason);
|
return this.delete(i.options.event, i.nonce, reason);
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
await i.options.run(data, stop);
|
await i.options.run(data as never, stop);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await i.options.onRunError?.(data, e, stop);
|
await i.options.onRunError?.(data as never, e, stop);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import type { UsingClient } from '../commands';
|
import type { UsingClient } from '../commands';
|
||||||
import type { ClientEvents } from './hooks';
|
import type { ClientEvents } from './hooks';
|
||||||
|
|
||||||
export interface DeclareEventsOptions {
|
export interface CustomEvents {}
|
||||||
name: `${keyof ClientEvents}`;
|
|
||||||
once?: boolean;
|
|
||||||
}
|
|
||||||
export type ClientNameEvents = Extract<keyof ClientEvents, string>;
|
export type ClientNameEvents = Extract<keyof ClientEvents, string>;
|
||||||
|
export type CustomEventsKeys = Extract<keyof CustomEvents, string>;
|
||||||
|
|
||||||
export interface ClientDataEvent {
|
export interface ClientDataEvent {
|
||||||
name: ClientNameEvents;
|
name: ClientNameEvents;
|
||||||
@ -14,6 +12,8 @@ export interface ClientDataEvent {
|
|||||||
|
|
||||||
export type CallbackEventHandler = {
|
export type CallbackEventHandler = {
|
||||||
[K in keyof ClientEvents]: (...data: [Awaited<ClientEvents[K]>, UsingClient, number]) => unknown;
|
[K in keyof ClientEvents]: (...data: [Awaited<ClientEvents[K]>, UsingClient, number]) => unknown;
|
||||||
|
} & {
|
||||||
|
[K in keyof CustomEvents]: (...data: [Parameters<CustomEvents[K]>, UsingClient, number]) => unknown;
|
||||||
};
|
};
|
||||||
export type EventContext<T extends { data: { name: ClientNameEvents } }> = Parameters<
|
export type EventContext<T extends { data: { name: ClientNameEvents } }> = Parameters<
|
||||||
CallbackEventHandler[T['data']['name']]
|
CallbackEventHandler[T['data']['name']]
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
import type {
|
import {
|
||||||
GatewayDispatchPayload,
|
type GatewayDispatchPayload,
|
||||||
GatewayMessageCreateDispatch,
|
type GatewayMessageCreateDispatch,
|
||||||
GatewayMessageDeleteBulkDispatch,
|
type GatewayMessageDeleteBulkDispatch,
|
||||||
GatewayMessageDeleteDispatch,
|
type GatewayMessageDeleteDispatch,
|
||||||
|
GatewayDispatchEvents,
|
||||||
} from 'discord-api-types/v10';
|
} from 'discord-api-types/v10';
|
||||||
import type { Client, WorkerClient } from '../client';
|
import type { Client, WorkerClient } from '../client';
|
||||||
import { BaseHandler, ReplaceRegex, magicImport, type MakeRequired, type SnakeCase } from '../common';
|
import { BaseHandler, ReplaceRegex, magicImport, type MakeRequired, type SnakeCase } from '../common';
|
||||||
import type { ClientEvents } from '../events/hooks';
|
import type { ClientEvents } from '../events/hooks';
|
||||||
import * as RawEvents from '../events/hooks';
|
import * as RawEvents from '../events/hooks';
|
||||||
import type { ClientEvent, ClientNameEvents } from './event';
|
import type { ClientEvent, CustomEvents, CustomEventsKeys, ClientNameEvents } from './event';
|
||||||
|
|
||||||
export type EventValue = MakeRequired<ClientEvent, '__filePath'> & { fired?: boolean };
|
export type EventValue = MakeRequired<ClientEvent, '__filePath'> & { fired?: boolean };
|
||||||
|
|
||||||
@ -19,12 +20,17 @@ export class EventHandler extends BaseHandler {
|
|||||||
super(client.logger);
|
super(client.logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
onFail = (event: GatewayEvents, err: unknown) => this.logger.warn('<Client>.events.onFail', err, event);
|
onFail = (event: GatewayEvents | CustomEventsKeys, err: unknown) =>
|
||||||
|
this.logger.warn('<Client>.events.onFail', err, event);
|
||||||
protected filter = (path: string) => path.endsWith('.js') || (!path.endsWith('.d.ts') && path.endsWith('.ts'));
|
protected filter = (path: string) => path.endsWith('.js') || (!path.endsWith('.d.ts') && path.endsWith('.ts'));
|
||||||
|
|
||||||
values: Partial<Record<GatewayEvents, EventValue>> = {};
|
values: Partial<Record<GatewayEvents | CustomEventsKeys, EventValue>> = {};
|
||||||
|
|
||||||
async load(eventsDir: string, instances?: { file: ClientEvent; path: string }[]) {
|
async load(eventsDir: string, instances?: { file: ClientEvent; path: string }[]) {
|
||||||
|
const discordEvents = Object.values(GatewayDispatchEvents).map(x =>
|
||||||
|
ReplaceRegex.camel(x.toLowerCase()),
|
||||||
|
) as ClientNameEvents[];
|
||||||
|
|
||||||
for (const i of instances ?? (await this.loadFilesK<ClientEvent>(await this.getFiles(eventsDir)))) {
|
for (const i of instances ?? (await this.loadFilesK<ClientEvent>(await this.getFiles(eventsDir)))) {
|
||||||
const instance = this.callback(i.file);
|
const instance = this.callback(i.file);
|
||||||
if (!instance) continue;
|
if (!instance) continue;
|
||||||
@ -36,7 +42,11 @@ export class EventHandler extends BaseHandler {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
instance.__filePath = i.path;
|
instance.__filePath = i.path;
|
||||||
this.values[ReplaceRegex.snake(instance.data.name).toUpperCase() as GatewayEvents] = instance as EventValue;
|
this.values[
|
||||||
|
discordEvents.includes(instance.data.name)
|
||||||
|
? (ReplaceRegex.snake(instance.data.name).toUpperCase() as GatewayEvents)
|
||||||
|
: (instance.data.name as CustomEventsKeys)
|
||||||
|
] = instance as EventValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,27 +111,43 @@ export class EventHandler extends BaseHandler {
|
|||||||
t: name,
|
t: name,
|
||||||
d: packet,
|
d: packet,
|
||||||
} as GatewayDispatchPayload);
|
} as GatewayDispatchPayload);
|
||||||
await Event.run(...[hook, client, shardId]);
|
await Event.run(hook, client, shardId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await this.onFail(name, e);
|
await this.onFail(name, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async reload(name: ClientNameEvents) {
|
async runCustom<T extends CustomEventsKeys>(name: T, ...args: Parameters<CustomEvents[T]>) {
|
||||||
const eventName = ReplaceRegex.snake(name).toUpperCase() as GatewayEvents;
|
const Event = this.values[name];
|
||||||
const event = this.values[eventName];
|
if (!Event) {
|
||||||
|
return this.client.collectors.run(name, args as never);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (Event.data.once && Event.fired) {
|
||||||
|
return this.client.collectors.run(name, args as never);
|
||||||
|
}
|
||||||
|
Event.fired = true;
|
||||||
|
this.logger.debug(`executed a custom event [${name}]`, Event.data.once ? 'once' : '');
|
||||||
|
await Promise.all([Event.run(args, this.client), this.client.collectors.run(name, args as never)]);
|
||||||
|
} catch (e) {
|
||||||
|
await this.onFail(name, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async reload(name: GatewayEvents | CustomEventsKeys) {
|
||||||
|
const event = this.values[name];
|
||||||
if (!event?.__filePath) return null;
|
if (!event?.__filePath) return null;
|
||||||
delete require.cache[event.__filePath];
|
delete require.cache[event.__filePath];
|
||||||
const imported = await magicImport(event.__filePath).then(x => x.default ?? x);
|
const imported = await magicImport(event.__filePath).then(x => x.default ?? x);
|
||||||
imported.__filePath = event.__filePath;
|
imported.__filePath = event.__filePath;
|
||||||
this.values[eventName] = imported;
|
this.values[name] = imported;
|
||||||
return imported;
|
return imported;
|
||||||
}
|
}
|
||||||
|
|
||||||
async reloadAll(stopIfFail = true) {
|
async reloadAll(stopIfFail = true) {
|
||||||
for (const i in this.values) {
|
for (const i in this.values) {
|
||||||
try {
|
try {
|
||||||
await this.reload(ReplaceRegex.camel(i) as ClientNameEvents);
|
await this.reload(i as GatewayEvents | CustomEventsKeys);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (stopIfFail) {
|
if (stopIfFail) {
|
||||||
throw e;
|
throw e;
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
type RuntimeConfig,
|
type RuntimeConfig,
|
||||||
type RuntimeConfigHTTP,
|
type RuntimeConfigHTTP,
|
||||||
} from './client/base';
|
} from './client/base';
|
||||||
import type { ClientNameEvents, EventContext } from './events';
|
import type { CustomEventsKeys, ClientNameEvents, EventContext } from './events';
|
||||||
import { isCloudfareWorker } from './common';
|
import { isCloudfareWorker } from './common';
|
||||||
export { Logger, PermissionStrings, Watcher } from './common';
|
export { Logger, PermissionStrings, Watcher } from './common';
|
||||||
//
|
//
|
||||||
@ -46,7 +46,7 @@ export function throwError(msg: string): never {
|
|||||||
* }
|
* }
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function createEvent<E extends ClientNameEvents>(data: {
|
export function createEvent<E extends ClientNameEvents | CustomEventsKeys>(data: {
|
||||||
data: { name: E; once?: boolean };
|
data: { name: E; once?: boolean };
|
||||||
run: (...args: EventContext<{ data: { name: E } }>) => any;
|
run: (...args: EventContext<{ data: { name: E } }>) => any;
|
||||||
}) {
|
}) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user