LINEボットで作るWikipedia風ボット

# 1 はじめに
　LINEのボット機能を使ってwikipediaのような情報共有をする仕組みを作ります。

## 1.1 用意するもの
+ PaizaCloudのアカウント
+ LINEのアカウント

## 1.2 参考URL
* [LINE Developers](https://developers.line.biz/ja/docs/messaging-api/)


#2 事前準備
　ボットを作成するにはLINEアカウントで専用のチャネルを設定し、そこから呼び出されるサーバーサイドのエントリポイントを登録する必要があります。

## 2.1 webhookエントリポイントの設定
　まず、今回のバックエンドサーバにはPaiza Cloudを利用します。[LINEの開発ドキュメント](https://developers.line.biz/ja/docs/messaging-api/building-sample-bot-with-heroku/#deploy-the-echo-sample-bot)ではHerokuが利用されています。Paiza cloudは無料での利用は時間制限があるので、実際に仲間内でりようするのであればHerokuで構築するのも手だと思います。

### (1) PaizaCloudの起動
　いつものようにPaizaCloudを使って環境構築を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/ff1fbd2d-1397-610c-e6a0-0f0d1663f185.png)

### (2) サーバーの作成
　nodejs,postgresを選択して新規サーバー作成ボタンを押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/c411df5b-9752-fc6a-11d4-10f35a073d75.png)
 
 この時、上記の画面にある**サーバー名**を控えておいてください。（この例ではnst-test345-1）


## 2.2 チャネル作成
　手順は[Messaging APIを始めよう](https://developers.line.biz/ja/docs/messaging-api/getting-started/)に記載されていますが、簡単に解説します。
### (1) LINE Developersコンソールにログインする
　[LINE Developersコンソール](https://developers.line.biz/console/register/messaging-api/provider/)へアクセスする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/6ce46d3e-6412-07f7-aeca-a0a6129898b3.png)

　緑色のLINEアカウントでログインをクリックする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/1e3828e3-64d5-7e9d-9387-5d50ee9d7dac.png)

　QRコードログインを選択し、表示されたQRコードをLINEで読み取ると、本人確認コードが表示されるので、これを確認する。

### (2) プロバイダの作成
　次にプロバイダを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/0da793c0-79cd-a767-1416-770d915f5408.png)

### (3) チャネルの作成
　作成したプロバイダに紐づくチャネルを作成します。このチャネルの内容がLINEのボットアカウントで表示される内容です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/bc4d8628-0e49-903c-e4b1-73290d70c468.png)

### (4) チャネルシークレットの取得
　再度プロバイダー画面を表示し、先ほど作成したチャンネルボタン（下記の画面右下）をクリックします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/f1b35232-b122-cf27-e818-0f2837d93671.png)

　クリックして画面をスクロールさせると**チャネルシークレット**という欄がありますので、そこに表示されているコピーしておきます。

### (5) Webhookの利用を設定

　先ほどの画面のトップにあるタブから「**Messaging APIの設定**」を選択します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/ba13bd0c-9b78-34f0-b6df-ae35d2040424.png)
　
　下のほうにスクロールさせると、**Webhook設定**という欄がありますので、ここでWebhookの利用をONにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/36ac64a3-259f-b9eb-b739-5ee0f0d85a75.png)

　その際にWebhook URLを手順2.1で設定したサーバー名をもとに入力します。Paiza Cloudの場合は
```
　https://サーバー名.paiza-user-free.cloud:3000/webhook
```
という感じです。後ろのwebhookというのが今回作成するエンドポイントになります。

※　また、この画面に表示されているQRコードを読み込むことで、作成したボットにアクセスすることができます。

### (6) チャンネルアクセストークンの取得
　先ほどの画面をさらに下にスクロールすると、**チャンネルアクセストークン**という長い文字列が表示されていますので、これもコピーしておきましょう。



# 3　エコーボットの作成
　まず手始めに単純なエコーボットを作成します。node.jsを使った[サンプルのドキュメント](https://line.github.io/line-bot-sdk-nodejs/getting-started/basic-usage.html#configuration)を参考にします。

## 3.1 サーバーサイドの作成
### (1) node.jsサイトを作成
 Paiza.cloudのコンソールを開き下記のコマンドを投入します。

```
mkdir testapp
cd testapp
npm init -y

npm install express

npm install @line/bot-sdk
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/cfcf6e0c-9c3c-5448-6cd2-a67a7e47b043.png)

 先ほどのドキュメントにあるSynopsisにあるソースをそのままコピーします。

```javascript:server.js
const express = require('express');
const line = require('@line/bot-sdk');

const config = {
  channelAccessToken: 'YOUR_CHANNEL_ACCESS_TOKEN',
  channelSecret: 'YOUR_CHANNEL_SECRET'
};

const app = express();
app.post('/webhook', line.middleware(config), (req, res) => {
  Promise
    .all(req.body.events.map(handleEvent))
    .then((result) => res.json(result));
});

const client = new line.Client(config);
function handleEvent(event) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }

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

app.listen(3000);
```

ソース中のコンフィグ部分を、外部ファイルから読み込む形式にします


```javascript:元
const config = {
  channelAccessToken: 'YOUR_CHANNEL_ACCESS_TOKEN',
  channelSecret: 'YOUR_CHANNEL_SECRET'
};
```


```javascript:新
const config = require('./config');
```

そして、手順2.2(4),(6)で取得したチャンネルアクセストークンとチャンネルシークレットをもとに、server.jsと同位置にconfig.jsonというファイルを作成してください

```json:config.json
{
  "channelAccessToken" : "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "channelSecret" : "xxxxxxxxxxxxxxxxxxx"
}

```

 上記の内容でtestapp配下にserver.jsとconfig.jsonを作成します


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/8f850671-3cc9-b4ab-ac8f-dd3e855c1a56.png)


### (2) 動作確認
先ほどのserver.jsを起動します

```
~/testapp$ npm start
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/190a1187-c021-4080-c404-0440c8026df3.png)

これで、LINEからのアクセスを受け取れますので、さっそくQRコードから追加してメッセージを送ってみてください。入力した内容がそのまま返されます。

#４　情報共有ボットの作成
　いよいよここからがwikipedia風ボットの作成になります。

## 4.1 DBアクセスの実装
　やり取りされるメッセージからキーワードや解説などを保存するためにデータベースを接続します。

　PaizaCloudでサーバーを構築した際にpostgresを指定していますので、すでにその環境ができています。
　まずは、データベースを作成します。PaizaCLoudのコンソールで作業を行ってください。

```sql
psql
create database testdb;
\c testdb
alter role session_user with password 'ubuntu';
create table memo ( key text, value text);
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/dd5a0919-3976-6601-c3ce-51d2331c25f7.png)

psqlでpostgresにログインしたのちに、データベースを作成します。そして\c（円サインｃ）と打ってtestdbに切り替えたのちに、パスワードの設定とテーブルのクリエイトを行っています。

　これで、単語と解説を格納するデータベースが出来上がりました。次にnodejsからデータベースへアクセスするためのライブラリを導入します。

```shell
npm install pg
```

 なお、nodeサーバーが起動中の場合はctrl-cでいったん終了させましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410751/f2a2be76-9e84-c96f-0229-717e333b3096.png)

ちなみに簡単なアクセスサンプルは下記のとおりです。



```javascript
const { Pool, Client } = require('pg')

const pool = new Pool({
  user: 'ubuntu',
  host: 'localhost',
  database: 'testdb',
  password: 'ubuntu',
  port: 5432,
})

pool.query('SELECT NOW()', (err, res) => {
  console.log(err, res)
  pool.end()
})

```


## 4.2 単語の検索と登録
　まず最初に実装する操作イメージは下記の通りです。

1. 教えてほしい単語を書き込む
1. 登録されている単語であれば、解説を返す
1. 登録されていなければ「教えて」と返して、次に入力されたメッセージを解説として登録する

という感じで、気軽に質問して気軽に登録するイメージです。

## 

```javascript:server.js
const express = require('express');
const line = require('@line/bot-sdk');
const config = require('./config');

const { Pool, Client } = require('pg')

const pool = new Pool({
  user: 'ubuntu',
  host: 'localhost',
  database: 'testdb',
  password: 'ubuntu',
  port: 5432,
})

const app = express();

var users=[]
var beforeText=[]
var actionFlag=[]

app.post('/webhook', line.middleware(config), (req, res) => {
  Promise
    .all(req.body.events.map(handleEvent))
    .then((result) => res.json(result));
});

const client = new line.Client(config);
function handleEvent(event) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }
  const userid = event.source.userId
  const message = event.message.text

  if (users.indexOf(userid)) {
    users.push(userid)
  }

  const indexNo = users.indexOf(userid)
  console.log("actionFlag[indexNo] = ",actionFlag[indexNo])
  console.log("beforeText[indexNo] = ",beforeText[indexNo])

  if (actionFlag[indexNo]==='insert') {
    const insertValue = {
      name: 'insert-value',
      text: 'insert into memo(key,value) values ($1,$2)',
      values: [beforeText[indexNo], event.message.text],
    }
    pool
      .query(insertValue)
      .then(res => {
        client.broadcast({
          type: 'text',
          text: beforeText[indexNo]+'\n--------------\n'+event.message.text
        })
        .then(() => {
          actionFlag[indexNo] = ''
          beforeText[indexNo] = ''
        })
        .catch((err) => {
          console.log(err)
        });      
      })
      .catch(e => console.error(e.stack))  
    
  } else {
    const fetchValue = {
      name: 'fetch-value',
      text: 'SELECT * FROM memo WHERE key = $1',
      values: [event.message.text],
    }
    pool
      .query(fetchValue)
      .then(res => {
        console.log(res.rows[0])
        if(res.rows.length === 0) {
          actionFlag[indexNo] = "insert"
          beforeText[indexNo] = message

          return client.replyMessage(event.replyToken, {
            type: 'text',
            text:  '教えて！'
          });

        } else {
          return client.replyMessage(event.replyToken, {
            type: 'text',
            text:  res.rows[0].value
          });
        }
      })
      .catch(e => console.error(e.stack))
  }

}

app.listen(3000);
```

 入力された単語を検索して、データベースになければその単語を保存しておき、同じユーザーから来た次のメッセージをその解答としてデータベースへ登録しています。


## 4.3 不明な単語をみんなに通知する

　ここで、もう一工夫ということで、不明な単語があった場合に、その単語をみんなに知らせる機能を付けます。この機能により、自分がしらない単語でも誰かが解説を登録してくれるかもしれません。

先ほどのソースに　「教えて」　-> 「わからない」で単語をブロードキャストしてみます。

```javascript:server.js
const express = require('express');
const line = require('@line/bot-sdk');
const config = require('./config');

const { Pool, Client } = require('pg')

const pool = new Pool({
  user: 'ubuntu',
  host: 'localhost',
  database: 'testdb',
  password: 'ubuntu',
  port: 5432,
})

const app = express();

var users=[]
var beforeText=[]
var actionFlag=[]

app.post('/webhook', line.middleware(config), (req, res) => {
  Promise
    .all(req.body.events.map(handleEvent))
    .then((result) => res.json(result));
});

const client = new line.Client(config);
function handleEvent(event) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null);
  }
  const userid = event.source.userId
  const message = event.message.text

  if (users.indexOf(userid)) {
    users.push(userid)
  }

  const indexNo = users.indexOf(userid)
  console.log("actionFlag[indexNo] = ",actionFlag[indexNo])
  console.log("beforeText[indexNo] = ",beforeText[indexNo])
  
  if (actionFlag[indexNo]==='insert') {
    if ( message ==='わからない' ||  message ==='知らない' ||  message ==='？') {
      client.broadcast({
        type: 'text',
        text: '誰か―\n'+beforeText[indexNo]+'\nってなにー？　教えてー'
      })
      .then(() => {
        actionFlag[indexNo]=''
        beforeText[indexNo] = ''
        console.log("broadcast")
      })
      .catch((err) => {
        console.log(err)
      });      
    } else {
      const insertValue = {
        name: 'insert-value',
        text: 'insert into memo(key,value) values ($1,$2)',
        values: [beforeText[indexNo], event.message.text],
      }
      pool
        .query(insertValue)
        .then(res => {
          client.broadcast({
            type: 'text',
            text: beforeText[indexNo]+'\n--------------\n'+event.message.text
          })
          .then(() => {
            actionFlag[indexNo] = ''
            beforeText[indexNo] = ''
          })
          .catch((err) => {
            console.log(err)
          });      
        })
        .catch(e => console.error(e.stack))  
    }
  } else {
    const fetchValue = {
      name: 'fetch-value',
      text: 'SELECT * FROM memo WHERE key = $1',
      values: [event.message.text],
    }
    pool
      .query(fetchValue)
      .then(res => {
        console.log(res.rows[0])
        if(res.rows.length === 0) {
          actionFlag[indexNo] = "insert"
          beforeText[indexNo] = message
          
          return client.replyMessage(event.replyToken, {
            type: 'text',
            text:  '教えて！'
          });
          
        } else {
          return client.replyMessage(event.replyToken, {
            type: 'text',
            text:  res.rows[0].value
          });
        }
      })
      .catch(e => console.error(e.stack))
  }

}

app.listen(3000);
```


# 最後に
　いかがだったでしょうか。キーワードを使った仲間内だけの情報共有ツールですが、キーワードを増やして解説の修正や追記、単語の削除なども機能追加できると思います。

# 次回は...
　いよいよブロックチェーンでしょうかね。頑張ります。

# 記事一覧
https://qiita.com/nstshirotays/items/cb06863dc7288c0fed25

https://qiita.com/nstshirotays/items/e47374a8c35241853280

https://qiita.com/nstshirotays/items/174c46610e962a345d13

https://qiita.com/nstshirotays/items/7cf78fdb9be4c212fd31

https://qiita.com/nstshirotays/items/c113b62b791931303447

https://qiita.com/nstshirotays/items/075cffee06f3a50c01db

https://qiita.com/nstshirotays/items/d865c20c2a338110a73e