feat: send attachments option by default

This commit is contained in:
MARCROCK22 2024-05-25 13:18:43 -04:00
parent 0b60586847
commit 006a9f390f
13 changed files with 200 additions and 47 deletions

View File

@ -386,7 +386,6 @@ export class ApiHandler {
if (options.request.reason) {
options.headers['X-Audit-Log-Reason'] = encodeURIComponent(options.request.reason);
}
return { data, finalUrl } as { data: typeof data; finalUrl: `/${string}` };
}

View File

@ -85,7 +85,7 @@ export class BaseClient {
options: BaseClientOptions;
/**@internal */
static _seyferConfig?: InternalRuntimeConfigHTTP | InternalRuntimeConfig;
static _seyfertConfig?: InternalRuntimeConfigHTTP | InternalRuntimeConfig;
constructor(options?: BaseClientOptions) {
this.options = MergeOptions(
@ -339,7 +339,7 @@ export class BaseClient {
async getRC<
T extends InternalRuntimeConfigHTTP | InternalRuntimeConfig = InternalRuntimeConfigHTTP | InternalRuntimeConfig,
>() {
const seyfertConfig = (BaseClient._seyferConfig ||
const seyfertConfig = (BaseClient._seyfertConfig ||
(await this.options.getRC?.()) ||
(await magicImport(join(process.cwd(), 'seyfert.config.js')).then(x => x.default ?? x))) as T;
@ -358,7 +358,7 @@ export class BaseClient {
output: join(process.cwd(), locations.output),
};
BaseClient._seyferConfig = seyfertConfig;
BaseClient._seyfertConfig = seyfertConfig;
return obj;
}

View File

@ -10,17 +10,19 @@ import type { BaseClientOptions, InternalRuntimeConfig, ServicesOptions, StartOp
import { BaseClient } from './base';
import { onInteractionCreate } from './oninteractioncreate';
import { onMessageCreate } from './onmessagecreate';
import { Collectors } from './collectors';
let parentPort: import('node:worker_threads').MessagePort;
export class Client<Ready extends boolean = boolean> extends BaseClient {
private __handleGuilds?: Set<string> = new Set();
gateway!: ShardManager;
events? = new EventHandler(this.logger);
me!: If<Ready, ClientUser>;
declare options: ClientOptions;
memberUpdateHandler = new MemberUpdateHandler();
presenceUpdateHandler = new PresenceUpdateHandler();
collectors = new Collectors();
events? = new EventHandler(this.logger, this.collectors);
constructor(options?: ClientOptions) {
super(options);
@ -49,7 +51,7 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
if (!rest.handlers.events) {
this.events = undefined;
} else if (typeof rest.handlers.events === 'function') {
this.events = new EventHandler(this.logger);
this.events = new EventHandler(this.logger, this.collectors);
this.events.setHandlers({
callback: rest.handlers.events,
});

97
src/client/collectors.ts Normal file
View File

@ -0,0 +1,97 @@
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>>]>) => 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.options.idle);
clearTimeout(collector.options.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);
break;
}
}
}
}

View File

@ -25,6 +25,7 @@ import { BaseClient } from './base';
import type { Client } from './client';
import { onInteractionCreate } from './oninteractioncreate';
import { onMessageCreate } from './onmessagecreate';
import { Collectors } from './collectors';
let workerData: WorkerData;
let manager: import('node:worker_threads').MessagePort;
@ -46,7 +47,8 @@ export class WorkerClient<Ready extends boolean = boolean> extends BaseClient {
name: `[Worker #${workerData.workerId}]`,
});
events? = new EventHandler(this.logger);
collectors = new Collectors();
events? = new EventHandler(this.logger, this.collectors);
me!: When<Ready, ClientUser>;
promises = new Map<string, { resolve: (value: any) => void; timeout: NodeJS.Timeout }>();
@ -117,7 +119,7 @@ export class WorkerClient<Ready extends boolean = boolean> extends BaseClient {
if (!rest.handlers.events) {
this.events = undefined;
} else if (typeof rest.handlers.events === 'function') {
this.events = new EventHandler(this.logger);
this.events = new EventHandler(this.logger, this.collectors);
this.events.setHandlers({
callback: rest.handlers.events,
});

View File

@ -1,4 +1,4 @@
import { BaseInteraction, type RawFile, WebhookMessage, resolveFiles, type ReplyInteractionBody, Modal } from '../..';
import { BaseInteraction, WebhookMessage, resolveFiles, type ReplyInteractionBody, Modal } from '../..';
import type { InteractionMessageUpdateBodyRequest, MessageWebhookCreateBodyRequest } from '../types/write';
import { BaseShorter } from './base';
@ -8,6 +8,7 @@ export class InteractionShorter extends BaseShorter {
const { files, ...rest } = body.data ?? {};
//@ts-expect-error
const data = body.data instanceof Modal ? body.data : rest;
const parsedFiles = files ? await resolveFiles(files) : undefined;
return this.client.proxy
.interactions(id)(token)
.callback.post({
@ -16,9 +17,10 @@ export class InteractionShorter extends BaseShorter {
type: body.type,
data,
},
parsedFiles,
this.client,
),
files: files ? await resolveFiles(files) : undefined,
files: parsedFiles,
});
}
@ -32,12 +34,13 @@ export class InteractionShorter extends BaseShorter {
async editMessage(token: string, messageId: string, body: InteractionMessageUpdateBodyRequest) {
const { files, ...data } = body;
const parsedFiles = files ? await resolveFiles(files) : undefined;
const apiMessage = await this.client.proxy
.webhooks(this.client.applicationId)(token)
.messages(messageId)
.patch({
body: BaseInteraction.transformBody(data, this.client),
files: files ? await resolveFiles(files) : undefined,
body: BaseInteraction.transformBody(data, parsedFiles, this.client),
files: parsedFiles,
});
return new WebhookMessage(this.client, apiMessage, this.client.applicationId, token);
}
@ -59,12 +62,12 @@ export class InteractionShorter extends BaseShorter {
}
async followup(token: string, { files, ...body }: MessageWebhookCreateBodyRequest) {
files = files ? await resolveFiles(files) : undefined;
const parsedFiles = files ? await resolveFiles(files) : undefined;
const apiMessage = await this.client.proxy
.webhooks(this.client.applicationId)(token)
.post({
body: BaseInteraction.transformBody(body, this.client),
files: files as RawFile[] | undefined,
body: BaseInteraction.transformBody(body, parsedFiles, this.client),
files: parsedFiles,
});
return new WebhookMessage(this.client, apiMessage, this.client.applicationId, token);
}

View File

@ -14,7 +14,11 @@ export class MessageShorter extends BaseShorter {
async write(channelId: string, { files, ...body }: MessageCreateBodyRequest) {
const parsedFiles = files ? await resolveFiles(files) : [];
const transformedBody = MessagesMethods.transformMessageBody<RESTPostAPIChannelMessageJSONBody>(body, this.client);
const transformedBody = MessagesMethods.transformMessageBody<RESTPostAPIChannelMessageJSONBody>(
body,
parsedFiles,
this.client,
);
return this.client.proxy
.channels(channelId)
.messages.post({
@ -33,7 +37,7 @@ export class MessageShorter extends BaseShorter {
.channels(channelId)
.messages(messageId)
.patch({
body: MessagesMethods.transformMessageBody<RESTPatchAPIChannelMessageJSONBody>(body, this.client),
body: MessagesMethods.transformMessageBody<RESTPatchAPIChannelMessageJSONBody>(body, parsedFiles, this.client),
files: parsedFiles,
})
.then(async message => {

View File

@ -83,11 +83,12 @@ export class WebhookShorter extends BaseShorter {
*/
async writeMessage(webhookId: string, token: string, { body: data, ...payload }: MessageWebhookMethodWriteParams) {
const { files, ...body } = data;
const parsedFiles = files ? await resolveFiles(files) : [];
const transformedBody = MessagesMethods.transformMessageBody<RESTPostAPIWebhookWithTokenJSONBody>(
body,
parsedFiles,
this.client,
);
const parsedFiles = files ? await resolveFiles(files) : [];
return this.client.proxy
.webhooks(webhookId)(token)
.post({ ...payload, files: parsedFiles, body: transformedBody })
@ -108,11 +109,12 @@ export class WebhookShorter extends BaseShorter {
{ messageId, body: data, ...json }: MessageWebhookMethodEditParams,
) {
const { files, ...body } = data;
const parsedFiles = files ? await resolveFiles(files) : [];
const transformedBody = MessagesMethods.transformMessageBody<RESTPostAPIWebhookWithTokenJSONBody>(
body,
parsedFiles,
this.client,
);
const parsedFiles = files ? await resolveFiles(files) : [];
return this.client.proxy
.webhooks(webhookId)(token)
.messages(messageId)

View File

@ -44,22 +44,24 @@ export class ComponentHandler extends BaseHandler {
this.values.set(messageId, {
components: [],
options,
idle: options.idle
? setTimeout(() => {
this.deleteValue(messageId);
options.onStop?.('idle', () => {
this.createComponentCollector(messageId, options);
});
}, options.idle)
: undefined,
timeout: options.timeout
? setTimeout(() => {
this.deleteValue(messageId);
options.onStop?.('timeout', () => {
this.createComponentCollector(messageId, options);
});
}, options.timeout)
: undefined,
idle:
options.idle && options.idle > 0
? setTimeout(() => {
this.deleteValue(messageId);
options.onStop?.('idle', () => {
this.createComponentCollector(messageId, options);
});
}, options.idle)
: undefined,
timeout:
options.timeout && options.timeout > 0
? setTimeout(() => {
this.deleteValue(messageId);
options.onStop?.('timeout', () => {
this.createComponentCollector(messageId, options);
});
}, options.timeout)
: undefined,
__run: (customId, callback) => {
if (this.values.has(messageId)) {
this.values.get(messageId)!.components.push({

View File

@ -5,16 +5,24 @@ import type {
GatewayMessageDeleteDispatch,
} from 'discord-api-types/v10';
import type { Client, WorkerClient } from '../client';
import { BaseHandler, ReplaceRegex, magicImport, type MakeRequired, type SnakeCase } from '../common';
import { BaseHandler, type Logger, ReplaceRegex, magicImport, type MakeRequired, type SnakeCase } from '../common';
import type { ClientEvents } from '../events/hooks';
import * as RawEvents from '../events/hooks';
import type { ClientEvent, ClientNameEvents } from './event';
import type { Collectors } from '../client/collectors';
export type EventValue = MakeRequired<ClientEvent, '__filePath'> & { fired?: boolean };
export type GatewayEvents = Uppercase<SnakeCase<keyof ClientEvents>>;
export class EventHandler extends BaseHandler {
constructor(
logger: Logger,
protected collectors: Collectors,
) {
super(logger);
}
onFail = (event: GatewayEvents, err: unknown) => this.logger.warn('<Client>.events.onFail', err, event);
protected filter = (path: string) => path.endsWith('.js') || (!path.endsWith('.d.ts') && path.endsWith('.ts'));
@ -69,6 +77,7 @@ export class EventHandler extends BaseHandler {
break;
}
await this.collectors.run(args[0].t, args[0].d);
await this.runEvent(args[0].t, args[1], args[0].d, args[2]);
}

View File

@ -86,7 +86,7 @@ export const config = {
port: 8080,
...data,
} as InternalRuntimeConfigHTTP;
if (isCloudfareWorker()) BaseClient._seyferConfig = obj;
if (isCloudfareWorker()) BaseClient._seyfertConfig = obj;
return obj;
},
};

View File

@ -35,6 +35,7 @@ import {
InteractionType,
type MessageFlags,
type RESTPostAPIInteractionCallbackJSONBody,
type RESTAPIAttachment,
} from 'discord-api-types/v10';
import { mix } from 'ts-mixer';
import type { RawFile } from '../api';
@ -114,7 +115,11 @@ export class BaseInteraction<
this.user = this.member?.user ?? new User(client, interaction.user!);
}
static transformBodyRequest(body: ReplyInteractionBody, self: UsingClient): APIInteractionResponse {
static transformBodyRequest(
body: ReplyInteractionBody,
files: RawFile[] | undefined,
self: UsingClient,
): APIInteractionResponse {
switch (body.type) {
case InteractionResponseType.ApplicationCommandAutocompleteResult:
case InteractionResponseType.DeferredMessageUpdate:
@ -131,9 +136,15 @@ export class BaseInteraction<
allowed_mentions: self.options?.allowedMentions,
...(body.data ?? {}),
//@ts-ignore
components: body.data?.components?.map(x => (x instanceof ActionRow ? x.toJSON() : x)) ?? undefined,
embeds: body.data?.embeds?.map(x => (x instanceof Embed ? x.toJSON() : x)) ?? undefined,
attachments: body.data?.attachments?.map((x, i) => ({ id: i, ...resolveAttachment(x) })) ?? undefined,
components: body.data?.components?.map(x => (x instanceof ActionRow ? x.toJSON() : x)),
embeds: body.data?.embeds?.map(x => (x instanceof Embed ? x.toJSON() : x)),
attachments:
body.data && 'attachments' in body.data
? body.data.attachments?.map((x, i) => ({ id: i, ...resolveAttachment(x) }))
: (files?.map((x, id) => ({
id,
filename: x.name,
})) as RESTAPIAttachment[]),
poll: poll ? (poll instanceof PollBuilder ? poll.toJSON() : poll) : undefined,
},
};
@ -166,14 +177,23 @@ export class BaseInteraction<
| MessageUpdateBodyRequest
| MessageCreateBodyRequest
| MessageWebhookCreateBodyRequest,
files: RawFile[] | undefined,
self: UsingClient,
) {
const poll = (body as MessageWebhookCreateBodyRequest).poll;
return {
allowed_mentions: self.options?.allowedMentions,
attachments:
'attachments' in body
? body.attachments?.map((x, i) => ({ id: i, ...resolveAttachment(x) }))
: (files?.map((x, id) => ({
id,
filename: x.name,
})) as RESTAPIAttachment[]),
...body,
components: body.components?.map(x => (x instanceof ActionRow ? x.toJSON() : x)) ?? undefined,
embeds: body?.embeds?.map(x => (x instanceof Embed ? x.toJSON() : x)) ?? undefined,
components: body.components?.map(x => (x instanceof ActionRow ? x.toJSON() : x)),
embeds: body?.embeds?.map(x => (x instanceof Embed ? x.toJSON() : x)),
poll: poll ? (poll instanceof PollBuilder ? poll.toJSON() : poll) : undefined,
} as T;
}
@ -184,9 +204,10 @@ export class BaseInteraction<
const { files, ...rest } = body.data ?? {};
//@ts-expect-error
const data = body.data instanceof Modal ? body.data : rest;
const parsedFiles = files ? await resolveFiles(files) : undefined;
return (this.replied = this.__reply({
body: BaseInteraction.transformBodyRequest({ data, type: body.type }, this.client),
files: files ? await resolveFiles(files) : undefined,
body: BaseInteraction.transformBodyRequest({ data, type: body.type }, parsedFiles, this.client),
files: parsedFiles,
}).then(() => (this.replied = true)));
}
return (this.replied = this.client.interactions.reply(this.id, this.token, body).then(() => (this.replied = true)));

View File

@ -1,6 +1,7 @@
import {
ChannelFlags,
ChannelType,
type RESTAPIAttachment,
VideoQualityMode,
type APIChannelBase,
type APIDMChannel,
@ -40,6 +41,7 @@ import type { GuildMember } from './GuildMember';
import type { GuildRole } from './GuildRole';
import { DiscordBase } from './extra/DiscordBase';
import { channelLink } from './extra/functions';
import type { RawFile } from '..';
export class BaseChannel<T extends ChannelType> extends DiscordBase<APIChannelBase<ChannelType>> {
declare type: T;
@ -250,14 +252,24 @@ export class MessagesMethods extends DiscordBase {
};
}
static transformMessageBody<T>(body: MessageCreateBodyRequest | MessageUpdateBodyRequest, self: UsingClient) {
static transformMessageBody<T>(
body: MessageCreateBodyRequest | MessageUpdateBodyRequest,
files: RawFile[] | undefined,
self: UsingClient,
) {
const poll = (body as MessageCreateBodyRequest).poll;
return {
allowed_mentions: self.options?.allowedMentions,
...body,
components: body.components?.map(x => (x instanceof ActionRow ? x.toJSON() : x)) ?? undefined,
embeds: body.embeds?.map(x => (x instanceof Embed ? x.toJSON() : x)) ?? undefined,
attachments: body.attachments?.map((x, i) => ({ id: i, ...resolveAttachment(x) })) ?? undefined,
attachments:
'attachments' in body
? body.attachments?.map((x, i) => ({ id: i, ...resolveAttachment(x) })) ?? undefined
: (files?.map((x, id) => ({
id,
filename: x.name,
})) as RESTAPIAttachment[]),
poll: poll ? (poll instanceof PollBuilder ? poll.toJSON() : poll) : undefined,
} as T;
}