SVSPART <nick> <#channel> [reason] forces a user out of a channel (admin-only), completing the SVS command family alongside SVSNICK and SVSJOIN. New ForcePart action rendered as the ircd SVSPART command.
This commit is contained in:
parent
53d3d63464
commit
ccbbb91fda
5 changed files with 83 additions and 0 deletions
|
|
@ -79,6 +79,7 @@ impl Service for OperServ {
|
|||
Some(cmd) if cmd.eq_ignore_ascii_case("STATS") => stats::handle(me, from, db, ctx),
|
||||
Some(cmd) if cmd.eq_ignore_ascii_case("SVSNICK") => svs::nick(me, from, args, ctx, net),
|
||||
Some(cmd) if cmd.eq_ignore_ascii_case("SVSJOIN") => svs::join(me, from, args, ctx, net),
|
||||
Some(cmd) if cmd.eq_ignore_ascii_case("SVSPART") => svs::part(me, from, args, ctx, net),
|
||||
Some(cmd) if cmd.eq_ignore_ascii_case("INFO") => info::handle(me, from, args, ctx, db),
|
||||
Some(cmd) if cmd.eq_ignore_ascii_case("OPER") => oper::handle(me, from, args, ctx, db),
|
||||
Some(cmd) if cmd.eq_ignore_ascii_case("SESSION") => session::handle_session(me, from, args, ctx, net),
|
||||
|
|
@ -112,6 +113,7 @@ const TOPICS: &[HelpEntry] = &[
|
|||
HelpEntry { cmd: "STATS", summary: "enforcement overview", detail: "Syntax: \x02STATS\x02\nShows an overview of the enforcement lists." },
|
||||
HelpEntry { cmd: "SVSNICK", summary: "force a nick change", detail: "Syntax: \x02SVSNICK <nick> <newnick>\x02\nForces a user to change nick." },
|
||||
HelpEntry { cmd: "SVSJOIN", summary: "force a channel join", detail: "Syntax: \x02SVSJOIN <nick> <#channel> [key]\x02\nForces a user to join a channel." },
|
||||
HelpEntry { cmd: "SVSPART", summary: "force a channel part", detail: "Syntax: \x02SVSPART <nick> <#channel> [reason]\x02\nForces a user out of a channel." },
|
||||
HelpEntry { cmd: "INFO", summary: "staff notes on a target", detail: "Syntax: \x02INFO <target> | INFO ADD <target> <note> | INFO DEL <target>\x02\nReads or sets staff notes on an account or channel. (Bulletins are on InfoServ.)" },
|
||||
HelpEntry { cmd: "OPER", summary: "runtime operators", detail: "Syntax: \x02OPER ADD <account> <priv[,priv]> [+duration] | OPER DEL <account> | OPER LIST\x02\nGrants or revokes runtime operator privileges: auspex, suspend, admin." },
|
||||
HelpEntry { cmd: "SESSION", summary: "inspect per-IP sessions", detail: "Syntax: \x02SESSION LIST <min> | SESSION VIEW <ip>\x02\nInspects per-IP session counts." },
|
||||
|
|
|
|||
|
|
@ -43,3 +43,26 @@ pub fn join(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net: &
|
|||
ctx.force_join(&uid, chan, args.get(3).copied().unwrap_or(""));
|
||||
ctx.notice(me, from.uid, format!("\x02{target}\x02 has been joined to \x02{chan}\x02."));
|
||||
}
|
||||
|
||||
// SVSPART <nick> <#channel> [reason]: force a user out of a channel. Admin-only.
|
||||
pub fn part(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net: &dyn NetView) {
|
||||
if !from.privs.has(Priv::Admin) {
|
||||
ctx.notice(me, from.uid, "Access denied — SVSPART needs the \x02admin\x02 privilege.");
|
||||
return;
|
||||
}
|
||||
let (Some(&target), Some(&chan)) = (args.get(1), args.get(2)) else {
|
||||
ctx.notice(me, from.uid, "Syntax: SVSPART <nick> <#channel> [reason]");
|
||||
return;
|
||||
};
|
||||
if !chan.starts_with('#') && !chan.starts_with('&') {
|
||||
ctx.notice(me, from.uid, "SVSPART targets a channel.");
|
||||
return;
|
||||
}
|
||||
let Some(uid) = net.uid_by_nick(target).map(str::to_string) else {
|
||||
ctx.notice(me, from.uid, format!("There's no \x02{target}\x02 online."));
|
||||
return;
|
||||
};
|
||||
let reason = if args.len() > 3 { args[3..].join(" ") } else { "Removed by services".to_string() };
|
||||
ctx.force_part(&uid, chan, &reason);
|
||||
ctx.notice(me, from.uid, format!("\x02{target}\x02 has been removed from \x02{chan}\x02."));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -290,6 +290,14 @@ impl Protocol for InspIrcd {
|
|||
}
|
||||
// SVSJOIN <uid> <chan> [key]: force a user into a channel, sourced from
|
||||
// the services server. Used to apply an account's auto-join list.
|
||||
// SVSPART <uid> <chan> [:reason]: force a user out of a channel.
|
||||
NetAction::ForcePart { uid, channel, reason } => {
|
||||
if reason.is_empty() {
|
||||
vec![self.sourced(format!("SVSPART {} {}", uid, channel))]
|
||||
} else {
|
||||
vec![self.sourced(format!("SVSPART {} {} :{}", uid, channel, reason))]
|
||||
}
|
||||
}
|
||||
NetAction::ForceJoin { uid, channel, key } => {
|
||||
if key.is_empty() {
|
||||
vec![self.sourced(format!("SVSJOIN {} {}", uid, channel))]
|
||||
|
|
@ -483,6 +491,14 @@ mod tests {
|
|||
assert_eq!(lines, vec![":42S ENCAP 0IR CHGIDENT 0IRAAAAAB cool".to_string()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serializes_svspart() {
|
||||
let lines = proto().serialize(&NetAction::ForcePart { uid: "0IRAAAAAB".into(), channel: "#chan".into(), reason: "bye".into() });
|
||||
assert_eq!(lines, vec![":42S SVSPART 0IRAAAAAB #chan :bye".to_string()]);
|
||||
let lines = proto().serialize(&NetAction::ForcePart { uid: "0IRAAAAAB".into(), channel: "#chan".into(), reason: String::new() });
|
||||
assert_eq!(lines, vec![":42S SVSPART 0IRAAAAAB #chan".to_string()]);
|
||||
}
|
||||
|
||||
// Free-form text with embedded line breaks must not smuggle a second line
|
||||
// onto the link: the serializer strips CR/LF/NUL from every outbound line.
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue