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