From 5046083608216201e98f938517bba49cb71306ba Mon Sep 17 00:00:00 2001 From: reverse Date: Sun, 16 Aug 2026 18:20:57 +0000 Subject: [PATCH] add HELP command with a built-in index and per-topic help (704/705/706) --- src/coremods/core_extra.rs | 70 ++++++++++++++++++++++++++++++++++++++ src/numeric.rs | 3 ++ 2 files changed, 73 insertions(+) diff --git a/src/coremods/core_extra.rs b/src/coremods/core_extra.rs index 63508a0..77c486f 100644 --- a/src/coremods/core_extra.rs +++ b/src/coremods/core_extra.rs @@ -18,9 +18,79 @@ pub fn commands() -> Vec> { Box::new(Info), Box::new(Stats), Box::new(Map), + Box::new(Help), ] } +/// HELP [topic] — built-in help: a short index, or detail for a command group. +struct Help; +impl Command for Help { + fn name(&self) -> &'static str { + "HELP" + } + fn handle(&self, s: &mut Server, uid: Uid, params: &[String]) -> CmdResult { + let topic = params + .first() + .map(|t| t.to_ascii_uppercase()) + .unwrap_or_default(); + let (head, body): (&str, &[&str]) = match topic.as_str() { + "" => ( + "*", + &[ + "Use HELP for detail. Topics: CHANNELS SERVICES OPER.", + "Channels: JOIN PART TOPIC MODE KICK INVITE NAMES LIST WHO WHOIS.", + "Messaging: PRIVMSG, NOTICE, and AWAY to mark yourself away.", + "Accounts: authenticate with SASL, or message NickServ (/NS).", + ], + ), + "CHANNELS" => ( + "CHANNELS", + &[ + "JOIN #chan [key] join (or create) a channel", + "PART #chan [:reason] leave a channel", + "MODE #chan [+modes] view or set channel modes", + "TOPIC #chan :text set the topic (needs +t rights)", + "KICK #chan nick remove a user (needs op/halfop)", + "INVITE nick #chan invite a user to a channel", + ], + ), + "SERVICES" => ( + "SERVICES", + &[ + "Register/identify with NickServ: /NS REGISTER, /NS IDENTIFY.", + "Channel ownership via ChanServ: /CS REGISTER #chan.", + "Aliases /NS /CS /MS /OS /BS /HS message the matching service.", + ], + ), + "OPER" => ( + "OPER", + &[ + "OPER become an IRC operator", + "KILL nick :reason disconnect a user", + "KLINE/GLINE/ZLINE ban a mask (network bans propagate)", + "SAMODE/SAJOIN/SAKICK act with services authority", + "REHASH reload the config", + ], + ), + other => { + s.numeric( + uid, + RPL_HELPSTART, + &format!("{other} :No help available for that topic"), + ); + s.numeric(uid, RPL_ENDOFHELP, &format!("{other} :End of /HELP")); + return CmdResult::Ok; + } + }; + s.numeric(uid, RPL_HELPSTART, &format!("{head} :{} help", s.name)); + for line in body { + s.numeric(uid, RPL_HELPTXT, &format!("{head} :{line}")); + } + s.numeric(uid, RPL_ENDOFHELP, &format!("{head} :End of /HELP")); + CmdResult::Ok + } +} + struct List; impl Command for List { fn name(&self) -> &'static str { diff --git a/src/numeric.rs b/src/numeric.rs index 5a5eccc..0e52ca9 100644 --- a/src/numeric.rs +++ b/src/numeric.rs @@ -101,6 +101,9 @@ pub const RPL_WHOISMODES: u16 = 379; // oper/self-only: "is using modes + :Server notice mask" after a +s change pub const ERR_NEEDREGGEDNICK: u16 = 477; // chan +R/+M — must be logged into an account pub const RPL_WHOISHOST: u16 = 378; // oper-only: real host/ip behind a cloak +pub const RPL_HELPSTART: u16 = 704; // /HELP first line +pub const RPL_HELPTXT: u16 = 705; // /HELP body line +pub const RPL_ENDOFHELP: u16 = 706; // /HELP terminator pub const RPL_WHOISSECURE: u16 = 671; // "is using a secure connection" (sslinfo) pub const RPL_WHOISCERTFP: u16 = 276; // "has client certificate fingerprint " pub const RPL_HOSTHIDDEN: u16 = 396; // "is now your displayed host" (cloak on/off)