import time import logging from yuxi.channel.extensions.slack.types import SlackProbeResult from yuxi.channel.extensions.slack.config import SlackConfigAdapter from yuxi.channel.protocols import ChannelAccountSnapshot, ChannelStatusIssue logger = logging.getLogger(__name__) class SlackStatus: default_runtime: ChannelAccountSnapshot | None = None async def probe(self, account: dict | None = None) -> bool: if not account: return False bot_token = account.get("bot_token", "") if not bot_token: return False probe_result = await self._probe_slack(bot_token) return probe_result.ok async def _probe_slack(self, token: str, timeout_ms: int = 5000) -> SlackProbeResult: try: from slack_sdk.web.async_client import AsyncWebClient except ImportError: return SlackProbeResult(ok=False, error="slack_sdk not installed") client = AsyncWebClient(token=token) start = time.monotonic() try: resp = await client.auth_test() latency_ms = (time.monotonic() - start) * 1000 return SlackProbeResult( ok=resp.get("ok", False), user=resp.get("user", ""), user_id=resp.get("user_id", ""), bot_id=resp.get("bot_id", ""), team=resp.get("team", ""), team_id=resp.get("team_id", ""), url=resp.get("url", ""), api_app_id=resp.get("api_app_id", ""), latency_ms=latency_ms, ) except Exception as e: latency_ms = (time.monotonic() - start) * 1000 return SlackProbeResult(ok=False, error=str(e), latency_ms=latency_ms) def build_summary(self, snapshot: object) -> dict: if not isinstance(snapshot, ChannelAccountSnapshot): return {} from yuxi.channel.protocols import build_standard_summary base = build_standard_summary(snapshot, "slack") base["status"] = getattr(snapshot, "status_state", "unknown") return base def build_channel_summary( self, account: dict, config: dict, default_account_id: str, snapshot: ChannelAccountSnapshot, ) -> dict: account_id = account.get("account_id", "") return { "channel": "slack", "account_id": account_id, "name": account.get("name", account_id), "is_default": account_id == default_account_id, "configured": snapshot.configured, "enabled": snapshot.enabled, "connected": snapshot.connected, "running": snapshot.running, "status": snapshot.status_state, "health_state": snapshot.health_state, "mode": snapshot.mode, "dm_policy": snapshot.dm_policy, "last_error": snapshot.last_error, "last_connected_at": snapshot.last_connected_at, "last_message_at": snapshot.last_message_at, "last_event_at": snapshot.last_event_at, "probe": snapshot.probe, "audit": snapshot.audit, } def build_account_snapshot( self, account: dict, config: dict, runtime: ChannelAccountSnapshot | None = None, probe: object | None = None, audit: object | None = None, ) -> ChannelAccountSnapshot: adapter = SlackConfigAdapter() account_id = account.get("account_id", "") configured = adapter.is_configured(account) enabled = adapter.is_enabled(account, config) status_state = self.resolve_account_state(account, config, configured, enabled) return ChannelAccountSnapshot( account_id=account_id, name=account.get("name", account_id), enabled=enabled, configured=configured, status_state=status_state, mode=account.get("mode", "socket"), dm_policy=account.get("dm_policy", "pairing"), allow_from=account.get("allow_from", []), probe=probe, audit=audit, ) def format_capabilities_probe(self, probe: object) -> list[dict]: if probe is None: return [] return [ { "label": "Slack 连接", "ok": getattr(probe, "ok", False), "detail": f"{getattr(probe, 'team', '')} / {getattr(probe, 'user', '')}", "latency_ms": getattr(probe, "latency_ms", None), } ] async def audit_account( self, account: dict, timeout_ms: int, config: dict, probe: object | None = None, ) -> object: bot_token = account.get("bot_token", "") if not bot_token: return {"ok": False, "error": "No bot token configured"} probe_result = await self._probe_slack(bot_token, timeout_ms=timeout_ms) return { "ok": probe_result.ok, "user": probe_result.user, "user_id": probe_result.user_id, "bot_id": probe_result.bot_id, "team": probe_result.team, "team_id": probe_result.team_id, "url": probe_result.url, "api_app_id": probe_result.api_app_id, "latency_ms": probe_result.latency_ms, "error": probe_result.error, } async def build_capabilities_diagnostics( self, account: dict, timeout_ms: int, config: dict, probe: object | None = None, audit: object | None = None, target: str | None = None, ) -> dict | None: return { "channel": "slack", "probe": self.format_capabilities_probe(probe), "audit": audit, } def log_self_id( self, account: dict, config: dict, runtime: object, include_channel_prefix: bool = False, ) -> None: prefix = "Slack " if include_channel_prefix else "" account_id = account.get("account_id", "default") name = account.get("name", account_id) logger.info(f"{prefix}Bot account loaded: {name} (id={account_id})") def resolve_account_state( self, account: dict, config: dict, configured: bool, enabled: bool, ) -> str: if not configured: return "unconfigured" if not enabled: return "disabled" return "enabled" def collect_status_issues(self, accounts: list[ChannelAccountSnapshot]) -> list[ChannelStatusIssue]: issues: list[ChannelStatusIssue] = [] for snapshot in accounts: if snapshot.connected is False: issues.append( ChannelStatusIssue( channel="slack", account_id=snapshot.account_id, kind="disconnected", message=f"Slack 账户 {snapshot.name or snapshot.account_id} 连接断开", fix="检查 Bot Token 和网络连接", ) ) if snapshot.last_error: issues.append( ChannelStatusIssue( channel="slack", account_id=snapshot.account_id, kind="error", message=f"Slack 账户 {snapshot.name or snapshot.account_id}: {snapshot.last_error}", fix="查看日志排查错误原因", ) ) if snapshot.status_state == "unconfigured": issues.append( ChannelStatusIssue( channel="slack", account_id=snapshot.account_id, kind="unconfigured", message=f"Slack 账户 {snapshot.name or snapshot.account_id} 未配置凭证", fix="在管理面板中配置 Slack Bot Token", ) ) return issues