diff --git a/changedetectionio/async_update_worker.py b/changedetectionio/async_update_worker.py index ff28f6a9..f796c9f6 100644 --- a/changedetectionio/async_update_worker.py +++ b/changedetectionio/async_update_worker.py @@ -309,6 +309,7 @@ async def async_update_worker(worker_id, q, notification_q, app, datastore): if not datastore.data['watching'].get(uuid): continue + logger.debug(f"Processing watch UUID: {uuid} - xpath_data length returned {len(update_handler.xpath_data) if update_handler.xpath_data else 'empty.'}") if process_changedetection_results: try: datastore.update_watch(uuid=uuid, update_obj=update_obj) diff --git a/changedetectionio/blueprint/ui/edit.py b/changedetectionio/blueprint/ui/edit.py index d394a906..12a932ea 100644 --- a/changedetectionio/blueprint/ui/edit.py +++ b/changedetectionio/blueprint/ui/edit.py @@ -223,19 +223,13 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMe watch = datastore.data['watching'].get(uuid) - # if system or watch is configured to need a chrome type browser - system_uses_webdriver = datastore.data['settings']['application']['fetch_backend'] == 'html_webdriver' - watch_needs_selenium_or_playwright = False - if (watch.get('fetch_backend') == 'system' and system_uses_webdriver) or watch.get('fetch_backend') == 'html_webdriver' or watch.get('fetch_backend', '').startswith('extra_browser_'): - watch_needs_selenium_or_playwright = True - - from zoneinfo import available_timezones - # Only works reliably with Playwright - # Import the global plugin system - from changedetectionio.pluggy_interface import collect_ui_edit_stats_extras + from changedetectionio.pluggy_interface import collect_ui_edit_stats_extras, get_fetcher_capabilities + + # Get fetcher capabilities instead of hardcoded logic + capabilities = get_fetcher_capabilities(watch, datastore) app_rss_token = datastore.data['settings']['application'].get('rss_access_token'), template_args = { 'available_processors': processors.available_processors(), @@ -266,7 +260,7 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMe 'using_global_webdriver_wait': not default['webdriver_delay'], 'uuid': uuid, 'watch': watch, - 'watch_needs_selenium_or_playwright': watch_needs_selenium_or_playwright, + 'capabilities': capabilities } included_content = None diff --git a/changedetectionio/blueprint/ui/templates/edit.html b/changedetectionio/blueprint/ui/templates/edit.html index bb4c50a0..c8389cb1 100644 --- a/changedetectionio/blueprint/ui/templates/edit.html +++ b/changedetectionio/blueprint/ui/templates/edit.html @@ -206,9 +206,8 @@ Math: {{ 1 + 1 }}") }}
- {% if watch_needs_selenium_or_playwright %} - {# Only works with playwright #} - {% if system_has_playwright_configured %} + {% if capabilities.supports_browser_steps %} + {% if visual_selector_data_ready %}
@@ -248,11 +247,7 @@ Math: {{ 1 + 1 }}") }}
{% else %} - {# it's configured to use selenium or chrome but system says its not configured #} - {{ playwright_warning() }} - {% if system_has_webdriver_configured %} - Selenium/Webdriver cant be used here because it wont fetch screenshots reliably. - {% endif %} + Visual Selector data is not ready, watch needs to be checked atleast once. {% endif %} {% else %} {# "This functionality needs chrome.." #} @@ -383,32 +378,28 @@ Math: {{ 1 + 1 }}") }}
- {% if watch_needs_selenium_or_playwright %} - {% if system_has_playwright_configured %} - - The Visual Selector tool lets you select the text elements that will be used for the change detection. It automatically fills-in the filters in the "CSS/JSONPath/JQ/XPath Filters" box of the Filters & Triggers tab. Use Shift+Click to select multiple items. - + {% if capabilities.supports_screenshots and capabilities.supports_xpath_element_data %} + {% if visual_selector_data_ready %} + + The Visual Selector tool lets you select the text elements that will be used for the change detection. It automatically fills-in the filters in the "CSS/JSONPath/JQ/XPath Filters" box of the Filters & Triggers tab. Use Shift+Click to select multiple items. + -
- Clear selection - - One moment, fetching screenshot and element information.. -
- -
Currently: Loading...
- {% else %} - {# The watch needed chrome but system says that playwright is not ready #} - {{ playwright_warning() }} - {% endif %} - {% if system_has_webdriver_configured %} - Selenium/Webdriver cant be used here because it wont fetch screenshots reliably. - {% endif %} +
+ Clear selection + + One moment, fetching screenshot and element information.. +
+ +
Currently: Loading...
+ {% else %} + Visual Selector data is not ready, watch needs to be checked atleast once. + {% endif %} {% else %} {# "This functionality needs chrome.." #} {{ only_playwright_type_watches_warning() }} diff --git a/changedetectionio/blueprint/ui/views.py b/changedetectionio/blueprint/ui/views.py index 27544cac..87503629 100644 --- a/changedetectionio/blueprint/ui/views.py +++ b/changedetectionio/blueprint/ui/views.py @@ -57,22 +57,26 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMe except Exception as e: content.append({'line': f"File doesnt exist or unable to read timestamp {timestamp}", 'classes': ''}) + from changedetectionio.pluggy_interface import get_fetcher_capabilities + capabilities = get_fetcher_capabilities(watch, datastore) + output = render_template("preview.html", + capabilities=capabilities, content=content, + current_diff_url=watch['url'], current_version=timestamp, - history_n=watch.history_n, extra_stylesheets=extra_stylesheets, extra_title=f" - Diff - {watch.label} @ {timestamp}", - triggered_line_numbers=triggered_line_numbers, - current_diff_url=watch['url'], - screenshot=watch.get_screenshot(), - watch=watch, - uuid=uuid, + history_n=watch.history_n, is_html_webdriver=is_html_webdriver, last_error=watch['last_error'], - last_error_text=watch.get_error_text(), last_error_screenshot=watch.get_error_snapshot(), - versions=versions + last_error_text=watch.get_error_text(), + screenshot=watch.get_screenshot(), + triggered_line_numbers=triggered_line_numbers, + uuid=uuid, + versions=versions, + watch=watch, ) return output @@ -174,29 +178,31 @@ def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMe password_enabled_and_share_is_off = not datastore.data['settings']['application'].get('shared_diff_access') datastore.set_last_viewed(uuid, time.time()) - + from changedetectionio.pluggy_interface import get_fetcher_capabilities + capabilities = get_fetcher_capabilities(watch, datastore) return render_template("diff.html", - current_diff_url=watch['url'], - from_version=str(from_version), - to_version=str(to_version), - extra_stylesheets=extra_stylesheets, - extra_title=f" - Diff - {watch.label}", - extract_form=extract_form, - is_html_webdriver=is_html_webdriver, - last_error=watch['last_error'], - last_error_screenshot=watch.get_error_snapshot(), - last_error_text=watch.get_error_text(), - left_sticky=True, - newest=to_version_file_contents, - newest_version_timestamp=dates[-1], - password_enabled_and_share_is_off=password_enabled_and_share_is_off, - from_version_file_contents=from_version_file_contents, - to_version_file_contents=to_version_file_contents, - screenshot=screenshot_url, - uuid=uuid, - versions=dates, # All except current/last - watch_a=watch - ) + capabilities=capabilities, + current_diff_url=watch['url'], + extra_stylesheets=extra_stylesheets, + extra_title=f" - Diff - {watch.label}", + extract_form=extract_form, + from_version=str(from_version), + from_version_file_contents=from_version_file_contents, + is_html_webdriver=is_html_webdriver, + last_error=watch['last_error'], + last_error_screenshot=watch.get_error_snapshot(), + last_error_text=watch.get_error_text(), + left_sticky=True, + newest=to_version_file_contents, + newest_version_timestamp=dates[-1], + password_enabled_and_share_is_off=password_enabled_and_share_is_off, + screenshot=screenshot_url, + to_version=str(to_version), + to_version_file_contents=to_version_file_contents, + uuid=uuid, + versions=dates, # All except current/last + watch_a=watch + ) @views_blueprint.route("/diff/", methods=['GET']) @login_optionally_required diff --git a/changedetectionio/content_fetchers/base.py b/changedetectionio/content_fetchers/base.py index abe1ce2a..247a9965 100644 --- a/changedetectionio/content_fetchers/base.py +++ b/changedetectionio/content_fetchers/base.py @@ -64,6 +64,12 @@ class Fetcher(): # Time ONTOP of the system defined env minimum time render_extract_delay = 0 + # Fetcher capability flags - subclasses should override these + # These indicate what features the fetcher supports + supports_browser_steps = False # Can execute browser automation steps + supports_screenshots = False # Can capture page screenshots + supports_xpath_element_data = False # Can extract xpath element positions/data for visual selector + @classmethod def get_status_icon_data(cls): """Return data for status icon to display in the watch overview. diff --git a/changedetectionio/content_fetchers/playwright.py b/changedetectionio/content_fetchers/playwright.py index ed82139c..51231f04 100644 --- a/changedetectionio/content_fetchers/playwright.py +++ b/changedetectionio/content_fetchers/playwright.py @@ -89,6 +89,11 @@ class fetcher(Fetcher): proxy = None + # Capability flags + supports_browser_steps = True + supports_screenshots = True + supports_xpath_element_data = True + @classmethod def get_status_icon_data(cls): """Return Chrome browser icon data for Playwright fetcher.""" diff --git a/changedetectionio/content_fetchers/puppeteer.py b/changedetectionio/content_fetchers/puppeteer.py index 04eec02b..5d0c1047 100644 --- a/changedetectionio/content_fetchers/puppeteer.py +++ b/changedetectionio/content_fetchers/puppeteer.py @@ -98,6 +98,11 @@ class fetcher(Fetcher): proxy = None + # Capability flags + supports_browser_steps = True + supports_screenshots = True + supports_xpath_element_data = True + @classmethod def get_status_icon_data(cls): """Return Chrome browser icon data for Puppeteer fetcher.""" diff --git a/changedetectionio/content_fetchers/webdriver_selenium.py b/changedetectionio/content_fetchers/webdriver_selenium.py index 50bee6a6..28def8f9 100644 --- a/changedetectionio/content_fetchers/webdriver_selenium.py +++ b/changedetectionio/content_fetchers/webdriver_selenium.py @@ -14,6 +14,11 @@ class fetcher(Fetcher): proxy = None proxy_url = None + # Capability flags + supports_browser_steps = True + supports_screenshots = True + supports_xpath_element_data = True + @classmethod def get_status_icon_data(cls): """Return Chrome browser icon data for WebDriver fetcher.""" diff --git a/changedetectionio/pluggy_interface.py b/changedetectionio/pluggy_interface.py index 223f5f09..88a7ffbe 100644 --- a/changedetectionio/pluggy_interface.py +++ b/changedetectionio/pluggy_interface.py @@ -229,4 +229,56 @@ def get_itemprop_availability_from_plugin(content, fetcher_name, fetcher_instanc if result.get('price') is not None or result.get('availability'): return result - return None \ No newline at end of file + return None + + +def get_fetcher_capabilities(watch, datastore): + """Get capability flags for a watch's fetcher. + + Args: + watch: The watch object/dict + datastore: The datastore to resolve 'system' fetcher + + Returns: + dict: Dictionary with capability flags: + { + 'supports_browser_steps': bool, + 'supports_screenshots': bool, + 'supports_xpath_element_data': bool + } + """ + # Get the fetcher name from watch + fetcher_name = watch.get('fetch_backend', 'system') + + # Resolve 'system' to actual fetcher + if fetcher_name == 'system': + fetcher_name = datastore.data['settings']['application'].get('fetch_backend', 'html_requests') + + # Get the fetcher class + from changedetectionio import content_fetchers + + # Try to get from built-in fetchers first + if hasattr(content_fetchers, fetcher_name): + fetcher_class = getattr(content_fetchers, fetcher_name) + return { + 'supports_browser_steps': getattr(fetcher_class, 'supports_browser_steps', False), + 'supports_screenshots': getattr(fetcher_class, 'supports_screenshots', False), + 'supports_xpath_element_data': getattr(fetcher_class, 'supports_xpath_element_data', False) + } + + # Try to get from plugin-provided fetchers + fetchers = collect_content_fetchers() + for name, fetcher_class in fetchers: + if name == fetcher_name: + return { + 'supports_browser_steps': getattr(fetcher_class, 'supports_browser_steps', False), + 'supports_screenshots': getattr(fetcher_class, 'supports_screenshots', False), + 'supports_xpath_element_data': getattr(fetcher_class, 'supports_xpath_element_data', False) + } + + # Default: no capabilities + return { + 'supports_browser_steps': False, + 'supports_screenshots': False, + 'supports_xpath_element_data': False + } \ No newline at end of file diff --git a/changedetectionio/templates/diff.html b/changedetectionio/templates/diff.html index f7fdf868..603720e4 100644 --- a/changedetectionio/templates/diff.html +++ b/changedetectionio/templates/diff.html @@ -112,7 +112,7 @@
For now, Differences are performed on text, not graphically, only the latest screenshot is available.
- {% if is_html_webdriver %} + {% if capabilities.get('supports_screenshots') %} {% if screenshot %}
{{watch_a.snapshot_screenshot_ctime|format_timestamp_timeago}}
Current screenshot from most recent request @@ -120,7 +120,7 @@ No screenshot available just yet! Try rechecking the page. {% endif %} {% else %} - Screenshot requires Playwright/WebDriver enabled + Screenshot requires a Content Fetcher ( Chrome, Zyte etc ) that supports screenshots. {% endif %}
diff --git a/changedetectionio/templates/preview.html b/changedetectionio/templates/preview.html index 826fc041..1f680581 100644 --- a/changedetectionio/templates/preview.html +++ b/changedetectionio/templates/preview.html @@ -89,7 +89,7 @@ For now, Differences are performed on text, not graphically, only the latest screenshot is available.

- {% if is_html_webdriver %} + {% if capabilities.supports_screenshots %} {% if screenshot %}
{{ watch.snapshot_screenshot_ctime|format_timestamp_timeago }}
Current screenshot from most recent request @@ -97,7 +97,7 @@ No screenshot available just yet! Try rechecking the page. {% endif %} {% else %} - Screenshot requires Playwright/WebDriver enabled + Screenshot requires a Content Fetcher ( Chrome, Zyte etc ) that supports screenshots. {% endif %}