diff --git a/config/config.go b/config/config.go index e5005f8..94f27a3 100644 --- a/config/config.go +++ b/config/config.go @@ -5,8 +5,27 @@ import ( "encoding/json" "fmt" "os" + "sync" ) +// botNodeConfigMu guards botNodeConfig, the in-memory mirror of +// bot_node_config.json. +var ( + botNodeConfigMu sync.RWMutex + botNodeConfig BotNodeMembers +) + +// 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. +func NodeStatusNotifyEnabled(uuid string) bool { + botNodeConfigMu.RLock() + opts, ok := botNodeConfig[uuid] + botNodeConfigMu.RUnlock() + return ok && opts.EnableStatusNotify +} + // LoadGlobalConfig reads and parses the configuration file, applies defaults, // and stores it as a global singleton. func LoadGlobalConfig(configPath string) (*Config, error) { @@ -116,12 +135,14 @@ func LoadBotUserConfig(configPath string) (*BotUserConfig, error) { return &cfg, nil } -// SaveBotNodeConfig persists the given node UUIDs to the bot node config file. -// 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 with an empty BotNodeOptions -// entry. The resulting JSON has its object keys emitted in sorted order by +// 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). func SaveBotNodeConfig(configPath string, uuids []string) error { @@ -151,6 +172,10 @@ 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 } diff --git a/config/variables.go b/config/variables.go index d5c4a60..03f7a75 100644 --- a/config/variables.go +++ b/config/variables.go @@ -172,14 +172,17 @@ type BotUserConfig struct { var C_botUserConfig *BotUserConfig // BotNodeOptions holds per-node options stored in bot_node_config.json. The -// struct is currently empty; per-node settings can be added here later without -// invalidating existing files. +// 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"` } // BotNodeMembers maps a node UUID (as reported by Komari) to its per-node -// options. A plain struct value marshals to an empty JSON object, so -// bot_node_config.json reads like: +// options, e.g. bot_node_config.json: // -// {"": {}, "": {}} -type BotNodeMembers map[string]BotNodeOptions +// {"": {"enableStatusNotify": true}, "": {}} +type BotNodeMembers map[string]BotNodeOptions \ No newline at end of file diff --git a/internal/controller/controller.go b/internal/controller/controller.go index 013834f..370261d 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -165,26 +165,26 @@ func (m *Manager) ShowBotServerList() { } } -// NotifyStatusChange sends a status change notification to all enabled controllers. +// NotifyStatusChange sends a status change notification to all enabled +// controllers. It honors the per-node allow-list in bot_node_config.json: a +// node whose enableStatusNotify is not true is skipped entirely, so no +// controller (chat bots or notification pipes) broadcasts its change. func (m *Manager) NotifyStatusChange(change node.StatusChange) { + if !config.NodeStatusNotifyEnabled(change.UUID) { + postLog.Debug(fmt.Sprintf("Status change for node %s skipped: enableStatusNotify is not enabled", change.UUID)) + return + } + m.mu.RLock() defer m.mu.RUnlock() - cfg := config.C_globalConfig - templateStr := cfg.ControllerMessage.ServerStatusChanged - params := template.BuildParamsFromStatusChange(change) - for _, ctrl := range m.controllers { if !ctrl.IsEnabled() { continue } - - // Get the names of controllers that support commands for the message. if err := ctrl.SendStatusChange(change); err != nil { postLog.Warning(fmt.Sprintf("Controller %s failed to send status change: %v", ctrl.Name(), err)) } - _ = templateStr - _ = params } }