diff --git a/actual_imap_poll/parsers.py b/actual_imap_poll/parsers.py index 28f3fbb..b6ede01 100644 --- a/actual_imap_poll/parsers.py +++ b/actual_imap_poll/parsers.py @@ -1,10 +1,11 @@ import re from abc import ABC, abstractmethod +from collections.abc import Sequence from datetime import date, datetime from decimal import Decimal from email.message import EmailMessage, Message -from logging import info, warning -from typing import Any, Optional, Sequence +from logging import debug, info, warning +from typing import Any, Optional from uuid import UUID from bs4 import BeautifulSoup @@ -308,3 +309,42 @@ class ScotiaBankParser(TransactionParser): return self.extract_transaction(msg) info("Subject `%s` didn't match any extractors", msg["Subject"]) return None + + +class NeoFinancialParser(TransactionParser): + PURCHASE_EXTRACT_RE = re.compile( + r"your purchase of \$(?P[0-9]+\.[0-9]{2}) at (?P.+) on (?P(January|February|March|April|May|June|July|August|September|October|November|December) \d{1,2}(st|nd|rd|th)?, \d{4})", # noqa: E501 + re.MULTILINE, + ) + + def __init__(self, account_id: UUID): + self.account_id = account_id + + def match(self, msg: Message) -> bool: + return msg["From"] == "Neo Financial " and msg[ + "Subject" + ].startswith("You earned") + + def extract_purchase(self, msg: EmailMessage) -> Optional[Transaction]: + content = self.get_content(msg, preferencelist=["plain"]) + matches = self.PURCHASE_EXTRACT_RE.search(content) + if matches is None: + debug(f"No match on\n\n{content}") + raise TransactionParsingFailed("no matches for purchase extraction RE") + + amount = Decimal(matches["amount"].replace(",", "")) * -1 + # Use date from email header, it seems to match the body and includes TZ info + date = parse_email_time(msg["Date"]).date() + return Transaction( + account=self.account_id, + date=date, + amount=amount, + payee=matches["payee"], + notes="via email", + imported_id=msg["Message-ID"], + ) + + def extract(self, msg: EmailMessage) -> Optional[Transaction]: + if msg["Subject"].startswith("You earned"): + return self.extract_purchase(msg) + return None diff --git a/pyproject.toml b/pyproject.toml index 76058fe..340131d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "actual-imap-poll" -version = "0.1.3" +version = "0.2.0" description = "Poll an IMAP mailbox looking for transactions to submit to Actual Budget" authors = [ {name = "Keenan Tims",email = "ktims@gotroot.ca"}