119 lines
4.1 KiB
TypeScript
119 lines
4.1 KiB
TypeScript
import { promises as fs } from "fs";
|
|
import { join, dirname } from "path";
|
|
import { Episode, fetchAllEpisodes } from "./database";
|
|
import path from "node:path";
|
|
import fsSync from "node:fs";
|
|
|
|
export async function updatePodcastRSS() {
|
|
const episodes: Episode[] = await fetchAllEpisodes();
|
|
|
|
const channelTitle =
|
|
import.meta.env["PODCAST_TITLE"] ?? "自動生成ポッドキャスト";
|
|
const channelLink =
|
|
import.meta.env["PODCAST_LINK"] ?? "https://your-domain.com/podcast";
|
|
const channelDescription =
|
|
import.meta.env["PODCAST_DESCRIPTION"] ??
|
|
"RSSフィードから自動生成された音声ポッドキャスト";
|
|
const channelLanguage = import.meta.env["PODCAST_LANGUAGE"] ?? "ja";
|
|
const channelAuthor = import.meta.env["PODCAST_AUTHOR"] ?? "管理者";
|
|
const channelCategories =
|
|
import.meta.env["PODCAST_CATEGORIES"] ?? "Technology";
|
|
const channelTTL = import.meta.env["PODCAST_TTL"] ?? "60";
|
|
const lastBuildDate = new Date().toUTCString();
|
|
const baseUrl =
|
|
import.meta.env["PODCAST_BASE_URL"] ?? "https://your-domain.com";
|
|
|
|
let itemsXml = "";
|
|
for (const ep of episodes) {
|
|
const fileUrl = `${baseUrl}/podcast_audio/${path.basename(ep.audioPath)}`;
|
|
const pubDate = new Date(ep.pubDate).toUTCString();
|
|
const fileSize = fsSync.statSync(
|
|
path.join(import.meta.dir, "..", "public/podcast_audio", ep.audioPath),
|
|
).size;
|
|
itemsXml += `
|
|
<item>
|
|
<title><![CDATA[${ep.title}]]></title>
|
|
<description><![CDATA[${ep.title.replace(/\]\]>/g, "]]>").replace(/&/g, "&").replace(/\]\]>/g, "]]>")}]]></description>
|
|
<author>${channelAuthor}</author>
|
|
<category>${channelCategories}</category>
|
|
<language>${channelLanguage}</language>
|
|
<ttl>${channelTTL}</ttl>
|
|
<enclosure url="${fileUrl}" length="${fileSize}" type="audio/mpeg" />
|
|
<guid>${fileUrl}</guid>
|
|
<pubDate>${pubDate}</pubDate>
|
|
</item>
|
|
`;
|
|
}
|
|
|
|
const outputPath = join(__dirname, "../public/podcast.xml");
|
|
|
|
// 既存のRSSファイルの読み込み
|
|
let existingXml = "";
|
|
try {
|
|
existingXml = await fs.readFile(outputPath, "utf-8");
|
|
} catch (err) {
|
|
// ファイルが存在しない場合は新規作成
|
|
console.log("既存のpodcast.xmlが見つかりません。新規作成します。");
|
|
}
|
|
|
|
if (existingXml) {
|
|
// 既存のitem部分を抽出
|
|
const existingItemsMatch = existingXml.match(
|
|
/<channel>([\s\S]*?)<\/channel>/,
|
|
);
|
|
if (existingItemsMatch) {
|
|
const existingItems = existingItemsMatch[1];
|
|
const newItemStartIndex = existingItems!.lastIndexOf("<item>");
|
|
|
|
// 新しいitemを追加
|
|
const updatedItems = existingItems + itemsXml;
|
|
|
|
// lastBuildDateを更新
|
|
const updatedXml = existingXml.replace(
|
|
/<lastBuildDate>.*?<\/lastBuildDate>/,
|
|
`<lastBuildDate>${lastBuildDate}</lastBuildDate>`,
|
|
);
|
|
|
|
// items部分を置き換え
|
|
const finalXml = updatedXml.replace(
|
|
/<channel>[\s\S]*?<\/channel>/,
|
|
`<channel>${updatedItems}</channel>`,
|
|
);
|
|
|
|
// ファイルに書き込み
|
|
await fs.writeFile(outputPath, finalXml.trim());
|
|
} else {
|
|
// 不正なフォーマットの場合は新規作成
|
|
const rssXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<rss version="2.0">
|
|
<channel>
|
|
<title>${channelTitle}</title>
|
|
<link>${channelLink}</link>
|
|
<description><![CDATA[${channelDescription}]]></description>
|
|
<lastBuildDate>${lastBuildDate}</lastBuildDate>
|
|
${itemsXml}
|
|
</channel>
|
|
</rss>
|
|
`;
|
|
await fs.writeFile(outputPath, rssXml.trim());
|
|
}
|
|
} else {
|
|
// 新規作成
|
|
const rssXml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<rss version="2.0">
|
|
<channel>
|
|
<title>${channelTitle}</title>
|
|
<link>${channelLink}</link>
|
|
<description><![CDATA[${channelDescription}]]></description>
|
|
<lastBuildDate>${lastBuildDate}</lastBuildDate>
|
|
${itemsXml}
|
|
</channel>
|
|
</rss>
|
|
`;
|
|
|
|
// Ensure directory exists
|
|
await fs.mkdir(dirname(outputPath), { recursive: true });
|
|
await fs.writeFile(outputPath, rssXml.trim());
|
|
}
|
|
}
|