Messaging API(Node.js)を使って、LINEで入力された文字を自然言語apiを使って感情分析して返してみる。

こんにちは。
新卒マイナス1年目エンジニアの和尚です。

LINEから入力された文をgoogle cloudの自然言語api(Natural language api)を使って解析して返す簡単なプログラムを作ったので自分用のメモがてら投稿します。
※JavaScript(node.js)で書いてます。

開発環境
- CentOS 6.9
- Vagrant 1.9.7
- node.js v8.11.2

##1.Node.jsからGoogle Cloudのapiを使えるようにする。
下の記事を参考にしてください(投げやり感)。

前回の記事：[Node.js[CentOS6]から自然言語[Cloud Natural Language API]を使ってみる。](https://qiita.com/osyou-create/items/a286b316783df8fc9388)

##2.Line Message apiを使えるようにする。
###I.LINE developersに登録する。
[開発者向けサイト](https://developers.line.me/ja/)から普段使っているLINEでログインします。
###Ⅱ.ボットを作成する。
1.ホームからMessaging API(ボット)を始める。を押します。
2.新規プロバイダを作成(名前はなんでも)して追加し次に進みます。
3.アプリアイコン、アプリ名、アプリ説明を入力します。(※アプリ名はしばらく変更できないので、変な名前はつけないようにしましょう。)
4.プランは「developer trial」で、大業種・小業種は使用する目的に応じて選んでください。
5.メアドを入力し、ボットを作成します。

###Ⅲ.管理者画面に移動してから
1.メッセージ送受信設定からアクセストークンを発行し、**アクセストークン**と基本情報の**Channnel　Secret**を.envファイルにぶち込んでください。
・nmpモジュール「dotenv」を使用して呼び出します。

```[.env]
CHANNEL_ACCESS_TOKEN = HKF423/jgdakgskegf.....
CHANNEL_SECRET = 4324hgj2432hf42.....
```
2.メッセージ送受信設定の設定をする。
・Webhook送信 =>　利用する。

3.QRコードから友達追加。
スマートフォン・タブレットを取り出し、LINEを起動し...とりあえず読み込んで追加しましょう。

###Ⅳ.Node.jsで開発
[前回の記事](https://qiita.com/osyou-create/items/a286b316783df8fc9388)で作成したファイルを使用して開発していきます。
>
参考記事：[1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest](https://qiita.com/n0bisuke/items/ceaa09ef8898bee8369d)

1.npmモジュールのインストール

```
$ npm install --save @line/bot-sdk express dotenv axios
```
- **@line/bot-sdk**　… line公式のnode.js用SDK
- **express** … node.js向けのwebフレームワーク
- **dotenv**　… .envファイルからproccess.envへ環境変数を読み込むモジュール
- **axios** … HTTPクライアント

2.プログラミング
server.jsファイルに書き込んで行きましょう。
前回のserver.jsに書いてあるのはテストコードなので、必要ないところは消してください。

※汚いコードですいませんTT。こう書いたらスタイリッシュってのがあったら教えてください。。。

```javascript:server.js
'use strict';

const language = require('@google-cloud/language');
//インストールしたモジュールを読み込む
const line = require('@line/bot-sdk');
const express = require('express');
const axios = require("axios");
require("dotenv").config();

//ポートをprocess.envファイルから読み込む
const PORT = process.env.PORT || 3000;

//先ほど書いた.envファイルからアクセストークンとチャンネルシークレットを引っ張ってくる。
const config = {
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN,
  channelSecret: process.env.CHANNEL_SECRET,
};
//それぞれインスタンスを作成。
const app = express();
const client_line = new line.Client(config);
const client_lang = new language.LanguageServiceClient();

//スタンプ送信処理に使用します。
let eva = 0;

//応答処理
const Analyze = async (text,event,userId) => {
  let replyText = "";
  const document = {
    content: text,
    type: 'PLAIN_TEXT',
  };

  client_lang
  .analyzeSentiment({document: document})
  .then(results => {
    //全体評価をpushMessageで返します(scoreで点数、magnitudeで感情の揺れが取得できます。)
    const sentiment = results[0].documentSentiment;
    pushText(`全体評価:\nスコア: ${rounding(sentiment.score)}\n感情の振れ幅: ${rounding(sentiment.magnitude)}`,userId);
    //一文ごとの点数と感情の揺れを返します。forEachで文が複数あれば一つずつpushMessageで返します。
    //出力される順番がおかしくなることがあるので、setTimeoutを使っていますが、解消されない時があります...
    const sentences = results[0].sentences;
    sentences.forEach(sentence => {
      setTimeout(() => {
        pushText(`「${sentence.text.content}」:\nスコア: ${rounding(sentence.sentiment.score)}\n感情の振れ幅: ${rounding(sentence.sentiment.magnitude)}`,userId);
      },1000);
    });
    //総合評価がニュートラル、正の値、負の値によってスタンプを送信します。
    evalResult(sentiment.score,userId)
  })
  .catch(err => {
    console.error('ERROR:', err);
  });
}


//切り捨て処理
const rounding = (num) =>{
  return Math.floor(num * 10)/10;
}

//テキスト送信処理
const pushText = (mes,userId) => {
  client_line.pushMessage(userId,{
    type: "text",
    text: mes,
  })
}

//総合評価でスタンプ送信。これもsetTimeout()を使って一番最後(２秒後)に出力されるようにします。
const evalResult = (num,userId) => {
  let stamp = 1;
  if(num === 0){
    stamp = 113
  }else if (num >= 0) {
    stamp = 14;
  }else{
    stamp = 6;
  }
  setTimeout(() => {
    client_line.pushMessage(userId,{
      type: "sticker",
      packageId: 1,
      stickerId: stamp,
    })
  },2000);
}

//LINEイベント(メッセージ入力)発火
const handleEvent = (event) => {
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }
  const sentText = event.message.text;
  const userId = event.source.userId;
  let message = "";
 
  if(event.message.text !== ""){
    //評価する前に一文出力して、解析っぽさを演出します。
    message = `「${sentText}」を解析します...`;
    //解析
    Analyze(sentText,event,userId);
  }

  return client_line.replyMessage(event.replyToken, {
    type: 'text',
    text: message
  });
}

//main
app.post('/webhook', line.middleware(config), (req, res) => {
  console.log(req.body.events);
  Promise
  .all(req.body.events.map(handleEvent))
  .then((result) => res.json(result));
});
app.listen(PORT);
console.log(`サーバー作動中:PORT ${PORT}`);

```

3.起動

```
$ node server.js
```
以下の表示が出てきたら成功です。

```
サーバー作動中:PORT 3000 
```

4.トンネリング
webhookURLをボットに登録するためにngrokを使用します。
npmでngrokをインストールして、起動します。

```
$ npm i -g ngrok
$ ngrok http 3000
```

```

ngrok by @inconshreveable                              (Ctrl+C to quit)

Session Status                online
Session Expires               7 hours, 59 minutes
Version                       2.2.8
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://31538887.ngrok.io -> localhost:3000
Forwarding                    https://31538887.ngrok.io -> localhost:3000

Connections                   ttl     opn     rt1     rt5     p50     p90
                              0       0       0.00    0.00    0.00    0.00
```
Forwardingで表示されている`https://31538887.ngrok.io`の`31538887.ngrok.io`を使用します。(ngrokを起動するごとに変更されます。)

管理者画面に戻り、メッセージ送受信設定のWebhookURLに`31538887.ngrok.io/webhook`を入力し、更新します。

##Ⅴ.LINEから解析したい文を送ってみる。
お疲れ様です。登録したLINEボットに向けてメッセージを送ってみましょう。
解析した文と感情スタンプが返ってきたら成功です。
<img width="272" alt="スクリーンショット 2018-05-26 10.33.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/243225/7e2703b1-1cd8-8e3a-9f5d-f59a7bdc8361.png">

※成功しない方いたら、コメントor自力でなんとかしてください。

[ソースコード:GitHub](https://github.com/osyou-create/lineBOT_-analyze)