add neo parser
This commit is contained in:
@@ -1,10 +1,11 @@
|
|||||||
import re
|
import re
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
from collections.abc import Sequence
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from email.message import EmailMessage, Message
|
from email.message import EmailMessage, Message
|
||||||
from logging import info, warning
|
from logging import debug, info, warning
|
||||||
from typing import Any, Optional, Sequence
|
from typing import Any, Optional
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
@@ -308,3 +309,42 @@ class ScotiaBankParser(TransactionParser):
|
|||||||
return self.extract_transaction(msg)
|
return self.extract_transaction(msg)
|
||||||
info("Subject `%s` didn't match any extractors", msg["Subject"])
|
info("Subject `%s` didn't match any extractors", msg["Subject"])
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class NeoFinancialParser(TransactionParser):
|
||||||
|
PURCHASE_EXTRACT_RE = re.compile(
|
||||||
|
r"your purchase of \$(?P<amount>[0-9]+\.[0-9]{2}) at (?P<payee>.+) on (?P<date>(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 <info@neofinancial.com>" 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
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "actual-imap-poll"
|
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"
|
description = "Poll an IMAP mailbox looking for transactions to submit to Actual Budget"
|
||||||
authors = [
|
authors = [
|
||||||
{name = "Keenan Tims",email = "ktims@gotroot.ca"}
|
{name = "Keenan Tims",email = "ktims@gotroot.ca"}
|
||||||
|
|||||||
Reference in New Issue
Block a user