Bottender - Linebot 實踐

tags: Bottender Node.js
category: Linebot
description: Bottender - Linebot 實踐
created_at: 2021/07/12 21:00:00

cover image


回到 手把手使用 Bottender 建立 Linebot


這篇會用到 Line Bot Designer ,所以可以先去官方網站下載:

https://developers.line.biz/en/bot-designer/download/


看看 Context

在開始使用 Designer 之前,先來了解一下怎麼回應給 User ,所以先打開專案下的 src/index.js 看看裡面有什麼東西

module.exports = async function App(context) {
  await context.sendText('Welcome to Bottender');
};

依照上次最後的結果,是輸入任何訊息都會回應 Welcome to Bottender,所以最基本的就是使用 context.sendText(訊息) 送出訊息給使用者。

那再來看看 context 裡面有什麼東西

LineContext {
  // ...
  _client: LineClient {
    // ...
    axios: [Function: wrap] {
      request: [Function: wrap],
      getUri: [Function: wrap],
      delete: [Function: wrap],
      get: [Function: wrap],
      head: [Function: wrap],
      options: [Function: wrap],
      post: [Function: wrap],
      put: [Function: wrap],
      patch: [Function: wrap],
      defaults: [Object],
      interceptors: [Object]
    },
    // ...
  },
  _event: LineEvent {
    _rawEvent: {
      type: 'message',
      message: { type: 'text', id: '14383270522846', text: 'Hello' },
      timestamp: 1626120352749,
      source: [Object],
      replyToken: '...',
      mode: 'active'
    },
    _destination: '...'
  },
  // ...
}

可以看到其實裡面已經包了一個 axios ,所以如果之後需要打 HTTP Request,可以直接拿來用

另外 event 就是我們主要會需要用到的東西,稍微看一下他的原始碼有什麼能用,而這些方法大多都能夠在官方文件中找到用法與範例: https://bottender.js.org/docs/en/api-line-event

// ...
export default class MessengerEvent implements Event<MessengerRawEvent> {
    // ...
    get rawEvent(): MessengerRawEvent;
    get timestamp(): number;
    get isMessage(): boolean;
    get message(): EventMessage | null;
    get isText(): boolean;
    get text(): string | null;
    get hasAttachment(): boolean;
    get attachments(): EventMessageAttachment[] | null;
    get isImage(): boolean;
    get image(): MediaAttachmentPayload | null;
    get isAudio(): boolean;
    get audio(): MediaAttachmentPayload | null;
    get isVideo(): boolean;
    get video(): MediaAttachmentPayload | null;
    get isLocation(): boolean;
    get location(): LocationAttachmentPayload | null;
    get isFile(): boolean;
    get file(): MediaAttachmentPayload | null;
    get isFallback(): boolean;
    get fallback(): FallbackAttachment | null;
    get isSticker(): boolean;
    get sticker(): number | null;
    get isLikeSticker(): boolean;
    get isQuickReply(): boolean;
    get quickReply(): EventMessageQuickReply | null;
    get isEcho(): boolean;
    get isPostback(): boolean;
    get postback(): EventPostback | null;
    get isGamePlay(): boolean;
    get gamePlay(): EventGamePlay | null;
    get isOptin(): boolean;
    get optin(): EventOptin | null;
    get isPayment(): boolean;
    get payment(): EventPayment | null;
    get isCheckoutUpdate(): boolean;
    get checkoutUpdate(): EventCheckoutUpdate | null;
    get isPreCheckout(): boolean;
    get preCheckout(): EventPreCheckout | null;
    get isRead(): boolean;
    get read(): EventRead | null;
    get isDelivery(): boolean;
    get delivery(): EventDelivery | null;
    get isPayload(): boolean;
    get payload(): string | null;
    get isPolicyEnforcement(): boolean;
    get policyEnforcement(): EventPolicyEnforcement | null;
    get isAppRoles(): boolean;
    get appRoles(): EventAppRoles | null;
    get isStandby(): boolean;
    get isPassThreadControl(): boolean;
    get passThreadControl(): EventPassThreadControl | null;
    get isTakeThreadControl(): boolean;
    get takeThreadControl(): EventTakeThreadControl | null;
    get isRequestThreadControl(): boolean;
    get isRequestThreadControlFromPageInbox(): boolean;
    get requestThreadControl(): EventRequestThreadControl | null;
    get isFromCustomerChatPlugin(): boolean;
    get isReferral(): boolean;
    get referral(): EventReferral | null;
    get ref(): string | null;
    get pageId(): string | null;
    get isBrandedCamera(): boolean;
    get brandedCamera(): EventBrandedCamera | null;
    get isAccountLinking(): boolean;
    get accountLinking(): EventAccountLinking | null;
    get isReaction(): boolean;
    get reaction(): EventReaction | null;
}

接收文字

所以假設我要取得使用者傳來的文字,回應他我收到什麼,只要像這樣:

module.exports = async function App(context) {
    if (context.event.isText) {
        await context.sendText(`我收到: ${context.event.text}`);
    } else {
        await context.sendText('你確定你傳的是文字?');
    }
};


Line Bot Designer

那學會回應文字給使用者之後,再來要嘗試怎麼回應一些 特殊 的東西給使用者,例如常常看到的下面這種內容

這時候我們就需要 Line Bot Designer 來輔助了,所以先打開他並登入

看到主畫面之後開啟新專案

  • 左邊可以建立訊息
  • 中間可以修改訊息的一些屬性
  • 右上方是預覽
  • 右下方是我們最後需要用的 JSON

Linebot 中訊息可以使用 JSON 做回應,所以剛才已經先使用函數的方式回應文字,現在改用 JSON 的方式回應試試

所以先在 Designer 左方建立一個文字訊息,會看到像這樣的結果

那我們把右邊的json拿來用

{
  "type": "text",
  "text": "您好!"
}

再來把程式改成這樣:

module.exports = async function App(context) {
    await context.send([{
        "type": "text",
        "text": "您好!"
    }]);
};

這樣他就會回應 您好! ,再來可能注意到了他是吃一個陣列,所以可以同時回應多個,所以如果改成下面這樣,他就會回應兩次 您好!

module.exports = async function App(context) {
    await context.send([{
        "type": "text",
        "text": "您好!"
    }, {
        "type": "text",
        "text": "您好!"
    }]);
};


圖文選單

再來下一步來做 圖文選單,圖文選單就是在官方帳號很常見的一個功能,可以在下方打開選單,點擊對應的功能,所以我們需要一張圖

打開你的小畫家

上面是我要使用的範例圖,如果沒有要自己設計一張的話可以考慮使用

官方連結: https://developers.line.biz/en/docs/messaging-api/using-rich-menus/

基本上 Line 圖文選單的邏輯是

  • 先定義每個區塊的大小與行為並註冊(POST),假設為 A
  • 上傳圖片綁定在 A 身上
  • 設定 A 為預設選單或給單一或特定使用者

所以我們先到 Designer 上新增一個圖文選單,並選擇幾個基本的行為:

  • 訊息動作: 單純回應訊息
  • 回傳動作: 回應訊息 + 傳送資料到 Server

在右上方預覽區域可以框選區域,大概框好設定好之後會變成這樣

建立一個圖文選單,老樣子的需要拿右下角的 JSON 來用

官方文件: https://developers.line.biz/en/docs/messaging-api/using-rich-menus/#create-a-rich-menu

可以看到以下資訊

POST: https://api.line.me/v2/bot/richmenu
Headers: {
    Authorization: Bearer {channel access token}',
    Content-Type: application/json'
}
Body: 那一坨 JSON

這邊可以使用 Postman 或是你熟悉的方式建立 HTTP POST

Postman 官方下載: https://www.postman.com/downloads/

POST 成功後會得到一個 id,這個 id 就代表剛才那組區域,所以要留好。

輸出範例

{
    "richMenuId": "richmenu-4e79b6821d8f3afa5f11254558bae360"
}

註冊好之後,再來要上傳圖片綁在他上面

官方文件: https://developers.line.biz/en/docs/messaging-api/using-rich-menus/#upload-the-rich-menu-image

上傳圖檔限制: https://developers.line.biz/en/reference/messaging-api/#upload-rich-menu-image

限制大致上就是:

  • 格式: JPEG, PNG
  • 寬度: 800px ~ 2500px
  • 高度: 250px ~ 更高
  • 寬高比: 1.45 or more
  • 大小限制: 1 MB
POST: https://api-data.line.me/v2/bot/richmenu/{richMenuId}/content
Headers: {
    Authorization: Bearer {channel access token}',
    Content-Type: image/(jpeg|png)'
}
Body: 圖檔

例如以我剛才的範例就是要POST: https://api-data.line.me/v2/bot/richmenu/richmenu-4e79b6821d8f3afa5f11254558bae360/content 這個網址

如果沒有錯誤的話會得到一個 {} 的回應

再來就是要綁定到使用者身上

官方文件: https://developers.line.biz/en/docs/messaging-api/using-rich-menus/#set-the-default-rich-menu

POST: https://api.line.me/v2/bot/user/all/richmenu/{richMenuId}
Headers: {
    Authorization: Bearer {channel access token}',
}

以我這次的例子的話就是去POST https://api.line.me/v2/bot/user/all/richmenu/richmenu-4e79b6821d8f3afa5f11254558bae360 這個網址

執行正常的話一樣會收到一個 {} 的回應,再來回到 Linebot 上面看就會發現已經成功綁定上去了。

綁定上去之後可以點點每一個區域,都會有對應到的行為,如果程式碼沒動的話,點擊上面兩個(A, B區域)都會顯示出兩次的 你好! ,而下面兩個(C, D區域)卻會顯示四次的 你好! ,這是為什麼呢?

因為上面有提到 回傳動作: 回應訊息 + 傳送資料到 Server 這件事情,所以他做了兩件事,而你 Server 又沒多做處理,自然就等於觸發了兩遍,當然就會有四次的輸出訊息。


處理回傳動作

你可以先 console.log(context.event) 把事件印出來看一下,這時你點擊 C區域 你應該會看到

LineEvent {
  _rawEvent: {
    type: 'postback',
    postback: { data: 'Hi Hi' },
    timestamp: 1626133167368,
    source: { type: 'user', userId: '...' },
    replyToken: '...',
    mode: 'active'
  },
  _destination: '...'
}
LineEvent {
  _rawEvent: {
    type: 'message',
    message: { type: 'text', id: '14383803081484', text: '動作 3' },
    timestamp: 1626133167368,
    source: { type: 'user', userId: '...' },
    replyToken: '...',
    mode: 'active'
  },
  _destination: '...'
}

不只有收到一個 text 的事件以外,還額外收到了一個 postback 的事件,這時你可以稍微做一點處理,例如

module.exports = async function App(context) {
    const {event} = context;

    if (event.isPostback) {
        const {data} = event.postback;
        console.log(data);
    } else if (event.isMessage) {
        await context.send([{
            "type": "text",
            "text": "您好!"
        }]);
    }
};

如果他是 postback 的事件,就會把資料抓出來印出來,而如果是一般的訊息就直接返回 您好!

最後這個時候你應該有能力自己根據每一種訊息做對應的處理了,下一篇將來使用 LIFF,在你的 Linebot 中使用網頁




最後更新時間: 2021年07月12日.