fix tests, add /roleypoly and /pickable-roles handlers

This commit is contained in:
41666 2022-02-04 11:16:18 -05:00
parent 8c61bfd4c7
commit 68b2b7323b
7 changed files with 174 additions and 14 deletions

View file

@ -5,6 +5,7 @@ import {
getPickableRoles, getPickableRoles,
} from '@roleypoly/api/src/guilds/getters'; } from '@roleypoly/api/src/guilds/getters';
import { import {
embedBuilder,
getName, getName,
InteractionHandler, InteractionHandler,
} from '@roleypoly/api/src/routes/interactions/helpers'; } from '@roleypoly/api/src/routes/interactions/helpers';
@ -112,7 +113,7 @@ export const pickableRoles: InteractionHandler = async (
return { return {
type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE, type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE,
data: { data: {
embeds: [embed], embeds: embedBuilder(embed),
components: [ components: [
{ {
type: 1, type: 1,

View file

@ -1,7 +1,7 @@
import { InteractionRequest, InteractionType } from '@roleypoly/types'; import { InteractionRequest, InteractionType } from '@roleypoly/types';
import nacl from 'tweetnacl'; import nacl from 'tweetnacl';
import { configContext } from '../../utils/testHelpers'; import { configContext } from '../../utils/testHelpers';
import { verifyRequest } from './helpers'; import { embedBuilder, verifyRequest } from './helpers';
// //
// Q: Why tweetnacl when WebCrypto is available? // Q: Why tweetnacl when WebCrypto is available?
@ -129,3 +129,51 @@ describe('verifyRequest', () => {
expect(await verifyRequest(context.config, request, body)).toBe(false); expect(await verifyRequest(context.config, request, body)).toBe(false);
}); });
}); });
describe('embedBuilder', () => {
it('builds embeds that discord approves of', () => {
const embeds = embedBuilder({
title: 'Test',
fields: [
{
name: 'Field 1',
value: 'role-1, role-2, role-3, role-4, role-5, '
.repeat(1024 / 30 - 15)
.replace(/, $/, ''),
},
{
name: 'Field 2',
value: 'role-1, role-2, role-3, role-4, role-5, '
.repeat(1024 / 30 + 4)
.replace(/, $/, ''),
},
],
color: 0xff0000,
});
expect(embeds).toMatchInlineSnapshot(`
Array [
Object {
"color": 16711680,
"fields": Array [
Object {
"name": "Field 1",
"value": "role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5",
},
Object {
"name": "Field 2",
"value": "role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3",
},
Object {
"name": "Field 2 (continued)",
"value": "role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5, role-1, role-2, role-3, role-4, role-5",
},
],
"title": "Test",
},
]
`);
expect(embeds.length).toBe(1);
expect(embeds[0].fields.length).toBe(3);
});
});

View file

@ -1,7 +1,7 @@
import { Config } from '@roleypoly/api/src/utils/config'; import { Config } from '@roleypoly/api/src/utils/config';
import { Context } from '@roleypoly/api/src/utils/context'; import { Context } from '@roleypoly/api/src/utils/context';
import { AuthType, discordFetch } from '@roleypoly/api/src/utils/discord'; import { AuthType, discordFetch } from '@roleypoly/api/src/utils/discord';
import { InteractionRequest, InteractionResponse } from '@roleypoly/types'; import { Embed, InteractionRequest, InteractionResponse } from '@roleypoly/types';
export const verifyRequest = async ( export const verifyRequest = async (
config: Config, config: Config,
@ -117,3 +117,87 @@ export const getName = (interaction: InteractionRequest): string => {
'friend' 'friend'
); );
}; };
/**
* Take a single big embed and fit it into Discord limits
* per embed, 25 fields, and 1024 characters per field.
* so we'll make new embeds/fields as the content gets too long.
*/
export const embedBuilder = (embed: Embed): Embed[] => {
const embeds: Embed[] = [];
const titleCorrection = (title: string, withContinued?: boolean) => {
const suffix = withContinued ? '... (continued)' : '...';
const offsetTitle = title.length + suffix.length;
return title.length > 256 - offsetTitle
? title.slice(0, 256 - offsetTitle) + suffix
: withContinued
? `${title} (continued)`
: title;
};
let currentEmbed: Embed = {
color: embed.color,
title: embed.title,
fields: [],
};
let knownFieldTitles: string[] = [];
const commitField = (field: Embed['fields'][0]) => {
if (currentEmbed.fields.length === 25) {
embeds.push(currentEmbed);
currentEmbed = {
color: embed.color,
title: `${embed.title} (continued)`,
fields: [],
};
}
console.warn({ field });
const addContinued = knownFieldTitles.includes(field.name);
if (!addContinued) {
knownFieldTitles.push(field.name);
}
field.name = titleCorrection(`${field.name}`, addContinued);
console.warn({ field, knownFieldTitles });
currentEmbed.fields.push(field);
};
for (let field of embed.fields) {
if (field.value.length <= 1024) {
commitField(field);
continue;
}
const split = field.value.split(', '); // we know we'll be using , as a delimiter
let fieldValue: Embed['fields'][0]['value'] = '';
for (let part of split) {
if (fieldValue.length + part.length > 1024) {
commitField({
name: field.name,
value: fieldValue.replace(/, $/, ''),
});
fieldValue = '';
} else {
fieldValue += part + ', ';
}
}
if (fieldValue.length > 0) {
commitField({
name: field.name,
value: fieldValue.replace(/, $/, ''),
});
}
}
if (currentEmbed.fields.length > 0) {
embeds.push(currentEmbed);
}
return embeds;
};

View file

@ -24,11 +24,7 @@ it('responds with a simple hello-world!', async () => {
}); });
expect(mockDiscordFetch).toBeCalledWith(expect.any(String), '', AuthType.None, { expect(mockDiscordFetch).toBeCalledWith(expect.any(String), '', AuthType.None, {
body: JSON.stringify({ body: JSON.stringify({
type: InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE, content: 'Hey there, test-user-nick',
data: {
flags: InteractionFlags.EPHEMERAL,
content: 'Hey there, test-user-nick',
},
}), }),
headers: expect.any(Object), headers: expect.any(Object),
method: 'PATCH', method: 'PATCH',

View file

@ -61,7 +61,6 @@ export const handleInteraction: RoleypolyHandler = async (
return json({ return json({
type: InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE, type: InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE,
data: { data: {
content: 'Figuring it out...',
flags: handler.ephemeral ? InteractionFlags.EPHEMERAL : 0, flags: handler.ephemeral ? InteractionFlags.EPHEMERAL : 0,
}, },
} as InteractionResponse); } as InteractionResponse);

View file

@ -119,11 +119,7 @@ export const mockUpdateCall = (
AuthType.None, AuthType.None,
{ {
body: JSON.stringify({ body: JSON.stringify({
type: InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE, ...data,
data: {
flags: InteractionFlags.EPHEMERAL,
...data,
},
}), }),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',

View file

@ -3,3 +3,39 @@ resource "discord-interactions_guild_command" "hello-world" {
description = "Says hello!" description = "Says hello!"
guild_id = "386659935687147521" guild_id = "386659935687147521"
} }
resource "discord-interactions_global_command" "roleypoly" {
name = "roleypoly"
description = "Find out how to use Roleypoly"
}
resource "discord-interactions_global_command" "pickable-roles" {
name = "pickable-roles"
description = "See the roles you can pick from"
}
resource "discord-interactions_guild_command" "pick-role" {
name = "pick-role"
description = "Pick a new role (see /pickable-roles for a full list)"
guild_id = "386659935687147521"
option {
name = "role"
description = "The role you want"
type = 8
required = true
}
}
resource "discord-interactions_guild_command" "remove-role" {
name = "remove-role"
description = "Remove a role you already have"
guild_id = "386659935687147521"
option {
name = "role"
description = "The role you want to remove"
type = 8
required = true
}
}