BotServ: add BOTLIST and AUTOASSIGN default bot
All checks were successful
CI / check (push) Successful in 3m33s
All checks were successful
CI / check (push) Successful in 3m33s
This commit is contained in:
parent
cd6d8b1b93
commit
dd43b9a331
12 changed files with 162 additions and 0 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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() });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1119,6 +1119,10 @@ fn audit_summary(event: &db::Event) -> Option<String> {
|
|||
ChannelBotUnassigned { channel } => format!("removed the bot from \x02{channel}\x02"),
|
||||
BotAdded(b) => format!("added bot \x02{}\x02", b.nick),
|
||||
BotRemoved { nick } => format!("removed bot \x02{nick}\x02"),
|
||||
DefaultBotSet { bot } => match bot {
|
||||
Some(b) => format!("set the auto-assign bot to \x02{b}\x02"),
|
||||
None => "cleared the auto-assign bot".to_string(),
|
||||
},
|
||||
VhostOfferAdded { host } => format!("added vhost offer \x02{host}\x02"),
|
||||
VhostOfferRemoved { host } => format!("removed vhost offer \x02{host}\x02"),
|
||||
VhostForbidAdded { pattern } => format!("forbade vhost pattern \x02{pattern}\x02"),
|
||||
|
|
|
|||
|
|
@ -1329,6 +1329,54 @@
|
|||
assert!(notice(&bs(&mut e, "000AAAAAC", "BOT LIST"), "Bendy"));
|
||||
}
|
||||
|
||||
// BotServ AUTOASSIGN sets a default bot that new channels get automatically,
|
||||
// and BOTLIST shows assignable bots to anyone.
|
||||
#[test]
|
||||
fn botserv_autoassign_and_botlist() {
|
||||
use echo_botserv::BotServ;
|
||||
use echo_chanserv::ChanServ;
|
||||
use echo_nickserv::NickServ;
|
||||
let path = std::env::temp_dir().join("echo-bsautoassign.jsonl");
|
||||
let _ = std::fs::remove_file(&path);
|
||||
let mut db = Db::open(&path, "42S");
|
||||
db.scram_iterations = 4096;
|
||||
db.register("staff", "password1", None).unwrap();
|
||||
let mut e = Engine::new(
|
||||
vec![
|
||||
Box::new(NickServ { uid: "42SAAAAAA".into(), guest_nick: "Guest".into(), guest_seq: 0 }),
|
||||
Box::new(ChanServ { uid: "42SAAAAAB".into() }),
|
||||
Box::new(BotServ { uid: "42SAAAAAD".into() }),
|
||||
],
|
||||
db,
|
||||
);
|
||||
let mut opers = std::collections::HashMap::new();
|
||||
opers.insert("staff".to_string(), Privs::default().with(echo_api::Priv::Admin));
|
||||
e.set_opers(opers);
|
||||
let bs = |e: &mut Engine, uid: &str, t: &str| e.handle(NetEvent::Privmsg { from: uid.into(), to: "42SAAAAAD".into(), text: t.into() });
|
||||
let cs = |e: &mut Engine, uid: &str, t: &str| e.handle(NetEvent::Privmsg { from: uid.into(), to: "42SAAAAAB".into(), text: t.into() });
|
||||
let notice = |out: &[NetAction], n: &str| out.iter().any(|a| matches!(a, NetAction::Notice { text, .. } if text.contains(n)));
|
||||
|
||||
e.handle(NetEvent::UserConnect { uid: "000AAAAAC".into(), nick: "staff".into(), host: "h".into(), ip: "0.0.0.0".into() });
|
||||
e.handle(NetEvent::Privmsg { from: "000AAAAAC".into(), to: "42SAAAAAA".into(), text: "IDENTIFY password1".into() });
|
||||
bs(&mut e, "000AAAAAC", "BOT ADD Bendy bot serv.host A Helper");
|
||||
|
||||
// BOTLIST shows the bot; setting a nonexistent default is rejected.
|
||||
assert!(notice(&bs(&mut e, "000AAAAAC", "BOTLIST"), "Bendy"), "botlist shows the bot");
|
||||
assert!(notice(&bs(&mut e, "000AAAAAC", "AUTOASSIGN Ghost"), "no bot named"), "unknown default rejected");
|
||||
assert!(notice(&bs(&mut e, "000AAAAAC", "AUTOASSIGN Bendy"), "auto-assigned"), "default set");
|
||||
assert_eq!(e.db.default_bot(), Some("Bendy"), "default bot stored canonically");
|
||||
|
||||
// Registering a new channel auto-assigns the default bot.
|
||||
e.handle(NetEvent::Join { uid: "000AAAAAC".into(), channel: "#new".into(), op: true });
|
||||
let out = cs(&mut e, "000AAAAAC", "REGISTER #new");
|
||||
assert!(notice(&out, "Assigned"), "bot auto-assigned on register: {out:?}");
|
||||
assert_eq!(e.db.channel("#new").and_then(|c| c.assigned_bot.clone()), Some("Bendy".to_string()), "assignment recorded");
|
||||
|
||||
// Turning it off stops auto-assignment.
|
||||
assert!(notice(&bs(&mut e, "000AAAAAC", "AUTOASSIGN OFF"), "no longer"), "default cleared");
|
||||
assert_eq!(e.db.default_bot(), None, "default bot cleared");
|
||||
}
|
||||
|
||||
// A bot is introduced on the network when added, and quit when deleted.
|
||||
#[test]
|
||||
fn botserv_introduces_and_quits_bots() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue