note liker

教材本文(公開準備中・試し読み)

自作自動化システムの構築・運用マニュアル

この教材は、現在のnote-likerの実装をもとに、自分のCloudflareアカウントへ構築して運用するための草稿です。配布用ソースの整備、新規環境での再現検証、更新とサポートの条件は販売開始前に確定します。

1. 完成するシステムの全貌(できること・できないこと)

構築するのは、現在のnote-likerと同じ構成のWebアプリケーションです。 ブラウザでnoteアカウントと探索するテーマを設定し、Cloudflare上で処理を実行して、結果を画面から確認します。

Web画面とAPIはNext.js/TypeScript、自動実行のWorkerはJavaScriptです。 既存のTypeScriptの通信処理と自動化エンジンをWorkerからも利用します。

ブラウザ
  ↓ ログイン・設定・手動実行・ログ確認
Web Worker(Next.js / OpenNext)
  ├─ Better Auth / Google OAuth
  ├─ D1:ユーザー・設定・暗号化した認証情報・実行履歴
  └─ Queues:実行ジョブを投入
                       ↓
Automation Worker(JavaScript)
  ├─ Cron:有効なスケジュールを読み、ジョブを投入
  ├─ Queue Consumer:対象を選定して処理
  ├─ Durable Objects:同時実行とリクエスト頻度を調整
  └─ D1:送信前の予約と実行結果を記録

教材Bでは、この仕組みを自分のCloudflareアカウントに構築し、設定と保守を自分で行います。 サービスCでは、運営が用意したnote-likerにログインして利用します。 教材の購入に、サービスCの利用契約や運営側のCloudflare環境へのアクセスは含まれません。

対象の記事からスキした人を探し、その人の公開記事を選びます。 現在の実装は「公開記事があるか」を調べますが、投稿日時の新しさや内容への共感を自動判定するものではありません。 スキやフォロワーの増加、アクセス制限を受けないことは保証しません。

2. 必要な知識・環境・運用費用

JavaScriptの非同期処理、TypeScriptの型、HTTP、SQL、ターミナル操作の基礎を前提にします。 開発用PCにNode.jsと、プロジェクトの packageManager に指定されたpnpmを用意します。 依存パッケージのバージョンは、配布版の package.json と pnpm-lock.yaml を基準にします。

実行環境としてCloudflareのWorkers、D1、Queues、Durable Objectsを使います。 ブラウザのログインにはGoogle OAuthの設定が必要です。 現行コードの本番認証初期化はStripe設定も必須としているため、個人利用だからStripe設定を省略できるわけではありません。

教材代と、Cloudflareなどの稼働費用は別です。 利用する機能と使用量に応じた料金を契約前に確認してください。 無料枠内で動くことや、月額費用が必ずゼロになることは約束しません。

3. セットアップとCloudflareへの配置

最初に配布版のソースを展開し、そのルートで依存関係をインストールします。

pnpm install --frozen-lockfile
pnpm lint
pnpm typecheck
pnpm test
pnpm build:cloudflare

最後のコマンドはWeb側のCloudflare向け成果物をビルドします。公開は行いません。

実装を読む入口は次のファイルです。

役割ファイル
Web画面とAPIsrc/app/
ログインとサービスの課金src/lib/server/auth.ts
noteとの通信src/lib/noteClient.ts
対象選定と送信src/lib/automation/likeEngine.ts
DBスキーマと変更履歴src/db/schema.ts、drizzle/
自動実行の入口workers/src/index.js
定期実行workers/src/scheduler.js
ジョブ実行workers/src/likeConsumer.js
レート制御と実行leaseworkers/src/do/rateCoordinator.js
Web側のCloudflare設定wrangler.jsonc
自動実行側のCloudflare設定workers/wrangler.toml

Cloudflareへの配置は、次の順で進めます。

  1. 自分のアカウントにD1、実行用Queue、失敗したジョブを送るDead Letter Queueを用意します。
  2. 両Workerの設定を、自分のWorker名、D1 ID、Queue名、ドメインに揃えます。Web側と自動実行側は同じD1と実行用Queueを参照します。
  3. Google OAuth、Stripe、認証と暗号化の秘密値を、自分の環境用に設定します。
  4. 配布版のマイグレーションを自分のD1へ適用します。
  5. Web側と自動実行側をそれぞれデプロイします。初回は自動実行を有効にせず、先にログイン、保存、ドライランを確認します。

元の運営環境のD1 ID、ドメイン、秘密情報をそのまま使ってはいけません。 既存の db:migrate:remote などには運営用のリソース名が含まれるため、配布版での置き換えが必要です。 新規アカウント向けのリソース作成からデプロイまでの確定手順は、配布用設定の整備と再現検証後に収録します。

4. 認証情報の登録と暗号化

noteへの送信には、自分のアカウントのセッション情報を使います。 現在の通信処理は _note_session_v5 を使用し、設定がある場合は gql_auth_token も付けます。

ブラウザで自分のnoteにログインし、開発者ツールのApplication → Storage → Cookiesで対象のCookieを確認します。 値は自分で構築したアプリの登録画面へ入力します。 教材への貼り付け、Gitへの保存、ログ出力はしないでください。

アプリは受け取った資格情報をAES-GCMで暗号化してD1へ保存します。 復号に使う CREDENTIALS_ENCRYPTION_KEY は両Workerに同じ値を設定し、ソースから分離します。 鍵を変える際は、現在のコードにあるprevious keyを使った移行手順と、既存データの再保存を確認します。

Web側にはBetter Auth、Google OAuth、Stripe、レート制限などの設定も必要です。 OWNER_USER_ID は実際のBetter AuthのユーザーIDであり、noteのユーザー名ではありません。 現行実装ではこの固定principalに個人利用の課金判定上の例外がありますが、Stripe設定自体を不要にする機能ではありません。

5. ターゲット抽出と書き手の判定

既存の likeEngine.ts を読み、次の処理を追います。

  1. 設定したテーマから起点の記事を探します。
  2. GET /api/v3/notes/{noteKey}/likes でスキ一覧を読み、data.likes[].user を参照します。
  3. ユーザーの urlname を使い、GET /api/v2/creators/{urlname}/contents?kind=note で公開記事を取得します。
  4. data.contents[] に記事キーがあるものから対象を選び、除外条件や履歴を確認します。

この実装は、公開記事一覧に記事があることを確認しています。 「最近投稿した人だけ」「記事を読んで共感した場合だけ」といった判断は、現行コードの動作と区別してください。

スキ一覧はページ指定なしの取得で、全件取得を保証していません。 ページング仕様は未確認のため、推測したパラメータを足して大量取得しないでください。

6. スキ送信とリクエスト頻度の制御

送信は noteClient.ts を経由します。 現行コードの送信先は POST /api/v3/notes/{noteKey}/likes、JSON本文は {} です。 認証CookieとOrigin、Refererなどを付けますが、各ヘッダーが必須かは今回の確認では確定していません。

処理間の待機だけでなく、Durable Objectでアカウント単位の実行leaseとリクエストのトークンを管理します。 手動実行と定期実行が重なった場合も、同じ調整処理を通す設計です。 429を検知した場合は共有クールダウンを設定します。

設定した間隔や上限は、note側の許容頻度を保証する数値ではありません。 実アカウントへの送信テストは、この教材の編集作業では行っていません。

7. D1による履歴管理と重複送信の抑止

送信が終わってから履歴を書くだけでは、送信直後の障害で結果が失われ、再実行で同じ記事へ送ってしまいます。 現行実装は、外部POSTの前にD1の like_intents へ送信予定を確保します。

note_account_id と note_key の一意制約で、同じアカウントから同じ記事への重複した予約を防ぎます。 実行単位の記録は like_logs、個別の処理結果は actions に保存します。

Queueは再配送されることを前提に扱います。 run ID、送信予定台帳、Durable Objectのleaseが、それぞれどの重複を抑止するかを確認してください。 同じ記事への重複抑止と、同じ相手への別記事の送信は別の条件です。

結果が不明な送信を自動でやり直すのは避け、状態を確認します。 障害からの復旧で台帳を消すと、この重複抑止が効かなくなります。

8. 動作確認とログの見方

最初にテストとローカルのモック環境を使い、実アカウントへの送信を伴わずに動作を確かめます。 リポジトリには mocks/note-api/ と scripts/local/smoke.mjs があります。 ローカル用設定を確認してから使用してください。

実環境の初回確認はドライランで進めます。 候補選定、除外条件、上限、ログ保存を確認し、送信しないことを確かめます。 その後、自分が承認した対象と上限で実行を検討してください。

画面では実行一覧と詳細を見て、処理件数、スキップ理由、停止理由を確認します。 障害調査ではWorkerのログも使いますが、認証情報やレスポンス全体を記録しないでください。

9. CronとQueuesによる定期実行

現行の自動実行Workerは、Cron Triggerで毎分スケジュールを確認します。 毎分すべてのアカウントから送信する、という意味ではありません。 D1の有効な設定と実行時刻を確認し、該当するジョブをQueueへ投入します。

Consumerは実行時にも所有権と契約条件を確認し、資格情報を復号して処理します。 Queueメッセージへ認証情報を直接入れない設計です。

Queue名、Consumer設定、Dead Letter Queue、Durable Objectのbindingが揃っていることを確認します。 Webだけをデプロイしても、自動実行Workerが未設定なら定期処理は動きません。

10. 停止とメンテナンス

定期運用を止める際は、対象の自動実行設定を無効にして、新しいジョブの投入を止めます。 ただし、設定の無効化だけで、すでにQueueに入ったジョブや実行中の通信が即座に取り消されるとは限りません。 現行実装に即時キャンセルを保証する機能があるかのようには扱わないでください。

メンテナンス前には新規投入を止め、待機中と実行中のジョブ、実行ログを確認します。 開発用PCのターミナルを閉じても、Cloudflare上の処理は停止しません。 結果不明の送信がある場合は、台帳を保持したまま状態を照合します。

11. トラブルシューティング

症状確認する箇所
ログインできないBetter AuthのURL、Google OAuthのリダイレクト先、必要なsecret
手動実行が受け付けられない所有権、資格情報、契約状態、固定オーナーIDの設定
設定した時刻に動かないCron、D1の有効設定と時刻、QueueのProducer/Consumerの接続
401 / 403処理を止め、認証期限、アクセス制限、仕様変更を確認。CSRF不足と断定しない
429停止理由と共有クールダウンを確認。一定時間後の再開成功を保証しない
復号できない両Workerの暗号化鍵、key ID、rotation中のprevious key
同じジョブが再配送されるWorkerエラー、D1保存、run状態、lease、Dead Letter Queue
JSONを読み取れないHTTPステータスとContent-Type、想定したレスポンス構造

12. 仕様変更を察知して修正する手順

まず実行を停止し、通常の閲覧が可能か、ログがどの工程で失敗しているかを確認します。 公開データの読み取りと、認証付きの書き込みを分けて調査してください。

通信仕様の修正は noteClient.ts、対象選定の修正は likeEngine.ts を入口にします。 リクエストのURL、Cookie、ヘッダー、JSONの構造を確認し、モックと回帰テストを更新してからドライランを行います。 認証情報を含む通信記録を、そのまま問い合わせやGitへ貼り付けないでください。

Webと自動実行Workerが共通コードを使うため、変更に応じて両方のビルドとデプロイを確認します。 本番更新前に差分と対象リソースを確認し、自分のCloudflare設定をバージョン管理します。

13. コード提供・更新・サポートの範囲

教材の対象コードは、現在のnote-likerのTypeScript/JavaScript実装です。 購入者自身のCloudflare環境で構築できるよう、配布用ソースと設定例を整える方針です。 運営の秘密情報、顧客データ、本番リソースへのアクセスは提供対象に含めません。

配布方法、ライセンス、教材の更新期間、個別サポートの有無は販売開始前に確定して表示します。 サービスCと同じ運営保守や無期限の仕様変更対応を、教材Bの購入に含むとは約束していません。