From 81514276e9b0cb296b64755863da79fb71d673e9 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Thu, 19 Aug 2021 19:33:47 +0300 Subject: [PATCH] Fix issues around embeds --- .../src/plugins/Logs/util/getLogMessage.ts | 6 +- .../src/plugins/Post/commands/PostEmbedCmd.ts | 2 +- .../Post/commands/ScheduledPostsListCmd.ts | 5 +- .../src/plugins/Tags/commands/TagEvalCmd.ts | 2 +- backend/src/plugins/Tags/types.ts | 1 + .../src/plugins/Tags/util/renderTagBody.ts | 5 +- .../plugins/Tags/util/renderTagFromString.ts | 12 +-- backend/src/utils.ts | 99 ++++++++++++++++++- 8 files changed, 111 insertions(+), 21 deletions(-) diff --git a/backend/src/plugins/Logs/util/getLogMessage.ts b/backend/src/plugins/Logs/util/getLogMessage.ts index f44afaa3..81714486 100644 --- a/backend/src/plugins/Logs/util/getLogMessage.ts +++ b/backend/src/plugins/Logs/util/getLogMessage.ts @@ -13,6 +13,7 @@ import { messageSummary, renderRecursively, resolveMember, + validateAndParseMessageContent, verboseChannelMention, verboseUserMention, verboseUserName, @@ -134,10 +135,7 @@ export async function getLogMessage( formatted = `\`[${timestamp}]\` ${formatted}`; } } else if (formatted != null) { - if (formatted.embed) { - formatted.embeds = [formatted.embed]; - delete formatted.embed; - } + formatted = validateAndParseMessageContent(formatted); if (formatted.embeds && Array.isArray(formatted.embeds) && includeEmbedTimestamp) { for (const embed of formatted.embeds) { diff --git a/backend/src/plugins/Post/commands/PostEmbedCmd.ts b/backend/src/plugins/Post/commands/PostEmbedCmd.ts index 6db7d1cc..e822cd3b 100644 --- a/backend/src/plugins/Post/commands/PostEmbedCmd.ts +++ b/backend/src/plugins/Post/commands/PostEmbedCmd.ts @@ -82,6 +82,6 @@ export const PostEmbedCmd = postCmd({ ); } - actualPostCmd(pluginData, msg, args.channel, { embed }, args); + actualPostCmd(pluginData, msg, args.channel, { embeds: [embed] }, args); }, }); diff --git a/backend/src/plugins/Post/commands/ScheduledPostsListCmd.ts b/backend/src/plugins/Post/commands/ScheduledPostsListCmd.ts index 7ac59698..17b1fc17 100644 --- a/backend/src/plugins/Post/commands/ScheduledPostsListCmd.ts +++ b/backend/src/plugins/Post/commands/ScheduledPostsListCmd.ts @@ -22,8 +22,7 @@ export const ScheduledPostsListCmd = postCmd({ let i = 1; const postLines = scheduledPosts.map(p => { - let previewText = - p.content.content || (p.content.embed && (p.content.embed.description || p.content.embed.title)) || ""; + let previewText = p.content.content || p.content.embeds?.[0]?.description || p.content.embeds?.[0]?.title || ""; const isTruncated = previewText.length > SCHEDULED_POST_PREVIEW_TEXT_LENGTH; @@ -37,7 +36,7 @@ export const ScheduledPostsListCmd = postCmd({ .format(timeAndDate.getDateFormat("pretty_datetime")); const parts = [`\`#${i++}\` \`[${prettyPostAt}]\` ${previewText}${isTruncated ? "..." : ""}`]; if (p.attachments.length) parts.push("*(with attachment)*"); - if (p.content.embed) parts.push("*(embed)*"); + if (p.content.embeds?.length) parts.push("*(embed)*"); if (p.repeat_until) { parts.push(`*(repeated every ${humanizeDuration(p.repeat_interval)} until ${p.repeat_until})*`); } diff --git a/backend/src/plugins/Tags/commands/TagEvalCmd.ts b/backend/src/plugins/Tags/commands/TagEvalCmd.ts index ee8c340f..bf9c3f1f 100644 --- a/backend/src/plugins/Tags/commands/TagEvalCmd.ts +++ b/backend/src/plugins/Tags/commands/TagEvalCmd.ts @@ -26,7 +26,7 @@ export const TagEvalCmd = tagsCmd({ { member: msg.member }, ); - if (!rendered.content && !rendered.embed) { + if (!rendered.content && !rendered.embeds?.length) { sendErrorMessage(pluginData, msg.channel, "Evaluation resulted in an empty text"); return; } diff --git a/backend/src/plugins/Tags/types.ts b/backend/src/plugins/Tags/types.ts index 17ad0fc8..71247dec 100644 --- a/backend/src/plugins/Tags/types.ts +++ b/backend/src/plugins/Tags/types.ts @@ -7,6 +7,7 @@ import { GuildTags } from "../../data/GuildTags"; import { tEmbed, tNullable } from "../../utils"; export const Tag = t.union([t.string, tEmbed]); +export type TTag = t.TypeOf; export const TagCategory = t.type({ prefix: tNullable(t.string), diff --git a/backend/src/plugins/Tags/util/renderTagBody.ts b/backend/src/plugins/Tags/util/renderTagBody.ts index 9e5fede7..cd8afcfe 100644 --- a/backend/src/plugins/Tags/util/renderTagBody.ts +++ b/backend/src/plugins/Tags/util/renderTagBody.ts @@ -1,14 +1,13 @@ -import * as t from "io-ts"; import { GuildPluginData } from "knub"; import { ExtendedMatchParams } from "knub/dist/config/PluginConfigManager"; import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter"; import { renderRecursively, StrictMessageContent } from "../../../utils"; -import { Tag, TagsPluginType } from "../types"; +import { TagsPluginType, TTag } from "../types"; import { findTagByName } from "./findTagByName"; export async function renderTagBody( pluginData: GuildPluginData, - body: t.TypeOf, + body: TTag, args: unknown[] = [], extraData = {}, subTagPermissionMatchParams?: ExtendedMatchParams, diff --git a/backend/src/plugins/Tags/util/renderTagFromString.ts b/backend/src/plugins/Tags/util/renderTagFromString.ts index 3f5c31b5..4a22310c 100644 --- a/backend/src/plugins/Tags/util/renderTagFromString.ts +++ b/backend/src/plugins/Tags/util/renderTagFromString.ts @@ -1,13 +1,11 @@ import { GuildMember } from "discord.js"; -import * as t from "io-ts"; import { GuildPluginData } from "knub"; import { parseArguments } from "knub-command-manager"; import { memberToTemplateSafeMember, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; -import { LogType } from "../../../data/LogType"; import { TemplateParseError } from "../../../templateFormatter"; -import { StrictMessageContent } from "../../../utils"; +import { StrictMessageContent, validateAndParseMessageContent } from "../../../utils"; import { LogsPlugin } from "../../Logs/LogsPlugin"; -import { Tag, TagsPluginType } from "../types"; +import { TagsPluginType, TTag } from "../types"; import { renderTagBody } from "./renderTagBody"; export async function renderTagFromString( @@ -15,7 +13,7 @@ export async function renderTagFromString( str: string, prefix: string, tagName: string, - tagBody: t.TypeOf, + tagBody: TTag, member: GuildMember, ): Promise { const variableStr = str.slice(prefix.length + tagName.length).trim(); @@ -23,7 +21,7 @@ export async function renderTagFromString( // Format the string try { - return renderTagBody( + const rendered = await renderTagBody( pluginData, tagBody, tagArgs, @@ -33,6 +31,8 @@ export async function renderTagFromString( }, { member }, ); + + return validateAndParseMessageContent(rendered); } catch (e) { if (e instanceof TemplateParseError) { const logs = pluginData.getPlugin(LogsPlugin); diff --git a/backend/src/utils.ts b/backend/src/utils.ts index 7be56779..57ab0888 100644 --- a/backend/src/utils.ts +++ b/backend/src/utils.ts @@ -44,6 +44,7 @@ import { ChannelTypeStrings } from "./types"; import { sendDM } from "./utils/sendDM"; import { waitForButtonConfirm } from "./utils/waitForInteraction"; import { decodeAndValidateStrict, StrictValidationError } from "./validatorUtils"; +import { z } from "zod"; const fsp = fs.promises; @@ -320,25 +321,117 @@ export const tEmbed = t.type({ ), }); +export const zEmbedInput = z.object({ + title: z.string().optional(), + description: z.string().optional(), + url: z.string().optional(), + timestamp: z.number().optional(), + color: z.number().optional(), + + footer: z.optional( + z.object({ + text: z.string(), + icon_url: z.string().optional(), + }), + ), + + image: z.optional( + z.object({ + url: z.string().optional(), + width: z.number().optional(), + height: z.number().optional(), + }), + ), + + thumbnail: z.optional( + z.object({ + url: z.string().optional(), + width: z.number().optional(), + height: z.number().optional(), + }), + ), + + video: z.optional( + z.object({ + url: z.string().optional(), + width: z.number().optional(), + height: z.number().optional(), + }), + ), + + provider: z.optional( + z.object({ + name: z.string(), + url: z.string().optional(), + }), + ), + + fields: z.optional( + z.array( + z.object({ + name: z.string().optional(), + value: z.string().optional(), + inline: z.boolean().optional(), + }), + ), + ), + + author: z + .optional( + z.object({ + name: z.string(), + url: z.string().optional(), + width: z.number().optional(), + height: z.number().optional(), + }), + ) + .nullable(), +}); + export type EmbedWith = MessageEmbedOptions & Pick, T>; +export const zStrictMessageContent = z.object({ + content: z.string().optional(), + tts: z.boolean().optional(), + embeds: z.array(zEmbedInput).optional(), +}); + +export type ZStrictMessageContent = z.infer; + export type StrictMessageContent = { content?: string; tts?: boolean; - disableEveryone?: boolean; - embed?: MessageEmbedOptions; + embeds?: MessageEmbedOptions[]; }; export const tStrictMessageContent = t.type({ content: tNullable(t.string), tts: tNullable(t.boolean), disableEveryone: tNullable(t.boolean), - embed: tNullable(tEmbed), + embeds: tNullable(t.array(tEmbed)), }); export const tMessageContent = t.union([t.string, tStrictMessageContent]); +export function validateAndParseMessageContent(input: unknown): StrictMessageContent { + if (input == null) { + return {}; + } + + if (typeof input !== "object") { + return { content: String(input) }; + } + + // Migrate embed -> embeds + if ((input as any).embed) { + (input as any).embeds = [(input as any).embed]; + delete (input as any).embed; + } + + return (zStrictMessageContent.parse(input) as unknown) as StrictMessageContent; +} + /** * Mirrors AllowedMentions from Eris */