feat: guild bans, formatter and minor functionality changes. (#203)

* fix(Colors#bold): bold returning weird values

* chore: testing something

* fix(Parsers): optionsParser & argsParser were being overriden by default options

* revert(colors): mistakenly pushed a unplanned change

* feat(Formatter): add a formatter class to add support to discord markdown, unix and etc

* chore: set exports for formatter class

* feat(AllGuildVoiceChannels): new type for all guild based voice channels

* feat(VoiceChannel#members): returns a list of members in the voice channel

* chore: small patch to voice channel methods

* feat(GuildBans): addition of bans into seyfert

* chore: request changes - incomplete

* fix(BanShorter#bulkCreate): fix method return, bulk-bans returns stats

* feat(GuildBan#methods): all methods added on the guild ban structure

* chore: missed some, here pushed them

* chore: requested change

---------

Co-authored-by: NotAditya69 <90441096+NotAditya69@users.noreply.github.com>
This commit is contained in:
Xeno 2024-06-10 13:48:54 +05:30 committed by GitHub
parent 37dfaef407
commit 390953082b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 1330 additions and 641 deletions

View File

@ -43,7 +43,8 @@
"noUselessConstructor": "off", "noUselessConstructor": "off",
"noThisInStatic": "off", "noThisInStatic": "off",
"noExcessiveCognitiveComplexity": "off", "noExcessiveCognitiveComplexity": "off",
"noVoid": "off" "noVoid": "off",
"noStaticOnlyClass": "off"
}, },
"a11y": { "a11y": {
"all": false "all": false

View File

@ -84,6 +84,8 @@ import type {
RESTPatchAPIGuildWidgetSettingsResult, RESTPatchAPIGuildWidgetSettingsResult,
RESTPostAPIAutoModerationRuleJSONBody, RESTPostAPIAutoModerationRuleJSONBody,
RESTPostAPIAutoModerationRuleResult, RESTPostAPIAutoModerationRuleResult,
RESTPostAPIGuildBulkBanJSONBody,
RESTPostAPIGuildBulkBanResult,
RESTPostAPIGuildChannelJSONBody, RESTPostAPIGuildChannelJSONBody,
RESTPostAPIGuildChannelResult, RESTPostAPIGuildChannelResult,
RESTPostAPIGuildEmojiJSONBody, RESTPostAPIGuildEmojiJSONBody,
@ -233,6 +235,11 @@ export interface GuildRoutes {
delete(args?: RestArguments<ProxyRequestMethod.Delete>): Promise<RESTDeleteAPIGuildBanResult>; delete(args?: RestArguments<ProxyRequestMethod.Delete>): Promise<RESTDeleteAPIGuildBanResult>;
}; };
}; };
'bulk-bans': {
post(
args: RestArguments<ProxyRequestMethod.Post, RESTPostAPIGuildBulkBanJSONBody>,
): Promise<RESTPostAPIGuildBulkBanResult>;
};
mfa: { mfa: {
post( post(
args: RestArguments<ProxyRequestMethod.Post, RESTPostAPIGuildsMFAJSONBody>, args: RestArguments<ProxyRequestMethod.Post, RESTPostAPIGuildsMFAJSONBody>,

24
src/cache/index.ts vendored
View File

@ -14,6 +14,7 @@ import { StageInstances } from './resources/stage-instances';
import { Stickers } from './resources/stickers'; import { Stickers } from './resources/stickers';
import { Threads } from './resources/threads'; import { Threads } from './resources/threads';
import { VoiceStates } from './resources/voice-states'; import { VoiceStates } from './resources/voice-states';
import { Bans } from './resources/bans';
import { ChannelType, GatewayIntentBits, type GatewayDispatchPayload } from 'discord-api-types/v10'; import { ChannelType, GatewayIntentBits, type GatewayDispatchPayload } from 'discord-api-types/v10';
import type { InternalOptions, UsingClient } from '../commands'; import type { InternalOptions, UsingClient } from '../commands';
@ -36,7 +37,8 @@ export type GuildRelated =
| 'presences' | 'presences'
| 'stageInstances' | 'stageInstances'
| 'overwrites' | 'overwrites'
| 'messages'; | 'messages'
| 'bans';
// ClientBased // ClientBased
export type NonGuildBased = 'users' | 'guilds'; export type NonGuildBased = 'users' | 'guilds';
@ -58,6 +60,8 @@ export type CachedEvents =
| 'GUILD_ROLE_CREATE' | 'GUILD_ROLE_CREATE'
| 'GUILD_ROLE_UPDATE' | 'GUILD_ROLE_UPDATE'
| 'GUILD_ROLE_DELETE' | 'GUILD_ROLE_DELETE'
| 'GUILD_BAN_ADD'
| 'GUILD_BAN_REMOVE'
| 'GUILD_EMOJIS_UPDATE' | 'GUILD_EMOJIS_UPDATE'
| 'GUILD_STICKERS_UPDATE' | 'GUILD_STICKERS_UPDATE'
| 'GUILD_MEMBER_ADD' | 'GUILD_MEMBER_ADD'
@ -93,6 +97,7 @@ export class Cache {
presences?: Presences; presences?: Presences;
stageInstances?: StageInstances; stageInstances?: StageInstances;
messages?: Messages; messages?: Messages;
bans?: Bans;
constructor( constructor(
public intents: number, public intents: number,
@ -144,6 +149,9 @@ export class Cache {
if (!this.disabledCache.includes('messages')) { if (!this.disabledCache.includes('messages')) {
this.messages = new Messages(this, client); this.messages = new Messages(this, client);
} }
if (!this.disabledCache.includes('bans')) {
this.bans = new Bans(this, client);
}
} }
/** @internal */ /** @internal */
@ -163,6 +171,7 @@ export class Cache {
this.threads?.__setClient(client); this.threads?.__setClient(client);
this.stageInstances?.__setClient(client); this.stageInstances?.__setClient(client);
this.messages?.__setClient(client); this.messages?.__setClient(client);
this.bans?.__setClient(client);
} }
flush(): ReturnCache<void> { flush(): ReturnCache<void> {
@ -206,6 +215,10 @@ export class Cache {
return this.hasIntent('DirectMessages'); return this.hasIntent('DirectMessages');
} }
get hasBansIntent() {
return this.hasIntent('GuildBans');
}
async bulkGet( async bulkGet(
keys: ( keys: (
| readonly [ | readonly [
@ -246,6 +259,7 @@ export class Cache {
case 'users': case 'users':
case 'guilds': case 'guilds':
case 'overwrites': case 'overwrites':
case 'bans':
case 'messages': case 'messages':
{ {
if (!allData[type]) { if (!allData[type]) {
@ -313,6 +327,7 @@ export class Cache {
case 'stageInstances': case 'stageInstances':
case 'emojis': case 'emojis':
case 'overwrites': case 'overwrites':
case 'bans':
case 'messages': case 'messages':
{ {
if (!this[type]?.filter(data, id, guildId)) continue; if (!this[type]?.filter(data, id, guildId)) continue;
@ -404,6 +419,7 @@ export class Cache {
case 'stageInstances': case 'stageInstances':
case 'emojis': case 'emojis':
case 'overwrites': case 'overwrites':
case 'bans':
case 'messages': case 'messages':
{ {
if (!this[type]?.filter(data, id, guildId)) continue; if (!this[type]?.filter(data, id, guildId)) continue;
@ -500,6 +516,12 @@ export class Cache {
case 'GUILD_ROLE_DELETE': case 'GUILD_ROLE_DELETE':
await this.roles?.remove(event.d.role_id, event.d.guild_id); await this.roles?.remove(event.d.role_id, event.d.guild_id);
break; break;
case 'GUILD_BAN_ADD':
await this.bans?.set(event.d.user.id, event.d.guild_id, event.d);
break;
case 'GUILD_BAN_REMOVE':
await this.bans?.remove(event.d.user.id, event.d.guild_id);
break;
case 'GUILD_EMOJIS_UPDATE': case 'GUILD_EMOJIS_UPDATE':
await this.emojis?.remove(await this.emojis?.keys(event.d.guild_id), event.d.guild_id); await this.emojis?.remove(await this.emojis?.keys(event.d.guild_id), event.d.guild_id);
await this.emojis?.set( await this.emojis?.set(

46
src/cache/resources/bans.ts vendored Normal file
View File

@ -0,0 +1,46 @@
import type { APIBan } from 'discord-api-types/v10';
import type { ReturnCache } from '../..';
import { fakePromise } from '../../common';
import { GuildBasedResource } from './default/guild-based';
import { GuildBan } from '../../structures/GuildBan';
export class Bans extends GuildBasedResource {
namespace = 'ban';
//@ts-expect-error
filter(data: APIBan, 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<GuildBan | undefined> {
return fakePromise(super.get(id, guild)).then(rawBan =>
rawBan ? new GuildBan(this.client, rawBan, guild) : undefined,
);
}
override bulk(ids: string[], guild: string): ReturnCache<GuildBan[]> {
return fakePromise(super.bulk(ids, guild)).then(
bans =>
bans
.map(rawBan => {
return rawBan ? new GuildBan(this.client, rawBan, guild) : undefined;
})
.filter(Boolean) as GuildBan[],
);
}
override values(guild: string): ReturnCache<GuildBan[]> {
return fakePromise(super.values(guild)).then(
bans =>
bans
.map(rawBan => {
return rawBan ? new GuildBan(this.client, rawBan, guild) : undefined;
})
.filter(Boolean) as GuildBan[],
);
}
}

View File

@ -10,7 +10,11 @@ import type {
RegisteredMiddlewares, RegisteredMiddlewares,
UsingClient, UsingClient,
} from '../commands'; } from '../commands';
import { IgnoreCommand, type InferWithPrefix, type MiddlewareContext } from '../commands/applications/shared'; import {
IgnoreCommand,
type InferWithPrefix,
type MiddlewareContext,
} from '../commands/applications/shared';
import { CommandHandler } from '../commands/handler'; import { CommandHandler } from '../commands/handler';
import { import {
ChannelShorter, ChannelShorter,
@ -33,8 +37,18 @@ import {
type MakeRequired, type MakeRequired,
} from '../common'; } from '../common';
import type { LocaleString, RESTPostAPIChannelMessageJSONBody } from 'discord-api-types/rest/v10'; import type {
import type { Awaitable, DeepPartial, IntentStrings, OmitInsert, PermissionStrings, When } from '../common/types/util'; LocaleString,
RESTPostAPIChannelMessageJSONBody,
} from 'discord-api-types/rest/v10';
import type {
Awaitable,
DeepPartial,
IntentStrings,
OmitInsert,
PermissionStrings,
When,
} from '../common/types/util';
import { ComponentHandler } from '../components/handler'; import { ComponentHandler } from '../components/handler';
import { LangsHandler } from '../langs/handler'; import { LangsHandler } from '../langs/handler';
import type { import type {
@ -45,8 +59,14 @@ import type {
ModalSubmitInteraction, ModalSubmitInteraction,
UserCommandInteraction, UserCommandInteraction,
} from '../structures'; } from '../structures';
import type { ComponentCommand, ComponentContext, ModalCommand, ModalContext } from '../components'; import type {
ComponentCommand,
ComponentContext,
ModalCommand,
ModalContext,
} from '../components';
import { promises } from 'node:fs'; import { promises } from 'node:fs';
import { BanShorter } from '../common/shorters/bans';
export class BaseClient { export class BaseClient {
rest!: ApiHandler; rest!: ApiHandler;
@ -63,6 +83,7 @@ export class BaseClient {
reactions = new ReactionShorter(this); reactions = new ReactionShorter(this);
emojis = new EmojiShorter(this); emojis = new EmojiShorter(this);
threads = new ThreadShorter(this); threads = new ThreadShorter(this);
bans = new BanShorter(this);
interactions = new InteractionShorter(this); interactions = new InteractionShorter(this);
debugger?: Logger; debugger?: Logger;
@ -79,7 +100,10 @@ export class BaseClient {
private _botId?: string; private _botId?: string;
middlewares?: Record<string, MiddlewareContext>; middlewares?: Record<string, MiddlewareContext>;
protected static assertString(value: unknown, message?: string): asserts value is string { protected static assertString(
value: unknown,
message?: string
): asserts value is string {
if (!(typeof value === 'string' && value !== '')) { if (!(typeof value === 'string' && value !== '')) {
throw new Error(message ?? 'Value is not a string'); throw new Error(message ?? 'Value is not a string');
} }
@ -99,43 +123,94 @@ export class BaseClient {
{ {
commands: { commands: {
defaults: { defaults: {
onRunError(context: CommandContext<any>, error: unknown): any { onRunError(
context.client.logger.fatal(`${context.command.name}.<onRunError>`, context.author.id, error); context: CommandContext<any>,
error: unknown
): any {
context.client.logger.fatal(
`${context.command.name}.<onRunError>`,
context.author.id,
error
);
}, },
onOptionsError(context: CommandContext<{}, never>, metadata: OnOptionsReturnObject): any { onOptionsError(
context.client.logger.fatal(`${context.command.name}.<onOptionsError>`, context.author.id, metadata); context: CommandContext<{}, never>,
metadata: OnOptionsReturnObject
): any {
context.client.logger.fatal(
`${context.command.name}.<onOptionsError>`,
context.author.id,
metadata
);
}, },
onMiddlewaresError(context: CommandContext<{}, never>, error: string): any { onMiddlewaresError(
context.client.logger.fatal(`${context.command.name}.<onMiddlewaresError>`, context.author.id, error); context: CommandContext<{}, never>,
error: string
): any {
context.client.logger.fatal(
`${context.command.name}.<onMiddlewaresError>`,
context.author.id,
error
);
}, },
onBotPermissionsFail(context: CommandContext<{}, never>, permissions: PermissionStrings): any { onBotPermissionsFail(
context: CommandContext<{}, never>,
permissions: PermissionStrings
): any {
context.client.logger.fatal( context.client.logger.fatal(
`${context.command.name}.<onBotPermissionsFail>`, `${context.command.name}.<onBotPermissionsFail>`,
context.author.id, context.author.id,
permissions, permissions
); );
}, },
onPermissionsFail(context: CommandContext<{}, never>, permissions: PermissionStrings): any { onPermissionsFail(
context: CommandContext<{}, never>,
permissions: PermissionStrings
): any {
context.client.logger.fatal( context.client.logger.fatal(
`${context.command.name}.<onPermissionsFail>`, `${context.command.name}.<onPermissionsFail>`,
context.author.id, context.author.id,
permissions, permissions
); );
}, },
onInternalError(client: UsingClient, command: Command, error?: unknown): any { onInternalError(
client.logger.fatal(`${command.name}.<onInternalError>`, error); client: UsingClient,
command: Command,
error?: unknown
): any {
client.logger.fatal(
`${command.name}.<onInternalError>`,
error
);
}, },
}, },
}, },
components: { components: {
defaults: { defaults: {
onRunError(context: ComponentContext, error: unknown): any { onRunError(
context.client.logger.fatal('ComponentCommand.<onRunError>', context.author.id, error); context: ComponentContext,
error: unknown
): any {
context.client.logger.fatal(
'ComponentCommand.<onRunError>',
context.author.id,
error
);
}, },
onMiddlewaresError(context: ComponentContext, error: string): any { onMiddlewaresError(
context.client.logger.fatal('ComponentCommand.<onMiddlewaresError>', context.author.id, error); context: ComponentContext,
error: string
): any {
context.client.logger.fatal(
'ComponentCommand.<onMiddlewaresError>',
context.author.id,
error
);
}, },
onInternalError(client: UsingClient, error?: unknown): any { onInternalError(
client: UsingClient,
error?: unknown
): any {
client.logger.fatal(error); client.logger.fatal(error);
}, },
}, },
@ -143,18 +218,32 @@ export class BaseClient {
modals: { modals: {
defaults: { defaults: {
onRunError(context: ModalContext, error: unknown): any { onRunError(context: ModalContext, error: unknown): any {
context.client.logger.fatal('ComponentCommand.<onRunError>', context.author.id, error); context.client.logger.fatal(
'ComponentCommand.<onRunError>',
context.author.id,
error
);
}, },
onMiddlewaresError(context: ModalContext, error: string): any { onMiddlewaresError(
context.client.logger.fatal('ComponentCommand.<onMiddlewaresError>', context.author.id, error); context: ModalContext,
error: string
): any {
context.client.logger.fatal(
'ComponentCommand.<onMiddlewaresError>',
context.author.id,
error
);
}, },
onInternalError(client: UsingClient, error?: unknown): any { onInternalError(
client: UsingClient,
error?: unknown
): any {
client.logger.fatal(error); client.logger.fatal(error);
}, },
}, },
}, },
} satisfies BaseClientOptions, } satisfies BaseClientOptions,
options, options
); );
} }
@ -163,7 +252,9 @@ export class BaseClient {
} }
get botId() { get botId() {
return this._botId ?? BaseClient.getBotIdFromToken(this.rest.options.token); return (
this._botId ?? BaseClient.getBotIdFromToken(this.rest.options.token)
);
} }
set applicationId(id: string) { set applicationId(id: string) {
@ -178,7 +269,13 @@ export class BaseClient {
return new Router(this.rest).createProxy(); return new Router(this.rest).createProxy();
} }
setServices({ rest, cache, langs, middlewares, handlers }: ServicesOptions) { setServices({
rest,
cache,
langs,
middlewares,
handlers,
}: ServicesOptions) {
if (rest) { if (rest) {
this.rest = rest; this.rest = rest;
} }
@ -187,7 +284,7 @@ export class BaseClient {
this.cache?.intents ?? 0, this.cache?.intents ?? 0,
cache?.adapter ?? this.cache?.adapter ?? new MemoryAdapter(), cache?.adapter ?? this.cache?.adapter ?? new MemoryAdapter(),
cache.disabledCache ?? this.cache?.disabledCache ?? [], cache.disabledCache ?? this.cache?.disabledCache ?? [],
this, this
); );
} }
if (middlewares) { if (middlewares) {
@ -199,7 +296,9 @@ export class BaseClient {
this.components = undefined; this.components = undefined;
} else if (typeof handlers.components === 'function') { } else if (typeof handlers.components === 'function') {
this.components ??= new ComponentHandler(this.logger, this); this.components ??= new ComponentHandler(this.logger, this);
this.components.setHandlers({ callback: handlers.components }); this.components.setHandlers({
callback: handlers.components,
});
} else { } else {
this.components = handlers.components; this.components = handlers.components;
} }
@ -227,7 +326,8 @@ export class BaseClient {
} }
if (langs) { if (langs) {
if (langs.default) this.langs!.defaultLang = langs.default; if (langs.default) this.langs!.defaultLang = langs.default;
if (langs.aliases) this.langs!.aliases = Object.entries(langs.aliases); if (langs.aliases)
this.langs!.aliases = Object.entries(langs.aliases);
} }
} }
@ -241,13 +341,20 @@ export class BaseClient {
} }
async start( async start(
options: Pick<DeepPartial<StartOptions>, 'langsDir' | 'commandsDir' | 'connection' | 'token' | 'componentsDir'> = { options: Pick<
DeepPartial<StartOptions>,
| 'langsDir'
| 'commandsDir'
| 'connection'
| 'token'
| 'componentsDir'
> = {
token: undefined, token: undefined,
langsDir: undefined, langsDir: undefined,
commandsDir: undefined, commandsDir: undefined,
connection: undefined, connection: undefined,
componentsDir: undefined, componentsDir: undefined,
}, }
) { ) {
await this.loadLangs(options.langsDir); await this.loadLangs(options.langsDir);
await this.loadCommands(options.commandsDir); await this.loadCommands(options.commandsDir);
@ -278,21 +385,32 @@ export class BaseClient {
} }
shouldUploadCommands(cachePath: string) { shouldUploadCommands(cachePath: string) {
return this.commands!.shouldUpload(cachePath).then(async should => { return this.commands!.shouldUpload(cachePath).then(async (should) => {
if (should) await promises.writeFile(cachePath, JSON.stringify(this.commands!.values.map(x => x.toJSON()))); if (should)
await promises.writeFile(
cachePath,
JSON.stringify(this.commands!.values.map((x) => x.toJSON()))
);
return should; return should;
}); });
} }
async uploadCommands(applicationId?: string) { async uploadCommands(applicationId?: string) {
applicationId ??= await this.getRC().then(x => x.applicationId ?? this.applicationId); applicationId ??= await this.getRC().then(
(x) => x.applicationId ?? this.applicationId
);
BaseClient.assertString(applicationId, 'applicationId is not a string'); BaseClient.assertString(applicationId, 'applicationId is not a string');
const commands = this.commands!.values; const commands = this.commands!.values;
const filter = filterSplit(commands, command => !command.guildId); const filter = filterSplit(commands, (command) => !command.guildId);
await this.proxy.applications(applicationId).commands.put({ await this.proxy.applications(applicationId).commands.put({
body: filter.expect.filter(cmd => !('ignore' in cmd) || cmd.ignore !== IgnoreCommand.Slash).map(x => x.toJSON()), body: filter.expect
.filter(
(cmd) =>
!('ignore' in cmd) || cmd.ignore !== IgnoreCommand.Slash
)
.map((x) => x.toJSON()),
}); });
const guilds = new Set<string>(); const guilds = new Set<string>();
@ -309,14 +427,19 @@ export class BaseClient {
.guilds(guild) .guilds(guild)
.commands.put({ .commands.put({
body: filter.never body: filter.never
.filter(cmd => cmd.guildId?.includes(guild) && (!('ignore' in cmd) || cmd.ignore !== IgnoreCommand.Slash)) .filter(
.map(x => x.toJSON()), (cmd) =>
cmd.guildId?.includes(guild) &&
(!('ignore' in cmd) ||
cmd.ignore !== IgnoreCommand.Slash)
)
.map((x) => x.toJSON()),
}); });
} }
} }
async loadCommands(dir?: string) { async loadCommands(dir?: string) {
dir ??= await this.getRC().then(x => x.commands); dir ??= await this.getRC().then((x) => x.commands);
if (dir && this.commands) { if (dir && this.commands) {
await this.commands.load(dir, this); await this.commands.load(dir, this);
this.logger.info('CommandHandler loaded'); this.logger.info('CommandHandler loaded');
@ -324,7 +447,7 @@ export class BaseClient {
} }
async loadComponents(dir?: string) { async loadComponents(dir?: string) {
dir ??= await this.getRC().then(x => x.components); dir ??= await this.getRC().then((x) => x.components);
if (dir && this.components) { if (dir && this.components) {
await this.components.load(dir); await this.components.load(dir);
this.logger.info('ComponentHandler loaded'); this.logger.info('ComponentHandler loaded');
@ -332,7 +455,7 @@ export class BaseClient {
} }
async loadLangs(dir?: string) { async loadLangs(dir?: string) {
dir ??= await this.getRC().then(x => x.langs); dir ??= await this.getRC().then((x) => x.langs);
if (dir && this.langs) { if (dir && this.langs) {
await this.langs.load(dir); await this.langs.load(dir);
this.logger.info('LangsHandler loaded'); this.logger.info('LangsHandler loaded');
@ -344,23 +467,37 @@ export class BaseClient {
} }
async getRC< async getRC<
T extends InternalRuntimeConfigHTTP | InternalRuntimeConfig = InternalRuntimeConfigHTTP | InternalRuntimeConfig, T extends InternalRuntimeConfigHTTP | InternalRuntimeConfig =
| InternalRuntimeConfigHTTP
| InternalRuntimeConfig
>() { >() {
const seyfertConfig = (BaseClient._seyfertConfig || const seyfertConfig = (BaseClient._seyfertConfig ||
(await this.options.getRC?.()) || (await this.options.getRC?.()) ||
(await magicImport(join(process.cwd(), 'seyfert.config.js')).then(x => x.default ?? x))) as T; (await magicImport(join(process.cwd(), 'seyfert.config.js')).then(
(x) => x.default ?? x
))) as T;
const { locations, debug, ...env } = seyfertConfig; const { locations, debug, ...env } = seyfertConfig;
const obj = { const obj = {
debug: !!debug, debug: !!debug,
...env, ...env,
templates: locations.templates ? join(process.cwd(), locations.base, locations.templates) : undefined, templates: locations.templates
langs: locations.langs ? join(process.cwd(), locations.output, locations.langs) : undefined, ? join(process.cwd(), locations.base, locations.templates)
: undefined,
langs: locations.langs
? join(process.cwd(), locations.output, locations.langs)
: undefined,
events: events:
'events' in locations && locations.events ? join(process.cwd(), locations.output, locations.events) : undefined, 'events' in locations && locations.events
components: locations.components ? join(process.cwd(), locations.output, locations.components) : undefined, ? join(process.cwd(), locations.output, locations.events)
commands: locations.commands ? join(process.cwd(), locations.output, locations.commands) : undefined, : undefined,
components: locations.components
? join(process.cwd(), locations.output, locations.components)
: undefined,
commands: locations.commands
? join(process.cwd(), locations.output, locations.commands)
: undefined,
base: join(process.cwd(), locations.base), base: join(process.cwd(), locations.base),
output: join(process.cwd(), locations.output), output: join(process.cwd(), locations.output),
}; };
@ -379,7 +516,7 @@ export interface BaseClientOptions {
| MessageCommandInteraction<boolean> | MessageCommandInteraction<boolean>
| ComponentInteraction | ComponentInteraction
| ModalSubmitInteraction | ModalSubmitInteraction
| When<InferWithPrefix, Message, never>, | When<InferWithPrefix, Message, never>
) => {}; ) => {};
globalMiddlewares?: readonly (keyof RegisteredMiddlewares)[]; globalMiddlewares?: readonly (keyof RegisteredMiddlewares)[];
commands?: { commands?: {
@ -410,7 +547,10 @@ export interface BaseClientOptions {
onAfterRun?: ModalCommand['onAfterRun']; onAfterRun?: ModalCommand['onAfterRun'];
}; };
}; };
allowedMentions?: Omit<NonNullable<RESTPostAPIChannelMessageJSONBody['allowed_mentions']>, 'parse'> & { allowedMentions?: Omit<
NonNullable<RESTPostAPIChannelMessageJSONBody['allowed_mentions']>,
'parse'
> & {
parse?: ('everyone' | 'roles' | 'users')[]; //nice types, d-api parse?: ('everyone' | 'roles' | 'users')[]; //nice types, d-api
}; };
getRC?(): Awaitable<InternalRuntimeConfig | InternalRuntimeConfigHTTP>; getRC?(): Awaitable<InternalRuntimeConfig | InternalRuntimeConfigHTTP>;
@ -455,11 +595,17 @@ export type InternalRuntimeConfigHTTP = Omit<
MakeRequired<RC, 'publicKey' | 'port' | 'applicationId'>, MakeRequired<RC, 'publicKey' | 'port' | 'applicationId'>,
'intents' | 'locations' 'intents' | 'locations'
> & { locations: Omit<RC['locations'], 'events'> }; > & { locations: Omit<RC['locations'], 'events'> };
export type RuntimeConfigHTTP = Omit<MakeRequired<RC, 'publicKey' | 'applicationId'>, 'intents' | 'locations'> & { export type RuntimeConfigHTTP = Omit<
MakeRequired<RC, 'publicKey' | 'applicationId'>,
'intents' | 'locations'
> & {
locations: Omit<RC['locations'], 'events'>; locations: Omit<RC['locations'], 'events'>;
}; };
export type InternalRuntimeConfig = Omit<MakeRequired<RC, 'intents'>, 'publicKey' | 'port'>; export type InternalRuntimeConfig = Omit<
MakeRequired<RC, 'intents'>,
'publicKey' | 'port'
>;
export type RuntimeConfig = OmitInsert< export type RuntimeConfig = OmitInsert<
InternalRuntimeConfig, InternalRuntimeConfig,
'intents', 'intents',
@ -476,7 +622,9 @@ export interface ServicesOptions {
middlewares?: Record<string, MiddlewareContext>; middlewares?: Record<string, MiddlewareContext>;
handlers?: { handlers?: {
components?: ComponentHandler | ComponentHandler['callback']; components?: ComponentHandler | ComponentHandler['callback'];
commands?: CommandHandler | Parameters<CommandHandler['setHandlers']>[0]; commands?:
| CommandHandler
| Parameters<CommandHandler['setHandlers']>[0];
langs?: LangsHandler | LangsHandler['callback']; langs?: LangsHandler | LangsHandler['callback'];
}; };
} }

View File

@ -26,13 +26,26 @@ import {
} from '../common'; } from '../common';
import { EventHandler } from '../events'; import { EventHandler } from '../events';
import { ClientUser } from '../structures'; import { ClientUser } from '../structures';
import { ShardManager, properties, type ShardManagerOptions } from '../websocket'; import {
ShardManager,
properties,
type ShardManagerOptions,
} from '../websocket';
import { MemberUpdateHandler } from '../websocket/discord/events/memberUpdate'; import { MemberUpdateHandler } from '../websocket/discord/events/memberUpdate';
import { PresenceUpdateHandler } from '../websocket/discord/events/presenceUpdate'; import { PresenceUpdateHandler } from '../websocket/discord/events/presenceUpdate';
import type { BaseClientOptions, InternalRuntimeConfig, ServicesOptions, StartOptions } from './base'; import type {
BaseClientOptions,
InternalRuntimeConfig,
ServicesOptions,
StartOptions,
} from './base';
import { BaseClient } from './base'; import { BaseClient } from './base';
import { onInteractionCreate } from './oninteractioncreate'; import { onInteractionCreate } from './oninteractioncreate';
import { defaultArgsParser, defaultOptionsParser, onMessageCreate } from './onmessagecreate'; import {
defaultArgsParser,
defaultOptionsParser,
onMessageCreate,
} from './onmessagecreate';
import { Collectors } from './collectors'; import { Collectors } from './collectors';
let parentPort: import('node:worker_threads').MessagePort; let parentPort: import('node:worker_threads').MessagePort;
@ -42,7 +55,10 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
gateway!: ShardManager; gateway!: ShardManager;
me!: If<Ready, ClientUser>; me!: If<Ready, ClientUser>;
declare options: Omit<ClientOptions, 'commands'> & { declare options: Omit<ClientOptions, 'commands'> & {
commands: MakeRequired<NonNullable<ClientOptions['commands']>, 'argsParser' | 'optionsParser'>; commands: MakeRequired<
NonNullable<ClientOptions['commands']>,
'argsParser' | 'optionsParser'
>;
}; };
memberUpdateHandler = new MemberUpdateHandler(); memberUpdateHandler = new MemberUpdateHandler();
presenceUpdateHandler = new PresenceUpdateHandler(); presenceUpdateHandler = new PresenceUpdateHandler();
@ -54,11 +70,14 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
this.options = MergeOptions( this.options = MergeOptions(
{ {
commands: { commands: {
argsParser: defaultArgsParser, argsParser:
optionsParser: defaultOptionsParser, options?.commands?.argsParser ?? defaultArgsParser,
optionsParser:
options?.commands?.optionsParser ??
defaultOptionsParser,
}, },
} satisfies ClientOptions, } satisfies ClientOptions,
this.options, this.options
); );
} }
@ -96,43 +115,60 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
} }
async loadEvents(dir?: string) { async loadEvents(dir?: string) {
dir ??= await this.getRC().then(x => x.events); dir ??= await this.getRC().then((x) => x.events);
if (dir && this.events) { if (dir && this.events) {
await this.events.load(dir); await this.events.load(dir);
this.logger.info('EventHandler loaded'); this.logger.info('EventHandler loaded');
} }
} }
protected async execute(options: { token?: string; intents?: number } = {}) { protected async execute(
options: { token?: string; intents?: number } = {}
) {
await super.execute(options); await super.execute(options);
const worker_threads = lazyLoadPackage<typeof import('node:worker_threads')>('node:worker_threads'); const worker_threads = lazyLoadPackage<
typeof import('node:worker_threads')
>('node:worker_threads');
if (worker_threads?.parentPort) { if (worker_threads?.parentPort) {
parentPort = worker_threads.parentPort; parentPort = worker_threads.parentPort;
} }
if (worker_threads?.workerData?.__USING_WATCHER__) { if (worker_threads?.workerData?.__USING_WATCHER__) {
parentPort?.on('message', (data: WatcherPayload | WatcherSendToShard) => { parentPort?.on(
switch (data.type) { 'message',
case 'PAYLOAD': (data: WatcherPayload | WatcherSendToShard) => {
this.gateway.options.handlePayload(data.shardId, data.payload); switch (data.type) {
break; case 'PAYLOAD':
case 'SEND_TO_SHARD': this.gateway.options.handlePayload(
this.gateway.send(data.shardId, data.payload); data.shardId,
break; data.payload
);
break;
case 'SEND_TO_SHARD':
this.gateway.send(data.shardId, data.payload);
break;
}
} }
}); );
} else { } else {
await this.gateway.spawnShards(); await this.gateway.spawnShards();
} }
} }
async start(options: Omit<DeepPartial<StartOptions>, 'httpConnection'> = {}, execute = true) { async start(
options: Omit<DeepPartial<StartOptions>, 'httpConnection'> = {},
execute = true
) {
await super.start(options); await super.start(options);
await this.loadEvents(options.eventsDir); await this.loadEvents(options.eventsDir);
const { token: tokenRC, intents: intentsRC, debug: debugRC } = await this.getRC<InternalRuntimeConfig>(); const {
token: tokenRC,
intents: intentsRC,
debug: debugRC,
} = await this.getRC<InternalRuntimeConfig>();
const token = options?.token ?? tokenRC; const token = options?.token ?? tokenRC;
const intents = options?.connection?.intents ?? intentsRC; const intents = options?.connection?.intents ?? intentsRC;
@ -149,9 +185,14 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
presence: this.options?.presence, presence: this.options?.presence,
debug: debugRC, debug: debugRC,
shardStart: this.options?.shards?.start, shardStart: this.options?.shards?.start,
shardEnd: this.options?.shards?.end ?? this.options?.shards?.total, shardEnd:
totalShards: this.options?.shards?.total ?? this.options?.shards?.end, this.options?.shards?.end ?? this.options?.shards?.total,
properties: { ...properties, ...this.options?.gateway?.properties }, totalShards:
this.options?.shards?.total ?? this.options?.shards?.end,
properties: {
...properties,
...this.options?.gateway?.properties,
},
compress: this.options?.gateway?.compress, compress: this.options?.gateway?.compress,
}); });
} }
@ -173,29 +214,59 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
if (!this.memberUpdateHandler.check(packet.d)) { if (!this.memberUpdateHandler.check(packet.d)) {
return; return;
} }
await this.events?.execute(packet.t, packet, this as Client<true>, shardId); await this.events?.execute(
packet.t,
packet,
this as Client<true>,
shardId
);
break; break;
case 'PRESENCE_UPDATE': case 'PRESENCE_UPDATE':
if (!this.presenceUpdateHandler.check(packet.d as any)) { if (!this.presenceUpdateHandler.check(packet.d as any)) {
return; return;
} }
await this.events?.execute(packet.t, packet, this as Client<true>, shardId); await this.events?.execute(
packet.t,
packet,
this as Client<true>,
shardId
);
break; break;
case 'GUILD_CREATE': { case 'GUILD_CREATE': {
if (this.__handleGuilds?.has(packet.d.id)) { if (this.__handleGuilds?.has(packet.d.id)) {
this.__handleGuilds.delete(packet.d.id); this.__handleGuilds.delete(packet.d.id);
if (!this.__handleGuilds.size && [...this.gateway.values()].every(shard => shard.data.session_id)) { if (
await this.events?.runEvent('BOT_READY', this, this.me, -1); !this.__handleGuilds.size &&
[...this.gateway.values()].every(
(shard) => shard.data.session_id
)
) {
await this.events?.runEvent(
'BOT_READY',
this,
this.me,
-1
);
} }
if (!this.__handleGuilds.size) delete this.__handleGuilds; if (!this.__handleGuilds.size) delete this.__handleGuilds;
return this.cache.onPacket(packet); return this.cache.onPacket(packet);
} }
await this.events?.execute(packet.t, packet, this as Client<true>, shardId); await this.events?.execute(
packet.t,
packet,
this as Client<true>,
shardId
);
break; break;
} }
//rest of the events //rest of the events
default: { default: {
await this.events?.execute(packet.t, packet, this as Client<true>, shardId); await this.events?.execute(
packet.t,
packet,
this as Client<true>,
shardId
);
switch (packet.t) { switch (packet.t) {
case 'INTERACTION_CREATE': case 'INTERACTION_CREATE':
await onInteractionCreate(this, packet.d, shardId); await onInteractionCreate(this, packet.d, shardId);
@ -209,19 +280,36 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
} }
this.botId = packet.d.user.id; this.botId = packet.d.user.id;
this.applicationId = packet.d.application.id; this.applicationId = packet.d.application.id;
this.me = new ClientUser(this, packet.d.user, packet.d.application) as never; this.me = new ClientUser(
this,
packet.d.user,
packet.d.application
) as never;
if ( if (
!( !(
this.__handleGuilds?.size && this.__handleGuilds?.size &&
(this.gateway.options.intents & GatewayIntentBits.Guilds) === GatewayIntentBits.Guilds (this.gateway.options.intents &
GatewayIntentBits.Guilds) ===
GatewayIntentBits.Guilds
) )
) { ) {
if ([...this.gateway.values()].every(shard => shard.data.session_id)) { if (
await this.events?.runEvent('BOT_READY', this, this.me, -1); [...this.gateway.values()].every(
(shard) => shard.data.session_id
)
) {
await this.events?.runEvent(
'BOT_READY',
this,
this.me,
-1
);
} }
delete this.__handleGuilds; delete this.__handleGuilds;
} }
this.debugger?.debug(`#${shardId}[${packet.d.user.username}](${this.botId}) is online...`); this.debugger?.debug(
`#${shardId}[${packet.d.user.username}](${this.botId}) is online...`
);
break; break;
} }
break; break;
@ -243,15 +331,21 @@ export interface ClientOptions extends BaseClientOptions {
}; };
commands?: BaseClientOptions['commands'] & { commands?: BaseClientOptions['commands'] & {
prefix?: (message: Message) => Promise<string[]> | string[]; prefix?: (message: Message) => Promise<string[]> | string[];
deferReplyResponse?: (ctx: CommandContext) => Parameters<Message['write']>[0]; deferReplyResponse?: (
ctx: CommandContext
) => Parameters<Message['write']>[0];
reply?: (ctx: CommandContext) => boolean; reply?: (ctx: CommandContext) => boolean;
argsParser?: (content: string, command: SubCommand | Command, message: Message) => Record<string, string>; argsParser?: (
content: string,
command: SubCommand | Command,
message: Message
) => Record<string, string>;
optionsParser?: ( optionsParser?: (
self: UsingClient, self: UsingClient,
command: Command | SubCommand, command: Command | SubCommand,
message: GatewayMessageCreateDispatchData, message: GatewayMessageCreateDispatchData,
args: Partial<Record<string, string>>, args: Partial<Record<string, string>>,
resolved: MakeRequired<ContextOptionsResolved>, resolved: MakeRequired<ContextOptionsResolved>
) => Awaitable<{ ) => Awaitable<{
errors: { errors: {
name: string; name: string;

View File

@ -1,8 +1,9 @@
export * from './it/constants'; export * from './it/constants';
export * from './it/utils'; export * from './it/utils';
//
export * from './it/colors'; export * from './it/colors';
export * from './it/logger'; export * from './it/logger';
export * from './it/formatter';
//
export * from './shorters/channels'; export * from './shorters/channels';
export * from './shorters/emojis'; export * from './shorters/emojis';
export * from './shorters/guilds'; export * from './shorters/guilds';
@ -15,6 +16,7 @@ export * from './shorters/users';
export * from './shorters/threads'; export * from './shorters/threads';
export * from './shorters/webhook'; export * from './shorters/webhook';
export * from './shorters/interaction'; export * from './shorters/interaction';
//
export * from './types/options'; export * from './types/options';
export * from './types/resolvables'; export * from './types/resolvables';
export * from './types/util'; export * from './types/util';

203
src/common/it/formatter.ts Normal file
View File

@ -0,0 +1,203 @@
/**
* Represents heading levels.
*/
export enum HeadingLevel {
/**
* Represents a level 1 heading. (#)
*/
H1 = 1,
/**
* Represents a level 2 heading. (##)
*/
H2 = 2,
/**
* Represents a level 3 heading. (###)
*/
H3 = 3,
}
/**
* Represents timestamp styles.
*/
export enum TimestampStyle {
/**
* Represents a short timestamp style.
*/
ShortTime = 't',
/**
* Represents a long timestamp style.
*/
LongTime = 'T',
/**
* Represents a short date style.
*/
ShortDate = 'd',
/**
* Represents a long date style.
*/
LongDate = 'D',
/**
* Represents a short time style.
*/
ShortDateTime = 'f',
/**
* Represents a long time style.
*/
LongDateTime = 'F',
/**
* Represents a relative time style.
*/
RelativeTime = 'R',
}
/**
* Represents a message link.
*/
type MessageLink = `https://discord.com/channels/${string}/${string}/${string}`;
/**
* Represents a timestamp.
*/
type Timestamp = `<t:${number}:${TimestampStyle}>`;
/**
* Represents a formatter utility for formatting content.
*/
export class Formatter {
/**
* Formats a code block.
* @param content The content of the code block.
* @param language The language of the code block. Defaults to 'txt'.
* @returns The formatted code block.
*/
static codeBlock(content: string, language = 'txt'): string {
return `\`\`\`${language}\n${content}\n\`\`\``;
}
/**
* Formats content into inline code.
* @param content The content to format.
* @returns The formatted content.
*/
static inlineCode(content: string): `\`${string}\`` {
return `\`${content}\``;
}
/**
* Formats content into bold text.
* @param content The content to format.
* @returns The formatted content.
*/
static bold(content: string): `**${string}**` {
return `**${content}**`;
}
/**
* Formats content into italic text.
* @param content The content to format.
* @returns The formatted content.
*/
static italic(content: string): `*${string}*` {
return `*${content}*`;
}
/**
* Formats content into underlined text.
* @param content The content to format.
* @returns The formatted content.
*/
static underline(content: string): `__${string}__` {
return `__${content}__`;
}
/**
* Formats content into strikethrough text.
* @param content The content to format.
* @returns The formatted content.
*/
static strikeThrough(content: string): `~~${string}~~` {
return `~~${content}~~`;
}
/**
* Formats content into a hyperlink.
* @param content The content to format.
* @param url The URL to hyperlink to.
* @returns The formatted content.
*/
static hyperlink(content: string, url: string): `[${string}](${string})` {
return `[${content}](${url})`;
}
/**
* Formats content into a spoiler.
* @param content The content to format.
* @returns The formatted content.
*/
static spoiler(content: string): `||${string}||` {
return `||${content}||`;
}
/**
* Formats content into a quote.
* @param content The content to format.
* @returns The formatted content.
*/
static blockQuote(content: string): string {
return `>>> ${content}`;
}
/**
* Formats content into a quote.
* @param content The content to format.
* @returns The formatted content.
*/
static quote(content: string): string {
return `> ${content}`;
}
/**
* Formats a message link.
* @param guildId The ID of the guild.
* @param channelId The ID of the channel.
* @param messageId The ID of the message.
* @returns The formatted message link.
*/
static messageLink(guildId: string, channelId: string, messageId: string): MessageLink {
return `https://discord.com/channels/${guildId}/${channelId}/${messageId}`;
}
/**
* Formats a header.
* @param content The content of the header.
* @param level The level of the header. Defaults to 1.
* @returns The formatted header.
*/
static header(content: string, level: HeadingLevel = HeadingLevel.H1): string {
return `${'#'.repeat(level)} ${content}`;
}
/**
* Formats a list.
* @param items The items of the list.
* @param ordered Whether the list is ordered. Defaults to false.
* @returns The formatted list.
*/
static list(items: string[], ordered = false): string {
return items
.map((item, index) => {
return (ordered ? `${index + 1}. ` : '- ') + item;
})
.join('\n');
}
/**
* Formats the given timestamp into discord unix timestamp format.
* @param timestamp The timestamp to format.
* @param style The style of the timestamp. Defaults to 't'.
* @returns The formatted timestamp.
*/
static timestamp(timestamp: Date, style: TimestampStyle = TimestampStyle.RelativeTime): Timestamp {
return `<t:${Math.floor(timestamp.getTime() / 1000)}:${style}>`;
}
}

View File

@ -0,0 +1,86 @@
import type {
APIBan,
RESTGetAPIGuildBansQuery,
RESTPostAPIGuildBulkBanJSONBody,
RESTPutAPIGuildBanJSONBody,
} from 'discord-api-types/v10';
import { BaseShorter } from './base';
import { GuildBan } from '../../structures/GuildBan';
export class BanShorter extends BaseShorter {
/**
* Bulk creates bans in the guild.
* @param guildId The ID of the guild.
* @param body The request body for bulk banning members.
* @param reason The reason for bulk banning members.
*/
async bulkCreate(guildId: string, body: RESTPostAPIGuildBulkBanJSONBody, reason?: string) {
const bans = await this.client.proxy.guilds(guildId)['bulk-bans'].post({ reason, body });
for (const id of bans.banned_users) this.client.cache.members?.removeIfNI('GuildBans', id, guildId);
return bans;
}
/**
* Unbans a member from the guild.
* @param guildId The ID of the guild.
* @param memberId The ID of the member to unban.
* @param reason The reason for unbanning the member.
*/
async remove(guildId: string, memberId: string, reason?: string) {
await this.client.proxy.guilds(guildId).bans(memberId).delete({ reason });
}
/**
* Bans a member from the guild.
* @param guildId The ID of the guild.
* @param memberId The ID of the member to ban.
* @param body The request body for banning the member.
* @param reason The reason for banning the member.
*/
async create(guildId: string, memberId: string, body?: RESTPutAPIGuildBanJSONBody, reason?: string) {
await this.client.proxy.guilds(guildId).bans(memberId).put({ reason, body });
await this.client.cache.members?.removeIfNI('GuildBans', memberId, guildId);
}
/**
* Fetches a ban from the guild.
* @param guildId The ID of the guild.
* @param userId The ID of the user to fetch.
* @param force Whether to force fetching the ban from the API even if it exists in the cache.
* @returns A Promise that resolves to the fetched ban.
*/
async fetch(guildId: string, userId: string, force = false) {
let ban;
if (!force) {
ban = await this.client.cache.bans?.get(userId, guildId);
if (ban) return ban;
}
ban = await this.client.proxy.guilds(guildId).bans(userId).get();
await this.client.cache.members?.set(ban.user!.id, guildId, ban);
return new GuildBan(this.client, ban, guildId);
}
/**
* Lists bans in the guild based on the provided query.
* @param guildId The ID of the guild.
* @param query The query parameters for listing bans.
* @param force Whether to force listing bans from the API even if they exist in the cache.
* @returns A Promise that resolves to an array of listed bans.
*/
async list(guildId: string, query?: RESTGetAPIGuildBansQuery, force = false) {
let bans;
if (!force) {
bans = (await this.client.cache.bans?.values(guildId)) ?? [];
if (bans.length) return bans;
}
bans = await this.client.proxy.guilds(guildId).bans.get({
query,
});
await this.client.cache.bans?.set(
bans.map<[string, APIBan]>(x => [x.user!.id, x]),
guildId,
);
return bans.map(m => new GuildBan(this.client, m, guildId));
}
}

View File

@ -9,7 +9,7 @@ import {
} from './client/base'; } from './client/base';
import type { CustomEventsKeys, 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, Formatter } from './common';
// //
export { Collection, LimitedCollection } from './collection'; export { Collection, LimitedCollection } from './collection';
// //

View File

@ -10,6 +10,7 @@ import { Sticker } from './Sticker';
import { BaseChannel, WebhookGuildMethods } from './channels'; import { BaseChannel, WebhookGuildMethods } from './channels';
import { BaseGuild } from './extra/BaseGuild'; import { BaseGuild } from './extra/BaseGuild';
import type { DiscordBase } from './extra/DiscordBase'; import type { DiscordBase } from './extra/DiscordBase';
import { GuildBan } from './GuildBan';
export interface Guild extends ObjectToLower<Omit<APIGuild, 'stickers' | 'emojis' | 'roles'>>, DiscordBase {} export interface Guild extends ObjectToLower<Omit<APIGuild, 'stickers' | 'emojis' | 'roles'>>, DiscordBase {}
export class Guild<State extends StructStates = 'api'> extends (BaseGuild as unknown as ToClass< export class Guild<State extends StructStates = 'api'> extends (BaseGuild as unknown as ToClass<
@ -75,6 +76,7 @@ export class Guild<State extends StructStates = 'api'> extends (BaseGuild as unk
roles = GuildRole.methods({ client: this.client, guildId: this.id }); roles = GuildRole.methods({ client: this.client, guildId: this.id });
channels = BaseChannel.allMethods({ client: this.client, guildId: this.id }); channels = BaseChannel.allMethods({ client: this.client, guildId: this.id });
emojis = GuildEmoji.methods({ client: this.client, guildId: this.id }); emojis = GuildEmoji.methods({ client: this.client, guildId: this.id });
bans = GuildBan.methods({ client: this.client, guildId: this.id });
} }
/** Maximun custom guild emojis per level */ /** Maximun custom guild emojis per level */

View File

@ -0,0 +1,49 @@
import type { APIBan, RESTGetAPIGuildBansQuery } from 'discord-api-types/v10';
import type { UsingClient } from '../commands';
import type { MethodContext, ObjectToLower } from '../common';
import { DiscordBase } from './extra/DiscordBase';
import type { BanShorter } from '../common/shorters/bans';
export interface GuildBan extends DiscordBase, ObjectToLower<Omit<APIBan, 'id'>> {}
export class GuildBan extends DiscordBase {
constructor(
client: UsingClient,
data: APIBan,
readonly guildId: string,
) {
super(client, { ...data, id: data.user.id });
}
create(body?: Parameters<BanShorter['create']>[2], reason?: string) {
return this.client.bans.create(this.guildId, this.id, body, reason);
}
remove(reason?: string) {
return this.client.bans.remove(this.guildId, this.id, reason);
}
guild(force = false) {
return this.client.guilds.fetch(this.guildId, force);
}
fetch(force = false) {
return this.client.bans.fetch(this.guildId, this.id, force);
}
toString() {
return `<@${this.id}>`;
}
static methods({ client, guildId }: MethodContext<{ guildId: string }>) {
return {
fetch: (userId: string, force = false) => client.bans.fetch(guildId, userId, force),
list: (query?: RESTGetAPIGuildBansQuery, force = false) => client.bans.list(guildId, query, force),
create: (memberId: string, body?: Parameters<BanShorter['create']>[2], reason?: string) =>
client.bans.create(guildId, memberId, body, reason),
remove: (memberId: string, reason?: string) => client.bans.remove(guildId, memberId, reason),
bulkCreate: (body: Parameters<BanShorter['bulkCreate']>[1], reason?: string) =>
client.bans.bulkCreate(guildId, body, reason),
};
}
}

View File

@ -41,7 +41,7 @@ import type { GuildMember } from './GuildMember';
import type { GuildRole } from './GuildRole'; import type { GuildRole } from './GuildRole';
import { DiscordBase } from './extra/DiscordBase'; import { DiscordBase } from './extra/DiscordBase';
import { channelLink } from './extra/functions'; import { channelLink } from './extra/functions';
import type { RawFile } from '..'; import { Collection, type RawFile } from '..';
export class BaseChannel<T extends ChannelType> extends DiscordBase<APIChannelBase<ChannelType>> { export class BaseChannel<T extends ChannelType> extends DiscordBase<APIChannelBase<ChannelType>> {
declare type: T; declare type: T;
@ -216,9 +216,15 @@ export class MessagesMethods extends DiscordBase {
return this.client.channels.typing(this.id); return this.client.channels.typing(this.id);
} }
messages = MessagesMethods.messages({ client: this.client, channelId: this.id }); messages = MessagesMethods.messages({
client: this.client,
channelId: this.id,
});
pins = MessagesMethods.pins({ client: this.client, channelId: this.id }); pins = MessagesMethods.pins({ client: this.client, channelId: this.id });
reactions = MessagesMethods.reactions({ client: this.client, channelId: this.id }); reactions = MessagesMethods.reactions({
client: this.client,
channelId: this.id,
});
static messages(ctx: MethodContext<{ channelId: string }>) { static messages(ctx: MethodContext<{ channelId: string }>) {
return { return {
@ -265,7 +271,10 @@ export class MessagesMethods extends DiscordBase {
embeds: body.embeds?.map(x => (x instanceof Embed ? x.toJSON() : x)) ?? undefined, embeds: body.embeds?.map(x => (x instanceof Embed ? x.toJSON() : x)) ?? undefined,
attachments: attachments:
'attachments' in body 'attachments' in body
? body.attachments?.map((x, i) => ({ id: i, ...resolveAttachment(x) })) ?? undefined ? body.attachments?.map((x, i) => ({
id: i,
...resolveAttachment(x),
})) ?? undefined
: (files?.map((x, id) => ({ : (files?.map((x, id) => ({
id, id,
filename: x.name, filename: x.name,
@ -378,10 +387,26 @@ export class VoiceChannelMethods extends DiscordBase {
const filter = states.filter(state => state.channelId === this.id); const filter = states.filter(state => state.channelId === this.id);
return filter; return filter;
} }
public async members(force?: boolean) {
const collection = new Collection<string, GuildMember>();
const states = await this.states();
for (const state of states) {
const member = await state.member(force);
collection.set(member.id, member);
}
return collection;
}
} }
export class WebhookGuildMethods extends DiscordBase { export class WebhookGuildMethods extends DiscordBase {
webhooks = WebhookGuildMethods.guild({ client: this.client, guildId: this.id }); webhooks = WebhookGuildMethods.guild({
client: this.client,
guildId: this.id,
});
static guild(ctx: MethodContext<{ guildId: string }>) { static guild(ctx: MethodContext<{ guildId: string }>) {
return { return {
@ -391,7 +416,10 @@ export class WebhookGuildMethods extends DiscordBase {
} }
export class WebhookChannelMethods extends DiscordBase { export class WebhookChannelMethods extends DiscordBase {
webhooks = WebhookChannelMethods.channel({ client: this.client, channelId: this.id }); webhooks = WebhookChannelMethods.channel({
client: this.client,
channelId: this.id,
});
static channel(ctx: MethodContext<{ channelId: string }>) { static channel(ctx: MethodContext<{ channelId: string }>) {
return { return {
@ -555,6 +583,7 @@ export type AllGuildChannels =
export type AllTextableChannels = TextGuildChannel | VoiceChannel | DMChannel | NewsChannel | ThreadChannel; export type AllTextableChannels = TextGuildChannel | VoiceChannel | DMChannel | NewsChannel | ThreadChannel;
export type AllGuildTextableChannels = TextGuildChannel | VoiceChannel | NewsChannel | ThreadChannel; export type AllGuildTextableChannels = TextGuildChannel | VoiceChannel | NewsChannel | ThreadChannel;
export type AllGuildVoiceChannels = VoiceChannel | StageChannel;
export type AllChannels = export type AllChannels =
| BaseChannel<ChannelType> | BaseChannel<ChannelType>