初始提交: Qihu-AIFlow v2.0.1 完整项目
- 前端 React+TypeScript (源码+构建产物) - 后端 Python FastAPI - 数据库迁移 001-015 - Docker部署配置 - 微信ClawBot API频道绑定 - 项目文档 README.md
This commit is contained in:
commit
78ff261b5a
199 files changed
+69126
No files matched your search
@@ -0,0 +1,146 @@
|
||||
"""
|
||||
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,
|
||||
}
|
||||
Reference in new issue
Block a user