butterrobot/internal/plugin/searchreplace/searchreplace.go
Felipe M. a9b4ad52cb
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/tag/release Pipeline was successful
plugin: search and replace
2025-05-09 14:01:00 +02:00

182 lines
4.7 KiB
Go

package searchreplace
import (
"fmt"
"regexp"
"strings"
"git.nakama.town/fmartingr/butterrobot/internal/model"
"git.nakama.town/fmartingr/butterrobot/internal/plugin"
)
// Regex pattern for search and replace operations: s/search/replace/[flags]
var searchReplacePattern = regexp.MustCompile(`^s/([^/]*)/([^/]*)(?:/([gimnsuy]*))?$`)
// SearchReplacePlugin is a plugin for performing search and replace operations on messages
type SearchReplacePlugin struct {
plugin.BasePlugin
}
// New creates a new SearchReplacePlugin instance
func New() *SearchReplacePlugin {
return &SearchReplacePlugin{
BasePlugin: plugin.BasePlugin{
ID: "util.searchreplace",
Name: "Search and Replace",
Help: "Reply to a message with a search and replace pattern (s/search/replace/[flags]) to create a modified message. " +
"Supported flags: g (global), i (case insensitive)",
},
}
}
// OnMessage handles incoming messages
func (p *SearchReplacePlugin) OnMessage(msg *model.Message, config map[string]interface{}) []*model.MessageAction {
// Only process replies to messages
if msg.ReplyTo == "" {
return nil
}
// Check if the message matches the search/replace pattern
match := searchReplacePattern.FindStringSubmatch(strings.TrimSpace(msg.Text))
if match == nil {
return nil
}
// Get the original message text from the reply_to_message structure in Telegram messages
var originalText string
// For Telegram messages
if msgData, ok := msg.Raw["message"].(map[string]interface{}); ok {
if replyMsg, ok := msgData["reply_to_message"].(map[string]interface{}); ok {
if text, ok := replyMsg["text"].(string); ok {
originalText = text
}
}
}
// Generic fallback for other platforms or if the above method fails
if originalText == "" && msg.Raw["original_message"] != nil {
if original, ok := msg.Raw["original_message"].(map[string]interface{}); ok {
if text, ok := original["text"].(string); ok {
originalText = text
}
}
}
if originalText == "" {
// If we couldn't find the original message text, inform the user
return []*model.MessageAction{
{
Type: model.ActionSendMessage,
Message: &model.Message{
Text: "Sorry, I couldn't find the original message text to perform the replacement.",
Chat: msg.Chat,
Channel: msg.Channel,
ReplyTo: msg.ID,
},
Chat: msg.Chat,
Channel: msg.Channel,
},
}
}
// Extract search pattern, replacement and flags
searchPattern := match[1]
replacement := match[2]
flags := ""
if len(match) > 3 {
flags = match[3]
}
// Process the replacement
result, err := p.performReplacement(originalText, searchPattern, replacement, flags)
if err != nil {
return []*model.MessageAction{
{
Type: model.ActionSendMessage,
Message: &model.Message{
Text: fmt.Sprintf("Error performing replacement: %s", err.Error()),
Chat: msg.Chat,
Channel: msg.Channel,
ReplyTo: msg.ID,
},
Chat: msg.Chat,
Channel: msg.Channel,
},
}
}
// Only send a response if the text actually changed
if result == originalText {
return []*model.MessageAction{
{
Type: model.ActionSendMessage,
Message: &model.Message{
Text: "No changes were made to the original message.",
Chat: msg.Chat,
Channel: msg.Channel,
ReplyTo: msg.ID,
},
Chat: msg.Chat,
Channel: msg.Channel,
},
}
}
// Create a response with the modified text
return []*model.MessageAction{
{
Type: model.ActionSendMessage,
Message: &model.Message{
Text: result,
Chat: msg.Chat,
Channel: msg.Channel,
ReplyTo: msg.ReplyTo, // Reply to the original message
},
Chat: msg.Chat,
Channel: msg.Channel,
},
}
}
// performReplacement performs the search and replace operation on the given text
func (p *SearchReplacePlugin) performReplacement(text, search, replace, flags string) (string, error) {
// Process flags
globalReplace := strings.Contains(flags, "g")
caseInsensitive := strings.Contains(flags, "i")
// Create the regex pattern
pattern := search
regexFlags := ""
if caseInsensitive {
regexFlags += "(?i)"
}
// Escape special characters if we're not in a regular expression
if !strings.Contains(flags, "n") {
pattern = regexp.QuoteMeta(pattern)
}
// Compile the regex
reg, err := regexp.Compile(regexFlags + pattern)
if err != nil {
return "", fmt.Errorf("invalid search pattern: %v", err)
}
// Perform the replacement
var result string
if globalReplace {
result = reg.ReplaceAllString(text, replace)
} else {
// For non-global replace, only replace the first occurrence
indices := reg.FindStringIndex(text)
if indices == nil {
// No match found
return text, nil
}
result = text[:indices[0]] + replace + text[indices[1]:]
}
return result, nil
}