【GAS】Gmailからメルカリの購入履歴をスプレッドシートに書き込むツールを作った

## はじめに
Google Apps Scriptでメルカリの購入履歴を自動取得するツールを作成しました。
「メールから情報取得→加工→スプレッドシートへ書き込み」という一連の作業が網羅されているので、GASの勉強にいい記事になったと思います。

## 制作手順

### 必要なもの
・Googleアカウントのみ

### 大まかな流れ
Gmailの情報をシートに取得するツールを作る際は
「メール取得」→「情報抽出」→「加工」→「書き込み」
この流れが基本ですので頭に入れておきましょう。

### 1. スプレッドシートの用意
![スクリーンショット 2022-11-04 22.17.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2932655/57db6e3a-94ce-0f9d-4d59-8c2a1cb706b9.png)
まずはスプレッドシートを用意します。Chromeの右上のマークから「スプレッドシート」を選択。
 新規のスプレッドシートを作成します。 

![スクリーンショット 2022-11-04 22.19.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2932655/b8c06c0b-cbf3-66de-a331-1e101ca5293d.png)
 「拡張機能」→「Apps Script」を選択。 

![スクリーンショット 2022-11-05 12.47.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2932655/83fd1e8f-303c-0979-f369-1ad03e604cf9.png)

こんな感じで何を取得したいか書いておきます。
メッセージIDはメール1件1件に紐づけられた固有の値です。これを利用して新規メールか否かを判定します。

### 2. Gmailからメールを取得する 

```javascript:getEmails
// メールを取得する関数
function getEmails(){

  // メルカリのメールアドレス
  const ADDRESS_MERCARI = 'no-reply@mercari.jp';

  // 検索条件
  const query = 'from:' +ADDRESS_MERCARI + ' subject:【メルカリ】ご購入ありがとうございます'
  const start = 0;
  const max = 15;

  // メールを取得
  const threads = GmailApp.search(query, start, max);
  const messages = GmailApp.getMessagesForThreads(threads);

  return messages;
}
```
関数getEmailsで検索条件に引っかかるメールをリストで取得してきます。

![スクリーンショット 2022-11-05 12.19.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2932655/912a0977-3bf6-1989-30ab-7aa38e941d76.png)

とりあえずGmailで「メルカリ」と検索してみます。これだけではメルカリの購入履歴だけには絞れてないのでもう少し工夫してみます。

![スクリーンショット 2022-11-05 12.21.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2932655/d73787d8-bde8-3088-57e0-39ee60e4eec0.png)

購入履歴はこのフォーマットで送られてくるみたいですね。
件名は【メルカリ】　ご購入ありがとうございます　で固定されているので、件名とメールアドレスを検索条件に含めれば購入履歴だけに絞れそうです。

![スクリーンショット 2022-11-05 12.24.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2932655/15186955-00c0-9ec7-dc52-2ca9228f8b7c.png)
この条件で検索をかけてみます。

![スクリーンショット 2022-11-05 12.24.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2932655/fef5a177-4a7f-f8b2-bb2f-14bd68028628.png)
いい感じで購入履歴だけに絞れてますね。検索条件は絞れたのでスクリプトに書き起こすだけです。

```javascript
// メルカリのメールアドレス
const ADDRESS_MERCARI = 'no-reply@mercari.jp';

// 検索条件
const query = 'from:' +ADDRESS_MERCARI + ' subject:【メルカリ】ご購入ありがとうございます'
const start = 0;
const max = 15;
```
queryはGmailの検索バーのところをコピペすれば多分OK。

### 3. メールから情報を抽出する 

```javascript:getProductInfo
// メールから商品情報を取得する関数 (message:obj -> purchace_info:obj)
function getProductInfo(message){

  let purchace_info = {}; // 購入情報を格納するオブジェクト

  purchace_info.message_id = message.getId(); // メッセージID
  purchace_info.date = message.getDate(); // 日付

  const body = message.getPlainBody(); // メール本文を取得
  purchace_info.product_id = Parser.data(body).from('商品ID : ').to('\r').build(); // 商品ID
  purchace_info.product_name = Parser.data(body).from('商品名 : ').to('\r').build(); // 商品名
  purchace_info.seller_name =Parser.data(body).from('出品者 : ').to('\r').build(); // 出品者
  purchace_info.price = Parser.data(body).from('商品代金 : ￥').to('\r').build(); // 商品代金

  return purchace_info;
}
```
取得したメールから必要な情報のみを抽出したオブジェクトにします。

```
 [ { toString: [Function],
      getFrom: [Function],
      getDate: [Function],
      getHeader: [Function],
      refresh: [Function],
      getId: [Function],
      createDraftReply: [Function],
      unstar: [Function],
      markUnread: [Function],
      isUnread: [Function],
      isInInbox: [Function],
      isInTrash: [Function],
      isInChats: [Function],
      replyAll: [Function],
      getCc: [Function],
      getBcc: [Function],
      moveToTrash: [Function],
      getThread: [Function],
      isDraft: [Function],
      getBody: [Function],
      reply: [Function],
      getSubject: [Function],
      star: [Function],
      getRawContent: [Function],
      isInPriorityInbox: [Function],
      createDraftReplyAll: [Function],
      getPlainBody: [Function],
      isStarred: [Function],
      getAttachments: [Function],
      markRead: [Function],
      getReplyTo: [Function],
      getTo: [Function],
      forward: [Function] } ],
```
1件のメールはこんな感じのオブジェクトで取得されるので、簡単に取得できそうです。
ただし、スレッド毎にリストになっているので、そこだけ注意。返信があった場合はオブジェクトのリストで取得してきます。

```javascript
const body = message.getPlainBody(); // メール本文を取得
purchace_info.product_id = Parser.data(body).from('商品ID : ').to('\r').build(); // 商品ID
purchace_info.product_name = Parser.data(body).from('商品名 : ').to('\r').build(); // 商品名
purchace_info.seller_name =Parser.data(body).from('出品者 : ').to('\r').build(); // 出品者
purchace_info.price = Parser.data(body).from('商品代金 : ￥').to('\r').build(); // 商品代金
```
この部分でParserライブラリを使用しています。文字列の抽出が楽になるライブラリです。

```
■商品情報
商品ID : m11411754271
商品名 : 夢をかなえるゾウ 3 (ブラックガネーシャの教え)
出品者 : ここ
■支払い金額
商品代金 : ￥720
クーポン：利用なし
ポイント利用 : P0
メルペイ残高利用 : ￥132
--------------------------
支払い金額 : ￥588
```
メール本文のこの部分から取得しています。

### 4. 情報を配列に加工する
```javascript:objectToArray
// 商品情報オブジェくトを配列に変形する関数 (purchace_info:obj -> purchace_array)
function objectToArray(purchace_info){
  const purchace_array = [
    purchace_info.date,
    purchace_info.product_id,
    purchace_info.product_name,
    purchace_info.seller_name,
    purchace_info.price,
    purchace_info.message_id
  ]

  return purchace_array;
}
```
3で取得したオブジェクトを突っ込むことでスプレッドシートに書き込む用の配列に直してくれます。
今回は1行ずつ```appendRow()```で書き込むようにしたいので1次元配列にします。

### 5. スプレッドシートに書き込む
```javascript:main
function main(){

  const sheet = SpreadsheetApp.getActiveSheet();

  messages = getEmails();

  for(message of messages){
    if(!hasId(message[0].getId())){  // メッセージIDが存在しなければ

      const purchace_info = getProductInfo(message[0]);
      const purchace_array = objectToArray(purchace_info);
      sheet.appendRow(purchace_array);
      
    }
  }
}
```
全体の処理はこんな感じ。
存在しない購入履歴のみの情報を取得して書き込みます。

```javascript:hasId
// メッセージIDが既に存在するか判定する関数 (id -> bool)
function hasId(id) {

  const sheet = SpreadsheetApp.getActiveSheet();

  if(sheet.getLastRow() == 1) return false;

  const ids = sheet.getRange(2, 6, sheet.getLastRow()-1).getValues();

  return ids.some(function(array, i, ids){
    return (array[0] == id);
  })
}
```
```hasId()```はシートにこれから取得する購入履歴が既に存在するかを判定する関数です。