iT邦幫忙

2026 iThome 鐵人賽

DAY 23
0
佛心分享-SideProject30

30 天走進 OpenClaw:一個 AI Agent 的誕生、掙扎與進化系列 第 23

第 23 天:LINE plugin 為什麼不是,Telegram 的複製貼上

  • 分享至 

  • xImage
  •  

開場故事

很多人第一次看 LINE plugin,會直覺地說:

「這不就跟 Telegram 一樣嗎?不就是另一個聊天平台?」

表面上看起來真的很像。

都是 bot、都是 webhook、都是收到訊息後交給 agent。
但你一旦真的把它們接進同一個系統,就會發現兩邊其實很不一樣:

  • Telegram 比較像 command / thread / streaming 的世界
  • LINE 比較像 webhook / signature / rich message 的世界

第 23 天我想講的重點是:

LINE plugin 不是 Telegram 的複製貼上,它是另一種入口,但接的是同一顆核心。

這篇會先看 LINE 怎麼被裝進 OpenClaw,再看它為什麼要把 reply payload 翻成 LINE 的 rich message 語言。

今天要解的問題

  • LINE plugin 的入口長什麼樣?
  • 它怎麼接到 OpenClaw 的 plugin contract?
  • 為什麼 LINE 需要自己的 reply payload transform?
  • LINE 的訊息表達和 Telegram 差在哪?
  • 為什麼 LINE 的 plugin 比 Telegram 更像「轉譯器」?

架構總覽

LINE 的接法我會切成四層:

  1. bundled entry:讓 OpenClaw 找得到這個 plugin
  2. chat channel plugin:把 LINE 裝進 core
  3. reply transform:把 OpenClaw 的輸出翻成 LINE rich message
  4. gateway / webhook / send:把訊息真正送進 LINE 世界

如果說 Telegram 比較像「強化過的聊天入口」,那 LINE 比較像「先翻譯、再投遞」。

因為 LINE 的介面語言跟 OpenClaw 的內部 reply payload 並不完全同型。

原始碼節錄

先看入口。

📄 原始碼:extensions/line/index.ts:3-50

import {
  defineBundledChannelEntry,
  type OpenClawPluginApi,
} from "openclaw/plugin-sdk/channel-entry-contract";

export default defineBundledChannelEntry({
  id: "line",
  name: "LINE",
  description: "LINE Messaging API channel plugin",
  importMetaUrl: import.meta.url,
  plugin: {
    specifier: "./api.js",
    exportName: "linePlugin",
  },
  runtime: {
    specifier: "./runtime-api.js",
    exportName: "setLineRuntime",
  },
  registerFull(api) {
    api.registerCommand({
      name: "card",
      description: "Send a rich card message (LINE).",
      acceptsArgs: true,
      requireAuth: false,
      async handler(ctx) {
        const command = await loadLineCardCommand(api);
        return await command.handler(ctx);
      },
    });
  },
});

這裡已經可以看出 LINE 的個性。

它不只是把 plugin 載入,還順手註冊了 /card 指令。
這代表 LINE 在 OpenClaw 裡不是只負責收發文字,它還有一組 rich message 的操作語法。

再看它怎麼接 core。

📄 原始碼:extensions/line/src/channel.ts:41-102

export const linePlugin: ChannelPlugin<ResolvedLineAccount> = createChatChannelPlugin({
  base: {
    id: "line",
    ...lineChannelPluginCommon,
    setupWizard: lineSetupWizard,
    groups: {
      resolveRequireMention: resolveLineGroupRequireMention,
    },
    messaging: {
      normalizeTarget: (target) => {
        const trimmed = target.trim();
        if (!trimmed) {
          return undefined;
        }
        return trimmed.replace(/^line:(group|room|user):/i, "").replace(/^line:/i, "");
      },
      resolveInboundConversation: lineBindingsAdapter.resolveInboundConversation,
      transformReplyPayload: ({ payload }) => {
        if (!payload.text || !hasLineDirectives(payload.text)) {
          return payload;
        }
        return parseLineDirectives(payload);
      },
      targetResolver: {
        looksLikeId: (id) => {
          const trimmed = id?.trim();
          if (!trimmed) {
            return false;
          }
          return /^[UCR][a-f0-9]{32}$/i.test(trimmed) || /^line:/i.test(trimmed);
        },
        hint: "<userId|groupId|roomId>",
      },
    },
    directory: createEmptyChannelDirectoryAdapter(),
    setup: lineSetupAdapter,
    status: lineStatusAdapter,
    gateway: lineGatewayAdapter,
    bindings: lineBindingsAdapter,
    conversationBindings: {
      defaultTopLevelPlacement: "current",
    },
  },
});

這段最值得記住的是 transformReplyPayload

它說明 LINE 不只是把 OpenClaw 回覆原封不動送出去,而是要先看文字裡有沒有 LINE directives,然後把它變成 LINE 可以理解的 rich message 結構。

白話拆解

1. LINE plugin 的任務不是「轉寄」,而是「轉譯」

Telegram 很多時候可以比較直接地把訊息送出去。

LINE 不一樣。
它很重視 rich message 形式,所以 OpenClaw 在產出 reply 之後,還要再做一次轉譯:

  • quick replies
  • location
  • confirm dialog
  • buttons
  • media player card
  • event / agenda / device control

這些不是裝飾,而是 LINE UX 的核心語言。

2. transformReplyPayload 是 LINE 的關鍵分水嶺

這個 hook 很漂亮。

它讓 OpenClaw core 不用知道 LINE 的全部視覺規則,只要照著自己的方式產出 payload。
到了 LINE plugin 這層,再把特殊 directive 翻成 rich message。

這種設計有一個很大的好處:

  • core 保持穩定
  • LINE 可以慢慢演化
  • 新增新的 rich message 類型時,不需要動整個系統

3. LINE 的 target 處理比想像中更像資料清洗

你看 normalizeTargetlooksLikeId 就會發現,LINE 很在意 target 的格式。

它要接受:

  • line:user:...
  • line:group:...
  • line:room:...
  • 以及原始 id

這代表 LINE plugin 不只是送訊息,它還在幫 OpenClaw 把各種 user-facing 輸入整理成正規化資料。

4. LINE 不是複製 Telegram,因為它的使用情境不同

Telegram 常見的是:

  • thread
  • command
  • reply-to
  • streaming drafts

LINE 常見的是:

  • webhook 驗證
  • rich card
  • quick replies
  • location / template
  • groups / rooms / users 的識別

所以如果你硬把 Telegram 的想法原封不動搬來 LINE,會很怪。

OpenClaw 的做法是:

  • core 保持一樣
  • channel adapter 讓每個平台有自己的表達方式

這才是正確的複用。

設計取捨

  • 好處是 LINE 可以保留自己的 UI 語彙,不需要硬被 Telegram 的模型壓扁
  • 好處是 OpenClaw core 不需要知道所有平台的視覺細節
  • 好處是 rich message 的能力可以集中在 LINE plugin 內演進
  • 代價是 LINE plugin 需要更多轉譯規則
  • 代價是 debug 時要分辨是 payload 本身錯了,還是 directive parse 錯了

但這就是 channel plugin 的價值。
不是把一切做成一樣,而是把共通部分保留,差異部分封裝。

今天的結論

  • LINE plugin 不是 Telegram 複製貼上,而是另一種 channel adapter
  • defineBundledChannelEntry 讓 OpenClaw 找得到這個 plugin
  • createChatChannelPlugin 把 LINE 接進 core
  • transformReplyPayload 是 LINE rich message 能力的關鍵
  • OpenClaw core 負責共通工作流,LINE plugin 負責平台語言翻譯

下一步

第 24 天我要接著看 LINE 的事件、驗證與常見坑。
因為 LINE 的入口很講究 raw body 和 signature,這裡一旦錯,後面整條流程就不會開始。


上一篇
第 22 天:Telegram plugin 的訊息流,從收到到回覆
系列文
30 天走進 OpenClaw:一個 AI Agent 的誕生、掙扎與進化23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言