Skip to content

Commit bbe7dc1

Browse files
authored
Add support for statements (#3)
* Add prototype for document download * Add support for statements * Update README.md * Bump version to 3.00 * Update EquatePlus.lua * Add signature
1 parent 7c65f4e commit bbe7dc1

6 files changed

Lines changed: 154 additions & 32 deletions

File tree

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
.DS_Store
22
credentials.txt
3-
equateplus_qr.png
3+
equateplus_qr.png
4+
equateplus.pckl
5+
documents/

EquatePlus.lua

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ end
66

77
local baseurl=""
88
local reportOnce
9-
local Version="2.1"
9+
local Version=3.00
1010
local CSRF_TOKEN=nil
1111
local CSRF2_TOKEN=nil
1212
local connection
@@ -378,6 +378,34 @@ function RefreshAccount (account, since)
378378
return {securities=securities}
379379
end
380380

381+
function FetchStatements (accounts, knownIdentifiers)
382+
local statements = {}
383+
384+
-- Load postbox page.
385+
local library = JSON(connectWithCSRF("POST","https://www.equateplus.com/EquatePlusParticipant2/services/documents/library?_cId="..cId.."&_rId="..rnd(),"{\"$type\":\"Object\"}","application/json;charset=UTF-8")):dictionary()
386+
387+
local pattern = "(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+)"
388+
for k,document in pairs(library["documents"]) do
389+
local statement = {}
390+
local year, month, day, hour, minute, second = document["date"]:match(pattern)
391+
statement.creationDate = os.time({year=year,month=month,day=day})
392+
statement.name = document["description"]
393+
statement.identifier = document["id"]
394+
statement.filename = (document["description"] .. "(" .. MM.localizeDate(statement.creationDate) .. ").pdf"):gsub("/", "-")
395+
if not knownIdentifiers[statement.identifier] then
396+
print("Downloading statement: " .. statement.filename)
397+
statement.pdf = connectWithCSRF("GET", "https://www.equateplus.com/EquatePlusParticipant2/services/statements/download?documentId="..statement.identifier.."&downloadType=inline&source=LIBRARY")
398+
if startsWith(statement.pdf, "{\"$type\":\"TechnicalError\"") then
399+
print("error downloading statement")
400+
else
401+
table.insert(statements, statement)
402+
end
403+
end
404+
end
405+
406+
return {statements=statements}
407+
end
408+
381409
function bugReport(status,err,v)
382410
if not status and reportOnce then
383411
reportOnce=false
@@ -389,7 +417,7 @@ end
389417

390418
function EndSession ()
391419
-- Logout.
392-
connectWithCSRF("GET","services/participant/logout")
420+
connectWithCSRF("GET","https://www.equateplus.com/EquatePlusParticipant2/services/participant/logout")
393421
end
394422

395-
-- SIGNATURE: MC0CFQCAvuO8tEE66YK0ZhAm76gHE6Tw0gIUD3F98TfMYQk+RDYZYHlMFdDwSSU=
423+
-- SIGNATURE: MCwCFAf5dPf+zi2l1NSWRHsSzplHaJyLAhQZSM+ssqOMG1BBGvMO2OTFeWkfbA==

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
A quick and dirty plugin for MoneyMoney for share depot. See `equateplus.py` as showcase for api calls.
44

5-
Plugin now supports EquateAccess App authentication.
5+
Plugin now supports:
6+
7+
* EquateAccess App authentication
8+
* Statement download
69

710
No warrenty or whatsoever!
811

_config.yml

Lines changed: 0 additions & 1 deletion
This file was deleted.

equateplus.py

Lines changed: 114 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,16 @@
88
- equateplus_qr.png: Scan QR code with EquateAccess app
99
"""
1010
from base64 import b64decode
11+
from datetime import datetime
1112
from pathlib import Path
13+
from pickle import dumps, loads
1214
from random import randint
1315
from time import sleep
1416

17+
import click
1518
import requests
1619

1720

18-
CREDENTIALS_PATH: Path = Path("credentials.txt")
19-
QR_CODE_PATH: Path = Path("equateplus_qr.png")
20-
21-
CREDENTIALS: list[str] = CREDENTIALS_PATH.read_text().strip().split("\n")
22-
23-
2421
def debug(func):
2522
def wrapper(*args, **kwargs):
2623
print(func.__name__, end=" ", flush=True)
@@ -54,8 +51,13 @@ def get_amount(data: dict) -> float:
5451
return quantity["amount"]
5552

5653

57-
class Equate:
58-
def __init__(self) -> None:
54+
class EquatePlus:
55+
def __init__(self, username: str, password: str, qr_code_path: Path) -> None:
56+
self.username: str = username
57+
self.password: str = password
58+
self.qr_code_path: Path = qr_code_path
59+
self.documents_dir: Path = Path("documents")
60+
5961
self.session: requests.Session = requests.Session()
6062
self.cookies: dict[str, str] = {}
6163
self.csrf: str | None = None
@@ -105,7 +107,7 @@ def send_user(self) -> bool:
105107
},
106108
data={
107109
"csrfpId": self.csrf,
108-
"isiwebuserid": CREDENTIALS[0],
110+
"isiwebuserid": self.username,
109111
"result": "Continue Login",
110112
},
111113
headers={
@@ -119,8 +121,8 @@ def send_credentials(self) -> bool:
119121
"https://www.equateplus.com/EquatePlusParticipant2/?login",
120122
data={
121123
"csrfpId": self.csrf,
122-
"isiwebuserid": CREDENTIALS[0],
123-
"isiwebpasswd": CREDENTIALS[1],
124+
"isiwebuserid": self.username,
125+
"isiwebpasswd": self.password,
124126
"result": "Continue",
125127
},
126128
headers={
@@ -134,7 +136,7 @@ def request_devices(self) -> bool:
134136
response: requests.Response = self.session.post(
135137
"https://www.equateplus.com/EquatePlusParticipant2/?login",
136138
data={
137-
"isiwebuserid": CREDENTIALS[0],
139+
"isiwebuserid": self.username,
138140
"isiwebpasswd": "null",
139141
"result": "null",
140142
},
@@ -164,7 +166,7 @@ def request_qr_code(self) -> bool:
164166

165167
try:
166168
data = response.json()
167-
QR_CODE_PATH.write_bytes(b64decode(data["dispatcherInformation"]["response"] + "=="))
169+
self.qr_code_path.write_bytes(b64decode(data["dispatcherInformation"]["response"] + "=="))
168170
self.session_id = data["sessionId"]
169171

170172
return True
@@ -253,30 +255,116 @@ def get_plan_details(self) -> bool:
253255
print(" ", end="", flush=True)
254256
return True
255257

258+
@debug
259+
def get_documents(self) -> bool:
260+
response: requests.Response = self.session.post(
261+
"https://www.equateplus.com/EquatePlusParticipant2/services/documents/library",
262+
params=self.ids(),
263+
json={
264+
"$type": "Object",
265+
},
266+
)
267+
try:
268+
for document in response.json()["documents"]:
269+
date: str = datetime.fromisoformat(document["date"]).strftime("%d.%m.%Y")
270+
file_name: str = document["description"] + f" ({date}).pdf"
271+
file_path: Path = self.documents_dir / file_name.replace("/", "-")
272+
273+
if self.download_document(document["id"], file_path):
274+
print(".", end="", flush=True)
275+
else:
276+
print("x", end="", flush=True)
277+
278+
except (KeyError, IndexError):
279+
return False
280+
281+
print(" ", end="", flush=True)
282+
return True
283+
284+
def download_document(self, document_id: str, file_path: Path) -> bool:
285+
response: requests.Response = self.session.get(
286+
"https://www.equateplus.com/EquatePlusParticipant2/services/statements/download",
287+
params={
288+
"documentId": document_id,
289+
"downloadType": "inline",
290+
"source": "LIBRARY",
291+
},
292+
)
293+
if response.content.startswith(b"{\"$type\":\"TechnicalError\""):
294+
return False
295+
296+
try:
297+
file_path.parent.mkdir(parents=True, exist_ok=True)
298+
file_path.write_bytes(response.content)
299+
300+
except:
301+
return False
302+
303+
return True
304+
256305
@debug
257306
def logout(self) -> None:
258307
self.session.get(
259308
"https://www.equateplus.com/EquatePlusParticipant2/services/participant/logout",
260309
)
261310

262311

263-
def main() -> None:
264-
e = Equate()
312+
@click.command()
313+
@click.option("--credentials-path", type=Path, default=Path("credentials.txt"), show_default=True)
314+
@click.option("--qr-code-path", type=Path, default=Path("equateplus_qr.png"), show_default=True)
315+
@click.option("--pickle-path", type=Path, default=Path("equateplus.pckl"), show_default=True)
316+
@click.option("--download-documents", is_flag=True, help="download documents")
317+
@click.option(
318+
"--store",
319+
is_flag=True,
320+
help="serialize EquatePlus object to file (and don't logout)",
321+
)
322+
@click.option(
323+
"--restore",
324+
is_flag=True,
325+
help="deserialize EquatePlus object from file (and don't login)",
326+
)
327+
@click.option("--no-logout", is_flag=True, help="don't logout")
328+
def main(credentials_path: Path, qr_code_path: Path, pickle_path: Path, download_documents: bool, store: bool, restore: bool, no_logout: bool) -> None:
329+
credentials: list[str] = credentials_path.read_text().strip().split("\n")
330+
equateplus = EquatePlus(username=credentials[0], password=credentials[1], qr_code_path=qr_code_path)
331+
login_successful: bool = False
265332
try:
266-
e.initialize() and \
267-
e.send_user() and \
268-
e.send_credentials() and \
269-
e.request_devices() and \
270-
e.request_qr_code() and \
271-
e.verify_qr_code() and \
272-
e.complete_login() and \
273-
e.get_plan_summary() and \
274-
e.get_plan_details()
333+
if restore:
334+
equateplus = loads(pickle_path.read_bytes())
335+
print("equateplus restored")
336+
login_successful = True
337+
338+
else:
339+
login_successful = equateplus.initialize() and \
340+
equateplus.send_user() and \
341+
equateplus.send_credentials() and \
342+
equateplus.request_devices() and \
343+
equateplus.request_qr_code() and \
344+
equateplus.verify_qr_code() and \
345+
equateplus.complete_login()
346+
347+
if store:
348+
if login_successful:
349+
pickle_path.write_bytes(dumps(equateplus))
350+
print("equateplus stored")
351+
else:
352+
print("equateplus not stored - login failed")
353+
354+
login_successful and \
355+
equateplus.get_plan_summary() and \
356+
equateplus.get_plan_details() and \
357+
download_documents and \
358+
equateplus.get_documents()
359+
275360
finally:
276-
e.logout()
361+
if not store and not no_logout:
362+
equateplus.logout()
363+
else:
364+
print("equateplus not logged out")
277365

278366
from pprint import pprint
279-
pprint(e.securities)
367+
pprint(equateplus.securities)
280368

281369

282370
if __name__ == "__main__":

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
click
2+
requests

0 commit comments

Comments
 (0)