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
|
|
@ -1049,6 +1049,10 @@ pub trait Store {
|
||||||
fn bots(&self) -> Vec<BotView>;
|
fn bots(&self) -> Vec<BotView>;
|
||||||
fn assign_bot(&mut self, channel: &str, bot: &str) -> Result<(), ChanError>;
|
fn assign_bot(&mut self, channel: &str, bot: &str) -> Result<(), ChanError>;
|
||||||
fn unassign_bot(&mut self, channel: &str) -> Result<bool, ChanError>;
|
fn unassign_bot(&mut self, channel: &str) -> Result<bool, ChanError>;
|
||||||
|
// BotServ AUTOASSIGN: the bot auto-assigned to newly registered channels
|
||||||
|
// (its canonical nick if set and still present), and the setter.
|
||||||
|
fn default_bot(&self) -> Option<String>;
|
||||||
|
fn set_default_bot(&mut self, bot: Option<&str>) -> Result<(), ChanError>;
|
||||||
// MemoServ: per-account memos (index is a 0-based position in memo_list).
|
// MemoServ: per-account memos (index is a 0-based position in memo_list).
|
||||||
fn memo_send(&mut self, account: &str, from: &str, text: &str, receipt: bool) -> Result<(), RegError>;
|
fn memo_send(&mut self, account: &str, from: &str, text: &str, receipt: bool) -> Result<(), RegError>;
|
||||||
fn memo_list(&self, account: &str) -> Vec<MemoView>;
|
fn memo_list(&self, account: &str) -> Vec<MemoView>;
|
||||||
|
|
|
||||||
34
modules/botserv/src/autoassign.rs
Normal file
34
modules/botserv/src/autoassign.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
use echo_api::{Priv, Sender, ServiceCtx, Store};
|
||||||
|
|
||||||
|
// AUTOASSIGN <bot> | OFF: set the bot automatically assigned to every newly
|
||||||
|
// registered channel, or turn auto-assignment off. Oper-only (Priv::Admin) —
|
||||||
|
// it's a network-wide default. No argument reports the current setting.
|
||||||
|
pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||||
|
if !from.privs.has(Priv::Admin) {
|
||||||
|
ctx.notice(me, from.uid, "Access denied — AUTOASSIGN is for services operators.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(&arg) = args.get(1) else {
|
||||||
|
match db.default_bot() {
|
||||||
|
Some(bot) => ctx.notice(me, from.uid, format!("New channels are auto-assigned \x02{bot}\x02. Use \x02AUTOASSIGN OFF\x02 to stop.")),
|
||||||
|
None => ctx.notice(me, from.uid, "No bot is auto-assigned to new channels. Set one with \x02AUTOASSIGN <bot>\x02."),
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if arg.eq_ignore_ascii_case("OFF") || arg.eq_ignore_ascii_case("NONE") {
|
||||||
|
match db.set_default_bot(None) {
|
||||||
|
Ok(()) => ctx.notice(me, from.uid, "New channels will no longer be auto-assigned a bot."),
|
||||||
|
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Store the bot's canonical nick, so the setting survives a re-cased lookup.
|
||||||
|
let Some(bot) = db.bots().into_iter().find(|b| b.nick.eq_ignore_ascii_case(arg)) else {
|
||||||
|
ctx.notice(me, from.uid, format!("There's no bot named \x02{arg}\x02. See \x02BOTLIST\x02."));
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
match db.set_default_bot(Some(&bot.nick)) {
|
||||||
|
Ok(()) => ctx.notice(me, from.uid, format!("New channels will be auto-assigned \x02{}\x02.", bot.nick)),
|
||||||
|
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||||
|
}
|
||||||
|
}
|
||||||
19
modules/botserv/src/botlist.rs
Normal file
19
modules/botserv/src/botlist.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
use echo_api::{Priv, Sender, ServiceCtx, Store};
|
||||||
|
|
||||||
|
// BOTLIST: show the bots a channel founder can assign. Available to everyone,
|
||||||
|
// unlike \x02BOT LIST\x02 (operator bot administration). Private bots are hidden
|
||||||
|
// from ordinary users but shown, flagged, to operators.
|
||||||
|
pub fn handle(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &dyn Store) {
|
||||||
|
let is_oper = from.privs.has(Priv::Admin);
|
||||||
|
let bots: Vec<_> = db.bots().into_iter().filter(|b| is_oper || !b.private).collect();
|
||||||
|
if bots.is_empty() {
|
||||||
|
ctx.notice(me, from.uid, "No bots are available.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.notice(me, from.uid, format!("Available bots ({}):", bots.len()));
|
||||||
|
for b in &bots {
|
||||||
|
let flag = if b.private { " \x02[private]\x02" } else { "" };
|
||||||
|
ctx.notice(me, from.uid, format!(" \x02{}\x02 ({}@{}){flag}", b.nick, b.user, b.host));
|
||||||
|
}
|
||||||
|
ctx.notice(me, from.uid, "Assign one with \x02ASSIGN <#channel> <bot>\x02.");
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,10 @@ use echo_api::{HelpEntry, NetView, Priv, Sender, Service, ServiceCtx, Store};
|
||||||
|
|
||||||
#[path = "bot.rs"]
|
#[path = "bot.rs"]
|
||||||
mod bot;
|
mod bot;
|
||||||
|
#[path = "botlist.rs"]
|
||||||
|
mod botlist;
|
||||||
|
#[path = "autoassign.rs"]
|
||||||
|
mod autoassign;
|
||||||
#[path = "assign.rs"]
|
#[path = "assign.rs"]
|
||||||
mod assign;
|
mod assign;
|
||||||
#[path = "info.rs"]
|
#[path = "info.rs"]
|
||||||
|
|
@ -50,6 +54,8 @@ const TOPICS: &[HelpEntry] = &[
|
||||||
HelpEntry { cmd: "BADWORDS", summary: "manage badword patterns", detail: "Syntax: \x02BADWORDS <#channel> ADD|DEL|LIST|CLEAR [pattern]\x02\nManages the channel's badword patterns." },
|
HelpEntry { cmd: "BADWORDS", summary: "manage badword patterns", detail: "Syntax: \x02BADWORDS <#channel> ADD|DEL|LIST|CLEAR [pattern]\x02\nManages the channel's badword patterns." },
|
||||||
HelpEntry { cmd: "TRIGGER", summary: "manage auto-responses", detail: "Syntax: \x02TRIGGER <#channel> ADD <regex>|<response>[|<cooldown>] | DEL <num> | LIST | CLEAR\x02\nManages the channel's auto-response triggers." },
|
HelpEntry { cmd: "TRIGGER", summary: "manage auto-responses", detail: "Syntax: \x02TRIGGER <#channel> ADD <regex>|<response>[|<cooldown>] | DEL <num> | LIST | CLEAR\x02\nManages the channel's auto-response triggers." },
|
||||||
HelpEntry { cmd: "COPY", summary: "clone bot settings", detail: "Syntax: \x02COPY <#source> <#dest>\x02\nClones a channel's bot settings to another channel." },
|
HelpEntry { cmd: "COPY", summary: "clone bot settings", detail: "Syntax: \x02COPY <#source> <#dest>\x02\nClones a channel's bot settings to another channel." },
|
||||||
|
HelpEntry { cmd: "BOTLIST", summary: "list assignable bots", detail: "Syntax: \x02BOTLIST\x02\nLists the service bots you can assign to a channel." },
|
||||||
|
HelpEntry { cmd: "AUTOASSIGN", summary: "default bot for new channels (operator)", detail: "Syntax: \x02AUTOASSIGN <bot> | OFF\x02\nSets the bot auto-assigned to every newly registered channel. Operators only." },
|
||||||
HelpEntry { cmd: "BOT", summary: "manage the bots (operator)", detail: "Syntax: \x02BOT ADD|CHANGE|DEL|LIST\x02\nCreates and manages the service bots themselves. Operators only." },
|
HelpEntry { cmd: "BOT", summary: "manage the bots (operator)", detail: "Syntax: \x02BOT ADD|CHANGE|DEL|LIST\x02\nCreates and manages the service bots themselves. Operators only." },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -76,6 +82,8 @@ impl Service for BotServ {
|
||||||
let me = self.uid.as_str();
|
let me = self.uid.as_str();
|
||||||
match args.first().map(|s| s.to_ascii_uppercase()).as_deref() {
|
match args.first().map(|s| s.to_ascii_uppercase()).as_deref() {
|
||||||
Some("BOT") => bot::handle(me, from, args, ctx, db),
|
Some("BOT") => bot::handle(me, from, args, ctx, db),
|
||||||
|
Some("BOTLIST") => botlist::handle(me, from, ctx, db),
|
||||||
|
Some("AUTOASSIGN") => autoassign::handle(me, from, args, ctx, db),
|
||||||
Some("ASSIGN") => assign::handle(me, from, args, ctx, db, true),
|
Some("ASSIGN") => assign::handle(me, from, args, ctx, db, true),
|
||||||
Some("UNASSIGN") => assign::handle(me, from, args, ctx, db, false),
|
Some("UNASSIGN") => assign::handle(me, from, args, ctx, db, false),
|
||||||
Some("INFO") => info::handle(me, from, args, ctx, db),
|
Some("INFO") => info::handle(me, from, args, ctx, db),
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,12 @@ impl Service for ChanServ {
|
||||||
ctx.channel_mode(me, chan, "+r"); // mark the channel registered
|
ctx.channel_mode(me, chan, "+r"); // mark the channel registered
|
||||||
ctx.count("chanserv.register");
|
ctx.count("chanserv.register");
|
||||||
ctx.notice(me, from.uid, format!("\x02{chan}\x02 is now registered and you are its founder. Enjoy!"));
|
ctx.notice(me, from.uid, format!("\x02{chan}\x02 is now registered and you are its founder. Enjoy!"));
|
||||||
|
// Auto-assign the network's default bot, if one is set (BotServ AUTOASSIGN).
|
||||||
|
if let Some(bot) = db.default_bot() {
|
||||||
|
if db.assign_bot(chan, &bot).is_ok() {
|
||||||
|
ctx.notice(me, from.uid, format!("Assigned \x02{bot}\x02 to \x02{chan}\x02. Change it with \x02/msg BotServ ASSIGN\x02."));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(ChanError::Exists) => ctx.notice(me, from.uid, format!("\x02{chan}\x02 is already registered. Try \x02INFO {chan}\x02 to see who owns it.")),
|
Err(ChanError::Exists) => ctx.notice(me, from.uid, format!("\x02{chan}\x02 is already registered. Try \x02INFO {chan}\x02 to see who owns it.")),
|
||||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||||
|
|
|
||||||
|
|
@ -562,6 +562,22 @@ impl Db {
|
||||||
self.bots.values()
|
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.
|
/// Assign a bot to a channel.
|
||||||
pub fn assign_bot(&mut self, channel: &str, bot: &str) -> Result<(), ChanError> {
|
pub fn assign_bot(&mut self, channel: &str, bot: &str) -> Result<(), ChanError> {
|
||||||
let k = key(channel);
|
let k = key(channel);
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,8 @@ pub enum Event {
|
||||||
ChannelBotUnassigned { channel: String },
|
ChannelBotUnassigned { channel: String },
|
||||||
BotAdded(Bot),
|
BotAdded(Bot),
|
||||||
BotRemoved { nick: String },
|
BotRemoved { nick: String },
|
||||||
|
// The bot auto-assigned to newly registered channels (BotServ AUTOASSIGN).
|
||||||
|
DefaultBotSet { bot: Option<String> },
|
||||||
VhostOfferAdded { host: String },
|
VhostOfferAdded { host: String },
|
||||||
VhostOfferRemoved { host: String },
|
VhostOfferRemoved { host: String },
|
||||||
VhostForbidAdded { pattern: String },
|
VhostForbidAdded { pattern: String },
|
||||||
|
|
@ -211,6 +213,7 @@ impl Event {
|
||||||
| Event::ChannelBotUnassigned { .. }
|
| Event::ChannelBotUnassigned { .. }
|
||||||
| Event::BotAdded(_)
|
| Event::BotAdded(_)
|
||||||
| Event::BotRemoved { .. }
|
| Event::BotRemoved { .. }
|
||||||
|
| Event::DefaultBotSet { .. }
|
||||||
| Event::VhostOfferAdded { .. }
|
| Event::VhostOfferAdded { .. }
|
||||||
| Event::VhostOfferRemoved { .. }
|
| Event::VhostOfferRemoved { .. }
|
||||||
| Event::VhostForbidAdded { .. }
|
| Event::VhostForbidAdded { .. }
|
||||||
|
|
@ -469,6 +472,13 @@ pub(crate) fn apply(accounts: &mut HashMap<String, Account>, channels: &mut Hash
|
||||||
}
|
}
|
||||||
Event::BotRemoved { nick } => {
|
Event::BotRemoved { nick } => {
|
||||||
bots.remove(&key(&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 } => {
|
Event::VhostOfferAdded { host } => {
|
||||||
if !host_cfg.offers.iter().any(|o| o == &host) {
|
if !host_cfg.offers.iter().any(|o| o == &host) {
|
||||||
|
|
|
||||||
|
|
@ -286,6 +286,9 @@ pub struct NetData {
|
||||||
pub opers: HashMap<String, OperGrant>,
|
pub opers: HashMap<String, OperGrant>,
|
||||||
// Session-limit exceptions (OperServ SESSION): per-IP-mask allowances.
|
// Session-limit exceptions (OperServ SESSION): per-IP-mask allowances.
|
||||||
pub sess_exceptions: Vec<SessionException>,
|
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
|
// 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() {
|
for b in self.bots.values() {
|
||||||
snapshot.push(Event::BotAdded(b.clone()));
|
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 {
|
for host in &self.host_cfg.offers {
|
||||||
snapshot.push(Event::VhostOfferAdded { host: host.clone() });
|
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> {
|
fn unassign_bot(&mut self, channel: &str) -> Result<bool, ChanError> {
|
||||||
Db::unassign_bot(self, channel)
|
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> {
|
fn memo_send(&mut self, account: &str, from: &str, text: &str, receipt: bool) -> Result<(), RegError> {
|
||||||
Db::memo_send(self, account, from, text, receipt)
|
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"),
|
ChannelBotUnassigned { channel } => format!("removed the bot from \x02{channel}\x02"),
|
||||||
BotAdded(b) => format!("added bot \x02{}\x02", b.nick),
|
BotAdded(b) => format!("added bot \x02{}\x02", b.nick),
|
||||||
BotRemoved { nick } => format!("removed bot \x02{nick}\x02"),
|
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"),
|
VhostOfferAdded { host } => format!("added vhost offer \x02{host}\x02"),
|
||||||
VhostOfferRemoved { host } => format!("removed vhost offer \x02{host}\x02"),
|
VhostOfferRemoved { host } => format!("removed vhost offer \x02{host}\x02"),
|
||||||
VhostForbidAdded { pattern } => format!("forbade vhost pattern \x02{pattern}\x02"),
|
VhostForbidAdded { pattern } => format!("forbade vhost pattern \x02{pattern}\x02"),
|
||||||
|
|
|
||||||
|
|
@ -1329,6 +1329,54 @@
|
||||||
assert!(notice(&bs(&mut e, "000AAAAAC", "BOT LIST"), "Bendy"));
|
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.
|
// A bot is introduced on the network when added, and quit when deleted.
|
||||||
#[test]
|
#[test]
|
||||||
fn botserv_introduces_and_quits_bots() {
|
fn botserv_introduces_and_quits_bots() {
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,7 @@ fn to_wire(entry: &LogEntry) -> Option<ReplicationEvent> {
|
||||||
| Event::ChannelBotUnassigned { .. }
|
| Event::ChannelBotUnassigned { .. }
|
||||||
| Event::BotAdded(_)
|
| Event::BotAdded(_)
|
||||||
| Event::BotRemoved { .. }
|
| Event::BotRemoved { .. }
|
||||||
|
| Event::DefaultBotSet { .. }
|
||||||
| Event::VhostOfferAdded { .. }
|
| Event::VhostOfferAdded { .. }
|
||||||
| Event::VhostOfferRemoved { .. }
|
| Event::VhostOfferRemoved { .. }
|
||||||
| Event::VhostForbidAdded { .. }
|
| Event::VhostForbidAdded { .. }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue