import { promises as fs } from "fs";
import { dirname } from "path";
import { fetchEpisodesWithFeedInfo } from "./database.js";
import path from "node:path";
import fsSync from "node:fs";
import { config } from "./config.js";
function escapeXml(text: string): string {
return text
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
function createItemXml(episode: any): string {
const fileUrl = `${config.podcast.baseUrl}/podcast_audio/${path.basename(episode.audioPath)}`;
const pubDate = new Date(episode.createdAt).toUTCString();
let fileSize = 0;
try {
const audioPath = path.join(
config.paths.podcastAudioDir,
episode.audioPath,
);
if (fsSync.existsSync(audioPath)) {
fileSize = fsSync.statSync(audioPath).size;
}
} catch (error) {
console.warn(`Could not get file size for ${episode.audioPath}:`, error);
}
// Build enhanced description with feed and article info
let description = episode.title;
if (episode.feedTitle || episode.articleTitle || episode.articleLink) {
description += "\n\n";
if (episode.feedTitle) {
description += `フィード: ${episode.feedTitle}\n`;
}
if (episode.articleTitle && episode.articleTitle !== episode.title) {
description += `元記事: ${episode.articleTitle}\n`;
}
if (episode.articlePubDate) {
description += `記事公開日: ${new Date(episode.articlePubDate).toLocaleString("ja-JP")}\n`;
}
if (episode.articleLink) {
description += `元記事URL: ${episode.articleLink}`;
}
}
return `
-
${escapeXml(config.podcast.author)}
${escapeXml(config.podcast.categories)}
${config.podcast.language}
${config.podcast.ttl}
${escapeXml(fileUrl)}
${pubDate}
${episode.articleLink ? `${escapeXml(episode.articleLink)}` : ""}
`;
}
export async function updatePodcastRSS(): Promise {
try {
// Use episodes with feed info for enhanced descriptions
const episodesWithFeedInfo = await fetchEpisodesWithFeedInfo();
// Filter episodes to only include those with valid audio files
const validEpisodes = episodesWithFeedInfo.filter((episode) => {
try {
const audioPath = path.join(
config.paths.podcastAudioDir,
episode.audioPath,
);
return fsSync.existsSync(audioPath);
} catch (error) {
console.warn(`Audio file not found for episode: ${episode.title}`);
return false;
}
});
console.log(
`Found ${episodesWithFeedInfo.length} episodes, ${validEpisodes.length} with valid audio files`,
);
const lastBuildDate = new Date().toUTCString();
const itemsXml = validEpisodes.map(createItemXml).join("\n");
const outputPath = path.join(config.paths.publicDir, "podcast.xml");
// Create RSS XML content
const rssXml = `
${escapeXml(config.podcast.title)}
${escapeXml(config.podcast.link)}
${config.podcast.language}
${lastBuildDate}
${config.podcast.ttl}
${escapeXml(config.podcast.author)}
${escapeXml(config.podcast.categories)}${itemsXml}
`;
// Ensure directory exists
await fs.mkdir(dirname(outputPath), { recursive: true });
await fs.writeFile(outputPath, rssXml);
console.log(
`RSS feed updated with ${validEpisodes.length} episodes (audio files verified)`,
);
} catch (error) {
console.error("Error updating podcast RSS:", error);
throw error;
}
}