Skip to content

feat(telegram): add Telegram bot with multi-user support and progress…#3

Merged
cybersecua merged 1 commit intomainfrom
claude/add-telegram-bot-NiAlw
Mar 4, 2026
Merged

feat(telegram): add Telegram bot with multi-user support and progress…#3
cybersecua merged 1 commit intomainfrom
claude/add-telegram-bot-NiAlw

Conversation

@cybersecua
Copy link
Owner

… streaming

Implements a Telegram bot via long-polling (no public IP required) that follows the same architecture as the existing DingTalk and Lark integrations.

Key changes:

  • internal/robot/telegram.go: new bot implementation using Telegram Bot API HTTP calls (no new dependencies). Features: long-polling with exponential reconnect backoff, multi-user sessions (AllowedUserIDs whitelist), group chat @ mention filtering, live progress streaming via throttled message edits, typing action indicator, automatic message splitting at 4096 chars.
  • internal/robot/conn.go: adds StreamingMessageHandler interface so Telegram can receive agent tool-call progress events.
  • internal/handler/robot.go: adds HandleMessageStream() satisfying the new interface; command dispatch is instant, agent messages use streaming.
  • internal/handler/agent.go: adds ProcessMessageForRobotStream() with a notifyFn callback that fires on tool_call/tool_result/progress events.
  • internal/config/config.go: adds RobotTelegramConfig (BotToken, AllowedUserIDs) and Telegram field to RobotsConfig.
  • internal/app/app.go: wires telegramCancel into startRobotConnections(), RestartRobotConnections(), and Shutdown().
  • web/templates/index.html + web/static/js/settings.js: Telegram section in Bot Settings (enable toggle, bot token, allowed user IDs field).
  • config.yaml: telegram block with commented defaults.
  • docs/robot_en.md: full Telegram setup guide (sections 3.3, 9, 12, 13).
  • ROADMAP.md: marks Telegram as shipped; adds near-term Telegram items and a detailed Telegram roadmap table.

… streaming

Implements a Telegram bot via long-polling (no public IP required) that follows
the same architecture as the existing DingTalk and Lark integrations.

Key changes:
- internal/robot/telegram.go: new bot implementation using Telegram Bot API
  HTTP calls (no new dependencies). Features: long-polling with exponential
  reconnect backoff, multi-user sessions (AllowedUserIDs whitelist), group
  chat @ mention filtering, live progress streaming via throttled message
  edits, typing action indicator, automatic message splitting at 4096 chars.
- internal/robot/conn.go: adds StreamingMessageHandler interface so Telegram
  can receive agent tool-call progress events.
- internal/handler/robot.go: adds HandleMessageStream() satisfying the new
  interface; command dispatch is instant, agent messages use streaming.
- internal/handler/agent.go: adds ProcessMessageForRobotStream() with a
  notifyFn callback that fires on tool_call/tool_result/progress events.
- internal/config/config.go: adds RobotTelegramConfig (BotToken,
  AllowedUserIDs) and Telegram field to RobotsConfig.
- internal/app/app.go: wires telegramCancel into startRobotConnections(),
  RestartRobotConnections(), and Shutdown().
- web/templates/index.html + web/static/js/settings.js: Telegram section in
  Bot Settings (enable toggle, bot token, allowed user IDs field).
- config.yaml: telegram block with commented defaults.
- docs/robot_en.md: full Telegram setup guide (sections 3.3, 9, 12, 13).
- ROADMAP.md: marks Telegram as shipped; adds near-term Telegram items and
  a detailed Telegram roadmap table.

https://claude.ai/code/session_01EHroFMw7DJwUuszDzcFkpk
@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly expands the bot integration capabilities by adding a robust Telegram bot. This new bot provides users with an interactive and responsive experience, including live progress updates during AI agent tasks, and is designed for easy setup and secure multi-user access. The changes ensure that the system's conversational AI features are accessible and performant across a wider range of messaging platforms.

Highlights

  • Telegram Bot Integration: Introduced a new Telegram bot that uses long-polling, eliminating the need for a public IP. It supports multi-user sessions, group chat @ mentions, and live progress streaming.
  • Streaming Progress Updates: Implemented a StreamingMessageHandler interface and ProcessMessageForRobotStream function to allow platforms like Telegram to display real-time progress updates during agent execution, by editing a placeholder message.
  • Configurability and UI: Added new configuration options for the Telegram bot in config.yaml and integrated a dedicated Telegram section into the web UI's Bot Settings for easy management of the bot token and allowed user IDs.
  • Comprehensive Documentation: Updated the ROADMAP.md and docs/robot_en.md with detailed setup guides, usage instructions, troubleshooting tips, and a specific roadmap for future Telegram bot enhancements.
Changelog
  • ROADMAP.md
    • Marked Telegram bot integration as completed in the Integrations & UX section.
    • Added future Telegram features like inline keyboards and file transfer to the near-term roadmap.
    • Introduced a detailed roadmap table specifically for Telegram bot enhancements.
  • config.yaml
    • Added a new telegram configuration block under robots with enabled, bot_token, and allowed_user_ids fields.
  • docs/robot_en.md
    • Updated the document to include Telegram as a supported chat platform.
    • Added a new section (3.3 Telegram) detailing setup, functionality, progress streaming, and multi-user support.
    • Modified existing sections (Bot commands, How to use, Testing without) to incorporate Telegram.
    • Introduced new troubleshooting and common pitfalls sections specific to Telegram.
  • internal/app/app.go
    • Added telegramCancel to the App struct for managing the Telegram polling lifecycle.
    • Updated Shutdown to include cancellation logic for the Telegram bot.
    • Modified startRobotConnections and RestartRobotConnections to initialize and manage the Telegram bot's long-polling connection.
  • internal/config/config.go
    • Updated the RobotsConfig struct to include a Telegram RobotTelegramConfig field.
    • Defined the RobotTelegramConfig struct to hold Telegram bot-specific settings like BotToken and AllowedUserIDs.
  • internal/handler/agent.go
    • Added ProcessMessageForRobotStream function to enable streaming progress updates for robot messages, utilizing a notifyFn callback for tool calls, results, and general progress.
  • internal/handler/robot.go
    • Implemented HandleMessageStream method, which dispatches quick commands instantly and uses the new ProcessMessageForRobotStream for agent interactions, providing real-time progress feedback.
  • internal/robot/conn.go
    • Defined a new StreamingMessageHandler interface, extending MessageHandler to support streaming progress updates.
  • internal/robot/telegram.go
    • Added a new file implementing the Telegram bot's core logic, including long-polling, API communication, message handling, user authorization, group mention filtering, message splitting, and throttled progress message editing.
  • web/static/js/settings.js
    • Updated JavaScript to load and save Telegram bot configuration from the web UI, including parsing allowed_user_ids.
  • web/templates/index.html
    • Added a new 'Telegram' section to the Bot Settings page in the web UI, providing input fields for enabling the bot, entering the bot token, and specifying allowed user IDs.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@cybersecua cybersecua merged commit 1f68d8d into main Mar 4, 2026
@cybersecua cybersecua deleted the claude/add-telegram-bot-NiAlw branch March 4, 2026 10:21
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive Telegram bot integration with multi-user support, progress streaming, and UI configuration. However, two significant security issues were identified: Broken Access Control (IDOR), where bot commands (list, switch, delete) lack conversation ownership enforcement, potentially leading to data leakage and unauthorized modification; and Sensitive Information Leakage, as the Telegram bot token might be logged if HTTP requests fail. Additionally, the review highlights opportunities to improve maintainability by reducing code duplication in the handler layer and enhancing Telegram API error handling robustness through refactoring to centralize logic.

Comment on lines +153 to +165
case strings.HasPrefix(text, robotCmdSwitch+" ") || strings.HasPrefix(text, robotCmdContinue+" ") || strings.HasPrefix(text, "switch ") || strings.HasPrefix(text, "continue "):
var id string
switch {
case strings.HasPrefix(text, robotCmdSwitch+" "):
id = strings.TrimSpace(text[len(robotCmdSwitch)+1:])
case strings.HasPrefix(text, robotCmdContinue+" "):
id = strings.TrimSpace(text[len(robotCmdContinue)+1:])
case strings.HasPrefix(text, "switch "):
id = strings.TrimSpace(text[7:])
default:
id = strings.TrimSpace(text[9:])
}
return h.cmdSwitch(platform, userID, id)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

This section of the RobotHandler implementation has a critical security vulnerability: it lacks ownership checks for conversations. This means any authorized user can use commands like list and switch to access or manipulate sensitive data belonging to other users. To fix this, implement user_id or owner_id in the conversations table, store the userID upon conversation creation, and enforce ownership verification in cmdSwitch.

Furthermore, the HandleMessageStream function duplicates the command dispatching switch block found in HandleMessage. Extracting this common logic into a private helper function would significantly reduce redundancy and improve maintainability.

Comment on lines +187 to +194
case strings.HasPrefix(text, robotCmdDelete+" ") || strings.HasPrefix(text, "delete "):
var convID string
if strings.HasPrefix(text, robotCmdDelete+" ") {
convID = strings.TrimSpace(text[len(robotCmdDelete)+1:])
} else {
convID = strings.TrimSpace(text[7:])
}
return h.cmdDelete(platform, userID, convID)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

Similar to the switch command, the delete command lacks ownership verification. Any authorized bot user can delete any conversation in the system if they know its ID. This could lead to unauthorized data loss.

Remediation: Ensure that the userID of the requester is checked against the conversation owner before performing the deletion.

Comment on lines +106 to +121
apiURL: fmt.Sprintf("%s/bot%s", telegramAPIBase, cfg.BotToken),
cfg: cfg,
h: h,
logger: logger,
allowedSet: allowedSet,
}

logger.Info("Telegram bot connecting...")
err := bot.runPollLoop(ctx)

if ctx.Err() != nil {
logger.Info("Telegram bot stopped per configuration reload")
return
}
if err != nil {
logger.Warn("Telegram bot polling error, will reconnect", zap.Error(err), zap.Duration("retry_after", backoff))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The Telegram bot token is embedded directly in the API URL. If an HTTP request to the Telegram API fails (e.g., due to a network timeout or connection error), the resulting error object returned by Go's http package typically includes the full URL, including the token. This error is then logged using zap.Warn (line 121), which will write the bot token into the application logs in plaintext.

To remediate this, you should wrap the error or use a custom error handler that scrubs the sensitive token from the URL before logging.

Comment on lines +504 to +624
func (h *AgentHandler) ProcessMessageForRobotStream(
ctx context.Context,
conversationID, message, role string,
notifyFn func(step string),
) (response string, convID string, err error) {
if conversationID == "" {
title := safeTruncateString(message, 50)
conv, createErr := h.db.CreateConversation(title)
if createErr != nil {
return "", "", fmt.Errorf("failed to create conversation: %w", createErr)
}
conversationID = conv.ID
} else {
if _, getErr := h.db.GetConversation(conversationID); getErr != nil {
return "", "", fmt.Errorf("conversation does not exist")
}
}

agentHistoryMessages, err := h.loadHistoryFromReActData(conversationID)
if err != nil {
historyMessages, getErr := h.db.GetMessages(conversationID)
if getErr != nil {
agentHistoryMessages = []agent.ChatMessage{}
} else {
agentHistoryMessages = make([]agent.ChatMessage, 0, len(historyMessages))
for _, msg := range historyMessages {
agentHistoryMessages = append(agentHistoryMessages, agent.ChatMessage{Role: msg.Role, Content: msg.Content})
}
}
}

finalMessage := message
var roleTools, roleSkills []string
if role != "" && role != "Default" && h.config.Roles != nil {
if r, exists := h.config.Roles[role]; exists && r.Enabled {
if r.UserPrompt != "" {
finalMessage = r.UserPrompt + "\n\n" + message
}
roleTools = r.Tools
roleSkills = r.Skills
}
}

if _, err = h.db.AddMessage(conversationID, "user", message, nil); err != nil {
return "", "", fmt.Errorf("failed to save user message: %w", err)
}

assistantMsg, err := h.db.AddMessage(conversationID, "assistant", "Processing...", nil)
if err != nil {
h.logger.Warn("Robot stream: failed to create assistant message placeholder", zap.Error(err))
}
var assistantMessageID string
if assistantMsg != nil {
assistantMessageID = assistantMsg.ID
}

// Build a sendEventFunc that forwards relevant events to notifyFn
var sendEventFunc func(eventType, message string, data interface{})
if notifyFn != nil {
sendEventFunc = func(eventType, evtMessage string, data interface{}) {
switch eventType {
case "tool_call":
if dataMap, ok := data.(map[string]interface{}); ok {
if toolName, ok := dataMap["toolName"].(string); ok && toolName != "" {
notifyFn("calling tool: " + toolName)
return
}
}
if evtMessage != "" {
notifyFn(evtMessage)
}
case "tool_result":
if dataMap, ok := data.(map[string]interface{}); ok {
if toolName, ok := dataMap["toolName"].(string); ok && toolName != "" {
notifyFn("tool result: " + toolName)
return
}
}
case "progress":
if evtMessage != "" {
notifyFn(evtMessage)
}
}
}
}

progressCallback := h.createProgressCallback(conversationID, assistantMessageID, sendEventFunc)

result, err := h.agent.AgentLoopWithProgress(ctx, finalMessage, agentHistoryMessages, conversationID, progressCallback, roleTools, roleSkills)
if err != nil {
errMsg := "Execution failed: " + err.Error()
if assistantMessageID != "" {
_, _ = h.db.Exec("UPDATE messages SET content = ? WHERE id = ?", errMsg, assistantMessageID)
_ = h.db.AddProcessDetail(assistantMessageID, conversationID, "error", errMsg, nil)
}
return "", conversationID, err
}

if assistantMessageID != "" {
mcpIDsJSON := ""
if len(result.MCPExecutionIDs) > 0 {
jsonData, _ := json.Marshal(result.MCPExecutionIDs)
mcpIDsJSON = string(jsonData)
}
_, err = h.db.Exec(
"UPDATE messages SET content = ?, mcp_execution_ids = ? WHERE id = ?",
result.Response, mcpIDsJSON, assistantMessageID,
)
if err != nil {
h.logger.Warn("Robot stream: failed to update assistant message", zap.Error(err))
}
} else {
if _, err = h.db.AddMessage(conversationID, "assistant", result.Response, result.MCPExecutionIDs); err != nil {
h.logger.Warn("Robot stream: failed to save assistant message", zap.Error(err))
}
}
if result.LastReActInput != "" || result.LastReActOutput != "" {
_ = h.db.SaveReActData(conversationID, result.LastReActInput, result.LastReActOutput)
}
return result.Response, conversationID, nil
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This new function ProcessMessageForRobotStream duplicates a significant amount of logic from the existing ProcessMessageForRobot function. Specifically, the setup code for conversation handling, history loading, and role application, as well as the teardown code for saving results, appears to be nearly identical. To improve maintainability and reduce redundancy, consider refactoring the common logic into shared private helper functions. For example, you could have a helper for preparing the agent call and another for finalizing the results, which both ProcessMessageForRobot and ProcessMessageForRobotStream could use. This would make the code cleaner and easier to maintain in the future.

Comment on lines +208 to +215
if err := b.apiPost(ctx, "sendMessage", params, &msg); err != nil {
// Fall back to plain text if Markdown parse fails
params["parse_mode"] = ""
if err2 := b.apiPost(ctx, "sendMessage", params, &msg); err2 != nil {
b.logger.Warn("Telegram sendMessage failed", zap.Error(err2), zap.Int("part", i))
continue
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The fallback logic here in sendMessage (and similarly in editMessageText at line 237) retries the API call without Markdown for any error. This could lead to unnecessary retries for non-parsing errors (e.g., network issues, other API errors). To make this more robust, consider making the retry conditional on a specific Markdown parsing error. The Telegram API typically returns an error message containing can't parse entities for such failures. Checking for this substring in the error would make the fallback more targeted and efficient.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants