diff --git a/changedetectionio/processors/restock_diff.py b/changedetectionio/processors/restock_diff.py index a40ad262a..09a09558a 100644 --- a/changedetectionio/processors/restock_diff.py +++ b/changedetectionio/processors/restock_diff.py @@ -1,5 +1,8 @@ from . import difference_detection_processor -from ..html_tools import xpath1_filter as xpath_filter # xpath1 is a lot faster and is sufficient here +from ..html_tools import xpath1_filter as xpath_filter +# xpath1 is a lot faster and is sufficient here +from ..html_tools import extract_json_as_string, has_ldjson_product_info + from copy import deepcopy from loguru import logger import hashlib @@ -36,9 +39,14 @@ def get_itemprop_availability(html_content): value = None try: - value = xpath_filter("//*[@itemtype='https://schema.org/Offer']//*[@itemprop='availability']/@href", html_content) + if has_ldjson_product_info(html_content): + value = extract_json_as_string(html_content.lower(), "json:$..offers.availability", ensure_is_ldjson_info_type=True) + +# value = xpath_filter("//*[@itemtype='https://schema.org/Offer']//*[@itemprop='availability']/@href", ldjson) + + if value: - value = re.sub(r'(?i)^http(s)+://schema.org/', '', value.strip()) + value = re.sub(r'(?i)^(https|http)://schema.org/', '', value.strip(' "\'')) except Exception as e: print("Exception getting get_itemprop_availability (itemprop='availability')", str(e)) @@ -82,13 +90,14 @@ class perform_site_check(difference_detection_processor): if availability: self.fetcher.instock_data = availability # Stored as the text snapshot + # @todo: Configurable? if any(availability in s for s in [ - 'InStock', - 'InStoreOnly', - 'LimitedAvailability', - 'OnlineOnly', - 'PreSale' # Debatable? + 'instock', + 'Instoreonly', + 'limitedavailability', + 'onlineonly', + 'presale' # Debatable? ]): update_obj['in_stock'] = True else: @@ -99,13 +108,14 @@ class perform_site_check(difference_detection_processor): # 'Possibly in stock' comes from stock-not-in-stock.js when no string found above the fold. update_obj['in_stock'] = True if self.fetcher.instock_data == 'Possibly in stock' else False logger.debug(f"Watch UUID {uuid} restock check returned '{self.fetcher.instock_data}' from JS scraper.") - else: + + if not self.fetcher.instock_data: raise UnableToExtractRestockData(status_code=self.fetcher.status_code) # Main detection method fetched_md5 = None - if self.fetcher.instock_data: - fetched_md5 = hashlib.md5(self.fetcher.instock_data.encode('utf-8')).hexdigest() + + fetched_md5 = hashlib.md5(self.fetcher.instock_data.encode('utf-8')).hexdigest() # The main thing that all this at the moment comes down to :) changed_detected = False diff --git a/changedetectionio/tests/test_restock_itemprop.py b/changedetectionio/tests/test_restock_itemprop.py new file mode 100644 index 000000000..6dd88ca7c --- /dev/null +++ b/changedetectionio/tests/test_restock_itemprop.py @@ -0,0 +1,69 @@ +#!/usr/bin/python3 +from flask import url_for +from .util import live_server_setup, wait_for_all_checks, extract_UUID_from_client + + + +instock_props = [ + '', + '', +] + +out_of_stock_props = [ + # out of stock AND contains multiples + '' +] + +def set_original_response(props_markup): + test_return_data = f""" + + Some initial text
+

Which is across multiple lines

+
+ So let's see what happens.
+
price: $10.99
+ {props_markup} + + + """ + + with open("test-datastore/endpoint-content.txt", "w") as f: + f.write(test_return_data) + return None + + + +def test_restock_itemprop_basic(client, live_server): + + live_server_setup(live_server) + + test_url = url_for('test_endpoint', _external=True) + + for p in instock_props: + set_original_response(props_markup=p) + client.post( + url_for("form_quick_watch_add"), + data={"url": test_url, "tags": '', 'processor': 'restock_diff'}, + follow_redirects=True + ) + wait_for_all_checks(client) + res = client.get(url_for("index")) + assert b' in-stock' in res.data + + res = client.get(url_for("form_delete", uuid="all"), follow_redirects=True) + assert b'Deleted' in res.data + + + for p in out_of_stock_props: + set_original_response(props_markup=p) + client.post( + url_for("form_quick_watch_add"), + data={"url": test_url, "tags": '', 'processor': 'restock_diff'}, + follow_redirects=True + ) + wait_for_all_checks(client) + res = client.get(url_for("index")) + assert b'not-in-stock' in res.data + + res = client.get(url_for("form_delete", uuid="all"), follow_redirects=True) + assert b'Deleted' in res.data \ No newline at end of file