refactor: QQ → OBv11 重命名 + 平台格式统一抽象

- 所有对外称呼从 QQ 改为 OBv11(注释/提示词/日志/配置项)
- 新增 PlatformFormat 结构体,统一管理平台消息标记格式
- defaultPlatformFormats() 注册表替代硬编码 qqTargetRe
- extractProactiveMessage 改为 Thinker 方法,遍历格式注册表匹配
- 配置项重命名: QQ_BOT_PORT → OBV11_BOT_PORT, QQBotPort → OBv11BotPort
- 标记格式: 【QQ群聊】→【OBv11群聊】、【QQ私聊】→【OBv11私聊】

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-06-22 20:48:07 +08:00
parent 70dbb23234
commit 41f653b672
25 changed files with 1609 additions and 82 deletions
+14 -14
View File
@@ -298,16 +298,16 @@ func main() {
}
// Sync admin identities from config fields.
syncAdminUIDs(mapper, platform, fields, cfg.AdminNickname)
// Restart QQ reader when QQ config changes.
// Restart OBv11 reader when OBv11 config changes.
if platform == "qq" {
startQQReaders(router)
startOBv11Readers(router)
}
} else {
router.RemoveAdapter(name)
fmt.Printf("Platform adapter removed: %s\n", name)
// Cancel reader goroutines for removed adapter.
if platform == "qq" {
startQQReaders(router)
startOBv11Readers(router)
}
}
})
@@ -324,8 +324,8 @@ func main() {
blh := handler.NewBlocklistHandler(blocklistStore)
blh.RegisterRoutes(mux)
// Start QQ message reader loop.
startQQReaders(router)
// Start OBv11 message reader loop.
startOBv11Readers(router)
addr := ":" + cfg.Port
srv := &http.Server{Addr: addr, Handler: mux}
@@ -355,8 +355,8 @@ func main() {
var qqReaderCancels = make(map[string]context.CancelFunc)
var qqReaderCancelsMu sync.Mutex
// startQQReaders cancels any existing QQ readers and starts one per registered QQ adapter.
func startQQReaders(router *bridge.PlatformRouter) {
// startOBv11Readers cancels any existing OBv11 readers and starts one per registered OBv11 adapter.
func startOBv11Readers(router *bridge.PlatformRouter) {
// Cancel all existing readers.
qqReaderCancelsMu.Lock()
for _, cancel := range qqReaderCancels {
@@ -392,7 +392,7 @@ func startQQReaders(router *bridge.PlatformRouter) {
// Dispatcher: route to worker by session hash so same conversation stays ordered.
go func() {
for msg := range rawCh {
idx := hashQQSession(msg) % numWorkers
idx := hashOBv11Session(msg) % numWorkers
workerChs[idx] <- msg
}
for i := 0; i < numWorkers; i++ {
@@ -503,7 +503,7 @@ func createAdapters(cfg *config.Config, store *config.Store) []bridge.PlatformAd
func createSingleAdapter(cfg *config.Config, platform, configName string, fields map[string]string) bridge.PlatformAdapter {
switch platform {
case "qq":
port := cfg.QQBotPort
port := cfg.OBv11BotPort
if p, ok := fields["bot_port"]; ok && p != "" {
port = p
}
@@ -564,8 +564,8 @@ func mergeFields(cfg *config.Config, platform string, stored *config.PlatformCon
if fields["webhook_url"] == "" && cfg.TelegramWebhookURL != "" && platform == "telegram" {
fields["webhook_url"] = cfg.TelegramWebhookURL
}
if fields["bot_port"] == "" && cfg.QQBotPort != "" && platform == "qq" {
fields["bot_port"] = cfg.QQBotPort
if fields["bot_port"] == "" && cfg.OBv11BotPort != "" && platform == "qq" {
fields["bot_port"] = cfg.OBv11BotPort
}
return fields
}
@@ -928,10 +928,10 @@ func hasOnlySilentMessages(messages []bridge.ResponseMessage) bool {
return true
}
// hashQQSession returns a hash for dispatching a QQ message to a worker.
// hashOBv11Session returns a hash for dispatching an OBv11 message to a worker.
// Messages from the same conversation (private or group) get the same hash,
// preserving ordering within a session while allowing cross-session parallelism.
func hashQQSession(msg *qqadapter.OBv11Message) uint32 {
func hashOBv11Session(msg *qqadapter.OBv11Message) uint32 {
h := fnv.New32a()
h.Write([]byte(msg.MessageType))
h.Write([]byte(":"))
@@ -966,7 +966,7 @@ func parseIntOr(s string, defaultVal int) int {
func seedIdentities(m *bridge.IdentityMapper, store *config.Store, adminNickname string) {
// From environment variables.
for _, entry := range []struct{ envKey, platform string }{
{"QQ_ADMIN_UID", "qq"},
{"OBV11_ADMIN_UID", "obv11"},
{"TELEGRAM_ADMIN_UID", "telegram"},
} {
if raw := os.Getenv(entry.envKey); raw != "" {
@@ -21,7 +21,7 @@ type PlatformAdapter interface {
}
// ProactiveSender is an optional interface for adapters that can
// proactively send messages (e.g., QQ bot sending without prior request).
// proactively send messages (e.g., OBv11 bot sending without prior request).
type ProactiveSender interface {
SendProactive(chatType string, userID, groupID int64, content string) error
}
@@ -14,7 +14,7 @@ type Config struct {
InternalToken string
// Platform-specific.
QQBotPort string // port for QQ OBv11 reverse WebSocket
OBv11BotPort string // port for OBv11 reverse WebSocket
TelegramToken string // Telegram Bot API token
TelegramWebhookURL string // public webhook URL for Telegram
@@ -34,7 +34,7 @@ func Load() *Config {
Env: "development",
GatewayURL: "http://localhost:8080",
AICoreURL: "http://localhost:8081",
QQBotPort: "8096",
OBv11BotPort: "8096",
}
if v := os.Getenv("PORT"); v != "" {
cfg.Port = v
@@ -51,8 +51,8 @@ func Load() *Config {
if v := os.Getenv("INTERNAL_SERVICE_TOKEN"); v != "" {
cfg.InternalToken = v
}
if v := os.Getenv("QQ_BOT_PORT"); v != "" {
cfg.QQBotPort = v
if v := os.Getenv("OBV11_BOT_PORT"); v != "" {
cfg.OBv11BotPort = v
}
if v := os.Getenv("TELEGRAM_BOT_TOKEN"); v != "" {
cfg.TelegramToken = v
@@ -180,7 +180,7 @@ func (h *BridgeHandler) sendProactive(w http.ResponseWriter, r *http.Request) {
return
}
// Map chat type to QQ message_type
// Map chat type to OBv11 message_type
msgType := req.ChatType
if msgType != "private" && msgType != "group" {
writeJSON(w, http.StatusBadRequest, errResp("chat_type must be private or group"))
@@ -196,7 +196,7 @@ func (h *BridgeHandler) sendProactive(w http.ResponseWriter, r *http.Request) {
content = fmt.Sprintf("[CQ:at,qq=%s] %s", req.AtUserID, content)
}
// Find the adapter. For QQ, use "qq" as the default adapter name.
// Find the adapter. For OBv11, use the platform name as adapter name.
adapterName := req.Platform
err := h.router.SendProactive(adapterName, msgType, userID, groupID, content)
if err != nil {