Compare commits

..

8 commits

Author SHA1 Message Date
377b1723c3
fix: default parse mode to text
Some checks failed
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/tag/release Pipeline was successful
2025-06-24 08:10:56 +02:00
60ceaffd82
fix: enable all plugins help text
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
2025-06-23 11:43:42 +02:00
3a5b5c216d
chore: try to ensure that code is checked after each session
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
2025-06-23 11:35:30 +02:00
bdc797d5c1
chore: make format 2025-06-23 11:34:27 +02:00
0edf41c792
fix: markdown parse mode breaking some plugins
Some checks failed
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/tag/release Pipeline was successful
2025-06-23 11:32:34 +02:00
35c14ce8a8
chore: update CLAUDE.md 2025-06-23 11:20:21 +02:00
e0ff369cff
chore: make format
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
2025-06-23 11:19:22 +02:00
368c45cd13
fix: twitter plugin replacement logic
Some checks failed
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/tag/release Pipeline was successful
2025-06-23 11:18:47 +02:00
10 changed files with 152 additions and 22 deletions

View file

@ -18,10 +18,12 @@ When creating, modifying, or removing plugins:
## Testing ## Testing
Before committing plugin changes: **CRITICAL**: After making ANY changes to code files, you MUST run these commands in order:
1. Check files are properly formatted: Run `make format` 1. **Format code**: `make format` - Format all code according to project standards
2. Check code style and linting: Run `make lint` 2. **Lint code**: `make lint` - Check code style and quality (must show "0 issues")
3. Test the plugin functionality: Run `make test` 3. **Run tests**: `make test` - Run all tests to ensure functionality works
4. Verify documentation accuracy 4. Verify documentation accuracy
5. Ensure all examples work as described 5. Ensure all examples work as described
**These commands are MANDATORY after every code change, no exceptions.**

View file

@ -32,7 +32,7 @@
<input class="form-check-input" type="checkbox" name="enable_all_plugins" value="true" {{if .Channel.EnableAllPlugins}}checked{{end}}> <input class="form-check-input" type="checkbox" name="enable_all_plugins" value="true" {{if .Channel.EnableAllPlugins}}checked{{end}}>
<span class="form-check-label">Enable All Plugins</span> <span class="form-check-label">Enable All Plugins</span>
</label> </label>
<div class="form-help"> <div>
When enabled, all registered plugins will be automatically enabled for this channel. Individual plugin settings will be ignored. When enabled, all registered plugins will be automatically enabled for this channel. Individual plugin settings will be ignored.
</div> </div>
</div> </div>
@ -124,4 +124,4 @@
</div> </div>
</div> </div>
</div> </div>
{{end}} {{end}}

View file

@ -200,4 +200,4 @@ func TestEnableAllPlugins(t *testing.T) {
t.Errorf("EnableAllPlugins should be true after update") t.Errorf("EnableAllPlugins should be true after update")
} }
}) })
} }

View file

@ -231,4 +231,4 @@ func TestChannelName(t *testing.T) {
t.Errorf("Expected channel name to fallback to 'fallback-id', got '%s'", result) t.Errorf("Expected channel name to fallback to 'fallback-id', got '%s'", result)
} }
}) })
} }

View file

@ -233,9 +233,17 @@ func (t *TelegramPlatform) SendMessage(msg *model.Message) error {
// Prepare payload // Prepare payload
payload := map[string]interface{}{ payload := map[string]interface{}{
"chat_id": chatID, "chat_id": chatID,
"text": msg.Text, "text": msg.Text,
"parse_mode": "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 empty string (no formatting)
payload["parse_mode"] = ""
} }
// Add reply if needed // Add reply if needed

View file

@ -131,12 +131,15 @@ func (p *HLTBPlugin) OnMessage(msg *model.Message, config map[string]interface{}
Channel: msg.Channel, 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 // Add game cover as attachment if available
if game.GameImage != "" { if game.GameImage != "" {
imageURL := p.getFullImageURL(game.GameImage) imageURL := p.getFullImageURL(game.GameImage)
if responseMsg.Raw == nil {
responseMsg.Raw = make(map[string]interface{})
}
responseMsg.Raw["image_url"] = imageURL responseMsg.Raw["image_url"] = imageURL
} }

View file

@ -74,6 +74,7 @@ func (p *HelpPlugin) OnMessage(msg *model.Message, config map[string]interface{}
Chat: msg.Chat, Chat: msg.Chat,
ReplyTo: msg.ID, ReplyTo: msg.ID,
Channel: msg.Channel, Channel: msg.Channel,
Raw: map[string]interface{}{"parse_mode": "Markdown"},
} }
return []*model.MessageAction{ return []*model.MessageAction{
@ -151,6 +152,7 @@ func (p *HelpPlugin) OnMessage(msg *model.Message, config map[string]interface{}
Chat: msg.Chat, Chat: msg.Chat,
ReplyTo: msg.ID, ReplyTo: msg.ID,
Channel: msg.Channel, Channel: msg.Channel,
Raw: map[string]interface{}{"parse_mode": "Markdown"},
} }
return []*model.MessageAction{ return []*model.MessageAction{

View file

@ -328,4 +328,4 @@ func TestPluginRegistry(t *testing.T) {
t.Errorf("Expected error when getting plugin after clearing registry, got nil") t.Errorf("Expected error when getting plugin after clearing registry, got nil")
} }
}) })
} }

View file

@ -54,17 +54,12 @@ func (p *TwitterExpander) OnMessage(msg *model.Message, config map[string]interf
// Parse the URL // Parse the URL
parsedURL, err := url.Parse(link) parsedURL, err := url.Parse(link)
if err != nil { 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 return link
} }
// Change the host to the configured domain // Change the host to the configured domain
if strings.Contains(parsedURL.Host, "twitter.com") { if strings.Contains(parsedURL.Host, "twitter.com") || strings.Contains(parsedURL.Host, "x.com") {
parsedURL.Host = strings.Replace(parsedURL.Host, "twitter.com", replacementDomain, 1) parsedURL.Host = replacementDomain
} else if strings.Contains(parsedURL.Host, "x.com") {
parsedURL.Host = strings.Replace(parsedURL.Host, "x.com", replacementDomain, 1)
} }
// Remove query parameters // Remove query parameters

View file

@ -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")
}
})
}
}