server: route announce through the same tagged path as snotice — REHASH notices now carry server-time + draft/json-log + hit the log ring

This commit is contained in:
Jean Chevronnet 2026-08-09 17:46:26 +00:00
parent 100e33c76b
commit 97b0d8ad89

View file

@ -598,9 +598,8 @@ impl Server {
} }
/// Send a server notice to every operator who has snomask (+s) on. /// Send a server notice to every operator who has snomask (+s) on.
pub fn snotice(&self, msg: &str) { /// Record one server-notice line in the rolling log (for RPC log.tail/events).
// record it in the rolling server log (for RPC log.tail / log.events) fn log_push(&self, msg: &str) {
{
let mut lg = self.log.borrow_mut(); let mut lg = self.log.borrow_mut();
lg.seq += 1; lg.seq += 1;
let id = lg.seq; let id = lg.seq;
@ -613,44 +612,76 @@ impl Server {
lg.ring.pop_front(); lg.ring.pop_front();
} }
} }
/// Deliver a `*** msg` server notice to `uid`, folding in that recipient's
/// server-time + `draft/json-log` tags — the two tags every server notice may
/// carry. `jval` is the per-notice escaped json-log value, shared across
/// recipients (`""` when nobody negotiated the cap). One path for both
/// `snotice` and `announce`, so tagging is consistent no matter the caller.
fn deliver_server_notice(&self, uid: Uid, msg: &str, jval: &str) {
let Some(u) = self.users.get(&uid) else {
return;
};
let nick = u.nick.clone();
let mut tags: Vec<String> = Vec::new();
if u.caps.server_time {
tags.push(format!("time={}", iso_time(now())));
}
if u.caps.json_log && !jval.is_empty() {
tags.push(format!("draft/json-log={jval}"));
}
let base = format!(":{} NOTICE {nick} :*** {msg}", self.name);
let line = if tags.is_empty() {
base
} else {
format!("@{} {base}", tags.join(";"))
};
self.emit_to(uid, line);
}
/// The escaped json-log value for `msg`, or `""` if none of `targets` want it
/// (so we skip building the JSON when no recipient has the cap).
fn json_log_value(&self, msg: &str, targets: &[Uid]) -> String {
let wanted = targets
.iter()
.any(|u| self.users.get(u).map(|x| x.caps.json_log).unwrap_or(false));
if wanted {
crate::modules::jsonlog::tag_value(self, msg)
} else {
String::new()
}
}
/// Send a server notice to every operator who has snomask (+s) on.
pub fn snotice(&self, msg: &str) {
self.log_push(msg);
let opers: Vec<Uid> = self let opers: Vec<Uid> = self
.users .users
.iter() .iter()
.filter(|(_, u)| u.flags.oper && u.flags.snomask) .filter(|(_, u)| u.flags.oper && u.flags.snomask)
.map(|(&u, _)| u) .map(|(&u, _)| u)
.collect(); .collect();
// draft/json-log: the structured tag value is the same for every recipient let jval = self.json_log_value(msg, &opers);
let jval = crate::modules::jsonlog::tag_value(self, msg);
for o in opers { for o in opers {
let (nick, json_cap, time_cap) = self self.deliver_server_notice(o, msg, &jval);
.users
.get(&o)
.map(|u| (u.nick.clone(), u.caps.json_log, u.caps.server_time))
.unwrap_or_default();
let base = format!(":{} NOTICE {nick} :*** {msg}", self.name);
if json_cap {
// build one tag block (server-time too, if negotiated) and emit raw,
// so we don't collide with the auto server-time tagging in `send`
let mut tags = String::new();
if time_cap {
tags.push_str(&format!("time={};", iso_time(now())));
}
tags.push_str(&format!("draft/json-log={jval}"));
self.emit_to(o, format!("@{tags} {base}"));
} else {
self.send(o, base);
}
} }
} }
/// Broadcast a `*** msg` server NOTICE to *every* registered local user — for /// Broadcast a `*** msg` server NOTICE to *every* registered local user — for
/// server-wide announcements everyone should see (e.g. a config reload). /// server-wide announcements everyone should see (e.g. a config reload). Goes
/// through the same tagged path as `snotice`, so cap-holders get the server-time
/// + `draft/json-log` tags and the line is recorded in the server log.
pub fn announce(&self, msg: &str) { pub fn announce(&self, msg: &str) {
for u in self.users.values() { self.log_push(msg);
if u.registered && !u.nick.is_empty() { let targets: Vec<Uid> = self
u.out .users
.send(format!(":{} NOTICE {} :*** {msg}", self.name, u.nick)); .iter()
} .filter(|(_, u)| u.registered && !u.nick.is_empty())
.map(|(&u, _)| u)
.collect();
let jval = self.json_log_value(msg, &targets);
for u in targets {
self.deliver_server_notice(u, msg, &jval);
} }
} }