88- equateplus_qr.png: Scan QR code with EquateAccess app
99"""
1010from base64 import b64decode
11+ from datetime import datetime
1112from pathlib import Path
13+ from pickle import dumps , loads
1214from random import randint
1315from time import sleep
1416
17+ import click
1518import 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-
2421def 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
282370if __name__ == "__main__" :
0 commit comments