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
|
RUN uv build
|
||||||
|
|
||||||
# Use an official Node.js runtime as a base
|
# Use an official Node.js runtime as a base
|
||||||
FROM node:20-alpine AS runtime
|
FROM node:25-alpine AS runtime
|
||||||
|
|
||||||
# Create app directory
|
# Create app directory
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|||||||
@@ -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
@@ -14,7 +14,7 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actual-app/api": "^25.11.0",
|
"@actual-app/api": "^26.5.2",
|
||||||
"yargs": "^17.7.2"
|
"yargs": "^17.7.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+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