# 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_: ```go 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: ```go 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: ```go // 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 // ... } ```