diff --git a/modules/chanserv/src/xop.rs b/modules/chanserv/src/xop.rs index cb3228d..d3ec348 100644 --- a/modules/chanserv/src/xop.rs +++ b/modules/chanserv/src/xop.rs @@ -1,5 +1,4 @@ -use echo_api::Store; -use echo_api::{Sender, ServiceCtx}; +use echo_api::{level_caps, Sender, ServiceCtx, Store}; // AOP/SOP/VOP <#channel> ADD | DEL | LIST — tiered // shortcuts over the access list. `level` is the access level they map to @@ -14,7 +13,14 @@ pub fn handle(me: &str, from: &Sender, word: &str, level: &str, args: &[&str], c None => ctx.notice(me, from.uid, format!("\x02{chan}\x02 isn't registered.")), Some(info) => { ctx.notice(me, from.uid, format!("{word} list for \x02{}\x02:", info.name)); - for a in info.access.iter().filter(|a| a.level == level) { + // Match by capability, not the raw level string, so entries set via + // granular FLAGS still list under the right tier (VOP = voice-only, + // AOP/SOP = op). + let want_op = level_caps(level).op; + for a in info.access.iter().filter(|a| { + let c = level_caps(&a.level); + if want_op { c.op } else { c.auto == Some("+v") && !c.op } + }) { ctx.notice(me, from.uid, format!(" \x02{}\x02", a.account)); } } diff --git a/modules/nickserv/src/alist.rs b/modules/nickserv/src/alist.rs index c8fa23c..92ee040 100644 --- a/modules/nickserv/src/alist.rs +++ b/modules/nickserv/src/alist.rs @@ -1,5 +1,4 @@ -use echo_api::Store; -use echo_api::{Sender, ServiceCtx}; +use echo_api::{level_caps, Sender, ServiceCtx, Store}; // ALIST: list the channels the sender's account founds or has access on. pub fn handle(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &dyn Store) { @@ -12,7 +11,20 @@ pub fn handle(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &dyn Store) { if c.founder.eq_ignore_ascii_case(account) { rows.push((c.name.clone(), "founder")); } else if let Some(a) = c.access.iter().find(|a| a.account.eq_ignore_ascii_case(account)) { - rows.push((c.name.clone(), if a.level == "voice" { "voice" } else { "op" })); + // Resolve the entry's capabilities rather than string-matching the + // level, so a role set via granular FLAGS (e.g. "v") reads correctly + // and isn't mislabelled "op". + let caps = level_caps(&a.level); + let role = if caps.op { + "op" + } else if caps.auto == Some("+v") { + "voice" + } else if caps.auto == Some("+h") { + "halfop" + } else { + "access" + }; + rows.push((c.name.clone(), role)); } } if rows.is_empty() {