Migrate interpolated service messages to the t! macro (8 modules)
This commit is contained in:
parent
b462d37bd5
commit
d207c3d60d
133 changed files with 782 additions and 702 deletions
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{NetView, Priv, Sender, ServiceCtx, Store, XlineKind};
|
||||
use echo_api::{t, NetView, Priv, Sender, ServiceCtx, Store, XlineKind};
|
||||
use std::collections::HashSet;
|
||||
|
||||
// CHANKILL <#channel> [reason]: AKILL every user in a channel by host, clearing
|
||||
|
|
@ -15,7 +15,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
};
|
||||
let members = net.channel_members(chan);
|
||||
if members.is_empty() {
|
||||
ctx.notice(me, from.uid, format!("No one is in \x02{chan}\x02 (or it isn't tracked)."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "No one is in \x02{chan}\x02 (or it isn't tracked).", chan = chan));
|
||||
return;
|
||||
}
|
||||
let reason = if args.len() > 2 { args[2..].join(" ") } else { "Channel cleared by services".to_string() };
|
||||
|
|
@ -34,5 +34,5 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
banned += 1;
|
||||
}
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("CHANKILL on \x02{chan}\x02: \x02{banned}\x02 host(s) AKILL'd."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "CHANKILL on \x02{chan}\x02: \x02{banned}\x02 host(s) AKILL'd.", chan = chan, banned = banned));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Priv, Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Priv, Sender, ServiceCtx, Store};
|
||||
|
||||
// DEFCON [1-5]: read or set the network defence level. 5 is normal; each lower
|
||||
// level adds a restriction — 4 freezes channel registrations, 3 freezes all
|
||||
|
|
@ -11,16 +11,20 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
return;
|
||||
}
|
||||
match args.get(1) {
|
||||
None => ctx.notice(me, from.uid, format!("The network is at \x02DEFCON {}\x02 — {}.", db.defcon(), describe(db.defcon()))),
|
||||
None => {
|
||||
let desc = describe(ctx, db.defcon());
|
||||
ctx.notice(me, from.uid, t!(ctx, "The network is at \x02DEFCON {level}\x02 — {desc}.", level = db.defcon(), desc = desc));
|
||||
}
|
||||
Some(&arg) => match arg.parse::<u8>() {
|
||||
Ok(level) if (1..=5).contains(&level) => {
|
||||
db.set_defcon(level);
|
||||
ctx.notice(me, from.uid, format!("DEFCON set to \x02{level}\x02 — {}.", describe(level)));
|
||||
let desc = describe(ctx, level);
|
||||
ctx.notice(me, from.uid, t!(ctx, "DEFCON set to \x02{level}\x02 — {desc}.", level = level, desc = desc));
|
||||
// Let everyone know the posture changed.
|
||||
let msg = if level == 5 {
|
||||
"The network is back to normal operations (DEFCON 5).".to_string()
|
||||
} else {
|
||||
format!("Network defence level is now \x02DEFCON {level}\x02: {}.", describe(level))
|
||||
format!("Network defence level is now \x02DEFCON {level}\x02: {}.", describe(ctx, level))
|
||||
};
|
||||
ctx.global(me, msg);
|
||||
}
|
||||
|
|
@ -29,12 +33,12 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
}
|
||||
}
|
||||
|
||||
fn describe(level: u8) -> &'static str {
|
||||
fn describe(ctx: &ServiceCtx, level: u8) -> String {
|
||||
match level {
|
||||
5 => "normal operations",
|
||||
4 => "channel registrations frozen",
|
||||
3 => "all registrations frozen",
|
||||
2 => "registrations frozen and sessions capped to one per host",
|
||||
_ => "full lockdown, no new connections",
|
||||
5 => t!(ctx, "normal operations"),
|
||||
4 => t!(ctx, "channel registrations frozen"),
|
||||
3 => t!(ctx, "all registrations frozen"),
|
||||
2 => t!(ctx, "registrations frozen and sessions capped to one per host"),
|
||||
_ => t!(ctx, "full lockdown, no new connections"),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{human_time, ForbidKind, Priv, Sender, ServiceCtx, Store};
|
||||
use echo_api::{human_time, t, ForbidKind, Priv, Sender, ServiceCtx, Store};
|
||||
|
||||
// FORBID ADD <NICK|CHAN|EMAIL> <mask> <reason> | DEL <NICK|CHAN|EMAIL> <mask> | LIST
|
||||
// Bans a nick, channel, or email pattern from being registered. Admin-only.
|
||||
|
|
@ -21,8 +21,8 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
let setter = from.account.unwrap_or(from.nick);
|
||||
let label = kind.wire().to_lowercase();
|
||||
match db.forbid_add(kind, mask, setter, &reason) {
|
||||
Ok(true) => ctx.notice(me, from.uid, format!("Forbade {label} \x02{mask}\x02.")),
|
||||
Ok(false) => ctx.notice(me, from.uid, format!("Updated the forbid on {label} \x02{mask}\x02.")),
|
||||
Ok(true) => ctx.notice(me, from.uid, t!(ctx, "Forbade {label} \x02{mask}\x02.", label = label, mask = mask)),
|
||||
Ok(false) => ctx.notice(me, from.uid, t!(ctx, "Updated the forbid on {label} \x02{mask}\x02.", label = label, mask = mask)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
@ -33,8 +33,8 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
};
|
||||
let label = kind.wire().to_lowercase();
|
||||
match db.forbid_del(kind, mask) {
|
||||
Ok(true) => ctx.notice(me, from.uid, format!("Removed the forbid on {label} \x02{mask}\x02.")),
|
||||
Ok(false) => ctx.notice(me, from.uid, format!("No forbid on {label} \x02{mask}\x02.")),
|
||||
Ok(true) => ctx.notice(me, from.uid, t!(ctx, "Removed the forbid on {label} \x02{mask}\x02.", label = label, mask = mask)),
|
||||
Ok(false) => ctx.notice(me, from.uid, t!(ctx, "No forbid on {label} \x02{mask}\x02.", label = label, mask = mask)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
@ -46,7 +46,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
}
|
||||
ctx.notice(me, from.uid, "Registration bans:");
|
||||
for f in &forbids {
|
||||
ctx.notice(me, from.uid, format!(" [{}] \x02{}\x02 by {} ({}) — {}", f.kind.wire(), f.mask, f.setter, human_time(f.ts), f.reason));
|
||||
ctx.notice(me, from.uid, t!(ctx, " [{kind}] \x02{mask}\x02 by {setter} ({when}) — {reason}", kind = f.kind.wire(), mask = f.mask, setter = f.setter, when = human_time(f.ts), reason = f.reason));
|
||||
}
|
||||
}
|
||||
_ => ctx.notice(me, from.uid, "Syntax: FORBID ADD <NICK|CHAN|EMAIL> <mask> <reason> | DEL <NICK|CHAN|EMAIL> <mask> | LIST"),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{parse_duration, Priv, Sender, ServiceCtx, Store};
|
||||
use echo_api::{parse_duration, t, Priv, Sender, ServiceCtx, Store};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
// IGNORE ADD [+expiry] <mask> [reason] | DEL <mask> | LIST: services silently
|
||||
|
|
@ -34,8 +34,12 @@ fn add(me: &str, from: &Sender, rest: &[&str], ctx: &mut ServiceCtx, db: &mut dy
|
|||
let reason = if reason_words.is_empty() { "No reason given".to_string() } else { reason_words.join(" ") };
|
||||
let expires = duration.map(|secs| now() + secs);
|
||||
db.ignore_add(mask, &reason, expires);
|
||||
let expiry = if expires.is_some() { " (temporary)" } else { "" };
|
||||
ctx.notice(me, from.uid, format!("Now ignoring \x02{mask}\x02{expiry}."));
|
||||
let msg = if expires.is_some() {
|
||||
t!(ctx, "Now ignoring \x02{mask}\x02 (temporary).", mask = mask)
|
||||
} else {
|
||||
t!(ctx, "Now ignoring \x02{mask}\x02.", mask = mask)
|
||||
};
|
||||
ctx.notice(me, from.uid, msg);
|
||||
}
|
||||
|
||||
fn del(me: &str, from: &Sender, arg: Option<&str>, ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
|
|
@ -44,9 +48,9 @@ fn del(me: &str, from: &Sender, arg: Option<&str>, ctx: &mut ServiceCtx, db: &mu
|
|||
return;
|
||||
};
|
||||
if db.ignore_del(mask) {
|
||||
ctx.notice(me, from.uid, format!("No longer ignoring \x02{mask}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "No longer ignoring \x02{mask}\x02.", mask = mask));
|
||||
} else {
|
||||
ctx.notice(me, from.uid, format!("\x02{mask}\x02 isn't on the ignore list."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{mask}\x02 isn't on the ignore list.", mask = mask));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -57,13 +61,13 @@ fn list(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
|||
return;
|
||||
}
|
||||
for (i, ig) in ignores.iter().enumerate() {
|
||||
let expiry = match ig.expires {
|
||||
Some(e) => format!(", expires in {}", human_secs(e.saturating_sub(now()))),
|
||||
None => String::new(),
|
||||
let msg = match ig.expires {
|
||||
Some(e) => t!(ctx, "{n}. \x02{mask}\x02 — {reason}, expires in {ttl}", n = i + 1, mask = ig.mask, reason = ig.reason, ttl = human_secs(e.saturating_sub(now()))),
|
||||
None => t!(ctx, "{n}. \x02{mask}\x02 — {reason}", n = i + 1, mask = ig.mask, reason = ig.reason),
|
||||
};
|
||||
ctx.notice(me, from.uid, format!("{}. \x02{}\x02 — {}{}", i + 1, ig.mask, ig.reason, expiry));
|
||||
ctx.notice(me, from.uid, msg);
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("End of ignore list ({} shown).", ignores.len()));
|
||||
ctx.notice(me, from.uid, t!(ctx, "End of ignore list ({count} shown).", count = ignores.len()));
|
||||
}
|
||||
|
||||
fn human_secs(secs: u64) -> String {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Priv, Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Priv, Sender, ServiceCtx, Store};
|
||||
|
||||
// INFO <target> | INFO ADD <target> <note> | INFO DEL <target>: attach a staff
|
||||
// note to an account or channel (a `#name` is a channel, else an account). The
|
||||
|
|
@ -38,11 +38,11 @@ fn set(me: &str, from: &Sender, target: Option<&str>, note: &[&str], ctx: &mut S
|
|||
(false, "account")
|
||||
};
|
||||
if !ok {
|
||||
ctx.notice(me, from.uid, format!("There's no {kind} \x02{target}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "There's no {kind} \x02{target}\x02.", kind = kind, target = target));
|
||||
} else if value.is_some() {
|
||||
ctx.notice(me, from.uid, format!("Staff note set on \x02{target}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Staff note set on \x02{target}\x02.", target = target));
|
||||
} else {
|
||||
ctx.notice(me, from.uid, format!("Staff note on \x02{target}\x02 cleared."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Staff note on \x02{target}\x02 cleared.", target = target));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ fn show(me: &str, from: &Sender, target: &str, ctx: &mut ServiceCtx, db: &mut dy
|
|||
db.resolve_account(target).map(str::to_string).and_then(|a| db.account_note(&a))
|
||||
};
|
||||
match note {
|
||||
Some(n) => ctx.notice(me, from.uid, format!("Staff note on \x02{target}\x02: {n}")),
|
||||
None => ctx.notice(me, from.uid, format!("No staff note on \x02{target}\x02.")),
|
||||
Some(n) => ctx.notice(me, from.uid, t!(ctx, "Staff note on \x02{target}\x02: {note}", target = target, note = n)),
|
||||
None => ctx.notice(me, from.uid, t!(ctx, "No staff note on \x02{target}\x02.", target = target)),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Priv, Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Priv, Sender, ServiceCtx, Store};
|
||||
|
||||
// JUPE <server.name> [reason] | JUPE DEL <server.name> | JUPE LIST: hold a
|
||||
// server name with a fake server so a rogue one can't link (or lift it). Admin-
|
||||
|
|
@ -16,7 +16,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
let by = from.account.unwrap_or(from.nick);
|
||||
let sid = db.jupe_add(name, &format!("({by}) {reason}"));
|
||||
ctx.jupe(name, &sid, &format!("({by}) {reason}"));
|
||||
ctx.notice(me, from.uid, format!("\x02{name}\x02 is now juped."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{name}\x02 is now juped.", name = name));
|
||||
}
|
||||
_ => ctx.notice(me, from.uid, "Syntax: JUPE <server.name> [reason] | JUPE DEL <server.name> | JUPE LIST"),
|
||||
}
|
||||
|
|
@ -30,9 +30,9 @@ fn del(me: &str, from: &Sender, name: Option<&str>, ctx: &mut ServiceCtx, db: &m
|
|||
match db.jupe_del(name) {
|
||||
Some(sid) => {
|
||||
ctx.squit(&sid, "Jupe lifted");
|
||||
ctx.notice(me, from.uid, format!("The jupe on \x02{name}\x02 has been lifted."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "The jupe on \x02{name}\x02 has been lifted.", name = name));
|
||||
}
|
||||
None => ctx.notice(me, from.uid, format!("\x02{name}\x02 isn't juped.")),
|
||||
None => ctx.notice(me, from.uid, t!(ctx, "\x02{name}\x02 isn't juped.", name = name)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ fn list(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
|||
return;
|
||||
}
|
||||
for (name, sid, reason) in &jupes {
|
||||
ctx.notice(me, from.uid, format!(" \x02{name}\x02 ({sid}) — {reason}"));
|
||||
ctx.notice(me, from.uid, t!(ctx, " \x02{name}\x02 ({sid}) — {reason}", name = name, sid = sid, reason = reason));
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("End of jupe list ({} shown).", jupes.len()));
|
||||
ctx.notice(me, from.uid, t!(ctx, "End of jupe list ({count} shown).", count = jupes.len()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{NetView, Priv, Sender, ServiceCtx};
|
||||
use echo_api::{t, NetView, Priv, Sender, ServiceCtx};
|
||||
|
||||
// KICK <#channel> <nick> [reason]: remove a user from a channel, sourced from
|
||||
// OperServ so it's clearly a staff action. Admin-only.
|
||||
|
|
@ -16,11 +16,11 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
return;
|
||||
};
|
||||
let Some(uid) = net.uid_by_nick(target).map(str::to_string) else {
|
||||
ctx.notice(me, from.uid, format!("There's no \x02{target}\x02 online."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "There's no \x02{target}\x02 online.", target = target));
|
||||
return;
|
||||
};
|
||||
let by = from.account.unwrap_or(from.nick);
|
||||
let reason = if args.len() > 3 { args[3..].join(" ") } else { "Removed by services".to_string() };
|
||||
ctx.kick(me, chan, &uid, &format!("({by}) {reason}"));
|
||||
ctx.notice(me, from.uid, format!("\x02{target}\x02 kicked from \x02{chan}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{target}\x02 kicked from \x02{chan}\x02.", target = target, chan = chan));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{NetView, Priv, Sender, ServiceCtx};
|
||||
use echo_api::{t, NetView, Priv, Sender, ServiceCtx};
|
||||
|
||||
// KILL <nick> [reason]: disconnect a user from the network. Admin-only.
|
||||
pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net: &dyn NetView) {
|
||||
|
|
@ -11,11 +11,11 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
return;
|
||||
};
|
||||
let Some(uid) = net.uid_by_nick(target).map(str::to_string) else {
|
||||
ctx.notice(me, from.uid, format!("There's no \x02{target}\x02 online."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "There's no \x02{target}\x02 online.", target = target));
|
||||
return;
|
||||
};
|
||||
let by = from.account.unwrap_or(from.nick);
|
||||
let reason = if args.len() > 2 { args[2..].join(" ") } else { "No reason given".to_string() };
|
||||
ctx.kill(me, &uid, &format!("({by}) {reason}"));
|
||||
ctx.notice(me, from.uid, format!("\x02{target}\x02 has been disconnected."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{target}\x02 has been disconnected.", target = target));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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::{HelpEntry, NetView, Sender, Service, ServiceCtx, Store};
|
||||
use echo_api::{t, HelpEntry, NetView, Sender, Service, ServiceCtx, Store};
|
||||
|
||||
#[path = "xline.rs"]
|
||||
mod xline;
|
||||
|
|
@ -106,7 +106,7 @@ impl Service for OperServ {
|
|||
Some(cmd) if cmd.eq_ignore_ascii_case("REHASH") => rehash::handle(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.")),
|
||||
Some(other) => ctx.notice(me, from.uid, t!(ctx, "I don't know \x02{other}\x02. Try \x02HELP\x02.", other = other)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{human_time, NetView, Priv, Sender, ServiceCtx};
|
||||
use echo_api::{human_time, t, NetView, Priv, Sender, ServiceCtx};
|
||||
|
||||
// LOGSEARCH [pattern]: search the recent action log — every kick, kill, ban,
|
||||
// registration/drop, akill, suspension, vhost, note, and so on. A bare id (as
|
||||
|
|
@ -17,7 +17,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
return;
|
||||
}
|
||||
for h in &hits {
|
||||
ctx.notice(me, from.uid, format!("[\x02#{}\x02] {} — {}", h.id, human_time(h.ts), h.summary));
|
||||
ctx.notice(me, from.uid, t!(ctx, "[\x02#{id}\x02] {when} — {summary}", id = h.id, when = human_time(h.ts), summary = h.summary));
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("End of results ({} shown, newest first — refine the search to narrow).", hits.len()));
|
||||
ctx.notice(me, from.uid, t!(ctx, "End of results ({count} shown, newest first — refine the search to narrow).", count = hits.len()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{NetView, Priv, Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, NetView, Priv, Sender, ServiceCtx, Store};
|
||||
|
||||
// MODE <#channel> <modes> [params]: set channel modes as a services override
|
||||
// (forced, so it applies regardless of the current TS). Admin-only. Status-mode
|
||||
|
|
@ -43,5 +43,5 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
}
|
||||
let full = if out_params.is_empty() { modes.to_string() } else { format!("{} {}", modes, out_params.join(" ")) };
|
||||
ctx.channel_mode(me, chan, &full);
|
||||
ctx.notice(me, from.uid, format!("Set \x02{modes}\x02 on \x02{chan}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Set \x02{modes}\x02 on \x02{chan}\x02.", modes = modes, chan = chan));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{human_time, parse_duration, Priv, Sender, ServiceCtx, Store};
|
||||
use echo_api::{human_time, parse_duration, t, Priv, Sender, ServiceCtx, Store};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
// The event letters a watch can carry, in help order.
|
||||
|
|
@ -20,7 +20,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
Some("VIEW") => list(me, from, true, args.get(2).copied(), ctx, db),
|
||||
Some("CLEAR") => match db.notify_clear() {
|
||||
Ok(0) => ctx.notice(me, from.uid, "The notify list is already empty."),
|
||||
Ok(n) => ctx.notice(me, from.uid, format!("Cleared \x02{n}\x02 notify watch(es).")),
|
||||
Ok(n) => ctx.notice(me, from.uid, t!(ctx, "Cleared \x02{n}\x02 notify watch(es).", n = n)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
},
|
||||
_ => ctx.notice(me, from.uid, SYNTAX),
|
||||
|
|
@ -43,7 +43,7 @@ fn add(me: &str, from: &Sender, rest: &[&str], ctx: &mut ServiceCtx, db: &mut dy
|
|||
Some(0) => None,
|
||||
Some(secs) => Some(now() + secs),
|
||||
None => {
|
||||
ctx.notice(me, from.uid, format!("\x02{d}\x02 isn't a valid expiry — try 30d, 12h, 45m, or 0."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{d}\x02 isn't a valid expiry — try 30d, 12h, 45m, or 0.", d = d));
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
|
@ -57,7 +57,7 @@ fn add(me: &str, from: &Sender, rest: &[&str], ctx: &mut ServiceCtx, db: &mut dy
|
|||
} else if flags_tok.chars().all(|c| ALL_FLAGS.contains(c)) && !flags_tok.is_empty() {
|
||||
flags_tok.to_string()
|
||||
} else {
|
||||
ctx.notice(me, from.uid, format!("Unknown flag(s). Valid: \x02{ALL_FLAGS}\x02 (or \x02*\x02 for all). {FLAG_LEGEND}"));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Unknown flag(s). Valid: \x02{flags}\x02 (or \x02*\x02 for all). c=connect d=disconnect o=oper-up j=join p=part k=kick m=chan-mode t=topic n=nick u=user-mode s=service-cmd S=SET", flags = ALL_FLAGS));
|
||||
return;
|
||||
};
|
||||
let Some(&mask) = rest.get(2) else {
|
||||
|
|
@ -78,8 +78,8 @@ fn add(me: &str, from: &Sender, rest: &[&str], ctx: &mut ServiceCtx, db: &mut dy
|
|||
let reason = rest[3..].join(" ");
|
||||
let setter = from.account.unwrap_or(from.nick);
|
||||
match db.notify_add(mask, &flags, &reason, setter, expires) {
|
||||
Ok(true) => ctx.notice(me, from.uid, format!("Now watching \x02{mask}\x02 [{flags}].")),
|
||||
Ok(false) => ctx.notice(me, from.uid, format!("Updated the watch on \x02{mask}\x02 [{flags}].")),
|
||||
Ok(true) => ctx.notice(me, from.uid, t!(ctx, "Now watching \x02{mask}\x02 [{flags}].", mask = mask, flags = flags)),
|
||||
Ok(false) => ctx.notice(me, from.uid, t!(ctx, "Updated the watch on \x02{mask}\x02 [{flags}].", mask = mask, flags = flags)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
@ -94,15 +94,15 @@ fn del(me: &str, from: &Sender, arg: Option<&str>, ctx: &mut ServiceCtx, db: &mu
|
|||
Ok(n) if n >= 1 => match db.notifies().get(n - 1) {
|
||||
Some(v) => v.mask.clone(),
|
||||
None => {
|
||||
ctx.notice(me, from.uid, format!("There's no notify number \x02{n}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "There's no notify number \x02{n}\x02.", n = n));
|
||||
return;
|
||||
}
|
||||
},
|
||||
_ => arg.to_string(),
|
||||
};
|
||||
match db.notify_del(&mask) {
|
||||
Ok(true) => ctx.notice(me, from.uid, format!("Stopped watching \x02{mask}\x02.")),
|
||||
Ok(false) => ctx.notice(me, from.uid, format!("No watch matches \x02{mask}\x02.")),
|
||||
Ok(true) => ctx.notice(me, from.uid, t!(ctx, "Stopped watching \x02{mask}\x02.", mask = mask)),
|
||||
Ok(false) => ctx.notice(me, from.uid, t!(ctx, "No watch matches \x02{mask}\x02.", mask = mask)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
@ -121,21 +121,19 @@ fn list(me: &str, from: &Sender, verbose: bool, pattern: Option<&str>, ctx: &mut
|
|||
continue;
|
||||
}
|
||||
}
|
||||
let expiry = match n.expires {
|
||||
Some(e) => format!(", expires in {}", human_secs(e.saturating_sub(now()))),
|
||||
None => String::new(),
|
||||
let msg = match (verbose, n.expires) {
|
||||
(true, Some(e)) => t!(ctx, "{n}. \x02{mask}\x02 [{flags}] by {setter} ({when}), expires in {ttl} — {reason}", n = i + 1, mask = n.mask, flags = n.flags, setter = n.setter, when = human_time(n.ts), ttl = human_secs(e.saturating_sub(now())), reason = n.reason),
|
||||
(true, None) => t!(ctx, "{n}. \x02{mask}\x02 [{flags}] by {setter} ({when}) — {reason}", n = i + 1, mask = n.mask, flags = n.flags, setter = n.setter, when = human_time(n.ts), reason = n.reason),
|
||||
(false, Some(e)) => t!(ctx, "{n}. \x02{mask}\x02 [{flags}] — {reason}, expires in {ttl}", n = i + 1, mask = n.mask, flags = n.flags, reason = n.reason, ttl = human_secs(e.saturating_sub(now()))),
|
||||
(false, None) => t!(ctx, "{n}. \x02{mask}\x02 [{flags}] — {reason}", n = i + 1, mask = n.mask, flags = n.flags, reason = n.reason),
|
||||
};
|
||||
if verbose {
|
||||
ctx.notice(me, from.uid, format!("{}. \x02{}\x02 [{}] by {} ({}){} — {}", i + 1, n.mask, n.flags, n.setter, human_time(n.ts), expiry, n.reason));
|
||||
} else {
|
||||
ctx.notice(me, from.uid, format!("{}. \x02{}\x02 [{}] — {}{}", i + 1, n.mask, n.flags, n.reason, expiry));
|
||||
}
|
||||
ctx.notice(me, from.uid, msg);
|
||||
shown += 1;
|
||||
}
|
||||
if shown == 0 {
|
||||
ctx.notice(me, from.uid, "No matching notify entries.");
|
||||
} else {
|
||||
ctx.notice(me, from.uid, format!("End of notify list ({shown} shown)."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "End of notify list ({shown} shown).", shown = shown));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -158,5 +156,4 @@ fn now() -> u64 {
|
|||
SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0)
|
||||
}
|
||||
|
||||
const FLAG_LEGEND: &str = "c=connect d=disconnect o=oper-up j=join p=part k=kick m=chan-mode t=topic n=nick u=user-mode s=service-cmd S=SET";
|
||||
const SYNTAX: &str = "Syntax: NOTIFY ADD +<expiry> <flags|*> <mask> <reason> | NOTIFY DEL <mask|number> | NOTIFY LIST [pattern] | NOTIFY VIEW [pattern] | NOTIFY CLEAR";
|
||||
const SYNTAX: &str ="Syntax: NOTIFY ADD +<expiry> <flags|*> <mask> <reason> | NOTIFY DEL <mask|number> | NOTIFY LIST [pattern] | NOTIFY VIEW [pattern] | NOTIFY CLEAR";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{parse_duration, Priv, Privs, Sender, ServiceCtx, Store};
|
||||
use echo_api::{parse_duration, t, Priv, Privs, Sender, ServiceCtx, Store};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
// OPER ADD <account> <priv[,priv…]> [+duration] | OPER DEL <account> | OPER LIST:
|
||||
|
|
@ -25,7 +25,7 @@ fn add(me: &str, from: &Sender, account: Option<&str>, privs: Option<&str>, dur:
|
|||
return;
|
||||
};
|
||||
let Some(account) = db.resolve_account(account).map(str::to_string) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{account}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{account}\x02 isn't registered.", account = account));
|
||||
return;
|
||||
};
|
||||
// Parse the privilege names, rejecting the whole grant on a typo rather than
|
||||
|
|
@ -39,17 +39,22 @@ fn add(me: &str, from: &Sender, account: Option<&str>, privs: Option<&str>, dur:
|
|||
}
|
||||
}
|
||||
if !unknown.is_empty() {
|
||||
ctx.notice(me, from.uid, format!("Unknown privilege(s): \x02{}\x02 (valid: {}).", unknown.join(", "), Priv::valid_names()));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Unknown privilege(s): \x02{privs}\x02 (valid: {valid}).", privs = unknown.join(", "), valid = Priv::valid_names()));
|
||||
return;
|
||||
}
|
||||
if names.is_empty() {
|
||||
ctx.notice(me, from.uid, format!("No privileges given (valid: {}).", Priv::valid_names()));
|
||||
ctx.notice(me, from.uid, t!(ctx, "No privileges given (valid: {valid}).", valid = Priv::valid_names()));
|
||||
return;
|
||||
}
|
||||
let expires = dur.and_then(|d| d.strip_prefix('+')).and_then(parse_duration).map(|secs| now() + secs);
|
||||
db.oper_grant(&account, names.clone(), expires);
|
||||
let window = if expires.is_some() { " (temporary)" } else { "" };
|
||||
ctx.notice(me, from.uid, format!("\x02{account}\x02 is now an operator ({}){window}.", names.join(", ")));
|
||||
let privlist = names.join(", ");
|
||||
let msg = if expires.is_some() {
|
||||
t!(ctx, "\x02{account}\x02 is now an operator ({privs}) (temporary).", account = account, privs = privlist)
|
||||
} else {
|
||||
t!(ctx, "\x02{account}\x02 is now an operator ({privs}).", account = account, privs = privlist)
|
||||
};
|
||||
ctx.notice(me, from.uid, msg);
|
||||
}
|
||||
|
||||
fn now() -> u64 {
|
||||
|
|
@ -63,9 +68,9 @@ fn del(me: &str, from: &Sender, account: Option<&str>, ctx: &mut ServiceCtx, db:
|
|||
};
|
||||
let canonical = db.resolve_account(account).map(str::to_string).unwrap_or_else(|| account.to_string());
|
||||
if db.oper_revoke(&canonical) {
|
||||
ctx.notice(me, from.uid, format!("\x02{canonical}\x02 is no longer a runtime operator."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{canonical}\x02 is no longer a runtime operator.", canonical = canonical));
|
||||
} else {
|
||||
ctx.notice(me, from.uid, format!("\x02{account}\x02 isn't a runtime operator (config opers are set in the daemon config)."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{account}\x02 isn't a runtime operator (config opers are set in the daemon config).", account = account));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -76,9 +81,14 @@ fn list(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
|||
return;
|
||||
}
|
||||
for (account, privs, expires) in &opers {
|
||||
let window = if expires.is_some() { " (temporary)" } else { "" };
|
||||
let tier = Privs::from_names(privs).tier();
|
||||
ctx.notice(me, from.uid, format!(" \x02{account}\x02 [{tier}] — {}{window}", privs.join(", ")));
|
||||
let privlist = privs.join(", ");
|
||||
let msg = if expires.is_some() {
|
||||
t!(ctx, " \x02{account}\x02 [{tier}] — {privs} (temporary)", account = account, tier = tier, privs = privlist)
|
||||
} else {
|
||||
t!(ctx, " \x02{account}\x02 [{tier}] — {privs}", account = account, tier = tier, privs = privlist)
|
||||
};
|
||||
ctx.notice(me, from.uid, msg);
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("End of runtime operator list ({} shown).", opers.len()));
|
||||
ctx.notice(me, from.uid, t!(ctx, "End of runtime operator list ({count} shown).", count = opers.len()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Priv, Sender, ServiceCtx};
|
||||
use echo_api::{t, Priv, Sender, ServiceCtx};
|
||||
|
||||
// REDACT <#channel|nick> <msgid> [reason]: delete a message by its IRCv3 msgid.
|
||||
// The msgid comes from the reporting oper's client (which saw the tagged message);
|
||||
|
|
@ -15,5 +15,5 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx) {
|
|||
};
|
||||
let reason = if args.len() > 3 { args[3..].join(" ") } else { String::new() };
|
||||
ctx.redact(me, target, msgid, &reason);
|
||||
ctx.notice(me, from.uid, format!("Redacted message \x02{msgid}\x02 in \x02{target}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Redacted message \x02{msgid}\x02 in \x02{target}\x02.", msgid = msgid, target = target));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{NetView, Priv, Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, NetView, Priv, Sender, ServiceCtx, Store};
|
||||
|
||||
// SESSION LIST <min> | SESSION VIEW <ip>: inspect live per-IP session counts.
|
||||
// EXCEPTION ADD <ip-mask> <limit> [reason] | DEL <ip-mask> | LIST: manage the
|
||||
|
|
@ -13,20 +13,20 @@ pub fn handle_session(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceC
|
|||
let min = args.get(2).and_then(|n| n.parse::<u32>().ok()).unwrap_or(2);
|
||||
let over = net.sessions_over(min);
|
||||
if over.is_empty() {
|
||||
ctx.notice(me, from.uid, format!("No IP has {min} or more sessions."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "No IP has {min} or more sessions.", min = min));
|
||||
return;
|
||||
}
|
||||
for (ip, n) in &over {
|
||||
ctx.notice(me, from.uid, format!(" \x02{n}\x02 sessions from \x02{ip}\x02"));
|
||||
ctx.notice(me, from.uid, t!(ctx, " \x02{n}\x02 sessions from \x02{ip}\x02", n = n, ip = ip));
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("End of session list ({} IP(s)).", over.len()));
|
||||
ctx.notice(me, from.uid, t!(ctx, "End of session list ({count} IP(s)).", count = over.len()));
|
||||
}
|
||||
Some("VIEW") => {
|
||||
let Some(&ip) = args.get(2) else {
|
||||
ctx.notice(me, from.uid, "Syntax: SESSION VIEW <ip>");
|
||||
return;
|
||||
};
|
||||
ctx.notice(me, from.uid, format!("\x02{ip}\x02 has \x02{}\x02 live session(s).", net.session_count(ip)));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{ip}\x02 has \x02{count}\x02 live session(s).", ip = ip, count = net.session_count(ip)));
|
||||
}
|
||||
_ => ctx.notice(me, from.uid, "Syntax: SESSION LIST <min> | SESSION VIEW <ip>"),
|
||||
}
|
||||
|
|
@ -45,8 +45,12 @@ pub fn handle_exception(me: &str, from: &Sender, args: &[&str], ctx: &mut Servic
|
|||
};
|
||||
let reason = if args.len() > 4 { args[4..].join(" ") } else { "No reason given".to_string() };
|
||||
db.session_except_add(mask, limit, &reason);
|
||||
let note = if limit == 0 { " (unlimited)".to_string() } else { format!(" (limit {limit})") };
|
||||
ctx.notice(me, from.uid, format!("Session exception for \x02{mask}\x02 set{note}."));
|
||||
let msg = if limit == 0 {
|
||||
t!(ctx, "Session exception for \x02{mask}\x02 set (unlimited).", mask = mask)
|
||||
} else {
|
||||
t!(ctx, "Session exception for \x02{mask}\x02 set (limit {limit}).", mask = mask, limit = limit)
|
||||
};
|
||||
ctx.notice(me, from.uid, msg);
|
||||
}
|
||||
Some("DEL") | Some("REMOVE") => {
|
||||
let Some(&mask) = args.get(2) else {
|
||||
|
|
@ -54,9 +58,9 @@ pub fn handle_exception(me: &str, from: &Sender, args: &[&str], ctx: &mut Servic
|
|||
return;
|
||||
};
|
||||
if db.session_except_del(mask) {
|
||||
ctx.notice(me, from.uid, format!("Session exception for \x02{mask}\x02 removed."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Session exception for \x02{mask}\x02 removed.", mask = mask));
|
||||
} else {
|
||||
ctx.notice(me, from.uid, format!("No session exception matches \x02{mask}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "No session exception matches \x02{mask}\x02.", mask = mask));
|
||||
}
|
||||
}
|
||||
Some("LIST") => {
|
||||
|
|
@ -66,10 +70,10 @@ pub fn handle_exception(me: &str, from: &Sender, args: &[&str], ctx: &mut Servic
|
|||
return;
|
||||
}
|
||||
for (mask, limit, reason) in &list {
|
||||
let lim = if *limit == 0 { "unlimited".to_string() } else { limit.to_string() };
|
||||
ctx.notice(me, from.uid, format!(" \x02{mask}\x02 → {lim} — {reason}"));
|
||||
let lim = if *limit == 0 { t!(ctx, "unlimited") } else { limit.to_string() };
|
||||
ctx.notice(me, from.uid, t!(ctx, " \x02{mask}\x02 → {lim} — {reason}", mask = mask, lim = lim, reason = reason));
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("End of exception list ({} shown).", list.len()));
|
||||
ctx.notice(me, from.uid, t!(ctx, "End of exception list ({count} shown).", count = list.len()));
|
||||
}
|
||||
_ => ctx.notice(me, from.uid, "Syntax: EXCEPTION ADD <ip-mask> <limit> [reason] | EXCEPTION DEL <ip-mask> | EXCEPTION LIST"),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Priv, Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Priv, Sender, ServiceCtx, Store};
|
||||
|
||||
// SET READONLY {ON|OFF}: put services into (or out of) read-only lockdown.
|
||||
// While on, no new registrations or changes are accepted; existing data and
|
||||
|
|
@ -12,7 +12,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
Some("READONLY") => {
|
||||
let Some(on) = args.get(2).and_then(|s| parse_toggle(s)) else {
|
||||
let state = if db.readonly() { "ON" } else { "OFF" };
|
||||
ctx.notice(me, from.uid, format!("READONLY is currently \x02{state}\x02. Syntax: SET READONLY {{ON|OFF}}"));
|
||||
ctx.notice(me, from.uid, t!(ctx, "READONLY is currently \x02{state}\x02. Syntax: SET READONLY {ON|OFF}", state = state));
|
||||
return;
|
||||
};
|
||||
db.set_readonly(on);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Priv, Sender, ServiceCtx};
|
||||
use echo_api::{t, Priv, Sender, ServiceCtx};
|
||||
|
||||
// SHUTDOWN / RESTART [reason]: stop the services process. `restart` selects
|
||||
// which — a restart exits so a supervisor respawns us, a shutdown stays down.
|
||||
|
|
@ -7,13 +7,13 @@ use echo_api::{Priv, Sender, ServiceCtx};
|
|||
pub fn handle(me: &str, from: &Sender, restart: bool, args: &[&str], ctx: &mut ServiceCtx) {
|
||||
let cmd = if restart { "RESTART" } else { "SHUTDOWN" };
|
||||
if !from.privs.has(Priv::Root) {
|
||||
ctx.notice(me, from.uid, format!("Access denied — {cmd} needs the \x02root\x02 privilege."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Access denied — {cmd} needs the \x02root\x02 privilege.", cmd = cmd));
|
||||
return;
|
||||
}
|
||||
let reason = if args.len() > 1 { args[1..].join(" ") } else { format!("Services {}", if restart { "restarting" } else { "shutting down" }) };
|
||||
let by = from.account.unwrap_or(from.nick);
|
||||
ctx.global(me, format!("[\x02Network\x02] Services are {} ({reason}).", if restart { "restarting" } else { "going down" }));
|
||||
ctx.notice(me, from.uid, format!("Services are {}.", if restart { "restarting" } else { "shutting down" }));
|
||||
ctx.notice(me, from.uid, if restart { "Services are restarting." } else { "Services are shutting down." });
|
||||
// The action is last, so the notices above are flushed before we exit.
|
||||
ctx.shutdown(restart, format!("{cmd} by {by}: {reason}"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{parse_duration, Priv, Sender, ServiceCtx, Store};
|
||||
use echo_api::{parse_duration, t, Priv, Sender, ServiceCtx, Store};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
// Default filter flags: apply to privmsg/notice/part/quit, exempt opers, match on
|
||||
|
|
@ -29,7 +29,7 @@ fn add(me: &str, from: &Sender, rest: &[&str], ctx: &mut ServiceCtx, db: &mut dy
|
|||
return;
|
||||
};
|
||||
if !echo_api::is_filter_action(action) {
|
||||
ctx.notice(me, from.uid, format!("\x02{action}\x02 isn't a filter action. Use one of: {}.", echo_api::FILTER_ACTIONS.join(", ")));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{action}\x02 isn't a filter action. Use one of: {actions}.", action = action, actions = echo_api::FILTER_ACTIONS.join(", ")));
|
||||
return;
|
||||
}
|
||||
// An optional leading +duration, then the pattern (one glob token), then reason.
|
||||
|
|
@ -57,9 +57,14 @@ fn add(me: &str, from: &Sender, rest: &[&str], ctx: &mut ServiceCtx, db: &mut dy
|
|||
match db.filter_add(pattern, action, DEFAULT_FLAGS, setter, &reason, expires) {
|
||||
Ok(fresh) => {
|
||||
ctx.filter_push(echo_api::encode_filter(pattern, action, DEFAULT_FLAGS, duration.unwrap_or(0), &reason));
|
||||
let word = if fresh { "added" } else { "updated" };
|
||||
let expiry = if expires.is_some() { " (temporary)" } else { "" };
|
||||
ctx.notice(me, from.uid, format!("Spam filter {word}: \x02{pattern}\x02 ({}){expiry}.", action.to_ascii_lowercase()));
|
||||
let action_l = action.to_ascii_lowercase();
|
||||
let msg = match (fresh, expires.is_some()) {
|
||||
(true, true) => t!(ctx, "Spam filter added: \x02{pattern}\x02 ({action}) (temporary).", pattern = pattern, action = action_l),
|
||||
(true, false) => t!(ctx, "Spam filter added: \x02{pattern}\x02 ({action}).", pattern = pattern, action = action_l),
|
||||
(false, true) => t!(ctx, "Spam filter updated: \x02{pattern}\x02 ({action}) (temporary).", pattern = pattern, action = action_l),
|
||||
(false, false) => t!(ctx, "Spam filter updated: \x02{pattern}\x02 ({action}).", pattern = pattern, action = action_l),
|
||||
};
|
||||
ctx.notice(me, from.uid, msg);
|
||||
}
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
|
|
@ -75,15 +80,15 @@ fn del(me: &str, from: &Sender, arg: Option<&str>, ctx: &mut ServiceCtx, db: &mu
|
|||
Ok(n) if n >= 1 => match db.filters().get(n - 1) {
|
||||
Some(f) => f.pattern.clone(),
|
||||
None => {
|
||||
ctx.notice(me, from.uid, format!("There's no spam filter number \x02{n}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "There's no spam filter number \x02{n}\x02.", n = n));
|
||||
return;
|
||||
}
|
||||
},
|
||||
_ => arg.to_string(),
|
||||
};
|
||||
match db.filter_del(&pattern) {
|
||||
Ok(true) => ctx.notice(me, from.uid, format!("Spam filter \x02{pattern}\x02 removed. It stops being re-applied, but stays live on the ircd until the next services relink or a manual \x02/FILTER\x02 removal.")),
|
||||
Ok(false) => ctx.notice(me, from.uid, format!("No spam filter matches \x02{pattern}\x02.")),
|
||||
Ok(true) => ctx.notice(me, from.uid, t!(ctx, "Spam filter \x02{pattern}\x02 removed. It stops being re-applied, but stays live on the ircd until the next services relink or a manual \x02/FILTER\x02 removal.", pattern = pattern)),
|
||||
Ok(false) => ctx.notice(me, from.uid, t!(ctx, "No spam filter matches \x02{pattern}\x02.", pattern = pattern)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
@ -97,17 +102,17 @@ fn list(me: &str, from: &Sender, pattern: Option<&str>, ctx: &mut ServiceCtx, db
|
|||
continue;
|
||||
}
|
||||
}
|
||||
let expiry = match f.expires {
|
||||
Some(e) => format!(", expires in {}", human_secs(e.saturating_sub(now()))),
|
||||
None => String::new(),
|
||||
let msg = match f.expires {
|
||||
Some(e) => t!(ctx, "{n}. \x02{pattern}\x02 [{action}] by {setter} — {reason}, expires in {ttl}", n = i + 1, pattern = f.pattern, action = f.action, setter = f.setter, reason = f.reason, ttl = human_secs(e.saturating_sub(now()))),
|
||||
None => t!(ctx, "{n}. \x02{pattern}\x02 [{action}] by {setter} — {reason}", n = i + 1, pattern = f.pattern, action = f.action, setter = f.setter, reason = f.reason),
|
||||
};
|
||||
ctx.notice(me, from.uid, format!("{}. \x02{}\x02 [{}] by {} — {}{}", i + 1, f.pattern, f.action, f.setter, f.reason, expiry));
|
||||
ctx.notice(me, from.uid, msg);
|
||||
shown += 1;
|
||||
}
|
||||
if shown == 0 {
|
||||
ctx.notice(me, from.uid, "No matching spam filters.");
|
||||
} else {
|
||||
ctx.notice(me, from.uid, format!("End of spam-filter list ({shown} shown)."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "End of spam-filter list ({shown} shown).", shown = shown));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Priv, Sender, ServiceCtx, Store, XlineKind};
|
||||
use echo_api::{t, Priv, Sender, ServiceCtx, Store, XlineKind};
|
||||
|
||||
// STATS: an at-a-glance summary of the enforcement state OperServ holds — how
|
||||
// many network bans of each kind and how many services ignores are live.
|
||||
|
|
@ -13,5 +13,5 @@ pub fn handle(me: &str, from: &Sender, db: &mut dyn Store, ctx: &mut ServiceCtx)
|
|||
let qlines = akills.iter().filter(|a| a.kind == XlineKind::Qline).count();
|
||||
let rlines = akills.iter().filter(|a| a.kind == XlineKind::Rline).count();
|
||||
let ignores = db.ignores().len();
|
||||
ctx.notice(me, from.uid, format!("Live network bans: \x02{glines}\x02 AKILL, \x02{qlines}\x02 SQLINE, \x02{rlines}\x02 SNLINE. Services ignores: \x02{ignores}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Live network bans: \x02{glines}\x02 AKILL, \x02{qlines}\x02 SQLINE, \x02{rlines}\x02 SNLINE. Services ignores: \x02{ignores}\x02.", glines = glines, qlines = qlines, rlines = rlines, ignores = ignores));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{NetView, Priv, Sender, ServiceCtx};
|
||||
use echo_api::{t, NetView, Priv, Sender, ServiceCtx};
|
||||
|
||||
// SVSNICK <nick> <newnick>: force a user to change nick. Admin-only.
|
||||
pub fn nick(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net: &dyn NetView) {
|
||||
|
|
@ -11,15 +11,15 @@ pub fn nick(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net: &
|
|||
return;
|
||||
};
|
||||
if newnick.is_empty() || newnick.starts_with('#') || newnick.contains(|c: char| c.is_whitespace() || c == ',') {
|
||||
ctx.notice(me, from.uid, format!("\x02{newnick}\x02 isn't a valid nick."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{newnick}\x02 isn't a valid nick.", newnick = newnick));
|
||||
return;
|
||||
}
|
||||
let Some(uid) = net.uid_by_nick(target).map(str::to_string) else {
|
||||
ctx.notice(me, from.uid, format!("There's no \x02{target}\x02 online."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "There's no \x02{target}\x02 online.", target = target));
|
||||
return;
|
||||
};
|
||||
ctx.force_nick(&uid, newnick);
|
||||
ctx.notice(me, from.uid, format!("\x02{target}\x02 has been renamed to \x02{newnick}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{target}\x02 has been renamed to \x02{newnick}\x02.", target = target, newnick = newnick));
|
||||
}
|
||||
|
||||
// SVSJOIN <nick> <#channel> [key]: force a user into a channel. Admin-only.
|
||||
|
|
@ -37,11 +37,11 @@ pub fn join(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net: &
|
|||
return;
|
||||
}
|
||||
let Some(uid) = net.uid_by_nick(target).map(str::to_string) else {
|
||||
ctx.notice(me, from.uid, format!("There's no \x02{target}\x02 online."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "There's no \x02{target}\x02 online.", target = target));
|
||||
return;
|
||||
};
|
||||
ctx.force_join(&uid, chan, args.get(3).copied().unwrap_or(""));
|
||||
ctx.notice(me, from.uid, format!("\x02{target}\x02 has been joined to \x02{chan}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{target}\x02 has been joined to \x02{chan}\x02.", target = target, chan = chan));
|
||||
}
|
||||
|
||||
// SVSPART <nick> <#channel> [reason]: force a user out of a channel. Admin-only.
|
||||
|
|
@ -59,10 +59,10 @@ pub fn part(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net: &
|
|||
return;
|
||||
}
|
||||
let Some(uid) = net.uid_by_nick(target).map(str::to_string) else {
|
||||
ctx.notice(me, from.uid, format!("There's no \x02{target}\x02 online."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "There's no \x02{target}\x02 online.", target = target));
|
||||
return;
|
||||
};
|
||||
let reason = if args.len() > 3 { args[3..].join(" ") } else { "Removed by services".to_string() };
|
||||
ctx.force_part(&uid, chan, &reason);
|
||||
ctx.notice(me, from.uid, format!("\x02{target}\x02 has been removed from \x02{chan}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{target}\x02 has been removed from \x02{chan}\x02.", target = target, chan = chan));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{parse_duration, Priv, Sender, ServiceCtx, Store, XlineKind};
|
||||
use echo_api::{parse_duration, t, Priv, Sender, ServiceCtx, Store, XlineKind};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
// A network-ban command family (AKILL, SQLINE, …): the ircd X-line `kind`, the
|
||||
|
|
@ -14,14 +14,14 @@ pub struct Xline {
|
|||
impl Xline {
|
||||
pub fn handle(&self, me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
if !from.privs.has(Priv::Oper) {
|
||||
ctx.notice(me, from.uid, format!("Access denied — {} needs the \x02operator\x02 privilege.", self.name));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Access denied — {name} needs the \x02operator\x02 privilege.", name = self.name));
|
||||
return;
|
||||
}
|
||||
match args.get(1).map(|s| s.to_ascii_uppercase()).as_deref() {
|
||||
Some("ADD") => self.add(me, from, &args[2..], ctx, db),
|
||||
Some("DEL") | Some("REMOVE") => self.del(me, from, args.get(2).copied(), ctx, db),
|
||||
Some("LIST") | Some("VIEW") => self.list(me, from, args.get(2).copied(), ctx, db),
|
||||
_ => ctx.notice(me, from.uid, format!("Syntax: {0} ADD [+expiry] <{1}> <reason> | {0} DEL <{1}|number> | {0} LIST [pattern]", self.name, self.target)),
|
||||
_ => ctx.notice(me, from.uid, t!(ctx, "Syntax: {name} ADD [+expiry] <{target}> <reason> | {name} DEL <{target}|number> | {name} LIST [pattern]", name = self.name, target = self.target)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -33,11 +33,11 @@ impl Xline {
|
|||
rest = &rest[1..];
|
||||
}
|
||||
let Some((&raw, reason_words)) = rest.split_first() else {
|
||||
ctx.notice(me, from.uid, format!("Syntax: {} ADD [+expiry] <{}> <reason>", self.name, self.target));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Syntax: {name} ADD [+expiry] <{target}> <reason>", name = self.name, target = self.target));
|
||||
return;
|
||||
};
|
||||
let Some(mask) = (self.normalize)(raw) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{raw}\x02 isn't a valid \x02{}\x02 mask.", self.target));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{raw}\x02 isn't a valid \x02{target}\x02 mask.", raw = raw, target = self.target));
|
||||
return;
|
||||
};
|
||||
if reason_words.is_empty() {
|
||||
|
|
@ -54,9 +54,13 @@ impl Xline {
|
|||
match db.akill_add(self.kind, &mask, setter, &reason, expires) {
|
||||
Ok(fresh) => {
|
||||
ctx.add_line(self.kind, &mask, from.nick, duration.unwrap_or(0), &reason);
|
||||
let word = if fresh { "added" } else { "updated" };
|
||||
let expiry = if expires.is_some() { " (temporary)" } else { "" };
|
||||
ctx.notice(me, from.uid, format!("{} {word} for \x02{mask}\x02{expiry}.", self.name));
|
||||
let msg = match (fresh, expires.is_some()) {
|
||||
(true, true) => t!(ctx, "{name} added for \x02{mask}\x02 (temporary).", name = self.name, mask = mask),
|
||||
(true, false) => t!(ctx, "{name} added for \x02{mask}\x02.", name = self.name, mask = mask),
|
||||
(false, true) => t!(ctx, "{name} updated for \x02{mask}\x02 (temporary).", name = self.name, mask = mask),
|
||||
(false, false) => t!(ctx, "{name} updated for \x02{mask}\x02.", name = self.name, mask = mask),
|
||||
};
|
||||
ctx.notice(me, from.uid, msg);
|
||||
}
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
|
|
@ -64,7 +68,7 @@ impl Xline {
|
|||
|
||||
fn del(&self, me: &str, from: &Sender, arg: Option<&str>, ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
let Some(arg) = arg else {
|
||||
ctx.notice(me, from.uid, format!("Syntax: {} DEL <{}|number>", self.name, self.target));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Syntax: {name} DEL <{target}|number>", name = self.name, target = self.target));
|
||||
return;
|
||||
};
|
||||
// A number targets the nth entry of this kind in the list; else a mask.
|
||||
|
|
@ -72,7 +76,7 @@ impl Xline {
|
|||
Ok(n) if n >= 1 => match self.mine(db).get(n - 1) {
|
||||
Some(mask) => mask.clone(),
|
||||
None => {
|
||||
ctx.notice(me, from.uid, format!("There's no {} number \x02{n}\x02.", self.name));
|
||||
ctx.notice(me, from.uid, t!(ctx, "There's no {name} number \x02{n}\x02.", name = self.name, n = n));
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
|
@ -81,9 +85,9 @@ impl Xline {
|
|||
match db.akill_del(self.kind, &mask) {
|
||||
Ok(true) => {
|
||||
ctx.del_line(self.kind, &mask);
|
||||
ctx.notice(me, from.uid, format!("{} for \x02{mask}\x02 removed.", self.name));
|
||||
ctx.notice(me, from.uid, t!(ctx, "{name} for \x02{mask}\x02 removed.", name = self.name, mask = mask));
|
||||
}
|
||||
Ok(false) => ctx.notice(me, from.uid, format!("No {} matches \x02{mask}\x02.", self.name)),
|
||||
Ok(false) => ctx.notice(me, from.uid, t!(ctx, "No {name} matches \x02{mask}\x02.", name = self.name, mask = mask)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
@ -97,17 +101,17 @@ impl Xline {
|
|||
continue;
|
||||
}
|
||||
}
|
||||
let expiry = match a.expires {
|
||||
Some(e) => format!(", expires in {}", human_secs(e.saturating_sub(now()))),
|
||||
None => String::new(),
|
||||
let msg = match a.expires {
|
||||
Some(e) => t!(ctx, "{n}. \x02{mask}\x02 by {setter} — {reason}, expires in {ttl}", n = i + 1, mask = a.mask, setter = a.setter, reason = a.reason, ttl = human_secs(e.saturating_sub(now()))),
|
||||
None => t!(ctx, "{n}. \x02{mask}\x02 by {setter} — {reason}", n = i + 1, mask = a.mask, setter = a.setter, reason = a.reason),
|
||||
};
|
||||
ctx.notice(me, from.uid, format!("{}. \x02{}\x02 by {} — {}{}", i + 1, a.mask, a.setter, a.reason, expiry));
|
||||
ctx.notice(me, from.uid, msg);
|
||||
shown += 1;
|
||||
}
|
||||
if shown == 0 {
|
||||
ctx.notice(me, from.uid, format!("No matching {} entries.", self.name));
|
||||
ctx.notice(me, from.uid, t!(ctx, "No matching {name} entries.", name = self.name));
|
||||
} else {
|
||||
ctx.notice(me, from.uid, format!("End of {} list ({shown} shown).", self.name));
|
||||
ctx.notice(me, from.uid, t!(ctx, "End of {name} list ({shown} shown).", name = self.name, shown = shown));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue