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:
@@ -932,7 +932,7 @@ func handleChat(
|
||||
}
|
||||
|
||||
// Admin private messages: redirect to the main admin session so conversation
|
||||
// history is shared across platforms (QQ, web UI, etc.).
|
||||
// history is shared across platforms (OBv11, web UI, etc.).
|
||||
if req.UserID == "admin" && req.Source.ChannelType == "direct" && adminSessionID != "" {
|
||||
req.SessionID = adminSessionID
|
||||
}
|
||||
|
||||
@@ -30,24 +30,59 @@ type PendingThought struct {
|
||||
Consumed bool `json:"consumed"`
|
||||
}
|
||||
|
||||
// ProactiveTarget describes the target for proactive platform messages (QQ, etc.).
|
||||
// ProactiveTarget describes the target for proactive platform messages (OBv11, etc.).
|
||||
// When nil, the message goes through the existing Web push path.
|
||||
type ProactiveTarget struct {
|
||||
Platform string // "qq"
|
||||
ChatType string // "private" or "group"
|
||||
UserID string // QQ user number
|
||||
GroupID string // QQ group number (group chat)
|
||||
AtUserID string // QQ number to @mention (optional)
|
||||
UserID string // OBv11 user number
|
||||
GroupID string // OBv11 group number (group chat)
|
||||
AtUserID string // OBv11 number to @mention (optional)
|
||||
}
|
||||
|
||||
// PlatformChannel represents a platform channel to observe for background thinking.
|
||||
type PlatformChannel struct {
|
||||
Platform string // qq, telegram, etc.
|
||||
ChannelType string // group, private
|
||||
ChannelID string // group ID or user QQ number
|
||||
ChannelID string // group ID or user OBv11 number
|
||||
ChannelName string // group name or user display name (resolved at runtime)
|
||||
}
|
||||
|
||||
// PlatformFormat describes how proactive message targets are formatted for a platform.
|
||||
type PlatformFormat struct {
|
||||
// Human-readable platform label (e.g. "OBv11", "Telegram").
|
||||
Label string
|
||||
// Regex pattern with 3 capture groups: (chatType, channelID, atUserID).
|
||||
// chatType group must contain the platform-specific chat type keyword (e.g. "群聊", "私聊").
|
||||
TargetRegex string
|
||||
// Compiled regex (populated at registration time).
|
||||
compiled *regexp.Regexp
|
||||
// Format verbs for building markers in prompts.
|
||||
GroupMarkerFmt string // e.g. "【OBv11群聊:%s】"
|
||||
PrivateMarkerFmt string // e.g. "【OBv11私聊:%s】"
|
||||
GroupAtMarkerFmt string // e.g. "【OBv11群聊:%s@%s】"
|
||||
// Chat type strings used by this platform in regex and markers.
|
||||
GroupKeyword string // e.g. "群聊"
|
||||
PrivateKeyword string // e.g. "私聊"
|
||||
}
|
||||
|
||||
// defaultPlatformFormats returns the built-in platform format registry.
|
||||
func defaultPlatformFormats() map[string]*PlatformFormat {
|
||||
formats := make(map[string]*PlatformFormat)
|
||||
qq := &PlatformFormat{
|
||||
Label: "OBv11",
|
||||
TargetRegex: `【OBv11(私聊|群聊):(\d+)(?:@(\d+))?】`,
|
||||
GroupMarkerFmt: "【OBv11群聊:%s】",
|
||||
PrivateMarkerFmt: "【OBv11私聊:%s】",
|
||||
GroupAtMarkerFmt: "【OBv11群聊:%s@%s】",
|
||||
GroupKeyword: "群聊",
|
||||
PrivateKeyword: "私聊",
|
||||
}
|
||||
qq.compiled = regexp.MustCompile(qq.TargetRegex)
|
||||
formats["qq"] = qq
|
||||
return formats
|
||||
}
|
||||
|
||||
// ParsePlatformChannels parses PLATFORM_CHANNELS env var.
|
||||
// Format: "qq:group:123456,telegram:group:789012"
|
||||
// Optional 4th field for display name: "qq:group:123456:群名称"
|
||||
@@ -193,6 +228,9 @@ type Thinker struct {
|
||||
|
||||
// 平台 Bot UID (platform -> bot's own UID, e.g. "qq" -> "123456789")
|
||||
botUIDs map[string]string
|
||||
|
||||
// 平台格式注册表 (platform -> format)
|
||||
platformFormats map[string]*PlatformFormat
|
||||
}
|
||||
|
||||
// AutonomousToolPolicy 自主思考工具调用安全策略
|
||||
@@ -237,14 +275,14 @@ func (t *Thinker) SetMessagePusher(pusher func(string, string, string)) {
|
||||
t.messagePusher = pusher
|
||||
}
|
||||
|
||||
// SetPlatformMessagePusher sets the callback for pushing proactive messages to platform adapters (QQ, etc.).
|
||||
// SetPlatformMessagePusher sets the callback for pushing proactive messages to platform adapters (OBv11, etc.).
|
||||
func (t *Thinker) SetPlatformMessagePusher(pusher func(ProactiveTarget, string)) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
t.platformMessagePusher = pusher
|
||||
}
|
||||
|
||||
// SetBotUID sets the bot's own platform UID (e.g., QQ number).
|
||||
// SetBotUID sets the bot's own platform UID (e.g., OBv11 account).
|
||||
func (t *Thinker) SetBotUID(platform, uid string) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
@@ -412,6 +450,7 @@ func NewThinker(
|
||||
proactiveGuard: DefaultProactiveGuard(),
|
||||
platformChannels: cfg.PlatformChannels,
|
||||
platformThinkInterval: cfg.PlatformSilentThinkInterval,
|
||||
platformFormats: defaultPlatformFormats(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1481,7 +1520,7 @@ func (t *Thinker) buildThinkingUserPrompt(
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
|
||||
// QQ platform identity and available channels for proactive messaging.
|
||||
// OBv11 platform identity and available channels for proactive messaging.
|
||||
t.mu.Lock()
|
||||
qqChannels := t.platformChannels
|
||||
botUIDs := t.botUIDs
|
||||
@@ -1489,11 +1528,11 @@ func (t *Thinker) buildThinkingUserPrompt(
|
||||
lastMsgTime := t.lastUserMessage
|
||||
t.mu.Unlock()
|
||||
if len(qqChannels) > 0 {
|
||||
sb.WriteString("\n\n【你的QQ平台身份与可用频道】\n")
|
||||
sb.WriteString("\n\n【你的平台身份与可用频道】\n")
|
||||
|
||||
// Bot's own identity.
|
||||
if qqBotUID, ok := botUIDs["qq"]; ok && qqBotUID != "" {
|
||||
sb.WriteString(fmt.Sprintf("你在QQ上的账号是: %s(昔涟),这就是你。\n", qqBotUID))
|
||||
sb.WriteString(fmt.Sprintf("你在OBv11上的账号是: %s(昔涟),这就是你。\n", qqBotUID))
|
||||
}
|
||||
|
||||
// Active session context with time, group name, and trigger-aware guidance.
|
||||
@@ -1518,23 +1557,23 @@ func (t *Thinker) buildThinkingUserPrompt(
|
||||
if ch.ChannelType == "group" {
|
||||
if triggerReason == "post_chat" {
|
||||
if timeHint != "" && timeHint != "刚刚" {
|
||||
sb.WriteString(fmt.Sprintf("【%s】你当前正在QQ群聊 %s 中。刚刚群里有人说了话——如果你想回应,用【主动消息】【QQ群聊:%s】格式输出。\n", timeHint, chLabel, activeChID))
|
||||
sb.WriteString(fmt.Sprintf("【%s】你当前正在OBv11群聊 %s 中。刚刚群里有人说了话——如果你想回应,用【主动消息】【OBv11群聊:%s】格式输出。\n", timeHint, chLabel, activeChID))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("【刚刚】你当前正在QQ群聊 %s 中。群里有人说了话——如果你想回应,用【主动消息】【QQ群聊:%s】格式输出。\n", chLabel, activeChID))
|
||||
sb.WriteString(fmt.Sprintf("【刚刚】你当前正在OBv11群聊 %s 中。群里有人说了话——如果你想回应,用【主动消息】【OBv11群聊:%s】格式输出。\n", chLabel, activeChID))
|
||||
}
|
||||
} else {
|
||||
// Autonomous thinking (periodic, silence, startup).
|
||||
if timeHint != "" {
|
||||
sb.WriteString(fmt.Sprintf("【上次活跃: %s】你当前在QQ群聊 %s 中。现在是自主思考时间——如果你想主动对群里说些什么,用【主动消息】【QQ群聊:%s】格式。\n", timeHint, chLabel, activeChID))
|
||||
sb.WriteString(fmt.Sprintf("【上次活跃: %s】你当前在OBv11群聊 %s 中。现在是自主思考时间——如果你想主动对群里说些什么,用【主动消息】【OBv11群聊:%s】格式。\n", timeHint, chLabel, activeChID))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("你当前在QQ群聊 %s 中。现在是自主思考时间——如果你想主动对群里说些什么,用【主动消息】【QQ群聊:%s】格式。\n", chLabel, activeChID))
|
||||
sb.WriteString(fmt.Sprintf("你当前在OBv11群聊 %s 中。现在是自主思考时间——如果你想主动对群里说些什么,用【主动消息】【OBv11群聊:%s】格式。\n", chLabel, activeChID))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if triggerReason == "post_chat" {
|
||||
sb.WriteString(fmt.Sprintf("【刚刚】你当前正在与QQ用户 %s 私聊。想回应ta的话用【主动消息】【QQ私聊:%s】格式。\n", chLabel, activeChID))
|
||||
sb.WriteString(fmt.Sprintf("【刚刚】你当前正在与OBv11用户 %s 私聊。想回应ta的话用【主动消息】【OBv11私聊:%s】格式。\n", chLabel, activeChID))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("你当前在与QQ用户 %s 私聊。现在是自主思考时间——想主动发消息用【主动消息】【QQ私聊:%s】格式。\n", chLabel, activeChID))
|
||||
sb.WriteString(fmt.Sprintf("你当前在与OBv11用户 %s 私聊。现在是自主思考时间——想主动发消息用【主动消息】【OBv11私聊:%s】格式。\n", chLabel, activeChID))
|
||||
}
|
||||
}
|
||||
break
|
||||
@@ -1543,10 +1582,10 @@ func (t *Thinker) buildThinkingUserPrompt(
|
||||
}
|
||||
|
||||
sb.WriteString("\n发送主动消息的格式:\n")
|
||||
sb.WriteString("- QQ私聊: 【主动消息】【QQ私聊:QQ号】消息内容\n")
|
||||
sb.WriteString("- QQ群聊: 【主动消息】【QQ群聊:群号】消息内容\n")
|
||||
sb.WriteString("- QQ群聊@某人: 【主动消息】【QQ群聊:群号@QQ号】消息内容\n")
|
||||
sb.WriteString("\n可用的QQ频道列表:\n")
|
||||
sb.WriteString("- OBv11私聊: 【主动消息】【OBv11私聊:账号】消息内容\n")
|
||||
sb.WriteString("- OBv11群聊: 【主动消息】【OBv11群聊:群号】消息内容\n")
|
||||
sb.WriteString("- OBv11群聊@某人: 【主动消息】【OBv11群聊:群号@账号】消息内容\n")
|
||||
sb.WriteString("\n可用的平台频道列表:\n")
|
||||
for _, ch := range qqChannels {
|
||||
if ch.Platform != "qq" {
|
||||
continue
|
||||
@@ -1567,7 +1606,7 @@ func (t *Thinker) buildThinkingUserPrompt(
|
||||
sb.WriteString("- 群聊消息要有公共价值,不要当成私聊\n")
|
||||
sb.WriteString("- @某人时:确认这个人在群里有发言过,你的@内容是对他之前说的话的回应\n")
|
||||
if triggerReason == "post_chat" {
|
||||
sb.WriteString("- 重要:如果你在反思中想往QQ群发消息,必须用【主动消息】【QQ群聊:群号】格式,不要省略群号!\n")
|
||||
sb.WriteString("- 重要:如果你在反思中想往平台群发消息,必须用【主动消息】【OBv11群聊:群号】格式,不要省略群号!\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1639,8 +1678,8 @@ func (t *Thinker) storeThought(content string, toolCallsJSON string, toolCallCou
|
||||
t.pendingThoughts = t.pendingThoughts[len(t.pendingThoughts)-10:]
|
||||
}
|
||||
|
||||
// Extract proactive message and optional QQ target.
|
||||
proactiveMsg, proactiveTarget := extractProactiveMessage(content)
|
||||
// Extract proactive message and optional platform target.
|
||||
proactiveMsg, proactiveTarget := t.extractProactiveMessage(content)
|
||||
// Prefer active session, fall back to admin main session.
|
||||
pushSessionID := t.activeSessionID
|
||||
if pushSessionID == "" {
|
||||
@@ -1729,13 +1768,10 @@ func (t *Thinker) storeThought(content string, toolCallsJSON string, toolCallCou
|
||||
//
|
||||
// 要求标记独立成行(前面只有空白或行首),避免把自然语言中的提及
|
||||
// 当作指令(如 "不需要写【主动消息】" 这类否定表述)。
|
||||
// qqTargetRe matches QQ target markers like 【QQ私聊:123456】, 【QQ群聊:789012】, 【QQ群聊:789012@123456】
|
||||
var qqTargetRe = regexp.MustCompile(`【QQ(私聊|群聊):(\d+)(?:@(\d+))?】`)
|
||||
|
||||
// extractProactiveMessage extracts the 【主动消息】 marker and optional QQ target from thinking content.
|
||||
// extractProactiveMessage extracts the 【主动消息】 marker and optional platform target from thinking content.
|
||||
// Returns the message content and an optional ProactiveTarget for platform delivery.
|
||||
// When target is nil, the message goes through the existing Web push path.
|
||||
func extractProactiveMessage(content string) (string, *ProactiveTarget) {
|
||||
func (t *Thinker) extractProactiveMessage(content string) (string, *ProactiveTarget) {
|
||||
marker := "【主动消息】"
|
||||
|
||||
// Scan each line; only accept lines where the marker starts the line (ignoring leading whitespace).
|
||||
@@ -1756,33 +1792,38 @@ func extractProactiveMessage(content string) (string, *ProactiveTarget) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse optional QQ target marker right after 【主动消息】.
|
||||
// Parse optional platform target marker right after 【主动消息】.
|
||||
msg := raw
|
||||
var target *ProactiveTarget
|
||||
if loc := qqTargetRe.FindStringSubmatchIndex(raw); loc != nil && loc[0] == 0 {
|
||||
m := qqTargetRe.FindStringSubmatch(raw)
|
||||
if len(m) == 4 {
|
||||
chatType := "private"
|
||||
if m[1] == "群聊" {
|
||||
chatType = "group"
|
||||
}
|
||||
target = &ProactiveTarget{
|
||||
Platform: "qq",
|
||||
ChatType: chatType,
|
||||
UserID: m[2],
|
||||
GroupID: m[2],
|
||||
AtUserID: m[3],
|
||||
}
|
||||
// For private chat, UserID is the QQ number; GroupID stays empty.
|
||||
if chatType == "private" {
|
||||
target.GroupID = ""
|
||||
}
|
||||
}
|
||||
// Remove the QQ target marker from the message content.
|
||||
msg = strings.TrimSpace(raw[loc[1]:])
|
||||
if msg == "" {
|
||||
// Try each registered platform format.
|
||||
for platform, pf := range t.platformFormats {
|
||||
if pf.compiled == nil {
|
||||
continue
|
||||
}
|
||||
if loc := pf.compiled.FindStringSubmatchIndex(raw); loc != nil && loc[0] == 0 {
|
||||
m := pf.compiled.FindStringSubmatch(raw)
|
||||
if len(m) == 4 {
|
||||
chatType := "private"
|
||||
if m[1] == pf.GroupKeyword {
|
||||
chatType = "group"
|
||||
}
|
||||
target = &ProactiveTarget{
|
||||
Platform: platform,
|
||||
ChatType: chatType,
|
||||
UserID: m[2],
|
||||
GroupID: m[2],
|
||||
AtUserID: m[3],
|
||||
}
|
||||
if chatType == "private" {
|
||||
target.GroupID = ""
|
||||
}
|
||||
}
|
||||
msg = strings.TrimSpace(raw[loc[1]:])
|
||||
if msg == "" {
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Limit message length (200 chars max, keep it short).
|
||||
|
||||
@@ -96,7 +96,7 @@ func inferAudioFormat(urlStr, contentType string) string {
|
||||
if strings.Contains(contentType, "audio/ogg") || strings.Contains(contentType, "opus") {
|
||||
return "ogg"
|
||||
}
|
||||
return "amr" // default for QQ voice messages
|
||||
return "amr" // default for OBv11 voice messages
|
||||
}
|
||||
|
||||
func (p *DashScopeASRProvider) Transcribe(ctx context.Context, audioURL, language string) (string, error) {
|
||||
|
||||
@@ -83,7 +83,7 @@ func (e *Extractor) extractObservationsWithLLM(ctx context.Context, message stri
|
||||
观察到的消息: %s
|
||||
|
||||
请以JSON格式返回提取的记忆。这条消息来自群聊/频道,昔涟只是旁观者。
|
||||
消息格式为:[群聊 群号] 发送者昵称 (QQ号):消息内容
|
||||
消息格式为:[群聊 群号] 发送者昵称 (OBv11账号):消息内容
|
||||
提取角度:这条消息中包含了什么关于消息发送者、讨论主题、事件或氛围的信息?
|
||||
重要:请以实际发送者的名字为主语(如"某某说..."),不要统一用"开拓者"称呼所有发言者。
|
||||
|
||||
|
||||
@@ -138,8 +138,8 @@ type ProcessParams struct {
|
||||
Mode string // text / voice_msg / voice_assistant
|
||||
Nickname string
|
||||
ChannelType string // direct / group
|
||||
ChannelID string // platform channel ID (group ID or private QQ number)
|
||||
BotUID string // bot's own platform UID (e.g., QQ number)
|
||||
ChannelID string // platform channel ID (group ID or private OBv11 number)
|
||||
BotUID string // bot's own platform UID (e.g., OBv11 account)
|
||||
}
|
||||
|
||||
// ProcessResult 处理结果
|
||||
|
||||
@@ -216,7 +216,7 @@ func (s *Synthesizer) buildSynthesizeMessages(params SynthesizeParams) []model.L
|
||||
if params.ChannelType == "group" {
|
||||
messages = append(messages, model.LLMMessage{
|
||||
Role: model.RoleSystem,
|
||||
Content: "【群聊上下文】这条消息来自QQ群聊。消息前缀 [群聊 群号] 昵称 (QQ号) 标注了真实发送者。你不是在和开拓者一对一私聊,而是在群聊中和不同成员交流。请根据当前这条消息前缀中的发送者名字来称呼对方——即使你之前在历史对话中称呼过别人,也不要把之前用的称呼套在当前发送者身上。不同的人有不同的名字。只在对你说话或延续已有对话时才回复。",
|
||||
Content: "【群聊上下文】这条消息来自OBv11群聊。消息前缀 [群聊 群号] 昵称 (OBv11账号) 标注了真实发送者。你不是在和开拓者一对一私聊,而是在群聊中和不同成员交流。请根据当前这条消息前缀中的发送者名字来称呼对方——即使你之前在历史对话中称呼过别人,也不要把之前用的称呼套在当前发送者身上。不同的人有不同的名字。只在对你说话或延续已有对话时才回复。",
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user