BotServ: add BOTLIST and AUTOASSIGN default bot
All checks were successful
CI / check (push) Successful in 3m33s

This commit is contained in:
Jean Chevronnet 2026-07-16 00:57:06 +00:00
parent cd6d8b1b93
commit dd43b9a331
No known key found for this signature in database
12 changed files with 162 additions and 0 deletions

View file

@ -562,6 +562,22 @@ impl Db {
self.bots.values()
}
/// The bot auto-assigned to newly registered channels, if one is set and
/// still exists (its canonical nick).
pub fn default_bot(&self) -> Option<&str> {
let k = self.net.default_bot.as_ref()?;
self.bots.get(k).map(|b| b.nick.as_str())
}
/// Set (or clear) the auto-assign bot. The caller validates the bot exists;
/// the nick is stored casefolded.
pub fn set_default_bot(&mut self, bot: Option<&str>) -> Result<(), ChanError> {
let stored = bot.map(|b| b.to_string());
self.log.append(Event::DefaultBotSet { bot: stored.clone() }).map_err(|_| ChanError::Internal)?;
self.net.default_bot = stored.map(|b| key(&b));
Ok(())
}
/// Assign a bot to a channel.
pub fn assign_bot(&mut self, channel: &str, bot: &str) -> Result<(), ChanError> {
let k = key(channel);

View file

@ -56,6 +56,8 @@ pub enum Event {
ChannelBotUnassigned { channel: String },
BotAdded(Bot),
BotRemoved { nick: String },
// The bot auto-assigned to newly registered channels (BotServ AUTOASSIGN).
DefaultBotSet { bot: Option<String> },
VhostOfferAdded { host: String },
VhostOfferRemoved { host: String },
VhostForbidAdded { pattern: String },
@ -211,6 +213,7 @@ impl Event {
| Event::ChannelBotUnassigned { .. }
| Event::BotAdded(_)
| Event::BotRemoved { .. }
| Event::DefaultBotSet { .. }
| Event::VhostOfferAdded { .. }
| Event::VhostOfferRemoved { .. }
| Event::VhostForbidAdded { .. }
@ -469,6 +472,13 @@ pub(crate) fn apply(accounts: &mut HashMap<String, Account>, channels: &mut Hash
}
Event::BotRemoved { nick } => {
bots.remove(&key(&nick));
// A default bot that was just removed can't stay the default.
if net.default_bot.as_deref() == Some(key(&nick).as_str()) {
net.default_bot = None;
}
}
Event::DefaultBotSet { bot } => {
net.default_bot = bot.map(|b| key(&b));
}
Event::VhostOfferAdded { host } => {
if !host_cfg.offers.iter().any(|o| o == &host) {

View file

@ -286,6 +286,9 @@ pub struct NetData {
pub opers: HashMap<String, OperGrant>,
// Session-limit exceptions (OperServ SESSION): per-IP-mask allowances.
pub sess_exceptions: Vec<SessionException>,
// The bot auto-assigned to newly registered channels (BotServ AUTOASSIGN),
// if any. Casefolded nick; None disables auto-assignment.
pub default_bot: Option<String>,
}
// A session-limit exception: an IP-mask glob and the session allowance for IPs
@ -1060,6 +1063,9 @@ impl Db {
for b in self.bots.values() {
snapshot.push(Event::BotAdded(b.clone()));
}
if self.net.default_bot.is_some() {
snapshot.push(Event::DefaultBotSet { bot: self.net.default_bot.clone() });
}
for host in &self.host_cfg.offers {
snapshot.push(Event::VhostOfferAdded { host: host.clone() });
}

View file

@ -441,6 +441,12 @@ impl Store for Db {
fn unassign_bot(&mut self, channel: &str) -> Result<bool, ChanError> {
Db::unassign_bot(self, channel)
}
fn default_bot(&self) -> Option<String> {
Db::default_bot(self).map(str::to_string)
}
fn set_default_bot(&mut self, bot: Option<&str>) -> Result<(), ChanError> {
Db::set_default_bot(self, bot)
}
fn memo_send(&mut self, account: &str, from: &str, text: &str, receipt: bool) -> Result<(), RegError> {
Db::memo_send(self, account, from, text, receipt)
}