5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

LINE WORKSでタイ語自動翻訳機能を使う

Posted at

はじめに

LINE WORKSのトーク(チャット)機能は自動翻訳機能が付いています!
対応している言語は下記の通り。
image.png

自動翻訳機能をオンにするとこんな感じで、翻訳された文章が下に付与されて、文章が投稿されます。
私は毎日仕事でこの機能を使っています。むしろこれがないと無理w
image.png

image.png

ですが、LINEの普及が進んでいる「タイ語」にまだ対応していません・・・。
ないものは作るよね、ってことでタイ語翻訳Botを作りました。

完成版

image.png

構成

BotサーバーはHerokuを使います。
言語はNode.jsです。
翻訳APIは、Microsoft Cognitive ServiceのTranslator Text APIを使います。

image.png

構築の流れ

構築の流れは下記のStepになります。
1、2、4、5は「LINE WORKSで初めてのBot開発!」という記事で詳しく紹介しているので、そちらでご確認ください。
リンクを貼っておきます。

  1. LINE WORKS Developer Consoleの設定
  2. Botサーバーの用意
  3. Botプログラミング
  4. Herokuへデプロイ
  5. Botを公開

ここからは、「3.Botプログラミング」に付いて書きます。

Botプログラミング

Bot本体のメイン動作はindex.jsに、
Translater Text APIを使った翻訳機能はtranlator.jsに実装します。
※わかりやすいように簡易的な構成にしています。

Bot本体(index.js)

モジュールのインストール

下記モジュールを追加でインストールします。

$ npm install express --save
$ npm install body-parser --save
$ npm install jsonwebtoken --save
$ npm install https --save
$ npm install request --save
$ npm install querystring --save
$ npm install crypto --save

index.jsにて、モジュールをインポートしておきます。

index.js
//モジュールインポート
const express = require("express");
const bodyParser = require("body-parser");
const jwt = require('jsonwebtoken');
const https = require("https");
const request = require("request");
const qs = require("querystring");
const crypto = require("crypto"); //暗号化モジュール
const translate = require('./translate.js');//翻訳モジュール

環境変数設定

Server TokenやConsumer Keyは、Herokuの環境変数へ設定します。
Herokuの環境変数への設定はHerokuのダッシュボードから設定することができます。手順はこちらを参照してください。
今回は、下記5つの値をHerokuの環境変数として設定しましょう。

環境変数名 Developer Consoleの表示箇所
APIID [API]メニューのAPI IDに表示されている英数字
CONSUMERKEY [API]メニューのServer API Consumer KeyのKey欄に表示されている英数字
SERVERID [API]メニューのServer List(ID登録タイプ)のID欄に表示されている英数字
PRIVATEKEY [API]メニューのServer List(ID登録タイプ)の[認証キー再発行]ボタンからダウンロードします。

ダウンロードしたファイルに表示されている英数字
BOTNO [Bot]メニューに表示されているBotをクリックし、
[Bot No.]に表示されている4桁の数字

全て設定するとこんな感じになります。

設定したら、プログラムから利用できるよう変数として定義しておきましょう。

index.js
const APIID = process.env.APIID;
const SERVERID = process.env.SERVERID;
const CONSUMERKEY = process.env.CONSUMERKEY;
const PRIVATEKEY = process.env.PRIVATEKEY;
const BOTNO = process.env.BOTNO;

ユーザーのメッセージを受け取りメッセージに改ざんがないか確認する

メッセージはPOSTリクエストのbody 部分に含まれて通知されます。(メッセージのプロパティについて、詳細はこちらを参照してください。)

この時に受信したメッセージが、本当にLINE WORKSのサーバーから送信されたメッセージであり、そのメッセージが改ざんされていないかどうか、を確認するようにしてください。
LINE WORKS メッセージサーバーから送信されたメッセージの改ざん有無を確認するプロセスは以下となります。

  1. API ID を秘密鍵として利用し、LINE WORKSのサーバーから送られてきたメッセージの body の内容を HMAC-SHA256 アルゴリズムでエンコードします。
  2. 上記の HMAC-SHA256 アルゴリズムでエンコードした結果を BASE64 エンコードします。
  3. X-WORKS-Signature のヘッダー値と比較し、同一であればメッセージは改ざんされていないと判断できます。

確認のプロセスについて詳細はこちらを参照してください。
そして、このプロセスは下記のように実装できます。
下記サンプルコードではcryptoというモジュールを使っています。

index.js
//メッセージ改ざん有無チェック
const signature = req.headers['x-works-signature'];
if (signature){
    checkCallbackMessage(JSON.stringify(req.body), signature, (callback) => {
        if (callback){
            //正常処理をここに書く
        } else {
            console.log("LINE WORKSサーバー以外からのCallbackがありました。");
        };
    });
}


//LINE WORKSメッセージサーバーから送信されたメッセージの改ざん有無を確認
function checkCallbackMessage(httpRequestBody, signature, callback){
    //API ID を秘密鍵として利用し、LINE WORKSのサーバーから送られてきたメッセージの body の内容を HMAC-SHA256 アルゴリズムでエンコードします。
    // 暗号化
    const sha256 = crypto.createHmac('sha256', API_ID);
    //上記の HMAC-SHA256 アルゴリズムでエンコードした結果を BASE64 エンコードします。
    sha256.update(httpRequestBody);
    const hash = sha256.digest('base64');

    //X-WORKS-Signature のヘッダー値と比較し、同一であればメッセージは改ざんされていないと判断できます。
    if (hash == signature){
        callback(true);
    } else {
        callback(false);
    }
}

メッセージをユーザーへ送信するためのトークンを取得する

トークンは、下記の流れで取得します。

1.JWTを取得
2.JWTを使ってServer Tokenを取得

それぞれ実装のサンプルコードは下記となります。詳細な仕様についてはこちらを参照してください。

index.js
    getJWT((jwttoken) => {
        getServerToken(jwttoken, (newtoken) => {
            //取得したnewtokenを使ってメッセージ送信
        });
    });

//1. JWTを取得 
function getJWT(callback){
    const iss = SERVER_ID;
    const iat = Math.floor(Date.now() / 1000);
    const exp = iat + (60 * 60); //JWTの有効期間は1時間
    const cert = PRIVATEKEY;
    const token = [];
    const jwttoken = jwt.sign({"iss":iss, "iat":iat, "exp":exp}, cert, {algorithm:"RS256"}, (err, jwttoken) => {
        if (!err) {
            callback(jwttoken);
        } else {
            console.log(err);
        }
    });
}
//2.JWTを使ってServer Tokenを取得
function getServerToken(jwttoken, callback) {
    const postdata = {
        url: 'https://authapi.worksmobile.com/b/' + 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" : jwttoken
        }
    };
    request.post(postdata, (error, response, body) => {
        if (error) {
            console.log(error);
            callback(error);
        } else {
            const jsonobj = JSON.parse(body);
            const AccessToken = jsonobj.access_token;
            callback(AccessToken);
        }
    });
}

[注意]
Server Token(access_token)は24時間以内に利用されない場合には自動的に失効します。access_token発行時に、発行時間とaccess_tokenを保存し、Server APIを実行する際には有効期限を確認するようにしてください。有効期限内であれば保存したaccess_tokenを利用することができます。

翻訳(translator.js)に投げ、結果を受け取ってユーザーへ送信する

「ユーザーのメッセージを受け取りメッセージに改ざんがないか確認する」にて、改ざんされていないことが確認できたら、「翻訳(translator.js)」にメッセージを投げて、翻訳結果を受け取ります。

index.js
translate.replyTrans(message, (afterMessage) => {
    // 翻訳結果をユーザーへ送信する
    sendMessage(newtoken, accountId, message, afterMessage);
});

function sendMessage(token, accountId,  message, aftermessage) {
    const postdata = {
        url: 'https://apis.worksmobile.com/' + API_ID + '/message/sendMessage/v2',
        headers : {
          'Content-Type' : 'application/json;charset=UTF-8',
          'consumerKey' : CONSUMERKEY,
          'Authorization' : "Bearer " + token
        },
        json: {
            "botNo" : Number(BOTNO),
            "accountId" : accountId,
            "content" : {
                "type" : "text",
                "text" : message + "\n ------------------------------ \n" + aftermessage
            }
        }
    };
    request.post(postdata, (error, response, body) => {
        if (error) {
          console.log(error);
        }
        console.log(body);
    });
}

翻訳(translator.js)

いよいよ翻訳部分を実装していきます!

AzureADの設定

① https://portal.azure.com/アクセスし、全体管理者でログイン。
② 画面上部の「リソース、サービス、ドキュメントの検索」欄で「Cognitive Services」を検索します。
image.png

③[+追加]をクリックします。
image.png

④ [Translator Text API]を検索してクリックします。
image.png

⑤ [作成]をクリックします。
image.png

⑥ [Name]には利用用途がわかるように記載しておくと良いと思います。それ以外の設定項目は環境に合わせて設定してください。完了したら、[作成]をクリックしてTranslator Text APIの登録完了です。
image.png

⑦ さて、このTranslator Text APIをBotから叩けるようにします。ダッシュボードに先ほど登録したTranslator Text APIサービスが表示されていると思うので、クリックします。
image.png

⑧ 左メニューで[Keys]を選択し、[KEY1]をコピーします。
image.png

⑨Botから呼び出せるように、 Herokuの環境変数に設定しておきます。
image.png

これで準備はバッチリです!
次はプログラミングしていきます。

Bot本体(index.js)からのメッセージを受け取り翻訳し、Bot本体(index.js)へ返す

Translator Text APIを使うには下記のような流れで進めます。

1.AzureのKeyを使って、AccessTokenを取得
2.AccessTokenを使ってTranslator Text APIを叩く。

それぞれ解説します。

1.AzureのKeyを使って、AccessTokenを取得

translator.js

exports.replyTrans = function (message, callback){
    //実行
    getAccessToken((token) => {
        //翻訳処理を書く
        
    });
}

//アクセストーク取得
function getAccessToken(callback) {
    let body = '';
    let data = '';

    let req = https.request({
        host: 'api.cognitive.microsoft.com',
        path: '/sts/v1.0/issueToken',
        method: 'POST',
        headers: {
          'Ocp-Apim-Subscription-Key': azureClientSecret
        }
    }, (res) => {
        res.setEncoding('utf8');
        res.on('data', (chunk) => {
            body += chunk;
        }).on('end', () => {
            console.log(body);
            let resData = body;
            callback(resData);
        });

    }).on('error', (err) => {
        console.log(err);
    });
    req.write(qs.stringify(data));
    req.end();
}

2.AccessTokenを使ってTranslator Text APIを叩く。

日本語、タイ語、英語の翻訳結果を受け取るよう要求リクエストを投げています。
細かいパラメーター等は変更可能ですので、公式APIドキュメントを参照してください。

translator.js
function translate(token, message, callback) {
    const postdata = {
        url: 'https://api.cognitive.microsofttranslator.com/translate?category=generalnn&api-version=3.0&to=ja&to=th&to=en&profanityAction=Marked&profanityMarker=Asterisk',
        headers : {
          'Content-Type' : 'application/json',
          'Authorization' : "Bearer " + token
        },
        body : JSON.stringify ([{'Text' : message}])
    };
    request.post(postdata, (error, response, body) => {
        console.log(postdata);
        if (error) {
          console.log(error);
        }
        console.log(body);
        callback(body);
    });
}

翻訳結果は、Translator Text APIからは翻訳対象の言語コード、翻訳後の言語コードと文章が返ってきます。
そのため、翻訳対象の言語が「日本語」の場合は、「タイ語」に翻訳したものを返します。
翻訳対象の言語が「タイ語」の場合は、「日本語」に翻訳したものを返します。
翻訳対象の言語が「日本語」でも「タイ語」でもない場合は、「英語」に翻訳したものを返します。

全体のサンプルコードはこのようにしました。

translator.js
'use strict';

const http = require('http');
const https = require('https');
const qs = require('querystring');
const request = require("request");

const azureClientSecret = process.env.azureClientSecret;

//アクセストーク取得
function getAccessToken(callback) {
    let body = '';
    let data = '';

    let req = https.request({
        host: 'api.cognitive.microsoft.com',
        path: '/sts/v1.0/issueToken',
        method: 'POST',
        headers: {
          'Ocp-Apim-Subscription-Key': azureClientSecret
        }
    }, (res) => {
        res.setEncoding('utf8');
        res.on('data', (chunk) => {
            body += chunk;
        }).on('end', () => {
            console.log(body);
            let resData = body;
            callback(resData);
        });

    }).on('error', (err) => {
        console.log(err);
    });
    req.write(qs.stringify(data));
    req.end();
}

//翻訳
function translate(token, message, callback) {
    const postdata = {
        url: 'https://api.cognitive.microsofttranslator.com/translate?category=generalnn&api-version=3.0&to=ja&to=th&to=en&profanityAction=Marked&profanityMarker=Asterisk',
        headers : {
          'Content-Type' : 'application/json',
          'Authorization' : "Bearer " + token
        },
        body : JSON.stringify ([{'Text' : message}])
    };
    request.post(postdata, (error, response, body) => {
        console.log(postdata);
        if (error) {
          console.log(error);
        }
        console.log(body);
        callback(body);
    });
}

exports.replyTrans = function (message, callback){
    //実行
    getAccessToken((token) => {
        translate(token, message, (translated) => {
            let res = JSON.parse(translated);
            console.log(res[0]["detectedLanguage"]["language"]);
            if (res[0]["detectedLanguage"]["language"] == "ja"){
                //thを返す
                callback(res[0]["translations"][1]["text"]);
            } else if (res[0]["detectedLanguage"]["language"] == "th"){
                //jaを返す
                callback(res[0]["translations"][0]["text"]);
            } else {
                //enを返す
                callback(res[0]["translations"][2]["text"]);
            }
        });
    });
}

動作確認

さあ、色々な言語でBotに話しかけてみましょう!

日本語→タイ語
image.png

タイ語→日本語
image.png

スペイン語→英語
image.png

最後に

今回も長い記事になってしまいましたが、最後まで読んでいただきありがとうございます。
今回は、Translator Text APIとLINE WORKSを組み合わせたBotを紹介しました。このように色々なAPIとつなぐことでBotの能力は上がっていきます。
LINE WORKSをお使いの皆さま、ぜひご活用ください〜♪

5
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?