Appearance
有料記事 会話執筆 + WP 下書き投稿
Cursor チャットで有料記事の本文を会話しながら磨き、Markdown に保存したあと、ローカルスクリプトまたは MCP update_paid_article_draft で WordPress に 下書き(draft)のみ反映する手順です。公開は管理画面で人間が行います。公開後の週次更新では、公開済み記事の固定セクション meta だけを REST で更新できます(公開済み記事の固定セクションを週次更新する)。
役割分担(Issue #3494):
| 経路 | できること |
|---|---|
paid-article-tools MCP(データ取得) | 差枚・イベント・充足確認・月次同期 |
update_paid_article_draft MCP | 既存 draft の固定+可変セクションを Markdown からマージ更新(WP-CLI) |
create-paid-article-draft.mjs(--post-id なし) | REST で 新規 draft 作成(要 .env.local。可変セクション付きは不可) |
create-paid-article-draft.mjs --allow-published | 公開済み記事の固定セクション meta だけを REST で週次更新(Issue #3932) |
| 汎用 WordPress MCP サーバー | 未導入(CPT の REST 口は用意済み。設計は CPT-002) |
| 可変セクション | md の ## variable_section を WP-CLI / MCP で反映(title 照合) |
関連 Skill: .cursor/skills/paid-article-conversational-draft/SKILL.md
設計(REST): CPT-002 有料記事テンプレート構成設計
データ取得 MCP: mcp-paid-article-tools.md
初回セットアップ
- 接続先の WordPress で、管理者が「有料記事編集bot ユーザーの準備」に沿って bot ユーザーとアプリケーションパスワードを用意する
- リポジトリルートで:
bash
cp .env.local.example .env.local.env.localを編集する:
| 変数 | 内容 |
|---|---|
WP_SITE_URL | サイト URL(末尾スラッシュなし可)例: http://slotkouryaku.local |
PAID_ARTICLE_WP_USERNAME | 有料記事編集bot ユーザーのユーザー名 |
PAID_ARTICLE_WP_APP_PASSWORD | 有料記事編集bot ユーザーのアプリケーションパスワード(スペース込みで可) |
WP_USERNAME(旧) | PAID_ARTICLE_* が未設定のとき、下書きの作成・更新だけこのユーザーで接続する。--allow-published では使わない |
WP_APP_PASSWORD(旧) | 同上のアプリケーションパスワード |
PAID_ARTICLE_* は 2 つとも設定してください(片方だけだとエラーで止まり、旧変数には戻りません)。旧変数で接続したときは、切り替えを促すメッセージを表示します。
.env.local は .gitignore 対象です。コミットしないでください。
有料記事編集bot ユーザーの準備
スクリプトが REST で接続するユーザーは、人のアカウントではなく有料記事編集bot(paid_article_editor_bot)専用のユーザーにします。bot が叩く WordPress(ローカル・本番それぞれ)で、管理者が次の作業をします。
できること・できないこと
| 操作 | 有料記事編集bot |
|---|---|
| 有料記事の下書きの作成・更新(REST) | できる |
| 公開済み有料記事の固定セクション meta の更新(REST) | できる |
| 公開済み有料記事の status・タイトル・本文・スラッグの変更(下書きへの戻しを含む) | できない(403) |
| 有料記事の公開・削除 | できない |
| 有料記事以外の書き込み(日別記事・投稿・メディア、自分のプロフィールやアプリケーションパスワードの変更、XML-RPC) | できない(403) |
| wp-admin を開く | できない |
ロールが持つのは WordPress の read と有料記事の edit_paid_articles / edit_others_paid_articles / edit_published_paid_articles だけです(ADM-035 権限管理画面)。REST の書き込みは POST /wp/v2/paid_article と POST /wp/v2/paid_article/{id} に限り、公開済み記事には meta 以外を送れません(CPT-002 の「有料記事編集bot(週次更新)」)。
この制限がかかるのは paid_article_editor_bot と他の REST 用 bot ロールだけを持つユーザーです。編集者などのロールを足すと制限が外れます。日別記事編集bot も REST 用 bot ロールですが、兼用すると両方のロールに許可した書き込み(PUT /wp/v2/paid_article/{id} や日別記事 REST)が通るようになります。どちらとも兼用しないでください。
ログインパスワードには注意してください。 上の制限はアプリケーションパスワードでのアクセスと wp-admin が対象です。有料記事編集bot ユーザーのログインパスワードは長いランダムな値にし、どこにも保存せず使わないでください。bot にはアプリケーションパスワードだけを渡します。
1. 有料記事編集bot ユーザーを作る
- 管理画面 → ユーザー → 新規追加
- ユーザー名は用途が分かる名前にする(例:
paid-article-bot)。メールアドレスは管理者が受け取れるものにする - 権限グループで「有料記事編集bot」を選んで追加する
- パスワードは長いランダムな値を生成したまま、控えずに追加する
2. アプリケーションパスワードを発行する
有料記事編集bot ユーザーは wp-admin を開けず、自分でアプリケーションパスワードを発行できないため、管理者が発行します。
- 管理者でログイン → 管理画面 → ユーザー → 有料記事編集bot ユーザーの編集画面
- 「アプリケーションパスワード」で名前(例:
paid-article-weekly)を入れて追加する - 表示されたパスワード(
xxxx xxxx xxxx xxxx xxxx xxxx)を.env.localのPAID_ARTICLE_WP_APP_PASSWORDに書く。この画面を閉じると二度と表示されません
アプリケーションパスワードは HTTPS のサイトでのみ使えます(ローカルの HTTP では wp-config.php に define( 'WP_ENVIRONMENT_TYPE', 'local' ); が必要)。
3. 権限を確かめる
bash
SITE=https://example.com
AUTH='paid-article-bot:xxxx xxxx xxxx xxxx xxxx xxxx'
ID=26043 # 公開済みの有料記事
# 200 になる(読み取り)
curl -sS -o /dev/null -w '%{http_code}\n' -u "$AUTH" "$SITE/wp-json/wp/v2/paid_article/$ID?context=edit"
# 403 になる(公開済み記事のタイトル変更は拒否される)
curl -sS -o /dev/null -w '%{http_code}\n' -u "$AUTH" -H 'Content-Type: application/json' \
-d '{"title":"test"}' "$SITE/wp-json/wp/v2/paid_article/$ID"
# 403 になる(有料記事以外の書き込みは拒否される)
curl -sS -o /dev/null -w '%{http_code}\n' -u "$AUTH" -H 'Content-Type: application/json' \
-d '{"title":"test","status":"draft"}' "$SITE/wp-json/wp/v2/posts"4. 失効させる
bot をやめるときは、管理者が同じ画面で該当のアプリケーションパスワードを「取り消す」。
5. 漏れた疑いがあるとき
漏れたパスワードでできるのは、有料記事の下書きの作成・更新と、公開済み記事の固定セクションの書き換えです(公開・削除・タイトルや本文の変更はできません)。次のどちらかを行ってください。
- アプリケーションパスワードをすべて取り消し、ログインパスワードもリセットする
- 有料記事編集bot ユーザーを削除して作り直す(確実)
そのうえで、公開済み有料記事の固定セクションに見覚えのない変更がないか、記事の表示で確認します(meta の変更はリビジョンに残りません)。
会話執筆の始め方
Cursor チャットで例えば次のように依頼します。
- 「2026年8月号の有料記事を一緒に書きたい」
- 「paid-article draft」
Agent は Skill に従い、欄ごと(冒頭挨拶 → …)に推敲します。完成したら次のパスへ保存します。
text
docs/_drafts/paid-article/YYYY-MM.md実ファイル(*.md)は gitignore されます。形式の例は docs/_drafts/paid-article/2026-08.example.md を参照してください。
Markdown の要点
markdown
---
issue_number: 8
target_month: 2026-08
---
## paid_article_intro
8月号をご覧いただきありがとうございます。随時、本編と更新履歴に追記していきます。
**今号で書いていること**
- エスパス「前々前世」の導入結果と狙い方
## paid_article_month_diff.comment_before
グラフ前考察です。
- 要点A
- 要点B
## paid_article_month_diff.comment_after
グラフ後考察です。target_monthは必須(YYYY-MM)paid_article_intro(冒頭挨拶): 簡潔な挨拶+ベネフィット →(指示があれば)今後の方針 →今号で書いていることの 1 行見出し →(追記時)PaidArticle__changelog折りたたみ更新履歴。日付ログと案内一覧の役割分担は Skill paid-article-conversational-draft(Issue #3707 / #3650)。店舗略称はテンプレ自動表示のため手書きしない- 読者向け本文にショートコード名・属性名・Issue 番号を書かない(例:
before_period_start)。埋め込み用の単独行ショートコードは残してよい。期間の説明は「7/1〜増台前日」など日本語のみ。要点 memo にも属性名を書かない - 機種の強さは平均差枚で判断する(合計だけで「大勝ち」と書かない)。同じホールの他機種との店内位置・先月の上位帯の目安・延べ台数での少台数除外など、詳細は Skill paid-article-conversational-draft の執筆ルールを参照
- 考察本文は プレーン/簡易 Markdown 推奨(空行=段落、
-/*リスト、**太字**、[text](url)、WP ショートコード行。makkは開き〜閉じの複数行可)。反映時にprepareCommentHtmlForMeta()が HTML 化し、続けて改行除去(Issue #3419)する - 既存の
<p>/<ul>など HTML も可(変換スキップ+正規化のみ) - HTML 型欄(
intro/next_machine_comment)は変換後の本文をhtmlに包んで POST する - JSON 型欄は
.comment_before/.comment_after見出し、または単一見出し本文=comment_before、または JSON オブジェクト全文 - Gutenberg ブロックコメントは使わない
可変セクション(## variable_section)
markdown
## variable_section
### title
エスパス「前々前世」
### content
本文(プレーン/簡易 Markdown)。ショートコードは単独行に。- WP-CLI / MCP(
--post-id)反映時のみ。title が一致する既存を更新、無ければ最後の固定セクション(type=fixed)の直後へ新規追加(既存 optional があればその前) - REST 新規作成(
--post-idなし)では可変セクション付き Markdown はエラー
JSON メタと改行(Issue #3419)
WordPress の update_metadata() は保存前に wp_unslash()(実質 stripslashes)する。wp_json_encode が HTML 内の実改行を JSON 上の \n(バックスラッシュ + n)にすると、スラッシュなしで update_post_meta すると \n → n に潰れ、フロントにリテラルな n / nn が出る。
| 経路 | 対策 |
|---|---|
| 管理画面保存 | PostMetaJsonUtil::update_json_value()(update_post_meta 直前に wp_slash) |
| REST | WP_REST_Meta_Fields が update_metadata 前に wp_slash。sanitize_callback は unslash 済み JSON をそのまま返す |
| 下書きスクリプト | prepareCommentHtmlForMeta()(プレーン→HTML 後に normalizeHtmlForMeta() でタグ間改行除去)から POST(防御) |
| 表示(応急) | PaidArticleSectionMetaUtil::normalize_comment_newlines() がブロック級タグ境界の孤立 n〜nnnn を除去 |
register_post_meta の sanitize 戻り値に wp_slash してはいけない(sanitize 後は再 unslash されず、スラッシュ付き JSON が DB に残る)。古い meta に </p>nn<p> が残っている場合は再 POST か手動修正。
下書き投稿・更新
既存 draft を更新する(推奨・Application Password 不要)
bash
node scripts/create-paid-article-draft.mjs --post-id=26043 --via=wp-cli \
docs/_drafts/paid-article/2026-08/2026-08.mdMCP からも同じ処理を呼べます(Cursor の paid-article-tools):
- Tool:
update_paid_article_draft - 引数:
post_id,markdown_path
--via=wp-cli は WP_ROOT / WP_CLI_BIN(環境変数または config/local.env)を使います。status は draft のまま変更しません。可変セクション(## variable_section)も title 照合で更新・新規作成します。
初週のデータ比較・増台・減台の結果・新台出玉推移の kishu_list に機種マスタ ID として解釈できない値(機種名など)があると、REST(400)と同じ文言でエラー終了し、何も保存しません(kishu_list が配列でない場合も同様)。保存済みの値をそのまま送り返す場合は通ります。
新規 draft を作成する(REST)
bash
node scripts/create-paid-article-draft.mjs docs/_drafts/paid-article/2026-08/2026-08.md成功すると投稿 ID と編集画面 URL が表示されます。スクリプトは常に status: draft のみ送り、publish は送りません。新規作成時は comment_status: open も送ります。要 .env.local(Application Password)。
内部では次の二段で投稿します。
title/target_month/issue_numberのみで POST(InitialContentHooksが halls 等の初期デフォルトを入れる)- 初期化後の meta を GET し、Markdown の考察本文をマージして更新
同じ Markdown を --post-id なしで再実行すると 新しい draft が毎回増えます。既存号の反映は --post-id または MCP update_paid_article_draft を使ってください。
公開済み記事の固定セクションを週次更新する(REST)
公開後の週次更新では、--allow-published を付けて固定セクションだけを書き換えます。
bash
node scripts/create-paid-article-draft.mjs --post-id=26043 --via=rest --allow-published \
docs/_drafts/paid-article/2026-08/2026-08.md--post-idと REST(--via=rest。省略時は--allow-publishedがあれば REST)の組み合わせでだけ使えます.env.localのPAID_ARTICLE_WP_USERNAME/PAID_ARTICLE_WP_APP_PASSWORD(有料記事編集bot)が必須です。旧変数WP_USERNAMEには戻りません- 送るのは Markdown の
## paid_article_*から作った固定セクション meta だけです。status・タイトル・本文・スラッグと、号数・対象月(パーマリンクに関わる)は送りません - 既存の meta に Markdown の考察をマージして送るため、グラフ用の設定(
halls/kishu_list/ 期間など)は Markdown に書いた項目以外そのまま残ります - 号数・対象月は WordPress 側でも拒否します(有料記事編集bot が公開済み記事の
metaに含めると 403) - 更新後に WordPress が投稿を保存し直すため、本文(codoc 同期用)と codoc 側にもその場で反映されます。管理画面で再保存する必要はありません
- 更新後に status が
publishのままか確かめ、変わっていたらエラーで止まります - 可変セクション(
## variable_section)と並び順(sections_order)は REST に登録していないため対象外です。Markdown に可変セクションがあるとエラーになります。公開済み記事の可変セクションは管理画面で直してください --allow-publishedを付けずに公開済み記事を指定するとエラーで止まります(下書き用の更新が公開済み記事を書き換えないように)。下書きを指定した場合は付けていても従来どおりの下書き更新になります
管理画面での確認・公開
- 表示された編集 URL を開く
- 固定セクション・オプションセクション・並べ替えを確認する
- 必要なら編集画面の AI 生成(ADM-028 指針)で追記する
- 問題なければ 公開(スクリプトでは行わない)
本番への持ち運び(考察 Markdown)
下書きの考察移しには MCP や本番 REST(Application Password)直投稿は使いません。考察本文だけを Markdown で人手移しします。グラフ用データ meta は上書きしません。公開後の週次更新は例外で、上の「公開済み記事の固定セクションを週次更新する(REST)」を使います。
- 本番に対象月の
paid_article下書きを用意する(グラフ等は本番データで生成してよい) - 移す側(ローカル WP または Cursor の
docs/_drafts/paid-article/YYYY-MM/YYYY-MM.md)で考察を完成させる - ローカル WP の編集画面「考察の持ち運び」から クリップボードにコピー または Markdown をダウンロードする(Cursor の md をそのまま使っても可)
- 本番の下書き編集画面「考察の持ち運び」に貼り付け/ファイル読み込みし、下書きに反映する(EP-024 / EP-025)。front matter の号数・対象月は投稿 meta を書き換えない
- 画面が再読み込みされたら内容を目視し、人間が 公開する
反映・エクスポートの前に投稿を保存してください。未保存のエディタ内容はエクスポートに含まれません。
公開済み記事へのインポートは拒否されます。特定機種結果(SMR)は対象外です。
トラブルシュート
| 症状 | 確認 |
|---|---|
.env.local が見つかりません | リポジトリルートに .env.local があるか(REST 新規作成時) |
WP_ROOT が未設定 | config/local.env または環境変数(--via=wp-cli / MCP 更新時) |
API エラー HTTP 401 | ユーザー名 / Application Password |
API エラー HTTP 403 | ユーザーに paid_article 編集権限があるか |
HTTP 403: 公開済みの有料記事は固定セクション(meta)以外を変更できません | 有料記事編集bot で公開済み記事に meta 以外(status / title 等)や号数・対象月の meta を送った(paid_article_bot_published_meta_only)。スクリプトは --allow-published で meta だけ送る |
HTTP 403: このアカウントは有料記事の下書き・固定セクション更新以外の書き込みはできません | 有料記事編集bot で /wp/v2/paid_article 以外に書き込んだ(paid_article_bot_write_forbidden。記事削除・メディア・プロフィール等) |
公開済みの投稿です で止まる | 公開済み記事を --allow-published なしで指定した。週次更新なら付けて再実行 |
--allow-published には ... PAID_ARTICLE_WP_USERNAME | .env.local に有料記事編集bot の資格情報を設定する(旧 WP_USERNAME は使わない) |
WP-CLI が失敗しました: ... kishu_list に機種マスタ ID として解釈できない値があります | Markdown から送る kishu_list に、保存済みでない機種名などが入っている。機種マスタ ID(正の整数)に直して再実行(何も保存されていない) |
WP-CLI が失敗しました: ... kishu_list は機種マスタ ID(正の整数)の配列で指定してください | Markdown から送る kishu_list が配列ではない("101,202" のような文字列など)。[101, 202] の形に直して再実行(何も保存されていない) |
rest_no_route / 404 | show_in_rest の CPT 登録・パーマリンク・サイト URL |
| meta が空になる | 見出しが paid_article_* か、JSON 型は文字列 JSON になっているか |
| 下書きが複数できた | --post-id なしの再実行は毎回新規 POST。既存更新は --post-id または MCP |
段落の間に n / nn が出る | Issue #3419。管理画面は update_json_value の wp_slash、REST はコア側の wp_slash。スクリプトはタグ間改行を正規化。表示側もブロック境界の孤立 n を除去。古い meta は再 POST か手動修正 |
非対象
- 汎用 WordPress MCP サーバー本体の導入(REST 口は CPT 側で用意済み。Cursor からの書き戻しは上記スクリプト /
update_paid_article_draft) - 本番 MCP / 本番 REST による自動投稿・自動公開(公開済み記事の固定セクション meta を
--allow-publishedで更新することだけは行う。公開・タイトルや本文の変更・可変セクションはしない) - 特定機種結果(SMR)オプションの REST / MCP 一括作成(可変セクションは WP-CLI / MCP 反映可)