add SIGNORE: personal mutual server-side ignore across channels and PMs

This commit is contained in:
Jean Chevronnet 2026-08-20 14:48:17 +00:00
parent 2c9ce18cd7
commit 6c5f330746
No known key found for this signature in database
GPG key ID: 439666D63A9477E4
5 changed files with 107 additions and 1 deletions

View file

@ -599,7 +599,8 @@ pub(crate) fn deliver(s: &mut Server, uid: Uid, params: &[String], notice: bool)
}
// SILENCE: if the recipient silenced the sender, drop it silently — the
// sender is never told (that's the point), but still gets their own echo.
let silenced = s.is_silenced(tuid, &prefix);
// SIGNORE is mutual: drop if either party server-ignores the other.
let silenced = s.is_silenced(tuid, &prefix) || s.signore_blocks(uid, tuid);
let pm = format!(":{prefix} {cmd} {target} :{text}");
let ctags = s.line_ctags.clone();
let msgid = s.next_msgid();

View file

@ -14,6 +14,7 @@ pub fn commands() -> Vec<Box<dyn Command>> {
Box::new(Watch),
Box::new(Monitor),
Box::new(Silence),
Box::new(Signore),
Box::new(Accept),
]
}
@ -298,6 +299,77 @@ impl Command for Silence {
}
}
// --- SIGNORE (personal mutual server-side ignore) ---------------------------
fn signore_list(s: &Server, uid: Uid) {
let (nick, list) = s
.users
.get(&uid)
.map(|u| (u.nick.clone(), u.signore.clone()))
.unwrap_or_default();
let sn = &s.name;
if list.is_empty() {
s.send(uid, format!(":{sn} NOTICE {nick} :Your SIGNORE list is empty."));
} else {
for m in &list {
s.send(uid, format!(":{sn} NOTICE {nick} :SIGNORE {m}"));
}
}
s.send(uid, format!(":{sn} NOTICE {nick} :End of SIGNORE list."));
}
/// SIGNORE — a personal, mutual server-side ignore. `SIGNORE <mask>` (or `+mask`)
/// blocks a user both ways: neither of you sees the other's channel or private
/// messages. `SIGNORE -<mask>` lifts it; a bare `SIGNORE` lists your masks. A bare
/// nick becomes `nick!*@*`.
struct Signore;
impl Command for Signore {
fn name(&self) -> &'static str {
"SIGNORE"
}
fn handle(&self, s: &mut Server, uid: Uid, params: &[String]) -> CmdResult {
let Some(arg) = params.first() else {
signore_list(s, uid);
return CmdResult::Ok;
};
let nick = s.users.get(&uid).map(|u| u.nick.clone()).unwrap_or_default();
let sn = s.name.clone();
let (add, raw) = match arg.strip_prefix('-') {
Some(m) => (false, m),
None => (true, arg.strip_prefix('+').unwrap_or(arg)),
};
if raw.is_empty() {
signore_list(s, uid);
return CmdResult::Ok;
}
let mask = normalize_mask(raw);
if add {
let max = s.conf_num("maxsignore", 64usize);
let full = s
.users
.get(&uid)
.map(|u| u.signore.len() >= max && !u.signore.contains(&mask))
.unwrap_or(true);
if full {
s.send(uid, format!(":{sn} NOTICE {nick} :Your SIGNORE list is full ({max} max)."));
return CmdResult::Fail;
}
if let Some(u) = s.users.get_mut(&uid) {
if !u.signore.contains(&mask) {
u.signore.push(mask.clone());
}
}
s.send(uid, format!(":{sn} NOTICE {nick} :SIGNORE \x02{mask}\x02 added — you and they can no longer see each other's messages."));
} else {
if let Some(u) = s.users.get_mut(&uid) {
u.signore.retain(|x| x != &mask);
}
s.send(uid, format!(":{sn} NOTICE {nick} :SIGNORE \x02{mask}\x02 removed."));
}
CmdResult::Ok
}
}
// --- ACCEPT (callerid +g allow-list) ----------------------------------------
fn accept_list(s: &Server, uid: Uid) {