本日程式碼的範例連結
目前我們的 LINE Bot 主要使用 Text
方式回覆訊息。今天將探討 Text
類型的 Emoji
應用,以及 Sticker
回覆方式,基於 Day 8 版本進行改寫。
除了基礎的六種回覆類型外,LINE Bot 還提供了三種進階訊息回覆機制:
- LINE Text v1 官方說明
我們將先實作 v1
版本,再介紹 v2
版本的優勢。透過比較兩個版本的差異,了解為什麼需要升級到 v2,讓 LINE Bot 在回覆訊息時有更好的控制能力。
在開始之前,我們先對先前的版本進行重構。原先 line-webhook.module
負責太多職責,現在將其職責聚焦在接收 LINE 平台事件。
我們將把訊息格式處理獨立成專門的模組,這樣的架構有以下優勢:
Push
方式發送訊息時,也能複用訊息處理邏輯nest g module line-message
nest g controller line-message --no-spec
nest g service line-message --no-spec
官方提供 LINE Emoji 清單
LINE Text Message 不論是 v1 或 v2 版本,都支援發送以下兩種類型的表情符號:
Text Message v1
使用官方提供的 LINE Emoji 時,必須透過 emoji
物件來指定:
LINE Emoji
包我們可以在 line-message
資料夾中新增 types 資料夾,用於存放自定義的型別定義。
line-message/types/text-message.ts
假設我們這邊定義三個主題包的 Emoji 001 ~ 009 可以使用的情境!否則每次都要查詢
productId
// 假設我們只有使用到 001 ~ 009
type Emojis = ['001', '002', '003', '004', '005', '006', '007', '008', '009'];
/**
* LINE Emoji Project IDs
* - 表情符號系列(可愛臉部表情):670e0cce840a8236ddd4ee4c
* - 派對帽、蛋糕、禮物等慶祝主題:5ac2213e040ab15980c9b447
* - 彩色英文字母 A~I:5ac21a8c040ab15980c9b43f
*/
type ProjectIds = [
'670e0cce840a8236ddd4ee4c',
'5ac2213e040ab15980c9b447',
'5ac21a8c040ab15980c9b43f',
];
export interface TextMessageReq {
text: string;
emoji?: {
index: number;
productId: ProjectIds[number];
emojiId: Emojis[number];
};
}
函數採用 TextMessageReq
進行封裝,在使用時,IDE 會自動提供提示,顯示所有可用的輸入選項。如需了解特定參數 ID 的詳細定義,可直接看 textMessageReq
型別定義中的 JSDoc
說明文檔,以了解完整的參數說明。
方便後續可以加上預設值、驗證、客製化處理等機制!
在 Text(v1)
版本當中,可以把 $
字符替換成對應的官方 Emoji
。雖然這是可選欄位,但我們可以調整函式,讓使用者只需插入一個 Emoji 就能完成。
當 Emoji
插入位置超出原始字串長度時,我們使用 ~
字符補足字串長度作為占位符,確保索引位置有效,避免發送訊息時出現錯誤。
line-message/line-message.service.ts
// 略
export class LineMessageService {
createTextMessage(textMessageReq: TextMessageReq): TextMessage {
const { text, emoji } = textMessageReq;
let modifiedText = text;
// 如果有 Emoji 幫我插入指定位置
if (emoji) {
const emojiIndex = emoji.index;
const placeholderChar = '~';
const textArr = Array.from(text.padStart(emojiIndex, placeholderChar));
textArr.splice(emojiIndex, 0, '$ ');
modifiedText = textArr.join('');
}
const textMessage: TextMessage = {
type: MessageType.Text,
text: modifiedText,
...(emoji && {
emojis: [
{
index: emoji.index,
productId: emoji.productId,
emojiId: emoji.emojiId,
},
],
}),
};
return textMessage;
}
}
匯出
LineMessageService
,讓載入LineMessageModule
的模組可以使用此服務
line-message/line-message.module.ts
// 略
@Module({
providers: [LineMessageService],
exports: [LineMessageService],
})
export class LineMessageModule {}
line-webhook/line-webhook.module.ts
// 略
@Module({
imports: [LineMessageModule],
controllers: [LineWebhookController],
providers: [LineWebhookService],
})
export class LineWebhookModule {}
注入模組後可以透過初始化的
lineMessageService
實例操作創建的方法
import { LineMessageService } from 'src/line-message/line-message.service';
export class LineWebhookService {
constructor(
private readonly lineMessageService: LineMessageService,
) {}
}
Message
型別是 LINE 發送訊息所有型別的聯合型別
確保最終產生結果的型別符合 LINE 發送訊息所需的 Message
格式。未來這個格式除了可以應用在 LINE Reply Message
之外,也可以搭配 LINE Push Message
的方式使用。
line-webhook/line-webhook.type.ts
export type MessageEventHandlerMap = {
[K in EventMessage['type']]: (
event: Extract<EventMessage, { type: K }>,
) => Extract<Message, { type: K }>;
};
結合 line-message.service
處理 textMessage(v1)
格式,並根據是否需要 Emoji
自動切換處理:
Emoji
時,只需提供 text 回覆文字Emoji
時可指定插入位置(目前限制只能插入一個 Emoji)line-webhook/line-webhook.service.ts
private async handleMessageEvent(event: MessageEvent): Promise<void> {
const messageEventHandlerMap = {
text: (message) =>
this.lineMessageService.createTextMessage({
text: message.text,
emoji: {
index: 0,
productId: '5ac21c4e031a6752fb806d5b',
emojiId: '006',
},
}),
} satisfies Partial<MessageEventHandlerMap>;
let replyMessage;
const handler = messageEventHandlerMap[event.message.type];
// 這邊記得移除 await !!!
if (handler) replyMessage = handler(event.message);
await this.lineClient.replyMessage({
replyToken: event.replyToken,
messages: [replyMessage],
});
}
- LINE TextMessage(v2) 官方說明
以 Emoji
為例,置放方式不再像 v1
需要指定 index
,而是直接使用 {laugh}
置換字串特定位置,使用上更加方便。
v2 相對於 v1 有三大特色:
{laugh}
代表 emoji{user1}
代表 tag 特定用戶{everyone}
代表 tag 所有人emoji
和 mentions
混合在字串中使用line-message/line-message.service
createTextMessageV2(textMessageReq: TextMessageReq): TextMessageV2 {
const { text, emoji } = textMessageReq;
const modifiedText = `{laugh} ${text} {laugh}`; // Emoji 改使用 {laugh} 替換
const textMessage: TextMessageV2 = {
type: MessageType.TextV2,
text: modifiedText,
...(emoji && {
substitution: {
laugh: {
type: 'emoji',
productId: emoji.productId,
emojiId: emoji.emojiId,
},
},
}),
};
return textMessage;
}
這部分的處理跟文字和 Emoji 相似,但 Emoji 有 Unicode Emoji 可以選擇,而貼圖透過 API 只能使用 LINE 官方列出的貼圖包編號(packageId)
和貼圖編號(stickerId)
。
處理思路與文字 Emoji 相同,都是希望能限制使用的貼圖包跟貼圖區間,並搭配 IDE 型別提示加速開發
line-message/types/sticker-message.ts
export const stickerIds = [
{
packageId: '446',
stickerIds: ['1988', '1989', '1990', '1991', '1992'],
},
{
packageId: '789',
stickerIds: ['10855', '10856', '10857', '10858', '10859'],
},
] as const;
export type StickerMap = {
[K in (typeof stickerIds)[number]['packageId']]: Extract<
(typeof stickerIds)[number],
{ packageId: K }
>['stickerIds'][number][];
};
export type StickerMessageReq = {
[K in keyof StickerMap]: {
packageId: K;
stickerId: StickerMap[K][number];
};
}[keyof StickerMap];
使用者只需要決定要發送哪個貼圖包及貼圖編號
line-webhook/line-webhook.service
private async handleMessageEvent(event: MessageEvent): Promise<void> {
const messageEventHandlerMap = {
sticker: () =>
this.lineMessageService.createStickerMessage({
packageId: '6359',
stickerId: '11069850',
}),
} satisfies Partial<MessageEventHandlerMap>;
}
選擇不同的貼圖包出現的 stickerId
提示也會不同,感覺開發使用上會方便許多!!
畫了兩天的圖文選單,回歸到 LINE Message 的回覆上逐一說明。雖然 LINE Bot 文字回覆搭配 Emoji 跟貼圖回覆在實務上都很少見,送訊息通常會想要更多效果,一般會使用 Flex Message
跟 ImageMap Message
更為常見,但是能更深入理解 LINE Bot 還是很開心!
今天的部分,主要也是想了很久,思考在要發送貼圖有沒有什麼辦法可以有提示!!想了很久,或許這反而花更多時間,但是目前這個方式還算滿意,如果你有更棒的想法也歡迎分享。