3.3 KiB
The Command trait
A command is a stateless handler registered by name. Implement
crate::command::Command:
pub trait Command: Send {
fn name(&self) -> &'static str; // upper-case; also the registry key
fn min_params(&self) -> usize { 0 } // fewer args ⇒ core replies 461, skips you
fn before_reg(&self) -> bool { false } // may it run before registration?
fn handle(&self, srv: &mut Server, uid: Uid, params: &[String]) -> CmdResult;
}
pub enum CmdResult { Ok, Fail } // Fail is for your own bookkeeping/logging
The core does the boilerplate for you before handle is called:
- Arity — if the client sent fewer than
min_paramsarguments, the core replies461 ERR_NEEDMOREPARAMSand never calls you. - Registration gate — unless
before_reg()returnstrue, the command is refused until the client has registered. Only handshake commands (NICK/USER/CAP/PING/QUITand the like) setbefore_reg. - Module pre-hooks —
on_pre_commandruns first and mayDenyyou.
Inside handle, params is the already-split argument list (the trailing
:parameter is a single element). Return CmdResult::Ok / Fail.
A minimal command
use crate::command::{CmdResult, Command};
use crate::server::Server;
use crate::Uid;
pub struct Ping2;
impl Command for Ping2 {
fn name(&self) -> &'static str { "PING2" }
fn min_params(&self) -> usize { 1 }
fn handle(&self, srv: &mut Server, uid: Uid, params: &[String]) -> CmdResult {
let nick = srv.users.get(&uid).map(|u| u.nick.clone()).unwrap_or_default();
srv.send(uid, format!(":{} PONG {} :{}", srv.name, nick, params[0]));
CmdResult::Ok
}
}
Replying to the client
Use the Server helpers rather than building raw lines by
hand where you can:
srv.numeric(uid, code, rest)— send a numeric reply (005,461, …).srv.send(uid, line)— send a fully-formed protocol line.srv.fail(uid, command, code, desc)/warn(...)/note(...)— IRCv3 standard replies (FAIL/WARN/NOTE) for clients that support them.srv.snotice(msg)— a server notice to opers (for staff-facing feedback).
Registering
A module exposes its commands from a commands() function returning boxed
handlers:
pub fn commands() -> Vec<Box<dyn Command>> {
vec![Box::new(Ping2)]
}
Then chain it into module_commands() in src/modules/mod.rs:
pub fn module_commands() -> Vec<Box<dyn Command>> {
filter::commands()
// …existing chains…
.chain(mymod::commands())
.collect()
}
Command names are the registry key and must be unique and upper-case. The
abbreviation feature lets clients invoke a command by a unique prefix, so avoid
names that are prefixes of unrelated commands where it matters.
Oper-only and gated commands
There is no separate "oper command" type — gate inside handle:
fn handle(&self, srv: &mut Server, uid: Uid, params: &[String]) -> CmdResult {
if !srv.is_oper(uid) {
srv.numeric(uid, 481, ":Permission Denied- You're not an IRC operator");
return CmdResult::Fail;
}
// …privileged work…
CmdResult::Ok
}
See the Server API for lookups, permissions, config, and off-core
work.