Merge remote-tracking branch 'upstream/fp-attempt-9001' into fp-attempt-9001

# Conflicts:
#	src/bot.ts
This commit is contained in:
TriForMine
2021-10-21 17:25:13 +02:00
18 changed files with 347 additions and 318 deletions

View File

@@ -50,8 +50,8 @@ import {
baseEndpoints,
CHANNEL_MENTION_REGEX,
CONTEXT_MENU_COMMANDS_NAME_REGEX,
DISCORD_SNOWFLAKE_REGEX,
DISCORDENO_VERSION,
DISCORD_SNOWFLAKE_REGEX,
endpoints,
SLASH_COMMANDS_NAME_REGEX,
USER_AGENT,
@@ -90,7 +90,12 @@ export async function createBot(options: CreateBotOptions) {
isReady: false,
activeGuildIds: new Set<bigint>(),
constants: createBotConstants(),
handlers: createBotGatewayHandlers({}),
cache: {
forEach: function (
type: "DELETE_MESSAGES_FROM_GUILD" | "DELETE_CHANNELS_FROM_GUILD" | "DELETE_GUILD_FROM_MEMBER",
options: Record<string, any>
) {},
guilds: {
get: async function (id: bigint): Promise<DiscordenoGuild | undefined> {
return {} as any as DiscordenoGuild;
@@ -101,6 +106,9 @@ export async function createBot(options: CreateBotOptions) {
set: async function (id: bigint, guild: DiscordenoGuild): Promise<void> {
return;
},
delete: async function (id: bigint): Promise<void> {
return;
},
},
channels: {
get: async function (id: bigint): Promise<DiscordenoChannel | undefined> {
@@ -126,6 +134,9 @@ export async function createBot(options: CreateBotOptions) {
set: async function (id: bigint, member: DiscordenoMember): Promise<void> {
return;
},
delete: async function (id: bigint): Promise<void> {
return;
},
},
users: {
get: async function (id: bigint): Promise<DiscordenoUser | undefined> {
@@ -182,6 +193,15 @@ export function createEventHandlers(events: Partial<EventHandlers>): EventHandle
channelDelete: events.channelDelete ?? ignore,
channelPinsUpdate: events.channelPinsUpdate ?? ignore,
channelUpdate: events.channelUpdate ?? ignore,
guildEmojisUpdate: events.guildEmojisUpdate ?? ignore,
guildBanAdd: events.guildBanAdd ?? ignore,
guildBanRemove: events.guildBanRemove ?? ignore,
guildLoaded: events.guildLoaded ?? ignore,
guildCreate: events.guildCreate ?? ignore,
guildDelete: events.guildDelete ?? ignore,
guildUpdate: events.guildUpdate ?? ignore,
integrationsUpdate: events.integrationsUpdate ?? ignore,
raw: events.raw ?? ignore,
stageInstanceCreate: events.stageInstanceCreate ?? ignore,
stageInstanceDelete: events.stageInstanceDelete ?? ignore,
stageInstanceUpdate: events.stageInstanceUpdate ?? ignore,
@@ -319,6 +339,10 @@ export interface HelperUtils {
export function createGatewayManager(options: Partial<GatewayManager>): GatewayManager {
return {
cache: {
guildIds: new Set(),
loadingGuildIds: new Set(),
},
secretKey: options.secretKey ?? "",
url: options.url ?? "",
reshard: options.reshard ?? true,
@@ -347,12 +371,12 @@ export function createGatewayManager(options: Partial<GatewayManager>): GatewayM
loadingShards: options.loadingShards ?? new Collection(),
buckets: new Collection(),
utf8decoder: new TextDecoder(),
startGateway,
spawnShards,
createShard,
identify,
heartbeat,
handleDiscordPayload,
tellClusterToIdentify,
log,
resharder,
@@ -361,6 +385,18 @@ export function createGatewayManager(options: Partial<GatewayManager>): GatewayM
closeWS,
sendShardMessage,
resume,
handleDiscordPayload:
options.handleDiscordPayload ||
async function (_, data: GatewayPayload, shardId: number) {
// TRIGGER RAW EVENT
bot.events.raw(bot as Bot, data, shardId);
if (!data.t) return;
// RUN DISPATCH CHECK
await bot.events.dispatchRequirements(bot as Bot, data, shardId);
bot.handlers[data.t as GatewayDispatchEventNames]?.(bot, data, shardId);
},
};
}
@@ -394,8 +430,8 @@ export interface Transformers {
snowflake: typeof snowflakeToBigint;
channel: typeof transformChannel;
guild: typeof transformGuild;
member: typeof transformMember;
user: typeof transformUser;
member: typeof transformMember;
message: typeof transformMessage;
role: typeof transformRole;
voiceState: typeof transformVoiceState;
@@ -404,6 +440,13 @@ export interface Transformers {
export function createTransformers(options: Partial<Transformers>) {
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,
};
}
@@ -474,6 +517,11 @@ export interface GatewayManager {
>;
utf8decoder: TextDecoder;
cache: {
guildIds: Set<bigint>;
loadingGuildIds: Set<bigint>;
};
// METHODS
/** The handler function that starts the gateway. */
@@ -487,7 +535,7 @@ export interface GatewayManager {
/** Begins heartbeating of the shard to keep it alive. */
heartbeat: typeof heartbeat;
/** Sends the discord payload to another server. */
handleDiscordPayload: typeof handleDiscordPayload;
handleDiscordPayload: (gateway: GatewayManager, data: GatewayPayload, shardId: number) => any;
/** Tell the cluster/worker to begin identifying this shard */
tellClusterToIdentify: typeof tellClusterToIdentify;
/** Handle the different logs. Used for debugging. */
@@ -547,6 +595,21 @@ export interface EventHandlers {
discoverableDisabled: boolean;
}
) => any;
// TODO: THREADS
guildEmojisUpdate: (
bot: Bot,
guild: DiscordenoGuild,
emojis: Collection<bigint, Emoji>,
cachedEmojis: Collection<bigint, Emoji>
) => any;
guildBanAdd: (bot: Bot, user: DiscordenoUser, guildId: bigint) => any;
guildBanRemove: (bot: Bot, user: DiscordenoUser, guildId: bigint) => any;
guildLoaded: (bot: Bot, guild: DiscordenoGuild) => any;
guildCreate: (bot: Bot, guild: DiscordenoGuild) => any;
guildDelete: (bot: Bot, id: bigint, guild?: DiscordenoGuild) => any;
guildUpdate: (bot: Bot, guild: DiscordenoGuild, cachedGuild?: DiscordenoGuild) => any;
integrationsUpdate: (bot: Bot, data: { guildId: bigint }) => any;
raw: (bot: Bot, data: GatewayPayload, shardId: number) => any;
}
export function createBotConstants() {
@@ -565,3 +628,120 @@ export function createBotConstants() {
Errors,
};
}
export interface BotGatewayHandlerOptions {
READY: typeof handleReady;
CHANNEL_CREATE: typeof handleChannelCreate;
CHANNEL_DELETE: typeof handleChannelDelete;
CHANNEL_PINS_UPDATE: typeof handleChannelPinsUpdate;
CHANNEL_UPDATE: typeof handleChannelUpdate;
THREAD_CREATE: typeof handleThreadCreate;
THREAD_UPDATE: typeof handleThreadUpdate;
THREAD_DELETE: typeof handleThreadDelete;
THREAD_LIST_SYNC: typeof handleThreadListSync;
THREAD_MEMBER_UPDATE: typeof handleThreadMemberUpdate;
THREAD_MEMBERS_UPDATE: typeof handleThreadMembersUpdate;
STAGE_INSTANCE_CREATE: typeof handleStageInstanceCreate;
STAGE_INSTANCE_UPDATE: typeof handleStageInstanceUpdate;
STAGE_INSTANCE_DELETE: typeof handleStageInstanceDelete;
GUILD_BAN_ADD: typeof handleGuildBanAdd;
GUILD_BAN_REMOVE: typeof handleGuildBanRemove;
GUILD_CREATE: typeof handleGuildCreate;
GUILD_LOADED_DD: typeof handleGuildLoaded;
GUILD_DELETE: typeof handleGuildDelete;
GUILD_EMOJIS_UPDATE: typeof handleGuildEmojisUpdate;
GUILD_INTEGRATIONS_UPDATE: typeof handleGuildIntegrationsUpdate;
GUILD_MEMBER_ADD: typeof handleGuildMemberAdd;
GUILD_MEMBER_REMOVE: typeof handleGuildMemberRemove;
GUILD_MEMBER_UPDATE: typeof handleGuildMemberUpdate;
GUILD_MEMBERS_CHUNK: typeof handleGuildMembersChunk;
GUILD_ROLE_CREATE: typeof handleGuildRoleCreate;
GUILD_ROLE_DELETE: typeof handleGuildRoleDelete;
GUILD_ROLE_UPDATE: typeof handleGuildRoleUpdate;
GUILD_UPDATE: typeof handleGuildUpdate;
INTERACTION_CREATE: typeof handleInteractionCreate;
INVITE_CREATE: typeof handleInviteCreate;
INVITE_DELETE: typeof handleInviteCreate;
MESSAGE_CREATE: typeof handleMessageCreate;
MESSAGE_DELETE_BULK: typeof handleMessageDeleteBulk;
MESSAGE_DELETE: typeof handleMessageDelete;
MESSAGE_REACTION_ADD: typeof handleMessageReactionAdd;
MESSAGE_REACTION_REMOVE_ALL: typeof handleMessageReactionRemoveAll;
MESSAGE_REACTION_REMOVE_EMOJI: typeof handleMessageReactionRemoveEmoji;
MESSAGE_REACTION_REMOVE: typeof handleMessageReactionRemove;
MESSAGE_UPDATE: typeof handleMessageUpdate;
PRESENCE_UPDATE: typeof handlePresenceUpdate;
TYPING_START: typeof handleTypingStart;
USER_UPDATE: typeof handleUserUpdate;
VOICE_SERVER_UPDATE: typeof handleVoiceServerUpdate;
VOICE_STATE_UPDATE: typeof handleVoiceStateUpdate;
WEBHOOKS_UPDATE: typeof handleWebhooksUpdate;
INTEGRATION_CREATE: typeof handleIntegrationCreate;
INTEGRATION_UPDATE: typeof handleIntegrationUpdate;
INTEGRATION_DELETE: typeof handleIntegrationDelete;
}
export function createBotGatewayHandlers(options: Partial<BotGatewayHandlerOptions>) {
return {
// misc
READY: options.READY ?? handleReady,
// channels
CHANNEL_CREATE: options.CHANNEL_CREATE ?? handleChannelCreate,
CHANNEL_DELETE: options.CHANNEL_DELETE ?? handleChannelDelete,
CHANNEL_PINS_UPDATE: options.CHANNEL_PINS_UPDATE ?? handleChannelPinsUpdate,
CHANNEL_UPDATE: options.CHANNEL_UPDATE ?? handleChannelUpdate,
THREAD_CREATE: options.THREAD_CREATE ?? handleThreadCreate,
THREAD_UPDATE: options.THREAD_UPDATE ?? handleThreadUpdate,
THREAD_DELETE: options.THREAD_DELETE ?? handleThreadDelete,
THREAD_LIST_SYNC: options.THREAD_LIST_SYNC ?? handleThreadListSync,
THREAD_MEMBER_UPDATE: options.THREAD_MEMBER_UPDATE ?? handleThreadMemberUpdate,
THREAD_MEMBERS_UPDATE: options.THREAD_MEMBERS_UPDATE ?? handleThreadMembersUpdate,
STAGE_INSTANCE_CREATE: options.STAGE_INSTANCE_CREATE ?? handleStageInstanceCreate,
STAGE_INSTANCE_UPDATE: options.STAGE_INSTANCE_UPDATE ?? handleStageInstanceUpdate,
STAGE_INSTANCE_DELETE: options.STAGE_INSTANCE_DELETE ?? handleStageInstanceDelete,
// guilds
GUILD_BAN_ADD: options.GUILD_BAN_ADD ?? handleGuildBanAdd,
GUILD_BAN_REMOVE: options.GUILD_BAN_REMOVE ?? handleGuildBanRemove,
GUILD_CREATE: options.GUILD_CREATE ?? handleGuildCreate,
GUILD_LOADED_DD: options.GUILD_LOADED_DD ?? handleGuildLoaded,
GUILD_DELETE: options.GUILD_DELETE ?? handleGuildDelete,
GUILD_EMOJIS_UPDATE: options.GUILD_EMOJIS_UPDATE ?? handleGuildEmojisUpdate,
GUILD_INTEGRATIONS_UPDATE: options.GUILD_INTEGRATIONS_UPDATE ?? handleGuildIntegrationsUpdate,
GUILD_MEMBER_ADD: options.GUILD_MEMBER_ADD ?? handleGuildMemberAdd,
GUILD_MEMBER_REMOVE: options.GUILD_MEMBER_REMOVE ?? handleGuildMemberRemove,
GUILD_MEMBER_UPDATE: options.GUILD_MEMBER_UPDATE ?? handleGuildMemberUpdate,
GUILD_MEMBERS_CHUNK: options.GUILD_MEMBERS_CHUNK ?? handleGuildMembersChunk,
GUILD_ROLE_CREATE: options.GUILD_ROLE_CREATE ?? handleGuildRoleCreate,
GUILD_ROLE_DELETE: options.GUILD_ROLE_DELETE ?? handleGuildRoleDelete,
GUILD_ROLE_UPDATE: options.GUILD_ROLE_UPDATE ?? handleGuildRoleUpdate,
GUILD_UPDATE: options.GUILD_UPDATE ?? handleGuildUpdate,
// interactions
INTERACTION_CREATE: options.INTERACTION_CREATE ?? handleInteractionCreate,
// invites
INVITE_CREATE: options.INVITE_CREATE ?? handleInviteCreate,
INVITE_DELETE: options.INVITE_DELETE ?? handleInviteCreate,
// messages
MESSAGE_CREATE: options.MESSAGE_CREATE ?? handleMessageCreate,
MESSAGE_DELETE_BULK: options.MESSAGE_DELETE_BULK ?? handleMessageDeleteBulk,
MESSAGE_DELETE: options.MESSAGE_DELETE ?? handleMessageDelete,
MESSAGE_REACTION_ADD: options.MESSAGE_REACTION_ADD ?? handleMessageReactionAdd,
MESSAGE_REACTION_REMOVE_ALL: options.MESSAGE_REACTION_REMOVE_ALL ?? handleMessageReactionRemoveAll,
MESSAGE_REACTION_REMOVE_EMOJI: options.MESSAGE_REACTION_REMOVE_EMOJI ?? handleMessageReactionRemoveEmoji,
MESSAGE_REACTION_REMOVE: options.MESSAGE_REACTION_REMOVE ?? handleMessageReactionRemove,
MESSAGE_UPDATE: options.MESSAGE_UPDATE ?? handleMessageUpdate,
// presence
PRESENCE_UPDATE: options.PRESENCE_UPDATE ?? handlePresenceUpdate,
TYPING_START: options.TYPING_START ?? handleTypingStart,
USER_UPDATE: options.USER_UPDATE ?? handleUserUpdate,
// voice
VOICE_SERVER_UPDATE: options.VOICE_SERVER_UPDATE ?? handleVoiceServerUpdate,
VOICE_STATE_UPDATE: options.VOICE_STATE_UPDATE ?? handleVoiceStateUpdate,
// webhooks
WEBHOOKS_UPDATE: options.WEBHOOKS_UPDATE ?? handleWebhooksUpdate,
// integrations
INTEGRATION_CREATE: options.INTEGRATION_CREATE ?? handleIntegrationCreate,
INTEGRATION_UPDATE: options.INTEGRATION_UPDATE ?? handleIntegrationUpdate,
INTEGRATION_DELETE: options.INTEGRATION_DELETE ?? handleIntegrationDelete,
};
}

View File

@@ -1,7 +0,0 @@
import { eventHandlers } from "../../bot.ts";
import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
import type { ApplicationCommandCreateUpdateDelete } from "../../types/interactions/commands/application_command_create_update_delete.ts";
export function handleApplicationCommandCreate(data: DiscordGatewayPayload) {
eventHandlers.applicationCommandCreate?.(data.d as ApplicationCommandCreateUpdateDelete);
}

View File

@@ -1,7 +0,0 @@
import { eventHandlers } from "../../bot.ts";
import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
import type { ApplicationCommandCreateUpdateDelete } from "../../types/interactions/commands/application_command_create_update_delete.ts";
export function handleApplicationCommandDelete(data: DiscordGatewayPayload) {
eventHandlers.applicationCommandDelete?.(data.d as ApplicationCommandCreateUpdateDelete);
}

View File

@@ -1,7 +0,0 @@
import { eventHandlers } from "../../bot.ts";
import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
import type { ApplicationCommandCreateUpdateDelete } from "../../types/interactions/commands/application_command_create_update_delete.ts";
export function handleApplicationCommandUpdate(data: DiscordGatewayPayload) {
eventHandlers.applicationCommandUpdate?.(data.d as ApplicationCommandCreateUpdateDelete);
}

View File

@@ -1,19 +1,18 @@
import { eventHandlers } from "../../bot.ts";
import { cacheHandlers } from "../../cache.ts";
import { Bot } from "../../bot.ts";
import type { GuildEmojisUpdate } from "../../types/emojis/guild_emojis_update.ts";
import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
import { snowflakeToBigint } from "../../util/bigint.ts";
import { SnakeCasedPropertiesDeep } from "../../types/util.ts";
import { Collection } from "../../util/collection.ts";
export async function handleGuildEmojisUpdate(data: DiscordGatewayPayload) {
const payload = data.d as GuildEmojisUpdate;
const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId));
export async function handleGuildEmojisUpdate(bot: Bot, data: SnakeCasedPropertiesDeep<DiscordGatewayPayload>) {
const payload = data.d as SnakeCasedPropertiesDeep<GuildEmojisUpdate>;
const guild = await bot.cache.guilds.get(bot.transformers.snowflake(payload.guild_id));
if (!guild) return;
const cachedEmojis = guild.emojis;
guild.emojis = new Collection(payload.emojis.map((emoji) => [snowflakeToBigint(emoji.id!), emoji]));
guild.emojis = new Collection(payload.emojis.map((emoji) => [bot.transformers.snowflake(emoji.id!), emoji]));
await cacheHandlers.set("guilds", guild.id, guild);
await bot.cache.guilds.set(guild.id, guild);
eventHandlers.guildEmojisUpdate?.(guild, guild.emojis, cachedEmojis);
bot.events.guildEmojisUpdate(bot, guild, guild.emojis, cachedEmojis);
}

View File

@@ -1,14 +1,12 @@
import { eventHandlers } from "../../bot.ts";
import { cacheHandlers } from "../../cache.ts";
import { Bot } from "../../bot.ts";
import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
import type { GuildBanAddRemove } from "../../types/guilds/guild_ban_add_remove.ts";
import { snowflakeToBigint } from "../../util/bigint.ts";
import { SnakeCasedPropertiesDeep } from "../../types/util.ts";
export async function handleGuildBanAdd(data: DiscordGatewayPayload) {
const payload = data.d as GuildBanAddRemove;
const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId));
if (!guild) return;
const member = await cacheHandlers.get("members", snowflakeToBigint(payload.user.id));
eventHandlers.guildBanAdd?.(guild, payload.user, member);
export async function handleGuildBanAdd(bot: Bot, data: SnakeCasedPropertiesDeep<DiscordGatewayPayload>) {
const payload = data.d as SnakeCasedPropertiesDeep<GuildBanAddRemove>;
// FIRST COMPLETE THE END USERS EVENT
await bot.events.guildBanAdd(bot, bot.transformers.user(bot, payload.user), bot.transformers.snowflake(payload.guild_id));
// THEN DELETE THE MEMBER
await bot.cache.members.delete(bot.transformers.snowflake(payload.user.id));
}

View File

@@ -1,14 +1,14 @@
import { eventHandlers } from "../../bot.ts";
import { cacheHandlers } from "../../cache.ts";
import { Bot } from "../../bot.ts";
import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
import type { GuildBanAddRemove } from "../../types/guilds/guild_ban_add_remove.ts";
import { snowflakeToBigint } from "../../util/bigint.ts";
import { SnakeCasedPropertiesDeep } from "../../types/util.ts";
export async function handleGuildBanRemove(data: DiscordGatewayPayload) {
const payload = data.d as GuildBanAddRemove;
const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId));
if (!guild) return;
export async function handleGuildBanRemove(bot: Bot, data: SnakeCasedPropertiesDeep<DiscordGatewayPayload>) {
const payload = data.d as SnakeCasedPropertiesDeep<GuildBanAddRemove>;
const member = await cacheHandlers.get("members", snowflakeToBigint(payload.user.id));
eventHandlers.guildBanRemove?.(guild, payload.user, member);
await bot.events.guildBanRemove(
bot,
bot.transformers.user(bot, payload.user),
bot.transformers.snowflake(payload.guild_id)
);
}

View File

@@ -1,33 +1,17 @@
import { eventHandlers } from "../../bot.ts";
import { cache, cacheHandlers } from "../../cache.ts";
import { structures } from "../../structures/mod.ts";
import { Bot } from "../../bot.ts";
import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
import type { Guild } from "../../types/guilds/guild.ts";
import { snowflakeToBigint } from "../../util/bigint.ts";
import { ws } from "../../ws/ws.ts";
import { guildAvailable } from "../misc/READY.ts";
import { SnakeCasedPropertiesDeep } from "../../types/util.ts";
export async function handleGuildCreate(data: DiscordGatewayPayload, shardId: number) {
const payload = data.d as Guild;
// When shards resume they emit GUILD_CREATE again.
if (
(await cacheHandlers.has("guilds", snowflakeToBigint(payload.id))) ||
cache.dispatchedGuildIds.has(snowflakeToBigint(payload.id))
)
return;
export async function handleGuildCreate(
bot: Bot,
data: SnakeCasedPropertiesDeep<DiscordGatewayPayload>,
shardId: number
) {
const payload = data.d as SnakeCasedPropertiesDeep<Guild>;
const guild = await structures.createDiscordenoGuild(payload, shardId);
await cacheHandlers.set("guilds", guild.id, guild);
const guild = bot.transformers.guild(bot, { guild: payload, shardId });
await bot.cache.guilds.set(guild.id, guild);
const shard = ws.shards.get(shardId);
if (shard?.unavailableGuildIds.has(guild.id)) {
await cacheHandlers.delete("unavailableGuilds", guild.id);
guildAvailable(shard, guild.id);
return eventHandlers.guildAvailable?.(guild);
}
if (!cache.isReady) return eventHandlers.guildLoaded?.(guild);
eventHandlers.guildCreate?.(guild);
await bot.events.guildCreate(bot, guild);
}

View File

@@ -1,31 +1,22 @@
import { eventHandlers } from "../../bot.ts";
import { cacheHandlers } from "../../cache.ts";
import { Bot } from "../../bot.ts";
import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
import type { UnavailableGuild } from "../../types/guilds/unavailable_guild.ts";
import { snowflakeToBigint } from "../../util/bigint.ts";
import { ws } from "../../ws/ws.ts";
import { SnakeCasedPropertiesDeep } from "../../types/util.ts";
export async function handleGuildDelete(data: DiscordGatewayPayload, shardId: number) {
const payload = data.d as UnavailableGuild;
export async function handleGuildDelete(bot: Bot, data: DiscordGatewayPayload, shardId: number) {
const payload = data.d as SnakeCasedPropertiesDeep<UnavailableGuild>;
const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.id));
const id = bot.transformers.snowflake(payload.id);
const guild = await bot.cache.guilds.get(id);
await bot.events.guildDelete(bot, id, guild);
if (!guild) return;
await bot.cache.guilds.delete(id);
await cacheHandlers.delete("guilds", guild.id);
if (payload.unavailable) {
const shard = ws.shards.get(shardId);
if (shard) shard.unavailableGuildIds.add(guild.id);
await cacheHandlers.set("unavailableGuilds", guild.id, Date.now());
eventHandlers.guildUnavailable?.(guild);
} else {
eventHandlers.guildDelete?.(guild);
}
await Promise.all([
cacheHandlers.forEach("DELETE_MESSAGES_FROM_GUILD", { guildId: guild.id }),
cacheHandlers.forEach("DELETE_CHANNELS_FROM_GUILD", { guildId: guild.id }),
cacheHandlers.forEach("DELETE_GUILD_FROM_MEMBER", { guildId: guild.id }),
bot.cache.forEach("DELETE_MESSAGES_FROM_GUILD", { guildId: guild.id }),
bot.cache.forEach("DELETE_CHANNELS_FROM_GUILD", { guildId: guild.id }),
bot.cache.forEach("DELETE_GUILD_FROM_MEMBER", { guildId: guild.id }),
]);
}

View File

@@ -1,14 +1,10 @@
import { eventHandlers } from "../../bot.ts";
import { cacheHandlers } from "../../cache.ts";
import { Bot } from "../../bot.ts";
import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
import type { GuildIntegrationsUpdate } from "../../types/integrations/guild_integrations_update.ts";
import { snowflakeToBigint } from "../../util/bigint.ts";
import { SnakeCasedPropertiesDeep } from "../../types/util.ts";
export async function handleGuildIntegrationsUpdate(data: DiscordGatewayPayload) {
const payload = data.d as GuildIntegrationsUpdate;
export async function handleGuildIntegrationsUpdate(bot: Bot, data: DiscordGatewayPayload) {
const payload = data.d as SnakeCasedPropertiesDeep<GuildIntegrationsUpdate>;
const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId));
if (!guild) return;
eventHandlers.guildIntegrationsUpdate?.(guild);
bot.events.integrationsUpdate(bot, { guildId: bot.transformers.snowflake(payload.guild_id) });
}

View File

@@ -0,0 +1,17 @@
import { Bot } from "../../bot.ts";
import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
import type { Guild } from "../../types/guilds/guild.ts";
import { SnakeCasedPropertiesDeep } from "../../types/util.ts";
export async function handleGuildLoaded(
bot: Bot,
data: SnakeCasedPropertiesDeep<DiscordGatewayPayload>,
shardId: number
) {
const payload = data.d as SnakeCasedPropertiesDeep<Guild>;
const guild = bot.transformers.guild(bot, { guild: payload, shardId });
await bot.cache.guilds.set(guild.id, guild);
await bot.events.guildLoaded(bot, guild);
}

View File

@@ -1,44 +1,14 @@
import { eventHandlers } from "../../bot.ts";
import { cacheHandlers } from "../../cache.ts";
import { structures } from "../../structures/mod.ts";
import type { GuildUpdateChange } from "../../types/discordeno/guild_update_change.ts";
import { Bot } from "../../bot.ts";
import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
import type { Guild } from "../../types/guilds/guild.ts";
import { snowflakeToBigint } from "../../util/bigint.ts";
import { SnakeCasedPropertiesDeep } from "../../types/util.ts";
export async function handleGuildUpdate(data: DiscordGatewayPayload, shardId: number) {
const payload = data.d as Guild;
const oldGuild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.id));
if (!oldGuild) return;
const keysToSkip = ["id", "roles", "guildHashes", "guildId", "maxMembers", "emojis"];
const newGuild = await structures.createDiscordenoGuild(payload, shardId);
const changes = Object.entries(newGuild)
.map(([key, value]) => {
if (keysToSkip.includes(key)) return;
// @ts-ignore index signature
const cachedValue = oldGuild[key];
if (cachedValue === value) return;
// Guild create sends undefined and update sends false.
if (!cachedValue && !value) return;
if (Array.isArray(cachedValue) && Array.isArray(value)) {
const different =
cachedValue.length !== value.length ||
cachedValue.find((val) => !value.includes(val)) ||
value.find((val) => !cachedValue.includes(val));
if (!different) return;
}
return { key, oldValue: cachedValue, value };
})
.filter((change) => change) as GuildUpdateChange[];
await cacheHandlers.set("guilds", newGuild.id, newGuild);
eventHandlers.guildUpdate?.(newGuild, changes);
export async function handleGuildUpdate(bot: Bot, data: DiscordGatewayPayload, shardId: number) {
const payload = data.d as SnakeCasedPropertiesDeep<Guild>;
const guild = bot.transformers.guild(bot, { guild: payload, shardId });
const cached = await bot.cache.guilds.get(guild.id);
await bot.cache.guilds.set(guild.id, guild);
bot.events.guildUpdate(bot, guild, cached);
}

View File

@@ -11,9 +11,6 @@ import { handleThreadListSync } from "./channels/THREAD_LIST_SYNC.ts";
import { handleThreadMembersUpdate } from "./channels/THREAD_MEMBERS_UPDATE.ts";
import { handleThreadMemberUpdate } from "./channels/THREAD_MEMBER_UPDATE.ts";
import { handleThreadUpdate } from "./channels/THREAD_UPDATE.ts";
import { handleApplicationCommandCreate } from "./commands/APPLICATION_COMMAND_CREATE.ts";
import { handleApplicationCommandDelete } from "./commands/APPLICATION_COMMAND_DELETE.ts";
import { handleApplicationCommandUpdate } from "./commands/APPLICATION_COMMAND_UPDATE.ts";
import { handleGuildEmojisUpdate } from "./emojis/GUILD_EMOJIS_UPDATE.ts";
import { handleGuildBanAdd } from "./guilds/GUILD_BAN_ADD.ts";
import { handleGuildBanRemove } from "./guilds/GUILD_BAN_REMOVE.ts";
@@ -48,11 +45,9 @@ import { handleGuildRoleUpdate } from "./roles/GUILD_ROLE_UPDATE.ts";
import { handleVoiceServerUpdate } from "./voice/VOICE_SERVER_UPDATE.ts";
import { handleVoiceStateUpdate } from "./voice/VOICE_STATE_UPDATE.ts";
import { handleWebhooksUpdate } from "./webhooks/WEBHOOKS_UPDATE.ts";
import { handleGuildLoaded } from "./guilds/GUILD_LOADED_DD.ts";
export {
handleApplicationCommandCreate,
handleApplicationCommandDelete,
handleApplicationCommandUpdate,
handleChannelCreate,
handleChannelDelete,
handleChannelPinsUpdate,
@@ -102,77 +97,6 @@ export {
handleWebhooksUpdate,
};
export let handlers = {
// misc
READY: handleReady,
// channels
CHANNEL_CREATE: handleChannelCreate,
CHANNEL_DELETE: handleChannelDelete,
CHANNEL_PINS_UPDATE: handleChannelPinsUpdate,
CHANNEL_UPDATE: handleChannelUpdate,
THREAD_CREATE: handleThreadCreate,
THREAD_UPDATE: handleThreadUpdate,
THREAD_DELETE: handleThreadDelete,
THREAD_LIST_SYNC: handleThreadListSync,
THREAD_MEMBER_UPDATE: handleThreadMemberUpdate,
THREAD_MEMBERS_UPDATE: handleThreadMembersUpdate,
STAGE_INSTANCE_CREATE: handleStageInstanceCreate,
STAGE_INSTANCE_UPDATE: handleStageInstanceUpdate,
STAGE_INSTANCE_DELETE: handleStageInstanceDelete,
// commands
APPLICATION_COMMAND_CREATE: handleApplicationCommandCreate,
APPLICATION_COMMAND_DELETE: handleApplicationCommandDelete,
APPLICATION_COMMAND_UPDATE: handleApplicationCommandUpdate,
// guilds
GUILD_BAN_ADD: handleGuildBanAdd,
GUILD_BAN_REMOVE: handleGuildBanRemove,
GUILD_CREATE: handleGuildCreate,
GUILD_DELETE: handleGuildDelete,
GUILD_EMOJIS_UPDATE: handleGuildEmojisUpdate,
GUILD_INTEGRATIONS_UPDATE: handleGuildIntegrationsUpdate,
GUILD_MEMBER_ADD: handleGuildMemberAdd,
GUILD_MEMBER_REMOVE: handleGuildMemberRemove,
GUILD_MEMBER_UPDATE: handleGuildMemberUpdate,
GUILD_MEMBERS_CHUNK: handleGuildMembersChunk,
GUILD_ROLE_CREATE: handleGuildRoleCreate,
GUILD_ROLE_DELETE: handleGuildRoleDelete,
GUILD_ROLE_UPDATE: handleGuildRoleUpdate,
GUILD_UPDATE: handleGuildUpdate,
// interactions
INTERACTION_CREATE: handleInteractionCreate,
// invites
INVITE_CREATE: handleInviteCreate,
INVITE_DELETE: handleInviteCreate,
// messages
MESSAGE_CREATE: handleMessageCreate,
MESSAGE_DELETE_BULK: handleMessageDeleteBulk,
MESSAGE_DELETE: handleMessageDelete,
MESSAGE_REACTION_ADD: handleMessageReactionAdd,
MESSAGE_REACTION_REMOVE_ALL: handleMessageReactionRemoveAll,
MESSAGE_REACTION_REMOVE_EMOJI: handleMessageReactionRemoveEmoji,
MESSAGE_REACTION_REMOVE: handleMessageReactionRemove,
MESSAGE_UPDATE: handleMessageUpdate,
// presence
PRESENCE_UPDATE: handlePresenceUpdate,
TYPING_START: handleTypingStart,
USER_UPDATE: handleUserUpdate,
// voice
VOICE_SERVER_UPDATE: handleVoiceServerUpdate,
VOICE_STATE_UPDATE: handleVoiceStateUpdate,
// webhooks
WEBHOOKS_UPDATE: handleWebhooksUpdate,
// integrations
INTEGRATION_CREATE: handleIntegrationCreate,
INTEGRATION_UPDATE: handleIntegrationUpdate,
INTEGRATION_DELETE: handleIntegrationDelete,
export const handlers = {
};
export type Handlers = typeof handlers;
export function updateHandlers(newHandlers: Handlers) {
handlers = {
...handlers,
...newHandlers,
};
}

View File

@@ -1,8 +1,3 @@
import type { DiscordenoChannel } from "../../structures/channel.ts";
import type { DiscordenoGuild } from "../../structures/guild.ts";
import type { DiscordenoMember } from "../../structures/member.ts";
import type { DiscordenoMessage } from "../../structures/message.ts";
import type { DiscordenoRole } from "../../structures/role.ts";
import { DiscordenoThread } from "../../util/transformers/channel_to_thread.ts";
import type { Collection } from "../../util/collection.ts";
import type { PresenceUpdate } from "../activity/presence_update.ts";
@@ -29,12 +24,6 @@ import type { DebugArg } from "./debug_arg.ts";
import type { GuildUpdateChange } from "./guild_update_change.ts";
export type EventHandlersDefinitions = {
/** Sent when a new Slash Command is created, relevant to the current user. */
applicationCommandCreate: [data: ApplicationCommandCreateUpdateDelete];
/** Sent when a Slash Command relevant to the current user is updated. */
applicationCommandUpdate: [data: ApplicationCommandCreateUpdateDelete];
/** Sent when a Slash Command relevant to the current user is deleted. */
applicationCommandDelete: [data: ApplicationCommandCreateUpdateDelete];
/** Sent when properties about the user change. */
botUpdate: [user: User];
/** Sent when a new guild channel is created, relevant to the current user. */

View File

@@ -6,55 +6,53 @@ export interface GatewayPayload {
/** Sequence number, used for resuming sessions and heartbeats */
s: number | null;
/** The event name for this payload */
t:
| "READY"
| "RESUMED"
| "CHANNEL_CREATE"
| "CHANNEL_DELETE"
| "CHANNEL_PINS_UPDATE"
| "CHANNEL_UPDATE"
| "APPLICATION_COMMAND_CREATE"
| "APPLICATION_COMMAND_DELETE"
| "APPLICATION_COMMAND_UPDATE"
| "GUILD_BAN_ADD"
| "GUILD_BAN_REMOVE"
| "GUILD_CREATE"
| "GUILD_DELETE"
| "GUILD_EMOJIS_UPDATE"
| "GUILD_INTEGRATIONS_UPDATE"
| "GUILD_MEMBER_ADD"
| "GUILD_MEMBER_REMOVE"
| "GUILD_MEMBER_UPDATE"
| "GUILD_MEMBERS_CHUNK"
| "GUILD_ROLE_CREATE"
| "GUILD_ROLE_DELETE"
| "GUILD_ROLE_UPDATE"
| "GUILD_UPDATE"
| "INTERACTION_CREATE"
| "INVITE_CREATE"
| "INVITE_DELETE"
| "MESSAGE_CREATE"
| "MESSAGE_DELETE_BULK"
| "MESSAGE_DELETE"
| "MESSAGE_REACTION_ADD"
| "MESSAGE_REACTION_REMOVE_ALL"
| "MESSAGE_REACTION_REMOVE_EMOJI"
| "MESSAGE_REACTION_REMOVE"
| "MESSAGE_UPDATE"
| "PRESENCE_UPDATE"
| "TYPING_START"
| "USER_UPDATE"
| "VOICE_SERVER_UPDATE"
| "VOICE_STATE_UPDATE"
| "WEBHOOKS_UPDATE"
| "INTEGRATION_CREATE"
| "INTEGRATION_UPDATE"
| "INTEGRATION_DELETE"
| "STAGE_INSTANCE_CREATE"
| "STAGE_INSTANCE_UPDATE"
| "STAGE_INSTANCE_DELETE"
| null;
t: GatewayEventNames | null;
}
/** https://discord.com/developers/docs/topics/gateway#payloads-gateway-payload-structure */
export type DiscordGatewayPayload = GatewayPayload;
export type GatewayDispatchEventNames =
| "READY"
| "CHANNEL_CREATE"
| "CHANNEL_DELETE"
| "CHANNEL_PINS_UPDATE"
| "CHANNEL_UPDATE"
| "GUILD_BAN_ADD"
| "GUILD_BAN_REMOVE"
| "GUILD_CREATE"
| "GUILD_DELETE"
| "GUILD_EMOJIS_UPDATE"
| "GUILD_INTEGRATIONS_UPDATE"
| "GUILD_MEMBER_ADD"
| "GUILD_MEMBER_REMOVE"
| "GUILD_MEMBER_UPDATE"
| "GUILD_MEMBERS_CHUNK"
| "GUILD_ROLE_CREATE"
| "GUILD_ROLE_DELETE"
| "GUILD_ROLE_UPDATE"
| "GUILD_UPDATE"
| "INTERACTION_CREATE"
| "INVITE_CREATE"
| "INVITE_DELETE"
| "MESSAGE_CREATE"
| "MESSAGE_DELETE_BULK"
| "MESSAGE_DELETE"
| "MESSAGE_REACTION_ADD"
| "MESSAGE_REACTION_REMOVE_ALL"
| "MESSAGE_REACTION_REMOVE_EMOJI"
| "MESSAGE_REACTION_REMOVE"
| "MESSAGE_UPDATE"
| "PRESENCE_UPDATE"
| "TYPING_START"
| "USER_UPDATE"
| "VOICE_SERVER_UPDATE"
| "VOICE_STATE_UPDATE"
| "WEBHOOKS_UPDATE"
| "INTEGRATION_CREATE"
| "INTEGRATION_UPDATE"
| "INTEGRATION_DELETE"
| "STAGE_INSTANCE_CREATE"
| "STAGE_INSTANCE_UPDATE"
| "STAGE_INSTANCE_DELETE";
export type GatewayEventNames = GatewayDispatchEventNames | "READY" | "RESUMED";

View File

@@ -1,16 +0,0 @@
import type { DiscordGatewayPayload } from "../types/gateway/gateway_payload.ts";
import { GatewayManager } from "../bot.ts";
/** Handler for processing all dispatch payloads that should be sent/forwarded to another server/vps/process. */
export async function handleDiscordPayload(gateway: GatewayManager, data: DiscordGatewayPayload, shardId: number) {
await fetch(gateway.url, {
headers: {
authorization: gateway.secretKey,
},
method: "post",
body: JSON.stringify({
shardId,
data,
}),
}).catch(console.error);
}

View File

@@ -1,10 +1,13 @@
import { eventHandlers, GatewayManager } from "../bot.ts";
import { handlers } from "../handlers/mod.ts";
import { GatewayManager } from "../bot.ts";
import { DiscordGatewayOpcodes } from "../types/codes/gateway_opcodes.ts";
import type { DiscordGatewayPayload } from "../types/gateway/gateway_payload.ts";
import type { DiscordHello } from "../types/gateway/hello.ts";
import type { DiscordReady } from "../types/gateway/ready.ts";
import { camelize, delay } from "../util/utils.ts";
import { Guild } from "../types/guilds/guild.ts";
import { UnavailableGuild } from "../types/guilds/unavailable_guild.ts";
import { SnakeCasedPropertiesDeep } from "../types/util.ts";
import { snowflakeToBigint } from "../util/bigint.ts";
import { delay } from "../util/utils.ts";
import { decompressWith } from "./deps.ts";
/** Handler for handling every message event from websocket. */
@@ -27,7 +30,7 @@ export async function handleOnMessage(gateway: GatewayManager, message: any, sha
switch (messageData.op) {
case DiscordGatewayOpcodes.Heartbeat:
if (shard?.gateway.readyState !== WebSocket.OPEN) return;
if (shard?.ws.readyState !== WebSocket.OPEN) return;
shard.heartbeat.lastSentAt = Date.now();
// Discord randomly sends this requiring an immediate heartbeat back
@@ -91,15 +94,18 @@ export async function handleOnMessage(gateway: GatewayManager, message: any, sha
// Important for RESUME
if (messageData.t === "READY") {
const shard = gateway.shards.get(shardId);
const payload = messageData.d as DiscordReady;
if (shard) {
shard.sessionId = (messageData.d as DiscordReady).session_id;
shard.sessionId = payload.session_id;
}
payload.guilds.forEach((g) => gateway.cache.loadingGuildIds.add(snowflakeToBigint(g.id)));
gateway.loadingShards.get(shardId)?.resolve(true);
gateway.loadingShards.delete(shardId);
// Wait few seconds to spawn next shard
setTimeout(() => {
const bucket = gateway.buckets.get(shardId % gateway.botGatewayData.sessionStartLimit.maxConcurrency);
const bucket = gateway.buckets.get(shardId % gateway.maxConcurrency);
if (bucket) bucket.createNextShard.shift()?.();
}, gateway.spawnShardDelay);
}
@@ -112,18 +118,33 @@ export async function handleOnMessage(gateway: GatewayManager, message: any, sha
}
}
if (gateway.url) await gateway.handleDiscordPayload(gateway, messageData, shardId);
else {
eventHandlers.raw?.(messageData);
await eventHandlers.dispatchRequirements?.(messageData, shardId);
// MUST HANDLE GUILD_CREATE EVENTS AS THEY ARE EXPENSIVE WITHOUT GATEWAY CACHE
if (messageData.t === "GUILD_CREATE") {
const id = snowflakeToBigint((messageData.d as SnakeCasedPropertiesDeep<Guild>).id);
if (messageData.op !== DiscordGatewayOpcodes.Dispatch) return;
// SHARD RESUMED MOST LIKELY, THEY EMIT GUILD CREATES. OR GUILD BECAME AVAILABLE AGAIN
if (gateway.cache.guildIds.has(id)) return;
if (!messageData.t) return;
// GUILD WAS MARKED LOADING IN READY EVENT, THIS WAS THE FIRST GUILD_CREATE TO ARRIVE
if (gateway.cache.loadingGuildIds.has(id)) {
// @ts-ignore override with a custom event
messageData.t = "GUILD_LOADED_DD";
gateway.cache.loadingGuildIds.delete(id);
}
return handlers[messageData.t]?.(camelize(messageData), shardId);
gateway.cache.guildIds.add(id);
}
// MUST HANDLE GUILD_DELETE EVENTS FOR UNAVAILABLE
if (messageData.t === "GUILD_DELETE") {
if ((messageData.d as UnavailableGuild).unavailable) return;
}
// IF NO TYPE THEN THIS SHOULD NOT BE SENT FORWARD
if (!messageData.t) return;
await gateway.handleDiscordPayload(gateway, messageData, shardId);
break;
}
}

View File

@@ -1,7 +1,6 @@
export * from "./close_ws.ts";
export * from "./create_shard.ts";
export * from "./events.ts";
export * from "./handle_discord_payload.ts";
export * from "./handle_on_message.ts";
export * from "./heartbeat.ts";
export * from "./identify.ts";