UNPKG

@fantinodavide/discord-html-transcripts

Version:

A nicely formatted html transcript generator for discord.js.

196 lines 11.6 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.RenderType = void 0; exports.default = MessageContent; exports.MessageSingleASTNode = MessageSingleASTNode; exports.getChannelType = getChannelType; const discord_components_react_1 = require("@derockdev/discord-components-react"); const discord_js_1 = require("discord.js"); const react_1 = __importDefault(require("react")); const utils_1 = require("../../utils/utils"); const markdown_it_1 = __importDefault(require("markdown-it")); const html_react_parser_1 = __importDefault(require("html-react-parser")); // @ts-ignore const markdown_it_discord_inline_1 = require("./markdown-it-discord-inline"); const domhandler_1 = require("domhandler"); var RenderType; (function (RenderType) { RenderType[RenderType["EMBED"] = 0] = "EMBED"; RenderType[RenderType["REPLY"] = 1] = "REPLY"; RenderType[RenderType["NORMAL"] = 2] = "NORMAL"; RenderType[RenderType["WEBHOOK"] = 3] = "WEBHOOK"; })(RenderType || (exports.RenderType = RenderType = {})); // @ts-ignore function htmlToReact(html) { return (0, html_react_parser_1.default)(html, { replace(dom) { // 1. Ignore non-elements (text nodes, comments, etc.) if (!(0, domhandler_1.isTag)(dom)) return undefined; // 2. Now `dom` is guaranteed to be an Element const el = dom; switch (el.name) { case 'discord-bold': return react_1.default.createElement(discord_components_react_1.DiscordBold, null, el.children.map(c => ((0, domhandler_1.isText)(c) ? c.data : '')).join('')); case 'discord-italic': return react_1.default.createElement(discord_components_react_1.DiscordItalic, null, el.children.map(c => ((0, domhandler_1.isText)(c) ? c.data : '')).join('')); case 'discord-underline': return react_1.default.createElement(discord_components_react_1.DiscordUnderlined, null, el.children.map(c => ((0, domhandler_1.isText)(c) ? c.data : '')).join('')); case 'discord-strikethrough': return react_1.default.createElement("s", null, el.children.map(c => ((0, domhandler_1.isText)(c) ? c.data : '')).join('')); case 'discord-inline-code': return react_1.default.createElement(discord_components_react_1.DiscordInlineCode, null, el.children.map(c => ((0, domhandler_1.isText)(c) ? c.data : '')).join('')); case 'discord-code-block': return (react_1.default.createElement(discord_components_react_1.DiscordCodeBlock, { language: el.attribs.language || '', code: el.children.map(c => ((0, domhandler_1.isText)(c) ? c.data : '')).join('') })); case 'discord-mention': return (react_1.default.createElement(discord_components_react_1.DiscordMention, { type: el.attribs.type, id: el.attribs.id, color: el.attribs.color }, el.attribs.id)); default: return undefined; } }, }); } /** * Renders discord markdown content * @param content - The content to render * @param context - The context to render the content in * @returns */ function MessageContent({ content, context }) { if (context.type === RenderType.REPLY && content.length > 180) content = content.slice(0, 180) + '...'; // parse the markdown const parsed = [{ content, type: 'text' }]; // check if the parsed content is only emojis const isOnlyEmojis = parsed.every((node) => ['emoji', 'twemoji'].includes(node.type) || (node.type === 'text' && node.content.trim().length === 0)); if (isOnlyEmojis) { // now check if there are less than or equal to 25 emojis const emojis = parsed.filter((node) => ['emoji', 'twemoji'].includes(node.type)); if (emojis.length <= 25) { context._internal = { largeEmojis: true, }; } } return react_1.default.createElement(MessageASTNodes, { nodes: parsed, context: context }); } // This function can probably be combined into the MessageSingleASTNode function function MessageASTNodes({ nodes, context, }) { if (Array.isArray(nodes)) { return (react_1.default.createElement(react_1.default.Fragment, null, nodes.map((node, i) => (react_1.default.createElement(MessageSingleASTNode, { node: node, context: context, key: i }))))); } else { return react_1.default.createElement(MessageSingleASTNode, { node: nodes, context: context }); } } function MessageSingleASTNode({ node, context }) { var _a, _b, _c, _d, _e, _f; if (!node) return null; const md = new markdown_it_1.default('zero') .enable(['heading', 'emphasis', 'link', 'autolink', 'strikethrough']) .use(markdown_it_discord_inline_1.discordInlinePlugin, context); const type = node.type; switch (type) { case 'text': { const html = md.render(node.content) .replace(/\|\|([^\|]+)\|\|/g, (...match) => `<discord-spoiler>${match[1]}</discord-spoiler>`) .replace(/\n(?:<p>)?\-\#(.+)\n/gm, (...match) => `<span class="subtext">${match[1]}</span>`) .replace(/\`\`\`\n?(.+)\n?\`\`\`/gm, (...match) => `<discord-code-block>${match[1]}</discord-code-block>`) .replace(/\`(.+)\`/g, (...match) => `<discord-inline-code>${match[1]}</discord-inline-code>`); return htmlToReact(html); } case 'blockQuote': if (context.type === RenderType.REPLY) { return react_1.default.createElement(MessageASTNodes, { nodes: node.content, context: context }); } return (react_1.default.createElement(discord_components_react_1.DiscordQuote, null, react_1.default.createElement(MessageASTNodes, { nodes: node.content, context: context }))); case 'br': case 'newline': if (context.type === RenderType.REPLY) return ' '; return react_1.default.createElement("br", null); case 'channel': { const id = node.id; // Use the resolved channel name from profiles if available const channel = (_b = (_a = context.profiles) === null || _a === void 0 ? void 0 : _a._channels) === null || _b === void 0 ? void 0 : _b[id]; const isThread = channel && [discord_js_1.ChannelType.PrivateThread, discord_js_1.ChannelType.PublicThread, discord_js_1.ChannelType.AnnouncementThread].includes(channel.type); const channelName = (channel === null || channel === void 0 ? void 0 : channel.name) || `${id}`; return (react_1.default.createElement(discord_components_react_1.DiscordMention, { type: isThread ? 'thread' : 'channel' }, `${channelName}`)); } case 'role': { const id = node.id; // Use the resolved role name from profiles if available const role = (_d = (_c = context.profiles) === null || _c === void 0 ? void 0 : _c._roles) === null || _d === void 0 ? void 0 : _d[id]; const roleName = (role === null || role === void 0 ? void 0 : role.name) || `${id}`; return (react_1.default.createElement(discord_components_react_1.DiscordMention, { type: "role" }, `${roleName}`)); } case 'user': { const id = node.id; // Use the resolved username from profiles if available, otherwise fallback to ID const profile = (_e = context.profiles) === null || _e === void 0 ? void 0 : _e[id]; const displayName = (profile === null || profile === void 0 ? void 0 : profile.author) || `User ${id}`; return react_1.default.createElement(discord_components_react_1.DiscordMention, { type: "user" }, `${displayName}`); } case 'here': case 'everyone': return (react_1.default.createElement(discord_components_react_1.DiscordMention, { type: 'role', highlight: true }, `@${type}`)); case 'codeBlock': if (context.type !== RenderType.REPLY) { return react_1.default.createElement(discord_components_react_1.DiscordCodeBlock, { language: node.lang, code: node.content }); } return react_1.default.createElement(discord_components_react_1.DiscordInlineCode, null, node.content); case 'inlineCode': return react_1.default.createElement(discord_components_react_1.DiscordInlineCode, null, node.content); case 'em': return (react_1.default.createElement(discord_components_react_1.DiscordItalic, null, react_1.default.createElement(MessageASTNodes, { nodes: node.content, context: context }))); case 'strong': return (react_1.default.createElement(discord_components_react_1.DiscordBold, null, react_1.default.createElement(MessageASTNodes, { nodes: node.content, context: context }))); case 'underline': return (react_1.default.createElement(discord_components_react_1.DiscordUnderlined, null, react_1.default.createElement(MessageASTNodes, { nodes: node.content, context: context }))); case 'strikethrough': return (react_1.default.createElement("s", null, react_1.default.createElement(MessageASTNodes, { nodes: node.content, context: context }))); case 'emoticon': return typeof node.content === 'string' ? (node.content) : (react_1.default.createElement(MessageASTNodes, { nodes: node.content, context: context })); case 'spoiler': return (react_1.default.createElement(discord_components_react_1.DiscordSpoiler, null, react_1.default.createElement(MessageASTNodes, { nodes: node.content, context: context }))); case 'emoji': case 'twemoji': return (react_1.default.createElement(discord_components_react_1.DiscordCustomEmoji, { name: node.name, url: (0, utils_1.parseDiscordEmoji)(node), embedEmoji: context.type === RenderType.EMBED, largeEmoji: (_f = context._internal) === null || _f === void 0 ? void 0 : _f.largeEmojis })); case 'timestamp': return react_1.default.createElement(discord_components_react_1.DiscordTime, { timestamp: parseInt(node.timestamp) * 1000, format: node.format }); default: { console.log(`Unknown node type: ${type}`, node); return typeof node.content === 'string' ? (node.content) : (react_1.default.createElement(MessageASTNodes, { nodes: node.content, context: context })); } } } function getChannelType(channelType) { switch (channelType) { case discord_js_1.ChannelType.GuildCategory: case discord_js_1.ChannelType.GuildAnnouncement: case discord_js_1.ChannelType.GuildText: return 'channel'; case discord_js_1.ChannelType.GuildVoice: case discord_js_1.ChannelType.GuildStageVoice: return 'voice'; case discord_js_1.ChannelType.PublicThread: case discord_js_1.ChannelType.PrivateThread: case discord_js_1.ChannelType.AnnouncementThread: return 'thread'; case discord_js_1.ChannelType.GuildForum: return 'forum'; default: return 'channel'; } } //# sourceMappingURL=content.js.map