zoomのミーティングを作るLINE WORKS Botを作ってみた

LINEWORKS Advent Calendar 2020 / 21日目の記事です。

zoomのミーティングルームを作るLINE WORKS Botを作ってみました。
名前はZoomerです。

# 0. はじめに
記事の流れは下記の通りです。

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

# 1. こんなの作りました

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528703/097b2d95-dcc1-756a-f710-17860ef557c3.gif" width="300px">|
|:-:|

シンプルではありますが下記の手順で zoomのミーティングURLを取得します。
1. Zoomer Bot とのトークルームを作成する
2. Zoomer Bot に zoomアカウントIDを登録する
3. Zoomer Bot に いつミーティングをするか伝える
4. Zoomer Bot からミーティングURLが返ってくる

# 2. 環境準備

## Zoom API の利用準備

[Zoom APIへお試しリクエストを送る方法 (JWT・OAuth)](https://qiita.com/aharenchi/items/350b88fa888b3445546f) を参考に JWT app を作成しました。
https://marketplace.zoom.us/develop/create
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528703/c3b5c357-8e0e-ebd2-db26-ee46d9874434.png)

この状態になります。
API Key と API Secret は、Zoomer Bot で使用します。


## LINE WORKS Bot APIの利用準備
LINE WORKS の Developer Console で Zoomer Bot サーバーが LINE WORKS と通信するために必要な接続情報の発行と Zoomer Bot の登録を行います。

↓こちらの記事を参考にしていただければ幸いです。
[LINE WORKSで初めてのBot開発！(前編)](https://qiita.com/tokotan/items/f615f4a62219d655436f) の「[Developer ConsoleでAPIを使うための設定とBotを登録する](https://qiita.com/tokotan/items/f615f4a62219d655436f#developer-console%E3%81%A7api%E3%82%92%E4%BD%BF%E3%81%86%E3%81%9F%E3%82%81%E3%81%AE%E8%A8%AD%E5%AE%9A%E3%81%A8bot%E3%82%92%E7%99%BB%E9%8C%B2%E3%81%99%E3%82%8B)」
※Bot登録の際に指定する Callback URL は、[ngrok](https://ngrok.com/)を利用して取得するとローカルデバッグができるのでとっても便利です。
（記事：[ローカル環境で LINEWORKS Bot を動かす話](https://qiita.com/kunihiros/items/c9a5340f4512149e580d)が大変参考になりました）


## 開発環境
 * VS Code：IDE
 * node.js+Express：Botサーバー
 * [dotenv](https://www.npmjs.com/package/dotenv)：アプリケーションの環境変数定義
 * [ngrok](https://ngrok.com/)：ローカルデバッグ

node.jsでいろいろpackageを利用してますが省略します。

# 3. 作ってみる

GitHub の [line-works-zoomer](https://github.com/shotamaco/line-works-zoomer) で開発しました。詳細は GitHub を参照して下さい。


Zoomと連携する部分を抜粋して紹介します。
ZoomMeetingCreateAction.js は、Zoomer Bot が Zoom のミーティングルームの作成依頼を受け付けるアクションクラスです。作成結果を基にLINE WORKS の Zoomer Bot 利用者に結果メッセージを送信しています。
ZoomMeetingCreator（MeetingCreator）クラスで実際に Zoom API を使用してミーティングルームを作成しています。

```JavaScript:ZoomMeetingCreateAction.js
const moment = require("moment");
const BaseAction = require('./BaseAction');
const BotMessageSender = require('../lineworks/bot/MessageSender');
const ZoomMeetingCreator = require('../zoom/MeetingCreator');
const SessionRepository = require('../repositories/SessionRepository');

/**
 * ZoomMeetingCreateActionクラス
 */
module.exports = class ZoomMeetingCreateAction extends BaseAction {
  /**
   * 初期化します。
   * @param {object} callbackEvent コールバックイベント
   */
  constructor(callbackEvent) {
    super(callbackEvent);
  }

  /**
   * アクションを実行します。
   */
  async execute() {
    var postbackTime = this.callbackEvent.content.postback;

    const sessionRepos = new SessionRepository();
    let session = sessionRepos.get(this.getRoomId());

    const meetingCreator = new ZoomMeetingCreator();
    let startDateTime = null;
    if (postbackTime) {
      if (session.startDate) {
        startDateTime = moment(`${session.startDate}T${postbackTime}`);
      } else {
        startDateTime = moment(`${moment().format('YYYYY-MM-DD')}T${postbackTime}`);
      }
    }
    if (!startDateTime.isValid()) {
      startDateTime = null;
    }
    
    const res = await meetingCreator.create(session.zoomAccountId, startDateTime);
    console.log(res);
    let message1 = {
      content: {
        type: BotMessageSender.MESSAGE_CONTENT_TYPE.text,
        text: `Zoomミーティングを${session.zoomAccountId}で登録しました。\nHostのURLはこちら。`,
      }
    };
    this.setMessageTo(message1);

    let message2 = {
      content: {
        type: BotMessageSender.MESSAGE_CONTENT_TYPE.text,
        text: `${res.start_url}`,
      }
    };
    this.setMessageTo(message2);

    let message3 = {
      content: {
        type: BotMessageSender.MESSAGE_CONTENT_TYPE.text,
        text: `招待のURLはこちら。`,
      }
    };
    this.setMessageTo(message3);

    let message4 = {
      content: {
        type: BotMessageSender.MESSAGE_CONTENT_TYPE.text,
        text: `${res.join_url}`,
      }
    };
    this.setMessageTo(message4);

    const sender = new BotMessageSender();
    await sender.send([ message1, message2, message3, message4 ]);

    session.startDate = null;
    session.waitingForZoomAcountIdInput = false;
    sessionRepos.save(session);
  }
}
```

```JavaScript:MeetingCreator.js
const moment = require("moment");
const Promise = require("promise");
const request = require("request-promise");
const jwt = require('jsonwebtoken');

/**
 * MeetingCreatorクラス
 */
module.exports = class MeetingCreator {
  /**
   * Zoomのミーティングを作成し結果を返します。
   * @param {string} zoomAccountId ZoomアカウントID
   * @param {string} startDateTime ミーティング開始日時
   * @return {object} ミーティング作成結果オブジェクト
   */
  async create(zoomAccountId, startDateTime) {
    let json = {
      topic: 'LINE WORKS ZOOM連携',
      type: 1, 
      time_zone: 'Asia/Tokyo',
      agenda: 'LINE WORKS ZOOM連携してみる',
      settings: {
        host_video: true,
        participant_video: true,
        approval_type: 0,
        audio: 'both',
        enforce_login: false,
        waiting_room: false,
        registrants_email_notification: false
      }
    };

    if (startDateTime) {
      json.type = 2;
      json.start_time = startDateTime;
    }

    const payload = {
      iss: process.env.ZOOM_API_KEY,
      exp: ((new Date()).getTime() + 5000)
    };
    const token = jwt.sign(payload, process.env.ZOOM_API_SECRET);

    const options = {
      method: "POST",
      url: `https://api.zoom.us/v2/users/${zoomAccountId}/meetings`,
      headers: {
        "Content-type": "application/json",
        Authorization: `Bearer ${token}`,
      },
      json : json
    };

    return new Promise((resolve, reject) => {
      request(options)
        .then((res) => {
          console.log(res);
          resolve(res);
        })
        .catch((error) => {
          console.log(`ERROR: ${error}`);
          reject(error);
        });
    });
  }
};
```

# 4. 動かしてみる

###1) GitHub から line-works-zoomer をクローンする
https://github.com/shotamaco/line-works-zoomer

###2) VS Code のターミナルでプログラムが使用している node.js の package をインストール

```:VsCodeTerminal
npm install
```

##3) 環境変数 (.env)
.env.sample ファイルを .env に変更して、
「Zoom API の利用準備」と「LINE WORKS Bot APIの利用準備」で発行した接続情報を設定する。

```:.env
API_ID="API ID"
CONSUMER_KEY="Consumer key"
SERVER_ID="Server ID"
PRIVATE_KEY="Server 認証キー"
BOT_NO="Bot 番号"
ZOOM_API_KEY="Zoom API Key"
ZOOM_API_SECRET="API Secret"
```

###4) デバッグボタン(F5)クリック！
![vscode.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528703/c116999e-d2ac-2057-6055-ccaf6b7d82b3.png)

###5) http 3000 で ngrok 起動！

```:Terminal
ngrok http 3000
```

<img width="644" alt="ngrok2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528703/af241803-5c74-ac50-edc0-8b1a88732325.png">
※ ForwardingもとのURLが変わった場合は、Developer Console で Botの Callback URL の変更を必ずしてください。

###6) スマフォを手に持って LINE WORKS を動かす

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/528703/f53b2ce0-9459-4c1b-09ca-1118e2ae005e.gif" width="330px">|
|:-:|


# 5. まとめ

DBを使用していないのでstatic変数で Server Token や Session データを管理しています。。。また例外処理はほとんどしてません。。。あくまでもLINE WORKSとzoomを連携させることを目的にしています。

今回作成たコードは GitHub の [line-works-zoomer](https://github.com/shotamaco/line-works-zoomer) で公開してま〜す。

#Link
  * [LINE WORKS Developers](https://developers.worksmobile.com/)
  * [LINE WORKS Developers コミュニティ](https://forum.worksmobile.com/jp/)
  * [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/)
  * [Zoom API](https://marketplace.zoom.us/docs/api-reference/zoom-api)