2026-05-12 00:49:16 +08:00
|
|
|
"""Incoming Webhook sender for Synology Chat.
|
|
|
|
|
|
|
|
|
|
Provides an alternative send path via Synology Chat's Incoming Webhook URL,
|
|
|
|
|
as a lighter-weight alternative to the DSM API direct connection.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from typing import Any
|
|
|
|
|
|
|
|
|
|
import httpx
|
|
|
|
|
|
2026-05-12 14:51:53 +08:00
|
|
|
from yuxi.channels.adapters.synologychat.send import assert_safe_media_url
|
2026-05-12 00:49:16 +08:00
|
|
|
from yuxi.channels.models import DeliveryResult
|
|
|
|
|
from yuxi.utils.logging_config import logger
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def send_via_incoming_webhook(
|
|
|
|
|
webhook_url: str,
|
|
|
|
|
text: str,
|
|
|
|
|
file_url: str | None = None,
|
|
|
|
|
timeout: float = 30.0,
|
2026-05-12 14:51:53 +08:00
|
|
|
client: httpx.AsyncClient | None = None,
|
2026-05-12 00:49:16 +08:00
|
|
|
) -> DeliveryResult:
|
|
|
|
|
if not webhook_url:
|
|
|
|
|
return DeliveryResult(success=False, error="Incoming webhook URL not configured")
|
|
|
|
|
|
2026-05-12 14:51:53 +08:00
|
|
|
try:
|
|
|
|
|
await assert_safe_media_url(webhook_url)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
return DeliveryResult(success=False, error=f"SSRF check failed: {e}")
|
|
|
|
|
|
2026-05-12 00:49:16 +08:00
|
|
|
payload: dict[str, Any] = {"text": text}
|
|
|
|
|
if file_url:
|
|
|
|
|
payload["file_url"] = file_url
|
|
|
|
|
|
2026-05-12 14:51:53 +08:00
|
|
|
async def _do_send(http_client: httpx.AsyncClient) -> DeliveryResult:
|
|
|
|
|
response = await http_client.post(webhook_url, data=payload)
|
|
|
|
|
response.raise_for_status()
|
|
|
|
|
result = response.json()
|
|
|
|
|
if result.get("success"):
|
|
|
|
|
return DeliveryResult(success=True, message_id=result.get("data", {}).get("message_id"))
|
|
|
|
|
return DeliveryResult(
|
|
|
|
|
success=False,
|
|
|
|
|
error=f"Webhook send failed: {result.get('error', {})}",
|
|
|
|
|
)
|
|
|
|
|
|
2026-05-12 00:49:16 +08:00
|
|
|
try:
|
2026-05-12 14:51:53 +08:00
|
|
|
if client:
|
|
|
|
|
return await _do_send(client)
|
|
|
|
|
async with httpx.AsyncClient(timeout=httpx.Timeout(timeout)) as new_client:
|
|
|
|
|
return await _do_send(new_client)
|
2026-05-12 00:49:16 +08:00
|
|
|
except httpx.TimeoutException:
|
|
|
|
|
return DeliveryResult(success=False, error="Incoming webhook request timed out")
|
|
|
|
|
except httpx.HTTPStatusError as e:
|
|
|
|
|
return DeliveryResult(success=False, error=f"Incoming webhook HTTP {e.response.status_code}")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"[SynologyChat] Incoming webhook send failed: {e}")
|
|
|
|
|
return DeliveryResult(success=False, error=str(e))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_webhook_url(dsm_url: str, webhook_token: str) -> str:
|
|
|
|
|
"""Build a Synology Chat Incoming Webhook URL from DSM URL and token."""
|
|
|
|
|
base = dsm_url.rstrip("/")
|
|
|
|
|
return f"{base}/webapi/entry.cgi?api=SYNO.Chat.External&method=incoming&version=1&token={webhook_token}"
|