彼氏を作ろう
何かと疲れている現代人。私もまたそのひとりである。
ちょっとしたときめきと癒しが欲しい。仕事帰りに誰かに「今日も一日頑張ったね」と優しく言われたい。私のどうでもいい話に「うんうん」と相槌を打ってくれるだけでいい。
そうだ、LINE Botで彼氏を作ろう。
どんな彼氏?
優しい言葉をかけてくれるのは大前提で、今日の天気も教えてほしい。(よく傘を忘れてびしょ濡れになるので)
ついでに天気によって会話パターンも変化するとよい。
ということで、やりたいことは以下の2点。
- 天気予報(天気によって返事のパターンあり)
- ある程度キャッチボールのある会話
環境
Visual Studio Code 1.61.0
Node.js 7.24.0
ngrok
彼氏、できました
実際に作って動かしてみたものがこちら。
紹介します。彼氏の**「タケシ(仮)」**(LINE Bot名)です。
「タケシはきっとラーメン好き」という無駄な設定の元、LINEアイコンをラーメンにした。
(画像は私が週3で通っていた店の明太子油そば。激旨)
私にめちゃめちゃ優しい、彼氏ができてしまった。たまに(通信の関係で)返事がこなくて、既読スルーされるのもご愛敬。
ソースコード
'use strict';
// ########################################
// 初期設定など
// ########################################
// パッケージを使用します
const express = require('express');
const line = require('@line/bot-sdk');
const axios = require('axios');
// ローカル(自分のPC)でサーバーを公開するときのポート番号です
const PORT = process.env.PORT || 3000;
// Messaging APIで利用するクレデンシャル(秘匿情報)です。
const config = {
channelSecret: 'xxxxxxxxxxxxxxxxxxxxxxxxxxx', //作成したBOTのチャンネルシークレット
channelAccessToken: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' //作成したBOTのチャンネルアクセストークン
};
// ########## ▼▼▼ サンプル関数 ▼▼▼ ##########
const sampleFunction = async (event) => {
const userText = event.message.text;
let replyText = '';
if (userText === '天気は?') {
// 「リプライ」を使って先に返事しておきます
await client.replyMessage(event.replyToken, {
type: 'text',
text: 'ちょっと待ってね'
});
let pushText = '';
try {
// axiosで天気予報APIを叩きます(少し時間がかかる・ブロッキングする)
const res = await axios.get('https://weather.tsukumijima.net/api/forecast/city/130010');
// 本日の天気を取得
const today_weather = res.data.forecasts[0].telop;
// 返信用のテキストを作成
pushText = `今日の東京の天気は${today_weather}だよ`;
// 予報に「雨」が含まれている場合は、「傘を持って行った方がいいよ」と言ってくれる
if (pushText.indexOf(`雨`) > -1) {
pushText = `今日の東京の天気は${today_weather}だよ。傘を持って行った方がいいかも`;
}
} catch (error) {
pushText = 'ちょっと分からないや。ごめんね';
// APIからエラーが返ってきたらターミナルに表示する
console.error(error);
}
// 「プッシュ」で後からユーザーに通知します
return client.pushMessage(event.source.userId, {
type: 'text',
text: pushText,
});
}
// 部分一致1(「ねえ」という文字が1ヶ所でも含まれていたら反応)
if (userText.indexOf('ねえ') > -1) {
replyText = 'どうしたの?';
}
// 部分一致2(「曜」という単語が1ヶ所でも含まれていたら反応)
if (userText.indexOf('曜') > -1) {
replyText = '今週もあとちょっとだね!週末は何して過ごそうか?';
}
// 部分一致3(「れた」という単語が1ヶ所でも含まれていたら反応)※疲れた、つかれたの表記のぶれ用
if (userText.indexOf('れた') > -1) {
// 返事
const attractions = ['お疲れさま', '今日も頑張ったね', 'ごはん食べに行く?', '美味しいものでも食べに行こう', 'お仕事してえらい!'];
// 返事の種類数
const att_count = attractions.length;
// 乱数(0以上1未満の小数)* 種類数 をして小数以下切り捨て
const index = Math.floor(Math.random() * att_count);
// インデックスを指定して特定の返事を示す文字列を取り出す
replyText = attractions[index] ;
}
// 部分一致4(「おはよ(う)」という単語が1ヶ所でも含まれていたら反応)
if (userText.indexOf('おはよ') > -1) {
// 返事
const attractions = ['おはよう', 'おはよー','おはよ。よく寝れた?', 'おはよ。眠いよー'];
// 選択の種類数
const att_count = attractions.length;
// 乱数(0以上1未満の小数)* 種類数 をして小数以下切り捨て
const index = Math.floor(Math.random() * att_count);
// インデックスを指定して特定の返事を示す文字列を取り出す
replyText = attractions[index] ;
}
// 部分一致5(「好き!」という単語が1ヶ所でも含まれていたら反応)
if (userText.indexOf('好き!') > -1) {
// 返事
const attractions = ['僕も!', '好きー!', '大好きー!', '嬉しいな!', 'やったー!'];
// 選択の種類数
const att_count = attractions.length;
// 乱数(0以上1未満の小数)* 種類数 をして小数以下切り捨て
const index = Math.floor(Math.random() * att_count);
// インデックスを指定して特定の返事を示す文字列を取り出す
replyText = attractions[index] ;
}
// 完全一致したらランダムに返信
if (userText === '私のこと好き?') {
// 返事
const attractions = ['好きだよ!', '大好き!!', 'きみは?', '恥ずかしいな・・・'];
// 返事の種類数
const att_count = attractions.length;
// 乱数(0以上1未満の小数)* 種類数 をして小数以下切り捨て
const index = Math.floor(Math.random() * att_count);
// インデックスを指定して特定の返事を示す文字列を取り出す
replyText = attractions[index] ;
}
// 完全一致したら指定の文言を返信
if (userText === '無理') {
// 返事
replyText = 'イケるっしょ!';
}
// この時点でどの条件にも引っかかってない(replyTextが空文字列のまま)なら相槌をうっておく
if (replyText === '') {
const attractions = ['うんうん、そうだよね', 'そういうこともあるよね', 'そうなんだ', 'うん', '頑張りすぎないようにね','うんうん'];
// 返事の種類数
const att_count = attractions.length;
// 乱数(0以上1未満の小数)* 種類数 をして小数以下切り捨て
const index = Math.floor(Math.random() * att_count);
// インデックスを指定して特定の返事を示す文字列を取り出す
replyText = attractions[index] ;
}
return client.replyMessage(event.replyToken, {
type: 'text',
text: replyText
});
};
// ########## ▲▲▲ サンプル関数 ▲▲▲ ##########
// ########################################
// LINEサーバーからのWebhookデータを処理する部分
// ########################################
// LINE SDKを初期化します
const client = new line.Client(config);
// LINEサーバーからWebhookがあると「サーバー部分」から以下の "handleEvent" という関数が呼び出されます
async function handleEvent(event) {
// 受信したWebhookが「テキストメッセージ以外」であればnullを返すことで無視します
if (event.type !== 'message' || event.message.type !== 'text') {
return Promise.resolve(null);
}
// サンプル関数を実行します
return sampleFunction(event);
}
// ########################################
// Expressによるサーバー部分
// ########################################
// expressを初期化します
const app = express();
// HTTP POSTによって '/webhook' のパスにアクセスがあったら、POSTされた内容に応じて様々な処理をします
app.post('/webhook', line.middleware(config), (req, res) => {
// 検証ボタンをクリックしたときに飛んできたWebhookを受信したときのみ以下のif文内を実行
if (req.body.events.length === 0) {
res.send('Hello LINE BOT! (HTTP POST)'); // LINEサーバーに返答します(なくてもよい)
console.log('検証イベントを受信しました!'); // ターミナルに表示します
return; // これより下は実行されません
} else {
// 通常のメッセージなど … Webhookの中身を確認用にターミナルに表示します
console.log('受信しました:', req.body.events);
}
// あらかじめ宣言しておいた "handleEvent" 関数にWebhookの中身を渡して処理してもらい、
// 関数から戻ってきたデータをそのままLINEサーバーに「レスポンス」として返します
Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result));
});
// 最初に決めたポート番号でサーバーをPC内だけに公開します
// (環境によってはローカルネットワーク内にも公開されます)
app.listen(PORT);
console.log(`ポート${PORT}番でExpressサーバーを実行中です…`);
全体のコードは上記。
詳細については、下記に記載。
天気予報API
if (userText === '天気は?') {
// 「リプライ」を使って先に返事しておきます
await client.replyMessage(event.replyToken, {
type: 'text',
text: 'ちょっと待ってね'
});
let pushText = '';
try {
// axiosで天気予報APIを叩きます(少し時間がかかる・ブロッキングする)
const res = await axios.get('https://weather.tsukumijima.net/api/forecast/city/130010');
// 本日の天気を取得
const today_weather = res.data.forecasts[0].telop;
// 返信用のテキストを作成
pushText = `今日の東京の天気は${today_weather}だよ`;
// 予報に「雨」が含まれている場合は、「傘を持って行った方がいいよ」と言ってくれる
if (pushText.indexOf(`雨`) > -1) {
pushText = `今日の東京の天気は${today_weather}だよ。傘を持って行った方がいいかも`;
}
天気について、天気予報APIを使い、「天気は?」と聞いたら今日の東京の天気を教えてくれるように設定。
天気予報の結果に「雨」が入っていた場合、「傘を持って行った方がいいかも」と言ってくれる。
「雨」が入っていなかった場合は、天気予報のみ教えてくれる。
天気予報の地域の指定は、天気予報APIの全国の地点定義表でコードを確認し、https://weather.tsukumijima.net/api/forecast/city/
の後ろに記載する。(東京は130010)
const res = await axios.get('https://weather.tsukumijima.net/api/forecast/city/130010');
天気によって返信パターンを変える仕様について、上手く条件が指定できずに苦労した。
とりあえず表示できるようにしたが、もう少しよい方法があるような気がするので、今後時間をかけて追究していきたい。
部分一致(固定返信)
// 部分一致2(「曜」という単語が1ヶ所でも含まれていたら返信)
if (userText.indexOf('曜') > -1) {
replyText = '今週もあとちょっとだね!週末は何して過ごそうか?';
}
曜日に関して話しかけたら励ましてもらいたいな、と思い設定。
どの曜日を入れても反応してもらえるように、こちらからの問いかけに「曜」の文字が入っていたら固定の返信をしてくれるようにした。
日曜でも月曜でも「今週もあとちょっとだね!」と返してくれる。タケシ、可愛いな。
部分一致(ランダム返信)
// 部分一致3(「れた」という単語が1ヶ所でも含まれていたらランダムに返信)※疲れた、つかれたの表記のぶれ用
if (userText.indexOf('れた') > -1) {
// 返事
const attractions = ['お疲れさま', '今日も頑張ったね', 'ごはん食べに行く?', '美味しいものでも食べに行こう', 'お仕事してえらい!'];
// 返事の種類数
const att_count = attractions.length;
// 乱数(0以上1未満の小数)* 種類数 をして小数以下切り捨て
const index = Math.floor(Math.random() * att_count);
// インデックスを指定して特定の返事を示す文字列を取り出す
replyText = attractions[index] ;
}
「疲れた」「つかれた」の漢字とひらがな両方に対応するため、「れた」の言葉に反応するように設定。
複数の返信パターンをランダムで返すようにしている。
ここでは返信パターンは5個で設定しているけれど、5個以下でも可。5個以上も設定できるので、必要に応じてパターンを追加する。
完全一致(固定返信)
// 完全一致したら固定の返信
if (userText === '無理') {
// 返事
replyText = 'イケるっしょ!';
}
「無理」と弱音を吐いたら、「イケるっしょ!」と元気に励ましてくれる。
タケシ、いい子だね・・・
完全一致(ランダム返信)
// 完全一致したらランダムに返信
if (userText === '私のこと好き?') {
// 返事
const attractions = ['好きだよ!', '大好き!!', 'きみは?', '恥ずかしいな・・・'];
// 返事の種類数
const att_count = attractions.length;
// 乱数(0以上1未満の小数)* 種類数 をして小数以下切り捨て
const index = Math.floor(Math.random() * att_count);
// インデックスを指定して特定の返事を示す文字列を取り出す
replyText = attractions[index] ;
}
「私のこと好き?」と聞くと、ランダムで返信してくれる。
そろそろ狂気を感じてきましたか?
それ以外の場合
// どの条件にも引っかかってない(replyTextが空文字列のまま)なら相槌をうつ
if (replyText === '') {
const attractions = ['うんうん、そうだよね', 'そういうこともあるよね', 'そうなんだ', 'うん', '頑張りすぎないようにね'];
// 返事の種類数
const att_count = attractions.length;
// 乱数(0以上1未満の小数)* 種類数 をして小数以下切り捨て
const index = Math.floor(Math.random() * att_count);
// インデックスを指定して特定の返事を示す文字列を取り出す
replyText = attractions[index] ;
}
設定した言葉以外の問いかけをした場合、ランダムで相槌を打つように設定。
まとめ
夜中に思いつき、深夜テンションのまま勢いで作成。
シンプルな仕組みなので、一致の条件や選択肢の種類にバリエーションを持たせて楽しく作成できた。
時間の都合で簡単なものになってしまったけれど、ホットペッパーAPIというのも見つけたので、今後はタケシ(LINE Bot)に実装して、お店検索もできる彼氏にしたい。
彼氏改造計画の夢は広がる。
参考
- [1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest]
(https://qiita.com/n0bisuke/items/ceaa09ef8898bee8369d) - 【JavaScript】~を含むかのチェック