comparison bnpparibas.py @ 14:0a3509a12762

Implement download of montly records and add command line options
author Daniele Nicolodi <daniele@grinta.net>
date Tue, 12 Jan 2016 02:22:59 +0100
parents 37ce0dc68cad
children af2e222f2dad
comparison
equal deleted inserted replaced
13:37ce0dc68cad 14:0a3509a12762
2 import email 2 import email
3 import imp 3 import imp
4 import itertools 4 import itertools
5 import json 5 import json
6 import os.path 6 import os.path
7 import requests
8 import smtplib 7 import smtplib
9 import sqlite3 8 import sqlite3
10 import subprocess 9 import subprocess
11 import sys 10 import sys
12 import textwrap 11 import textwrap
17 from email.mime.text import MIMEText 16 from email.mime.text import MIMEText
18 from email.utils import format_datetime, localtime, parseaddr 17 from email.utils import format_datetime, localtime, parseaddr
19 from io import BytesIO 18 from io import BytesIO
20 from pprint import pprint 19 from pprint import pprint
21 from urllib.parse import urljoin 20 from urllib.parse import urljoin
21
22 import requests
23 import click
22 24
23 from html2text import HTML2Text 25 from html2text import HTML2Text
24 from PIL import Image 26 from PIL import Image
25 27
26 28
62 exec(compile(fd.read(), filename, 'exec'), module.__dict__) 64 exec(compile(fd.read(), filename, 'exec'), module.__dict__)
63 conf = {} 65 conf = {}
64 for key in dir(module): 66 for key in dir(module):
65 if key.isupper(): 67 if key.isupper():
66 conf[key] = getattr(module, key) 68 conf[key] = getattr(module, key)
69
70 conf['DATADIR'] = os.path.dirname(filename)
71 for key in 'DATABASE', 'GNUPGHOME':
72 # if path is not absolute, it is interpreted as relative
73 # to the location of the configuration file
74 if not os.path.isabs(conf[key]):
75 conf[key] = os.path.join(conf['DATADIR'], conf[key])
76
67 return conf 77 return conf
68 78
69 79
70 def wrap(p, indent): 80 def wrap(p, indent):
71 return textwrap.fill(p, 72, initial_indent=indent, subsequent_indent=indent) 81 return textwrap.fill(p, 72, initial_indent=indent, subsequent_indent=indent)
312 url = urljoin(URL, 'bmm-wspl/recupMsg') 322 url = urljoin(URL, 'bmm-wspl/recupMsg')
313 r = self.session.get(url, params={'identifiant': mid}) 323 r = self.session.get(url, params={'identifiant': mid})
314 v = self.validate(r) 324 v = self.validate(r)
315 return v['data'] 325 return v['data']
316 326
317 327 def records(self):
318 def main(): 328 # required to set some cookies required by the next call
319 conffile = sys.argv[1] 329 url = urljoin(URL, 'fr/connexion/virements-services/releves-en-ligne')
320 conf = loadconf(conffile) 330 r = self.session.get(url)
321 331 self.validate(r)
322 datadir = os.path.dirname(conffile) 332
323 for key in 'DATABASE', 'GNUPGHOME': 333 url = urljoin(URL, 'demat-wspl/rest/initialisationDemat')
324 # if path is not absolute, it is interpreted as relative 334 r = self.session.get(url)
325 # to the location of the configuration file 335 v = self.validate(r)
326 if not os.path.isabs(conf[key]): 336
327 conf[key] = os.path.join(datadir, conf[key]) 337 for branch in v['data']['initialisationDemat']['arbres']:
328 338 for leave in branch.get('arbre', ( )):
339 if leave['typeDoc'] == 'RELEV':
340 data = leave
341 break
342
343 self.iban = data['ibans'][0]['ibanCrypte']
344 query = {'famDoc': data['codeFamille'],
345 'idTypeDocument': data['idBranche'],
346 'listeIbanCrypte': [ self.iban, ],
347 'typeCpt': data['typeCompte'],
348 'typeDoc': data['typeDoc'],
349 'typeFamille': 'R001'} # ???
350
351 url = urljoin(URL, 'demat-wspl/rest/consultationDemat')
352 data = json.dumps(query)
353 headers = {'Content-Type': 'application/json'}
354 r = self.session.post(url, headers=headers, data=data)
355 v = self.validate(r)
356
357 years = v['data']['consultationDemat']['listeCompte'][0]['listeAnnee']
358 query['codeProduit'] = ''
359 documents = []
360
361 url = urljoin(URL, 'demat-wspl/rest/rechercheDemat')
362 for year in years:
363 query['anneeSelectionnee'] = year
364 data = json.dumps(query)
365 r = self.session.post(url, headers=headers, data=data)
366 v = self.validate(r)
367 documents += v['data']['consultationDemat']['listeCompte'][0]['listeDocument']
368
369 return documents
370
371 def document(self, x):
372 url = urljoin(URL, 'demat-wspl/rest/consultationDocumentDemat')
373 params = {'consulted': x['consulted'],
374 'familleDoc': x['famDoc'],
375 'ibanCrypte': self.iban,
376 'idDocument': x['idDoc'],
377 'idLocalisation': 'undefined',
378 'typeCpt': x['typeCompte'],
379 'typeDoc': x['typeDoc'],
380 'viDocDocument': x['viDocDocument'],
381 'typeFamille': 'R001'}
382 r = self.session.get(url, params=params)
383 self.validate(r)
384 return r.content
385
386
387 def transactions(conf):
329 db = sqlite3.connect(conf['DATABASE']) 388 db = sqlite3.connect(conf['DATABASE'])
330 db.execute('''CREATE TABLE IF NOT EXISTS messages (id TEXT PRIMARY KEY)''')
331 db.execute('''CREATE TABLE IF NOT EXISTS transactions (id INTEGER PRIMARY KEY)''') 389 db.execute('''CREATE TABLE IF NOT EXISTS transactions (id INTEGER PRIMARY KEY)''')
332 390
333 sendmail = Mailer(host=conf['SMTPHOST'], 391 sendmail = Mailer(host=conf['SMTPHOST'],
334 port=conf['SMTPPORT'], 392 port=conf['SMTPPORT'],
335 starttls=conf['SMTPSTARTTLS'], 393 starttls=conf['SMTPSTARTTLS'],
339 encrypt = GPG(conf['GNUPGHOME']).encrypt 397 encrypt = GPG(conf['GNUPGHOME']).encrypt
340 398
341 remote = BNPParibas() 399 remote = BNPParibas()
342 remote.login(conf['USERNAME'], conf['PASSWORD']) 400 remote.login(conf['USERNAME'], conf['PASSWORD'])
343 401
344 ## transactions
345 recent = remote.recent() 402 recent = remote.recent()
346 data = recent['listerOperations']['compte'] 403 data = recent['listerOperations']['compte']
347 transactions = [ Transaction.fromjson(x) for x in data['operationPassee'] ] 404 transactions = [ Transaction.fromjson(x) for x in data['operationPassee'] ]
348 balance = data['soldeDispo'] 405 balance = data['soldeDispo']
349 406
376 sendmail(message) 433 sendmail(message)
377 434
378 curs.executemany('''INSERT INTO transactions (id) VALUES (?)''', ((x.id, ) for x in unseen)) 435 curs.executemany('''INSERT INTO transactions (id) VALUES (?)''', ((x.id, ) for x in unseen))
379 db.commit() 436 db.commit()
380 437
381 ## messages 438
439 def messages(conf):
440 db = sqlite3.connect(conf['DATABASE'])
441 db.execute('''CREATE TABLE IF NOT EXISTS messages (id TEXT PRIMARY KEY)''')
442
443 sendmail = Mailer(host=conf['SMTPHOST'],
444 port=conf['SMTPPORT'],
445 starttls=conf['SMTPSTARTTLS'],
446 username=conf['SMTPUSER'],
447 password=conf['SMTPPASSWD']).send
448
449 encrypt = GPG(conf['GNUPGHOME']).encrypt
450
451 remote = BNPParibas()
452 remote.login(conf['USERNAME'], conf['PASSWORD'])
453
382 data = remote.info() 454 data = remote.info()
383 info = data['abonnement'] 455 info = data['abonnement']
384 nnew = info['nombreMessageBMMNonLus'] + info['nombreMessageBilatNonLus'] 456 nnew = info['nombreMessageBMMNonLus'] + info['nombreMessageBilatNonLus']
385 457
386 data = remote.messages() 458 data = remote.messages()
405 477
406 curs.execute('''INSERT INTO messages (id) VALUES (?)''', (m['id'], )) 478 curs.execute('''INSERT INTO messages (id) VALUES (?)''', (m['id'], ))
407 db.commit() 479 db.commit()
408 480
409 481
482 def records(conf):
483 db = sqlite3.connect(conf['DATABASE'])
484 db.execute('''CREATE TABLE IF NOT EXISTS records (id TEXT PRIMARY KEY)''')
485
486 remote = BNPParibas()
487 data = remote.login(conf['USERNAME'], conf['PASSWORD'])
488
489 records = remote.records()
490 for r in records:
491
492 curs = db.cursor()
493 curs.execute('''SELECT COUNT(*) FROM records WHERE id = ?''', (r['idDoc'], ))
494 if curs.fetchone()[0]:
495 # already handled
496 continue
497
498 data = remote.document(r)
499 date = datetime.strptime(r['dateDoc'], '%d/%m/%Y').strftime('bnpparibas-%Y%m%d.pdf')
500 filename = os.path.join(conf['DATADIR'], 'data', date)
501 if conf.get('VERBOSE'):
502 print(r['idDoc'], filename)
503 with open(filename, 'wb') as fd:
504 fd.write(data)
505
506 curs.execute('''INSERT INTO records (id) VALUES (?)''', (r['idDoc'], ))
507 db.commit()
508
509
510 @click.command()
511 @click.argument('conffile')
512 @click.option('--transactions', 'what', multiple=True, flag_value='transactions', help='Email new transactions.')
513 @click.option('--messages', 'what', multiple=True, flag_value='messages', help='Email new messages.')
514 @click.option('--records', 'what', multiple=True, flag_value='records', help='Download new montly records.')
515 @click.option('--verbose', is_flag=True, help='Verbose output.')
516 def main(conffile, what, verbose):
517
518 actions = {'transactions': transactions,
519 'messages': messages,
520 'records': records}
521
522 conf = loadconf(conffile)
523 if verbose:
524 conf['VERBOSE'] = True
525
526 for x in what:
527 action = actions.get(x)
528 action(conf)
529
530
410 if __name__ == '__main__': 531 if __name__ == '__main__':
411 main() 532 main()