【Node.js × LINE WORKS API】API で BOT を登録する

LINE WORKS Advent Calendar ５日目！どうぞよろしくお願いします♪

前々からお知らせされていたのですが、今年の 9月に**一部の古いバージョンの API が使用できなくなりました。**
それでふと、前に自分が書いた記事を見てみたら、やっぱりというか古いバージョンでの記事でした。
[API を使って LINE WORKS BOT を登録する](https://qiita.com/kunihiros/items/bad2063ee4a8bb6e7dae)

まぁ、記事を修正するよりは、Advent Calendar の時期ですし？
新しく記事を書きなおそうと思った次第なのです。('ω')

だって、２年まえの私は `async` `await` を使えなくて、非同期処理は全部 `Callback` でやってたんですよ( ﾟДﾟ)
おまけに [文字列内の変数は全部 `+` で結合](https://qiita.com/kunihiros/items/faf3ff58409ace27a6ed)してましたし。。。
あぁ、恥ずかしい。。。(ﾉωﾉ)

前置きが長くなりました。
そろそろ、API で BOT を登録していきましょう。

# LINE WORKS API で BOT を登録するためには

LINE WORKS BOT を登録するためには複数の API を使用します。

旧バージョンの API との比較ですが、新バージョンでは Callback URL の登録がトーク Bot のテナント登録 API でできるようになっています。
なので手順は一個減っているのですが、API Access Token を取得する手順を追加しますので、やっぱり 4 STEP ですね。

1. [API Access Token の取得](https://developers.worksmobile.com/jp/document/1002002?lang=ja)
2. [トーク Bot のテナント登録](https://developers.worksmobile.com/jp/document/1005002?lang=ja)
3. [トーク Bot のドメイン登録](https://developers.worksmobile.com/jp/document/1005004?lang=ja)
4. [トーク Bot からメッセージ送信](https://developers.worksmobile.com/jp/document/1005008?lang=ja)

# 事前準備

- LINE WORKS 認証情報を [【LINE WORKS】API 認証情報の取得方法まとめ](https://qiita.com/kunihiros/items/54853e910ed1dff9ebdc) を参考に取得しておきます。

- このプログラムコードではモジュール２つを使っています。予めインストールしておきます。

```
npm install request-promise --save
npm install jsonwebtoken --save
```

# Node.js で Bot を登録するコード

では、実際のコードです。

```main.js
// LINE WORKS 認証情報
const CONSTS = {
    "DOMAIN_ID": "xxxxxxxx";
    "API_ID": "xxxxxxxxxxx";
    "CONSUMER_KEY": "xxxxxxxxxxxxxxxxxx";
    "SERVER_ID": "xxxxxxxxxxxxxxxxxxxxxxxxxx";
    "PRIVATEKEY": "-----BEGIN PRIVATE KEY-----\nxxxxxxxxxx\n-----END PRIVATE KEY-----";
    "MANAGER_ID": "xxxx@yyyy-zzzz";
}
// module
const request = require("request-promise");

// main
main();

async function main() {
    const token = await getServerToken();
    const botNo = await addTalkBot(token);
    const result = await regTalkBot(token, botNo);
    if(result) sendMsg(token, botNo);
}

/**
 * LINE WORKS の Server Token を取得
 * @return {Promise object} Server Token
 */
async function getServerToken() {
    const jwt = require("jsonwebtoken");
    const iss = CONSTS.SERVER_ID;
    const iat = Math.floor(Date.now() / 1000);
    const exp = iat + 60 * 60; // 有効期間を 1時間に設定
    const cert = CONSTS.PRIVATEKEY;
    const options = {
        method: "POST",
        url: `https://auth.worksmobile.com/b/${CONSTS.API_ID}/server/token`,
        headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
        form: {
            grant_type: encodeURIComponent("urn:ietf:params:oauth:grant-type:jwt-bearer"),
            assertion: jwt.sign({ iss: iss, iat: iat, exp: exp }, cert, { algorithm: "RS256" }),
        }
    };
    return new Promise((resolve, reject) => {
        request(options).then((res) => {
            const result = JSON.parse(res);
            if (result.message) throw "Could not get token";
            resolve(result);
        }).catch((error) => {
            console.log(`Auth Error: ${error}`);
            reject(error);
        });
    });
}

/**
 * LINE WORKS にトーク Bot を登録
 * @param {object} token LINE WORKS の Server Token
 * @return {Promise object} 登録した Bot の botNo
 */
async function addTalkBot(token){
    const options = {
        method: "POST",
        url: `https://apis.worksmobile.com/r/${CONSTS.API_ID}/message/v1/bot`,
        headers: {
            "Content-type": "application/json",
            "consumerKey": CONSTS.CONSUMER_KEY,
            "Authorization": `${token.token_type} ${token.access_token}`
        },
        json: {
            "name": "test bot",
            "photoUrl": "https://developers.worksmobile.com/favicon.png",
            "description": "qiita sample bot",
            "managers": [CONSTS.MANAGER_ID]
        }
    };
    return new Promise((resolve, reject) => {
        request(options).then((res) => {
            if (res.errorMessage) throw `Could not add TalkBot:${JSON.stringify(res)}`;
            resolve(res.botNo);
        }).catch((error) => {
            console.log(`Add TalkBot Error: ${error}`);
            reject(error);
        });
    });
}

/**
 * Domain にトーク Bot を登録して公開する
 * @param {object} token LINE WORKS の Server Token
 * @param {string} botNo Domain に登録する Bot の botNo
 * @return {Promise object} 登録が成功したかどうかの真偽値
 */
async function regTalkBot(token, botNo){
    const options = {
        method: "POST",
        url: `https://apis.worksmobile.com/r/${CONSTS.API_ID}/message/v1/bot/${botNo}/domain/${CONSTS.DOMAIN_ID}`,
        headers: {
            "Content-type": "application/json",
            "consumerKey": CONSTS.CONSUMER_KEY,
            "Authorization": `${token.token_type} ${token.access_token}`
        }
    };
    return new Promise((resolve, reject) => {
        request(options).then((res) => {
            if (res.errorMessage) throw `Could not reg TalkBot:${JSON.stringify(res)}`;
            resolve(Boolean("true"));
        }).catch((error) => {
            console.log(`Add TalkBot Error: ${error}`);
            reject(Boolean("false"));
        });
    });
}

/**
 * 管理者に登録完了メッセージを送信
 * @param {object} token LINE WORKS の Server Token
 * @param {string} botNo メッセージを送信する Bot の botNo
 */
async function sendMsg(token, botNo){
    const options = {
        method: "POST",
        url: `https://apis.worksmobile.com/r/${CONSTS.API_ID}/message/v1/bot/${botNo}/message/push`,
        headers: {
            "Content-type": "application/json",
            "consumerKey": CONSTS.CONSUMER_KEY,
            "Authorization": `${token.token_type} ${token.access_token}`
        },
        json: {
            "accountId": CONSTS.MANAGER_ID,
            "content": {
              "type": "text",
              "text": "Bot 登録が完了しました！"
            }
        }
    };
    return new Promise((resolve, reject) => {
        request(options).then((res) => {
            if (res.errorMessage) throw `Could not send message:${JSON.stringify(res)}`;
            resolve("success");
        }).catch((error) => {
            console.log(`send message Error: ${error}`);
            reject("error");
        });
    });
}
```

# コードの解説

少しだけ解説を。

全体としては前回よりかなり長いコードになりましたが、可読性は上がったと思います。
また、Javadoc コメントの記述方法も勉強して、なるべく書くようにしています。

プログラムの内容ですが、前述の 4 STEP をそれぞれ関数にしました。
`async` `await` を使用して非同期処理をしています。
それぞれの機能を独立させ、メインプログラムで呼び出すようにしました。

1. API Access Token の取得 - `function getServerToken()`
2. トーク Bot のテナント登録 - `function addTalkBot(token)`
3. トーク Bot のドメイン登録 - `function regTalkBot(token, botNo)`
4. トーク Bot からメッセージ送信 - `function sendMsg(token, botNo)`

1 の API Access Token の取得はよく使うので、別のプログラム作るときにも応用が効きます。
実際、前に書いた記事からコピペしてきただけですしね( ﾟДﾟ)

# おわりに

ここまでお付き合いいただきありがとうございました。

前の記事を書いてから約１年半。
昔の内容と比べるとずいぶん成長したなぁ、と少し自分を誇らしく思っています。

まだまだ知らないこともたくさんありますし、覚えた知識も使わないと忘れます。
技術は日々進歩しますし、新しいサービスや機能も増えていきます。

明日も少しマシな自分を目指して、これからも頑張っていきたいです！

ではまた！(^^)/

# 参考にさせていただきましたm(_ _)m

[LINE WORKS Developers](https://developers.worksmobile.com/jp/document?lang=ja)