Google Apps Script を使って Gmail を Slack に投稿する

# はじめに

この記事は、[Supershipグループ Advent Calendar 2022](https://qiita.com/advent-calendar/2022/supership)の5日目の記事になります。

とても簡単な内容ですが、ご一読いただければ幸いです。
本文では、Gmailのアクセス方法・Slackの投稿方法を記載しています。
また、このスクリプトを実装して気づいた点を末尾に記載しております。

# Google Apps Script (GAS) で Gamil から情報を取得する

まず、[Apps Script](https://script.google.com/home) へアクセスし、新しいプロジェクトを作成します。
次にGmailをGASで利用可能にするためサービスを追加します。
サービスの `+` ボタンを押して、 **Gmail API v1** を追加します。
![サービスを追加](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30241/168e6943-f28b-32cb-85e6-c261ac0f3730.png)

Gmailへのアクセスリクエストが入るので許可を押します。
![Gmailへのアクセスリクエスト](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30241/319ae431-05b0-9579-a49f-7f38c332f238.png)
サービスの追加完了後、`コード.gs` に以下のコードを貼り付けます。

```js
function receiveMail() {
  const query = "重要";
  const threads = GmailApp.search(query, 0, 100).reverse();  
  for (var thread of threads) {
    for (var msg of thread.getMessages()) {
      Logger.log(`${msg.getDate()} ${msg.getSubject()} ${msg.getPlainBody()}`);
    }
  }
}
```

最後に**実行**ボタンを押すとログに日時・タイトル・本文が表示されます。
![Gmailログ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30241/e64ece45-43be-8bce-800f-88f994a40832.png)


これでGmailの取得が可能になりました。

# Google Apps Script (GAS) で Slack に投稿する

まずは[Slack](https://api.slack.com/)にてアプリを作成します。
詳しい方法は別途Qiitaで記事を検索するなりしてください。
そこで得られたアクセストークンを利用します。
先程のプロジェクトに以下のスクリプトを追加します。

```js
// Slack API
var AUTH_TOKEN = "xoxp-...";
var SLACK_URL_ROOMS_MESSAGE = "https://slack.com/api/chat.postMessage";

// Slack にメッセージを投稿
function chatPostMessage(text) {
  var payload = {
    'token'      : AUTH_TOKEN,
    'channel'    : "#general",
    'parse'      : 'none',
    'username'   : 'Gmailくん',
    'icon_emoji' : ':icon_name:',
    'text': text
  };  
  var params = {
    'method' : "post",
    'payload' : payload
  };
  var response = UrlFetchApp.fetch(SLACK_URL_ROOMS_MESSAGE, params);
}
```

Slack用のAPIが用意されているわけではないのでGASにある汎用APIを利用します。
`UrlFetchApp` を利用することで他のアプリケーションと通信したり、ウェブ上の他のリソースにアクセスしたりできます。
これで外部へのデータの送信が可能になりました。
今回は送信したいデータを `chatPostMessage(text)` に渡してあげるだけでSlackに投稿できます。

# 一度取得したメールを再度取得しないようにする

同じメールが何度も処理されるのを防ぐ方法を記載します。

処理した日時を保存しておけば、次の処理時にその日時以降のメールだけを処理できます。
どこに保存するかの問題ですが、GASの機能の1つであるスクリプトプロパティを利用します。

以下、コードになります。

```js
const LATEST_CHECK_MAIL_DATE = 'LATEST_CHECK_MAIL_DATE';

// メールの更新をチェックした日時
function saveLatestCheckMailDate() {
  const scriptProperties = PropertiesService.getScriptProperties();
  scriptProperties.setProperty(LATEST_CHECK_MAIL_DATE, new Date());
}
function getLatestCheckMailDate() {
  const scriptProperties = PropertiesService.getScriptProperties();
  return new Date(scriptProperties.getProperty(LATEST_CHECK_MAIL_DATE));
}

function receiveMail() {
  const query = "重要";
  const threads = GmailApp.search(query, 0, 100).reverse();  
  const latestCheckMailDate = getLatestCheckMailDate();
  for (var thread of threads) {
    for (var msg of thread.getMessages()) {
　　　　　　　　　　　　// 日時の判定
      if (latestCheckMailDate) {
        if (latestCheckMailDate.getTime() > msg.getDate().getTime()) {
          return;
        }
      }        
      Logger.log(`${msg.getDate()} ${msg.getSubject()} ${msg.getPlainBody()}`);
    }
  }
  saveLatestCheckMailDate();
}
```

先程のSlackのアクセストークンもスクリプトプロパティに保存しておくと良いかもしれません。

# コード全体

実際に試したコードを掲載しておきます。

走らせる場合は**実行する関数**を`receiveMail`にして下さい。

```js
const LATEST_CHECK_MAIL_DATE = 'LATEST_CHECK_MAIL_DATE';
// メールの更新をチェックした日時
function saveLatestCheckMailDate() {
  const scriptProperties = PropertiesService.getScriptProperties();
  scriptProperties.setProperty(LATEST_CHECK_MAIL_DATE, new Date());
}
function getLatestCheckMailDate() {
  const scriptProperties = PropertiesService.getScriptProperties();
  return new Date(scriptProperties.getProperty(LATEST_CHECK_MAIL_DATE));
}

var AUTH_TOKEN = "xoxp-...";
var SLACK_URL_ROOMS_MESSAGE = "https://slack.com/api/chat.postMessage";
// Slack にメッセージを投稿
function chatPostMessage(text) {
  var payload = {
    'token'      : AUTH_TOKEN,
    'channel'    : "#general",
    'parse'      : 'none',
    'username'   : 'Gmailくん',
    'icon_emoji' : ':icon_name:',
    'text': text
  };  
  var params = {
    'method' : "post",
    'payload' : payload
  };
  var response = UrlFetchApp.fetch(SLACK_URL_ROOMS_MESSAGE, params);
}

// メールの受信処理
function receiveMail() {
  const query = "{取得したいメールのクエリ}";
  const threads = GmailApp.search(query, 0, 100).reverse();  
  for (var thread of threads) {
    for (var msg of thread.getMessages()) {
      sendMessageChangeAssign(msg.getSubject(), msg.getPlainBody(), msg.getDate());
    }
  }
  saveLatestCheckMailDate();
}

// メール情報からアサイン情報をチェックしてSlackに投稿
function sendMessageChangeAssign(subject, plainBody, date) {
  if (!date) {
    return;
  }

  var latestCheckMailDate = getLatestCheckMailDate();
  if (latestCheckMailDate) {
    if (latestCheckMailDate.getTime() > date.getTime()) {
      return;
    }
  }

  var user = plainBody.match(/担当者 (.*)\r\n/m)[1];

  // Slack に送信する前に動作が問題ないかログで確認しよう
  // Logger.log(`${date} ${subject}`);  
  chatPostMessage(`${user} から ${subject} が届きました`);
}
```

# 雑感

実装している途中で非常に恐ろしいなと思う点があったので書き記します。

## Google Apps Script (GAS) の脆弱性について

便利に利用させてもらっているGASですが、大きな問題となる機能があります。
それが `UrlFetchApp` です。
`UrlFetchApp` を利用すれば、GASで取得可能なGoogle Workspace内のデータは全て外部に流出させることが可能となる点です。
今回は例としてGmailとSlackにしましたが、ここはいくらでも変更可能で他のサービスに投稿することが出来ます。
Google Workspaceの管理者の方々は不審なスクリプトファイルが存在しないかチェックすることをおすすめします。

また、アクセストークンに関しても偽装が簡単に行えます。
とても簡単でアクセストークンを他のSlackのワークスペースのものにしてしまえばいいのです。
アクセストークンはひと目で正しいものか判別ができない上に、アクセストークンにアクセスできる人が限られているため正しさをチェックできないのが欠点です。

対策としては余計な人をメーリングリストに追加したり、アクセス権を渡したりしないことです。
このスクリプトはあくまで手動でできる処理を自動化させただけなので、スクリプトファイルの利用を禁止しても根本的な解決にはなりません。

# おわりに

Google Apps Script を使って Gmail を Slack に投稿するスクリプトを書きました。
十分注意を払った上で利用しましょう。

Supershipではプロダクト開発やサービス開発に関わる人を絶賛募集しております。
ご興味がある方は以下リンクよりご確認ください。

[Supership 採用サイト](https://supership.jp/recruit/)

是非ともよろしくお願いします。