mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-08-07 09:13:04 +02:00
Fanout hitlist fixes: bugs, quality, tests, webhook HMAC signing
This commit is contained in:
@@ -88,8 +88,8 @@ def _send_sync(urls_raw: str, body: str, *, preserve_identity: bool) -> bool:
|
||||
class AppriseModule(FanoutModule):
|
||||
"""Sends push notifications via Apprise for incoming messages."""
|
||||
|
||||
def __init__(self, config_id: str, config: dict) -> None:
|
||||
super().__init__(config_id, config)
|
||||
def __init__(self, config_id: str, config: dict, *, name: str = "") -> None:
|
||||
super().__init__(config_id, config, name=name)
|
||||
self._last_error: str | None = None
|
||||
|
||||
async def on_message(self, data: dict) -> None:
|
||||
|
||||
+2
-1
@@ -12,9 +12,10 @@ class FanoutModule:
|
||||
Subclasses must override the ``status`` property.
|
||||
"""
|
||||
|
||||
def __init__(self, config_id: str, config: dict) -> None:
|
||||
def __init__(self, config_id: str, config: dict, *, name: str = "") -> None:
|
||||
self.config_id = config_id
|
||||
self.config = config
|
||||
self.name = name
|
||||
|
||||
async def start(self) -> None:
|
||||
"""Start the module (e.g. connect to broker). Override for persistent connections."""
|
||||
|
||||
+3
-4
@@ -20,8 +20,7 @@ class BotModule(FanoutModule):
|
||||
"""
|
||||
|
||||
def __init__(self, config_id: str, config: dict, *, name: str = "Bot") -> None:
|
||||
super().__init__(config_id, config)
|
||||
self._name = name
|
||||
super().__init__(config_id, config, name=name)
|
||||
|
||||
async def on_message(self, data: dict) -> None:
|
||||
"""Kick off bot execution in a background task so we don't block dispatch."""
|
||||
@@ -110,10 +109,10 @@ class BotModule(FanoutModule):
|
||||
timeout=BOT_EXECUTION_TIMEOUT,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
logger.warning("Bot '%s' execution timed out", self._name)
|
||||
logger.warning("Bot '%s' execution timed out", self.name)
|
||||
return
|
||||
except Exception as e:
|
||||
logger.warning("Bot '%s' execution error: %s", self._name, e)
|
||||
logger.warning("Bot '%s' execution error: %s", self.name, e)
|
||||
return
|
||||
|
||||
if response:
|
||||
|
||||
@@ -108,10 +108,7 @@ class FanoutManager:
|
||||
return
|
||||
|
||||
try:
|
||||
if config_type == "bot":
|
||||
module = cls(config_id, config_blob, name=cfg.get("name", "Bot"))
|
||||
else:
|
||||
module = cls(config_id, config_blob)
|
||||
module = cls(config_id, config_blob, name=cfg.get("name", ""))
|
||||
await module.start()
|
||||
self._modules[config_id] = (module, scope)
|
||||
logger.info(
|
||||
|
||||
@@ -29,8 +29,8 @@ def _config_to_settings(config: dict) -> SimpleNamespace:
|
||||
class MqttCommunityModule(FanoutModule):
|
||||
"""Wraps a CommunityMqttPublisher for community packet sharing."""
|
||||
|
||||
def __init__(self, config_id: str, config: dict) -> None:
|
||||
super().__init__(config_id, config)
|
||||
def __init__(self, config_id: str, config: dict, *, name: str = "") -> None:
|
||||
super().__init__(config_id, config, name=name)
|
||||
self._publisher = CommunityMqttPublisher()
|
||||
|
||||
async def start(self) -> None:
|
||||
|
||||
@@ -29,8 +29,8 @@ def _config_to_settings(config: dict) -> SimpleNamespace:
|
||||
class MqttPrivateModule(FanoutModule):
|
||||
"""Wraps an MqttPublisher instance for private MQTT forwarding."""
|
||||
|
||||
def __init__(self, config_id: str, config: dict) -> None:
|
||||
super().__init__(config_id, config)
|
||||
def __init__(self, config_id: str, config: dict, *, name: str = "") -> None:
|
||||
super().__init__(config_id, config, name=name)
|
||||
self._publisher = MqttPublisher()
|
||||
|
||||
async def start(self) -> None:
|
||||
|
||||
+15
-6
@@ -2,6 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import logging
|
||||
|
||||
import httpx
|
||||
@@ -14,8 +16,8 @@ logger = logging.getLogger(__name__)
|
||||
class WebhookModule(FanoutModule):
|
||||
"""Delivers message data to an HTTP endpoint via POST (or configurable method)."""
|
||||
|
||||
def __init__(self, config_id: str, config: dict) -> None:
|
||||
super().__init__(config_id, config)
|
||||
def __init__(self, config_id: str, config: dict, *, name: str = "") -> None:
|
||||
super().__init__(config_id, config, name=name)
|
||||
self._client: httpx.AsyncClient | None = None
|
||||
self._last_error: str | None = None
|
||||
|
||||
@@ -44,18 +46,25 @@ class WebhookModule(FanoutModule):
|
||||
|
||||
method = self.config.get("method", "POST").upper()
|
||||
extra_headers = self.config.get("headers", {})
|
||||
secret = self.config.get("secret", "")
|
||||
hmac_secret = self.config.get("hmac_secret", "")
|
||||
hmac_header = self.config.get("hmac_header", "X-Webhook-Signature")
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Webhook-Event": event_type,
|
||||
**extra_headers,
|
||||
}
|
||||
if secret:
|
||||
headers["X-Webhook-Secret"] = secret
|
||||
|
||||
import json as _json
|
||||
|
||||
body_bytes = _json.dumps(data, separators=(",", ":"), sort_keys=True).encode()
|
||||
|
||||
if hmac_secret:
|
||||
sig = hmac.new(hmac_secret.encode(), body_bytes, hashlib.sha256).hexdigest()
|
||||
headers[hmac_header or "X-Webhook-Signature"] = f"sha256={sig}"
|
||||
|
||||
try:
|
||||
resp = await self._client.request(method, url, json=data, headers=headers)
|
||||
resp = await self._client.request(method, url, content=body_bytes, headers=headers)
|
||||
resp.raise_for_status()
|
||||
self._last_error = None
|
||||
except httpx.HTTPStatusError as exc:
|
||||
|
||||
@@ -44,10 +44,10 @@ def _validate_mqtt_private_config(config: dict) -> None:
|
||||
def _validate_mqtt_community_config(config: dict) -> None:
|
||||
"""Validate mqtt_community config blob."""
|
||||
iata = config.get("iata", "")
|
||||
if iata and not _IATA_RE.fullmatch(iata.upper().strip()):
|
||||
if not iata or not _IATA_RE.fullmatch(iata.upper().strip()):
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="IATA code must be exactly 3 uppercase alphabetic characters",
|
||||
detail="IATA code is required and must be exactly 3 uppercase alphabetic characters",
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user