LINE WORKS Bot APIをひと通り触ってみる（node.js）#2

LINEWORKS Advent Calendar 2019 / 25日目の最終日の記事です。

本記事では、LINE WORKS Bot の[メッセージ送信 API](https://developers.worksmobile.com/jp/document/1005008?lang=ja) をnode.jsでひと通り触ってみたいと思います。

前回：[LINE WORKS Bot APIをひと通り触ってみる（node.js）#1](https://qiita.com/shotamaco/items/bd510729adc0497d7d0b)

#0. はじめに
記事の流れになります。

1. こんなの作ります
2. 環境準備
3. 作ってみる
4. 動かしてみる
5. 気づいたこと
6. まとめ

#1. こんなの作ります
LINE WORKS Botのメッセージ送信には下表のタイプが存在し、そのタイプをすべて網羅するBotを作ります。
また、quick reply （クイックリプライ）も使用します。

| 送信タイプ | 説明 |
|:--|:--|
| [text](https://developers.worksmobile.com/jp/document/100500801?lang=ja) | テキストメッセージ送信 |
| [image](https://developers.worksmobile.com/jp/document/100500802?lang=ja) | 画像送信 |
| [link](https://developers.worksmobile.com/jp/document/100500803?lang=ja) | リンク送信 |
| [sticker](https://developers.worksmobile.com/jp/document/100500806?lang=ja) | スタンプ送信 |
| [button_template](https://developers.worksmobile.com/jp/document/100500804?lang=ja) | ボタンテンプレート送信 |
| [list_template](https://developers.worksmobile.com/jp/document/100500805?lang=ja) | リストテンプレート送信 |
| [carousel](https://developers.worksmobile.com/jp/document/100500808?lang=ja) | カルーセル送信 |
| [image_carousel](https://developers.worksmobile.com/jp/document/100500809?lang=ja) | 画像カルーセル送信 |

 * LINE WORKS アプリより、下記の値を入力するとそれぞれのメッセージを Bot が返します。

| 入力値(※1) | Botが返すメッセージ |
|:--|:--|
| Q | クイックリプライ |
| B | ボタンテンプレート |
| L | リストテンプレート |
| C | カルーセル |
| I | 画像カルーセル |
| U | リンク ＋ クイックリプライ |
| スタンプ(※2) | 同じスタンプ ＋ クイックリプライ |
| 画像 | 同じ画像 ＋ クイックリプライ |
| 場所 | 住所、経度緯度の文字 ＋ クイックリプライ |
| その他の文字 | テキストメッセージ |

　　※1)入力値は大文字小文字を意識しません
　　※2)使用できるスタンプは[こちら](https://static.worksmobile.net/static/wm/media/message-bot-api/line_works_sticker_list_new.pdf)

 * クイックリプライ、ボタンテンプレート、リストテンプレート、カルーセル、画像カルーセルが使用できる Action Object もすべて網羅します。

![04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528703/385d97d3-2da2-aa99-668a-5ec5b67eb810.png)|
|:-:|

![05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528703/78b85944-93c4-84a2-b527-8c70f319ca42.png)|
|:-:|

![07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528703/c923c534-6e21-a354-e608-443d299d5dac.png)|
|:-:|

![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528703/83357ba2-7905-26ef-ebca-e06884908ba1.png)|
|:-:|






#2. 環境準備

「[LINE WORKS Bot APIをひと通り触ってみる（node.js）#1](https://qiita.com/shotamaco/items/bd510729adc0497d7d0b) 」の 「[2. 環境準備](https://qiita.com/shotamaco/items/bd510729adc0497d7d0b#2-%E7%92%B0%E5%A2%83%E6%BA%96%E5%82%99) 」を参照。

#3. 作ってみる

「[LINE WORKS Bot APIをひと通り触ってみる（node.js）#1](https://qiita.com/shotamaco/items/bd510729adc0497d7d0b) 」の 「[メッセージの送受信制御 (BotMessageService.js)](https://qiita.com/shotamaco/items/bd510729adc0497d7d0b#%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E3%81%AE%E9%80%81%E5%8F%97%E4%BF%A1%E5%88%B6%E5%BE%A1-botmessageservicejs) 」の BotMessageService クラスをカスタマイズします。
ベタ書き＆すべての機能を網羅するため長いプログラムになってすいません。

```JavaScript:BotMessageService.js
const request = require('request');

/**
 * コールバックタイプ
 */
const CALL_BACK_TYPE = {
  /**
   * メンバーからのメッセージ
   */
  message : 'message',
  /**
   * Bot が複数人トークルームに招待された
   * このイベントがコールされるタイミング
   *  ・API を使って Bot がトークルームを生成した
   *  ・API を使って Bot がトークルームを生成した
   *  ・メンバーが Bot を含むトークルームを作成した
   *  ・Bot が複数人のトークルームに招待された
   * ※メンバー１人と Bot のトークルームに他のメンバーを招待したらjoinがコールされる（最初の１回だけ）
   *  招待したメンバーを退会させ、再度他のメンバーを招待するとjoinedがコールされるこれ仕様？
   *  たぶん、メンバー１人と Botの場合、トークルームIDが払い出されてないことが原因だろう。。。
   */
  join : 'join',
  /**
   * Bot が複数人トークルームから退室した
   * このイベントがコールされるタイミング
   *  ・API を使って Bot を退室させた
   *  ・メンバーが Bot をトークルームから退室させた
   *  ・何らかの理由で複数人のトークルームが解散した
   */
  leave : 'leave',
  /**
   * メンバーが Bot のいるトークルームに参加した
   * このイベントがコールされるタイミング
   *  ・Bot がトークルームを生成した
   *  ・Bot が他のメンバーをトークルームに招待した
   *  ・トークルームにいるメンバーが他のメンバーを招待した
   */
  joined : 'joined',
  /**
   * メンバーが Bot のいるトークルームから退室した
   * このイベントがコールされるタイミング
   *  ・Bot が属するトークルームでメンバーが自ら退室した、もしくは退室させられた
   *  ・何らかの理由でトークルームが解散した
   */
  left : 'left',
  /**
   * postback タイプのメッセージ
   * このイベントがコールされるタイミング
   *  ・メッセージ送信(Carousel)
   *  ・メッセージ送信(Image Carousel)
   *  ・トークリッチメニュー
   */
  postback : 'postback',
};

/**
 * コールバックコンテンツタイプ
 */
const CALL_BACK_MESSAGE_CONTENT_TYPE = {
  /**
   * テキスト
   */
  text : 'text',
  /**
   * 場所
   */
  location : 'location',
  /**
   * スタンプ
   */
  sticker : 'sticker',
  /**
   * 画像
   */
  image : 'image'
};

/**
 * メッセージコンテンツタイプ
 */
const MESSAGE_CONTENT_TYPE = {
  /**
   * テキスト
   */
  text : 'text',
  /**
   * 画像
   */
  image : 'image',
  /**
   * リンク
   */
  link : 'link',
  /**
   * スタンプ
   */
  sticker : 'sticker',
  /**
   * ボタンテンプレート
   */
  buttonTemplate : 'button_template',
  /**
   * リストテンプレート
   */
  listTemplate : 'list_template',
  /**
   * カルーセル
   */
  carousel : 'carousel',
  /**
   * 画像カルーセル
   */
  imageCarousel : 'image_carousel'
};

/**
 * BotMessageServiceクラス
 */
module.exports = class BotMessageService {

  /**
   * BotMessageServiceを初期化します。
   * @param {string} serverToken Serverトークン
   */
  constructor (serverToken) {
    this._serverToken = serverToken;
    this.imageIndex = 0;
  }

  /**
   * LINE WORKS にBotメッセージを送信します。
   * @param {object} callbackEvent リクエストのコールバックイベント
   */
  async send(callbackEvent) {
    let res = this._getResponse(callbackEvent);
    if (!res) return;
    return new Promise((resolve, reject) => {
      // LINE WORKS にメッセージを送信するリクエスト
      request.post(this._createMessage(res), (error, response, body) => {
          if (error) {
            console.log('BotService.send error');
            console.log(error);
          }
          console.log(body);
          // 揉み消してます！
          resolve();
      });
    });
  }

  /**
   * LINE WORKS に送信するBotメッセージを作成して返します。
   * @param {object} res レスポンスデータ
   */
  _createMessage(res) {
    return {
      url: `https://apis.worksmobile.com/r/${process.env.API_ID}/message/v1/bot/${process.env.BOT_NO}/message/push`,
      //url: `https://apis.worksmobile.com/${process.env.API_ID}/message/sendMessage/v2`,
      headers: {
        'Content-Type': 'application/json;charset=UTF-8',
        consumerKey: process.env.CONSUMER_KEY,
        Authorization: `Bearer ${this._serverToken}`
      },
      json: res
    };
  }

  /**
   * メンバーIDを連結して返します。
   * @param {Array} memberList メンバーリスト
   * @return {string} メンバーIDリスト文字列
   */
  _buildMember(memberList) {
    let result = '';
    if (!memberList) return result;
    memberList.forEach(m => {
      if (result.length > 0)　result += ',';
      result += m;
    });
    return result;
  }
  
  /**
   * Bot実装部
   * @param {object} callbackEvent リクエストのコールバックイベント
   * @return {string} レスポンスメッセージ
   */
  _getResponse(callbackEvent) {
    console.log(callbackEvent);

    let res = {};

    if (callbackEvent.source.roomId) {
      // 受信したデータにトークルームIDがある場合は、送信先にも同じトークルームIDを指定します。
      res.roomId = callbackEvent.source.roomId;
    } else {
      // トークルームIDがない場合はBotとユーザーとの1:1のチャットです。
      res.accountId = callbackEvent.source.accountId;
    }

    switch (callbackEvent.type) {
      case CALL_BACK_TYPE.message:

        switch (callbackEvent.content.type) {
          case CALL_BACK_MESSAGE_CONTENT_TYPE.text:
            if (callbackEvent.content.postback == 'start') {
              // メンバーと Bot との初回トークを開始する画面で「利用開始」を押すと、自動的に「利用開始」というメッセージがコールされる
              console.log(`start`);
              res.content = { type: MESSAGE_CONTENT_TYPE.text, text: 'ト〜クルームに〜〜。ボトやまが〜くる〜！\n下記を入力するとボトやまが特別な応答をします（大文字小文字を区別しません）。\n・b:button template\n・l:List template\n・c:carousel\n・i:image carousel\n・q:quick reply' };
              return res;
            }

            let content = this._getButtonTemplateContent(callbackEvent.content.postback, callbackEvent.content.text)
              || this._getListTemplateContent(callbackEvent.content.postback, callbackEvent.content.text)
              || this._getCarouselContent(callbackEvent.content.postback, callbackEvent.content.text)
              || this._getImageCarouselContent(callbackEvent.content.postback, callbackEvent.content.text)
              || this._getQuickReplyContent(callbackEvent.content.postback, callbackEvent.content.text)
              || this._getLinkContent(callbackEvent.content.postback, callbackEvent.content.text);
            if (content) {
              res.content = content;
            } else {
              console.log(CALL_BACK_TYPE.message);
              res.content = { type: MESSAGE_CONTENT_TYPE.text, text: `ですよね〜〜〜。\n（受信データ：${callbackEvent.content.text}）` };
            }
            break;
    
          case CALL_BACK_MESSAGE_CONTENT_TYPE.location:
            // 場所のコールバックは場所データをテキストで返す
            res.content = { 
              type: MESSAGE_CONTENT_TYPE.text, 
              text: `住所：${callbackEvent.content.address}\n緯度：${callbackEvent.content.latitude}\n経度：${callbackEvent.content.longitude}`,
              quickReply: this._getQuickReplyItems()
             };
            break;
    
          case CALL_BACK_MESSAGE_CONTENT_TYPE.sticker:
            // スタンプのコールバックはおうむ返し（同じスタンプを返す）
            // ※使えないスタンプがあるようです（LINE WORKSぽいスタンプは使えない。。。）
            res.content = { 
              type: MESSAGE_CONTENT_TYPE.sticker, 
              packageId: callbackEvent.content.packageId, 
              stickerId: callbackEvent.content.stickerId,
              quickReply: this._getQuickReplyItems()
            };
            break;
    
          case CALL_BACK_MESSAGE_CONTENT_TYPE.image:
            // 画像のコールバックはおうむ返し（同じ画像を返す）
            res.content = { 
              type: MESSAGE_CONTENT_TYPE.image, 
              resourceId: callbackEvent.content.resourceId,
              quickReply: this._getQuickReplyItems()
            };
            break;
          
          default:
            console.log('知らないcontent.typeですね。。。');
            return null;
        }
        break;

      case CALL_BACK_TYPE.join:
        console.log(CALL_BACK_TYPE.join);
        res.content = { type: MESSAGE_CONTENT_TYPE.text, text: 'うぃーん!' };
        break;

      case CALL_BACK_TYPE.leave:
        console.log(CALL_BACK_TYPE.leave);
        break;

      case CALL_BACK_TYPE.joined: {
        console.log(CALL_BACK_TYPE.joined);
        res.content = { type: MESSAGE_CONTENT_TYPE.text, text: `${this._buildMember(callbackEvent.memberList)} いらっしゃいませ〜そのせつは〜` };
        break;
      }

      case CALL_BACK_TYPE.left: {
        console.log(CALL_BACK_TYPE.left);
        res.content = { type: MESSAGE_CONTENT_TYPE.text, text: `${this._buildMember(callbackEvent.memberList)} そうなります？` };
        break;
      }

      case CALL_BACK_TYPE.postback:
        // QuickReply, Carousel, ImageCarouselからのPostback（このコールバック後、CALL_BACK_TYPE.messageのコールバックがコールされる）
        console.log(CALL_BACK_TYPE.postback);
        let content = this._getButtonTemplateContent(callbackEvent.data) 
        || this._getListTemplateContent(callbackEvent.data)
        || this._getCarouselContent(callbackEvent.data)
        || this._getImageCarouselContent(callbackEvent.data)
        || this._getQuickReplyContent(callbackEvent.data);
        if (content) res.content = content;
        break;

      default:
        console.log('知らないコールバックですね。。。');
        return null;
    }

    return res;
  }
  
  /**
   * Link コンテンツを返します。
   * @param {Array} conditions 条件
   * @return {object} コンテンツ
   */
  _getLinkContent(...conditions) {
    if (!conditions.some(condition => condition && condition.toUpperCase() === 'U')) return;
    return { 
      type: MESSAGE_CONTENT_TYPE.link, 
      contentText: 'Link からの〜〜〜。',
      linkText: 'LINE WORKS',
      link: 'https://line.worksmobile.com/jp/',
      quickReply: this._getQuickReplyItems()
    };
  }

  /**
   * Quick reply コンテンツを返します。
   * @param {Array} conditions 条件
   * @return {object} コンテンツ
   */
  _getQuickReplyContent(...conditions) {
    if (!conditions.some(condition => condition && condition.toUpperCase() === 'Q')) return;
    return { 
      type: MESSAGE_CONTENT_TYPE.text, 
      text: 'QuickReply からの〜〜〜。',
      quickReply: this._getQuickReplyItems()
    };
  }

  /**
   * Quick reply アイテムリストを返します。
   * @return {Array} アイテムリスト
   */
  _getQuickReplyItems() {
    return {
      items: [
        {
          imageUrl: `${process.env.IMAGE_FILE_HOST}/images/giraffe01.png`,
          action: {
            type: 'postback',
            label: 'ButtonTemp',
            data: 'button_template',
            displayText: 'button_template ください'
          }
        },
        {
          imageUrl: `${process.env.IMAGE_FILE_HOST}/images/panda01.png`,
          action: {
            type: 'postback',
            label: 'ListTemp',
            data: 'list_template',
            displayText: 'list_template ください'
          }
        },
        {
          imageUrl: `${process.env.IMAGE_FILE_HOST}/images/giraffe02.png`,
          action: {
            type: 'postback',
            label: 'Carousel',
            data: 'carousel',
            displayText: 'carousel ください'
          }
        },
        {
          imageUrl: `${process.env.IMAGE_FILE_HOST}/images/panda02.png`,
          action: {
            type: 'postback',
            label: 'ImageCarousel',
            data: 'image_carousel',
            displayText: 'image_carousel ください'
          }
        },
        {
          imageUrl: `${process.env.IMAGE_FILE_HOST}/images/sushi.png`,
          action: {
            type: 'postback',
            label: 'QuickReply',
            data: 'q',
            displayText: 'QuickReply ください'
          }
        },
        {
          imageUrl: `${process.env.IMAGE_FILE_HOST}/images/sushi.png`,
          action: {
            type: 'message',
            label: 'すし',
            text: 'すし'
          }
        },
        {
          imageUrl: `${process.env.IMAGE_FILE_HOST}/images/lw.png`,
          action: {
            type: 'uri',
            label: 'LINE WORKS',
            uri: 'https://line.worksmobile.com/jp/'
          }
        },
        {
          action: {
            type: 'camera',
            label: 'カメラ'
          }
        },
        {
          action: {
            type: 'cameraRoll',
            label: 'カメラロール'
          }
        },
        {
          action: {
            type: 'location',
            label: '場所'
          }
        }
      ]
    }
  }

  /**
   * Button template コンテンツを返します。
   * @param {Array} conditions 条件
   * @return {object} コンテンツ
   */
  _getButtonTemplateContent(...conditions) {
    if (!conditions.some(condition => condition && (condition.toUpperCase() === 'B' || condition === MESSAGE_CONTENT_TYPE.buttonTemplate))) return;
    return { 
      type: MESSAGE_CONTENT_TYPE.buttonTemplate, 
      contentText: 'ButtonTemplate からの〜〜〜。',
      actions: this._getButtonActions(),
      //quickReply: this._getQuickReplyItems()
    };
  }

  /**
   * Buttonアクションリストを返します。
   * @return {Array} アクションリスト
   */
  _getButtonActions() {
    return [
      // button_templateのactionでは typeはmessageとuriしか使えない。つまり postback、camera、cameraRoll、locationは使えない
      {
        type: 'message',
        label: 'Message lable',
        text: 'Message text'
      },
      {
        type: 'message',
        label: 'Button postback',
        text: 'button_template ください',
        postback: 'button_template' 
      },
      {
        type: 'message',
        label: 'List postback',
        text: 'list_template ください',
        postback: 'list_template'
      },
      {
        type: 'message',
        label: 'Carousel postback',
        text: 'carousel ください',
        postback: 'carousel'
      },
      {
        type: 'message',
        label: 'Image carousel pb',
        text: 'image_carousel ください',
        postback: 'image_carousel'
      },
      {
        type: 'message',
        label: 'QuickReply postback',
        text: 'QuickReply ください',
        postback: 'q'
      },
      {
        type: 'uri',
        label: 'LINE WORKS',
        uri: 'https://line.worksmobile.com/jp/'
      }
    ];
  }

  /**
   * List template コンテンツを返します。
   * @param {Array} conditions 条件
   * @return {object} コンテンツ
   */
  _getListTemplateContent(...conditions) {
    if (!conditions.some(condition => condition && (condition.toUpperCase() === 'L' || condition === MESSAGE_CONTENT_TYPE.listTemplate))) return;
    return { 
      type: MESSAGE_CONTENT_TYPE.listTemplate, 
      coverData: {
        backgroundImage: `${process.env.IMAGE_FILE_HOST}/images/lw.png`,
        //backgroundResourceId: '',
        title: 'ListTemplate からの〜〜〜。(title)',
        subtitle: 'サブタイトル',
      },
      // 最大4つの要素を指定可能
      elements: this._getListElements(),
      // 最大2*2の配列でアクションを指定可能
      actions: this._getListActions(),
      //quickReply: this._getQuickReplyItems()
    };
  }

  /**
   * List要素リストを返します。
   * @return {Array} 要素リスト
   */
  _getListElements() {
    // list_template.elementsのactionでは typeはmessageとuriしか使えない。つまり postback、camera、cameraRoll、locationは使えない
    return [
      {
        title: 'List message title',
        subtitle: 'List message subtitle',
        image: `${process.env.IMAGE_FILE_HOST}/images/lw.png`,
        //resourceId: '',
        action: {
          type: 'message',
          label: 'Message',
          text: 'Message text'
        }
      },
      {
        title: 'Button postback title',
        subtitle: 'Button postback subtitle',
        image: `${process.env.IMAGE_FILE_HOST}/images/lw.png`,
        //resourceId: '',
        action: {
          type: 'message',
          label: 'Button',
          text: 'button_template ください',
          postback: 'button_template'
        }
      },
      {
        title: 'List postback title',
        subtitle: 'List postback subtitle',
        image: `${process.env.IMAGE_FILE_HOST}/images/lw.png`,
        //resourceId: '',
        action: {
          type: 'message',
          label: 'List',
          text: 'list_template ください',
          postback: 'list_template'
        }
      },
      {
        title: 'List uri title',
        subtitle: 'List uri subtitle',
        image: `${process.env.IMAGE_FILE_HOST}/images/security.png`,
        //resourceId: '',
        action: {
          type: 'uri',
          label: 'LINE WORKS',
          uri: 'https://line.worksmobile.com/jp/'
        }
      }
    ];
  }

  /**
   * Listアクションリストを返します。
   * @return {Array} アクションリスト
   */
  _getListActions() {
    // list_template.actionsのactionでは typeはmessageとuriしか使えない。つまり postback、camera、cameraRoll、locationは使えない
    return [
      [
        {
          type: 'message',
          label: 'Carousel postback',
          text: 'carousel ください',
          postback: 'carousel'
        },
        {
          type: 'message',
          label: 'Image Car postback',
          text: 'image_carousel ください',
          postback: 'image_carousel'
        }
      ],
      [
        {
          type: 'message',
          label: 'QuickReply',
          text: 'QuickReply ください',
          postback: 'q'
        },
        {
          type: 'message',
          label: 'No',
          text: 'No'
        }
      ]
    ];
  }

  /**
   * Carousel コンテンツを返します。
   * @param {Array} conditions 条件
   * @return {object} コンテンツ
   */
  _getCarouselContent(...conditions) {
    if (!conditions.some(condition => condition && (condition.toUpperCase() === 'C' || condition === MESSAGE_CONTENT_TYPE.carousel))) return;
    return { 
      type: MESSAGE_CONTENT_TYPE.carousel,
      //imageAspectRatio: '',
      //imageSize: '',
      columns: this._getCarouselColumns(),
      //quickReply: this._getQuickReplyItems()
    };
  }

  /**
   * Carousel カラムリストを返します。
   * @return {Array} カラムリスト
   */
  _getCarouselColumns() {
    // carousel.columnsのactionでは typeはmessageとuri、postbackしか使えない。つまり camera、cameraRoll、locationは使えない
    // carouselは、postbackをつかえる！！！！
    return [
      {
        thumbnailImageUrl: `${process.env.IMAGE_FILE_HOST}/images/giraffe01.png`,
        //thumbnailImageResourceId: '',
        title: 'Carousel postback title',
        text: 'Carousel postback text (default button)',
        defaultAction: {
          type: 'postback',
          label: 'ButtonTemp',
          data: 'button_template',
          displayText: 'button_template ください'
        },
        actions: [
          {
            type: 'postback',
            label: 'ListTemp',
            data: 'list_template',
            displayText: 'list_template ください'
          },
          {
            type: 'postback',
            label: 'Carousel',
            data: 'carousel',
            displayText: 'carousel ください'
          },
          {
            type: 'postback',
            label: 'QuickReply',
            data: 'q',
            displayText: 'QuickReply ください'
          }
        ]
      },
      {
        thumbnailImageUrl: `${process.env.IMAGE_FILE_HOST}/images/panda01.png`,
        //thumbnailImageResourceId: '',
        title: 'Carousel uri title',
        text: 'Carousel uri text',
        defaultAction: {
          type: 'uri',
          label: 'LINE WORKS',
          uri: 'https://line.worksmobile.com/jp/'
        },
        actions: [
          {
            type: 'uri',
            label: 'LINE WORKS',
            uri: 'https://line.worksmobile.com/jp/'
          },
          {
            type: 'uri',
            label: 'bot Action Objects',
            uri: 'https://developers.worksmobile.com/jp/document/1005050?lang=ja'
          }
        ]
      },
      {
        thumbnailImageUrl: `${process.env.IMAGE_FILE_HOST}/images/sushi.png`,
        //thumbnailImageResourceId: '',
        title: 'Carousel message title',
        text: 'Carousel message text',
        defaultAction: {
          type: 'message',
          label: 'Message',
          text: 'Message text'
        },
        actions: [
          {
            type: 'message',
            label: 'Yes',
            text: 'Yes'
          },
          {
            type: 'message',
            label: 'No',
            text: 'No'
          }
        ]
      }
    ];
  }

  /**
   * Image carousel コンテンツを返します。
   * @param {Array} conditions 条件
   * @return {object} コンテンツ
   */
  _getImageCarouselContent(...conditions) {
    if (!conditions.some(condition => condition && (condition.toUpperCase() === 'I' || condition === MESSAGE_CONTENT_TYPE.imageCarousel))) return;
    return { 
      type: MESSAGE_CONTENT_TYPE.imageCarousel,
      columns: this._getImageCarouselColumns(),
      //quickReply: this._getQuickReplyItems()
    };
  }

  /**
   * Image carousel カラムリストを返します。
   * @return {Array} カラムリスト
   */
  _getImageCarouselColumns() {
    // image_carousel.columnsのactionでは typeはmessageとuri、postbackしか使えない。つまり camera、cameraRoll、locationは使えない
    // image_carousellは、postbackを使える！！！！
    // 最大３つまでのカラムしか使えない！！！
    return [
      {
        imageUrl: `${process.env.IMAGE_FILE_HOST}/images/giraffe01.png`,
        //imageResourceId: '',
        action: {
          type: 'postback',
          label: 'ButtonTemp',
          data: 'button_template',
          displayText: 'button_template ください'
        }
      },
      {
        imageUrl: `${process.env.IMAGE_FILE_HOST}/images/lw.png`,
        //imageResourceId: '',
        action: {
          type: 'postback',
          label: 'ListTemp',
          data: 'list_template',
          displayText: 'list_template ください'
        }
      },
      {
        imageUrl: `${process.env.IMAGE_FILE_HOST}/images/sushi.png`,
        //imageResourceId: '',
        action: {
          type: 'postback',
          label: 'Carousel',
          data: 'carousel',
          displayText: 'carousel ください'
        }
      }
    ];
  }
}
```

##環境変数 (.env)

 1. .env.sample ファイルを .env に変更する
 2. 「LINE WORKS Bot APIの利用準備」で発行した接続情報を設定する
 3. 「IMAGE_FILE_HOST」に ngrog で取得したホスト （ https://xxxxx.io ） を指定する

```:.env
API_ID="API ID"
CONSUMER_KEY="Consumer key"
SERVER_ID="Server ID"
PRIVATE_KEY="認証キー"
BOT_NO="Bot No"
IMAGE_FILE_HOST="ホスト名（ngrogで取得したホスト https://xxxxx.io）"
```

#4. 動かしてみる

「[LINE WORKS Bot APIをひと通り触ってみる（node.js）#1](https://qiita.com/shotamaco/items/bd510729adc0497d7d0b) 」の 「[いざデバッグ開始！](https://qiita.com/shotamaco/items/bd510729adc0497d7d0b#%E3%81%84%E3%81%96%E3%83%87%E3%83%90%E3%83%83%E3%82%B0%E9%96%8B%E5%A7%8B)」を参照して、Botを起動し、LINE WORKSアプリにBotを追加した状態にする。

##シナリオ
 1. Botの利用開始
 2. 「I」を入力し送信（画像カルーセル要求）
 3. 画像カルーセルの「ButtonTemp」をクリック（ボタンテンプレート要求）
 4. ボタンテンプレートの「List postback」をクリック（リストテンプレート要求）
 5. リストテンプレートの「QuickReply」をクリック（クイックリプライ要求）
 6. クイックリプライの「場所」をクリック（マップ要求）
 7. マップの「位置を共有」をクリック
 8. クイックリプライの「Calousel」をクリック（カルーセル要求）

![aaa.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528703/e298e575-47ef-9f6c-7b1c-666ca9b3f120.gif)|
|:-:|

#5. 気づいたこと

##camera、cameraRoll、location、postback Action は、すべての 送信タイプで指定できるわけではない

| 送信タイプ | camera | cameraRoll | location | postback |
|:--|:--|:--|:--|:--|
| [quick reply](https://developers.worksmobile.com/jp/document/100500807?lang=ja) （共通プロパティ） | :ok_woman_tone1: ○ | :ok_woman_tone1: ○ | :ok_woman_tone1: ○ | :ok_woman_tone1: ○ |
| [text](https://developers.worksmobile.com/jp/document/100500801?lang=ja) | ー | ー | ー | ー |
| [image](https://developers.worksmobile.com/jp/document/100500802?lang=ja) | ー | ー | ー | ー |
| [link](https://developers.worksmobile.com/jp/document/100500803?lang=ja) | ー | ー | ー | ー |
| [sticker](https://developers.worksmobile.com/jp/document/100500806?lang=ja) | ー  | ー | ー | ー |
| [button_template](https://developers.worksmobile.com/jp/document/100500804?lang=ja) | :no_good_tone1: | :no_good_tone1: | :no_good_tone1: | :no_good_tone1: |
| [list_template](https://developers.worksmobile.com/jp/document/100500805?lang=ja) | :no_good_tone1: | :no_good_tone1: | :no_good_tone1: | :no_good_tone1: |
| [carousel](https://developers.worksmobile.com/jp/document/100500808?lang=ja) | :no_good_tone1: | :no_good_tone1: | :no_good_tone1: | :ok_woman_tone1: ○ |
| [image_carousel](https://developers.worksmobile.com/jp/document/100500809?lang=ja) | :no_good_tone1: | :no_good_tone1: | :no_good_tone1: |:ok_woman_tone1:  ○ |
※ ー は Action Object を使用できない送信タイプです

##postback には２つの種類がある
 * 送信タイプ： button_template、list_template の Action Object の場合、type:message で postback プロパティにデータを乗せる

```JavaScript:ActionObject
{
  type: 'message',
  label: 'Button',
  text: 'button_template ください',
  postback: 'button_template'
}
```

 * 送信タイプ： quick reply（共通プロパティ）、carousel、image_carousel の Action Object の場合、type:postback で data プロパティにデータを乗せる
 
 （この Action が実行された場合、postback と message の２つのイベントが Bot 側にコールバックされる）

```JavaScript:ActionObject
{
  type: 'postback',
  label: 'ImageCarousel',
  data: 'image_carousel',
  displayText: 'image_carousel ください'
}
```

#6. まとめ

LINE WORKS Bot APIのメッセージ送信部分の動作をひと通り確認できました。
ベタ書きのコードですいません。でも、一通りは網羅したつもりです。
今回作成たコードは GitHub の [line-works-bot01-node](https://github.com/shotamaco/line-works-bot01-node)  の tag:v2.0 で公開してま〜す。（issueがあればお知らせください。修正します。）

次回は、トーク固定メニューとリッチメニューをやってみたい！

#Link
  * [LINE WORKS Developers](https://developers.worksmobile.com/)
  * [LINE WORKS ガイド（全般）](https://guide.worksmobile.com/)
  * [LINE WORKS ガイド（管理者画面）](https://guide.worksmobile.com/admin/)
  * [LINE WORKS コミュニティ](https://community.worksmobile.com/)
  * [LINE WORKS ヘルプセンター](https://help.worksmobile.com/)
  * [LINE WORKS Blog](https://line.worksmobile.com/jp/blog/)
  * [さわってわかったLINE WORKS](https://ascii.jp/elem/000/001/955/1955720/)