diff --git a/config.example.toml b/config.example.toml index e7712c5..7d27021 100644 --- a/config.example.toml +++ b/config.example.toml @@ -56,12 +56,14 @@ protocol = 1206 # InspIRCd link protocol version (1206 = insp4, 1205 # surfaced. Omit the section to disable the feed. # [log] # channel = "#services" -# Masks OperServ NOTIFY never announces, so they can't flood the feed. A `#channel` -# mask mutes that channel (e.g. keep a broad `#*` watch out of "#staff"); any other -# mask mutes a user — nick globs, user@host, or extbans, same syntax as a NOTIFY -# mask. "*/*" silences PyLink relay clients (their nicks carry a `/network` suffix). +# Masks OperServ NOTIFY never announces, so they can't flood the feed. Three kinds: +# "#channel" mute a channel (keep a broad `#*` watch out of "#staff") +# "server:" mute everyone on a server (a relay whose users have clean +# nicks; alias "via:", matching the "via " in the feed) +# anything else mute a user — nick glob, user@host, or extban ("*/*" catches +# PyLink relays that suffix nicks with /network) # Reloadable with OperServ REHASH. -# notify_exclude = ["*/*", "#staff"] +# notify_exclude = ["*/*", "server:chatnova.relay", "#staff"] # Inactivity-expiry: accounts not identified to, and channels not joined, for # longer than the threshold are dropped on a periodic pass (opers, live diff --git a/src/config.rs b/src/config.rs index f696998..ba52415 100644 --- a/src/config.rs +++ b/src/config.rs @@ -123,11 +123,12 @@ impl Expire { #[derive(Debug, Deserialize, Clone)] pub struct Log { pub channel: String, - // Masks NOTIFY never announces: a `#channel` mask mutes that channel, any other - // mask (nick glob / user@host / extban) mutes a user. Set "*/*" to silence - // PyLink relay clients — their nicks carry a `/network` suffix — and add a - // channel like "#staff" to keep a broad `#*` watch out of it. Reloadable with - // REHASH. + // Masks NOTIFY never announces, three kinds: `#channel` mutes that channel, + // `server:` (or `via:`) mutes everyone on a matching server — the handle + // on a relay whose users have clean nicks — and any other mask (nick glob / + // user@host / extban) mutes a user. E.g. "*/*" silences PyLink relays that + // suffix nicks, "server:chatnova.relay" a relay that doesn't, "#staff" keeps a + // broad `#*` watch out of a channel. Reloadable with REHASH. #[serde(default)] pub notify_exclude: Vec, } diff --git a/src/engine/db/network.rs b/src/engine/db/network.rs index 354b9bc..b24ba3f 100644 --- a/src/engine/db/network.rs +++ b/src/engine/db/network.rs @@ -191,7 +191,11 @@ impl Db { // mutes events in that channel, any other mask mutes a user (e.g. `*/*` for // PyLink relay clients). A match means the event never reaches the feed. let excluded = self.notify_exclude.iter().any(|m| { - if m.starts_with('#') || m.starts_with('&') { + if let Some(srv) = m.strip_prefix("server:").or_else(|| m.strip_prefix("via:")) { + // `server:` mutes everyone on a matching server — the only handle + // on a relay whose users carry clean nicks (no /network suffix). + target.is_some_and(|t| glob_match(&srv.to_ascii_lowercase(), &t.server.to_ascii_lowercase())) + } else if m.starts_with('#') || m.starts_with('&') { chan.is_some_and(|c| glob_match(m, c)) } else { target.is_some_and(|t| echo_api::akick_matches(m, t) || (!m.contains(['@', '!']) && glob_match(m, t.nick))) diff --git a/src/engine/tests.rs b/src/engine/tests.rs index 4089d8c..b10a37b 100644 --- a/src/engine/tests.rs +++ b/src/engine/tests.rs @@ -5389,6 +5389,12 @@ fn notify_flags_match_user_nick_channel_and_expiry() { db.set_notify_exclude(vec!["#staff".to_string()]); assert!(db.notify_flags(Some(&bt("nice", "h.example")), Some("#staff")).is_empty(), "excluded channel is muted"); assert!(db.notify_flags(Some(&bt("nice", "h.example")), Some("#warez7")).contains('j'), "other channels still watched"); + // `server:` mutes everyone on a matching server — a relay whose users have + // clean nicks. bt() puts users on server "s.net". + db.set_notify_exclude(vec!["server:s.net".to_string()]); + assert!(db.notify_flags(Some(&bt("relayed", "h.example")), Some("#warez7")).is_empty(), "user on the excluded server is muted"); + db.set_notify_exclude(vec!["via:other.net".to_string()]); + assert!(db.notify_flags(Some(&bt("relayed", "h.example")), Some("#warez7")).contains('j'), "user on a different server still watched"); db.set_notify_exclude(vec![]); // DEL by mask removes just that one. assert!(db.notify_del("baddie*!*@*").unwrap());