夏休み自由工作:通販したら着弾日がTimeTreeに登録されるやつ②
ハロートナミです。引き続き
Amazonから届く確認メールをあれこれして、TimeTreeに着弾時間を登録するやつ
を作っていきます。
これは続きなので、前のやつを読みたい方はこれを読んで下さい。
前回の記事でTimeTreeにAPIを投げて予定を登録する処理を確認したので、残りのやることが
- Amazonからの発送通知メールをいい感じに取ってくる実装
- Lambdaに乗せる
- 動かしてみる
という感じになっていました。それではやっていきましょう。
Amazonからの発送通知メールをそれがし
元々Amazonのアカウントはyahoomailで作っていたのですが、これを期にGmailに変更して
IFTTTとかそのへんの仕組みを使ってシュッと作っていきます。
……と思ってたんですが、GmailのIFTTT連携がセキュリティ上の理由で消滅していました。
かなりガッカリしました。
調べるとGoogle Apps Script(GAS)で頑張って上記内容を実現してる人もいたんですが
今回は別にリアルタイムに処理されなくても良いし、管理する対象が増えるのが嫌なので
メールサーバからメールを取得してメキメキ処理するオールドスクールな方法でいきます。
メールの取得はやった事無いのでインターネットで何とかします。
qiita.com特にこの記事を参考にしました。
最終的に生まれたコードがこちらです。
import sys import imaplib import email from email.header import decode_header, make_header import datetime import re class MailServer(): IMAP_cli = None def __init__(self, server): self.IMAP_cli = imaplib.IMAP4_SSL(server) def login(self, user, password): self.IMAP_cli.login(user, password) def logout(self): self.IMAP_cli.close() self.IMAP_cli.logout() def _decode_message(self, msg): # メール本文のデコード if msg.is_multipart() is False: # シングルパートのとき payload = msg.get_payload(decode=True) charset = msg.get_content_charset() if charset is not None: message = payload.decode(charset, "ignore") else: # マルチパートのとき for part in msg.walk(): payload = part.get_payload(decode=True) if payload is None: continue charset = part.get_content_charset() if charset is not None: message = payload.decode(charset, "ignore") return message def get_amazon_arriving_estimates(self): self.IMAP_cli.select('amazon') # まだ確認していない、件名に発送が入っているアマゾンからのメールを抽出 typ, data = self.IMAP_cli.search(None, f'(SUBJECT "item has shipped" FROM "Amazon.co.jp" NEW)') estimates = [] for num in data[0].split(): typ, data = self.IMAP_cli.fetch(num, '(RFC822)') email_message = email.message_from_bytes(data[0][1]) message = self._decode_message(email_message) # 1通のメールに複数配達日が書いてあるケースを考慮し、配達日毎のまとまりに分割 arrivings = message.split('Arriving')[1:] # 0個目はヘッダ等なので省く for arriving in arrivings: description = '' # 配達予定の商品を抽出する # 商品名 - 販売元……の順で記載されているので、Sold byで分割し、各要素の最後の部分を使う solds = arriving.split('Sold by:')[:-1] for sold_by in solds: if description != '': description += '\n' # 各まとまりを後ろから商品名周辺のタグで検索し、スライスで商品名を抽出 description += sold_by[sold_by.rfind('sans-serif">') + 13:sold_by.rfind('</a>')] # 商品名の記載が無ければ追加しない if description == '': continue # 配達予定日を抽出。大体ここに書いてあるというアタリで取り出す estimate_date = re.search(r'\d{2}\/\d{2}', arriving[:50]).group() estimates.append((estimate_date, description)) return estimates
なげー
メールの取得までは素直だと思うんですが
メールの文字列を気合パースして配達予定日と配達内容を取得してるところがカオスで
自分でもメンテナンスしたくない感じのコードになりました。
コードだけじゃ絶対分からないのでコメントが沢山書いてあります。酷いですね
何はともあれ、これを以下の感じで使うと、配達予定日と商品のセットが取得出来るはずです。
import os ms = MailServer(os.environ['MAIL_SERVER']) ms.login(os.environ['MAIL_SERVER_USER'], os.environ['MAIL_SERVER_PASS']) estimates = ms.get_amazon_arriving_estimates() for estimate_date, description in estimates: print(estimate_date) print(description) # 08/06 # CANARE XLRケーブル マイクケーブル ノイトリックコネクター 黒色 1.5m EC015-B/黒 # 【国内正規品】RODE ロード PodMic ポッドキャスト向けダイナミックマイク PODMIC # 08/07 # Rode PSA1 プロフェッショナル・スタジオ・ブームアーム [並行輸入品] # 08/06 # サンワダイレクト チェアマット 半透明タイプ 傷防止 フローリング 畳 Pタイル 対応 100-MAT002
メールの仕様が変わったら?その時はおしまいです。
やってて気付いたんですが、amazonの配達お知らせメールって時間まで書いてないんです。
メールからは日付しか取れないし、なんなら時間を取る手段って良く分からない(配送会社に依存する?)ので
とりあえず諦めて、日付だけ指定の終日予定として登録する事に変更しました。
色々迷走しましたが、何とかAmazonの配達予定日とかを取得する部分が出来ました。
先日作ったTimeTreeに予定を登録するやつと組み合わせる用に、形式を変換する処理とかを適当に書いて……
estimates = ms.get_amazon_arriving_estimates() for estimate_date, description in estimates: yaer = datetime.datetime.now().year if datetime.datetime.now().month == 12 and '01/' in estimate_date: # 12月中に届いた1月のお届予定は来年1月として処理 yaer += 1 estimate_date = datetime.datetime(year=yaer, month=int(estimate_date[0:2]), day=int(estimate_date[3:5])) tt.add_schedule('amazon来る', description, estimate_date)
メールの検索ルールをちょっと弄って、古い通知メールで動作確認です。
---
---
この注文が
---
---
こう。いい感じです。
何とかamazonの配達通知をTimeTreeの予定に追加する事が出来ました。
残るはLambdaに搭載する作業と、適当に実行ルール作って動かしてみる作業ですが
コード貼った関係で記事が長くなったので、続きは次の記事に持ち越そうと思います。
次の記事で終わりにしたいですね。技術っぽい話は書くの大変な割に誰も求めてないですからね。
あ、手元のコードはもうちょっと整理したらGithubに投げてリンクしておきます。
それでは次のエントリでお会いしましょう。バイ