From 368c45cd1357e5c20b283a775c68f849b9146db8 Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Mon, 23 Jun 2025 11:18:47 +0200 Subject: [PATCH 1/8] fix: twitter plugin replacement logic --- internal/plugin/social/twitter.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/internal/plugin/social/twitter.go b/internal/plugin/social/twitter.go index d98eee6..f2c6cc9 100644 --- a/internal/plugin/social/twitter.go +++ b/internal/plugin/social/twitter.go @@ -54,17 +54,12 @@ func (p *TwitterExpander) OnMessage(msg *model.Message, config map[string]interf // Parse the URL parsedURL, err := url.Parse(link) if err != nil { - // If parsing fails, just do the simple replacement - link = strings.Replace(link, "twitter.com", replacementDomain, 1) - link = strings.Replace(link, "x.com", replacementDomain, 1) return link } // Change the host to the configured domain - if strings.Contains(parsedURL.Host, "twitter.com") { - parsedURL.Host = strings.Replace(parsedURL.Host, "twitter.com", replacementDomain, 1) - } else if strings.Contains(parsedURL.Host, "x.com") { - parsedURL.Host = strings.Replace(parsedURL.Host, "x.com", replacementDomain, 1) + if strings.Contains(parsedURL.Host, "twitter.com") || strings.Contains(parsedURL.Host, "x.com") { + parsedURL.Host = replacementDomain } // Remove query parameters From e0ff369cffcc644714c86f9c4889b893cc9e73a9 Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Mon, 23 Jun 2025 11:19:22 +0200 Subject: [PATCH 2/8] chore: make format --- internal/db/db_test.go | 2 +- internal/model/message_test.go | 2 +- internal/plugin/plugin_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/db/db_test.go b/internal/db/db_test.go index 88ab470..beb485d 100644 --- a/internal/db/db_test.go +++ b/internal/db/db_test.go @@ -200,4 +200,4 @@ func TestEnableAllPlugins(t *testing.T) { t.Errorf("EnableAllPlugins should be true after update") } }) -} \ No newline at end of file +} diff --git a/internal/model/message_test.go b/internal/model/message_test.go index d94c5ea..d2dfedc 100644 --- a/internal/model/message_test.go +++ b/internal/model/message_test.go @@ -231,4 +231,4 @@ func TestChannelName(t *testing.T) { t.Errorf("Expected channel name to fallback to 'fallback-id', got '%s'", result) } }) -} \ No newline at end of file +} diff --git a/internal/plugin/plugin_test.go b/internal/plugin/plugin_test.go index e0b68d3..0bfd207 100644 --- a/internal/plugin/plugin_test.go +++ b/internal/plugin/plugin_test.go @@ -328,4 +328,4 @@ func TestPluginRegistry(t *testing.T) { t.Errorf("Expected error when getting plugin after clearing registry, got nil") } }) -} \ No newline at end of file +} From 35c14ce8a81add52f6508516e428d2e7888875a2 Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Mon, 23 Jun 2025 11:20:21 +0200 Subject: [PATCH 3/8] 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 4/8] 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 From bdc797d5c107136a1e7afec0244fcf2b109b2c0d Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Mon, 23 Jun 2025 11:34:27 +0200 Subject: [PATCH 5/8] chore: make format --- internal/platform/telegram/telegram.go | 2 +- internal/plugin/social/twitter_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/platform/telegram/telegram.go b/internal/platform/telegram/telegram.go index 07c65a9..d35bb6b 100644 --- a/internal/platform/telegram/telegram.go +++ b/internal/platform/telegram/telegram.go @@ -236,7 +236,7 @@ func (t *TelegramPlatform) SendMessage(msg *model.Message) error { "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 diff --git a/internal/plugin/social/twitter_test.go b/internal/plugin/social/twitter_test.go index d94cad4..c0e1681 100644 --- a/internal/plugin/social/twitter_test.go +++ b/internal/plugin/social/twitter_test.go @@ -117,4 +117,4 @@ func TestTwitterExpander_OnMessage(t *testing.T) { } }) } -} \ No newline at end of file +} From 3a5b5c216ddc3c719c095bbf7bf40d80e344b391 Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Mon, 23 Jun 2025 11:35:30 +0200 Subject: [PATCH 6/8] chore: try to ensure that code is checked after each session --- CLAUDE.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 0261dd2..cb4e70a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,10 +18,12 @@ When creating, modifying, or removing plugins: ## Testing -After every session that contains changes to files: +**CRITICAL**: After making ANY changes to code files, you MUST run these commands in order: -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` +1. **Format code**: `make format` - Format all code according to project standards +2. **Lint code**: `make lint` - Check code style and quality (must show "0 issues") +3. **Run tests**: `make test` - Run all tests to ensure functionality works 4. Verify documentation accuracy 5. Ensure all examples work as described + +**These commands are MANDATORY after every code change, no exceptions.** From 60ceaffd82dd209877f38771e719fc426dfff725 Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Mon, 23 Jun 2025 11:43:42 +0200 Subject: [PATCH 7/8] fix: enable all plugins help text --- internal/admin/templates/channel_detail.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/admin/templates/channel_detail.html b/internal/admin/templates/channel_detail.html index 9f9a78d..7e12d57 100644 --- a/internal/admin/templates/channel_detail.html +++ b/internal/admin/templates/channel_detail.html @@ -32,7 +32,7 @@ Enable All Plugins -
+
When enabled, all registered plugins will be automatically enabled for this channel. Individual plugin settings will be ignored.
@@ -124,4 +124,4 @@ -{{end}} \ No newline at end of file +{{end}} From 377b1723c31975c9cb43587a949c2014e2ae81ae Mon Sep 17 00:00:00 2001 From: "Felipe M." Date: Tue, 24 Jun 2025 08:10:56 +0200 Subject: [PATCH 8/8] fix: default parse mode to text --- internal/platform/telegram/telegram.go | 6 +++--- internal/plugin/social/twitter.go | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/internal/platform/telegram/telegram.go b/internal/platform/telegram/telegram.go index d35bb6b..b015793 100644 --- a/internal/platform/telegram/telegram.go +++ b/internal/platform/telegram/telegram.go @@ -237,13 +237,13 @@ func (t *TelegramPlatform) SendMessage(msg *model.Message) error { "text": msg.Text, } - // Set parse_mode based on plugin preference or default to Markdown + // Set parse_mode based on plugin preference or default to empty string 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" + // Default to empty string (no formatting) + payload["parse_mode"] = "" } // Add reply if needed diff --git a/internal/plugin/social/twitter.go b/internal/plugin/social/twitter.go index 69bd979..f2c6cc9 100644 --- a/internal/plugin/social/twitter.go +++ b/internal/plugin/social/twitter.go @@ -75,7 +75,6 @@ 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{