diff --git a/config.example.toml b/config.example.toml index 0f0446c..125d39f 100644 --- a/config.example.toml +++ b/config.example.toml @@ -17,6 +17,7 @@ gaming = "!gamingroomid:matrix.example.org" lego = "!legoroomid:matrix.example.org" [posting] +enabled = true # master switch for auto-posting news to Matrix; false = web-only (commands still work) min_interval_seconds = 300 burst_cap_count = 3 burst_cap_window_seconds = 1800 diff --git a/internal/config/config.go b/internal/config/config.go index d710a1a..3ad5177 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -57,10 +57,16 @@ type MatrixConfig struct { } type PostingConfig struct { - MinIntervalSeconds int `toml:"min_interval_seconds"` - BurstCapCount int `toml:"burst_cap_count"` - BurstCapWindowSeconds int `toml:"burst_cap_window_seconds"` - DedupCooldownHours int `toml:"dedup_cooldown_hours"` + // Enabled is the master switch for automatic news posting to Matrix. + // When false, stories are still ingested, classified, and served to the + // web UI, but Pete never auto-posts them to rooms. Command replies + // (!post, !petestats) still work. Pointer so an absent key defaults to + // true (posting on) rather than Go's zero-value false. + Enabled *bool `toml:"enabled"` + MinIntervalSeconds int `toml:"min_interval_seconds"` + BurstCapCount int `toml:"burst_cap_count"` + BurstCapWindowSeconds int `toml:"burst_cap_window_seconds"` + DedupCooldownHours int `toml:"dedup_cooldown_hours"` // DailyCapTotal is the hard global cap on posts across ALL channels in a // rolling 24-hour window. 0 disables the cap. DailyCapTotal int `toml:"daily_cap_total"` @@ -198,6 +204,10 @@ func (c *Config) applyDefaults() { if c.Matrix.DisplayName == "" { c.Matrix.DisplayName = "Pete" } + if c.Posting.Enabled == nil { + on := true + c.Posting.Enabled = &on + } if c.Posting.MinIntervalSeconds == 0 { c.Posting.MinIntervalSeconds = 300 } diff --git a/main.go b/main.go index f174ead..6b6b56c 100644 --- a/main.go +++ b/main.go @@ -174,7 +174,11 @@ func main() { go queue.Start(ctx) slog.Info("post queue started") + postingEnabled := *cfg.Posting.Enabled roundRobinMode := cfg.Posting.RoundRobin.Enabled + if !postingEnabled { + slog.Info("automatic Matrix posting is disabled (posting.enabled=false); stories will still be ingested and served to the web UI, and !post still works") + } // Pipeline callback: route the story to its direct_route channel, then // either enqueue immediately or leave it for the round-robin scheduler. @@ -188,6 +192,12 @@ func main() { channel := item.DirectRoute storage.MarkClassified(item.GUID, channel, "[]") + // Posting disabled: classify and keep for the web UI / manual !post, + // but never auto-enqueue to Matrix. + if !postingEnabled { + return + } + if roundRobinMode { slog.Info("story routed, awaiting round-robin tick", "guid", item.GUID, "source", item.Source, "channel", channel) @@ -215,7 +225,7 @@ func main() { poller.Start(ctx) slog.Info("pollers started") - if roundRobinMode { + if postingEnabled && roundRobinMode { channelNames := make([]string, 0, len(cfg.Matrix.Channels)) for name := range cfg.Matrix.Channels { channelNames = append(channelNames, name)