helpmode: oper-settable user mode +h (helpop) shown in whois

This commit is contained in:
Jean Chevronnet 2026-08-10 09:01:38 +00:00
parent 512185d6df
commit 71d6e7441b
4 changed files with 34 additions and 2 deletions

View file

@ -120,6 +120,8 @@ amu_target = both
# --- chanlog (m_chanlog): mirror the oper server-notice stream into a channel so
# staff can watch it in a normal window. Set the channel (create/keep it opped):
# chanlog = #snotices
# --- helpmode (m_helpmode): no config — adds oper-settable user mode +h (helpop),
# which shows "is available for help" in the user's WHOIS.
# --- globops (m_globops): no config — adds the oper command /GLOBOPS <message>,
# broadcasting to all opers (like the server-notice stream).
# --- autodrop (m_autodrop): silently drop a not-yet-registered client that sends

View file

@ -253,6 +253,10 @@ impl Command for Whois {
if let Some(line) = crate::modules::profilelink::line(s, &account) {
s.numeric(uid, RPL_WHOISSPECIAL, &format!(":{line}"));
}
// helpmode: +h marks a user available for help (visible to everyone)
if s.users.get(&tuid).map(|u| u.flags.helpop).unwrap_or(false) {
s.numeric(uid, RPL_WHOISSPECIAL, ":is available for help.");
}
// customtitle: a claimed vanity title
if let Some(line) = crate::modules::customtitle::line(s, tuid) {
s.numeric(uid, RPL_WHOISSPECIAL, &format!(":{line}"));

View file

@ -1064,6 +1064,7 @@ pub fn user_mode(c: char) -> Option<&'static (dyn UserMode + Sync)> {
static USER_MODES: &[&(dyn UserMode + Sync)] = &[
&INVISIBLE,
&WALLOPS,
&HELPOP,
&OPER,
&CLOAK,
&BOT,
@ -1196,6 +1197,27 @@ impl UserMode for OperMode {
}
}
/// `+h` — helpop: marks a user as available for help (shown in WHOIS). Only opers
/// may set it on themselves; anyone may clear it.
struct HelpMode;
static HELPOP: HelpMode = HelpMode;
impl UserMode for HelpMode {
fn letter(&self) -> char {
'h'
}
fn apply(&self, s: &mut Server, uid: Uid, adding: bool) -> bool {
if adding && !s.is_oper(uid) {
return false; // only opers may declare themselves a helpop
}
if let Some(u) = s.users.get_mut(&uid) {
u.flags.helpop = adding;
true
} else {
false
}
}
}
/// `+x` — host cloaking. The cloak string is computed once at connect by
/// [`crate::modules::cloak`]; this handler only toggles whether it's shown.
/// `-x` (revealing the real host) is oper-only, so +x can't be flipped to dodge
@ -1301,7 +1323,7 @@ mod tests {
#[test]
fn registry_covers_all_user_modes() {
for c in "iwoxBDIHrRzsgWc".chars() {
for c in "iwoxBDIHrRzsgWhc".chars() {
assert!(user_mode(c).is_some(), "missing umode +{c}");
}
assert!(user_mode('Q').is_none());

View file

@ -30,6 +30,7 @@ pub struct UserFlags {
pub snomask: bool, // +s (oper: receive server notices)
pub callerid: bool, // +g (only accept PMs from users on the ACCEPT list)
pub showwhois: bool, // +W (get a notice when someone WHOISes you)
pub helpop: bool, // +h (helpop: available for help; shown in WHOIS)
pub deny_uncommon: bool, // +c (only users sharing a channel may PM you)
pub nick_locked: bool, // NICKLOCK: services/oper holds this nick (no self-change)
pub via_webirc: bool, // connected through a WEBIRC gateway (securitygroups)
@ -82,6 +83,9 @@ impl UserFlags {
if self.showwhois {
s.push('W');
}
if self.helpop {
s.push('h');
}
if self.deny_uncommon {
s.push('c');
}
@ -465,7 +469,7 @@ impl Server {
uid,
RPL_MYINFO,
&format!(
"{} echoircd-{VERSION} iowxsgBDIHrRzWc qaohvbeIklimnpstzCTcSNORMfjFLgGuBQAPJUdKXwD",
"{} echoircd-{VERSION} iowxsgBDIHrRzWhc qaohvbeIklimnpstzCTcSNORMfjFLgGuBQAPJUdKXwD",
self.name
),
);