add HELP command with a built-in index and per-topic help (704/705/706)

This commit is contained in:
Jean Chevronnet 2026-08-16 18:20:57 +00:00
parent ba9d62b172
commit 5046083608
2 changed files with 73 additions and 0 deletions

View file

@ -18,9 +18,79 @@ pub fn commands() -> Vec<Box<dyn Command>> {
Box::new(Info), Box::new(Info),
Box::new(Stats), Box::new(Stats),
Box::new(Map), 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 <topic> 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 <name> <pass> 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; struct List;
impl Command for List { impl Command for List {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {

View file

@ -101,6 +101,9 @@ pub const RPL_WHOISMODES: u16 = 379; // oper/self-only: "is using modes +<umodes
pub const RPL_SNOMASKIS: u16 = 8; // "+<mask> :Server notice mask" after a +s change pub const RPL_SNOMASKIS: u16 = 8; // "+<mask> :Server notice mask" after a +s change
pub const ERR_NEEDREGGEDNICK: u16 = 477; // chan +R/+M — must be logged into an account 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_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_WHOISSECURE: u16 = 671; // "is using a secure connection" (sslinfo)
pub const RPL_WHOISCERTFP: u16 = 276; // "has client certificate fingerprint <fp>" pub const RPL_WHOISCERTFP: u16 = 276; // "has client certificate fingerprint <fp>"
pub const RPL_HOSTHIDDEN: u16 = 396; // "is now your displayed host" (cloak on/off) pub const RPL_HOSTHIDDEN: u16 = 396; // "is now your displayed host" (cloak on/off)