diff --git a/sable_ircd/src/command/handlers/help.rs b/sable_ircd/src/command/handlers/help.rs new file mode 100644 index 00000000..45d25af7 --- /dev/null +++ b/sable_ircd/src/command/handlers/help.rs @@ -0,0 +1,74 @@ +use super::*; + +use itertools::Itertools; + +#[command_handler("HELP", "UHELP")] +/// HELP [] +/// +/// HELP displays information for topic requested. +/// If no topic is requested, it will list available +/// help topics. +fn help_handler( + command: &dyn Command, + response: &dyn CommandResponse, + server: &ClientServer, + source: UserSource, + topic: Option<&str>, +) -> CommandResult { + // TODO: oper help? (and if oper help is on the same command, UHELP like solanum?) + // TODO: non-command help topics + let is_oper = command.command().to_ascii_uppercase() != "UHELP" && source.is_oper(); + + match topic { + Some(s) => { + let topic = s.to_ascii_uppercase(); + let topic = topic + .split_once(' ') + .map_or(topic.clone(), |(t, _)| t.to_string()); + + if let Some(cmd) = server.get_command(&topic) { + if cmd.docs.len() > 0 { + // TODO + if cmd.restricted && is_oper { + response.numeric(make_numeric!(HelpNotFound, &topic)); + return Ok(()); + } + let mut lines = cmd.docs.iter(); + response.numeric(make_numeric!( + HelpStart, + &topic, + lines.next().unwrap_or(&topic.as_str()) + )); + for line in lines { + response.numeric(make_numeric!(HelpText, &topic, line)); + } + response.numeric(make_numeric!(EndOfHelp, &topic)); + return Ok(()); + } + } + response.numeric(make_numeric!(HelpNotFound, &topic)); + } + None => { + let topic = "*"; + response.numeric(make_numeric!(HelpStart, topic, "Available help topics:")); + response.numeric(make_numeric!(HelpText, topic, "")); + for chunk in &server + .iter_commands() + .filter_map(|(k, v)| { + if !v.restricted || is_oper { + Some(k.to_ascii_uppercase()) + } else { + None + } + }) + .sorted() + .chunks(4) + { + let line = format!("{:16}", chunk.format(" ")); + response.numeric(make_numeric!(HelpText, topic, &line)); + } + response.numeric(make_numeric!(EndOfHelp, topic)); + } + }; + Ok(()) +} diff --git a/sable_ircd/src/command/mod.rs b/sable_ircd/src/command/mod.rs index eb63bcce..8179c4a9 100644 --- a/sable_ircd/src/command/mod.rs +++ b/sable_ircd/src/command/mod.rs @@ -42,6 +42,7 @@ mod handlers { mod ban; mod cap; mod chathistory; + mod help; mod invite; mod join; mod kick; diff --git a/sable_ircd/src/messages/numeric.rs b/sable_ircd/src/messages/numeric.rs index 627a6134..3b045fb8 100644 --- a/sable_ircd/src/messages/numeric.rs +++ b/sable_ircd/src/messages/numeric.rs @@ -67,6 +67,10 @@ define_messages! { 381(YoureOper) => { () => "You are now an IRC operator" }, + 704(HelpStart) => { (subj: &str, line: &str) => "{subj} :{line}" }, + 705(HelpText) => { (subj: &str, line: &str) => "{subj} :{line}" }, + 706(EndOfHelp) => { (subj: &str) => "{subj} :End of /HELP" }, + 401(NoSuchTarget) => { (unknown: &str) => "{unknown} :No such nick/channel" }, 402(NoSuchServer) => { (server_name: &ServerName) => "{server_name} :No such server" }, @@ -122,6 +126,8 @@ define_messages! { 440(ServicesNotAvailable) => { () => ":Services are not available"}, + 524(HelpNotFound) => { (subj: &str) => "{subj} :No help available on this topic" }, + // https://ircv3.net/specs/extensions/monitor 730(MonOnline) => { (content: &str ) => ":{content}" }, 731(MonOffline) => { (content: &str ) => ":{content}" },