From f2ba876ce431debfd1af25624a49a7349d8485e6 Mon Sep 17 00:00:00 2001 From: Jean Date: Fri, 17 Jul 2026 17:11:25 +0000 Subject: [PATCH] botserv: bots join +ao (protected admin + op), keeping op alongside protect --- api/src/lib.rs | 4 ++-- modules/protocol/inspircd/src/lib.rs | 8 ++++---- src/engine/mod.rs | 7 ++++--- src/engine/tests.rs | 4 ++-- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/api/src/lib.rs b/api/src/lib.rs index aa71ff8..00a0bfd 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -118,8 +118,8 @@ pub enum NetAction { // Remove one of our pseudo-clients (e.g. a deleted bot) from the network. QuitUser { uid: String, reason: String }, // A services pseudo-client (a bot) joins / parts a channel. `modes` are the - // status-mode letters it joins with: "a" (protected admin) for BotServ bots, - // "o" for the core service pseudo-clients in the services channel. + // status-mode letters it joins with: "ao" (protected admin + op) for BotServ + // bots, "o" for the core service pseudo-clients in the services channel. ServiceJoin { uid: String, channel: String, modes: String }, ServicePart { uid: String, channel: String }, // Set channel modes from services, e.g. +r on a registered channel. `from` is diff --git a/modules/protocol/inspircd/src/lib.rs b/modules/protocol/inspircd/src/lib.rs index 423182d..90a355c 100644 --- a/modules/protocol/inspircd/src/lib.rs +++ b/modules/protocol/inspircd/src/lib.rs @@ -354,8 +354,8 @@ impl Protocol for InspIrcd { // IJOIN needs a membid (`IJOIN `); without it the // ircd rejects the whole link with "Insufficient parameters". The // trailing "1 " is the 4-param form (ts, status modes) — a - // BotServ bot joins "+a" (protected admin), core services "+o". ts 1 - // keeps it below the channel TS so the status is always applied. + // BotServ bot joins "+ao" (protected admin + op), core services "+o". + // ts 1 keeps it below the channel TS so the status is always applied. self.membid += 1; vec![format!(":{} IJOIN {} {} 1 {}", uid, channel, self.membid, modes)] } @@ -745,8 +745,8 @@ mod tests { #[test] fn service_join_ijoin_includes_membid() { let mut p = proto(); - let a = p.serialize(&NetAction::ServiceJoin { uid: "00DB00000".into(), channel: "#taverne".into(), modes: "a".into() }); - assert_eq!(a, vec![":00DB00000 IJOIN #taverne 1 1 a".to_string()], "bot joins +a (protected) with a membid"); + let a = p.serialize(&NetAction::ServiceJoin { uid: "00DB00000".into(), channel: "#taverne".into(), modes: "ao".into() }); + assert_eq!(a, vec![":00DB00000 IJOIN #taverne 1 1 ao".to_string()], "bot joins +ao (protected admin + op) with a membid"); // membid is monotonic across joins; core services join +o let b = p.serialize(&NetAction::ServiceJoin { uid: "00DB00000".into(), channel: "#quizz".into(), modes: "o".into() }); assert_eq!(b, vec![":00DB00000 IJOIN #quizz 2 1 o".to_string()]); diff --git a/src/engine/mod.rs b/src/engine/mod.rs index 063c30e..9dd181d 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -444,9 +444,10 @@ impl Engine { for (bot_lc, chan) in &desired { if !self.bot_channels.contains(&(bot_lc.clone(), chan.clone())) { if let Some(uid) = self.bot_uids.get(bot_lc) { - // BotServ bots join as protected admins (+a) so ordinary ops - // can't kick or deop them — the standard bot default. - out.push(NetAction::ServiceJoin { uid: uid.clone(), channel: chan.clone(), modes: "a".into() }); + // BotServ bots join as protected admins + op (+ao) so they hold + // op and ordinary ops still can't kick or deop them — the + // standard bot default. + out.push(NetAction::ServiceJoin { uid: uid.clone(), channel: chan.clone(), modes: "ao".into() }); self.bot_channels.insert((bot_lc.clone(), chan.clone())); } } diff --git a/src/engine/tests.rs b/src/engine/tests.rs index 5f10dea..45c12a9 100644 --- a/src/engine/tests.rs +++ b/src/engine/tests.rs @@ -2217,9 +2217,9 @@ ns(&mut e, "000AAAAAC", "IDENTIFY password1"); bs(&mut e, "000AAAAAC", "BOT ADD Bendy bot serv.host Helper"); // goes live - // The founder assigns the bot: it joins the channel as a protected admin (+a). + // The founder assigns the bot: it joins as a protected admin + op (+ao). let out = bs(&mut e, "000AAAAAB", "ASSIGN #c Bendy"); - assert!(out.iter().any(|a| matches!(a, NetAction::ServiceJoin { channel, modes, .. } if channel == "#c" && modes == "a")), "joins +a on assign: {out:?}"); + assert!(out.iter().any(|a| matches!(a, NetAction::ServiceJoin { channel, modes, .. } if channel == "#c" && modes == "ao")), "joins +ao on assign: {out:?}"); // Unassigning parts it. let out = bs(&mut e, "000AAAAAB", "UNASSIGN #c"); assert!(out.iter().any(|a| matches!(a, NetAction::ServicePart { channel, .. } if channel == "#c")), "parts on unassign: {out:?}");