l10n and cmd generator WIP

This commit is contained in:
2026-02-17 22:44:23 +03:00
parent 4527dd661a
commit bb51a0ecb1
4 changed files with 149 additions and 67 deletions

8
bot.go
View File

@@ -192,16 +192,16 @@ func (b *Bot) AddPlugins(plugin ...Plugin) *Bot {
func (b *Bot) AddMiddleware(middleware ...Middleware) *Bot { func (b *Bot) AddMiddleware(middleware ...Middleware) *Bot {
b.middlewares = append(b.middlewares, middleware...) b.middlewares = append(b.middlewares, middleware...)
for _, m := range 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 { sort.Slice(b.middlewares, func(i, j int) bool {
first := b.middlewares[i] first := b.middlewares[i]
second := b.middlewares[j] second := b.middlewares[j]
if first.Order == second.Order { if first.order == second.order {
return first.Name < second.Name return first.name < second.name
} }
return first.Order < second.Order return first.order < second.order
}) })
return b return b

26
cmd_generator.go Normal file
View File

@@ -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
}

View File

@@ -1,6 +1,7 @@
package laniakea package laniakea
import ( import (
"encoding/base64"
"encoding/json" "encoding/json"
"strings" "strings"
@@ -112,3 +113,32 @@ func (b *Bot) checkPrefixes(text string) (string, bool) {
} }
return "", false 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))
}

View File

@@ -1,72 +1,97 @@
package laniakea package laniakea
import ( import (
"log" "regexp"
"git.nix13.pw/scuroneko/extypes" "git.nix13.pw/scuroneko/extypes"
) )
type CommandExecutor func(ctx *MsgContext, dbContext *DatabaseContext) type CommandExecutor func(ctx *MsgContext, dbContext *DatabaseContext)
type PluginBuilder struct { const (
name string CommandValueStringType CommandValueType = "string"
commands map[string]CommandExecutor CommandValueIntType CommandValueType = "int"
payloads map[string]CommandExecutor CommandValueBoolType CommandValueType = "bool"
middlewares extypes.Slice[*PluginMiddleware] 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 { type Plugin struct {
Name string Name string
Commands map[string]CommandExecutor Commands map[string]Command
Payloads map[string]CommandExecutor Payloads map[string]Command
Middlewares extypes.Slice[*PluginMiddleware] Middlewares extypes.Slice[PluginMiddleware]
} }
func NewPlugin(name string) *PluginBuilder { func NewPlugin(name string) *Plugin {
return &PluginBuilder{ return &Plugin{
name: name, Name: name,
commands: make(map[string]CommandExecutor), Commands: map[string]Command{},
payloads: make(map[string]CommandExecutor), Payloads: map[string]Command{},
Middlewares: extypes.Slice[PluginMiddleware]{},
} }
} }
func (p *PluginBuilder) Command(f CommandExecutor, cmd ...string) *PluginBuilder { func (p *Plugin) AddCommand(command Command) *Plugin {
for _, c := range cmd { p.Commands[command.command] = command
p.commands[c] = f
}
return p return p
} }
func (p *PluginBuilder) Payload(f CommandExecutor, payloads ...string) *PluginBuilder { func (p *Plugin) AddPayload(command Command) *Plugin {
for _, payload := range payloads { p.Payloads[command.command] = command
p.payloads[payload] = f
}
return p return p
} }
func (p *PluginBuilder) AddMiddleware(middleware *PluginMiddleware) *PluginBuilder { func (p *Plugin) AddMiddleware(middleware PluginMiddleware) *Plugin {
p.middlewares = p.middlewares.Push(middleware) p.Middlewares = p.Middlewares.Push(middleware)
return p 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) { 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) { 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 { func (p *Plugin) executeMiddlewares(ctx *MsgContext, db *DatabaseContext) bool {
for _, m := range p.Middlewares { for _, m := range p.Middlewares {
if !m.Execute(ctx, db) { if !m.Execute(ctx, db) {
@@ -75,42 +100,43 @@ func (p *Plugin) executeMiddlewares(ctx *MsgContext, db *DatabaseContext) bool {
} }
return true 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 { type Middleware struct {
Name string name string
Executor CommandExecutor exec CommandExecutor
Order int order int
Async bool async bool
}
type MiddlewareBuilder struct {
name string
executor CommandExecutor
order int
async bool
} }
func NewMiddleware(name string, executor CommandExecutor) *MiddlewareBuilder { func NewMiddleware(name string, executor CommandExecutor) *Middleware {
return &MiddlewareBuilder{name: name, executor: executor, order: 0, async: false} return &Middleware{name, executor, 0, false}
} }
func (m *MiddlewareBuilder) SetOrder(order int) *MiddlewareBuilder { func (m *Middleware) SetOrder(order int) *Middleware {
m.order = order m.order = order
return m return m
} }
func (m *MiddlewareBuilder) SetAsync(async bool) *MiddlewareBuilder { func (m *Middleware) SetAsync(async bool) *Middleware {
m.async = async m.async = async
return m return m
} }
func (m *MiddlewareBuilder) Build() Middleware { func (m *Middleware) Execute(ctx *MsgContext, db *DatabaseContext) {
return Middleware{ if m.async {
Name: m.name, go m.exec(ctx, db)
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)
} else { } else {
m.Execute(ctx, db) m.Execute(ctx, db)
} }