109 lines
4.1 KiB
Python
109 lines
4.1 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import logging
|
|
import time
|
|
|
|
from yuxi.channel.extensions.googlechat.auth import probe_account
|
|
from yuxi.channel.extensions.googlechat.config import GoogleChatConfigAdapter
|
|
from yuxi.channel.extensions.googlechat.types import ResolvedGoogleChatAccount
|
|
from yuxi.channel.extensions.googlechat.webhook import GoogleChatWebhookHandler
|
|
from yuxi.channel.gateway.routes import webhook_registry
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_STATUS_OK = "ok"
|
|
_STATUS_ERROR = "error"
|
|
|
|
|
|
class GoogleChatGatewayAdapter:
|
|
def __init__(self):
|
|
self._config_adapter = GoogleChatConfigAdapter()
|
|
self._webhook_handler = GoogleChatWebhookHandler()
|
|
self._running: dict[str, bool] = {}
|
|
self._start_times: dict[str, float] = {}
|
|
self._status_snapshots: dict[str, dict] = {}
|
|
|
|
async def start(self, ctx) -> object:
|
|
account_dict = getattr(ctx, "account", {}) or {}
|
|
account = account_dict.get("resolved")
|
|
if not isinstance(account, ResolvedGoogleChatAccount):
|
|
account_id = account_dict.get("account_id", "default")
|
|
resolved_dict = await self._config_adapter.resolve_account(account_id)
|
|
account = resolved_dict.get("resolved")
|
|
|
|
if not account or not account.is_configured:
|
|
logger.warning("Google Chat account not configured: %s", account.account_id if account else "unknown")
|
|
return asyncio.Queue()
|
|
|
|
account_id = account.account_id
|
|
webhook_path = account.webhook_path
|
|
|
|
self._webhook_handler.register_target(webhook_path, account)
|
|
|
|
webhook_registry.register(
|
|
"googlechat",
|
|
self._webhook_handler.handle_webhook,
|
|
guard_config=None,
|
|
)
|
|
|
|
self._running[account_id] = True
|
|
self._start_times[account_id] = time.time()
|
|
self._status_snapshots[account_id] = {
|
|
"running": True,
|
|
"webhook_path": webhook_path,
|
|
"audience_type": account.audience_type,
|
|
"audience": account.audience,
|
|
"credential_source": account.credential_source,
|
|
"start_time": time.time(),
|
|
}
|
|
|
|
self._warn_app_principal_misconfiguration(account)
|
|
|
|
logger.info("Google Chat gateway started for account: %s, webhook: %s", account_id, webhook_path)
|
|
|
|
queue = getattr(ctx, "queue", asyncio.Queue())
|
|
return queue
|
|
|
|
async def stop(self, ctx) -> None:
|
|
account_dict = getattr(ctx, "account", {}) or {}
|
|
account_id = account_dict.get("account_id", "default")
|
|
|
|
account = account_dict.get("resolved")
|
|
if isinstance(account, ResolvedGoogleChatAccount):
|
|
self._webhook_handler.unregister_target(account.webhook_path)
|
|
|
|
self._running.pop(account_id, None)
|
|
self._status_snapshots[account_id] = {
|
|
"running": False,
|
|
"stop_time": time.time(),
|
|
}
|
|
|
|
logger.info("Google Chat gateway stopped for account: %s", account_id)
|
|
|
|
def get_status(self, account_id: str) -> dict:
|
|
return self._status_snapshots.get(account_id, {})
|
|
|
|
async def probe(self, account_dict: dict) -> dict:
|
|
account = account_dict.get("resolved")
|
|
if not isinstance(account, ResolvedGoogleChatAccount):
|
|
return {"ok": False, "status": "no_account"}
|
|
|
|
return await probe_account(account)
|
|
|
|
@staticmethod
|
|
def _warn_app_principal_misconfiguration(account: ResolvedGoogleChatAccount) -> None:
|
|
if account.audience_type != "app-url":
|
|
return
|
|
if not account.app_principal:
|
|
logger.error(
|
|
'appPrincipal is missing for audienceType "app-url"; '
|
|
"add-on token verification will fail. "
|
|
"Set appPrincipal to the numeric OAuth 2.0 client ID (uniqueId, 21 digits), not an email."
|
|
)
|
|
elif "@" in account.app_principal:
|
|
logger.error(
|
|
'appPrincipal "%s" looks like an email address. '
|
|
"Set appPrincipal to the numeric OAuth 2.0 client ID (uniqueId, 21 digits), not an email.",
|
|
account.app_principal,
|
|
) |