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:
parent
ed7da9c713
commit
43def8ee23
4 changed files with 264 additions and 12 deletions
|
|
@ -301,6 +301,20 @@ pub struct KickerSettings {
|
|||
pub reverses: bool,
|
||||
#[serde(default)]
|
||||
pub italics: bool,
|
||||
// Kick users who send too many lines too fast.
|
||||
#[serde(default)]
|
||||
pub flood: bool,
|
||||
// Lines within `flood_secs` that trip the flood kicker (0 = default, 6).
|
||||
#[serde(default)]
|
||||
pub flood_lines: u16,
|
||||
#[serde(default)]
|
||||
pub flood_secs: u16,
|
||||
// Kick users who repeat the same line.
|
||||
#[serde(default)]
|
||||
pub repeat: bool,
|
||||
// Consecutive repeats that trip the repeat kicker (0 = default, 3).
|
||||
#[serde(default)]
|
||||
pub repeat_times: u16,
|
||||
// Don't kick channel operators, whatever they send.
|
||||
#[serde(default)]
|
||||
pub dontkickops: bool,
|
||||
|
|
@ -309,7 +323,16 @@ pub struct KickerSettings {
|
|||
impl KickerSettings {
|
||||
// Any kicker enabled? (dontkickops alone doesn't count.)
|
||||
pub fn any(&self) -> bool {
|
||||
self.caps || self.bolds || self.colors || self.underlines || self.reverses || self.italics
|
||||
self.caps || self.bolds || self.colors || self.underlines || self.reverses || self.italics || self.flood || self.repeat
|
||||
}
|
||||
|
||||
// Resolved thresholds, applying Anope's defaults for a 0 (unset) value.
|
||||
pub fn flood_thresholds(&self) -> (u16, u16) {
|
||||
(if self.flood_lines < 2 { 6 } else { self.flood_lines }, if self.flood_secs == 0 { 10 } else { self.flood_secs })
|
||||
}
|
||||
|
||||
pub fn repeat_threshold(&self) -> u16 {
|
||||
if self.repeat_times == 0 { 3 } else { self.repeat_times }
|
||||
}
|
||||
|
||||
// The reason to kick `text` for, or None if it trips no enabled kicker.
|
||||
|
|
@ -1372,6 +1395,8 @@ impl Db {
|
|||
Kicker::Underlines => k.underlines = on,
|
||||
Kicker::Reverses => k.reverses = on,
|
||||
Kicker::Italics => k.italics = on,
|
||||
Kicker::Flood => k.flood = on,
|
||||
Kicker::Repeat => k.repeat = on,
|
||||
Kicker::DontKickOps => k.dontkickops = on,
|
||||
})
|
||||
}
|
||||
|
|
@ -1385,6 +1410,23 @@ impl Db {
|
|||
})
|
||||
}
|
||||
|
||||
/// Enable the flood kicker with its lines/seconds thresholds (0 = default).
|
||||
pub fn set_flood_kicker(&mut self, channel: &str, lines: u16, secs: u16) -> Result<(), ChanError> {
|
||||
self.update_kickers(channel, |k| {
|
||||
k.flood = true;
|
||||
k.flood_lines = lines;
|
||||
k.flood_secs = secs;
|
||||
})
|
||||
}
|
||||
|
||||
/// Enable the repeat kicker with its repeat-count threshold (0 = default).
|
||||
pub fn set_repeat_kicker(&mut self, channel: &str, times: u16) -> Result<(), ChanError> {
|
||||
self.update_kickers(channel, |k| {
|
||||
k.repeat = true;
|
||||
k.repeat_times = times;
|
||||
})
|
||||
}
|
||||
|
||||
// Read-modify-write the whole kicker struct through one event.
|
||||
fn update_kickers(&mut self, channel: &str, f: impl FnOnce(&mut KickerSettings)) -> Result<(), ChanError> {
|
||||
let k = key(channel);
|
||||
|
|
@ -1963,6 +2005,12 @@ impl Store for Db {
|
|||
fn set_caps_kicker(&mut self, channel: &str, caps_min: u16, caps_percent: u16) -> Result<(), ChanError> {
|
||||
Db::set_caps_kicker(self, channel, caps_min, caps_percent)
|
||||
}
|
||||
fn set_flood_kicker(&mut self, channel: &str, lines: u16, secs: u16) -> Result<(), ChanError> {
|
||||
Db::set_flood_kicker(self, channel, lines, secs)
|
||||
}
|
||||
fn set_repeat_kicker(&mut self, channel: &str, times: u16) -> Result<(), ChanError> {
|
||||
Db::set_repeat_kicker(self, channel, times)
|
||||
}
|
||||
fn set_channel_topic(&mut self, channel: &str, topic: &str) -> Result<(), ChanError> {
|
||||
Db::set_channel_topic(self, channel, topic)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue