mirror of
https://github.com/Ad-closeNN/blog-fuwari.git
synced 2026-05-31 02:20:05 -04:00
使用新版 RSS 构建器
https://billyle.dev/posts/adding-rss-feed-content-and-fixing-markdown-image-paths-in-astro https://github.com/afoim/fuwari/blob/main/src/pages/rss.xml.ts
This commit is contained in:
+74
-33
@@ -1,43 +1,84 @@
|
||||
import rss from "@astrojs/rss";
|
||||
import { getSortedPosts } from "@utils/content-utils";
|
||||
import { url } from "@utils/url-utils";
|
||||
import type { APIContext } from "astro";
|
||||
import MarkdownIt from "markdown-it";
|
||||
import sanitizeHtml from "sanitize-html";
|
||||
import { siteConfig } from "@/config";
|
||||
import rss from '@astrojs/rss';
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
import MarkdownIt from 'markdown-it';
|
||||
import { getCollection } from 'astro:content';
|
||||
import { siteConfig } from '@/config';
|
||||
import { parse as htmlParser } from 'node-html-parser';
|
||||
import { getImage } from 'astro:assets';
|
||||
import type { APIContext, ImageMetadata } from 'astro';
|
||||
import type { RSSFeedItem } from '@astrojs/rss';
|
||||
import { getSortedPosts } from '@/utils/content-utils';
|
||||
|
||||
const parser = new MarkdownIt();
|
||||
const markdownParser = new MarkdownIt();
|
||||
|
||||
function stripInvalidXmlChars(str: string): string {
|
||||
return str.replace(
|
||||
// biome-ignore lint/suspicious/noControlCharactersInRegex: https://www.w3.org/TR/xml/#charsets
|
||||
/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\uFDD0-\uFDEF\uFFFE\uFFFF]/g,
|
||||
"",
|
||||
);
|
||||
}
|
||||
// get dynamic import of images as a map collection
|
||||
const imagesGlob = import.meta.glob<{ default: ImageMetadata }>(
|
||||
'/src/content/**/*.{jpeg,jpg,png,gif,webp}', // include posts and assets
|
||||
);
|
||||
|
||||
export async function GET(context: APIContext) {
|
||||
const blog = await getSortedPosts();
|
||||
if (!context.site) {
|
||||
throw Error('site not set');
|
||||
}
|
||||
|
||||
// Use the same ordering as site listing (pinned first, then by published desc)
|
||||
const posts = await getSortedPosts();
|
||||
const feed: RSSFeedItem[] = [];
|
||||
|
||||
for (const post of posts) {
|
||||
// convert markdown to html string
|
||||
const body = markdownParser.render(post.body);
|
||||
// convert html string to DOM-like structure
|
||||
const html = htmlParser.parse(body);
|
||||
// hold all img tags in variable images
|
||||
const images = html.querySelectorAll('img');
|
||||
|
||||
for (const img of images) {
|
||||
const src = img.getAttribute('src');
|
||||
if (!src) continue;
|
||||
|
||||
// Handle content-relative images and convert them to built _astro paths
|
||||
if (src.startsWith('./') || src.startsWith('../')) {
|
||||
let importPath: string | null = null;
|
||||
|
||||
if (src.startsWith('./')) {
|
||||
// Path relative to the post file directory
|
||||
const prefixRemoved = src.slice(2);
|
||||
importPath = `/src/content/posts/${prefixRemoved}`;
|
||||
} else {
|
||||
// Path like ../assets/images/xxx -> relative to /src/content/
|
||||
const cleaned = src.replace(/^\.\.\//, '');
|
||||
importPath = `/src/content/${cleaned}`;
|
||||
}
|
||||
|
||||
const imageMod = await imagesGlob[importPath]?.()?.then((res) => res.default);
|
||||
if (imageMod) {
|
||||
const optimizedImg = await getImage({ src: imageMod });
|
||||
img.setAttribute('src', new URL(optimizedImg.src, context.site).href);
|
||||
}
|
||||
} else if (src.startsWith('/')) {
|
||||
// images starting with `/` are in public dir
|
||||
img.setAttribute('src', new URL(src, context.site).href);
|
||||
}
|
||||
}
|
||||
|
||||
feed.push({
|
||||
title: post.data.title,
|
||||
description: post.data.description,
|
||||
pubDate: post.data.published,
|
||||
link: `/posts/${post.slug}/`,
|
||||
// sanitize the new html string with corrected image paths
|
||||
content: sanitizeHtml(html.toString(), {
|
||||
allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
return rss({
|
||||
title: siteConfig.title,
|
||||
description: siteConfig.subtitle || "No description",
|
||||
site: context.site ?? "https://fuwari.vercel.app",
|
||||
items: blog.map((post) => {
|
||||
const content =
|
||||
typeof post.body === "string" ? post.body : String(post.body || "");
|
||||
const cleanedContent = stripInvalidXmlChars(content);
|
||||
return {
|
||||
title: post.data.title,
|
||||
pubDate: post.data.published,
|
||||
description: post.data.description || "",
|
||||
link: url(`/posts/${post.slug}/`),
|
||||
content: sanitizeHtml(parser.render(cleanedContent), {
|
||||
allowedTags: sanitizeHtml.defaults.allowedTags.concat(["img"]),
|
||||
}),
|
||||
};
|
||||
}),
|
||||
|
||||
description: siteConfig.subtitle || 'No description',
|
||||
site: context.site,
|
||||
items: feed,
|
||||
// 准备迎接我的 Folo 之验证吧!——鲁迅 not 达摩(bushi
|
||||
customData: `<language>${siteConfig.lang}</language><follow_challenge><feedId>177350379949135872</feedId><userId>83370505718413312</userId></follow_challenge>`,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user