am͜a͉zonの配達日をGASで自動可視化してみる

<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

皆さんGoogle Apps Script好きですかー？自分はV8になってから好きになりました

# はじめに
📦📦📦📦📦
📦 **am͜a͉zon** 📦
📦📦📦📦📦

今回は**Amazon発送メール × Gmail × Google Calendar**の組み合わせなので、Outlookの方は180°回ってお帰りいただくか今すぐGmailに移行しましょう。

# なぜ今更GAS？？？
察しの良い方はTwitterでお気づきかもしれませんが、コロナ感染者が異常に多い所に5月頃引っ越して一人暮らしを始めました。(ついでにGASからIHになりました。ぴえん🥺)
単純な話、今までは実家暮らしで誰かが受け取ってくれていたので、配達日を把握して自分が家に居る必要がなかったんですね。
~~宅配ボックス使えよと言われる前に言いますが宅配ボックスまで行くのが面倒~~

# このスクリプトの特徴
- 時間帯指定に対応
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71322/3d3af16e-f068-36e1-2bf1-5ffc20939c14.png)

- 営業所止めに対応
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Amazon発送メールをGASでゴリゴリするやつ、正規表現乱用して営業所止めにも対応した<br><br>そのうちQiitaに書く <a href="https://t.co/a1roODWU91">pic.twitter.com/a1roODWU91</a></p>&mdash; 冷凍こめ (@come25136_spd) <a href="https://twitter.com/come25136_spd/status/1281280022708228096?ref_src=twsrc%5Etfw">July 9, 2020</a></blockquote>
もちろん自宅等での受け取りにも対応していますよ。

# 処理フロー
GmailにAmazonから発送メールが届く
↓←────────────┐
イベントトリガー等で定期処理┘
↓
Googleカレンダーに登録

# コピペで動くコード ...の前に
[日付＆時刻の便利ライブラリ「Moment.js」をGoogle Apps Scriptで使う方法](https://tonari-it.com/gas-moment-js-moment/) を参考にGASでMoment.jsを使えるようにしてください。

# コピペで動くコード
2, 3行目は各自調整してください。

```javascript
function amazonEvent() {
  const daysAgo = 1 // n日前から実行日時までのメールを処理する
  const calendar = CalendarApp.getCalendarById('カレンダーID') // http://www.sukicomi.net/2018/07/google-calendarid.html

  const now = Moment.moment()

  GmailApp
    .search(`from:(Amazon.co.jp) Amazon.co.jpでのご注文 after:${now.clone().subtract(daysAgo, 'd').format('YYYY/MM/DD')}`)
    .sort((a, b) => a.getLastMessageDate() - b.getLastMessageDate())
    .forEach(thread => {
      const messages = thread.getMessages()

      messages
        .sort((a, b) => a.getDate() - b.getDate()) // 古い日付が先に来るようにする
        .forEach(message => {
          const msgRecvDate = Moment.moment(message.getDate())
          const msgBody = message.getPlainBody()

          // Kindleを除く
          const deliveryScheduleMsgResult = msgBody.match(/お届け予定日?時?[：:](.+)配送(状況は下記ページ|オプション)/s)
          if (deliveryScheduleMsgResult === null) return
          const [, deliveryScheduleMsgRaw] = deliveryScheduleMsgResult // 冗長だけどデバッグ用に変数化
          const deliveryScheduleMsg = deliveryScheduleMsgRaw.replace(/^\s+|\s+$|.{3,},\s|\r?\n/g, '').replace(/[\t\s]+/, ' ')
          const [deliveryStartScheduleMsg, deliveryEndScheduleMsg] = deliveryScheduleMsg.split(' - ')
          const [deliveryStartDateMsg, deliveryStartTimeMsg] = deliveryStartScheduleMsg.split(' ')
          const deliveryStartTimeMsgIsTime = /\d\d:\d\d/.test(deliveryStartTimeMsg)
          const [, deliveryEndDateMsg, deliveryEndTimeMsg] = (deliveryEndScheduleMsg || deliveryStartScheduleMsg).match(/([0-1]\d\/[0-3]?\d)?\s?(([01][0-9]|2[0-3]):[0-5][0-9])?/)

          // 年跨ぐ場合のメールを見たことないのでバグるかも
          const deliveryStartDate = Moment.moment(`${deliveryStartDateMsg} ${deliveryStartTimeMsgIsTime ? deliveryStartTimeMsg : '00:00'}`, 'MM/DD HH:mm').set('y', msgRecvDate.year())
          const deliveryEndDate = Moment.moment(`${deliveryEndDateMsg || deliveryStartDateMsg} ${deliveryEndTimeMsg || '23:59'}`, 'MM/DD HH:mm').set('y', msgRecvDate.year())

          const [, orderNumber] = msgBody.match(/注文番号：?\s?(.+)/)

          const oldEvents = calendar.getEvents(deliveryStartDate.clone().subtract(2, 'w').toDate(), deliveryStartDate.clone().add(2, 'w').toDate())
          const oldEvent = oldEvents.find(e => e.getTag('order_number') === orderNumber)

          // 正規表現1つにまとめたいけど上手く動いてるので妥協
          const [, deliveryAddMsg] = msgBody.match(/お届け先：\s*(.+)\r?\n\s{0,5}\r?\n.*===/s)
          const [name, addressMsg] = deliveryAddMsg.replace(/お客様の商品は.+|===.+/s, '').split(' 様')
          if (addressMsg === undefined) {
            console.log(message)
          }
          const address =
            addressMsg
              .replace(/\r?\n/g, '') // 改行削除
              .replace(/\s{2,}/g, ' ') // スペースが2個以上連続しているのを削除
              .replace(/[1-9]+件中[1-9]+件目の発送.+/, '')
              .replace(/\s不在時.*/, '') // 住所の最後に「不在時はOKIPPA希望」と入れてるのでそれを削除(削除しないとGoogle Mapで上手く表示されない)

          const title = /営業所/.test(name) ? `荷物営業所止め(${name})` : `荷物受け取り`
          const description = `<a href="${message.getThread().getPermalink()}">メールを開く✉</a><br>${orderNumber}`

          if (oldEvent) {
            oldEvent
              .setTitle(title)
              .setDescription(description)
              .setLocation(address)

            const setTime = deliveryStartTimeMsgIsTime ? oldEvent.setTime : oldEvent.setAllDayDates

            if (deliveryStartTimeMsgIsTime)
              setTime(deliveryStartDate.toDate(), deliveryEndDate.toDate())
            else
              oldEvent.setAllDayDate(deliveryStartDate.toDate())
          } else {
            const createEvent = deliveryStartTimeMsgIsTime ? calendar.createEvent : calendar.createAllDayEvent
            const event = createEvent(
              title,
              deliveryStartDate.toDate(),
              deliveryEndScheduleMsg ? deliveryEndDate.toDate() : undefined,
              {
                description,
                location: address
              }
            )
            event.setTag('order_number', orderNumber)
          }
        })
    })
}
```

# 使い方
自由に煮るなり焼くなりしてもらって構いませんが、自分はイベントトリガーのタイマーをこんな感じに設定して使っています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71322/a648f582-04e2-0377-61e0-f200e537adf8.png)

# 注意
Amazonは複数商品の注文を複数日に分けて配達してくれる機能があるのですが、いつもまとめて配達してもらっているので動作確認出来ていません。
これに関しては発送後の追跡番号で識別すれば良いだけなのですが、自分が困ったら対応するということで。そのうち。

# 参考にさせていただいた記事
[Amazonの注文確認のgmailから、googleカレンダーにその受け取り時刻の予定を自動で入れる - Qiita](https://qiita.com/gawawa124/items/d8594194d2fa3a2fd5e8)

# さいごに
Amazonのお急ぎ便大体翌日に来るけど、深夜に注文すると**寝るまでが今日理論で**配達が翌々日になるので脳がバグる

アルゴリズムに何の捻りもないのと、GASは公式ドキュメントが充実している為、あえてコードの解説を書いていませんが何かあれば[@come25136_spd](https://twitter.com/come25136_spd)まで気軽にどうぞ