diff --git a/src/bot.ts b/src/bot.ts index e7e481bef..624e003e2 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -17,6 +17,25 @@ import { Collection } from "./util/collection.ts"; import { DiscordenoUser, transformMember, transformUser } from "./transformers/member.ts"; import { SnakeCasedPropertiesDeep } from "./types/util.ts"; import { Channel } from "./types/channels/channel.ts"; +import { DiscordenoChannel, transformChannel } from "./transformers/channel.ts"; +import { transformVoiceState } from "./transformers/voice_state.ts"; +import { transformRole } from "./transformers/role.ts"; +import { transformMessage } from "./transformers/message.ts"; +import { transformGuild } from "./transformers/guild.ts"; +import { DiscordenoShard } from "./ws/ws.ts"; +import { startGateway } from "./ws/start_gateway.ts"; +import { spawnShards } from "./ws/spawn_shards.ts"; +import { createShard } from "./ws/create_shard.ts"; +import { identify } from "./ws/identify.ts"; +import { heartbeat } from "./ws/heartbeat.ts"; +import { resharder } from "./ws/resharder.ts"; +import { tellClusterToIdentify } from "./ws/tell_cluster_to_identify.ts"; +import { handleDiscordPayload } from "./ws/handle_discord_payload.ts"; +import { log } from "./ws/events.ts"; +import { handleOnMessage } from "./ws/handle_on_message.ts"; +import { closeWS } from "./ws/close_ws.ts"; +import { sendShardMessage } from "./ws/send_shard_message.ts"; +import { resume } from "./ws/resume.ts"; export async function createBot(options: CreateBotOptions) { return { @@ -102,17 +121,55 @@ export async function startBot(bot: Bot) { bot.rest = createRestManager({ token: bot.token }); // START WS - bot.gateway = createGatewayManager(); - - + bot.gateway = createGatewayManager({}); } -export interface CreateGatewayManagerOptions { - transformers: Partial; -} - -export function createGatewayManager(options: CreateGatewayManagerOptions) { +export function createGatewayManager(options: Partial): GatewayManager { + return { + secretKey: options.secretKey ?? "", + url: options.url ?? "", + reshard: options.reshard ?? true, + reshardPercentage: options.reshardPercentage ?? 80, + spawnShardDelay: options.spawnShardDelay ?? 2600, + maxShards: options.maxShards ?? 0, + useOptimalLargeBotSharding: options.useOptimalLargeBotSharding ?? true, + shardsPerCluster: options.shardsPerCluster ?? 25, + maxClusters: options.maxClusters ?? 4, + firstShardId: options.firstShardId ?? 0, + lastShardId: options.lastShardId ?? 1, + token: options.token ?? "", + compress: options.compress ?? false, + $os: options.$os ?? "linux", + $browser: options.$browser ?? "Discordeno", + $device: options.$device ?? "Discordeno", + intents: options.intents ?? 0, + shard: options.shard ?? [0, 0], + urlWSS: options.urlWSS ?? "wss://gateway.discord.gg/?v=9&encoding=json", + shardsRecommended: options.shardsRecommended ?? 1, + sessionStartLimitTotal: options.sessionStartLimitTotal ?? 1000, + sessionStartLimitRemaining: options.sessionStartLimitRemaining ?? 1000, + sessionStartLimitResetAfter: options.sessionStartLimitResetAfter ?? 0, + maxConcurrency: options.maxConcurrency ?? 1, + shards: options.shards ?? new Collection(), + loadingShards: options.loadingShards ?? new Collection(), + buckets: new Collection(), + utf8decoder: new TextDecoder(), + startGateway, + spawnShards, + createShard, + identify, + heartbeat, + handleDiscordPayload, + tellClusterToIdentify, + log, + resharder, + handleOnMessage, + processQueue, + closeWS, + sendShardMessage, + resume, + }; } export function stopBot(bot: Bot) { @@ -124,7 +181,7 @@ export interface CreateBotOptions { token: string; botId: bigint; applicationId?: bigint; - events: EventHandlers; + events: Partial; intents: (keyof typeof DiscordGatewayIntents)[]; botGatewayData?: GetGatewayBot; rest?: Omit; @@ -145,22 +202,128 @@ export type Bot = CreatedBot & { export interface Transformers { snowflake: typeof snowflakeToBigint; + channel: typeof transformChannel; + guild: typeof transformGuild; user: typeof transformUser; member: typeof transformMember; - channel: typeof transformChannel; + message: typeof transformMessage; + role: typeof transformRole; + voiceState: typeof transformVoiceState; } export function createTransformers(options: Partial) { return { snowflake: options.snowflake || snowflakeToBigint, + channel: options.channel || transformChannel, + guild: options.guild || transformGuild, + user: options.user || transformUser, + member: options.member || transformMember, + message: options.message || transformMessage, + role: options.role || transformRole, + voiceState: options.voiceState || transformVoiceState, }; } export type RestManager = ReturnType; -export interface GatewayManager {} +export interface GatewayManager { + /** The secret key authorization header the bot will expect when sending payloads. */ + secretKey: string; + /** The url that all discord payloads for the dispatch type should be sent to. */ + url: string; + /** Whether or not to automatically reshard. */ + reshard: boolean; + /** The percentage at which resharding should occur. */ + reshardPercentage: number; + /** The delay in milliseconds to wait before spawning next shard. OPTIMAL IS ABOVE 2500. YOU DON"T WANT TO HIT THE RATE LIMIT!!! */ + spawnShardDelay: number; + /** The maximum shard Id number. Useful for zero-downtime updates or resharding. */ + maxShards: number; + /** Whether or not the resharder should automatically switch to LARGE BOT SHARDING when you are above 100K servers. */ + useOptimalLargeBotSharding: boolean; + /** The amount of shards to load per cluster. */ + shardsPerCluster: number; + /** The maximum amount of clusters to use for your bot. */ + maxClusters: number; + /** The first shard Id to start spawning. */ + firstShardId: number; + /** The last shard Id for this cluster. */ + lastShardId: number; + token: ""; + compress: false; + $os: "linux"; + $browser: "Discordeno"; + $device: "Discordeno"; + intents: 0; + shard: [0, 0]; + + /** The WSS URL that can be used for connecting to the gateway. */ + urlWSS: "wss://gateway.discord.gg/?v=9&encoding=json"; + /** The recommended number of shards to use when connecting. */ + shardsRecommended: 1; + /** The total number of session starts the current user is allowed. */ + sessionStartLimitTotal: 1000; + /** The remaining number of session starts the current user is allowed. */ + sessionStartLimitRemaining: 1000; + /** Milliseconds left until limit is reset. */ + sessionStartLimitResetAfter: 0; + /** The number of identify requests allowed per 5 seconds. + * So, if you had a max concurrency of 16, and 16 shards for example, you could start them all up at the same time. + * Whereas if you had 32 shards, if you tried to start up shard 0 and 16 at the same time for example, it would not work. You can start shards 0-15 concurrently, then 16-31... + */ + maxConcurrency: 1; + shards: Collection; + loadingShards: Collection< + number, + { + shardId: number; + resolve: (value: unknown) => void; + startedAt: number; + } + >; + /** Stored as bucketId: { clusters: [clusterId, [ShardIds]], createNextShard: boolean } */ + buckets: Collection< + number, + { + clusters: number[][]; + createNextShard: (() => unknown)[]; + } + >; + utf8decoder: TextDecoder; + + // METHODS + + /** The handler function that starts the gateway. */ + startGateway: typeof startGateway; + /** The handler for spawning ALL the shards. */ + spawnShards: typeof spawnShards; + /** Create the websocket and adds the proper handlers to the websocket. */ + createShard: typeof createShard; + /** Begins identification of the shard to discord. */ + identify: typeof identify; + /** Begins heartbeating of the shard to keep it alive. */ + heartbeat: typeof heartbeat; + /** Sends the discord payload to another server. */ + handleDiscordPayload: typeof handleDiscordPayload; + /** Tell the cluster/worker to begin identifying this shard */ + tellClusterToIdentify: typeof tellClusterToIdentify; + /** Handle the different logs. Used for debugging. */ + log: typeof log; + /** Handles resharding the bot when necessary. */ + resharder: typeof resharder; + /** Handles the message events from websocket. */ + handleOnMessage: typeof handleOnMessage; + /** Handles processing queue of requests send to this shard. */ + processQueue: typeof processQueue; + /** Closes shard WebSocket connection properly. */ + closeWS: typeof closeWS; + /** Properly adds a message to the shards queue. */ + sendShardMessage: typeof sendShardMessage; + /** Properly resume an old shards session. */ + resume: typeof resume; +} export interface EventHandlers { debug: (text: string) => unknown; - channelCreate: (bot: Bot, channel: DiscordenoChannel); + channelCreate: (bot: Bot, channel: DiscordenoChannel) => unknown; } diff --git a/src/plugins/proxy_events.ts b/src/plugins/proxy_events.ts deleted file mode 100644 index 289a3235c..000000000 --- a/src/plugins/proxy_events.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { eventHandlers, replaceEventHandlers } from "../bot.ts"; -import type { EventHandlers } from "../types/discordeno/event_handlers.ts"; -import type { EventEmitter } from "https://deno.land/std@0.97.0/node/events.ts"; - -export function proxyEvent(emitter: EventEmitter) { - replaceEventHandlers( - new Proxy(eventHandlers, { - get(target, prop: keyof EventHandlers) { - return target[prop] !== undefined ? target[prop] : (...args: unknown[]) => emitter.emit(prop, ...args); - }, - }) - ); -} diff --git a/src/transformers/mod.ts b/src/transformers/mod.ts index 220d793b8..4696210ea 100644 --- a/src/transformers/mod.ts +++ b/src/transformers/mod.ts @@ -1,46 +1,6 @@ -import { createDiscordenoChannel } from "./channel.ts"; -import { createDiscordenoGuild } from "./guild.ts"; -import { createDiscordenoMember } from "./member.ts"; -import { createDiscordenoMessage } from "./message.ts"; -import { createDiscordenoRole } from "./role.ts"; -import { createDiscordenoVoiceState } from "./voice_state.ts"; - -import type { DiscordenoChannel } from "./channel.ts"; -import type { DiscordenoGuild } from "./guild.ts"; -import type { DiscordenoMember } from "./member.ts"; -import type { DiscordenoMessage } from "./message.ts"; -import type { DiscordenoRole } from "./role.ts"; -import type { DiscordenoVoiceState } from "./voice_state.ts"; - -/** This is the placeholder where the structure creation functions are kept. */ -export let structures = { - createDiscordenoChannel, - createDiscordenoGuild, - createDiscordenoMember, - createDiscordenoMessage, - createDiscordenoRole, - createDiscordenoVoiceState, -}; - -export type { - DiscordenoChannel, - DiscordenoGuild, - DiscordenoMember, - DiscordenoMessage, - DiscordenoRole, - DiscordenoVoiceState, -}; - -export type Structures = typeof structures; - -/** This function is used to update/reload/customize the internal structures of Discordeno. - * - * ⚠️ **ADVANCED USE ONLY: If you customize this incorrectly, you could potentially create many new errors/bugs. - * Please take caution when using this.** - */ -export function updateStructures(newStructures: Structures) { - structures = { - ...structures, - ...newStructures, - }; -} +export * from "./channel.ts"; +export * from "./guild.ts"; +export * from "./member.ts"; +export * from "./message.ts"; +export * from "./role.ts"; +export * from "./voice_state.ts";