import rss from "@astrojs/rss";
import { getCollection } from "astro:content";
import { siteConfig } from "../config";
import { shouldShowPost, sortPostsByDate, getPostSlug } from "../utils/markdown";
import { optimizePostImagePath } from "../utils/images";
// Helper function to extract image path from Obsidian bracket syntax
function extractImagePath(image: string): string {
if (!image || typeof image !== "string") return "";
// Handle Obsidian bracket syntax: [[path/to/image.jpg]] (unquoted)
if (image.startsWith("[[") && image.endsWith("]]")) {
return image.slice(2, -2); // Remove [[ and ]]
}
// Handle quoted Obsidian bracket syntax: "[[path/to/image.jpg]]"
if (image.startsWith('"[[') && image.endsWith(']]"')) {
return image.slice(3, -3); // Remove "[[ and ]]"
}
// Return as-is for regular paths
return image;
}
function getMimeTypeFromPath(path: string): string {
const ext = path.split(".").pop()?.toLowerCase();
switch (ext) {
case "jpg":
case "jpeg":
return "image/jpeg";
case "png":
return "image/png";
case "gif":
return "image/gif";
case "webp":
return "image/webp";
default:
// Default to WebP since sync-images.js converts images to WebP
return "image/webp";
}
}
// Helper function to normalize siteUrl - ensure it ends with a single slash
function normalizeSiteUrl(url: string): string {
return url.replace(/\/+$/, '') + '/';
}
export async function GET() {
// Get all posts
const posts = await getCollection("posts");
// Filter and sort posts based on environment (in dev, show all including drafts)
const isDev = import.meta.env.DEV;
const visiblePosts = posts.filter(
(post) => shouldShowPost(post, isDev)
);
const sortedPosts = sortPostsByDate(visiblePosts);
const siteUrl = normalizeSiteUrl(import.meta.env.SITE || siteConfig.site);
return rss({
title: siteConfig.title,
description: siteConfig.description,
site: siteUrl,
items: sortedPosts.map((post) => {
const postUrl = `${siteUrl}posts/${getPostSlug(post)}/`;
return {
title: post.data.title,
description: post.data.description || "",
pubDate: post.data.date,
link: postUrl,
categories: post.data.tags || [],
author: siteConfig.author,
// Include image if available and marked for OG
enclosure:
post.data.image && post.data.imageOG
? {
url: (() => {
const imagePath = extractImagePath(post.data.image);
if (typeof imagePath === "string" && imagePath.startsWith("http")) {
return imagePath;
}
// Use optimizePostImagePath to handle folder-based posts and WebP conversion
const optimizedPath = optimizePostImagePath(imagePath, (post as any).id, (post as any).id);
// optimizedPath always starts with /, so remove it since siteUrl already ends with /
return `${siteUrl}${optimizedPath.startsWith('/') ? optimizedPath.slice(1) : optimizedPath}`;
})(),
type: getMimeTypeFromPath(extractImagePath(post.data.image)),
length: 0, // Length is optional
}
: undefined,
customData: [
post.data.targetKeyword &&
`${post.data.targetKeyword}`,
post.data.image &&
`${(() => {
const imagePath = extractImagePath(post.data.image);
if (typeof imagePath === "string" && imagePath.startsWith("http")) {
return imagePath;
}
// Use optimizePostImagePath to handle folder-based posts and WebP conversion
const optimizedPath = optimizePostImagePath(imagePath, (post as any).id, (post as any).id);
// optimizedPath always starts with /, so remove it since siteUrl already ends with /
return `${siteUrl}${optimizedPath.startsWith('/') ? optimizedPath.slice(1) : optimizedPath}`;
})()}`,
]
.filter(Boolean)
.join(""),
};
}),
// RSS 2.0 extensions
customData: `
${siteConfig.language}
Copyright © ${new Date().getFullYear()} ${
siteConfig.author
}
${siteConfig.author}
${siteConfig.author}
${new Date().toUTCString()}
Astro RSS
https://www.rssboard.org/rss-specification
60
`,
xmlns: {
atom: "http://www.w3.org/2005/Atom",
content: "http://purl.org/rss/1.0/modules/content/",
dc: "http://purl.org/dc/elements/1.1/",
},
});
}