diff --git a/src/engine/db/mod.rs b/src/engine/db/mod.rs index 59ae3cf..899ba8c 100644 --- a/src/engine/db/mod.rs +++ b/src/engine/db/mod.rs @@ -539,7 +539,7 @@ pub struct Trigger { // BotServ's per-channel "kickers": the assigned bot kicks a message that trips // an enabled rule. Typed, like ChanSettings — a new kicker is one field. -#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] pub struct KickerSettings { #[serde(default)] pub caps: bool, @@ -1276,7 +1276,10 @@ impl Db { if let Some(bot) = &c.assigned_bot { snapshot.push(Event::ChannelBotAssigned { channel: c.name.clone(), bot: bot.clone() }); } - if c.kickers.any() || c.kickers.dontkickops || c.kickers.ttb > 0 || c.kickers.ban_expire > 0 || c.kickers.votekick > 0 { + // Snapshot whenever any kicker field differs from default, so standalone + // flags (warn, dontkickvoices) survive compaction too, not just the ones + // that force a kicker on. + if c.kickers != KickerSettings::default() { snapshot.push(Event::ChannelKickerSet { channel: c.name.clone(), kickers: c.kickers.clone() }); } if !c.badwords.is_empty() { diff --git a/src/engine/mod.rs b/src/engine/mod.rs index 48f40c0..c2a7108 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -809,8 +809,10 @@ impl Engine { } if let Some(ttl) = self.account_ttl { for account in self.db.expired_accounts(now, ttl) { - // Never expire an operator's account or one still in use. - if self.opers.contains_key(&account.to_ascii_lowercase()) || !self.network.uids_logged_into(&account).is_empty() { + // Never expire an operator's account or one still in use. oper_privs + // unions config [[oper]] AND runtime OperServ OPER grants — self.opers + // is config-only, so a runtime-granted oper was being dropped. + if self.oper_privs(&account).any() || !self.network.uids_logged_into(&account).is_empty() { continue; } if self.db.drop_account(&account).unwrap_or(false) {