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 }