feat: QQ主动消息管道 + 戳一戳事件 + thinker提示词优化
- ProactiveTarget 结构体 + QQ目标正则解析(extractProactiveMessage 返回双值) - storeThought 双路推送:platform消息 → platformMessagePusher,web消息 → messagePusher - platform-bridge: SendProactive接口 + ProactiveSender + /api/v1/internal/send-proactive端点 - QQ戳一戳/notice事件 → noticeToUnified → ContentType "action" - thinker提示词注入QQ频道上下文:群名称(群号)、活跃时间、trigger-aware引导 - SetBotUID/SetPlatformMessagePusher/AddOrUpdatePlatformChannel 方法 - ai-core source 增加 BotUID/GroupName,platform-bridge source 增加 bot_uid/group_name - 支持 [CQ:at,qq=xxx] @提及标签自动拼接 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -2,18 +2,26 @@ package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene/platform-bridge/internal/bridge"
|
||||
)
|
||||
|
||||
// BridgeHandler exposes the Platform Bridge REST API.
|
||||
type BridgeHandler struct {
|
||||
router *bridge.PlatformRouter
|
||||
router *bridge.PlatformRouter
|
||||
internalToken string
|
||||
}
|
||||
|
||||
func NewBridgeHandler(router *bridge.PlatformRouter) *BridgeHandler {
|
||||
return &BridgeHandler{router: router}
|
||||
return &BridgeHandler{
|
||||
router: router,
|
||||
internalToken: os.Getenv("INTERNAL_SERVICE_TOKEN"),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BridgeHandler) RegisterRoutes(mux *http.ServeMux) {
|
||||
@@ -21,6 +29,7 @@ func (h *BridgeHandler) RegisterRoutes(mux *http.ServeMux) {
|
||||
mux.HandleFunc("/api/v1/platforms", h.listPlatforms)
|
||||
mux.HandleFunc("/api/v1/platforms/", h.platformInfo)
|
||||
mux.HandleFunc("/api/v1/identities", h.listIdentities)
|
||||
mux.HandleFunc("/api/v1/internal/send-proactive", h.sendProactive)
|
||||
mux.HandleFunc("/api/v1/webhook/telegram", h.telegramWebhook)
|
||||
mux.HandleFunc("/api/v1/webhook/", h.genericWebhook)
|
||||
}
|
||||
@@ -138,6 +147,83 @@ func (h *BridgeHandler) genericWebhook(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// sendProactive handles internal proactive message delivery to platform adapters.
|
||||
// POST /api/v1/internal/send-proactive
|
||||
func (h *BridgeHandler) sendProactive(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
writeJSON(w, http.StatusMethodNotAllowed, errResp("method not allowed"))
|
||||
return
|
||||
}
|
||||
|
||||
// Validate internal token
|
||||
token := r.Header.Get("X-Internal-Token")
|
||||
if h.internalToken == "" || token != h.internalToken {
|
||||
writeJSON(w, http.StatusUnauthorized, errResp("unauthorized"))
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Platform string `json:"platform"`
|
||||
ChatType string `json:"chat_type"`
|
||||
UserID string `json:"user_id"`
|
||||
GroupID string `json:"group_id"`
|
||||
AtUserID string `json:"at_user_id"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, errResp("invalid request body"))
|
||||
return
|
||||
}
|
||||
|
||||
if req.Platform == "" || req.ChatType == "" || req.Content == "" {
|
||||
writeJSON(w, http.StatusBadRequest, errResp("platform, chat_type, and content are required"))
|
||||
return
|
||||
}
|
||||
|
||||
// Map chat type to QQ message_type
|
||||
msgType := req.ChatType
|
||||
if msgType != "private" && msgType != "group" {
|
||||
writeJSON(w, http.StatusBadRequest, errResp("chat_type must be private or group"))
|
||||
return
|
||||
}
|
||||
|
||||
userID := parseIntSafe(req.UserID)
|
||||
groupID := parseIntSafe(req.GroupID)
|
||||
|
||||
// Prepend CQ @mention tag if at_user_id is specified
|
||||
content := req.Content
|
||||
if req.AtUserID != "" {
|
||||
content = fmt.Sprintf("[CQ:at,qq=%s] %s", req.AtUserID, content)
|
||||
}
|
||||
|
||||
// Find the adapter. For QQ, use "qq" as the default adapter name.
|
||||
adapterName := req.Platform
|
||||
err := h.router.SendProactive(adapterName, msgType, userID, groupID, content)
|
||||
if err != nil {
|
||||
log.Printf("[send-proactive] 发送失败: adapter=%s err=%v", adapterName, err)
|
||||
writeJSON(w, http.StatusInternalServerError, errResp("send failed: "+err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[send-proactive] 已发送: adapter=%s chat=%s user=%d group=%d at=%s len=%d",
|
||||
adapterName, msgType, userID, groupID, req.AtUserID, len(content))
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"success": true,
|
||||
"message": "消息已发送",
|
||||
})
|
||||
}
|
||||
|
||||
func parseIntSafe(s string) int64 {
|
||||
if s == "" {
|
||||
return 0
|
||||
}
|
||||
n, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func errResp(msg string) map[string]string {
|
||||
return map[string]string{"error": msg}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user