refactor: extract processFeedUrl function
This commit is contained in:
		@@ -24,7 +24,6 @@ const __filename = fileURLToPath(import.meta.url);
 | 
				
			|||||||
const __dirname = path.dirname(__filename);
 | 
					const __dirname = path.dirname(__filename);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function main() {
 | 
					async function main() {
 | 
				
			||||||
  const parser = new Parser<FeedItem>();
 | 
					 | 
				
			||||||
  const feedUrlsFile = import.meta.env["FEED_URLS_FILE"] ?? "feed_urls.txt";
 | 
					  const feedUrlsFile = import.meta.env["FEED_URLS_FILE"] ?? "feed_urls.txt";
 | 
				
			||||||
  const feedUrlsPath = path.resolve(__dirname, "..", feedUrlsFile);
 | 
					  const feedUrlsPath = path.resolve(__dirname, "..", feedUrlsFile);
 | 
				
			||||||
  let feedUrls: string[];
 | 
					  let feedUrls: string[];
 | 
				
			||||||
@@ -41,101 +40,105 @@ async function main() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  // フィードごとに処理
 | 
					  // フィードごとに処理
 | 
				
			||||||
  for (const url of feedUrls) {
 | 
					  for (const url of feedUrls) {
 | 
				
			||||||
    const feed = await parser.parseURL(url);
 | 
					    try {
 | 
				
			||||||
 | 
					      await processFeedUrl(url);
 | 
				
			||||||
    // フィードのカテゴリ分類
 | 
					    } finally {
 | 
				
			||||||
    const feedTitle = feed.title || url;
 | 
					      await updatePodcastRSS();
 | 
				
			||||||
    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;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await updatePodcastRSS();
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  console.log("処理完了:", new Date().toISOString());
 | 
					  console.log("処理完了:", new Date().toISOString());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const processFeedUrl = async (url: string) => {
 | 
				
			||||||
 | 
					  const parser = new Parser<FeedItem>();
 | 
				
			||||||
 | 
					  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) => {
 | 
					main().catch((err) => {
 | 
				
			||||||
  console.error("エラー発生:", err);
 | 
					  console.error("エラー発生:", err);
 | 
				
			||||||
  process.exit(1);
 | 
					  process.exit(1);
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user