annotate brinksmoney.py @ 0:72fab2710469 default tip

Import
author Daniele Nicolodi <daniele@grinta.net>
date Fri, 05 Aug 2016 23:16:31 -0600
parents
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
0
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
1 import email
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
2 import imp
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
3 import itertools
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
4 import json
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
5 import os.path
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
6 import smtplib
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
7 import smtplib
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
8 import sqlite3
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
9 import sqlite3
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
10 import subprocess
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
11
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
12 import re
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
13 import csv
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
14
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
15 from contextlib import contextmanager
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
16 from datetime import datetime, date, timedelta
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
17 from email.mime.text import MIMEText
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
18 from email.utils import format_datetime, localtime, parseaddr
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
19 from base64 import b32encode
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
20 from hashlib import sha1
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
21 from urllib.parse import urljoin
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
22
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
23 import click
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
24 import requests
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
25
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
26
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
27 URL = 'https://www.brinksmoney.com/'
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
28
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
29
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
30 # transactions table row template
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
31 TRANSACTION = """{0.shortid:} {0.date:%d-%m-%Y} {0.description:57s} {0.amount:+8.2f}"""
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
32
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
33 # transactions table header
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
34 HEADER = """{:14s} {:10s} {:57s} {:>8s}""".format('Id', 'Date', 'Description', 'Amount')
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
35
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
36 # transactions table footer
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
37 FOOTER = """{:14s} {:10s} {:57s} {{balance:8.2f}}""".format('', '', 'BALANCE')
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
38
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
39 # transactions table horizontal separator
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
40 SEP = """-""" * len(HEADER)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
41
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
42
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
43 # GPG encrypted text is ascii and as such does not require encoding
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
44 # but its decrypted form is utf-8 and therefore the charset header
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
45 # must be set accordingly. define an appropriate charset object
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
46 email.charset.add_charset('utf8 7bit', header_enc=email.charset.SHORTEST,
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
47 body_enc=None, output_charset='utf-8')
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
48
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
49
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
50 def prevmonth(d):
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
51 year = d.year
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
52 month = d.month - 1
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
53 if month < 1:
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
54 year = yeat - 1
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
55 return date(year, month, 1)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
56
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
57
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
58 class Mailer:
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
59 def __init__(self, host='localhost', port=25, starttls=True, username=None, password=None):
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
60 self.host = host
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
61 self.port = port
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
62 self.starttls = starttls
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
63 self.username = username
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
64 self.password = password
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
65
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
66 @contextmanager
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
67 def connect(self):
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
68 smtp = smtplib.SMTP(self.host, self.port)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
69 if self.starttls:
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
70 smtp.starttls()
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
71 if self.username:
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
72 smtp.login(self.username, self.password)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
73 yield smtp
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
74 smtp.quit()
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
75
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
76 def send(self, message, fromaddr=None, toaddr=None):
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
77 if not fromaddr:
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
78 fromaddr = message['From']
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
79 if not toaddr:
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
80 toaddr = message['To']
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
81 with self.connect() as conn:
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
82 conn.sendmail(fromaddr, toaddr, str(message))
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
83
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
84
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
85 class GPG:
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
86 def __init__(self, homedir):
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
87 self.homedir = homedir
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
88
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
89 def encrypt(self, message, sender, recipient):
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
90 sender = parseaddr(sender)[1]
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
91 recipient = parseaddr(recipient)[1]
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
92 cmd = [ "gpg2", "--homedir", self.homedir, "--sign", "--encrypt",
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
93 "--batch", "--no-options", "--yes", "--armor",
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
94 "--local-user", sender, "--recipient", recipient, ]
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
95 p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
96 encdata, err = p.communicate(input=message.encode('utf-8'))
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
97 if p.returncode:
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
98 raise RuntimeError(p.returncode, err)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
99 return encdata.decode('ascii')
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
100
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
101
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
102 def normalize(s):
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
103 s = s.strip()
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
104 m = re.match(r'Debit: (Signature|PIN) purchase from \d*\s+(.*)', s)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
105 if m:
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
106 return m.group(2)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
107 m = re.match(r'Debit: ATM Cash Withdrawal at \d*\s+(.*)', s)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
108 if m:
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
109 return 'ATM ' + m.group(1)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
110 m = re.match(r'Debit: (ATM Cash Withdrawal Fee .*)', s)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
111 if m:
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
112 return m.group(1).upper()
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
113 m = re.match(r'Credit: Direct Deposit from (.+) for (.+)', s)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
114 if m:
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
115 return m.group(2) + ' ' + m.group(1)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
116 return s
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
117
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
118
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
119 class Transaction(object):
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
120 __slots__ = 'id', 'shortid', 'date', 'description', 'amount', 'balance'
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
121
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
122 def __init__(self, date, description, amount, balance):
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
123 r = repr((date, description, amount, balance))
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
124 self.id = b32encode(sha1(r.encode('utf8')).digest()).decode()
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
125 self.shortid = self.id[-14:]
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
126 self.date = date
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
127 self.description = description
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
128 self.amount = amount
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
129 self.balance = balance
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
130
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
131 @classmethod
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
132 def fromcsv(cls, x):
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
133 if x[2]:
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
134 amount = -float(re.sub('[^\d.]', '', x[2]))
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
135 if x[3]:
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
136 amount = +float(re.sub('[^\d.]', '', x[3]))
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
137 data = {'date': datetime.strptime(x[0], '%m/%d/%Y %I:%M%p'),
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
138 'description': normalize(x[1]),
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
139 'amount': amount,
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
140 'balance': float(re.sub('[^\d.]', '', x[4]))}
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
141 return cls(**data)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
142
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
143 def __str__(self):
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
144 return TRANSACTION.format(self)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
145
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
146
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
147 class BRINKSMoney(object):
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
148 def __init__(self):
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
149 self.session = requests.Session()
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
150
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
151 def login(self, username, password):
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
152 url = urljoin(URL, 'account/authenticate.m')
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
153
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
154 # acquire session id
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
155 r = self.session.get(url)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
156 r.raise_for_status()
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
157
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
158 # credentials
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
159 data = { 'blackBox': '',
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
160 'identifier': username,
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
161 'secret': password,
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
162 'type': 'pwd',
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
163 'login': 'Log In' }
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
164 r = self.session.post(url, data=data)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
165 r.raise_for_status()
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
166
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
167 def transactions(self, year, month):
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
168 url = urljoin(URL, 'account/acctHistory.m')
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
169
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
170 months = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
171 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
172
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
173 datestr = '{} {}'.format(months[month - 1], year)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
174
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
175 params = { 'selectedAcct': 'CARD',
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
176 'CSV': 'true',
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
177 'selectedMonth': datestr }
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
178
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
179 r = self.session.get(url, params=params)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
180 r.raise_for_status()
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
181
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
182 lines = r.text.splitlines()
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
183
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
184 # skip header and footer
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
185 lines = lines[10:-9]
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
186
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
187 rows = csv.reader(lines, doublequote=False, strict=True)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
188 transactions = [ Transaction.fromcsv(row) for row in rows ]
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
189
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
190 return transactions
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
191
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
192
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
193 def transactions(conf):
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
194 db = sqlite3.connect(conf['DATABASE'])
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
195 db.execute('''CREATE TABLE IF NOT EXISTS transactions (id TEXT PRIMARY KEY)''')
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
196
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
197 sendmail = Mailer(host=conf['SMTPHOST'],
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
198 port=conf['SMTPPORT'],
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
199 starttls=conf['SMTPSTARTTLS'],
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
200 username=conf['SMTPUSER'],
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
201 password=conf['SMTPPASSWD']).send
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
202
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
203 encrypt = GPG(conf['GNUPGHOME']).encrypt
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
204
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
205 remote = BRINKSMoney()
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
206 remote.login(conf['USERNAME'], conf['PASSWORD'])
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
207
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
208 this = date.today().replace(day=1)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
209 prev = prevmonth(this)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
210
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
211 recent = ( remote.transactions(this.year, this.month) +
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
212 remote.transactions(prev.year, prev.month) )
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
213
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
214 recent.sort(key=lambda x: x.date)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
215
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
216 balance = recent[-1].balance
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
217
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
218 curs = db.cursor()
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
219 unseen = []
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
220 for t in recent:
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
221 curs.execute('''SELECT COUNT(*) FROM transactions WHERE id = ?''', (t.id, ))
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
222 if not curs.fetchone()[0]:
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
223 # not seen before
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
224 unseen.append(t)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
225
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
226 if unseen:
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
227 lines = []
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
228 lines.append(HEADER)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
229 lines.append(SEP)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
230 for t in unseen:
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
231 lines.append(str(t))
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
232 lines.append(SEP)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
233 lines.append(FOOTER.format(balance=balance))
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
234
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
235 text = '\n'.join(lines)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
236 payload = encrypt(text, conf['MAILFROM'], conf['MAILTO'])
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
237
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
238 message = MIMEText(payload, _charset='utf8 7bit')
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
239 message['Subject'] = 'BRINKS Money Account update'
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
240 message['From'] = conf['MAILFROM']
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
241 message['To'] = conf['MAILTO']
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
242 message['Date'] = format_datetime(localtime())
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
243
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
244 sendmail(message)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
245
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
246 curs.executemany('''INSERT INTO transactions (id) VALUES (?)''', ((x.id, ) for x in unseen))
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
247 db.commit()
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
248
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
249
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
250 def loadconf(filename):
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
251 module = imp.new_module('conf')
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
252 module.__file__ = filename
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
253 with open(filename) as fd:
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
254 exec(compile(fd.read(), filename, 'exec'), module.__dict__)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
255 conf = {}
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
256 for key in dir(module):
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
257 if key.isupper():
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
258 conf[key] = getattr(module, key)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
259
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
260 conf['DATADIR'] = os.path.dirname(filename)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
261 for key in 'DATABASE', 'GNUPGHOME':
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
262 # if path is not absolute, it is interpreted as relative
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
263 # to the location of the configuration file
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
264 if not os.path.isabs(conf[key]):
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
265 conf[key] = os.path.join(conf['DATADIR'], conf[key])
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
266
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
267 return conf
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
268
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
269
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
270 @click.command()
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
271 @click.argument('conffile')
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
272 @click.option('--verbose', is_flag=True, help='Verbose output.')
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
273 def main(conffile, verbose):
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
274
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
275 conf = loadconf(conffile)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
276 if verbose:
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
277 conf['VERBOSE'] = True
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
278
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
279 transactions(conf)
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
280
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
281
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
282 if __name__ == '__main__':
Daniele Nicolodi <daniele@grinta.net>
parents:
diff changeset
283 main()