概要
・サーバレスでLINE botを作りたい ・費用をかけずに作りたい ということで、Google Apps ScriptでLINE BOTを開発してみました。 この記事では、Line側の設定、LineBot本体の開発、外部公開までの手順を記載しています。
使用するサービス
- GoogleAppsScript
- JavaScriptベースとしている
- Googleアカウントが必要
- LINE Messaging API
- Lineアカウントが必要
開発の流れ
Line側の設定(Channel作成)
LINE Developer コンソールにアクセスし、ログインボタンをクリックして自身のLINEアカウントでログインします
はじめてログインしたときには開発者登録を求められますので必要事項を入力してください。(下記が入力済の様子)
次にプロバイダーを作成します。プロバイダーとはこれから作成するBotの提供元として表示される情報です。
必要情報を入力して進んでください。
次にChannelを作成します。ChannelにはLINEログインのほか三つが存在します。今回作成するのはBotなのでMessaging APIを選択してください。
こちらの画面で必要情報を入力していきます。
- アプリ名:任意アプリ名
- アプリ説明:任意の説明
- 大業種:任意選択
- 小業種:任意選択
- メールアドレス:任意のメールアドレス
これでChannelが作成されますがまだもう少し設定が必要です。「TutorialBot」をクリックします。
メッセージ送受信設定のセクションにあるアクセストークンの「再発行」ボタンをクリックします。
これでアクセストークンが発行されます。このトークンはMessaging APIの呼び出し時に必要になります。
あと残りの項目を下記の通り設定します。
これでChannelの設定はほぼ完了です。Bot本体をクラウドにデプロイしてからWebhook URLの設定では、後述します。 また、これからおこなうBot本体の開発でChannel Secretとアクセストークンが必要になりますので、メモしておいた方が良いです。
LineBot本体の開発
Googleドライブにログイン後、Googleスプレッドシートを選択。
そしたらエディタが開くのでここにbotの中身をガリガリ書いていきましょう。
下記より、今回開発したソースのコア部分を抜粋して掲載しています。
doPost関数
ユーザから発信したメッセージを受けて、処理を分岐させる役割を担う(コントロールに相当)
[do.Post.gs]
function doPost(e) { var json = e.postData.contents var events = JSON.parse(json).events; events.forEach(function(event) { if(event.type == "follow") { follow(event); } else if(event.type == "message") { var text = event.message.text; var userId = event.source.userId; var groupId = event.source.groupId; if (groupId !== '' && groupId !== undefined) { writeLog('groupid', groupId); return; } writeLog('userId', userId); writeLog('text', text); var userDataRow = -1 switch (text) { case '予約': // 削除用sheet内容クリア(キャンセルが終わらせなくても、予約の場合、キャンセルをやり直すようにする cancelsheet.getDataRange().clear(); replyText = '予約ですね!いつですか? 例:「6月12日 13:00ー14:00」を教えて下さいね!'; userDataRow = searchUserDataRow(userId); if (userDataRow === -1) { appendToSheet(userId); } // ユーザ毎絞り込みフラグが立てば削除する stopForRefineSch(userId); break; case '確認': reply_message(event); // ユーザ毎絞り込みフラグが立てば削除する stopForRefineSch(userId); break; case 'キャンセル': userDataRow = searchUserDataRow(userId); if (userDataRow !==-1) { replyText = cancel(userDataRow); } else { reply_message(event); } // ユーザ毎絞り込みフラグが立てば削除する stopForRefineSch(userId); break; case '日付で確認': reply_message(event); break; case '日付でキャンセル': reply_message(event); break; case '絞り込み条件検索_確認': reply_message(event); setUserDataForRefineSch(userId, '確認'); break; case '絞り込み条件検索_キャンセル': reply_message(event); setUserDataForRefineSch(userId, 'キャンセル'); break; case 'やめる': if (stopForRefineSch(userId)) { replyText = '絞り込み検索をやめました。'; } else { replyText = '絞り込み検索が始まりませんよ。「確認」で絞り込み検索を行えます。'; } break; case '検索': if (isRefineSchFromUserId(userId)) { if (isEmptyForKikan(getRefineSchCell(userId, 2).getValue(), getRefineSchCell(userId, 3).getValue())) { replyText = '検索「期間」を設定してください。(必須)'; } else { getRefineSchCell(userId, 2).getValue() replyText = doSearch(userId); // ユーザ毎絞り込みフラグが立てば削除する stopForRefineSch(userId); } } else { replyText = '検索条件が設定されません。「確認」から行って下さい。'; } break; case '使い方': break; default: var iscancel = getCancelCell(0).getValue() === 'cancel' ? true : false; var isRefineSch = isRefineSchFromUserId(userId); // 絞り込み検索 if (isRefineSch) { replyText = getRefineSchFromInput(text, userId); break; } // 予約キャンセル if (iscancel) { replyText = eventCancel(text, userId); break; } userDataRow = searchUserDataRow(userId); if (userDataRow === -1) { replyText = getMenuInfoMessage(); } else if (userDataRow === -2) { replyText = '申し訳ございません。\n予約操作を始めたが、一定時間登録が出来なかったため、再度「予約」から始めて下さい。'; } else { var todoDate = getDateStartCell(userDataRow).getValue(); var location = getLocationCell(userDataRow).getValue(); var registUser = getRegistUserCell(userDataRow).getValue(); if (todoDate === '') { replyText = getDateFromInput(text, userDataRow); // sendLineMessageFromReplyToken(event, replyText, false); } else if (location === ''){ replyText = getLocationFromInput(text, userDataRow); } else if (registUser === ''){ replyText = getRegistUserFromInput(text, userDataRow, event); } break; } } if (userDataRow === -1 || userDataRow === -2) { sendLineMessageFromReplyToken(event, replyText, false); return; } if (getRegistUserCell(userDataRow).getValue() !== '') { // 登録者指定までできていれば、カレンダーに登録 createEvents(userDataRow); // trueならば、”予約済み”の旨を伝える sendLineMessageFromReplyToken(event, replyText, true); if (isPushToGroup) { // 予約をグループに通知 sendLineMessageFromGroupId(getEventContextByTime(userDataRow)); } return; } sendLineMessageFromReplyToken(event, replyText, false); } else if (event.type == "postback") { // 日付を選択後、コールバック関数で値を受け取る post_back(event); } }); }
Botからの返答処理
ユーザ発信メッセージに応じた返答メッセージを返す
[sendLineMessageFromReplyToken.gs]
function sendLineMessageFromReplyToken(e, msg) { var message; message = { "replyToken" : e.replyToken, "messages" : [{"type": "text", "text" : msg}] }; var options = { "method" : "post", "headers" : headers, "payload" : JSON.stringify(message) }; UrlFetchApp.fetch(url, options); }
上記インスタンス変数「headers」、「url」が下記ように定義してある
[instance.gs]
// channel_token:Line設定側の「アクセストークン」を設定 var headers = { "Content-Type": "application/json; charset=UTF-8", "Authorization": "Bearer " + channel_token }; // 応答API var url = "https://api.line.me/v2/bot/message/reply"
グループへの発信処理
[sendLineMessageFromGroupId.gs]
function sendLineMessageFromGroupId(text) { // push APIを利用 var url = "https://api.line.me/v2/bot/message/push"; // groupIdを予め取得しておかないと var postData = { "to": groupID, "messages": [{ "type": "text", "text": text }] }; var options = { "method": "POST", "headers": headers, "payload": JSON.stringify(postData) }; return UrlFetchApp.fetch(url, options); }
Googleカレンダーへの登録処理
(ポイントのみ記載)
[createEvents.gs]
function createEvents(userDataRow) { try { var option = { description: registUser + '\n' + eventId, location: getLocationCell(userDataRow).getValue() } // googleカレンダーAPI取得 var calendar = CalendarApp.getDefaultCalendar(); calendar.createEvent(title, startTime, endTime, option); writeLog('createEvents', '成功'); } catch(e) { writeLog('createEvents', '失敗 ' + e); } }
定時アナウンストリガー設定方法
「現在のプロジェクトのトリガー」を選択
下図の通り、現在、日々9時に「setTriggerByDaily」関数を実行させるようなトリガーになっていますが、新規設定するには、「トリガー追加」をクリックします。
トリガー設定ダイアログ画面
上記トリガー設定することで、時間になったら、「setTriggerByDaily」関数が呼ばれるが、具体的に何時何分にメッセージをプッシュするか、という2ステップのトリガー設定が必要。何故ならば、上記「トリガー設定ダイアログ画面」にて、トリガー発火時間帯のみ選択できるためです。今回では[7時-8時]を設定しました。
[setTriggerByDaily.gs]
function setTriggerByDaily() { var triggerDay = new Date(); // sendEventToGroupByDailyを8:59分に実行させる triggerDay.setHours(8); triggerDay.setMinutes(50); ScriptApp.newTrigger("sendEventToGroupByDaily").timeBased().at(triggerDay).create(); writeLog('setTriggerByDaily', '成功'); }
Botに教えた登録内容をどこに記録するか
ウェブアプリ開発とは異なり、入力した内容を全てformというものに乗っけるため、コミット後、登録内容を一気に取得できます。しかし、Bot開発では、自然対話の中、正確と判断した内容をどこに記録しておかないと、登録まで(カレンダー登録)至りません。そのため、下記イメージ通り、GoogleScriptSheetというのを利用しています。
現在運用中Botでは、登録際に下記ような内容が書き込まれています。
その他
開発に関してのポイントが、上記述べたようになりますが、ニーズに応じてブラッシュアップしなければいかないのもありましが一旦割愛させて頂きます。
アプリケーション公開・利用方法
公開手順
スクリプトエディタのメニューから「公開」→「ウェブアプリケーションとして導入」を選択します。
すると、「ウェブアプリケーションとして導入」ウィンドウが開きます。
プロジェクトバージョン は、このウェブアプリケーションのバージョン管理をするためのものです。今回は最初ですから「新規作成」で入力内容は「最初のバージョン」などとしておけばよいです。
アプリケーションにアクセスできるユーザー で公開範囲を決めます。以下の通りですので、用途に合わせて使い分けて下さい。
- 自分だけ:自分のアカウントでログインしていればアクセスできます
- 全員:Googleアカウントでログインしていればアクセスできます
- 全員(匿名ユーザー含む):ログインせずにアクセスできます
最後に「導入」とすると、公開が完了します。ウェブアプリケーションのURLが生成されます。
LineBotとの繋ぎ込み
ウェブアプリケーションURLをコピーし、Line設定ページ「Webhook URL」に張り付けます。
これで、LineBotが利用可能になります。
参考文献
Line Botを開発するならGoogle Apps Script(GAS)が最強だった https://tanaken.me/posts/190320/
プラン・料金 LINE@のサービス運用に関連する各種プラン・費用についてご紹介 https://at.line.me/jp/plan
以上です。