From c830055a5258ab6458052aea08c9d62ae3f2cd00 Mon Sep 17 00:00:00 2001 From: ITOH Date: Wed, 23 Mar 2022 21:23:27 +0100 Subject: [PATCH 1/9] style: run deno fmt --- helpers/roles/modifyRolePositions.ts | 1 - plugins/permissions/src/roles/edit.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/helpers/roles/modifyRolePositions.ts b/helpers/roles/modifyRolePositions.ts index 5b43997a5..a2face3e7 100644 --- a/helpers/roles/modifyRolePositions.ts +++ b/helpers/roles/modifyRolePositions.ts @@ -23,4 +23,3 @@ export interface ModifyRolePositions { /** The sorting position for the role. */ position?: number | null; } - diff --git a/plugins/permissions/src/roles/edit.ts b/plugins/permissions/src/roles/edit.ts index e277fbfbf..ff14a4d4a 100644 --- a/plugins/permissions/src/roles/edit.ts +++ b/plugins/permissions/src/roles/edit.ts @@ -29,4 +29,3 @@ export default function editRole(bot: BotWithCache) { return await editRoleOld(guildId, id, options); }; } - From b0ecdf1a621ce50d613222ade75cc6689be20b2e Mon Sep 17 00:00:00 2001 From: ITOH Date: Thu, 24 Mar 2022 14:29:01 +0100 Subject: [PATCH 2/9] fix(plugins): createApplicationCommand validation --- .../permissions/src/interactions/commands.ts | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/plugins/permissions/src/interactions/commands.ts b/plugins/permissions/src/interactions/commands.ts index 5c369e26c..aae92fa5e 100644 --- a/plugins/permissions/src/interactions/commands.ts +++ b/plugins/permissions/src/interactions/commands.ts @@ -92,6 +92,41 @@ export function createApplicationCommand(bot: BotWithCache) { // Only slash need to be lowercase options.name = options.name.toLowerCase(); + + // Slash commands require description + if ( + !options.description && + (isChatInput) + ) { + throw new Error( + "Slash commands require some form of a description be provided.", + ); + } + + if ( + options.description && + ((options.type === ApplicationCommandTypes.User) || + (options.type === ApplicationCommandTypes.Message)) + ) { + throw new Error("Context menu commands do not allow a description."); + } + + if ( + options.description && + !bot.utils.validateLength(options.description, { min: 1, max: 100 }) + ) { + throw new Error( + "Application command descriptions must be between 1 and 100 characters.", + ); + } + + if (options.options?.length) { + if (options.options.length > 25) { + throw new Error("Only 25 options are allowed to be provided."); + } + + options.options = validateApplicationCommandOptions(bot, options.options); + } } else { if (!CONTEXT_MENU_COMMANDS_NAME_REGEX.test(options.name)) { throw new Error( @@ -100,41 +135,6 @@ export function createApplicationCommand(bot: BotWithCache) { } } - // Slash commands require description - if ( - !options.description && - (isChatInput) - ) { - throw new Error( - "Slash commands require some form of a description be provided.", - ); - } - - if ( - options.description && - ((options.type === ApplicationCommandTypes.User) || - (options.type === ApplicationCommandTypes.Message)) - ) { - throw new Error("Context menu commands do not allow a description."); - } - - if ( - options.description && - !bot.utils.validateLength(options.description, { min: 1, max: 100 }) - ) { - throw new Error( - "Application command descriptions must be between 1 and 100 characters.", - ); - } - - if (options.options?.length) { - if (options.options.length > 25) { - throw new Error("Only 25 options are allowed to be provided."); - } - - options.options = validateApplicationCommandOptions(bot, options.options); - } - return await createApplicationCommandOld(options, guildId); }; } From 75eb35df6e847054d70bd6309e50f391af6f2b19 Mon Sep 17 00:00:00 2001 From: ITOH Date: Thu, 24 Mar 2022 14:34:26 +0100 Subject: [PATCH 3/9] fix(plugins): createApplicationCommand checks --- .../permissions/src/interactions/commands.ts | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/plugins/permissions/src/interactions/commands.ts b/plugins/permissions/src/interactions/commands.ts index aae92fa5e..6a92757c0 100644 --- a/plugins/permissions/src/interactions/commands.ts +++ b/plugins/permissions/src/interactions/commands.ts @@ -83,6 +83,7 @@ export function createApplicationCommand(bot: BotWithCache) { if (!options.name) { throw new Error("A name is required to create a options."); } + if (isChatInput) { if (!SLASH_COMMANDS_NAME_REGEX.test(options.name)) { throw new Error( @@ -94,27 +95,11 @@ export function createApplicationCommand(bot: BotWithCache) { options.name = options.name.toLowerCase(); // Slash commands require description - if ( - !options.description && - (isChatInput) - ) { + if (!options.description) { throw new Error( "Slash commands require some form of a description be provided.", ); - } - - if ( - options.description && - ((options.type === ApplicationCommandTypes.User) || - (options.type === ApplicationCommandTypes.Message)) - ) { - throw new Error("Context menu commands do not allow a description."); - } - - if ( - options.description && - !bot.utils.validateLength(options.description, { min: 1, max: 100 }) - ) { + } else if (!bot.utils.validateLength(options.description, { min: 1, max: 100 })) { throw new Error( "Application command descriptions must be between 1 and 100 characters.", ); From 4ad02d0a20b60ec67fec97dd6e2cac5b23924f63 Mon Sep 17 00:00:00 2001 From: ITOH Date: Thu, 24 Mar 2022 15:02:01 +0100 Subject: [PATCH 4/9] ci: cache template code (#2126) * ci: cache template code * f * f * f * f --- .github/workflows/local_tests.yml | 4 ++++ .github/workflows/test.yml | 3 +++ 2 files changed, 7 insertions(+) diff --git a/.github/workflows/local_tests.yml b/.github/workflows/local_tests.yml index 3b5f4387c..6dafe8c14 100644 --- a/.github/workflows/local_tests.yml +++ b/.github/workflows/local_tests.yml @@ -9,6 +9,7 @@ on: - "rest/**" - "tests/**" - "transformers/**" + - "template/**" - "types/**" - "util/**" - "gateway/**" @@ -20,6 +21,7 @@ on: - "rest/**" - "tests/**" - "transformers/**" + - "template/**" - "types/**" - "util/**" - "gateway/**" @@ -37,5 +39,7 @@ jobs: deno-version: ${{ matrix.deno }} - name: Cache dependencies run: deno cache mod.ts + - name: Cache Templates + run: deno cache template/beginner/mod.ts template/bigbot/src/bot/mod.ts template/bigbot/src/gateway/mod.ts template/bigbot/src/rest/mod.ts template/minimal/mod.ts - name: Run Local tests that don't need Discord's API run: deno test -A tests/local.ts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6f57eaa23..37221e1c9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,6 +9,7 @@ on: - "rest/**" - "tests/**" - "transformers/**" + - "template/**" - "types/**" - "util/**" - "ws/**" @@ -27,6 +28,8 @@ jobs: deno-version: ${{ matrix.deno }} - name: Cache dependencies run: deno cache mod.ts + - name: Cache Templates + run: deno cache template/beginner/mod.ts template/bigbot/src/bot/mod.ts template/bigbot/src/gateway/mod.ts template/bigbot/src/rest/mod.ts template/minimal/mod.ts - name: Run test script for maintainers if: ${{ github.actor == 'Skillz4Killz' || github.actor == 'itohatweb' }} run: deno test --unstable --coverage=coverage -A tests/mod.ts From 4b469c72900bc855b0d288e42c87f07e69b8b21f Mon Sep 17 00:00:00 2001 From: ITOH Date: Thu, 24 Mar 2022 15:02:28 +0100 Subject: [PATCH 5/9] ci: fix tests for changes made to the gateway/ directory (#2127) --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 37221e1c9..e487d06e8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ on: - "template/**" - "types/**" - "util/**" - - "ws/**" + - "gateway/**" jobs: test: From 292f710599d78733365bccf333e440ac5c4fabfc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Mar 2022 17:28:32 +0000 Subject: [PATCH 6/9] build(deps): bump minimist from 1.2.5 to 1.2.6 in /site (#2135) Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6. - [Release notes](https://github.com/substack/minimist/releases) - [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6) --- updated-dependencies: - dependency-name: minimist dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- site/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/site/package-lock.json b/site/package-lock.json index 4396c2b6b..9b9961c9f 100644 --- a/site/package-lock.json +++ b/site/package-lock.json @@ -8182,9 +8182,9 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "node_modules/mkdirp": { "version": "0.5.5", @@ -18866,9 +18866,9 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "mkdirp": { "version": "0.5.5", From a575324c9d02a74b6d21257eb96da755c71d73b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Mar 2022 17:28:48 +0000 Subject: [PATCH 7/9] build(deps): bump node-forge from 1.2.1 to 1.3.0 in /site (#2134) Bumps [node-forge](https://github.com/digitalbazaar/forge) from 1.2.1 to 1.3.0. - [Release notes](https://github.com/digitalbazaar/forge/releases) - [Changelog](https://github.com/digitalbazaar/forge/blob/main/CHANGELOG.md) - [Commits](https://github.com/digitalbazaar/forge/compare/v1.2.1...v1.3.0) --- updated-dependencies: - dependency-name: node-forge dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- site/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/site/package-lock.json b/site/package-lock.json index 9b9961c9f..ec498d504 100644 --- a/site/package-lock.json +++ b/site/package-lock.json @@ -8288,9 +8288,9 @@ } }, "node_modules/node-forge": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz", - "integrity": "sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz", + "integrity": "sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA==", "engines": { "node": ">= 6.13.0" } @@ -18943,9 +18943,9 @@ } }, "node-forge": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz", - "integrity": "sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz", + "integrity": "sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA==" }, "node-releases": { "version": "2.0.1", From 4bdd8330d090a9f08eb52b2485165d038442a45f Mon Sep 17 00:00:00 2001 From: ITOH Date: Fri, 25 Mar 2022 00:04:23 +0100 Subject: [PATCH 8/9] feat(plugins): validate interaction response title (#2130) Modal titles must have a length between 1 and 45 characters. This adds a check to the permissions plugin. Reference: https://github.com/discord/discord-api-docs/pull/4649 --- plugins/permissions/src/interactions/mod.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/permissions/src/interactions/mod.ts b/plugins/permissions/src/interactions/mod.ts index afc56bde6..55a7738ef 100644 --- a/plugins/permissions/src/interactions/mod.ts +++ b/plugins/permissions/src/interactions/mod.ts @@ -6,6 +6,14 @@ export function sendInteractionResponse(bot: BotWithCache) { const sendInteractionResponseOld = bot.helpers.sendInteractionResponse; bot.helpers.sendInteractionResponse = function (id, token, options) { + if (options.data?.title !== undefined) { + if (!bot.utils.validateLength(options.data.title, { min: 1, max: 45 })) { + throw new Error( + "Invalid modal title. Must be between 1-45 characters long.", + ); + } + } + options.data?.choices?.every((choice) => { if (!bot.utils.validateLength(choice.name, { min: 1, max: 100 })) { throw new Error( From 2c2ccb252ce5103a960e72c8301426611147837b Mon Sep 17 00:00:00 2001 From: ITOH Date: Fri, 25 Mar 2022 11:15:17 +0100 Subject: [PATCH 9/9] feat(helpers,types)!: add guild template transformer (#2132) * feat(helpers,types)!: add guild template transformer This adds a template transformer. Also this fixes incorrect type definitions regarding templates. Breaking Change: The previously called `Template` interface which was used for creating templates has been renamed to `CreateTemplate` * fmt * forgot to add this * use data --- bot.ts | 5 +++ helpers/templates/createGuildTemplate.ts | 49 ++++++------------------ helpers/templates/getGuildTemplates.ts | 2 +- helpers/templates/getTemplate.ts | 4 +- transformers/mod.ts | 1 + transformers/template.ts | 23 +++++++++++ types/discord.ts | 32 +++++++++++++++- types/shared.ts | 6 +++ 8 files changed, 81 insertions(+), 41 deletions(-) create mode 100644 transformers/template.ts diff --git a/bot.ts b/bot.ts index 0aa6de58b..3be8da661 100644 --- a/bot.ts +++ b/bot.ts @@ -8,11 +8,13 @@ import { Message, Role, ScheduledEvent, + Template, transformChannel, transformGuild, transformMember, transformMessage, transformRole, + transformTemplate, transformUser, transformVoiceState, User, @@ -67,6 +69,7 @@ import { DiscordInteractionDataOption, DiscordReady, DiscordStickerPack, + DiscordTemplate, } from "./types/discord.ts"; import { Errors, GatewayDispatchEventNames, GatewayIntents } from "./types/shared.ts"; @@ -407,6 +410,7 @@ export interface Transformers { stageInstance: (bot: Bot, payload: DiscordStageInstance) => StageInstance; sticker: (bot: Bot, payload: DiscordSticker) => Sticker; stickerPack: (bot: Bot, payload: DiscordStickerPack) => StickerPack; + template: (bot: Bot, payload: DiscordTemplate) => Template; } export function createTransformers(options: Partial) { @@ -447,6 +451,7 @@ export function createTransformers(options: Partial) { sticker: options.sticker || transformSticker, stickerPack: options.stickerPack || transformStickerPack, gatewayBot: options.gatewayBot || transformGatewayBot, + template: options.template || transformTemplate, }; } diff --git a/helpers/templates/createGuildTemplate.ts b/helpers/templates/createGuildTemplate.ts index 21d04b091..dbc8770a0 100644 --- a/helpers/templates/createGuildTemplate.ts +++ b/helpers/templates/createGuildTemplate.ts @@ -1,10 +1,8 @@ import type { Bot } from "../../bot.ts"; -import { Guild } from "../../transformers/guild.ts"; -import { User } from "../../transformers/member.ts"; import { DiscordTemplate } from "../../types/discord.ts"; /** Creates a template for the guild. Requires the `MANAGE_GUILD` permission. */ -export async function createGuildTemplate(bot: Bot, guildId: bigint, data: Template) { +export async function createGuildTemplate(bot: Bot, guildId: bigint, data: CreateTemplate) { if (data.name.length < 1 || data.name.length > 100) { throw new Error("The name can only be in between 1-100 characters."); } @@ -13,42 +11,17 @@ export async function createGuildTemplate(bot: Bot, guildId: bigint, data: Templ throw new Error("The description can only be in between 0-120 characters."); } - return await bot.rest.runMethod(bot.rest, "post", bot.constants.endpoints.GUILD_TEMPLATES(guildId), { - code: data.code, - name: data.name, - description: data.description, - usage_count: data.usageCount, - creator_id: data.creatorId, - creator: data.creator, - created_at: data.createdAt, - updated_at: data.updatedAt, - source_guild_id: data.sourceGuildId, - serialized_source_guild: data.serializedSourceGuild, - is_dirty: data.isDirty, - }); + return await bot.rest.runMethod( + bot.rest, + "post", + bot.constants.endpoints.GUILD_TEMPLATES(guildId), + data, + ); } -export interface Template { - /** The template code (unique Id) */ - code: string; - /** Template name */ +export interface CreateTemplate { + /** Name which the template should have */ name: string; - /** The description for the template */ - description: string; - /** Number of times this template has been used */ - usageCount: number; - /** The Id of the user who created the template */ - creatorId: string; - /** The user who created the template */ - creator: User; - /** When this template was created */ - createdAt: string; - /** When this template was last synced to the source guild */ - updatedAt: string; - /** The Id of the guild this template is based on */ - sourceGuildId: string; - /** The guild snapshot this template contains */ - serializedSourceGuild: Partial; - /** Whether the template has unsynced changes */ - isDirty: boolean; + /** Description of the template */ + description?: string; } diff --git a/helpers/templates/getGuildTemplates.ts b/helpers/templates/getGuildTemplates.ts index cde995bdd..da086f687 100644 --- a/helpers/templates/getGuildTemplates.ts +++ b/helpers/templates/getGuildTemplates.ts @@ -10,5 +10,5 @@ export async function getGuildTemplates(bot: Bot, guildId: bigint) { bot.constants.endpoints.GUILD_TEMPLATES(guildId), ); - return new Collection(templates.map((template) => [template.code, template])); + return new Collection(templates.map((template) => [template.code, bot.transformers.template(bot, template)])); } diff --git a/helpers/templates/getTemplate.ts b/helpers/templates/getTemplate.ts index 785fefc9c..1389fbb67 100644 --- a/helpers/templates/getTemplate.ts +++ b/helpers/templates/getTemplate.ts @@ -3,9 +3,11 @@ import { DiscordTemplate } from "../../types/discord.ts"; /** Returns the guild template if it exists */ export async function getTemplate(bot: Bot, templateCode: string) { - return await bot.rest.runMethod( + const result = await bot.rest.runMethod( bot.rest, "get", bot.constants.endpoints.GUILD_TEMPLATE(templateCode), ); + + return bot.transformers.template(bot, result); } diff --git a/transformers/mod.ts b/transformers/mod.ts index 38c514967..fd52050d9 100644 --- a/transformers/mod.ts +++ b/transformers/mod.ts @@ -30,3 +30,4 @@ export * from "./voiceState.ts"; export * from "./webhook.ts"; export * from "./welcomeScreen.ts"; export * from "./widget.ts"; +export * from "./template.ts"; diff --git a/transformers/template.ts b/transformers/template.ts new file mode 100644 index 000000000..af76569b1 --- /dev/null +++ b/transformers/template.ts @@ -0,0 +1,23 @@ +import { Bot } from "../bot.ts"; +import { DiscordTemplate } from "../types/discord.ts"; +import { Optionalize } from "../types/shared.ts"; + +export function transformTemplate(bot: Bot, payload: DiscordTemplate) { + const template = { + code: payload.code, + name: payload.name, + description: payload.description, + usageCount: payload.usage_count, + creatorId: bot.transformers.snowflake(payload.creator_id), + creator: bot.transformers.user(bot, payload.creator), + createdAt: Date.parse(payload.created_at), + updatedAt: Date.parse(payload.updated_at), + sourceGuildId: bot.transformers.snowflake(payload.source_guild_id), + serializedSourceGuild: payload.serialized_source_guild, + isDirty: payload.is_dirty ?? undefined, + }; + + return template as Optionalize; +} + +export interface Template extends ReturnType {} diff --git a/types/discord.ts b/types/discord.ts index 89a899ff6..b8a65445f 100644 --- a/types/discord.ts +++ b/types/discord.ts @@ -21,6 +21,7 @@ import { MessageTypes, MfaLevels, OverwriteTypes, + PickPartial, PremiumTiers, PremiumTypes, ScheduledEventEntityType, @@ -2000,7 +2001,36 @@ export interface DiscordTemplate { /** The Id of the guild this template is based on */ source_guild_id: string; /** The guild snapshot this template contains */ - serialized_source_guild: Partial; + serialized_source_guild: + & Omit< + PickPartial< + DiscordGuild, + | "name" + | "description" + | "verification_level" + | "default_message_notifications" + | "explicit_content_filter" + | "preferred_locale" + | "afk_timeout" + | "channels" + | "afk_channel_id" + | "system_channel_id" + | "system_channel_flags" + >, + "roles" + > + & { + roles: ( + & Omit< + PickPartial< + DiscordRole, + "name" | "color" | "hoist" | "mentionable" | "permissions" | "icon" | "unicode_emoji" + >, + "id" + > + & { id: number } + )[]; + }; /** Whether the template has unsynced changes */ is_dirty: boolean | null; } diff --git a/types/shared.ts b/types/shared.ts index 522d5bea5..e3c331d56 100644 --- a/types/shared.ts +++ b/types/shared.ts @@ -1335,3 +1335,9 @@ export type Optionalize = } > : T; + +export type PickPartial = + & { + [P in keyof T]?: T[P] | undefined; + } + & { [P in K]: T[P] };