diff --git a/modules/kiwibank/README.md b/modules/kiwibank/README.md new file mode 100644 index 00000000..21f5acd8 --- /dev/null +++ b/modules/kiwibank/README.md @@ -0,0 +1,4 @@ +# [weboob-kiwibank](https://github.com/infertux/weboob-kiwibank) + +[Kiwibank](http://www.kiwibank.co.nz/) module for the [Weboob](http://weboob.org/) project + diff --git a/modules/kiwibank/__init__.py b/modules/kiwibank/__init__.py new file mode 100644 index 00000000..f12139c5 --- /dev/null +++ b/modules/kiwibank/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2015 Cédric Félizard +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + + +from .module import KiwibankModule + +__all__ = ['KiwibankModule'] diff --git a/modules/kiwibank/browser.py b/modules/kiwibank/browser.py new file mode 100644 index 00000000..5fdea61b --- /dev/null +++ b/modules/kiwibank/browser.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2015 Cédric Félizard +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + + +from weboob.browser import LoginBrowser, URL, need_login +from weboob.exceptions import BrowserIncorrectPassword +from .pages import LoginPage, AccountPage, HistoryPage + + +__all__ = ['Kiwibank'] + + +class HistoryUnavailable(Exception): + pass + + +class Kiwibank(LoginBrowser): + BASEURL = 'https://www.ib.kiwibank.co.nz/mobile/' + CERTHASH = ['5dc8be7430a2e37fab4dbfe232038ec60feed827d7ce0f68613532676962c197'] + TIMEOUT = 30 + + login = URL('login/', LoginPage) + login_error = URL('login-error/', LoginPage) + accounts = URL('accounts/$', AccountPage) + account = URL('/accounts/view/[0-9A-F]+$', HistoryPage) + + def do_login(self): + self.login.stay_or_go() + self.page.login(self.username, self.password) + + if self.login.is_here() or self.login_error.is_here(): + raise BrowserIncorrectPassword() + + @need_login + def get_accounts(self): + self.accounts.stay_or_go() + return self.page.get_accounts() + + @need_login + def get_history(self, account): + if account._link is None: + raise HistoryUnavailable() + + self.location(account._link) + + return self.page.get_history() diff --git a/modules/kiwibank/favicon.png b/modules/kiwibank/favicon.png new file mode 100644 index 00000000..1fab8861 Binary files /dev/null and b/modules/kiwibank/favicon.png differ diff --git a/modules/kiwibank/module.py b/modules/kiwibank/module.py new file mode 100644 index 00000000..f9ca1589 --- /dev/null +++ b/modules/kiwibank/module.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2015 Cédric Félizard +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + + +from weboob.capabilities.bank import CapBank, AccountNotFound +from weboob.capabilities.base import find_object +from weboob.tools.backend import Module, BackendConfig +from weboob.tools.value import ValueBackendPassword +from .browser import Kiwibank + + +__all__ = ['KiwibankModule'] + + +class KiwibankModule(Module, CapBank): + NAME = 'kiwibank' + MAINTAINER = u'Cédric Félizard' + EMAIL = 'cedric@felizard.fr' + VERSION = '1.0.0' + LICENSE = 'AGPLv3+' + DESCRIPTION = u'Kiwibank' + CONFIG = BackendConfig( + ValueBackendPassword('username', label='Username', masked=False), + ValueBackendPassword('password', label='Password'), + ) + BROWSER = Kiwibank + + def create_default_browser(self): + return self.create_browser(self.config['username'].get(), self.config['password'].get()) + + def iter_accounts(self): + return self.browser.get_accounts() + + def get_account(self, _id): + return find_object(self.browser.get_accounts(), id=_id, error=AccountNotFound) + + def iter_history(self, account): + for transaction in self.browser.get_history(account): + yield transaction diff --git a/modules/kiwibank/pages.py b/modules/kiwibank/pages.py new file mode 100644 index 00000000..6a1f00d4 --- /dev/null +++ b/modules/kiwibank/pages.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2015 Cédric Félizard +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + + +import datetime +import re +from decimal import Decimal +from weboob.browser.pages import HTMLPage, LoggedPage +from weboob.capabilities.bank import Account +from weboob.tools.capabilities.bank.transactions import AmericanTransaction as EnglishTransaction + + +__all__ = ['LoginPage', 'AccountPage', 'HistoryPage'] + + +class LoginPage(HTMLPage): + def login(self, username, password): + form = self.get_form(name='aspnetForm') + form['ctl00$chi$txtUserName'] = username + form['ctl00$chi$txtPassword'] = password + form.submit() + + +class AccountPage(LoggedPage, HTMLPage): + def get_accounts(self): + for el in self.doc.getroot().cssselect('div#content tr.row'): + account = Account() + + balance = el.cssselect('td.Balance')[0].text + account.balance = Decimal(Transaction.clean_amount(balance)) + account.id = el.cssselect('span')[0].text.strip() + account.currency = u'NZD' # TODO: handle other currencies + + if el.cssselect('td.AccountName > a'): + label_el = el.cssselect('td.AccountName > a')[0] + account._link = label_el.get('href') + else: + label_el = el.cssselect('td.AccountName')[0] + account._link = None + + account.label = unicode(label_el.text.strip()) + + yield account + + +class HistoryPage(LoggedPage, HTMLPage): + def get_history(self): + # TODO: get more results from "next" page, only 15 transactions per page + for el in self.doc.getroot().cssselect('div#content tr.row'): + transaction = Transaction() + + label = unicode(el.cssselect('td.tranDesc')[0].text) + transaction.label = label + + for pattern, _type in Transaction.PATTERNS: + match = pattern.match(label) + if match: + transaction.type = _type + break + + date = el.cssselect('td.tranDate')[0].text + transaction.date = datetime.datetime.strptime(date, '%d %b \'%y') + + amount = el.cssselect('td.tranAmnt')[0].text + transaction.amount = Decimal(Transaction.clean_amount(amount)) + + yield transaction + + +class Transaction(EnglishTransaction): + PATTERNS = [ + (re.compile(r'^POS W/D (?P.*)'), EnglishTransaction.TYPE_CARD), + (re.compile(r'^ATM W/D (?P.*)'), EnglishTransaction.TYPE_WITHDRAWAL), + (re.compile(r'^(PAY|FROM) (?P.*)'), EnglishTransaction.TYPE_TRANSFER), + ] diff --git a/modules/kiwibank/test.py b/modules/kiwibank/test.py new file mode 100644 index 00000000..fcb428ad --- /dev/null +++ b/modules/kiwibank/test.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2015 Cédric Félizard +# +# This file is part of weboob. +# +# weboob is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# weboob is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with weboob. If not, see . + + +from weboob.tools.test import BackendTest + + +__all__ = ['KiwibankTest'] + + +class KiwibankTest(BackendTest): + MODULE = 'kiwibank' + + def test_kiwibank(self): + l = list(self.backend.iter_accounts()) + if len(l) > 0: + a = l[0] + list(self.backend.iter_history(a))