mirror of
https://github.com/roleypoly/roleypoly.git
synced 2025-04-24 11:29:12 +00:00
fix tests, add /roleypoly and /pickable-roles handlers
This commit is contained in:
parent
8c61bfd4c7
commit
68b2b7323b
7 changed files with 174 additions and 14 deletions
|
@ -5,6 +5,7 @@ import {
|
|||
getPickableRoles,
|
||||
} from '@roleypoly/api/src/guilds/getters';
|
||||
import {
|
||||
embedBuilder,
|
||||
getName,
|
||||
InteractionHandler,
|
||||
} from '@roleypoly/api/src/routes/interactions/helpers';
|
||||
|
@ -112,7 +113,7 @@ export const pickableRoles: InteractionHandler = async (
|
|||
return {
|
||||
type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE,
|
||||
data: {
|
||||
embeds: [embed],
|
||||
embeds: embedBuilder(embed),
|
||||
components: [
|
||||
{
|
||||
type: 1,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { InteractionRequest, InteractionType } from '@roleypoly/types';
|
||||
import nacl from 'tweetnacl';
|
||||
import { configContext } from '../../utils/testHelpers';
|
||||
import { verifyRequest } from './helpers';
|
||||
import { embedBuilder, verifyRequest } from './helpers';
|
||||
|
||||
//
|
||||
// Q: Why tweetnacl when WebCrypto is available?
|
||||
|
@ -129,3 +129,51 @@ describe('verifyRequest', () => {
|
|||
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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +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 { InteractionRequest, InteractionResponse } from '@roleypoly/types';
|
||||
import { Embed, InteractionRequest, InteractionResponse } from '@roleypoly/types';
|
||||
|
||||
export const verifyRequest = async (
|
||||
config: Config,
|
||||
|
@ -117,3 +117,87 @@ export const getName = (interaction: InteractionRequest): string => {
|
|||
'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;
|
||||
};
|
||||
|
|
|
@ -24,11 +24,7 @@ it('responds with a simple hello-world!', async () => {
|
|||
});
|
||||
expect(mockDiscordFetch).toBeCalledWith(expect.any(String), '', AuthType.None, {
|
||||
body: JSON.stringify({
|
||||
type: InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE,
|
||||
data: {
|
||||
flags: InteractionFlags.EPHEMERAL,
|
||||
content: 'Hey there, test-user-nick',
|
||||
},
|
||||
content: 'Hey there, test-user-nick',
|
||||
}),
|
||||
headers: expect.any(Object),
|
||||
method: 'PATCH',
|
||||
|
|
|
@ -61,7 +61,6 @@ 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);
|
||||
|
|
|
@ -119,11 +119,7 @@ export const mockUpdateCall = (
|
|||
AuthType.None,
|
||||
{
|
||||
body: JSON.stringify({
|
||||
type: InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE,
|
||||
data: {
|
||||
flags: InteractionFlags.EPHEMERAL,
|
||||
...data,
|
||||
},
|
||||
...data,
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
|
|
@ -3,3 +3,39 @@ resource "discord-interactions_guild_command" "hello-world" {
|
|||
description = "Says hello!"
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue