annotate bnpparibas.py @ 6:13a8bc43bc09

Simplify package structure
author Daniele Nicolodi <daniele@grinta.net>
date Mon, 11 Jan 2016 18:57:25 +0100
parents src/bnpparibas.py@a47012c9db15
children 90f4e0bd0c2d
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
0
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
1 import email
3
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
2 import imp
0
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
3 import os.path
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
4 import re
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
5 import smtplib
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
6 import sqlite3
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
7 import subprocess
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
8 import textwrap
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
9
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
10 from collections import namedtuple, defaultdict
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
11 from contextlib import contextmanager
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
12 from datetime import datetime
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
13 from decimal import Decimal
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
14 from email.mime.text import MIMEText
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
15 from email.utils import format_datetime, localtime, parseaddr
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
16 from io import BytesIO
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
17 from itertools import product, islice
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
18 from urllib.parse import urljoin
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
19
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
20 import bs4
3
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
21 import click
0
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
22 import requests
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
23
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
24 from PIL import Image
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
25
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
26
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
27 # message template
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
28 MESSAGE = """\
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
29 From: {sender:}
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
30 Subject: {subject:}
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
31 Date: {date:}
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
32 Message-Id: {id:}
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
33
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
34 {body:}
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
35 """
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
36
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
37 # transaction template
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
38 HEADER = '{:14s} {:10s} {:59s} {:>8s}'.format('Id', 'Date', 'Description', 'Amount')
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
39 TRANSACTION = '{id:} {date:%d/%m/%Y} {descr:59s} {amount:>8s}'
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
40
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
41 # as defined in bnpbaribas web app
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
42 CATEGORIES = {
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
43 '1': 'Alimentation',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
44 '7': 'Logement',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
45 '8': 'Loisirs',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
46 '9': 'Transport',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
47 '12': 'Opérations bancaires',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
48 '13': 'Non défini',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
49 '14': 'Multimédia',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
50 '20': 'Energies',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
51 '22': 'Retrait',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
52 '23': 'Sorties',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
53 'R58': 'Non défini',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
54 }
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
55
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
56 # euro symbol
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
57 EURO = b'\xe2\x82\xac'.decode('utf-8')
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
58
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
59
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
60 # load configuration
3
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
61 def loadconfig(filename):
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
62 module = imp.new_module('config')
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
63 module.__file__ = filename
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
64 try:
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
65 with open(filename) as fd:
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
66 exec(compile(fd.read(), filename, 'exec'), module.__dict__)
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
67 except IOError as e:
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
68 e.strerror = 'Unable to load configuration file (%s)' % e.strerror
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
69 raise
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
70 config = {}
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
71 for key in dir(module):
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
72 if key.isupper():
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
73 config[key] = getattr(module, key)
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
74 return config
0
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
75
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
76
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
77 # GPG encrypted text is ascii and as such does not require encoding
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
78 # but its decrypted form is utf-8 and therefore the charset header
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
79 # must be set accordingly. define an appropriate charset object
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
80 email.charset.add_charset('utf8 7bit', header_enc=email.charset.SHORTEST,
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
81 body_enc=None, output_charset='utf-8')
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
82
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
83
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
84 Message = namedtuple('Message', 'id read icon sender subject date validity'.split())
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
85
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
86
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
87 class Transaction:
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
88 def __init__(self, tid, date, descr, debit, credit, category):
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
89 self.id = tid
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
90 self.date = date
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
91 self.descr = descr
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
92 self.debit = debit
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
93 self.credit = credit
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
94 self.category = category
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
95
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
96 def __str__(self):
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
97 # there does not seem to be an easy way to format Decimal
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
98 # objects with a leading sign in both the positive and
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
99 # negative value cases so do it manually
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
100 d = vars(self)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
101 if d['debit']:
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
102 d['amount'] = '-' + str(d['debit'])
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
103 if d['credit']:
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
104 d['amount'] = '+' + str(d['credit'])
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
105 return TRANSACTION.format(**d)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
106
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
107
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
108 def imslice(image):
2
ad577744dd8e Drop dependency on numpy
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents: 0
diff changeset
109 for y, x in product(range(0, 5), range(0, 5)):
ad577744dd8e Drop dependency on numpy
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents: 0
diff changeset
110 yield image.crop((27 * x + 1, 27 * y + 1, 27 * (x + 1), 27 * (y + 1)))
0
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
111
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
112
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
113 def imdecode(image):
2
ad577744dd8e Drop dependency on numpy
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents: 0
diff changeset
114 # load reference keypad
ad577744dd8e Drop dependency on numpy
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents: 0
diff changeset
115 keypad = Image.open(os.path.join(os.path.dirname(__file__), 'keypad.png')).convert('L')
ad577744dd8e Drop dependency on numpy
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents: 0
diff changeset
116 keypad = [ keypad.crop((26 * i, 0, 26 * (i + 1), 26)) for i in range(10) ]
0
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
117 immap = {}
2
ad577744dd8e Drop dependency on numpy
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents: 0
diff changeset
118 for n, tile in enumerate(imslice(image)):
ad577744dd8e Drop dependency on numpy
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents: 0
diff changeset
119 # skip tiles with background only
ad577744dd8e Drop dependency on numpy
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents: 0
diff changeset
120 if tile.getextrema()[0] > 0:
0
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
121 continue
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
122 # compare to reference tiles
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
123 for d in range(0, 10):
2
ad577744dd8e Drop dependency on numpy
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents: 0
diff changeset
124 if tile == keypad[d]:
0
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
125 immap[d] = n + 1
2
ad577744dd8e Drop dependency on numpy
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents: 0
diff changeset
126 break
ad577744dd8e Drop dependency on numpy
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents: 0
diff changeset
127 if sorted(immap.keys()) != list(range(10)):
ad577744dd8e Drop dependency on numpy
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents: 0
diff changeset
128 raise ValueError('keypad decode failed')
0
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
129 return immap
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
130
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
131
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
132 def amountparse(value):
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
133 # empty
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
134 if value == '\xa0':
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
135 return None
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
136 m = re.match(r'\s+((?:\d+\.)?\d+,\d+)\s+([^\s]+)\s+$', value, re.U|re.S)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
137 if m is None:
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
138 raise ValueError(repr(value))
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
139 # euro
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
140 currency = m.group(2)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
141 if currency != EURO:
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
142 raise ValueError(repr(currency))
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
143 return Decimal(m.group(1).replace('.', '').replace(',', '.'))
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
144
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
145
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
146 class Site:
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
147 def __init__(self):
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
148 self.url = 'https://www.secure.bnpparibas.net'
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
149 self.req = requests.Session()
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
150
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
151 def login(self, user, passwd):
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
152 # login page
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
153 url = urljoin(self.url, '/banque/portail/particulier/HomeConnexion')
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
154 r = self.req.get(url, params={'type': 'homeconnex'})
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
155 r.raise_for_status()
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
156 # login form
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
157 soup = bs4.BeautifulSoup(r.text)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
158 form = soup.find('form', attrs={'name': 'logincanalnet'})
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
159 # extract relevant data
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
160 action = form['action']
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
161 data = { field['name']: field['value'] for field in form('input') }
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
162
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
163 # keyboard image url
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
164 tag = soup.find(attrs={'id': 'secret-nbr-keyboard'})
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
165 for prop in tag['style'].split(';'):
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
166 match = re.match(r'background-image:\s+url\(\'(.*)\'\)\s*', prop)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
167 if match:
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
168 src = match.group(1)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
169 break
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
170 # download keyboard image
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
171 r = self.req.get(urljoin(self.url, src))
2
ad577744dd8e Drop dependency on numpy
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents: 0
diff changeset
172 image = Image.open(BytesIO(r.content)).convert('L')
0
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
173 # decode digits position
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
174 passwdmap = imdecode(image)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
175
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
176 # encode password
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
177 passwdenc = ''.join('%02d' % passwdmap[d] for d in map(int, passwd))
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
178
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
179 # username and password
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
180 data['ch1'] = user
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
181 data['ch5'] = passwdenc
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
182
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
183 # post
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
184 r = self.req.post(urljoin(self.url, action), data=data)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
185 r.raise_for_status()
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
186 # redirection
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
187 m = re.search(r'document\.location\.replace\(\"(.+)\"\)', r.text)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
188 dest = m.group(1)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
189 r = self.req.get(dest)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
190 r.raise_for_status()
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
191
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
192 # check for errors
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
193 soup = bs4.BeautifulSoup(r.text)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
194 err = soup.find(attrs={'class': 'TitreErreur'})
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
195 if err:
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
196 raise ValueError(err.text)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
197
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
198
3
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
199 def recent(self, contract):
0
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
200 data = {
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
201 'BeginDate': '',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
202 'Categs': '',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
203 'Contracts': '',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
204 'EndDate': '',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
205 'OpTypes': '',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
206 'cboFlowName': 'flow/iastatement',
3
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
207 'contractId': contract,
0
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
208 'contractIds': '',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
209 'entryDashboard': '',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
210 'execution': 'e6s1',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
211 'externalIAId': 'IAStatements',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
212 'g1Style': 'expand',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
213 'g1Type': '',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
214 'g2Style': 'collapse',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
215 'g2Type': '',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
216 'g3Style': 'collapse',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
217 'g3Type': '',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
218 'g4Style': 'collapse',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
219 'g4Type': '',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
220 'groupId': '-2',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
221 'groupSelected': '-2',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
222 'gt': 'homepage:basic-theme',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
223 'pageId': 'releveoperations',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
224 'pastOrPendingOperations': '1',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
225 'sendEUD': 'true',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
226 'step': 'STAMENTS', }
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
227
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
228 url = urljoin(self.url, '/banque/portail/particulier/FicheA')
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
229 r = self.req.post(url, data=data)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
230 r.raise_for_status()
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
231 text = r.text
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
232
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
233 # the html is so broken beautifulsoup does not understand it
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
234 text = text.replace(
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
235 '<th class="thTitre" style="width:7%">Pointage </td>',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
236 '<th class="thTitre" style="width:7%">Pointage </th>')
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
237 s = bs4.BeautifulSoup(text)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
238
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
239 # extract transactions
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
240 table = s.find('table', id='tableCompte')
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
241 rows = table.find_all('tr')
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
242 for row in rows:
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
243 fields = row.find_all('td')
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
244 if not fields:
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
245 # skip headers row
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
246 continue
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
247 id = int(fields[0].input['id'].lstrip('_'))
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
248 date = datetime.strptime(fields[1].text, '%d/%m/%Y')
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
249 descr = fields[2].text.strip()
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
250 debit = amountparse(fields[3].text)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
251 credit = amountparse(fields[4].text)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
252 category = fields[5].text.strip()
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
253 categoryid = fields[6].span['class'][2][4:]
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
254 yield Transaction(id, date, descr, debit, credit, categoryid)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
255
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
256
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
257 def messages(self):
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
258 data = {
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
259 'identifiant': 'BmmFicheListerMessagesRecus_20100607022434',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
260 'type': 'fiche', }
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
261
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
262 url = urljoin(self.url, '/banque/portail/particulier/Fiche')
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
263 r = self.req.post(url, data=data)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
264 r.raise_for_status()
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
265 s = bs4.BeautifulSoup(r.text)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
266
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
267 # messages list
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
268 table = s.find('table', id='listeMessages')
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
269 for row in table.find_all('tr', recursive=False):
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
270 # skip headers and separators
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
271 if 'entete' in row['class']:
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
272 continue
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
273 # skip separators
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
274 if 'sep' in row['class']:
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
275 continue
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
276 # skip footer
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
277 if 'actions_bas' in row['class']:
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
278 continue
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
279 fields = row.find_all('td')
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
280 icon = fields[1].img['src']
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
281 sender = fields[2].text.strip()
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
282 subject = fields[4].a.text.strip()
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
283 date = datetime.strptime(fields[5]['data'], '%Y/%m/%d:%Hh%Mmin%Ssec')
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
284 validity = datetime.strptime(fields[6]['data'], '%Y/%m/%d:%Hh%Mmin%Ssec')
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
285 m = re.match(r'''validerFormulaire\('BmmFicheLireMessage_20100607022346','(.+)','(true|false)'\);$''', fields[4].a['onclick'])
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
286 mid = m.group(1)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
287 read = m.group(2) == 'false'
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
288 yield Message(mid, read, icon, sender, subject, date, validity)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
289
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
290
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
291 def message(self, mid):
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
292 data = {
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
293 'etape': 'boiteReception',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
294 'idMessage': mid,
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
295 'identifiant': 'BmmFicheLireMessage_20100607022346',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
296 'maxPagination': 2,
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
297 'minPagination': 1,
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
298 'nbElementParPage': 20,
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
299 'nbEltPagination': 5,
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
300 'nbPages': 2,
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
301 'newMsg': 'false',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
302 'pagination': 1,
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
303 'type': 'fiche',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
304 'typeAction': '', }
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
305
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
306 url = urljoin(self.url, '/banque/portail/particulier/Fiche')
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
307 r = self.req.post(url, data=data)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
308 r.raise_for_status()
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
309 # fix badly broken html
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
310 text = r.text.replace('<br>', '<br/>').replace('</br>', '')
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
311 s = bs4.BeautifulSoup(text)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
312
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
313 envelope = s.find('div', attrs={'class': 'enveloppe'})
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
314 rows = envelope.find_all('tr')
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
315 fields = rows[1].find_all('td')
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
316 # the messages list present a truncated sender
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
317 sender = fields[0].text.strip()
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
318 # not used
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
319 subject = fields[1].text.strip()
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
320 date = fields[2].text.strip()
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
321
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
322 content = s.find('div', attrs={'class': 'txtMessage'})
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
323 # clean up text
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
324 for t in content.find_all('style'):
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
325 t.extract()
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
326 for t in content.find_all('script'):
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
327 t.extract()
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
328 for t in content.find_all(id='info_pro'):
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
329 t.extract()
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
330 for t in content.find_all('br'):
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
331 t.replace_with('\n\n')
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
332 for t in content.find_all('b'):
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
333 if t.string:
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
334 t.replace_with('*%s*' % t.string.strip())
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
335 for t in content.find_all('li'):
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
336 t.replace_with('- %s\n\n' % t.text.strip())
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
337 # format nicely
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
338 text = re.sub(' +', ' ', content.text)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
339 text = re.sub(r'\s+([\.:])', r'\1', text)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
340 pars = []
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
341 for p in re.split('\n\n+', text):
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
342 p = p.strip()
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
343 if p:
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
344 pars.append('\n'.join(textwrap.wrap(p, 72)))
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
345 body = '\n\n'.join(pars)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
346 return sender, body
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
347
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
348
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
349 def transactions(self):
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
350 data = {'ch_memo': 'NON',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
351 'ch_rop_cpt_0': 'FR7630004001640000242975804',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
352 'ch_rop_dat': 'tous',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
353 'ch_rop_dat_deb': '',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
354 'ch_rop_dat_fin': '',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
355 'ch_rop_fmt_dat': 'JJMMAAAA',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
356 'ch_rop_fmt_fic': 'RTEXC',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
357 'ch_rop_fmt_sep': 'PT',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
358 'ch_rop_mon': 'EUR',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
359 'x': '55',
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
360 'y': '7'}
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
361 r = self.req.post(urljoin(self.url, '/SAF_TLC_CNF'), data=data)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
362 r.raise_for_status()
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
363 s = bs4.BeautifulSoup(r.text)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
364 path = s.find('a')['href']
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
365 r = self.req.get(urljoin(self.url, path))
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
366 r.raise_for_status()
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
367 return r.text
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
368
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
369
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
370 class Mailer:
3
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
371 def __init__(self, config):
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
372 self.server = config.get('SMTPSERVER', 'localhost')
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
373 self.port = config.get('SMTPPORT', 25)
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
374 self.starttls = config.get('SMTPSTARTTLS', False)
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
375 self.username = config.get('SMTPUSER', '')
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
376 self.password = config.get('SMTPPASSWD', '')
0
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
377
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
378 @contextmanager
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
379 def connect(self):
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
380 smtp = smtplib.SMTP(self.server, self.port)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
381 if self.starttls:
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
382 smtp.starttls()
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
383 if self.username:
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
384 smtp.login(self.username, self.password)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
385 yield smtp
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
386 smtp.quit()
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
387
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
388 def send(self, message, fromaddr=None, toaddr=None):
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
389 if not fromaddr:
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
390 fromaddr = message['From']
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
391 if not toaddr:
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
392 toaddr = message['To']
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
393 with self.connect() as conn:
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
394 conn.sendmail(fromaddr, toaddr, str(message))
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
395
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
396
3
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
397 class GPG:
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
398 def __init__(self, homedir):
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
399 self.homedir = homedir
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
400
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
401 def encrypt(self, message, sender, recipient):
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
402 sender = parseaddr(sender)[1]
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
403 recipient = parseaddr(recipient)[1]
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
404 cmd = [ "gpg", "--homedir", self.homedir, "--batch", "--yes", "--no-options", "--armor",
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
405 "--local-user", sender, "--recipient", recipient, "--sign", "--encrypt"]
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
406 p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
5
Daniele Nicolodi <daniele@grinta.net>
parents: 4
diff changeset
407 encdata, err = p.communicate(input=message.encode('utf-8'))
4
b4c2db70bbf2 Detect and report gpg invocations errors
Daniele Nicolodi <daniele@grinta.net>
parents: 3
diff changeset
408 if p.returncode:
b4c2db70bbf2 Detect and report gpg invocations errors
Daniele Nicolodi <daniele@grinta.net>
parents: 3
diff changeset
409 raise RuntimeError(p.returncode, err)
5
Daniele Nicolodi <daniele@grinta.net>
parents: 4
diff changeset
410 return encdata.decode('ascii')
0
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
411
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
412
3
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
413 @click.command()
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
414 @click.argument('filename')
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
415 def main(filename):
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
416 # load configuration
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
417 config = loadconfig(filename)
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
418
0
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
419 bnp = Site()
3
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
420 bnp.login(config['USERNAME'], config['PASSWORD'])
0
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
421
3
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
422 db = sqlite3.connect(config['DATABASE'])
0
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
423 db.execute('''CREATE TABLE IF NOT EXISTS messages (id TEXT PRIMARY KEY)''')
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
424 db.execute('''CREATE TABLE IF NOT EXISTS transactions (id INTEGER PRIMARY KEY)''')
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
425
3
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
426 mailer = Mailer(config)
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
427 encrypt = GPG(config['GNUPGHOME']).encrypt
0
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
428
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
429 ## unread messages
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
430 messages = filter(lambda x: not x.read, bnp.messages())
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
431 for m in sorted(messages, key=lambda x: x.date):
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
432 curs = db.cursor()
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
433 curs.execute('''SELECT IFNULL((SELECT id FROM messages WHERE id = ?), 0)''', (m.id, ))
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
434 if curs.fetchone()[0]:
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
435 # already handled
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
436 continue
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
437
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
438 # retrieve complete sender and message body
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
439 sender, body = bnp.message(m.id)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
440
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
441 # compose and send message
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
442 body = MESSAGE.format(id=m.id, sender=sender, date=m.date, subject=m.subject, body=body)
3
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
443 message = MIMEText(encrypt(body, config['MAILFROM'], config['MAILTO']), _charset='utf8 7bit')
0
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
444 message['Subject'] = 'BNP Paribas message'
3
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
445 message['From'] = config['MAILFROM']
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
446 message['To'] = config['MAILTO']
0
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
447 message['Date'] = format_datetime(localtime(m.date))
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
448 mailer.send(message)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
449
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
450 curs.execute('''INSERT INTO messages (id) VALUES (?)''', (m.id, ))
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
451 db.commit()
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
452
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
453
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
454 ## transactions
3
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
455 transactions = bnp.recent(config['CONTRACT'])
0
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
456 curs = db.cursor()
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
457 lines = []
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
458 for t in transactions:
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
459 curs.execute('''SELECT IFNULL((SELECT id FROM transactions WHERE id = ?), 0)''', (t.id, ))
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
460 if curs.fetchone()[0]:
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
461 # already handled
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
462 continue
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
463 lines.append(str(t))
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
464 curs.execute('''INSERT INTO transactions (id) VALUES (?)''', (t.id, ))
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
465
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
466 if lines:
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
467 lines.insert(0, HEADER)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
468 lines.insert(1, '-' * len(HEADER))
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
469 body = '\n'.join(lines)
3
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
470 message = MIMEText(encrypt(body, config['MAILFROM'], config['MAILTO']), _charset='utf8 7bit')
0
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
471 message['Subject'] = 'BNP Paribas update'
3
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
472 message['From'] = config['MAILFROM']
1311f6533978 Load configuration from file specified on the command line
Daniele Nicolodi <daniele@grinta.net>
parents: 2
diff changeset
473 message['To'] = config['MAILTO']
0
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
474 message['Date'] = format_datetime(localtime())
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
475 mailer.send(message)
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
476
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
477 db.commit()
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
478
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
479
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
480 if __name__ == '__main__':
Daniele Nicolodi <daniele.nicolodi@obspm.fr>
parents:
diff changeset
481 main()