From 52d543f10a0e290a024b6b3d4a0e4ffcc4e9c003 Mon Sep 17 00:00:00 2001 From: AK Date: Sun, 2 Aug 2026 08:54:01 -0700 Subject: [PATCH] fix: honor ask environment overrides --- plugins/ask.go | 26 +++++++++++++++++++++++++- plugins/ask_test.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/plugins/ask.go b/plugins/ask.go index 248ff3f..67776a9 100644 --- a/plugins/ask.go +++ b/plugins/ask.go @@ -9,6 +9,7 @@ import ( "net/http" "net/url" "os" + "strconv" "strings" "sync" "time" @@ -34,12 +35,35 @@ func (p *Ask) Help() string { } func (p *Ask) Init(c bot.PluginConfig, _ *storage.DB) error { - p.cfg = c + p.cfg = withAskEnvironment(c) p.last = make(map[string]time.Time) p.lastWarning = make(map[string]time.Time) return nil } +// withAskEnvironment applies the ask-specific environment variables after +// the config file has been decoded. Viper can read bound scalar environment +// values with Get, but nested map values are not reliably reflected when the +// whole plugin map is unmarshaled into PluginConfig. Applying these two +// switches here makes the documented .env overrides authoritative without +// requiring secrets in config.yaml. +func withAskEnvironment(c bot.PluginConfig) bot.PluginConfig { + cfg := make(bot.PluginConfig, len(c)+2) + for key, value := range c { + cfg[key] = value + } + + if provider := strings.TrimSpace(os.Getenv("BOT_ASK_PROVIDER")); provider != "" { + cfg["provider"] = provider + } + if raw, ok := os.LookupEnv("BOT_ASK_AI_REWRITE"); ok { + if enabled, err := strconv.ParseBool(strings.TrimSpace(raw)); err == nil { + cfg["ai_rewrite"] = enabled + } + } + return cfg +} + func (p *Ask) Handle(b *bot.Bot, m bot.Message) bool { cmd, arg, ok := bot.IsCommand(m, b.Config.CommandPrefix) if !ok || !isAskCommand(cmd) { diff --git a/plugins/ask_test.go b/plugins/ask_test.go index 9ea59eb..8341075 100644 --- a/plugins/ask_test.go +++ b/plugins/ask_test.go @@ -46,6 +46,38 @@ func TestAskProviderNoneDoesNotCallAI(t *testing.T) { } } +func TestAskEnvironmentOverridesNestedConfig(t *testing.T) { + t.Setenv("BOT_ASK_PROVIDER", "openrouter") + t.Setenv("BOT_ASK_AI_REWRITE", "true") + + p := &Ask{} + if err := p.Init(bot.PluginConfig{ + "provider": "none", + "ai_rewrite": false, + "max_length": 360, + }, nil); err != nil { + t.Fatalf("init failed: %v", err) + } + if got := p.cfg.String("provider", "none"); got != "openrouter" { + t.Fatalf("provider = %q, want openrouter", got) + } + if !p.cfg.Bool("ai_rewrite", false) { + t.Fatal("ai_rewrite remained disabled despite BOT_ASK_AI_REWRITE=true") + } +} + +func TestAskInvalidEnvironmentBooleanLeavesConfigUnchanged(t *testing.T) { + t.Setenv("BOT_ASK_AI_REWRITE", "not-a-boolean") + + p := &Ask{} + if err := p.Init(bot.PluginConfig{"ai_rewrite": true}, nil); err != nil { + t.Fatalf("init failed: %v", err) + } + if !p.cfg.Bool("ai_rewrite", false) { + t.Fatal("invalid environment boolean unexpectedly changed config") + } +} + func TestAskSenderKeyUsesAccountWhenAvailable(t *testing.T) { key := askSenderKey(bot.Message{Nick: "Echo", Account: "UserAccount"}) if key != "account:useraccount" {