繁體中文 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);
    // 記錄到監控服務、發送警報等
  }
});

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 位收件人