はじめに
こんにちは、 今回始めてAdvent Calendarに参加します @nakaaza です!
よろしくおねがいします。
さて、LINE公式アカウントはコードを書かずにOfficial Account Managerからメッセージ配信や自動応答設定をできるので大変便利です。ただ、Official Account Managerだけでは、友だちからのメッセージへの自動返信(botモード)と個別返信(チャットモード)の両立というのは僕の知る限りできません。
ただ、LINEはメッセージ操作をするAPI(LINE messaging API)が充実しているので、これを活用すれば自動返信と個別返信の両立が可能です!
なにか単発のイベントや、そこまでがっつりシステム大規模でないサービスにおけるやりとりであれば、Google Apps Script(GAS)でちゃちゃっとそんな仕組みを構築できます!
今回はそんな仕組みを紹介します。
システムの概要
要件
- 友だちからの特定のメッセージに対して自動返信できる
- 友だちからのメッセージに対して、アカウントの管理者が手動で個別の内容で返信できる
- アカウントの管理者に友だちからのメッセージ受信を公式アカウントから通知する
- アカウントの管理者が指定の形式で公式アカウントにメッセージ送信することで、該当の友だちにメッセージ返信できる
ざっくり仕組み
自動返信
手動返信
実装
LINEチャネルの設定
-
LINE Developers Consoleの「新規チャネル作成」から
Messaging API
を選択して新規チャネルを作成します(LINE Developersの登録等は割愛)。 - 「Messging API設定」>「チャンネルアクセストークン」からアクセストークンを発行します。
Spreadsheetの設定
新規Spreadsheetに以下のシートを作成します。
- line_user_id
、name
カラムを持つusers
シート
- line_user_id
、name
カラムを持つadmin
シート
- id
、sent_at
、line_user_id
、sender
、content
カラムを持つmessages
シート
コード
GASとLINE公式アカウントを連携
1.上記で作成したSpreadsheetの「ツール」>「スクリプトエディタ」からGASプロジェクトを作成します。
2.コード.gs
に上記で取得したLINE公式アカウントのアクセストークンを記述します。
var ACCESS_TOKEN = '{LINE公式アカウントのアクセストークン}';
3.「公開」>「ウェブアプリケーションとして導入」からWho has access to the app
をAnyone, even anonymous
としてデプロイします(この設定をしないとLINEからのwebhook送信を受け付けることができません)。
4.デプロイ完了後に表示されるCurrent web app URL
に記載のURL(https://script.google.com/macros/s/hogehoge
)を、LINE Developers Consoleの「Messaging API」>「Webhook URL」に指定します。「検証」をクリックして画像のように成功メッセージが表示されれば連携完了です!このとき「Webhookの利用」をONにすることをお忘れなく。
友だち追加時の処理
LINE公式アカウントに対して何らかのアクションが行われると、上記で設定したWebhook URL
に対してデータがPOST
され、GAS側で処理できるようになります。
GASプロジェクトに対するPOST
リクエストを受けるためには、doPost(e)
という関数を定義します。e
はリクエストに含まれるデータで、それを基に処理していきます。
※ ウェブアプリケーションとして公開したGASプロジェクトは、都度バージョンを上げて再デプロイしないと、ウェブアプリケーションに最新のコードが反映されません。面倒ですが、編集して挙動を確認する際には毎回デプロイしましょう。
function doPost(e) {
// Webhookから受け取ったデータからユーザ情報(line_user_id, アカウント名)を取得します
var event = JSON.parse(e.postData.contents).events[0]; // eventsはWebhookイベントの配列ですが、経験上複数個送られてくることはないので、0番目ののみ取得すれば十分です。
var line_user_id = event.source.userId;
var display_name = getDisplayName(line_user_id);
var sheet_user = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('users');
if(event.type === "follow"){ // "follow"はLINE友だち追加を示すイベントです
var last_row = sheet_user.getLastRow();
// 初回友だち追加時のみ、ユーザデータをusersシートに挿入(ブロック解除も`follow`イベントのため)
if(last_row > 1){
var users = sheet_user.getRange(2,1,last_row - 1, 1).getValues().map(e => e[0]);
} else {
var users = [];
}
if(!users.includes(line_user_id)){
sheet_user.getRange(last_row + 1, 1, 1, 2).setValues([[line_user_id, display_name]]);
}
}
}
// LINEユーザのアカウント名を取得するためにLINE Messaging APIを叩きます
function getDisplayName(line_user_id){
var url = 'https://api.line.me/v2/bot/profile/' + line_user_id;
var res = UrlFetchApp.fetch(url, {
'headers': {
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Bearer ' + ACCESS_TOKEN,
},
'method': 'get'
});
return JSON.parse(res).displayName;
}
ここまできたら、LINE Developers ConsoleにあるQRコードから公式アカウントと友だちになります。
users
シートにアカウント情報が追加されているはずです!
そして、アカウントの管理者にするユーザが友だちになったら、そのユーザ情報をusers
からコピーしてadmin
シートに貼り付けます(複数人設定可)。メッセージの通知機能や手動返信機能はこのadmin
シートに記載のあるユーザのみ可能になります。
自動返信機能
以下のような感じで、予め指定したメッセージに対しては決まった文言で返信できるようにします。
function doPost(e){
// 中略
if(event.type === "follow"){
// 中略
} else if(event.type === "message"){
if(event.message.type === "text"){
var text = event.message.text;
switch(text){
case "サウナ":
var reply_token = event.replyToken;
var messages = [{ "type": "text", "text": "ととのう" }];
sendReplyMessage(reply_token, messages);
case "銭湯":
var reply_token = event.replyToken;
var messages = [{ "type": "text", "text": "あったかい" }];
sendReplyMessage(reply_token, messages);
}
}
}
}
// あるメッセージに返信するReply APIを叩きます。Webhookイベントには必ず`replyToken`が設定されていて、それをReply APIにわたすと、即時返信ができるようになります
function sendReplyMessage(reply_token, messages){
var url = 'https://api.line.me/v2/bot/message/reply';
var res = UrlFetchApp.fetch(url, {
'headers': {
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Bearer ' + ACCESS_TOKEN,
},
'method': 'post',
'payload': JSON.stringify({
'replyToken': reply_token,
'messages': messages
}),
});
return res;
}
手動返信機能
受信したメッセージを保存し、管理者に通知する
function doPost(e) {
if(event.type === "follow"){
// 中略
} else if(event.type === "message"){
if(event.message.type === "text"){
var text = event.message.text;
switch(text){
case "サウナ":
// 中略
case "銭湯":
// 中略
default: // 自動応答キーワード以外のメッセージを管理者に通知します
// 受信したメッセージを`messages`シートに保存
var sheet_messages = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('messages');
var now = new Date();
var id = sheet_messages.getLastRow();
sheet_messages.getRange(id + 1, 1, 1, 5).setValues([[id, now, line_user_id, display_name, text]]);
// 管理者に受信したメッセージを通知
var message = "========================\nメッセージID: " + id + "\n" + display_name + "さんからのメッセージ\n========================\n" + text;
var messages = [{ "type": "text", "text": message }];
sendToAdmin(messages);
}
}
}
}
// 特定したユーザへメッセージを送信するPush APIを叩きます
function sendPushMessage(line_user_id, messages){
var url = 'https://api.line.me/v2/bot/message/push';
var res = UrlFetchApp.fetch(url, {
'headers': {
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Bearer ' + ACCESS_TOKEN,
},
'method': 'post',
'payload': JSON.stringify({
'to': line_user_id,
'messages': messages
}),
});
return res;
}
// 管理者を抽出します
function getAdmin(){
var sheet_admin = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('admin');
var admin = sheet_admin.getRange(2, 1, sheet_admin.getLastRow() - 1, 1).getValues().map(a => a[0]);
return admin;
}
// 管理者全員にメッセージを送信します
function sendToAdmin(messages){
var admin = getAdmin();
for(var i=0; i<admin.length; i++){
sendPushMessage(admin[i], messages);
}
}
実際にやってみるとこんな感じです。
ユーザからメッセージが届くと、messages
シートにデータが保存され…
管理者に公式アカウントから通知メッセージが届きました!
特定のメッセージに対して管理者から個別返信する
このシステムでは管理者もユーザと同じくLINEアプリの公式アカウントをインターフェースとして使います。そのためあるメッセージに個別に返信するためには、何らかの形で「どのメッセージに返信するのか」を指定しなければいけません。
今回は、メッセージを受信した際に一意のIDを割り当てたので、それを用いることとしました。
具体的には、返信メッセージの先頭行にメッセージID(半角数字)を記入し、2行目以降に返信内容を記述することとしました。
function doPost(e) {
if(event.type === "follow"){
// 中略
} else if(event.type === "message"){
if(event.message.type === "text"){
var text = event.message.text;
switch(text){
case "サウナ":
// 中略
case "銭湯":
// 中略
default:
var sheet_messages = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('messages');
var admin = getAdmin();
var message_id = text.split("\n")[0]; // メッセージを行ごとに分割して、配列に格納します
if(admin.includes(line_user_id) && isNumber(message_id)){ // 「メッセージの送信者が管理者 かつ メッセージの1行目が数値である」ときは管理者からの返信メッセージと判定します
var message_to_reply = find_message_by_id(message_id, sheet_messages);
var reply_message = text.split("\n").slice(1).join("\n"); // 実際の返信メッセージには1行目のメッセージIDは不要なので削除します
sendPushMessage(messege_to_reply[2], [{ "type": "text", "text": reply_message }]); // 返信先のユーザに、管理者からのメッセージを送信します
} else { // 返信でないメッセージを処理します。前述の通りです
// 受信したメッセージを`messages`シートに保存
// 中略
// 管理者に受信したメッセージを通知
// 中略
}
}
}
}
// 引数の値が数値であるかを判定します。こちらの記事を参考にさせていただきました→ https://chaika.hatenablog.com/entry/2019/12/18/083000
function isNumber(n){
var type = typeof(n);
if ( type === 'number' && Number.isFinite(n) ) {
return true;
}
if ( type === 'string' && n.trim() !== '' && Number.isFinite(n - 0) ) {
return true;
}
return false;
};
function find_message_by_id(id, sheet_messages){
var messages = sheet_messages.getDataRange().getValues(); // 保存されたメッセージ全体を取得します
// 指定したIDを持つメッセージを抽出します
for(var i=1; i<messages.length; i++){
if(messages[i][0] == id){
console.log(messages[i]);
return messages[i];
}
}
return null;
}
LINE Messaging APIのメッセージ送信メソッドには大別して2種類あります。ひとつはそれぞれのWebhookイベントに紐づくreplyToken
を使ったReply API
、もう一つは送信先のユーザを直接指定するPush API
です。前者は自動応答、後者は個別返信と両者を使い分けることで、Offcial Account Managerだけではできなかった2種類のメッセージ送信方法の両立を可能にしています。
おわりに
LINE botをGASで作ることのメリットとしては、今回の自動/手動返信の両立などOfficial Account Managerだけでは実現できない機能をもたせることができる点、環境構築が要らないので気軽にササッとできる点があります。
GASはどちらかというとGoogleの各サービスをインタラクティブに、そして拡張性高く操作するためのツールとしての印象がありますが、今回のように外部サービスと連携することでさらにその活用の幅が広がります!
今回のシステムはまだまだ中途半端な部分も多いですが、どなたかの参考になれば嬉しい限りです.
Advent Calendar当日になって慌てて書いたのでやや雑な投稿となってしまいしたが、なにかご意見・ご質問あればコメントをください
それではみなさま、よいGASライフをお過ごしください