engine: handle inbound KILL — forget users, reintroduce bots

KILL was never parsed, so a killed user lingered in services' state (stale
session, session-limit slot, channel membership) and a killed services bot
vanished for good. The parser now surfaces UserKilled; a killed real user is
forgotten like a QUIT (shared forget_user helper), while a killed bot is
dropped and reconciled so it is reintroduced and rejoins its channels.
This commit is contained in:
Jean Chevronnet 2026-07-16 10:06:36 +00:00
parent a9a3408de0
commit 63ecd79b12
No known key found for this signature in database
4 changed files with 84 additions and 4 deletions

View file

@ -202,6 +202,12 @@ impl Protocol for InspIrcd {
}
}
"QUIT" => vec![NetEvent::Quit { uid: source.unwrap_or_default() }],
// :<src> KILL <uid> :<reason> — a user forcibly removed. We forget
// them (or reintroduce, if it was one of our bots).
"KILL" => match tokens.next() {
Some(uid) if !uid.is_empty() => vec![NetEvent::UserKilled { uid: uid.to_string() }],
_ => vec![],
},
// ENCAP <target> <subcmd> … — we care about relayed SASL and the
// account-registration relay (the ircd's account module forwards a
// leaf's REGISTER/VERIFY/RESEND/STATUS to us, the authority server).
@ -465,6 +471,13 @@ mod tests {
assert!(p.parse(":0IR METADATA 0IRAAAAAB ssl_cert :deadbeef").is_empty(), "non-account keys ignored");
}
// A KILL surfaces as a UserKilled for the target uid.
#[test]
fn parses_kill() {
let ev = proto().parse(":0IR KILL 0IRAAAAAB :bye now");
assert!(matches!(ev.as_slice(), [NetEvent::UserKilled { uid }] if uid == "0IRAAAAAB"), "{ev:?}");
}
// A UID burst introduces the user under their current nick.
#[test]
fn parses_uid_burst() {