add /roleypoly and /pickable-roles slash commands, fix framework issues

This commit is contained in:
41666 2022-02-04 01:37:14 -05:00
parent fd7ed13e9d
commit 5c5258ef5e
6 changed files with 329 additions and 19 deletions

View file

@ -0,0 +1,135 @@
import {
getGuild,
getGuildData,
getGuildMember,
getPickableRoles,
} from '@roleypoly/api/src/guilds/getters';
import {
getName,
InteractionHandler,
} from '@roleypoly/api/src/routes/interactions/helpers';
import { Context } from '@roleypoly/api/src/utils/context';
import {
CategoryType,
Embed,
InteractionCallbackType,
InteractionRequest,
InteractionResponse,
Role,
} from '@roleypoly/types';
export const pickableRoles: InteractionHandler = async (
interaction: InteractionRequest,
context: Context
): Promise<InteractionResponse> => {
if (!interaction.guild_id) {
return {
type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
embeds: [
{
color: 0xff0000,
title: ':x: Error',
description: `:x: Hey ${getName(
interaction
)}. You need to use this command in a server, not in a DM.`,
},
],
},
};
}
const [guildData, guild, member] = await Promise.all([
getGuildData(context.config, interaction.guild_id),
getGuild(context.config, interaction.guild_id),
getGuildMember(context.config, interaction.guild_id, interaction.member?.user?.id!),
]);
if (!guildData || !guild) {
return {
type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
embeds: [
{
color: 0xff0000,
title: ':x: Error',
description: `:x: Hey ${getName(
interaction
)}. Something's wrong with the server you're in. Try picking your roles at ${
context.config.uiPublicURI
}/s/${interaction.guild_id} instead.`,
},
],
},
};
}
const roles = getPickableRoles(guildData, guild);
if (roles.length === 0) {
console.warn('/pickable-roles turned up empty?', { roles, guild, guildData });
return {
type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
embeds: [
{
color: 0xff0000,
title: ':fire: Error',
description: `Hey ${getName(
interaction
)}. This server might not be set up to use Roleypoly yet, as there are no roles to pick from.`,
},
],
},
};
}
const makeBoldIfMemberHasRole = (role: Role, base: string): string => {
if (member?.roles.includes(role.id)) {
return `__${base}__`;
}
return base;
};
const embed: Embed = {
color: 0xab9b9a,
fields: roles.map(({ category, roles }) => {
return {
name: `${category.name}${
category.type === CategoryType.Single ? ' *(pick one)*' : ''
}`,
value: roles
.map((role) => makeBoldIfMemberHasRole(role, `<@&${role.id}>`))
.join(', '),
} as Embed['fields'][0];
}),
title: 'You can pick any of these roles with /pick-role',
footer: {
text: `Roles with an __underline__ are already picked by you.`,
},
};
return {
type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
embeds: [embed],
components: [
{
type: 1,
components: [
// Link to Roleypoly
{
type: 2,
label: 'Pick roles on your browser',
url: `${context.config.uiPublicURI}/s/${interaction.guild_id}`,
style: 5,
},
],
},
],
},
};
};
pickableRoles.ephemeral = true;
pickableRoles.deferred = true;

View file

@ -0,0 +1,61 @@
import {
getName,
InteractionHandler,
} from '@roleypoly/api/src/routes/interactions/helpers';
import { Context } from '@roleypoly/api/src/utils/context';
import {
Embed,
InteractionCallbackType,
InteractionRequest,
InteractionResponse,
} from '@roleypoly/types';
export const roleypoly: InteractionHandler = (
interaction: InteractionRequest,
context: Context
): InteractionResponse => {
if (!interaction.guild_id) {
return {
type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
content: `:x: Hey ${getName(
interaction
)}. You need to use this command in a server, not in a DM.`,
},
};
}
return {
type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
embeds: [
{
color: 0x453e3d,
title: `:beginner: Hey there, ${getName(interaction)}!`,
description: `Try these slash commands, or pick roles from your browser!`,
fields: [
{ name: 'See all the roles', value: '/pickable-roles' },
{ name: 'Pick a role', value: '/pick-role' },
{ name: 'Remove a role', value: '/remove-role' },
],
} as Embed,
],
components: [
{
type: 1,
components: [
// Link to Roleypoly
{
type: 2,
label: `Pick roles on ${new URL(context.config.uiPublicURI).hostname}`,
url: `${context.config.uiPublicURI}/s/${interaction.guild_id}`,
style: 5,
},
],
},
],
},
};
};
roleypoly.ephemeral = true;

View file

@ -1,12 +1,7 @@
import { Config } from '@roleypoly/api/src/utils/config';
import { Context } from '@roleypoly/api/src/utils/context';
import { AuthType, discordFetch } from '@roleypoly/api/src/utils/discord';
import {
InteractionCallbackType,
InteractionFlags,
InteractionRequest,
InteractionResponse,
} from '@roleypoly/types';
import { InteractionRequest, InteractionResponse } from '@roleypoly/types';
export const verifyRequest = async (
config: Config,
@ -76,17 +71,19 @@ export const runAsync = async (
try {
const response = await handler(interaction, context);
if (!response) {
throw new Error('Interaction handler returned no response');
}
console.log({ response });
await discordFetch(url, '', AuthType.None, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE,
data: {
flags: handler.ephemeral ? InteractionFlags.EPHEMERAL : 0,
...response.data,
},
...response.data,
}),
});
} catch (e) {
@ -105,13 +102,18 @@ export const runAsync = async (
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE,
data: {
content: "I'm sorry, I'm having trouble processing this request.",
flags: InteractionFlags.EPHEMERAL,
},
} as InteractionResponse),
content: "I'm sorry, I'm having trouble processing this request.",
} as InteractionResponse['data']),
});
} catch (e) {}
}
};
export const getName = (interaction: InteractionRequest): string => {
return (
interaction.member?.nick ||
interaction.member?.user?.username ||
interaction.user?.username ||
'friend'
);
};

View file

@ -1,4 +1,6 @@
import { helloWorld } from '@roleypoly/api/src/routes/interactions/commands/hello-world';
import { pickableRoles } from '@roleypoly/api/src/routes/interactions/commands/pickable-roles';
import { roleypoly } from '@roleypoly/api/src/routes/interactions/commands/roleypoly';
import {
InteractionHandler,
runAsync,
@ -18,6 +20,8 @@ import {
const commands: Record<InteractionData['name'], InteractionHandler> = {
'hello-world': helloWorld,
roleypoly: roleypoly,
'pickable-roles': pickableRoles,
};
export const handleInteraction: RoleypolyHandler = async (
@ -57,6 +61,7 @@ export const handleInteraction: RoleypolyHandler = async (
return json({
type: InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE,
data: {
content: 'Figuring it out...',
flags: handler.ephemeral ? InteractionFlags.EPHEMERAL : 0,
},
} as InteractionResponse);