mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-07-18 09:06:06 +00:00
trigger_event in apps/connect/utils.py iterates pm.list_plugins() and
on disabled plugins emits a debug log that accesses dict items as if
they were attributes:
logger.debug(f"Skipping disabled plugin id={plugin.key} name={plugin.name}")
Python evaluates f-string arguments eagerly even when the logger
discards the message at INFO level, so this raises AttributeError on
the first disabled plugin encountered. The exception bubbles out of
trigger_event with no try/except in the loop, aborting dispatch for
every plugin sorted after the disabled one.
Effect for users: any plugin subscribed to events via `events: [...]`
on an action silently never receives events whenever any
alphabetically-earlier plugin is disabled. Manual button actions still
work, masking the failure as a plugin bug rather than a dispatch one.
Fix: replace the two attribute accesses with dict access. Add a
regression test under apps/connect/tests/ that mocks PluginManager to
return [disabled, enabled-with-events], asserts the enabled plugin's
action is dispatched, and a sanity check that non-matching events
aren't dispatched. Verified the test fails against the original code
with the expected AttributeError and passes with the fix.
116 lines
4.4 KiB
Python
116 lines
4.4 KiB
Python
# connect/utils.py
|
|
import logging, json
|
|
from django.template import Template, Context
|
|
from .models import EventSubscription, DeliveryLog, SUPPORTED_EVENTS
|
|
from .handlers.webhook import WebhookHandler
|
|
from .handlers.script import ScriptHandler
|
|
from apps.plugins.loader import PluginManager
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
HANDLERS = {
|
|
"webhook": WebhookHandler,
|
|
"script": ScriptHandler,
|
|
}
|
|
|
|
|
|
def trigger_event(event_name, payload):
|
|
if event_name not in SUPPORTED_EVENTS:
|
|
logger.debug(f"Unsupported event '{event_name}' - skipping")
|
|
return
|
|
|
|
logger.debug(
|
|
f"Triggering connect event: {event_name} payload_keys={list((payload or {}).keys())}"
|
|
)
|
|
subscriptions = EventSubscription.objects.filter(
|
|
event=event_name, enabled=True
|
|
).select_related("integration")
|
|
|
|
count = subscriptions.count()
|
|
logger.info(f"Found {count} connect subscription(s) for event '{event_name}'")
|
|
|
|
# First, fetch all subscriptions and trigger
|
|
for sub in subscriptions:
|
|
integration = sub.integration
|
|
if not integration.enabled:
|
|
logger.debug(
|
|
f"Skipping disabled integration id={integration.id} name={integration.name}"
|
|
)
|
|
continue
|
|
|
|
# apply optional payload template (only for webhook integrations)
|
|
# If the rendered template is valid JSON, use that object as the payload.
|
|
# Otherwise, pass the rendered string as-is.
|
|
final_payload = payload
|
|
if integration.type == 'webhook' and sub.payload_template:
|
|
try:
|
|
template = Template(sub.payload_template)
|
|
final_payload = template.render(Context(payload)).strip()
|
|
except Exception as e:
|
|
logger.error(
|
|
f"Payload template render failed for subscription id={sub.id}: {e}"
|
|
)
|
|
final_payload = payload
|
|
|
|
handler_cls = HANDLERS.get(integration.type)
|
|
if not handler_cls:
|
|
DeliveryLog.objects.create(
|
|
subscription=sub,
|
|
status="failed",
|
|
request_payload=final_payload,
|
|
error_message=f"No handler for integration type '{integration.type}'",
|
|
)
|
|
logger.error(
|
|
f"No handler for integration type '{integration.type}' (integration id={integration.id})"
|
|
)
|
|
continue
|
|
|
|
handler = handler_cls(integration, sub, final_payload)
|
|
logger.debug(
|
|
f"Executing handler type={integration.type} integration_id={integration.id} subscription_id={sub.id}"
|
|
)
|
|
|
|
try:
|
|
result = handler.execute()
|
|
DeliveryLog.objects.create(
|
|
subscription=sub,
|
|
status="success" if result.get("success") else "failed",
|
|
request_payload=final_payload,
|
|
response_payload=result,
|
|
)
|
|
logger.info(
|
|
f"Connect delivery succeeded for subscription id={sub.id} integration '{integration.name}'"
|
|
)
|
|
except Exception as e:
|
|
DeliveryLog.objects.create(
|
|
subscription=sub,
|
|
status="failed",
|
|
request_payload=final_payload,
|
|
error_message=str(e),
|
|
)
|
|
logger.error(
|
|
f"Connect delivery failed for subscription id={sub.id} integration '{integration.name}': {e}"
|
|
)
|
|
|
|
pm = PluginManager.get()
|
|
pm.discover_plugins(sync_db=False, use_cache=True)
|
|
plugins = pm.list_plugins()
|
|
|
|
logger.debug(f"Checking {len(plugins)} plugins for event '{event_name}'")
|
|
for plugin in plugins:
|
|
if not plugin["enabled"]:
|
|
logger.debug(f"Skipping disabled plugin id={plugin['key']} name={plugin['name']}")
|
|
continue
|
|
|
|
logger.debug(json.dumps(plugin))
|
|
for action in plugin["actions"]:
|
|
if "events" in action and event_name in action["events"]:
|
|
key = plugin["key"]
|
|
params = {"event": event_name, "payload": payload}
|
|
action_name = action.get("label") or action.get("id")
|
|
action_id = action.get("id")
|
|
logger.debug(
|
|
f"Triggering plugin action for event '{event_name}' on plugin id={key} action={action_name}"
|
|
)
|
|
if action_id:
|
|
pm.run_action(key, action_id, params)
|