← 所有文章
教學

我畀 Claude Code 起咗個 X 私訊同提及監聽器——完全冇掂過 X 嘅 API — UnifyPort

X 嘅 API 冇免費方案。讀取一條私訊收 $0.015,讀取一條帖文收 $0.005。如果你係一個兩人團隊,淨係想知有人喺 X 上面發咗私訊或者提及咗你,官方開發者平台會要求你先增值、建立專案、等審批、開計量收費——你一條訊息都未處理過。

我跳過晒呢啲。打開 Claude Code,將 UnifyPort 嘅 webhook 介面文件貼到上下文度,叫佢用 Node.js 起一個 X 私訊同提及監聽器。四十分鐘之後,我有咗一個 Express 伺服器,可以驗證每次交付嘅簽章、將事件寫入結構化日誌檔、同埋推送告警到 Discord 頻道。唔使 X 開發者帳號,唔使按次收費,唔使排隊審批。

你最後會得到咩

一個 Node.js Express 伺服器:

  1. 接收 UnifyPort 統一 webhook 嘅 message.received 事件
  2. 用 HMAC-SHA256 同 signing_secret 驗證每次交付嘅簽章
  3. 將每條 X 訊息寫入 messages.jsonl 檔案
  4. 透過 webhook 向 Discord 頻道發送告警

時間:唔使一個鐘。你需要一個 UnifyPort 工作區,透過 UnifyPort Exporter 瀏覽器擴充功能連接 X 帳號——一鍵工作階段匯入,唔使開發者憑證——同埋一個 Discord webhook URL。

準備工作:將文件餵畀 Claude Code

喺提示之前,先將 API 文件送入代理嘅上下文。Claude Code 可以直接讀檔案,所以喺專案根目錄建立 unifyport-reference.md,包含:

  • message.received 事件酬載結構
  • x-unifyport-signature 標頭同 HMAC-SHA256 簽章驗證說明
  • POST /v1/webhook-endpoints 建立呼叫

亦可以將呢啲內容直接貼到對話度。重點:真實嘅欄位名輸入,正確嘅程式碼輸出。冇文件嘅話,代理會自己發明一個 X 專用 SDK 或者根本唔存在嘅 dm.received 事件類型。

Cursor、Windsurf、Copilot 用法一樣——用 @Docs、文件面板或者直接貼文件到上下文。工具係次要嘅,文件先係核心。

建構過程:逐條提示

第一個提示——骨架:

讀取 unifyport-reference.md。用 Express 寫一個 server.js,包含 POST /webhook 路由。當 event 係 “message.received” 時,印出 provider、from 同 text 欄位。所有請求回傳 200。

Claude Code 輸出:

import express from "express";

const app = express();
app.use(express.json());

app.post("/webhook", (req, res) => {
  const evt = req.body;
  if (evt.event === "message.received") {
    console.log(`[${evt.provider}] ${evt.from}: ${evt.text}`);
  }
  res.sendStatus(200);
});

app.listen(3000, () => console.log("listening on :3000"));

六行處理器。因為事件結構喺六個平台上完全一致,唔使任何 X 專用嘅解析——evt.provider 顯示 "x",但結構同 WhatsApp 或 Telegram 訊息完全一樣。

第二個提示——簽章驗證:

每次交付都包含 x-unifyport-signature 標頭。用環境變數入面嘅 signing_secret 做 HMAC-SHA256 驗證。HMAC 一定要對原始請求本體計算——唔可以對重新序列化嘅 JSON 計算。驗證失敗回傳 401。用時間安全比較。

Claude Code 加上驗證層:

import crypto from "crypto";

const SIGNING_SECRET = process.env.UNIFYPORT_SIGNING_SECRET;

app.use(express.json({
  verify: (req, _res, buf) => { req.rawBody = buf; }
}));

function isValid(req) {
  const sig = req.get("x-unifyport-signature") || "";
  const expected = crypto
    .createHmac("sha256", SIGNING_SECRET)
    .update(req.rawBody)
    .digest("hex");
  return sig.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}

verify 回呼喺 Express 解析 JSON 之前捕獲原始 buffer——如果代理用咗 req.body 再序列化成 JSON,HMAC 永遠唔會匹配。因為提示講明咗「原始請求本體」,代理一次就寫啱。

第三個提示——日誌記錄同 Discord 告警:

加兩個功能:(1) 將每個 message.received 事件附加到 messages.jsonl——每行一個 JSON 物件,包含 timestamp、provider、from、text 同 message_id。(2) 向環境變數 DISCORD_WEBHOOK_URL 指定嘅 Discord webhook 發送一行告警。

完整嘅 server.js

import express from "express";
import crypto from "crypto";
import { appendFileSync } from "fs";

const SIGNING_SECRET = process.env.UNIFYPORT_SIGNING_SECRET;
const DISCORD_WEBHOOK = process.env.DISCORD_WEBHOOK_URL;

const app = express();
app.use(express.json({
  verify: (req, _res, buf) => { req.rawBody = buf; }
}));

function isValid(req) {
  const sig = req.get("x-unifyport-signature") || "";
  const expected = crypto
    .createHmac("sha256", SIGNING_SECRET)
    .update(req.rawBody)
    .digest("hex");
  return sig.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}

app.post("/webhook", async (req, res) => {
  if (!isValid(req)) return res.sendStatus(401);

  const evt = req.body;
  if (evt.event === "message.received") {
    const record = JSON.stringify({
      timestamp: evt.timestamp,
      provider: evt.provider,
      from: evt.from,
      text: evt.text,
      message_id: evt.message_id,
    });
    appendFileSync("messages.jsonl", record + "\n");

    if (DISCORD_WEBHOOK) {
      await fetch(DISCORD_WEBHOOK, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          content: `**[${evt.provider}]** ${evt.from}: ${evt.text}`,
        }),
      });
    }
  }
  res.sendStatus(200);
});

app.listen(3000, () => console.log("listening on :3000"));

唔使 50 行。簽章驗證、磁碟日誌、Discord 轉發全部搞掂。處理器唔會對 provider 做分支判斷——事件結構一樣,唔理訊息係嚟自 X、WhatsApp 定其他平台。

執行同睇到 X 訊息到達

喺 UnifyPort 控制台連接你嘅 X 帳號。X 用 UnifyPort Exporter 瀏覽器擴充功能做工作階段匯入——撳擴充功能圖示、授權,幾秒鐘帳號就連接好。唔使開發者後台,唔使申請 API 金鑰,唔使審批佇列。

註冊一個 webhook 端點指向你嘅伺服器,設定 subscribed_events: ["message.received"]signing_secret。啟動伺服器,然後用另一個帳號畀你嘅 X 帳號發一條測試私訊。事件到達:

{
  "event": "message.received",
  "account_id": "acct_7Kp3mR",
  "provider": "x",
  "from": "user_9d4f2b",
  "text": "Hey, are you still taking freelance projects?",
  "timestamp": 1750521600,
  "message_id": "x_msg_8c1e5a"
}

你嘅伺服器驗證簽章、將紀錄附加到 messages.jsonl、同埋將告警發送到 Discord。如果簽章驗證失敗——401——檢查 UNIFYPORT_SIGNING_SECRET 係咪同 webhook 端點上設定嘅值一致。

擴展:零程式碼改動加入 Telegram

喺同一個工作區連接 Telegram 帳號,訂閱同一個 webhook 端點。Telegram 訊息以完全相同嘅結構到達——唔同嘅 provider 欄位,結構一模一樣:

{
  "event": "message.received",
  "account_id": "acct_2Xn5wL",
  "provider": "telegram",
  "from": "user_4a7c8d",
  "text": "Can you hop on a call tomorrow?",
  "timestamp": 1750521660,
  "message_id": "tg_msg_3b9f1e"
}

唔使新嘅處理器,唔使 Telegram 專用嘅解析邏輯。messages.jsonl 檔案而家同時包含 X 同 Telegram 事件,用相同嘅 schema,Discord 顯示兩個來源嘅告警。之後加 WhatsApp、LINE、Zalo 或 TikTok 都係同樣流程——連接帳號,現有嘅處理器自動處理一切。

呢個同直接對接 X API 嘅分別唔止係成本——而係準入門檻。X 要求通過審核嘅開發者帳號、增值嘅專案同按次計費嘅承諾。UnifyPort 嘅工作階段匯入路徑透過瀏覽器連接你嘅個人 X 帳號,將每條私訊同提及正規化為同其他五個平台完全相同嘅事件結構。將 Claude Code 指向呢個介面——一個事件 schema、一個簽章機制——你就可以喺食晏之前跑起一個帶驗證、日誌、告警嘅監聽器。然後加埋其他所有平台,程式碼一行都唔使改。