Appearance
API-007 X告知 REST
概要
X告知担当 bot が、公開済みの日別記事・有料記事を X で告知するための REST。告知に使う記事のメタ情報(URL・タイトル・考察日・号数・アイキャッチ URL 等)を読み、X に投稿した結果(告知履歴)を記事に書き戻す。X告知担当ロール(x_announcer、Capability read_x_announce / record_x_announcement)のユーザーがアプリケーションパスワードで呼ぶ。
- 返す項目は許可リストで固定する。本文(
post_content)・考察欄・抜粋・有料記事の冒頭挨拶やセクション・任意の post meta は返さない。告知文の要約は各記事の編集 bot が作る - 公開済み(
publish)の記事だけを対象にする。下書き・予約・非公開は一覧に出さず、1 件取得と記録は 404 - 書き込みは告知履歴の記録(API-007-5)だけ。記事本体は変更しない
- X への投稿は bot 側で行う。X の API キー・アクセストークンは bot のシークレットに置き、WordPress には保存しない
- bot が X API で投稿するか人間が投稿するかは、管理画面(ADM-036)で記事の種類ごとに設定し、bot は API-007-6 で読む。bot からは変更できない
- 運用確認・日別記事編集・取込用の REST とは namespace を分ける。アカウントとアプリケーションパスワードも分ける
日別記事の項目
| field | 型 | 説明 |
|---|---|---|
id | int | 投稿 ID |
url | string | パーマリンク |
title | string | タイトル |
kousatsu_date | string | 考察日(Y-m-d。Ymd で保存されている場合も Y-m-d に揃える) |
published_gmt | string | 公開日時(GMT、Y-m-d H:i:s) |
modified_gmt | string | 更新日時(GMT、Y-m-d H:i:s) |
featured_image_url | string|null | アイキャッチ画像の URL(full)。無ければ null。添付するかは bot が決める |
announcements | array | 告知履歴(下記)。古い順 |
有料記事の項目
日別記事の kousatsu_date の代わりに次を持つ(他は同じ)。
| field | 型 | 説明 |
|---|---|---|
issue_number | int|null | 号数(PaidArticleMetaKeys::ISSUE_NUMBER)。未設定は null |
target_month | string | 対象月(PaidArticleMetaKeys::TARGET_MONTH)。未設定は空文字 |
is_free_release | bool | 無料公開中か(PaidArticleMetaKeys::IS_FREE_RELEASE が 1) |
告知履歴の項目
記事ごとに protected meta _x_announcements(JSON 配列、古い順)へ保存する。21 件目を記録すると最も古い行を消し、最新 20 件だけ残す(XAnnouncementConstants::MAX_ENTRIES_PER_POST)。
| field | 型 | 説明 |
|---|---|---|
kind | string | daily_pre(示唆考察)/ daily_after(結果考察)/ paid_update(有料記事) |
tweet_id | string | X の投稿 ID(数字だけ) |
tweet_url | string | https://x.com/i/web/status/{tweet_id} |
posted_at | string | X に投稿した日時(UTC、Y-m-d\TH:i:s\Z) |
recorded_at | string | WordPress に記録した日時(UTC、Y-m-d\TH:i:s\Z) |
保存値が壊れている・項目が欠けている行は返さない。
API-007-1 GET /daily-articles
入力(リクエスト)
| param | 必須 | 型・制約 | 説明 |
|---|---|---|---|
date_from | はい | Y-m-d | 考察日の開始日(含む) |
date_to | はい | Y-m-d | 考察日の終了日(含む) |
期間は両端を含めて最大 31 日(XAnnounceRestController::MAX_RANGE_DAYS)。
出力(レスポンス)
| field | 型 | 説明 |
|---|---|---|
success | bool | true |
date_from | string | 開始日 |
date_to | string | 終了日 |
count | int | items の件数 |
truncated | bool | 上限で打ち切ったか |
items | array | 日別記事の項目。考察日・ID の昇順 |
HTTP ステータス: 200。1 回の取得は最大 200 件(XAnnounceArticleReaderInterface::DAILY_LIST_LIMIT)。超えるときは投稿 ID の小さい順に 200 件を返し、truncated を true にする。期間を分けて取り直すこと。
失敗・エラー条件
| 条件 | HTTP | message |
|---|---|---|
| どちらかが無い・Y-m-d でない・存在しない日付 | 400 | XAnnounceRestCopy::DATE_RANGE_REQUIRED |
date_from が date_to より後 | 400 | XAnnounceRestCopy::DATE_RANGE_ORDER_INVALID |
| 期間が 31 日を超える | 400 | XAnnounceRestCopy::DATE_RANGE_TOO_LONG |
API-007-2 GET /daily-articles/{id}
入力(リクエスト)
| param | 必須 | 型・制約 | 説明 |
|---|---|---|---|
id | はい | 正の整数(パス) | 日別記事の投稿 ID |
出力(レスポンス)
| field | 型 | 説明 |
|---|---|---|
success | bool | true |
item | object | 日別記事の項目 |
HTTP ステータス: 200
失敗・エラー条件
| 条件 | HTTP | message |
|---|---|---|
id 不正 | 400 | Messages::REST_INVALID_ID_MESSAGE |
| 日別記事でない・公開済みでない・存在しない | 404 | XAnnounceRestCopy::DAILY_ARTICLE_NOT_FOUND |
API-007-3 GET /paid-articles
入力(リクエスト)
| param | 必須 | 型・制約 | 説明 |
|---|---|---|---|
modified_after | いいえ | ISO 8601(タイムゾーン必須、小数秒は任意)、または modified_gmt と同じ Y-m-d H:i:s(UTC として扱う) | この日時より後(含まない)に更新された記事だけ返す |
modified_after は UTC に直して post_modified_gmt と比べる。前回取得時の modified_gmt をそのまま渡せば、更新された有料記事だけを取れる。タイムゾーンのオフセットは ±14:59 まで。
出力(レスポンス)
| field | 型 | 説明 |
|---|---|---|
success | bool | true |
modified_after | string|null | 受け付けた modified_after(UTC、Y-m-d\TH:i:s\Z)。省略時は null |
count | int | items の件数 |
truncated | bool | 上限で打ち切ったか |
items | array | 有料記事の項目。更新日時の新しい順 |
HTTP ステータス: 200。1 回の取得は最大 50 件(XAnnounceArticleReaderInterface::PAID_LIST_LIMIT)。超えるときは更新日時の新しい順に 50 件を返し、truncated を true にする(有料記事は月 1 本程度のため、通常は上限に達しない)。
失敗・エラー条件
| 条件 | HTTP | message |
|---|---|---|
modified_after が上記の形式でない(T 区切りでタイムゾーンが無い等)・存在しない日時・オフセットが範囲外 | 400 | XAnnounceRestCopy::MODIFIED_AFTER_INVALID |
API-007-4 GET /paid-articles/{id}
入力(リクエスト)
| param | 必須 | 型・制約 | 説明 |
|---|---|---|---|
id | はい | 正の整数(パス) | 有料記事の投稿 ID |
出力(レスポンス)
| field | 型 | 説明 |
|---|---|---|
success | bool | true |
item | object | 有料記事の項目 |
HTTP ステータス: 200
失敗・エラー条件
| 条件 | HTTP | message |
|---|---|---|
id 不正 | 400 | Messages::REST_INVALID_ID_MESSAGE |
| 有料記事でない・公開済みでない・存在しない | 404 | XAnnounceRestCopy::PAID_ARTICLE_NOT_FOUND |
API-007-5 POST /announcements
X に投稿したあとに 1 回呼ぶ。告知文の本文は受け取らない。
入力(リクエスト)
JSON ボディまたはフォームパラメータ。
| param | 必須 | 型・制約 | 説明 |
|---|---|---|---|
post_id | はい | 正の整数 | 告知した記事の投稿 ID |
kind | はい | daily_pre / daily_after / paid_update | daily_* は日別記事、paid_update は有料記事 |
tweet_id | はい | 文字列。数字だけ 1〜20 桁 | X の投稿 ID。JSON の数値は精度が落ちるため文字列で送る |
posted_at | はい | ISO 8601(タイムゾーン必須、小数秒は任意。オフセットは ±14:59 まで) | X に投稿した日時。UTC に直して保存する |
出力(レスポンス)
| field | 型 | 説明 |
|---|---|---|
success | bool | true |
result | string | created(新規に記録)/ duplicate(同じ記事の保存中の履歴に同じ tweet_id があった) |
announcement.post_id | int | 投稿 ID |
announcement.kind | string | 告知の種類 |
announcement.tweet_id | string | X の投稿 ID |
announcement.tweet_url | string | X の投稿 URL |
announcement.posted_at | string | 投稿日時(UTC、Y-m-d\TH:i:s\Z) |
HTTP ステータス: created は 201、duplicate は 200。同じ tweet_id を再送しても履歴は増えないため、タイムアウト後の再送はそのまま行ってよい。重複の判定は保存中の最新 20 件だけが対象で、上限で消えた古い tweet_id を送り直すと created として記録し直す。
失敗・エラー条件
| 条件 | HTTP | message |
|---|---|---|
post_id が正の整数でない | 400 | XAnnounceRestCopy::POST_ID_INVALID |
kind が上記以外 | 400 | XAnnounceRestCopy::KIND_INVALID |
tweet_id が文字列でない・数字以外を含む・21 桁以上 | 400 | XAnnounceRestCopy::TWEET_ID_INVALID |
posted_at が ISO 8601 でない・タイムゾーンが無い・存在しない日時・オフセットが範囲外 | 400 | XAnnounceRestCopy::POSTED_AT_INVALID |
記事が kind の投稿タイプでない・公開済みでない・存在しない | 404 | XAnnounceRestCopy::DAILY_ARTICLE_NOT_FOUND / PAID_ARTICLE_NOT_FOUND |
| post meta を保存できなかった | 503 | XAnnounceRestCopy::RECORD_FAILED |
監査ログ
BotRestAuditLogger で error_log に 1 行 JSON(接頭辞 [bot-rest-audit])を書く。記録するのはルート・ユーザー ID・post_id・kind・tweet_id・結果(outcome: created / duplicate / failed / not_found)だけ。入力検証で 400 になったリクエストは記録しない。
API-007-6 GET /settings
X への投稿方法を記事の種類ごとに返す。bot は告知文を作ったあと、投稿前に毎回呼ぶ。値は管理画面(ADM-036)で管理者だけが変更でき、REST に書き込みのルートは無い。
入力(リクエスト)
なし。
出力(レスポンス)
| field | 型 | 説明 |
|---|---|---|
success | bool | true |
posting_modes.daily_article | string | 日別記事の告知(daily_pre / daily_after)の投稿方法 |
posting_modes.paid_article | string | 有料記事の告知(paid_update)の投稿方法 |
HTTP ステータス: 200。
| 値 | bot の動き |
|---|---|
api | 承認後に bot が X API で投稿し、API-007-5 で告知履歴を記録する |
manual | X には投稿せず、告知文とアイキャッチ URL を人間に渡して終わる。人間が投稿した分の告知履歴は記録しない(API-007-5 を呼ばない) |
保存先は wp_options x_announce_posting_modes(XAnnouncementConstants::OPTION_POSTING_MODES、投稿タイプ => 値の配列)。未設定・不正値は manual(API キーを用意していないまま自動投稿しないため)。
失敗・エラー条件
権限・レート制限以外の失敗は無い。
権限・nonce・レート制限
全エンドポイント共通。XAnnounceRestHandler + WordPressXAnnouncePermissionChecker。Handler がルートごとに action(read / record)を付け、Checker は action に応じて Capability とレート制限を変える。action が無い・不明なときは 403。
| 項目 | GET(API-007-1〜4・6) | POST(API-007-5) |
|---|---|---|
| Capability | read_x_announce | record_x_announcement |
| レート制限 | ユーザー単位で 1 分あたり 120 回 | ユーザー単位で 1 分あたり 20 回 |
| カウンタ不可時 | 通す(fail-open) | 拒否する(fail-closed、429) |
| 項目 | 内容 |
|---|---|
| 匿名アクセス | 403 |
| nonce | 検証しない(アプリケーションパスワード前提)。Cookie 認証で呼ぶ場合の REST nonce は WordPress コア側の挙動に従う |
| レート制限 | BotRestRateLimiter(transient、60 秒の固定ウィンドウ)。バケットは x-announce:read / x-announce:record。超過時は 429 |
| ログ | ログイン済みユーザーの 403 と、429 のときだけ error_log に 1 行(ステータス・user_id・ルート)。未ログインの 403 と許可したアクセスは記録しない |
| Local バイパス | API-001 の WordPressPermissionChecker と同じ条件(レート制限もかけない) |
X告知担当ロールは wp-admin を開けない(MemberAdminAccessGuardHooks がサイトトップへリダイレクトし、管理バーも出さない)。記事の Capability(edit_daily_articles / edit_paid_articles 等)は持たない。
WordPress コア経由の書き込みの遮断
x_announcer を持ち、他には REST 用 bot ロール(ops_status_reader / birthday_linker)しか持たないユーザーには BotWriteGuardHooks(MemberServiceProvider で登録)で、運用確認bot(API-006)と同じ制限をかける。違いは REST の POST /x-announce/v1/announcements を通すこと。他の bot ロールを併せ持つ場合は、そのロールの許可ルートも合わせて通す。birthday_linker を併せ持つユーザーには、下表のエラーコードの代わりに birthday_write_forbidden / birthday_xmlrpc_forbidden(API-008)を返す。bot ロール以外のロールを併せ持つユーザーは対象外。
ログインパスワードで wp-login からブラウザにログインした場合、フロントの会員プロフィール・退会フォームやコメント投稿は止めない。そのため、X告知担当のログインパスワードは長いランダムな値にして保存・使用せず、アプリケーションパスワードだけで運用する(利用手順)。
| 経路 | フック | 内容 |
|---|---|---|
| REST | rest_pre_dispatch | GET / HEAD / OPTIONS と POST /x-announce/v1/announcements 以外は 403(x_announce_write_forbidden、XAnnounceRestCopy::WRITE_FORBIDDEN)。/wp/v2/users/me の更新やアプリケーションパスワードの API も含む |
| 権限 | map_meta_cap | 自分自身に対する edit_user / create_app_password / edit_app_password / delete_app_password / delete_app_passwords を do_not_allow。管理者が bot に対して行う操作は対象外 |
| XML-RPC | authenticate(優先度 100) | XML-RPC リクエスト中は bot のログインを拒否する(x_announce_xmlrpc_forbidden)。アプリケーションパスワード認証(優先度 20)の後で判定する |