BotServ kickers: FLOOD and REPEAT

KICK <#channel> FLOOD ON [lines [secs]] kicks a user who sends too many
lines too fast (default 6 in 10s); KICK <#channel> REPEAT ON [times]
kicks one who repeats the same line (default 3, case-insensitive).

These are stateful, so the engine keeps per-channel/per-user counters in
an ephemeral map that is never event-logged. Hot-path conscious: a channel
with no kickers costs one lookup and returns; a returning speaker allocates
nothing (get_mut, not entry); repeat comparison is an allocation-free
case-folding 64-bit hash. Counters are dropped when a user parts or quits,
so the map stays bounded. Flood time is injectable for deterministic tests.
This commit is contained in:
Jean Chevronnet 2026-07-13 15:18:39 +00:00
parent ed7da9c713
commit 43def8ee23
No known key found for this signature in database
4 changed files with 264 additions and 12 deletions

View file

@ -412,6 +412,8 @@ pub enum Kicker {
Underlines,
Reverses,
Italics,
Flood,
Repeat,
// Exemption, not a rule: never kick channel operators.
DontKickOps,
}
@ -549,6 +551,8 @@ pub trait Store {
fn set_channel_setting(&mut self, channel: &str, setting: ChanSetting, on: bool) -> Result<(), ChanError>;
fn set_kicker(&mut self, channel: &str, kicker: Kicker, on: bool) -> Result<(), ChanError>;
fn set_caps_kicker(&mut self, channel: &str, caps_min: u16, caps_percent: u16) -> Result<(), ChanError>;
fn set_flood_kicker(&mut self, channel: &str, lines: u16, secs: u16) -> Result<(), ChanError>;
fn set_repeat_kicker(&mut self, channel: &str, times: u16) -> Result<(), ChanError>;
fn set_channel_topic(&mut self, channel: &str, topic: &str) -> Result<(), ChanError>;
fn suspend_channel(&mut self, channel: &str, by: &str, reason: &str, expires: Option<u64>) -> Result<(), ChanError>;
fn unsuspend_channel(&mut self, channel: &str) -> Result<bool, ChanError>;