LoginSignup
13
6

More than 1 year has passed since last update.

「LINE Messaging API + Googleスプレッドシート + 猫画像API」による1問1答ではないチャットボットを作りました

Last updated at Posted at 2020-07-20

通常のチャットボットは1問1答式ですが、データベースと連携することで1問1答式以外の可能性が広がると思い、試しにこんなものを作りました。
私が若かりし頃(30年前くらい)のコンパとかの宴会芸のネタの1つですww

作ったもの

LINEチャットボット
「心を読む猫ボット」
スクリーンショット_iphonexspacegrey_portrait.png
ちなみに、iPhoneXへのハメコミ画像はこちらで作りました。
https://mockuphone.com/

何をするチャットボットなの?

数回の質問で、頭の中にある数字をあてます。
つまり、あなたの心を読むことができるボットなのです。
Image from Gyazo

なぜ猫なの?

テキストだけでは寂しいですし、老若男女に大人気の猫に便乗して少しでも利用してもらおうという姑息な手マーケティング手法です。
それに、今思い付いたのですが、心を読むチャットボットに飽きても、猫画像に癒やされるチャットボットとして長く利用してもらえるという狙いもあるのです。
ちなみにリッチメニュー「タッチしてください」の画像は、我が家の猫ちゃんです。

本当に人の心が読めるの?

チャットボットが人の心を読むなんて信じないでしょうから、その証拠動画をお見せいたしましょう。
Image from Gyazo

ちなみにこの動画は、@tkyko13さんに教えてもらったGyazoというサービスを利用しました。

これで、興味津々になった方は、今すぐこちらのQRコードにスマホのカメラを向けましょう。

システム

環境

  • Node 14.5.0

構成図

Image from Gyazo

ボットが数回質問した内容からユーザーの頭の中にある数字を当てるという仕組みです。

過去のデータの蓄積が必要なのでデータベースとしてGoogleスプレッドシートを利用しました。

プログラムは node.jsで、herokuに構築しました。
node.jsをherokuに構築するには、実行プログラムと同じディレクトリに Procfile を追加するだけで、できました。

Procfile
web: node app.js

使用API

なぜ心を読めるの?

超能力です。

というのは冗談で、簡単な算数問題です。
最後の「最初の数を引いてください」ってところがポイントです。

答えはコードの中にもあります。
(コード読むより、普通に考えたほうが簡単ですが・・・)

コード

app.js
'use strict';
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
const axios = require("axios");
const line = require('@line/bot-sdk');
//LINE Messaging API のいろいろ
const config = {
    channelSecret: 'LINE_CHANNEL_SECRET',
    channelAccessToken: 'LINE_CHANNEL_ACCESS_TOKEN' 
  };

//Googleスプレッドシートを使う設定
let {google} = require('googleapis');
const creds = {
    "type": "service_account",
    "project_id": "PROJECT_ID",
    "private_key_id": "PRIVATE_KEY_ID",
    "private_key": "PRIVATE_KEY",
    "client_email": "CLIENT_EMAIL",
    "client_id": "CLIENT_ID",
    "auth_uri": "AUTH_URI",
    "token_uri": "TOKEN_URI",
    "auth_provider_x509_cert_url": "AUTH_PROVIDER_X509_CERT_URL",
    "client_x509_cert_url": "CLIENT_X509_CERT_URL"
  };
// JSON Web Token(JWT)の設定
let jwtClient = new google.auth.JWT(
    creds.client_email,
    null,
    creds.private_key,
    ['https://www.googleapis.com/auth/spreadsheets',
     'https://www.googleapis.com/auth/drive']
  );
const sheet = 'シートID';
//シートIDとは、スプレッドシートのIDのことで仮にスプレッドシートのURLが以下の場合は
//ABCDEFGABCDEFGABCDEFGABCDEFGABCDEFG の部分です。
//https://docs.google.com/spreadsheets/d/ABCDEFGABCDEFGABCDEFGABCDEFGABCDEFG/edit#gid=0

// スプレッドシートAPIはv4を使う
let sheets = google.sheets('v4');

app.post('/webhook', line.middleware(config), (req, res) => {
console.log(req.body.events);
    //ここのif分はdeveloper consoleの"接続確認"用なので削除して問題ないです。
    if(req.body.events[0].replyToken === '00000000000000000000000000000000' && req.body.events[1].replyToken === 'ffffffffffffffffffffffffffffffff'){
        res.send('Hello LINE BOT!(POST)');
        console.log('疎通確認用');
        return; 
    }
    Promise
      .all(req.body.events.map(handleEvent))
      .then((result) => res.json(result));
});

const client = new line.Client(config);

async function handleEvent(event) {
  //GoogleスプレッドシートのJSON Web Token(JWT) の認証
  let resultJwtClient;
  try {
    resultJwtClient = await jwtClient.authorize();
  } catch (error) {
    console.log("Auth Error: " + error);
  }

  //シートを読み込む
 let responseGetSheet;
 try {
   responseGetSheet =  await sheets.spreadsheets.values.get({
     auth: jwtClient,
     spreadsheetId: sheet,
     range: "シート1",
    });
   } catch (error) {
     console.log('The API returned an error: ' + error);
   }
 //シートから読み込んだデータ
 let data = responseGetSheet.data.values

 let bot_message; //返すセリフ
 let newComerFlag = 1;
 let userIndexNumber; //アクセス者のインデックス

 //アクセス者のインデックスを調べる(初めてかどうかも調べる)
 for (let i = 0; i < data.length ; i++ ) {
   if (data[i][0] == event.source.userId){
       newComerFlag =0;
       userIndexNumber= i ;  
   }
 }
//最初ののアクセスのときの処理
if (newComerFlag == 1){
    // googleスプレッドシートへの書き込み(ユーザーID、回数、数字の初期値)
    bot_message = "数字を1つ思い浮かべてください";
    appendData("シート1!A1",event.source.userId,0,0)
    }
    //最後の1つ前のアクセスのときの処理
    else if (data[userIndexNumber][1] == 4) {
      bot_message = "それに、最初に思った数字を引いてください";
      data[userIndexNumber][1] = Number(data[userIndexNumber][1]) + 1;
      //googleスプレッドシートのデータをアップデート
      updateData("シート1!"+String(userIndexNumber+1)+":"+String(userIndexNumber+1),data[userIndexNumber][0],data[userIndexNumber][1],data[userIndexNumber][2])
    }
    //最後のアクセスの処理
    else if (data[userIndexNumber][1] == 5) {
      bot_message = "今あなたの頭にある数字は "+ data[userIndexNumber][2] + " ですね";
      //googleスプレッドシートの内容を消去する
      updateData("シート1!"+String(userIndexNumber+1)+":"+String(userIndexNumber+1),"","","")  
    }
    else{
     //通常時のアクセスの処理
     let plusMinus;
     let add_mes;
     let random1 = Math.floor( Math.random()*10 )+ 1;  
     if (random1 > 2) {add_mes=" を足してください";plusMinus = 1;}
                   else{add_mes=" を引いてください";plusMinus =- 1;}
     let random2 = Math.floor( Math.random()*10 )+ 1;
     bot_message = "それに " + random2 + add_mes;
     data[userIndexNumber][2] = Number(data[userIndexNumber][2])+ (random2*plusMinus);
     data[userIndexNumber][1] = Number(data[userIndexNumber][1]) + 1;
     //googleスプレッドシート更新(回数、現在の数値)
      updateData("シート1!"+String(userIndexNumber+1)+":"+String(userIndexNumber+1),data[userIndexNumber][0],data[userIndexNumber][1],data[userIndexNumber][2])  
    }

 //猫の写真をAPIからランダムにとってくる
 let cat_picture = await axios.get("https://api.thecatapi.com/v1/images/search");
 let cat_url= cat_picture.data[0].url;

 //ボットのメッセージを返す処理
 return client.replyMessage(event.replyToken, {
    "type": "template",
    "altText": "This is a buttons template",
    "template": {
        "type": "buttons",
        "thumbnailImageUrl": cat_url,
        "imageAspectRatio": "rectangle",
        "imageSize": "cover",
        "imageBackgroundColor": "#FFFFFF",
        "title": bot_message,
        "text": " ",
        "defaultAction": {
            "type": "uri",
            "label": "View detail",
            "uri": cat_url
        },
        "actions": [
            {
                "type":"message",
                "label":"OK",
                "text":"OK"
            }
        ]
    }
  });
}

//googleスプレッドシートにデータを追加(行を追加)
async function appendData(range0,value1,value2,value3) {
  let responseAppendSheet;
    try {
      responseAppendSheet = await sheets.spreadsheets.values.append({
      auth: jwtClient,
      spreadsheetId: sheet,
      range: range0,
      valueInputOption: "USER_ENTERED",
      insertDataOption : "INSERT_ROWS",
      resource : {
        values : [[value1,value2,value3]]
      }
    });
  } catch (error) {
     console.log('The API returned an error: ' + error);
  }
}

//googleスプレッドシートのデータをアップデート(上書き)
async function updateData(range0,value1,value2,value3) {
  let responseAppendSheet;
    try {
      responseAppendSheet = await sheets.spreadsheets.values.update({
      auth: jwtClient,
      spreadsheetId: sheet,
      range: range0,
      valueInputOption: "USER_ENTERED",
      resource : {
        values : [[value1,value2,value3]]
      }
    });
  } catch (error) {
      console.log('The API returned an error: ' + error);
  }
}

app.listen(PORT);
console.log(`Server running at ${PORT}`);

フローチャート

上記のコードを大雑把にフローチャートにすると、
Untitled Diagram.png

ちなみに、このフローチャートはこちらで作りました。
draw.io
https://www.draw.io/

可能性

LINEのチャットボットを外部のデータベースとやりとりできれば、可能性が広がります。
ぱっと浮かんだだけで、以下のようなことができそうなので、気が向いたらチャレンジしてみようと思います。

  • しりとり、山手線ゲーム
  • ポーカーなどのトランプ
  • アドベンチャーゲーム、脱出ゲーム
  • LINEから外部の機械やガジェットを操作
  • センサーからマイコンを通じて情報取得
  • 会話のやりとりをクラウドに保存して機械学習
  • Unityと連携したゲームや、メディアアート
13
6
0

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
13
6