Google Apps Scriptでクレジットカード利用明細を毎月自動で奥さんにメール送信する

# はじめに
多くのプログラマーにとって、最大の関心事は主に2つです。

1. いかに良いコードを書くか
2. いかに奥さんのご機嫌を取るか

今回のテーマは「2. いかに奥さんのご機嫌を取るか」という課題をGoogle Apps Scriptで解決するという話です。

>夫婦円満の秘訣は、財布を奥さんに預けることである

先人達は、後世の若者たちのために良い知恵を遺してくれました。リスクを共にする夫婦間でお金の管理をクリアーにすることが夫婦円満の秘訣ということです。我が家でも先達の知恵に倣い、お給料を全て奥さんに預けてお小遣い制を導入しています。

しかし、お金の管理を不透明にするやっかいな代物があります。ネットショッピングに欠かせないクレジットカードです。10年ほど前であれば、クレジットカード利用明細が毎月カード会社から郵送されてきたので、奥さんが利用明細を見ることで出費の透明性が担保されていました。しかし、最近では利用明細は電子化されており、個人のID・パスワードが無いと見ることができません。ID・パスワードを奥さんに渡すのも嫌だし、毎月毎月PDFやCSVでダウンロードして奥さんに渡すのも激しくめんどくさいです。

# Google Apps Scriptでクレジットカード利用明細を自動で取得する
毎月のように利用明細を奥さんから督促されるので、頭にきて自動で利用明細を取得するツールを作ることにしました。ツールに要求される機能は以下の3つです。

1. クレジットカードの会員サイトにログインして利用明細の電子ファイル(PDF or CSV)をダウンロードする
2. 利用明細の電子ファイルをメール添付して送信する
3. 毎月定期実行する

何で実装しようかなーといろいろ調べたところ、Google Apps Script(以降GAS)を使うと結構簡単に実現できるみたいなので、GASの勉強がてら作って見ることにしました。GASはGoogle DocsやSpreadSheetのマクロみたいなものですが、メール送信やHTTPリクエスト・レスポンスの処理、Google Driveへのファイル保存、トリガーによる定期実行にも対応しており、機能が充実しています。おまけに無料で使えます。素晴らしい。

# GASの始め方
Google Driveのスクリプトエディタで新しいプロジェクトを作成します。Google Driveで**新規 > その他 > Google Apps Script**を選択して作れます[^1]。

[^1]: SpreadSheetのスクリプトとかでもOKです。SpreadSheetを新規に作成し、メニューの[ツール]-[スクリプトエディタ]からGASのプロジェクトを作成できます。

## クレジットカードの会員サイトにログインして利用明細の電子ファイル(PDF or CSV)をダウンロードする

自分のメインカードはJCBカードです。JCBカードでは[MyJCB](https://my.jcb.co.jp/Login)という会員サイトで、利用明細をPDFかCSVでダウンロードできます。

### [UrlFetchApp](https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app)でログイン処理
まずは、ログイン処理をGASで書いてみます。GASでは[UrlFetchApp](https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app)を使うことで、HTTPリクエストを簡単に実装できます。ログイン処理では、認証用URLにID・パスワードをPOSTし、cookieからセッションIDを取得するなど色々やることがありますが、[UrlFetchApp.fetch(url, params)](https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app#fetch(String,Object))を使ってサクッと書くことができます。
具体的なMyJCBへのログイン処理のコードは以下のようになります。

```js:MyJCBへのログイン処理
var LOGIN_URL = "https://my.jcb.co.jp/iss-pc/member/user_manage/Login";
var userid = "myuserid";
var password = "xxxxxxxx";

// HTTPリクエストのパラメータをobjectで設定
// POSTで渡すフォームデータはpayloadで指定
var options = {
  method : "post",
  followRedirects: false,
  contentType: "application/x-www-form-urlencoded",
  payload : {
    userId: userid,
    password: password
  }
};

// ログイン
var response = UrlFetchApp.fetch(LOGIN_URL, options);

// レスポンスヘッダーからcookieを取得
var headers = response.getAllHeaders();
var cookies = [];
if ( typeof headers['Set-Cookie'] !== 'undefined' ) {
  // Set-Cookieヘッダーが2つ以上の場合はheaders['Set-Cookie']の中身は配列
  var cookies = typeof headers['Set-Cookie'] == 'string' ? [ headers['Set-Cookie'] ] : headers['Set-Cookie'];
  for (var i = 0; i < cookies.length; i++) {
    // Set-Cookieヘッダーからname=valueだけ取り出し、セミコロン以降の属性は除外する
    cookies[i] = cookies[i].split( ';' )[0];
  };
}
```

### [HTTPResponse](https://developers.google.com/apps-script/reference/url-fetch/http-response)でPDFファイル取得
MyJCBにログインできたら、次は利用明細ページからPDFをダウンロードします。
利用明細ページは`https://my.jcb.co.jp/iss-pc/member/details_inquiry/detail.html?detailMonth=1&output=web`という形式で、URLパラメータの`detailMonth`で表示する月を指定するようになっています。`detailMonth`は1が当月、2が前月、以降3,4,...と過去に遡って指定するようになっており、表示したい年月を直接指定できないので、ちょっと不便です。まあ、毎月当月分のしか取得しないので、常に`detailMonth=1`でよいでしょう。

PDFのダウンロード用URLは`https://my.jcb.co.jp/iss-pc/member/details_inquiry/detailDbPdf.html?detailMonth=1&output=pdf`という形式[^2]で、同じく`detailMonth=1`で当月の明細を取得できます。

[^2]: 利用明細ページが`detail.html?output=web`なので、PDFも`detail.html?output=pdf`にすればいいのに、なぜか`detailDbPdf.html?output=pdf`です。なぜ？教えてMyJCBの中の人。

PDFダウンロード処理のコードは以下のようになります。PDFファイルのダウンロード先にはGoogle Driveを使うことにします。Google Driveに保存する際はBlob形式の方が都合がいいので、[HTTPResponse.getBlob()](https://developers.google.com/apps-script/reference/url-fetch/http-response#getBlob())を使ってBlob形式でPDFファイルを取得します。

ちなみに、なぜかMyJCBではPDFダウンロードURLに直接アクセスしてもPDFダウンロードできません。利用明細ページに飛ばされます。事前に必ず利用明細ページを踏まないといけないようです。Refererに利用明細ページURLを設定しておいてもダメです。仕組みがよく判りません。謎です。

```js:PDFダウンロード処理
// 当月の利用明細ページ
var DETAIL_URL = "https://my.jcb.co.jp/iss-pc/member/details_inquiry/detail.html?detailMonth=1&output=web";
// 当月のPDFダウンロード用URL
var PDF_DETAIL_URL = "https://my.jcb.co.jp/iss-pc/member/details_inquiry/detailDbPdf.html?detailMonth=1&output=pdf";

// Getリクエストのパラメータ
// ログイン処理で取得したcookieをセットする
var options = {
  method: "get",
  followRedirects: false,
  headers: {
    Cookie: cookies.join(';')
  }
};

// PDFダウンロードの前に明細ページヘアクセス
// これをやらないと、なぜかPDFダウンロードできない
// 理由は不明
UrlFetchApp.fetch(DETAIL_URL , options);

var response = UrlFetchApp.fetch(PDF_DETAIL_URL , options);
var headers = response.getAllHeaders();  
var fileBlob = response.getBlob();
if ( typeof headers['Content-Disposition'] !== 'undefined' ) {
  // ファイル名をContent-Dispositionヘッダーから取得してファイル名にセットする
  // <YYYYMM>meisai.pdfという形式
  // 2015年12月の利用明細なら201512meisai.pdf
  var filename = headers['Content-Disposition'].match('filename[^;=\n]*=["]*((["]).*?\2|[^;"\n]*)')[1];
  fileBlob.setName(filename);
}
```

### [DriveApp](https://developers.google.com/apps-script/reference/drive/drive-app)でGoogle DriveにPDFファイルを保存
Google Driveにファイルを保存するには[DriveApp](https://developers.google.com/apps-script/reference/drive/drive-app)を使います。保存先の[Folder](https://developers.google.com/apps-script/reference/drive/folder)オブジェクトを取得し、[createFile(blob)](https://developers.google.com/apps-script/reference/drive/folder#createFile(BlobSource))メソッドの引数にPDFファイルのBlobオブジェクトを渡します。
Google Driveに保存したファイルであれば、共有リンクをメールに貼り付けるだけで奥さんに渡せます。保存したPDFファイルに「リンクを知っている全員が閲覧可」の共有リンクを付けるには[setSharing(accessType, permissionType)](https://developers.google.com/apps-script/reference/drive/file#setSharing(Access,Permission))メソッドを使います。`accessType`には[DriveApp.Access.ANYONE_WITH_LINK](https://developers.google.com/apps-script/reference/drive/access)、`permissionType`には[DriveApp.Permission.VIEW](https://developers.google.com/apps-script/reference/drive/permission)を指定します。
共有リンクは[getUrl()](https://developers.google.com/apps-script/reference/drive/file#getUrl())で取得します。

```js:GoogleDriveにPDFファイルを保存
var outputDir = DriveApp.getRootFolder();
var pdfDetail = outputDir.createFile(fileBlob);
pdfDetail.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
var sharingUrl = pdfDetail.getUrl();
```

## 利用明細の電子ファイルをメール添付して送信する
利用明細のPDFダウンロードができたら、次はメール送信処理を実装します。

### [MailApp](https://developers.google.com/apps-script/reference/mail/mail-app)でPDFファイルの共有リンクをメール送信

メールを送信する一番シンプルなやり方は[MailApp.sendEmail(recipient, subject, body)](https://developers.google.com/apps-script/reference/mail/mail-app#sendEmail(String,String,String))です。[sendEmail(recipient, subject, body, options)](https://developers.google.com/apps-script/reference/mail/mail-app#sendEmail(String,String,String,Object))を使えば、ファイル添付やHTMLメールなどのリッチなメールが送信できますが、今回は共有リンクを貼り付けただけのシンプルなメール送信を実装します。

```js:PDFファイルの共有リンクをメール送信
var mailTo = "my-sweetheart@example.com";

// ファイル名から年月を抜き出し
var yyyymm = pdfDetail.getName().slice(0, 4) + "年" + pdfDetail.getName().slice(4, 6) + "月"; 
var subject = "JCBクレジットカード利用明細:" + yyyymm + "分";
var mailBody = "JCBクレジットカード利用明細:" + yyyymm + "分を取得しました。\n";
    mailBody+= "下記URLからダウンロードしてください。\n\n";
    mailBody+= sharingUrl;
MailApp.sendEmail(mailTo, subject, mailBody);
```

なお、MailAppで送信するメールのFromは、MailAppを実行するGmailアカウントのメールアドレスになりますが、[Gmailで設定した別の送信元メールアドレス/エイリアス](https://support.google.com/mail/answer/22370?hl=ja)であれば、[sendEmail(recipient, subject, body, options)](https://developers.google.com/apps-script/reference/mail/mail-app#sendEmail(String,String,String,Object))の`option`で`{from:'別のメールアドレス'}`とすれば[指定できるらしい](http://www.teradas.net/archives/14770/)です[^3]。

[^3]: リファレンスにはFromを変更する方法が書いていないので、非推奨なのかも。

## 毎月定期実行する
GASのコードを定期実行するには、トリガーの設定を行います。
まず、定期実行したいコードをfunctionとして定義します。

```js
function GetLatestDetailFile() {
  // ログイン処理
  var LOGIN_URL = "https://my.jcb.co.jp/iss-pc/member/user_manage/Login";
  var userid = "myuserid";
  var password = "xxxxxxxx";

〜中略〜

  MailApp.sendEmail(mailTo, subject, mailBody);
}
```

次に、スクリプトエディタにて**リソース > 現在のプロジェクトのトリガー**を選択し、定義したfunctionを**時間主導型 > 月タイマー**で毎月指定の日付に実行するように設定する。
毎月5日には当月の利用明細がダウンロードできるらしいので、とりあえず5日で設定。
![トリガー設定](https://qiita-image-store.s3.amazonaws.com/0/81556/fe8981d3-a0e2-f19d-87de-c7c1670917c1.png)

# 動かしてみた
こんな風にメールが来る！リンクをクリックするとPDFの利用明細も見られる！
![メール]
(https://qiita-image-store.s3.amazonaws.com/0/81556/a9fb7be3-ed26-3333-253e-68bae8cd230d.jpeg)

奥さんも大喜び。自分も毎月の督促から開放され大喜び。我が家に再び平和が戻ったのでありました。めでたしめでたし。

# おまけ:GASのクォータ
GASには便利なAPIがたくさんありますが、使用量に制限があります。
https://developers.google.com/apps-script/guides/services/quotas?hl=ja

例えば、MailApp.sendEmail()は100件/日、UrlFetchApp.fetch()は20,000回/日。今回みたいに1件/月みたいな軽いツールなら全く問題ないけど、毎日メールを頻繁に飛ばすようなアグレッシブなツールだと厳しいかも。

# 追記:2015.12.12
我が家で運用しているコードです。ご参考まで。
https://github.com/takeruko/gas-myjcb-detail-checker