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, )