feat(config): implement loading and saving of bot node configuration with default notification settings

This commit is contained in:
2026-09-06 14:00:45 +08:00
parent 6a0c2d1fc4
commit ec1cf20347
3 changed files with 63 additions and 36 deletions
+44 -28
View File
@@ -5,25 +5,45 @@ import (
"encoding/json"
"fmt"
"os"
"sync"
)
// botNodeConfigMu guards botNodeConfig, the in-memory mirror of
// bot_node_config.json.
var (
botNodeConfigMu sync.RWMutex
botNodeConfig BotNodeMembers
)
// LoadBotNodeConfig reads and parses bot_node_config.json and stores it as the
// global C_botNodeConfig singleton, mirroring LoadGlobalConfig. Unlike the
// other config files this one is auto-generated and optional: a missing or
// empty file yields an empty map, so every node falls back to its default
// enableStatusNotify value (true).
func LoadBotNodeConfig(configPath string) error {
cfg := BotNodeMembers{}
data, err := os.ReadFile(configPath)
if err != nil {
if os.IsNotExist(err) {
C_botNodeConfig = cfg
return nil
}
return fmt.Errorf("failed to read bot node config file: %w", err)
}
if len(bytes.TrimSpace(data)) > 0 {
if err := json.Unmarshal(data, &cfg); err != nil {
return fmt.Errorf("failed to parse bot node config file: %w", err)
}
}
C_botNodeConfig = cfg
return nil
}
// NodeStatusNotifyEnabled reports whether the node identified by uuid should
// broadcast status-change notifications, per bot_node_config.json. The flag is
// allow-list only: a node notifies when its entry has enableStatusNotify set to
// true, and stays silent when the entry is absent or not enabled.
// 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.
func NodeStatusNotifyEnabled(uuid string) bool {
botNodeConfigMu.RLock()
opts, ok := botNodeConfig[uuid]
botNodeConfigMu.RUnlock()
return ok && opts.EnableStatusNotify
if C_botNodeConfig == nil {
return true
}
opts, ok := C_botNodeConfig[uuid]
if !ok || opts.EnableStatusNotify == nil {
return true
}
return *opts.EnableStatusNotify
}
// LoadGlobalConfig reads and parses the configuration file, applies defaults,
@@ -135,16 +155,16 @@ func LoadBotUserConfig(configPath string) (*BotUserConfig, error) {
return &cfg, nil
}
// SaveBotNodeConfig persists the given node UUIDs to the bot node config file
// and refreshes the in-memory mirror read by NodeStatusNotifyEnabled. Entries
// already present in the file are always preserved: a UUID Komari no longer
// reports on a given fetch is kept rather than deleted, so per-node settings
// for stale nodes survive a node-list refresh that does not include them. UUIDs
// seen for the first time are added disabled (enableStatusNotify defaults to
// false); enable a node's status notifications by setting the flag to true in
// the file. The resulting JSON has its object keys emitted in sorted order by
// encoding/json, keeping the file deterministic across writes. The path is
// supplied by the caller (typically global.ConfigPath.BotNodeConfig).
// SaveBotNodeConfig persists the given node UUIDs to the bot node config file,
// which is a plain registry of the nodes Komari reports (read back at startup
// by LoadBotNodeConfig). Entries already present are always preserved: a UUID
// Komari no longer reports on a given fetch is kept rather than deleted, and
// any enableStatusNotify a user set by hand is left untouched. UUIDs seen for
// the first time are added as an empty object, i.e. no parameter is written, so
// they inherit the enableStatusNotify default (true). The resulting JSON has
// its object keys emitted in sorted order by encoding/json, keeping the file
// deterministic across writes. The path is supplied by the caller (typically
// global.ConfigPath.BotNodeConfig).
func SaveBotNodeConfig(configPath string, uuids []string) error {
// Start from whatever is already on disk so nothing is dropped. An empty or
// missing file is treated as an empty map.
@@ -172,10 +192,6 @@ func SaveBotNodeConfig(configPath string, uuids []string) error {
if err := os.WriteFile(configPath, data, 0o644); err != nil {
return fmt.Errorf("failed to write bot node config %s: %w", configPath, err)
}
botNodeConfigMu.Lock()
botNodeConfig = members
botNodeConfigMu.Unlock()
return nil
}
+11 -7
View File
@@ -175,14 +175,18 @@ var C_botUserConfig *BotUserConfig
// file is auto-populated by the node tracker for every node Komari reports;
// per-node options are edited by hand in the JSON file.
type BotNodeOptions struct {
// EnableStatusNotify is an allow-list switch: a node broadcasts status-change
// notifications only when this is true. Nodes default to false (silent)
// unless explicitly enabled.
EnableStatusNotify bool `json:"enableStatusNotify"`
// EnableStatusNotify controls whether this node broadcasts status-change
// notifications. It defaults to true: only an explicit false in the JSON
// file disables a node's notifications. The pointer (rather than a plain
// bool) lets an absent field be told apart from an explicit false, and
// omitempty keeps untouched nodes stored as {}.
EnableStatusNotify *bool `json:"enableStatusNotify,omitempty"`
}
// BotNodeMembers maps a node UUID (as reported by Komari) to its per-node
// options, e.g. bot_node_config.json:
//
// {"<uuid1>": {"enableStatusNotify": true}, "<uuid2>": {}}
// options.
type BotNodeMembers map[string]BotNodeOptions
// C_botNodeConfig is the global singleton mirroring bot_node_config.json,
// populated by LoadBotNodeConfig.
var C_botNodeConfig BotNodeMembers
+7
View File
@@ -47,6 +47,13 @@ func main() {
log.Fatalf("Failed to load bot user config: %v", err)
}
// Load the node registry config. Unlike the other files it is optional:
// missing or empty bot_node_config.json simply means every node keeps its
// default enableStatusNotify (true).
if err := config.LoadBotNodeConfig(global.ConfigPath.BotNodeConfig); err != nil {
log.Fatalf("Failed to load bot node config: %v", err)
}
// Initialize logging.
postLog.SetDebugMode(cfg.System.DebugMode)
postLog.InitLogBroadcaster()