Wire centralized HELP into the remaining services
ChanServ, BotServ, HostServ, MemoServ, OperServ, and StatServ now route HELP through the shared renderer with per-command topic tables, so every service supports HELP <command>. The renderer matches slash-grouped names (OP/DEOP/VOICE/DEVOICE) so a paired command resolves as one topic. Adds unit tests for the renderer.
This commit is contained in:
parent
c9e392630b
commit
69a93198ff
7 changed files with 180 additions and 22 deletions
|
|
@ -1150,7 +1150,7 @@ pub fn help(me: &str, from: &Sender, ctx: &mut ServiceCtx, blurb: &str, topics:
|
|||
}
|
||||
ctx.notice(me, from.uid, "Type \x02HELP <command>\x02 for detail on one command.");
|
||||
}
|
||||
Some(t) => match topics.iter().find(|e| e.cmd.eq_ignore_ascii_case(t)) {
|
||||
Some(t) => match topics.iter().find(|e| e.cmd.split('/').any(|c| c.eq_ignore_ascii_case(t))) {
|
||||
Some(e) => {
|
||||
for line in e.detail.lines() {
|
||||
ctx.notice(me, from.uid, line);
|
||||
|
|
@ -1214,3 +1214,62 @@ mod tests {
|
|||
assert!(!Privs::default().any(), "empty set is not an oper");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod help_tests {
|
||||
use super::*;
|
||||
|
||||
fn texts(ctx: &ServiceCtx) -> Vec<String> {
|
||||
ctx.actions
|
||||
.iter()
|
||||
.filter_map(|a| match a {
|
||||
NetAction::Notice { text, .. } => Some(text.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
const T: &[HelpEntry] = &[
|
||||
HelpEntry { cmd: "REGISTER", summary: "register your nick", detail: "Syntax: REGISTER <password>\nRegisters your nick." },
|
||||
HelpEntry { cmd: "OP/DEOP", summary: "give or take op", detail: "Syntax: OP <#chan>\nGives or takes op." },
|
||||
];
|
||||
|
||||
fn sender<'a>() -> Sender<'a> {
|
||||
Sender { uid: "u1", nick: "nick", account: None, privs: Privs::default() }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overview_lists_every_command() {
|
||||
let mut ctx = ServiceCtx::default();
|
||||
help("Svc", &sender(), &mut ctx, "the blurb", T, None);
|
||||
let t = texts(&ctx);
|
||||
assert_eq!(t[0], "the blurb");
|
||||
assert!(t.iter().any(|l| l.contains("REGISTER") && l.contains("register your nick")));
|
||||
assert!(t.iter().any(|l| l.contains("OP/DEOP")));
|
||||
assert!(t.last().unwrap().contains("HELP <command>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn topic_shows_detail_lines_only() {
|
||||
let mut ctx = ServiceCtx::default();
|
||||
help("Svc", &sender(), &mut ctx, "the blurb", T, Some("register"));
|
||||
let t = texts(&ctx);
|
||||
assert!(t[0].contains("Syntax: REGISTER"));
|
||||
assert!(t.iter().any(|l| l == "Registers your nick."));
|
||||
assert!(!t.iter().any(|l| l == "the blurb"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn topic_matches_a_slash_alias() {
|
||||
let mut ctx = ServiceCtx::default();
|
||||
help("Svc", &sender(), &mut ctx, "b", T, Some("DEOP"));
|
||||
assert!(texts(&ctx)[0].contains("Syntax: OP"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_topic_reports_no_help() {
|
||||
let mut ctx = ServiceCtx::default();
|
||||
help("Svc", &sender(), &mut ctx, "b", T, Some("nope"));
|
||||
assert!(texts(&ctx)[0].contains("No help"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
//! and (in later slices) run fantasy commands. `lib.rs` holds the dispatcher;
|
||||
//! each command lives in its own file, matching NickServ/ChanServ.
|
||||
|
||||
use echo_api::{NetView, Priv, Sender, Service, ServiceCtx, Store};
|
||||
use echo_api::{HelpEntry, NetView, Priv, Sender, Service, ServiceCtx, Store};
|
||||
|
||||
#[path = "bot.rs"]
|
||||
mod bot;
|
||||
|
|
@ -37,6 +37,22 @@ fn require_channel_admin(me: &str, from: &Sender, chan: &str, ctx: &mut ServiceC
|
|||
true
|
||||
}
|
||||
|
||||
const BLURB: &str = "BotServ keeps service bots for your channels: assign a bot, speak through it, and configure kickers, badwords, and triggers.";
|
||||
|
||||
const TOPICS: &[HelpEntry] = &[
|
||||
HelpEntry { cmd: "ASSIGN", summary: "put a bot in a channel", detail: "Syntax: \x02ASSIGN <#channel> <bot>\x02\nPuts a service bot in your channel." },
|
||||
HelpEntry { cmd: "UNASSIGN", summary: "remove a channel's bot", detail: "Syntax: \x02UNASSIGN <#channel>\x02\nRemoves the assigned bot from your channel." },
|
||||
HelpEntry { cmd: "INFO", summary: "show bot/channel settings", detail: "Syntax: \x02INFO <bot | #channel>\x02\nShows a bot's details or a channel's bot settings." },
|
||||
HelpEntry { cmd: "SAY", summary: "speak through the bot", detail: "Syntax: \x02SAY <#channel> <text>\x02\nSays a line as the channel's bot." },
|
||||
HelpEntry { cmd: "ACT", summary: "action through the bot", detail: "Syntax: \x02ACT <#channel> <text>\x02\nSends a /me action as the channel's bot." },
|
||||
HelpEntry { cmd: "SET", summary: "configure bot behavior", detail: "Syntax: \x02SET <#channel> <GREET|BANEXPIRE|NOBOT|VOTEKICK> <value>\x02, or \x02SET <bot> PRIVATE <ON|OFF>\x02\nConfigures a channel's bot behavior, or marks a bot private." },
|
||||
HelpEntry { cmd: "KICK", summary: "configure kickers", detail: "Syntax: \x02KICK <#channel> <CAPS|FLOOD|REPEAT|BADWORDS|...> <ON|OFF> [params]\x02, or \x02KICK <#channel> TTB <n> | TEST <message>\x02\nConfigures a channel's automatic kickers." },
|
||||
HelpEntry { cmd: "BADWORDS", summary: "manage badword patterns", detail: "Syntax: \x02BADWORDS <#channel> ADD|DEL|LIST|CLEAR [pattern]\x02\nManages the channel's badword patterns." },
|
||||
HelpEntry { cmd: "TRIGGER", summary: "manage auto-responses", detail: "Syntax: \x02TRIGGER <#channel> ADD <regex>|<response>[|<cooldown>] | DEL <num> | LIST | CLEAR\x02\nManages the channel's auto-response triggers." },
|
||||
HelpEntry { cmd: "COPY", summary: "clone bot settings", detail: "Syntax: \x02COPY <#source> <#dest>\x02\nClones a channel's bot settings to another channel." },
|
||||
HelpEntry { cmd: "BOT", summary: "manage the bots (operator)", detail: "Syntax: \x02BOT ADD|CHANGE|DEL|LIST\x02\nCreates and manages the service bots themselves. Operators only." },
|
||||
];
|
||||
|
||||
pub struct BotServ {
|
||||
pub uid: String,
|
||||
}
|
||||
|
|
@ -66,7 +82,7 @@ impl Service for BotServ {
|
|||
Some("BADWORDS") => badwords::handle(me, from, args, ctx, db),
|
||||
Some("COPY") => copy::handle(me, from, args, ctx, db),
|
||||
Some("TRIGGER") => trigger::handle(me, from, args, ctx, db),
|
||||
Some("HELP") | None => ctx.notice(me, from.uid, "BotServ keeps service bots for your channels: \x02ASSIGN\x02 <#channel> <bot> puts a bot in your channel, \x02UNASSIGN\x02 <#channel> removes it, \x02INFO\x02 <bot|#channel> shows details, \x02SAY\x02/\x02ACT\x02 <#channel> <text> speaks through the bot, \x02SET\x02 <#channel> GREET <on|off> toggles greets, \x02KICK\x02 <#channel> <type> <on|off> configures kickers (with \x02TEST\x02/\x02WARN\x02/\x02TTB\x02), \x02BADWORDS\x02 and \x02TRIGGER\x02 <#channel> ADD|DEL|LIST manage badword regexes and auto-responses, \x02COPY\x02 <#src> <#dst> clones settings. Operators also have \x02BOT\x02 ADD|CHANGE|DEL|LIST."),
|
||||
Some("HELP") | None => echo_api::help(me, from, ctx, BLURB, TOPICS, args.get(1).copied()),
|
||||
Some(other) => ctx.notice(me, from.uid, format!("I don't know the command \x02{other}\x02. Try \x02HELP\x02.")),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use echo_api::{ChanError, ChannelView, Priv, Store};
|
||||
use echo_api::{Sender, Service, ServiceCtx};
|
||||
use echo_api::{HelpEntry, Sender, Service, ServiceCtx};
|
||||
use echo_api::NetView;
|
||||
|
||||
#[path = "mode.rs"]
|
||||
|
|
@ -43,6 +43,35 @@ mod xop;
|
|||
mod suspend;
|
||||
mod noexpire;
|
||||
|
||||
const BLURB: &str = "ChanServ registers and looks after channels. Register a channel you hold ops in, then manage its access, modes, and topic.";
|
||||
|
||||
const TOPICS: &[HelpEntry] = &[
|
||||
HelpEntry { cmd: "REGISTER", summary: "register a channel", detail: "Syntax: \x02REGISTER <#channel>\x02\nRegisters a channel to you. You must currently hold ops in it." },
|
||||
HelpEntry { cmd: "INFO", summary: "show channel information", detail: "Syntax: \x02INFO <#channel>\x02\nShows a channel's registration and settings." },
|
||||
HelpEntry { cmd: "LIST", summary: "list registered channels", detail: "Syntax: \x02LIST\x02\nLists registered channels." },
|
||||
HelpEntry { cmd: "SET", summary: "change founder or settings", detail: "Syntax: \x02SET <#channel> FOUNDER <account> | DESC <text> | SIGNKICK|PRIVATE|PEACE|SECUREOPS|KEEPTOPIC|TOPICLOCK {ON|OFF}\x02\nTransfers the founder or changes a channel setting." },
|
||||
HelpEntry { cmd: "ACCESS", summary: "manage the access list", detail: "Syntax: \x02ACCESS <#channel> LIST | ADD <account> <op|voice> | DEL <account>\x02\nManages the channel access list." },
|
||||
HelpEntry { cmd: "FLAGS", summary: "granular per-account flags", detail: "Syntax: \x02FLAGS <#channel> [account [+/-flags]]\x02\nViews or changes granular per-account channel flags." },
|
||||
HelpEntry { cmd: "AOP/SOP/VOP", summary: "tiered access shortcuts", detail: "Syntax: \x02AOP|SOP|VOP <#channel> ADD <account> | DEL <account> | LIST\x02\nTiered shortcuts over ACCESS: AOP and SOP grant op, VOP grants voice." },
|
||||
HelpEntry { cmd: "STATUS", summary: "show a user's access", detail: "Syntax: \x02STATUS <#channel> [nick]\x02\nShows a user's access level on a channel." },
|
||||
HelpEntry { cmd: "OP/DEOP/VOICE/DEVOICE", summary: "give or take op/voice", detail: "Syntax: \x02OP|DEOP|VOICE|DEVOICE <#channel> [nick]\x02\nGives or takes channel op or voice." },
|
||||
HelpEntry { cmd: "KICK", summary: "kick from the channel", detail: "Syntax: \x02KICK <#channel> <nick> [reason]\x02\nKicks a user from the channel." },
|
||||
HelpEntry { cmd: "BAN/UNBAN", summary: "ban or unban a user", detail: "Syntax: \x02BAN <#channel> <nick> [reason]\x02, \x02UNBAN <#channel> [nick]\x02\nBans or unbans a user by host." },
|
||||
HelpEntry { cmd: "AKICK", summary: "manage the auto-kick list", detail: "Syntax: \x02AKICK <#channel> ADD <mask> [reason] | DEL <mask> | LIST\x02\nManages the auto-kick list; matches are banned and kicked on join." },
|
||||
HelpEntry { cmd: "ENFORCE", summary: "re-apply lock and access", detail: "Syntax: \x02ENFORCE <#channel>\x02\nRe-applies the mode-lock, access, and akick list to everyone present." },
|
||||
HelpEntry { cmd: "TOPIC", summary: "set the topic", detail: "Syntax: \x02TOPIC <#channel> <text>\x02\nSets the channel topic." },
|
||||
HelpEntry { cmd: "ENTRYMSG", summary: "message on join", detail: "Syntax: \x02ENTRYMSG <#channel> [CLEAR | <text>]\x02\nSets a message noticed to users as they join." },
|
||||
HelpEntry { cmd: "INVITE", summary: "invite into the channel", detail: "Syntax: \x02INVITE <#channel> [nick]\x02\nInvites you, or a nick, into the channel." },
|
||||
HelpEntry { cmd: "GETKEY", summary: "show the channel key", detail: "Syntax: \x02GETKEY <#channel>\x02\nShows the channel key (+k), if one is set." },
|
||||
HelpEntry { cmd: "SEEN", summary: "when a nick was last seen", detail: "Syntax: \x02SEEN <nick>\x02\nShows when a nick was last seen." },
|
||||
HelpEntry { cmd: "CLONE", summary: "copy channel settings", detail: "Syntax: \x02CLONE <source> <target>\x02\nCopies one channel's settings to another you own." },
|
||||
HelpEntry { cmd: "MODE", summary: "set channel modes", detail: "Syntax: \x02MODE <#channel> <modes>\x02\nSets modes on the channel, e.g. MODE #chan +nt." },
|
||||
HelpEntry { cmd: "MLOCK", summary: "lock channel modes", detail: "Syntax: \x02MLOCK <#channel> [modes]\x02\nLocks modes set or unset, e.g. MLOCK #chan +nt-s." },
|
||||
HelpEntry { cmd: "DROP", summary: "delete the registration", detail: "Syntax: \x02DROP <#channel>\x02\nDeletes the channel registration." },
|
||||
HelpEntry { cmd: "SUSPEND/UNSUSPEND", summary: "suspend a channel (operator)", detail: "Syntax: \x02SUSPEND <#channel> [+expiry] [reason]\x02, \x02UNSUSPEND <#channel>\x02\nSuspends or unsuspends a channel. Operators only." },
|
||||
HelpEntry { cmd: "NOEXPIRE", summary: "pin against expiry (operator)", detail: "Syntax: \x02NOEXPIRE <#channel> {ON|OFF}\x02\nPins a channel so inactivity expiry never drops it. Operators only." },
|
||||
];
|
||||
|
||||
pub struct ChanServ {
|
||||
pub uid: String,
|
||||
}
|
||||
|
|
@ -237,7 +266,7 @@ impl Service for ChanServ {
|
|||
Some("AOP") => xop::handle(me, from, "AOP", "op", args, ctx, db),
|
||||
Some("SOP") => xop::handle(me, from, "SOP", "op", args, ctx, db),
|
||||
Some("VOP") => xop::handle(me, from, "VOP", "voice", args, ctx, db),
|
||||
Some("HELP") => ctx.notice(me, from.uid, "ChanServ registers and looks after channels. Commands: \x02REGISTER\x02, \x02INFO\x02, \x02LIST\x02, \x02SET\x02, \x02ACCESS\x02, \x02FLAGS\x02, \x02AOP\x02/\x02SOP\x02/\x02VOP\x02, \x02STATUS\x02, \x02OP\x02/\x02DEOP\x02, \x02VOICE\x02/\x02DEVOICE\x02, \x02KICK\x02, \x02BAN\x02/\x02UNBAN\x02, \x02AKICK\x02, \x02ENFORCE\x02, \x02TOPIC\x02, \x02ENTRYMSG\x02, \x02INVITE\x02, \x02GETKEY\x02, \x02SEEN\x02, \x02CLONE\x02, \x02MODE\x02, \x02MLOCK\x02, \x02DROP\x02. Operators also have \x02SUSPEND\x02/\x02UNSUSPEND\x02 and \x02NOEXPIRE\x02 <#channel> {ON|OFF}."),
|
||||
Some("HELP") => echo_api::help(me, from, ctx, BLURB, TOPICS, args.get(1).copied()),
|
||||
Some(other) => ctx.notice(me, from.uid, format!("I don't know the command \x02{other}\x02. Try \x02HELP\x02.")),
|
||||
None => {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
//! with SET/DEL and review with LIST. `lib.rs` holds the dispatcher; each
|
||||
//! command lives in its own file.
|
||||
|
||||
use echo_api::{NetView, Priv, Sender, Service, ServiceCtx, Store};
|
||||
use echo_api::{HelpEntry, NetView, Priv, Sender, Service, ServiceCtx, Store};
|
||||
|
||||
#[path = "on.rs"]
|
||||
mod on;
|
||||
|
|
@ -32,6 +32,26 @@ mod template;
|
|||
#[path = "default.rs"]
|
||||
mod default;
|
||||
|
||||
const BLURB: &str = "HostServ gives you a virtual host (vhost). Activate an assigned one, pick one from the menu, or request one from an operator.";
|
||||
|
||||
const TOPICS: &[HelpEntry] = &[
|
||||
HelpEntry { cmd: "ON", summary: "activate your vhost", detail: "Syntax: \x02ON\x02\nActivates your assigned vhost on your host." },
|
||||
HelpEntry { cmd: "OFF", summary: "restore your normal host", detail: "Syntax: \x02OFF\x02\nRestores your normal host." },
|
||||
HelpEntry { cmd: "REQUEST", summary: "request a vhost", detail: "Syntax: \x02REQUEST <host>\x02\nAsks an operator to approve a vhost for you." },
|
||||
HelpEntry { cmd: "OFFERLIST", summary: "show the vhost menu", detail: "Syntax: \x02OFFERLIST\x02\nShows the self-serve vhost menu." },
|
||||
HelpEntry { cmd: "TAKE", summary: "take a vhost from the menu", detail: "Syntax: \x02TAKE <number>\x02\nTakes a vhost from the menu (see OFFERLIST)." },
|
||||
HelpEntry { cmd: "SET", summary: "assign a vhost (operator)", detail: "Syntax: \x02SET <account> <host> [duration]\x02\nAssigns a vhost to an account. Operators only." },
|
||||
HelpEntry { cmd: "DEL", summary: "remove a vhost (operator)", detail: "Syntax: \x02DEL <account>\x02\nRemoves an account's vhost. Operators only." },
|
||||
HelpEntry { cmd: "LIST", summary: "list vhosts (operator)", detail: "Syntax: \x02LIST\x02\nLists assigned vhosts. Operators only." },
|
||||
HelpEntry { cmd: "WAITING", summary: "pending requests (operator)", detail: "Syntax: \x02WAITING\x02\nLists pending vhost requests. Operators only." },
|
||||
HelpEntry { cmd: "ACTIVATE", summary: "approve a request (operator)", detail: "Syntax: \x02ACTIVATE <account>\x02\nApproves a pending vhost request. Operators only." },
|
||||
HelpEntry { cmd: "REJECT", summary: "reject a request (operator)", detail: "Syntax: \x02REJECT <account>\x02\nRejects a pending vhost request. Operators only." },
|
||||
HelpEntry { cmd: "OFFER", summary: "add a menu vhost (operator)", detail: "Syntax: \x02OFFER <host>\x02\nAdds a vhost to the self-serve menu. Operators only." },
|
||||
HelpEntry { cmd: "OFFERDEL", summary: "remove a menu vhost (operator)", detail: "Syntax: \x02OFFERDEL <number>\x02\nRemoves a menu entry (see OFFERLIST). Operators only." },
|
||||
HelpEntry { cmd: "FORBID", summary: "forbid a vhost pattern (operator)", detail: "Syntax: \x02FORBID <regex>\x02\nForbids vhosts matching a pattern. Operators only." },
|
||||
HelpEntry { cmd: "FORBIDDEL", summary: "remove a forbidden pattern (operator)", detail: "Syntax: \x02FORBIDDEL <number>\x02\nRemoves a forbidden pattern (see FORBIDLIST). Operators only." },
|
||||
];
|
||||
|
||||
pub struct HostServ {
|
||||
pub uid: String,
|
||||
}
|
||||
|
|
@ -71,7 +91,7 @@ impl Service for HostServ {
|
|||
Some("FORBIDDEL") => forbid::del(me, from, args, ctx, db),
|
||||
Some("TEMPLATE") => template::handle(me, from, args, ctx, db),
|
||||
Some("DEFAULT") => default::handle(me, from, ctx, db),
|
||||
Some("HELP") | None => ctx.notice(me, from.uid, "HostServ gives you a vhost: \x02ON\x02 activates your assigned vhost, \x02OFF\x02 restores your normal host, \x02REQUEST\x02 <host> asks for one, \x02OFFERLIST\x02 + \x02TAKE\x02 <n> pick from the menu. Operators use \x02SET\x02/\x02DEL\x02 <account>, \x02LIST\x02, \x02WAITING\x02 + \x02ACTIVATE\x02/\x02REJECT\x02, and \x02OFFER\x02/\x02OFFERDEL\x02 for the menu."),
|
||||
Some("HELP") | None => echo_api::help(me, from, ctx, BLURB, TOPICS, args.get(1).copied()),
|
||||
Some(other) => ctx.notice(me, from.uid, format!("I don't know the command \x02{other}\x02. Try \x02HELP\x02.")),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
//! account data. `lib.rs` holds the dispatcher; each command lives in its own
|
||||
//! file, matching NickServ/ChanServ.
|
||||
|
||||
use echo_api::{NetView, Sender, Service, ServiceCtx, Store};
|
||||
use echo_api::{HelpEntry, NetView, Sender, Service, ServiceCtx, Store};
|
||||
|
||||
#[path = "send.rs"]
|
||||
mod send;
|
||||
|
|
@ -15,6 +15,15 @@ mod read;
|
|||
#[path = "del.rs"]
|
||||
mod del;
|
||||
|
||||
const BLURB: &str = "MemoServ delivers messages to registered users, online or not. You must be identified to use it.";
|
||||
|
||||
const TOPICS: &[HelpEntry] = &[
|
||||
HelpEntry { cmd: "SEND", summary: "leave a memo for an account", detail: "Syntax: \x02SEND <nick> <text>\x02\nLeaves a memo on a registered account's mailbox." },
|
||||
HelpEntry { cmd: "LIST", summary: "list your memos", detail: "Syntax: \x02LIST\x02\nLists the memos in your mailbox." },
|
||||
HelpEntry { cmd: "READ", summary: "read a memo", detail: "Syntax: \x02READ <num>|NEW|ALL\x02\nReads a memo by number, your unread ones, or all of them." },
|
||||
HelpEntry { cmd: "DEL", summary: "delete a memo", detail: "Syntax: \x02DEL <num>|ALL\x02\nDeletes a memo by number, or your whole mailbox." },
|
||||
];
|
||||
|
||||
pub struct MemoServ {
|
||||
pub uid: String,
|
||||
}
|
||||
|
|
@ -34,7 +43,7 @@ impl Service for MemoServ {
|
|||
let me = self.uid.as_str();
|
||||
let cmd = args.first().map(|s| s.to_ascii_uppercase());
|
||||
if matches!(cmd.as_deref(), Some("HELP") | None) {
|
||||
ctx.notice(me, from.uid, "MemoServ delivers messages to registered users, online or not. Commands: \x02SEND\x02 <nick> <text>, \x02LIST\x02, \x02READ\x02 <num>|NEW|ALL, \x02DEL\x02 <num>|ALL.");
|
||||
echo_api::help(me, from, ctx, BLURB, TOPICS, args.get(1).copied());
|
||||
return;
|
||||
}
|
||||
// Everything else needs you to be identified (memos are per-account).
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
//! announcement to every user, and KILL to disconnect one. `lib.rs` dispatches;
|
||||
//! each command family is its own file.
|
||||
|
||||
use echo_api::{NetView, Sender, Service, ServiceCtx, Store};
|
||||
use echo_api::{HelpEntry, NetView, Sender, Service, ServiceCtx, Store};
|
||||
|
||||
#[path = "xline.rs"]
|
||||
mod xline;
|
||||
|
|
@ -80,13 +80,35 @@ impl Service for OperServ {
|
|||
Some(cmd) if cmd.eq_ignore_ascii_case("CHANKILL") => chankill::handle(me, from, args, ctx, net, db),
|
||||
Some(cmd) if cmd.eq_ignore_ascii_case("LOGSEARCH") => logsearch::handle(me, from, args, ctx, net),
|
||||
Some(cmd) if cmd.eq_ignore_ascii_case("DEFCON") => defcon::handle(me, from, args, ctx, db),
|
||||
Some(cmd) if cmd.eq_ignore_ascii_case("HELP") => help(me, from, ctx),
|
||||
None => help(me, from, ctx),
|
||||
Some(cmd) if cmd.eq_ignore_ascii_case("HELP") => echo_api::help(me, from, ctx, BLURB, TOPICS, args.get(1).copied()),
|
||||
None => echo_api::help(me, from, ctx, BLURB, TOPICS, None),
|
||||
Some(other) => ctx.notice(me, from.uid, format!("I don't know \x02{other}\x02. Try \x02HELP\x02.")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn help(me: &str, from: &Sender, ctx: &mut ServiceCtx) {
|
||||
ctx.notice(me, from.uid, "OperServ holds network operator tools (Priv::Admin): \x02AKILL\x02 ADD|DEL|LIST (user@host bans), \x02SQLINE\x02 ADD|DEL|LIST (nick bans), \x02SNLINE\x02 ADD|DEL|LIST (realname bans), \x02SHUN\x02 ADD|DEL|LIST (silence a host), \x02CBAN\x02 ADD|DEL|LIST (ban a channel name), \x02CHANKILL\x02 <#chan> (clear a channel), \x02GLOBAL\x02 <message> (announce to everyone), \x02KILL\x02 <nick> [reason] (disconnect a user), \x02KICK\x02 <#chan> <nick> [reason], \x02MODE\x02 <#chan> <modes> [params], \x02IGNORE\x02 ADD|DEL|LIST (silence a user from services), \x02STATS\x02 (enforcement overview), \x02SVSNICK\x02 <nick> <newnick>, \x02SVSJOIN\x02 <nick> <#chan> [key], \x02INFO\x02 ADD|DEL <target> (staff notes — bulletins are on \x02InfoServ\x02), \x02OPER\x02 ADD|DEL|LIST (runtime operators), \x02SESSION\x02 LIST|VIEW + \x02EXCEPTION\x02 ADD|DEL|LIST (per-IP session limits), \x02JUPE\x02 <server> | DEL | LIST, \x02LOGSEARCH\x02 [pattern] (search the action log), \x02DEFCON\x02 [1-5] (network defence level).");
|
||||
}
|
||||
const BLURB: &str = "OperServ holds the network operator tools. Every command is operator-only.";
|
||||
|
||||
const TOPICS: &[HelpEntry] = &[
|
||||
HelpEntry { cmd: "AKILL", summary: "network-ban a user@host", detail: "Syntax: \x02AKILL ADD [+expiry] <user@host> <reason> | AKILL DEL <mask|number> | AKILL LIST [pattern]\x02\nA network-wide user@host ban." },
|
||||
HelpEntry { cmd: "SQLINE", summary: "ban a nick pattern", detail: "Syntax: \x02SQLINE ADD [+expiry] <nick> <reason> | SQLINE DEL <mask|number> | SQLINE LIST [pattern]\x02\nBans matching nicknames." },
|
||||
HelpEntry { cmd: "SNLINE", summary: "ban a realname pattern", detail: "Syntax: \x02SNLINE ADD [+expiry] <realname> <reason> | SNLINE DEL <mask|number> | SNLINE LIST [pattern]\x02\nBans matching real names." },
|
||||
HelpEntry { cmd: "SHUN", summary: "silence a host", detail: "Syntax: \x02SHUN ADD [+expiry] <mask> <reason> | SHUN DEL <mask|number> | SHUN LIST [pattern]\x02\nSilences a matching host: it stays connected but can't act." },
|
||||
HelpEntry { cmd: "CBAN", summary: "ban a channel name", detail: "Syntax: \x02CBAN ADD [+expiry] <#channel> <reason> | CBAN DEL <mask|number> | CBAN LIST [pattern]\x02\nBlocks matching channel names from being joined." },
|
||||
HelpEntry { cmd: "CHANKILL", summary: "clear a channel", detail: "Syntax: \x02CHANKILL <#channel> [reason]\x02\nRemoves everyone from a channel and holds it briefly." },
|
||||
HelpEntry { cmd: "GLOBAL", summary: "announce to everyone", detail: "Syntax: \x02GLOBAL <message>\x02\nSends a notice to every user on the network." },
|
||||
HelpEntry { cmd: "KILL", summary: "disconnect a user", detail: "Syntax: \x02KILL <nick> [reason]\x02\nDisconnects a user from the network." },
|
||||
HelpEntry { cmd: "KICK", summary: "kick from a channel", detail: "Syntax: \x02KICK <#channel> <nick> [reason]\x02\nKicks a user from a channel." },
|
||||
HelpEntry { cmd: "MODE", summary: "set channel modes", detail: "Syntax: \x02MODE <#channel> <modes> [params]\x02\nSets modes on a channel." },
|
||||
HelpEntry { cmd: "IGNORE", summary: "silence a user from services", detail: "Syntax: \x02IGNORE ADD [+expiry] <mask> [reason] | IGNORE DEL <mask> | IGNORE LIST\x02\nMakes services ignore commands from a matching user." },
|
||||
HelpEntry { cmd: "STATS", summary: "enforcement overview", detail: "Syntax: \x02STATS\x02\nShows an overview of the enforcement lists." },
|
||||
HelpEntry { cmd: "SVSNICK", summary: "force a nick change", detail: "Syntax: \x02SVSNICK <nick> <newnick>\x02\nForces a user to change nick." },
|
||||
HelpEntry { cmd: "SVSJOIN", summary: "force a channel join", detail: "Syntax: \x02SVSJOIN <nick> <#channel> [key]\x02\nForces a user to join a channel." },
|
||||
HelpEntry { cmd: "INFO", summary: "staff notes on a target", detail: "Syntax: \x02INFO <target> | INFO ADD <target> <note> | INFO DEL <target>\x02\nReads or sets staff notes on an account or channel. (Bulletins are on InfoServ.)" },
|
||||
HelpEntry { cmd: "OPER", summary: "runtime operators", detail: "Syntax: \x02OPER ADD <account> <priv[,priv]> [+duration] | OPER DEL <account> | OPER LIST\x02\nGrants or revokes runtime operator privileges: auspex, suspend, admin." },
|
||||
HelpEntry { cmd: "SESSION", summary: "inspect per-IP sessions", detail: "Syntax: \x02SESSION LIST <min> | SESSION VIEW <ip>\x02\nInspects per-IP session counts." },
|
||||
HelpEntry { cmd: "EXCEPTION", summary: "session-limit exceptions", detail: "Syntax: \x02EXCEPTION ADD <ip-mask> <limit> [reason] | EXCEPTION DEL <ip-mask> | EXCEPTION LIST\x02\nAdjusts the per-IP session limit for a mask (0 = unlimited)." },
|
||||
HelpEntry { cmd: "JUPE", summary: "jupe a server name", detail: "Syntax: \x02JUPE <server.name> [reason] | JUPE DEL <server.name> | JUPE LIST\x02\nBlocks a server name from linking." },
|
||||
HelpEntry { cmd: "LOGSEARCH", summary: "search the action log", detail: "Syntax: \x02LOGSEARCH [pattern]\x02\nSearches the incident and action log." },
|
||||
HelpEntry { cmd: "DEFCON", summary: "network defence level", detail: "Syntax: \x02DEFCON [1-5]\x02\nShows or sets the network defence level (5 = normal, 1 = lockdown)." },
|
||||
];
|
||||
|
|
|
|||
|
|
@ -3,13 +3,20 @@
|
|||
//! (a #channel argument, for its founder). `lib.rs` holds the dispatcher; each
|
||||
//! view lives in its own file.
|
||||
|
||||
use echo_api::{NetView, Priv, Sender, Service, ServiceCtx, Store};
|
||||
use echo_api::{HelpEntry, NetView, Priv, Sender, Service, ServiceCtx, Store};
|
||||
|
||||
#[path = "global.rs"]
|
||||
mod global;
|
||||
#[path = "channel.rs"]
|
||||
mod channel;
|
||||
|
||||
const BLURB: &str = "StatServ reports statistics: \x02SERVER\x02 for network-wide counters (operators), or a \x02#channel\x02 for its activity (founder).";
|
||||
|
||||
const TOPICS: &[HelpEntry] = &[
|
||||
HelpEntry { cmd: "SERVER", summary: "network-wide statistics", detail: "Syntax: \x02SERVER\x02\nShows network-wide counters. Operators only. Also \x02GLOBAL\x02." },
|
||||
HelpEntry { cmd: "CHANNEL", summary: "a channel's statistics", detail: "Syntax: \x02<#channel>\x02\nShows a channel's activity statistics. Available to the channel's founder." },
|
||||
];
|
||||
|
||||
pub struct StatServ {
|
||||
pub uid: String,
|
||||
}
|
||||
|
|
@ -30,17 +37,13 @@ impl Service for StatServ {
|
|||
match args.first().copied() {
|
||||
Some(chan) if chan.starts_with('#') => channel::handle(me, from, chan, ctx, net, db),
|
||||
Some(cmd) if cmd.eq_ignore_ascii_case("SERVER") || cmd.eq_ignore_ascii_case("GLOBAL") => global::handle(me, from, ctx, net, db),
|
||||
Some(cmd) if cmd.eq_ignore_ascii_case("HELP") => help(me, from, ctx),
|
||||
None => help(me, from, ctx),
|
||||
Some(cmd) if cmd.eq_ignore_ascii_case("HELP") => echo_api::help(me, from, ctx, BLURB, TOPICS, args.get(1).copied()),
|
||||
None => echo_api::help(me, from, ctx, BLURB, TOPICS, None),
|
||||
Some(other) => ctx.notice(me, from.uid, format!("I don't know \x02{other}\x02. Try \x02SERVER\x02, a \x02#channel\x02, or \x02HELP\x02.")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn help(me: &str, from: &Sender, ctx: &mut ServiceCtx) {
|
||||
ctx.notice(me, from.uid, "StatServ reports statistics: \x02SERVER\x02 for network-wide counters (operators), or \x02<#channel>\x02 for a channel's activity (its founder).");
|
||||
}
|
||||
|
||||
// Shared gate: the sender may see a channel's stats only as its founder or a
|
||||
// services admin.
|
||||
fn require_channel_admin(me: &str, from: &Sender, chan: &str, ctx: &mut ServiceCtx, db: &dyn Store) -> bool {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue