新增Slack适配器全套核心模块,包括消息处理流水线、会话管理、配置适配、权限控制等完整功能: 1. 新增语音、视觉相关的TTS和图像分析导出接口 2. 实现消息预处理、路由、线程上下文处理的完整流水线 3. 新增账号管理、缓存机制、房间上下文提取功能 4. 支持Webhook和Socket Mode两种事件接收方式 5. 实现权限白名单、审批配对、自动状态管理功能 6. 新增配置迁移、作用域校验、重连策略等辅助模块
138 lines
4.0 KiB
Python
138 lines
4.0 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
from typing import Any
|
|
|
|
from yuxi.channels.adapters.slack.blocks import (
|
|
build_button,
|
|
build_actions,
|
|
build_static_select,
|
|
build_option,
|
|
)
|
|
|
|
_MAX_BUTTONS = 5
|
|
_MAX_SELECT_OPTIONS = 100
|
|
_AUTO_SELECT_THRESHOLD = 5
|
|
_AUTO_SELECT_MAX_ITEMS = 12
|
|
|
|
_BUTTON_PATTERN = re.compile(
|
|
r"\[\[slack_buttons:\s*(.+?):\s*(.+?)(?::\s*(primary|danger))?\s*\]\]",
|
|
re.IGNORECASE,
|
|
)
|
|
|
|
_SELECT_PATTERN = re.compile(
|
|
r"\[\[slack_select:\s*(.+?)\s*\|\s*(.+?):\s*(.+?)\s*\]\]",
|
|
re.IGNORECASE,
|
|
)
|
|
|
|
_AUTO_SELECT_LINE = re.compile(r"^\s*Options:\s*$", re.IGNORECASE)
|
|
_OPTION_LINE = re.compile(r"^\s*-\s*(.+?):\s*(.+?)\s*$")
|
|
|
|
|
|
def compile_interactive_replies(text: str) -> tuple[str, list[dict[str, Any]] | None]:
|
|
blocks = _compile_buttons(text)
|
|
if blocks:
|
|
cleaned = _BUTTON_PATTERN.sub("", text).strip()
|
|
return cleaned, blocks
|
|
|
|
blocks = _compile_select(text)
|
|
if blocks:
|
|
cleaned = _SELECT_PATTERN.sub("", text).strip()
|
|
return cleaned, blocks
|
|
|
|
cleaned, auto_blocks = _compile_auto_select(text)
|
|
if auto_blocks:
|
|
return cleaned, auto_blocks
|
|
|
|
return text, None
|
|
|
|
|
|
def _compile_buttons(text: str) -> list[dict[str, Any]] | None:
|
|
matches = _BUTTON_PATTERN.findall(text)
|
|
if not matches:
|
|
return None
|
|
|
|
buttons = []
|
|
for label, value, style in matches[:_MAX_BUTTONS]:
|
|
btn_style = style.lower().strip() if style else ""
|
|
action_id = f"slack_button_{value.strip().replace(' ', '_')}"
|
|
btn = build_button(
|
|
label.strip(),
|
|
action_id,
|
|
value=value.strip(),
|
|
style=btn_style if btn_style in ("primary", "danger") else "",
|
|
)
|
|
buttons.append(btn)
|
|
|
|
return [build_actions(*buttons)] if buttons else None
|
|
|
|
|
|
def _compile_select(text: str) -> list[dict[str, Any]] | None:
|
|
matches = _SELECT_PATTERN.findall(text)
|
|
if not matches:
|
|
return None
|
|
|
|
options = []
|
|
for placeholder, label, value in matches[:_MAX_SELECT_OPTIONS]:
|
|
options.append(build_option(label.strip(), value.strip()))
|
|
|
|
if not options:
|
|
return None
|
|
|
|
placeholder_text = matches[0][0].strip() if matches else "选择一个选项"
|
|
select = build_static_select(
|
|
"slack_select_menu",
|
|
*options,
|
|
placeholder=placeholder_text,
|
|
)
|
|
return [{"type": "section", "text": {"type": "mrkdwn", "text": "请选择:"}, "accessory": select}]
|
|
|
|
|
|
def _compile_auto_select(text: str) -> tuple[str, list[dict[str, Any]] | None]:
|
|
lines = text.split("\n")
|
|
options_start = -1
|
|
|
|
for i, line in enumerate(lines):
|
|
if _AUTO_SELECT_LINE.match(line):
|
|
options_start = i
|
|
break
|
|
|
|
if options_start < 0:
|
|
return text, None
|
|
|
|
option_lines = []
|
|
for line in lines[options_start + 1 :]:
|
|
match = _OPTION_LINE.match(line)
|
|
if match:
|
|
if len(option_lines) >= _AUTO_SELECT_MAX_ITEMS:
|
|
break
|
|
option_lines.append((match.group(1).strip(), match.group(2).strip()))
|
|
elif option_lines and not line.strip():
|
|
break
|
|
elif not line.strip():
|
|
continue
|
|
else:
|
|
break
|
|
|
|
if not option_lines:
|
|
return text, None
|
|
|
|
cleaned_lines = lines[:options_start]
|
|
cleaned = "\n".join(cleaned_lines).strip()
|
|
|
|
if len(option_lines) <= _AUTO_SELECT_THRESHOLD:
|
|
buttons = [
|
|
build_button(label, f"auto_button_{value.replace(' ', '_')}", value=value) for label, value in option_lines
|
|
]
|
|
blocks = [build_actions(*buttons)]
|
|
return cleaned, blocks
|
|
|
|
select_options = [build_option(label, value) for label, value in option_lines]
|
|
select = build_static_select("auto_select_menu", *select_options, placeholder="选择一个选项")
|
|
blocks = [{"type": "section", "text": {"type": "mrkdwn", "text": "请选择:"}, "accessory": select}]
|
|
return cleaned, blocks
|
|
|
|
|
|
def has_interactive_syntax(text: str) -> bool:
|
|
return bool(_BUTTON_PATTERN.search(text) or _SELECT_PATTERN.search(text) or _AUTO_SELECT_LINE.search(text))
|