diff --git a/src/cache.ts b/src/cache.ts index 7d51916e6..f765c5ec9 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -11,7 +11,7 @@ import { Collection } from "./util/collection.ts"; export const cache = { isReady: false, /** All of the guild objects the bot has access to, mapped by their Ids */ - guilds: new Collection(), + guilds: new Collection([], { sweeper: { filter: guildSweeper, interval: 3600000 } }), /** All of the channel objects the bot has access to, mapped by their Ids */ channels: new Collection(), /** All of the message objects the bot has cached since the bot acquired `READY` state, mapped by their Ids */ @@ -32,6 +32,9 @@ export const cache = { this.guilds.reduce((a, b) => [...a, ...b.emojis.map((e) => [e.id, e])], [] as any[]) ); }, + activeGuildIds: new Set(), + dispatchedGuildIds: new Set(), + dispatchedChannelIds: new Set(), }; function messageSweeper(message: DiscordenoMessage) { @@ -54,6 +57,26 @@ function memberSweeper(member: DiscordenoMember) { return true; } +export function guildSweeper(guild: DiscordenoGuild) { + if (cache.activeGuildIds.has(guild.id)) return false; + + // This is inactive guild. Not a single thing has happened for atleast 30 minutes. + // Not a reaction, not a message, not any event! + cache.guilds.delete(guild.id); + cache.dispatchedGuildIds.add(guild.id); + + // Remove all channel if they were dispatched + cache.channels.forEach((channel) => { + if (!cache.dispatchedGuildIds.has(channel.guildId)) return; + + cache.channels.delete(channel.id); + cache.dispatchedChannelIds.add(channel.id); + }); + + // Reset activity for next interval + cache.activeGuildIds.clear(); +} + export let cacheHandlers = { /** Deletes all items from the cache */ async clear(table: TableName) { diff --git a/src/util/dispatch_requirements.ts b/src/util/dispatch_requirements.ts new file mode 100644 index 000000000..90221e515 --- /dev/null +++ b/src/util/dispatch_requirements.ts @@ -0,0 +1,93 @@ +import { botId } from "../bot.ts"; +import { cache } from "../cache.ts"; +import { getChannels } from "../helpers/channels/get_channels.ts"; +import { getGuild } from "../helpers/guilds/get_guild.ts"; +import { getMember } from "../helpers/members/get_member.ts"; +import { structures } from "../structures/mod.ts"; +import type { DiscordGatewayPayload } from "../types/gateway/gateway_payload.ts"; +import type { Guild } from "../types/guilds/guild.ts"; +import { snowflakeToBigint } from "./bigint.ts"; +import { delay } from "./utils.ts"; + +const processing = new Set(); + +export async function dispatchRequirements(data: DiscordGatewayPayload, shardId: number) { + if (!cache.isReady) return; + + // DELETE MEANS WE DONT NEED TO FETCH. CREATE SHOULD HAVE DATA TO CACHE + if (data.t && ["GUILD_CREATE", "GUILD_DELETE"].includes(data.t)) return; + + const id = snowflakeToBigint( + (data.t && ["GUILD_UPDATE"].includes(data.t) + ? // deno-lint-ignore no-explicit-any + (data.d as any)?.id + : // deno-lint-ignore no-explicit-any + (data.d as any)?.guild_id) ?? "" + ); + + if (!id || cache.activeGuildIds.has(id)) return; + + // If this guild is in cache, it has not been swept and we can cancel + if (cache.guilds.has(id)) { + cache.activeGuildIds.add(id); + return; + } + + if (processing.has(id)) { + console.info(`[DISPATCH] New Guild ID already being processed: ${id} in ${data.t} event`); + + let runs = 0; + do { + await delay(500); + runs++; + } while (processing.has(id) && runs < 40); + + if (!processing.has(id)) return; + + return console.warn(`[DISPATCH] Already processed guild was not successfully fetched: ${id} in ${data.t} event`); + } + + processing.add(id); + + // New guild id has appeared, fetch all relevant data + console.info(`[DISPATCH] New Guild ID has appeared: ${id} in ${data.t} event`); + + const rawGuild = (await getGuild(id, { + counts: true, + addToCache: false, + }).catch(console.info)) as Guild | undefined; + + if (!rawGuild) { + processing.delete(id); + return console.warn(`[DISPATCH] Guild ID ${id} failed to fetch.`); + } + + console.info(`[DISPATCH] Guild ID ${id} has been found. ${rawGuild.name}`); + + const [channels, botMember] = await Promise.all([ + getChannels(id, false), + getMember(id, botId, { force: true }), + ]).catch((error) => { + console.warn(error); + return []; + }); + + if (!botMember || !channels) { + processing.delete(id); + return console.info(`[DISPATCH] Guild ID ${id} Name: ${rawGuild.name} failed. Unable to get botMember or channels`); + } + + const guild = await structures.createDiscordenoGuild(rawGuild, shardId); + + // Add to cache + cache.guilds.set(id, guild); + cache.dispatchedGuildIds.delete(id); + channels.forEach((channel) => { + cache.dispatchedChannelIds.delete(channel.id); + cache.channels.set(channel.id, channel); + }); + + processing.delete(id); + + console.info(`[DISPATCH] Guild ID ${id} Name: ${guild.name} completely loaded.`); +}