add SIGNORE: personal mutual server-side ignore across channels and PMs
This commit is contained in:
parent
2c9ce18cd7
commit
6c5f330746
5 changed files with 107 additions and 1 deletions
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -390,6 +390,7 @@ impl Server {
|
|||
watch: Vec::new(),
|
||||
monitor: Vec::new(),
|
||||
silence: Vec::new(),
|
||||
signore: Vec::new(),
|
||||
accept: Vec::new(),
|
||||
quitting: None,
|
||||
flags: UserFlags::default(),
|
||||
|
|
@ -1200,6 +1201,10 @@ impl Server {
|
|||
let members: Vec<Uid> = ch.members.keys().copied().collect();
|
||||
let time_tag = format!("time={}", iso_time(now()));
|
||||
let account = self.users.get(&src).and_then(|su| su.account.clone());
|
||||
// SIGNORE (mutual server-ignore): the sender's mask + list, hoisted once.
|
||||
let src_mask = self.users.get(&src).map(|su| su.prefix()).unwrap_or_default();
|
||||
let src_signore: Vec<String> =
|
||||
self.users.get(&src).map(|su| su.signore.clone()).unwrap_or_default();
|
||||
// one cached line per (server_time, account_tag, message_tags) combination
|
||||
let mut cache: [Option<std::sync::Arc<str>>; 8] = std::array::from_fn(|_| None);
|
||||
for m in members {
|
||||
|
|
@ -1212,6 +1217,13 @@ impl Server {
|
|||
if u.flags.deaf {
|
||||
continue;
|
||||
}
|
||||
// SIGNORE: skip a member mutually server-ignored with the sender
|
||||
if (!src_signore.is_empty() || !u.signore.is_empty())
|
||||
&& (src_signore.iter().any(|p| crate::channels::glob_match(p, &u.prefix()))
|
||||
|| u.signore.iter().any(|p| crate::channels::glob_match(p, &src_mask)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if op_only && self.rank(m, key) < crate::channels::RANK_HALFOP {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -1507,6 +1519,7 @@ mod tests {
|
|||
watch: Vec::new(),
|
||||
monitor: Vec::new(),
|
||||
silence: Vec::new(),
|
||||
signore: Vec::new(),
|
||||
accept: Vec::new(),
|
||||
quitting: None,
|
||||
flags: UserFlags::default(),
|
||||
|
|
|
|||
|
|
@ -252,6 +252,7 @@ pub struct User {
|
|||
pub watch: Vec<String>, // WATCH list — lowercased nicks
|
||||
pub monitor: Vec<String>, // MONITOR list — lowercased nicks
|
||||
pub silence: Vec<String>, // SILENCE masks — nick!user@host globs
|
||||
pub signore: Vec<String>, // SIGNORE masks — mutual server-side ignore (both ways)
|
||||
pub accept: Vec<String>, // ACCEPT list — lowercased nicks (callerid +g)
|
||||
pub quitting: Option<String>, // set by QUIT; drained by the core
|
||||
pub flags: UserFlags,
|
||||
|
|
|
|||
19
src/watch.rs
19
src/watch.rs
|
|
@ -208,4 +208,23 @@ impl Server {
|
|||
.map(|u| u.silence.iter().any(|m| glob_match(m, sender_mask)))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// True if `a` and `b` mutually SIGNORE each other (either one has the other on
|
||||
/// their SIGNORE list): a bidirectional block — neither sees the other's channel
|
||||
/// or PM messages, triggered by whichever one ran SIGNORE.
|
||||
pub fn signore_blocks(&self, a: Uid, b: Uid) -> bool {
|
||||
self.signore_one_way(a, b) || self.signore_one_way(b, a)
|
||||
}
|
||||
|
||||
/// Does `by`'s SIGNORE list match `who`'s current mask?
|
||||
fn signore_one_way(&self, by: Uid, who: Uid) -> bool {
|
||||
let Some(byu) = self.users.get(&by) else {
|
||||
return false;
|
||||
};
|
||||
if byu.signore.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let mask = self.users.get(&who).map(|u| u.prefix()).unwrap_or_default();
|
||||
byu.signore.iter().any(|m| glob_match(m, &mask))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue