mirror of
https://github.com/tiramisulabs/seyfert.git
synced 2025-07-02 04:56:07 +00:00
feat: optimize code & implement onRunError and onStopError in collectors & use Promise.all
This commit is contained in:
parent
3850ef38a6
commit
d2157e7324
140
src/cache/resources/members.ts
vendored
140
src/cache/resources/members.ts
vendored
@ -1,70 +1,70 @@
|
||||
import type { APIGuildMember } from 'discord-api-types/v10';
|
||||
import type { ReturnCache } from '../..';
|
||||
import { fakePromise } from '../../common';
|
||||
import { GuildMember } from '../../structures';
|
||||
import { GuildBasedResource } from './default/guild-based';
|
||||
export class Members extends GuildBasedResource {
|
||||
namespace = 'member';
|
||||
|
||||
//@ts-expect-error
|
||||
filter(data: APIGuildMember, id: string, guild_id: string) {
|
||||
return true;
|
||||
}
|
||||
|
||||
override parse(data: any, key: string, guild_id: string) {
|
||||
const { user, ...rest } = super.parse(data, data.user?.id ?? key, guild_id);
|
||||
return rest;
|
||||
}
|
||||
|
||||
override get(id: string, guild: string): ReturnCache<GuildMember | undefined> {
|
||||
return fakePromise(super.get(id, guild)).then(rawMember =>
|
||||
fakePromise(this.client.cache.users?.get(id)).then(user =>
|
||||
rawMember && user ? new GuildMember(this.client, rawMember, user, guild) : undefined,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
override bulk(ids: string[], guild: string): ReturnCache<GuildMember[]> {
|
||||
return fakePromise(super.bulk(ids, guild)).then(members =>
|
||||
fakePromise(this.client.cache.users?.bulk(ids) ?? []).then(
|
||||
users =>
|
||||
members
|
||||
.map(rawMember => {
|
||||
const user = users.find(x => x.id === rawMember.id);
|
||||
return user ? new GuildMember(this.client, rawMember, user, guild) : undefined;
|
||||
})
|
||||
.filter(Boolean) as GuildMember[],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
override values(guild: string): ReturnCache<GuildMember[]> {
|
||||
return fakePromise(super.values(guild)).then(members =>
|
||||
fakePromise(this.client.cache.users?.values() ?? []).then(
|
||||
users =>
|
||||
members
|
||||
.map(rawMember => {
|
||||
const user = users.find(x => x.id === rawMember.id);
|
||||
return user ? new GuildMember(this.client, rawMember, user, rawMember.guild_id) : undefined;
|
||||
})
|
||||
.filter(Boolean) as GuildMember[],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
override async set(memberId: string, guildId: string, data: any): Promise<void>;
|
||||
override async set(memberId_dataArray: [string, any][], guildId: string): Promise<void>;
|
||||
override async set(__keys: string | [string, any][], guild: string, data?: any) {
|
||||
const keys: [string, any][] = Array.isArray(__keys) ? __keys : [[__keys, data]];
|
||||
const bulkData: (['members', any, string, string] | ['users', any, string])[] = [];
|
||||
|
||||
for (const [id, value] of keys) {
|
||||
if (value.user) {
|
||||
bulkData.push(['members', value, id, guild]);
|
||||
bulkData.push(['users', value.user, id]);
|
||||
}
|
||||
}
|
||||
|
||||
await this.cache.bulkSet(bulkData);
|
||||
}
|
||||
}
|
||||
import type { APIGuildMember } from 'discord-api-types/v10';
|
||||
import type { ReturnCache } from '../..';
|
||||
import { fakePromise } from '../../common';
|
||||
import { GuildMember } from '../../structures';
|
||||
import { GuildBasedResource } from './default/guild-based';
|
||||
export class Members extends GuildBasedResource {
|
||||
namespace = 'member';
|
||||
|
||||
//@ts-expect-error
|
||||
filter(data: APIGuildMember, id: string, guild_id: string) {
|
||||
return true;
|
||||
}
|
||||
|
||||
override parse(data: any, key: string, guild_id: string) {
|
||||
const { user, ...rest } = super.parse(data, data.user?.id ?? key, guild_id);
|
||||
return rest;
|
||||
}
|
||||
|
||||
override get(id: string, guild: string): ReturnCache<GuildMember | undefined> {
|
||||
return fakePromise(super.get(id, guild)).then(rawMember =>
|
||||
fakePromise(this.client.cache.users?.get(id)).then(user =>
|
||||
rawMember && user ? new GuildMember(this.client, rawMember, user, guild) : undefined,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
override bulk(ids: string[], guild: string): ReturnCache<GuildMember[]> {
|
||||
return fakePromise(super.bulk(ids, guild)).then(members =>
|
||||
fakePromise(this.client.cache.users?.bulk(ids)).then(
|
||||
users =>
|
||||
members
|
||||
.map(rawMember => {
|
||||
const user = users?.find(x => x.id === rawMember.id);
|
||||
return user ? new GuildMember(this.client, rawMember, user, guild) : undefined;
|
||||
})
|
||||
.filter(Boolean) as GuildMember[],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
override values(guild: string): ReturnCache<GuildMember[]> {
|
||||
return fakePromise(super.values(guild)).then(members =>
|
||||
fakePromise(this.client.cache.users?.values()).then(
|
||||
users =>
|
||||
members
|
||||
.map(rawMember => {
|
||||
const user = users?.find(x => x.id === rawMember.id);
|
||||
return user ? new GuildMember(this.client, rawMember, user, rawMember.guild_id) : undefined;
|
||||
})
|
||||
.filter(Boolean) as GuildMember[],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
override async set(memberId: string, guildId: string, data: any): Promise<void>;
|
||||
override async set(memberId_dataArray: [string, any][], guildId: string): Promise<void>;
|
||||
override async set(__keys: string | [string, any][], guild: string, data?: any) {
|
||||
const keys: [string, any][] = Array.isArray(__keys) ? __keys : [[__keys, data]];
|
||||
const bulkData: (['members', any, string, string] | ['users', any, string])[] = [];
|
||||
|
||||
for (const [id, value] of keys) {
|
||||
if (value.user) {
|
||||
bulkData.push(['members', value, id, guild]);
|
||||
bulkData.push(['users', value.user, id]);
|
||||
}
|
||||
}
|
||||
|
||||
await this.cache.bulkSet(bulkData);
|
||||
}
|
||||
}
|
||||
|
@ -1,99 +1,115 @@
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import type { Awaitable, CamelCase, SnakeCase } from '../common';
|
||||
import type { ClientNameEvents, GatewayEvents } from '../events';
|
||||
import type { ClientEvents } from '../events/hooks';
|
||||
|
||||
type SnakeCaseClientNameEvents = Uppercase<SnakeCase<ClientNameEvents>>;
|
||||
|
||||
type RunData<T extends SnakeCaseClientNameEvents> = {
|
||||
options: {
|
||||
event: T;
|
||||
idle?: number;
|
||||
timeout?: number;
|
||||
onStop?: (reason: string) => unknown;
|
||||
filter: (arg: Awaited<ClientEvents[CamelCase<Lowercase<T>>]>) => Awaitable<boolean>;
|
||||
run: (arg: Awaited<ClientEvents[CamelCase<Lowercase<T>>]>, stop: (reason?: string) => void) => unknown;
|
||||
};
|
||||
idle?: NodeJS.Timeout;
|
||||
timeout?: NodeJS.Timeout;
|
||||
nonce: string;
|
||||
};
|
||||
|
||||
export class Collectors {
|
||||
readonly values = new Map<SnakeCaseClientNameEvents, RunData<any>[]>();
|
||||
|
||||
private generateRandomUUID(name: SnakeCaseClientNameEvents) {
|
||||
const collectors = this.values.get(name);
|
||||
if (!collectors) return '*';
|
||||
|
||||
let nonce = randomUUID();
|
||||
|
||||
while (collectors.find(x => x.nonce === nonce)) {
|
||||
nonce = randomUUID();
|
||||
}
|
||||
|
||||
return nonce;
|
||||
}
|
||||
|
||||
create<T extends SnakeCaseClientNameEvents>(options: RunData<T>['options']) {
|
||||
if (!this.values.has(options.event)) {
|
||||
this.values.set(options.event, []);
|
||||
}
|
||||
|
||||
const nonce = this.generateRandomUUID(options.event);
|
||||
|
||||
this.values.get(options.event)!.push({
|
||||
options: {
|
||||
...options,
|
||||
name: options.event,
|
||||
} as RunData<any>['options'],
|
||||
idle:
|
||||
options.idle && options.idle > 0
|
||||
? setTimeout(() => {
|
||||
return this.delete(options.event, nonce, 'idle');
|
||||
}, options.idle)
|
||||
: undefined,
|
||||
timeout:
|
||||
options.timeout && options.timeout > 0
|
||||
? setTimeout(() => {
|
||||
return this.delete(options.event, nonce, 'timeout');
|
||||
}, options.timeout)
|
||||
: undefined,
|
||||
nonce,
|
||||
});
|
||||
return options;
|
||||
}
|
||||
|
||||
private delete(name: SnakeCaseClientNameEvents, nonce: string, reason = 'unknown') {
|
||||
const collectors = this.values.get(name);
|
||||
|
||||
if (!collectors?.length) {
|
||||
if (collectors) this.values.delete(name);
|
||||
return;
|
||||
}
|
||||
|
||||
const index = collectors.findIndex(x => x.nonce === nonce);
|
||||
if (index === -1) return;
|
||||
const collector = collectors[index];
|
||||
clearTimeout(collector.idle);
|
||||
clearTimeout(collector.timeout);
|
||||
collectors.splice(index, 1);
|
||||
return collector.options.onStop?.(reason);
|
||||
}
|
||||
|
||||
/**@internal */
|
||||
async run<T extends GatewayEvents>(name: T, data: Awaited<ClientEvents[CamelCase<Lowercase<T>>]>) {
|
||||
const collectors = this.values.get(name);
|
||||
if (!collectors) return;
|
||||
|
||||
for (const i of collectors) {
|
||||
if (await i.options.filter(data)) {
|
||||
i.idle?.refresh();
|
||||
await i.options.run(data, (reason = 'unknown') => {
|
||||
return this.delete(i.options.event, i.nonce, reason);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import type { Awaitable, CamelCase, SnakeCase } from '../common';
|
||||
import type { ClientNameEvents, GatewayEvents } from '../events';
|
||||
import type { ClientEvents } from '../events/hooks';
|
||||
import { error } from 'node:console';
|
||||
|
||||
type SnakeCaseClientNameEvents = Uppercase<SnakeCase<ClientNameEvents>>;
|
||||
|
||||
type RunData<T extends SnakeCaseClientNameEvents> = {
|
||||
options: {
|
||||
event: T;
|
||||
idle?: number;
|
||||
timeout?: number;
|
||||
onStop?: (reason: string) => unknown;
|
||||
onStopError?: (reason: string, error: unknown) => unknown;
|
||||
filter: (arg: Awaited<ClientEvents[CamelCase<Lowercase<T>>]>) => Awaitable<boolean>;
|
||||
run: (arg: Awaited<ClientEvents[CamelCase<Lowercase<T>>]>, stop: (reason?: string) => void) => unknown;
|
||||
onRunError?: (
|
||||
arg: Awaited<ClientEvents[CamelCase<Lowercase<T>>]>,
|
||||
error: unknown,
|
||||
stop: (reason?: string) => void,
|
||||
) => unknown;
|
||||
};
|
||||
idle?: NodeJS.Timeout;
|
||||
timeout?: NodeJS.Timeout;
|
||||
nonce: string;
|
||||
};
|
||||
|
||||
export class Collectors {
|
||||
readonly values = new Map<SnakeCaseClientNameEvents, RunData<any>[]>();
|
||||
|
||||
private generateRandomUUID(name: SnakeCaseClientNameEvents) {
|
||||
const collectors = this.values.get(name);
|
||||
if (!collectors) return '*';
|
||||
|
||||
let nonce = randomUUID();
|
||||
|
||||
while (collectors.find(x => x.nonce === nonce)) {
|
||||
nonce = randomUUID();
|
||||
}
|
||||
|
||||
return nonce;
|
||||
}
|
||||
|
||||
create<T extends SnakeCaseClientNameEvents>(options: RunData<T>['options']) {
|
||||
if (!this.values.has(options.event)) {
|
||||
this.values.set(options.event, []);
|
||||
}
|
||||
|
||||
const nonce = this.generateRandomUUID(options.event);
|
||||
|
||||
this.values.get(options.event)!.push({
|
||||
options: {
|
||||
...options,
|
||||
name: options.event,
|
||||
} as RunData<any>['options'],
|
||||
idle:
|
||||
options.idle && options.idle > 0
|
||||
? setTimeout(() => {
|
||||
return this.delete(options.event, nonce, 'idle');
|
||||
}, options.idle)
|
||||
: undefined,
|
||||
timeout:
|
||||
options.timeout && options.timeout > 0
|
||||
? setTimeout(() => {
|
||||
return this.delete(options.event, nonce, 'timeout');
|
||||
}, options.timeout)
|
||||
: undefined,
|
||||
nonce,
|
||||
});
|
||||
return options;
|
||||
}
|
||||
|
||||
private async delete(name: SnakeCaseClientNameEvents, nonce: string, reason = 'unknown') {
|
||||
const collectors = this.values.get(name);
|
||||
|
||||
if (!collectors?.length) {
|
||||
if (collectors) this.values.delete(name);
|
||||
return;
|
||||
}
|
||||
|
||||
const index = collectors.findIndex(x => x.nonce === nonce);
|
||||
if (index === -1) return;
|
||||
const collector = collectors[index];
|
||||
clearTimeout(collector.idle);
|
||||
clearTimeout(collector.timeout);
|
||||
collectors.splice(index, 1);
|
||||
try {
|
||||
await collector.options.onStop?.(reason);
|
||||
} catch (e) {
|
||||
await collector.options.onStopError?.(reason, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**@internal */
|
||||
async run<T extends GatewayEvents>(name: T, data: Awaited<ClientEvents[CamelCase<Lowercase<T>>]>) {
|
||||
const collectors = this.values.get(name);
|
||||
if (!collectors) return;
|
||||
|
||||
for (const i of collectors) {
|
||||
if (await i.options.filter(data)) {
|
||||
i.idle?.refresh();
|
||||
const stop = (reason = 'unknown') => {
|
||||
return this.delete(i.options.event, i.nonce, reason);
|
||||
};
|
||||
try {
|
||||
await i.options.run(data, stop);
|
||||
} catch (e) {
|
||||
await i.options.onRunError?.(data, e, stop);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,362 +1,361 @@
|
||||
import {
|
||||
ApplicationCommandOptionType,
|
||||
ApplicationCommandType,
|
||||
type ApplicationIntegrationType,
|
||||
type InteractionContextType,
|
||||
type APIApplicationCommandBasicOption,
|
||||
type APIApplicationCommandOption,
|
||||
type APIApplicationCommandSubcommandGroupOption,
|
||||
type LocaleString,
|
||||
} from 'discord-api-types/v10';
|
||||
import type {
|
||||
ComponentContext,
|
||||
MenuCommandContext,
|
||||
ModalContext,
|
||||
PermissionStrings,
|
||||
SeyfertNumberOption,
|
||||
SeyfertStringOption,
|
||||
} from '../..';
|
||||
import type { Attachment } from '../../builders';
|
||||
import { magicImport, type FlatObjectKeys } from '../../common';
|
||||
import type { AllChannels, AutocompleteInteraction, GuildRole, InteractionGuildMember, User } from '../../structures';
|
||||
import type { Groups, RegisteredMiddlewares } from '../decorators';
|
||||
import type { OptionResolver } from '../optionresolver';
|
||||
import type { CommandContext } from './chatcontext';
|
||||
import type {
|
||||
DefaultLocale,
|
||||
IgnoreCommand,
|
||||
OKFunction,
|
||||
OnOptionsReturnObject,
|
||||
PassFunction,
|
||||
StopFunction,
|
||||
UsingClient,
|
||||
} from './shared';
|
||||
|
||||
export interface ReturnOptionsTypes {
|
||||
1: never; // subcommand
|
||||
2: never; // subcommandgroup
|
||||
3: string;
|
||||
4: number; // integer
|
||||
5: boolean;
|
||||
6: InteractionGuildMember | User;
|
||||
7: AllChannels;
|
||||
8: GuildRole;
|
||||
9: GuildRole | AllChannels | User;
|
||||
10: number; // number
|
||||
11: Attachment;
|
||||
}
|
||||
|
||||
type Wrap<N extends ApplicationCommandOptionType> = N extends
|
||||
| ApplicationCommandOptionType.Subcommand
|
||||
| ApplicationCommandOptionType.SubcommandGroup
|
||||
? never
|
||||
: {
|
||||
required?: boolean;
|
||||
value?(
|
||||
data: { context: CommandContext; value: ReturnOptionsTypes[N] },
|
||||
ok: OKFunction<any>,
|
||||
fail: StopFunction,
|
||||
): void;
|
||||
} & {
|
||||
description: string;
|
||||
description_localizations?: APIApplicationCommandBasicOption['description_localizations'];
|
||||
name_localizations?: APIApplicationCommandBasicOption['name_localizations'];
|
||||
locales?: {
|
||||
name?: FlatObjectKeys<DefaultLocale>;
|
||||
description?: FlatObjectKeys<DefaultLocale>;
|
||||
};
|
||||
};
|
||||
|
||||
export type __TypeWrapper<T extends ApplicationCommandOptionType> = Wrap<T>;
|
||||
|
||||
export type __TypesWrapper = {
|
||||
[P in keyof typeof ApplicationCommandOptionType]: `${(typeof ApplicationCommandOptionType)[P]}` extends `${infer D extends
|
||||
number}`
|
||||
? Wrap<D>
|
||||
: never;
|
||||
};
|
||||
|
||||
export type AutocompleteCallback = (interaction: AutocompleteInteraction) => any;
|
||||
export type OnAutocompleteErrorCallback = (interaction: AutocompleteInteraction, error: unknown) => any;
|
||||
export type CommandBaseOption = __TypesWrapper[keyof __TypesWrapper];
|
||||
export type CommandBaseAutocompleteOption = __TypesWrapper[keyof __TypesWrapper] & {
|
||||
autocomplete: AutocompleteCallback;
|
||||
onAutocompleteError?: OnAutocompleteErrorCallback;
|
||||
};
|
||||
export type CommandAutocompleteOption = CommandBaseAutocompleteOption & { name: string };
|
||||
export type __CommandOption = CommandBaseOption; //| CommandBaseAutocompleteOption;
|
||||
export type CommandOption = __CommandOption & { name: string };
|
||||
export type OptionsRecord = Record<string, __CommandOption & { type: ApplicationCommandOptionType }>;
|
||||
|
||||
type KeysWithoutRequired<T extends OptionsRecord> = {
|
||||
[K in keyof T]-?: T[K]['required'] extends true ? never : K;
|
||||
}[keyof T];
|
||||
|
||||
type ContextOptionsAux<T extends OptionsRecord> = {
|
||||
[K in Exclude<keyof T, KeysWithoutRequired<T>>]: T[K]['value'] extends (...args: any) => any
|
||||
? Parameters<Parameters<T[K]['value']>[1]>[0]
|
||||
: T[K] extends SeyfertStringOption | SeyfertNumberOption
|
||||
? T[K]['choices'] extends NonNullable<SeyfertStringOption['choices'] | SeyfertNumberOption['choices']>
|
||||
? T[K]['choices'][number]['value']
|
||||
: ReturnOptionsTypes[T[K]['type']]
|
||||
: ReturnOptionsTypes[T[K]['type']];
|
||||
} & {
|
||||
[K in KeysWithoutRequired<T>]?: T[K]['value'] extends (...args: any) => any
|
||||
? Parameters<Parameters<T[K]['value']>[1]>[0]
|
||||
: T[K] extends SeyfertStringOption | SeyfertNumberOption
|
||||
? T[K]['choices'] extends NonNullable<SeyfertStringOption['choices'] | SeyfertNumberOption['choices']>
|
||||
? T[K]['choices'][number]['value']
|
||||
: ReturnOptionsTypes[T[K]['type']]
|
||||
: ReturnOptionsTypes[T[K]['type']];
|
||||
};
|
||||
|
||||
export type ContextOptions<T extends OptionsRecord> = ContextOptionsAux<T>;
|
||||
|
||||
export class BaseCommand {
|
||||
middlewares: (keyof RegisteredMiddlewares)[] = [];
|
||||
|
||||
__filePath?: string;
|
||||
__t?: { name: string | undefined; description: string | undefined };
|
||||
__autoload?: true;
|
||||
|
||||
guildId?: string[];
|
||||
name!: string;
|
||||
type!: number; // ApplicationCommandType.ChatInput | ApplicationCommandOptionType.Subcommand
|
||||
nsfw?: boolean;
|
||||
description!: string;
|
||||
defaultMemberPermissions?: bigint;
|
||||
integrationTypes?: ApplicationIntegrationType[];
|
||||
contexts?: InteractionContextType[];
|
||||
botPermissions?: bigint;
|
||||
name_localizations?: Partial<Record<LocaleString, string>>;
|
||||
description_localizations?: Partial<Record<LocaleString, string>>;
|
||||
|
||||
options?: CommandOption[] | SubCommand[];
|
||||
|
||||
ignore?: IgnoreCommand;
|
||||
|
||||
aliases?: string[];
|
||||
|
||||
/** @internal */
|
||||
async __runOptions(
|
||||
ctx: CommandContext<{}, never>,
|
||||
resolver: OptionResolver,
|
||||
): Promise<[boolean, OnOptionsReturnObject]> {
|
||||
if (!this?.options?.length) {
|
||||
return [false, {}];
|
||||
}
|
||||
const data: OnOptionsReturnObject = {};
|
||||
let errored = false;
|
||||
for (const i of this.options ?? []) {
|
||||
try {
|
||||
const option = this.options!.find(x => x.name === i.name) as __CommandOption;
|
||||
const value =
|
||||
resolver.getHoisted(i.name)?.value !== undefined
|
||||
? await new Promise(
|
||||
(res, rej) =>
|
||||
option.value?.({ context: ctx, value: resolver.getValue(i.name) } as never, res, rej) ||
|
||||
res(resolver.getValue(i.name)),
|
||||
)
|
||||
: undefined;
|
||||
if (value === undefined) {
|
||||
if (option.required) {
|
||||
errored = true;
|
||||
data[i.name] = {
|
||||
failed: true,
|
||||
value: `${i.name} is required but returned no value`,
|
||||
};
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// @ts-expect-error
|
||||
ctx.options[i.name] = value;
|
||||
data[i.name] = {
|
||||
failed: false,
|
||||
value,
|
||||
};
|
||||
} catch (e) {
|
||||
errored = true;
|
||||
data[i.name] = {
|
||||
failed: true,
|
||||
value: e instanceof Error ? e.message : `${e}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
return [errored, data];
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
static __runMiddlewares(
|
||||
context: CommandContext<{}, never> | ComponentContext | MenuCommandContext<any> | ModalContext,
|
||||
middlewares: (keyof RegisteredMiddlewares)[],
|
||||
global: boolean,
|
||||
): Promise<{ error?: string; pass?: boolean }> {
|
||||
if (!middlewares.length) {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
let index = 0;
|
||||
|
||||
return new Promise(res => {
|
||||
let running = true;
|
||||
const pass: PassFunction = () => {
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
running = false;
|
||||
return res({ pass: true });
|
||||
};
|
||||
function next(obj: any) {
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
// biome-ignore lint/style/noArguments: yes
|
||||
// biome-ignore lint/correctness/noUndeclaredVariables: xd
|
||||
if (arguments.length) {
|
||||
// @ts-expect-error
|
||||
context[global ? 'globalMetadata' : 'metadata'][middlewares[index]] = obj;
|
||||
}
|
||||
if (++index >= middlewares.length) {
|
||||
running = false;
|
||||
return res({});
|
||||
}
|
||||
context.client.middlewares![middlewares[index]]({ context, next, stop, pass });
|
||||
}
|
||||
const stop: StopFunction = err => {
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
running = false;
|
||||
return res({ error: err });
|
||||
};
|
||||
context.client.middlewares![middlewares[0]]({ context, next, stop, pass });
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
__runMiddlewares(context: CommandContext<{}, never>) {
|
||||
return BaseCommand.__runMiddlewares(context, this.middlewares as (keyof RegisteredMiddlewares)[], false);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
__runGlobalMiddlewares(context: CommandContext<{}, never>) {
|
||||
return BaseCommand.__runMiddlewares(
|
||||
context,
|
||||
(context.client.options?.globalMiddlewares ?? []) as (keyof RegisteredMiddlewares)[],
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
const data = {
|
||||
name: this.name,
|
||||
type: this.type,
|
||||
nsfw: !!this.nsfw,
|
||||
description: this.description,
|
||||
name_localizations: this.name_localizations,
|
||||
description_localizations: this.description_localizations,
|
||||
guild_id: this.guildId,
|
||||
default_member_permissions: this.defaultMemberPermissions ? this.defaultMemberPermissions.toString() : undefined,
|
||||
contexts: this.contexts,
|
||||
integration_types: this.integrationTypes,
|
||||
} as {
|
||||
name: BaseCommand['name'];
|
||||
type: BaseCommand['type'];
|
||||
nsfw: BaseCommand['nsfw'];
|
||||
description: BaseCommand['description'];
|
||||
name_localizations: BaseCommand['name_localizations'];
|
||||
description_localizations: BaseCommand['description_localizations'];
|
||||
guild_id: BaseCommand['guildId'];
|
||||
default_member_permissions: string;
|
||||
contexts: BaseCommand['contexts'];
|
||||
integration_types: BaseCommand['integrationTypes'];
|
||||
};
|
||||
return data;
|
||||
}
|
||||
|
||||
async reload() {
|
||||
delete require.cache[this.__filePath!];
|
||||
|
||||
for (const i of this.options ?? []) {
|
||||
if (i instanceof SubCommand && i.__filePath) {
|
||||
await i.reload();
|
||||
}
|
||||
}
|
||||
|
||||
const __tempCommand = await magicImport(this.__filePath!).then(x => x.default ?? x);
|
||||
|
||||
Object.setPrototypeOf(this, __tempCommand.prototype);
|
||||
}
|
||||
|
||||
run?(context: CommandContext): any;
|
||||
onAfterRun?(context: CommandContext, error: unknown | undefined): any;
|
||||
onRunError?(context: CommandContext, error: unknown): any;
|
||||
onOptionsError?(context: CommandContext, metadata: OnOptionsReturnObject): any;
|
||||
onMiddlewaresError?(context: CommandContext, error: string): any;
|
||||
onBotPermissionsFail?(context: CommandContext, permissions: PermissionStrings): any;
|
||||
onPermissionsFail?(context: CommandContext, permissions: PermissionStrings): any;
|
||||
onInternalError?(client: UsingClient, command: Command | SubCommand, error?: unknown): any;
|
||||
}
|
||||
|
||||
export class Command extends BaseCommand {
|
||||
type = ApplicationCommandType.ChatInput;
|
||||
|
||||
groups?: Parameters<typeof Groups>[0];
|
||||
groupsAliases?: Record<string, string>;
|
||||
__tGroups?: Record<
|
||||
string /* name for group*/,
|
||||
{
|
||||
name: string | undefined;
|
||||
description: string | undefined;
|
||||
defaultDescription: string;
|
||||
}
|
||||
>;
|
||||
|
||||
toJSON() {
|
||||
const options: APIApplicationCommandOption[] = [];
|
||||
|
||||
for (const i of this.options ?? []) {
|
||||
if (!(i instanceof SubCommand)) {
|
||||
options.push({ ...i, autocomplete: 'autocomplete' in i } as APIApplicationCommandBasicOption);
|
||||
continue;
|
||||
}
|
||||
if (i.group) {
|
||||
if (!options.find(x => x.name === i.group)) {
|
||||
options.push({
|
||||
type: ApplicationCommandOptionType.SubcommandGroup,
|
||||
name: i.group,
|
||||
description: this.groups![i.group].defaultDescription,
|
||||
description_localizations: Object.fromEntries(this.groups?.[i.group].description ?? []),
|
||||
name_localizations: Object.fromEntries(this.groups?.[i.group].name ?? []),
|
||||
options: [],
|
||||
});
|
||||
}
|
||||
const group = options.find(x => x.name === i.group) as APIApplicationCommandSubcommandGroupOption;
|
||||
group.options?.push(i.toJSON());
|
||||
continue;
|
||||
}
|
||||
options.push(i.toJSON());
|
||||
}
|
||||
|
||||
return {
|
||||
...super.toJSON(),
|
||||
options,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class SubCommand extends BaseCommand {
|
||||
type = ApplicationCommandOptionType.Subcommand;
|
||||
group?: string;
|
||||
declare options?: CommandOption[];
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
...super.toJSON(),
|
||||
options: (this.options ?? []).map(
|
||||
x => ({ ...x, autocomplete: 'autocomplete' in x }) as APIApplicationCommandBasicOption,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
abstract run(context: CommandContext<any>): any;
|
||||
}
|
||||
import {
|
||||
ApplicationCommandOptionType,
|
||||
ApplicationCommandType,
|
||||
type ApplicationIntegrationType,
|
||||
type InteractionContextType,
|
||||
type APIApplicationCommandBasicOption,
|
||||
type APIApplicationCommandOption,
|
||||
type APIApplicationCommandSubcommandGroupOption,
|
||||
type LocaleString,
|
||||
} from 'discord-api-types/v10';
|
||||
import type {
|
||||
ComponentContext,
|
||||
MenuCommandContext,
|
||||
ModalContext,
|
||||
PermissionStrings,
|
||||
SeyfertNumberOption,
|
||||
SeyfertStringOption,
|
||||
} from '../..';
|
||||
import type { Attachment } from '../../builders';
|
||||
import { magicImport, type FlatObjectKeys } from '../../common';
|
||||
import type { AllChannels, AutocompleteInteraction, GuildRole, InteractionGuildMember, User } from '../../structures';
|
||||
import type { Groups, RegisteredMiddlewares } from '../decorators';
|
||||
import type { OptionResolver } from '../optionresolver';
|
||||
import type { CommandContext } from './chatcontext';
|
||||
import type {
|
||||
DefaultLocale,
|
||||
IgnoreCommand,
|
||||
OKFunction,
|
||||
OnOptionsReturnObject,
|
||||
PassFunction,
|
||||
StopFunction,
|
||||
UsingClient,
|
||||
} from './shared';
|
||||
|
||||
export interface ReturnOptionsTypes {
|
||||
1: never; // subcommand
|
||||
2: never; // subcommandgroup
|
||||
3: string;
|
||||
4: number; // integer
|
||||
5: boolean;
|
||||
6: InteractionGuildMember | User;
|
||||
7: AllChannels;
|
||||
8: GuildRole;
|
||||
9: GuildRole | AllChannels | User;
|
||||
10: number; // number
|
||||
11: Attachment;
|
||||
}
|
||||
|
||||
type Wrap<N extends ApplicationCommandOptionType> = N extends
|
||||
| ApplicationCommandOptionType.Subcommand
|
||||
| ApplicationCommandOptionType.SubcommandGroup
|
||||
? never
|
||||
: {
|
||||
required?: boolean;
|
||||
value?(
|
||||
data: { context: CommandContext; value: ReturnOptionsTypes[N] },
|
||||
ok: OKFunction<any>,
|
||||
fail: StopFunction,
|
||||
): void;
|
||||
} & {
|
||||
description: string;
|
||||
description_localizations?: APIApplicationCommandBasicOption['description_localizations'];
|
||||
name_localizations?: APIApplicationCommandBasicOption['name_localizations'];
|
||||
locales?: {
|
||||
name?: FlatObjectKeys<DefaultLocale>;
|
||||
description?: FlatObjectKeys<DefaultLocale>;
|
||||
};
|
||||
};
|
||||
|
||||
export type __TypeWrapper<T extends ApplicationCommandOptionType> = Wrap<T>;
|
||||
|
||||
export type __TypesWrapper = {
|
||||
[P in keyof typeof ApplicationCommandOptionType]: `${(typeof ApplicationCommandOptionType)[P]}` extends `${infer D extends
|
||||
number}`
|
||||
? Wrap<D>
|
||||
: never;
|
||||
};
|
||||
|
||||
export type AutocompleteCallback = (interaction: AutocompleteInteraction) => any;
|
||||
export type OnAutocompleteErrorCallback = (interaction: AutocompleteInteraction, error: unknown) => any;
|
||||
export type CommandBaseOption = __TypesWrapper[keyof __TypesWrapper];
|
||||
export type CommandBaseAutocompleteOption = __TypesWrapper[keyof __TypesWrapper] & {
|
||||
autocomplete: AutocompleteCallback;
|
||||
onAutocompleteError?: OnAutocompleteErrorCallback;
|
||||
};
|
||||
export type CommandAutocompleteOption = CommandBaseAutocompleteOption & { name: string };
|
||||
export type __CommandOption = CommandBaseOption; //| CommandBaseAutocompleteOption;
|
||||
export type CommandOption = __CommandOption & { name: string };
|
||||
export type OptionsRecord = Record<string, __CommandOption & { type: ApplicationCommandOptionType }>;
|
||||
|
||||
type KeysWithoutRequired<T extends OptionsRecord> = {
|
||||
[K in keyof T]-?: T[K]['required'] extends true ? never : K;
|
||||
}[keyof T];
|
||||
|
||||
type ContextOptionsAux<T extends OptionsRecord> = {
|
||||
[K in Exclude<keyof T, KeysWithoutRequired<T>>]: T[K]['value'] extends (...args: any) => any
|
||||
? Parameters<Parameters<T[K]['value']>[1]>[0]
|
||||
: T[K] extends SeyfertStringOption | SeyfertNumberOption
|
||||
? T[K]['choices'] extends NonNullable<SeyfertStringOption['choices'] | SeyfertNumberOption['choices']>
|
||||
? T[K]['choices'][number]['value']
|
||||
: ReturnOptionsTypes[T[K]['type']]
|
||||
: ReturnOptionsTypes[T[K]['type']];
|
||||
} & {
|
||||
[K in KeysWithoutRequired<T>]?: T[K]['value'] extends (...args: any) => any
|
||||
? Parameters<Parameters<T[K]['value']>[1]>[0]
|
||||
: T[K] extends SeyfertStringOption | SeyfertNumberOption
|
||||
? T[K]['choices'] extends NonNullable<SeyfertStringOption['choices'] | SeyfertNumberOption['choices']>
|
||||
? T[K]['choices'][number]['value']
|
||||
: ReturnOptionsTypes[T[K]['type']]
|
||||
: ReturnOptionsTypes[T[K]['type']];
|
||||
};
|
||||
|
||||
export type ContextOptions<T extends OptionsRecord> = ContextOptionsAux<T>;
|
||||
|
||||
export class BaseCommand {
|
||||
middlewares: (keyof RegisteredMiddlewares)[] = [];
|
||||
|
||||
__filePath?: string;
|
||||
__t?: { name: string | undefined; description: string | undefined };
|
||||
__autoload?: true;
|
||||
|
||||
guildId?: string[];
|
||||
name!: string;
|
||||
type!: number; // ApplicationCommandType.ChatInput | ApplicationCommandOptionType.Subcommand
|
||||
nsfw?: boolean;
|
||||
description!: string;
|
||||
defaultMemberPermissions?: bigint;
|
||||
integrationTypes?: ApplicationIntegrationType[];
|
||||
contexts?: InteractionContextType[];
|
||||
botPermissions?: bigint;
|
||||
name_localizations?: Partial<Record<LocaleString, string>>;
|
||||
description_localizations?: Partial<Record<LocaleString, string>>;
|
||||
|
||||
options?: CommandOption[] | SubCommand[];
|
||||
|
||||
ignore?: IgnoreCommand;
|
||||
|
||||
aliases?: string[];
|
||||
|
||||
/** @internal */
|
||||
async __runOptions(
|
||||
ctx: CommandContext<{}, never>,
|
||||
resolver: OptionResolver,
|
||||
): Promise<[boolean, OnOptionsReturnObject]> {
|
||||
if (!this?.options?.length) {
|
||||
return [false, {}];
|
||||
}
|
||||
const data: OnOptionsReturnObject = {};
|
||||
let errored = false;
|
||||
for (const i of this.options ?? []) {
|
||||
try {
|
||||
const option = this.options!.find(x => x.name === i.name) as __CommandOption;
|
||||
const value =
|
||||
resolver.getHoisted(i.name)?.value !== undefined
|
||||
? await new Promise(
|
||||
(res, rej) =>
|
||||
option.value?.({ context: ctx, value: resolver.getValue(i.name) } as never, res, rej) ||
|
||||
res(resolver.getValue(i.name)),
|
||||
)
|
||||
: undefined;
|
||||
if (value === undefined) {
|
||||
if (option.required) {
|
||||
errored = true;
|
||||
data[i.name] = {
|
||||
failed: true,
|
||||
value: `${i.name} is required but returned no value`,
|
||||
};
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// @ts-expect-error
|
||||
ctx.options[i.name] = value;
|
||||
data[i.name] = {
|
||||
failed: false,
|
||||
value,
|
||||
};
|
||||
} catch (e) {
|
||||
errored = true;
|
||||
data[i.name] = {
|
||||
failed: true,
|
||||
value: e instanceof Error ? e.message : `${e}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
return [errored, data];
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
static __runMiddlewares(
|
||||
context: CommandContext<{}, never> | ComponentContext | MenuCommandContext<any> | ModalContext,
|
||||
middlewares: (keyof RegisteredMiddlewares)[],
|
||||
global: boolean,
|
||||
): Promise<{ error?: string; pass?: boolean }> {
|
||||
if (!middlewares.length) {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
let index = 0;
|
||||
|
||||
return new Promise(res => {
|
||||
let running = true;
|
||||
const pass: PassFunction = () => {
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
running = false;
|
||||
return res({ pass: true });
|
||||
};
|
||||
function next(obj: any) {
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
// biome-ignore lint/style/noArguments: yes
|
||||
// biome-ignore lint/correctness/noUndeclaredVariables: xd
|
||||
if (arguments.length) {
|
||||
// @ts-expect-error
|
||||
context[global ? 'globalMetadata' : 'metadata'][middlewares[index]] = obj;
|
||||
}
|
||||
if (++index >= middlewares.length) {
|
||||
running = false;
|
||||
return res({});
|
||||
}
|
||||
context.client.middlewares![middlewares[index]]({ context, next, stop, pass });
|
||||
}
|
||||
const stop: StopFunction = err => {
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
running = false;
|
||||
return res({ error: err });
|
||||
};
|
||||
context.client.middlewares![middlewares[0]]({ context, next, stop, pass });
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
__runMiddlewares(context: CommandContext<{}, never>) {
|
||||
return BaseCommand.__runMiddlewares(context, this.middlewares as (keyof RegisteredMiddlewares)[], false);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
__runGlobalMiddlewares(context: CommandContext<{}, never>) {
|
||||
return BaseCommand.__runMiddlewares(
|
||||
context,
|
||||
(context.client.options?.globalMiddlewares ?? []) as (keyof RegisteredMiddlewares)[],
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
const data = {
|
||||
name: this.name,
|
||||
type: this.type,
|
||||
nsfw: !!this.nsfw,
|
||||
description: this.description,
|
||||
name_localizations: this.name_localizations,
|
||||
description_localizations: this.description_localizations,
|
||||
guild_id: this.guildId,
|
||||
default_member_permissions: this.defaultMemberPermissions ? this.defaultMemberPermissions.toString() : undefined,
|
||||
contexts: this.contexts,
|
||||
integration_types: this.integrationTypes,
|
||||
} as {
|
||||
name: BaseCommand['name'];
|
||||
type: BaseCommand['type'];
|
||||
nsfw: BaseCommand['nsfw'];
|
||||
description: BaseCommand['description'];
|
||||
name_localizations: BaseCommand['name_localizations'];
|
||||
description_localizations: BaseCommand['description_localizations'];
|
||||
guild_id: BaseCommand['guildId'];
|
||||
default_member_permissions: string;
|
||||
contexts: BaseCommand['contexts'];
|
||||
integration_types: BaseCommand['integrationTypes'];
|
||||
};
|
||||
return data;
|
||||
}
|
||||
|
||||
async reload() {
|
||||
delete require.cache[this.__filePath!];
|
||||
|
||||
for (const i of this.options ?? []) {
|
||||
if (i instanceof SubCommand && i.__filePath) {
|
||||
await i.reload();
|
||||
}
|
||||
}
|
||||
|
||||
const __tempCommand = await magicImport(this.__filePath!).then(x => x.default ?? x);
|
||||
|
||||
Object.setPrototypeOf(this, __tempCommand.prototype);
|
||||
}
|
||||
|
||||
run?(context: CommandContext): any;
|
||||
onAfterRun?(context: CommandContext, error: unknown | undefined): any;
|
||||
onRunError?(context: CommandContext, error: unknown): any;
|
||||
onOptionsError?(context: CommandContext, metadata: OnOptionsReturnObject): any;
|
||||
onMiddlewaresError?(context: CommandContext, error: string): any;
|
||||
onBotPermissionsFail?(context: CommandContext, permissions: PermissionStrings): any;
|
||||
onPermissionsFail?(context: CommandContext, permissions: PermissionStrings): any;
|
||||
onInternalError?(client: UsingClient, command: Command | SubCommand, error?: unknown): any;
|
||||
}
|
||||
|
||||
export class Command extends BaseCommand {
|
||||
type = ApplicationCommandType.ChatInput;
|
||||
|
||||
groups?: Parameters<typeof Groups>[0];
|
||||
groupsAliases?: Record<string, string>;
|
||||
__tGroups?: Record<
|
||||
string /* name for group*/,
|
||||
{
|
||||
name: string | undefined;
|
||||
description: string | undefined;
|
||||
defaultDescription: string;
|
||||
}
|
||||
>;
|
||||
|
||||
toJSON() {
|
||||
const options: APIApplicationCommandOption[] = [];
|
||||
|
||||
for (const i of this.options ?? []) {
|
||||
if (!(i instanceof SubCommand)) {
|
||||
options.push({ ...i, autocomplete: 'autocomplete' in i } as APIApplicationCommandBasicOption);
|
||||
continue;
|
||||
}
|
||||
if (i.group) {
|
||||
if (!options.find(x => x.name === i.group)) {
|
||||
options.push({
|
||||
type: ApplicationCommandOptionType.SubcommandGroup,
|
||||
name: i.group,
|
||||
description: this.groups![i.group].defaultDescription,
|
||||
description_localizations: Object.fromEntries(this.groups?.[i.group].description ?? []),
|
||||
name_localizations: Object.fromEntries(this.groups?.[i.group].name ?? []),
|
||||
options: [],
|
||||
});
|
||||
}
|
||||
const group = options.find(x => x.name === i.group) as APIApplicationCommandSubcommandGroupOption;
|
||||
group.options?.push(i.toJSON());
|
||||
continue;
|
||||
}
|
||||
options.push(i.toJSON());
|
||||
}
|
||||
|
||||
return {
|
||||
...super.toJSON(),
|
||||
options,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class SubCommand extends BaseCommand {
|
||||
type = ApplicationCommandOptionType.Subcommand;
|
||||
group?: string;
|
||||
declare options?: CommandOption[];
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
...super.toJSON(),
|
||||
options:
|
||||
this.options?.map(x => ({ ...x, autocomplete: 'autocomplete' in x }) as APIApplicationCommandBasicOption) ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
abstract run(context: CommandContext<any>): any;
|
||||
}
|
||||
|
@ -73,8 +73,10 @@ export class EventHandler extends BaseHandler {
|
||||
break;
|
||||
}
|
||||
|
||||
await this.runEvent(args[0].t, args[1], args[0].d, args[2]);
|
||||
await this.client.collectors.run(args[0].t, args[0].d);
|
||||
await Promise.all([
|
||||
this.runEvent(args[0].t, args[1], args[0].d, args[2]),
|
||||
this.client.collectors.run(args[0].t, args[0].d),
|
||||
]);
|
||||
}
|
||||
|
||||
async runEvent(name: GatewayEvents, client: Client | WorkerClient, packet: any, shardId: number) {
|
||||
|
@ -1,271 +1,274 @@
|
||||
import type {
|
||||
APIChannelMention,
|
||||
APIEmbed,
|
||||
APIGuildMember,
|
||||
APIMessage,
|
||||
APIUser,
|
||||
GatewayMessageCreateDispatchData,
|
||||
} from 'discord-api-types/v10';
|
||||
import type { ListenerOptions } from '../builders';
|
||||
import type { UsingClient } from '../commands';
|
||||
import { toCamelCase, type ObjectToLower } from '../common';
|
||||
import type { EmojiResolvable } from '../common/types/resolvables';
|
||||
import type { MessageCreateBodyRequest, MessageUpdateBodyRequest } from '../common/types/write';
|
||||
import type { ActionRowMessageComponents } from '../components';
|
||||
import { MessageActionRowComponent } from '../components/ActionRow';
|
||||
import { GuildMember } from './GuildMember';
|
||||
import { User } from './User';
|
||||
import type { MessageWebhookMethodEditParams, MessageWebhookMethodWriteParams } from './Webhook';
|
||||
import { DiscordBase } from './extra/DiscordBase';
|
||||
import { messageLink } from './extra/functions';
|
||||
import { Embed, Poll } from '..';
|
||||
|
||||
export type MessageData = APIMessage | GatewayMessageCreateDispatchData;
|
||||
|
||||
export interface BaseMessage
|
||||
extends DiscordBase,
|
||||
ObjectToLower<Omit<MessageData, 'timestamp' | 'author' | 'mentions' | 'components' | 'poll' | 'embeds'>> {}
|
||||
export class BaseMessage extends DiscordBase {
|
||||
guildId: string | undefined;
|
||||
timestamp?: number;
|
||||
author!: User;
|
||||
member?: GuildMember;
|
||||
components: MessageActionRowComponent<ActionRowMessageComponents>[];
|
||||
poll?: Poll;
|
||||
mentions: {
|
||||
roles: string[];
|
||||
channels: APIChannelMention[];
|
||||
users: (GuildMember | User)[];
|
||||
};
|
||||
embeds: InMessageEmbed[];
|
||||
|
||||
constructor(client: UsingClient, data: MessageData) {
|
||||
super(client, data);
|
||||
this.mentions = {
|
||||
roles: data.mention_roles ?? [],
|
||||
channels: data.mention_channels ?? [],
|
||||
users: [],
|
||||
};
|
||||
this.components = (data.components ?? []).map(x => new MessageActionRowComponent(x));
|
||||
this.embeds = data.embeds.map(embed => new InMessageEmbed(embed));
|
||||
this.patch(data);
|
||||
}
|
||||
|
||||
get user() {
|
||||
return this.author;
|
||||
}
|
||||
|
||||
createComponentCollector(options?: ListenerOptions) {
|
||||
return this.client.components!.createComponentCollector(this.id, options);
|
||||
}
|
||||
|
||||
get url() {
|
||||
return messageLink(this.channelId, this.id, this.guildId);
|
||||
}
|
||||
|
||||
guild(force = false) {
|
||||
if (!this.guildId) return;
|
||||
return this.client.guilds.fetch(this.guildId, force);
|
||||
}
|
||||
|
||||
async channel(force = false) {
|
||||
return this.client.channels.fetch(this.channelId, force);
|
||||
}
|
||||
|
||||
react(emoji: EmojiResolvable) {
|
||||
return this.client.reactions.add(this.id, this.channelId, emoji);
|
||||
}
|
||||
|
||||
private patch(data: MessageData) {
|
||||
if ('guild_id' in data) {
|
||||
this.guildId = data.guild_id;
|
||||
}
|
||||
|
||||
if (data.type !== undefined) {
|
||||
this.type = data.type;
|
||||
}
|
||||
|
||||
if ('timestamp' in data && data.timestamp) {
|
||||
this.timestamp = Date.parse(data.timestamp);
|
||||
}
|
||||
|
||||
if ('application_id' in data) {
|
||||
this.applicationId = data.application_id;
|
||||
}
|
||||
if ('author' in data && data.author) {
|
||||
this.author = new User(this.client, data.author);
|
||||
}
|
||||
|
||||
if ('member' in data && data.member) {
|
||||
this.member = new GuildMember(this.client, data.member, this.author, this.guildId!);
|
||||
}
|
||||
|
||||
if (data.mentions?.length) {
|
||||
this.mentions.users = this.guildId
|
||||
? data.mentions.map(
|
||||
m =>
|
||||
new GuildMember(
|
||||
this.client,
|
||||
{
|
||||
...(m as APIUser & { member?: Omit<APIGuildMember, 'user'> }).member!,
|
||||
user: m,
|
||||
},
|
||||
m,
|
||||
this.guildId!,
|
||||
),
|
||||
)
|
||||
: data.mentions.map(u => new User(this.client, u));
|
||||
}
|
||||
|
||||
if (data.poll) {
|
||||
this.poll = new Poll(this.client, data.poll, this.channelId, this.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface Message
|
||||
extends BaseMessage,
|
||||
ObjectToLower<Omit<MessageData, 'timestamp' | 'author' | 'mentions' | 'components' | 'poll' | 'embeds'>> {}
|
||||
|
||||
export class Message extends BaseMessage {
|
||||
constructor(client: UsingClient, data: MessageData) {
|
||||
super(client, data);
|
||||
}
|
||||
|
||||
fetch() {
|
||||
return this.client.messages.fetch(this.id, this.channelId);
|
||||
}
|
||||
|
||||
reply(body: Omit<MessageCreateBodyRequest, 'message_reference'>, fail = true) {
|
||||
return this.write({
|
||||
...body,
|
||||
message_reference: {
|
||||
message_id: this.id,
|
||||
channel_id: this.channelId,
|
||||
guild_id: this.guildId,
|
||||
fail_if_not_exists: fail,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
edit(body: MessageUpdateBodyRequest) {
|
||||
return this.client.messages.edit(this.id, this.channelId, body);
|
||||
}
|
||||
|
||||
write(body: MessageCreateBodyRequest) {
|
||||
return this.client.messages.write(this.channelId, body);
|
||||
}
|
||||
|
||||
delete(reason?: string) {
|
||||
return this.client.messages.delete(this.id, this.channelId, reason);
|
||||
}
|
||||
|
||||
crosspost(reason?: string) {
|
||||
return this.client.messages.crosspost(this.id, this.channelId, reason);
|
||||
}
|
||||
}
|
||||
|
||||
export type EditMessageWebhook = Omit<MessageWebhookMethodEditParams, 'messageId'>['body'] &
|
||||
Pick<MessageWebhookMethodEditParams, 'query'>;
|
||||
export type WriteMessageWebhook = MessageWebhookMethodWriteParams['body'] &
|
||||
Pick<MessageWebhookMethodWriteParams, 'query'>;
|
||||
|
||||
export class WebhookMessage extends BaseMessage {
|
||||
constructor(
|
||||
client: UsingClient,
|
||||
data: MessageData,
|
||||
readonly webhookId: string,
|
||||
readonly webhookToken: string,
|
||||
) {
|
||||
super(client, data);
|
||||
}
|
||||
|
||||
fetch() {
|
||||
return this.api.webhooks(this.webhookId)(this.webhookToken).get({ query: this.thread?.id });
|
||||
}
|
||||
|
||||
edit(body: EditMessageWebhook) {
|
||||
const { query, ...rest } = body;
|
||||
return this.client.webhooks.editMessage(this.webhookId, this.webhookToken, {
|
||||
body: rest,
|
||||
query,
|
||||
messageId: this.id,
|
||||
});
|
||||
}
|
||||
|
||||
write(body: WriteMessageWebhook) {
|
||||
const { query, ...rest } = body;
|
||||
return this.client.webhooks.writeMessage(this.webhookId, this.webhookToken, {
|
||||
body: rest,
|
||||
query,
|
||||
});
|
||||
}
|
||||
|
||||
delete(reason?: string) {
|
||||
return this.client.webhooks.deleteMessage(this.webhookId, this.webhookToken, this.id, reason);
|
||||
}
|
||||
}
|
||||
|
||||
export class InMessageEmbed {
|
||||
constructor(public data: APIEmbed) {}
|
||||
|
||||
get title() {
|
||||
return this.data.title;
|
||||
}
|
||||
|
||||
get type() {
|
||||
return this.data.type;
|
||||
}
|
||||
|
||||
get description() {
|
||||
return this.data.description;
|
||||
}
|
||||
|
||||
get url() {
|
||||
return this.data.url;
|
||||
}
|
||||
|
||||
get timestamp() {
|
||||
return this.data.timestamp;
|
||||
}
|
||||
|
||||
get color() {
|
||||
return this.data.color;
|
||||
}
|
||||
|
||||
get footer() {
|
||||
return toCamelCase(this.data.footer ?? {});
|
||||
}
|
||||
|
||||
get image() {
|
||||
return toCamelCase(this.data.image ?? {});
|
||||
}
|
||||
|
||||
get thumbnail() {
|
||||
return toCamelCase(this.data.thumbnail ?? {});
|
||||
}
|
||||
|
||||
get video() {
|
||||
return toCamelCase(this.data.video ?? {});
|
||||
}
|
||||
|
||||
get provider() {
|
||||
return this.data.provider;
|
||||
}
|
||||
|
||||
get author() {
|
||||
return toCamelCase(this.data.author ?? {});
|
||||
}
|
||||
|
||||
get fields() {
|
||||
return this.data.fields;
|
||||
}
|
||||
|
||||
toBuilder() {
|
||||
return new Embed(this.data);
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return { ...this.data };
|
||||
}
|
||||
}
|
||||
import type {
|
||||
APIChannelMention,
|
||||
APIEmbed,
|
||||
APIGuildMember,
|
||||
APIMessage,
|
||||
APIUser,
|
||||
GatewayMessageCreateDispatchData,
|
||||
} from 'discord-api-types/v10';
|
||||
import type { ListenerOptions } from '../builders';
|
||||
import type { UsingClient } from '../commands';
|
||||
import { toCamelCase, type ObjectToLower } from '../common';
|
||||
import type { EmojiResolvable } from '../common/types/resolvables';
|
||||
import type { MessageCreateBodyRequest, MessageUpdateBodyRequest } from '../common/types/write';
|
||||
import type { ActionRowMessageComponents } from '../components';
|
||||
import { MessageActionRowComponent } from '../components/ActionRow';
|
||||
import { GuildMember } from './GuildMember';
|
||||
import { User } from './User';
|
||||
import type { MessageWebhookMethodEditParams, MessageWebhookMethodWriteParams } from './Webhook';
|
||||
import { DiscordBase } from './extra/DiscordBase';
|
||||
import { messageLink } from './extra/functions';
|
||||
import { Embed, Poll } from '..';
|
||||
|
||||
export type MessageData = APIMessage | GatewayMessageCreateDispatchData;
|
||||
|
||||
export interface BaseMessage
|
||||
extends DiscordBase,
|
||||
ObjectToLower<Omit<MessageData, 'timestamp' | 'author' | 'mentions' | 'components' | 'poll' | 'embeds'>> {}
|
||||
export class BaseMessage extends DiscordBase {
|
||||
guildId: string | undefined;
|
||||
timestamp?: number;
|
||||
author!: User;
|
||||
member?: GuildMember;
|
||||
components: MessageActionRowComponent<ActionRowMessageComponents>[];
|
||||
poll?: Poll;
|
||||
mentions: {
|
||||
roles: string[];
|
||||
channels: APIChannelMention[];
|
||||
users: (GuildMember | User)[];
|
||||
};
|
||||
embeds: InMessageEmbed[];
|
||||
|
||||
constructor(client: UsingClient, data: MessageData) {
|
||||
super(client, data);
|
||||
this.mentions = {
|
||||
roles: data.mention_roles ?? [],
|
||||
channels: data.mention_channels ?? [],
|
||||
users: [],
|
||||
};
|
||||
this.components = data.components?.map(x => new MessageActionRowComponent(x)) ?? [];
|
||||
this.embeds = data.embeds.map(embed => new InMessageEmbed(embed));
|
||||
this.patch(data);
|
||||
}
|
||||
|
||||
get user() {
|
||||
return this.author;
|
||||
}
|
||||
|
||||
createComponentCollector(options?: ListenerOptions) {
|
||||
return this.client.components!.createComponentCollector(this.id, options);
|
||||
}
|
||||
|
||||
get url() {
|
||||
return messageLink(this.channelId, this.id, this.guildId);
|
||||
}
|
||||
|
||||
guild(force = false) {
|
||||
if (!this.guildId) return;
|
||||
return this.client.guilds.fetch(this.guildId, force);
|
||||
}
|
||||
|
||||
async channel(force = false) {
|
||||
return this.client.channels.fetch(this.channelId, force);
|
||||
}
|
||||
|
||||
react(emoji: EmojiResolvable) {
|
||||
return this.client.reactions.add(this.id, this.channelId, emoji);
|
||||
}
|
||||
|
||||
private patch(data: MessageData) {
|
||||
if ('guild_id' in data) {
|
||||
this.guildId = data.guild_id;
|
||||
}
|
||||
|
||||
if (data.type !== undefined) {
|
||||
this.type = data.type;
|
||||
}
|
||||
|
||||
if ('timestamp' in data && data.timestamp) {
|
||||
this.timestamp = Date.parse(data.timestamp);
|
||||
}
|
||||
|
||||
if ('application_id' in data) {
|
||||
this.applicationId = data.application_id;
|
||||
}
|
||||
if ('author' in data && data.author) {
|
||||
this.author = new User(this.client, data.author);
|
||||
}
|
||||
|
||||
if ('member' in data && data.member) {
|
||||
this.member = new GuildMember(this.client, data.member, this.author, this.guildId!);
|
||||
}
|
||||
|
||||
if (data.mentions?.length) {
|
||||
this.mentions.users = this.guildId
|
||||
? data.mentions.map(
|
||||
m =>
|
||||
new GuildMember(
|
||||
this.client,
|
||||
{
|
||||
...(m as APIUser & { member?: Omit<APIGuildMember, 'user'> }).member!,
|
||||
user: m,
|
||||
},
|
||||
m,
|
||||
this.guildId!,
|
||||
),
|
||||
)
|
||||
: data.mentions.map(u => new User(this.client, u));
|
||||
}
|
||||
|
||||
if (data.poll) {
|
||||
this.poll = new Poll(this.client, data.poll, this.channelId, this.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface Message
|
||||
extends BaseMessage,
|
||||
ObjectToLower<Omit<MessageData, 'timestamp' | 'author' | 'mentions' | 'components' | 'poll' | 'embeds'>> {}
|
||||
|
||||
export class Message extends BaseMessage {
|
||||
constructor(client: UsingClient, data: MessageData) {
|
||||
super(client, data);
|
||||
}
|
||||
|
||||
fetch() {
|
||||
return this.client.messages.fetch(this.id, this.channelId);
|
||||
}
|
||||
|
||||
reply(body: Omit<MessageCreateBodyRequest, 'message_reference'>, fail = true) {
|
||||
return this.write({
|
||||
...body,
|
||||
message_reference: {
|
||||
message_id: this.id,
|
||||
channel_id: this.channelId,
|
||||
guild_id: this.guildId,
|
||||
fail_if_not_exists: fail,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
edit(body: MessageUpdateBodyRequest) {
|
||||
return this.client.messages.edit(this.id, this.channelId, body);
|
||||
}
|
||||
|
||||
write(body: MessageCreateBodyRequest) {
|
||||
return this.client.messages.write(this.channelId, body);
|
||||
}
|
||||
|
||||
delete(reason?: string) {
|
||||
return this.client.messages.delete(this.id, this.channelId, reason);
|
||||
}
|
||||
|
||||
crosspost(reason?: string) {
|
||||
return this.client.messages.crosspost(this.id, this.channelId, reason);
|
||||
}
|
||||
}
|
||||
|
||||
export type EditMessageWebhook = Omit<MessageWebhookMethodEditParams, 'messageId'>['body'] &
|
||||
Pick<MessageWebhookMethodEditParams, 'query'>;
|
||||
export type WriteMessageWebhook = MessageWebhookMethodWriteParams['body'] &
|
||||
Pick<MessageWebhookMethodWriteParams, 'query'>;
|
||||
|
||||
export class WebhookMessage extends BaseMessage {
|
||||
constructor(
|
||||
client: UsingClient,
|
||||
data: MessageData,
|
||||
readonly webhookId: string,
|
||||
readonly webhookToken: string,
|
||||
) {
|
||||
super(client, data);
|
||||
}
|
||||
|
||||
fetch() {
|
||||
return this.api.webhooks(this.webhookId)(this.webhookToken).get({ query: this.thread?.id });
|
||||
}
|
||||
|
||||
edit(body: EditMessageWebhook) {
|
||||
const { query, ...rest } = body;
|
||||
return this.client.webhooks.editMessage(this.webhookId, this.webhookToken, {
|
||||
body: rest,
|
||||
query,
|
||||
messageId: this.id,
|
||||
});
|
||||
}
|
||||
|
||||
write(body: WriteMessageWebhook) {
|
||||
const { query, ...rest } = body;
|
||||
return this.client.webhooks.writeMessage(this.webhookId, this.webhookToken, {
|
||||
body: rest,
|
||||
query,
|
||||
});
|
||||
}
|
||||
|
||||
delete(reason?: string) {
|
||||
return this.client.webhooks.deleteMessage(this.webhookId, this.webhookToken, this.id, reason);
|
||||
}
|
||||
}
|
||||
|
||||
export class InMessageEmbed {
|
||||
constructor(public data: APIEmbed) {}
|
||||
|
||||
get title() {
|
||||
return this.data.title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
get type() {
|
||||
return this.data.type;
|
||||
}
|
||||
|
||||
get description() {
|
||||
return this.data.description;
|
||||
}
|
||||
|
||||
get url() {
|
||||
return this.data.url;
|
||||
}
|
||||
|
||||
get timestamp() {
|
||||
return this.data.timestamp;
|
||||
}
|
||||
|
||||
get color() {
|
||||
return this.data.color;
|
||||
}
|
||||
|
||||
get footer() {
|
||||
return this.data.footer ? toCamelCase(this.data.footer) : undefined;
|
||||
}
|
||||
|
||||
get image() {
|
||||
return this.data.image ? toCamelCase(this.data.image) : undefined;
|
||||
}
|
||||
|
||||
get thumbnail() {
|
||||
return this.data.thumbnail ? toCamelCase(this.data.thumbnail) : undefined;
|
||||
}
|
||||
|
||||
get video() {
|
||||
return this.data.video ? toCamelCase(this.data.video) : undefined;
|
||||
}
|
||||
|
||||
get provider() {
|
||||
return this.data.provider;
|
||||
}
|
||||
|
||||
get author() {
|
||||
return this.data.author ? toCamelCase(this.data.author) : undefined;
|
||||
}
|
||||
|
||||
get fields() {
|
||||
return this.data.fields;
|
||||
}
|
||||
|
||||
toBuilder() {
|
||||
return new Embed(this.data);
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return { ...this.data };
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user