【LINE WORKS】Node.jsでオウム返しBotを作る【API 2.0】

# はじめに
今回は、Node.jsによるAPI2.0に対応したBot実装例を、シンプルなオウム返しBotをベースにまとめてみる。

- [Python + FastAPI編](https://qiita.com/mmclsntr/items/5ee3b7f2422b05bc256c) はこちら。

# ソースコード
具体的な実装は以下を参照。[Express](https://expressjs.com/) を使用した。

https://github.com/mmclsntr/lw-api-2_0-echo-bot-nodejs-express

以下、実装内容について一部抜粋して解説する。

# 実装解説

- 環境: Node.js 16

## 改ざんチェック

LINE WORKS APIのBotには、Callbackで受け取ったRequest Eventの改ざんチェックを行う機能がある。

参考 : https://developers.worksmobile.com/jp/reference/bot-callback?lang=ja

Request headerの X-WORKS-Signature によって渡される署名と、Developer ConsoleのBot画面で発行されている Bot Secret を用いて、Request bodyの改ざんチェックを行う。

以下、コード例。

```js
var crypto = require("crypto");


let safeCompare = (a, b) => {
    if (a.length !== b.length) {
        return false;
    }
    return crypto.timingSafeEqual(a, b);
};


/**
 * Validate request
 * @param {Object} body - Request Body
 * @param {string} signature - value of X-WORKS-Signature header
 * @param  {string} botSecret - Bot Secret
 * @return {boolean} is valid
 */
let validateRequest = (body, signature, botSecret) => {
    return safeCompare(
        crypto.createHmac("SHA256", botSecret).update(body).digest(),
        Buffer.from(signature, "base64"),
    );
};

```

## Access Token取得
送られてきたメッセージへのリプライにはAPIを利用する。APIの利用のためにまずはAccess Tokenを取得する。

「Service Account認証」というJWTを使った認可の仕組みを使ってAccess Tokenを取得する。

参考 : https://developers.worksmobile.com/jp/reference/authorization-sa?lang=ja 

Access Token取得までの流れ。

1. (事前準備) Developer ConsoleからAppを作成し、以下の各種認証情報を設定・取得する。
    - Client ID
    - Client Secret
    - Service Account
    - Private Key
    - OAuth Scopeの設定
2. JWTの生成。以下の情報を利用。
    - Client ID
    - Service Account
    - Private Key
3. Access Tokenを取得する。以下の情報を利用
    - 生成したJWT
    - Client ID
    - Client Secret
    - 必要なScope
        - 今回はBotへの返答を行うのみであるため `bot` scopeを指定する。

以下、Access Token取得のコード例。

```js
const jwt = require('jsonwebtoken');
const axios = require("axios");

/**
 * Generate JWT for access token
 * @param {string} clientId - Client ID
 * @param {string} serviceAccount - Service Account
 * @param {string} privatekey - Private Key
 * @return {string} JWT
 */
let getJWT = (clientId, serviceAccount, privatekey) => {
    current_time = Date.now() / 1000;
    iss = clientId;
    sub = serviceAccount;
    iat = current_time;
    exp = current_time + (60 * 60); // 1 hour

    jws = jwt.sign(
        {
            "iss": iss,
            "sub": sub,
            "iat": iat,
            "exp": exp
        }, privatekey, {algorithm: "RS256"});

    return jws;
};


/**
 * Get Access Token
 * @async
 * @param {string} clientId - Client ID
 * @param {string} clientSecret - Client Secret
 * @param {string} serviceAccount - Service Account
 * @param {string} privatekey - Private Key
 * @param {string} scope - OAuth Scope
 * @return {string} Access Token
 */
let getAccessToken = async (clientId, clientSecret, serviceAccount, privatekey, scope) => {
    const jwt = getJWT(clientId, serviceAccount, privatekey);

    const params = new URLSearchParams({
        assertion: jwt,
        grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
        client_id: clientId,
        client_secret: clientSecret,
        scope: scope,
    });

    const res = await axios.post("https://auth.worksmobile.com/oauth2/v2.0/token", params);

    const accessToken = res.data.access_token;

    return accessToken;
};

```

## メッセージリプライ
リプライには「メッセージ送信」のAPIを使う。

https://developers.worksmobile.com/jp/reference/bot-user-message-send?lang=ja

送信先ユーザーに、Request bodyの `source.userId` を指定し、コンテンツには送られてきたテキストをそのまま指定する。

```json
{
    "content": {
        "type": "text",
        "text": "{Reply text}"
    }
}
```

参考 : https://developers.worksmobile.com/jp/reference/bot-send-text?lang=ja

また、API呼び出し時に先ほど取得したAccess Tokenを `Authorization` headerに指定する。

```text
Authorization: Bearer {{Access Token}}
```

メッセージ送信のコード例は以下の通り。

```js
const axios = require("axios");


/**
 * Send message to a user
 * @async
 * @param {Object} content - Message Content
 * @param {string} botId - Bot ID
 * @param {string} userId - User ID
 * @param {string} accessToken - Access Token
 * @return {Object} response
 */
let sendMessageToUser = async (content, botId, userId, accessToken) => {
    const headers = {
        Authorization: `Bearer ${accessToken}`
    };

    const res = await axios.post(`https://www.worksapis.com/v1.0/bots/${botId}/users/${userId}/messages`, content,
        { headers }
    );
    return res;
};

```

### API利用時の考慮
注意点として、APIリクエストにはRate Limitが設けられており、制限を超えた際は429のエラーが返る。その際は時間を置いて再送する必要がある。そのため、それを考慮した再送処理を実装する必要がある。

https://developers.worksmobile.com/jp/reference/rate-limits?lang=ja 

加えて、Access Tokenも期限切れがあるため、再発行の処理も追加する。

詳しくはソースコードを直接参照ください。

# まとめ
Node.js + Expressを使ったAPI2.0対応のLINE WORKS Botの実装例をまとめた。
API1.0からの移行としては、改ざんチェックやAccess Token取得の部分など、API1.0のBotのコードから流用できたものが多い。

もしこれらサンプルに不具合がある場合は、この記事のコメントもしくはGithubリポジトリのIssuesでお知らせください。