From 4ee047e41de3ee923c58ea29e11bb508f0e46a99 Mon Sep 17 00:00:00 2001 From: xhyrom Date: Thu, 14 Apr 2022 12:41:23 +0200 Subject: [PATCH] feat: support for select menus --- packages/bot/bot.ts | 75 +++++-------------- packages/bot/utils/badFormatting.ts | 12 +++ packages/bot/utils/resolveComponents.ts | 73 ++++++++++++++++++ packages/bot/utils/respond.ts | 3 + packages/bot/utils/verify.ts | 2 +- packages/website/next.config.js | 2 +- packages/website/pages/index.tsx | 5 ++ packages/website/public/script.js | 27 ++++--- packages/website/styles/css/style.css | 4 +- packages/website/styles/css/style.css.map | 2 +- packages/website/styles/scss/_forumstyle.scss | 4 +- 11 files changed, 135 insertions(+), 74 deletions(-) create mode 100644 packages/bot/utils/badFormatting.ts create mode 100644 packages/bot/utils/resolveComponents.ts create mode 100644 packages/bot/utils/respond.ts diff --git a/packages/bot/bot.ts b/packages/bot/bot.ts index e96f86c..83c7603 100644 --- a/packages/bot/bot.ts +++ b/packages/bot/bot.ts @@ -1,33 +1,23 @@ -import { APIApplicationCommandInteraction, APIInteractionResponse, APIMessageComponentInteraction, APIPingInteraction, InteractionResponseType, InteractionType, MessageFlags, RouteBases, Routes } from 'discord-api-types/v9'; +import { APIApplicationCommandInteraction, APIMessageComponentInteraction, APIPingInteraction, ApplicationCommandType, ComponentType, InteractionResponseType, InteractionType, MessageFlags, RouteBases, Routes } from 'discord-api-types/v9'; +import { badFormatting } from './utils/badFormatting'; import { isJSON } from './utils/isJson'; -import { resolvePartialEmoji } from './utils/resolveEmoji'; +import { resolveButtonComponents, resolveSelectMenuComponents } from './utils/resolveComponents'; +import { respond } from './utils/respond'; import { verify } from './utils/verify'; -const respond = (response: APIInteractionResponse) => new Response(JSON.stringify(response), {headers: {'content-type': 'application/json'}}); - -const badFormatting = (rolesMax?: boolean) => { - return respond({ - type: InteractionResponseType.ChannelMessageWithSource, - data: { - flags: 64, - content: `${rolesMax ? 'You can have maximum 25 buttons. (5x5)' : 'Bad formatting, generate [here](https://xhyrom.github.io/roles-bot)'}` - } - }); -}; - export const handleRequest = async(request: Request): Promise => { if (!request.headers.get('X-Signature-Ed25519') || !request.headers.get('X-Signature-Timestamp')) return Response.redirect('https://www.youtube.com/watch?v=dQw4w9WgXcQ'); if (!await verify(request)) return new Response('', { status: 401 }); const interaction = await request.json() as APIPingInteraction | APIApplicationCommandInteraction | APIMessageComponentInteraction; + console.log(interaction); if (interaction.type === InteractionType.Ping) return respond({ type: InteractionResponseType.Pong }); - if (interaction.type === InteractionType.ApplicationCommand && interaction.data.name === 'setup') { - + if (interaction.type === InteractionType.ApplicationCommand && interaction.data.type === ApplicationCommandType.ChatInput && interaction.data.name === 'setup') { if ((Number(interaction.member?.permissions) & 0x10) !== 0x10) return respond({ type: InteractionResponseType.ChannelMessageWithSource, data: { @@ -36,10 +26,8 @@ export const handleRequest = async(request: Request): Promise => { } }); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const json = isJSON(interaction.data.options[0].value) ? JSON.parse(interaction.data.options[0].value) : null; - + // @ts-expect-error No typings for value + const json = isJSON(interaction.data.options?.[0]?.value) ? JSON.parse(interaction.data.options?.[0]?.value) : null; if (!json) return badFormatting(); const channelId = json.channel; @@ -50,34 +38,7 @@ export const handleRequest = async(request: Request): Promise => { if (!message) message = '​'; if (!roles || Object.values(json.roles).filter((role: any) => role.id && role.label).length === 0 || roles.length === 0 || roles.length > 25) return badFormatting(roles.length > 25); - roles = roles.map((r: any) => { - const o: any = { - type: 2, - style: r.style || 2, - label: r.label, - custom_id: r.id - }; - - if (r.emoji) o.emoji = resolvePartialEmoji(r.emoji); - - return o; - }); - - let finalComponents = []; - for (let i = 0; i <= roles.length; i += 5) { - const row: any = { - type: 1, - components: [] - }; - - const btnslice: any = roles.slice(i, i + 5); - - for (let y = 0; y < btnslice.length; y++) row.components.push(btnslice[y]); - - finalComponents.push(row); - } - - finalComponents = finalComponents.filter(a => a.components.length > 0); + const finalComponents = json.type === 1 ? resolveButtonComponents(roles) : resolveSelectMenuComponents(roles, json.placeholder?.toString()); const fetched = await fetch(`${RouteBases.api}/channels/${channelId}/messages`, { method: 'POST', @@ -99,7 +60,7 @@ export const handleRequest = async(request: Request): Promise => { } }); } else if (interaction.type === InteractionType.MessageComponent) { - const roleId = interaction.data.custom_id; + const roleId = interaction.data.component_type === ComponentType.Button ? interaction.data.custom_id : interaction.data.values[0]; const url = `${RouteBases.api}${Routes.guildMemberRole(interaction.guild_id || '', interaction.member?.user.id || '', roleId)}`; let method = ''; @@ -128,13 +89,13 @@ export const handleRequest = async(request: Request): Promise => { allowed_mentions: { parse: [] } } }); + } else { + return respond({ + type: InteractionResponseType.ChannelMessageWithSource, + data: { + flags: MessageFlags.Ephemeral, + content: 'Beep boop, boop beep?' + } + }); } - - return respond({ - type: InteractionResponseType.ChannelMessageWithSource, - data: { - flags: MessageFlags.Ephemeral, - content: 'Beep boop, boop beep?' - } - }); }; \ No newline at end of file diff --git a/packages/bot/utils/badFormatting.ts b/packages/bot/utils/badFormatting.ts new file mode 100644 index 0000000..71c4e71 --- /dev/null +++ b/packages/bot/utils/badFormatting.ts @@ -0,0 +1,12 @@ +import { InteractionResponseType } from 'discord-api-types/v9'; +import { respond } from './respond'; + +export const badFormatting = (rolesMax?: boolean) => { + return respond({ + type: InteractionResponseType.ChannelMessageWithSource, + data: { + flags: 64, + content: `${rolesMax ? 'You can have maximum 25 buttons. (5x5)' : 'Bad formatting, generate [here](https://xhyrom.github.io/roles-bot)'}` + } + }); +}; \ No newline at end of file diff --git a/packages/bot/utils/resolveComponents.ts b/packages/bot/utils/resolveComponents.ts new file mode 100644 index 0000000..3a284de --- /dev/null +++ b/packages/bot/utils/resolveComponents.ts @@ -0,0 +1,73 @@ +import { ButtonStyle, ComponentType, Snowflake } from 'discord-api-types/v9'; +import { resolvePartialEmoji } from './resolveEmoji'; + +interface Role { + id: Snowflake; + style?: ButtonStyle; + label: string; + description?: string; + emoji: string | null; +} + +export const resolveButtonComponents = (roles: Array) => { + roles = roles.map((r: Role) => { + const o: any = { + type: ComponentType.Button, + style: r.style || ButtonStyle.Secondary, + label: r.label, + custom_id: r.id + }; + + if (r.emoji) o.emoji = resolvePartialEmoji(r.emoji); + + return o; + }); + + let finalComponents = []; + for (let i = 0; i <= roles.length; i += 5) { + const row: any = { + type: ComponentType.ActionRow, + components: [] + }; + + const btnslice: any = roles.slice(i, i + 5); + for (let y = 0; y < btnslice.length; y++) row.components.push(btnslice[y]); + + finalComponents.push(row); + } + + finalComponents = finalComponents.filter(a => a.components.length > 0); + return finalComponents; +} + +export const resolveSelectMenuComponents = (roles: Array, placeholder?: string) => { + roles = roles.map((r: Role) => { + const o: any = { + label: r.label, + value: r.id, + description: r.description, + }; + + if (r.emoji) o.emoji = resolvePartialEmoji(r.emoji); + + return o; + }); + + const actionRow = [ + { + type: ComponentType.ActionRow, + components: [ + { + type: ComponentType.SelectMenu, + custom_id: 'role_select', + options: roles, + } + ] + } + ]; + + // @ts-expect-error No typings + if (placeholder) actionRow[0].components[0].placeholder = placeholder; + + return actionRow; +} \ No newline at end of file diff --git a/packages/bot/utils/respond.ts b/packages/bot/utils/respond.ts new file mode 100644 index 0000000..cb34d3d --- /dev/null +++ b/packages/bot/utils/respond.ts @@ -0,0 +1,3 @@ +import { APIInteractionResponse } from 'discord-api-types/v9'; + +export const respond = (response: APIInteractionResponse) => new Response(JSON.stringify(response), {headers: {'content-type': 'application/json'}}); \ No newline at end of file diff --git a/packages/bot/utils/verify.ts b/packages/bot/utils/verify.ts index cf3a127..256d405 100644 --- a/packages/bot/utils/verify.ts +++ b/packages/bot/utils/verify.ts @@ -5,7 +5,7 @@ function hex2bin(hex: string) { const buf = new Uint8Array(Math.ceil(hex.length / 2)); for (let i = 0; i < buf.length; i++) { - buf[i] = parseInt(hex.substr(i * 2, 2), 16); + buf[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16); } return buf; } diff --git a/packages/website/next.config.js b/packages/website/next.config.js index d0dcbcd..c0de10d 100644 --- a/packages/website/next.config.js +++ b/packages/website/next.config.js @@ -1,7 +1,7 @@ const { createSecureHeaders } = require('next-secure-headers'); module.exports = { - assetPrefix: '/roles-bot/', + assetPrefix: '/', async headers() { return [{ source: '/(.*)', diff --git a/packages/website/pages/index.tsx b/packages/website/pages/index.tsx index b344216..eecd347 100644 --- a/packages/website/pages/index.tsx +++ b/packages/website/pages/index.tsx @@ -18,6 +18,11 @@ export default function Home() {

Generate


+
diff --git a/packages/website/public/script.js b/packages/website/public/script.js index 27974d6..8fa74cd 100644 --- a/packages/website/public/script.js +++ b/packages/website/public/script.js @@ -1,28 +1,34 @@ const json = { - roles: [] + roles: [], + type: "1", }; +document.getElementById('json').innerHTML = hljs.highlight(JSON.stringify(json), { language: 'json' }).value; -$('input').change((e) => { - json[e.currentTarget.id] = e.currentTarget.value.replaceAll('\\n', '\n'); - - document.getElementById('json').innerHTML = hljs.highlight(JSON.stringify(json), { language: 'json' }).value; -}); +for (const tag of ['input', 'select']) { + $(tag).change((e) => { + json[e.currentTarget.id] = e.currentTarget.value.replaceAll('\\n', '\n'); + + document.getElementById('json').innerHTML = hljs.highlight(JSON.stringify(json), { language: 'json' }).value; + }); +} $('button[id=addRole]').click((e) => { Swal.fire({ title: 'Add Role', html: - '' + + `` + '' + '' + - '', + `${json.type === "2" ? '' : ''} ` + + `${json.type === "1" ? '' : ''}`, preConfirm: function () { return new Promise(function (resolve) { resolve([ $('#swal-input1').val(), $('#swal-input2').val(), $('#swal-input3').val(), - $('#swal-input4').val() + $('#swal-input4').val(), + $('#swal-input5').val() ]); }); } @@ -32,7 +38,8 @@ $('button[id=addRole]').click((e) => { id: result.value[1], label: result.value[0], emoji: result.value[2] || null, - style: parseInt(result.value[3]) || 2 + placeholder: result.value[3] || null, + style: parseInt(result.value[4]) || 2 }); document.getElementById('json').innerHTML = hljs.highlight(JSON.stringify(json), { language: 'json' }).value; diff --git a/packages/website/styles/css/style.css b/packages/website/styles/css/style.css index 9626264..189cd0a 100644 --- a/packages/website/styles/css/style.css +++ b/packages/website/styles/css/style.css @@ -128,7 +128,7 @@ button:hover { background-color: var(--yellow-dark); } -input { +input, select { color: white; padding: .2rem .5rem; background-color: var(--codeblock); @@ -154,7 +154,7 @@ input { } @media screen and (max-width: 600px) { - input { + input, select { font-size: 1rem; } button { diff --git a/packages/website/styles/css/style.css.map b/packages/website/styles/css/style.css.map index 800fc72..1a3cf4a 100644 --- a/packages/website/styles/css/style.css.map +++ b/packages/website/styles/css/style.css.map @@ -1,6 +1,6 @@ { "version": 3, - "mappings": "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", + "mappings": "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", "sources": [ "../scss/style.scss", "../scss/_globals.scss", diff --git a/packages/website/styles/scss/_forumstyle.scss b/packages/website/styles/scss/_forumstyle.scss index 7088131..fb44979 100644 --- a/packages/website/styles/scss/_forumstyle.scss +++ b/packages/website/styles/scss/_forumstyle.scss @@ -14,7 +14,7 @@ button { } } -input { +input, select { color: white; padding: .2rem .5rem; background-color: var(--codeblock); @@ -40,7 +40,7 @@ max-width: 900px; } @media screen and (max-width:600px) { - input { + input, select { font-size: 1rem; } button {