From d6a3a2253e2d7d4b59c194771f5e449defe33656 Mon Sep 17 00:00:00 2001 From: chenhg5 Date: Thu, 5 Mar 2026 13:59:25 +0800 Subject: [PATCH] feat: add banned words filter functionality Add banned words configuration and filtering logic to block messages containing prohibited content. Includes: - Config file support for banned_words list - Engine methods to set and check banned words - Case-insensitive matching during message processing - Localization support for blocked message response - Documentation updates in config example generated by llmgit Co-Authored-By: Claude --- cmd/cc-connect/main.go | 8 ++++++++ config.example.toml | 10 ++++++++++ config/config.go | 19 ++++++++++--------- core/engine.go | 39 +++++++++++++++++++++++++++++++++++++++ core/i18n.go | 9 +++++++++ 5 files changed, 76 insertions(+), 9 deletions(-) diff --git a/cmd/cc-connect/main.go b/cmd/cc-connect/main.go index 99219df..b5ae4be 100644 --- a/cmd/cc-connect/main.go +++ b/cmd/cc-connect/main.go @@ -205,6 +205,11 @@ func main() { return config.RemoveAlias(name) }) + // Wire banned words + if len(cfg.BannedWords) > 0 { + engine.SetBannedWords(cfg.BannedWords) + } + // Wire display truncation settings { dcfg := core.DisplayCfg{ @@ -569,6 +574,9 @@ func reloadConfig(configPath, projName string, engine *core.Engine) (*core.Confi engine.AddAlias(a.Name, a.Command) } + // Reload banned words + engine.SetBannedWords(cfg.BannedWords) + slog.Info("config reloaded", "project", projName) return result, nil } diff --git a/config.example.toml b/config.example.toml index 4b236f8..d3873df 100644 --- a/config.example.toml +++ b/config.example.toml @@ -132,6 +132,16 @@ level = "info" # debug, info, warn, error # directory (e.g. .claude/commands/*.md, .gemini/commands/*.md). # 此外,cc-connect 还会自动发现 Agent 的 commands 目录下的 .md 文件。 +# ============================================================================= +# Banned Words / 违禁词 +# ============================================================================= +# +# Messages containing any of these words will be blocked and NOT forwarded to +# the AI agent. Matching is case-insensitive. +# 消息中包含以下任一违禁词时,将被拦截,不会转发给 AI Agent。匹配不区分大小写。 + +# banned_words = ["违禁词1", "违禁词2", "secret_project"] + # ============================================================================= # Command Aliases / 命令别名 # ============================================================================= diff --git a/config/config.go b/config/config.go index 221efd8..f2ffca0 100644 --- a/config/config.go +++ b/config/config.go @@ -16,15 +16,16 @@ var configMu sync.Mutex var ConfigPath string type Config struct { - DataDir string `toml:"data_dir"` // session store directory, default ~/.cc-connect - Projects []ProjectConfig `toml:"projects"` - Commands []CommandConfig `toml:"commands"` // global custom slash commands - Aliases []AliasConfig `toml:"aliases"` // global command aliases - Log LogConfig `toml:"log"` - Language string `toml:"language"` // "en" or "zh", default is "en" - Speech SpeechConfig `toml:"speech"` - Display DisplayConfig `toml:"display"` - Quiet *bool `toml:"quiet,omitempty"` // global default for quiet mode; project-level overrides this + DataDir string `toml:"data_dir"` // session store directory, default ~/.cc-connect + Projects []ProjectConfig `toml:"projects"` + Commands []CommandConfig `toml:"commands"` // global custom slash commands + Aliases []AliasConfig `toml:"aliases"` // global command aliases + BannedWords []string `toml:"banned_words"` // messages containing any of these words are blocked + Log LogConfig `toml:"log"` + Language string `toml:"language"` // "en" or "zh", default is "en" + Speech SpeechConfig `toml:"speech"` + Display DisplayConfig `toml:"display"` + Quiet *bool `toml:"quiet,omitempty"` // global default for quiet mode; project-level overrides this } // DisplayConfig controls how intermediate messages (thinking, tool output) are shown. diff --git a/core/engine.go b/core/engine.go index 74441f6..4dd6140 100644 --- a/core/engine.go +++ b/core/engine.go @@ -139,6 +139,9 @@ type Engine struct { aliasSaveAddFunc func(name, command string) error aliasSaveDelFunc func(name string) error + bannedWords []string + bannedMu sync.RWMutex + // Interactive agent session management interactiveMu sync.Mutex interactiveStates map[string]*interactiveState // key = sessionKey @@ -293,6 +296,17 @@ func (e *Engine) ClearAliases() { e.aliases = make(map[string]string) } +// SetBannedWords replaces the banned words list. +func (e *Engine) SetBannedWords(words []string) { + e.bannedMu.Lock() + defer e.bannedMu.Unlock() + lower := make([]string, len(words)) + for i, w := range words { + lower[i] = strings.ToLower(w) + } + e.bannedWords = lower +} + // RemoveCommand removes a custom command by name. Returns false if not found. func (e *Engine) RemoveCommand(name string) bool { return e.commands.Remove(name) @@ -402,6 +416,22 @@ func (e *Engine) Stop() error { return nil } +// matchBannedWord returns the first banned word found in content, or "". +func (e *Engine) matchBannedWord(content string) string { + e.bannedMu.RLock() + defer e.bannedMu.RUnlock() + if len(e.bannedWords) == 0 { + return "" + } + lower := strings.ToLower(content) + for _, w := range e.bannedWords { + if strings.Contains(lower, w) { + return w + } + } + return "" +} + // resolveAlias checks if the content (or its first word) matches an alias and replaces it. func (e *Engine) resolveAlias(content string) string { e.aliasMu.RLock() @@ -443,6 +473,15 @@ func (e *Engine) handleMessage(p Platform, msg *Message) { content = e.resolveAlias(content) msg.Content = content + // Banned words check (skip for slash commands) + if !strings.HasPrefix(content, "/") { + if word := e.matchBannedWord(content); word != "" { + slog.Info("message blocked by banned word", "word", word, "user", msg.UserName) + e.reply(p, msg.ReplyCtx, e.i18n.T(MsgBannedWordBlocked)) + return + } + } + if len(msg.Images) == 0 && strings.HasPrefix(content, "/") { if e.handleCommand(p, msg, content) { return diff --git a/core/i18n.go b/core/i18n.go index d4a8338..ea892bf 100644 --- a/core/i18n.go +++ b/core/i18n.go @@ -279,6 +279,8 @@ const ( MsgDeleteSuccess MsgKey = "delete_success" MsgDeleteActiveDenied MsgKey = "delete_active_denied" MsgDeleteNotSupported MsgKey = "delete_not_supported" + + MsgBannedWordBlocked MsgKey = "banned_word_blocked" ) var messages = map[MsgKey]map[Language]string{ @@ -1520,6 +1522,13 @@ var messages = map[MsgKey]map[Language]string{ LangJapanese: "❌ このエージェントはセッション削除をサポートしていません。", LangSpanish: "❌ Este agente no admite la eliminación de sesiones.", }, + MsgBannedWordBlocked: { + LangEnglish: "⚠️ Your message was blocked because it contains a prohibited word.", + LangChinese: "⚠️ 消息已被拦截,包含违禁词。", + LangTraditionalChinese: "⚠️ 訊息已被攔截,包含違禁詞。", + LangJapanese: "⚠️ 禁止ワードが含まれているため、メッセージがブロックされました。", + LangSpanish: "⚠️ Su mensaje fue bloqueado porque contiene una palabra prohibida.", + }, } func (i *I18n) T(key MsgKey) string {