add readme & docstrings

This commit is contained in:
Keenan Tims 2025-04-24 16:30:25 -07:00
parent 336b732c60
commit b3cfb961d2
3 changed files with 114 additions and 1 deletions

87
README.md Normal file
View File

@ -0,0 +1,87 @@
# Actual IMAP Poll
This project is designed to poll an IMAP server, looking for transaction notification emails which are pushed to an Actual Budget instance.
## Docker Usage
To run this project using Docker, you need to configure the following environment variables:
### Environment Variables
| Variable Name | Description | Example Value |
| ----------------- | --------------------------------------------------------------------------------------- | -------------------------------------- |
| `IMAP_SERVER` | The hostname or IP address of the IMAP server. | `imap.example.com` |
| `IMAP_PORT` | The port number for the IMAP server. | `143` |
| `IMAP_USER` | The username for authenticating with the IMAP server. | `user@example.com` |
| `IMAP_PASS` | The password for authenticating with the IMAP server. | `yourpassword` |
| `IMAP_STARTTLS` | Whether to use TLS for the IMAP connection (`true` or `false`). | `true` |
| `IMAP_INTERVAL` | The interval (in seconds) between polling the IMAP server. | `300` |
| `ACTUAL_SERVER` | The URL to the Actual Budget instance to submit transactions to | `https://demo.actualbudget.org` |
| `ACTUAL_PASSWORD` | The password for the Actual Budget instance | `password` |
| `ACTUAL_SYNC_ID` | The 'sync ID' UUID of the Actual Budget budget on the server (see Settings / Advanced) | `145b1875-8d2c-4eac-aa2c-b3fd937c6a6d` |
| `CONFIG_PATH` | Path to a Python module containing a description of the parsers to execute (see below). | `/data/config.py` |
## Parser Definition
The parsers to execute and their parameters are defined in a Python source file which should be mounted to the container at the location specified by `CONFIG_PATH` (e.g. `/data/config.py`). This file should define a list called `PARSERS` which holds a list of `TransactionParser` instances. Since it is Python source, it can also contain custom `TransactionParser` instances.
Each parser instance will generally require the account UUID to link the transactions to, which can be obtained from the end of the URL while viewing the account in Actual.
### Example config.py
```python config.py
from parsers import RogersBankParser, MBNAParser
from uuid import UUID
PARSERS = [
RogersBankParser(UUID("ae662c3c-3f36-4aee-a558-aa32ff7b75c8")),
MBNAParser(UUID("82a382e6-ff27-49af-841f-0cbddee9fe28")),
]
```
## Custom Parser Implementation
Parsers must implement the abstract class `TransactionParser` defined in `parsers.py`. This class requires two methods:
1. `match(self, msg: Message) -> bool` accepts a `Message/EmailMessage` object and uses it to determine if this parser is responsible for the message and it contains an interesting transaction
2. `extract(self, msg: EmailMessage) -> Transaction` accepts an `EmailMessage` object and extracts a `Transaction` dataclass object for submission to Actual
### Example custom parser
Below is a minimal example of a custom parser for a fictional bank, "Minimal Bank." This parser matches emails from "Minimal Bank" and extracts transaction details. It should appear in the `config.py` alongside the `PARSERS` definition.
```python
class MinimalBankParser(TransactionParser):
def __init__(self, account_id):
self.account_id = account_id # Actual account UUID to submit the matching transactions to
def match(self, msg):
return msg["From"] == "Minimal Bank <alerts@minimalbank.com>" and "Transaction Alert" in msg["Subject"]
def extract(self, msg):
body = msg.get_body().as_string()
amount = ... # Extract amount from body
date = ... # Extract date from body
payee = ... # Extract payee from body
return Transaction(
account=self.account_id,
date=date,
amount=amount,
payee=payee,
notes="via Minimal Bank email",
imported_id=msg["Message-ID"],
)
```
### Example Docker Command
```bash
docker run -d \
-e IMAP_SERVER=imap.example.com \
-e IMAP_USER=user@example.com \
-e IMAP_PASS=yourpassword \
-e ACTUAL_SERVER=https://demo.actualbudget.org \
-e ACTUAL_PASSWORD=password \
-e ACTUAL_SYNC_ID=145b1875-8d2c-4eac-aa2c-b3fd937c6a6d \
-v /path/to/config.py:/data/config.py \
actual-imap-poll:latest
```

View File

@ -6,9 +6,14 @@ from uuid import UUID
@dataclass
class Transaction:
"""Represents an Actual Budget transaction to be submitted.
See: https://actualbudget.org/docs/api/reference#transaction for field descriptions
"""
account: UUID
date: date
amount: Decimal
amount: Decimal # Note: decimal dollars, JS shim will convert to cents as described in the API
payee: str # imported_payee in API
notes: str
imported_id: str

View File

@ -13,10 +13,31 @@ from model import Transaction
class TransactionParser(ABC):
@abstractmethod
def match(self, msg: Message) -> bool:
"""
Determines if the given email message matches the criteria for this parser.
Args:
msg (Message): The email message to evaluate.
Returns:
bool: True if the message matches the parser's criteria, False otherwise.
"""
pass
@abstractmethod
def extract(self, msg: EmailMessage) -> Transaction:
"""
Extracts transaction details from the given email message.
Args:
msg (EmailMessage): The email message to parse.
Returns:
Transaction: A Transaction object containing the extracted details.
Raises:
TransactionParsingFailed: If the message cannot be parsed successfully.
"""
pass