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):