From 8490e383acdb618f7fbd07f28683f18c6ea5f974 Mon Sep 17 00:00:00 2001 From: Satsuki Akiba Date: Wed, 4 Jun 2025 15:12:28 +0900 Subject: [PATCH] refactor: extract processFeedUrl function --- scripts/fetch_and_generate.ts | 183 +++++++++++++++++----------------- 1 file changed, 93 insertions(+), 90 deletions(-) diff --git a/scripts/fetch_and_generate.ts b/scripts/fetch_and_generate.ts index 85fffeb..e932b27 100644 --- a/scripts/fetch_and_generate.ts +++ b/scripts/fetch_and_generate.ts @@ -24,7 +24,6 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); async function main() { - const parser = new Parser(); const feedUrlsFile = import.meta.env["FEED_URLS_FILE"] ?? "feed_urls.txt"; const feedUrlsPath = path.resolve(__dirname, "..", feedUrlsFile); let feedUrls: string[]; @@ -41,101 +40,105 @@ async function main() { // フィードごとに処理 for (const url of feedUrls) { - const feed = await parser.parseURL(url); - - // フィードのカテゴリ分類 - const feedTitle = feed.title || url; - const category = await openAI_ClassifyFeed(feedTitle); - console.log(`フィード分類完了: ${feedTitle} - ${category}`); - - // 昨日の記事のみフィルタリング - const yesterday = new Date(); - yesterday.setDate(yesterday.getDate() - 1); - - const yesterdayItems = feed.items.filter((item) => { - const pub = new Date(item.pubDate || ""); - return ( - pub.getFullYear() === yesterday.getFullYear() && - pub.getMonth() === yesterday.getMonth() && - pub.getDate() === yesterday.getDate() - ); - }); - - if (yesterdayItems.length === 0) { - console.log(`昨日の記事が見つかりません: ${feedTitle}`); - continue; + try { + await processFeedUrl(url); + } finally { + await updatePodcastRSS(); } - - // ポッドキャスト原稿生成 - console.log(`ポッドキャスト原稿生成開始: ${feedTitle}`); - const validItems = yesterdayItems.filter((item): item is FeedItem => { - return !!item.title && !!item.link; - }); - const podcastContent = await openAI_GeneratePodcastContent( - feedTitle, - validItems, - ); - - // トピックごとの統合音声生成 - const feedUrlHash = crypto.createHash("md5").update(url).digest("hex"); - const categoryHash = crypto - .createHash("md5") - .update(category) - .digest("hex"); - const uniqueId = `${feedUrlHash}-${categoryHash}`; - - const audioFilePath = await generateTTS(uniqueId, podcastContent); - console.log(`音声ファイル生成完了: ${audioFilePath}`); - - // エピソードとして保存(各フィードにつき1つの統合エピソード) - const firstItem = yesterdayItems[0]; - if (!firstItem) { - console.warn("アイテムが空です"); - continue; - } - const pub = new Date(firstItem.pubDate || ""); - - await saveEpisode({ - id: uniqueId, - title: `${category}: ${feedTitle}`, - pubDate: pub.toISOString(), - audioPath: audioFilePath, - sourceLink: url, - }); - - console.log(`エピソード保存完了: ${category} - ${feedTitle}`); - - // 個別記事の処理記録 - for (const item of yesterdayItems) { - const itemId = item["id"] as string | undefined; - const fallbackId = item.link || item.title || JSON.stringify(item); - const finalItemId = - itemId && typeof itemId === "string" && itemId.trim() !== "" - ? itemId - : `fallback-${Buffer.from(fallbackId).toString("base64")}`; - - if (!finalItemId || finalItemId.trim() === "") { - console.warn(`フィードアイテムのIDを生成できませんでした`, { - feedUrl: url, - itemTitle: item.title, - itemLink: item.link, - }); - continue; - } - - const already = await markAsProcessed(url, finalItemId); - if (already) { - console.log(`既に処理済み: ${finalItemId}`); - continue; - } - } - - await updatePodcastRSS(); } console.log("処理完了:", new Date().toISOString()); } +const processFeedUrl = async (url: string) => { + const parser = new Parser(); + const feed = await parser.parseURL(url); + + // フィードのカテゴリ分類 + const feedTitle = feed.title || url; + const category = await openAI_ClassifyFeed(feedTitle); + console.log(`フィード分類完了: ${feedTitle} - ${category}`); + + // 昨日の記事のみフィルタリング + const yesterday = new Date(); + yesterday.setDate(yesterday.getDate() - 1); + + const yesterdayItems = feed.items.filter((item) => { + const pub = new Date(item.pubDate || ""); + return ( + pub.getFullYear() === yesterday.getFullYear() && + pub.getMonth() === yesterday.getMonth() && + pub.getDate() === yesterday.getDate() + ); + }); + + if (yesterdayItems.length === 0) { + console.log(`昨日の記事が見つかりません: ${feedTitle}`); + continue; + } + + // ポッドキャスト原稿生成 + console.log(`ポッドキャスト原稿生成開始: ${feedTitle}`); + const validItems = yesterdayItems.filter((item): item is FeedItem => { + return !!item.title && !!item.link; + }); + const podcastContent = await openAI_GeneratePodcastContent( + feedTitle, + validItems, + ); + + // トピックごとの統合音声生成 + const feedUrlHash = crypto.createHash("md5").update(url).digest("hex"); + const categoryHash = crypto.createHash("md5").update(category).digest("hex"); + const uniqueId = `${feedUrlHash}-${categoryHash}`; + + const audioFilePath = await generateTTS(uniqueId, podcastContent); + console.log(`音声ファイル生成完了: ${audioFilePath}`); + + // エピソードとして保存(各フィードにつき1つの統合エピソード) + const firstItem = yesterdayItems[0]; + if (!firstItem) { + console.warn("アイテムが空です"); + continue; + } + const pub = new Date(firstItem.pubDate || ""); + + await saveEpisode({ + id: uniqueId, + title: `${category}: ${feedTitle}`, + pubDate: pub.toISOString(), + audioPath: audioFilePath, + sourceLink: url, + }); + + console.log(`エピソード保存完了: ${category} - ${feedTitle}`); + + // 個別記事の処理記録 + for (const item of yesterdayItems) { + const itemId = item["id"] as string | undefined; + const fallbackId = item.link || item.title || JSON.stringify(item); + const finalItemId = + itemId && typeof itemId === "string" && itemId.trim() !== "" + ? itemId + : `fallback-${Buffer.from(fallbackId).toString("base64")}`; + + if (!finalItemId || finalItemId.trim() === "") { + console.warn(`フィードアイテムのIDを生成できませんでした`, { + feedUrl: url, + itemTitle: item.title, + itemLink: item.link, + }); + continue; + } + + const already = await markAsProcessed(url, finalItemId); + if (already) { + console.log(`既に処理済み: ${finalItemId}`); + continue; + } + } +}; + main().catch((err) => { console.error("エラー発生:", err); process.exit(1);