BotServ kickers: caps + formatting, with DONTKICKOPS

The assigned bot now enforces kickers on channel lines: KICK <#channel>
CAPS ON [min [percent]], and BOLDS/COLORS/UNDERLINES/REVERSES/ITALICS
for control-code formatting. DONTKICKOPS exempts channel operators. A
tripping line is kicked by the bot itself.

Kicker config is a typed KickerSettings struct on the channel (event-
sourced like ChanSettings) with a violation() check; the engine runs it
on every channel PRIVMSG. Factored the founder-or-admin gate into a
shared require_channel_admin helper (assign/set/kick).
This commit is contained in:
Jean Chevronnet 2026-07-13 14:54:14 +00:00
parent ad8041bd9d
commit ed7da9c713
No known key found for this signature in database
8 changed files with 310 additions and 21 deletions

View file

@ -2,7 +2,7 @@
//! and (in later slices) run fantasy commands. `lib.rs` holds the dispatcher;
//! each command lives in its own file, matching NickServ/ChanServ.
use fedserv_api::{NetView, Sender, Service, ServiceCtx, Store};
use fedserv_api::{NetView, Priv, Sender, Service, ServiceCtx, Store};
#[path = "bot.rs"]
mod bot;
@ -14,6 +14,22 @@ mod info;
mod say;
#[path = "set.rs"]
mod set;
#[path = "kick.rs"]
mod kick;
// Shared gate: the sender may administer <chan>'s bot options only as its
// founder or a services admin. Notices and returns false on failure.
fn require_channel_admin(me: &str, from: &Sender, chan: &str, ctx: &mut ServiceCtx, db: &dyn Store) -> bool {
let Some(founder) = db.channel(chan).map(|c| c.founder) else {
ctx.notice(me, from.uid, format!("\x02{chan}\x02 isn't registered."));
return false;
};
if from.account != Some(founder.as_str()) && !from.privs.has(Priv::Admin) {
ctx.notice(me, from.uid, format!("Only \x02{chan}\x02's founder can change that."));
return false;
}
true
}
pub struct BotServ {
pub uid: String,
@ -40,7 +56,8 @@ impl Service for BotServ {
Some("SAY") => say::handle(me, from, args, ctx, net, db, false),
Some("ACT") => say::handle(me, from, args, ctx, net, db, true),
Some("SET") => set::handle(me, from, args, ctx, db),
Some("HELP") | None => ctx.notice(me, from.uid, "BotServ keeps service bots for your channels: \x02ASSIGN\x02 <#channel> <bot> puts a bot in your channel, \x02UNASSIGN\x02 <#channel> removes it, \x02INFO\x02 <bot|#channel> shows details, \x02SAY\x02/\x02ACT\x02 <#channel> <text> speaks through the bot, \x02SET\x02 <#channel> GREET <on|off> toggles greets. Operators also have \x02BOT\x02 ADD|DEL|LIST."),
Some("KICK") => kick::handle(me, from, args, ctx, db),
Some("HELP") | None => ctx.notice(me, from.uid, "BotServ keeps service bots for your channels: \x02ASSIGN\x02 <#channel> <bot> puts a bot in your channel, \x02UNASSIGN\x02 <#channel> removes it, \x02INFO\x02 <bot|#channel> shows details, \x02SAY\x02/\x02ACT\x02 <#channel> <text> speaks through the bot, \x02SET\x02 <#channel> GREET <on|off> toggles greets, \x02KICK\x02 <#channel> <type> <on|off> configures kickers. Operators also have \x02BOT\x02 ADD|DEL|LIST."),
Some(other) => ctx.notice(me, from.uid, format!("I don't know the command \x02{other}\x02. Try \x02HELP\x02.")),
}
}