Files
qihuaiflow/backend/python/app/platform_adapters/slack_adapter.py
T
qyyfw 78ff261b5a 初始提交: Qihu-AIFlow v2.0.1 完整项目
- 前端 React+TypeScript (源码+构建产物)
- 后端 Python FastAPI
- 数据库迁移 001-015
- Docker部署配置
- 微信ClawBot API频道绑定
- 项目文档 README.md
2026-07-02 14:40:46 +08:00

147 lines
4.6 KiB
Python

"""
Slack平台适配器
"""
import hashlib
import hmac
import json
import logging
import time
from typing import Any
from ..platform_adapter import register_platform_adapter, BasePlatformAdapter, PlatformMetadata
logger = logging.getLogger(__name__)
@register_platform_adapter(
name="slack",
display_name="Slack",
description="Slack Events API消息适配器,支持文本消息和URL验证",
support_streaming=True,
support_image=True,
support_file=True,
support_voice=False,
)
class SlackPlatformAdapter(BasePlatformAdapter):
"""Slack平台适配器 - 签名验证 + challenge响应"""
def get_metadata(self) -> PlatformMetadata:
from ..platform_adapter import platform_registry
return platform_registry["slack"]
def verify_signature(self, payload: dict, signature: str, timestamp: str) -> bool:
"""Slack签名验证: v0=HMAC-SHA256(SigningSecret, 'v0:timestamp:body')"""
if not signature or not timestamp:
return True # 无签名默认通过
signing_secret = self.config.get("signing_secret", "")
if not signing_secret:
logger.warning("[Slack] 未配置signing_secret,跳过签名验证")
return True
try:
# Slack签名格式: v0=XXXX
# 签名基础字符串: v0:timestamp:body
body_str = json.dumps(payload, separators=(",", ":"), ensure_ascii=False)
sig_basestring = f"v0:{timestamp}:{body_str}"
computed_sig = "v0=" + hmac.new(
signing_secret.encode("utf-8"),
sig_basestring.encode("utf-8"),
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(computed_sig, signature)
except Exception as e:
logger.error(f"[Slack] 签名验证异常: {e}")
return False
def parse_webhook(self, payload: dict) -> dict:
"""解析Slack webhook payload为统一消息格式"""
# Slack URL验证 challenge 在外部处理
event = payload.get("event", {})
event_type = event.get("type", "message")
subtype = event.get("subtype", "")
content = ""
content_type = "text"
sender_id = ""
sender_name = ""
message_id = ""
group_id = None
group_name = None
if event_type == "message":
text = event.get("text", "")
user = event.get("user", "")
channel = event.get("channel", "")
ts = event.get("ts", "")
content = text
content_type = "text"
sender_id = user
sender_name = event.get("user_profile", {}).get("display_name", user)
message_id = ts
# 区分私聊/群聊
channel_type = event.get("channel_type", "")
if channel or channel_type == "channel":
group_id = channel
elif channel:
group_id = channel
elif event_type == "app_mention":
text = event.get("text", "")
user = event.get("user", "")
channel = event.get("channel", "")
ts = event.get("ts", "")
# 去除@bot标记
content = text
content_type = "text"
sender_id = user
sender_name = user
message_id = ts
group_id = channel
else:
content = json.dumps(event, ensure_ascii=False)
content_type = event_type
return {
"content_type": content_type,
"content": content,
"sender_id": sender_id,
"sender_name": sender_name,
"message_id": message_id,
"timestamp": int(float(message_id.split(".")[0]) if message_id else time.time()),
"group_id": group_id,
"group_name": group_name,
"raw": payload,
}
def build_reply(self, channel_config: dict, content: str, reply_to: str = None) -> dict:
"""构建Slack回复消息"""
bot_token = channel_config.get("bot_token", "")
channel = channel_config.get("channel", "")
if not bot_token:
logger.warning("[Slack] 未配置bot_token,无法构建回复")
return {}
body = {
"channel": channel,
"text": content,
}
if reply_to:
body["thread_ts"] = reply_to
return {
"api_url": "https://slack.com/api/chat.postMessage",
"headers": {
"Content-Type": "application/json",
"Authorization": f"Bearer {bot_token}",
},
"body": body,
}