Compare commits
2
Commits
7b6b497c0f
..
v0.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d414d0cf09 | ||
|
|
a15970d292 |
+1
-1
@@ -5,7 +5,7 @@ RUN apk add py3-uv
|
||||
RUN uv build
|
||||
|
||||
# Use an official Node.js runtime as a base
|
||||
FROM node:20-alpine AS runtime
|
||||
FROM node:25-alpine AS runtime
|
||||
|
||||
# Create app directory
|
||||
WORKDIR /app
|
||||
|
||||
@@ -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<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
@@ -14,7 +14,7 @@
|
||||
"license": "ISC",
|
||||
"type": "commonjs",
|
||||
"dependencies": {
|
||||
"@actual-app/api": "^25.11.0",
|
||||
"@actual-app/api": "^26.5.2",
|
||||
"yargs": "^17.7.2"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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"}
|
||||
|
||||
Reference in New Issue
Block a user