refactor: python -> go
All checks were successful
ci/woodpecker/tag/release Pipeline was successful

This commit is contained in:
Felipe M 2025-04-20 13:54:22 +02:00
parent 9c78ea2d48
commit 7c684af8c3
Signed by: fmartingr
GPG key ID: CCFBC5637D4000A8
79 changed files with 3594 additions and 3257 deletions

View file

@ -0,0 +1,50 @@
package fun
import (
"math/rand"
"strings"
"time"
"git.nakama.town/fmartingr/butterrobot/internal/model"
"git.nakama.town/fmartingr/butterrobot/internal/plugin"
)
// CoinPlugin flips a coin
type CoinPlugin struct {
plugin.BasePlugin
rand *rand.Rand
}
// NewCoin creates a new CoinPlugin instance
func NewCoin() *CoinPlugin {
source := rand.NewSource(time.Now().UnixNano())
return &CoinPlugin{
BasePlugin: plugin.BasePlugin{
ID: "fun.coin",
Name: "Coin Flip",
Help: "Flips a coin when you type 'flip a coin'",
},
rand: rand.New(source),
}
}
// OnMessage handles incoming messages
func (p *CoinPlugin) OnMessage(msg *model.Message, config map[string]interface{}) []*model.Message {
if !strings.Contains(strings.ToLower(msg.Text), "flip a coin") {
return nil
}
result := "Heads"
if p.rand.Intn(2) == 0 {
result = "Tails"
}
response := &model.Message{
Text: result,
Chat: msg.Chat,
ReplyTo: msg.ID,
Channel: msg.Channel,
}
return []*model.Message{response}
}

118
internal/plugin/fun/dice.go Normal file
View file

@ -0,0 +1,118 @@
package fun
import (
"fmt"
"math/rand"
"regexp"
"strconv"
"strings"
"time"
"git.nakama.town/fmartingr/butterrobot/internal/model"
"git.nakama.town/fmartingr/butterrobot/internal/plugin"
)
// DicePlugin rolls dice based on standard dice notation
type DicePlugin struct {
plugin.BasePlugin
rand *rand.Rand
}
// NewDice creates a new DicePlugin instance
func NewDice() *DicePlugin {
source := rand.NewSource(time.Now().UnixNano())
return &DicePlugin{
BasePlugin: plugin.BasePlugin{
ID: "fun.dice",
Name: "Dice Roller",
Help: "Rolls dice when you type '!dice [formula]' (default: 1d20)",
},
rand: rand.New(source),
}
}
// OnMessage handles incoming messages
func (p *DicePlugin) OnMessage(msg *model.Message, config map[string]interface{}) []*model.Message {
if !strings.HasPrefix(strings.TrimSpace(strings.ToLower(msg.Text)), "!dice") {
return nil
}
// Extract dice formula
formula := strings.TrimSpace(strings.TrimPrefix(msg.Text, "!dice"))
formula = strings.TrimSpace(strings.TrimPrefix(formula, "!dice"))
if formula == "" {
formula = "1d20" // Default formula
}
// Parse and roll the dice
result, err := p.rollDice(formula)
responseText := ""
if err != nil {
responseText = fmt.Sprintf("Error: %s", err.Error())
} else {
responseText = fmt.Sprintf("%d", result)
}
response := &model.Message{
Text: responseText,
Chat: msg.Chat,
ReplyTo: msg.ID,
Channel: msg.Channel,
}
return []*model.Message{response}
}
// rollDice parses a dice formula string and returns the result
func (p *DicePlugin) rollDice(formula string) (int, error) {
// Support basic dice notation like "2d6", "1d20+5", etc.
diceRegex := regexp.MustCompile(`^(\d+)d(\d+)(?:([+-])(\d+))?$`)
matches := diceRegex.FindStringSubmatch(formula)
if matches == nil {
return 0, fmt.Errorf("invalid dice formula: %s", formula)
}
// Parse number of dice
numDice, err := strconv.Atoi(matches[1])
if err != nil || numDice < 1 {
return 0, fmt.Errorf("invalid number of dice")
}
if numDice > 100 {
return 0, fmt.Errorf("too many dice (max 100)")
}
// Parse number of sides
sides, err := strconv.Atoi(matches[2])
if err != nil || sides < 1 {
return 0, fmt.Errorf("invalid number of sides")
}
if sides > 1000 {
return 0, fmt.Errorf("too many sides (max 1000)")
}
// Roll the dice
total := 0
for i := 0; i < numDice; i++ {
roll := p.rand.Intn(sides) + 1
total += roll
}
// Apply modifier if present
if len(matches) > 3 && matches[3] != "" {
modifier, err := strconv.Atoi(matches[4])
if err != nil {
return 0, fmt.Errorf("invalid modifier")
}
if matches[3] == "+" {
total += modifier
} else if matches[3] == "-" {
total -= modifier
}
}
return total, nil
}

View file

@ -0,0 +1,40 @@
package fun
import (
"strings"
"git.nakama.town/fmartingr/butterrobot/internal/model"
"git.nakama.town/fmartingr/butterrobot/internal/plugin"
)
// LoquitoPlugin replies with "Loquito tu." when someone says "lo quito"
type LoquitoPlugin struct {
plugin.BasePlugin
}
// NewLoquito creates a new LoquitoPlugin instance
func NewLoquito() *LoquitoPlugin {
return &LoquitoPlugin{
BasePlugin: plugin.BasePlugin{
ID: "fun.loquito",
Name: "Loquito Reply",
Help: "Replies with 'Loquito tu.' when someone says 'lo quito'",
},
}
}
// OnMessage handles incoming messages
func (p *LoquitoPlugin) OnMessage(msg *model.Message, config map[string]interface{}) []*model.Message {
if !strings.Contains(strings.ToLower(msg.Text), "lo quito") {
return nil
}
response := &model.Message{
Text: "Loquito tu.",
Chat: msg.Chat,
ReplyTo: msg.ID,
Channel: msg.Channel,
}
return []*model.Message{response}
}

View file

@ -0,0 +1,40 @@
package ping
import (
"strings"
"git.nakama.town/fmartingr/butterrobot/internal/model"
"git.nakama.town/fmartingr/butterrobot/internal/plugin"
)
// PingPlugin is a simple ping/pong plugin
type PingPlugin struct {
plugin.BasePlugin
}
// New creates a new PingPlugin instance
func New() *PingPlugin {
return &PingPlugin{
BasePlugin: plugin.BasePlugin{
ID: "dev.ping",
Name: "Ping",
Help: "Responds to 'ping' with 'pong'",
},
}
}
// OnMessage handles incoming messages
func (p *PingPlugin) OnMessage(msg *model.Message, config map[string]interface{}) []*model.Message {
if !strings.EqualFold(strings.TrimSpace(msg.Text), "ping") {
return nil
}
response := &model.Message{
Text: "pong",
Chat: msg.Chat,
ReplyTo: msg.ID,
Channel: msg.Channel,
}
return []*model.Message{response}
}

82
internal/plugin/plugin.go Normal file
View file

@ -0,0 +1,82 @@
package plugin
import (
"sync"
"git.nakama.town/fmartingr/butterrobot/internal/model"
)
var (
// plugins holds all registered plugins
plugins = make(map[string]model.Plugin)
// pluginsMu protects the plugins map
pluginsMu sync.RWMutex
)
// Register registers a plugin with the given ID
func Register(plugin model.Plugin) {
pluginsMu.Lock()
defer pluginsMu.Unlock()
plugins[plugin.GetID()] = plugin
}
// Get returns a plugin by ID
func Get(id string) (model.Plugin, error) {
pluginsMu.RLock()
defer pluginsMu.RUnlock()
plugin, exists := plugins[id]
if !exists {
return nil, model.ErrPluginNotFound
}
return plugin, nil
}
// GetAvailablePlugins returns all registered plugins
func GetAvailablePlugins() map[string]model.Plugin {
pluginsMu.RLock()
defer pluginsMu.RUnlock()
// Create a copy to avoid race conditions
result := make(map[string]model.Plugin, len(plugins))
for id, plugin := range plugins {
result[id] = plugin
}
return result
}
// BasePlugin provides a common base for plugins
type BasePlugin struct {
ID string
Name string
Help string
ConfigRequired bool
}
// GetID returns the plugin ID
func (p *BasePlugin) GetID() string {
return p.ID
}
// GetName returns the plugin name
func (p *BasePlugin) GetName() string {
return p.Name
}
// GetHelp returns the plugin help text
func (p *BasePlugin) GetHelp() string {
return p.Help
}
// RequiresConfig indicates if the plugin requires configuration
func (p *BasePlugin) RequiresConfig() bool {
return p.ConfigRequired
}
// OnMessage is the default implementation that does nothing
func (p *BasePlugin) OnMessage(msg *model.Message, config map[string]interface{}) []*model.Message {
return nil
}