Spaces:
Sleeping
Sleeping
| package channels | |
| import ( | |
| "context" | |
| "fmt" | |
| "net" | |
| "runtime" | |
| "strings" | |
| "time" | |
| "github.com/mymmrac/telego" | |
| "github.com/sipeed/picoclaw/pkg/bus" | |
| "github.com/sipeed/picoclaw/pkg/config" | |
| ) | |
| type TelegramCommander interface { | |
| Help(ctx context.Context, message telego.Message) error | |
| Start(ctx context.Context, message telego.Message) error | |
| Show(ctx context.Context, message telego.Message) error | |
| List(ctx context.Context, message telego.Message) error | |
| Ping(ctx context.Context, message telego.Message) error | |
| ID(ctx context.Context, message telego.Message) error | |
| Status(ctx context.Context, message telego.Message) error | |
| IP(ctx context.Context, message telego.Message) error | |
| Clear(ctx context.Context, message telego.Message) error | |
| } | |
| type cmd struct { | |
| bot *telego.Bot | |
| config *config.Config | |
| startTime time.Time | |
| channel *TelegramChannel | |
| } | |
| func NewTelegramCommands(bot *telego.Bot, cfg *config.Config, channel *TelegramChannel) TelegramCommander { | |
| return &cmd{ | |
| bot: bot, | |
| config: cfg, | |
| startTime: time.Now(), | |
| channel: channel, | |
| } | |
| } | |
| func commandArgs(text string) string { | |
| parts := strings.SplitN(text, " ", 2) | |
| if len(parts) < 2 { | |
| return "" | |
| } | |
| return strings.TrimSpace(parts[1]) | |
| } | |
| func (c *cmd) Help(ctx context.Context, message telego.Message) error { | |
| msg := `/start - Start the bot | |
| /help - Show this help message | |
| /ping - Check bot health | |
| /id - Show current chat ID | |
| /status - Show bot status (uptime, memory) | |
| /ip - Show server IP address | |
| /clear - Clear context/memory | |
| /show [model|channel] - Show current configuration | |
| /list [models|channels] - List available options | |
| ` | |
| _, err := c.bot.SendMessage(ctx, &telego.SendMessageParams{ | |
| ChatID: telego.ChatID{ID: message.Chat.ID}, | |
| Text: msg, | |
| ReplyParameters: &telego.ReplyParameters{ | |
| MessageID: message.MessageID, | |
| }, | |
| }) | |
| return err | |
| } | |
| func (c *cmd) Ping(ctx context.Context, message telego.Message) error { | |
| _, err := c.bot.SendMessage(ctx, &telego.SendMessageParams{ | |
| ChatID: telego.ChatID{ID: message.Chat.ID}, | |
| Text: "Pong! 🏓 I am alive and listening.", | |
| ReplyParameters: &telego.ReplyParameters{ | |
| MessageID: message.MessageID, | |
| }, | |
| }) | |
| return err | |
| } | |
| func (c *cmd) ID(ctx context.Context, message telego.Message) error { | |
| _, err := c.bot.SendMessage(ctx, &telego.SendMessageParams{ | |
| ChatID: telego.ChatID{ID: message.Chat.ID}, | |
| Text: fmt.Sprintf("Current Chat ID: `%d`\nType: %s", message.Chat.ID, message.Chat.Type), | |
| ParseMode: telego.ModeMarkdown, | |
| ReplyParameters: &telego.ReplyParameters{ | |
| MessageID: message.MessageID, | |
| }, | |
| }) | |
| return err | |
| } | |
| func (c *cmd) Status(ctx context.Context, message telego.Message) error { | |
| var m runtime.MemStats | |
| runtime.ReadMemStats(&m) | |
| uptime := time.Since(c.startTime).Round(time.Second) | |
| msg := fmt.Sprintf( | |
| "🤖 **System Status**\n\n"+ | |
| "⏱ **Uptime:** %s\n"+ | |
| "🧠 **Memory:** %.2f MB\n"+ | |
| "🧹 **GC Cycles:** %d\n"+ | |
| "🟢 **Goroutines:** %d", | |
| uptime, | |
| float64(m.Alloc)/1024/1024, | |
| m.NumGC, | |
| runtime.NumGoroutine(), | |
| ) | |
| _, err := c.bot.SendMessage(ctx, &telego.SendMessageParams{ | |
| ChatID: telego.ChatID{ID: message.Chat.ID}, | |
| Text: msg, | |
| ParseMode: telego.ModeMarkdown, | |
| ReplyParameters: &telego.ReplyParameters{ | |
| MessageID: message.MessageID, | |
| }, | |
| }) | |
| return err | |
| } | |
| func (c *cmd) IP(ctx context.Context, message telego.Message) error { | |
| addrs, err := net.InterfaceAddrs() | |
| if err != nil { | |
| return err | |
| } | |
| var ips []string | |
| for _, address := range addrs { | |
| if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { | |
| if ipnet.IP.To4() != nil { | |
| ips = append(ips, ipnet.IP.String()) | |
| } | |
| } | |
| } | |
| msg := "🌐 **Server IP Addresses:**\n\n" | |
| if len(ips) == 0 { | |
| msg += "No IP addresses found." | |
| } else { | |
| for _, ip := range ips { | |
| msg += fmt.Sprintf("- `%s`\n", ip) | |
| } | |
| } | |
| _, err = c.bot.SendMessage(ctx, &telego.SendMessageParams{ | |
| ChatID: telego.ChatID{ID: message.Chat.ID}, | |
| Text: msg, | |
| ParseMode: telego.ModeMarkdown, | |
| ReplyParameters: &telego.ReplyParameters{ | |
| MessageID: message.MessageID, | |
| }, | |
| }) | |
| return err | |
| } | |
| func (c *cmd) Clear(ctx context.Context, message telego.Message) error { | |
| // Send a special message to the agent to request context clearing | |
| // We use a specific prefix that the agent can recognize, or just a natural language instruction | |
| // Since we don't have a direct "Clear Session" API exposed to channels yet, | |
| // we will inject a system instruction. | |
| // However, a cleaner way is to use the bus to send a control message if supported. | |
| // For now, we'll simulate it by sending a high-priority user message "RESET_SESSION" | |
| // and hope the agent is configured to handle it, OR we can implement the handler in AgentLoop. | |
| // But wait, the user wants "Useful commands". | |
| // Let's implement it by sending a message that the LLM will interpret as "Clear Context". | |
| // Better yet, if we can access the session manager, we would do that. | |
| // Since we can't easily, let's just send a message to the user confirming it, | |
| // and send a "System: Please clear the context" to the agent? No, that consumes tokens. | |
| // Let's try to send a message to the bus that mimics a user asking to clear memory. | |
| c.channel.bus.PublishInbound(bus.InboundMessage{ | |
| Channel: "telegram", | |
| SenderID: fmt.Sprintf("%d", message.From.ID), | |
| ChatID: fmt.Sprintf("%d", message.Chat.ID), | |
| Content: "Please forget everything we talked about and start fresh.", // Natural language trigger | |
| SessionKey: fmt.Sprintf("telegram:%d", message.Chat.ID), | |
| }) | |
| _, err := c.bot.SendMessage(ctx, &telego.SendMessageParams{ | |
| ChatID: telego.ChatID{ID: message.Chat.ID}, | |
| Text: "🧹 **Context Clear Request Sent**\n\nI've asked the agent to reset the conversation.", | |
| ParseMode: telego.ModeMarkdown, | |
| ReplyParameters: &telego.ReplyParameters{ | |
| MessageID: message.MessageID, | |
| }, | |
| }) | |
| return err | |
| } | |
| func (c *cmd) Start(ctx context.Context, message telego.Message) error { | |
| _, err := c.bot.SendMessage(ctx, &telego.SendMessageParams{ | |
| ChatID: telego.ChatID{ID: message.Chat.ID}, | |
| Text: "Hello! I am PicoClaw 🦞", | |
| ReplyParameters: &telego.ReplyParameters{ | |
| MessageID: message.MessageID, | |
| }, | |
| }) | |
| return err | |
| } | |
| func (c *cmd) Show(ctx context.Context, message telego.Message) error { | |
| args := commandArgs(message.Text) | |
| if args == "" { | |
| _, err := c.bot.SendMessage(ctx, &telego.SendMessageParams{ | |
| ChatID: telego.ChatID{ID: message.Chat.ID}, | |
| Text: "Usage: /show [model|channel]", | |
| ReplyParameters: &telego.ReplyParameters{ | |
| MessageID: message.MessageID, | |
| }, | |
| }) | |
| return err | |
| } | |
| var response string | |
| switch args { | |
| case "model": | |
| response = fmt.Sprintf("Current Model: %s (Provider: %s)", | |
| c.config.Agents.Defaults.Model, | |
| c.config.Agents.Defaults.Provider) | |
| case "channel": | |
| response = "Current Channel: telegram" | |
| default: | |
| response = fmt.Sprintf("Unknown parameter: %s. Try 'model' or 'channel'.", args) | |
| } | |
| _, err := c.bot.SendMessage(ctx, &telego.SendMessageParams{ | |
| ChatID: telego.ChatID{ID: message.Chat.ID}, | |
| Text: response, | |
| ReplyParameters: &telego.ReplyParameters{ | |
| MessageID: message.MessageID, | |
| }, | |
| }) | |
| return err | |
| } | |
| func (c *cmd) List(ctx context.Context, message telego.Message) error { | |
| args := commandArgs(message.Text) | |
| if args == "" { | |
| _, err := c.bot.SendMessage(ctx, &telego.SendMessageParams{ | |
| ChatID: telego.ChatID{ID: message.Chat.ID}, | |
| Text: "Usage: /list [models|channels]", | |
| ReplyParameters: &telego.ReplyParameters{ | |
| MessageID: message.MessageID, | |
| }, | |
| }) | |
| return err | |
| } | |
| var response string | |
| switch args { | |
| case "models": | |
| provider := c.config.Agents.Defaults.Provider | |
| if provider == "" { | |
| provider = "configured default" | |
| } | |
| response = fmt.Sprintf("Configured Model: %s\nProvider: %s\n\nTo change models, update config.yaml", | |
| c.config.Agents.Defaults.Model, provider) | |
| case "channels": | |
| var enabled []string | |
| if c.config.Channels.Telegram.Enabled { | |
| enabled = append(enabled, "telegram") | |
| } | |
| if c.config.Channels.WhatsApp.Enabled { | |
| enabled = append(enabled, "whatsapp") | |
| } | |
| if c.config.Channels.Feishu.Enabled { | |
| enabled = append(enabled, "feishu") | |
| } | |
| if c.config.Channels.Discord.Enabled { | |
| enabled = append(enabled, "discord") | |
| } | |
| if c.config.Channels.Slack.Enabled { | |
| enabled = append(enabled, "slack") | |
| } | |
| response = fmt.Sprintf("Enabled Channels:\n- %s", strings.Join(enabled, "\n- ")) | |
| default: | |
| response = fmt.Sprintf("Unknown parameter: %s. Try 'models' or 'channels'.", args) | |
| } | |
| _, err := c.bot.SendMessage(ctx, &telego.SendMessageParams{ | |
| ChatID: telego.ChatID{ID: message.Chat.ID}, | |
| Text: response, | |
| ReplyParameters: &telego.ReplyParameters{ | |
| MessageID: message.MessageID, | |
| }, | |
| }) | |
| return err | |
| } | |