GAS 用 LINE Messaging API ターミナル
目的
ウェブアプリケーションとしてデプロイされた GAS は、基本的に1つのプロジェクトにつきひとつの URL が払い出される. だが、ひとつの LINE Channel はひとつの Webhook URL しか設定できない. だから、複数の GAS アプリケーションから LINE メッセージを送ろうとしてもできない. そこで、GAS アプリケーションから LINE メッセージを送るためのリクエストを集約する、代表の GAS アプリケーションを作成する.
実装
構造
GoogleDrive/
├ LINENotification # Google Spread Sheet
└ LINENotification # GAS
Google Spread Sheet 作成
LINENotification という Google Spread Sheet ファイルを作成し、中に post_table というシートを作成. A1 から B1 に次の値を入力.
セル番地 | 値 | 概要 |
---|---|---|
A1 | command |
POST する URL の条件 |
B1 | url |
command を満たす LINE メッセージを受け取ったとき、POST する URL |
このシートでは、LINE ユーザからのメッセージを受け取ったとき、それをどの GAS アプリケーションに振り分けるかを指定することになる.
GAS セットアップ
スクリプトプロパティを設定
新しい GAS のファイルを作成し、以下のスクリプトプロパティを作成.
キー1: SPREAD_SHEET_ID
値1: 先ほど作ったスプレッドシートの ID
キー2: LINE_TOKEN
値2: LINE Channel のアクセストークン
ライブラリをインポート
以下のライブラリを使用する.
- BetterLog: https://github.com/peterherrmann/BetterLog
- SpreadSheetsSQL: https://github.com/roana0229/spreadsheets-sql
- LineBotSDK: https://github.com/kobanyan/line-bot-sdk-gas
GAS のコード
Logger.gs
BetterLog の基本設定を行う.
const Logger = (() => {
const ssId = PropertiesService
.getScriptProperties().getProperty('SPREAD_SHEET_ID');
// ログ出力先は SPREAD_SHEET_ID
const _logger = BetterLog.useSpreadsheet(ssId);
// ログのフォーマットを指定
_logger.DATE_TIME_LAYOUT = "yyyy-MM-dd kk:mm:ss.SS Z '[LINENotification]'";
return _logger;
})();
LineClient.gs
LineBotSDK の基本設定を行う.
const LineClient = (() => {
const channelAccessToken = PropertiesService
.getScriptProperties().getProperty("LINE_TOKEN");
const _LineClient = new LineBotSDK.Client({ channelAccessToken });
return _LineClient;
})();
Response.gs
POST リクエストを受け取ったときのレスポンスを生成する関数.
const Response = (() => {
const _json = (payload) => {
Logger.log('Send response message. Response Body: %s', payload);
const output = ContentService.createTextOutput()
.setMimeType(ContentService.MimeType.JSON)
.setContent(JSON.stringify(payload));
return output;
};
// 成功時のメソッド
const ok = (message) => _json({
statusCode: 200,
body: message,
});
// 失敗時のメソッド
const error = (statusCode, message) => _json({
statusCode: statusCode,
message: message
});
// ステータスコードだけを返すメソッド (LINE Webhook 検証用)
const statusCode = (code) => code;
return { ok, error, statusCode };
})();
sendPost.gs
POST を送信するための関数.
function sendPost(url, payload) {
const options = {
"method" : "POST",
headers: {
'Content-Type': 'application/json',
},
payload: JSON.stringify(payload),
};
return UrlFetchApp.fetch(url, options);
}
doPost.gs
POST リクエストを受け取ったときに起動する関数.
想定として、リクエストには二種類あり、それぞれを、イベントオブジェクト (e) の内容に応じて区別する.
- LINE Messaging API からのリクエスト
リクエストデータにevents
というキーがある.
ユーザからメッセージが送られてきたときに発生するので、post_table に指定された URL にその内容を POST する. - GAS アプリケーションからのリクエスト
上記以外.messages
,to
,replyToken
というキーが含まれる.
ユーザにメッセージを送信するときに発生するので、LINE Messaging API にメッセージ情報を POST する.
function doPost(e) {
try {
if (e === undefined) return;
Logger.log('POST Event: %s', JSON.stringify(e));
// Content-Type: application/json のみ受け付ける
if (e.postData.type !== 'application/json') return Response.error(400, 'Not an acceptable data format');
const contents = JSON.parse(e.postData.contents);
// 1. LINE Messaging API からのリクエスト
// そのコマンドに応じて post_table に従ったURLに振り分ける
if ('events' in contents) {
// Webhook 検証用 | ステータスコード 200 を送信
if (contents.events.length === 0) return Response.statusCode(200);
Logger.log('LINE Event:: %s', JSON.stringify(contents.events));
const ssId = PropertiesService
.getScriptProperties().getProperty("SPREAD_SHEET_ID");
const TableSQL = SpreadSheetsSQL.open(ssId, 'post_table');
const table = TableSQL.select(['command', 'url']).result();
const keys = table.map((obj) => obj.key);
const urls = table.map((obj) => obj.url);
// ユーザからのイベントごとに処理する
contents.events.forEach((event) => {
if (event.type !== 'message') return;
const { message, timestamp, source, replyToken } = event;
if (message.type !== 'text' || source.type !== 'user') return;
const { text } = message;
const { userId } = source;
Logger.log('Message Contents:: text: %s, timestamp: %s, userId: %s, replyToken: %s', text, timestamp, userId, replyToken);
// メッセージをコロンで分割して、最も左の文字列をコマンドとする
const commands = text.split(':');
if (commands.length !== 2) return;
const idx = keys.indexOf(commands[0].trim());
if (idx > -1) {
const url = urls[idx];
return sendPost(url, { message: text });
}
});
return;
}
// 2. GAS アプリケーションからのリクエスト
// messages と、to または replyToken が必須
if (!('messages' in contents)) return Response.error(400, 'Bad Request. "messages" is required');
if (!('to' in contents) && !('replyToken' in contents)) {
return Response.error(400, 'Bad Request. Either "to" or "replyToken" is required');
}
if ('to' in contents) {
LineClient.pushMessage(contents.to, JSON.parse(contents.messages));
}
if ('replyToken' in contents) {
LineClient.pushMessage(contents.replyToken, JSON.parse(contents.messages));
}
return Response.ok('Success! Message is sended.');
} catch (error) {
error = (typeof error === 'string') ? new Error(error) : error;
Logger.severe('%s: %s (line %s, file "%s"). Stack: "%s".',
error.name||'', error.message||'', error.lineNumber||'',
error.fileName||'', error.stack||'');
return Response.error(500, 'Internal Server Error');
}
}
使い方
この GAS プロジェクトを ウェブアプリケーションとしてデプロイして、準備完了.
LINE メッセージを送るとき
この GAS プロジェクトの URL に対して POST リクエストを送る. Content-Type は application/json
で、body には、messages
と to
または replyToken
を含める. 例えば、以下の場合、Uxxx123abc という LINE User ID を持つ人に hello! というメッセージを送信する.
{
"messages": [{
"type": "text",
"text": "hello!"
}],
"to": "Uxxx123abc"
}
LINE メッセージを受け取ったとき
SpreadSheet の post_table に定められた URL に内容を POST する.
例えば、Project1: こんにちは
という LINE メッセージを受け取ったとき、command
が Project1
に該当する URL に Project1: こんにちは
というデータが送られる.
Discussion