From 35c14ce8a81add52f6508516e428d2e7888875a2 Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Mon, 23 Jun 2025 11:20:21 +0200 Subject: [PATCH 1/2] chore: update CLAUDE.md --- CLAUDE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2191848..0261dd2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,9 +18,9 @@ When creating, modifying, or removing plugins: ## Testing -Before committing plugin changes: +After every session that contains changes to files: -1. Check files are properly formatted: Run `make format` +1. Check files are properly formatted running `make format` 2. Check code style and linting: Run `make lint` 3. Test the plugin functionality: Run `make test` 4. Verify documentation accuracy From 0edf41c792c1ac0fa33e5026067ec00b5c6c4439 Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Mon, 23 Jun 2025 11:32:34 +0200 Subject: [PATCH 2/2] fix: markdown parse mode breaking some plugins --- internal/platform/telegram/telegram.go | 14 ++- internal/plugin/fun/hltb.go | 9 +- internal/plugin/help/help.go | 2 + internal/plugin/social/twitter.go | 1 + internal/plugin/social/twitter_test.go | 120 +++++++++++++++++++++++++ 5 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 internal/plugin/social/twitter_test.go diff --git a/internal/platform/telegram/telegram.go b/internal/platform/telegram/telegram.go index 24714f2..07c65a9 100644 --- a/internal/platform/telegram/telegram.go +++ b/internal/platform/telegram/telegram.go @@ -233,9 +233,17 @@ func (t *TelegramPlatform) SendMessage(msg *model.Message) error { // Prepare payload payload := map[string]interface{}{ - "chat_id": chatID, - "text": msg.Text, - "parse_mode": "Markdown", + "chat_id": chatID, + "text": msg.Text, + } + + // Set parse_mode based on plugin preference or default to Markdown + if msg.Raw != nil && msg.Raw["parse_mode"] != nil { + // Plugin explicitly set parse_mode + payload["parse_mode"] = msg.Raw["parse_mode"] + } else { + // Default to Markdown for backward compatibility + payload["parse_mode"] = "Markdown" } // Add reply if needed diff --git a/internal/plugin/fun/hltb.go b/internal/plugin/fun/hltb.go index 227d637..f94f2ba 100644 --- a/internal/plugin/fun/hltb.go +++ b/internal/plugin/fun/hltb.go @@ -131,12 +131,15 @@ func (p *HLTBPlugin) OnMessage(msg *model.Message, config map[string]interface{} Channel: msg.Channel, } + // Set parse mode for markdown formatting + if responseMsg.Raw == nil { + responseMsg.Raw = make(map[string]interface{}) + } + responseMsg.Raw["parse_mode"] = "Markdown" + // Add game cover as attachment if available if game.GameImage != "" { imageURL := p.getFullImageURL(game.GameImage) - if responseMsg.Raw == nil { - responseMsg.Raw = make(map[string]interface{}) - } responseMsg.Raw["image_url"] = imageURL } diff --git a/internal/plugin/help/help.go b/internal/plugin/help/help.go index 88b25dd..4e6215a 100644 --- a/internal/plugin/help/help.go +++ b/internal/plugin/help/help.go @@ -74,6 +74,7 @@ func (p *HelpPlugin) OnMessage(msg *model.Message, config map[string]interface{} Chat: msg.Chat, ReplyTo: msg.ID, Channel: msg.Channel, + Raw: map[string]interface{}{"parse_mode": "Markdown"}, } return []*model.MessageAction{ @@ -151,6 +152,7 @@ func (p *HelpPlugin) OnMessage(msg *model.Message, config map[string]interface{} Chat: msg.Chat, ReplyTo: msg.ID, Channel: msg.Channel, + Raw: map[string]interface{}{"parse_mode": "Markdown"}, } return []*model.MessageAction{ diff --git a/internal/plugin/social/twitter.go b/internal/plugin/social/twitter.go index f2c6cc9..69bd979 100644 --- a/internal/plugin/social/twitter.go +++ b/internal/plugin/social/twitter.go @@ -75,6 +75,7 @@ func (p *TwitterExpander) OnMessage(msg *model.Message, config map[string]interf Chat: msg.Chat, ReplyTo: msg.ID, Channel: msg.Channel, + Raw: map[string]interface{}{"parse_mode": ""}, } action := &model.MessageAction{ diff --git a/internal/plugin/social/twitter_test.go b/internal/plugin/social/twitter_test.go new file mode 100644 index 0000000..d94cad4 --- /dev/null +++ b/internal/plugin/social/twitter_test.go @@ -0,0 +1,120 @@ +package social + +import ( + "testing" + + "git.nakama.town/fmartingr/butterrobot/internal/model" +) + +func TestTwitterExpander_OnMessage(t *testing.T) { + plugin := NewTwitterExpander() + + tests := []struct { + name string + input string + config map[string]interface{} + expected string + hasReply bool + }{ + { + name: "Twitter URL with default domain", + input: "https://twitter.com/user/status/123456789", + config: map[string]interface{}{}, + expected: "https://fxtwitter.com/user/status/123456789", + hasReply: true, + }, + { + name: "X.com URL with custom domain", + input: "https://x.com/elonmusk/status/987654321", + config: map[string]interface{}{"domain": "vxtwitter.com"}, + expected: "https://vxtwitter.com/elonmusk/status/987654321", + hasReply: true, + }, + { + name: "Twitter URL with tracking parameters", + input: "https://twitter.com/openai/status/555?ref_src=twsrc%5Etfw&s=20", + config: map[string]interface{}{}, + expected: "https://fxtwitter.com/openai/status/555", + hasReply: true, + }, + { + name: "www.twitter.com URL", + input: "https://www.twitter.com/user/status/789", + config: map[string]interface{}{"domain": "nitter.net"}, + expected: "https://nitter.net/user/status/789", + hasReply: true, + }, + { + name: "Mixed text with Twitter URL", + input: "Check this out: https://twitter.com/user/status/123 amazing!", + config: map[string]interface{}{}, + expected: "Check this out: https://fxtwitter.com/user/status/123 amazing!", + hasReply: true, + }, + { + name: "No Twitter URLs", + input: "Just some regular text https://youtube.com/watch?v=abc", + config: map[string]interface{}{}, + expected: "", + hasReply: false, + }, + { + name: "Empty message", + input: "", + config: map[string]interface{}{}, + expected: "", + hasReply: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + msg := &model.Message{ + ID: "test_msg", + Text: tt.input, + Chat: "test_chat", + Channel: &model.Channel{ + ID: 1, + Platform: "telegram", + PlatformChannelID: "test_chat", + }, + } + + actions := plugin.OnMessage(msg, tt.config, nil) + + if !tt.hasReply { + if len(actions) != 0 { + t.Errorf("Expected no actions, got %d", len(actions)) + } + return + } + + if len(actions) != 1 { + t.Errorf("Expected 1 action, got %d", len(actions)) + return + } + + action := actions[0] + if action.Type != model.ActionSendMessage { + t.Errorf("Expected ActionSendMessage, got %s", action.Type) + } + + if action.Message == nil { + t.Error("Expected message in action, got nil") + return + } + + if action.Message.Text != tt.expected { + t.Errorf("Expected '%s', got '%s'", tt.expected, action.Message.Text) + } + + if action.Message.ReplyTo != msg.ID { + t.Errorf("Expected ReplyTo '%s', got '%s'", msg.ID, action.Message.ReplyTo) + } + + if action.Message.Raw == nil || action.Message.Raw["parse_mode"] != "" { + t.Error("Expected parse_mode to be empty string to disable markdown parsing") + } + }) + } +} \ No newline at end of file