/*==============================
LINE BOTのメイン処理
==============================*/
//LINEアクセストークン
var ACCESS_TOKEN = "xxxxxxxxxxxxxxxxxxxx";
//絵文字
var EMOJI_HAPPY = "\uDBC0\uDC01" //わーい
var EMOJI_MOON_GRIN = "\uDBC0\uDC8D"; //えへへ
var EMOJI_FROZEN = "\uDBC0\uDC83"; //固まる
var EMOJI_TIRED = "\uDBC0\uDC17"; //ごめん
var EMOJI_SHINY = "\uDBC0\uDC2D"; //きらきら
var EMOJI_OK = "\uDBC0\uDC33"; //OK
var EMOJI_EIGHTHNOTE = "\uDBC0\uDC39"; //音符
var EMOJI_STAR = "\uDBC0\uDCB2"; //スター
var EMOJI_TRAIN = "\uDBC0\uDC47"; //電車
var EMOJI_BUILDING = "\uDBC0\uDC4C"; //ビル
var EMOJI_POSTOFFICE = "\uDBC0\uDC4D"; //郵便局
var EMOJI_HOSPITAL = "\uDBC0\uDC4E"; //病院
var EMOJI_SCHOOL = "\uDBC0\uDC4F"; //学校
var EMOJI_SPA = "\uDBC0\uDC51"; //温泉
/*
POSTリクエスト受信
request:受信リクエスト
*/
function doPost(request) {
//POSTリクエストをJSONデータにパース
var receiveJSON = JSON.parse(request.postData.contents);
for (var i = 0; i < receiveJSON.events.length; i++){
var event = receiveJSON.events[i];
//ユーザID取得
var userId = event.source.userId;
//応答トークン取得
var replyToken = event.replyToken;
//イベント種別取得
var eventType = event.type;
switch(eventType){
case "follow": //友だち追加
//ユーザ情報を削除(ゴミが残っていた時のため)
deleteUserInfo(userId);
//新しくユーザ情報を追加
addUserInfo(userId);
var messages = [];
//ウェルカムメッセージ
var msg1 = welcomeMessage();
messages.push(msg1);
//住所確認メッセージ
var msg2 = confirmAddressMessage();
messages.push(msg2);
//LINEの応答
replyMessage(replyToken, messages);
break;
case "message": //メッセージ受信
//応答メッセージの作成
var messages = createReplyMessage(userId, event);
//LINEの応答
if (messages.length > 0){
replyMessage(replyToken, messages);
}
break;
case "unfollow": //ブロック
//ユーザ情報を削除
deleteUserInfo(userId);
break;
case "postback": //ボタン操作による応答
//「最初から検索」を選択
if (event.postback.data == "gotoFirst"){
//最初から検索するための処理
gotoFirst(userId);
var messages = [];
//住所確認メッセージ
var msg1 = confirmAddressMessage();
messages.push(msg1);
//LINEの応答
replyMessage(replyToken, messages);
}
//「続きを見る」を選択
else if (event.postback.data == "nextResults"){
//次の結果表示
var messages = nextResultsMessage(userId);
//LINEの応答
if (messages.length > 0){
replyMessage(replyToken, messages);
}
}
break;
}
}
}
/*
応答メッセージの作成
userId:ユーザID
event:LINEから受信したeventデータ
*/
function createReplyMessage(userId, event){
var messages = [];
//ユーザ情報の行番号を取得
var userRow = getUserDataRow(userId);
if (userRow == -1){
//ユーザ情報がない場合は、新しくユーザ情報を追加
userRow = addUserInfo(userId);
}
//ユーザ情報から処理状況を取得
var status = getUserStatus(userRow);
switch(status){
case "address": //住所確認中
var messageType = event.message.type;
if (messageType != "text" && messageType != "location"){
//メッセージがテキスト、位置情報以外ならば終了
return messages;
}
//ユーザ情報の検索条件クリア
clearSearchOption(userRow);
//ユーザ情報の検索結果クリア
clearSearchResult(userRow);
var location = new Object();
var searchStatus;
//テキスト受信
if (messageType == "text"){
//テキストメッセージ取得
var address = event.message.text;
//改行をスペースに変換
address = address.replace(/\r?\n/g, " ");
//ユーザ情報に検索住所を保存
setSearchAddress(userRow, address);
//【Geocoding API】住所→緯度・経度
var ret1 = getLocation(address);
if (typeof(ret1) == "object"){
location = ret1;
searchStatus = "OK";
} else {
searchStatus = ret1;
}
}
//位置情報受信
else if (messageType == "location") {
searchStatus = "LINE_LOCATION";
//LINEから送られてきた緯度・経度を取得
location.lat = event.message.latitude;
location.lng = event.message.longitude;
//LINEから送られてきた住所を取得
var address = event.message.address;
//ユーザ情報に検索住所を保存
setSearchAddress(userRow, address);
}
//位置情報取得成功
if (searchStatus == "OK" || searchStatus == "LINE_LOCATION"){
//キーワード確認メッセージ作成
var msg1 = confirmKeywordMessage();
messages.push(msg1);
//ユーザ情報に位置情報を保存
setSearchLocation(userRow, location.lat, location.lng);
//ユーザ情報の処理状況を「keyword」(キーワード確認中)に更新
setUserStatus(userRow, "keyword");
}
//検索結果ゼロ
else if (searchStatus == "ZERO_RESULTS"){
//住所検索結果ゼロのメッセージ作成
var msg1 = zeroAddressMessage();
messages.push(msg1);
}
//Google Maps APIのエラー
else {
//Google Maps APIのエラーメッセージ作成
var msg1 = googleErrorMessage("【Geocoding API】", searchStatus);
messages.push(msg1);
}
break;
case "keyword": //キーワード確認中
case "searched": //検索終了後
var messageType = event.message.type;
if (messageType != "text"){
//メッセージがテキスト以外ならば終了
return messages;
}
//ユーザ情報の検索結果クリア
clearSearchResult(userRow);
//テキストメッセージ取得
var keyword = event.message.text;
//改行をスペースに変換
keyword = keyword.replace(/\r?\n/g, " ");
//前後のスペースを除去
keyword = keyword.trim();
//「なし」は指定なしの意味なので、キーワードをクリア
if (keyword == "なし"){
keyword = "";
}
//ユーザ情報に検索キーワードを保存
setSearchKeyword(userRow, keyword);
//ユーザ情報から緯度・経度を取得
var location = getSearchLocation(userRow);
var placesList = new Array();
//【Places API】検索エリアのお店情報取得
var ret1 = getPlace(location.lat, location.lng, keyword);
if (typeof(ret1) == "object"){
placesList = ret1;
searchStatus = "OK";
} else {
searchStatus = ret1;
}
//お店情報取得成功
if (searchStatus == "OK"){
//検索結果を保存
setSearchResult(userRow, placesList);
//ユーザ情報の処理状況を「searched」(検索終了後)に更新
setUserStatus(userRow, "searched");
//検索完了メッセージ作成
var msg1 = searchFinishMessage(userRow);
messages.push(msg1);
//お店情報メッセージ作成
var msg2 = placesMessage(userRow);
messages.push(msg2);
}
//検索結果ゼロ
else if (searchStatus == "ZERO_RESULTS"){
//お店検索結果ゼロのメッセージ作成
var msg1 = zeroPlaceMessage(userRow);
messages.push(msg1);
//ユーザ情報の処理状況を「address」(住所確認中)に更新
setUserStatus(userRow, "address");
}
//Google Maps APIのエラー
else {
//Google Maps APIのエラーメッセージ作成
var msg1 = googleErrorMessage("【Places API】", searchStatus);
messages.push(msg1);
//ユーザ情報の処理状況を「address」(住所確認中)に更新
setUserStatus(userRow, "address");
}
break;
}
return messages;
}
/*
LINEの応答
replyToken:応答トークン
messages:応答メッセージ
*/
function replyMessage(replyToken, messages){
var replyURL = "https://api.line.me/v2/bot/message/reply";
UrlFetchApp.fetch(replyURL, {
"headers": {
"Content-Type": "application/json",
"Authorization": "Bearer " + ACCESS_TOKEN
},
"method": "post",
"payload": JSON.stringify({
"replyToken": replyToken,
"messages": messages
}),
});
}
/*
最初から検索するための処理
userId:ユーザID
*/
function gotoFirst(userId){
//ユーザ情報の行番号を取得
var userRow = getUserDataRow(userId);
if (userRow == -1){
//ユーザ情報がない場合は、新しくユーザ情報を追加
userRow = addUserInfo(userId);
}
//ユーザ情報の処理状況を「address」(住所確認中)に更新
setUserStatus(userRow, "address");
//ユーザ情報の検索条件クリア
clearSearchOption(userRow);
//ユーザ情報の検索結果クリア
clearSearchResult(userRow);
}
/*
次の結果表示
userId:ユーザID
*/
function nextResultsMessage(userId){
var messages = [];
//ユーザ情報の行番号を取得
var userRow = getUserDataRow(userId);
if (userRow == -1){
//ユーザ情報がない場合は、新しくユーザ情報を追加
userRow = addUserInfo(userId);
}
//ユーザ情報から処理状況を取得
var status = getUserStatus(userRow);
//検索終了後の場合
if (status == "searched"){
//お店情報メッセージ作成
var msg1 = placesMessage(userRow);
messages.push(msg1);
}
return messages;
}
/*==============================
LINEメッセージ作成
==============================*/
/*
ウェルカムメッセージ作成
*/
function welcomeMessage(){
var msg = {
"type": "text",
"text": "友だち登録ありがとうございます" + EMOJI_HAPPY +"\n\n"
+ "周辺のレストラン情報を、口コミランキング順に紹介いたします"
+ EMOJI_SHINY + EMOJI_SHINY + "\n\n"
+ "Powered by Google Maps API でございます" + EMOJI_MOON_GRIN
};
return msg;
}
/*
住所確認メッセージ作成
*/
function confirmAddressMessage(){
var msg = {
"type": "text",
"text": "それでは、お店を調べたい場所の「住所」を教えてね!\n\n"
+ "近くにある「ランドマーク」(目印となる場所)"
+ "の名前でもお調べしますよ。\n"
+ EMOJI_TRAIN + EMOJI_BUILDING + EMOJI_POSTOFFICE + EMOJI_HOSPITAL + EMOJI_SCHOOL + EMOJI_SPA + "\n\n"
+ "現在地の情報ならば、左下の+ボタンから「位置情報」を送信すると簡単かも" + EMOJI_OK
};
return msg;
}
/*
キーワード確認メッセージ作成
*/
function confirmKeywordMessage(){
var msg = {
"type" : "text",
"text" : "気になるお店のジャンルはあるかしら?\n\n"
+ "例えば「寿司」「ラーメン」「イタリアン」「タイ料理」とかで絞り込むことができるわ"
+ EMOJI_HAPPY + "\n\n"
+ "特にない場合は「なし」とつぶやいてね!"
};
return msg;
}
/*
住所検索結果ゼロのメッセージ作成
*/
function zeroAddressMessage(){
var msg = {
"type": "text",
"text": "ごめんなさい!\n"
+ "教えてくれた住所が見つからなかったの" + EMOJI_TIRED + "\n\n"
+ "もう一度、別の住所をお願いできるかしら" + EMOJI_MOON_GRIN
};
return msg;
}
/*
お店検索結果ゼロのメッセージ作成
row:ユーザ情報の対象行
*/
function zeroPlaceMessage(row){
//検索住所
var address = getSearchAddress(row);
//検索キーワード
var keyword = getSearchKeyword(row);
if (keyword == ""){
keyword = "指定なし";
}
var msg = {
"type": "text",
"text": "残念!\n"
+ "お店がみつからなかったわ" + EMOJI_TIRED + "\n\n"
+ "検索場所:" + address + "\n"
+ "ジャンル:" + keyword + "\n\n"
+ "他の場所とジャンルで探してみましょうか?\n"
+ "もう一度、住所から教えてね" + EMOJI_MOON_GRIN
};
return msg;
}
/*
Google Maps APIのエラーメッセージ作成
title:エラータイトル
status:エラーのステータス
*/
function googleErrorMessage(title, status){
var msg;
switch(status){
case "OVER_QUERY_LIMIT":
//利用制限超過
msg = {
"type": "text",
"text": "あらら。パワー切れみたい!\n\n"
+ "沢山の方からご利用いただいたので、本日はサービス終了です。\n"
+ "1日に利用できるリクエスト数が決まっているの" + EMOJI_TIRED + "\n\n"
+ "明日のご利用をお待ちしております" + EMOJI_MOON_GRIN
};
break;
default:
//その他のエラー
msg = {
"type": "text",
"text": "ガガガガガガガガ・・・ピー!\n\n"
+ "あわわわわ!システムエラーで固まっちゃった" + EMOJI_FROZEN + "\n\n"
+ "エンジニアさんに連絡して調べてもらうので、また後日ご利用くださいね" + EMOJI_TIRED
};
//エラーを管理者にメール通知
sendErrorMail(title + status);
break;
};
return msg;
}
/*
検索完了メッセージ作成
row:ユーザ情報の対象行
*/
function searchFinishMessage(row){
//検索住所
var address = getSearchAddress(row);
//検索キーワード
var keyword = getSearchKeyword(row);
if (keyword == ""){
keyword = "(指定なし)";
}
//検索件数
var resultCnt = getResultCnt(row);
var msg = {
"type": "text",
"text": "お待たせしました!検索結果が出ましたよ" + EMOJI_EIGHTHNOTE + "\n\n"
+ "検索場所:" + address + "\n"
+ "ジャンル:" + keyword + "\n\n"
+ "【検索結果】" + resultCnt + "件\n\n"
+ "口コミランキング順にお店を紹介します" + EMOJI_STAR + EMOJI_STAR + EMOJI_STAR + "\n\n"
+ "検索するジャンルを変更したい場合は、新しいジャンルをつぶやいてくださいね" + EMOJI_OK
};
return msg;
}
/*
お店情報メッセージ作成
row:ユーザ情報の対象行
*/
function placesMessage(row){
//検索件数
var resultCnt = getResultCnt(row);
//表示完了件数
var dispCnt = getDispCnt(row);
//検索結果を表示完了件数以降から5件分取得
var resultsList = new Array();
var startCol = 9 + dispCnt;
var values = sheet1.getRange(row, startCol, 1, 5).getValues();
for (var i = 0; i < values[0].length; i++){
var result = values[0][i];
if (result != ""){
resultsList.push(result);
}
}
//表示完了件数を更新
var updateCnt = dispCnt + 5;
if (updateCnt > resultCnt) updateCnt = resultCnt;
setDispCount(row, updateCnt);
//表示する検索結果分のメッセージを作成
var bubbleList = new Array();
for (var i = 0; i < resultsList.length; i++){
var result = resultsList[i].split("\n");
//評価
var rating = result[0];
var star = "";
if (isNaN(rating) == false){
rating = Math.round(rating * 10) / 10;
var cnt = Math.floor(Number(rating));
var star = new Array(cnt+1).join("★");
}
//名前
var name = result[1];
//周辺住所
var vicinity = result[2];
//写真ID
var photoID = "";
if (result.length > 3){
photoID = result[3];
}
//Flexメッセージに設定するBubbleメッセージを作成
var bubbleMsg = {
"type": "bubble",
"body": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "text",
"text": rating + " " + star,
"wrap": true,
"color": "#ff8c00"
},
{
"type": "text",
"text": name,
"wrap": true
},
{
"type": "text",
"text": vicinity,
"wrap": true,
"color": "#778899",
"margin": "xl"
}
]
},
"footer": {
"type": "box",
"layout": "horizontal",
"contents": [
{
"type": "button",
"action": {
"type": "uri",
"label": "詳細表示",
"uri": "https://maps.google.co.jp/maps?q="
+ encodeURIComponent(name + " " + vicinity)
+ "&z=15&iwloc=A"
}
},
{
"type": "button",
"action": {
"type": "postback",
"label": "最初から検索",
"data": "gotoFirst",
}
}
]
},
"styles": {
"footer": {
"backgroundColor": "#b0c4de",
"separator": true,
"separatorColor": "#c0c0c0"
}
}
};
//最初の5件は画像付き
if (dispCnt == 0){
var imgUrl = "";
if (photoID != ""){
imgUrl = getPhotoURL(photoID);
} else {
imgUrl = "https://www.delta-ss.com/labo/data/cmn/noimage.png";
}
var imgContent = {
"type": "image",
"url": imgUrl,
"size": "full",
"aspectRatio": "20:13",
"aspectMode": "cover",
};
bubbleMsg.hero = imgContent;
}
bubbleList.push(bubbleMsg);
}
//続きがある場合のBubbleメッセージを作成
var nextCnt = resultCnt - dispCnt - resultsList.length;
if (nextCnt > 0){
var bubbleMsg2 = {
"type": "bubble",
"body": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "text",
"text": "続きのお店情報があります!",
"wrap": true,
"color": "#c71585"
},
{
"type": "text",
"text": "【残り件数】" + nextCnt + "件",
"wrap": true,
"margin": "xl"
}
]
},
"footer": {
"type": "box",
"layout": "horizontal",
"contents": [
{
"type": "button",
"action": {
"type": "postback",
"label": "続きを見る",
"data": "nextResults",
}
}
]
},
"styles": {
"footer": {
"backgroundColor": "#e3adc1",
"separator": true,
"separatorColor": "#c0c0c0"
}
}
};
bubbleList.push(bubbleMsg2);
}
//Flexメッセージ作成
var flexMsg = {
"type": "flex",
"altText": "口コミランキング順お店情報",
"contents": {
"type": "carousel",
"contents": bubbleList
}
};
return flexMsg;
}