seyfert/packages/biscuit/Session.ts
2022-07-13 14:10:45 -03:00

360 lines
12 KiB
TypeScript

import type {
ApplicationCommandPermissionTypes,
AtLeastOne,
DiscordApplicationCommand,
DiscordApplicationCommandOption,
DiscordGetGatewayBot,
DiscordGuildApplicationCommandPermissions,
GatewayBot,
GatewayIntents,
Localization,
} from "../discordeno/mod.ts";
import type { DiscordGatewayPayload, Shard } from "../discordeno/mod.ts";
import type { Events } from "./Actions.ts";
import type { PermissionResolvable } from "./structures/Permissions.ts";
import { Permissions } from "./structures/Permissions.ts";
import { Snowflake } from "./Snowflake.ts";
import { EventEmitter } from "./util/EventEmmiter.ts";
import {
ApplicationCommandTypes,
createGatewayManager,
createRestManager,
getBotIdFromToken,
} from "../discordeno/mod.ts";
import * as Routes from "./Routes.ts";
import * as Actions from "./Actions.ts";
export type DiscordRawEventHandler = (shard: Shard, data: DiscordGatewayPayload) => unknown;
// INTERACTIONS
/**
* @link https://discord.com/developers/docs/interactions/application-commands#endpoints-json-params
*/
export interface CreateApplicationCommand {
name: string;
nameLocalizations?: Localization;
description: string;
descriptionLocalizations?: Localization;
type?: ApplicationCommandTypes;
options?: DiscordApplicationCommandOption[];
defaultMemberPermissions?: PermissionResolvable;
dmPermission?: boolean;
}
/**
* @link https://discord.com/developers/docs/interactions/application-commands#endpoints-json-params
*/
export interface CreateContextApplicationCommand extends Omit<CreateApplicationCommand, "options"> {
type: ApplicationCommandTypes.Message | ApplicationCommandTypes.User;
}
/**
* @link https://discord.com/developers/docs/interactions/application-commands#endpoints-query-string-params
*/
export interface GetApplicationCommand {
guildId?: Snowflake;
withLocalizations?: boolean;
}
export interface UpsertApplicationCommands extends CreateApplicationCommand {
id?: Snowflake;
}
/**
* @link https://discord.com/developers/docs/interactions/application-commands#edit-application-command-permissions
*/
export interface ApplicationCommandPermissions {
id: Snowflake;
type: ApplicationCommandPermissionTypes;
permission: boolean;
}
/**
* @link https://discord.com/developers/docs/interactions/application-commands#edit-application-command-permissions
*/
export interface ApplicationCommandPermissions {
id: Snowflake;
type: ApplicationCommandPermissionTypes;
permission: boolean;
}
// END INTERACTIONS
export interface RestOptions {
secretKey?: string;
applicationId?: Snowflake;
}
export interface GatewayOptions {
botId?: Snowflake;
data?: GatewayBot;
}
export interface SessionOptions {
token: string;
rawHandler?: DiscordRawEventHandler;
intents?: GatewayIntents;
rest?: RestOptions;
gateway?: GatewayOptions;
}
/**
* Receives a Token, connects
* Most of the command implementations were adapted from Discordeno (https://github.com/discordeno/discordeno)
*/
export class Session extends EventEmitter {
options: SessionOptions;
rest: ReturnType<typeof createRestManager>;
gateway: ReturnType<typeof createGatewayManager>;
#botId: Snowflake;
#applicationId?: Snowflake;
set applicationId(id: Snowflake) {
this.#applicationId = id;
}
get applicationId() {
return this.#applicationId!;
}
set botId(id: Snowflake) {
this.#botId = id;
}
get botId() {
return this.#botId;
}
constructor(options: SessionOptions) {
super();
this.options = options;
const defHandler: DiscordRawEventHandler = (shard, data) => {
Actions.raw(this, shard.id, data);
if (!data.t || !data.d) {
return;
}
// deno-lint-ignore no-explicit-any
Actions[data.t as keyof typeof Actions]?.(this, shard.id, data.d as any);
};
this.rest = createRestManager({
token: this.options.token,
debug: (text) => {
// TODO: set this using the event emitter
super.rawListeners("debug")?.forEach((fn) => fn(text));
},
secretKey: this.options.rest?.secretKey ?? undefined,
});
this.gateway = createGatewayManager({
gatewayBot: this.options.gateway?.data ?? {} as GatewayBot, // TODO
gatewayConfig: {
token: this.options.token,
intents: this.options.intents,
},
handleDiscordPayload: this.options.rawHandler ?? defHandler,
});
this.#botId = getBotIdFromToken(options.token).toString();
}
override on<K extends keyof Events>(event: K, func: Events[K]): this;
override on<K extends string>(event: K, func: (...args: unknown[]) => unknown): this {
return super.on(event, func);
}
override off<K extends keyof Events>(event: K, func: Events[K]): this;
override off<K extends string>(event: K, func: (...args: unknown[]) => unknown): this {
return super.off(event, func);
}
override once<K extends keyof Events>(event: K, func: Events[K]): this;
override once<K extends string>(event: K, func: (...args: unknown[]) => unknown): this {
return super.once(event, func);
}
override emit<K extends keyof Events>(event: K, ...params: Parameters<Events[K]>): boolean;
override emit<K extends string>(event: K, ...params: unknown[]): boolean {
return super.emit(event, ...params);
}
createApplicationCommand(options: CreateApplicationCommand | CreateContextApplicationCommand, guildId: Snowflake) {
return this.rest.runMethod<DiscordApplicationCommand>(
this.rest,
"POST",
guildId
? Routes.GUILD_APPLICATION_COMMANDS(this.applicationId, guildId)
: Routes.APPLICATION_COMMANDS(this.applicationId),
this.isContextApplicationCommand(options)
? {
name: options.name,
name_localizations: options.nameLocalizations,
type: options.type,
}
: {
name: options.name,
name_localizations: options.nameLocalizations,
description: options.description,
description_localizations: options.descriptionLocalizations,
type: options.type,
options: options.options,
default_member_permissions: options.defaultMemberPermissions
? new Permissions(options.defaultMemberPermissions).bitfield.toString()
: undefined,
dm_permission: options.dmPermission,
},
);
}
deleteApplicationCommand(id: Snowflake, guildId?: Snowflake) {
return this.rest.runMethod<undefined>(
this.rest,
"DELETE",
guildId
? Routes.GUILD_APPLICATION_COMMANDS(this.applicationId, guildId, id)
: Routes.APPLICATION_COMMANDS(this.applicationId, id),
);
}
updateApplicationCommandPermissions(
guildId: Snowflake,
id: Snowflake,
bearerToken: string,
options: ApplicationCommandPermissions[],
) {
return this.rest.runMethod<DiscordGuildApplicationCommandPermissions>(
this.rest,
"PUT",
Routes.GUILD_APPLICATION_COMMANDS_PERMISSIONS(this.applicationId, guildId, id),
{
permissions: options,
},
{
headers: { authorization: `Bearer ${bearerToken}` },
},
);
}
fetchApplicationCommand(id: Snowflake, options?: GetApplicationCommand) {
return this.rest.runMethod<DiscordApplicationCommand>(
this.rest,
"GET",
options?.guildId
? Routes.GUILD_APPLICATION_COMMANDS_LOCALIZATIONS(
this.applicationId,
options.guildId,
id,
options?.withLocalizations,
)
: Routes.APPLICATION_COMMANDS(this.applicationId, id),
);
}
fetchApplicationCommandPermissions(guildId: Snowflake) {
return this.rest.runMethod<DiscordGuildApplicationCommandPermissions[]>(
this.rest,
"GET",
Routes.GUILD_APPLICATION_COMMANDS_PERMISSIONS(this.applicationId, guildId),
);
}
fetchApplicationCommandPermission(guildId: Snowflake, id: Snowflake) {
return this.rest.runMethod<DiscordGuildApplicationCommandPermissions>(
this.rest,
"GET",
Routes.GUILD_APPLICATION_COMMANDS_PERMISSIONS(this.applicationId, guildId, id),
);
}
upsertApplicationCommand(
id: Snowflake,
options: AtLeastOne<CreateApplicationCommand> | AtLeastOne<CreateContextApplicationCommand>,
guildId?: Snowflake,
) {
return this.rest.runMethod<DiscordApplicationCommand>(
this.rest,
"PATCH",
guildId
? Routes.GUILD_APPLICATION_COMMANDS(this.applicationId, guildId)
: Routes.APPLICATION_COMMANDS(this.applicationId, id),
this.isContextApplicationCommand(options)
? {
name: options.name,
type: options.type,
}
: {
name: options.name,
description: options.description,
type: options.type,
options: options.options,
},
);
}
upsertApplicationCommands(
options: Array<UpsertApplicationCommands | CreateContextApplicationCommand>,
guildId?: Snowflake,
) {
return this.rest.runMethod<DiscordApplicationCommand[]>(
this.rest,
"PUT",
guildId
? Routes.GUILD_APPLICATION_COMMANDS(this.applicationId, guildId)
: Routes.APPLICATION_COMMANDS(this.applicationId),
options.map((o) =>
this.isContextApplicationCommand(o)
? {
name: o.name,
type: o.type,
}
: {
name: o.name,
description: o.description,
type: o.type,
options: o.options,
}
),
);
}
fetchCommands() {}
// deno-fmt-ignore
isContextApplicationCommand(cmd: AtLeastOne<CreateContextApplicationCommand> | AtLeastOne<CreateApplicationCommand>): cmd is AtLeastOne<CreateContextApplicationCommand> {
return cmd.type === ApplicationCommandTypes.Message || cmd.type === ApplicationCommandTypes.User;
}
async start() {
const getGatewayBot = () => this.rest.runMethod<DiscordGetGatewayBot>(this.rest, "GET", Routes.GATEWAY_BOT());
// check if is empty
if (!Object.keys(this.options.gateway?.data ?? {}).length) {
const nonParsed = await getGatewayBot();
this.gateway.gatewayBot = {
url: nonParsed.url,
shards: nonParsed.shards,
sessionStartLimit: {
total: nonParsed.session_start_limit.total,
remaining: nonParsed.session_start_limit.remaining,
resetAfter: nonParsed.session_start_limit.reset_after,
maxConcurrency: nonParsed.session_start_limit.max_concurrency,
},
};
this.gateway.lastShardId = this.gateway.gatewayBot.shards - 1;
this.gateway.manager.totalShards = this.gateway.gatewayBot.shards;
}
this.gateway.spawnShards();
}
}