diff --git a/modules/vicseccard/__init__.py b/modules/vicseccard/__init__.py new file mode 100644 index 00000000..0a1c9a4d --- /dev/null +++ b/modules/vicseccard/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2015 Oleg Plakhotniuk +# +# 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 VicSecCardModule + +__all__ = ['VicSecCardModule'] diff --git a/modules/vicseccard/browser.py b/modules/vicseccard/browser.py new file mode 100644 index 00000000..8154e742 --- /dev/null +++ b/modules/vicseccard/browser.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2015 Oleg Plakhotniuk +# +# 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 AccountNotFound, Account, Transaction +from weboob.tools.capabilities.bank.transactions import \ + AmericanTransaction as AmTr +from weboob.browser import LoginBrowser, URL, need_login +from weboob.browser.pages import HTMLPage +from weboob.exceptions import BrowserIncorrectPassword +from datetime import datetime + + +__all__ = ['VicSecCard'] + + +class SomePage(HTMLPage): + @property + def logged(self): + return bool(self.doc.xpath(u'//a[text()="Sign out"]')) + + +class LoginPage(SomePage): + def login(self, username, password): + form = self.get_form(name='frmLogin') + form['username_input'] = username + form['userName'] = username + form['password_input'] = password + form['hiddenPassword'] = password + form['btnLogin'] = 'btnLogin' + form.submit() + + +class HomePage(SomePage): + def account(self): + id_ = self.doc.xpath(u'//h1[contains(text(),' + '"Information for account ending in")]/text()')[0].strip()[-4:] + balance = self.amount(u'Current balance') + cardlimit = self.amount(u'Credit limit') + paymin = self.amount(u'Minimum payment') + a = Account() + a.id = id_ + a.label = u'ACCOUNT ENDING IN %s' % id_ + a.currency = Account.get_currency(balance) + a.balance = -AmTr.decimal_amount(balance) + a.type = Account.TYPE_CARD + a.cardlimit = AmTr.decimal_amount(cardlimit) + a.paymin = AmTr.decimal_amount(paymin) + #TODO: Add paydate. + #Oleg: I don't have an account with scheduled payment. + # Need to wait for a while... + return a + + def amount(self, name): + return self.doc.xpath( + u'//td[contains(text(),"%s")]/../td[2]/text()' % name)[0].strip() + + +class RecentPage(SomePage): + def iter_transactions(self): + for tr in self.doc.xpath('//table[@id="allTransactionList_table1"]' + '/tbody/tr'): + date = tr.xpath('td[1]/text()')[0] + label = u''.join(x.strip() for x in tr.xpath('td[2]/a/text()') + + tr.xpath('td[2]/text()')) + amount = tr.xpath('td[4]/text()')[0] + t = Transaction() + t.date = datetime.strptime(date, '%m/%d/%Y') + t.rdate = datetime.strptime(date, '%m/%d/%Y') + t.type = Transaction.TYPE_UNKNOWN + t.raw = unicode(label) + t.label = unicode(label) + t.amount = -AmTr.decimal_amount(amount) + yield t + + +class VicSecCard(LoginBrowser): + BASEURL = 'https://c.comenity.net' + login = URL(r'/victoriassecret/$', LoginPage) + home = URL(r'/victoriassecret/secure/SecureHome.xhtml', HomePage) + recent = URL(r'/victoriassecret/secure/accountactivity/Transactions.xhtml', + RecentPage) + unknown = URL('.*', SomePage) + + def get_account(self, id_): + a = next(self.iter_accounts()) + if (a.id != id_): + raise AccountNotFound() + return a + + @need_login + def iter_accounts(self): + yield self.home.stay_or_go().account() + + @need_login + def iter_history(self, account): + for trans in self.recent.stay_or_go().iter_transactions(): + yield trans + + def do_login(self): + self.session.cookies.clear() + self.login.go().login(self.username, self.password) + if not self.page.logged: + raise BrowserIncorrectPassword() diff --git a/modules/vicseccard/favicon.png b/modules/vicseccard/favicon.png new file mode 100644 index 00000000..34624cf5 Binary files /dev/null and b/modules/vicseccard/favicon.png differ diff --git a/modules/vicseccard/module.py b/modules/vicseccard/module.py new file mode 100644 index 00000000..d3942eb1 --- /dev/null +++ b/modules/vicseccard/module.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2015 Oleg Plakhotniuk +# +# 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 +from weboob.tools.backend import Module, BackendConfig +from weboob.tools.value import ValueBackendPassword + +from .browser import VicSecCard + + +__all__ = ['VicSecCardModule'] + + +class VicSecCardModule(Module, CapBank): + NAME = 'vicseccard' + MAINTAINER = u'Oleg Plakhotniuk' + EMAIL = 'olegus8@gmail.com' + VERSION = '1.1' + LICENSE = 'AGPLv3+' + DESCRIPTION = u'Victoria\'s Secret Angel Card' + CONFIG = BackendConfig( + ValueBackendPassword('username', label='User name', masked=False), + ValueBackendPassword('password', label='Password')) + BROWSER = VicSecCard + + def create_default_browser(self): + return self.create_browser(self.config['username'].get(), + self.config['password'].get()) + + def iter_accounts(self): + return self.browser.iter_accounts() + + def get_account(self, id_): + return self.browser.get_account(id_) + + def iter_history(self, account): + return self.browser.iter_history(account) diff --git a/modules/vicseccard/test.py b/modules/vicseccard/test.py new file mode 100644 index 00000000..14cf85bc --- /dev/null +++ b/modules/vicseccard/test.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2015 Oleg Plakhotniuk +# +# 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 +from itertools import chain + + +class VicSecCardTest(BackendTest): + MODULE = 'vicseccard' + + def test_history(self): + """ + Test that there's at least one transaction in the whole history. + """ + b = self.backend + ts = chain(*[b.iter_history(a) for a in b.iter_accounts()]) + t = next(ts, None) + self.assertNotEqual(t, None)