From 869e2517932d97fad74e36e1cc0a4975ff513e41 Mon Sep 17 00:00:00 2001 From: dgtlmoon Date: Wed, 16 Apr 2025 19:06:28 +0200 Subject: [PATCH] Adding wordcount condition --- changedetectionio/PLUGIN_README.md | 98 +++++++++++++++++++ .../conditions/plugins/wordcount_plugin.py | 82 ++++++++++++++++ changedetectionio/pluggy_interface.py | 7 +- 3 files changed, 181 insertions(+), 6 deletions(-) create mode 100644 changedetectionio/PLUGIN_README.md create mode 100644 changedetectionio/conditions/plugins/wordcount_plugin.py diff --git a/changedetectionio/PLUGIN_README.md b/changedetectionio/PLUGIN_README.md new file mode 100644 index 000000000..5c0f0d603 --- /dev/null +++ b/changedetectionio/PLUGIN_README.md @@ -0,0 +1,98 @@ +# Creating Plugins for changedetection.io + +This document describes how to create plugins for changedetection.io. Plugins can be used to extend the functionality of the application in various ways. + +## Plugin Types + +### UI Stats Tab Plugins + +These plugins can add content to the Stats tab in the Edit page. This is useful for adding custom statistics or visualizations about a watch. + +#### Creating a UI Stats Tab Plugin + +1. Create a Python file in a directory that will be loaded by the plugin system. + +2. Use the `global_hookimpl` decorator to implement the `ui_edit_stats_extras` hook: + +```python +import pluggy +from loguru import logger + +global_hookimpl = pluggy.HookimplMarker("changedetectionio") + +@global_hookimpl +def ui_edit_stats_extras(watch): + """Add custom content to the stats tab""" + # Calculate or retrieve your stats + my_stat = calculate_something(watch) + + # Return HTML content as a string + html = f""" +
+

My Plugin Statistics

+

My statistic: {my_stat}

+
+ """ + return html +``` + +3. The HTML you return will be included in the Stats tab. + +## Plugin Loading + +Plugins can be loaded from: + +1. Built-in plugin directories in the codebase +2. External packages using setuptools entry points + +To add a new plugin directory, modify the `plugin_dirs` dictionary in `pluggy_interface.py`. + +## Example Plugin + +Here's a simple example of a plugin that adds a word count statistic to the Stats tab: + +```python +import pluggy +from loguru import logger + +global_hookimpl = pluggy.HookimplMarker("changedetectionio") + +def count_words_in_history(watch): + """Count words in the latest snapshot""" + try: + if not watch.history.keys(): + return 0 + + latest_key = list(watch.history.keys())[-1] + latest_content = watch.get_history_snapshot(latest_key) + return len(latest_content.split()) + except Exception as e: + logger.error(f"Error counting words: {str(e)}") + return 0 + +@global_hookimpl +def ui_edit_stats_extras(watch): + """Add word count to the Stats tab""" + word_count = count_words_in_history(watch) + + html = f""" +
+

Content Analysis

+ + + + + + + +
Word count (latest snapshot){word_count}
+
+ """ + return html +``` + +## Testing Your Plugin + +1. Place your plugin in one of the directories scanned by the plugin system +2. Restart changedetection.io +3. Go to the Edit page of a watch and check the Stats tab to see your content \ No newline at end of file diff --git a/changedetectionio/conditions/plugins/wordcount_plugin.py b/changedetectionio/conditions/plugins/wordcount_plugin.py new file mode 100644 index 000000000..a19d33538 --- /dev/null +++ b/changedetectionio/conditions/plugins/wordcount_plugin.py @@ -0,0 +1,82 @@ +import pluggy +from loguru import logger + +# Support both plugin systems +conditions_hookimpl = pluggy.HookimplMarker("changedetectionio_conditions") +global_hookimpl = pluggy.HookimplMarker("changedetectionio") + +def count_words_in_history(watch, incoming_text=None): + """Count words in snapshot text""" + try: + if incoming_text is not None: + # When called from add_data with incoming text + return len(incoming_text.split()) + elif watch.history.keys(): + # When called from UI extras to count latest snapshot + latest_key = list(watch.history.keys())[-1] + latest_content = watch.get_history_snapshot(latest_key) + return len(latest_content.split()) + return 0 + except Exception as e: + logger.error(f"Error counting words: {str(e)}") + return 0 + +# Implement condition plugin hooks +@conditions_hookimpl +def register_operators(): + # No custom operators needed + return {} + +@conditions_hookimpl +def register_operator_choices(): + # No custom operator choices needed + return [] + +@conditions_hookimpl +def register_field_choices(): + # Add a field that will be available in conditions + return [ + ("word_count", "Word count of content"), + ] + +@conditions_hookimpl +def add_data(current_watch_uuid, application_datastruct, ephemeral_data): + """Add word count data for conditions""" + result = {} + watch = application_datastruct['watching'].get(current_watch_uuid) + + if watch and 'text' in ephemeral_data: + word_count = count_words_in_history(watch, ephemeral_data['text']) + result['word_count'] = word_count + + return result + +def _generate_stats_html(watch): + """Generate the HTML content for the stats tab""" + word_count = count_words_in_history(watch) + + html = f""" +
+

Content Analysis

+ + + + + + + +
Word count (latest snapshot){word_count}
+

Word count is a simple measure of content length, calculated by splitting text on whitespace.

+
+ """ + return html + +@conditions_hookimpl +def ui_edit_stats_extras(watch): + """Add word count stats to the UI through conditions plugin system""" + return _generate_stats_html(watch) + +@global_hookimpl +def ui_edit_stats_extras(watch): + """Add word count stats to the UI using the global plugin system""" + return _generate_stats_html(watch) \ No newline at end of file diff --git a/changedetectionio/pluggy_interface.py b/changedetectionio/pluggy_interface.py index 8ee1fe93f..fe2f7182e 100644 --- a/changedetectionio/pluggy_interface.py +++ b/changedetectionio/pluggy_interface.py @@ -40,12 +40,7 @@ def load_plugins_from_directories(): # Add more plugin directories here as needed } - # Also load plugins from the root directory (for example plugins) - try: - import example_word_count_plugin - plugin_manager.register(example_word_count_plugin, "example_word_count_plugin") - except ImportError: - pass + # Note: Removed the direct import of example_word_count_plugin as it's now in the conditions/plugins directory for dir_name, dir_path in plugin_dirs.items(): if not os.path.exists(dir_path):