「X 抽選キャンペーンの当選者だけが LINE に登録できる、流入元タグ付き連携を作りたい。4日くらいかかる?」
最初の見積もりはそう答えました。X 側で一意トークンを発行し、DM の LP リンクに付与し、LP で受け取って LINE に渡し、LINE 側で X user_id に解決して friend_diagnosis に保存する。各ステップの実装で1日ずつ、合計4日。
実際は 半日で本番稼働 しました。鍵は OSS コードリーディング でした。
当初の見積もりと実態の差
実装ボリュームを最初に見積もったときの内訳と、実際の差は次の通りです。
| レイヤー | 当初見積 | 実態 | ギャップの理由 |
|---|---|---|---|
| X Harness: 当選DMに一意token付与 | 半日〜1日 | ✅ 既に実装済み | OSS が appendRef(gate.link, 'xh:' + delivery.token) を engagement-gate.ts:152 で既に実装 |
| X Harness: token→ X user_id 検索API | 1日 | ✅ 既に実装済み | OSS の GET /api/tokens/:token/resolve がすでに公開エンドポイントとして稼働 |
LP: ?ref=xh: 抽出 | 半日 | 🟡 5-10行追加で完了 | URL パラメータ解析 + JSON 同梱だけ |
| LINE Harness: lp-hook.ts の ref処理 | 数時間 | 🟡 30-50行追加で完了 | 既存 decodeSource を拡張 + 新ヘルパ関数 |
| LINE Harness: friend_diagnosis 拡張 | 数時間 | 🟡 migration 111 で4カラム追加 | ALTER TABLE 1ファイル |
| X↔LINE webhook | 1日 | ❌ 不要 | OSS resolveToken() が atomic consume するため webhook 不要 |
| 合計 | 3-4日 | 約半日 | OSS既存実装の発見が時短の本体 |
OSS のコードを読まずに自前で書き始めると、4日かかった可能性が高い実装でした。読むだけで時間が10分の1になりました。
設計の核 — atomic resolveToken() を発見
OSS(x-harness-oss)の packages/db/src/engagement-gates.ts:256 に、こんな関数がすでにありました。
``typescript
export async function resolveToken(db: D1Database, token: string): Promise
// Atomically mark the token as consumed and return its data in a single statement.
// If consumed_at is already set (or token doesn't exist), the UPDATE matches zero rows
// and we return null — preventing double-redemption races.
const now = jstNow();
const row = await db
.prepare('UPDATE engagement_gate_deliveries SET consumed_at = ? WHERE token = ? AND consumed_at IS NULL RETURNING x_user_id, x_username, gate_id')
.bind(now, token)
.first<{ x_user_id: string; x_username: string | null; gate_id: string }>();
if (!row) return null;
// ... LINE Harness config を取得して返す
}
`
そして apps/worker/src/routes/engagement-gates.ts:120-125 には、auth 不要の public endpoint として公開されていました。
`typescript`
// Public endpoint — no auth required (token is the secret)
engagementGates.get('/api/tokens/:token/resolve', async (c) => {
const result = await resolveToken(c.env.DB, c.req.param('token'));
if (!result) return c.json({ success: false, error: 'Token invalid or already consumed' }, 404);
return c.json({ success: true, data: result });
});
UPDATE...RETURNING` 句で atomic に consume + return を1コールで完結 しています。race condition も発生しません。token 自体が64文字のランダム秘密のため、auth は不要という設計判断も合理的です。
私が当初書こうとしていた「verify-token」「claim-token」の2エンドポイント分割は、この1関数で全部置き換えられます。

💡 KEY TAKEAWAYS
SaaS 連携実装で「自前で書く」前に OSS の README、SPEC、エンドポイント一覧を読む こと。既存機能の存在を見落として車輪の再発明をすると、工数が10倍違います。
4つの落とし穴 — 実装中につまずいた場所
実装中に4箇所で立ち止まりました。それぞれの原因と突破方法を共有します。
落とし穴 1: Permission Read-only で 403 連発
実装が一通り終わって動作確認を始めたら、X API がすべて 403 で弾かれました。
``json`
{
"title": "Forbidden",
"status": 403,
"detail": "Your client app is not configured with the appropriate oauth1 app permissions for this endpoint."
}
最初は OSS のコードを疑い、X Harness Worker の routes/posts.ts を読み返しました。問題なし。次に の x_accounts テーブルを確認。トークンも揃っている。
原因は X Developer Portal のアプリ権限が Read-only のままだったことでした。アクセストークンが発行された当時は Read で足りていて、後から Write/DM 機能を追加実装したのに、X Developer Portal のアプリ権限を変更し忘れていました。
- X Developer Portal → 該当アプリ → Settings → User authentication settings → Edit
- App permissions を 「Read and write and Direct message」 に変更
- Keys and tokens タブ → Access Token を再生成(権限変更後は旧トークン無効)
- D1 の x_accounts テーブルに新トークンを UPDATE
`bash`
npx wrangler d1 execute kei-soulmate-x --remote --command \
"UPDATE x_accounts SET access_token='$NEW_T', access_token_secret='$NEW_S' WHERE id='...';"
教訓: SaaS 連携の「動かない」の8割は権限・スコープ・トークン。コードを掘る前に、まず Developer Portal でアプリ権限と最新トークンを確認すべきです。
落とし穴 2: PowerShell で bash の curl は動かない
検証用の curl コマンドを PowerShell に貼ったところ、3回連続でエラーになりました。
`text`
curl : ドライブが見つかりません。名前 'https' のドライブが存在しません。
PowerShell では curl が Invoke-WebRequest のエイリアスになっており、-s フラグが drive として解釈されています。bash の curl とは構文が完全に別物です。
突破: 3つの解決策があります。
`powershell方法A: curl.exe を明示
curl.exe -s "https://api.example.com" -H "Authorization: Bearer xxx"
方法B: PowerShell ネイティブ
$h = @{ "Authorization" = "Bearer xxx" } Invoke-RestMethod -Uri "https://api.example.com" -Headers $h方法C: git-bash で実行
bash の構文がそのまま動く
`
最終的にチーム内で混在させないため、check.ps1 という PowerShell スクリプト1本 を置いて、検証時はそれを実行する規約にしました。
Invoke-RestMethod ベースで書かれているため、PowerShell から .\check.ps1 で1行実行できます。
教訓: シェル混在環境ではコマンドを書くより、実行ヘルパーを置く方が生産性が高い。
落とし穴 3: がクロスプラットフォームで破壊される
LINE Harness をデプロイしようとしたら、ローカルの wrangler が起動しませんでした。
`text
Error: Cannot find module '.../node_modules/wrangler/bin/wrangler.js'
MODULE_NOT_FOUND
`
原因は Mac で pnpm install した node_modules を 経由で Windows に同期したことでした。
node_modules/.bin/wrangler のシンボリックリンクは存在しているのに、本体の node_modules/wrangler/ ディレクトリが部分的に欠損していました。
pnpm/npm の node_modules には OS 固有のバイナリ(workerd、、native bindings)が含まれており、クロスプラットフォーム同期で壊れます。
突破: 3つの解決策があります。
`bash
方法A: Nextcloud で node_modules を同期除外
.nextcloudignore に node_modules/, .next/, dist/, .wrangler/ を追加
方法B: Win 側で pnpm install --force を再実行
cd line-harness-oss && pnpm install --force
方法C(採用): npx -y wrangler@4 で都度ダウンロード
npx -y wrangler@4 d1 execute line-harness --remote --file=migration.sql
`
最終的に方法C を採用しました。バージョン固定で安定し、node_modules の破壊問題から永久に解放されます。
教訓: クロスプラットフォーム同期するプロジェクトでは、node_modules を同期しない。npx の都度ダウンロードが最も安定。
落とし穴 4: bash の curl で日本語 JSON 送信が 文字化け
ゲートの replyKeyword を「鑑定希望」から「運命」に更新するために、curl で PUT リクエストを送ったら、こうなりました。
`json
"replyKeyword": "\udcefソス^\udcefソス\udcefソス"
`
bash のコマンドライン引数で渡した日本語が、Shift-JIS と UTF-8 の混在環境で破壊されました。
突破: JSON を で生成してファイルに書き出し、curl の
--data-binary @file で送信する。
`bash
python <<'EOF' > /tmp/gate_update.json
import json, sys
sys.stdout.buffer.write(json.dumps({"replyKeyword": "運命"}, ensure_ascii=False).encode("utf-8"))
EOF
curl -s -X PUT "$WORKER/api/engagement-gates/$GATE" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json; charset=utf-8" \
--data-binary @/tmp/gate_update.json
`
教訓: 日本語を含む JSON は Python 経由で生成、ファイルから送信が安全。シェル引数の文字化けは環境差で再現性が低く、デバッグ時間を奪います。

4つの落とし穴と突破方法
学び — OSS活用の組織体力
この実装から得た学びは3つです。
1. 自前実装の前に OSS の README / SPEC / エンドポイント一覧を読む。
コードリーディングは「面倒な前置き」ではなく、最も投資効率の高い時間です。当方は今回30分の OSS リーディングで4日を半日に圧縮しました。
2. _custom/ パターンで OSS のコアコードに触れない規約を持つ。
upstream 追従コストを最小化できます。今回も最初に書いた _custom/deliveries.ts を OSS 機能と重複と判明した時点で削除し、コア未編集ルールを完全準拠で運用継続しました。
3. SaaS 連携の「動かない」の8割は権限・スコープ・トークン。
コードを疑う前に Developer Portal のアプリ権限と最新トークンを確認する。OAuth Permission の罠は誰もが踏みます。
当方の OSS 運用ルール(参考)
`markdown
X / LINE Harness OSS 運用ルール
コア未編集の原則
- packages/db/, apps/worker/src/routes/ は触らない
- 独自機能は apps/worker/src/_custom/ 配下に追加
- カスタムDB拡張は _custom/db/migrations/<番号>_<名前>.sql
upstream 追従
- 月1回 git fetch upstream && git merge upstream/main
- conflict が発生しないように _custom/ 以外を編集しない
例外的にコアに触れる場合
- MODIFICATIONS.md に経緯と差分を記載
- 次回 upstream マージ時の手順を明記
デプロイ前チェック
- [ ] _custom/ 配下のみ変更されているか
- [ ] migration 番号が連番になっているか
- [ ] secrets が D1 ではなく Worker secrets に入っているか
``
次のアクション
OSS をローカル fork で運用するか、自前で1から書くか、SaaS をフルマネージドで使うかの判断は、組織の OSS活用体力 によって変わります。当方は + D1 + 自家 fork OSS の組み合わせを軸にしていますが、これは一定のコードリーディング体力を前提にしています。
御社の組織が OSS を活用できる体質か、SaaS 中心で行くべきか、3分でセルフチェックしてみたい方は 細マッチョ企業診断 をご利用ください。当方が今回の実装経験から組み立てた診断項目を反映しています。
