GmailをLINE Notifyを使用してLINEに転送する

## やりたいこと
Gmailで特定のラベルがついたメールを、自動でLINEに転送して通知させたい

## きっかけ
家族と一緒に生活している中で、水道代や電気代のお知らせメールなどを私が一元管理していたのですが、重要なメールなども基本的には家族内で共有しておきたいと思い、自動でGmailに転送したら楽だなと思ったためです

みんなのためにも「これ知らないんだけど！！」が起こりづらい環境構築をしておきましょう。

## 使用するもの
- Google Apps Script(GAS)
- LINE Notifiy API
- Gmail
- 上記に必要なアカウント

### LINE Notify API
LINEが提供している通知をしてくれるサービス
https://notify-bot.line.me/ja/
LINE Messaging API(いわゆるbot)も考えたのですが通知だけなのでLINE Notify APIで十分かと思います

### Google App Script
通称GAS
Microsoft OfficeでいうVBAのようなもの
スプレッドシートやGmailなど、さまざまなGoogleサービスを連携して使用することができる
Webスクレイピングもできる模様

## 事前準備
アカウントの作成は割愛

### GmailからLINEに通知するラベルを作成
転送するメールを絞るために転送するメールにラベルをつけておきます
今回は自分で管理するように既にラベルをつけていて`家`というラベルにしています
あと、実装方法にもよりますが転送済みのメールを再度転送させないために`通知済み`ラベルも作成しておきます

### Gmailで転送したいメールのラベルに自動振り分け
転送したメールをわざわざ手動でラベルつけていたら自動化の意味がないので、自動で振り分けしてもらえるようにGmailで設定しておきます
検索からフィルタを作るでも良いと思います
自分は水道や電気などのメールのドメインを`OR`でフィルタをかけて、ヒットしたものにはラベルをつけています
![スクリーンショット 2022-11-06 16.33.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2064382/5371e090-5d3f-e855-84f0-f8b517916b41.png)

### LINE Notify APIトークンの発行
1. [LINE Notify API](https://notify-bot.line.me/ja/)にアクセスしてログイン
1. マイページから`トークンを発行する`からトークンの作成
![スクリーンショット 2022-11-06 16.13.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2064382/d98bd650-de20-61fa-0ef0-5021eb69996b.png)
1. トークン名とトークルームを選択
今回はグループで送りたかったのでLINEでグループを作成しておいてそれを設定しました
![スクリーンショット 2022-11-06 16.15.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2064382/1060ec08-4af4-da94-13c2-9c9b2af5e3b5.png)
1. 発行されたトークンを控える

:::note alert
トークンは絶対に公開しないでください
公開すると知らない人にメッセージを送られる危険があります
:::
:::note warn
トークンは再度表示できません
忘れないように他の人がわからないところに保管しておいてください
:::

## 実装
GASでの実装になります
自分もGAS自体は初めてだったのですがJavascriptはかじっていたので難しくはなかったです

スクリプトファイルの生成はいくつかあるみたいですが
Googleドライブから`新規` > `その他` > `Google Apps Script`がわかりやすい気がしました
![スクリーンショット 2022-11-06 16.38.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2064382/22a9bed4-ca2f-7d7a-b8e3-0c7849d801bc.png)

Editorが立ち上がるのでそこに書いていきます
今回書いたプログラム
``` js:コード.gs
const HOME_LABEL = '家';
const READ_LABEL = '通知済み';
const BODY_MAX_LENGTH = 300;

function main() {
  const newMessages = fetchHomeMail();
  newMessages.forEach(message => {
    sendLine(message);
  });
}

/** LINEヘ送信 */
function sendLine(message) {
  const lineToken = PropertiesService.getScriptProperties().getProperty('LINE_TOKEN');
  const payload = { 'message': message };
  const options = {
    "method": "post",
    "headers": { "Authorization": "Bearer " + lineToken },
    "payload": payload
  };

  UrlFetchApp.fetch("https://notify-api.line.me/api/notify", options);
}

/** メールを取得 */
function fetchHomeMail() {
  const sendLabel = GmailApp.getUserLabelByName(READ_LABEL);
  const searchTerms = Utilities.formatString("label:%s -label:%s", HOME_LABEL, READ_LABEL);

  //取得
  const fetchedThreads = GmailApp.search(searchTerms);
  const fetchedMessages = GmailApp.getMessagesForThreads(fetchedThreads);
  const sendMessages = [];

  // 送信メッセージ配列に詰める
  fetchedMessages.forEach(message => {
    sendMessages.push(gmailToString(message[message.length - 1]));
  });

  // 通知済みラベルを追加
  fetchedThreads.forEach(thread => {
    thread.addLabel(sendLabel);
  });

  // LINE用に配列の順番を反転させる
  return sendMessages.reverse();
}

/** メールの整形 */
function gmailToString(mail) {
  const mailFrom = mail.getFrom();
  const subject = mail.getSubject();
  const body = mail.getPlainBody().slice(0, BODY_MAX_LENGTH);

  return Utilities.formatString("\n%s\n\n件名：\n%s\n\n内容：\n%s", mailFrom, subject, body);
}

```

わりと見たまんまな気がします
`sendLine(message)`の引数に`lineToken`を追加するとか、`fetchHomeMail()`の引数に検索条件を渡すのもありかと思います

### メール取得
メールの取得してLINEに送信するメッセージを返しています

↓通知済みのラベルを定義
``` js
const sendLabel = GmailApp.getUserLabelByName(READ_LABEL);
```

↓転送したいメールの検索条件
``` js
const searchTerms = Utilities.formatString("label:%s -label:%s", HOME_LABEL, READ_LABEL);
```
今回は `label:家 -label:通知済み`にして`通知済み`になっていない`家`のラベルがついたメールを検索しています

自分はGmail上でも確認したくて未読/既読状態を保持するためにラベルを使用しましたが、未読なら未転送の判定にして、転送したメールは既読にする方法もありかなと思います

↓メールを取得して転送メッセージに変換
``` js
//取得
const fetchedThreads = GmailApp.search(searchTerms);
const fetchedMessages = GmailApp.getMessagesForThreads(fetchedThreads);
const sendMessages = [];

// 送信メッセージ配列に詰める
fetchedMessages.forEach(message => {
  sendMessages.push(gmailToString(message[message.length - 1]));
});
```
~~message[0]`なのは、返信なども含めてスレッドになっているのですが、今回は基本お知らせメールを転送したいのでfirstのみを詰めています~~
全部渡したい場合はさらにforEachとか必要になると思います

:::note warn
2024/06/05 更新
配列最後のもの(`message[message.length - 1]`)が最新のメッセージになるので変更しました。
:::

``` js
  // 通知済みラベルを追加
  fetchedThreads.forEach(thread => {
    thread.addLabel(sendLabel);
  });

  // LINE用に配列の順番を反転させる
  return sendMessages.reverse();
}
```
あとは通知済みラベルを付与して再度転送しないようにする処理と
LINEは古いメッセージが上、新しいメッセージが下なので、配列の順序を反転させています

### LINEヘ送信
LINEヘ送信する文字列を受け取ってLINEへ送信しています

先ほど取得したAPIトークンを渡しています
``` js
const lineToken = PropertiesService.getScriptProperties().getProperty('LINE_TOKEN');
```
こちらはプロジェクトプロパティにトークンを格納していて、そこから取得しています
`プロジェクトの設定`からスクリプト プロパティで設定ができます
![スクリーンショット 2022-11-06 17.13.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2064382/f648c6a3-d1aa-3603-d78a-8d2cecb8d255.png)
別にセキュアな保管ではないのですがトークンやAPIキーはハードコーディング避けたいのでこれを活用しました

:::note info
機密情報であれば閲覧範囲が決められているスプレッドシートに保存してそこらか取得するのが良さそうです
:::

あとはmainでメールを取得する関数を実行して、forEachでLINEに送信しています

## 定期実行
プログラムができたのであとは定期実行をさせるだけです
1. `トリガー`から`トリガーを追加`
1. 実行したい関数を選択(今回では`main`)
1. トリガーを今回は`時間手動型`にして`分ベース`、`15分`おきにしました
メールの取得頻度はこれくらいで十分かと思います
![スクリーンショット 2022-11-06 17.36.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2064382/483224ee-48f6-0f97-fe9b-631ea3f03d24.png)

これで15分おきにメールを取得して、未転送のメールはLINEに通知してくれるようになりました

## 所感
GASを始めて触りましたがほぼJavascriptだったので簡単でした
ネットの情報も結構転がっているので、もっと調べれば他のことにもできそう