Appearance
API-008 誕生日 REST
概要
誕生日紐付け担当 bot が、誕生日データ(db_birthday)・作品名マスタ・作品名と機種の紐付け(db_birthday_kishu)を wp-admin を開かずに検索・追加・更新するための REST。誕生日紐付け担当ロール(birthday_linker)のユーザーがアプリケーションパスワードで呼ぶ。Issue #3892。
- 書き込みはすべて人間の確認後に行う(bot はプレビューや対象を人間に見せ、了承を得てから書き込みのルートを呼ぶ)。REST 側では確認の有無を判定しない。MCP の書き込みツールの説明に明記する(利用手順)
- 紐付けと誕生日の追加・作品名の変更では、作品名・機種が有効(
is_active = 1)でなければ 400。無効な作品名・機種へは新しく紐付けない - 機種マスタの編集・作品名の統合・全件シード(ADM-013 の「シードを実行」)はできない。管理画面(ADM-013 等)で管理者が行う
- 書き込みは
BotRestAuditLoggerで監査ログに残す(下記「監査ログ」) - 運用確認・日別記事編集・取込・X告知用の REST とは namespace を分ける。アカウントとアプリケーションパスワードも分ける
- 状況の件数だけなら運用確認bot が API-006-6 で読める
- 日別記事 bot 向けのホール別・前後 N 日の誕生日(紐付いた機種の設置台数・去年の結果つき)と声優名・キャラ名の検索は、日別記事側の API-001-26 / 27(
daily-article/v1、edit_daily_articles。Issue #3995)。本 API の権限(read_birthday_data)は日別記事 bot に付けなくてよい
項目
作品名
| field | 型 | 説明 |
|---|---|---|
id | int | 作品名 ID |
name | string | 作品名 |
is_active | bool | 有効か |
birthday_count | int | この作品名を参照する誕生日データの件数(一覧のときだけ) |
link_count | int | この作品名の機種紐付けの件数(一覧のときだけ) |
機種: id(機種 ID)・name(機種名)。アクティブな機種だけを返す。
誕生日データ
| field | 型 | 説明 |
|---|---|---|
id | int | 誕生日データ ID |
month | int | 月 |
day | int | 日 |
divi | string | キャラ誕 / 声優誕 |
actor | string | 声優名(声優誕のときだけ。それ以外は空文字) |
chara | string | キャラ名 |
title_id | int | 作品名 ID(作品名なしは空の作品名の ID) |
title | string | 作品名 |
紐付け: title_id・title・kishu_id・kishu_name。
一覧の共通入出力
一覧(API-008-1〜5)は limit(1〜200、既定 50。200 を超える値は 200、不正値は既定値)と offset(0 以上、既定 0)を受け付け、success・total(絞り込み後の全件数)・count・limit・offset・items を返す。
エラー応答
コントローラーが返すエラーはすべて {"success": false, "code": "<code>", "message": "<文言>"}。bot は code で次の操作を分ける(message は人間に見せる文言で、変わることがある)。code は BirthdayRestErrorCodes の定数(snake_case)で、値を変えるときは本書と MCP 利用手順 も合わせる。Issue #3969。
code | HTTP | 意味・bot の次の操作 |
|---|---|---|
invalid_parameter | 400 | パラメータの形式・値が不正、作品名・機種がマスタに無い。入力を直す |
title_inactive | 400 | 作品名はマスタにあるが無効。人間の確認後に API-008-15 で有効にしてから再実行する |
kishu_inactive | 400 | 機種はマスタにあるが無効。bot からは有効にできない(管理画面で管理者が扱う) |
no_change | 400 | 変更前と同じ内容(紐付け・誕生日データ・作品名・有効/無効)。書き込みは不要 |
title_protected | 400 | 作品名なし用の行は改名・無効化できない |
not_found | 404 | 対象(紐付け・誕生日データ・作品名)が無い。一覧で探し直す |
duplicate | 409 | 同じデータ(紐付け・誕生日データ・作品名)が既にある |
sulocale_run_in_progress | 409 | 他の誕生日取込が実行中(何も書き込んでいない)。時間をおいて再実行する |
sulocale_preview_mismatch | 409 | プレビュー後にページ・登録状況が変わった(何も書き込んでいない)。API-008-16 からやり直す |
sulocale_page_invalid | 422 | スロカレの作品ページとして読み取れない。URL を確認する |
sulocale_too_many_rows | 422 | スロカレの抽出件数が上限を超えた |
unexpected_error | 500 | 想定外の例外(例外のメッセージは返さない) |
sulocale_fetch_failed | 502 | スロカレのページを取得できなかった。時間をおいて再実行する |
write_failed | 503 | DB 書き込みの失敗。時間をおいて再実行する |
以下の各節で「400(MONTH_DAY_INVALID)」のように括弧内に書いたのは message の定数名(BirthdayRestCopy 等)。code は上の表のとおり。
権限チェックで拒否した 403・429 と、WordPress 側で返るエラー(存在しないルートの 404 rest_no_route、BotWriteGuardHooks の 403 birthday_write_forbidden、認証失敗の 401 等)はコントローラーを通らないため、WordPress の WP_Error 形式({"code", "message", "data": {"status"}}、success なし)で返る。権限チェックの 403・429 はどちらも code: rest_forbidden のため、HTTP ステータスで分ける(MCP 利用手順 の「トラブルシューティング」)。
読み取り(GET)
API-008-1 GET /titles
| param | 必須 | 説明 |
|---|---|---|
search | いいえ | 作品名の部分一致 |
active | いいえ | 1=有効のみ / 0=無効のみ / それ以外=すべて |
作品名の昇順。items は作品名の項目。
API-008-2 GET /kishu
| param | 必須 | 説明 |
|---|---|---|
search | いいえ | 機種名の部分一致 |
API-008-3 GET /birthdays
| param | 必須 | 説明 |
|---|---|---|
chara | いいえ | キャラ名の部分一致 |
title | いいえ | 作品名の部分一致 |
divi | いいえ | キャラ誕 / 声優誕 |
month | いいえ | 1〜12 |
day | いいえ | 1〜31 |
月日の昇順。divi が不正なら 400(BirthdayRestCopy::DIVI_FILTER_INVALID)、month / day が範囲外・存在しない月日なら 400(MONTH_DAY_INVALID)。
API-008-4 GET /links
| param | 必須 | 説明 |
|---|---|---|
title | いいえ | 作品名の部分一致 |
kishu | いいえ | 機種名の部分一致 |
作品名の昇順。
API-008-5 GET /unlinked-titles
誕生日データがあり、機種が 1 件も紐付いていない有効な作品名(空の作品名は除く)。items[] は id・name・birthday_count(BirthdayLinkStatusServiceInterface::list_unlinked_titles)。
API-008-6 GET /upcoming
| param | 必須 | 説明 |
|---|---|---|
days | いいえ | 1〜60(既定 14、今日を含む) |
今日(サイトのタイムゾーン)から days 日間の誕生日。success・days・count・items を返し、items[] は date(Y-m-d)・linked(その作品名に機種が紐付いているか)と誕生日データの項目。2/29 は平年には出さない。days が範囲外なら 400(DAYS_INVALID)。
紐付け
API-008-7 POST /links
| param | 必須 | 説明 |
|---|---|---|
title_id | はい | 作品名 ID |
kishu_id | はい | 機種 ID |
成功時 201 で link(紐付けの項目)を返す。
API-008-8 PATCH /links
| param | 必須 | 説明 |
|---|---|---|
old_title_id | はい | 今の作品名 ID |
old_kishu_id | はい | 今の機種 ID |
title_id | はい | 変更後の作品名 ID |
kishu_id | はい | 変更後の機種 ID |
成功時 200 で link を返す。
API-008-9 DELETE /links
title_id・kishu_id(クエリまたはボディ)。成功時 200 で deleted: {title_id, kishu_id}。
紐付けの失敗・エラー条件
| 条件 | HTTP | code | message |
|---|---|---|---|
| ID が正の整数でない | 400 | invalid_parameter | BirthdayRestCopy::TITLE_ID_INVALID / KISHU_ID_INVALID |
| 作品名・機種がマスタに無い(追加・変更先) | 400 | invalid_parameter | Messages::BIRTHDAY_KISHU_ADMIN_* |
| 作品名が無効(追加・変更先) | 400 | title_inactive | Messages::BIRTHDAY_KISHU_ADMIN_TITLE_NOT_ACTIVE |
| 機種が無効(追加・変更先) | 400 | kishu_inactive | Messages::BIRTHDAY_KISHU_ADMIN_KISHU_NOT_ACTIVE |
| 変更前と同じ(PATCH) | 400 | no_change | LINK_NO_CHANGE |
| 対象の紐付けが無い(PATCH・DELETE) | 404 | not_found | LINK_NOT_FOUND |
| 既にある(POST・PATCH の変更先) | 409 | duplicate | LINK_DUPLICATE |
| DB 書き込みの失敗 | 503 | write_failed | WRITE_FAILED |
誕生日データ
API-008-10 POST /birthdays
| param | 必須 | 説明 |
|---|---|---|
month | はい | 1〜12 |
day | はい | 存在する日 |
divi | はい | キャラ誕 / 声優誕 |
chara | はい | キャラ名 |
actor | 声優誕 | 声優名 |
title_id | いいえ | 作品名 ID(有効なもの)。省略・0 は作品名なし |
検証は管理画面の手入力(BirthDayAdminServiceInterface::validate_birthday_form)と同じで、作品名は有効必須(無効なら 400、code: title_inactive、BirthdaySeedMessages::TITLE_NOT_ACTIVE。その他の検証エラーは invalid_parameter)。成功時 201 で birthday(誕生日データの項目)。同じ月日・区分・キャラ・作品名があれば 409(BIRTHDAY_DUPLICATE)。
API-008-11 PATCH /birthdays/{id}
API-008-10 と同じ項目のうち、指定したものだけを変える(指定しない項目は今の値)。title_id は API-008-10 と同じく 0 を作品名なしとして受け付ける(name が空の作品名の ID を直接渡してもよい)。変えるときは変更先が有効であること(変えないときは今の作品名が無効でもよい)。成功時 200 で birthday。何も変わらなければ 400(BIRTHDAY_NO_CHANGE)、変更後が他の行と同じなら 409(BIRTHDAY_DUPLICATE。事前の判定で見つからなくても、DB の一意キー(大文字小文字を区別しない照合順序)で重複になった場合も 409)、無ければ 404(BIRTHDAY_NOT_FOUND)。
API-008-12 DELETE /birthdays/{id}
成功時 200 で deleted: {id}。無ければ 404。
{id} は URL パスの値だけを使う(include_url_params)。クエリ・ボディの id は見ない。
作品名マスタ
API-008-13 POST /titles
name(必須)。有効な状態で作る。成功時 201 で title: {id, name, is_active}。同じ作品名(無効なものを含む)があれば 409(TITLE_DUPLICATE)、空なら 400(TITLE_NAME_REQUIRED)、長すぎれば 400(TitleMasterAdminMessages::NAME_MAX)。
API-008-14 PATCH /titles/{id}
name(必須)で改名する。成功時 200 で title: {id, name}。無ければ 404(TITLE_NOT_FOUND)、同じ名前なら 400(TITLE_NAME_NO_CHANGE)、他の作品名と重なれば 409(TITLE_DUPLICATE)。
API-008-15 POST /titles/{id}/active
is_active(true / false、1 / 0 も可)で有効/無効を切り替える。成功時 200 で title: {id, is_active}。既にその状態なら 400(TITLE_ACTIVE_NO_CHANGE)、不正値は 400(TITLE_ACTIVE_INVALID)。無効にしても既存の誕生日データ・紐付けは残る。
作品名なし用の行(name が空)は改名・無効化できない(400、TITLE_EMPTY_PROTECTED。TitleMasterAdminService で止めるため、管理画面の改名・無効化でも同じ)。作品名の統合はできない(ADM の作品名マスタ画面で行う)。
スロカレ取込
API-008-16 POST /sulocale/preview
url(必須、https://sulocale.sulopachinews.com/archives/<数字>。末尾の / は取り除いて取得する)のページを取得・解析し、取り込んだ場合の結果を返す。DB・作品名マスタには書き込まない。
| field | 型 | 説明 |
|---|---|---|
processed | int | 抽出した件数(重複を除く) |
new_count | int | 新規に登録される件数 |
existing_count | int | 登録済みでスキップされる件数 |
new_titles | string[] | 作品名マスタに無く、取込で新規作成される作品名 |
entries[] | array | month・day・divi・actor_display・chara・title・status(new / existing) |
preview_hash | string | API-008-17 の expected_preview_hash に渡す照合用の値(SHA-256、16 進 64 文字) |
preview_hash は URL のアーカイブ番号・new_titles・entries[](status を含む全行の並び)から作る。別の URL のプレビューの値は一致しない(末尾の / の有無は同じ扱い)。ページの内容が同じでも、その間に誕生日データや作品名マスタへ登録されて status や new_titles が変われば値が変わる。
API-008-17 POST /sulocale/import
同じ url を取り込む。作品名マスタに無い作品名は新規作成する。bot は直前に API-008-16 の結果(特に new_titles)を人間に見せ、了承を得てから呼ぶ。Issue #3901。
| param | 必須 | 説明 |
|---|---|---|
url | ○ | API-008-16 と同じ URL |
expected_preview_hash | 任意 | 人間に見せた API-008-16 の preview_hash(16 進 64 文字) |
expected_preview_hash を指定したときは、ページを取得し直して API-008-16 と同じ方法で preview_hash を作り、一致しなければ 誕生日データ・作品名マスタのどちらにも書き込まずに 409(SULOCALE_PREVIEW_MISMATCH)を返す。プレビューから取込までの間にページが更新された・他の経路で登録されたなど、人間が確認していない内容を登録しないためのもの。409 のときは API-008-16 からやり直し、結果を人間に見せ直す。
照合は取込のロック(BirthdaySeedRunLock)の中で書き込み直前に行うが、このロックは誕生日取込どうし(本 API・管理画面の URL 取込/全件シード)しか排他しない。照合から書き込みまでのごく短い間に、ロックを取らない経路(API-008-10・13、管理画面の作品名マスタ・誕生日の編集)で登録されることまでは防がない。その場合も作品名の新規作成が減る・行が重複スキップになる方向にしかずれず、人間が確認していない作品名や誕生日は登録されない。
未指定(または空文字)のときは照合せずに取り込む(Issue #3901 以前の呼び出しとの互換のため)。MCP の birthday_sulocale_import は expected_preview_hash を必須にしているため、bot からの取込は常に照合される。
| field | 型 | 説明 |
|---|---|---|
processed | int | 抽出した件数 |
inserted | int | 登録した件数 |
skipped | int | 登録済みでスキップした件数 |
created_titles | string[] | 新規作成した作品名 |
inserted_entries | array | 登録した行(entries[] と同じ形、status なし) |
skipped_entries | array | スキップした行 |
管理画面のシード・URL 取込と同じロック(BirthdaySeedRunLock)を取る。他の取込が実行中なら 409(SULOCALE_RUN_IN_PROGRESS)。成功したら最終取得日時(API-006-6 の last_sulocale_import_at)を更新する。全件シードの 1 時間クールダウンは URL 取込にはかからない(外部取得の回数はレート制限で抑える)。
スロカレの失敗・エラー条件
| 条件 | HTTP | code | message |
|---|---|---|---|
| URL がスロカレのアーカイブ URL でない | 400 | invalid_parameter | BirthdayRestCopy::URL_INVALID(取得はしない) |
| 作品ページとして読み取れない | 422 | sulocale_page_invalid | SULOCALE_PAGE_INVALID |
| 抽出件数が上限(500 件)を超えた | 422 | sulocale_too_many_rows | SULOCALE_TOO_MANY_ROWS |
| 取得に失敗した(200 以外・HTML 以外・2MB 超等) | 502 | sulocale_fetch_failed | SULOCALE_FETCH_FAILED |
| 他の取込が実行中(import のみ) | 409 | sulocale_run_in_progress | SULOCALE_RUN_IN_PROGRESS |
expected_preview_hash が 16 進 64 文字でない(import のみ。取得はしない) | 400 | invalid_parameter | SULOCALE_PREVIEW_HASH_INVALID |
取得し直した結果が expected_preview_hash と一致しない(import のみ。何も書き込まない) | 409 | sulocale_preview_mismatch | SULOCALE_PREVIEW_MISMATCH |
| 想定外の例外 | 500 | unexpected_error | UNEXPECTED_ERROR |
レート制限は権限チェックで先に数えるため、URL の形式が不正で 400 になったリクエストも 1 時間 10 回の枠を 1 回使う。例外のメッセージはレスポンスに載せない。取得は wp_safe_remote_get(リダイレクトを追わない・2MB・15 秒)で、取得先は URL の検証を通ったスロカレのアーカイブだけ。
権限・nonce・レート制限
全エンドポイント共通。BirthdayRestHandler + WordPressBirthdayPermissionChecker。Handler がルートごとに action を付け、Checker は action に応じて Capability とレート制限を変える。action が無い・不明なときは 403。
action | ルート | Capability | レート制限(ユーザー単位) | カウンタ不可時 |
|---|---|---|---|---|
read | API-008-1〜6 | read_birthday_data | 1 分 120 回 | 通す |
link | API-008-7〜9 | link_birthday_kishu | 1 分 30 回 | 拒否(429) |
add | API-008-10・13 | add_birthday_data | 1 分 30 回 | 拒否(429) |
sulocale | API-008-16・17 | add_birthday_data | 1 時間 10 回(合算) | 拒否(429) |
update | API-008-11・12・14・15 | update_birthday_data | 1 分 30 回 | 拒否(429) |
| 項目 | 内容 |
|---|---|
| 匿名アクセス | 403 |
| nonce | 検証しない(アプリケーションパスワード前提) |
| レート制限 | BotRestRateLimiter(transient、固定ウィンドウ)。バケットは birthday:read / birthday:link / birthday:add / birthday:sulocale / birthday:update |
| ログ | ログイン済みユーザーの 403 と、429 のときだけ error_log に 1 行(BirthdayRestCopy::LOG_DENIED_FORMAT) |
| Local バイパス | API-001 の WordPressPermissionChecker と同じ条件(レート制限もかけない) |
誕生日紐付け担当ロールは wp-admin を開けない(MemberAdminAccessGuardHooks)。誕生日の管理画面(ADM-013 等、manage_options)は開放しない。
WordPress コア経由の書き込みの遮断
birthday_linker を持ち、他には REST 用 bot ロール(ops_status_reader / x_announcer)しか持たないユーザーには BotWriteGuardHooks(MemberServiceProvider で登録)で、X告知担当(API-007)と同じ制限をかける。違いは通す書き込みが BotWriteGuardHooks::BOT_ROLES の birthday_linker の allowed_writes(上の API-008-7〜17 のメソッドとパス)であること。他の bot ロールを併せ持つ場合は、そのロールの許可ルートも合わせて通す。
| 経路 | フック | 内容 |
|---|---|---|
| REST | rest_pre_dispatch | GET / HEAD / OPTIONS と allowed_writes 以外は 403(birthday_write_forbidden、BirthdayRestCopy::WRITE_FORBIDDEN) |
| 権限 | map_meta_cap | 自分自身に対する edit_user とアプリケーションパスワードの発行・編集・削除を do_not_allow |
| XML-RPC | authenticate(優先度 100) | XML-RPC リクエスト中は bot のログインを拒否する(birthday_xmlrpc_forbidden) |
監査ログ
書き込み(API-008-7〜17)は BotRestAuditLogger で error_log に 1 行 JSON(接頭辞 [bot-rest-audit])を書く。記録するのはルート(birthday/v1/<パス>)・ユーザー ID・操作(action)・対象の ID・結果(outcome: created / updated / deleted / duplicate / not_found / no_change / failed 等)・件数だけ。スロカレはアーカイブ番号と件数だけを残し、URL 全体・キャラ名・作品名は残さない(取込はプレビューと照合したかを preview_checked に、不一致で中止したときは outcome: preview_mismatch を残す。ハッシュの値は残さない)。ID・必須項目の形式や作品名・機種の有効性の検証で 400 になったリクエストは記録しない(作品名の作成・改名・有効/無効の切り替えはサービス側で検証するため、失敗も invalid / no_change 等の outcome で記録する)。