feat(controller): add per-node notify switch controller

This commit is contained in:
2026-09-06 10:48:38 +08:00
parent 5bc85effa7
commit 6a0c2d1fc4
3 changed files with 49 additions and 21 deletions
+31 -6
View File
@@ -5,8 +5,27 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"os" "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, // LoadGlobalConfig reads and parses the configuration file, applies defaults,
// and stores it as a global singleton. // and stores it as a global singleton.
func LoadGlobalConfig(configPath string) (*Config, error) { func LoadGlobalConfig(configPath string) (*Config, error) {
@@ -116,12 +135,14 @@ func LoadBotUserConfig(configPath string) (*BotUserConfig, error) {
return &cfg, nil return &cfg, nil
} }
// SaveBotNodeConfig persists the given node UUIDs to the bot node config file. // 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 // and refreshes the in-memory mirror read by NodeStatusNotifyEnabled. Entries
// longer reports on a given fetch is kept rather than deleted, so per-node // already present in the file are always preserved: a UUID Komari no longer
// settings for stale nodes survive a node-list refresh that does not include // reports on a given fetch is kept rather than deleted, so per-node settings
// them. UUIDs seen for the first time are added with an empty BotNodeOptions // for stale nodes survive a node-list refresh that does not include them. UUIDs
// entry. The resulting JSON has its object keys emitted in sorted order by // 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 // encoding/json, keeping the file deterministic across writes. The path is
// supplied by the caller (typically global.ConfigPath.BotNodeConfig). // supplied by the caller (typically global.ConfigPath.BotNodeConfig).
func SaveBotNodeConfig(configPath string, uuids []string) error { 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 { if err := os.WriteFile(configPath, data, 0o644); err != nil {
return fmt.Errorf("failed to write bot node config %s: %w", configPath, err) return fmt.Errorf("failed to write bot node config %s: %w", configPath, err)
} }
botNodeConfigMu.Lock()
botNodeConfig = members
botNodeConfigMu.Unlock()
return nil return nil
} }
+8 -5
View File
@@ -172,14 +172,17 @@ type BotUserConfig struct {
var C_botUserConfig *BotUserConfig var C_botUserConfig *BotUserConfig
// BotNodeOptions holds per-node options stored in bot_node_config.json. The // 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 // file is auto-populated by the node tracker for every node Komari reports;
// invalidating existing files. // per-node options are edited by hand in the JSON file.
type BotNodeOptions struct { 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 // 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 // options, e.g. bot_node_config.json:
// bot_node_config.json reads like:
// //
// {"<uuid1>": {}, "<uuid2>": {}} // {"<uuid1>": {"enableStatusNotify": true}, "<uuid2>": {}}
type BotNodeMembers map[string]BotNodeOptions type BotNodeMembers map[string]BotNodeOptions
+9 -9
View File
@@ -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) { 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() m.mu.RLock()
defer m.mu.RUnlock() defer m.mu.RUnlock()
cfg := config.C_globalConfig
templateStr := cfg.ControllerMessage.ServerStatusChanged
params := template.BuildParamsFromStatusChange(change)
for _, ctrl := range m.controllers { for _, ctrl := range m.controllers {
if !ctrl.IsEnabled() { if !ctrl.IsEnabled() {
continue continue
} }
// Get the names of controllers that support commands for the message.
if err := ctrl.SendStatusChange(change); err != nil { if err := ctrl.SendStatusChange(change); err != nil {
postLog.Warning(fmt.Sprintf("Controller %s failed to send status change: %v", ctrl.Name(), err)) postLog.Warning(fmt.Sprintf("Controller %s failed to send status change: %v", ctrl.Name(), err))
} }
_ = templateStr
_ = params
} }
} }