fix: platform_silent记忆提取 + 群聊上下文整合 + 多QQ实例支持
- platform_silent模式接入Orchestrator记忆提取:被动观察群聊时提取值得记住的信息到对应命名空间 - post_chat后台思考注入平台观察:对话后思考也能看到群聊摘要 - QQ适配器:OneBot v11 self_id动态捕获、CQ图片URL提取、视觉+OCR并行处理 - Router解耦:ConfigName/PlatformName分离,支持多QQ实例独立连接 - 黑白名单功能:后端API + Ethend代理 + UI面板 - \n\n双换行断句:AI回复按双换行分割为多条消息按间隔发送 - @提及修复:bot自感知UID进行@检测 - 群聊上下文共享:channel-based userID避免记忆碎片化 - 消息日志显示处理后内容而非原始SSE数据 - platform-bridge Dockerfile + docker-compose.yml更新 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// BlocklistMode is either "blacklist" or "whitelist".
|
||||
type BlocklistSettings struct {
|
||||
Mode string `json:"mode"` // "blacklist" (default) or "whitelist"
|
||||
GroupIDs []string `json:"group_ids"` // group IDs to block/allow
|
||||
UserIDs []string `json:"user_ids"` // private chat user IDs to block/allow
|
||||
}
|
||||
|
||||
// BlocklistStore manages persistence of blocklist settings.
|
||||
type BlocklistStore struct {
|
||||
mu sync.RWMutex
|
||||
path string
|
||||
settings BlocklistSettings
|
||||
}
|
||||
|
||||
// NewBlocklistStore loads or creates blocklist settings file.
|
||||
func NewBlocklistStore(path string) (*BlocklistStore, error) {
|
||||
s := &BlocklistStore{
|
||||
path: path,
|
||||
settings: BlocklistSettings{
|
||||
Mode: "blacklist",
|
||||
GroupIDs: []string{},
|
||||
UserIDs: []string{},
|
||||
},
|
||||
}
|
||||
if err := s.load(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *BlocklistStore) load() error {
|
||||
data, err := os.ReadFile(s.path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return s.save() // write defaults
|
||||
}
|
||||
return fmt.Errorf("read blocklist file: %w", err)
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if err := json.Unmarshal(data, &s.settings); err != nil {
|
||||
return fmt.Errorf("parse blocklist file: %w", err)
|
||||
}
|
||||
if s.settings.Mode == "" {
|
||||
s.settings.Mode = "blacklist"
|
||||
}
|
||||
if s.settings.GroupIDs == nil {
|
||||
s.settings.GroupIDs = []string{}
|
||||
}
|
||||
if s.settings.UserIDs == nil {
|
||||
s.settings.UserIDs = []string{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *BlocklistStore) save() error {
|
||||
s.mu.RLock()
|
||||
data, err := json.MarshalIndent(s.settings, "", " ")
|
||||
s.mu.RUnlock()
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal blocklist: %w", err)
|
||||
}
|
||||
tmpPath := s.path + ".tmp"
|
||||
if err := os.WriteFile(tmpPath, data, 0640); err != nil {
|
||||
return fmt.Errorf("write blocklist: %w", err)
|
||||
}
|
||||
return os.Rename(tmpPath, s.path)
|
||||
}
|
||||
|
||||
// Get returns current blocklist settings.
|
||||
func (s *BlocklistStore) Get() BlocklistSettings {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
cp := BlocklistSettings{
|
||||
Mode: s.settings.Mode,
|
||||
GroupIDs: make([]string, len(s.settings.GroupIDs)),
|
||||
UserIDs: make([]string, len(s.settings.UserIDs)),
|
||||
}
|
||||
copy(cp.GroupIDs, s.settings.GroupIDs)
|
||||
copy(cp.UserIDs, s.settings.UserIDs)
|
||||
return cp
|
||||
}
|
||||
|
||||
// Set updates and persists blocklist settings.
|
||||
func (s *BlocklistStore) Set(bs BlocklistSettings) error {
|
||||
if bs.Mode != "blacklist" && bs.Mode != "whitelist" {
|
||||
return fmt.Errorf("invalid mode: %s (must be blacklist or whitelist)", bs.Mode)
|
||||
}
|
||||
if bs.GroupIDs == nil {
|
||||
bs.GroupIDs = []string{}
|
||||
}
|
||||
if bs.UserIDs == nil {
|
||||
bs.UserIDs = []string{}
|
||||
}
|
||||
s.mu.Lock()
|
||||
s.settings = bs
|
||||
s.mu.Unlock()
|
||||
return s.save()
|
||||
}
|
||||
|
||||
// IsBlocked checks whether a message should be blocked based on channel type and ID.
|
||||
// In blacklist mode: returns true if the id is IN the list.
|
||||
// In whitelist mode: returns true if the id is NOT in the list.
|
||||
// Admin users should call this with isAdmin=true to always bypass.
|
||||
func (s *BlocklistStore) IsBlocked(channelType, channelID, senderID string, isAdmin bool) bool {
|
||||
if isAdmin {
|
||||
return false
|
||||
}
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
switch s.settings.Mode {
|
||||
case "whitelist":
|
||||
// Block if NOT in the whitelist.
|
||||
if channelType == "group" {
|
||||
return !contains(s.settings.GroupIDs, channelID)
|
||||
}
|
||||
return !contains(s.settings.UserIDs, senderID)
|
||||
|
||||
case "blacklist":
|
||||
fallthrough
|
||||
default:
|
||||
// Block if IN the blacklist.
|
||||
if channelType == "group" {
|
||||
return contains(s.settings.GroupIDs, channelID)
|
||||
}
|
||||
return contains(s.settings.UserIDs, senderID)
|
||||
}
|
||||
}
|
||||
|
||||
func contains(list []string, val string) bool {
|
||||
for _, v := range list {
|
||||
if v == val {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user