zeroclaw / pkg /channels /telegram_commands.go
personalbotai
Move picoclaw_space to root for Hugging Face Spaces deployment
c1dcaaa
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
}