ForcePilot/backend/package/yuxi/channels/adapters/slack/message_actions.py
Kris a2aa782b86 feat(slack adapter): 实现完整的Slack频道适配器基础功能
新增Slack适配器全套核心模块,包括消息处理流水线、会话管理、配置适配、权限控制等完整功能:
1. 新增语音、视觉相关的TTS和图像分析导出接口
2. 实现消息预处理、路由、线程上下文处理的完整流水线
3. 新增账号管理、缓存机制、房间上下文提取功能
4. 支持Webhook和Socket Mode两种事件接收方式
5. 实现权限白名单、审批配对、自动状态管理功能
6. 新增配置迁移、作用域校验、重连策略等辅助模块
2026-05-12 00:48:57 +08:00

166 lines
5.2 KiB
Python

from __future__ import annotations
from dataclasses import dataclass, field
from enum import StrEnum
from typing import Any
class ActionCategory(StrEnum):
REACTIONS = "reactions"
MESSAGES = "messages"
PINS = "pins"
MEMBER_INFO = "memberInfo"
EMOJI_LIST = "emojiList"
ACTION_CATEGORY_DEFAULTS: dict[ActionCategory, bool] = {
ActionCategory.REACTIONS: True,
ActionCategory.MESSAGES: True,
ActionCategory.PINS: True,
ActionCategory.MEMBER_INFO: True,
ActionCategory.EMOJI_LIST: True,
}
ACTION_CATEGORY_ACTIONS: dict[ActionCategory, list[str]] = {
ActionCategory.REACTIONS: ["react", "remove_reaction", "list_reactions", "remove_own_reactions"],
ActionCategory.MESSAGES: ["send", "read", "edit", "delete", "upload-file", "download-file"],
ActionCategory.PINS: ["pin", "unpin", "list-pins"],
ActionCategory.MEMBER_INFO: ["member-info"],
ActionCategory.EMOJI_LIST: ["emoji-list"],
}
@dataclass
class MessageAction:
action_id: str
label: str
value: str = ""
style: str = ""
type: str = "button"
uri: str = ""
def to_dict(self) -> dict[str, Any]:
result: dict[str, Any] = {"action_id": self.action_id, "label": self.label, "value": self.value}
if self.style:
result["style"] = self.style
if self.type != "button":
result["type"] = self.type
if self.uri:
result["uri"] = self.uri
return result
@dataclass
class MessageActionsConfig:
enabled: bool = True
max_actions_per_message: int = 5
actions: list[MessageAction] = field(default_factory=list)
categories: dict[ActionCategory, bool] = field(default_factory=lambda: dict(ACTION_CATEGORY_DEFAULTS))
def is_category_enabled(self, category: ActionCategory) -> bool:
return self.categories.get(category, True)
def is_action_enabled(self, action_name: str) -> bool:
for category, actions in ACTION_CATEGORY_ACTIONS.items():
if action_name in actions:
return self.is_category_enabled(category)
return self.enabled
def enabled_categories(self) -> set[ActionCategory]:
return {c for c, enabled in self.categories.items() if enabled}
def disabled_categories(self) -> set[ActionCategory]:
return {c for c, enabled in self.categories.items() if not enabled}
@classmethod
def from_config(cls, config: dict[str, Any] | None) -> MessageActionsConfig:
if not config:
return cls()
actions_cfg = config.get("messageActions", {}) or {}
actions = []
for a in actions_cfg.get("actions", []):
if isinstance(a, dict):
actions.append(
MessageAction(
action_id=a.get("action_id", ""),
label=a.get("label", ""),
value=a.get("value", ""),
style=a.get("style", ""),
type=a.get("type", "button"),
uri=a.get("uri", ""),
)
)
categories: dict[ActionCategory, bool] = {}
category_config = actions_cfg.get("categories") or actions_cfg.get("actions") or {}
if isinstance(category_config, dict):
for cat in ActionCategory:
cat_val = category_config.get(cat.value)
if cat_val is not None:
categories[cat] = bool(cat_val)
else:
categories[cat] = ACTION_CATEGORY_DEFAULTS.get(cat, True)
else:
categories = dict(ACTION_CATEGORY_DEFAULTS)
return cls(
enabled=bool(actions_cfg.get("enabled", True)),
max_actions_per_message=int(actions_cfg.get("maxActionsPerMessage", 5)),
actions=actions,
categories=categories,
)
def gate_message_actions(
actions_config: MessageActionsConfig,
*,
chat_type: str = "",
) -> bool:
if not actions_config.enabled:
return False
if not actions_config.actions:
return False
return True
def gate_action_category(
actions_config: MessageActionsConfig,
category: ActionCategory,
) -> bool:
if not actions_config.enabled:
return False
return actions_config.is_category_enabled(category)
def gate_action(
actions_config: MessageActionsConfig,
action_name: str,
) -> bool:
if not actions_config.enabled:
return False
return actions_config.is_action_enabled(action_name)
def validate_action_allowed(
actions_config: MessageActionsConfig,
action_name: str,
) -> tuple[bool, str]:
if not actions_config.enabled:
return False, "message actions disabled"
if not actions_config.is_action_enabled(action_name):
return False, f"action category for '{action_name}' is disabled"
return True, ""
def compile_message_actions(
actions_config: MessageActionsConfig,
*,
override_action_id_prefix: str = "",
) -> list[dict[str, Any]]:
result = []
for a in actions_config.actions[: actions_config.max_actions_per_message]:
d = a.to_dict()
if override_action_id_prefix:
d["action_id"] = f"{override_action_id_prefix}_{d['action_id']}"
result.append(d)
return result