From 8b2a554c911ba0ec1ec857fe16a3c96a0fac64be Mon Sep 17 00:00:00 2001 From: ITOH Date: Thu, 27 May 2021 16:38:34 +0200 Subject: [PATCH 01/21] add: message sweeper --- src/cache.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/cache.ts b/src/cache.ts index 02f30ca09..b93bd7773 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -14,7 +14,7 @@ export const cache = { /** 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 */ - messages: new Collection(), + messages: new Collection([], { sweeper: { filter: messageSweeper, interval: 300000 } }), /** All of the member objects that have been cached since the bot acquired `READY` state, mapped by their Ids */ members: new Collection(), /** All of the unavailable guilds, mapped by their Ids (id, timestamp) */ @@ -33,6 +33,16 @@ export const cache = { }, }; +function messageSweeper(message: DiscordenoMessage) { + // DM messages aren't needed + if (!message.guildId) return true; + + // Only delete messages older than 10 minutes + if (Date.now() - message.timestamp > 600000) return true; + + return false; +} + export let cacheHandlers = { /** Deletes all items from the cache */ async clear(table: TableName) { From 5319ed23096ca2568509443df895b5e20f8bd070 Mon Sep 17 00:00:00 2001 From: ITOH Date: Thu, 27 May 2021 16:43:41 +0200 Subject: [PATCH 02/21] add: member sweeper --- src/cache.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/cache.ts b/src/cache.ts index b93bd7773..4ff58d907 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -1,4 +1,5 @@ // deno-lint-ignore-file require-await no-explicit-any prefer-const +import { botId } from "./bot.ts"; import type { DiscordenoChannel } from "./structures/channel.ts"; import type { DiscordenoGuild } from "./structures/guild.ts"; import type { DiscordenoMember } from "./structures/member.ts"; @@ -16,7 +17,7 @@ export const cache = { /** All of the message objects the bot has cached since the bot acquired `READY` state, mapped by their Ids */ messages: new Collection([], { sweeper: { filter: messageSweeper, interval: 300000 } }), /** All of the member objects that have been cached since the bot acquired `READY` state, mapped by their Ids */ - members: new Collection(), + members: new Collection([], { sweeper: { filter: memberSweeper, interval: 1800000 } }), /** All of the unavailable guilds, mapped by their Ids (id, timestamp) */ unavailableGuilds: new Collection(), /** All of the presence update objects received in PRESENCE_UPDATE gateway event, mapped by their user Id */ @@ -43,6 +44,12 @@ function messageSweeper(message: DiscordenoMessage) { return false; } +function memberSweeper(member: DiscordenoMember) { + if (member.id === botId) return false; + + return true; +} + export let cacheHandlers = { /** Deletes all items from the cache */ async clear(table: TableName) { From 31e4929e6214277ae6f451e2edbc425703f2f9c2 Mon Sep 17 00:00:00 2001 From: ITOH Date: Thu, 27 May 2021 16:56:34 +0200 Subject: [PATCH 03/21] add: cachedAt property for member struct useful for sweeping --- src/structures/member.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/structures/member.ts b/src/structures/member.ts index 7166f34ea..127f397a8 100644 --- a/src/structures/member.ts +++ b/src/structures/member.ts @@ -169,6 +169,7 @@ export async function createDiscordenoMember( /** The guild related data mapped by guild id */ guilds: createNewProp(new Collection()), bitfield: createNewProp(bitfield), + cachedAt: createNewProp(Date.now()), }); const cached = await cacheHandlers.get("members", snowflakeToBigint(user.id)); @@ -210,6 +211,8 @@ export interface DiscordenoMember extends Omit; /** Holds all the boolean toggles. */ bitfield: bigint; + /** When the member has been cached the last time. */ + cachedAt: number; // GETTERS /** The avatar url using the default format and size. */ @@ -230,7 +233,9 @@ export interface DiscordenoMember extends Omit & { joinedAt?: number; premiumSince?: number; From 58e20cc9253eb980d2b44d795492bca8ad7e6f33 Mon Sep 17 00:00:00 2001 From: ITOH Date: Thu, 27 May 2021 16:56:45 +0200 Subject: [PATCH 04/21] fix buggs --- src/cache.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/cache.ts b/src/cache.ts index 4ff58d907..b388d29d8 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -17,7 +17,7 @@ export const cache = { /** All of the message objects the bot has cached since the bot acquired `READY` state, mapped by their Ids */ messages: new Collection([], { sweeper: { filter: messageSweeper, interval: 300000 } }), /** All of the member objects that have been cached since the bot acquired `READY` state, mapped by their Ids */ - members: new Collection([], { sweeper: { filter: memberSweeper, interval: 1800000 } }), + members: new Collection([], { sweeper: { filter: memberSweeper, interval: 300000 } }), /** All of the unavailable guilds, mapped by their Ids (id, timestamp) */ unavailableGuilds: new Collection(), /** All of the presence update objects received in PRESENCE_UPDATE gateway event, mapped by their user Id */ @@ -45,8 +45,12 @@ function messageSweeper(message: DiscordenoMessage) { } function memberSweeper(member: DiscordenoMember) { + // Don't sweep the bot else strange things will happen if (member.id === botId) return false; + // Only sweep members who were not active the last 30 minutes + if (member.cachedAt - Date.now() < 1800000) return false; + return true; } From 355d3cb582501ec33c0efdbcdce012e84a071a50 Mon Sep 17 00:00:00 2001 From: ITOH Date: Thu, 27 May 2021 16:57:34 +0200 Subject: [PATCH 05/21] better explanation --- src/handlers/misc/READY.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers/misc/READY.ts b/src/handlers/misc/READY.ts index 8bee0fbdf..1109fb1d5 100644 --- a/src/handlers/misc/READY.ts +++ b/src/handlers/misc/READY.ts @@ -38,7 +38,7 @@ function checkReady(payload: Ready, shard: DiscordenoShard) { // Check if all guilds were loaded if (!shard.unavailableGuildIds.size) return loaded(shard); - // If the last GUILD_CREATE has been received before 5 seconds if so most likely the remaining guilds are unavailable + // If the last GUILD_CREATE was received 5 seconds ago, the remaining guilds are most likely not available if (shard.lastAvailable + 5000 < Date.now()) { eventHandlers.shardFailedToLoad?.(shard.id, shard.unavailableGuildIds); // Force execute the loaded function to prevent infinite loop From 6a8b183fbff99ead18d5743069fa87ac20ca9047 Mon Sep 17 00:00:00 2001 From: ITOH Date: Thu, 27 May 2021 16:57:45 +0200 Subject: [PATCH 06/21] add: presence sweeper --- src/cache.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cache.ts b/src/cache.ts index b388d29d8..7d51916e6 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -21,7 +21,7 @@ export const cache = { /** All of the unavailable guilds, mapped by their Ids (id, timestamp) */ unavailableGuilds: new Collection(), /** All of the presence update objects received in PRESENCE_UPDATE gateway event, mapped by their user Id */ - presences: new Collection(), + presences: new Collection([], { sweeper: { filter: () => true, interval: 300000 } }), fetchAllMembersProcessingRequests: new Collection< string, (value: Collection | PromiseLike>) => void From 7f280bd17be2cd7c80b79a87be5e2f9693ec588b Mon Sep 17 00:00:00 2001 From: ITOH Date: Thu, 27 May 2021 17:17:09 +0200 Subject: [PATCH 07/21] add: guild dispatch --- src/cache.ts | 25 ++++++++- src/util/dispatch_requirements.ts | 93 +++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 src/util/dispatch_requirements.ts 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.`); +} From 12691f2567acbc19f3ed1fb54bdeacdf8a82d406 Mon Sep 17 00:00:00 2001 From: ITOH Date: Thu, 27 May 2021 17:19:42 +0200 Subject: [PATCH 08/21] fix buggs --- src/bot.ts | 3 ++- src/cache.ts | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/bot.ts b/src/bot.ts index 637df063d..2f023e0a1 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -5,13 +5,14 @@ import { DiscordGatewayIntents } from "./types/gateway/gateway_intents.ts"; import { snowflakeToBigint } from "./util/bigint.ts"; import { GATEWAY_VERSION } from "./util/constants.ts"; import { ws } from "./ws/ws.ts"; +import { dispatchRequirements } from "./util/dispatch_requirements.ts"; // deno-lint-ignore prefer-const export let secretKey = ""; export let botId = 0n; export let applicationId = 0n; -export let eventHandlers: EventHandlers = {}; +export let eventHandlers: EventHandlers = { dispatchRequirements }; export let proxyWSURL = `wss://gateway.discord.gg`; diff --git a/src/cache.ts b/src/cache.ts index f765c5ec9..ab88f2537 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -62,7 +62,6 @@ export function guildSweeper(guild: DiscordenoGuild) { // 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 @@ -74,7 +73,9 @@ export function guildSweeper(guild: DiscordenoGuild) { }); // Reset activity for next interval - cache.activeGuildIds.clear(); + cache.activeGuildIds.delete(guild.id); + + return true; } export let cacheHandlers = { From c7e3a05816294dba687ee0187c33919e28e4e09d Mon Sep 17 00:00:00 2001 From: ITOH Date: Thu, 27 May 2021 17:20:37 +0200 Subject: [PATCH 09/21] return condition --- src/cache.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/cache.ts b/src/cache.ts index ab88f2537..e19c393d5 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -42,9 +42,7 @@ function messageSweeper(message: DiscordenoMessage) { if (!message.guildId) return true; // Only delete messages older than 10 minutes - if (Date.now() - message.timestamp > 600000) return true; - - return false; + return Date.now() - message.timestamp > 600000; } function memberSweeper(member: DiscordenoMember) { @@ -52,9 +50,7 @@ function memberSweeper(member: DiscordenoMember) { if (member.id === botId) return false; // Only sweep members who were not active the last 30 minutes - if (member.cachedAt - Date.now() < 1800000) return false; - - return true; + return member.cachedAt - Date.now() < 1800000; } export function guildSweeper(guild: DiscordenoGuild) { From cccde10fd2df38fa850fe9f1a567e3679cdb9624 Mon Sep 17 00:00:00 2001 From: ITOH Date: Thu, 27 May 2021 18:04:05 +0200 Subject: [PATCH 10/21] Update collection.ts --- src/util/collection.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/util/collection.ts b/src/util/collection.ts index 97173eebb..894542077 100644 --- a/src/util/collection.ts +++ b/src/util/collection.ts @@ -31,6 +31,18 @@ export class Collection extends Map { return clearInterval(this.sweeper?.intervalId); } + changeSweeperInterval(newInterval: number) { + if (!this.sweeper) return; + + this.startSweeper({ filter: this.sweeper.filter, interval: newInterval }); + } + + changeSweeperFilter(newFilter: (value: V, key: K) => boolean | Promise) { + if (!this.sweeper) return; + + this.startSweeper({ filter: newFilter, interval: this.sweeper.interval }); + } + set(key: K, value: V) { // When this collection is maxSizeed make sure we can add first if ((this.maxSize || this.maxSize === 0) && this.size >= this.maxSize) { From a293cd27d3fc470388f43de82cc4cdfea2fef1de Mon Sep 17 00:00:00 2001 From: Skillz Date: Thu, 27 May 2021 23:45:12 -0400 Subject: [PATCH 11/21] shtuff --- src/helpers/mod.ts | 5 +- src/helpers/type_guards/is_action_row.ts | 8 -- src/helpers/type_guards/is_button.ts | 7 +- src/helpers/type_guards/is_select_menu.ts | 7 ++ .../application_command_interaction_data.ts | 4 - src/types/interactions/interaction.ts | 19 ++++- src/types/messages/components/action_row.ts | 10 ++- src/types/messages/components/button_data.ts | 6 ++ .../components/message_component_types.ts | 2 + .../messages/components/message_components.ts | 5 +- src/types/messages/components/select_data.ts | 8 ++ src/types/messages/components/select_menu.ts | 14 ++++ .../messages/components/select_option.ts | 21 ++++++ src/types/messages/mod.ts | 3 + src/util/utils.ts | 73 +++++++++---------- 15 files changed, 130 insertions(+), 62 deletions(-) delete mode 100644 src/helpers/type_guards/is_action_row.ts create mode 100644 src/helpers/type_guards/is_select_menu.ts create mode 100644 src/types/messages/components/button_data.ts create mode 100644 src/types/messages/components/select_data.ts create mode 100644 src/types/messages/components/select_menu.ts create mode 100644 src/types/messages/components/select_option.ts diff --git a/src/helpers/mod.ts b/src/helpers/mod.ts index 5d928852c..639177c4e 100644 --- a/src/helpers/mod.ts +++ b/src/helpers/mod.ts @@ -114,8 +114,9 @@ import { getGuildTemplates } from "./templates/get_guild_templates.ts"; import { getTemplate } from "./templates/get_template.ts"; import { syncGuildTemplate } from "./templates/sync_guild_template.ts"; // Type Guards -import { isActionRow } from "./type_guards/is_action_row.ts"; import { isButton } from "./type_guards/is_button.ts"; +import { isSelectMenu } from "./type_guards/is_select_menu.ts"; + import { createWebhook } from "./webhooks/create_webhook.ts"; import { deleteWebhook } from "./webhooks/delete_webhook.ts"; import { deleteWebhookMessage } from "./webhooks/delete_webhook_message.ts"; @@ -239,8 +240,8 @@ export { guildBannerURL, guildIconURL, guildSplashURL, - isActionRow, isButton, + isSelectMenu, isChannelSynced, kick, kickMember, diff --git a/src/helpers/type_guards/is_action_row.ts b/src/helpers/type_guards/is_action_row.ts deleted file mode 100644 index 145f89a60..000000000 --- a/src/helpers/type_guards/is_action_row.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { ActionRow } from "../../types/messages/components/action_row.ts"; -import type { MessageComponent } from "../../types/messages/components/message_components.ts"; -import { MessageComponentTypes } from "../../types/messages/components/message_component_types.ts"; - -/** A type guard function to tell if it is a action row component */ -export function isActionRow(component: MessageComponent): component is ActionRow { - return component.type === MessageComponentTypes.ActionRow; -} diff --git a/src/helpers/type_guards/is_button.ts b/src/helpers/type_guards/is_button.ts index 89483cd94..236d28fe9 100644 --- a/src/helpers/type_guards/is_button.ts +++ b/src/helpers/type_guards/is_button.ts @@ -1,8 +1,7 @@ import type { ButtonComponent } from "../../types/messages/components/button_component.ts"; -import type { MessageComponent } from "../../types/messages/components/message_components.ts"; -import { MessageComponentTypes } from "../../types/messages/components/message_component_types.ts"; +import type { ActionRoleComponents } from "../../types/messages/components/message_components.ts"; /** A type guard function to tell if it is a button component */ -export function isButton(component: MessageComponent): component is ButtonComponent { - return component.type === MessageComponentTypes.Button; +export function isButton(component: ActionRoleComponents): component is ButtonComponent { + return Reflect.has(component, "type"); } diff --git a/src/helpers/type_guards/is_select_menu.ts b/src/helpers/type_guards/is_select_menu.ts new file mode 100644 index 000000000..1e77f5319 --- /dev/null +++ b/src/helpers/type_guards/is_select_menu.ts @@ -0,0 +1,7 @@ +import type { ActionRoleComponents } from "../../types/messages/components/message_components.ts"; +import { SelectMenuComponent } from "../../types/messages/components/select_menu.ts"; + +/** A type guard function to tell if it is a button component */ +export function isSelectMenu(component: ActionRoleComponents): component is SelectMenuComponent { + return !Reflect.has(component, "type"); +} diff --git a/src/types/interactions/commands/application_command_interaction_data.ts b/src/types/interactions/commands/application_command_interaction_data.ts index e209cd208..1be81169a 100644 --- a/src/types/interactions/commands/application_command_interaction_data.ts +++ b/src/types/interactions/commands/application_command_interaction_data.ts @@ -11,8 +11,4 @@ export interface ApplicationCommandInteractionData { resolved?: ApplicationCommandInteractionDataResolved; /** The params + values from the user */ options?: ApplicationCommandInteractionDataOption[]; - /** with the value you defined for this component */ - customId?: string; - /** The type of this component */ - componentType?: 2; } diff --git a/src/types/interactions/interaction.ts b/src/types/interactions/interaction.ts index 09dc367cc..4d2181981 100644 --- a/src/types/interactions/interaction.ts +++ b/src/types/interactions/interaction.ts @@ -3,17 +3,32 @@ import { User } from "../users/user.ts"; import { ApplicationCommandInteractionData } from "./commands/application_command_interaction_data.ts"; import { InteractionGuildMember } from "./interaction_guild_member.ts"; import { DiscordInteractionTypes } from "./interaction_types.ts"; +import { SelectMenuData } from "../messages/components/select_data.ts"; +import { ButtonData } from "../messages/components/button_data.ts"; /** https://discord.com/developers/docs/interactions/slash-commands#interaction */ export interface Interaction { + /** The command data payload */ + data?: ApplicationCommandInteractionData | ButtonData | SelectMenuData; +} + +export interface SlashCommandInteraction extends BaseInteraction { + type: DiscordInteractionTypes.ApplicationCommand; + data?: ApplicationCommandInteractionData; +} + +export interface ComponentInteraction extends BaseInteraction { + type: DiscordInteractionTypes.MessageComponent; + data?: ButtonData | SelectMenuData; +} + +export interface BaseInteraction { /** Id of the interaction */ id: string; /** Id of the application this interaction is for */ applicationId: string; /** The type of interaction */ type: DiscordInteractionTypes; - /** The command data payload */ - data?: ApplicationCommandInteractionData; /** The guild it was sent from */ guildId?: string; /** The channel it was sent from */ diff --git a/src/types/messages/components/action_row.ts b/src/types/messages/components/action_row.ts index 27a4a26e2..09e9f2a8d 100644 --- a/src/types/messages/components/action_row.ts +++ b/src/types/messages/components/action_row.ts @@ -1,9 +1,15 @@ import { ButtonComponent } from "./button_component.ts"; +import { SelectMenuComponent } from "./select_menu.ts"; // TODO: add docs link export interface ActionRow { /** Action rows are a group of buttons. */ type: 1; - /** The button components */ - components: ButtonComponent[]; + /** The components in this row */ + components: + | [SelectMenuComponent | ButtonComponent] + | [ButtonComponent, ButtonComponent] + | [ButtonComponent, ButtonComponent, ButtonComponent] + | [ButtonComponent, ButtonComponent, ButtonComponent, ButtonComponent] + | [ButtonComponent, ButtonComponent, ButtonComponent, ButtonComponent, ButtonComponent]; } diff --git a/src/types/messages/components/button_data.ts b/src/types/messages/components/button_data.ts new file mode 100644 index 000000000..6080abfe8 --- /dev/null +++ b/src/types/messages/components/button_data.ts @@ -0,0 +1,6 @@ +export interface ButtonData { + /** with the value you defined for this component */ + customId: string; + /** The type of this component */ + componentType: 2; +} diff --git a/src/types/messages/components/message_component_types.ts b/src/types/messages/components/message_component_types.ts index 18e37875e..410653c0b 100644 --- a/src/types/messages/components/message_component_types.ts +++ b/src/types/messages/components/message_component_types.ts @@ -4,6 +4,8 @@ export enum DiscordMessageComponentTypes { ActionRow = 1, /** A button! */ Button, + /** A select menu. */ + SelectMenu, } export type MessageComponentTypes = DiscordMessageComponentTypes; diff --git a/src/types/messages/components/message_components.ts b/src/types/messages/components/message_components.ts index 045b0f75a..d0d9e5d8d 100644 --- a/src/types/messages/components/message_components.ts +++ b/src/types/messages/components/message_components.ts @@ -1,6 +1,7 @@ import { ActionRow } from "./action_row.ts"; import { ButtonComponent } from "./button_component.ts"; +import { SelectMenuComponent } from "./select_menu.ts"; -export type MessageComponent = ActionRow | ButtonComponent; +export type ActionRoleComponents = ButtonComponent | SelectMenuComponent; -export type MessageComponents = MessageComponent[]; +export type MessageComponents = ActionRow[]; diff --git a/src/types/messages/components/select_data.ts b/src/types/messages/components/select_data.ts new file mode 100644 index 000000000..241498695 --- /dev/null +++ b/src/types/messages/components/select_data.ts @@ -0,0 +1,8 @@ +export interface SelectMenuData { + /** The type of component */ + componentType: 3; + /** The custom id provided for this component. */ + customId: string; + /** The values chosen by the user. */ + values: string[]; +} diff --git a/src/types/messages/components/select_menu.ts b/src/types/messages/components/select_menu.ts new file mode 100644 index 000000000..6ddf342ef --- /dev/null +++ b/src/types/messages/components/select_menu.ts @@ -0,0 +1,14 @@ +import { SelectOption } from "./select_option.ts"; + +export interface SelectMenuComponent { + /** A custom identifier for this component. Maximum 100 characters. */ + customId: string; + /** A custom placeholder text if nothing is selected. Maximum 100 characters. */ + placeholder?: string; + /** The minimum number of items that must be selected. Default 1. Between 1-25. */ + minValues?: number; + /** The maximum number of items that can be selected. Default 1. Between 1-25. */ + maxValues?: number; + /** The choices! Maximum of 25 items. */ + options: SelectOption[]; +} \ No newline at end of file diff --git a/src/types/messages/components/select_option.ts b/src/types/messages/components/select_option.ts new file mode 100644 index 000000000..390226a31 --- /dev/null +++ b/src/types/messages/components/select_option.ts @@ -0,0 +1,21 @@ +export interface SelectOption { + /** The user-facing name of the option. Maximum 25 characters. */ + label: string; + /** The dev-defined value of the option. Maximum 100 characters. */ + value: string; + /** An additional description of the option. Maximum 50 characters. */ + description?: string; + /** The id, name, and animated properties of an emoji. */ + emoji?: + | string + | { + /** Emoji id */ + id?: string; + /** Emoji name */ + name?: string; + /** Whether this emoji is animated */ + animated?: boolean; + }; + /** Will render this option as already-selected by default. */ + default: boolean; +} diff --git a/src/types/messages/mod.ts b/src/types/messages/mod.ts index 3b700f956..2d8486264 100644 --- a/src/types/messages/mod.ts +++ b/src/types/messages/mod.ts @@ -6,6 +6,9 @@ export * from "./components/button_component.ts"; export * from "./components/button_styles.ts"; export * from "./components/message_component_types.ts"; export * from "./components/message_components.ts"; +export * from "./components/select_data.ts"; +export * from "./components/select_menu.ts"; +export * from "./components/select_option.ts"; export * from "./create_message.ts"; export * from "./edit_message.ts"; export * from "./get_messages.ts"; diff --git a/src/util/utils.ts b/src/util/utils.ts index 43d48466e..4e69fc8a5 100644 --- a/src/util/utils.ts +++ b/src/util/utils.ts @@ -1,6 +1,5 @@ import { encode } from "./deps.ts"; import { eventHandlers } from "../bot.ts"; -import { isActionRow } from "../helpers/type_guards/is_action_row.ts"; import { isButton } from "../helpers/type_guards/is_button.ts"; import { Errors } from "../types/discordeno/errors.ts"; import type { ApplicationCommandOption } from "../types/interactions/commands/application_command_option.ts"; @@ -215,43 +214,6 @@ export function validateComponents(components: MessageComponents) { let actionRowCounter = 0; for (const component of components) { - // 5 Link buttons can not have a customId - if (isButton(component)) { - if (component.type === ButtonStyles.Link && component.customId) { - throw new Error(Errors.LINK_BUTTON_CANNOT_HAVE_CUSTOM_ID); - } - // Other buttons must have a customId - if (!component.customId && component.type !== ButtonStyles.Link) { - throw new Error(Errors.BUTTON_REQUIRES_CUSTOM_ID); - } - - if (!validateLength(component.label, { max: 80 })) { - throw new Error(Errors.COMPONENT_LABEL_TOO_BIG); - } - - if (component.customId && !validateLength(component.customId, { max: 100 })) { - throw new Error(Errors.COMPONENT_CUSTOM_ID_TOO_BIG); - } - - if (typeof component.emoji === "string") { - // A snowflake id was provided - if (/^[0-9]+$/.test(component.emoji)) { - component.emoji = { - id: component.emoji, - }; - } else { - // A unicode emoji was provided - component.emoji = { - name: component.emoji, - }; - } - } - } - - if (!isActionRow(component)) { - continue; - } - actionRowCounter++; // Max of 5 ActionRows per message if (actionRowCounter > 5) throw new Error(Errors.TOO_MANY_ACTION_ROWS); @@ -260,5 +222,40 @@ export function validateComponents(components: MessageComponents) { if (component.components?.length > 5) { throw new Error(Errors.TOO_MANY_COMPONENTS); } + + for (const subcomponent of component.components) { + // 5 Link buttons can not have a customId + if (isButton(subcomponent)) { + if (subcomponent.type === ButtonStyles.Link && subcomponent.customId) { + throw new Error(Errors.LINK_BUTTON_CANNOT_HAVE_CUSTOM_ID); + } + // Other buttons must have a customId + if (!subcomponent.customId && subcomponent.type !== ButtonStyles.Link) { + throw new Error(Errors.BUTTON_REQUIRES_CUSTOM_ID); + } + + if (!validateLength(subcomponent.label, { max: 80 })) { + throw new Error(Errors.subcomponent_LABEL_TOO_BIG); + } + + if (subcomponent.customId && !validateLength(subcomponent.customId, { max: 100 })) { + throw new Error(Errors.subcomponent_CUSTOM_ID_TOO_BIG); + } + + if (typeof subcomponent.emoji === "string") { + // A snowflake id was provided + if (/^[0-9]+$/.test(subcomponent.emoji)) { + subcomponent.emoji = { + id: subcomponent.emoji, + }; + } else { + // A unicode emoji was provided + subcomponent.emoji = { + name: subcomponent.emoji, + }; + } + } + } + } } } From e6faf72cc204a8ca74633d22a7924db68fe5dfd5 Mon Sep 17 00:00:00 2001 From: Skillz4Killz Date: Fri, 28 May 2021 11:35:05 +0000 Subject: [PATCH 12/21] change: prettier code --- src/types/messages/components/select_menu.ts | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/types/messages/components/select_menu.ts b/src/types/messages/components/select_menu.ts index 6ddf342ef..c10f7d56c 100644 --- a/src/types/messages/components/select_menu.ts +++ b/src/types/messages/components/select_menu.ts @@ -1,14 +1,14 @@ import { SelectOption } from "./select_option.ts"; export interface SelectMenuComponent { - /** A custom identifier for this component. Maximum 100 characters. */ - customId: string; - /** A custom placeholder text if nothing is selected. Maximum 100 characters. */ - placeholder?: string; - /** The minimum number of items that must be selected. Default 1. Between 1-25. */ - minValues?: number; - /** The maximum number of items that can be selected. Default 1. Between 1-25. */ - maxValues?: number; - /** The choices! Maximum of 25 items. */ - options: SelectOption[]; -} \ No newline at end of file + /** A custom identifier for this component. Maximum 100 characters. */ + customId: string; + /** A custom placeholder text if nothing is selected. Maximum 100 characters. */ + placeholder?: string; + /** The minimum number of items that must be selected. Default 1. Between 1-25. */ + minValues?: number; + /** The maximum number of items that can be selected. Default 1. Between 1-25. */ + maxValues?: number; + /** The choices! Maximum of 25 items. */ + options: SelectOption[]; +} From 1e61943e737353a1a42ef294865f8d84b34777a7 Mon Sep 17 00:00:00 2001 From: Skillz Date: Fri, 28 May 2021 09:20:30 -0400 Subject: [PATCH 13/21] more fixes for limits --- src/helpers/type_guards/is_button.ts | 3 +- src/helpers/type_guards/is_select_menu.ts | 5 +- src/types/discordeno/errors.ts | 10 ++ src/types/interactions/interaction.ts | 2 +- src/types/messages/components/select_menu.ts | 1 + src/util/utils.ts | 98 +++++++++++++++++--- 6 files changed, 101 insertions(+), 18 deletions(-) diff --git a/src/helpers/type_guards/is_button.ts b/src/helpers/type_guards/is_button.ts index 236d28fe9..9b00f106c 100644 --- a/src/helpers/type_guards/is_button.ts +++ b/src/helpers/type_guards/is_button.ts @@ -1,7 +1,8 @@ import type { ButtonComponent } from "../../types/messages/components/button_component.ts"; import type { ActionRoleComponents } from "../../types/messages/components/message_components.ts"; +import { MessageComponentTypes } from "../../types/messages/components/message_component_types.ts"; /** A type guard function to tell if it is a button component */ export function isButton(component: ActionRoleComponents): component is ButtonComponent { - return Reflect.has(component, "type"); + return component.type === MessageComponentTypes.Button; } diff --git a/src/helpers/type_guards/is_select_menu.ts b/src/helpers/type_guards/is_select_menu.ts index 1e77f5319..7643a0d0c 100644 --- a/src/helpers/type_guards/is_select_menu.ts +++ b/src/helpers/type_guards/is_select_menu.ts @@ -1,7 +1,8 @@ import type { ActionRoleComponents } from "../../types/messages/components/message_components.ts"; -import { SelectMenuComponent } from "../../types/messages/components/select_menu.ts"; +import { MessageComponentTypes } from "../../types/messages/components/message_component_types.ts"; +import type { SelectMenuComponent } from "../../types/messages/components/select_menu.ts"; /** A type guard function to tell if it is a button component */ export function isSelectMenu(component: ActionRoleComponents): component is SelectMenuComponent { - return !Reflect.has(component, "type"); + return component.type === MessageComponentTypes.SelectMenu; } diff --git a/src/types/discordeno/errors.ts b/src/types/discordeno/errors.ts index 8bddc36ca..e40798257 100644 --- a/src/types/discordeno/errors.ts +++ b/src/types/discordeno/errors.ts @@ -98,4 +98,14 @@ export enum Errors { COMPONENT_LABEL_TOO_BIG = "COMPONENT_LABEL_TOO_BIG", COMPONENT_CUSTOM_ID_TOO_BIG = "COMPONENT_CUSTOM_ID_TOO_BIG", BUTTON_REQUIRES_CUSTOM_ID = "BUTTON_REQUIRES_CUSTOM_ID", + COMPONENT_SELECT_MUST_BE_ALONE = "COMPONENT_SELECT_MUST_BE_ALONE", + COMPONENT_PLACEHOLDER_TOO_BIG = "COMPONENT_PLACEHOLDER_TOO_BIG", + COMPONENT_SELECT_MINVALUE_TOO_LOW = "COMPONENT_SELECT_MINVALUE_TOO_LOW", + COMPONENT_SELECT_MINVALUE_TOO_MANY = "COMPONENT_SELECT_MINVALUE_TOO_MANY", + COMPONENT_SELECT_MAXVALUE_TOO_LOW = "COMPONENT_SELECT_MAXVALUE_TOO_LOW", + COMPONENT_SELECT_MAXVALUE_TOO_MANY = "COMPONENT_SELECT_MAXVALUE_TOO_MANY", + COMPONENT_SELECT_OPTIONS_TOO_LOW = "COMPONENT_SELECT_OPTIONS_TOO_LOW", + COMPONENT_SELECT_OPTIONS_TOO_MANY = "COMPONENT_SELECT_OPTIONS_TOO_MANY", + SELECT_OPTION_LABEL_TOO_BIG = "SELECT_OPTION_LABEL_TOO_BIG", + SELECT_OPTION_VALUE_TOO_BIG = "SELECT_OPTION_VALUE_TOO_BIG", } diff --git a/src/types/interactions/interaction.ts b/src/types/interactions/interaction.ts index 4d2181981..aa56a91a1 100644 --- a/src/types/interactions/interaction.ts +++ b/src/types/interactions/interaction.ts @@ -7,7 +7,7 @@ import { SelectMenuData } from "../messages/components/select_data.ts"; import { ButtonData } from "../messages/components/button_data.ts"; /** https://discord.com/developers/docs/interactions/slash-commands#interaction */ -export interface Interaction { +export interface Interaction extends BaseInteraction { /** The command data payload */ data?: ApplicationCommandInteractionData | ButtonData | SelectMenuData; } diff --git a/src/types/messages/components/select_menu.ts b/src/types/messages/components/select_menu.ts index 6ddf342ef..0dadbbcd8 100644 --- a/src/types/messages/components/select_menu.ts +++ b/src/types/messages/components/select_menu.ts @@ -1,6 +1,7 @@ import { SelectOption } from "./select_option.ts"; export interface SelectMenuComponent { + type: 3; /** A custom identifier for this component. Maximum 100 characters. */ customId: string; /** A custom placeholder text if nothing is selected. Maximum 100 characters. */ diff --git a/src/util/utils.ts b/src/util/utils.ts index 4e69fc8a5..2f817cb45 100644 --- a/src/util/utils.ts +++ b/src/util/utils.ts @@ -13,6 +13,7 @@ import type { DiscordImageFormat } from "../types/misc/image_format.ts"; import type { DiscordImageSize } from "../types/misc/image_size.ts"; import { SLASH_COMMANDS_NAME_REGEX } from "./constants.ts"; import { validateLength } from "./validate_length.ts"; +import { isSelectMenu } from "../helpers/type_guards/is_select_menu.ts"; export async function urlToBase64(url: string) { const buffer = await fetch(url).then((res) => res.arrayBuffer()); @@ -221,9 +222,18 @@ export function validateComponents(components: MessageComponents) { // Max of 5 Buttons (or any component type) within an ActionRow if (component.components?.length > 5) { throw new Error(Errors.TOO_MANY_COMPONENTS); + } else if ( + component.components?.length > 1 && + component.components.some((subcomponent) => isSelectMenu(subcomponent)) + ) { + throw new Error(Errors.COMPONENT_SELECT_MUST_BE_ALONE); } for (const subcomponent of component.components) { + if (subcomponent.customId && !validateLength(subcomponent.customId, { max: 100 })) { + throw new Error(Errors.COMPONENT_CUSTOM_ID_TOO_BIG); + } + // 5 Link buttons can not have a customId if (isButton(subcomponent)) { if (subcomponent.type === ButtonStyles.Link && subcomponent.customId) { @@ -235,27 +245,87 @@ export function validateComponents(components: MessageComponents) { } if (!validateLength(subcomponent.label, { max: 80 })) { - throw new Error(Errors.subcomponent_LABEL_TOO_BIG); + throw new Error(Errors.COMPONENT_LABEL_TOO_BIG); } - if (subcomponent.customId && !validateLength(subcomponent.customId, { max: 100 })) { - throw new Error(Errors.subcomponent_CUSTOM_ID_TOO_BIG); + subcomponent.emoji = makeEmojiFromString(subcomponent.emoji); + } + + if (isSelectMenu(subcomponent)) { + if (subcomponent.placeholder && !validateLength(subcomponent.placeholder, { max: 100 })) { + throw new Error(Errors.COMPONENT_PLACEHOLDER_TOO_BIG); } - if (typeof subcomponent.emoji === "string") { - // A snowflake id was provided - if (/^[0-9]+$/.test(subcomponent.emoji)) { - subcomponent.emoji = { - id: subcomponent.emoji, - }; - } else { - // A unicode emoji was provided - subcomponent.emoji = { - name: subcomponent.emoji, - }; + if (subcomponent.minValues) { + if (subcomponent.minValues < 1) { + throw new Error(Errors.COMPONENT_SELECT_MINVALUE_TOO_LOW); } + + if (subcomponent.minValues > 25) { + throw new Error(Errors.COMPONENT_SELECT_MINVALUE_TOO_MANY); + } + } + + if (subcomponent.maxValues) { + if (subcomponent.maxValues < 1) { + throw new Error(Errors.COMPONENT_SELECT_MAXVALUE_TOO_LOW); + } + + if (subcomponent.maxValues > 25) { + throw new Error(Errors.COMPONENT_SELECT_MAXVALUE_TOO_MANY); + } + } + + if (subcomponent.options.length < 1) { + throw new Error(Errors.COMPONENT_SELECT_OPTIONS_TOO_LOW); + } + + if (subcomponent.options.length > 25) { + throw new Error(Errors.COMPONENT_SELECT_OPTIONS_TOO_MANY); + } + + for (const option of subcomponent.options) { + if (!validateLength(option.label, { max: 25 })) { + throw new Error(Errors.SELECT_OPTION_LABEL_TOO_BIG); + } + + if (!validateLength(option.value, { max: 100 })) { + throw new Error(Errors.SELECT_OPTION_VALUE_TOO_BIG); + } + + if (option.description && !validateLength(option.description, { max: 50 })) { + throw new Error(Errors.SELECT_OPTION_VALUE_TOO_BIG); + } + + option.emoji = makeEmojiFromString(option.emoji); } } } } } + +function makeEmojiFromString( + emoji?: + | string + | { + id?: string | undefined; + name?: string | undefined; + animated?: boolean | undefined; + } +) { + if (typeof emoji !== "string") return emoji; + + // A snowflake id was provided + if (/^[0-9]+$/.test(emoji)) { + emoji = { + id: emoji, + }; + } else { + // A unicode emoji was provided + emoji = { + name: emoji, + }; + } + + return emoji; +} From c73b3a55e6a94411d38fff68db4283d926718e37 Mon Sep 17 00:00:00 2001 From: Skillz Date: Fri, 28 May 2021 10:59:49 -0400 Subject: [PATCH 14/21] more edge cases --- src/types/discordeno/errors.ts | 2 ++ src/util/utils.ts | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/types/discordeno/errors.ts b/src/types/discordeno/errors.ts index e40798257..e26da1f58 100644 --- a/src/types/discordeno/errors.ts +++ b/src/types/discordeno/errors.ts @@ -108,4 +108,6 @@ export enum Errors { COMPONENT_SELECT_OPTIONS_TOO_MANY = "COMPONENT_SELECT_OPTIONS_TOO_MANY", SELECT_OPTION_LABEL_TOO_BIG = "SELECT_OPTION_LABEL_TOO_BIG", SELECT_OPTION_VALUE_TOO_BIG = "SELECT_OPTION_VALUE_TOO_BIG", + SELECT_OPTION_TOO_MANY_DEFAULTS = "SELECT_OPTION_TOO_MANY_DEFAULTS", + COMPONENT_SELECT_MIN_HIGHER_THAN_MAX = "COMPONENT_SELECT_MIN_HIGHER_THAN_MAX", } diff --git a/src/util/utils.ts b/src/util/utils.ts index 2f817cb45..e1fa966e4 100644 --- a/src/util/utils.ts +++ b/src/util/utils.ts @@ -264,6 +264,11 @@ export function validateComponents(components: MessageComponents) { if (subcomponent.minValues > 25) { throw new Error(Errors.COMPONENT_SELECT_MINVALUE_TOO_MANY); } + + if (!subcomponent.maxValues) subcomponent.maxValues = subcomponent.minValues; + if (subcomponent.minValues > subcomponent.maxValues) { + throw new Error(Errors.COMPONENT_SELECT_MIN_HIGHER_THAN_MAX); + } } if (subcomponent.maxValues) { @@ -284,7 +289,16 @@ export function validateComponents(components: MessageComponents) { throw new Error(Errors.COMPONENT_SELECT_OPTIONS_TOO_MANY); } + let defaults = 0; + for (const option of subcomponent.options) { + if (option.default) { + defaults++; + if (defaults > (subcomponent.maxValues || 25)) { + throw new Error(Errors.SELECT_OPTION_TOO_MANY_DEFAULTS); + } + } + if (!validateLength(option.label, { max: 25 })) { throw new Error(Errors.SELECT_OPTION_LABEL_TOO_BIG); } From 0efa060380cbcd8b1579ca7ff5bb1e0edcc3eb33 Mon Sep 17 00:00:00 2001 From: ITOH Date: Sat, 29 May 2021 15:42:35 +0200 Subject: [PATCH 15/21] better guild & channel dispatch --- src/cache.ts | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/cache.ts b/src/cache.ts index e19c393d5..745ee39df 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -53,23 +53,18 @@ function memberSweeper(member: DiscordenoMember) { return member.cachedAt - Date.now() < 1800000; } -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.dispatchedGuildIds.add(guild.id); - - // Remove all channel if they were dispatched - cache.channels.forEach((channel) => { - if (!cache.dispatchedGuildIds.has(channel.guildId)) return; +function guildSweeper(guild: DiscordenoGuild) { + // Reset activity for next interval + if (!cache.activeGuildIds.delete(guild.id)) return false; + guild.channels.forEach((channel) => { cache.channels.delete(channel.id); cache.dispatchedChannelIds.add(channel.id); }); - // Reset activity for next interval - cache.activeGuildIds.delete(guild.id); + // This is inactive guild. Not a single thing has happened for atleast 30 minutes. + // Not a reaction, not a message, not any event! + cache.dispatchedGuildIds.add(guild.id); return true; } From 5e616f14b30487587fc02d95dbad13bea86d5b0c Mon Sep 17 00:00:00 2001 From: ITOH Date: Sat, 29 May 2021 19:29:43 +0200 Subject: [PATCH 16/21] fix: endless tests --- tests/ws/ws_close.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/ws/ws_close.ts b/tests/ws/ws_close.ts index fdc7d2493..df8a8c40d 100644 --- a/tests/ws/ws_close.ts +++ b/tests/ws/ws_close.ts @@ -1,3 +1,4 @@ +import { cache } from "../../src/cache.ts"; import { delay } from "../../src/util/utils.ts"; import { ws } from "../../src/ws/ws.ts"; import { defaultTestOptions } from "./start_bot.ts"; @@ -12,6 +13,13 @@ Deno.test({ }); await delay(3000); + + // clear all the sweeper intervals + for (const c of Object.values(cache)) { + if (!(c instanceof Map)) continue; + + c.stopSweeper(); + } }, ...defaultTestOptions, }); From ada3e9b43a916d1dafa46046faf7d4b816449cc3 Mon Sep 17 00:00:00 2001 From: rigormorrtiss Date: Sat, 29 May 2021 21:37:58 +0400 Subject: [PATCH 17/21] perf(rest): use Object.keys() instead --- src/rest/process_queue.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/rest/process_queue.ts b/src/rest/process_queue.ts index bc39fef09..df7ddecf2 100644 --- a/src/rest/process_queue.ts +++ b/src/rest/process_queue.ts @@ -44,8 +44,13 @@ export async function processQueue(id: string) { // IF THIS IS A GET REQUEST, CHANGE THE BODY TO QUERY PARAMETERS const query = queuedRequest.request.method.toUpperCase() === "GET" && queuedRequest.payload.body - ? Object.entries(queuedRequest.payload.body) - .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value as string)}`) + ? Object.keys(queuedRequest.payload.body) + .map( + (key) => + `${encodeURIComponent(key)}=${encodeURIComponent( + (queuedRequest.payload.body as Record)[key] + )}` + ) .join("&") : ""; const urlToUse = From 15442a54e32168e4e0a35086b82134d274fc9f83 Mon Sep 17 00:00:00 2001 From: ITOH Date: Sat, 29 May 2021 20:40:37 +0200 Subject: [PATCH 18/21] fix more --- tests/local.ts | 6 ++++++ tests/mod.ts | 1 + tests/stop_sweepers.ts | 16 ++++++++++++++++ tests/ws/ws_close.ts | 8 -------- 4 files changed, 23 insertions(+), 8 deletions(-) create mode 100644 tests/stop_sweepers.ts diff --git a/tests/local.ts b/tests/local.ts index 6676768cb..41f77367a 100644 --- a/tests/local.ts +++ b/tests/local.ts @@ -1,3 +1,9 @@ +// THE ORDER OF THE IMPORTS IN THIS FILE MATTER! +// DO NOT MOVE THEM UNLESS YOU KNOW WHAT YOUR DOING! + import "./util/utils.ts"; import "./util/validate_length.ts"; import "./util/loop_object.ts"; + +// Final cleanup +import "./stop_sweepers.ts"; diff --git a/tests/mod.ts b/tests/mod.ts index d804f71f1..e0dba20da 100644 --- a/tests/mod.ts +++ b/tests/mod.ts @@ -74,3 +74,4 @@ import "./discoveries/valid_discovery_term.ts"; // Final cleanup import "./guilds/delete_guild.ts"; import "./ws/ws_close.ts"; +import "./stop_sweepers.ts"; diff --git a/tests/stop_sweepers.ts b/tests/stop_sweepers.ts new file mode 100644 index 000000000..0e7a807f4 --- /dev/null +++ b/tests/stop_sweepers.ts @@ -0,0 +1,16 @@ +import { cache } from "../src/cache.ts"; +import { defaultTestOptions } from "./ws/start_bot.ts"; + +// Exit the Deno process once all tests are done. +Deno.test({ + name: "[chache] Stop all sweepers manually.", + fn() { + // clear all the sweeper intervals + for (const c of Object.values(cache)) { + if (!(c instanceof Map)) continue; + + c.stopSweeper(); + } + }, + ...defaultTestOptions, +}); diff --git a/tests/ws/ws_close.ts b/tests/ws/ws_close.ts index df8a8c40d..fdc7d2493 100644 --- a/tests/ws/ws_close.ts +++ b/tests/ws/ws_close.ts @@ -1,4 +1,3 @@ -import { cache } from "../../src/cache.ts"; import { delay } from "../../src/util/utils.ts"; import { ws } from "../../src/ws/ws.ts"; import { defaultTestOptions } from "./start_bot.ts"; @@ -13,13 +12,6 @@ Deno.test({ }); await delay(3000); - - // clear all the sweeper intervals - for (const c of Object.values(cache)) { - if (!(c instanceof Map)) continue; - - c.stopSweeper(); - } }, ...defaultTestOptions, }); From d817659941b376aa8e85d07b6b7cd04ce2dc074e Mon Sep 17 00:00:00 2001 From: Skillz4Killz <23035000+Skillz4Killz@users.noreply.github.com> Date: Sat, 29 May 2021 18:46:15 +0000 Subject: [PATCH 19/21] use enums --- src/types/messages/components/select_data.ts | 4 +++- src/types/messages/components/select_menu.ts | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/types/messages/components/select_data.ts b/src/types/messages/components/select_data.ts index 241498695..c347bd0a9 100644 --- a/src/types/messages/components/select_data.ts +++ b/src/types/messages/components/select_data.ts @@ -1,6 +1,8 @@ +import { DiscordMessageComponentTypes } from "./message_component_types.ts"; + export interface SelectMenuData { /** The type of component */ - componentType: 3; + componentType: DiscordMessageComponentTypes.SelectMenu; /** The custom id provided for this component. */ customId: string; /** The values chosen by the user. */ diff --git a/src/types/messages/components/select_menu.ts b/src/types/messages/components/select_menu.ts index 3a701cc3c..6f2afbd52 100644 --- a/src/types/messages/components/select_menu.ts +++ b/src/types/messages/components/select_menu.ts @@ -1,7 +1,8 @@ +import { DiscordMessageComponentTypes } from "./message_component_types.ts"; import { SelectOption } from "./select_option.ts"; export interface SelectMenuComponent { - type: 3; + type: DiscordMessageComponentTypes.SelectMenu; /** A custom identifier for this component. Maximum 100 characters. */ customId: string; /** A custom placeholder text if nothing is selected. Maximum 100 characters. */ From 63b42112aecdac1e16221794a8660af712eaec68 Mon Sep 17 00:00:00 2001 From: ITOH Date: Sat, 29 May 2021 20:52:27 +0200 Subject: [PATCH 20/21] Update stop_sweepers.ts --- tests/stop_sweepers.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/stop_sweepers.ts b/tests/stop_sweepers.ts index 0e7a807f4..2ecf30762 100644 --- a/tests/stop_sweepers.ts +++ b/tests/stop_sweepers.ts @@ -1,5 +1,4 @@ import { cache } from "../src/cache.ts"; -import { defaultTestOptions } from "./ws/start_bot.ts"; // Exit the Deno process once all tests are done. Deno.test({ @@ -12,5 +11,4 @@ Deno.test({ c.stopSweeper(); } }, - ...defaultTestOptions, }); From d5e3315b984c99b1ca69b8964b3dd7838bc35d47 Mon Sep 17 00:00:00 2001 From: ITOH Date: Sat, 29 May 2021 21:09:07 +0200 Subject: [PATCH 21/21] some hacky fix --- tests/local.ts | 15 ++++++++++++++- tests/mod.ts | 15 ++++++++++++++- tests/stop_sweepers.ts | 14 -------------- 3 files changed, 28 insertions(+), 16 deletions(-) delete mode 100644 tests/stop_sweepers.ts diff --git a/tests/local.ts b/tests/local.ts index 41f77367a..85e15eef6 100644 --- a/tests/local.ts +++ b/tests/local.ts @@ -6,4 +6,17 @@ import "./util/validate_length.ts"; import "./util/loop_object.ts"; // Final cleanup -import "./stop_sweepers.ts"; + +import { cache } from "../src/cache.ts"; +import { delay } from "../src/util/utils.ts"; +if (import.meta.main) { + // clear all the sweeper intervals + for (const c of Object.values(cache)) { + if (!(c instanceof Map)) continue; + + c.stopSweeper(); + console.log("Cleaned"); + } + + await delay(3000); +} diff --git a/tests/mod.ts b/tests/mod.ts index e0dba20da..13c4dc1a2 100644 --- a/tests/mod.ts +++ b/tests/mod.ts @@ -74,4 +74,17 @@ import "./discoveries/valid_discovery_term.ts"; // Final cleanup import "./guilds/delete_guild.ts"; import "./ws/ws_close.ts"; -import "./stop_sweepers.ts"; + +import { cache } from "../src/cache.ts"; +import { delay } from "../src/util/utils.ts"; +if (import.meta.main) { + // clear all the sweeper intervals + for (const c of Object.values(cache)) { + if (!(c instanceof Map)) continue; + + c.stopSweeper(); + console.log("Cleaned"); + } + + await delay(3000); +} diff --git a/tests/stop_sweepers.ts b/tests/stop_sweepers.ts deleted file mode 100644 index 2ecf30762..000000000 --- a/tests/stop_sweepers.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { cache } from "../src/cache.ts"; - -// Exit the Deno process once all tests are done. -Deno.test({ - name: "[chache] Stop all sweepers manually.", - fn() { - // clear all the sweeper intervals - for (const c of Object.values(cache)) { - if (!(c instanceof Map)) continue; - - c.stopSweeper(); - } - }, -});