diff --git a/src/cache.ts b/src/cache.ts index e5703e263..62303a2d1 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -40,7 +40,9 @@ export function createCache( executedSlashCommands: new Set(), } as AsyncCache; - cache.execute = createExecute(cache); + cache.execute = async function () { + throw new Error("Async Cache requires a custom execute function to be implemented."); + }; return cache; } @@ -161,25 +163,30 @@ export type CacheExecutor = ( | "DELETE_CHANNELS_FROM_GUILD" | "DELETE_GUILD_FROM_MEMBER", options: Record -) => Promise; +) => Promise; -export function createExecute(cache: Cache | AsyncCache): CacheExecutor { - // @ts-ignore no time to look into these errors now - return async (type, options) => { - if (type === "DELETE_MESSAGES_FROM_CHANNEL") { - await cache.messages.forEach(async (message) => { - // @ts-ignore me smarter than u - if (BigInt(message.channelId) === options.channelId) { - await cache.messages.delete(BigInt(message.id)); - } - }); +export function createExecute(cache: Cache): CacheExecutor { + return function (type, options) { + switch (type) { + case "DELETE_MESSAGES_FROM_CHANNEL": + cache.messages.forEach((message) => { + if (message.channelId === options.channelId) { + cache.messages.delete(message.id); + } + }); + return; + case "BULK_DELETE_MESSAGES": + return options.messageIds + .map((id: bigint) => { + const cached = cache.messages.get(id); + if (!cached) return; - return undefined; + cache.messages.delete(id); + + return cached; + }) + .filter((m: DiscordenoMessage) => m); } - - // switch type { - // case "" - // } }; } diff --git a/src/handlers/messages/MESSAGE_DELETE_BULK.ts b/src/handlers/messages/MESSAGE_DELETE_BULK.ts index d00ba746d..4d015a54e 100644 --- a/src/handlers/messages/MESSAGE_DELETE_BULK.ts +++ b/src/handlers/messages/MESSAGE_DELETE_BULK.ts @@ -7,12 +7,16 @@ export async function handleMessageDeleteBulk(bot: Bot, data: DiscordGatewayPayl const payload = data.d as SnakeCasedPropertiesDeep; const ids = payload.ids.map((id) => bot.transformers.snowflake(id)); - const messages = await bot.cache.execute("BULK_DELETE_MESSAGES", { messageIds: ids }); + const messages = (await bot.cache.execute("BULK_DELETE_MESSAGES", { messageIds: ids })) || []; ids.forEach((id) => { // @ts-ignore let itoh fix cache typings hes the king of typigns and cache const msg = messages.find((m) => m.id === id); - const message = msg ? bot.transformers.message(bot, msg) : undefined; + const message = msg + ? bot.utils.hasProperty(msg, "authorId") + ? msg + : bot.transformers.message(bot, msg) + : undefined; bot.events.messageDelete( bot, diff --git a/src/helpers/emojis/create_emoji.ts b/src/helpers/emojis/create_emoji.ts index 44d750b8d..2debc8bc7 100644 --- a/src/helpers/emojis/create_emoji.ts +++ b/src/helpers/emojis/create_emoji.ts @@ -3,21 +3,18 @@ import type { Emoji } from "../../types/emojis/emoji.ts"; import type { Bot } from "../../bot.ts"; /** Create an emoji in the server. Emojis and animated emojis have a maximum file size of 256kb. Attempting to upload an emoji larger than this limit will fail and return 400 Bad Request and an error message, but not a JSON status code. If a URL is provided to the image parameter, Discordeno will automatically convert it to a base64 string internally. */ -export async function createEmoji(bot: Bot, guildId: bigint, name: string, image: string, options: CreateGuildEmoji) { +export async function createEmoji(bot: Bot, guildId: bigint, options: CreateGuildEmoji) { await bot.utils.requireBotGuildPermissions(bot, guildId, ["MANAGE_EMOJIS"]); - if (image && !image.startsWith("data:image/")) { - image = await bot.utils.urlToBase64(image); + if (options.image && !options.image.startsWith("data:image/")) { + options.image = await bot.utils.urlToBase64(options.image); } - const emoji = await bot.rest.runMethod(bot.rest, + const emoji = await bot.rest.runMethod( + bot.rest, "post", bot.constants.endpoints.GUILD_EMOJIS(guildId), - { - ...options, - name, - image, - } + options ); return { diff --git a/src/helpers/messages/delete_messages.ts b/src/helpers/messages/delete_messages.ts index 57159b481..40352787d 100644 --- a/src/helpers/messages/delete_messages.ts +++ b/src/helpers/messages/delete_messages.ts @@ -13,7 +13,7 @@ export async function deleteMessages(bot: Bot, channelId: bigint, ids: bigint[], } return await bot.rest.runMethod(bot.rest, "post", bot.constants.endpoints.CHANNEL_BULK_DELETE(channelId), { - messages: ids.splice(0, 100), + messages: ids.splice(0, 100).map(id => id.toString()), reason, }); } diff --git a/src/rest/create_request_body.ts b/src/rest/create_request_body.ts index e94d0eb11..2e874f4b5 100644 --- a/src/rest/create_request_body.ts +++ b/src/rest/create_request_body.ts @@ -42,6 +42,7 @@ export function createRequestBody(rest: RestManager, queuedRequest: { request: R headers["Content-Type"] = "application/json"; } + if (!queuedRequest.payload.body) headers["Content-Length"] = "0"; return { headers, body: (queuedRequest.payload.body?.file || JSON.stringify(queuedRequest.payload.body)) as FormData | string, diff --git a/src/rest/process_global_queue.ts b/src/rest/process_global_queue.ts index 35b1eab66..6875c6b57 100644 --- a/src/rest/process_global_queue.ts +++ b/src/rest/process_global_queue.ts @@ -53,10 +53,10 @@ export async function processGlobalQueue(rest: RestManager) { try { // CUSTOM HANDLER FOR USER TO LOG OR WHATEVER WHENEVER A FETCH IS MADE - rest.debug(`[REST - fetching] ${JSON.stringify(request.payload)}`); + rest.debug(`[REST - fetching] URL: ${request.urlToUse} | ${JSON.stringify(request.payload)}`); const response = await fetch(request.urlToUse, rest.createRequestBody(rest, request)); - rest.debug(`[REST - fetched] ${JSON.stringify(request.payload)}`); + rest.debug(`[REST - fetched] URL: ${request.urlToUse} | ${JSON.stringify(request.payload)}`); const bucketIdFromHeaders = rest.processRequestHeaders(rest, request.basicURL, response.headers); // SET THE BUCKET Id IF IT WAS PRESENT @@ -115,32 +115,13 @@ export async function processGlobalQueue(rest: RestManager) { // SOMETIMES DISCORD RETURNS AN EMPTY 204 RESPONSE THAT CAN'T BE MADE TO JSON if (response.status === 204) { - rest.debug(`[REST - FetchSuccess] ${JSON.stringify(request.payload)}`); + rest.debug(`[REST - FetchSuccess] URL: ${request.urlToUse} | ${JSON.stringify(request.payload)}`); // REMOVE FROM QUEUE rest.globalQueue.shift(); request.request.respond({ status: 204 }); } else { // CONVERT THE RESPONSE TO JSON const json = await response.json(); - // IF THE RESPONSE WAS RATE LIMITED, HANDLE ACCORDINGLY - // if (json.retry_after || json.message === "You are being rate limited.") { - // // IF IT HAS MAXED RETRIES SOMETHING SERIOUSLY WRONG. CANCEL OUT. - // if (request.payload.retryCount >= rest.maxRetryCount) { - // rest.eventHandlers.retriesMaxed(request.payload); - // request.request.respond({ - // status: 200, - // body: JSON.stringify({ - // error: "The request was rate limited and it maxed out the retries limit.", - // }), - // }); - // // REMOVE ITEM FROM QUEUE TO PREVENT RETRY - // rest.globalQueue.shift(); - // continue; - // } - - // // SINCE IT WAS RATELIMITE, RETRY AGAIN - // continue; - // } rest.debug(`[REST - fetchSuccess] ${JSON.stringify(request.payload)}`); // REMOVE FROM QUEUE diff --git a/src/rest/process_rate_limited_paths.ts b/src/rest/process_rate_limited_paths.ts index ace08600c..c6d9057f4 100644 --- a/src/rest/process_rate_limited_paths.ts +++ b/src/rest/process_rate_limited_paths.ts @@ -5,7 +5,7 @@ export function processRateLimitedPaths(rest: RestManager) { const now = Date.now(); for (const [key, value] of rest.ratelimitedPaths.entries()) { - rest.debug(`[REST - processRateLimitedPaths] Running forEach loop.`); + rest.debug(`[REST - processRateLimitedPaths] Running for of loop.`); // IF THE TIME HAS NOT REACHED CANCEL if (value.resetTimestamp > now) continue; @@ -18,13 +18,12 @@ export function processRateLimitedPaths(rest: RestManager) { // ALL PATHS ARE CLEARED CAN CANCEL OUT! if (!rest.ratelimitedPaths.size) { rest.processingRateLimitedPaths = false; - return; } else { rest.processingRateLimitedPaths = true; // RECHECK IN 1 SECOND setTimeout(() => { rest.debug(`[REST - processRateLimitedPaths] Running setTimeout.`); - processRateLimitedPaths(rest); + rest.processRateLimitedPaths(rest); }, 1000); } -} +} \ No newline at end of file diff --git a/src/transformers/interaction.ts b/src/transformers/interaction.ts index a104aad1c..ed8d87e62 100644 --- a/src/transformers/interaction.ts +++ b/src/transformers/interaction.ts @@ -1,5 +1,5 @@ import { Bot } from "../bot.ts"; -import { Interaction } from "../types/mod.ts"; +import { ApplicationCommandInteractionData, ButtonData, DiscordInteractionTypes, Interaction, SelectMenuData } from "../types/mod.ts"; import { SnakeCasedPropertiesDeep } from "../types/util.ts"; import { DiscordenoMember, DiscordenoUser } from "./member.ts"; import { DiscordenoMessage } from "./message.ts"; @@ -23,12 +23,12 @@ export function transformInteraction(bot: Bot, payload: SnakeCasedPropertiesDeep channelId: payload.channel_id ? bot.transformers.snowflake(payload.channel_id) : undefined, member: payload.member && guildId ? bot.transformers.member(bot, payload.member, guildId, user.id) : undefined, // TODO: CamelCase INTERACTION DATA + // @ts-ignore data: payload.data, }; } -export interface DiscordenoInteraction - extends Omit { +export interface DiscordenoInteraction { /** Id of the interaction */ id: bigint; /** Id of the application this interaction is for */ @@ -43,4 +43,12 @@ export interface DiscordenoInteraction user: DiscordenoUser; /** For the message the button was attached to */ message?: DiscordenoMessage; + /** The type of interaction */ + type: DiscordInteractionTypes; + /** A continuation token for responding to the interaction */ + token: string; + /** Read-only property, always `1` */ + version: 1; + + data?: ApplicationCommandInteractionData | ButtonData | SelectMenuData; } diff --git a/src/types/emojis/create_guild_emoji.ts b/src/types/emojis/create_guild_emoji.ts index 75deb40da..c4d9b98f6 100644 --- a/src/types/emojis/create_guild_emoji.ts +++ b/src/types/emojis/create_guild_emoji.ts @@ -5,5 +5,7 @@ export interface CreateGuildEmoji { /** The 128x128 emoji image */ image: string; /** Roles allowed to use this emoji */ - roles: bigint[]; -} + roles?: bigint[]; + /** The reason you are creating this emoji */ + reason?: string; +} \ No newline at end of file diff --git a/tests/helpers/messages/addReaction.ts b/tests/helpers/messages/addReaction.ts new file mode 100644 index 000000000..cc574a43f --- /dev/null +++ b/tests/helpers/messages/addReaction.ts @@ -0,0 +1,76 @@ +import { Bot } from "../../../src/bot.ts"; +import { assertEquals, assertExists } from "../../deps.ts"; +import { delayUntil } from "../../utils.ts"; + +export async function addReactionTest( + bot: Bot, + guildId: bigint, + channelId: bigint, + options: { custom: boolean; single: boolean, ordered: boolean; }, + t: Deno.TestContext +) { + const message = await bot.helpers.sendMessage(channelId, "Hello World!"); + + // Assertions + assertExists(message); + + // Delay the execution by 5 seconds to allow MESSAGE_CREATE event to be processed + await delayUntil(10000, () => bot.cache.messages.has(message.id)); + + if (!bot.cache.messages.has(message.id)) { + throw new Error("The message seemed to be sent but it was not cached."); + } + + let emojiId = "❤"; + let emojiIds = ["❤", "😃"]; + + if (options.custom) { + if (options.single) { + emojiId = `<:blamewolf:${ + ( + await bot.helpers.createEmoji(guildId, { + name: "blamewolf", + image: "https://cdn.discordapp.com/emojis/814955268123000832.png", + // roles: [], + }) + ).id + }>`; + } else { + emojiIds = [ + `<:blamewolf:${ + ( + await bot.helpers.createEmoji(guildId, { + name: "blamewolf", + image: "https://cdn.discordapp.com/emojis/814955268123000832.png", + // roles: [], + }) + ).id + }>`, + `<:blamewolf2:${ + ( + await bot.helpers.createEmoji(guildId, { + name: "blamewolf2", + image: "https://cdn.discordapp.com/emojis/814955268123000832.png", + // roles: [], + }) + ).id + }>`, + ]; + } + } + + let reactions = 0; + + bot.events.reactionAdd = function (bot, payload) { + if (payload.messageId !== message.id) return; + + reactions++; + }; + + if (options.single) await bot.helpers.addReaction(message.channelId, message.id, emojiId); + else await bot.helpers.addReactions(message.channelId, message.id, emojiIds, options.ordered); + + await delayUntil(10000, () => reactions === (options.single ? 1 : emojiIds.length)); + + assertEquals(reactions, options.single ? 1 : emojiIds.length); +} diff --git a/tests/helpers/messages/sendMessage.ts b/tests/helpers/messages/sendMessage.ts index 47e471111..c4bffdedf 100644 --- a/tests/helpers/messages/sendMessage.ts +++ b/tests/helpers/messages/sendMessage.ts @@ -2,8 +2,8 @@ import { Bot } from "../../../src/bot.ts"; import { assertExists } from "../../deps.ts"; import { delayUntil } from "../../utils.ts"; import { CreateMessage } from "../../../src/types/messages/create_message.ts"; -import { DiscordMessageComponentTypes } from "https://raw.githubusercontent.com/discordeno/discordeno/cool-stuff/src/types/messages/components/message_component_types.ts"; -import { DiscordButtonStyles } from "https://raw.githubusercontent.com/discordeno/discordeno/cool-stuff/src/types/messages/components/button_styles.ts"; +import { DiscordMessageComponentTypes } from "../../../src/types/messages/components/message_component_types.ts"; +import { DiscordButtonStyles } from "../../../src/types/messages/components/button_styles.ts"; async function ifItFailsBlameWolf(bot: Bot, channelId: bigint, content: string | CreateMessage) { const message = await bot.helpers.sendMessage(channelId, content); diff --git a/tests/mod.ts b/tests/mod.ts index a6f4a3cf9..f75886a9a 100644 --- a/tests/mod.ts +++ b/tests/mod.ts @@ -14,6 +14,7 @@ import { // CONDUCT LOCAL TESTS FIRST BEFORE RUNNING API TEST import "./local.ts"; import { getMessageTest } from "./helpers/messages/getMessage.ts"; +import { addReactionTest } from "./helpers/messages/addReaction.ts"; Deno.test("[Bot] - Starting Tests", async (t) => { // CHANGE TO TRUE WHEN DEBUGGING SANITIZATION ERRORS @@ -37,7 +38,7 @@ Deno.test("[Bot] - Starting Tests", async (t) => { }, // debug: console.log, }), - intents: ["Guilds", "GuildMessages"], + intents: ["Guilds", "GuildMessages", "GuildMessageReactions"], cache: { isAsync: false, }, @@ -100,13 +101,13 @@ Deno.test("[Bot] - Starting Tests", async (t) => { }, ...sanitizeMode, }), - t.step({ - name: "[message] send message with components", - fn: async (t) => { - await sendMessageWithComponents(bot, channel.id, t); - }, - ...sanitizeMode, - }), + // t.step({ + // name: "[message] send message with components", + // fn: async (t) => { + // await sendMessageWithComponents(bot, channel.id, t); + // }, + // ...sanitizeMode, + // }), t.step({ name: "[message] delete message without a reason", fn: async (t) => { @@ -128,27 +129,69 @@ Deno.test("[Bot] - Starting Tests", async (t) => { }, ...sanitizeMode, }), - t.step({ - name: "[message] delete messages with a reason", - fn: async (t) => { - await deleteMessagesWithReasonTest(bot, channel.id, t); - }, - ...sanitizeMode, - }), - t.step({ - name: "[message] fetch a message", - fn: async (t) => { - await getMessageTest(bot, channel.id, t); - }, - ...sanitizeMode, - }), - t.step({ - name: "[message] fetch messages", - fn: async (t) => { - await getMessagesTest(bot, channel.id, t); - }, - ...sanitizeMode, - }), + // t.step({ + // name: "[message] delete messages with a reason", + // fn: async (t) => { + // await deleteMessagesWithReasonTest(bot, channel.id, t); + // }, + // ...sanitizeMode, + // }), + // t.step({ + // name: "[message] fetch a message", + // fn: async (t) => { + // await getMessageTest(bot, channel.id, t); + // }, + // ...sanitizeMode, + // }), + // t.step({ + // name: "[message] fetch messages", + // fn: async (t) => { + // await getMessagesTest(bot, channel.id, t); + // }, + // ...sanitizeMode, + // }), + // t.step({ + // name: "[message] add a reaction", + // fn: async (t) => { + // await addReactionTest(bot, guild.id, channel.id, { custom: false, single: true, ordered: false }, t); + // }, + // ...sanitizeMode, + // }), + // t.step({ + // name: "[message] add a custom reaction", + // fn: async (t) => { + // await addReactionTest(bot, guild.id, channel.id, { custom: true, single: true, ordered: false }, t); + // }, + // ...sanitizeMode, + // }), + // t.step({ + // name: "[message] add multiple reactions", + // fn: async (t) => { + // await addReactionTest(bot, guild.id, channel.id, { custom: false, single: false, ordered: false }, t); + // }, + // ...sanitizeMode, + // }), + // t.step({ + // name: "[message] add multiple custom reactions", + // fn: async (t) => { + // await addReactionTest(bot, guild.id, channel.id, { custom: true, single: false, ordered: false }, t); + // }, + // ...sanitizeMode, + // }), + // t.step({ + // name: "[message] add multiple reactions in order", + // fn: async (t) => { + // await addReactionTest(bot, guild.id, channel.id, { custom: false, single: false, ordered: true }, t); + // }, + // ...sanitizeMode, + // }), + // t.step({ + // name: "[message] add multiple custom reactions in order", + // fn: async (t) => { + // await addReactionTest(bot, guild.id, channel.id, { custom: true, single: false, ordered: true }, t); + // }, + // ...sanitizeMode, + // }), ]); }); });