はじめに
今回は、Node.jsによるAPI2.0に対応したBot実装例を、シンプルなオウム返しBotをベースにまとめてみる。
- Python + FastAPI編 はこちら。
ソースコード
具体的な実装は以下を参照。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の改ざんチェックを行う。
以下、コード例。
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取得までの流れ。
- (事前準備) Developer ConsoleからAppを作成し、以下の各種認証情報を設定・取得する。
- Client ID
- Client Secret
- Service Account
- Private Key
- OAuth Scopeの設定
- JWTの生成。以下の情報を利用。
- Client ID
- Service Account
- Private Key
- Access Tokenを取得する。以下の情報を利用
- 生成したJWT
- Client ID
- Client Secret
- 必要なScope
- 今回はBotへの返答を行うのみであるため
bot
scopeを指定する。
- 今回はBotへの返答を行うのみであるため
以下、Access Token取得のコード例。
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を使う。
送信先ユーザーに、Request bodyの source.userId
を指定し、コンテンツには送られてきたテキストをそのまま指定する。
{
"content": {
"type": "text",
"text": "{Reply text}"
}
}
参考 : https://developers.worksmobile.com/jp/reference/bot-send-text?lang=ja
また、API呼び出し時に先ほど取得したAccess Tokenを Authorization
headerに指定する。
Authorization: Bearer {{Access Token}}
メッセージ送信のコード例は以下の通り。
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のエラーが返る。その際は時間を置いて再送する必要がある。そのため、それを考慮した再送処理を実装する必要がある。
加えて、Access Tokenも期限切れがあるため、再発行の処理も追加する。
詳しくはソースコードを直接参照ください。
まとめ
Node.js + Expressを使ったAPI2.0対応のLINE WORKS Botの実装例をまとめた。
API1.0からの移行としては、改ざんチェックやAccess Token取得の部分など、API1.0のBotのコードから流用できたものが多い。
もしこれらサンプルに不具合がある場合は、この記事のコメントもしくはGithubリポジトリのIssuesでお知らせください。