echoIRCd/src/modules/rmode.rs

103 lines
3.5 KiB
Rust

//! `RMODE <channel> <listmode> [pattern]` — bulk-remove entries from a channel list
//! mode (`b` bans, `e` ban exceptions, `I` invite exceptions). With a `pattern` glob
//! only matching entries are cleared; without one, all are. Needs half-op+ (opers
//! bypass). Removals go through the normal mode engine (chunked to keep each `MODE`
//! line legal), so they broadcast and propagate like any other.
use crate::channels::{glob_match, RANK_HALFOP};
use crate::command::{CmdResult, Command};
use crate::coremods::core_mode::apply_mode;
use crate::numeric::{ERR_CHANOPRIVSNEEDED, ERR_NOSUCHCHANNEL};
use crate::server::Server;
use crate::Uid;
pub fn commands() -> Vec<Box<dyn Command>> {
vec![Box::new(RMode)]
}
struct RMode;
impl Command for RMode {
fn name(&self) -> &'static str {
"RMODE"
}
fn min_params(&self) -> usize {
2
}
fn handle(&self, s: &mut Server, uid: Uid, params: &[String]) -> CmdResult {
let chan = params[0].clone();
let key = chan.to_ascii_lowercase();
let Some(mode) = params[1].chars().find(|c| c.is_ascii_alphabetic()) else {
return CmdResult::Fail;
};
if !s.channels.contains_key(&key) {
s.numeric(uid, ERR_NOSUCHCHANNEL, &format!("{chan} :No such channel"));
return CmdResult::Fail;
}
let is_oper = s.is_oper(uid);
if !is_oper && s.rank(uid, &key) < RANK_HALFOP {
s.numeric(
uid,
ERR_CHANOPRIVSNEEDED,
&format!("{chan} :You're not a channel operator"),
);
return CmdResult::Fail;
}
// collect the matching masks from the requested list mode
let pattern = params.get(2).map(|p| p.as_str()).unwrap_or("*");
let masks: Vec<String> = {
let ch = &s.channels[&key];
let list = match mode {
'b' => &ch.bans,
'e' => &ch.excepts,
'I' => &ch.invex,
_ => {
let nick = s
.users
.get(&uid)
.map(|u| u.nick.clone())
.unwrap_or_default();
s.send(
uid,
format!(
":{} NOTICE {nick} :RMODE only supports list modes b, e, I",
s.name
),
);
return CmdResult::Fail;
}
};
list.iter()
.filter(|b| glob_match(pattern, &b.mask))
.map(|b| b.mask.clone())
.collect()
};
let removed = masks.len();
// apply in chunks so each MODE line stays within limits
for chunk in masks.chunks(6) {
let mut p = vec![
chan.clone(),
format!("-{}", mode.to_string().repeat(chunk.len())),
];
p.extend(chunk.iter().cloned());
if is_oper {
s.mode_sudo = true;
}
apply_mode(s, uid, &p);
s.mode_sudo = false;
}
let nick = s
.users
.get(&uid)
.map(|u| u.nick.clone())
.unwrap_or_default();
let plural = if removed == 1 { "y" } else { "ies" };
s.send(
uid,
format!(
":{} NOTICE {nick} :RMODE: removed {removed} +{mode} entr{plural} from {chan}",
s.name
),
);
CmdResult::Ok
}
}