feat: support for select menus

This commit is contained in:
xhyrom 2022-04-14 12:41:23 +02:00
parent 1f23b2536e
commit 4ee047e41d
11 changed files with 135 additions and 74 deletions

View file

@ -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<Response> => {
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<Response> => {
}
});
// 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<Response> => {
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<Response> => {
}
});
} 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<Response> => {
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?'
}
});
};

View file

@ -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)'}`
}
});
};

View file

@ -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<Role>) => {
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<Role>, 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;
}

View file

@ -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'}});

View file

@ -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;
}

View file

@ -1,7 +1,7 @@
const { createSecureHeaders } = require('next-secure-headers');
module.exports = {
assetPrefix: '/roles-bot/',
assetPrefix: '/',
async headers() {
return [{
source: '/(.*)',

View file

@ -18,6 +18,11 @@ export default function Home() {
<h1>Generate</h1>
<form>
<input placeholder="Your Message" name="message" id="message"/><br />
<select placeholder="Message Type" name="type" id="type" required>
<option value="" disabled={true}>Select a type</option>
<option value="1">Buttons</option>
<option value="2">Select Menu</option>
</select><br />
<input placeholder="Channel Id*" name="channel" id="channel" required/>
</form>
<button id="addRole">Add Role</button>

View file

@ -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');
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;
});
document.getElementById('json').innerHTML = hljs.highlight(JSON.stringify(json), { language: 'json' }).value;
});
}
$('button[id=addRole]').click((e) => {
Swal.fire({
title: 'Add Role',
html:
'<input id="swal-input1" class="swal2-input" placeholder="Button Label*" required />' +
`<input id="swal-input1" class="swal2-input" placeholder="${json.type === "1" ? 'Button' : 'Option'} Label*" required />` +
'<input id="swal-input2" class="swal2-input" placeholder="Role Id*" required />' +
'<input id="swal-input3" class="swal2-input" placeholder="Emoji" />' +
'<select id="swal-input4" class="swal2-input" style="display: flex;"><option value="" disabled="">Select a style</option><option value="1">Primary</option><option value="2">Secondary</option><option value="3">Success</option><option value="4">Danger</option></select>',
`${json.type === "2" ? '<input id="swal-input4" class="swal2-input" placeholder="Option Placeholder" />' : ''} ` +
`${json.type === "1" ? '<select id="swal-input5" class="swal2-input" style="display: flex;"><option value="" disabled="">Select a style</option><option value="1">Primary</option><option value="2">Secondary</option><option value="3">Success</option><option value="4">Danger</option></select>' : ''}`,
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;

View file

@ -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 {

View file

@ -1,6 +1,6 @@
{
"version": 3,
"mappings": "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",
"mappings": "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",
"sources": [
"../scss/style.scss",
"../scss/_globals.scss",

View file

@ -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 {