ForcePilot/backend/package/yuxi/channels/adapters/slack/interaction_dispatch.py
Kris df1a7c7bca refactor(slack-adapter): 整理导入顺序并修复格式问题
本次提交包含多个Slack适配器相关的代码优化:
1. 统一多个文件中datetime和UTC的导入顺序
2. 调整collection.abc导入的参数顺序
3. 修复normalizer.py的文件末尾空行问题
4. 重新排序blocks.py中的函数导入
5. 调整directory_config.py中的函数顺序
6. 重构http_handler中的channel_manager调用方式
7. 新增Slack原生流探测逻辑和相关状态管理
8. 扩展消息动作分类和默认配置
9. 新增大量Slack消息块构建工具函数
10. 大幅重构__init__.py的导出内容,整理导入顺序
11. 为adapter新增熔断机制、缓存持久化和更多API方法
12. 新增多种系统事件处理逻辑
2026-05-13 16:14:38 +08:00

132 lines
3.9 KiB
Python

from __future__ import annotations
from collections.abc import Awaitable, Callable
from dataclasses import dataclass, field
from typing import Any
from yuxi.utils.logging_config import logger
@dataclass
class InteractionPayload:
type: str = ""
action_id: str = ""
user: str = ""
channel: str = ""
ts: str = ""
thread_ts: str = ""
value: str = ""
raw: dict[str, Any] | None = None
@property
def is_button(self) -> bool:
return self.type == "button"
@property
def is_select(self) -> bool:
return self.type == "static_select"
@property
def is_overflow(self) -> bool:
return self.type == "overflow"
@dataclass
class InteractionRoute:
action_id: str
handler: Callable[[InteractionPayload], Awaitable[dict[str, Any]]]
description: str = ""
async def execute(self, payload: InteractionPayload) -> dict[str, Any]:
try:
return await self.handler(payload)
except Exception as e:
logger.error(f"Interaction route '{self.action_id}' failed: {e}")
return {"error": str(e)}
@dataclass
class InteractionRouter:
routes: dict[str, InteractionRoute] = field(default_factory=dict)
def register(self, action_id: str, handler: Callable, description: str = "") -> None:
self.routes[action_id] = InteractionRoute(
action_id=action_id,
handler=handler,
description=description,
)
def unregister(self, action_id: str) -> None:
self.routes.pop(action_id, None)
def get_route(self, action_id: str) -> InteractionRoute | None:
if action_id in self.routes:
return self.routes[action_id]
for prefix, route in self.routes.items():
if prefix.endswith("*") and action_id.startswith(prefix[:-1]):
return route
return None
async def dispatch(self, payload: InteractionPayload) -> dict[str, Any]:
route = self.get_route(payload.action_id)
if route:
return await route.execute(payload)
logger.debug(f"No route found for action_id: {payload.action_id}")
return {"acknowledged": True, "routed": False}
@property
def registered_actions(self) -> list[str]:
return list(self.routes.keys())
async def dispatch_interaction(
client,
payload: InteractionPayload,
*,
handlers: dict[str, Any] | None = None,
) -> dict[str, Any]:
if not handlers:
handlers = {}
handler = handlers.get(payload.action_id)
if handler:
try:
return await handler(payload)
except Exception as e:
logger.error(f"Interaction handler error for {payload.action_id}: {e}")
return {"error": str(e)}
logger.debug(f"No handler found for action_id: {payload.action_id}")
return {"acknowledged": True}
def parse_interaction_payload(raw_payload: dict[str, Any]) -> InteractionPayload:
actions = raw_payload.get("actions", [])
action = actions[0] if actions else {}
return InteractionPayload(
type=raw_payload.get("type", action.get("type", "")),
action_id=action.get("action_id", ""),
user=raw_payload.get("user", {}).get("id", ""),
channel=raw_payload.get("channel", {}).get("id", ""),
ts=raw_payload.get("message", {}).get("ts", ""),
thread_ts=raw_payload.get("message", {}).get("thread_ts", ""),
value=action.get("value", action.get("selected_option", {}).get("value", "")),
raw=raw_payload,
)
_external_menu_store: dict[str, list[dict[str, Any]]] = {}
def store_external_options(request_id: str, options: list[dict[str, Any]]) -> None:
_external_menu_store[request_id] = options
def get_external_options(request_id: str) -> list[dict[str, Any]]:
return _external_menu_store.pop(request_id, [])
def clear_external_options() -> None:
_external_menu_store.clear()