butterrobot/docs/creating-a-plugin.md
Felipe M. 21e4c434fd
All checks were successful
ci/woodpecker/tag/release Pipeline was successful
docs: updated plugin docs
2025-04-21 18:10:30 +02:00

4 KiB

Creating a Plugin

Plugin Categories

ButterRobot organizes plugins into different categories:

  • Development: Utility plugins like ping
  • Fun: Entertainment plugins like dice rolling, coin flipping
  • Social: Social media related plugins like URL transformers/expanders

When creating a new plugin, consider which category it fits into and place it in the appropriate directory.

Plugin Examples

Basic Example: Marco Polo

This simple "Marco Polo" plugin will answer Polo to the user that says Marco:

package myplugin

import (
	"strings"

	"git.nakama.town/fmartingr/butterrobot/internal/model"
	"git.nakama.town/fmartingr/butterrobot/internal/plugin"
)

// MarcoPlugin is a simple Marco/Polo plugin
type MarcoPlugin struct {
	plugin.BasePlugin
}

// New creates a new MarcoPlugin instance
func New() *MarcoPlugin {
	return &MarcoPlugin{
		BasePlugin: plugin.BasePlugin{
			ID:   "test.marco",
			Name: "Marco/Polo",
			Help: "Responds to 'Marco' with 'Polo'",
		},
	}
}

// OnMessage handles incoming messages
func (p *MarcoPlugin) OnMessage(msg *model.Message, config map[string]interface{}) []*model.Message {
	if !strings.EqualFold(strings.TrimSpace(msg.Text), "Marco") {
		return nil
	}

	response := &model.Message{
		Text:     "Polo",
		Chat:     msg.Chat,
		ReplyTo:  msg.ID,
		Channel:  msg.Channel,
	}

	return []*model.Message{response}
}

Advanced Example: URL Transformer

This more complex plugin transforms URLs, useful for improving media embedding in chat platforms:

package social

import (
	"net/url"
	"regexp"
	"strings"

	"git.nakama.town/fmartingr/butterrobot/internal/model"
	"git.nakama.town/fmartingr/butterrobot/internal/plugin"
)

// TwitterExpander transforms twitter.com links to fxtwitter.com links
type TwitterExpander struct {
	plugin.BasePlugin
}

// New creates a new TwitterExpander instance
func NewTwitter() *TwitterExpander {
	return &TwitterExpander{
		BasePlugin: plugin.BasePlugin{
			ID:   "social.twitter",
			Name: "Twitter Link Expander",
			Help: "Automatically converts twitter.com links to fxtwitter.com links and removes tracking parameters",
		},
	}
}

// OnMessage handles incoming messages
func (p *TwitterExpander) OnMessage(msg *model.Message, config map[string]interface{}) []*model.Message {
	// Skip empty messages
	if strings.TrimSpace(msg.Text) == "" {
		return nil
	}

	// Regex to match twitter.com links
	twitterRegex := regexp.MustCompile(`https?://(www\.)?(twitter\.com|x\.com)/[^\s]+`)

	// Check if the message contains a Twitter link
	if !twitterRegex.MatchString(msg.Text) {
		return nil
	}

	// Transform the URL
	transformed := twitterRegex.ReplaceAllStringFunc(msg.Text, func(link string) string {
		// 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", "fxtwitter.com", 1)
			link = strings.Replace(link, "x.com", "fxtwitter.com", 1)
			return link
		}
		
		// Change the host
		if strings.Contains(parsedURL.Host, "twitter.com") {
			parsedURL.Host = strings.Replace(parsedURL.Host, "twitter.com", "fxtwitter.com", 1)
		} else if strings.Contains(parsedURL.Host, "x.com") {
			parsedURL.Host = strings.Replace(parsedURL.Host, "x.com", "fxtwitter.com", 1)
		}
		
		// Remove query parameters
		parsedURL.RawQuery = ""
		
		// Return the cleaned URL
		return parsedURL.String()
	})

	// Create response message
	response := &model.Message{
		Text:    transformed,
		Chat:    msg.Chat,
		ReplyTo: msg.ID,
		Channel: msg.Channel,
	}

	return []*model.Message{response}
}

Registering Plugins

To use the plugin, register it in your application:

// In app.go or similar initialization file
func (a *App) Run() error {
    // ...

    // Register plugins
    plugin.Register(ping.New())                 // Development plugin
    plugin.Register(fun.NewCoin())              // Fun plugin  
    plugin.Register(social.NewTwitter())        // Social media plugin
    plugin.Register(myplugin.New())             // Your custom plugin

    // ...
}