Compare commits
3 Commits
v0.1.2
...
7b6b497c0f
| Author | SHA1 | Date | |
|---|---|---|---|
| 7b6b497c0f | |||
| 097128c290 | |||
| 2662747ebe |
@@ -1,14 +1,14 @@
|
||||
# .pre-commit-config.yaml
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.1.5 # Check for the latest version
|
||||
rev: v0.15.12 # Check for the latest version
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: ["check", "--select", "I", "--fix"]
|
||||
- id: ruff-format
|
||||
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0 # Check for the latest version
|
||||
rev: v6.0.0 # Check for the latest version
|
||||
hooks:
|
||||
- id: check-yaml
|
||||
- id: end-of-file-fixer
|
||||
@@ -16,6 +16,6 @@ repos:
|
||||
- id: requirements-txt-fixer
|
||||
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 6.0.0 # Check for the latest version
|
||||
rev: 7.3.0 # Check for the latest version
|
||||
hooks:
|
||||
- id: flake8
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import datetime
|
||||
from dataclasses import dataclass
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
from uuid import UUID
|
||||
|
||||
@@ -12,7 +12,7 @@ class Transaction:
|
||||
"""
|
||||
|
||||
account: UUID
|
||||
date: date
|
||||
date: datetime.date
|
||||
amount: Decimal # Note: decimal dollars, JS shim will convert to cents as described in the API
|
||||
payee: str # imported_payee in API
|
||||
notes: str
|
||||
|
||||
+21
-11
@@ -58,6 +58,18 @@ class TransactionParser(ABC):
|
||||
raise TransactionParsingFailed("No content of message found")
|
||||
return content
|
||||
|
||||
@staticmethod
|
||||
def strip_html(msg: EmailMessage) -> str:
|
||||
body = msg.get_body(preferencelist=("html", "plain"))
|
||||
if body is None:
|
||||
raise TransactionParsingFailed("No HTML body of message found")
|
||||
content = body.get_content()
|
||||
if content is None:
|
||||
raise TransactionParsingFailed("No content of message found")
|
||||
|
||||
soup = BeautifulSoup(content, "html.parser")
|
||||
return soup.get_text()
|
||||
|
||||
|
||||
class TransactionParsingFailed(Exception):
|
||||
pass
|
||||
@@ -65,7 +77,7 @@ class TransactionParsingFailed(Exception):
|
||||
|
||||
class RogersBankParser(TransactionParser):
|
||||
EXTRACT_RE = re.compile(
|
||||
r"Attempt of \$([0-9,]+\.\d{2}) was made on ([A-z]{3} \d{1,2}, \d{4})[^<]*at ([^<]+) in ([^<]+)." # noqa: E501
|
||||
r"\$([0-9,]+\.\d{2}) on ((Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [0-9]{2}, [0-9]{4}) on your credit card ending in ([0-9]{4}). Details: ([^.]+)" # noqa: E501
|
||||
)
|
||||
|
||||
def __init__(self, account_id: UUID):
|
||||
@@ -78,17 +90,16 @@ class RogersBankParser(TransactionParser):
|
||||
)
|
||||
|
||||
def extract(self, msg: EmailMessage) -> Optional[Transaction]:
|
||||
content = self.get_content(msg)
|
||||
content = self.strip_html(msg)
|
||||
matches = self.EXTRACT_RE.search(content)
|
||||
if matches is None:
|
||||
raise TransactionParsingFailed("No matches for extraction RE")
|
||||
amount = Decimal(matches[1].replace(",", "")) * -1
|
||||
|
||||
date_raw = matches[2]
|
||||
payee = matches[3]
|
||||
location = matches[4]
|
||||
payee = matches[5]
|
||||
|
||||
if "Rebate" == location and "CashBack" in payee:
|
||||
if "CashBack" in payee:
|
||||
amount = amount * -1
|
||||
|
||||
date = datetime.strptime(date_raw, "%b %d, %Y").date()
|
||||
@@ -97,7 +108,7 @@ class RogersBankParser(TransactionParser):
|
||||
date=date,
|
||||
amount=amount,
|
||||
payee=payee,
|
||||
notes=f"in {location} (via email)",
|
||||
notes="(via email)",
|
||||
imported_id=msg["Message-ID"],
|
||||
)
|
||||
|
||||
@@ -136,8 +147,8 @@ class MBNAParser(TransactionParser):
|
||||
|
||||
class BMOParser(TransactionParser):
|
||||
EXTRACT_RE = re.compile(
|
||||
r"We want to let you know that a (withdrawal|deposit) of\s+\$([0-9,]+\.\d{2})\s+has been made (?:to|from) your account ending\s+in\s+(\d{3})", # noqa: E501
|
||||
flags=re.MULTILINE,
|
||||
r"There was a (withdrawal|deposit).*Amount:\s*\$([0-9,]+\.\d{2}).*Account:\s*Ending in ([0-9]+)", # noqa: E501
|
||||
flags=re.DOTALL,
|
||||
)
|
||||
|
||||
def __init__(self, account_map: dict[int, UUID]):
|
||||
@@ -147,9 +158,8 @@ class BMOParser(TransactionParser):
|
||||
return msg["From"] == "bmoalerts@bmo.com"
|
||||
|
||||
def extract(self, msg: EmailMessage) -> Optional[Transaction]:
|
||||
content = self.get_content(msg)
|
||||
soup = BeautifulSoup(content, "html.parser")
|
||||
matches = self.EXTRACT_RE.search(soup.get_text())
|
||||
content = self.strip_html(msg)
|
||||
matches = self.EXTRACT_RE.search(content)
|
||||
if matches is None:
|
||||
raise TransactionParsingFailed("No matches for extraction RE")
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "actual-imap-poll"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
description = "Poll an IMAP mailbox looking for transactions to submit to Actual Budget"
|
||||
authors = [
|
||||
{name = "Keenan Tims",email = "ktims@gotroot.ca"}
|
||||
|
||||
Reference in New Issue
Block a user