通常のチャットボットは1問1答式ですが、データベースと連携することで1問1答式以外の可能性が広がると思い、試しにこんなものを作りました。
私が若かりし頃(30年前くらい)のコンパとかの宴会芸のネタの1つですww
##作ったもの
LINEチャットボット
「心を読む猫ボット」
ちなみに、iPhoneXへのハメコミ画像はこちらで作りました。
https://mockuphone.com/
###何をするチャットボットなの?
数回の質問で、頭の中にある数字をあてます。
つまり、あなたの心を読むことができるボットなのです。
###なぜ猫なの?
テキストだけでは寂しいですし、老若男女に大人気の猫に便乗して少しでも利用してもらおうという姑息な手マーケティング手法です。
それに、~~今思い付いたのですが、~~心を読むチャットボットに飽きても、猫画像に癒やされるチャットボットとして長く利用してもらえるという狙いもあるのです。
ちなみにリッチメニュー「タッチしてください」の画像は、我が家の猫ちゃんです。
###本当に人の心が読めるの?
チャットボットが人の心を読むなんて信じないでしょうから、その証拠動画をお見せいたしましょう。
ちなみにこの動画は、@tkyko13さんに教えてもらったGyazoというサービスを利用しました。
これで、興味津々になった方は、今すぐこちらのQRコードにスマホのカメラを向けましょう。
システム
環境
- Node 14.5.0
構成図
ボットが数回質問した内容からユーザーの頭の中にある数字を当てるという仕組みです。
過去のデータの蓄積が必要なのでデータベースとしてGoogleスプレッドシートを利用しました。
プログラムは node.jsで、herokuに構築しました。
node.jsをherokuに構築するには、実行プログラムと同じディレクトリに Procfile を追加するだけで、できました。
web: node app.js
###使用API
-
LINE Messaging API
Node.jsでLINE BOTを作る方法はこちらを参考にしました
1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017
-
Google Sheets API
過去の情報を保有するためにGoogleスプレッドシートを使いました。- Googleスプレッドシートの導入部分はこちらを参考
Node.js googleapis npmパッケージで Google スプレッドシートを await/async で読み取るメモ 〜1ft-seabass.jp.MEMO
- データの読み書きはこちらのサイトを参考
【Node.js + Sheets API v4】Googleスプレッドシートを読み書きする
- Googleスプレッドシートの導入部分はこちらを参考
- [**TheCatAPI**](https://docs.thecatapi.com/)
いろんな猫APIがあります。その中からURLをたたくだけでランダムにネコの画像を取得できるAPIを使用しました。
###なぜ心を読めるの?
超能力です。
というのは冗談で、簡単な算数問題です。
最後の「最初の数を引いてください」ってところがポイントです。
答えはコードの中にもあります。
(コード読むより、普通に考えたほうが簡単ですが・・・)
###コード
'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}`);
###フローチャート
上記のコードを大雑把にフローチャートにすると、
ちなみに、このフローチャートはこちらで作りました。
draw.io
https://www.draw.io/
##可能性
LINEのチャットボットを外部のデータベースとやりとりできれば、可能性が広がります。
ぱっと浮かんだだけで、以下のようなことができそうなので、気が向いたらチャレンジしてみようと思います。
- しりとり、山手線ゲーム
- ポーカーなどのトランプ
- アドベンチャーゲーム、脱出ゲーム
- LINEから外部の機械やガジェットを操作
- センサーからマイコンを通じて情報取得
- 会話のやりとりをクラウドに保存して機械学習
- Unityと連携したゲームや、メディアアート