chanserv: op, deop, voice, devoice, kick, ban, unban

Adds channel moderation commands sourced from the ChanServ pseudoclient.
Tracks connected users' nicks and hosts so targets can be resolved by
nick and banned by *!*@host. All commands require operator access.
This commit is contained in:
Jean Chevronnet 2026-07-12 11:21:35 +00:00
parent f0ce2d9d1b
commit acd0e60946
No known key found for this signature in database
13 changed files with 233 additions and 26 deletions

View file

@ -72,11 +72,14 @@ impl Protocol for InspIrcd {
}
// UID <uuid> <nickts> <nick> … — track who's online so services can
// resolve a sender's current nick.
// UID <uuid> <nickts> <nick> <realhost> <disphost> … — take the
// displayed host for ban masks.
"UID" => {
let a: Vec<&str> = tokens.collect();
match (a.first(), a.get(2)) {
(Some(uid), Some(nick)) => {
vec![NetEvent::UserConnect { uid: uid.to_string(), nick: nick.to_string() }]
let host = a.get(4).unwrap_or(&"").to_string();
vec![NetEvent::UserConnect { uid: uid.to_string(), nick: nick.to_string(), host }]
}
_ => vec![],
}
@ -230,6 +233,9 @@ impl Protocol for InspIrcd {
vec![format!(":{} {}", from, cmd)]
}
}
NetAction::Kick { from, channel, uid, reason } => {
vec![format!(":{} KICK {} {} :{}", from, channel, uid, reason)]
}
NetAction::Raw(s) => vec![s.clone()],
// Internal: the link layer handles this before serialization.
NetAction::DeferRegister { .. } => vec![],
@ -273,7 +279,7 @@ mod tests {
fn parses_uid_burst() {
let ev = proto().parse(":0IR UID 0IRAAAAAB 1783833000 alice host host user user 0.0.0.0 1783833000 +i :real");
assert!(
matches!(ev.as_slice(), [NetEvent::UserConnect { uid, nick }] if uid == "0IRAAAAAB" && nick == "alice"),
matches!(ev.as_slice(), [NetEvent::UserConnect { uid, nick, .. }] if uid == "0IRAAAAAB" && nick == "alice"),
"{ev:?}"
);
}

View file

@ -10,7 +10,7 @@ pub enum NetEvent {
EndBurst,
Ping { token: String, from: Option<String> },
Privmsg { from: String, to: String, text: String },
UserConnect { uid: String, nick: String },
UserConnect { uid: String, nick: String, host: String },
NickChange { uid: String, nick: String },
// A channel was created or bursted (InspIRCd FJOIN). Subsequent single joins
// arrive as IJOIN and are not surfaced.
@ -50,6 +50,8 @@ pub enum NetAction {
// the pseudoclient uid to source it from (empty = the services server). The
// protocol stamps a timestamp the ircd will accept.
ChannelMode { from: String, channel: String, modes: String },
// Kick a user from a channel, sourced from pseudoclient `from`.
Kick { from: String, channel: String, uid: String, reason: String },
Raw(String),
// Internal only, never serialized to the wire: a registration whose password
// still needs its (expensive) key derivation. The link layer runs the