教學指南
1. 建立 LINE Bot
本教學涵蓋建立一個完整功能的 LINE Bot,可以回應訊息、追蹤和 postback 事件。
步驟 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
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) => {
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: 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"
npx tsx src/index.ts
提示:使用 ngrok 或類似工具來測試 webhook:
ngrok http 3000
2. LINE Login 整合
在你的網頁應用程式中實作「使用 LINE 登入」功能,包含 CSRF 保護。
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";
export function getLoginUrl(session: any) {
const state = login.generateState();
const nonce = login.generateNonce();
session.lineLoginState = state;
session.lineLoginNonce = nonce;
return generateAuthUrl({
channelId: CHANNEL_ID,
redirectUri: REDIRECT_URI,
state,
nonce,
scope: "profile openid email",
});
}
export async function handleCallback(
code: string,
returnedState: string,
session: any
) {
if (!login.validateState(session.lineLoginState, returnedState)) {
throw new Error("無效的 state - 可能的 CSRF 攻擊");
}
try {
const tokens = await issueAccessToken(
CHANNEL_ID,
CHANNEL_SECRET,
code,
REDIRECT_URI
);
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;
}
}
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: "哈囉!"
}]);
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
}]);
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" }
]
}
}
}]);
await multicast(TOKEN, [userId1, userId2, userId3], [{
type: "text",
text: "大家好!"
}]);
5. 錯誤處理
正確處理來自 LINE API 的錯誤。
import { LineClientError } from "@linekit/messaging";
import { LineLoginError } from "@linekit/login";
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) {
} else if (error.status === 429) {
}
}
}
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") {
}
}
}
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
});
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...", ];
const results = await bulkMulticast(
TOKEN,
userList,
[{ type: "text", text: "每週更新!" }],
{
chunkSize: 500,
delayMs: 1000
}
);
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
});
const { liffId } = await client.add({
view: {
type: "full",
url: "https://mysite.com/app"
}
});
console.log("已建立 LIFF App: https://liff.line.me/" + liffId);