【GAS】何もわからない。俺たちは雰囲気でGmailAppでメールを取得している

「GAS何もわからない」から「GASチョットダケ、ワカル」になったので、GASを使ったGmailのメール情報取得処理について簡単にまとめました。
~~（まとめながらGASわからないじゃなくてGmailAppわからないだったのでは、と思ったのはここだけの話）~~
メール検索してスプレッドシートに吐き出したいんじゃ！！なのに思ったようにメールが取れないんじゃ！ってなったとき用。

#Gmailの受信メール収集
###押さえておくべき前提事項

Gmailがスレッド表示ではない場合、**GmailApp.searchで検索される結果とGmailの検索結果は異なる結果**になります。

* Gmailでは個々のメッセージ（メール）が検索される
* GmailApp.searchで検索されるのはスレッドが検索される

普段Gmailをスレッドでまとめて表示させていないのでスレッドで取得した際のメッセージ取得イメージがいまいち湧かなかったのと、どうやらsearchで検索したスレッドの中身は自分が欲していない内容も入っていそうだぞ、ということに気が付きました。

スレッド形式の場合は**同じ件名のメールがまとめて表示**されるため、同日の同じ件名のメールは同一スレッドにまとめられます。（同一件名の返信メールも同一スレッドにまとめられます）


そのため、searchで取得した際のスレッドとメッセージのイメージはこんな感じになります。
（検索Queryは例です）

![GmailApp_取得イメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241926/c2008a81-1f44-4a73-db06-40a93c318078.png )

私はこのスレッドの部分を理解していなかったので、取得したいメールの件数が多かったり少なかったり、そもそも検索したい対象ではないメールも取得されてしまって大いに混乱しました。笑

###実際の処理コード
スレッドからメッセージを取り出すには下記の方法を今回使いました。

```js:スレッド取得処理
// GmailApp.search(検索条件[,開始スレッドのインデックス,最大取得数])
var myThreads = GmailApp.search("検索条件");
var messagesArr = GmailApp.getMessagesForThreads(myThreads);

// 今回はスプレッドシートに収集したメッセージを出力させたいので、配列へ格納
var msgArr = []; 
var i = 0; // 行数カウント用
messagesArr.forEach(function(messages) {
  // getMessagesForThreadsの戻り値が二次元配列になっているので配列をループ
  messages.forEach(function(message) {
    // メールを一つずつ取り出す
    if (message.getFrom().indexOf("検索対象のメールアドレス") > 0) {
      // スレッド内での返信メールは除外したいので、特定のアドレスからのメールのみ処理
      msgArr[i] = [];
      msgArr[i][0] = "=ROW()-1"; // データのNo出力用に関数をセルへ出力
      msgArr[i][1] = message.getDate(); // 受信時間
      msgArr[i][2] = message.getFrom(); // 送信者
      msgArr[i][3] = message.getSubject(); // 件名
      msgArr[i][4] = message.getPlainBody(); // 本文
      i++;
    }
  });
});
```

上記の実装では純粋に１つのメールアドレスからの自動送信メールをまとめたかったので、返信メールの除外策としてメールアドレスが該当しないものの場合は処理しないようにしています。
（もっといい方法あるかもしれない）

#おまけ
###メール本文内から特定の文字から改行までを切り出したいときのメモ
本旨とはずれますが
例えば、メール本文内にある
`氏名：ほげほげ ふがふが`
ってあったら`"ほげほげ ふがふが"`の部分だけ取得したい。
みたいなこともあるかと思うので、そんなときは下記のような形で本文内の単語から位置を検索して部分切り出し。

```js:メール本文から取得したい情報だけ切り出し
/* （前略）「スレッド取得処理」のメールを一つずつ取り出す部分以降の処理 */
var targetStr = "検索用語";
var mailMsg = message.getPlainBody();
//str.substr(start[, length])：strのうちlength文字分を、start の位置から数えて抽出
mailMsg.substring(mailMsg.indexOf(targetStr) + targetStr.length,
                  mailMsg.indexOf("\n",mailMsg.indexOf(targetStr))-1); //改行文字はいらないので-1
```

スプレッドシートに出力する場合は関数でMID、FIND、LENを組み合わせてもできるのですが、あまりにも面倒くさかったのとイケてなかったのでGAS側で処理するようにしました。

###thread配列をループさせてgetMessages()で取り出す方法じゃあかんのけ？
結果としてどちらを使ってもメールのデータは取得可能です。
ただし、**処理速度についてはgetMessagesForThreadsを用いて処理したほうが速い**です。

getMessagesForThreadsでは`messages[][]を受け取り、2次元配列をそれぞれループで処理する`のに対し、getMessagesを用いる場合は、`searchで取得したthread配列をループし、更にthreadからmessagesを取得してループ処理を行う`必要があるため、処理が遅くなるのかと思います。

```js:getMessages()を用いた場合
var myThreads = GmailApp.search(SearchString);

var msgArr = [];
var i = 0;
myThreads.forEach(function(thread) {
  // スレッド内のメール一覧を取得するためスレッド配列をループ
  var messages = thread.getMessages();
  messages.forEach(function(message) {
    // スレッドからメールを一つずつ取り出す
    if (message.getFrom().indexOf("検索対象のメールアドレス") > 0) {
      // スレッド内での返信メールは除外したいので、特定のアドレスからのメールのみ処理
      msgArr[i] = [];
      msgArr[i][0] = "=ROW()-1";
      msgArr[i][1] = message.getDate();
      msgArr[i][2] = message.getFrom();
      msgArr[i][3] = message.getSubject();
      msgArr[i][4] = message.getPlainBody();
      i++;
    }
  });
});
```

###【参考】

公式リファレンス
[searchリファレンス](https://developers.google.com/apps-script/reference/gmail/gmail-app#search(String))
[getMessagesForThreadsリファレンス](https://developers.google.com/apps-script/reference/gmail/gmail-app#getMessagesForThreads(GmailThread))
[getMessagesリファレンス](https://developers.google.com/apps-script/reference/gmail/gmail-thread#getMessages())

メール検索～スプレッドシートに出力の処理は検索すればいろいろと参考になるものは見つかるのですが、やはり公式リファレンスちゃんと読んだほうがより処理内容が理解できていいな…と改めて思いました。
英語オンリーだけど。