dictserv: opt-in DICT (RFC 2229) lookup service — bots and /msg answer dict/define/thes/acronym/law/etc via dict.org off-reactor
All checks were successful
CI / check (push) Successful in 4m4s

This commit is contained in:
Jean Chevronnet 2026-07-18 20:21:30 +00:00
parent 76be932131
commit 623750e638
No known key found for this signature in database
15 changed files with 444 additions and 4 deletions

View file

@ -95,6 +95,7 @@ pub struct Engine {
sasl_source: HashMap<String, (Instant, String)>, // client uid -> real host/IP from the SASL H message
reg_limiter: RegLimiter,
cmd_limiter: CmdLimiter, // per-host flood control for service commands
dict_limiter: DictLimiter, // global rate cap on outbound DICT lookups
chan_service: Option<String>, // uid to source channel modes from (ChanServ)
nick_service: Option<String>, // uid of the account service (NickServ), for its notices
irc_out: Option<mpsc::UnboundedSender<NetAction>>, // services-initiated actions -> the uplink
@ -236,6 +237,7 @@ impl Engine {
sasl_source: HashMap::new(),
reg_limiter: RegLimiter::new(),
cmd_limiter: CmdLimiter::default(),
dict_limiter: DictLimiter::new(),
chan_service,
nick_service,
irc_out: None,
@ -2046,6 +2048,36 @@ impl RegLimiter {
}
}
// A global token bucket bounding how fast echo hits the DICT server, so a channel
// of users spamming !dict can't hammer dict.org (and get echo throttled). Bursts
// of DICT_BURST, then DICT_REFILL_PER_SEC sustained; an over-budget lookup is
// dropped — the bot simply doesn't answer that one.
struct DictLimiter {
tokens: f64,
last: Instant,
}
const DICT_BURST: f64 = 6.0;
const DICT_REFILL_PER_SEC: f64 = 1.0;
impl DictLimiter {
fn new() -> Self {
Self { tokens: DICT_BURST, last: Instant::now() }
}
fn allow(&mut self) -> bool {
let now = Instant::now();
self.tokens = (self.tokens + now.duration_since(self.last).as_secs_f64() * DICT_REFILL_PER_SEC).min(DICT_BURST);
self.last = now;
if self.tokens >= 1.0 {
self.tokens -= 1.0;
true
} else {
false
}
}
}
// Per-host flood control for anonymous service commands. A bot that connects and
// spams `/msg NickServ HELP` in a loop is warned, then silently dropped until it
// slows down — never answered per line, which would just double the flood on