#!/usr/bin/env python3 from email import message import imaplib import os import email from html.parser import HTMLParser from typing import Any, Iterable, List, Set class Transaction: def __init__(self, message_id="", amount="", card_ending_in="", merchant="", date="", time=""): self.message_id = message_id self.amount = amount self.card_ending_in = card_ending_in self.merchant = merchant self.date = date self.time = time def all_set(self) -> bool: for val in self.__dict__.values(): if not val: return False return True class MyHTMLParser(HTMLParser): def __init__(self): self.output = Transaction() self.__start_tds = 0 self.__processing_card_ending_in = False self.__processing_merchant = False self.__processing_date = False self.__processing_time = False super().__init__() def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None: self.__start_tds += 1 def handle_endtag(self, tag: str) -> None: self.__start_tds -= 1 def handle_data(self, data: str) -> None: if self.__start_tds > 0: if self.output.all_set(): return data = data.strip() if not data: return if self.__processing_card_ending_in: self.__processing_card_ending_in = False self.output.card_ending_in = data return if self.__processing_merchant: self.__processing_merchant = False self.output.merchant = data return if self.__processing_date: self.__processing_date = False self.output.date = data return if self.__processing_time: self.__processing_time = False self.output.time = data return if data.startswith("Amount: $"): self.output.amount = data.removeprefix("Amount: ") return if data == "Card Ending In": self.__processing_card_ending_in = True return if data == "Merchant": self.__processing_merchant = True return if data == "Date": self.__processing_date = True return if data == "Time": self.__processing_time = True return def get_transactions(imap_host: str, imap_port: int, imap_user: str, imap_password: str, imap_mailbox: str, ignore_message_ids: Set[str]) -> Iterable[Transaction]: with imaplib.IMAP4_SSL(host=imap_host, port=imap_port) as mail: mail.login(user=imap_user, password=imap_password) mail.select(mailbox=f'"{imap_mailbox}"', readonly=True) typ, data = mail.search(None, 'SUBJECT "transaction was made"') for num in data[0].split(): typ, data = mail.fetch(num, "(RFC822)") msg = email.message_from_bytes(data[0][1]) msg_id = msg.get("Message-ID") if msg_id in ignore_message_ids: continue body = b"" if msg.is_multipart(): for part in msg.walk(): sub_body = part.get_payload(decode=True) if sub_body is None: continue body += sub_body else: body = msg.get_payload(decode=True) parser = MyHTMLParser() parser.output.message_id = msg_id parser.feed(str(body, 'utf-8')) yield parser.output if __name__ == "__main__": with open(os.environ["IMAP_PASSWORD_FILE"]) as password_file: imap_password = password_file.read() message_id_ignore_set: Set[str] = set() with open(os.environ["MESSAGE_ID_LIST"], "a+") as message_id_file: message_id_file.seek(0, 0) for message_id in message_id_file: print(message_id.strip()) message_id_ignore_set.add(message_id.strip()) transactions = get_transactions( imap_host=os.environ["IMAP_HOST"], imap_port=int(os.environ["IMAP_PORT"]), imap_user=os.environ["IMAP_USER"], imap_password=imap_password, imap_mailbox=os.environ["IMAP_MAILBOX"], ignore_message_ids=message_id_ignore_set, ) for transaction in transactions: message_id_file.writelines([transaction.message_id, "\n"])