本次提交包含多个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. 新增多种系统事件处理逻辑
132 lines
3.9 KiB
Python
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()
|