Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac5587d0c8 | ||
|
|
2524e40566 | ||
|
|
5167d799bc | ||
|
|
cb2df5076d | ||
|
|
2d5cf39151 |
@@ -6,6 +6,10 @@ bot_node_config.json
|
||||
*.exe
|
||||
nukumizu-linux-amd64
|
||||
|
||||
# Scratch test files dropped in the repo root stay untracked; the real suite
|
||||
# lives next to the code it covers (config/, handler/, ...) and is tracked.
|
||||
/*test.go
|
||||
|
||||
# The built web console. web/embed.go compiles it into the binary, and the
|
||||
# build-*.sh / build-*.bat scripts rebuild it before every compile.
|
||||
/web/dist/
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
## Ver.0.2.0.5
|
||||
## Ver.0.2.0.5-661cf08.pre-release
|
||||
### Features
|
||||
- [Enhance security of websocket log system]("https://gitea.nanami.tech/NanamiAdmin/Nukumizu/commit/da467c9297e641e2ab9a1889252589503791b7e9")
|
||||
- [Implement incoming webhook API interface with configurable endpoints]("https://gitea.nanami.tech/NanamiAdmin/Nukumizu/commit/48533404fac20c23134e3ec1d3305ebdf4423c80")
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"nukumizu-backend/global"
|
||||
)
|
||||
|
||||
// TestConcurrentReloadAndRead drives every configuration accessor from reader
|
||||
// goroutines while UpdateSettings and SaveBotNodeConfig replace the in-memory
|
||||
// configurations underneath them. Run with -race to check that the swap is
|
||||
// race-free: before the singletons were published through atomic pointers this
|
||||
// pattern was an unsynchronized read of a variable written by LoadGlobalConfig
|
||||
// and friends, which the race detector reports.
|
||||
//
|
||||
// The readers call the accessors the way production code does — take the value
|
||||
// and use it immediately, never store it — because that is what keeps a reader
|
||||
// pinned to one complete version of the configuration.
|
||||
func TestConcurrentReloadAndRead(t *testing.T) {
|
||||
writeTempConfig(t, &global.ConfigPath.Global, `{
|
||||
"system": {
|
||||
"debugMode": true,
|
||||
"listenPort": "8080"
|
||||
},
|
||||
"webhook": {
|
||||
"endpoints": {
|
||||
"example": { "enabled": true, "token": "t", "notifyPipes": ["ntfy"] }
|
||||
}
|
||||
},
|
||||
"controllerMessage": {
|
||||
"BOT_STARTED": "hello"
|
||||
}
|
||||
}`)
|
||||
writeTempConfig(t, &global.ConfigPath.BotUserConfig, `{
|
||||
"qq(napcat)": {
|
||||
"admins": { "1": { "event_reply": true } },
|
||||
"trustedGroups": { "2": { "event_status_notify": true } }
|
||||
}
|
||||
}`)
|
||||
writeTempConfig(t, &global.ConfigPath.BotNodeConfig, `{
|
||||
"node-1": { "enableStatusNotify": true }
|
||||
}`)
|
||||
|
||||
// Seed every singleton so the readers start from a loaded configuration
|
||||
// rather than racing the first store.
|
||||
if _, err := LoadGlobalConfig(global.ConfigPath.Global); err != nil {
|
||||
t.Fatalf("LoadGlobalConfig: %v", err)
|
||||
}
|
||||
if _, err := LoadBotUserConfig(global.ConfigPath.BotUserConfig); err != nil {
|
||||
t.Fatalf("LoadBotUserConfig: %v", err)
|
||||
}
|
||||
if err := LoadBotNodeConfig(global.ConfigPath.BotNodeConfig); err != nil {
|
||||
t.Fatalf("LoadBotNodeConfig: %v", err)
|
||||
}
|
||||
|
||||
const readers = 4
|
||||
const rounds = 40
|
||||
|
||||
var readersWg, writersWg sync.WaitGroup
|
||||
stop := make(chan struct{})
|
||||
|
||||
for i := 0; i < readers; i++ {
|
||||
readersWg.Add(1)
|
||||
go func() {
|
||||
defer readersWg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-stop:
|
||||
return
|
||||
default:
|
||||
}
|
||||
if cfg := Current(); cfg != nil {
|
||||
_ = cfg.System.DebugMode
|
||||
_ = cfg.System.ListenPort
|
||||
_ = cfg.ControllerMessage.BotStarted
|
||||
_ = cfg.Webhook.Endpoints
|
||||
}
|
||||
if users := BotUsers(); users != nil {
|
||||
_ = users.QQ.Admins.IDs()
|
||||
_ = users.QQ.TrustedGroups.IDs()
|
||||
}
|
||||
_ = BotNodes()
|
||||
_ = IsDebugMode()
|
||||
_ = NodeStatusNotifyEnabled("node-1")
|
||||
_ = WebhookEndpoints()
|
||||
_, _ = GetWebhookEndpoint("example")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Writer: reloads the global and bot user configurations, and rewrites the
|
||||
// node registry through UpdateSettings so its reload runs too.
|
||||
writersWg.Add(1)
|
||||
go func() {
|
||||
defer writersWg.Done()
|
||||
for i := 0; i < rounds; i++ {
|
||||
enabled := i%2 == 0
|
||||
patch := map[string]interface{}{
|
||||
"system": map[string]interface{}{"debugMode": enabled},
|
||||
"controllerMessage": map[string]interface{}{
|
||||
"BOT_STARTED": "hello",
|
||||
},
|
||||
}
|
||||
if err := UpdateSettings(SettingGlobal, patch); err != nil {
|
||||
t.Errorf("UpdateSettings(global): %v", err)
|
||||
return
|
||||
}
|
||||
if err := UpdateSettings(SettingBotUserConfig, map[string]interface{}{
|
||||
"qq(napcat)": map[string]interface{}{
|
||||
"admins": map[string]interface{}{
|
||||
"1": map[string]interface{}{"event_reply": enabled},
|
||||
},
|
||||
},
|
||||
}); err != nil {
|
||||
t.Errorf("UpdateSettings(bot_user_config): %v", err)
|
||||
return
|
||||
}
|
||||
if err := UpdateSettings(SettingBotNodeConfig, map[string]interface{}{
|
||||
"node-2": map[string]interface{}{"enableStatusNotify": enabled},
|
||||
}); err != nil {
|
||||
t.Errorf("UpdateSettings(bot_node_config): %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Second writer: the node tracker's background save, which shares the same
|
||||
// read-modify-write lock as the admin edits above.
|
||||
writersWg.Add(1)
|
||||
go func() {
|
||||
defer writersWg.Done()
|
||||
for i := 0; i < rounds; i++ {
|
||||
if err := SaveBotNodeConfig(global.ConfigPath.BotNodeConfig, []string{"node-1", "node-2"}); err != nil {
|
||||
t.Errorf("SaveBotNodeConfig: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Let the writers finish, then release the readers. Waiting on the readers
|
||||
// first would deadlock: they only return once stop is closed.
|
||||
writersWg.Wait()
|
||||
close(stop)
|
||||
readersWg.Wait()
|
||||
|
||||
// The last write must be visible: the accessors are not allowed to serve a
|
||||
// stale configuration once UpdateSettings has returned.
|
||||
if err := UpdateSettings(SettingGlobal, map[string]interface{}{
|
||||
"system": map[string]interface{}{"debugMode": true},
|
||||
}); err != nil {
|
||||
t.Fatalf("final UpdateSettings(global): %v", err)
|
||||
}
|
||||
cfg := Current()
|
||||
if cfg == nil {
|
||||
t.Fatal("Current() is nil after a successful reload")
|
||||
}
|
||||
if !cfg.System.DebugMode {
|
||||
t.Error("Current() did not observe the reloaded debugMode")
|
||||
}
|
||||
if cfg.System.ListenPort != "8080" {
|
||||
t.Errorf("reload dropped an untouched sibling: listenPort = %q", cfg.System.ListenPort)
|
||||
}
|
||||
}
|
||||
+10
-11
@@ -17,7 +17,7 @@ func LoadBotNodeConfig(configPath string) error {
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
C_botNodeConfig = cfg
|
||||
botNodeConfig.Store(&cfg)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("failed to read bot node config file: %w", err)
|
||||
@@ -27,19 +27,17 @@ func LoadBotNodeConfig(configPath string) error {
|
||||
return fmt.Errorf("failed to parse bot node config file: %w", err)
|
||||
}
|
||||
}
|
||||
C_botNodeConfig = cfg
|
||||
botNodeConfig.Store(&cfg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// NodeStatusNotifyEnabled reports whether the node identified by uuid should
|
||||
// broadcast status-change notifications, per bot_node_config.json.
|
||||
// enableStatusNotify defaults to true: a node notifies unless its entry
|
||||
// explicitly sets the flag to false.
|
||||
// explicitly sets the flag to false. A missing configuration (nil map) yields
|
||||
// the same default.
|
||||
func NodeStatusNotifyEnabled(uuid string) bool {
|
||||
if C_botNodeConfig == nil {
|
||||
return true
|
||||
}
|
||||
opts, ok := C_botNodeConfig[uuid]
|
||||
opts, ok := BotNodes()[uuid]
|
||||
if !ok || opts.EnableStatusNotify == nil {
|
||||
return true
|
||||
}
|
||||
@@ -146,7 +144,7 @@ func LoadGlobalConfig(configPath string) (*Config, error) {
|
||||
cfg.ControllerMessage.ServerExecuteResult = "Command execute result:\nServer Name: {{ serverName }}\nCommand: {{ command }}\n***Result***\n\n{{ result }}\n\n************\nTime: {{ time }}"
|
||||
}
|
||||
|
||||
C_globalConfig = &cfg
|
||||
globalConfig.Store(&cfg)
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
@@ -162,7 +160,7 @@ func LoadBotUserConfig(configPath string) (*BotUserConfig, error) {
|
||||
if err := json.Unmarshal(data, &cfg); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse bot user config file: %w", err)
|
||||
}
|
||||
C_botUserConfig = &cfg
|
||||
botUserConfig.Store(&cfg)
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
@@ -213,8 +211,9 @@ func SaveBotNodeConfig(configPath string, uuids []string) error {
|
||||
|
||||
// IsDebugMode returns whether debug mode is enabled.
|
||||
func IsDebugMode() bool {
|
||||
if C_globalConfig == nil {
|
||||
cfg := Current()
|
||||
if cfg == nil {
|
||||
return false
|
||||
}
|
||||
return C_globalConfig.System.DebugMode
|
||||
return cfg.System.DebugMode
|
||||
}
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"nukumizu-backend/global"
|
||||
)
|
||||
|
||||
// writeTempConfig writes content to a fresh temp file and points the matching
|
||||
// global.ConfigPath field at it, returning a cleanup that restores the original.
|
||||
func writeTempConfig(t *testing.T, field *string, content string) {
|
||||
t.Helper()
|
||||
path := filepath.Join(t.TempDir(), "config.json")
|
||||
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
||||
t.Fatalf("write temp config: %v", err)
|
||||
}
|
||||
original := *field
|
||||
*field = path
|
||||
t.Cleanup(func() { *field = original })
|
||||
}
|
||||
|
||||
func TestUpdateSettingsDeepMerge(t *testing.T) {
|
||||
writeTempConfig(t, &global.ConfigPath.BotUserConfig, `{
|
||||
"qq(napcat)": {
|
||||
"admins": {
|
||||
"100000001": {
|
||||
"event_status_notify": true,
|
||||
"event_bot_started": true
|
||||
}
|
||||
},
|
||||
"trustedGroups": {
|
||||
"200000002": {
|
||||
"event_status_notify": true,
|
||||
"event_bot_started": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}`)
|
||||
|
||||
patch := map[string]interface{}{
|
||||
"qq(napcat)": map[string]interface{}{
|
||||
"admins": map[string]interface{}{
|
||||
"100000001": map[string]interface{}{
|
||||
"event_status_notify": false, // toggle an existing nested flag
|
||||
"event_reply": true, // add a key that is not in the file
|
||||
},
|
||||
},
|
||||
"trustedGroups": map[string]interface{}{
|
||||
"12345": map[string]interface{}{ // add a whole new member
|
||||
"event_bot_started": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := UpdateSettings(SettingBotUserConfig, patch); err != nil {
|
||||
t.Fatalf("UpdateSettings: %v", err)
|
||||
}
|
||||
|
||||
data, err := GetSettings(SettingBotUserConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("GetSettings: %v", err)
|
||||
}
|
||||
got := string(data)
|
||||
|
||||
for _, want := range []string{
|
||||
`"event_status_notify": false`,
|
||||
`"event_reply": true`,
|
||||
`"event_bot_started": true`,
|
||||
`"event_status_notify": true`, // sibling under trustedGroups 200000002 kept
|
||||
`"12345"`,
|
||||
} {
|
||||
if !strings.Contains(got, want) {
|
||||
t.Errorf("merged config missing %q:\n%s", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
// The in-memory singleton must reflect the merged file too.
|
||||
users := BotUsers()
|
||||
if users == nil {
|
||||
t.Fatal("bot user config not reloaded")
|
||||
}
|
||||
if users.QQ.Admins["100000001"].EventStatusNotify {
|
||||
t.Error("expected reloaded admin event_status_notify = false")
|
||||
}
|
||||
if !users.QQ.Admins["100000001"].EventReply {
|
||||
t.Error("expected reloaded admin event_reply = true")
|
||||
}
|
||||
if !users.QQ.TrustedGroups["12345"].EventBotStarted {
|
||||
t.Error("expected new trusted group event_bot_started = true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateSettingsReplacesArraysAndKeepsNumbers(t *testing.T) {
|
||||
writeTempConfig(t, &global.ConfigPath.Global, `{
|
||||
"system": {
|
||||
"debugMode": true,
|
||||
"listenPort": "8080"
|
||||
},
|
||||
"controllerMethod": {
|
||||
"email": {
|
||||
"enabled": false,
|
||||
"smtpHost": "smtp.example.com",
|
||||
"smtpPort": 587,
|
||||
"to": ["old@example.com"]
|
||||
}
|
||||
}
|
||||
}`)
|
||||
|
||||
patch := map[string]interface{}{
|
||||
"system": map[string]interface{}{
|
||||
"debugMode": false, // partial: listenPort must survive
|
||||
},
|
||||
"controllerMethod": map[string]interface{}{
|
||||
"email": map[string]interface{}{
|
||||
"enabled": true,
|
||||
"smtpPort": json.Number("465"),
|
||||
"to": []interface{}{"new@example.com"}, // arrays replace, not merge
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := UpdateSettings(SettingGlobal, patch); err != nil {
|
||||
t.Fatalf("UpdateSettings: %v", err)
|
||||
}
|
||||
|
||||
data, err := GetSettings(SettingGlobal)
|
||||
if err != nil {
|
||||
t.Fatalf("GetSettings: %v", err)
|
||||
}
|
||||
got := string(data)
|
||||
|
||||
for _, want := range []string{
|
||||
`"debugMode": false`,
|
||||
`"listenPort": "8080"`, // sibling untouched
|
||||
`"smtpHost": "smtp.example.com"`, // sibling untouched
|
||||
`"smtpPort": 465`, // number kept verbatim, not 465.0
|
||||
`"new@example.com"`,
|
||||
} {
|
||||
if !strings.Contains(got, want) {
|
||||
t.Errorf("merged config missing %q:\n%s", want, got)
|
||||
}
|
||||
}
|
||||
if strings.Contains(got, "old@example.com") {
|
||||
t.Errorf("array was merged instead of replaced:\n%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSettingsTypeValidation(t *testing.T) {
|
||||
for _, valid := range []string{SettingGlobal, SettingBotUserConfig, SettingBotNodeConfig} {
|
||||
if !IsValidSettingsType(valid) {
|
||||
t.Errorf("expected %q to be a valid settings type", valid)
|
||||
}
|
||||
}
|
||||
for _, invalid := range []string{"", "system", "node", "bot"} {
|
||||
if IsValidSettingsType(invalid) {
|
||||
t.Errorf("expected %q to be an invalid settings type", invalid)
|
||||
}
|
||||
if _, err := GetSettings(invalid); err != ErrUnsupportedSettingsType {
|
||||
t.Errorf("GetSettings(%q) error = %v, want ErrUnsupportedSettingsType", invalid, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSettingsBotNodeMissingFile(t *testing.T) {
|
||||
// Point at a temp path that does not exist yet.
|
||||
writeTempConfig(t, &global.ConfigPath.BotNodeConfig, "")
|
||||
os.Remove(global.ConfigPath.BotNodeConfig)
|
||||
|
||||
data, err := GetSettings(SettingBotNodeConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("GetSettings on missing bot_node_config: %v", err)
|
||||
}
|
||||
if string(data) != "{}" {
|
||||
t.Errorf("expected empty object for missing bot_node_config, got %s", data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateSettingsRemovesKeysWithNull(t *testing.T) {
|
||||
writeTempConfig(t, &global.ConfigPath.BotUserConfig, `{
|
||||
"qq(napcat)": {
|
||||
"admins": {
|
||||
"100000001": { "event_status_notify": true },
|
||||
"200000002": { "event_status_notify": false }
|
||||
},
|
||||
"trustedGroups": {
|
||||
"999": { "event_bot_started": true }
|
||||
}
|
||||
},
|
||||
"telegram": {
|
||||
"admins": {}
|
||||
}
|
||||
}`)
|
||||
|
||||
// null removes a nested member and keeps its siblings; an empty section stays.
|
||||
patch := map[string]interface{}{
|
||||
"qq(napcat)": map[string]interface{}{
|
||||
"admins": map[string]interface{}{
|
||||
"100000001": nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := UpdateSettings(SettingBotUserConfig, patch); err != nil {
|
||||
t.Fatalf("UpdateSettings: %v", err)
|
||||
}
|
||||
|
||||
data, err := GetSettings(SettingBotUserConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("GetSettings: %v", err)
|
||||
}
|
||||
got := string(data)
|
||||
if strings.Contains(got, "100000001") {
|
||||
t.Errorf("deleted member still present:\n%s", got)
|
||||
}
|
||||
for _, want := range []string{"200000002", `"trustedGroups"`, `"telegram"`} {
|
||||
if !strings.Contains(got, want) {
|
||||
t.Errorf("unrelated content missing %q:\n%s", want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
+45
-8
@@ -1,6 +1,9 @@
|
||||
package config
|
||||
|
||||
import "sort"
|
||||
import (
|
||||
"sort"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// SystemConfig holds system-level configuration.
|
||||
type SystemConfig struct {
|
||||
@@ -136,10 +139,11 @@ type WebhookReceiverConfig struct {
|
||||
// GetWebhookEndpoint returns the incoming webhook endpoint registered under the
|
||||
// given name, and whether such an endpoint exists.
|
||||
func GetWebhookEndpoint(name string) (WebhookEndpointConfig, bool) {
|
||||
if C_globalConfig == nil {
|
||||
cfg := Current()
|
||||
if cfg == nil {
|
||||
return WebhookEndpointConfig{}, false
|
||||
}
|
||||
endpoint, ok := C_globalConfig.Webhook.Endpoints[name]
|
||||
endpoint, ok := cfg.Webhook.Endpoints[name]
|
||||
return endpoint, ok
|
||||
}
|
||||
|
||||
@@ -165,7 +169,19 @@ type Config struct {
|
||||
DBPath string `json:"dbPath"`
|
||||
}
|
||||
|
||||
var C_globalConfig *Config
|
||||
// globalConfig holds the configuration currently in effect. It is replaced as a
|
||||
// whole by LoadGlobalConfig — and therefore by every settings update — and never
|
||||
// mutated in place, so a reader that loads the pointer always observes a fully
|
||||
// initialized Config. Read it through Current rather than caching the result: a
|
||||
// cached pointer stops tracking reloads.
|
||||
var globalConfig atomic.Pointer[Config]
|
||||
|
||||
// Current returns the configuration currently in effect, or nil before the
|
||||
// first successful LoadGlobalConfig. It is safe to call from any goroutine, and
|
||||
// must be called on every use rather than stored, so the caller sees reloads.
|
||||
func Current() *Config {
|
||||
return globalConfig.Load()
|
||||
}
|
||||
|
||||
// BotUserOptions holds per-member options stored in bot_user_config.json.
|
||||
type BotUserOptions struct {
|
||||
@@ -220,7 +236,16 @@ type BotUserConfig struct {
|
||||
Telegram BotUser_TelegramConfig `json:"telegram"`
|
||||
}
|
||||
|
||||
var C_botUserConfig *BotUserConfig
|
||||
// botUserConfig mirrors bot_user_config.json the same way globalConfig mirrors
|
||||
// config.json: replaced wholesale on reload and read through BotUsers.
|
||||
var botUserConfig atomic.Pointer[BotUserConfig]
|
||||
|
||||
// BotUsers returns the bot user configuration currently in effect, or nil
|
||||
// before the first successful LoadBotUserConfig. Like Current it must be called
|
||||
// on every use rather than stored.
|
||||
func BotUsers() *BotUserConfig {
|
||||
return botUserConfig.Load()
|
||||
}
|
||||
|
||||
// BotNodeOptions holds per-node options stored in bot_node_config.json. The
|
||||
// file is auto-populated by the node tracker for every node Komari reports;
|
||||
@@ -238,6 +263,18 @@ type BotNodeOptions struct {
|
||||
// options.
|
||||
type BotNodeMembers map[string]BotNodeOptions
|
||||
|
||||
// C_botNodeConfig is the global singleton mirroring bot_node_config.json,
|
||||
// populated by LoadBotNodeConfig.
|
||||
var C_botNodeConfig BotNodeMembers
|
||||
// botNodeConfig mirrors bot_node_config.json, populated by LoadBotNodeConfig.
|
||||
// The map is rebuilt rather than mutated on every load, so the pointer can be
|
||||
// swapped atomically; read it through BotNodes.
|
||||
var botNodeConfig atomic.Pointer[BotNodeMembers]
|
||||
|
||||
// BotNodes returns the per-node options currently in effect, or nil before the
|
||||
// first LoadBotNodeConfig. Like Current it must be called on every use rather
|
||||
// than stored.
|
||||
func BotNodes() BotNodeMembers {
|
||||
nodes := botNodeConfig.Load()
|
||||
if nodes == nil {
|
||||
return nil
|
||||
}
|
||||
return *nodes
|
||||
}
|
||||
|
||||
+9
-7
@@ -37,10 +37,11 @@ var webhookEndpointFields = map[string]func(interface{}) bool{
|
||||
// configuration.
|
||||
func WebhookEndpoints() map[string]WebhookEndpointConfig {
|
||||
endpoints := map[string]WebhookEndpointConfig{}
|
||||
if C_globalConfig == nil {
|
||||
cfg := Current()
|
||||
if cfg == nil {
|
||||
return endpoints
|
||||
}
|
||||
for name, endpoint := range C_globalConfig.Webhook.Endpoints {
|
||||
for name, endpoint := range cfg.Webhook.Endpoints {
|
||||
endpoints[name] = endpoint
|
||||
}
|
||||
return endpoints
|
||||
@@ -107,14 +108,15 @@ func DeleteWebhookEndpoint(name string) error {
|
||||
}
|
||||
|
||||
// webhookEndpoint returns the named endpoint held by the loaded configuration.
|
||||
// No lock is needed to read it: a reload replaces the whole configuration
|
||||
// rather than mutating it in place, and the value is read from whichever
|
||||
// version is current.
|
||||
// No extra lock is needed to read it: a reload replaces the whole configuration
|
||||
// rather than mutating it in place, and Current publishes the replacement
|
||||
// atomically, so the value read is always from one complete version.
|
||||
func webhookEndpoint(name string) (WebhookEndpointConfig, bool) {
|
||||
if C_globalConfig == nil {
|
||||
cfg := Current()
|
||||
if cfg == nil {
|
||||
return WebhookEndpointConfig{}, false
|
||||
}
|
||||
endpoint, exists := C_globalConfig.Webhook.Endpoints[name]
|
||||
endpoint, exists := cfg.Webhook.Endpoints[name]
|
||||
return endpoint, exists
|
||||
}
|
||||
|
||||
|
||||
+16
-2
@@ -51,13 +51,27 @@ func setupAdminToken() {
|
||||
utils.AddToken("test-admin-token", 1, "admin", "tester")
|
||||
}
|
||||
|
||||
// decodeResponse decodes a success envelope and returns its data object, which
|
||||
// the getInfo and getStatus handlers key by uuid. Responses are wrapped by
|
||||
// utils.SendSuccessResponse as {success, message?, data:{...}}, the shape the
|
||||
// console reads as `response.data[uuid]` (see frontend/src/api/index.js), so
|
||||
// the tests index the returned map by uuid rather than by envelope key.
|
||||
func decodeResponse(t *testing.T, w *httptest.ResponseRecorder) map[string]json.RawMessage {
|
||||
t.Helper()
|
||||
var body map[string]json.RawMessage
|
||||
var body struct {
|
||||
Success bool `json:"success"`
|
||||
Data map[string]json.RawMessage `json:"data"`
|
||||
}
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil {
|
||||
t.Fatalf("decode response: %v; body=%s", err, w.Body.String())
|
||||
}
|
||||
return body
|
||||
if !body.Success {
|
||||
t.Fatalf("response is not a success envelope: %s", w.Body.String())
|
||||
}
|
||||
if body.Data == nil {
|
||||
t.Fatalf("response has no data object: %s", w.Body.String())
|
||||
}
|
||||
return body.Data
|
||||
}
|
||||
|
||||
func TestServerGetInfoAll(t *testing.T) {
|
||||
|
||||
@@ -144,7 +144,7 @@ func (m *Manager) ShowBotInitMessage() {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
cfg := config.C_globalConfig
|
||||
cfg := config.Current()
|
||||
params := template.BuildBotInitializationMsgParams()
|
||||
|
||||
for _, ctrl := range m.controllers {
|
||||
@@ -176,7 +176,7 @@ func (m *Manager) ShowBotServerList() {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
cfg := config.C_globalConfig
|
||||
cfg := config.Current()
|
||||
params := template.BuildParamsFromServerList()
|
||||
|
||||
for _, ctrl := range m.controllers {
|
||||
|
||||
@@ -71,7 +71,7 @@ func (e *EmailController) SendStatusChange(change node.StatusChange) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
cfg := config.C_globalConfig
|
||||
cfg := config.Current()
|
||||
params := template.BuildParamsFromStatusChange(change)
|
||||
body := template.Render(cfg.ControllerMessage.ServerStatusChanged, params, e.cfg.Markdown)
|
||||
|
||||
@@ -85,7 +85,7 @@ func (e *EmailController) SendServerList(onlineServers, offlineServers string) e
|
||||
return nil
|
||||
}
|
||||
|
||||
cfg := config.C_globalConfig
|
||||
cfg := config.Current()
|
||||
params := template.BuildParamsFromServerList()
|
||||
body := template.Render(cfg.ControllerMessage.ServerList, params, e.cfg.Markdown)
|
||||
|
||||
@@ -98,7 +98,7 @@ func (e *EmailController) SendExecuteResult(serverName, serverUUID, command, res
|
||||
return nil
|
||||
}
|
||||
|
||||
cfg := config.C_globalConfig
|
||||
cfg := config.Current()
|
||||
params := template.BuildParamsFromExecResult(serverName, serverUUID, command, result)
|
||||
body := template.Render(cfg.ControllerMessage.ServerExecuteResult, params, e.cfg.Markdown)
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ func (n *NtfyController) SendStatusChange(change node.StatusChange) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
cfg := config.C_globalConfig
|
||||
cfg := config.Current()
|
||||
params := template.BuildParamsFromStatusChange(change)
|
||||
message := template.Render(cfg.ControllerMessage.ServerStatusChanged, params, n.cfg.Markdown)
|
||||
|
||||
@@ -79,7 +79,7 @@ func (n *NtfyController) SendServerList(onlineServers, offlineServers string) er
|
||||
return nil
|
||||
}
|
||||
|
||||
cfg := config.C_globalConfig
|
||||
cfg := config.Current()
|
||||
params := template.BuildParamsFromServerList()
|
||||
message := template.Render(cfg.ControllerMessage.ServerList, params, n.cfg.Markdown)
|
||||
|
||||
@@ -92,7 +92,7 @@ func (n *NtfyController) SendExecuteResult(serverName, serverUUID, command, resu
|
||||
return nil
|
||||
}
|
||||
|
||||
cfg := config.C_globalConfig
|
||||
cfg := config.Current()
|
||||
params := template.BuildParamsFromExecResult(serverName, serverUUID, command, result)
|
||||
message := template.Render(cfg.ControllerMessage.ServerExecuteResult, params, n.cfg.Markdown)
|
||||
|
||||
|
||||
@@ -17,6 +17,15 @@ import (
|
||||
"nukumizu-backend/postLog"
|
||||
)
|
||||
|
||||
// actionLogEnabled reports whether the NapCat HTTP API methods echo each action
|
||||
// they send, per the showNapcatAction toggle. Unlike the WebSocket message path
|
||||
// in qq.go it deliberately does not also require debugMode: these methods have
|
||||
// always logged on this toggle alone, and preserving that is intentional.
|
||||
func actionLogEnabled() bool {
|
||||
cfg := config.Current()
|
||||
return cfg != nil && cfg.Debug.ShowNapcatAction
|
||||
}
|
||||
|
||||
// APIResponse mirrors NapCat's HTTP API response envelope.
|
||||
type APIResponse struct {
|
||||
Status string `json:"status"`
|
||||
@@ -155,7 +164,7 @@ func (c *Client) SendMsg(targetType string, targetID int64, msg string, hasAt bo
|
||||
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
||||
}
|
||||
|
||||
if config.C_globalConfig.Debug.ShowNapcatAction {
|
||||
if actionLogEnabled() {
|
||||
postLog.Debug(fmt.Sprintf("[Napcat] SendMsg -> %s (%s): %s", endpoint, targetType, message))
|
||||
}
|
||||
|
||||
@@ -176,7 +185,7 @@ func (c *Client) RecallMsg(msgID int64) (*APIResponse, error) {
|
||||
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
||||
}
|
||||
|
||||
if config.C_globalConfig.Debug.ShowNapcatAction {
|
||||
if actionLogEnabled() {
|
||||
postLog.Debug(fmt.Sprintf("[Napcat] RecallMsg -> /delete_msg: %d", msgID))
|
||||
}
|
||||
|
||||
@@ -190,7 +199,7 @@ func (c *Client) RecallMsg(msgID int64) (*APIResponse, error) {
|
||||
|
||||
// GetGroupList retrieves the list of joined groups from NapCat.
|
||||
func (c *Client) GetGroupList() (*APIResponse, error) {
|
||||
if config.C_globalConfig.Debug.ShowNapcatAction {
|
||||
if actionLogEnabled() {
|
||||
postLog.Debug("[Napcat] GetGroupList -> /get_group_list")
|
||||
}
|
||||
|
||||
@@ -211,7 +220,7 @@ func (c *Client) GetGroupInfo(groupID int64) (*APIResponse, error) {
|
||||
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
||||
}
|
||||
|
||||
if config.C_globalConfig.Debug.ShowNapcatAction {
|
||||
if actionLogEnabled() {
|
||||
postLog.Debug(fmt.Sprintf("[Napcat] GetGroupInfo -> /get_group_info: %d", groupID))
|
||||
}
|
||||
|
||||
@@ -225,7 +234,7 @@ func (c *Client) GetGroupInfo(groupID int64) (*APIResponse, error) {
|
||||
|
||||
// GetFriendsList retrieves the friends list from NapCat.
|
||||
func (c *Client) GetFriendsList() (*APIResponse, error) {
|
||||
if config.C_globalConfig.Debug.ShowNapcatAction {
|
||||
if actionLogEnabled() {
|
||||
postLog.Debug("[Napcat] GetFriendsList -> /get_friend_list")
|
||||
}
|
||||
|
||||
|
||||
@@ -100,24 +100,34 @@ func (q *QQController) handleNapcatEvent(raw []byte) {
|
||||
return
|
||||
}
|
||||
|
||||
if config.C_globalConfig.System.DebugMode && config.C_globalConfig.Debug.ShowNapcatMsg {
|
||||
// The configuration is read once per event: the debug guards below are
|
||||
// evaluated several times, and taking them all from one version keeps a
|
||||
// concurrent reload from splitting them mid-event.
|
||||
cfg := config.Current()
|
||||
if cfg == nil {
|
||||
return
|
||||
}
|
||||
debugMode := cfg.System.DebugMode
|
||||
showAction := debugMode && cfg.Debug.ShowNapcatAction
|
||||
|
||||
if debugMode && cfg.Debug.ShowNapcatMsg {
|
||||
postLog.Debug("Napcat WS event received: " + string(raw))
|
||||
}
|
||||
|
||||
// Only handle message events; ignore notice/request/meta_event.
|
||||
if ev.PostType != "message" {
|
||||
if config.C_globalConfig.System.DebugMode && config.C_globalConfig.Debug.ShowNapcatAction {
|
||||
if showAction {
|
||||
postLog.Debug("Ignoring Napcat WS event: " + string(raw))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Ignore messages the bot itself sent (echo prevention).
|
||||
if q.isSelfMessage(ev) && !config.C_globalConfig.System.DebugMode {
|
||||
if q.isSelfMessage(ev) && !debugMode {
|
||||
return
|
||||
}
|
||||
if q.isSelfMessage(ev) && config.C_globalConfig.System.DebugMode && config.C_globalConfig.Debug.NapcatIgnoreSelfMsg {
|
||||
if config.C_globalConfig.System.DebugMode && config.C_globalConfig.Debug.ShowNapcatAction {
|
||||
if q.isSelfMessage(ev) && debugMode && cfg.Debug.NapcatIgnoreSelfMsg {
|
||||
if showAction {
|
||||
postLog.Debug("Ignoring Napcat WS self message: " + string(raw))
|
||||
}
|
||||
return
|
||||
@@ -138,7 +148,7 @@ func (q *QQController) handleNapcatEvent(raw []byte) {
|
||||
|
||||
response := q.processCommand(cmd)
|
||||
if response == "" {
|
||||
if config.C_globalConfig.System.DebugMode && config.C_globalConfig.Debug.ShowNapcatAction {
|
||||
if showAction {
|
||||
postLog.Debug("Napcat WS command discarded: " + string(raw))
|
||||
}
|
||||
return
|
||||
@@ -160,6 +170,7 @@ func (q *QQController) handleNapcatEvent(raw []byte) {
|
||||
// complete command to the unified processor. It returns the response text to
|
||||
// reply with; an empty response means the message was discarded.
|
||||
func (q *QQController) processCommand(cmd controller.Command) string {
|
||||
cfg := config.Current()
|
||||
text := cmd.RawText
|
||||
|
||||
// In "at" listen mode, require an @mention of the bot and strip it before
|
||||
@@ -168,7 +179,7 @@ func (q *QQController) processCommand(cmd controller.Command) string {
|
||||
if q.cfg.ListenMethod == "at" {
|
||||
atMention := fmt.Sprintf("[CQ:at,qq=%d]", q.cfg.BotQQID)
|
||||
if !strings.Contains(text, atMention) {
|
||||
if config.C_globalConfig.System.DebugMode && config.C_globalConfig.Debug.ShowNapcatAction {
|
||||
if cfg != nil && cfg.System.DebugMode && cfg.Debug.ShowNapcatAction {
|
||||
postLog.Debug("Napcat WS message ignored (no @mention): " + text)
|
||||
}
|
||||
return "" // Not mentioned, ignore.
|
||||
@@ -190,7 +201,7 @@ func (q *QQController) processCommand(cmd controller.Command) string {
|
||||
// Hand the complete command to the unified processor, which checks group
|
||||
// vs private, trusted groups, admin permissions, and executes it.
|
||||
response, err := controller.GetManager().Trigger(parsed, q.trustedGroupIDs(), q.adminIDs(), q.cfg.ListenMethod)
|
||||
if config.C_globalConfig.System.DebugMode && config.C_globalConfig.Debug.ShowTriggerCmdEcho {
|
||||
if cfg != nil && cfg.System.DebugMode && cfg.Debug.ShowTriggerCmdEcho {
|
||||
postLog.Debug(fmt.Sprintf("[qq_napcat] triggered command: \"/%s\" with args: \"%s\" from chatID: %d and senderID: %d", parsed.Command, strings.Join(parsed.Args, ", "), cmd.ChatID, cmd.SenderID))
|
||||
}
|
||||
if err != nil {
|
||||
@@ -213,7 +224,7 @@ func (q *QQController) isSelfMessage(ev oneBotEvent) bool {
|
||||
|
||||
// adminIDs returns the QQ admin IDs from bot_user_config.json.
|
||||
func (q *QQController) adminIDs() []string {
|
||||
if c := config.C_botUserConfig; c != nil {
|
||||
if c := config.BotUsers(); c != nil {
|
||||
return c.QQ.Admins.IDs()
|
||||
}
|
||||
return nil
|
||||
@@ -221,7 +232,7 @@ func (q *QQController) adminIDs() []string {
|
||||
|
||||
// trustedGroupIDs returns the QQ trusted group IDs from bot_user_config.json.
|
||||
func (q *QQController) trustedGroupIDs() []string {
|
||||
if c := config.C_botUserConfig; c != nil {
|
||||
if c := config.BotUsers(); c != nil {
|
||||
return c.QQ.TrustedGroups.IDs()
|
||||
}
|
||||
return nil
|
||||
@@ -237,7 +248,7 @@ func (q *QQController) SendMessage(message controller.Message) error {
|
||||
}
|
||||
|
||||
// Only notify trusted groups and admins whose options allow this message type.
|
||||
if uc := config.C_botUserConfig; uc != nil {
|
||||
if uc := config.BotUsers(); uc != nil {
|
||||
for groupID, opts := range uc.QQ.TrustedGroups {
|
||||
if !controller.MemberReceives(opts, message.Type) {
|
||||
continue
|
||||
@@ -260,12 +271,12 @@ func (q *QQController) SendStatusChange(change node.StatusChange) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
cfg := config.C_globalConfig
|
||||
cfg := config.Current()
|
||||
params := template.BuildParamsFromStatusChange(change)
|
||||
message := template.Render(cfg.ControllerMessage.ServerStatusChanged, params, q.cfg.Markdown)
|
||||
|
||||
// Only notify trusted groups and admins whose event_status_notify is true.
|
||||
if uc := config.C_botUserConfig; uc != nil {
|
||||
if uc := config.BotUsers(); uc != nil {
|
||||
for groupID, opts := range uc.QQ.TrustedGroups {
|
||||
if !opts.EventStatusNotify {
|
||||
continue
|
||||
@@ -289,7 +300,7 @@ func (q *QQController) SendServerList(onlineServers, offlineServers string) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
cfg := config.C_globalConfig
|
||||
cfg := config.Current()
|
||||
params := template.BuildParamsFromServerList()
|
||||
message := template.Render(cfg.ControllerMessage.ServerList, params, q.cfg.Markdown)
|
||||
|
||||
@@ -305,7 +316,7 @@ func (q *QQController) SendExecuteResult(serverName, serverUUID, command, result
|
||||
return nil
|
||||
}
|
||||
|
||||
cfg := config.C_globalConfig
|
||||
cfg := config.Current()
|
||||
params := template.BuildParamsFromExecResult(serverName, serverUUID, command, result)
|
||||
message := template.Render(cfg.ControllerMessage.ServerExecuteResult, params, q.cfg.Markdown)
|
||||
|
||||
|
||||
@@ -148,7 +148,7 @@ func (t *TelegramController) handleUpdate(_ context.Context, _ *bot.Bot, update
|
||||
}
|
||||
msg := update.Message
|
||||
|
||||
if config.C_globalConfig.System.DebugMode && config.C_globalConfig.Debug.ShowTelegramMsg {
|
||||
if cfg := config.Current(); cfg != nil && cfg.System.DebugMode && cfg.Debug.ShowTelegramMsg {
|
||||
raw, _ := json.Marshal(update)
|
||||
postLog.Debug("Telegram update received: " + string(raw))
|
||||
}
|
||||
@@ -232,7 +232,7 @@ func (t *TelegramController) processCommand(cmd controller.Command) string {
|
||||
// Hand the complete command to the unified processor, which checks group
|
||||
// vs private, trusted groups, admin permissions, and executes it.
|
||||
response, err := controller.GetManager().Trigger(parsed, t.trustedGroupIDs(), t.resolvedAdminList(), t.cfg.ListenMethod)
|
||||
if config.C_globalConfig.System.DebugMode && config.C_globalConfig.Debug.ShowTriggerCmdEcho {
|
||||
if cfg := config.Current(); cfg != nil && cfg.System.DebugMode && cfg.Debug.ShowTriggerCmdEcho {
|
||||
postLog.Debug(fmt.Sprintf("[telegram] triggered command: \"/%s\" with args: \"%s\" from chatID: %d and senderID: %d", parsed.Command, strings.Join(parsed.Args, ", "), cmd.ChatID, cmd.SenderID))
|
||||
}
|
||||
if err != nil {
|
||||
@@ -252,7 +252,7 @@ func (t *TelegramController) SendMessage(message controller.Message) error {
|
||||
}
|
||||
|
||||
// Only notify trusted groups and admins whose options allow this message type.
|
||||
if uc := config.C_botUserConfig; uc != nil {
|
||||
if uc := config.BotUsers(); uc != nil {
|
||||
for groupID, opts := range uc.Telegram.TrustedGroups {
|
||||
if !controller.MemberReceives(opts, message.Type) {
|
||||
continue
|
||||
@@ -275,12 +275,12 @@ func (t *TelegramController) SendStatusChange(change node.StatusChange) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
cfg := config.C_globalConfig
|
||||
cfg := config.Current()
|
||||
params := template.BuildParamsFromStatusChange(change)
|
||||
message := template.Render(cfg.ControllerMessage.ServerStatusChanged, params, t.cfg.Markdown)
|
||||
|
||||
// Only notify trusted groups and admins whose event_status_notify is true.
|
||||
if uc := config.C_botUserConfig; uc != nil {
|
||||
if uc := config.BotUsers(); uc != nil {
|
||||
for groupID, opts := range uc.Telegram.TrustedGroups {
|
||||
if !opts.EventStatusNotify {
|
||||
continue
|
||||
@@ -303,7 +303,7 @@ func (t *TelegramController) SendServerList(onlineServers, offlineServers string
|
||||
return nil
|
||||
}
|
||||
|
||||
cfg := config.C_globalConfig
|
||||
cfg := config.Current()
|
||||
params := template.BuildParamsFromServerList()
|
||||
message := template.Render(cfg.ControllerMessage.ServerList, params, t.cfg.Markdown)
|
||||
|
||||
@@ -317,7 +317,7 @@ func (t *TelegramController) SendExecuteResult(serverName, serverUUID, command,
|
||||
return nil
|
||||
}
|
||||
|
||||
cfg := config.C_globalConfig
|
||||
cfg := config.Current()
|
||||
params := template.BuildParamsFromExecResult(serverName, serverUUID, command, result)
|
||||
message := template.Render(cfg.ControllerMessage.ServerExecuteResult, params, t.cfg.Markdown)
|
||||
|
||||
@@ -395,7 +395,7 @@ func (t *TelegramController) resolveUsername(username string) (int64, bool) {
|
||||
// adminIDs returns the Telegram admin entries (numeric user ID or @username)
|
||||
// from bot_user_config.json.
|
||||
func (t *TelegramController) adminIDs() []string {
|
||||
if c := config.C_botUserConfig; c != nil {
|
||||
if c := config.BotUsers(); c != nil {
|
||||
return c.Telegram.Admins.IDs()
|
||||
}
|
||||
return nil
|
||||
@@ -403,7 +403,7 @@ func (t *TelegramController) adminIDs() []string {
|
||||
|
||||
// trustedGroupIDs returns the Telegram trusted group IDs from bot_user_config.json.
|
||||
func (t *TelegramController) trustedGroupIDs() []string {
|
||||
if c := config.C_botUserConfig; c != nil {
|
||||
if c := config.BotUsers(); c != nil {
|
||||
return c.Telegram.TrustedGroups.IDs()
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -66,7 +66,7 @@ func (w *WebhookController) SendStatusChange(change node.StatusChange) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
cfg := config.C_globalConfig
|
||||
cfg := config.Current()
|
||||
params := template.BuildParamsFromStatusChange(change)
|
||||
message := template.Render(cfg.ControllerMessage.ServerStatusChanged, params, w.cfg.Markdown)
|
||||
|
||||
@@ -87,7 +87,7 @@ func (w *WebhookController) SendServerList(onlineServers, offlineServers string)
|
||||
return nil
|
||||
}
|
||||
|
||||
cfg := config.C_globalConfig
|
||||
cfg := config.Current()
|
||||
params := template.BuildParamsFromServerList()
|
||||
message := template.Render(cfg.ControllerMessage.ServerList, params, w.cfg.Markdown)
|
||||
|
||||
@@ -108,7 +108,7 @@ func (w *WebhookController) SendExecuteResult(serverName, serverUUID, command, r
|
||||
return nil
|
||||
}
|
||||
|
||||
cfg := config.C_globalConfig
|
||||
cfg := config.Current()
|
||||
params := template.BuildParamsFromExecResult(serverName, serverUUID, command, result)
|
||||
message := template.Render(cfg.ControllerMessage.ServerExecuteResult, params, w.cfg.Markdown)
|
||||
|
||||
|
||||
@@ -22,13 +22,13 @@ func commandMarkdown(cmd Command) bool {
|
||||
}
|
||||
|
||||
func handleHelp(cmd Command) (string, error) {
|
||||
cfg := config.C_globalConfig
|
||||
cfg := config.Current()
|
||||
params := template.BuildBotInitializationMsgParams()
|
||||
return template.Render(cfg.ControllerMessage.BotHelp, params, commandMarkdown(cmd)), nil
|
||||
}
|
||||
|
||||
func handleList(cmd Command) (string, error) {
|
||||
cfg := config.C_globalConfig
|
||||
cfg := config.Current()
|
||||
params := template.BuildParamsFromServerList()
|
||||
return template.Render(cfg.ControllerMessage.ServerList, params, commandMarkdown(cmd)), nil
|
||||
}
|
||||
@@ -139,7 +139,7 @@ func handleRun(cmd Command) (string, error) {
|
||||
return fmt.Sprintf("Error getting results: %v", err), nil
|
||||
}
|
||||
|
||||
cfg := config.C_globalConfig
|
||||
cfg := config.Current()
|
||||
params := template.BuildParamsFromExecResult(uuidArg, uuidArg, command, formatTaskResults(results))
|
||||
return template.Render(cfg.ControllerMessage.ServerExecuteResult, params, commandMarkdown(cmd)), nil
|
||||
}
|
||||
@@ -189,7 +189,7 @@ func handleInfo(cmd Command) (string, error) {
|
||||
}
|
||||
|
||||
func telegram_handleStart(cmd Command) (string, error) {
|
||||
cfg := config.C_globalConfig
|
||||
cfg := config.Current()
|
||||
params := template.BuildBotInitializationMsgParams()
|
||||
return template.Render(cfg.ControllerMessage.Tg_BotStart, params, commandMarkdown(cmd)), nil
|
||||
}
|
||||
|
||||
@@ -18,6 +18,14 @@ import (
|
||||
"nukumizu-backend/postLog"
|
||||
)
|
||||
|
||||
// taskEchoEnabled reports whether Komari task progress should be echoed to the
|
||||
// log: debug mode plus the showKomariTaskEcho toggle. The configuration is read
|
||||
// once per call so both flags come from the same reload.
|
||||
func taskEchoEnabled() bool {
|
||||
cfg := config.Current()
|
||||
return cfg != nil && cfg.System.DebugMode && cfg.Debug.ShowKomariTaskEcho
|
||||
}
|
||||
|
||||
// NodeInfo represents a single node as returned by Komari's
|
||||
// common:getNodes RPC2 method.
|
||||
type NodeInfo struct {
|
||||
@@ -146,7 +154,7 @@ func (c *Client) Login(username, password string) error {
|
||||
|
||||
var kr KomariResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&kr); err != nil {
|
||||
if config.C_globalConfig.System.DebugMode {
|
||||
if config.IsDebugMode() {
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("failed to parse komari login response: %w.\nResponse: %s", err, respBody)
|
||||
}
|
||||
@@ -332,7 +340,7 @@ func (c *Client) ExecTask(uuids []string, command string) (string, error) {
|
||||
return "", fmt.Errorf("failed to parse komari task exec data: %w", err)
|
||||
}
|
||||
|
||||
if config.C_globalConfig.System.DebugMode && config.C_globalConfig.Debug.ShowKomariTaskEcho {
|
||||
if taskEchoEnabled() {
|
||||
postLog.Debug(fmt.Sprintf("Created Komari task %s for %d clients", result.TaskID, len(uuids)))
|
||||
}
|
||||
return result.TaskID, nil
|
||||
@@ -375,7 +383,7 @@ func (c *Client) GetTaskResult(taskID string) ([]TaskResult, bool, error) {
|
||||
// PollTaskResult polls for task results every 1 second until all results are
|
||||
// available or 60 seconds have elapsed.
|
||||
func (c *Client) PollTaskResult(taskID string) ([]TaskResult, error) {
|
||||
if config.C_globalConfig.System.DebugMode && config.C_globalConfig.Debug.ShowKomariTaskEcho {
|
||||
if taskEchoEnabled() {
|
||||
postLog.Debug(fmt.Sprintf("Polling for Komari task %s results...", taskID))
|
||||
}
|
||||
|
||||
@@ -393,7 +401,7 @@ func (c *Client) PollTaskResult(taskID string) ([]TaskResult, error) {
|
||||
return nil, err
|
||||
}
|
||||
if done {
|
||||
if config.C_globalConfig.System.DebugMode && config.C_globalConfig.Debug.ShowKomariTaskEcho {
|
||||
if taskEchoEnabled() {
|
||||
postLog.Info(fmt.Sprintf("Task %s completed with %d results", taskID, len(results)))
|
||||
}
|
||||
return results, nil
|
||||
|
||||
@@ -409,7 +409,10 @@ func GetWSClient() *WSClient {
|
||||
|
||||
// LoginAndStart performs the Komari login and returns an error if it fails.
|
||||
func LoginAndStart() error {
|
||||
cfg := config.C_globalConfig
|
||||
cfg := config.Current()
|
||||
if cfg == nil {
|
||||
return fmt.Errorf("configuration not loaded")
|
||||
}
|
||||
client := GetClient()
|
||||
if client == nil {
|
||||
return fmt.Errorf("komari client not initialized")
|
||||
|
||||
@@ -20,7 +20,11 @@ import (
|
||||
// proxyURL returns the system-wide network proxy URL, or nil when none is
|
||||
// configured. A missing scheme is normalized to http:// for convenience.
|
||||
func proxyURL() *url.URL {
|
||||
raw := config.C_globalConfig.System.NetworkProxy
|
||||
cfg := config.Current()
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
raw := cfg.System.NetworkProxy
|
||||
if raw == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user