初めまして、ponです。初めての記事執筆ですので、
お手柔らかにお願いします🙇♂️
今回は、GAS(Google Apps Script) を用いた、自動問い合わせLineBot を作成してみましたので、その手法についてまとめてみました。
初めての記事執筆・LBot作成となりますので、「こういった文章の方がわかりやすい」といったアドバイスや、「より効率的なプログラムがある」といったアドバイスがあれば是非お寄せいただければと思います。
※注意
初学者ゆえに、情報に誤りがある可能性があります。
引用される際には、自己責任でお願いいたします。
何故これを作ろうと思ったか
おいしいレストランは、様々な検索エンジン・インフルエンサーの投稿で紹介されています。そこから、インフルエンサーAとインフルエンサーBの投稿で同じお店が紹介されているなどで、情報が煩雑になってしまい、「渋谷駅周辺でおすすめのお店」を調べようにも、せっかく保存した投稿から探すのは手間がかかってしまいます。それをグーグルスプレッドシートに一旦まとめて、LINEBOTでリコメンデーションして欲しいと思い、作りました。
はじめに自動返信をつくる
こちらのサイトに詳細はまとまっておりますので、割愛させていただきます。
条件分岐でリコメンデーションを変化させる
今回は、エリア・駅のみで選択肢を絞っていくことを試みました。
エリアの絞り込み(ボタンテンプレート)
駅だけの情報では、絞り込みが難しく、リコメンデーションに不向きでしたので、
保存した投稿が多かった箇所ごとにまとめました。
今回は、エリアは4箇所しか表示できないボタンテンプレートを使用しましたが、勿論、4個しか表示できないものではなく、10個表示できる・カルーセルテンプレートを用いればより詳細にエリア選択を実施することもできます。
エリアは、東京都心(渋谷・新宿)、おしゃれなお店が多いと個人的に考えている(吉祥寺・下北沢、自由が丘・横浜(東急線沿線))とその他にしてみました。
(このエリア以外にもまとめたくなったら変更するかもです)
ボタンテンプレート部分のサンプルコード
function messageAction(userid) {
/* ボタンテンプレートメッセージを送る(message) */
UrlFetchApp.fetch('https://api.line.me/v2/bot/message/push', {
'headers': {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token, // スクリプトプロパティにトークンは事前に追加しておく
},
'method': 'POST',
'payload': JSON.stringify({
"to": userid, // スクリプトプロパティに送信先IDは事前に追加しておく
"messages": [{
"type": "template",
"altText": "message",
"template": {
"type": "buttons",
"thumbnailImageUrl": "", // 画像ファイルのURL
"title": "エリアからお店を探す",
"text": "以下より選択してください。",
"actions": [
{
"type": "message",
"label": "渋谷、新宿",
"text": "渋谷、新宿"
},
{
"type": "message",
"label": "下北沢、吉祥寺",
"text": "下北沢、吉祥寺"
},
{
"type": "message",
"label": "東急線線沿線",
"text": "東急線沿線"
},
{
"type": "message",
"label": "その他のエリア",
"text": "その他のエリア"
}]
}
}],
"notificationDisabled": false // trueだとユーザーに通知されない
}),
});
}
駅から絞る
上記のように、エリアからの絞り込みが完了したら次は駅単体で検索します。
ただ、最初から駅がわかっている場合はエリアから探す必要なく、直接LineBotに「渋谷」といったように入力すれば、リコメンデーションしてくれます。
駅から絞るのは13個まで表示できるクイックリプライを使用
クイックリプライ部分のサンプルコード
{
const postData2 = {
"to": userid, //対象ユーザーのUserId
"messages": [{
"type": "text",
"text": "駅名の候補をお送りします!",
"quickReply": {"items": tempStations2}
}]
};
const params = {
"methods": "POST",
"headers": HEADERS,
"payload": JSON.stringify(postData2)
};
UrlFetchApp.fetch(LINE_PUSH_ENDPOINT, params);
}
rich menuの作成
上記URL(LINE Official Account Manager)から作成できます。
- ホーム画面の「リッチメニュー」をクリックします。
- リッチメニューの「作成」をクリックします。
こちらを使用することで簡単にデザインすることができます。
欠点は、PC版のラインでは出来なさそうです。
感想
今回実装してみて、LineBotは簡単にできる点は大いに優れているものの、web検索のような多くの条件(今回の場合だと、エリア+ジャンル+予算で絞るなど)で検索をかけたい場合には入力が多くなってしまい、かえって使いにくくなる懸念があるなど一長一短あると感じました。
ただ、GASはpythonなど他言語と比べると、主流ではないかもしれませんが、
- googleの各種サービス(ドキュメント、スプレッドシート、フォーム、Gメール)だけでなく、slackやLine、teamsなどにも応用が効くこと
- 時間トリガーを容易に設定できるため、"自動で" 処理できること
など多くのメリット・可能性を感じる言語でした!
今後も新しい、業務効率化ツール・日常を便利にするツールを作成してみたいです
サンプルコード全文
const token = "発行したトークンを入力";
function doPost(e) {
// Webhookで取得したJSONデータをオブジェクト化し、取得
const eventData = JSON.parse(e.postData.contents).events[0];
//取得したデータから、応答用のトークンを取得
const replyToken = eventData.replyToken;
//取得したデータから、ユーザーが投稿したメッセージを取得
const userMessage = eventData.message.text;
//useridの取得
const userid = eventData.source.userId
// 応答メッセージ用のAPI URLを定義
const url = 'https://api.line.me/v2/bot/message/reply';
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sh = ss.getActiveSheet();
const restaurants = sh.getRange(1,1,sh.getLastRow(),1).getValues();
const stations = sh.getRange(1,2,sh.getLastRow(),1).getValues();
const areas = sh.getRange(1,3,sh.getLastRow(),1).getValues();
const genre = sh.getRange(1,4,sh.getLastRow(),1).getValues();
let recomend = [];
let tempAreas =[];
let tempStations = [];
let tempStations2 = [];
let alleRcomend = "";
//重複するものの削除の定義
function eliminateDuplicate(x, i, array){
return array.indexOf(x) == i;
};
if (userMessage == "エリアから探す"){
messageAction(userid);
}else if(userMessage == "渋谷、新宿"||userMessage == "下北沢、吉祥寺"||userMessage == "東急線沿線"||userMessage == "その他のエリア"){
const HEADERS = {
"Content-Type": "application/json; chrset=shift-jis",
"Authorization": "Bearer " + token
}
const LINE_PUSH_ENDPOINT = "https://api.line.me/v2/bot/message/push";
//エリアを入力する
for(let i = 0; i < areas.length; i++) {
tempAreas.push(areas[i][0]);
}
tempAreas = tempAreas.filter(eliminateDuplicate);//重複削除
for(let i = 0; i < tempAreas.length; i++) {
if (userMessage == tempAreas[i]){
for(let j = 1; j < stations.length; j++) {
if (areas[j][0] == userMessage){
tempStations.push(stations[j][0])
}
};
}
}
tempStations = tempStations.filter(eliminateDuplicate)//駅データの重複を削除
//tempStations2に紹介する駅ボタンを格納する
for(let i = 0; i < tempStations.length; i++) {
tempStations2.push({
"type": "action",
"action": {
"type": "message",
"label": tempStations[i].toString(),
"text": tempStations[i].toString()
}
})
};
if (tempStations2.length <= 13){
const postData2 = {
"to": userid, //対象ユーザーのUserId
"messages": [{
"type": "text",
"text": "駅名の候補をお送りします!",
"quickReply": {"items": tempStations2}
}]
};
const params = {
"methods": "POST",
"headers": HEADERS,
"payload": JSON.stringify(postData2)
};
UrlFetchApp.fetch(LINE_PUSH_ENDPOINT, params);
}else{
allRecomend = "該当エリアには駅が多く登録されています!\n一覧表示します\n\n" + tempStations.join("\n");
replyMessage(token,replyToken, allRecomend,url);
}
}else{
recomend.push([restaurants[0][0],genre[0][0]])
for (let i = 1; i < stations.length; i++) {
if (stations[i][0].includes(userMessage)){
recomend.push([restaurants[i][0],genre[i][0]])
}
}
//一次元配列recomend[i]の要素を": "で連結し、変数tempRecomendにpushする
let tempRecomend = [];
for(let i = 0; i < recomend.length; i++) {
tempRecomend.push(recomend[i].join(": "));
}
//一次元配列tempOfficeの要素を改行コード"\n"で連結する
allRecomend = tempRecomend.join("\n");
if (allRecomend == "お店名: ジャンル"){
allRecomend = userMessage+ "駅周辺のおすすめのお店は登録されておりません";
}else{
allRecomend = userMessage+ "駅周辺のおすすめのお店をご紹介します!\n" +allRecomend;
}
replyMessage(token,replyToken, allRecomend,url);
}
}
function replyMessage(token,replyToken, allRecomend,url){
const messages = [{ 'type': 'text', 'text': allRecomend}];
//APIリクエスト時にセットするペイロード値を設定する
const payload = {
'replyToken': replyToken,
'messages': messages
};
//HTTPSのPOST時のオプションパラメータを設定する
const options = {
'payload': JSON.stringify(payload),
'myamethod': 'POST',
'headers': { "Authorization": "Bearer " + token },
'contentType': 'application/json'
};
//LINE Messaging APIにリクエストし、ユーザーからの投稿に返答する
UrlFetchApp.fetch(url, options);
}
/*
ボタンテンプレートメッセージを送る(message)
———————————–*/
function messageAction(userid) {
/* スクリプトプロパティのオブジェクトを取得 */
/* ボタンテンプレートメッセージを送る(message) */
UrlFetchApp.fetch('https://api.line.me/v2/bot/message/push', {
'headers': {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token, // スクリプトプロパティにトークンは事前に追加しておく
},
'method': 'POST',
'payload': JSON.stringify({
"to": userid, // スクリプトプロパティに送信先IDは事前に追加しておく
"messages": [{
"type": "template",
"altText": "message",
"template": {
"type": "buttons",
"thumbnailImageUrl": "" // 画像ファイルのURLを入れ込む
"title": "エリアからお店を探す",
"text": "以下より選択してください。",
"actions": [
{
"type": "message",
"label": "渋谷、新宿",
"text": "渋谷、新宿"
},
{
"type": "message",
"label": "下北沢、吉祥寺",
"text": "下北沢、吉祥寺"
},
{
"type": "message",
"label": "東急線線沿線",
"text": "東急線沿線"
},
{
"type": "message",
"label": "その他のエリア",
"text": "その他のエリア"
}]
}
}],
"notificationDisabled": false // trueだとユーザーに通知されない
}),
});
}