diff --git a/src/cache.ts b/src/cache.ts index 6f20b06ad..5f5cf005d 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -68,12 +68,14 @@ export function createCache( tableCreator?: (bot: Bot, tableName: TableNames) => CacheHandler | AsyncCacheHandler; } ): Omit | Omit { + let cache: Cache | AsyncCache; + if (options.isAsync) { if (!options.tableCreator) { throw new Error("Async cache requires a tableCreator to be passed."); } - const cache = { + cache = { guilds: options.tableCreator(bot, "guilds"), users: options.tableCreator(bot, "users"), members: options.tableCreator(bot, "members"), @@ -85,34 +87,50 @@ export function createCache( dispatchedGuildIds: options.tableCreator(bot, "dispatchedGuildIds"), dispatchedChannelIds: options.tableCreator(bot, "dispatchedChannelIds"), activeGuildIds: options.tableCreator(bot, "activeGuildIds"), - executedSlashCommands: new Set(), + unrepliedInteractions: new Set(), fetchAllMembersProcessingRequests: new Map(), execute: async function () { throw new Error("Async Cache requires a custom execute function to be implemented."); }, } as AsyncCache; + } else { + if (!options.tableCreator) options.tableCreator = createTable; - return cache; + cache = { + guilds: options.tableCreator(bot, "guilds"), + users: options.tableCreator(bot, "users"), + members: options.tableCreator(bot, "members"), + channels: options.tableCreator(bot, "channels"), + messages: options.tableCreator(bot, "messages"), + presences: options.tableCreator(bot, "presences"), + // threads: options.tableCreator(bot, "threads"), + unavailableGuilds: options.tableCreator(bot, "unavailableGuilds"), + dispatchedGuildIds: new Set(), + dispatchedChannelIds: new Set(), + activeGuildIds: new Set(), + unrepliedInteractions: new Set(), + fetchAllMembersProcessingRequests: new Map(), + } as Cache; + + cache.execute = createExecute(cache); } - if (!options.tableCreator) options.tableCreator = createTable; - const cache = { - guilds: options.tableCreator(bot, "guilds"), - users: options.tableCreator(bot, "users"), - members: options.tableCreator(bot, "members"), - channels: options.tableCreator(bot, "channels"), - messages: options.tableCreator(bot, "messages"), - presences: options.tableCreator(bot, "presences"), - // threads: options.tableCreator(bot, "threads"), - unavailableGuilds: options.tableCreator(bot, "unavailableGuilds"), - dispatchedGuildIds: new Set(), - dispatchedChannelIds: new Set(), - activeGuildIds: new Set(), - executedSlashCommands: new Set(), - fetchAllMembersProcessingRequests: new Map(), - } as Cache; - - cache.execute = createExecute(cache); + // Interaction sweeper in case users don't reply do slash commands + // PS: always reply .-. its good practise + setInterval(() => { + const values = cache.unrepliedInteractions.values(); + const now = Date.now(); + for (let val; (val = values.next().value); ) { + // Interaction is older than 15 minutes + // and a reply has never been send + // so remove it from cache + // PS: DON'T USE THIS CODE TO CONVERT DC SNOWFLAKES TO UNIX + // SINCE U WILL GET AN INVALID RESULT + if ((val >> 22n) + 1420071300000n < now) { + cache.unrepliedInteractions.delete(val); + } + } + }, 300000); return cache; } @@ -131,7 +149,7 @@ export interface Cache { dispatchedGuildIds: Set; dispatchedChannelIds: Set; activeGuildIds: Set; - executedSlashCommands: Set; + unrepliedInteractions: Set; fetchAllMembersProcessingRequests: Map; execute: CacheExecutor; } @@ -154,7 +172,7 @@ export interface AsyncCache { dispatchedGuildIds: AsyncCacheHandler; dispatchedChannelIds: AsyncCacheHandler; activeGuildIds: AsyncCacheHandler; - executedSlashCommands: Set; + unrepliedInteractions: Set; fetchAllMembersProcessingRequests: Map; execute: CacheExecutor; } diff --git a/src/handlers/interactions/INTERACTION_CREATE.ts b/src/handlers/interactions/INTERACTION_CREATE.ts index dde1d8685..b89769abb 100644 --- a/src/handlers/interactions/INTERACTION_CREATE.ts +++ b/src/handlers/interactions/INTERACTION_CREATE.ts @@ -4,5 +4,6 @@ import type { Interaction } from "../../types/interactions/interaction.ts"; import { SnakeCasedPropertiesDeep } from "../../types/util.ts"; export async function handleInteractionCreate(bot: Bot, data: DiscordGatewayPayload) { + bot.cache.unrepliedInteractions.add(bot.transformers.snowflake((data.d as SnakeCasedPropertiesDeep).id)); bot.events.interactionCreate(bot, bot.transformers.interaction(bot, data.d as SnakeCasedPropertiesDeep)); } diff --git a/src/helpers/interactions/sendInteractionResponse.ts b/src/helpers/interactions/sendInteractionResponse.ts index 8bf77a83a..b0f92b449 100644 --- a/src/helpers/interactions/sendInteractionResponse.ts +++ b/src/helpers/interactions/sendInteractionResponse.ts @@ -16,7 +16,7 @@ export async function sendInteractionResponse( options: DiscordenoInteractionResponse ) { // TODO: add more options validations - if (options.data?.components) bot.utils.validateComponents(bot, options.data?.components); + if (options.data?.components) bot.utils.validateComponents(bot, options.data.components); // If the user wants this as a private message mark it ephemeral if (options.private) { @@ -28,239 +28,121 @@ export async function sendInteractionResponse( options.data = { ...options.data, allowedMentions: { parse: [] } }; } - const allowedMentions: AllowedMentions = options.data?.allowedMentions - ? { - parse: options.data?.allowedMentions.parse, - repliedUser: options.data?.allowedMentions.repliedUser, - users: options.data?.allowedMentions.users?.map((id) => id.toString()), - roles: options.data?.allowedMentions.roles?.map((id) => id.toString()), - } - : { parse: [] }; - - // If its already been executed, we need to send a followup response - if (bot.cache.executedSlashCommands.has(token)) { - // FOLLOWUP RESPONSES ARE DIFFERENT STYLED RESPONSE - return await bot.rest.runMethod(bot.rest, "post", bot.constants.endpoints.WEBHOOK(bot.applicationId, token), { - content: options.data.content, - tts: options.data.tts, - embeds: options.data.embeds?.map((embed) => ({ - title: embed.title, - type: embed.type, - description: embed.description, - url: embed.url, - timestamp: embed.timestamp, - color: embed.color, - footer: embed.footer - ? { - text: embed.footer.text, - icon_url: embed.footer.iconUrl, - proxy_icon_url: embed.footer.proxyIconUrl, - } - : undefined, - image: embed.image - ? { - url: embed.image.url, - proxy_url: embed.image.proxyUrl, - height: embed.image.height, - width: embed.image.width, - } - : undefined, - thumbnail: embed.thumbnail - ? { - url: embed.thumbnail.url, - proxy_url: embed.thumbnail.proxyUrl, - height: embed.thumbnail.height, - width: embed.thumbnail.width, - } - : undefined, - video: embed.video - ? { - url: embed.video.url, - proxy_url: embed.video.proxyUrl, - height: embed.video.height, - width: embed.video.width, - } - : undefined, - provider: embed.provider, - author: embed.author - ? { - name: embed.author.name, - url: embed.author.url, - icon_url: embed.author.iconUrl, - proxy_icon_url: embed.author.proxyIconUrl, - } - : undefined, - fields: embed.fields, - })), - allowed_mentions: { - parse: allowedMentions.parse, - roles: allowedMentions.roles, - users: allowedMentions.users, - replied_user: allowedMentions.repliedUser, - }, - file: options.data.file, - components: options.data.components?.map((component) => ({ - type: component.type, - components: component.components.map((subcomponent) => { - if (subcomponent.type === MessageComponentTypes.SelectMenu) - return { - type: subcomponent.type, - custom_id: subcomponent.customId, - placeholder: subcomponent.placeholder, - min_values: subcomponent.minValues, - max_values: subcomponent.maxValues, - options: subcomponent.options.map((option) => ({ - label: option.label, - value: option.value, - description: option.description, - emoji: option.emoji - ? { - id: option.emoji.id?.toString(), - name: option.emoji.name, - animated: option.emoji.animated, - } - : undefined, - default: option.default, - })), - }; - + // DRY code a little bit + const data = { + content: options.data.content, + tts: options.data.tts, + embeds: options.data.embeds?.map((embed) => ({ + title: embed.title, + type: embed.type, + description: embed.description, + url: embed.url, + timestamp: embed.timestamp, + color: embed.color, + footer: embed.footer + ? { + text: embed.footer.text, + icon_url: embed.footer.iconUrl, + proxy_icon_url: embed.footer.proxyIconUrl, + } + : undefined, + image: embed.image + ? { + url: embed.image.url, + proxy_url: embed.image.proxyUrl, + height: embed.image.height, + width: embed.image.width, + } + : undefined, + thumbnail: embed.thumbnail + ? { + url: embed.thumbnail.url, + proxy_url: embed.thumbnail.proxyUrl, + height: embed.thumbnail.height, + width: embed.thumbnail.width, + } + : undefined, + video: embed.video + ? { + url: embed.video.url, + proxy_url: embed.video.proxyUrl, + height: embed.video.height, + width: embed.video.width, + } + : undefined, + provider: embed.provider, + author: embed.author + ? { + name: embed.author.name, + url: embed.author.url, + icon_url: embed.author.iconUrl, + proxy_icon_url: embed.author.proxyIconUrl, + } + : undefined, + fields: embed.fields, + })), + allowed_mentions: { + parse: options.data.allowedMentions!.parse, + replied_user: options.data.allowedMentions!.repliedUser, + users: options.data.allowedMentions!.users?.map((id) => id.toString()), + roles: options.data.allowedMentions!.roles?.map((id) => id.toString()), + }, + file: options.data.file, + components: options.data.components?.map((component) => ({ + type: component.type, + components: component.components.map((subcomponent) => { + if (subcomponent.type === MessageComponentTypes.SelectMenu) return { type: subcomponent.type, custom_id: subcomponent.customId, - label: subcomponent.label, - style: subcomponent.style, - emoji: subcomponent.emoji - ? { - id: subcomponent.emoji.id?.toString(), - name: subcomponent.emoji.name, - animated: subcomponent.emoji.animated, - } - : undefined, - url: subcomponent.url, - disabled: subcomponent.disabled, + placeholder: subcomponent.placeholder, + min_values: subcomponent.minValues, + max_values: subcomponent.maxValues, + options: subcomponent.options.map((option) => ({ + label: option.label, + value: option.value, + description: option.description, + emoji: option.emoji + ? { + id: option.emoji.id?.toString(), + name: option.emoji.name, + animated: option.emoji.animated, + } + : undefined, + default: option.default, + })), }; - }), - })), - flags: options.data.flags, + + return { + type: subcomponent.type, + custom_id: subcomponent.customId, + label: subcomponent.label, + style: subcomponent.style, + emoji: subcomponent.emoji + ? { + id: subcomponent.emoji.id?.toString(), + name: subcomponent.emoji.name, + animated: subcomponent.emoji.animated, + } + : undefined, + url: subcomponent.url, + disabled: subcomponent.disabled, + }; + }), + })), + flags: options.data.flags, + }; + + // A reply has never been send + if (bot.cache.unrepliedInteractions.has(id)) { + bot.cache.unrepliedInteractions.delete(id); + + return await bot.rest.runMethod(bot.rest, "post", bot.constants.endpoints.INTERACTION_ID_TOKEN(id, token), { + type: options.type, + data, }); } - // Expire in 15 minutes - bot.cache.executedSlashCommands.add(token); - setTimeout(() => { - bot.events.debug(`Running setTimeout in send_interaction_response file.`); - bot.cache.executedSlashCommands.delete(token); - }, 900000); - - return await bot.rest.runMethod( - bot.rest, - "post", - bot.constants.endpoints.INTERACTION_ID_TOKEN(typeof id === "bigint" ? id : bot.transformers.snowflake(id), token), - { - type: options.type, - data: { - content: options.data.content, - tts: options.data.tts, - embeds: options.data.embeds?.map((embed) => ({ - title: embed.title, - type: embed.type, - description: embed.description, - url: embed.url, - timestamp: embed.timestamp, - color: embed.color, - footer: embed.footer - ? { - text: embed.footer.text, - icon_url: embed.footer.iconUrl, - proxy_icon_url: embed.footer.proxyIconUrl, - } - : undefined, - image: embed.image - ? { - url: embed.image.url, - proxy_url: embed.image.proxyUrl, - height: embed.image.height, - width: embed.image.width, - } - : undefined, - thumbnail: embed.thumbnail - ? { - url: embed.thumbnail.url, - proxy_url: embed.thumbnail.proxyUrl, - height: embed.thumbnail.height, - width: embed.thumbnail.width, - } - : undefined, - video: embed.video - ? { - url: embed.video.url, - proxy_url: embed.video.proxyUrl, - height: embed.video.height, - width: embed.video.width, - } - : undefined, - provider: embed.provider, - author: embed.author - ? { - name: embed.author.name, - url: embed.author.url, - icon_url: embed.author.iconUrl, - proxy_icon_url: embed.author.proxyIconUrl, - } - : undefined, - fields: embed.fields, - })), - allowed_mentions: { - parse: allowedMentions.parse, - roles: allowedMentions.roles, - users: allowedMentions.users, - replied_user: allowedMentions.repliedUser, - }, - file: options.data.file, - components: options.data.components?.map((component) => ({ - type: component.type, - components: component.components.map((subcomponent) => { - if (subcomponent.type === MessageComponentTypes.SelectMenu) - return { - type: subcomponent.type, - custom_id: subcomponent.customId, - placeholder: subcomponent.placeholder, - min_values: subcomponent.minValues, - max_values: subcomponent.maxValues, - options: subcomponent.options.map((option) => ({ - label: option.label, - value: option.value, - description: option.description, - emoji: option.emoji - ? { - id: option.emoji.id?.toString(), - name: option.emoji.name, - animated: option.emoji.animated, - } - : undefined, - default: option.default, - })), - }; - - return { - type: subcomponent.type, - custom_id: subcomponent.customId, - label: subcomponent.label, - style: subcomponent.style, - emoji: subcomponent.emoji - ? { - id: subcomponent.emoji.id?.toString(), - name: subcomponent.emoji.name, - animated: subcomponent.emoji.animated, - } - : undefined, - url: subcomponent.url, - disabled: subcomponent.disabled, - }; - }), - })), - flags: options.data.flags, - }, - } - ); + // If its already been executed, we need to send a followup response + return await bot.rest.runMethod(bot.rest, "post", bot.constants.endpoints.WEBHOOK(bot.applicationId, token), data); } diff --git a/src/transformers/interaction.ts b/src/transformers/interaction.ts index 9de8b943e..3ca707f9d 100644 --- a/src/transformers/interaction.ts +++ b/src/transformers/interaction.ts @@ -122,7 +122,7 @@ export function transformInteractionDataResolved( ); } - return resolved; + return transformed; } export interface DiscordenoInteraction { diff --git a/src/util/constants.ts b/src/util/constants.ts index 0c816f059..0efd73de7 100644 --- a/src/util/constants.ts +++ b/src/util/constants.ts @@ -9,7 +9,7 @@ export const GATEWAY_VERSION = 9; // TODO: update this version /** https://github.com/discordeno/discordeno/releases */ -export const DISCORDENO_VERSION = "13.0.0-rc4"; +export const DISCORDENO_VERSION = "13.0.0-rc5"; /** https://discord.com/developers/docs/reference#user-agent */ export const USER_AGENT = `DiscordBot (https://github.com/discordeno/discordeno, v${DISCORDENO_VERSION})`;