Browse Source

support for namecheap; indiegogo: added support for non reward confirms in english

Fabian Peter Hammerle 7 years ago
parent
commit
3e406857a6

+ 16 - 0
dingguo/__init__.py

@@ -14,6 +14,8 @@ yaml.Loader.add_constructor(
     lambda loader, node: unicode(loader.construct_scalar(node)),
     )
 ioex.datetimeex.register_yaml_timestamp_constructor(yaml.Loader)
+ioex.datetimeex.Duration.register_yaml_constructor(yaml.Loader)
+ioex.datetimeex.Duration.register_yaml_representer(yaml.Dumper)
 ioex.datetimeex.Period.register_yaml_constructor(yaml.Loader)
 ioex.datetimeex.Period.register_yaml_representer(yaml.Dumper)
 
@@ -364,6 +366,20 @@ class Article(Item):
         assert delivery_date is None or type(delivery_date) is datetime.date
         self.delivery_date = delivery_date
 
+class Service(Item):
+
+    yaml_tag = u'!service'
+
+    def __init__(
+            self,
+            duration = None,
+            **kwargs
+            ):
+        super(Service, self).__init__(**kwargs)
+        if duration is not None:
+            assert type(duration) is ioex.datetimeex.Duration
+            self.duration = duration
+
 class Transportation(Item):
 
     yaml_tag = u'!transportation'

+ 2 - 0
dingguo/parser/__init__.py

@@ -12,6 +12,7 @@ import ingdiba
 import kickstarter
 import lieferservice
 import mytaxi
+import namecheap
 import oebb
 import thomann
 import uber
@@ -27,6 +28,7 @@ order_confirmation_parsers = [
     kickstarter.parse_order_confirmation_mail,
     lieferservice.parse_order_confirmation_mail,
     mytaxi.parse_order_confirmation_mail,
+    namecheap.parse_order_confirmation_mail,
     oebb.parse_order_confirmation_mail,
     thomann.parse_order_confirmation_mail,
     uber.parse_order_confirmation_mail,

+ 7 - 3
dingguo/parser/indiegogo.py

@@ -32,17 +32,21 @@ def parse_order_confirmation_mail(mail):
 
     doc = BeautifulSoup.BeautifulSoup(html)
 
-    campaign_name_tag = doc.find(text = re.compile(u'vielen Dank für deinen Beitrag')).findNext('a')
+    campaign_name_tag = doc.find(
+            text = re.compile(u'(vielen Dank für deinen Beitrag|thank you for contributing)')
+            ).findNext('a')
     reward_label_tag = doc.find(text = re.compile(u'Perk:'))
 
     order.items.append(dingguo.Contribution(
         campaign = dingguo.Campaign(
             name = campaign_name_tag.text,
-            founder = doc.find(text = re.compile(u'Kontaktiere den Kampagnenstarter')).findNext().text,
+            founder = doc.find(
+                text = re.compile(u'(Kontaktiere den Kampagnenstarter|Contact the campaign owner)')
+                ).findNext().text,
             website_url = campaign_name_tag['href'],
             ),
         price_brutto = dingguo.Sum.parse_text(
-            doc.find(text = re.compile(u'Unterstützung:')).findNext().text
+            doc.find(text = re.compile(u'(Unterstützung|Contribution):')).findNext().text
             ),
         reward = reward_label_tag.findNext().text if reward_label_tag else None,
         ))

+ 61 - 0
dingguo/parser/namecheap.py

@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+
+import BeautifulSoup
+import datetime
+import dateutil.parser
+import dingguo
+import email.message
+import ioex.datetimeex
+import re
+
+def parse_order_confirmation_mail(mail):
+
+    assert isinstance(mail, email.message.Message)
+
+    html = mail.get_payload()[1].get_payload(decode = True).decode('utf-8')
+
+    if not 'Namecheap' in html:
+        raise Exception()
+
+    doc = BeautifulSoup.BeautifulSoup(html)
+
+    email_date = dateutil.parser.parse(mail['date'])
+    order_date = dateutil.parser.parse(
+        doc.find(text = re.compile('Order Date:')).findNext(text = re.compile('\d+'))
+        ).replace(tzinfo = email_date.tzinfo)
+    # ensure timezone is correct
+    assert (email_date - order_date) < datetime.timedelta(minutes = 30)
+
+    order = dingguo.Order(
+        platform = u'namecheap',
+        order_id = doc.find(text = re.compile('Order Number:')).findNext().findNext().text,
+        order_date = order_date,
+        customer_id = doc.find(text = re.compile('User Name:')).findNext().findNext().text,
+        )
+
+    for item_row_tag in doc.find(text = re.compile('TITLE')).findParent('table').findAll('tr')[1:-2]:
+        title_tag, quantity_tag, duration_tag, price_tag, subtotal_tag = item_row_tag.findAll('td')
+        assert int(quantity_tag.text.replace('&nbsp;', '')) == 1
+        item = dingguo.Service(
+            name = title_tag.text.replace('\r\n', '').replace('&nbsp;', '').strip(),
+            price_brutto = dingguo.Sum.parse_text(price_tag.text.replace('&nbsp;', '')),
+            duration = ioex.datetimeex.Duration(
+                years = int(duration_tag.text.replace(' year&nbsp;', '')),
+                ),
+            )
+        order.items.append(item)
+        fee_tag = subtotal_tag.find('small')
+        if fee_tag:
+            fee_name, fee_price = fee_tag.text.split('  ')
+            order.items.append(dingguo.Service(
+                name = '%s %s' % (item.name, fee_name),
+                price_brutto = dingguo.Sum.parse_text(fee_price),
+                duration = item.duration,
+                ))
+
+    total_price = dingguo.Sum.parse_text(
+            doc.find(text = re.compile('^TOTAL$')).findNext('strong').text
+            )
+    assert total_price.value == sum([i.price_brutto.value for i in order.items])
+
+    return [order]

+ 1 - 1
setup.py

@@ -18,7 +18,7 @@ setup(
         ],
     scripts = glob.glob('scripts/*'),
     install_requires = [
-        'ioex>=0.5',
+        'ioex>=0.6',
         'pdfminer>=20140328', 
         'pytz',
         ],

+ 20 - 0
tests/test_comparison.py

@@ -4,6 +4,7 @@ import pytest
 
 import datetime
 import dingguo
+import ioex.datetimeex
 import os
 import pprint
 import pytz
@@ -14,12 +15,27 @@ def get_item_a():
             name = u'item',
             price_brutto = dingguo.Sum(1.0, u'EUR'),
             )
+
 def get_item_b():
     return dingguo.Item(
             name = u'item',
             price_brutto = dingguo.Sum(2.0, u'EUR'),
             )
 
+def get_service_a():
+    return dingguo.Service(
+            name = u'service',
+            price_brutto = dingguo.Sum(1.0, u'EUR'),
+            duration = ioex.datetimeex.Duration(years = 2),
+            )
+
+def get_service_b():
+    return dingguo.Service(
+            name = u'service',
+            price_brutto = dingguo.Sum(1.0, u'EUR'),
+            duration = ioex.datetimeex.Duration(),
+            )
+
 def get_discount_a():
     return dingguo.Discount(
             name = u'discount',
@@ -144,6 +160,8 @@ def get_shipping():
     [get_person_b(), get_person_b()],
     [get_pledge_a(), get_pledge_a()],
     [get_pledge_b(), get_pledge_b()],
+    [get_service_a(), get_service_a()],
+    [get_service_b(), get_service_b()],
     [get_shipping(), get_shipping()],
     ])
 def test_eq(a, b):
@@ -162,6 +180,7 @@ def test_eq(a, b):
     [get_contribution_a(), get_contribution_b()],
     [get_contribution_a(), get_item_b()],
     [get_discount_a(), get_discount_b()],
+    [get_discount_a(), get_item_b()],
     [get_item_a(), get_item_b()],
     [get_order_a(), get_order_a(items = False)],
     [get_order_a(), get_order_b()],
@@ -171,6 +190,7 @@ def test_eq(a, b):
     [get_pledge_a(), get_contribution_b()],
     [get_pledge_a(), get_item_b()],
     [get_pledge_a(), get_pledge_b()],
+    [get_service_a(), get_service_b()],
     ])
 def test_neq(a, b):
     assert a != b

+ 1 - 0
tests/test_parser.py

@@ -18,6 +18,7 @@ test_data_path = os.path.join(project_root_path, 'tests', 'data')
     ('kickstarter', os.path.join(test_data_path, 'kickstarter', 'mail_orbitkey.eml')),
     ('lieferservice.at', os.path.join(test_data_path, 'lieferservice.at', 'mail_1.eml')),
     ('mytaxi', os.path.join(test_data_path, 'mytaxi', 'mail_1.eml')),
+    ('namecheap', os.path.join(test_data_path, 'namecheap', 'mail_1_year_domain_renewal.eml')),
     ('oebb', os.path.join(test_data_path, 'oebb', 'mail_1.eml')),
     ('thomann', os.path.join(test_data_path, 'thomann', '1.eml')),
     ('uber', os.path.join(test_data_path, 'uber', 'mail_1.eml')),

+ 24 - 0
tests/test_parser_namecheap.py

@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+
+import pytest
+
+import dingguo.parser.namecheap
+import email
+import glob
+import os
+import test_yaml
+import yaml
+
+project_root_path = os.path.realpath(os.path.join(__file__, '..', '..'))
+test_data_path = os.path.join(project_root_path, 'tests', 'data', 'namecheap')
+
+@pytest.mark.parametrize('mail_path', glob.glob(os.path.join(test_data_path, '*.eml')))
+def test_parse_confirmation_mail(mail_path):
+    with open(mail_path) as mail:
+        parsed_orders = dingguo.parser.namecheap.parse_order_confirmation_mail(
+                email.message_from_file(mail)
+                )
+    with open(mail_path.replace('.eml', '.yml')) as yaml_file:
+        expected_orders = yaml.load(yaml_file.read())
+    assert expected_orders == parsed_orders, \
+            test_yaml.yaml_diff(expected_orders, parsed_orders)

+ 19 - 0
tests/test_yaml.py

@@ -34,6 +34,13 @@ def get_item_b():
             price_brutto = get_sum_b(),
             )
 
+def get_service():
+    return dingguo.Service(
+            name = u'service',
+            price_brutto = dingguo.Sum(1.0, u'EUR'),
+            duration = ioex.datetimeex.Duration(years = 2),
+            )
+
 def get_person_a():
     return dingguo.Person(
         first_name = u'Fabian Peter',
@@ -214,6 +221,12 @@ name: campaign a
 founder: company
 name: campaign without end
 website_url: http://campaign.com
+"""],
+    [get_service(), u"""!service
+duration: !duration
+  years: 2
+name: service
+price_brutto: !sum '1.0 EUR'
 """],
     [get_pledge(), u"""!pledge
 campaign: !campaign
@@ -359,6 +372,12 @@ end: 2016-07-23 09:23:17+01:05
 founder: company
 name: campaign without end
 website_url: http://campaign.com
+"""],
+    [get_service(), u"""!service
+duration: !duration
+  years: 2
+name: service
+price_brutto: !sum '1.0 EUR'
 """],
     [get_pledge(), u"""!pledge
 campaign: !campaign