Mercurial > hg > bnpparibas
comparison src/bnpparibas.py @ 3:1311f6533978
Load configuration from file specified on the command line
author | Daniele Nicolodi <daniele@grinta.net> |
---|---|
date | Wed, 25 Feb 2015 01:44:50 +0100 |
parents | ad577744dd8e |
children | b4c2db70bbf2 |
comparison
equal
deleted
inserted
replaced
2:ad577744dd8e | 3:1311f6533978 |
---|---|
1 import email | 1 import email |
2 import imp | |
2 import os.path | 3 import os.path |
3 import re | 4 import re |
4 import smtplib | 5 import smtplib |
5 import sqlite3 | 6 import sqlite3 |
6 import subprocess | 7 import subprocess |
15 from io import BytesIO | 16 from io import BytesIO |
16 from itertools import product, islice | 17 from itertools import product, islice |
17 from urllib.parse import urljoin | 18 from urllib.parse import urljoin |
18 | 19 |
19 import bs4 | 20 import bs4 |
21 import click | |
20 import requests | 22 import requests |
21 | 23 |
22 from PIL import Image | 24 from PIL import Image |
23 | 25 |
24 DB = 'bnpparibas.sqlite' | |
25 | 26 |
26 # message template | 27 # message template |
27 MESSAGE = """\ | 28 MESSAGE = """\ |
28 From: {sender:} | 29 From: {sender:} |
29 Subject: {subject:} | 30 Subject: {subject:} |
55 # euro symbol | 56 # euro symbol |
56 EURO = b'\xe2\x82\xac'.decode('utf-8') | 57 EURO = b'\xe2\x82\xac'.decode('utf-8') |
57 | 58 |
58 | 59 |
59 # load configuration | 60 # load configuration |
60 from config import * | 61 def loadconfig(filename): |
62 module = imp.new_module('config') | |
63 module.__file__ = filename | |
64 try: | |
65 with open(filename) as fd: | |
66 exec(compile(fd.read(), filename, 'exec'), module.__dict__) | |
67 except IOError as e: | |
68 e.strerror = 'Unable to load configuration file (%s)' % e.strerror | |
69 raise | |
70 config = {} | |
71 for key in dir(module): | |
72 if key.isupper(): | |
73 config[key] = getattr(module, key) | |
74 return config | |
61 | 75 |
62 | 76 |
63 # GPG encrypted text is ascii and as such does not require encoding | 77 # GPG encrypted text is ascii and as such does not require encoding |
64 # but its decrypted form is utf-8 and therefore the charset header | 78 # but its decrypted form is utf-8 and therefore the charset header |
65 # must be set accordingly. define an appropriate charset object | 79 # must be set accordingly. define an appropriate charset object |
145 # extract relevant data | 159 # extract relevant data |
146 action = form['action'] | 160 action = form['action'] |
147 data = { field['name']: field['value'] for field in form('input') } | 161 data = { field['name']: field['value'] for field in form('input') } |
148 | 162 |
149 # keyboard image url | 163 # keyboard image url |
150 src = '' | |
151 tag = soup.find(attrs={'id': 'secret-nbr-keyboard'}) | 164 tag = soup.find(attrs={'id': 'secret-nbr-keyboard'}) |
152 for prop in tag['style'].split(';'): | 165 for prop in tag['style'].split(';'): |
153 match = re.match(r'background-image:\s+url\(\'(.*)\'\)\s*', prop) | 166 match = re.match(r'background-image:\s+url\(\'(.*)\'\)\s*', prop) |
154 if match: | 167 if match: |
155 src = match.group(1) | 168 src = match.group(1) |
181 err = soup.find(attrs={'class': 'TitreErreur'}) | 194 err = soup.find(attrs={'class': 'TitreErreur'}) |
182 if err: | 195 if err: |
183 raise ValueError(err.text) | 196 raise ValueError(err.text) |
184 | 197 |
185 | 198 |
186 def recent(self): | 199 def recent(self, contract): |
187 data = { | 200 data = { |
188 'BeginDate': '', | 201 'BeginDate': '', |
189 'Categs': '', | 202 'Categs': '', |
190 'Contracts': '', | 203 'Contracts': '', |
191 'EndDate': '', | 204 'EndDate': '', |
192 'OpTypes': '', | 205 'OpTypes': '', |
193 'cboFlowName': 'flow/iastatement', | 206 'cboFlowName': 'flow/iastatement', |
194 'contractId': CONTRACT, | 207 'contractId': contract, |
195 'contractIds': '', | 208 'contractIds': '', |
196 'entryDashboard': '', | 209 'entryDashboard': '', |
197 'execution': 'e6s1', | 210 'execution': 'e6s1', |
198 'externalIAId': 'IAStatements', | 211 'externalIAId': 'IAStatements', |
199 'g1Style': 'expand', | 212 'g1Style': 'expand', |
353 r.raise_for_status() | 366 r.raise_for_status() |
354 return r.text | 367 return r.text |
355 | 368 |
356 | 369 |
357 class Mailer: | 370 class Mailer: |
358 def __init__(self): | 371 def __init__(self, config): |
359 self.server = SMTPSERVER | 372 self.server = config.get('SMTPSERVER', 'localhost') |
360 self.port = SMTPPORT | 373 self.port = config.get('SMTPPORT', 25) |
361 self.starttls = SMTPSTARTTLS | 374 self.starttls = config.get('SMTPSTARTTLS', False) |
362 self.username = SMTPUSER | 375 self.username = config.get('SMTPUSER', '') |
363 self.password = SMTPPASSWD | 376 self.password = config.get('SMTPPASSWD', '') |
364 | 377 |
365 @contextmanager | 378 @contextmanager |
366 def connect(self): | 379 def connect(self): |
367 smtp = smtplib.SMTP(self.server, self.port) | 380 smtp = smtplib.SMTP(self.server, self.port) |
368 if self.starttls: | 381 if self.starttls: |
379 toaddr = message['To'] | 392 toaddr = message['To'] |
380 with self.connect() as conn: | 393 with self.connect() as conn: |
381 conn.sendmail(fromaddr, toaddr, str(message)) | 394 conn.sendmail(fromaddr, toaddr, str(message)) |
382 | 395 |
383 | 396 |
384 def encrypt(message, sender, recipient): | 397 class GPG: |
385 sender = parseaddr(sender)[1] | 398 def __init__(self, homedir): |
386 recipient = parseaddr(recipient)[1] | 399 self.homedir = homedir |
387 cmd = [ "gpg", "--homedir", GNUPGHOME, "--batch", "--yes", "--no-options", "--armor", | 400 |
388 "--local-user", sender, "--recipient", recipient, "--sign", "--encrypt"] | 401 def encrypt(self, message, sender, recipient): |
389 p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | 402 sender = parseaddr(sender)[1] |
390 encdata = p.communicate(input=message.encode('utf-8'))[0].decode('ascii') | 403 recipient = parseaddr(recipient)[1] |
391 return encdata | 404 cmd = [ "gpg", "--homedir", self.homedir, "--batch", "--yes", "--no-options", "--armor", |
392 | 405 "--local-user", sender, "--recipient", recipient, "--sign", "--encrypt"] |
393 | 406 p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
394 def main(): | 407 encdata = p.communicate(input=message.encode('utf-8'))[0].decode('ascii') |
408 return encdata | |
409 | |
410 | |
411 @click.command() | |
412 @click.argument('filename') | |
413 def main(filename): | |
414 # load configuration | |
415 config = loadconfig(filename) | |
416 | |
395 bnp = Site() | 417 bnp = Site() |
396 bnp.login(USERNAME, PASSWORD) | 418 bnp.login(config['USERNAME'], config['PASSWORD']) |
397 | 419 |
398 db = sqlite3.connect(DB) | 420 db = sqlite3.connect(config['DATABASE']) |
399 db.execute('''CREATE TABLE IF NOT EXISTS messages (id TEXT PRIMARY KEY)''') | 421 db.execute('''CREATE TABLE IF NOT EXISTS messages (id TEXT PRIMARY KEY)''') |
400 db.execute('''CREATE TABLE IF NOT EXISTS transactions (id INTEGER PRIMARY KEY)''') | 422 db.execute('''CREATE TABLE IF NOT EXISTS transactions (id INTEGER PRIMARY KEY)''') |
401 | 423 |
402 mailer = Mailer() | 424 mailer = Mailer(config) |
425 encrypt = GPG(config['GNUPGHOME']).encrypt | |
403 | 426 |
404 ## unread messages | 427 ## unread messages |
405 messages = filter(lambda x: not x.read, bnp.messages()) | 428 messages = filter(lambda x: not x.read, bnp.messages()) |
406 for m in sorted(messages, key=lambda x: x.date): | 429 for m in sorted(messages, key=lambda x: x.date): |
407 curs = db.cursor() | 430 curs = db.cursor() |
413 # retrieve complete sender and message body | 436 # retrieve complete sender and message body |
414 sender, body = bnp.message(m.id) | 437 sender, body = bnp.message(m.id) |
415 | 438 |
416 # compose and send message | 439 # compose and send message |
417 body = MESSAGE.format(id=m.id, sender=sender, date=m.date, subject=m.subject, body=body) | 440 body = MESSAGE.format(id=m.id, sender=sender, date=m.date, subject=m.subject, body=body) |
418 message = MIMEText(encrypt(body, MAILFROM, MAILTO), _charset='utf8 7bit') | 441 message = MIMEText(encrypt(body, config['MAILFROM'], config['MAILTO']), _charset='utf8 7bit') |
419 message['Subject'] = 'BNP Paribas message' | 442 message['Subject'] = 'BNP Paribas message' |
420 message['From'] = MAILFROM | 443 message['From'] = config['MAILFROM'] |
421 message['To'] = MAILTO | 444 message['To'] = config['MAILTO'] |
422 message['Date'] = format_datetime(localtime(m.date)) | 445 message['Date'] = format_datetime(localtime(m.date)) |
423 mailer.send(message) | 446 mailer.send(message) |
424 | 447 |
425 curs.execute('''INSERT INTO messages (id) VALUES (?)''', (m.id, )) | 448 curs.execute('''INSERT INTO messages (id) VALUES (?)''', (m.id, )) |
426 db.commit() | 449 db.commit() |
427 | 450 |
428 | 451 |
429 ## transactions | 452 ## transactions |
430 transactions = bnp.recent() | 453 transactions = bnp.recent(config['CONTRACT']) |
431 curs = db.cursor() | 454 curs = db.cursor() |
432 lines = [] | 455 lines = [] |
433 for t in transactions: | 456 for t in transactions: |
434 curs.execute('''SELECT IFNULL((SELECT id FROM transactions WHERE id = ?), 0)''', (t.id, )) | 457 curs.execute('''SELECT IFNULL((SELECT id FROM transactions WHERE id = ?), 0)''', (t.id, )) |
435 if curs.fetchone()[0]: | 458 if curs.fetchone()[0]: |
440 | 463 |
441 if lines: | 464 if lines: |
442 lines.insert(0, HEADER) | 465 lines.insert(0, HEADER) |
443 lines.insert(1, '-' * len(HEADER)) | 466 lines.insert(1, '-' * len(HEADER)) |
444 body = '\n'.join(lines) | 467 body = '\n'.join(lines) |
445 message = MIMEText(encrypt(body, MAILFROM, MAILTO), _charset='utf8 7bit') | 468 message = MIMEText(encrypt(body, config['MAILFROM'], config['MAILTO']), _charset='utf8 7bit') |
446 message['Subject'] = 'BNP Paribas update' | 469 message['Subject'] = 'BNP Paribas update' |
447 message['From'] = MAILFROM | 470 message['From'] = config['MAILFROM'] |
448 message['To'] = MAILTO | 471 message['To'] = config['MAILTO'] |
449 message['Date'] = format_datetime(localtime()) | 472 message['Date'] = format_datetime(localtime()) |
450 mailer.send(message) | 473 mailer.send(message) |
451 | 474 |
452 db.commit() | 475 db.commit() |
453 | 476 |