Amebaブログ機能 データ設計書
最終更新: 2026-08-12
1. 概要
officialSns に Ameba Blog(ameblo.jp)の URL を active: true で登録しているメンバーの投稿タイトルを、Ameba の RSS フィードから取得する。Instagram・TikTok とは異なり Ameba 専用の Blob ファイルや DB テーブルは持たず、既存のメンバー近況同期バッチ(members.json)にそのまま格納する方式をとる。
2. データ格納先
| 格納先 | 概要 | 更新タイミング |
|---|---|---|
members.json の Member.latestStatus |
直近の Ameba ブログ投稿(タイトルのみ) | sync-members ワークフロー実行時(毎日 JST 20:30) |
members.json の Member.statusHistory |
過去の Ameba ブログ投稿履歴(タイトルのみ、古い順に蓄積) | 同上 |
ameba/sync-status.json |
Topページ新着投稿セクションの新着ウィンドウ判定に使う同期ステータス(詳細は §8) | 同上 |
members.jsonは Ameba 専用の Blob ファイル・DB テーブルを持たない。取得・書き込みの詳細フローはバッチ設計書「メンバー近況同期バッチ」§「Ameba RSS 解析」を参照。ameba/sync-status.jsonは Instagram・TikTok 等と同様の Ameba 専用 Blob ファイル(§8 参照)。
3. 型定義
型定義ファイル: types/member.ts
Ameba 専用の型は存在せず、他 SNS・Web 検索由来の近況と共通の StatusEntry 型を使用する。
type StatusEntry = {
content: string; // Ameba の場合: 投稿タイトルのみ(本文は著作権保護のため保存しない、#1328)
lastUpdated: string; // RSS の pubDate を JST の YYYY-MM-DD に変換した値(#1171)
sources: string[]; // 個別投稿URL(https://ameblo.jp/{amebaId}/entry-{数字}.html)を1件のみ格納
capturedAt?: string; // このエントリを sync-status.ts が蓄積した日時(ISO 8601、#1408)
};
Member.officialSns(SnsEntry[])に Ameba Blog の URL(https://ameblo.jp/{amebaId}/)を active: true で登録することで、当該メンバーが Ameba RSS 同期の対象になる。
4. RSS取得仕様
officialSns に active な Ameba Blog エントリを持つメンバーは、Exa 検索を使わず Ameba の RSS フィードから直接近況を取得する。実装は scripts/workflow/sync-status.ts の fetchAmebaRss・buildAmebaCache・parseAmebaRss・parseAmebaRssItems・findMemberAmebaItem・findAllNewMemberAmebaItems・resolveAmebaGroupBlogAuthorName。
- エンドポイント:
https://rssblog.ameba.jp/{amebaId}/rss20.xml - RSS 2.0 の
<item>からtitle/description/link/pubDateを正規表現(extractRssField)で抽出するdescriptionは CDATA(<![CDATA[...]]>)で囲まれているが、タグと<![CDATA[/]]>の間に改行を挟む構造になっている。抽出用の正規表現はこの改行を許容する必要がある(許容していないと素朴なタグ除去へのフォールバックが発生し、本文が壊れた形で漏れ出すバグになる。#1328 で修正)
- content にはタイトルのみを格納する(著作権保護のため、投稿本文の抽出・保存は行わない。#1328)
description(本文)は HTML タグ除去のうえ、グループブログでの投稿者判定(メンバー名が本文に含まれるか)にのみ使用し、出力には含めない
- グループブログ(複数メンバー共有ブログ)は
titleにメンバー名を含む投稿を優先する。タイトルに一致しない場合、グループブログでは本文(description)一致によるフォールバックを行わない(isGroupBlogフラグで判定)。あるメンバーが自分の投稿内で別メンバーに言及しただけで、言及されたメンバー側にもその投稿が誤帰属してしまうため(#1328)。個人ブログ(isGroupBlog: false)は誤帰属のリスクがないため、本文一致・先頭アイテムへのフォールバックを許容する(#679, #789, #1328) - タイトルに複数の共有メンバー名が含まれる投稿(例: 「井上春華にお土産渡してみた弓桁朱琴」)は、Amebaの運用慣習として投稿者本人の氏名がタイトル末尾に付記されるため、共有メンバー名の候補のうちタイトル内で最も末尾に近い位置に出現する名前を投稿者と判定する(
resolveAmebaGroupBlogAuthorName、#1480)。この判定は日次バッチ(buildAmebaCache)と、既存データの誤帰属を解消する手動パッチ(scripts/patch/patch-ameba-misattributed-status.ts)の両方で共有する - sync 実行後に投稿された記事が翌日 sync 時に新着投稿の陰で取りこぼされないよう、
statusHistoryの既知 URL を除いた全新着を古い順に蓄積する(findAllNewMemberAmebaItems、#1167)- 古い投稿を蓄積するループの前後どちらでも、直前に蓄積した投稿と投稿URLが同一の場合は
shouldSkipAccumulationで重複蓄積をスキップする(ループ後の最新投稿の蓄積も対称にチェックする。#1329)。タイトルではなくURLで判定する。タイトルが定型化されているブログ(例:「Coordinate by WEAR」)では、URL・投稿日が異なる新規投稿でもタイトルだけが一致し誤って重複扱いされてしまうため(#1417)
- 古い投稿を蓄積するループの前後どちらでも、直前に蓄積した投稿と投稿URLが同一の場合は
pubDateは RFC 2822 形式(例:Thu, 19 Jun 2026 13:00:00 +0900)。JST のYYYY-MM-DDに変換しlastUpdatedに反映する(#1171)- 同一 Ameba ブログを共有する複数メンバーがいる場合、RSS 取得は1回のみ(
buildAmebaCache、#789) - RSS フィードの返却件数は Ameba 側の仕様により日によって変動することがある(例: 10件〜30件)。件数が少ない日に投稿が表示範囲から外れると新着検出の機会を失い、取りこぼしのリスクがある。早期発見のため、取得件数が30件未満(
AMEBA_RSS_COUNT_WARNING_THRESHOLD)の場合は警告ログを出力する(恒久対策ではなく検知のみ。#1418)
5. ユーティリティ関数
実装ファイル: lib/sns.ts
| 関数 | 用途 |
|---|---|
extractAmebaId(url) |
ameblo.jp URL から amebaId を抽出(Ameba URL でない場合は null) |
isAmebaRssEntryUrl(url) |
個別投稿URL(https://ameblo.jp/{id}/entry-{数字}.html)形式かどうかを判定 |
extractAmebaTitle(content) |
タイトルと本文が \n\n で連結された旧形式の content からタイトル部分のみを抽出(データ移行用) |
findSupportedBlogUrl(sources) |
sources 配列から Ameba URL を1つ探索(表示側で使用) |
6. 環境変数
Ameba RSS の取得は公開フィードへの直接アクセスのため、Ameba 専用の API キー等は不要。メンバー近況同期バッチの共通環境変数(BLOB_READ_WRITE_TOKEN 等)を流用する。
7. 画面設計(UI)
7-1. Amebaブログ一覧画面(/ameba)
概要: 公式 Ameba ブログを登録している全メンバーへのリンクを一覧表示するハブ画面(実装: app/ameba/page.tsx)。
| 要素 | 内容 |
|---|---|
| ナビゲーション | 「← ホームへ戻る」リンク(/ へ) |
| タイトル | 「Ameba」 |
| 説明文 | 「メンバーの Ameba ブログ リンク集」 |
| メンバー一覧セクション | SectionLabel(色 #00C000、ラベル「メンバー一覧」) |
| 対象メンバー | officialSns に extractAmebaId で抽出可能な URL を持つ全メンバー(active フラグの判定は行わない) |
| メンバー表示順 | sortMembers で現役→OG順に並び替えて表示する(#1491) |
| データなし表示 | 対象メンバーが0件の場合「ブログデータが見つかりませんでした。」の glass カードを表示 |
一覧アイテムの表示内容:
| 項目 | 内容 |
|---|---|
| メンバー名 | クリックで /members/{member.id} へ遷移 |
| ブログリンク | ameblo.jp/{amebaId} 表記。クリックで officialSns に登録された Ameba ブログ URL を新しいタブで開く |
Topページの新着投稿セクション内「Ameba」ブロック(
components/NewPostsSection.tsx)は本画面とは別実装で、SectionLabelの色に#2dad21を使用しており本画面(#00C000)とは異なる。表示対象も「新着ウィンドウ内に投稿があるメンバーのみ」(§8)に限定される点が本画面(登録済み全メンバー)と異なる。詳細は 画面設計書 の Topページ節「新着投稿セクション」を参照。
8. 新着表示の判定基準(Topページ新着投稿セクション)
Topページの新着投稿セクション内「Ameba」ブロックは、メール通知(デイリーダイジェスト、batch-design.mdx W14)の新着件数と表示件数が食い違わないよう、「新着ウィンドウ」内の投稿を全件表示する方式を採る(#1387)。
8-1. 型定義
型定義ファイル: types/ameba.ts
type AmebaSyncStatus = {
lastSyncedAt: string; // 直近の実行完了時刻(手動実行含む)
lastAutoSyncedAt: string; // 直近の「定期実行」の完了時刻
previousAutoSyncedAt: string; // その1つ前の「定期実行」の完了時刻
};
Blob格納先: ameba/sync-status.json(members.json と同じ Blob ストア上の専用ファイル)。読み取りは lib/blob.ts の getAmebaSyncStatusFromBlob()。
8-2. 書き込みロジック
scripts/workflow/sync-status.ts の syncMembers() が、members.json・デイリーダイジェスト書き出しに続けて実行のたびに書き込む(SEARCH_API_KEY 未設定のドライラン時はスキップ)。
- 実行トリガーの判定: GitHub Actions がすべてのステップに自動的に注入するデフォルト環境変数
GITHUB_EVENT_NAMEがscheduleかどうかで、定期実行/手動実行(workflow_dispatch)を判定する - 定期実行の場合:
lastAutoSyncedAtを今回の実行時刻に更新し、旧lastAutoSyncedAtを新しいpreviousAutoSyncedAtにスライドさせる(チェックポイント履歴を1つ進める) - 手動実行の場合:
lastAutoSyncedAt・previousAutoSyncedAtは据え置き、lastSyncedAtのみ今回の実行時刻に更新する(手動実行はチェックポイント履歴を進めない) - 既存の
ameba/sync-status.jsonが存在しない場合(初回実行)は全フィールドが今回の実行時刻にフォールバックする
8-3. 表示側のウィンドウ計算
components/NewPostsSection.tsx は、[previousAutoSyncedAt, lastSyncedAt] を新着ウィンドウとし、各メンバーの statusHistory のうち capturedAt がこの範囲に入るエントリを全件新着として表示する(1メンバー1件だった従来の latestStatus のみの表示制約を撤廃)。
- ウィンドウ開始点に固定の「24時間前」を使わず、実際の
previousAutoSyncedAt(前々回の定期実行時刻)を使う理由: 定期実行自体が遅延した場合(例: 通常20:00実行が22:00に遅延)、固定24時間前で計算すると遅延分の投稿を取りこぼす - ウィンドウ終了点に
lastSyncedAt(手動実行含む)を使う理由: 障害対応等の手動実行で収集した投稿も、次の定期実行を待たずに表示するため - 手動実行がチェックポイント履歴(
lastAutoSyncedAt・previousAutoSyncedAt)を進めない理由: 手動実行の直前の定期実行からの間隔が短い場合、そこを起点にすると新着ウィンドウが数時間程度に縮み、メール通知との内容差異が拡大してしまうため statusHistoryが存在しない(未設定の)既存データはlatestStatusのみを対象にフォールバックする- 判定基準に
lastUpdated(RSS のpubDate=投稿日時)ではなくcapturedAt(収集日時)を使う理由: 投稿日時は収集タイミングと無関係にずれることがあり、収集が遅延した投稿(例: RSS反映の遅れで前日以前のpubDateを持つ投稿が当日収集される場合)が投稿日時ベースの判定では新着ウィンドウから漏れてしまう(#1408) capturedAtを持たないエントリ(本フィールド追加前に蓄積された既存データ)は新着ウィンドウ判定の対象外とする(収集日時が不明なため)
8-4. マージ直後の初期データ投入
ameba/sync-status.json は sync-members ワークフローの初回実行まで Blob に存在しない(getAmebaSyncStatusFromBlob() は null を返し、Ameba ブロックは表示されない)。初回の定期実行を待たずに表示確認したい場合は scripts/patch/seed-ameba-sync-status.ts(一回限りの橋渡し用、詳細は batch-design.mdx の一覧表を参照)で暫定データを投入できる。
capturedAt フィールド追加(#1408)以前に蓄積された statusHistory エントリは capturedAt を持たないため新着表示されない。マージ後すぐに動作確認したい場合は scripts/patch/backfill-ameba-captured-at.ts(一回限りの検証用、Issue #1408 本文ログ記載の投稿URLに一致するエントリへスクリプト実行時刻を capturedAt として設定する)で暫定的に投入できる。
改訂履歴
| 版 | 更新日 | 変更内容 |
|---|---|---|
| 1.5 | 2026-08-12 | SNS投稿一覧のルール統一(#1491): §7-1 タイトルを「Ameba ブログ」から媒体名のみの「Ameba」に変更。メンバー表示順を「並び替えを行わない」から sortMembers による現役→OG順の並び替えに変更(他媒体との統一ルールに合わせるため、意図的な仕様変更) |
| 1.4 | 2026-08-07 | グループブログのタイトルに複数の共有メンバー名が含まれる投稿の投稿者判定ルール(タイトル末尾に最も近い名前を採用、resolveAmebaGroupBlogAuthorName)を追加。日次バッチと patch-ameba-misattributed-status.ts の判定ロジックを共通化(#1480) |
| 1.3 | 2026-07-25 | 重複蓄積の判定基準をタイトル一致からURL一致に変更(タイトルが定型化されているブログでの誤スキップを是正、#1417)。RSS取得件数が30件未満の場合に警告ログを出力する検知機構を追加(件数変動による取りこぼしの早期発見、恒久対策ではない、#1418) |
| 1.2 | 2026-07-24 | StatusEntryにcapturedAt(収集日時)を追加。§8-3 新着ウィンドウ判定をlastUpdated(投稿日時)からcapturedAt(収集日時)基準に変更。§8-4 に検証用パッチスクリプトbackfill-ameba-captured-at.tsを追記(#1408) |
| 1.1 | 2026-07-20 | §8 新着表示の判定基準(ameba/sync-status.json・AmebaSyncStatus型・新着ウィンドウ計算)を追加。§2 データ格納先に ameba/sync-status.json を追加(#1387) |
| 1.0 | 2026-07-19 | 初版作成。データ格納先(members.json への直接格納)・ユーティリティ関数・/ameba 画面設計を記載。RSS取得仕様はバッチ設計書「Ameba RSS 解析」から本書に集約(#1367) |