diff --git a/src/link.rs b/src/link.rs index 1446698..1abdb9f 100644 --- a/src/link.rs +++ b/src/link.rs @@ -1453,20 +1453,11 @@ impl Server { if !self.channels.contains_key(&key) { return; } + // echo/services badges a service source for message-tags clients; the + // line is built once and shared by Arc across all members (not cloned + // per recipient). let base = format!(":{prefix} {cmd} {target} :{text}"); - // echo/services: tag messages from a network service so capable - // clients can badge them. Per-recipient (needs the message-tags cap). - let is_service = src_is_service; - let tagged = format!("@echo/services {base}"); - let members: Vec = self.channels[&key].members.keys().copied().collect(); - for m in members { - if self.users.get(&m).map(|u| u.flags.deaf).unwrap_or(false) { - continue; - } - let want_tag = is_service - && self.users.get(&m).map(|u| u.caps.message_tags).unwrap_or(false); - self.send(m, if want_tag { tagged.clone() } else { base.clone() }); - } + self.relay_channel_message(&key, &base, src_is_service); // forward to the other links that have members in this channel for l in self.channel_link_targets(&key, Some(via)) { self.link_out(l, format!(":{src} {cmd} {target} :{text}")); diff --git a/src/server.rs b/src/server.rs index e220738..1f82967 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1018,6 +1018,44 @@ impl Server { } } + /// Fan an already-composed S2S channel message (`:prefix PRIVMSG #c :text`) out + /// to local members, sharing the line by `Arc` instead of cloning a String per + /// recipient. When `service`, message-tags clients get the `echo/services` badge; + /// server-time and +D deaf filtering are applied per recipient. At most three + /// distinct lines are built (plain / time-tagged / service-tagged) whatever the + /// channel size. + pub fn relay_channel_message(&self, key: &str, base: &str, service: bool) { + let Some(ch) = self.channels.get(key) else { + return; + }; + let sourced = base.starts_with(':'); // only `:prefix …` lines carry server-time + let plain: std::sync::Arc = std::sync::Arc::from(base); + let tagged: Option> = + service.then(|| std::sync::Arc::from(format!("@echo/services {base}").as_str())); + let mut plain_time: Option> = None; + for &m in ch.members.keys() { + let Some(u) = self.users.get(&m) else { + continue; + }; + if u.flags.deaf { + continue; + } + // a service-badged line already carries a tag block, so — as before — it + // isn't additionally server-time tagged. + let buf = if service && u.caps.message_tags { + LineBuf::Shared(tagged.clone().unwrap()) + } else if sourced && u.caps.server_time { + let t = plain_time.get_or_insert_with(|| { + std::sync::Arc::from(format!("@time={} {base}", iso_time(now())).as_str()) + }); + LineBuf::Shared(t.clone()) + } else { + LineBuf::Shared(plain.clone()) + }; + self.emit_to(m, buf); + } + } + /// Send a message body (`:prefix CMD …`) from `src` to `uid`, composing its /// IRCv3 tag prefix from *that recipient's* caps: `time=` (server-time), /// `account=` (account-tag, from the sender's login) plus the client-only tags