import echoircd — from-scratch irc daemon in native rust

This commit is contained in:
Jean Chevronnet 2026-08-05 16:10:31 +00:00
commit 9b12791774
38 changed files with 9757 additions and 0 deletions

84
src/watch.rs Normal file
View file

@ -0,0 +1,84 @@
//! WATCH / MONITOR notification plumbing + SILENCE matching.
//!
//! The lists themselves live on the [`crate::users::User`] (`watch` / `monitor` /
//! `silence`); the commands are in [`crate::coremods::core_watch`]. Whenever a
//! nick's online-state flips — registration, quit, or a nick change — the
//! lifecycle code calls [`Server::watch_notify_online`] / [`Server::watch_notify_offline`],
//! which scan for anyone WATCHing/MONITORing that nick and send the right numeric.
//!
//! An O(users) scan, not a reverse index: correct-by-construction (nothing to keep
//! in sync) and fine on a small server — a reverse index can slot in later if it
//! ever needs to scale, exactly the kind of change the borrow checker makes safe.
use crate::channels::glob_match;
use crate::numeric::*;
use crate::server::{now, Server};
use crate::Uid;
pub const WATCH_MAX: usize = 128;
pub const MONITOR_MAX: usize = 128;
pub const SILENCE_MAX: usize = 32;
impl Server {
/// A nick just came online (registered, or someone renamed to it): tell its
/// WATCHers (600 RPL_LOGON) and MONITORers (730 RPL_MONONLINE).
pub fn watch_notify_online(&self, nick: &str) {
let low = nick.to_ascii_lowercase();
let Some((dnick, ident, host, ts)) = self.find_nick(nick).and_then(|tu| {
self.users.get(&tu).map(|x| {
(
x.nick.clone(),
x.ident.clone(),
x.host_display().to_string(),
x.signon,
)
})
}) else {
return;
};
for (&uid, u) in &self.users {
if u.watch.contains(&low) {
self.numeric(
uid,
RPL_LOGON,
&format!("{dnick} {ident} {host} {ts} :is now online"),
);
}
if u.monitor.contains(&low) {
self.numeric(uid, RPL_MONONLINE, &format!(":{dnick}!{ident}@{host}"));
}
}
}
/// A nick just went offline (quit, or renamed away): tell its WATCHers
/// (601 RPL_LOGOFF) and MONITORers (731 RPL_MONOFFLINE).
pub fn watch_notify_offline(&self, nick: &str) {
let low = nick.to_ascii_lowercase();
let ts = now();
for (&uid, u) in &self.users {
if u.watch.contains(&low) {
self.numeric(uid, RPL_LOGOFF, &format!("{nick} * * {ts} :is now offline"));
}
if u.monitor.contains(&low) {
self.numeric(uid, RPL_MONOFFLINE, &format!(":{nick}"));
}
}
}
/// How many users currently WATCH `nick` (for `WATCH S` stats).
pub fn watchers_of(&self, nick: &str) -> usize {
let low = nick.to_ascii_lowercase();
self.users
.values()
.filter(|u| u.watch.contains(&low))
.count()
}
/// True if user `by` has silenced someone whose prefix is `sender_mask`.
pub fn is_silenced(&self, by: Uid, sender_mask: &str) -> bool {
self.users
.get(&by)
.map(|u| u.silence.iter().any(|m| glob_match(m, sender_mask)))
.unwrap_or(false)
}
}