diff --git a/src/engine/state.rs b/src/engine/state.rs index 0726094..c5d472c 100644 --- a/src/engine/state.rs +++ b/src/engine/state.rs @@ -341,11 +341,16 @@ impl Network { } self.users.remove(uid); self.accounts.remove(uid); - for c in self.channels.values_mut() { + // Drop the membership, and forget any channel it emptied — an emptied + // channel is destroyed by the ircd, so a lingering entry would both grow + // unbounded over the process lifetime and hand stale modes (e.g. a `+k` + // key) to whoever recreates the name later. + self.channels.retain(|_, c| { c.members.remove(uid); c.ops.remove(uid); c.voices.remove(uid); - } + !c.members.is_empty() + }); } pub fn nick_of(&self, uid: &str) -> Option<&str> { @@ -389,10 +394,19 @@ impl Network { // A user left a channel (part/kick): drop membership and record last-seen. pub fn channel_part(&mut self, channel: &str, uid: &str) { - if let Some(c) = self.channels.get_mut(&lc(channel)) { + let key = lc(channel); + let emptied = if let Some(c) = self.channels.get_mut(&key) { c.members.remove(uid); c.ops.remove(uid); c.voices.remove(uid); + c.members.is_empty() + } else { + false + }; + // An emptied channel is gone on the ircd; drop it so a later channel of + // the same name can't inherit its stale modes (see user_quit). + if emptied { + self.channels.remove(&key); } if let Some(nick) = self.nick_of(uid).map(str::to_string) { self.record_seen(lc(&nick), Seen { nick, ts: now(), what: format!("leaving {channel}") });