Google Apps Scriptで書籍を管理する

## 動機
　最近、シリーズもののライトノベルや漫画を頻繁に購入しているのですが、その量があまりにも増えてきていて買い損ねるケースが増えてきました。

　なので、何らかの手段で買い忘れを通知してくれる簡単なアプリを作れないかなと考えました。

## 技術選定理由
　最初はAWSのRDSとLambda、それにNode.jsあたりを組み合わせて作ればいいかなと思い、実装していました。

　しかし、Expressを使ってAPIを作るだけで休日を使い果たしてしまい、費用対効果が悪いと感じていました。

　その折に、Google Apps Scriptを使えば、WebサーバーやAPIを作れると聞いたので挑戦してみようと思いました。

## 仕様
　スプレッドシートにシリーズ物のデータを突っ込んで、予想される発売日（最新刊出版日＋出版間隔）を超えたシリーズをGmailで通知するという仕様です。

　出版間隔はこれまでの傾向を見て、自分で判断することとします。

## データ設計
実際のデータは下のような感じです。シート名は「series」です。

![screenshot.1.jpg](https://qiita-image-store.s3.amazonaws.com/0/229574/ee10d842-62bb-2ee0-784f-a971bae0aec5.jpeg)

　列の意味は以下の通りです。不要な列があったり、ISBNがないとかちょっと考慮不足はありますが、とりあえず目的を果たしていればいいかという感じです。

| 列名 | 意味 |
|:-:|:-:|
|id|ユニークなキー|
|name|書籍名|
|latest|最新刊番号|
|period|出版期間|
|subscribe|購読サイン|
|purchased|最新刊購入日|
|published|最新刊出版日|
|author|作者|

## 実装

　スプレッドシートにスクリプトを追加していきます。一応順を追って手順を書きます。

### データをオブジェクトの配列として取得する

```javascript
// テーブルデータの取得
function getTableArr(sheetName){
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = spreadsheet.getSheetByName(sheetName);
  var arr2D = sheet.getRange(1,
                             1,
                             sheet.getLastRow(),
                             sheet.getLastColumn()).getValues();
  return arr2D;
}

// テーブルデータをJavaScriptのオブジェクトの配列に変換
function toJSObj(arr2D) {
  var titles = arr2D[0];
  return arr2D.slice(1).map(function(arr) {
    var obj = {};
    for(var i = 0; i < arr.length; i++){
      obj[titles[i]] = arr[i];
    }
    return obj;      
  }); 
}

// シリーズ一覧を取得
function getSeries() {
  var series2D = getTableArr("series");
  var series = toJSObj(series2D);
  return series;
}
```

　データを取得するのはgetTableArr関数です。シートにはテーブル以外置かないと決めているので、行、列の最大値からRangeを計算して取得しています。

　構造化されていない２次元配列をそのまま扱うのは面倒なので、JavaScriptのオブジェクトに変換しました。それが、toJSObj関数です。

　ここでは1行目のヘッダーをプロパティ名に割り当てて変換しました。

　これでシリーズのデータが取得できました。

### フィルタリングする

```javascript
// おすすめを取得
function getRecommendedSeries(){
  var series = getSeries();
  var now = new Date();
  var today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
  var recommended = series.filter(function(s) {
    var limit = new Date(s.published.valueOf());
    limit.setMonth(limit.getMonth() + s.period);
    return s.subscribe && (limit.getTime() <= today.getTime());
  });
  return recommended;
}
```

　ふつうのJavaScriptの操作ですね。

　購読サインが立っていて、かつ予想される発売日を超えているものを取得しています。

　日付は一応コピーを作ってから破壊的な操作（日付の変更）をしていますが、今回はしてもしなくても特に影響はありません。

### メールを送信する

```javascript
// mailを送信
function sendMail(recommeded) {
 
  var to = "my-email@gmail.com";
  var subject = "推薦書籍の紹介";

  var body = "以下の書籍の購入はお済ですか？\n";
  for(var i = 0; i< recommeded.length; i++){
    body += recommeded[i].name + "\n";
  }

  var from = "my-email@gmail.com";
  var sender = "my-name";
  
  GmailApp.sendEmail(
    to,
    subject,
    body,
    {
      from: from,
      name: sender
    }
  );
}

// 通知する
function notifyBooks(){
  var recommeded = getRecommendedSeries();
  if(recommeded.length) {
    sendMail(recommeded);
  }
}
```

　件名、送信者、本文当たりの情報を詰め込んで送るだけで、とてもシンプルな記述でやりたいことができました。

### 定期実行のトリガーを仕掛ける

　これでnotifyBooks関数に定期実行のトリガーを仕込みます。

　その際に権限周りの認証を行えば、それで完成です。

## まとめ
　ここまでの記述で大体2時間くらいで完成しました。

　今回のような小さなシステムであれば、Google App Scriptでさくっと作ってしまうのがとても楽でいいなぁと思いました。

　あとはAmazonのAPIとつなげたり、WebAPIとして公開して他のインターフェースと連携（Slack、Alexa、Lineあたり？）できると面白そうかなぁと思います。