-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathlloyds.py
153 lines (129 loc) · 5.28 KB
/
lloyds.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
from datetime import date, datetime
from decimal import Decimal as D
import csv
import mechanize
import re
class Transaction(object):
""" Single transaction """
EXPLANATIONS = {
'BGC': 'Bank giro credit',
'BP': 'Bill payment',
'CD': 'Card payment * (followed by the last 4 digits of the card)',
'CHG': 'Charge',
'CHQ': 'Cheque',
'COMM': 'Commission',
'COR': 'Correction',
'CPT': 'Cashpoint',
'CSH': 'Cash',
'CSQ': 'Cash / Cheque',
'DD': 'Direct Debit',
'DEB': 'Debit card',
'DEP': 'Deposit',
'DR': 'Overdrawn balance',
'EUR': 'Euro cheque',
'FPI': 'Faster payments incoming',
'FPO': 'Faster payments outgoing',
'IB': 'Internet Banking',
'MTU': 'Mobile top up',
'PAY': 'Payment',
'PSV': 'Paysave',
'SAL': 'Salary',
'SO': 'Standing order',
'TFR': 'Transfer',
}
def __init__(self, fields):
dt = datetime.strptime(fields['Transaction Date'], '%d/%m/%Y')
self.date = date(dt.year, dt.month, dt.day)
self.transaction_type = fields['Transaction Type']
self.account_number = fields['Account Number']
self.sort_code = fields['Sort Code'].strip("'")
if fields['Debit Amount']:
self.amount = -D(fields['Debit Amount'])
else:
self.amount = D(fields['Credit Amount'])
self.balance = D(fields['Balance'])
self.card = None
self.raw_description = fields['Transaction Description']
self.description = self._parse_description()
def _parse_description(self):
""" Parse the cryptic description:
- remove date from end
- remove card detail
"""
desc = self.raw_description.strip()
end_date = self.date.strftime("%d%b%y").upper()
if desc.endswith(end_date):
desc = desc[:-len(end_date)].strip()
match = re.match('(.*?) CD (\d{4})$', desc)
if match:
desc = match.group(1).strip()
self.card = match.group(2)
return desc
def __str__(self):
return "Transaction(%s, %s, %s, %s)" % (
self.date.strftime('%d/%m/%Y'), self.get_type_explanation(),
self.description, self.amount)
def get_type_explanation(self):
""" Return explanation of type """
if self.transaction_type.startswith('CD'):
four_digits = self.transaction_type[len('CD '):]
return 'Paid by card **** **** **** %d' % four_digits
else:
return self.EXPLANATIONS.get(self.transaction_type, '')
class LloydsBank(object):
USER_AGENT = 'Mozilla/5.0 Gecko/20100101 Firefox/24.0'
LOGIN_URL = 'https://online.lloydsbank.co.uk/personal/logon/login.jsp'
def __init__(self):
self._agent = mechanize.Browser()
# Ignore robots, they prevent everybody
self._agent.set_handle_robots(False)
self._agent.addheaders = [('User-agent', self.USER_AGENT)]
self.accounts = []
def login(self, user_id, password, secret):
""" Log into internet account """
assert user_id and password and secret
# Login credentials
self._agent.open(self.LOGIN_URL)
form_name = 'frmLogin'
self._agent.select_form(form_name)
self._agent['frmLogin:strCustomerLogin_userID'] = user_id
self._agent['frmLogin:strCustomerLogin_pwd'] = password
response = self._agent.submit()
assert response.code == 200 and form_name not in response.read()
# Secret information
form_name = 'frmentermemorableinformation1'
self._agent.select_form(form_name)
prefix = '%s:strEnterMemorableInformation_memInfo' % form_name
for control in self._agent.form.controls:
if control.name.startswith(prefix):
label = control.get_labels()[0].text
position = int(re.findall(r'^Character (\d+) :$', label)[0])
# Make position to be 0-indexed
position -= 1
assert 0 <= position < len(secret)
self._agent[control.name] = [" " + secret[position]]
self._agent.submit()
assert 'Personal Account Overview' in self._agent.title()
# Save list of accounts
self.accounts = []
for link in self._agent.links():
attrs = dict(link.attrs)
if 'lkImageRetail' in attrs.get('id', ''):
self.accounts.append(link.absolute_url)
if 'ViewOnlineStatementsAnchor1' in attrs.get('class', ''):
self.accounts.append(link.absolute_url)
def get_transactions(self, account_url):
self._agent.open(account_url)
export_link = self._agent.find_link(text="Export")
self._agent.follow_link(export_link)
for form in self._agent.forms():
if form.name == 'export-statement-form':
self._agent.select_form(f.name)
self._agent['export-format'] = [
'Internet banking text/spreadsheet (.CSV)']
else:
# Fallback to the old form
self._agent.select_form('frmTest')
self._agent.submit()
rows = csv.DictReader(self._agent.response())
return [Transaction(row) for row in rows]