diff --git a/pkg/channels/telegram/telegram.go b/pkg/channels/telegram/telegram.go index 85ca25142..013aff205 100644 --- a/pkg/channels/telegram/telegram.go +++ b/pkg/channels/telegram/telegram.go @@ -787,10 +787,28 @@ func (c *TelegramChannel) handleBusinessMessage(ctx context.Context, message *te if businessConnectionID == "" { return fmt.Errorf("business message missing business_connection_id") } + if c.isBusinessOwnerMessage(message) { + logger.DebugCF("telegram", "Business message ignored from configured owner", map[string]any{ + "business_connection_id": businessConnectionID, + "user_id": fmt.Sprintf("%d", message.From.ID), + }) + return nil + } c.markBusinessMessageRead(ctx, businessConnectionID, message.Chat.ID, message.MessageID) return c.handleTelegramMessage(ctx, message, businessConnectionID) } +func (c *TelegramChannel) isBusinessOwnerMessage(message *telego.Message) bool { + if c == nil || c.tgCfg == nil || message == nil || message.From == nil { + return false + } + ownerID := strings.TrimSpace(c.tgCfg.BusinessOwner) + if ownerID == "" { + return false + } + return ownerID == fmt.Sprintf("%d", message.From.ID) +} + func (c *TelegramChannel) markBusinessMessageRead( ctx context.Context, businessConnectionID string, diff --git a/pkg/channels/telegram/telegram_dispatch_test.go b/pkg/channels/telegram/telegram_dispatch_test.go index eae6fd0e3..a12f5d19d 100644 --- a/pkg/channels/telegram/telegram_dispatch_test.go +++ b/pkg/channels/telegram/telegram_dispatch_test.go @@ -84,6 +84,43 @@ func TestHandleBusinessMessage_DisabledBusinessModeIgnoresMessage(t *testing.T) } } +func TestHandleBusinessMessage_BusinessOwnerIgnoresMessage(t *testing.T) { + messageBus := bus.NewMessageBus() + ch := &TelegramChannel{ + BaseChannel: channels.NewBaseChannel("telegram", nil, messageBus, nil), + chatIDs: make(map[string]int64), + ctx: context.Background(), + tgCfg: &config.TelegramSettings{ + BusinessMode: true, + BusinessOwner: "42", + }, + } + + msg := &telego.Message{ + Text: "owner should be ignored", + MessageID: 19, + BusinessConnectionID: "biz-conn-1", + Chat: telego.Chat{ + ID: 777, + Type: "private", + }, + From: &telego.User{ + ID: 42, + FirstName: "Owner", + }, + } + + if err := ch.handleBusinessMessage(context.Background(), msg); err != nil { + t.Fatalf("handleBusinessMessage error: %v", err) + } + + select { + case inbound := <-messageBus.InboundChan(): + t.Fatalf("expected owner business message to be ignored, got %#v", inbound) + default: + } +} + func TestTelegramAllowedUpdates_BusinessMode(t *testing.T) { disabled := strings.Join(telegramAllowedUpdates(false), ",") if strings.Contains(disabled, telego.BusinessMessageUpdates) { diff --git a/pkg/config/config.go b/pkg/config/config.go index d96c6d7c5..cd9d74b1a 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -364,6 +364,7 @@ type TelegramSettings struct { Proxy string `json:"proxy" yaml:"-" env:"PICOCLAW_CHANNELS_TELEGRAM_PROXY"` Streaming StreamingConfig `json:"streaming,omitempty" yaml:"-"` BusinessMode bool `json:"business_mode" yaml:"-" env:"PICOCLAW_CHANNELS_TELEGRAM_BUSINESS_MODE"` + BusinessOwner string `json:"business_owner" yaml:"-" env:"PICOCLAW_CHANNELS_TELEGRAM_BUSINESS_OWNER"` UseMarkdownV2 bool `json:"use_markdown_v2" yaml:"-" env:"PICOCLAW_CHANNELS_TELEGRAM_USE_MARKDOWN_V2"` } diff --git a/pkg/config/config_channel_test.go b/pkg/config/config_channel_test.go index e942ffe3f..a848ddeab 100644 --- a/pkg/config/config_channel_test.go +++ b/pkg/config/config_channel_test.go @@ -18,6 +18,7 @@ type testTelegramConfig struct { BaseURL string `json:"base_url" yaml:"-"` Proxy string `json:"proxy" yaml:"-"` BusinessMode bool `json:"business_mode" yaml:"-"` + BusinessOwner string `json:"business_owner" yaml:"-"` UseMarkdownV2 bool `json:"use_markdown_v2" yaml:"-"` Streaming StreamingConfig `json:"streaming,omitempty" yaml:"-"` Token SecureString `json:"token,omitzero" yaml:"token,omitempty"` @@ -109,6 +110,7 @@ func TestChannel_JSON_Unmarshal(t *testing.T) { "settings": { "base_url": "https://custom-api.example.com", "business_mode": true, + "business_owner": "42", "use_markdown_v2": true, "streaming": {"enabled": true, "throttle_seconds": 2}, "token": "[NOT_HERE]" @@ -129,6 +131,7 @@ func TestChannel_JSON_Unmarshal(t *testing.T) { require.NoError(t, ch.Decode(&cfg)) assert.Equal(t, "https://custom-api.example.com", cfg.BaseURL) assert.True(t, cfg.BusinessMode) + assert.Equal(t, "42", cfg.BusinessOwner) assert.True(t, cfg.UseMarkdownV2) assert.True(t, cfg.Streaming.Enabled) assert.Equal(t, 2, cfg.Streaming.ThrottleSeconds) diff --git a/pkg/config/defaults.go b/pkg/config/defaults.go index d4def2790..c0682d5de 100644 --- a/pkg/config/defaults.go +++ b/pkg/config/defaults.go @@ -498,6 +498,7 @@ func defaultChannels() ChannelsConfig { "settings": map[string]any{ "streaming": map[string]any{"enabled": true, "throttle_seconds": 3, "min_growth_chars": 200}, "business_mode": false, + "business_owner": "", "use_markdown_v2": false, }, }, diff --git a/web/frontend/src/components/channels/channel-forms/telegram-form.tsx b/web/frontend/src/components/channels/channel-forms/telegram-form.tsx index b45d3dd74..31ce47363 100644 --- a/web/frontend/src/components/channels/channel-forms/telegram-form.tsx +++ b/web/frontend/src/components/channels/channel-forms/telegram-form.tsx @@ -96,6 +96,17 @@ export function TelegramForm({ ariaLabel={t("channels.field.businessMode")} /> + + + onChange("business_owner", e.target.value)} + placeholder="123456789" + /> + diff --git a/web/frontend/src/i18n/locales/en.json b/web/frontend/src/i18n/locales/en.json index 744d89050..9af75283e 100644 --- a/web/frontend/src/i18n/locales/en.json +++ b/web/frontend/src/i18n/locales/en.json @@ -376,6 +376,7 @@ "encryptKey": "Encrypt Key", "baseUrl": "API Base URL", "businessMode": "Business Mode", + "businessOwner": "Business Owner", "proxy": "HTTP Proxy", "mentionOnly": "Mention Only", "typingEnabled": "Typing Indicator", @@ -416,6 +417,7 @@ "encryptKey": "Encryption key used to decrypt callback payloads.", "baseUrl": "Platform API base URL. Official endpoint is used by default.", "businessMode": "Receive and reply to Telegram Business messages for connected business accounts.", + "businessOwner": "Telegram user ID of the business account owner. Business messages from this user are ignored.", "proxy": "HTTP proxy address for outbound network access.", "mentionOnly": "Only respond when the bot is explicitly mentioned in group chats.", "typingEnabled": "Display typing status while the assistant is generating a response.", diff --git a/web/frontend/src/i18n/locales/pt-br.json b/web/frontend/src/i18n/locales/pt-br.json index c5fc59e83..44a365568 100644 --- a/web/frontend/src/i18n/locales/pt-br.json +++ b/web/frontend/src/i18n/locales/pt-br.json @@ -368,6 +368,7 @@ "encryptKey": "Chave de Criptografia", "baseUrl": "URL Base da API", "businessMode": "Modo Business", + "businessOwner": "Proprietário Business", "proxy": "Proxy HTTP", "mentionOnly": "Apenas com Menção", "typingEnabled": "Indicador de Digitação", @@ -408,6 +409,7 @@ "encryptKey": "Chave de criptografia usada para descriptografar payloads de callback.", "baseUrl": "URL base da API da plataforma. O endpoint oficial é usado por padrão.", "businessMode": "Receber e responder mensagens do Telegram Business para contas comerciais conectadas.", + "businessOwner": "ID de usuário do Telegram do proprietário da conta comercial. Mensagens Business desse usuário são ignoradas.", "proxy": "Endereço de proxy HTTP para acesso de rede de saída.", "mentionOnly": "Responder apenas quando o bot for explicitamente mencionado em chats em grupo.", "typingEnabled": "Exibir status de digitação enquanto o assistente está gerando uma resposta.", diff --git a/web/frontend/src/i18n/locales/zh.json b/web/frontend/src/i18n/locales/zh.json index 6e70d5c76..f0c9557c2 100644 --- a/web/frontend/src/i18n/locales/zh.json +++ b/web/frontend/src/i18n/locales/zh.json @@ -376,6 +376,7 @@ "encryptKey": "Encrypt Key", "baseUrl": "API Base URL", "businessMode": "Business Mode", + "businessOwner": "Business Owner", "proxy": "HTTP 代理", "mentionOnly": "仅提及时响应", "typingEnabled": "输入中提示", @@ -416,6 +417,7 @@ "encryptKey": "消息加密密钥,用于解密回调内容", "baseUrl": "平台 API 地址,默认使用官方地址", "businessMode": "接收并回复已连接商业账号的 Telegram Business 消息", + "businessOwner": "商业账号所有者的 Telegram 用户 ID。来自该用户的 Business 消息会被忽略。", "proxy": "HTTP 代理地址,用于网络访问", "mentionOnly": "在群聊中仅当明确提及时才响应", "typingEnabled": "在生成回复时显示“正在输入”状态",