diff --git a/changedetectionio/processors/__init__.py b/changedetectionio/processors/__init__.py index e7413c1bb..5b5a4b5df 100644 --- a/changedetectionio/processors/__init__.py +++ b/changedetectionio/processors/__init__.py @@ -396,8 +396,6 @@ class RestockDiffPlugin: return Watch.model return None -# Import our example plugins -from .example_processor_plugin import ExampleProcessorPlugin # For backward compatibility def get_custom_watch_obj_for_processor(processor_name): @@ -406,15 +404,3 @@ def get_custom_watch_obj_for_processor(processor_name): # Register the built-in processor plugins plugin_manager.register(TextJsonDiffPlugin()) plugin_manager.register(RestockDiffPlugin()) -plugin_manager.register(ExampleProcessorPlugin()) - -# Check for test plugin and conditionally register it -try: - # This avoids circular imports - from .test_plugin_example import ExampleProcessorPlugin as TestExampleProcessorPlugin - test_plugin_instance = TestExampleProcessorPlugin() - # Only register if it has a different name than the regular example plugin - if test_plugin_instance.get_processor_name() != "example_processor": - plugin_manager.register(test_plugin_instance) -except (ImportError, AttributeError): - pass \ No newline at end of file diff --git a/changedetectionio/processors/example_processor_plugin.py b/changedetectionio/processors/example_processor_plugin.py deleted file mode 100644 index 37aab8f31..000000000 --- a/changedetectionio/processors/example_processor_plugin.py +++ /dev/null @@ -1,168 +0,0 @@ -""" -Example plugin to demonstrate how to create a new processor plugin -""" - -from .pluggy_interface import hookimpl - -class ExampleProcessorPlugin: - """ - Example processor plugin that extends the text_json_diff processor - """ - - def random_string(self, length=50): - import random - import string - - return ''.join(random.choices(string.ascii_letters + string.digits, k=length)) - - @hookimpl - def get_processor_name(self): - return "example_processor" - - @hookimpl - def get_processor_description(self): - return "Example Processor Plugin - For demonstration purposes" - - @hookimpl - def get_processor_version(self): - return "0.1.0-beta" - - @hookimpl - def perform_site_check(self, datastore, watch_uuid): - watch = datastore.data['watching'].get(watch_uuid) - if watch and watch.get('processor') == 'example_processor': - # Log that we're using our special example processor - from loguru import logger - - # Check if the example mode is enabled - if watch.is_example_mode_enabled(): - # Get the threshold value for our plugin - threshold = watch.get_example_threshold() - logger.info(f"Example processor using mode: {watch.get('example_settings', {}).get('mode')} with threshold: {threshold}") - - # Check if advanced features are enabled - advanced_features = watch.get('example_settings', {}).get('example_toggle', False) - if advanced_features: - logger.info("Example processor advanced features are enabled") - else: - logger.info("Example processor is in OFF mode, using standard processing") - - # Import here to avoid circular imports - from changedetectionio.processors.text_json_diff.processor import perform_site_check - return perform_site_check(datastore=datastore, watch_uuid=watch_uuid) - return None - - @hookimpl - def get_form_class(self, processor_name): - if processor_name == 'example_processor': - # Import here to avoid circular imports - from changedetectionio import forms - from wtforms import StringField, BooleanField, TextAreaField, RadioField, FloatField - from wtforms.validators import Optional, NumberRange - from wtforms.fields.form import FormField - from wtforms.form import Form - - # Create a settings form for the example plugin - class ExampleSettingsForm(Form): - mode = RadioField(label='Example Mode', choices=[ - ('mode_a', "Mode A - Default behavior"), - ('mode_b', "Mode B - Alternative behavior"), - ('off', "Off - Disable example functionality"), - ], default="mode_a") - - threshold = FloatField('Threshold value', [ - Optional(), - NumberRange(min=0, max=100, message="Should be between 0 and 100") - ], render_kw={"placeholder": "0", "size": "5"}) - - example_toggle = BooleanField('Enable advanced features', default=False) - example_notes = TextAreaField('Notes', validators=[Optional()]) - - # Create the main form by extending the base form - class ExampleProcessorForm(forms.processor_text_json_diff_form): - example_settings = FormField(ExampleSettingsForm) - - def extra_tab_content(self): - return 'Example Plugin' - - def extra_form_content(self): - output = "" - - # Show warning if tag overrides settings (similar to restock plugin) - if getattr(self, 'watch', None) and getattr(self, 'datastore'): - for tag_uuid in self.watch.get('tags'): - tag = self.datastore.data['settings']['application']['tags'].get(tag_uuid, {}) - if tag.get('overrides_watch'): - output = f"""

Note! A Group tag overrides the example plugin settings here.

""" - - output += """ - {% from '_helpers.html' import render_field, render_checkbox_field, render_button %} - - -
-
-
- {{ render_field(form.example_settings.mode) }} -
-
- {{ render_checkbox_field(form.example_settings.example_toggle) }} - Enable advanced example features -
-
- {{ render_field(form.example_settings.threshold) }} - Set the threshold percentage for this example plugin - For example, 5% means the plugin will only activate when changes exceed 5% of the content -
-
- {{ render_field(form.example_settings.example_notes, rows=3, placeholder="Add any notes here...") }} - Additional notes for this watch -
-
-
- """ - return output - - return ExampleProcessorForm - return None - - @hookimpl - def get_watch_model_class(self, processor_name): - if processor_name == 'example_processor': - # Import here to avoid circular imports - from changedetectionio.model import Watch - - # Create a custom Watch model class for the example plugin - class ExampleWatchModel(Watch.model): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - # Initialize example plugin settings if not present - if not self.get('example_settings'): - self['example_settings'] = { - 'mode': 'mode_a', - 'threshold': 0, - 'example_toggle': False, - 'example_notes': '' - } - - # Add any custom methods for the example plugin - def get_example_threshold(self): - """Get the threshold value or return the default""" - settings = self.get('example_settings', {}) - return settings.get('threshold', 0) - - def is_example_mode_enabled(self): - """Check if the example plugin is enabled""" - settings = self.get('example_settings', {}) - return settings.get('mode') != 'off' - - return ExampleWatchModel - return None - -# This function would be called by the setup.py entry_points -def register_plugin(plugin_manager): - plugin_manager.register(ExampleProcessorPlugin()) \ No newline at end of file diff --git a/changedetectionio/processors/pluggy_interface.py b/changedetectionio/processors/pluggy_interface.py index 4ab9692a0..350821d36 100644 --- a/changedetectionio/processors/pluggy_interface.py +++ b/changedetectionio/processors/pluggy_interface.py @@ -1,4 +1,5 @@ import pluggy +from loguru import logger # Ensure that the namespace in HookspecMarker matches PluginManager PLUGIN_NAMESPACE = "changedetectionio_processors" @@ -60,5 +61,9 @@ plugin_manager = pluggy.PluginManager(PLUGIN_NAMESPACE) # Register hookspecs plugin_manager.add_hookspecs(ProcessorSpec) -# Discover installed plugins from external packages (if any) -plugin_manager.load_setuptools_entrypoints(PLUGIN_NAMESPACE) \ No newline at end of file +try: + # Discover installed plugins from external packages (if any) + plugin_manager.load_setuptools_entrypoints(PLUGIN_NAMESPACE) + logger.info(f"Loaded plugins: {plugin_manager.get_plugins()}") +except Exception as e: + logger.critical(f"Error loading plugins: {str(e)}") \ No newline at end of file diff --git a/changedetectionio/processors/test_plugin_example.py b/changedetectionio/processors/test_plugin_example.py deleted file mode 100644 index 6a12bf7c2..000000000 --- a/changedetectionio/processors/test_plugin_example.py +++ /dev/null @@ -1,46 +0,0 @@ -""" -Example plugin to demonstrate how to create a new processor plugin -""" -from .pluggy_interface import hookimpl -from .text_json_diff.processor import perform_site_check as text_json_diff_perform_site_check -from changedetectionio import forms - -class ExampleProcessorPlugin: - """ - Example processor plugin that extends the text_json_diff processor - """ - - @hookimpl - def get_processor_name(self): - return "example_processor" - - @hookimpl - def get_processor_description(self): - return "Example Processor Plugin - For demonstration purposes" - - @hookimpl - def perform_site_check(self, datastore, watch_uuid): - watch = datastore.data['watching'].get(watch_uuid) - if watch and watch.get('processor') == 'example_processor': - # This processor is just a wrapper around text_json_diff for demonstration - return text_json_diff_perform_site_check(datastore=datastore, watch_uuid=watch_uuid) - return None - - @hookimpl - def get_form_class(self, processor_name): - if processor_name == 'example_processor': - # Use the default form for this example - return forms.processor_text_json_diff_form - return None - - @hookimpl - def get_watch_model_class(self, processor_name): - if processor_name == 'example_processor': - # Use the default Watch model for this example - from changedetectionio.model import Watch - return Watch.model - return None - -# This function would be called by the setup.py entry_points -def register_plugin(plugin_manager): - plugin_manager.register(ExampleProcessorPlugin()) \ No newline at end of file