From bb51a0ecb1c4682c573ec1d0424af0de54e927e7 Mon Sep 17 00:00:00 2001 From: ScuroNeko Date: Tue, 17 Feb 2026 22:44:23 +0300 Subject: [PATCH] l10n and cmd generator WIP --- bot.go | 8 +-- cmd_generator.go | 26 ++++++++ handler.go | 30 ++++++++++ plugins.go | 152 +++++++++++++++++++++++++++-------------------- 4 files changed, 149 insertions(+), 67 deletions(-) create mode 100644 cmd_generator.go diff --git a/bot.go b/bot.go index 352c0d2..b16b8fe 100644 --- a/bot.go +++ b/bot.go @@ -192,16 +192,16 @@ func (b *Bot) AddPlugins(plugin ...Plugin) *Bot { func (b *Bot) AddMiddleware(middleware ...Middleware) *Bot { b.middlewares = append(b.middlewares, middleware...) for _, m := range middleware { - b.logger.Debugln(fmt.Sprintf("middleware with name \"%s\" registered", m.Name)) + b.logger.Debugln(fmt.Sprintf("middleware with name \"%s\" registered", m.name)) } sort.Slice(b.middlewares, func(i, j int) bool { first := b.middlewares[i] second := b.middlewares[j] - if first.Order == second.Order { - return first.Name < second.Name + if first.order == second.order { + return first.name < second.name } - return first.Order < second.Order + return first.order < second.order }) return b diff --git a/cmd_generator.go b/cmd_generator.go new file mode 100644 index 0000000..9653f96 --- /dev/null +++ b/cmd_generator.go @@ -0,0 +1,26 @@ +package laniakea + +import "git.nix13.pw/scuroneko/laniakea/tgapi" + +func generateBotCommand(cmd Command) tgapi.BotCommand { + return tgapi.BotCommand{ + Command: cmd.command, Description: cmd.command, + } +} + +func generateBotCommandForPlugin(pl Plugin) []tgapi.BotCommand { + cmds := make([]tgapi.BotCommand, 0) + for _, cmd := range pl.Commands { + cmds = append(cmds, generateBotCommand(cmd)) + } + return cmds +} + +func (b *Bot) AutoGenerateCommands() error { + commands := make([]tgapi.BotCommand, 0) + for _, pl := range b.plugins { + commands = append(commands, generateBotCommandForPlugin(pl)...) + } + _, err := b.api.SetMyCommands(tgapi.SetMyCommandsP{Commands: commands}) + return err +} diff --git a/handler.go b/handler.go index c250277..9d73ea1 100644 --- a/handler.go +++ b/handler.go @@ -1,6 +1,7 @@ package laniakea import ( + "encoding/base64" "encoding/json" "strings" @@ -112,3 +113,32 @@ func (b *Bot) checkPrefixes(text string) (string, bool) { } return "", false } + +func encodeJsonPayload(d CallbackData) (string, error) { + b, err := json.Marshal(d) + if err != nil { + return "", err + } + return string(b), nil +} +func decodeJsonPayload(s string) (CallbackData, error) { + var data CallbackData + err := json.Unmarshal([]byte(s), &data) + return data, err +} +func encodeBase64Payload(d CallbackData) (string, error) { + data, err := encodeJsonPayload(d) + if err != nil { + return "", err + } + dst := make([]byte, base64.StdEncoding.EncodedLen(len([]byte(data)))) + base64.StdEncoding.Encode(dst, []byte(data)) + return string(dst), nil +} +func decodeBase64Payload(s string) (CallbackData, error) { + b, err := base64.StdEncoding.DecodeString(s) + if err != nil { + return CallbackData{}, err + } + return decodeJsonPayload(string(b)) +} diff --git a/plugins.go b/plugins.go index d6768f9..aa04a36 100644 --- a/plugins.go +++ b/plugins.go @@ -1,72 +1,97 @@ package laniakea import ( - "log" + "regexp" "git.nix13.pw/scuroneko/extypes" ) type CommandExecutor func(ctx *MsgContext, dbContext *DatabaseContext) -type PluginBuilder struct { - name string - commands map[string]CommandExecutor - payloads map[string]CommandExecutor - middlewares extypes.Slice[*PluginMiddleware] +const ( + CommandValueStringType CommandValueType = "string" + CommandValueIntType CommandValueType = "int" + CommandValueBoolType CommandValueType = "bool" + CommandValueAnyType CommandValueType = "any" +) + +var ( + CommandRegexInt = regexp.MustCompile("\\d+") + CommandRegexString = regexp.MustCompile("\\.+") +) + +type CommandValueType string +type CommandArg struct { + valueType CommandValueType + text string + regex *regexp.Regexp +} + +func NewCommandArg(text string, valueType CommandValueType) CommandArg { + regex := CommandRegexString + switch valueType { + case CommandValueIntType: + regex = CommandRegexInt + } + return CommandArg{valueType, text, regex} +} + +type Command struct { + command string + exec CommandExecutor + args []CommandArg + middlewares []Middleware +} + +func NewCommand(exec CommandExecutor, command string, args ...CommandArg) *Command { + return &Command{command, exec, args, make([]Middleware, 0)} } type Plugin struct { Name string - Commands map[string]CommandExecutor - Payloads map[string]CommandExecutor - Middlewares extypes.Slice[*PluginMiddleware] + Commands map[string]Command + Payloads map[string]Command + Middlewares extypes.Slice[PluginMiddleware] } -func NewPlugin(name string) *PluginBuilder { - return &PluginBuilder{ - name: name, - commands: make(map[string]CommandExecutor), - payloads: make(map[string]CommandExecutor), +func NewPlugin(name string) *Plugin { + return &Plugin{ + Name: name, + Commands: map[string]Command{}, + Payloads: map[string]Command{}, + Middlewares: extypes.Slice[PluginMiddleware]{}, } } -func (p *PluginBuilder) Command(f CommandExecutor, cmd ...string) *PluginBuilder { - for _, c := range cmd { - p.commands[c] = f - } +func (p *Plugin) AddCommand(command Command) *Plugin { + p.Commands[command.command] = command return p } -func (p *PluginBuilder) Payload(f CommandExecutor, payloads ...string) *PluginBuilder { - for _, payload := range payloads { - p.payloads[payload] = f - } +func (p *Plugin) AddPayload(command Command) *Plugin { + p.Payloads[command.command] = command return p } -func (p *PluginBuilder) AddMiddleware(middleware *PluginMiddleware) *PluginBuilder { - p.middlewares = p.middlewares.Push(middleware) +func (p *Plugin) AddMiddleware(middleware PluginMiddleware) *Plugin { + p.Middlewares = p.Middlewares.Push(middleware) return p } -func (p *PluginBuilder) Build() Plugin { - if len(p.commands) == 0 && len(p.payloads) == 0 { - log.Printf("no command or payloads for %s", p.name) - } - return Plugin{ - p.name, p.commands, - p.payloads, p.middlewares, - } -} - func (p *Plugin) Execute(cmd string, ctx *MsgContext, dbContext *DatabaseContext) { - (p.Commands[cmd])(ctx, dbContext) + command := p.Commands[cmd] + if !command.validateArgs(ctx.Args) { + return + } + command.exec(ctx, dbContext) } - func (p *Plugin) ExecutePayload(payload string, ctx *MsgContext, dbContext *DatabaseContext) { - (p.Payloads[payload])(ctx, dbContext) + pl := p.Payloads[payload] + if !pl.validateArgs(ctx.Args) { + return + } + pl.exec(ctx, dbContext) } - func (p *Plugin) executeMiddlewares(ctx *MsgContext, db *DatabaseContext) bool { for _, m := range p.Middlewares { if !m.Execute(ctx, db) { @@ -75,42 +100,43 @@ func (p *Plugin) executeMiddlewares(ctx *MsgContext, db *DatabaseContext) bool { } return true } +func (c *Command) validateArgs(args []string) bool { + if len(args) != len(c.args) { + return false + } + + for i, arg := range c.args { + if arg.regex == nil { + continue + } + if !arg.regex.MatchString(args[i]) { + return false + } + } + return true +} type Middleware struct { - Name string - Executor CommandExecutor - Order int - Async bool -} -type MiddlewareBuilder struct { - name string - executor CommandExecutor - order int - async bool + name string + exec CommandExecutor + order int + async bool } -func NewMiddleware(name string, executor CommandExecutor) *MiddlewareBuilder { - return &MiddlewareBuilder{name: name, executor: executor, order: 0, async: false} +func NewMiddleware(name string, executor CommandExecutor) *Middleware { + return &Middleware{name, executor, 0, false} } -func (m *MiddlewareBuilder) SetOrder(order int) *MiddlewareBuilder { +func (m *Middleware) SetOrder(order int) *Middleware { m.order = order return m } -func (m *MiddlewareBuilder) SetAsync(async bool) *MiddlewareBuilder { +func (m *Middleware) SetAsync(async bool) *Middleware { m.async = async return m } -func (m *MiddlewareBuilder) Build() Middleware { - return Middleware{ - Name: m.name, - Executor: m.executor, - Order: m.order, - Async: m.async, - } -} -func (m Middleware) Execute(ctx *MsgContext, db *DatabaseContext) { - if m.Async { - go m.Executor(ctx, db) +func (m *Middleware) Execute(ctx *MsgContext, db *DatabaseContext) { + if m.async { + go m.exec(ctx, db) } else { m.Execute(ctx, db) }