mirror of
https://github.com/roleypoly/roleypoly.git
synced 2025-04-24 19:39:11 +00:00
add /pick-role and /remove-role, refactor responses
This commit is contained in:
parent
0836d548b2
commit
5aa5a6ae1c
7 changed files with 253 additions and 54 deletions
18
packages/api/src/routes/interactions/commands/pick-role.ts
Normal file
18
packages/api/src/routes/interactions/commands/pick-role.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { InteractionHandler } from '@roleypoly/api/src/routes/interactions/helpers';
|
||||
import { rolePickerCommon } from '@roleypoly/api/src/routes/interactions/role-picker-common';
|
||||
import { Context } from '@roleypoly/api/src/utils/context';
|
||||
import {
|
||||
InteractionRequest,
|
||||
InteractionResponse,
|
||||
TransactionType,
|
||||
} from '@roleypoly/types';
|
||||
|
||||
export const pickRole: InteractionHandler = async (
|
||||
interaction: InteractionRequest,
|
||||
context: Context
|
||||
): Promise<InteractionResponse> => {
|
||||
return rolePickerCommon(interaction, context, TransactionType.Add);
|
||||
};
|
||||
|
||||
pickRole.ephemeral = true;
|
||||
pickRole.deferred = true;
|
|
@ -9,6 +9,10 @@ import {
|
|||
getName,
|
||||
InteractionHandler,
|
||||
} from '@roleypoly/api/src/routes/interactions/helpers';
|
||||
import {
|
||||
embedPalette,
|
||||
embedResponse,
|
||||
} from '@roleypoly/api/src/routes/interactions/responses';
|
||||
import { Context } from '@roleypoly/api/src/utils/context';
|
||||
import {
|
||||
CategoryType,
|
||||
|
@ -24,20 +28,13 @@ export const pickableRoles: InteractionHandler = async (
|
|||
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(
|
||||
return embedResponse(
|
||||
':x: Error',
|
||||
`Hey ${getName(
|
||||
interaction
|
||||
)}. You need to use this command in a server, not in a DM.`,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
embedPalette.error
|
||||
);
|
||||
}
|
||||
|
||||
const [guildData, guild, member] = await Promise.all([
|
||||
|
@ -47,41 +44,26 @@ export const pickableRoles: InteractionHandler = async (
|
|||
]);
|
||||
|
||||
if (!guildData || !guild) {
|
||||
return {
|
||||
type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE,
|
||||
data: {
|
||||
embeds: [
|
||||
{
|
||||
color: 0xff0000,
|
||||
title: ':x: Error',
|
||||
description: `:x: Hey ${getName(
|
||||
return embedResponse(
|
||||
':x: Error',
|
||||
`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.`,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
embedPalette.error
|
||||
);
|
||||
}
|
||||
|
||||
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(
|
||||
return embedResponse(
|
||||
':fire: Error',
|
||||
`Hey ${getName(
|
||||
interaction
|
||||
)}. This server might not be set up to use Roleypoly yet, as there are no roles to pick from.`,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
embedPalette.error
|
||||
);
|
||||
}
|
||||
|
||||
const makeBoldIfMemberHasRole = (role: Role, base: string): string => {
|
||||
|
@ -93,7 +75,7 @@ export const pickableRoles: InteractionHandler = async (
|
|||
};
|
||||
|
||||
const embed: Embed = {
|
||||
color: 0xab9b9a,
|
||||
color: embedPalette.neutral,
|
||||
fields: roles.map(({ category, roles }) => {
|
||||
return {
|
||||
name: `${category.name}${
|
||||
|
|
18
packages/api/src/routes/interactions/commands/remove-role.ts
Normal file
18
packages/api/src/routes/interactions/commands/remove-role.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { InteractionHandler } from '@roleypoly/api/src/routes/interactions/helpers';
|
||||
import { rolePickerCommon } from '@roleypoly/api/src/routes/interactions/role-picker-common';
|
||||
import { Context } from '@roleypoly/api/src/utils/context';
|
||||
import {
|
||||
InteractionRequest,
|
||||
InteractionResponse,
|
||||
TransactionType,
|
||||
} from '@roleypoly/types';
|
||||
|
||||
export const removeRole: InteractionHandler = async (
|
||||
interaction: InteractionRequest,
|
||||
context: Context
|
||||
): Promise<InteractionResponse> => {
|
||||
return rolePickerCommon(interaction, context, TransactionType.Remove);
|
||||
};
|
||||
|
||||
removeRole.ephemeral = true;
|
||||
removeRole.deferred = true;
|
|
@ -2,6 +2,7 @@ import {
|
|||
getName,
|
||||
InteractionHandler,
|
||||
} from '@roleypoly/api/src/routes/interactions/helpers';
|
||||
import { embedResponse } from '@roleypoly/api/src/routes/interactions/responses';
|
||||
import { Context } from '@roleypoly/api/src/utils/context';
|
||||
import {
|
||||
Embed,
|
||||
|
@ -15,14 +16,12 @@ export const roleypoly: InteractionHandler = (
|
|||
context: Context
|
||||
): InteractionResponse => {
|
||||
if (!interaction.guild_id) {
|
||||
return {
|
||||
type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE,
|
||||
data: {
|
||||
content: `:x: Hey ${getName(
|
||||
return embedResponse(
|
||||
':x: Error',
|
||||
`Hey ${getName(
|
||||
interaction
|
||||
)}. You need to use this command in a server, not in a DM.`,
|
||||
},
|
||||
};
|
||||
)}. You need to use this command in a server, not in a DM.`
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { helloWorld } from '@roleypoly/api/src/routes/interactions/commands/hello-world';
|
||||
import { pickRole } from '@roleypoly/api/src/routes/interactions/commands/pick-role';
|
||||
import { pickableRoles } from '@roleypoly/api/src/routes/interactions/commands/pickable-roles';
|
||||
import { removeRole } from '@roleypoly/api/src/routes/interactions/commands/remove-role';
|
||||
import { roleypoly } from '@roleypoly/api/src/routes/interactions/commands/roleypoly';
|
||||
import {
|
||||
InteractionHandler,
|
||||
|
@ -22,6 +24,8 @@ const commands: Record<InteractionData['name'], InteractionHandler> = {
|
|||
'hello-world': helloWorld,
|
||||
roleypoly: roleypoly,
|
||||
'pickable-roles': pickableRoles,
|
||||
'pick-role': pickRole,
|
||||
'remove-role': removeRole,
|
||||
};
|
||||
|
||||
export const handleInteraction: RoleypolyHandler = async (
|
||||
|
|
|
@ -36,3 +36,26 @@ export const notImplemented: InteractionHandler = (): InteractionResponse => ({
|
|||
flags: InteractionFlags.EPHEMERAL,
|
||||
},
|
||||
});
|
||||
|
||||
export const embedResponse = (
|
||||
title: string,
|
||||
description: string,
|
||||
color?: number
|
||||
): InteractionResponse => ({
|
||||
type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE,
|
||||
data: {
|
||||
embeds: [
|
||||
{
|
||||
color: color || 0x00ff00,
|
||||
title,
|
||||
description,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
export const embedPalette = {
|
||||
success: 0x1d8227,
|
||||
error: 0xf14343,
|
||||
neutral: 0x2c2f33,
|
||||
};
|
||||
|
|
155
packages/api/src/routes/interactions/role-picker-common.ts
Normal file
155
packages/api/src/routes/interactions/role-picker-common.ts
Normal file
|
@ -0,0 +1,155 @@
|
|||
import { getGuild, getGuildData } from '@roleypoly/api/src/guilds/getters';
|
||||
import { calculateNewRoles } from '@roleypoly/api/src/routes/guilds/guild-roles-put';
|
||||
import { getName } from '@roleypoly/api/src/routes/interactions/helpers';
|
||||
import {
|
||||
embedPalette,
|
||||
embedResponse,
|
||||
} from '@roleypoly/api/src/routes/interactions/responses';
|
||||
import { Context } from '@roleypoly/api/src/utils/context';
|
||||
import { APIMember, AuthType, discordFetch } from '@roleypoly/api/src/utils/discord';
|
||||
import { isIdenticalArray } from '@roleypoly/misc-utils/collection-tools';
|
||||
import {
|
||||
CategoryType,
|
||||
InteractionRequest,
|
||||
InteractionResponse,
|
||||
RoleTransaction,
|
||||
TransactionType,
|
||||
} from '@roleypoly/types';
|
||||
|
||||
export const rolePickerCommon = async (
|
||||
interaction: InteractionRequest,
|
||||
context: Context,
|
||||
action: TransactionType
|
||||
): Promise<InteractionResponse> => {
|
||||
if (!interaction.guild_id) {
|
||||
return embedResponse(
|
||||
':x: Error',
|
||||
`Hey ${getName(
|
||||
interaction
|
||||
)}. You need to use this command in a server, not in a DM.`
|
||||
);
|
||||
}
|
||||
|
||||
const currentRoles = interaction.member?.roles || [];
|
||||
|
||||
const [guildData, guild] = await Promise.all([
|
||||
getGuildData(context.config, interaction.guild_id),
|
||||
getGuild(context.config, interaction.guild_id),
|
||||
]);
|
||||
|
||||
if (!guildData || !guild) {
|
||||
return embedResponse(
|
||||
':x: Error',
|
||||
`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.`,
|
||||
embedPalette.error
|
||||
);
|
||||
}
|
||||
|
||||
const roleToPick = interaction.data?.options?.[0]?.value;
|
||||
if (!roleToPick) {
|
||||
return embedResponse(
|
||||
':fire: Discord sent me the wrong data',
|
||||
`Hey ${getName(interaction)}. Please try again later.`,
|
||||
embedPalette.error
|
||||
);
|
||||
}
|
||||
|
||||
const hasRole = interaction.member?.roles.includes(roleToPick);
|
||||
if (action === TransactionType.Add && hasRole) {
|
||||
return embedResponse(
|
||||
`:white_check_mark: You already have that role.`,
|
||||
`Hey ${getName(interaction)}. You already have <@&${roleToPick}>!`,
|
||||
embedPalette.neutral
|
||||
);
|
||||
}
|
||||
|
||||
if (action === TransactionType.Remove && !hasRole) {
|
||||
return embedResponse(
|
||||
`:white_check_mark: You don't have that role.`,
|
||||
`Hey ${getName(interaction)}. You already don't have <@&${roleToPick}>!`,
|
||||
embedPalette.neutral
|
||||
);
|
||||
}
|
||||
|
||||
const extraTransactions: RoleTransaction[] = [];
|
||||
let isSingle = false;
|
||||
if (action === TransactionType.Add) {
|
||||
// For single-type categories, let's also generate the remove rules for the other roles in the category
|
||||
const category = guildData.categories.find((category) =>
|
||||
category.roles.includes(roleToPick)
|
||||
);
|
||||
if (category?.type === CategoryType.Single) {
|
||||
const otherRoles = category.roles.filter((role) => role !== roleToPick);
|
||||
extraTransactions.push(
|
||||
...otherRoles.map((role) => ({ action: TransactionType.Remove, id: role }))
|
||||
);
|
||||
isSingle = true;
|
||||
}
|
||||
}
|
||||
|
||||
const newRoles = calculateNewRoles({
|
||||
currentRoles,
|
||||
guildRoles: guild.roles,
|
||||
guildData,
|
||||
updateRequest: {
|
||||
knownState: currentRoles,
|
||||
transactions: [{ action, id: roleToPick }, ...extraTransactions],
|
||||
},
|
||||
});
|
||||
|
||||
if (isIdenticalArray(currentRoles, newRoles)) {
|
||||
return embedResponse(
|
||||
':x: You cannot pick this role.',
|
||||
`Hey ${getName(
|
||||
interaction
|
||||
)}. <@&${roleToPick}> isn't pickable. Check /pickable-roles to see which roles you can use.`,
|
||||
embedPalette.error
|
||||
);
|
||||
}
|
||||
|
||||
const patchMemberRoles = await discordFetch<APIMember>(
|
||||
`/guilds/${interaction.guild_id}/members/${interaction.member?.user?.id}`,
|
||||
context.config.botToken,
|
||||
AuthType.Bot,
|
||||
{
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
'x-audit-log-reason': `Picked their roles via /${
|
||||
action === TransactionType.Add ? 'pick' : 'remove'
|
||||
}-role`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
roles: newRoles,
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
if (!patchMemberRoles) {
|
||||
return embedResponse(
|
||||
':x: Discord stopped me from updating your roles.',
|
||||
`Hey ${getName(
|
||||
interaction
|
||||
)}. Discord didn't let me give you <@&${roleToPick}>. Could you try again later?`,
|
||||
embedPalette.error
|
||||
);
|
||||
}
|
||||
|
||||
return action === TransactionType.Add
|
||||
? embedResponse(
|
||||
':white_check_mark: You got it!',
|
||||
`Hey ${getName(interaction)}, I gave you <@&${roleToPick}>!${
|
||||
isSingle ? `\nThe other roles in this category have been removed.` : ''
|
||||
}`,
|
||||
embedPalette.success
|
||||
)
|
||||
: embedResponse(
|
||||
":white_check_mark: You (don't) got it!",
|
||||
`Hey ${getName(interaction)}, I took away <@&${roleToPick}>!`,
|
||||
embedPalette.success
|
||||
);
|
||||
};
|
Loading…
Add table
Reference in a new issue