(new): logger replacer for token
Golang lint / lint (push) Successful in 1m19s

(fix): scene command routing
This commit is contained in:
2026-04-23 21:50:00 +03:00
parent d6da95394c
commit 768dc859d7
10 changed files with 110 additions and 34 deletions
+13
View File
@@ -0,0 +1,13 @@
name: Golang lint
run-name: Linting code in ${{ gitea.repository }} by ${{ gitea.actor }}
on: [push]
jobs:
lint:
runs-on: go-latest
steps:
- name: Check out repository code
uses: actions/checkout@v6
- name: Run golangci-lint
run: golangci-lint run
-19
View File
@@ -1,19 +0,0 @@
name: Gitea Actions Demo
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
on: [push]
jobs:
Explore-Gitea-Actions:
runs-on: arch-latest
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
- name: Check out repository code
uses: actions/checkout@v4
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ gitea.workspace }}
- run: echo "🍏 This job's status is ${{ job.status }}."
View File
+2
View File
@@ -7,10 +7,12 @@
- Added plugin-level message fallback handlers for text messages and channel posts that do not match commands.
- Added godoc for the exported `BotOpts` file codec and load/save helpers.
- README, README_RU, and bot-configuration wiki pages now document file-based `BotOpts` loading, built-in JSON support, env placeholder expansion, and custom codec usage including the TOML example.
- Active scenes now let unmatched slash-commands continue into normal bot command routing instead of also executing the current scene step or scene message fallback.
### Tests
- Added regression coverage for JSON `BotOpts` file codecs, file load/save helpers, decode failures, and env placeholder expansion.
- Added regression coverage for plugin message fallback routing, observer lifecycle events, command precedence, and middleware blocking.
- Added regression coverage proving unmatched slash-commands do not trigger active scene step handlers before normal bot command routing.
## v1.0.0-rc.14
+9
View File
@@ -104,12 +104,21 @@ func (codec BotOptsFileJsonCodec) ToBytes(opts *BotOpts) ([]byte, error) {
return data, nil
}
func (codec BotOptsFileJsonCodec) Load(filename string) (*BotOpts, error) {
return LoadBotOptsFile(codec, filename)
}
func (codec BotOptsFileJsonCodec) Save(filename string, opts *BotOpts) error {
return SaveBotOptsFile(codec, filename, opts)
}
var envParameterRegex = regexp.MustCompile(`\{\{\s*(\w+)\s*\}\}`)
// BotOptsFileCodec decodes and encodes BotOpts file formats.
type BotOptsFileCodec interface {
FromBytes([]byte) (*BotOpts, error)
ToBytes(*BotOpts) ([]byte, error)
Load(filename string) (*BotOpts, error)
Save(filename string, opts *BotOpts) error
}
// LoadBotOptsFile reads a config file, expands env placeholders, and decodes BotOpts.
+2 -2
View File
@@ -41,7 +41,7 @@ func (bot *Bot[T]) initLoggers(opts *BotOpts) {
level = slog.DEBUG
}
bot.logger = utils.CreateLogger("BOT", level)
bot.logger = utils.CreateLogger("BOT", level).AddReplacer(bot.token, "<TOKEN>")
if opts.WriteToFile {
path := fmt.Sprintf("%s/main.log", strings.TrimRight(opts.LoggerBasePath, "/"))
logger, err := utils.CreateFileLogger("BOT", level, path)
@@ -53,7 +53,7 @@ func (bot *Bot[T]) initLoggers(opts *BotOpts) {
}
if opts.UseRequestLogger {
bot.RequestLogger = utils.CreateLogger("REQUESTS", level)
bot.RequestLogger = utils.CreateLogger("REQUESTS", level).AddReplacer(bot.token, "<TOKEN>")
if opts.WriteToFile {
path := fmt.Sprintf("%s/requests.log", strings.TrimRight(opts.LoggerBasePath, "/"))
logger, err := utils.CreateFileLogger("REQUESTS", level, path)
+4 -4
View File
@@ -6,14 +6,14 @@ retract v1.0.0-rc.5
require (
git.scuroneko.dev/scuroneko/extypes v1.2.3
git.scuroneko.dev/scuroneko/slog v1.1.3
git.scuroneko.dev/scuroneko/slog v1.2.0
github.com/alitto/pond/v2 v2.7.0
golang.org/x/time v0.15.0
)
require (
github.com/fatih/color v1.18.0 // indirect
github.com/fatih/color v1.19.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
golang.org/x/sys v0.42.0 // indirect
github.com/mattn/go-isatty v0.0.21 // indirect
golang.org/x/sys v0.43.0 // indirect
)
+8 -9
View File
@@ -1,17 +1,16 @@
git.scuroneko.dev/scuroneko/extypes v1.2.3 h1:n7QsfTZEn9fJNZLXGH/LkNq4cADaRk+LTu6LNMv9y6s=
git.scuroneko.dev/scuroneko/extypes v1.2.3/go.mod h1:MhYpXC6sloLOpoM2guf64eSOrz+ET/QJZ8toobc3Ors=
git.scuroneko.dev/scuroneko/slog v1.1.3 h1:vI4GZykn8gDb6OJ2xq+KLcEk38M7O4e/z1kzpeRHEHw=
git.scuroneko.dev/scuroneko/slog v1.1.3/go.mod h1:gnDap54sfZv3EuSyZd7fjOH46aLbDFpvtN2wgFcWkgE=
git.scuroneko.dev/scuroneko/slog v1.2.0 h1:xbwzrMcmN0NG/zTgEn508mn2JVnfZN5z/Zsi3PREfDM=
git.scuroneko.dev/scuroneko/slog v1.2.0/go.mod h1:r+oz9NzvvdtWd9/PjeS+n5vQoNHL38BdcdLoBtJPvFU=
github.com/alitto/pond/v2 v2.7.0 h1:c76L+yN916m/DRXjGCeUBHHu92uWnh/g1bwVk4zyyXg=
github.com/alitto/pond/v2 v2.7.0/go.mod h1:xkjYEgQ05RSpWdfSd1nM3OVv7TBhLdy7rMp3+2Nq+yE=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
+4
View File
@@ -81,6 +81,10 @@ func (bot *Bot[T]) executeScene(ctx *SceneContext, scene *Scene[T]) (bool, error
}
return ok, err
}
// Unmatched slash-commands should continue through normal bot command routing
// instead of also triggering the active scene step or fallback handler.
return false, nil
}
ctx.Text = text
ctx.Args = nil
+68
View File
@@ -509,6 +509,74 @@ func TestScenePassDoesNotPersistSessionData(t *testing.T) {
}
}
func TestSceneUnmatchedCommandFallsThroughWithoutRunningStep(t *testing.T) {
commandCalled := false
stepCalled := false
plugin := NewPlugin[NoData]("wizard")
plugin.NewScene("signup").
SetEntry("start").
OnStep("start", func(ctx *SceneContext, db NoData) (SceneResult, error) {
stepCalled = true
return ctx.Stay(), nil
})
plugin.NewCommand(func(ctx *MsgContext, db NoData) error {
commandCalled = true
return nil
}, "ping")
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
prefixes: []string{"/"},
sessionStore: NewMemorySessionStore(),
sceneScopePriority: []SceneScope{SceneScopeUserChat, SceneScopeChat, SceneScopeUser},
}
bot.AddPlugins(plugin)
enterCtx := &MsgContext{
Msg: &tgapi.Message{Chat: &tgapi.Chat{ID: 100, Type: tgapi.ChatTypePrivate}},
FromID: 42,
sceneRuntime: bot,
}
if err := enterCtx.EnterScene("signup"); err != nil {
t.Fatalf("EnterScene returned error: %v", err)
}
key, ok := buildSceneKey(SceneScopeUserChat, &MsgContext{
Msg: &tgapi.Message{Chat: &tgapi.Chat{ID: 100, Type: tgapi.ChatTypePrivate}},
FromID: 42,
})
if !ok {
t.Fatal("expected scene key to be built")
}
bot.handle(context.Background(), &tgapi.Update{
UpdateID: 5,
Type: tgapi.UpdateTypeMessage,
Message: &tgapi.Message{
MessageID: 10,
Text: "/ping",
Chat: &tgapi.Chat{ID: 100, Type: tgapi.ChatTypePrivate},
From: &tgapi.User{ID: 42},
},
})
if !commandCalled {
t.Fatal("expected normal command routing to handle /ping")
}
if stepCalled {
t.Fatal("scene step must not run for an unmatched slash-command")
}
after, err := bot.sessionStore.Get(key)
if err != nil {
t.Fatalf("Get after handle returned error: %v", err)
}
if after.Scene != "signup" || after.Step != "start" {
t.Fatalf("unexpected session after command fallback: %#v", after)
}
}
func TestSceneMessageFallbackRunsWhenNoCommandOrStepMatch(t *testing.T) {
fallbackCalled := false