🚀

GAS 用 LINE Messaging API ターミナル

2024/08/01に公開

目的

ウェブアプリケーションとしてデプロイされた 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 のアクセストークン

ライブラリをインポート

以下のライブラリを使用する.

GAS のコード

Logger.gs

BetterLog の基本設定を行う.

Logger.gs
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 の基本設定を行う.

LineClient.gs
const LineClient = (() => {
    const channelAccessToken = PropertiesService
        .getScriptProperties().getProperty("LINE_TOKEN");
    const _LineClient = new LineBotSDK.Client({ channelAccessToken });

    return _LineClient;
})();

Response.gs

POST リクエストを受け取ったときのレスポンスを生成する関数.

Response.gs
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 を送信するための関数.

sendPost.gs
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) の内容に応じて区別する.

  1. LINE Messaging API からのリクエスト
    リクエストデータに events というキーがある.
    ユーザからメッセージが送られてきたときに発生するので、post_table に指定された URL にその内容を POST する.
  2. GAS アプリケーションからのリクエスト
    上記以外. messages, to, replyToken というキーが含まれる.
    ユーザにメッセージを送信するときに発生するので、LINE Messaging API にメッセージ情報を POST する.
doPost.gs
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 には、messagesto または replyToken を含める. 例えば、以下の場合、Uxxx123abc という LINE User ID を持つ人に hello! というメッセージを送信する.

{
    "messages": [{
        "type": "text",
        "text": "hello!"
    }],
    "to": "Uxxx123abc"
}

LINE メッセージを受け取ったとき

SpreadSheet の post_table に定められた URL に内容を POST する.
例えば、Project1: こんにちは という LINE メッセージを受け取ったとき、commandProject1 に該当する URL に Project1: こんにちは というデータが送られる.

Discussion