mirror of
https://github.com/roleypoly/roleypoly.git
synced 2025-04-24 19:39:11 +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,
|
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,
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -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;
|
||||||
|
};
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue