GASでメールから情報を抽出する方法 (ついでにカレンダーに登録)

<h1>はじめに</h1>
知人にタイトルの依頼がきたので、興味本位でやってみた。(GAS触れる自慢がしたかったのです)

ロジックは非常にシンプルです。箇条書きすると下記になります。
<ol>
<li>Gmail APIを使って特定のメールを取得</li>
<li>正規表現を使って必要な情報を本文から取得</li>
<li>Google Calendar APIを使ってカレンダーに登録</li>
</ol>
となります！

<h1>その前に</h1>
ちなみにGmailには予約完了メール系を勝手にカレンダーに登録してくれる機能があります。
そういったメールをしたい場合は設定だけで済むのでこっちは不要です！

また、今回はプログラムだけではなく、ラベルや自動振り分けなどのGmail自体の機能も使っています。
そちらは詳細に説明しないので調べてもらえればと思います！
このコードを使うには前もって情報を抽出したいメールを手動or自動振り分けでラベル付けとスターを付けていることが前提となります。

あと、スレッドに受信するメールがたまることを前提にコードを書いているので、一部美しくないところがあります。

<h1>やってみた</h1>

実際にコードを書いてみました！
業務でも使われるコードなので適当なコードになっているところもあります。ご了承ください。

~~~javascript:メールtoカレンダー
// gmailに飛んできた楽天からの予約完了メールを自動的にカレンダーに登録するプログラム

function main() {
  
  // オブジェクトの作成
  const label = GmailApp.getUserLabelByName("ラベル");

  console.log("メールから情報を取得");
  const infomations = getMail(label);

  console.log("カレンダーに登録");
  setCalendar(infomations);

};

function getMail(label) {
  // ラベルから必要なメールを取得して情報を取ってくる

  let infomations = [];
  let start = 0;
  let max = 30;
  while (true) {

    
    let threads = label.getThreads(start, max); // 取得エラーの原因になるので一気に取得しない
    if (threads.length <= 0) {
      break;
    };
    start += (max - 1);

    for (thread of threads) {
      if (!thread.hasStarredMessages()) { // すべてのメッセージにスターが外れている場合は無視する
        continue;
      }

      messages = thread.getMessages();
      
      for (message of messages) {
        // 本文から必要な情報を抜き出すスクリプト
        if (!message.isStarred()) { // スターが外れているメッセージは無視
          continue;
        }
        let body = message.getBody();
        body = body.replace(/\u3000/g, "") // すべての全角空白を消す
        body = body.replace(/\n/g, "").replace(/\s+/g, "") // すべての改行を消す

        infomations.push(extractRegexp(body));  
        message = message.markRead(); // 既読にする
        message = message.unstar(); // スターを外す

        console.log("既読とアンスター処理の完了");

      };

      console.log("スレッドの読み込み完了")
        
    };
  };

  return infomations;

};

function extractRegexp(body) {
  /*
    正規表現で美しくデータを抽出するスクリプト
  */
  reg = /日時：(\d{4}-\d{1,2}-\d{1,2})/
  let tmp = body.match(reg)[1];
  info = {"日時" : tmp}
  return info
};

function setCalendar(infomations) {
  // googleカレンダーに取得した情報を説明付きで登録する

  for (info of infomations) {

    let event = CalendarApp.getDefaultCalendar().createEventFromDescription(
        "いい感じのタイトル"
    ); // タイトル付きでカレンダーの作成

    let startTime = new Date("正規表現で取得した日時とか (yyyy-mm-dd hh:MM:ss フォーマットで)");
    let endTime = new Date("正規表現で取得した日時とか (yyyy-mm-dd hh:MM:ss フォーマットで)");

    // 詳細設定
    event.setTime(startTime, endTime); // 日時の登録
    event.setDescription(
      "本文または記載しておきたい予定の情報"
    );

  };
};
~~~

では順を追って説明していきます。(自分的に重要なコードのみの説明となります！)

<h2>Main部分の説明</h2>

~~~javascript:main
// オブジェクトの作成
  const label = GmailApp.getUserLabelByName("ラベル");
~~~
このコードで自作したラベルのオブジェクトの作成をしています。このオブジェクトからスレッドやメール本文の取得が可能になります。

ちなみにラベルとはGmailのサイドバーにある、こんなやつ。
自分で作る必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1674311/b3397ddf-c47b-d230-c395-cb0e8b78f1ee.png)

<h2>メールの本文抽出部分の説明</h2>

~~~javascript:getMail①
    
    let threads = label.getThreads(start, max); // 取得エラーの原因になるので一気に取得しない
    if (threads.length <= 0) {
      break;
    };
    start += (max - 1);
~~~
ドキュメントに一度で読み込むメールの量が大きすぎるとエラー落ちすると記載があったので、エラー対策として書いています。

~~~javascript:getMail②
    
    for (thread of threads) {
      if (!thread.hasStarredMessages()) { // すべてのメッセージにスターが外れている場合は無視する
        continue;
      }

      messages = thread.getMessages();
      
      for (message of messages) {
        // 本文から必要な情報を抜き出すスクリプト
        if (!message.isStarred()) { // スターが外れているメッセージは無視
          continue;
        }
~~~
情報を抽出する対象となるメールはスターがついているもののみを対象としています。

`thread.hasStarredMessages()`でスレッド内のメールにスターがついているか、
`message.isStarred()` でメール単体にスターがついているかを確認しています。
受信時にスターをつけて、情報を抽出できてカレンダーに登録できたものはスターを外すとしているので、この処理をすることで同じメールを処理しないようにしてます。

~~~javascript:getMail③
        let body = message.getBody();
        body = body.replace(/\u3000/g, "") // すべての全角空白を消す
        body = body.replace(/\n/g, "").replace(/\s+/g, "") // すべての改行を消す

        infomations.push(extractRegexp(body));  
        message = message.markRead(); // 既読にする
        message = message.unstar(); // スターを外す
~~~
メールの本文を取得して、正規表現で情報を抜き出す自作関数を呼んでいます。
本文から情報を取得する際に面倒なのは、スクショのように整った形(改行がしっかり入る。)で取得してしまうことです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1674311/c4420c14-08c7-3ab2-259f-aae15601e609.png)

なので、 `body.replace(/\n/g, "").replace(/\s+/g, "")` を使って改行や空白を削除して、一行の文字列になるように変更します。これをしないと正規表現が上手く使えないので <b>MUST</b>です
必要であれば全角削除をするために `body.replace(/\u3000/g, "")` も使うといいかもしれません。

<h2>正規表現部分の説明</h2>

~~~javascript:
function extractRegexp(body) {
  /*
    正規表現で美しくデータを抽出するスクリプト
  */
  reg = /日時：(\d{4}-\d{1,2}-\d{1,2})/
  let tmp = body.match(reg)[1];
  info = {"日時" : tmp}
  return info
};
~~~
すべてを見せることができないのですが、こんな感じで、正規表現で情報を取ります。
`body.match(reg)[1]` とすることで `(\d{4}-\d{1,2}-\d{1,2})` この()部分の情報のみを取得出来ます。
正規表現は非常に考えるのが面倒ですが、chatGPTさんが優秀なので、考えてもらいましょう。


<h2>カレンダー登録部分の説明</h2>

~~~javascript:setCalendar
function setCalendar(infomations) {
  // googleカレンダーに取得した情報を説明付きで登録する

  for (info of infomations) {

    let event = CalendarApp.getDefaultCalendar().createEventFromDescription(
        "いい感じのタイトル"
    ); // タイトル付きでカレンダーの作成

    let startTime = new Date("正規表現で取得した日時とか (yyyy-mm-dd hh:MM:ss フォーマットで)");
    let endTime = new Date("正規表現で取得した日時とか (yyyy-mm-dd hh:MM:ss フォーマットで)");

    // 詳細設定
    event.setTime(startTime, endTime); // 日時の登録
    event.setDescription(
      "本文または記載しておきたい予定の情報"
    );

  };
~~~

~~~javascript:setCalendar①
let event = CalendarApp.getDefaultCalendar().createEventFromDescription(
        "いい感じのタイトル"
    ); // タイトル付きでカレンダーの作成
~~~
ここで event(カレンダー)の作成を行います。
`createEventFromDescription` desciption となってややこしいですが、カレンダーのタイトルになります。

~~~javascript:setCalendar②
// 詳細設定
    event.setTime(startTime, endTime); // 日時の登録
    event.setDescription(
      "本文または記載しておきたい予定の情報"
    );
~~~
日時の登録と詳細の情報の記入を行っています。
日時は dateオブジェクトを入れる必要があるので注意です！

<h1>さいごに</h1>
Gmailはスレッドの概念があり、ちょっとコーディングめんどいなと思った。(スレッド単位でラベル付けしちゃうところとか)

APIのドキュメントはめちゃくちゃ分かりやすいので、是非参照しながらコードをかいてみてください！
[APIドキュメント](https://developers.google.com/apps-script/reference/gmail?hl=ja&authuser=1)