LINE messaging apiを利用してグループLINEへメッセージを送信する

## 前提
- 複数店舗を持つ業種が想定顧客
- 各店舗には別々のスタッフが在籍
- 店舗別、スタッフ別にその日の売上はGoogle formで統括部に報告
- フォームで報告された事項はスプレッドシートで管理

## クライアントが抱える課題
- フォーム内容を見て、統括部側で修正を加えたとしてもその報告が手間
- 計算ミスやフォームでの報告漏れがあったときに都度スタッフへ連絡を入れるのが手間
    - これまでは統括部の事務→上長→店舗スタッフへの報告の流れとなっていて、コミュニケーションコストがかかっていた

 ## 要件
 - 報告を受けた日、実際に業務を行った日、店舗情報などを定型分として連絡できる仕組みを作りたい
 - スプレッドシート内にボタンを設置し、ボタンクリックによってメッセージが通知されるようにしたい
 - 元のイメージはスタッフへのメール通知だったが、『メール』へこだわりがあるわけではないので通知できればなんでもOK


## 技術選定
- GAS
- Typescipt
- LINE Messaging API

はい、このような条件で、**GASを利用してグループLINEへのメッセージ通知bot**を作りました。
手順としては
1. スプレッドシートからデータを取得して加工しておく
1. messageing apiの有効化と公式LINEアカウントの発行
1. 公式LINEをグループLINEに追加することで、グループLINE IDを取得
1. 取得したグループLINEに対してメッセージの送信（push通知）ができるようにする

といった感じで考えていきました。
基本的にはGASを直接書くのではなく、Typescriptベースで書いてClasp使ってpushしています。
公式LINEでの通知は、別に公式LINEへの登録ユーザーじゃなくても、グループLINEへの発信もできるので便利ですね。

## ①スプレッドシートからのデータ加工
スプレッドシートからのデータ加工については、必要箇所のデータ取得という手順と日付によるフィルタリングとい手順に分けて作りました。

### 必要箇所のデータ取得（Typesciptで書いてclasp実行）
コードに書いてる『カラム○』は例えば日付とか報告カテゴリとか好きなものに置き換えてOK
```typescript:main.ts
function getDatum(sheetName: string): { dateUnixTime: number, カラムA: string; カラムB: string; カラムC: any; カラムD: any; }[] {
    const ss = SpreadsheetApp.getActiveSpreadsheet();
    const targetSheet = ss.getSheetByName(sheetName);

    const lastRow: number | undefined = targetSheet?.getRange(2,2).getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow();
    // console.log(lastRow);

    if(!lastRow || lastRow < 4) return [];
    const allData = targetSheet?.getRange(4, 2, lastRow-3, 4).getValues();

    return allData?.map((row) => ({
        "dateUnixTime": Date.parse(row[0])/1000,
        "カラムA": Utilities.formatDate(row[0],"JST", "yyyy/MM/dd"),
        "カラムB": Utilities.formatDate(row[1],"JST", "yyyy/MM/dd"),
        "カラムC": row[2],
        "カラムD": row[3],
    })) ?? [];
}
```

### 日付によるフィルタリング
```typescript:main.ts
function getFilterdData(){
    const sheetName = "シート1";
    // 今日のUnixタイムスタンプを取得
    const today = Math.floor(Date.now() / 1000);
    // 1週間前のUnixタイムスタンプを取得 (7日 * 24時間 * 60分 * 60秒)
    const oneWeekAgo = today - (7 * 24 * 60 * 60);
    
    // getDatumで取得したデータを一週間以内に絞り込む
    const data = getDatum(sheetName).filter(item => {
        return item.dateUnixTime >= oneWeekAgo && item.dateUnixTime <= today;
    });

    console.log(data);
    return data;
}
```
といった感じで、最終的には`data`オブジェクトとして返ってくるようにしました。

## API有効化と公式LINEアカウントの発行
[LINEデベロッパーコンソール](https://developers.line.biz/console/)から、新規プロバイダーを作成し、Messaging apiのチャンネルを作ります。
その流れで公式LINEアカウントを作るような画面になると思うので、手順にならって作成します。
コンソール画面の方では**webhookの有効化**と**グループトークへの参加をオン**にしておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/524273/98400fc6-b7f5-331d-e65b-78ceb2185d08.png)

## 公式LINEをグループLINEへ追加してIDを取得
作った公式LINEをグループLINEへ追加するのですが、この時に**webhookの参加イベント**が起こり、
>LINEプラットフォームからWebhook URL（ボットサーバー）にHTTPS POSTリクエストが送信されます。
となります。
なので、ポストリクエストを受け取るdoPost関数をGAS側で用意し、ウェブアプリとしてデプロイしたURLをwebhookURLとしてコンソール画面に貼り付ければ、必要な情報が取得できるなどカスタマイズが可能になります。
```typescript:main.ts
function doPost(e){
    const contents = JSON.parse(e.postData.contents);
    if(contents.events && contents.events.length > 0) {
        const event = contents.events[0];

        if(event.type === "join"){
            const groupId = event.source.groupId;
            console.log(groupId);
            //ここにID通知のためのコード
        }
    }

    return ContentService.createTextOutput(JSON.stringify({ status: "200" })).setMimeType(ContentService.MimeType.JSON);
}
```
グループIDがわかれば色々できるので、そのIDを取得するために私の場合はgmailで自分宛にメールを送信して確かめました。
このdoPost関数ができたタイミングでwebhookURLに追加し、公式LINEアカウントをグループLINEに招待しました。


## 任意のタイミングでメッセージ送信
ここまでできればあとはグループLINEへメッセージ送信する仕組みを作れば良いだけになります。
いろんなメッセージ送信方法がありますが、今回はスプレッドシートでボタンクリック時にメッセージを送信したいという目的だったので、push通知を選択しました。
```typescript:main.ts
// LINEでグループにpush通知するプログラム
const LINE_TOKEN = PropertiesService.getScriptProperties().getProperty("LINE_ACCESS_TOKEN");
const LINE_ENDPOINT = 'https://api.line.me/v2/bot/message/push';

function pushToLine(groupId: string, messages: any) {
    const today = Utilities.formatDate(new Date(), "JST", "yyyy年MM月dd日")

    const headers = {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${LINE_TOKEN}`
    };

    const postData = {
        'to': groupId,
        "messages": [
            {
                "type": "text",
                "text": `${today}までの連絡事項となります。\n${messages}\n以上となります。ご確認をお願いいたします。`
            },
        ]
    };

    const options: GoogleAppsScript.URL_Fetch.URLFetchRequestOptions = {
        method: "post" as GoogleAppsScript.URL_Fetch.HttpMethod,
        headers: headers,
        payload: JSON.stringify(postData)
    };
    UrlFetchApp.fetch(LINE_ENDPOINT, options);
}

function onButtonClick() {
    const groupId = PropertiesService.getScriptProperties().getProperty("GROUP_LINE_ID")!;
    const filteredData = getFilterdData();
    
    if (filteredData.length === 0) {
        return;
    }

    // データを整形してメッセージを作成
    const messages = filteredData.map((item, rowIndex) => {
        return `【${rowIndex+1}件目】\n` + 
        `カラムA: ${item.店舗名}\n` +
        `カラムB: ${item.記入日}\n` +
        `カラムC: ${item.施術業務日}\n` +
        `カラムD: ${item.連絡内容}`;
        }
    ).join("\n\n");

    pushToLine(groupId, messages);
}
// LINEでグループにpush通知するプログラムここまで
```

ここまでやって、スプレッドシートにボタンを作成し、スクリプトの割り当て→`onButtonClick`とすると
ボタンクリックにより1週間以内にフォーム報告のあった事項が通知されるようになります。
push通知のメッセージ内容は、オブジェクトで分かれてしまっていると通知件数が増えてしまうので、`join`メソッドを利用して一件にメッセージがまとまるように工夫しました。