From 7acd3667514dd74f85dad8114c8917e81eadf093 Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Thu, 24 Jul 2025 04:13:17 -0700 Subject: [PATCH] feat: add YouTube and media embed support to RSS feed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Support video nodes with YouTube URL detection - Support urlEmbed nodes for rich media previews - Convert YouTube URLs to embedded iframes in RSS - Add Twitter/X embed preview support - Support generic iframe embeds - Provide fallback links for better RSS reader compatibility 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/routes/rss/+server.ts | 77 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/src/routes/rss/+server.ts b/src/routes/rss/+server.ts index 43b360b..902f2ed 100644 --- a/src/routes/rss/+server.ts +++ b/src/routes/rss/+server.ts @@ -58,6 +58,83 @@ function convertContentToHTML(content: any): string { const src = node.attrs?.src || '' const alt = node.attrs?.alt || '' return src ? `
${escapeXML(alt)}
` : '' + case 'video': + const videoSrc = node.attrs?.src || '' + if (!videoSrc) return '' + // Check if it's a YouTube URL + const youtubeMatch = videoSrc.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/) + if (youtubeMatch) { + const videoId = youtubeMatch[1] + return `` + } + // For other video sources, include a video tag + return `` + case 'urlEmbed': + const embedUrl = node.attrs?.url || '' + const embedTitle = node.attrs?.title || '' + const embedDescription = node.attrs?.description || '' + const embedImage = node.attrs?.image || '' + const embedSiteName = node.attrs?.siteName || '' + + if (!embedUrl) return '' + + // Check if it's a YouTube URL + const ytMatch = embedUrl.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/) + if (ytMatch) { + const videoId = ytMatch[1] + let html = '
' + html += `` + if (embedTitle) { + html += `

${escapeXML(embedTitle)}

` + } + if (embedDescription) { + html += `

${escapeXML(embedDescription)}

` + } + html += '
' + return html + } + + // Check if it's a Twitter/X URL + const twitterMatch = embedUrl.match(/(?:twitter\.com|x\.com)\/\w+\/status\/(\d+)/) + if (twitterMatch) { + // For Twitter/X, we can't embed the actual tweet in RSS, but we can provide a nice preview + let html = '' + return html + } + + // For other URL embeds, create a rich preview + let html = '
' + if (embedImage) { + html += `${escapeXML(embedTitle || '')}` + } + html += '
' + if (embedSiteName) { + html += `${escapeXML(embedSiteName)}` + } + if (embedTitle) { + html += `

${escapeXML(embedTitle)}

` + } + if (embedDescription) { + html += `

${escapeXML(embedDescription)}

` + } + html += '
' + return html + case 'iframe': + const iframeSrc = node.attrs?.src || '' + const iframeWidth = node.attrs?.width || 560 + const iframeHeight = node.attrs?.height || 315 + if (!iframeSrc) return '' + return `` default: const defaultText = extractTextFromNode(node) return defaultText ? `

${defaultText}

` : ''