package channels import ( "context" "fmt" "github.com/bwmarrin/discordgo" "github.com/sipeed/picoclaw/pkg/bus" "github.com/sipeed/picoclaw/pkg/config" "github.com/sipeed/picoclaw/pkg/logger" ) type DiscordChannel struct { *BaseChannel session *discordgo.Session config config.DiscordConfig } func NewDiscordChannel(cfg config.DiscordConfig, bus *bus.MessageBus) (*DiscordChannel, error) { session, err := discordgo.New("Bot " + cfg.Token) if err != nil { return nil, fmt.Errorf("failed to create discord session: %w", err) } base := NewBaseChannel("discord", cfg, bus, cfg.AllowFrom) return &DiscordChannel{ BaseChannel: base, session: session, config: cfg, }, nil } func (c *DiscordChannel) Start(ctx context.Context) error { logger.InfoC("discord", "Starting Discord bot") c.session.AddHandler(c.handleMessage) if err := c.session.Open(); err != nil { return fmt.Errorf("failed to open discord session: %w", err) } c.setRunning(true) botUser, err := c.session.User("@me") if err != nil { return fmt.Errorf("failed to get bot user: %w", err) } logger.InfoCF("discord", "Discord bot connected", map[string]interface{}{ "username": botUser.Username, "user_id": botUser.ID, }) return nil } func (c *DiscordChannel) Stop(ctx context.Context) error { logger.InfoC("discord", "Stopping Discord bot") c.setRunning(false) if err := c.session.Close(); err != nil { return fmt.Errorf("failed to close discord session: %w", err) } return nil } func (c *DiscordChannel) Send(ctx context.Context, msg bus.OutboundMessage) error { if !c.IsRunning() { return fmt.Errorf("discord bot not running") } channelID := msg.ChatID if channelID == "" { return fmt.Errorf("channel ID is empty") } message := msg.Content if _, err := c.session.ChannelMessageSend(channelID, message); err != nil { return fmt.Errorf("failed to send discord message: %w", err) } return nil } func (c *DiscordChannel) handleMessage(s *discordgo.Session, m *discordgo.MessageCreate) { if m == nil || m.Author == nil { return } if m.Author.ID == s.State.User.ID { return } senderID := m.Author.ID senderName := m.Author.Username if m.Author.Discriminator != "" && m.Author.Discriminator != "0" { senderName += "#" + m.Author.Discriminator } content := m.Content mediaPaths := []string{} for _, attachment := range m.Attachments { mediaPaths = append(mediaPaths, attachment.URL) if content != "" { content += "\n" } content += fmt.Sprintf("[attachment: %s]", attachment.URL) } if content == "" && len(mediaPaths) == 0 { return } if content == "" { content = "[media only]" } logger.DebugCF("discord", "Received message", map[string]interface{}{ "sender_name": senderName, "sender_id": senderID, "preview": truncateString(content, 50), }) metadata := map[string]string{ "message_id": m.ID, "user_id": senderID, "username": m.Author.Username, "display_name": senderName, "guild_id": m.GuildID, "channel_id": m.ChannelID, "is_dm": fmt.Sprintf("%t", m.GuildID == ""), } c.HandleMessage(senderID, m.ChannelID, content, mediaPaths, metadata) }