TileLog Lens は、雀魂 / Mahjong Soul の対局後スクリーンショットから確認した数値を記録する、非公式・個人利用向けの戦績トラッカーです。
累積戦績のスナップショットを保存し、傾向分析、期間差分、CSV/JSONエクスポートを行えます。用途は 対局後の個人記録 に限定しています。
- 自分の対局後・プロフィール戦績スクリーンショットから確認した数値を記録します。
- すべての記録で観測日と
HH:mm形式の時刻を必須にします。 - ローカルスクリーンショットに対して、ブラウザ内だけで任意のOCRを実行し、読み取り位置を調整できます。
- OCR結果は項目ごとに信頼度を表示し、読み取り位置プリセットを保存できます。
- 東風戦、半荘戦、三人戦、その他のゲームモードに対応します。
- 確認済みの数値データを Cloudflare D1 に保存します。
- トレンドチャート、推定期間差分、直近期の成績差分を表示します。
- 直近10件と月単位の期間比較を表示します。
- 最新値と直近期から分析コメントを自動生成します。
- 改善優先度スコアを表示します。
- ゲームモード別にダッシュボードを切り替えられます。
- 任意の2つのスナップショットを比較できます。
- 保存前に、前回記録との不自然な差分、重複候補、段位ポイント上限超過を警告します。
- データ品質レポートで、修正候補の記録を一覧化します。
- データ品質レポートから該当記録の編集画面へ移動できます。
- 記録更新時の変更履歴を保存します。
- インポート履歴に、保存結果、画像ハッシュ、ファイル名、OCR項目数を記録します。
- 表計算向けCSVをエクスポートします。
- 外部AI分析に渡しやすいJSONをエクスポートします。
- CSV/JSONはダウンロード前に画面上でプレビューできます。
- AI用JSONから数値スナップショットを復元インポートできます。
- Cloudflare Access のメールOTPで所有者だけがログインできます。
- 設定ページで所有者情報や基本設定を確認できます。
- スクリーンショット画像をサーバー側に保存しません。
- ゲームクライアントを改変しません。
- 通信内容を解析しません。
- 対局を自動化しません。
- 対局中のリアルタイム助言を提供しません。
- アプリUIに公式ロゴ、キャラクター、著作物アセットを使用しません。
- Yostar または Mahjong Soul / 雀魂 との公式な関係を示しません。
ユーザーが保存済みデータをより柔軟に分析できるように、次の順で機能を拡張します。いずれの機能も、保存済みの数値・メモ・メタデータだけを対象にし、スクリーンショット画像やbase64データは扱いません。
- 期間・条件フィルタの強化: 任意期間、ゲームモード、対戦数範囲、成績条件で分析対象を絞り込めるようにします。
- 比較ビューの拡張: 期間A/B、モード別、好調/不調期間などを比較し、改善・悪化の差分を見やすくします。
- カスタム指標:
和了率 - 放銃率のようなユーザー定義指標を保存し、ダッシュボードで確認できるようにします。 - 分析テンプレート: ラス回避、攻撃/守備、段位pt効率など、目的別レポートを選べるようにします。
- 異常値・変化点検出: 直近データが過去平均から大きくズレた指標を自動検出します。
- グラフの自由選択: 任意の指標を選んで時系列、移動平均、差分表示を切り替えられるようにします。
- メモ・タグ機能: 記録にタグを付け、タグ別の成績比較やAI JSON出力に活用できるようにします。
- AI分析リクエストのカスタマイズ: AI JSONに含める分析目的・注目指標をユーザーが編集できるようにします。
- CSV/JSONエクスポートのカスタム化: 期間、列、匿名化、出力形式を選んでエクスポートできるようにします。
- データ品質ビューの拡張: 欠損、重複、極端値、順位率合計のズレを分析前に確認しやすくします。
本アプリは、雀魂 / Mahjong Soul の非公式・個人用戦績記録ツールです。
対局後の個人記録だけを目的としており、ゲームクライアントの改変、通信解析、自動操作、リアルタイム支援は行いません。
スクリーンショット画像はブラウザ内でのみ処理し、サーバーには保存しません。保存されるのは、ユーザーが確認した戦績数値と任意の画像メタデータだけです。
- React SPA
- Vite
- TypeScript
- Hono
- Cloudflare Workers
- Cloudflare D1
- Cloudflare Access email One-time PIN / OTP
- Zod
- jose
- Vitest
flowchart LR
User["所有者のブラウザ"]
Screenshot["ローカルスクリーンショット"]
SPA["React SPA\nインポート / ダッシュボード / エクスポート"]
OCR["ブラウザ内OCR\nSHA-256 + メタデータ抽出"]
Access["Cloudflare Access\nメールOTP"]
Worker["Cloudflare Worker\nHono API"]
Auth["Access JWT検証\n所有者メール確認"]
Guards["リクエストガード\n画像/base64ペイロード拒否"]
D1["Cloudflare D1\n確認済み数値だけ保存"]
Assets["Cloudflare Assets\nビルド済みSPA"]
Export["ユーザー操作によるCSV / JSON出力\nAI JSONは既定で匿名化"]
User --> SPA
Screenshot --> OCR
OCR --> SPA
SPA --> Access
Access --> Worker
Worker --> Auth
Auth --> Guards
Guards --> D1
D1 --> Worker
Worker --> Export
Worker --> Assets
Screenshot -. "画像本体はブラウザ内に留まる" .-> SPA
SPA -. "スクリーンショットをアップロードしない" .-> Guards
スクリーンショットはブラウザ内だけで処理します。APIリクエストには、確認済みの戦績数値と、必要に応じてハッシュ・画像サイズ・ファイル名などのメタデータだけを含めます。スクリーンショット画像やbase64ペイロードは送信しません。
.
├─ migrations/
│ └─ 0001_init.sql
├─ src/
│ ├─ worker/
│ ├─ web/
│ └─ shared/
├─ tests/
├─ AGENTS.md
├─ DESIGN.md
├─ PLAN.md
├─ README.md
└─ SPEC.mdWorker が想定する環境は次の通りです。
interface Env {
DB: D1Database;
ENVIRONMENT: "development" | "preview" | "production";
OWNER_EMAIL: string;
ACCESS_AUD: string;
ACCESS_ISSUER: string;
ACCESS_JWKS_URL: string;
}wrangler.jsonc の例です。
Cloudflare Access 関連値は公開リポジトリにteam domainを出さないため、Worker secretsとして設定します。
wrangler secret put OWNER_EMAIL
wrangler secret put ACCESS_AUD
wrangler secret put ACCESS_ISSUER
wrangler secret put ACCESS_JWKS_URLwrangler.jsonc には、D1 database ID、本番ホスト名など、デプロイ固有の識別子が含まれる場合があります。
これらは単体ではアプリケーション秘密情報ではありませんが、デプロイ構成のメタデータです。公開リポジトリにする場合は、コミットしたままにするかプレースホルダーへ置き換えるかを確認してください。
次の値はコミットしないでください。
OWNER_EMAILACCESS_AUDACCESS_ISSUERACCESS_JWKS_URL- APIトークン
- Cloudflareアカウントトークン
- 秘密鍵や認証情報
- Cloudflare Zero Trust を開きます。
- 本番ホスト名用の self-hosted Access application を作成します。
- One-time PIN / OTP ログインを有効にします。
- Allow policy を作成します。
- 所有者のメールアドレスだけを許可対象にします。
Everyone、広すぎるドメイン、bypass policy は追加しません。- Application audience tag を確認し、
ACCESS_AUDとして設定します。 - JWT検証用に
ACCESS_ISSUERとACCESS_JWKS_URLを設定します。 - 本番では
workers_devを無効にするか、workers.dev も Access で保護します。
ACCESS_ISSUER は https://<team-name>.cloudflareaccess.com、ACCESS_JWKS_URL は <ACCESS_ISSUER>/cdn-cgi/access/certs の形式です。<team-name> はCloudflare Zero Trustのteam domainです。
現在想定している本番ホスト名は次の通りです。
tilelog-lens.hamakyo.devWorker route はこのホスト名向けに設定されています。ホスト名でアクセスするには、Cloudflare DNS で tilelog-lens.hamakyo.dev の proxied DNS record を作成してください。
Cloudflare Access が設定され、ACCESS_AUD、ACCESS_ISSUER、ACCESS_JWKS_URL が正しく設定されるまでは、有効な Access JWT がないリクエストを Worker が拒否します。
基本コマンドです。
pnpm install
pnpm run typecheck
pnpm test
pnpm run buildローカルD1を使う場合:
wrangler d1 create tilelog_lens
pnpm run db:migrate:local
pnpm run devリモートD1にマイグレーションを適用する場合:
wrangler d1 migrations apply tilelog_lens --remoteGitHub Actions、Cloudflareデプロイ、Terraformによる周辺インフラ管理は docs/infra.md を参照してください。
Terraform設定は infra/terraform/README.md にあります。Worker本体と静的assetsはWrangler、D1・DNS・Cloudflare AccessはTerraformで管理します。
リモートマイグレーションやリスクのあるデータ変更の前に、リモートD1のSQLバックアップを作成します。
pnpm run db:backup:remoteこのコマンドは backups/tilelog_lens-remote-latest.sql を出力します。backups/*.sql は、個人戦績、メモ、プレイヤー識別子、ソースメタデータを含む可能性があるため、意図的にgitignoreしています。
タイムスタンプ付きバックアップを作成する場合:
mkdir -p backups
wrangler d1 export tilelog_lens --remote --skip-confirmation --output "backups/tilelog_lens-remote-$(date -u +%Y%m%dT%H%M%SZ).sql"復元はまずローカルD1で試し、アプリの表示を確認してからリモートに適用してください。
wrangler d1 execute tilelog_lens --local --file backups/tilelog_lens-remote-latest.sql
pnpm run devリモート復元は意図的に手動操作にしています。
wrangler d1 execute tilelog_lens --remote --file backups/tilelog_lens-remote-latest.sqlリモート復元は、対象データベースとバックアップファイルを確認してから実行してください。バックアップファイルはコミットしないでください。
pnpm run deployGitHub Actions は main へのpushとpull requestで主要な検証を実行します。
pnpm run typecheck
pnpm test
pnpm run build
pnpm run test:e2eE2Eテストは合成画像フィクスチャだけを使用し、モバイルインポート、ローカルメタデータ抽出、安全なAPIペイロード形状、必須 HH:mm、エクスポートリンク到達性を検証します。
- Cloudflare Access のメールOTPでログインします。
- インポートページを開きます。
- 観測日と必須の
HH:mm時刻を入力します。 - 必要に応じてローカルスクリーンショットを選択し、ブラウザ内プレビュー/OCRを使います。
- OCRの読み取り位置がずれる場合は、横位置・縦位置・領域サイズを調整します。
- OCR結果を確認し、必要な項目を手動で修正します。
- 保存前の警告を確認し、スナップショットをD1に保存します。
- ダッシュボードでモード別の傾向を確認します。
- 比較ページで任意の2記録の差分を確認します。
- CSVまたは匿名化済みAI JSONをダウンロードします。
- 設定ページで所有者情報やエクスポート設定を確認します。
主テーブルは stat_snapshots です。
保存する主なデータ:
- 観測日時
- ゲームモード
- 段位情報
- 対戦数
- 順位率
- 和了率、放銃率、副露率、立直率
- 任意メモ
- 任意のソース画像ハッシュ
- 任意のソースメタデータ
MVPでは、任意メモを stat_snapshots.note に直接保存します。より詳細なメモ機能向けの play_notes テーブルは将来用に予約しており、初期マイグレーションには含めていません。
保存しないデータ:
- 画像バイト列
- base64スクリーンショット
- スクリーンショットURL
- 公式アセット
CSV/JSONファイルは、D1からオンデマンドで生成し、ダウンロードレスポンスとして返します。サーバー側には保存しません。
デフォルトのAI JSONエクスポートはプレイヤー識別子を匿名化し、次の内容を含みます。
- 指標説明
- スナップショット
- 派生指標
- 推定差分
- 直近期の期間分析
- 改善優先度
- 段位ポイント分析
- データ品質警告
- 分析リクエスト
- スクリーンショットが含まれないことを示すプライバシーメタデータ
- アプリは Cloudflare Access の背後で非公開にしてください。
- エクスポートする意図がない限り、メモ欄に機微な個人情報を入れないでください。
- JSON/CSVを第三者AIツールへアップロードする前に内容を確認してください。
- 既定では匿名化エクスポートを使用してください。
- Workerで Access JWT を検証します。
OWNER_EMAILだけを許可します。- APIリクエスト内の画像/base64ペイロードを拒否します。
- OCRテキストとスクリーンショットはブラウザ内に留め、APIへ送信しません。
- リクエストボディ制限は小さく保ちます。
- JWT、メモ、プレイヤーID、エクスポートペイロードをログに出力しません。
- Workerのエラーログは、イベント、メソッド、パス、エラー種別/メッセージ、snapshot id などの非機微な識別子に限定します。
- 本番では、別途保護しない限り
workers_devを無効にします。
- OCRは固定クロップ領域を使うため、手動修正が必要な場合があります。
- クロップ領域のキャリブレーションUIは未実装です。
- 過去データ一括投入用のCSVインポートは未実装です。
- PWA / オフラインモードは未実装です。
- ダークモードは未実装です。
- Cloudflare Access One-time PIN: https://developers.cloudflare.com/cloudflare-one/integrations/identity-providers/one-time-pin/
- Cloudflare Access JWT validation: https://developers.cloudflare.com/cloudflare-one/access-controls/applications/http-apps/authorization-cookie/validating-json/
- Cloudflare D1 with Hono: https://developers.cloudflare.com/d1/examples/d1-and-hono/
- Hono on Cloudflare Workers: https://hono.dev/docs/getting-started/cloudflare-workers
公開前にライセンスを選択してください。個人利用の非公開リポジトリであれば、公開ライセンスは必須ではありません。リポジトリを公開する場合はMITが無難ですが、Mahjong Soul / 雀魂のアセットは含めないでください。
{ "name": "tilelog-lens", "main": "src/worker/index.ts", "compatibility_date": "2026-06-01", "workers_dev": false, "d1_databases": [ { "binding": "DB", "database_name": "tilelog_lens", "database_id": "<database-id>" } ], "vars": { "ENVIRONMENT": "production" } }