Victoria's Secret module. Closes #1731

This commit is contained in:
Oleg Plakhotniuk 2015-02-24 23:27:13 -06:00 committed by Romain Bignon
commit 5113f32e59
5 changed files with 310 additions and 0 deletions

View file

@ -0,0 +1,23 @@
# -*- 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 <http://www.gnu.org/licenses/>.
from .module import VicSecModule
__all__ = ['VicSecModule']

194
modules/vicsec/browser.py Normal file
View file

@ -0,0 +1,194 @@
# -*- 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 <http://www.gnu.org/licenses/>.
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.capabilities.base import Currency
from weboob.capabilities.shop import OrderNotFound, Order, Item, Payment
from weboob.exceptions import BrowserIncorrectPassword
from datetime import datetime
from decimal import Decimal
from itertools import chain
import re
__all__ = ['VicSec']
class VicSecPage(HTMLPage):
@property
def logged(self):
return bool(self.doc.xpath(
'//a[@href="https://www.victoriassecret.com/account/profile"]'))
class LoginPage(VicSecPage):
def login(self, email, password):
form = self.get_form(name='accountLogonForm')
form['j_username'] = email
form['j_password'] = password
form.submit()
class HistoryPage(VicSecPage):
def iter_orders(self):
return [onum for date, onum in sorted(
chain(self.orders(), self.returns()), reverse=True)]
def orders(self):
for tr in self.doc.xpath('//table[@class="order-status"]/tbody[1]/tr'):
date = datetime.strptime(tr.xpath('td[1]/text()')[0], '%m/%d/%Y')
num = tr.xpath('td[2]/a/text()')[0]
status = tr.xpath('td[4]/span/text()')[0]
if status == u'Delivered':
yield date, num
def returns(self):
for tr in self.doc.xpath('//table[@class="order-status"]/tbody[3]/tr'):
num = tr.xpath('td[1]/a/text()')[0]
date = datetime.strptime(tr.xpath('td[2]/text()')[0], '%m/%d/%Y')
status = tr.xpath('td[4]/span/text()')[0]
if status == u'Complete':
yield date, num
class OrderPage(VicSecPage):
def order(self):
order = Order(id=self.order_number())
order.date = self.order_date()
order.tax = self.tax()
order.discount = self.discount()
order.shipping = self.shipping()
return order
def payments(self):
for tr in self.doc.xpath('//tbody[@class="payment-summary"]'
'//th[text()="Payment Summary"]/../../../tbody/tr'):
method = tr.xpath('td[1]/text()')[0]
amount = tr.xpath('td[2]')[0].text_content().strip()
pmt = Payment()
pmt.date = self.order_date()
pmt.method = unicode(method)
pmt.amount = AmTr.decimal_amount(amount)
yield pmt
def items(self):
for tr in self.doc.xpath('//tbody[@class="order-items"]/tr'):
label = tr.xpath('*//h1')[0].text_content()
price = AmTr.decimal_amount(re.match(r'^\s*([^\s]+)(\s+.*)?',
tr.xpath('*//div[@class="price"]')[0].text_content(),
re.DOTALL).group(1))
url = 'http:' + tr.xpath('*//img/@src')[0]
item = Item()
item.label = unicode(label)
item.url = unicode(url)
item.price = price
yield item
def is_void(self):
return not self.doc.xpath('//tbody[@class="order-items"]/tr')
def order_number(self):
return self.order_info(u'Order Number')
def order_date(self):
return datetime.strptime(self.order_info(u'Order Date'), '%m/%d/%Y')
def tax(self):
return self.payment_part(u'Sales Tax')
def shipping(self):
return self.payment_part(u'Shipping & Handling')
def order_info(self, which):
info = self.doc.xpath('//p[@class="orderinfo details"]'
)[0].text_content()
return re.match(u'.*%s:\\s+([^\\s]+)\\s'%which,info,re.DOTALL).group(1)
def discount(self):
# Sometimes subtotal doesn't add up with items.
# I saw that "Special Offer" was actually added to the item's price,
# instead of being subtracted. Looks like a bug on VS' side.
# To compensate for it I'm correcting discount value.
dcnt = self.payment_part(u'Special Offer')
subt = self.payment_part(u'Merchandise Subtotal')
rett = self.payment_part(u'Return Merchandise Total')
items = sum(i.price for i in self.items())
return dcnt + subt + rett - items
def payment_part(self, which):
for node in self.doc.xpath('//tbody[@class="payment-summary"]'
'//td[contains(text(),"%s")]/../td[2]' % which):
x = node.text_content().strip()
return Decimal(0) if x == u'FREE' else AmTr.decimal_amount(x)
return Decimal(0)
class VicSec(LoginBrowser):
BASEURL = 'https://www.victoriassecret.com'
login = URL(r'/account/signin/overlay$', LoginPage)
history = URL(r'/account/orderhistory$', HistoryPage)
order = URL(r'/account/orderdetails\?orderNumber=(?P<order_num>\d+)$',
r'/account/orderdetails.*$',
OrderPage)
unknown = URL(r'/.*$', VicSecPage)
def get_currency(self):
# Victoria's Secret uses only U.S. dollars.
return Currency.get_currency(u'$')
def get_order(self, id_):
return self.to_order(id_).order()
def iter_orders(self):
for order in self.to_history().iter_orders():
yield self.to_order(order).order()
def iter_payments(self, order):
return self.to_order(order.id).payments()
def iter_items(self, order):
return self.to_order(order.id).items()
@need_login
def to_history(self):
self.history.stay_or_go()
assert self.history.is_here()
return self.page
@need_login
def to_order(self, order_num):
self.order.stay_or_go(order_num=order_num)
assert self.order.is_here(order_num=order_num)
if self.page.is_void():
raise OrderNotFound()
return self.page
def do_login(self):
self.session.cookies.clear()
# Need to go there two times. Perhaps because of cookies...
self.login.go()
self.login.go().login(self.username, self.password)
self.history.go()
if not self.history.is_here():
raise BrowserIncorrectPassword()

BIN
modules/vicsec/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

60
modules/vicsec/module.py Normal file
View file

@ -0,0 +1,60 @@
# -*- 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 <http://www.gnu.org/licenses/>.
from weboob.capabilities.shop import CapShop
from weboob.tools.backend import Module, BackendConfig
from weboob.tools.value import ValueBackendPassword
from .browser import VicSec
__all__ = ['VicSecModule']
class VicSecModule(Module, CapShop):
NAME = 'vicsec'
MAINTAINER = u'Oleg Plakhotniuk'
EMAIL = 'olegus8@gmail.com'
VERSION = '1.1'
LICENSE = 'AGPLv3+'
DESCRIPTION = u'Victoria\'s Secret'
CONFIG = BackendConfig(
ValueBackendPassword('email', label='Username', masked=False),
ValueBackendPassword('password', label='Password'))
BROWSER = VicSec
def create_default_browser(self):
return self.create_browser(self.config['email'].get(),
self.config['password'].get())
def get_currency(self):
return self.browser.get_currency()
def get_order(self, id_):
return self.browser.get_order(id_)
def iter_orders(self):
return self.browser.iter_orders()
def iter_payments(self, order):
return self.browser.iter_payments(order)
def iter_items(self, order):
return self.browser.iter_items(order)

33
modules/vicsec/test.py Normal file
View file

@ -0,0 +1,33 @@
# -*- 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 <http://www.gnu.org/licenses/>.
from weboob.tools.test import BackendTest
class VicSecTest(BackendTest):
MODULE = 'vicsec'
def test_history(self):
"""
Test that at least one item was ordered in the whole history.
"""
b = self.backend
items = (i for o in b.iter_orders() for i in b.iter_items(o))
item = next(items, None)
self.assertNotEqual(item, None)