繁體中文 English

linekit

模組化、型別安全、安全的 LINE 服務開發工具包。
模組化
只安裝需要的套件:Core、Messaging、Login 或 Adapters。
型別安全
使用 TypeScript 開發,提供完整型別定義。
安全
時序安全的簽章驗證、輸入驗證、CSRF 保護。
框架無關
支援 Express、Fastify、Next.js 或標準 Fetch API。

開始使用

前置需求

安裝

# LINE Bot 開發
npm install @linekit/core @linekit/messaging @linekit/express

# LINE Login
npm install @linekit/login

# 或安裝全部
npm install @linekit/core @linekit/messaging @linekit/login @linekit/express

快速開始:Echo Bot

import express from "express";
import { createLineApp } from "@linekit/core";
import { lineMiddleware } from "@linekit/express";

const app = express();

const config = {
  channelSecret: process.env.CHANNEL_SECRET,
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN,
};

const line = createLineApp(config);

app.post("/webhook",
  lineMiddleware(config),
  line.router({
    message: async (ctx) => {
      if (ctx.event.message.type === "text") {
        await ctx.replyText(`你說:${ctx.event.message.text}`);
      }
    },
  })
);

app.listen(3000, () => console.log("Bot 運行於 port 3000"));

套件總覽

@linekit/core

Webhook 驗證(時序安全)、Context、Router 含錯誤處理。

@linekit/messaging

完整 Messaging API:reply、push、multicast、broadcast。Rich Menu 管理。含輸入驗證。

@linekit/login

OAuth 2.1 流程、ID Token 驗證、CSRF state 輔助工具。

@linekit/express

Express.js 適配器,自動處理 body parsing 和簽章驗證 middleware。

@linekit/liff

伺服器端 LIFF App 管理 (CRUD) 與型別定義。

教學指南

1. 建立 LINE Bot

本教學涵蓋建立一個完整功能的 LINE Bot,可以回應訊息、追蹤和 postback 事件。

Webhook 運作流程

使用者
發送訊息
→
LINE 平台
接收並轉發
→
你的伺服器
linekit 處理
→
LINE API
發送回覆
→
使用者
收到回覆

步驟 1:建立專案

mkdir my-line-bot && cd my-line-bot
npm init -y
npm install express @linekit/core @linekit/messaging @linekit/express
npm install -D typescript @types/node @types/express tsx

步驟 2:建立 Bot

// src/index.ts
import express from "express";
import { createLineApp } from "@linekit/core";
import { lineMiddleware } from "@linekit/express";

const app = express();

const config = {
  channelSecret: process.env.CHANNEL_SECRET!,
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN!,
};

const line = createLineApp(config);

// Webhook 端點
app.post("/webhook",
  lineMiddleware(config),
  line.router({
    // 處理文字訊息
    message: async (ctx) => {
      const { message } = ctx.event;

      if (message.type === "text") {
        // 回應相同的訊息
        await ctx.replyText(`Echo: ${message.text}`);
      } else if (message.type === "sticker") {
        // 回應相同的貼圖
        await ctx.reply({
          type: "sticker",
          packageId: message.packageId,
          stickerId: message.stickerId,
        });
      } else if (message.type === "image") {
        await ctx.replyText("收到圖片!");
      }
    },

    // 處理新追蹤者
    follow: async (ctx) => {
      await ctx.replyText("感謝追蹤!請傳送訊息給我。");
    },

    // 處理 postback 事件(來自按鈕、rich menus)
    postback: async (ctx) => {
      const data = ctx.event.postback.data;
      await ctx.replyText(`你點擊了:${data}`);
    },
  }, {
    // 選用:自訂錯誤處理
    onError: (error, ctx) => {
      console.error(`用戶 ${ctx.userId} 錯誤:`, error);
    }
  })
);

app.listen(3000, () => {
  console.log("Bot 運行於 http://localhost:3000");
});

步驟 3:執行 Bot

# 設定環境變數
export CHANNEL_SECRET="your-channel-secret"
export CHANNEL_ACCESS_TOKEN="your-channel-access-token"

# 使用 tsx 執行
npx tsx src/index.ts
提示:使用 ngrok 或類似工具來測試 webhook: ngrok http 3000

2. LINE Login 整合

在你的網頁應用程式中實作「使用 LINE 登入」功能,包含 CSRF 保護。

OAuth 2.1 登入流程

使用者
點擊登入
→
你的應用
產生 state
→
LINE 認證
使用者登入
→
Callback
驗證 state
→
LINE API
交換 token
→
登入成功
使用者資料
// src/auth.ts
import {
  login,
  generateAuthUrl,
  issueAccessToken,
  LineLoginError
} from "@linekit/login";

const CHANNEL_ID = process.env.LINE_LOGIN_CHANNEL_ID!;
const CHANNEL_SECRET = process.env.LINE_LOGIN_CHANNEL_SECRET!;
const REDIRECT_URI = "https://example.com/callback";

// 步驟 1:產生登入 URL
export function getLoginUrl(session: any) {
  // 產生安全的隨機 state 用於 CSRF 保護
  const state = login.generateState();
  const nonce = login.generateNonce();

  // 儲存到 session 以便後續驗證
  session.lineLoginState = state;
  session.lineLoginNonce = nonce;

  return generateAuthUrl({
    channelId: CHANNEL_ID,
    redirectUri: REDIRECT_URI,
    state,
    nonce,
    scope: "profile openid email",
  });
}

// 步驟 2:處理 callback
export async function handleCallback(
  code: string,
  returnedState: string,
  session: any
) {
  // 驗證 state 以防止 CSRF 攻擊
  if (!login.validateState(session.lineLoginState, returnedState)) {
    throw new Error("無效的 state - 可能的 CSRF 攻擊");
  }

  try {
    // 用 code 交換 tokens
    const tokens = await issueAccessToken(
      CHANNEL_ID,
      CHANNEL_SECRET,
      code,
      REDIRECT_URI
    );

    // 驗證 ID token 並取得使用者資料
    const user = await login.verify(tokens.id_token!, CHANNEL_ID);

    return {
      userId: user.sub,
      name: user.name,
      picture: user.picture,
      email: user.email,
      accessToken: tokens.access_token,
    };
  } catch (error) {
    if (error instanceof LineLoginError) {
      console.error(`登入失敗:${error.code}`);
    }
    throw error;
  }
}

// Express 路由範例
app.get("/login", (req, res) => {
  const url = getLoginUrl(req.session);
  res.redirect(url);
});

app.get("/callback", async (req, res) => {
  const { code, state } = req.query;
  const user = await handleCallback(code, state, req.session);
  req.session.user = user;
  res.redirect("/dashboard");
});

3. 發送訊息類型

LINE 支援多種訊息類型,以下是各類型的發送方式。

import {
  replyMessage,
  pushMessage,
  multicast
} from "@linekit/messaging";

const TOKEN = process.env.CHANNEL_ACCESS_TOKEN!;

// 文字訊息
await pushMessage(TOKEN, userId, [{
  type: "text",
  text: "哈囉!"
}]);

// 帶 emoji 的文字
await pushMessage(TOKEN, userId, [{
  type: "text",
  text: "哈囉 $ LINE emoji!",
  emojis: [{
    index: 3,
    productId: "5ac1bfd5040ab15980c9b435",
    emojiId: "001"
  }]
}]);

// 貼圖
await pushMessage(TOKEN, userId, [{
  type: "sticker",
  packageId: "446",
  stickerId: "1988"
}]);

// 圖片
await pushMessage(TOKEN, userId, [{
  type: "image",
  originalContentUrl: "https://example.com/image.jpg",
  previewImageUrl: "https://example.com/preview.jpg"
}]);

// 位置
await pushMessage(TOKEN, userId, [{
  type: "location",
  title: "LINE 台北辦公室",
  address: "台北市內湖區",
  latitude: 25.0825,
  longitude: 121.5656
}]);

// Flex Message(豐富版面)
await pushMessage(TOKEN, userId, [{
  type: "flex",
  altText: "這是 Flex Message",
  contents: {
    type: "bubble",
    body: {
      type: "box",
      layout: "vertical",
      contents: [
        { type: "text", text: "哈囉", weight: "bold", size: "xl" },
        { type: "text", text: "世界", size: "md" }
      ]
    }
  }
}]);

// 群發訊息(最多 500 人)
await multicast(TOKEN, [userId1, userId2, userId3], [{
  type: "text",
  text: "大家好!"
}]);

4. Rich Menu 管理

以程式方式建立和管理 Rich Menu。

import { richMenu } from "@linekit/messaging";

const TOKEN = process.env.CHANNEL_ACCESS_TOKEN!;

// 建立 Rich Menu
const menu = await richMenu.create(TOKEN, {
  size: { width: 2500, height: 1686 },
  selected: true,
  name: "主選單",
  chatBarText: "選單",
  areas: [
    {
      bounds: { x: 0, y: 0, width: 1250, height: 843 },
      action: { type: "postback", data: "action=menu1" }
    },
    {
      bounds: { x: 1250, y: 0, width: 1250, height: 843 },
      action: { type: "uri", uri: "https://example.com" }
    },
    {
      bounds: { x: 0, y: 843, width: 1250, height: 843 },
      action: { type: "message", text: "你好!" }
    },
    {
      bounds: { x: 1250, y: 843, width: 1250, height: 843 },
      action: { type: "postback", data: "action=menu4" }
    }
  ]
});

console.log("Rich Menu 已建立:", menu.richMenuId);

// 設為所有使用者的預設選單
await richMenu.setDefault(TOKEN, menu.richMenuId);

// 連結到特定使用者
await richMenu.linkUser(TOKEN, userId, menu.richMenuId);

// 取消連結
await richMenu.unlinkUser(TOKEN, userId);

// 刪除 Rich Menu
await richMenu.delete(TOKEN, menu.richMenuId);
注意:你需要另外使用 LINE API 上傳 Rich Menu 圖片。 圖片尺寸應為 2500x1686 或 2500x843 像素。

5. 錯誤處理

正確處理來自 LINE API 的錯誤。

import { LineClientError } from "@linekit/messaging";
import { LineLoginError } from "@linekit/login";

// Messaging API 錯誤
try {
  await pushMessage(TOKEN, userId, messages);
} catch (error) {
  if (error instanceof LineClientError) {
    console.error(`LINE API 錯誤:${error.message}`);
    console.error(`狀態碼:${error.status}`);
    console.error(`詳細資訊:`, error.data);

    // 處理特定錯誤
    if (error.status === 400) {
      // 錯誤的請求 - 檢查訊息格式
    } else if (error.status === 401) {
      // 無效的 token
    } else if (error.status === 429) {
      // 超過速率限制 - 實作 backoff
    }
  }
}

// Login 錯誤
try {
  const user = await login.verify(idToken, channelId);
} catch (error) {
  if (error instanceof LineLoginError) {
    console.error(`登入錯誤:${error.code}`);

    if (error.code === "INVALID_PARAMETER") {
      // 缺少或無效的參數
    } else if (error.code === "VERIFICATION_FAILED") {
      // Token 驗證失敗
    }
  }
}

// Router 錯誤處理
const router = line.router({
  message: async (ctx) => {
    // 你的處理程式碼
  }
}, {
  onError: async (error, ctx) => {
    console.error(`處理 ${ctx.event.type} 時發生錯誤:`, error);
    // 記錄到監控服務、發送警報等
  }
});

7. LIFF App 管理

從伺服器端透過程式碼新增與管理 LIFF App。

import { LiffClient } from "@linekit/liff";

const client = new LiffClient({
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN
});

// 建立新的 LIFF App
const { liffId } = await client.add({
  view: {
    type: "full",
    url: "https://mysite.com/app"
  }
});

console.log("已建立 LIFF App: https://liff.line.me/" + liffId);

6. 行銷推播

自動分批處理大量用戶清單並發送訊息,內建速率限制功能。

import { bulkMulticast } from "@linekit/messaging";

const userList = ["U1...", "U2...", /* 1000+ 位用戶 */];

const results = await bulkMulticast(
  TOKEN,
  userList,
  [{ type: "text", text: "每週更新!" }],
  {
    chunkSize: 500, // LINE API 限制
    delayMs: 1000   // 每批次暫停 1 秒
  }
);

const successCount = results.filter(r => r.success).reduce((acc, r) => acc + r.count, 0);
console.log(`已發送給 ${successCount} 位用戶`);

7. LIFF App 管理

從伺服器端透過程式碼新增與管理 LIFF App。

import { LiffClient } from "@linekit/liff";

const client = new LiffClient({
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN
});

// 建立新的 LIFF App
const { liffId } = await client.add({
  view: {
    type: "full",
    url: "https://mysite.com/app"
  }
});

console.log("已建立 LIFF App: https://liff.line.me/" + liffId);

API 參考

@linekit/core

@linekit/messaging

@linekit/login

安全功能

linekit 以安全為優先考量:
安全最佳實踐:
  • 絕不在客戶端程式碼中暴露 channel secret 或 access token
  • 永遠驗證 OAuth state 參數
  • 所有 webhook 端點使用 HTTPS
  • 定期更換 access token

LINE API 相容性

本工具包基於以下 LINE API 版本開發:

API 版本 參考文件
Messaging API v2 文件
LINE Login v2.1 (OAuth 2.1) 文件
LIFF v2 文件

API 端點 Base URL

API 限制(由 linekit 強制執行)

每次請求訊息數

最多 5 則訊息(reply、push、multicast)

Multicast 收件人數

每次請求最多 500 位收件人