Appearance
API-001-19 日別記事bot編集 REST
概要
日別記事のブロックエディタで編集している項目(考察日・ホール・イベント・〇〇の日・ホール別考察・絵文字)を、管理画面を開かずに日別記事編集bot(daily_article_editor_bot)がアプリケーションパスワードで読み書きするための REST。名前空間は daily-article/v1(API-001 と同じ)。
| ID | パス | メソッド | 内容 |
|---|---|---|---|
API-001-19 | /articles | GET | 考察日の範囲で日別記事を一覧する |
API-001-20 | /articles/{id} | GET | 1 記事の編集項目を取得する |
API-001-21 | /articles/{id}/fields | POST | 編集項目を部分更新する |
API-001-22 | /articles/{id}/trash | POST | 未公開の日別記事をゴミ箱へ移す |
API-001-23 | /events | GET | ホールのイベント候補を取得する |
API-001-24 | /events | POST | イベントマスタへ登録する(同名は既存 ID) |
API-001-25 | /what-day-master | POST | 〇〇の日マスタへ登録する(同名は既存 ID) |
API-001-26 | /birthdays | GET | 前後 N 日の誕生日とホール別の台数を返す |
API-001-27 | /birthdays/search | GET | 声優名・キャラ名で誕生日を検索する |
bot が触れるのは日別記事の下表の項目(DailyArticleFieldsUpdateServiceInterface::ALLOWED_KEYS)だけ。本文・タイトル・ステータス・公開日時・投稿者・ほかの投稿タイプは変更できない。日別記事の show_in_rest は false のままで、WordPress コアの /wp/v2/ からは扱えない。
API-001-26 / 27 は考察の材料にする誕生日データの読み取り専用ルート(Issue #3995)。誕生日マスタの保守(紐付けの追加など)は誕生日紐付け担当の API-008 で行い、本 API からは書き込めない。
〇〇の日 の候補と機種名の候補は既存の API-001-5・API-001-8 を使う(同じ権限で呼べる)。
編集項目
値の正規化(サニタイズ・保存形式)は管理画面の DailyArticleBlockEditorSaver と同じ DailyArticleFieldNormalizer で行う。管理画面は不正値を読み飛ばすが、本 API は 1 つでも不正があれば何も書かずに 400 を返す。
| キー | 型(入力) | 検証 | 空にしたとき |
|---|---|---|---|
kousatsu_date | string YYYY-MM-DD | 実在する日付。変えるとき、保存済みの espasu_what_day_manual が新しい考察日の候補に無ければ espasu_what_day_manual も送らないと 400(黙って消さない) | 空にできない |
halls | string[](カンマ区切り文字列も可) | 日別記事で使えるホール(HallEnum::is_available_for_daily_article())。重複は 1 つにまとめる | meta を削除 |
daily_article_events | { ホール: イベントID[] } | ホールは記事の halls(同じリクエストで halls も送ればその値)に含まれること。ID はイベントマスタに存在し、ホールが一致すること。1 ホール 20 件まで | meta を削除 |
espasu_what_day_manual | string[] | 考察日の「〇〇の日」候補(API-001-5 と同じ WhatDayMasterRepository::select_visible_by_mmdd)にある名前だけ | 空配列で保存 |
island_pre 等 6 項目 | string(HTML) | 文字列であること。管理画面と同じ sanitize_post_field のあと、unfiltered_html を持たないユーザー(日別記事編集bot 等)は wp_kses_post を通す | 空文字で保存 |
island_emoji 等 3 項目 | string | 文字列・500 文字以内。管理画面と同じ EmojiTextSanitizer | 空文字で保存 |
ホール別考察は island_pre / island_after / espasu_pre / espasu_after / bigapple_pre / bigapple_after、絵文字は island_emoji / espasu_emoji / bigapple_emoji。
イベントは ID だけを受け取り、保存する名前はイベントマスタから引き直す(保存形式は管理画面と同じ { ホール: [ { id, name } ] })。
ホール別考察の KSES は管理画面の保存(DailyArticleBlockEditorSaver)にも同じくかかる。WordPress コアが unfiltered_html を持たないユーザーの post_content を保存時に KSES へ通すのと同じ扱いで、そうしたユーザー(編集者権限を絞ったアカウント・マルチサイトの管理者以外など)が管理画面で保存すると、6 項目に既に入っている <iframe>・<script> 等の許可されていないタグや属性はその保存で消える。unfiltered_html を持つ管理者の保存では変わらない。wp_kses は HTML コメントを残すため、ブロックコメント(<!-- wp:... -->)・data-* 属性・エディタで使うブロック(段落・画像・埋め込み・ショートコード・コード・グループ・Cocoon 白box)のマークアップは変わらない。ブロックエディタの文字色(<mark style="background-color:rgba(...)">)が消えないよう、KSES の間だけ color / background-color の rgb() / rgba() 値を許可する(DailyArticleFieldNormalizer::allow_rgb_color_css)。
公開済み記事の考察日の検証
公開済み記事は保存のたびに ValidationHooks(save_post 20 / 25)が考察日を検証し、未入力・日付として読めない・別の公開済み日別記事と重複のいずれかなら下書きに戻す。本 API は保存前に同じ判定で止める。
| 条件 | HTTP | message |
|---|---|---|
保存済みの考察日が DailyArticleDateUtil::normalize_date() で読めない(kousatsu_date を送っていない) | 400 | FIELDS_INVALID(errors に KOUSATSU_DATE_STORED_INVALID) |
保存後の考察日が別の公開済み日別記事と重複する(考察日を変えないときも確認する。判定は DailyArticlePublishedDateDuplicateFinder を ValidationHooks と共有) | 409 | KOUSATSU_DATE_DUPLICATE |
| 事前確認をすり抜けて保存後にステータスが変わった(項目は保存済み。下の「保存後に下書きに戻された場合」) | 409 | STATUS_REVERTED(saved: true) |
旧形式(2026-1-5 など)でも normalize_date() で読める考察日なら、ほかの項目だけを更新できる。
重複の事前確認は、公開済み記事を実際に保存するときだけ行う(値が変わらず保存しないリクエストでは ValidationHooks も動かないため確認しない)。考察日を変えない保存でも ValidationHooks は毎回重複を検証するので、考察日の変更有無では省かない。
保存後に下書きに戻された場合(STATUS_REVERTED)
事前確認と保存の間に別の記事が同じ考察日で公開された場合など、まれに事前確認をすり抜けることがある。このとき送った項目は保存済みで、記事は ValidationHooks の考察日の検証(重複・不正な日付)によって下書きに戻っている。同じリクエストを再送すると、値は保存済みなので changed_keys が空の 200 が返り、成功したように見えるが記事は下書きのまま。
- bot は再送しない。人が管理画面で考察日を確認・修正(重複の解消など)してから公開し直す
- 409 のボディに次を返す。監査ログにも
outcome: saved_but_reverted・before_status/after_status・文字数を残す
| field | 型 | 説明 |
|---|---|---|
success | bool | false |
message | string | DailyArticleBotRestCopy::STATUS_REVERTED |
saved | bool | true(項目は保存済み) |
id | int | 記事 ID |
status | string | 今のステータス(通常 draft) |
previous_status | string | 保存前のステータス(publish) |
changed_keys | string[] | 保存したキー |
modified_gmt | string | 保存後の modified_gmt |
保存時の副作用
管理画面で保存したときと同じ処理が走るよう、meta は wp_update_post( [ 'ID' => $id ] ) が発火する save_post_daily_article / save_post(どちらも優先度 10)の中で書く。両方に載せ、先に来た方で 1 度だけ書く(保存後のフックが中で wp_update_post を呼んで再発火しても書き直さない)。
WordPress は save_post_daily_article を save_post より先に発火するため、通常は save_post のどのコールバックよりも前に meta を書く。管理画面の Saver は save_post 10 で書くので、ここだけ順序が違う。今回の meta を読む save_post 10 以下のフックは無いため結果は変わらないが、save_post の低い優先度に meta を読むフックを足すときは、bot 経由では meta が先に書かれている前提で作ること。
| フック | 処理 |
|---|---|
pre_post_update | 変更前の考察日を控える(旧日付のキャッシュ無効化用)。入れ子の更新では上書きしない |
save_post_daily_article 10(外されていれば save_post 10) | 本 API が meta を書く。絵文字は専用テーブル(daily_article_emoji)へ同期する |
| meta 更新フック | 考察テーブルの同期、イベント画像の紐付け予約 |
save_post 20 / 25 | 考察日からタイトルを作り直す、公開済み記事の考察日の検証 |
save_post 99 / 100 | link_day の同期、キャッシュ無効化 |
タイトルが変わると save_post 20 の中で入れ子の wp_update_post が走る。pre_post_update の控えは最初の値(変更前の考察日)のまま残り、入れ子の save_post 100 で旧日付・新日付の両方(それぞれ前後日を含む)のキャッシュが消える。外側の save_post 100 は控えが無いため、新しい考察日のキャッシュだけを消し直す。
管理画面の Saver は $_POST['_wpnonce'] が無いため何もしない(二重に書かない)。送った値がすべて今の値と同じときは保存しない(changed_keys は空、modified_gmt は変わらない)。
フックで書けなかった場合(フォールバック)
wp_update_post は成功した(modified_gmt が進んだ)のに、どちらのフックでも書き込みが呼ばれなかったとき(他のフックが書き込み用のフックを外した等)は、wp_update_post の直後に meta を書いて 200 を返す。投稿だけ更新されて meta が古いまま残る状態にはしない。meta 更新フック(考察テーブルの同期・イベント画像の紐付け予約)はこの書き込みで動く。
1 回目の wp_update_post の save_post 20 以降は古い meta のまま終わっているため、save_post が発火していた場合は wp_update_post をもう一度呼び、タイトル生成・検証・キャッシュ無効化を新しい meta で走らせ直す(旧考察日のキャッシュは 1 回目、新考察日のキャッシュは 2 回目で消える)。返す modified_gmt は 2 回目の後の値。
1 回目の検証(ValidationHooks)は古い meta で行われるため、古い考察日が別の公開済み記事と重複している状態を bot が考察日を変えて直そうとすると、新しい値が正しくても 1 回目で下書きに戻り 409(STATUS_REVERTED)になる(監査ログには save_path: fallback が残る)。
save_post 自体が発火していなかった場合(side_effects_rerun = skipped)と、2 回目が失敗した場合(side_effects_rerun = wp_error)は、次の処理が古い meta のままになる。
- 考察日を変えてもタイトルが作り直されない
- 新しい考察日とその前後日のテンプレートキャッシュが消えない(
save_postが発火していなければ、考察本文の Transient も消えない) - 公開済み記事の考察日の検証(
ValidationHooks)が新しい考察日で行われていない(事前の重複確認は済んでいる)
監査ログに "save_path":"fallback" かつ side_effects_rerun が done 以外の行が出たら、その記事を管理画面で開いて更新し直す(タイトル・キャッシュ・検証が管理画面保存として走る)。
監査ログの save_path で書いた経路を区別する。typed_hook_fired / save_post_fired は原因の切り分け用で、wp_update_post の前後で did_action() の回数が増えたかを記録する。did_action() はリクエスト全体の回数のため、保存後のフックが中で呼ぶ wp_update_post や別投稿の保存でも増える(「対象記事のフックが発火した」ことまでは保証しない)。
save_path | 意味 |
|---|---|
hook_typed | save_post_daily_article の中で書いた(通常) |
hook_save_post | save_post_daily_article では書かれず、save_post の中で書いた |
fallback | どちらでも書かれず、wp_update_post の直後に書いた |
wp_update_post が WP_Error を返したときは 500(UPDATE_FAILED、reason: wp_error)で、meta は書かない。エラーコード(get_error_code())は監査ログの error_code にそのまま出す。レスポンスの error_code には、wp_insert_post が返すコアのコード(DailyArticleBotRestController::PUBLIC_WP_ERROR_CODES: invalid_post / invalid_page_template / invalid_date / empty_content / db_update_error)だけをそのまま出し、それ以外(コアの将来の変更などで想定外のコードが来た場合)は other にする。
wp_insert_post は投稿行を UPDATE した後、save_post を発火する前に page_template(ID だけ渡しても get_post( ARRAY_A ) から保存済みの _wp_page_template が引き継がれる)をテーマのテンプレート一覧と照合する。テーマは 1 階層下までしかテンプレートを探さないため、TemplateHooks が保存する myCustom/myTemplate/single-daily-article-template.php と旧値 myTemplate/single-daily-article-template.php は PageTemplateRegistry の対応表に載せ、PageTemplateRegistryHooks(theme_templates フィルタ)で一覧に加えている。外すと invalid_page_template で modified だけ進む 500 になる。一覧が空でなくなると編集画面にテンプレート選択(ページ属性)が出るため、pageparentdiv メタボックスは外している(値は保存時に auto_set_template が上書きする)。
API-001-19 GET /articles
入力(リクエスト)
| param | 必須 | 型・制約 | 説明 |
|---|---|---|---|
date_from | はい | YYYY-MM-DD | 考察日の開始(含む) |
date_to | はい | YYYY-MM-DD(date_from 以上・開始から 31 日以内) | 考察日の終了(含む) |
status | いいえ | publish / draft / pending / future のカンマ区切りか配列 | 絞り込むステータス(省略時は 4 つすべて) |
出力(レスポンス)
| field | 型 | 説明 |
|---|---|---|
success | bool | true |
items | array | { id, title, kousatsu_date, status, author_id, author_name, modified_gmt }[]。考察日の昇順、最大 200 件 |
HTTP ステータス: 200
API-001-20 GET /articles/{id}
出力(レスポンス)
| field | 型 | 説明 |
|---|---|---|
success | bool | true |
article | object | id / title / status / modified_gmt と編集項目 13 個。halls と espasu_what_day_manual は配列、daily_article_events は { ホール: [ { id, name } ] }(無ければ {}) |
HTTP ステータス: 200
API-001-21 POST /articles/{id}/fields
入力(リクエスト)
JSON オブジェクトのボディ。送ったキーだけ更新する。
| param | 必須 | 型・制約 | 説明 |
|---|---|---|---|
| 編集項目 | 1 つ以上 | 上の「編集項目」 | 許可リスト外のキーが 1 つでもあれば 400(unknown_keys)で何も更新しない |
expected_modified | いいえ | YYYY-MM-DD HH:MM:SS(T・末尾 Z 可) | 最後に取得した modified_gmt。現在の値と違えば 409 で何も更新しない(ほかの人の編集を上書きしないため) |
出力(レスポンス)
| field | 型 | 説明 |
|---|---|---|
success | bool | true |
message | string | DailyArticleBotRestCopy::UPDATE_SUCCESS |
id | int | 記事 ID |
changed_keys | string[] | 今の値と比べて値が変わったキー(送っても同じ値のキーは含まない) |
modified_gmt | string | 更新後の modified_gmt(次の更新で使う) |
HTTP ステータス: 200
保存に失敗したとき(500)のボディ:
| field | 型 | 説明 |
|---|---|---|
success | bool | false |
message | string | DailyArticleBotRestCopy::UPDATE_FAILED |
reason | string | 失敗の種類。wp_error(wp_update_post が WP_Error を返した)/ unknown(想定外) |
error_code | string | reason: wp_error のときだけ。WordPress コアのエラーコード、またはそれ以外を表す other |
modified_gmt | string | 失敗後の現在の modified_gmt。再送するときはこの値を expected_modified に使う |
API-001-22 POST /articles/{id}/trash
下書き(draft / auto-draft)と承認待ち(pending)の日別記事だけをゴミ箱へ移す。公開済み・予約済みは 409。完全削除はしない(EMPTY_TRASH_DAYS が 0 でゴミ箱が無効な環境では 409)。
出力(レスポンス)
| field | 型 | 説明 |
|---|---|---|
success | bool | true |
message | string | DailyArticleBotRestCopy::TRASH_SUCCESS |
id | int | 記事 ID |
HTTP ステータス: 200
API-001-23 GET /events
入力(リクエスト)
| param | 必須 | 型・制約 | 説明 |
|---|---|---|---|
hall | はい | 日別記事で使えるホール | 対象ホール |
出力(レスポンス)
| field | 型 | 説明 |
|---|---|---|
success | bool | true |
hall | string | ホールキー |
items | array | { id, name }[](ブロックエディタのイベント候補と同じ一覧) |
HTTP ステータス: 200
API-001-24 POST /events
入力(リクエスト)
| param | 必須 | 型・制約 | 説明 |
|---|---|---|---|
hall | はい | 日別記事で使えるホール | 登録先ホール |
name | はい | 1〜255 文字(前後の空白は除く) | イベント名(sanitize_text_field) |
出力(レスポンス)
| field | 型 | 説明 |
|---|---|---|
success | bool | true |
message | string | DailyArticleBotRestCopy::EVENT_REGISTERED / EVENT_ALREADY_EXISTS |
id | int | イベント ID |
hall | string | ホールキー |
name | string | 保存したイベント名 |
created | bool | 新規登録なら true。同じホールに同名が既にあれば false(既存 ID を返し、登録しない) |
HTTP ステータス: 新規登録 201 / 既存 200
API-001-25 POST /what-day-master
〇〇の日 の候補(API-001-5)に無い日を、bot が〇〇の日マスタ(db_what_day_master)へ足すためのルート。登録後に API-001-5 を呼び直せば候補に出る(is_show が true のとき)。
入力(リクエスト)
| param | 必須 | 型・制約 | 説明 |
|---|---|---|---|
name | はい | 1〜255 文字(前後の空白は除く) | 〇〇の日の名前(sanitize_text_field) |
mmdd | はい | MMDD の整数(101〜1231 の実在日。2 月 29 日は可)。数字だけの文字列も受け付ける | 月日(例: 10 月 4 日 → 1004) |
is_show | いいえ | true / false(1 / 0、"true" / "false" も可)。既定 true | 投稿の選択肢(API-001-5)に出すか |
同名が既にあるとき
既存行はテーブルの一意キー(uk_name)と同じ照合順序で探す(大文字・小文字、全角・半角、濁点・半濁点の違いは同名とみなされることがある)。bot がほかの日付の候補を壊さないよう、既存行は表記まで一致したときに次の範囲だけ変える。更新は条件付きの 1 文で行い、変更後の行を読み直して結果を返す。
| 既存行の状態 | 結果(outcome) | HTTP | 既存行への変更 |
|---|---|---|---|
| 表記が違う(例: ハチの日 / パチの日) | name_mismatch | 409 | なし(別名で登録したいときは管理画面で人が行う) |
| 同じ月日・表示中 | exists | 200 | なし(is_show: false を送っても非表示にはしない) |
同じ月日・非表示で is_show が true | updated | 200 | 表示に切り替える |
| 月日が未設定 | updated | 200 | 月日を入れる(is_show: true なら表示にも切り替える。表示中の行は is_show: false でも表示のまま) |
| 別の月日 | mmdd_conflict | 409 | なし(月日の付け替えは〇〇の日マスタ管理画面で人が行う) |
名前・年月日(yyyymmdd)の変更と削除はこのルートではできない。
出力(レスポンス)
| field | 型 | 説明 |
|---|---|---|
success | bool | true |
message | string | DailyArticleBotRestCopy::WHAT_DAY_MASTER_REGISTERED / WHAT_DAY_MASTER_UPDATED / WHAT_DAY_MASTER_ALREADY_EXISTS |
id | int | 〇〇の日マスタ ID |
name | string | 登録済みの名前 |
mmdd | int | 登録済みの月日 |
is_show | bool | 登録済みの表示フラグ |
created | bool | 新規登録なら true |
updated | bool | 同名の既存行の表示・月日を変えたなら true |
HTTP ステータス: 新規登録 201 / 既存(変更あり・なし) 200
409 のボディは { success: false, message, id, name, mmdd }(message は別の月日なら WHAT_DAY_MASTER_MMDD_CONFLICT、表記違いなら WHAT_DAY_MASTER_NAME_MISMATCH。name / mmdd は登録済みの値で、mmdd は未設定なら null)。
API-001-26 GET /birthdays
基準日の前後 N 日の誕生日を、紐付いた機種とホール別の設置台数つきで返す。Controller は DailyArticleBirthdayRestController、集計は DailyArticleBirthdayService。
入力(リクエスト)
| param | 必須 | 型・制約 | 説明 |
|---|---|---|---|
date | はい | YYYY-MM-DD | 基準日 |
days | いいえ | 0〜7 の整数。既定 3 | 基準日の前後 N 日(両端を含む。最大 15 日分) |
halls | いいえ | 日別記事で使えるホール(island / espasu / bigapple)のカンマ区切り。既定 island,espasu | 台数を出すホール。重複は 1 つにまとめる |
include_last_year | いいえ | 1 / 0(true / false も可)。既定 0 | 各機種に去年同日の台数・差枚(last_year)を付ける |
処理
- 範囲の各日の月日で誕生日(
db_birthday)を引く。月・年をまたいでよい。閏年でない年の 2/28 には 2/29 の誕生日も含める(誕生日ピックアップと同じ。Issue #1337) - 紐付いた機種は
db_birthday_kishu(作品名 ↔ 機種)から引く。機種名が空・-の紐付けは除く。紐付けが無い誕生日もkishu: []で返す - 設置台数: ホールごとに「
date以前でデータがある最新日(最大 7 日さかのぼる)」を台数の基準日とし、その日の台数を返す。データがある日とは、summary(db_daily_article_kishu_single_day_summary)か台別(db2023)にそのホールの行がある日- 台数は summary の
countを優先し、summary に無いホール × 機種は台別の行数で埋める(誕生日ピックアップBirthDayMachineResultServiceと同じ規則。Issue #2092) - ピックアップは「記事日〜2 日前・全ホール共通の基準日」だが、bot は取込前の日付で呼ぶことがあるため、基準日をホール別にし、7 日まで広げている。基準日が同じなら台数はピックアップと一致する
- 台数は summary の
- 去年の結果(
include_last_year=1): 各誕生日のdateの 1 年前(2/29 は 2/28)の、その日ちょうどの値(基準日をずらさない)。summary を優先し、無ければ台別(行数・samaiの合計・平均)。誕生日マスタ・紐付けは今の内容で引く(去年の紐付けの履歴は持っていない) - BB/RB・回転数は読まない・返さない
- 月日ごとの誕生日 × 機種の一覧はオブジェクトキャッシュに最大 1 時間残る(公開ページの誕生日ピックアップと共通)。機種の紐付け(
db_birthday_kishu)を変えるとキャッシュは消えるが、誕生日マスタ(db_birthday)・作品名の追加・修正は最大 1 時間遅れて反映される
出力(レスポンス)
| field | 型 | 説明 |
|---|---|---|
success | bool | true |
date | string | 基準日 |
days | int | 前後の日数 |
date_from / date_to | string | 範囲の両端 |
halls | string[] | 台数を出したホール |
count_reference_dates | object | { ホール: 台数の基準日(YYYY-MM-DD)| null }。7 日以内にデータが無いホールは null |
items | array | 誕生日。並びは date 昇順 → birthday_id 昇順(下表) |
items[]:
| field | 型 | 説明 |
|---|---|---|
date | string | 範囲内の日(閏年でない年の 2/29 生まれは 2/28 に入る) |
offset | int | 基準日からの日数(-N〜N) |
birthday_id | int | null | db_birthday.id(今の SQL では null にならない) |
divi | string | キャラ誕 / 声優誕 |
actor | string | 声優名(divi の (声誕) 以降)。キャラ誕は空文字 |
chara | string | キャラ名(声優誕の行は、その声優が演じたキャラ) |
title_id | int | null | 作品名マスタ ID(作品名マスタと INNER JOIN するため、今は null にならない) |
title | string | 作品名 |
kishu | array | { kishu_id, kishu_name, halls: { ホール: { count } }, last_year? }[] |
count: 基準日にそのホールのデータはあるが機種が無ければ0。そのホールに基準日が無ければnulllast_year(include_last_year=1のときだけ):{ date, halls: { ホール: { count, total_samai, avg_samai } | null } }。その日そのホールに該当機種のデータが無ければnull。avg_samaiは小数 1 桁
json
{
"success": true,
"date": "2026-10-10",
"days": 3,
"date_from": "2026-10-07",
"date_to": "2026-10-13",
"halls": ["island", "espasu"],
"count_reference_dates": { "island": "2026-10-09", "espasu": "2026-10-09" },
"items": [
{
"date": "2026-10-08",
"offset": -2,
"birthday_id": 123,
"divi": "声優誕",
"actor": "声優名",
"chara": "キャラ名",
"title_id": 45,
"title": "作品名",
"kishu": [
{
"kishu_id": 678,
"kishu_name": "機種名",
"halls": { "island": { "count": 3 }, "espasu": { "count": 0 } },
"last_year": {
"date": "2025-10-08",
"halls": { "island": { "count": 3, "total_samai": 1234, "avg_samai": 411.3 }, "espasu": null }
}
}
]
}
]
}HTTP ステータス: 200
API-001-27 GET /birthdays/search
声優名・キャラ名の部分一致で誕生日を返す。台数は返さない(必要なら API-001-26 を date 指定で呼ぶ)。
入力(リクエスト)
| param | 必須 | 型・制約 | 説明 |
|---|---|---|---|
q | はい | 1〜50 文字(sanitize_text_field 後、前後の半角・全角空白を除く) | 検索語(部分一致) |
field | いいえ | any / actor / chara。既定 any | actor は divi の (声誕) 以降、chara は chara 列、any は両方 |
limit | いいえ | 1〜50 の整数。既定 20 | 誕生日の件数上限(紐付いた機種の数ではない) |
半角・全角スペースは検索語・列の両方から除いて比べる(「花澤 香菜」と「花澤香菜」を同じにする)。検索結果も API-001-26 と同じくオブジェクトキャッシュに最大 1 時間残る。機種の紐付けを変えると消えるが、誕生日マスタ(db_birthday)・作品名の追加・修正では消えないため、追加したばかりの声優・キャラは最大 1 時間見つからないことがある。
出力(レスポンス)
| field | 型 | 説明 |
|---|---|---|
success | bool | true |
q | string | 前後の空白を除いた検索語 |
field | string | 検索した項目 |
count | int | items の件数 |
items | array | { birthday_id, month, day, divi, actor, chara, title_id, title, kishu: [ { kishu_id, kishu_name } ] }[]。並びは month → day → birthday_id |
- 声優で引いたとき、声優誕の行の
charaが演じたキャラ、month/dayが声優の誕生日 - キャラで引いたときは、キャラ誕の行(キャラの誕生日)と声優誕の行(演じた声優とその誕生日)の両方が返る
- 機種名が空・
-の紐付けはkishuに含めない
HTTP ステータス: 200
失敗・エラー条件
失敗時のボディは { success: false, message }。message は DailyArticleBotRestCopy の固定文言で、入力値や例外メッセージは載せない。
| 条件 | HTTP | message |
|---|---|---|
id が正の整数でない | 400 | ARTICLE_ID_INVALID |
date_from / date_to 不正 | 400 | DATE_RANGE_INVALID |
| 一覧の範囲が 31 日を超える | 400 | DATE_RANGE_TOO_LONG |
status 不正 | 400 | STATUS_INVALID |
| ボディが JSON オブジェクトでない | 400 | BODY_INVALID |
| 更新する項目が無い | 400 | FIELDS_EMPTY |
| 許可リスト外のキーがある | 400 | FIELDS_UNKNOWN(unknown_keys 付き) |
| 項目の値が不正 | 400 | FIELDS_INVALID(errors に項目ごとの固定文言) |
expected_modified の形式不正 | 400 | EXPECTED_MODIFIED_INVALID |
hall 不正 | 400 | HALL_INVALID |
name 不正 | 400 | EVENT_NAME_INVALID / WHAT_DAY_MASTER_NAME_INVALID(API-001-25) |
mmdd 不正(API-001-25) | 400 | WHAT_DAY_MASTER_MMDD_INVALID |
is_show 不正(API-001-25) | 400 | WHAT_DAY_MASTER_IS_SHOW_INVALID |
date / days / halls / include_last_year 不正(API-001-26) | 400 | BIRTHDAY_DATE_INVALID / BIRTHDAY_DAYS_INVALID / BIRTHDAY_HALLS_INVALID / BIRTHDAY_INCLUDE_LAST_YEAR_INVALID |
q / field / limit 不正(API-001-27) | 400 | BIRTHDAY_QUERY_INVALID / BIRTHDAY_FIELD_INVALID / BIRTHDAY_LIMIT_INVALID |
| 未ログイン(Cookie 認証で nonce が無い場合を含む) | 401 | LOGIN_REQUIRED |
| 権限が無い | 403 | WordPress REST 標準の 403 |
| 記事が無い・日別記事でない・ゴミ箱にある | 404 | ARTICLE_NOT_FOUND |
expected_modified が現在の modified_gmt と違う | 409 | EXPECTED_MODIFIED_MISMATCH |
| 公開済み記事の考察日が、別の公開済み記事と同じ日になる | 409 | KOUSATSU_DATE_DUPLICATE |
| 保存後に公開済み記事が下書きに戻された(項目は保存済み) | 409 | STATUS_REVERTED(saved: true。再送しない) |
| ゴミ箱へ移せないステータス | 409 | TRASH_STATUS_CONFLICT |
ゴミ箱が無効(EMPTY_TRASH_DAYS が 0) | 409 | TRASH_DISABLED |
| 同名の〇〇の日が別の月日で登録済み | 409 | WHAT_DAY_MASTER_MMDD_CONFLICT |
| 照合順序で同名だが表記が違う〇〇の日が登録済み | 409 | WHAT_DAY_MASTER_NAME_MISMATCH |
| レート制限を超えた | 429 | Messages::RATE_LIMIT_EXCEEDED |
| 更新・登録・ゴミ箱移動に失敗 | 500 | UPDATE_FAILED / EVENT_REGISTER_FAILED / WHAT_DAY_MASTER_REGISTER_FAILED / TRASH_FAILED |
DB の読み取りに失敗(API-001-26 / 27。0 件と区別するため DatabaseErrorProbeInterface で判定) | 503 | BIRTHDAY_READ_FAILED(例外メッセージは error_log にだけ出す) |
レート制限
ユーザーごとの固定ウィンドウ(60 秒)。BotRestRateLimiter(transient)で数える。
| 対象 | 上限 | カウンタを保存できないとき |
|---|---|---|
| 項目更新(API-001-21) | 60 回 / 分 | 拒否(429) |
| イベント登録(API-001-24) | 20 回 / 分 | 拒否(429) |
| 〇〇の日マスタ登録(API-001-25) | 20 回 / 分 | 拒否(429) |
| ゴミ箱移動(API-001-22) | 20 回 / 分 | 拒否(429) |
読み取り(API-001-19 / 20 / 23 / 26 / 27 の共通。バケット daily-article:read) | 120 回 / 分 | 通す |
API-001-5 / 6 / 8 にはユーザー単位のレート制限を置かない(ブロックエディタからの呼び出しと共通のため)。
監査ログ
読み取り(API-001-19 / 20 / 23 / 26 / 27)は残さない。更新系(API-001-21 / 22 / 24 / 25)は BotRestAuditLogger で error_log に 1 行 JSON(接頭辞 [bot-rest-audit])を書く。リクエストボディ・考察本文・イベント名・〇〇の日の名前は書かない。
| ルート | 記録する項目 |
|---|---|
| 項目更新 | ユーザー ID、post_id、結果(outcome)、送ったキーと変わったキーの件数、変わったキーごとの変更前後の文字数。保存を試みたときは save_path(hook_typed / hook_save_post / fallback)、typed_hook_fired、save_post_fired、フォールバック時は side_effects_rerun(done / skipped / wp_error)、失敗時は failure_reason と error_code |
| ゴミ箱移動 | ユーザー ID、post_id、結果、移動前のステータス |
| イベント登録 | ユーザー ID、ホール、結果(created / exists / failed)、イベント ID、イベント名の文字数 |
| 〇〇の日マスタ登録 | ユーザー ID、結果(created / updated / exists / mmdd_conflict / name_mismatch / failed)、マスタ ID(master_id)、名前の文字数、mmdd、is_show |
権限・nonce
DailyArticleBotRestHandler + WordPressDailyArticleBotPermissionChecker。アプリケーションパスワード(Basic 認証)で呼ぶ前提のため nonce は検証しない。Cookie 認証で nonce が無いリクエストは WordPress コア(rest_cookie_check_errors)が未ログイン扱いにするため、ブラウザのセッションを流用した CSRF にはならない。Local バイパスの条件は WordPressPermissionChecker と同じ。
| ルート | 必要な Capability |
|---|---|
/articles・/events(GET / POST) | edit_daily_articles |
/birthdays・/birthdays/search(GET) | edit_daily_articles(公開中の日別記事の誕生日ピックアップに出ている内容のため。紐付け担当の read_birthday_data は要らない) |
/articles/{id}・/articles/{id}/fields | edit_daily_articles と、その記事の edit_post |
/articles/{id}/trash | 上に加えて trash_daily_article_drafts(ADM-035。既定は日別記事編集bot)または その記事の delete_post |
/what-day-master(POST) | edit_daily_articles と manage_what_day_master(ADM-035。既定は日別記事編集bot。〇〇の日マスタ管理画面の権限とは別) |
記事 ID は権限判定・コントローラーとも URL パスの {id}(WP_REST_Request::get_url_params())だけを読む。ボディやクエリに id を付けても対象は変わらない(get_params() はボディ・クエリが URL より優先されるため使わない)。
記事が存在しない・日別記事でない ID は権限判定を通し、コントローラーが 404 を返す(ほかの投稿タイプの存在を 403 / 404 の違いで推測させないため、日別記事以外は一律 404)。