l10n and bot command auto generation; v0.6.0
This commit is contained in:
16
bot.go
16
bot.go
@@ -41,7 +41,7 @@ func LoadSettingsFromEnv() *BotSettings {
|
|||||||
func LoadPrefixesFromEnv() []string {
|
func LoadPrefixesFromEnv() []string {
|
||||||
prefixesS, exists := os.LookupEnv("PREFIXES")
|
prefixesS, exists := os.LookupEnv("PREFIXES")
|
||||||
if !exists {
|
if !exists {
|
||||||
return []string{"!"}
|
return []string{"/"}
|
||||||
}
|
}
|
||||||
return strings.Split(prefixesS, ";")
|
return strings.Split(prefixesS, ";")
|
||||||
}
|
}
|
||||||
@@ -61,6 +61,7 @@ type Bot struct {
|
|||||||
|
|
||||||
dbContext *DatabaseContext
|
dbContext *DatabaseContext
|
||||||
api *tgapi.API
|
api *tgapi.API
|
||||||
|
l10n L10n
|
||||||
|
|
||||||
dbWriterRequested extypes.Slice[*slog.Logger]
|
dbWriterRequested extypes.Slice[*slog.Logger]
|
||||||
|
|
||||||
@@ -76,7 +77,7 @@ func NewBot(settings *BotSettings) *Bot {
|
|||||||
updateOffset: 0, plugins: make([]Plugin, 0), debug: settings.Debug, errorTemplate: "%s",
|
updateOffset: 0, plugins: make([]Plugin, 0), debug: settings.Debug, errorTemplate: "%s",
|
||||||
prefixes: settings.Prefixes, updateTypes: make([]tgapi.UpdateType, 0), runners: make([]Runner, 0),
|
prefixes: settings.Prefixes, updateTypes: make([]tgapi.UpdateType, 0), runners: make([]Runner, 0),
|
||||||
updateQueue: updateQueue, api: api, dbWriterRequested: make([]*slog.Logger, 0),
|
updateQueue: updateQueue, api: api, dbWriterRequested: make([]*slog.Logger, 0),
|
||||||
token: settings.Token,
|
token: settings.Token, l10n: L10n{},
|
||||||
}
|
}
|
||||||
bot.dbWriterRequested = bot.dbWriterRequested.Push(api.Logger)
|
bot.dbWriterRequested = bot.dbWriterRequested.Push(api.Logger)
|
||||||
|
|
||||||
@@ -182,9 +183,9 @@ func (b *Bot) Debug(debug bool) *Bot {
|
|||||||
b.debug = debug
|
b.debug = debug
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
func (b *Bot) AddPlugins(plugin ...Plugin) *Bot {
|
func (b *Bot) AddPlugins(plugin ...*Plugin) *Bot {
|
||||||
b.plugins = append(b.plugins, plugin...)
|
|
||||||
for _, p := range plugin {
|
for _, p := range plugin {
|
||||||
|
b.plugins = append(b.plugins, *p)
|
||||||
b.logger.Debugln(fmt.Sprintf("plugins with name \"%s\" registered", p.Name))
|
b.logger.Debugln(fmt.Sprintf("plugins with name \"%s\" registered", p.Name))
|
||||||
}
|
}
|
||||||
return b
|
return b
|
||||||
@@ -211,6 +212,13 @@ func (b *Bot) AddRunner(runner Runner) *Bot {
|
|||||||
b.logger.Debugln(fmt.Sprintf("runner with name \"%s\" registered", runner.Name))
|
b.logger.Debugln(fmt.Sprintf("runner with name \"%s\" registered", runner.Name))
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
func (b *Bot) AddL10n(l L10n) *Bot {
|
||||||
|
b.l10n = l
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
func (b *Bot) L10n(lang, key string) string {
|
||||||
|
return b.l10n.Translate(lang, key)
|
||||||
|
}
|
||||||
func (b *Bot) Logger() *slog.Logger {
|
func (b *Bot) Logger() *slog.Logger {
|
||||||
return b.logger
|
return b.logger
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,59 @@
|
|||||||
package laniakea
|
package laniakea
|
||||||
|
|
||||||
import "git.nix13.pw/scuroneko/laniakea/tgapi"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.nix13.pw/scuroneko/laniakea/tgapi"
|
||||||
|
)
|
||||||
|
|
||||||
func generateBotCommand(cmd Command) tgapi.BotCommand {
|
func generateBotCommand(cmd Command) tgapi.BotCommand {
|
||||||
return tgapi.BotCommand{
|
desc := cmd.command
|
||||||
Command: cmd.command, Description: cmd.command,
|
if len(cmd.description) > 0 {
|
||||||
|
desc = cmd.description
|
||||||
}
|
}
|
||||||
|
var descArgs []string
|
||||||
|
for _, a := range cmd.args {
|
||||||
|
if a.required {
|
||||||
|
descArgs = append(descArgs, fmt.Sprintf("%s", a.text))
|
||||||
|
} else {
|
||||||
|
descArgs = append(descArgs, fmt.Sprintf("[%s]", a.text))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
desc = fmt.Sprintf("%s. Usage: /%s %s", desc, cmd.command, strings.Join(descArgs, " "))
|
||||||
|
return tgapi.BotCommand{Command: cmd.command, Description: desc}
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateBotCommandForPlugin(pl Plugin) []tgapi.BotCommand {
|
func generateBotCommandForPlugin(pl Plugin) []tgapi.BotCommand {
|
||||||
cmds := make([]tgapi.BotCommand, 0)
|
commands := make([]tgapi.BotCommand, 0)
|
||||||
for _, cmd := range pl.Commands {
|
for _, cmd := range pl.Commands {
|
||||||
cmds = append(cmds, generateBotCommand(cmd))
|
commands = append(commands, generateBotCommand(cmd))
|
||||||
}
|
}
|
||||||
return cmds
|
return commands
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bot) AutoGenerateCommands() error {
|
func (b *Bot) AutoGenerateCommands() error {
|
||||||
|
_, err := b.api.DeleteMyCommands(tgapi.DeleteMyCommandsP{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
commands := make([]tgapi.BotCommand, 0)
|
commands := make([]tgapi.BotCommand, 0)
|
||||||
for _, pl := range b.plugins {
|
for _, pl := range b.plugins {
|
||||||
commands = append(commands, generateBotCommandForPlugin(pl)...)
|
commands = append(commands, generateBotCommandForPlugin(pl)...)
|
||||||
}
|
}
|
||||||
_, err := b.api.SetMyCommands(tgapi.SetMyCommandsP{Commands: commands})
|
|
||||||
|
privateChatsScope := &tgapi.BotCommandScope{Type: tgapi.BotCommandScopePrivateType}
|
||||||
|
groupChatsScope := &tgapi.BotCommandScope{Type: tgapi.BotCommandScopeGroupType}
|
||||||
|
chatAdminsScope := &tgapi.BotCommandScope{Type: tgapi.BotCommandScopeAllChatAdministratorsType}
|
||||||
|
_, err = b.api.SetMyCommands(tgapi.SetMyCommandsP{Commands: commands, Scope: privateChatsScope})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = b.api.SetMyCommands(tgapi.SetMyCommandsP{Commands: commands, Scope: groupChatsScope})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = b.api.SetMyCommands(tgapi.SetMyCommandsP{Commands: commands, Scope: chatAdminsScope})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
5
go.mod
5
go.mod
@@ -1,11 +1,11 @@
|
|||||||
module git.nix13.pw/scuroneko/laniakea
|
module git.nix13.pw/scuroneko/laniakea
|
||||||
|
|
||||||
go 1.25
|
go 1.26
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.nix13.pw/scuroneko/extypes v1.2.0
|
git.nix13.pw/scuroneko/extypes v1.2.0
|
||||||
git.nix13.pw/scuroneko/slog v1.0.2
|
git.nix13.pw/scuroneko/slog v1.0.2
|
||||||
github.com/redis/go-redis/v9 v9.17.3
|
github.com/redis/go-redis/v9 v9.18.0
|
||||||
github.com/vinovest/sqlx v1.7.1
|
github.com/vinovest/sqlx v1.7.1
|
||||||
go.mongodb.org/mongo-driver/v2 v2.5.0
|
go.mongodb.org/mongo-driver/v2 v2.5.0
|
||||||
)
|
)
|
||||||
@@ -23,6 +23,7 @@ require (
|
|||||||
github.com/xdg-go/scram v1.2.0 // indirect
|
github.com/xdg-go/scram v1.2.0 // indirect
|
||||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||||
|
go.uber.org/atomic v1.11.0 // indirect
|
||||||
golang.org/x/crypto v0.48.0 // indirect
|
golang.org/x/crypto v0.48.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.41.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
|
|||||||
10
go.sum
10
go.sum
@@ -22,6 +22,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
|||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
|
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
|
||||||
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
@@ -36,8 +38,8 @@ github.com/muir/sqltoken v0.3.0 h1:3xbcqr80f3IA4OlwkOpdIHC4DTu6gsi1TwMqgYL4Dpg=
|
|||||||
github.com/muir/sqltoken v0.3.0/go.mod h1:+OSmbGI22QcVZ6DCzlHT8EAzEq/mqtqedtPP91Le+3A=
|
github.com/muir/sqltoken v0.3.0/go.mod h1:+OSmbGI22QcVZ6DCzlHT8EAzEq/mqtqedtPP91Le+3A=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/redis/go-redis/v9 v9.17.3 h1:fN29NdNrE17KttK5Ndf20buqfDZwGNgoUr9qjl1DQx4=
|
github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs=
|
||||||
github.com/redis/go-redis/v9 v9.17.3/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/vinovest/sqlx v1.7.1 h1:kdq4v0N9kRLpytWGSWOw4aulOGdQPmIoMR6Y+cTBxow=
|
github.com/vinovest/sqlx v1.7.1 h1:kdq4v0N9kRLpytWGSWOw4aulOGdQPmIoMR6Y+cTBxow=
|
||||||
@@ -51,8 +53,12 @@ github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gi
|
|||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||||
|
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||||
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
|
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
|
||||||
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||||
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
|
|||||||
12
handler.go
12
handler.go
@@ -46,7 +46,6 @@ func (b *Bot) handleMessage(update *tgapi.Update, ctx *MsgContext) {
|
|||||||
text = strings.TrimSpace(text[len(prefix):])
|
text = strings.TrimSpace(text[len(prefix):])
|
||||||
|
|
||||||
for _, plugin := range b.plugins {
|
for _, plugin := range b.plugins {
|
||||||
// Check every command
|
|
||||||
for cmd := range plugin.Commands {
|
for cmd := range plugin.Commands {
|
||||||
if !strings.HasPrefix(text, cmd) {
|
if !strings.HasPrefix(text, cmd) {
|
||||||
continue
|
continue
|
||||||
@@ -60,17 +59,22 @@ func (b *Bot) handleMessage(update *tgapi.Update, ctx *MsgContext) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isValid {
|
if !isValid {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Text = strings.TrimSpace(text[len(cmd):])
|
ctx.Text = strings.TrimSpace(text[len(cmd):])
|
||||||
ctx.Args = strings.Split(ctx.Text, " ")
|
if ctx.Text == "" {
|
||||||
|
ctx.Args = []string{}
|
||||||
|
} else {
|
||||||
|
ctx.Args = strings.Split(ctx.Text, " ")
|
||||||
|
}
|
||||||
|
|
||||||
if !plugin.executeMiddlewares(ctx, b.dbContext) {
|
if !plugin.executeMiddlewares(ctx, b.dbContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go plugin.Execute(cmd, ctx, b.dbContext)
|
go plugin.executeCmd(cmd, ctx, b.dbContext)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,7 +104,7 @@ func (b *Bot) handleCallback(update *tgapi.Update, ctx *MsgContext) {
|
|||||||
if !plugin.executeMiddlewares(ctx, b.dbContext) {
|
if !plugin.executeMiddlewares(ctx, b.dbContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go plugin.ExecutePayload(data.Command, ctx, b.dbContext)
|
go plugin.executePayload(data.Command, ctx, b.dbContext)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
27
l10n.go
Normal file
27
l10n.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package laniakea
|
||||||
|
|
||||||
|
// DictEntry {key:{ru:123,en:123}}
|
||||||
|
type DictEntry map[string]string
|
||||||
|
type L10n struct {
|
||||||
|
entries map[string]DictEntry
|
||||||
|
fallbackLang string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewL10n(fallbackLanguage string) *L10n {
|
||||||
|
return &L10n{make(map[string]DictEntry), fallbackLanguage}
|
||||||
|
}
|
||||||
|
func (l *L10n) AddDictEntry(key string, value DictEntry) *L10n {
|
||||||
|
l.entries[key] = value
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
func (l *L10n) GetFallbackLanguage() string {
|
||||||
|
return l.fallbackLang
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *L10n) Translate(lang, key string) string {
|
||||||
|
s, ok := l.entries[key]
|
||||||
|
if !ok {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
return s[lang]
|
||||||
|
}
|
||||||
@@ -212,3 +212,11 @@ func (ctx *MsgContext) error(err error) {
|
|||||||
func (ctx *MsgContext) Error(err error) {
|
func (ctx *MsgContext) Error(err error) {
|
||||||
ctx.error(err)
|
ctx.error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctx *MsgContext) Translate(key string) string {
|
||||||
|
if ctx.From == nil {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
lang := Val(ctx.From.LanguageCode, ctx.Bot.l10n.GetFallbackLanguage())
|
||||||
|
return ctx.Bot.L10n(lang, key)
|
||||||
|
}
|
||||||
|
|||||||
147
plugins.go
147
plugins.go
@@ -1,13 +1,12 @@
|
|||||||
package laniakea
|
package laniakea
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"git.nix13.pw/scuroneko/extypes"
|
"git.nix13.pw/scuroneko/extypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CommandExecutor func(ctx *MsgContext, dbContext *DatabaseContext)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
CommandValueStringType CommandValueType = "string"
|
CommandValueStringType CommandValueType = "string"
|
||||||
CommandValueIntType CommandValueType = "int"
|
CommandValueIntType CommandValueType = "int"
|
||||||
@@ -17,7 +16,12 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
CommandRegexInt = regexp.MustCompile("\\d+")
|
CommandRegexInt = regexp.MustCompile("\\d+")
|
||||||
CommandRegexString = regexp.MustCompile("\\.+")
|
CommandRegexString = regexp.MustCompile(".+")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrCmdArgCountMismatch = errors.New("command arg count mismatch")
|
||||||
|
ErrCmdArgRegexpMismatch = errors.New("command arg regexp mismatch")
|
||||||
)
|
)
|
||||||
|
|
||||||
type CommandValueType string
|
type CommandValueType string
|
||||||
@@ -25,69 +29,102 @@ type CommandArg struct {
|
|||||||
valueType CommandValueType
|
valueType CommandValueType
|
||||||
text string
|
text string
|
||||||
regex *regexp.Regexp
|
regex *regexp.Regexp
|
||||||
|
required bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCommandArg(text string, valueType CommandValueType) CommandArg {
|
func NewCommandArg(text string, valueType CommandValueType) *CommandArg {
|
||||||
regex := CommandRegexString
|
regex := CommandRegexString
|
||||||
switch valueType {
|
switch valueType {
|
||||||
case CommandValueIntType:
|
case CommandValueIntType:
|
||||||
regex = CommandRegexInt
|
regex = CommandRegexInt
|
||||||
}
|
}
|
||||||
return CommandArg{valueType, text, regex}
|
return &CommandArg{valueType, text, regex, false}
|
||||||
|
}
|
||||||
|
func (c *CommandArg) SetRequired() *CommandArg {
|
||||||
|
c.required = true
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CommandExecutor func(ctx *MsgContext, dbContext *DatabaseContext)
|
||||||
type Command struct {
|
type Command struct {
|
||||||
command string
|
command string
|
||||||
|
description string
|
||||||
exec CommandExecutor
|
exec CommandExecutor
|
||||||
args []CommandArg
|
args extypes.Slice[CommandArg]
|
||||||
middlewares []Middleware
|
middlewares extypes.Slice[Middleware]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCommand(exec CommandExecutor, command string, args ...CommandArg) *Command {
|
func NewCommand(exec CommandExecutor, command string, args ...CommandArg) *Command {
|
||||||
return &Command{command, exec, args, make([]Middleware, 0)}
|
return &Command{command, "", exec, args, make(extypes.Slice[Middleware], 0)}
|
||||||
|
}
|
||||||
|
func (c *Command) Use(m Middleware) *Command {
|
||||||
|
c.middlewares = c.middlewares.Push(m)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
func (c *Command) SetDescription(desc string) *Command {
|
||||||
|
c.description = desc
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
func (c *Command) validateArgs(args []string) error {
|
||||||
|
cmdArgs := c.args.Filter(func(e CommandArg) bool { return !e.required })
|
||||||
|
if len(args) < cmdArgs.Len() {
|
||||||
|
return ErrCmdArgCountMismatch
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, arg := range args {
|
||||||
|
if i >= c.args.Len() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
cmdArg := c.args.Get(i)
|
||||||
|
if cmdArg.regex == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !cmdArg.regex.MatchString(arg) {
|
||||||
|
return ErrCmdArgRegexpMismatch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Plugin struct {
|
type Plugin struct {
|
||||||
Name string
|
Name string
|
||||||
Commands map[string]Command
|
Commands map[string]Command
|
||||||
Payloads map[string]Command
|
Payloads map[string]Command
|
||||||
Middlewares extypes.Slice[PluginMiddleware]
|
Middlewares extypes.Slice[Middleware]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPlugin(name string) *Plugin {
|
func NewPlugin(name string) *Plugin {
|
||||||
return &Plugin{
|
return &Plugin{
|
||||||
Name: name,
|
name, map[string]Command{},
|
||||||
Commands: map[string]Command{},
|
map[string]Command{}, extypes.Slice[Middleware]{},
|
||||||
Payloads: map[string]Command{},
|
|
||||||
Middlewares: extypes.Slice[PluginMiddleware]{},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Plugin) AddCommand(command Command) *Plugin {
|
func (p *Plugin) AddCommand(command *Command) *Plugin {
|
||||||
p.Commands[command.command] = command
|
p.Commands[command.command] = *command
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
func (p *Plugin) AddPayload(command *Command) *Plugin {
|
||||||
func (p *Plugin) AddPayload(command Command) *Plugin {
|
p.Payloads[command.command] = *command
|
||||||
p.Payloads[command.command] = command
|
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
func (p *Plugin) AddMiddleware(middleware Middleware) *Plugin {
|
||||||
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 *Plugin) Execute(cmd string, ctx *MsgContext, dbContext *DatabaseContext) {
|
func (p *Plugin) executeCmd(cmd string, ctx *MsgContext, dbContext *DatabaseContext) {
|
||||||
command := p.Commands[cmd]
|
command := p.Commands[cmd]
|
||||||
if !command.validateArgs(ctx.Args) {
|
if err := command.validateArgs(ctx.Args); err != nil {
|
||||||
|
ctx.error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
command.exec(ctx, dbContext)
|
command.exec(ctx, dbContext)
|
||||||
}
|
}
|
||||||
func (p *Plugin) ExecutePayload(payload string, ctx *MsgContext, dbContext *DatabaseContext) {
|
func (p *Plugin) executePayload(payload string, ctx *MsgContext, dbContext *DatabaseContext) {
|
||||||
pl := p.Payloads[payload]
|
pl := p.Payloads[payload]
|
||||||
if !pl.validateArgs(ctx.Args) {
|
if err := pl.validateArgs(ctx.Args); err != nil {
|
||||||
|
ctx.error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pl.exec(ctx, dbContext)
|
pl.exec(ctx, dbContext)
|
||||||
@@ -100,30 +137,19 @@ 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 {
|
type MiddlewareExecutor func(ctx *MsgContext, db *DatabaseContext) bool
|
||||||
if arg.regex == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !arg.regex.MatchString(args[i]) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Middleware
|
||||||
|
// When async, returned value ignored
|
||||||
type Middleware struct {
|
type Middleware struct {
|
||||||
name string
|
name string
|
||||||
exec CommandExecutor
|
executor MiddlewareExecutor
|
||||||
order int
|
order int
|
||||||
async bool
|
async bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMiddleware(name string, executor CommandExecutor) *Middleware {
|
func NewMiddleware(name string, executor MiddlewareExecutor) *Middleware {
|
||||||
return &Middleware{name, executor, 0, false}
|
return &Middleware{name, executor, 0, false}
|
||||||
}
|
}
|
||||||
func (m *Middleware) SetOrder(order int) *Middleware {
|
func (m *Middleware) SetOrder(order int) *Middleware {
|
||||||
@@ -134,40 +160,7 @@ func (m *Middleware) SetAsync(async bool) *Middleware {
|
|||||||
m.async = async
|
m.async = async
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
func (m *Middleware) Execute(ctx *MsgContext, db *DatabaseContext) {
|
func (m *Middleware) Execute(ctx *MsgContext, db *DatabaseContext) bool {
|
||||||
if m.async {
|
|
||||||
go m.exec(ctx, db)
|
|
||||||
} else {
|
|
||||||
m.Execute(ctx, db)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type PluginMiddlewareExecutor func(ctx *MsgContext, db *DatabaseContext) bool
|
|
||||||
|
|
||||||
// PluginMiddleware
|
|
||||||
// When async, returned value ignored
|
|
||||||
type PluginMiddleware struct {
|
|
||||||
executor PluginMiddlewareExecutor
|
|
||||||
order int
|
|
||||||
async bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPluginMiddleware(executor PluginMiddlewareExecutor) *PluginMiddleware {
|
|
||||||
return &PluginMiddleware{
|
|
||||||
executor: executor,
|
|
||||||
order: 0,
|
|
||||||
async: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (m *PluginMiddleware) SetOrder(order int) *PluginMiddleware {
|
|
||||||
m.order = order
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
func (m *PluginMiddleware) SetAsync(async bool) *PluginMiddleware {
|
|
||||||
m.async = async
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
func (m *PluginMiddleware) Execute(ctx *MsgContext, db *DatabaseContext) bool {
|
|
||||||
if m.async {
|
if m.async {
|
||||||
go m.executor(ctx, db)
|
go m.executor(ctx, db)
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -67,9 +67,6 @@ func (r TelegramRequest[R, P]) DoWithContext(ctx context.Context, api *API) (R,
|
|||||||
return zero, err
|
return zero, err
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
if res.StatusCode != http.StatusOK {
|
|
||||||
return zero, fmt.Errorf("unexpected status code: %d", res.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
reader := io.LimitReader(res.Body, 10<<20)
|
reader := io.LimitReader(res.Body, 10<<20)
|
||||||
data, err = io.ReadAll(reader)
|
data, err = io.ReadAll(reader)
|
||||||
@@ -77,6 +74,9 @@ func (r TelegramRequest[R, P]) DoWithContext(ctx context.Context, api *API) (R,
|
|||||||
return zero, err
|
return zero, err
|
||||||
}
|
}
|
||||||
api.Logger.Debugln("RES", r.method, string(data))
|
api.Logger.Debugln("RES", r.method, string(data))
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
return zero, fmt.Errorf("unexpected status code: %d", res.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
var resp ApiResponse[R]
|
var resp ApiResponse[R]
|
||||||
err = json.Unmarshal(data, &resp)
|
err = json.Unmarshal(data, &resp)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ type BotCommandScopeType string
|
|||||||
const (
|
const (
|
||||||
BotCommandScopeDefaultType BotCommandScopeType = "default"
|
BotCommandScopeDefaultType BotCommandScopeType = "default"
|
||||||
BotCommandScopePrivateType BotCommandScopeType = "all_private_chats"
|
BotCommandScopePrivateType BotCommandScopeType = "all_private_chats"
|
||||||
BotCommandScopeGroupType BotCommandScopeType = "all_groups_chats"
|
BotCommandScopeGroupType BotCommandScopeType = "all_group_chats"
|
||||||
BotCommandScopeAllChatAdministratorsType BotCommandScopeType = "all_chat_administrators"
|
BotCommandScopeAllChatAdministratorsType BotCommandScopeType = "all_chat_administrators"
|
||||||
BotCommandScopeChatType BotCommandScopeType = "chat"
|
BotCommandScopeChatType BotCommandScopeType = "chat"
|
||||||
BotCommandScopeChatAdministratorsType BotCommandScopeType = "chat_administrators"
|
BotCommandScopeChatAdministratorsType BotCommandScopeType = "chat_administrators"
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
const (
|
const (
|
||||||
VersionString = "0.5.0"
|
VersionString = "0.6.0"
|
||||||
VersionMajor = 0
|
VersionMajor = 0
|
||||||
VersionMinor = 5
|
VersionMinor = 6
|
||||||
VersionPatch = 0
|
VersionPatch = 0
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user