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::{Priv, Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Priv, Sender, ServiceCtx, Store};
|
||||
|
||||
// ASSIGN <#channel> <bot> / UNASSIGN <#channel>: put a bot in a channel (or take
|
||||
// it out). Channel founder only (or a services admin).
|
||||
|
|
@ -14,13 +14,13 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
// NOBOT reserves (un)assignment for services operators.
|
||||
let is_admin = from.privs.has(Priv::Admin);
|
||||
if !is_admin && db.channel(chan).is_some_and(|c| c.nobot) {
|
||||
ctx.notice(me, from.uid, format!("\x02{chan}\x02 is set \x02NOBOT\x02 — only a services operator can change its bot."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 is set \x02NOBOT\x02 — only a services operator can change its bot.", chan = chan));
|
||||
return;
|
||||
}
|
||||
if !assigning {
|
||||
match db.unassign_bot(chan) {
|
||||
Ok(true) => ctx.notice(me, from.uid, format!("The bot has left \x02{chan}\x02.")),
|
||||
Ok(false) => ctx.notice(me, from.uid, format!("\x02{chan}\x02 has no bot assigned.")),
|
||||
Ok(true) => ctx.notice(me, from.uid, t!(ctx, "The bot has left \x02{chan}\x02.", chan = chan)),
|
||||
Ok(false) => ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 has no bot assigned.", chan = chan)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
return;
|
||||
|
|
@ -30,16 +30,16 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
return;
|
||||
};
|
||||
let Some(target) = db.bots().into_iter().find(|b| b.nick.eq_ignore_ascii_case(bot)) else {
|
||||
ctx.notice(me, from.uid, format!("There's no bot named \x02{bot}\x02. See \x02BOT LIST\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "There's no bot named \x02{nick}\x02. See \x02BOT LIST\x02.", nick = bot));
|
||||
return;
|
||||
};
|
||||
if target.private && !is_admin {
|
||||
ctx.notice(me, from.uid, format!("Bot \x02{}\x02 is private — only a services operator can assign it.", target.nick));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Bot \x02{nick}\x02 is private — only a services operator can assign it.", nick = target.nick));
|
||||
return;
|
||||
}
|
||||
let botnick = target.nick;
|
||||
match db.assign_bot(chan, &botnick) {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("Bot \x02{botnick}\x02 is now assigned to \x02{chan}\x02.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Bot \x02{botnick}\x02 is now assigned to \x02{chan}\x02.", botnick = botnick, chan = chan)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Priv, Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Priv, Sender, ServiceCtx, Store};
|
||||
|
||||
// AUTOASSIGN <bot> | OFF: set the bot automatically assigned to every newly
|
||||
// registered channel, or turn auto-assignment off. Oper-only (Priv::Admin) —
|
||||
|
|
@ -10,7 +10,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
}
|
||||
let Some(&arg) = args.get(1) else {
|
||||
match db.default_bot() {
|
||||
Some(bot) => ctx.notice(me, from.uid, format!("New channels are auto-assigned \x02{bot}\x02. Use \x02AUTOASSIGN OFF\x02 to stop.")),
|
||||
Some(bot) => ctx.notice(me, from.uid, t!(ctx, "New channels are auto-assigned \x02{bot}\x02. Use \x02AUTOASSIGN OFF\x02 to stop.", bot = bot)),
|
||||
None => ctx.notice(me, from.uid, "No bot is auto-assigned to new channels. Set one with \x02AUTOASSIGN <bot>\x02."),
|
||||
}
|
||||
return;
|
||||
|
|
@ -24,11 +24,11 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
}
|
||||
// Store the bot's canonical nick, so the setting survives a re-cased lookup.
|
||||
let Some(bot) = db.bots().into_iter().find(|b| b.nick.eq_ignore_ascii_case(arg)) else {
|
||||
ctx.notice(me, from.uid, format!("There's no bot named \x02{arg}\x02. See \x02BOTLIST\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "There's no bot named \x02{nick}\x02. See \x02BOTLIST\x02.", nick = arg));
|
||||
return;
|
||||
};
|
||||
match db.set_default_bot(Some(&bot.nick)) {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("New channels will be auto-assigned \x02{}\x02.", bot.nick)),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "New channels will be auto-assigned \x02{nick}\x02.", nick = bot.nick)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{ChanError, Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, ChanError, Sender, ServiceCtx, Store};
|
||||
|
||||
// BADWORDS <#channel> ADD <regex> | DEL <regex> | LIST | CLEAR: manage the
|
||||
// channel's badword patterns. Each entry is a regular expression, so a channel
|
||||
|
|
@ -21,9 +21,9 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
}
|
||||
let pattern = args[3..].join(" ");
|
||||
match db.badword_add(chan, &pattern) {
|
||||
Ok(true) => ctx.notice(me, from.uid, format!("Added badword pattern to \x02{chan}\x02: {pattern}")),
|
||||
Ok(true) => ctx.notice(me, from.uid, t!(ctx, "Added badword pattern to \x02{chan}\x02: {pattern}", chan = chan, pattern = pattern)),
|
||||
Ok(false) => ctx.notice(me, from.uid, "That pattern is already on the list."),
|
||||
Err(ChanError::InvalidPattern) => ctx.notice(me, from.uid, format!("\x02{pattern}\x02 isn't a valid regular expression.")),
|
||||
Err(ChanError::InvalidPattern) => ctx.notice(me, from.uid, t!(ctx, "\x02{pattern}\x02 isn't a valid regular expression.", pattern = pattern)),
|
||||
Err(_) => reg_error(me, from, chan, ctx),
|
||||
}
|
||||
}
|
||||
|
|
@ -34,30 +34,30 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
}
|
||||
let pattern = args[3..].join(" ");
|
||||
match db.badword_del(chan, &pattern) {
|
||||
Ok(true) => ctx.notice(me, from.uid, format!("Removed badword pattern from \x02{chan}\x02.")),
|
||||
Ok(true) => ctx.notice(me, from.uid, t!(ctx, "Removed badword pattern from \x02{chan}\x02.", chan = chan)),
|
||||
Ok(false) => ctx.notice(me, from.uid, "That pattern isn't on the list."),
|
||||
Err(_) => reg_error(me, from, chan, ctx),
|
||||
}
|
||||
}
|
||||
Some("CLEAR") => match db.badword_clear(chan) {
|
||||
Ok(n) => ctx.notice(me, from.uid, format!("Cleared \x02{n}\x02 badword pattern(s) from \x02{chan}\x02.")),
|
||||
Ok(n) => ctx.notice(me, from.uid, t!(ctx, "Cleared \x02{n}\x02 badword pattern(s) from \x02{chan}\x02.", n = n, chan = chan)),
|
||||
Err(_) => reg_error(me, from, chan, ctx),
|
||||
},
|
||||
None | Some("LIST") => {
|
||||
let words = db.badwords(chan);
|
||||
if words.is_empty() {
|
||||
ctx.notice(me, from.uid, format!("\x02{chan}\x02 has no badword patterns."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 has no badword patterns.", chan = chan));
|
||||
return;
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("Badword patterns for \x02{chan}\x02 ({}):", words.len()));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Badword patterns for \x02{chan}\x02 ({count}):", chan = chan, count = words.len()));
|
||||
for (i, w) in words.iter().enumerate() {
|
||||
ctx.notice(me, from.uid, format!(" {}. {w}", i + 1));
|
||||
ctx.notice(me, from.uid, t!(ctx, " {num}. {word}", num = i + 1, word = w));
|
||||
}
|
||||
}
|
||||
Some(other) => ctx.notice(me, from.uid, format!("Unknown BADWORDS command \x02{other}\x02. Use ADD, DEL, LIST or CLEAR.")),
|
||||
Some(other) => ctx.notice(me, from.uid, t!(ctx, "Unknown BADWORDS command \x02{other}\x02. Use ADD, DEL, LIST or CLEAR.", other = other)),
|
||||
}
|
||||
}
|
||||
|
||||
fn reg_error(me: &str, from: &Sender, chan: &str, ctx: &mut ServiceCtx) {
|
||||
ctx.notice(me, from.uid, format!("Couldn't update \x02{chan}\x02 — please try again in a moment."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Couldn't update \x02{chan}\x02 — please try again in a moment.", chan = chan));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{ChanError, Priv, Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, ChanError, Priv, Sender, ServiceCtx, Store};
|
||||
|
||||
// BOT ADD <nick> <user> <host> [gecos] | BOT DEL <nick> | BOT LIST — manage the
|
||||
// bot registry. Administering bots is oper-only (Priv::Admin).
|
||||
|
|
@ -15,8 +15,8 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
};
|
||||
let gecos = if args.len() > 5 { args[5..].join(" ") } else { "Service Bot".to_string() };
|
||||
match db.bot_add(nick, user, host, &gecos) {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("Bot \x02{nick}\x02 (\x02{user}@{host}\x02) added.")),
|
||||
Err(_) => ctx.notice(me, from.uid, format!("A bot named \x02{nick}\x02 already exists, or that didn't work.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Bot \x02{nick}\x02 (\x02{user}@{host}\x02) added.", nick = nick, user = user, host = host)),
|
||||
Err(_) => ctx.notice(me, from.uid, t!(ctx, "A bot named \x02{nick}\x02 already exists, or that didn't work.", nick = nick)),
|
||||
}
|
||||
}
|
||||
Some("CHANGE") => {
|
||||
|
|
@ -26,15 +26,15 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
};
|
||||
// Omitted fields keep the bot's current values.
|
||||
let Some(cur) = db.bots().into_iter().find(|b| b.nick.eq_ignore_ascii_case(old)) else {
|
||||
ctx.notice(me, from.uid, format!("There's no bot named \x02{old}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "There's no bot named \x02{nick}\x02.", nick = old));
|
||||
return;
|
||||
};
|
||||
let user = args.get(4).map(|s| s.to_string()).unwrap_or(cur.user);
|
||||
let host = args.get(5).map(|s| s.to_string()).unwrap_or(cur.host);
|
||||
let gecos = if args.len() > 6 { args[6..].join(" ") } else { cur.gecos };
|
||||
match db.bot_change(old, newnick, &user, &host, &gecos) {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("Bot \x02{old}\x02 is now \x02{newnick}\x02 (\x02{user}@{host}\x02).")),
|
||||
Err(ChanError::Exists) => ctx.notice(me, from.uid, format!("A bot named \x02{newnick}\x02 already exists.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Bot \x02{old}\x02 is now \x02{newnick}\x02 (\x02{user}@{host}\x02).", old = old, newnick = newnick, user = user, host = host)),
|
||||
Err(ChanError::Exists) => ctx.notice(me, from.uid, t!(ctx, "A bot named \x02{newnick}\x02 already exists.", newnick = newnick)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
@ -45,14 +45,14 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
};
|
||||
if nick == "*" {
|
||||
match db.bot_del_all() {
|
||||
Ok(n) => ctx.notice(me, from.uid, format!("Removed all \x02{n}\x02 bot(s).")),
|
||||
Ok(n) => ctx.notice(me, from.uid, t!(ctx, "Removed all \x02{n}\x02 bot(s).", n = n)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
return;
|
||||
}
|
||||
match db.bot_del(nick) {
|
||||
Ok(true) => ctx.notice(me, from.uid, format!("Bot \x02{nick}\x02 deleted.")),
|
||||
Ok(false) => ctx.notice(me, from.uid, format!("There's no bot named \x02{nick}\x02.")),
|
||||
Ok(true) => ctx.notice(me, from.uid, t!(ctx, "Bot \x02{nick}\x02 deleted.", nick = nick)),
|
||||
Ok(false) => ctx.notice(me, from.uid, t!(ctx, "There's no bot named \x02{nick}\x02.", nick = nick)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
@ -62,12 +62,12 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
ctx.notice(me, from.uid, "No bots have been added yet. Add one with \x02BOT ADD\x02.");
|
||||
return;
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("Bots ({}):", bots.len()));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Bots ({count}):", count = bots.len()));
|
||||
for b in &bots {
|
||||
let flag = if b.private { " \x02[private]\x02" } else { "" };
|
||||
ctx.notice(me, from.uid, format!(" \x02{}\x02 ({}@{}) — {}{flag}", b.nick, b.user, b.host, b.gecos));
|
||||
ctx.notice(me, from.uid, t!(ctx, " \x02{nick}\x02 ({user}@{host}) — {gecos}{flag}", nick = b.nick, user = b.user, host = b.host, gecos = b.gecos, flag = flag));
|
||||
}
|
||||
}
|
||||
Some(other) => ctx.notice(me, from.uid, format!("Unknown BOT command \x02{other}\x02. Use \x02ADD\x02, \x02CHANGE\x02, \x02DEL\x02 or \x02LIST\x02.")),
|
||||
Some(other) => ctx.notice(me, from.uid, t!(ctx, "Unknown BOT command \x02{other}\x02. Use \x02ADD\x02, \x02CHANGE\x02, \x02DEL\x02 or \x02LIST\x02.", other = other)),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Priv, Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Priv, Sender, ServiceCtx, Store};
|
||||
|
||||
// BOTLIST: show the bots a channel founder can assign. Available to everyone,
|
||||
// unlike \x02BOT LIST\x02 (operator bot administration). Private bots are hidden
|
||||
|
|
@ -10,10 +10,10 @@ pub fn handle(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &dyn Store) {
|
|||
ctx.notice(me, from.uid, "No bots are available.");
|
||||
return;
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("Available bots ({}):", bots.len()));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Available bots ({count}):", count = bots.len()));
|
||||
for b in &bots {
|
||||
let flag = if b.private { " \x02[private]\x02" } else { "" };
|
||||
ctx.notice(me, from.uid, format!(" \x02{}\x02 ({}@{}){flag}", b.nick, b.user, b.host));
|
||||
ctx.notice(me, from.uid, t!(ctx, " \x02{nick}\x02 ({user}@{host}){flag}", nick = b.nick, user = b.user, host = b.host, flag = flag));
|
||||
}
|
||||
ctx.notice(me, from.uid, "Assign one with \x02ASSIGN <#channel> <bot>\x02.");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Sender, ServiceCtx, Store};
|
||||
|
||||
// COPY <#source> <#dest>: copy a channel's bot configuration — kickers,
|
||||
// badwords, greet and nobot — onto another. Requires founder-or-admin on both.
|
||||
|
|
@ -11,7 +11,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
return;
|
||||
}
|
||||
match db.copy_bot_config(src, dst) {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("Copied \x02{src}\x02's bot settings (kickers, badwords, greet, nobot) to \x02{dst}\x02.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Copied \x02{src}\x02's bot settings (kickers, badwords, greet, nobot) to \x02{dst}\x02.", src = src, dst = dst)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Sender, ServiceCtx, Store};
|
||||
|
||||
// INFO <bot> — describe a bot and list the channels it serves.
|
||||
// INFO <#channel> — show which bot (if any) is assigned to a channel.
|
||||
|
|
@ -10,12 +10,12 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
|
||||
if target.starts_with('#') {
|
||||
let Some(chan) = db.channel(target) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{target}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 isn't registered.", chan = target));
|
||||
return;
|
||||
};
|
||||
match &chan.assigned_bot {
|
||||
Some(bot) => ctx.notice(me, from.uid, format!("\x02{target}\x02 is served by bot \x02{bot}\x02.")),
|
||||
None => ctx.notice(me, from.uid, format!("\x02{target}\x02 has no bot assigned. Assign one with \x02ASSIGN\x02 {target} <bot>.")),
|
||||
Some(bot) => ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 is served by bot \x02{bot}\x02.", chan = target, bot = bot)),
|
||||
None => ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 has no bot assigned. Assign one with \x02ASSIGN\x02 {chan} <bot>.", chan = target)),
|
||||
}
|
||||
// The BotServ options in effect on this channel.
|
||||
let mut opts = Vec::new();
|
||||
|
|
@ -29,12 +29,12 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
opts.push("nobot");
|
||||
}
|
||||
let summary = if opts.is_empty() { "none".to_string() } else { opts.join(", ") };
|
||||
ctx.notice(me, from.uid, format!(" Options: {summary}"));
|
||||
ctx.notice(me, from.uid, t!(ctx, " Options: {summary}", summary = summary));
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(bot) = db.bots().into_iter().find(|b| b.nick.eq_ignore_ascii_case(target)) else {
|
||||
ctx.notice(me, from.uid, format!("There's no bot named \x02{target}\x02. See \x02BOT LIST\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "There's no bot named \x02{nick}\x02. See \x02BOT LIST\x02.", nick = target));
|
||||
return;
|
||||
};
|
||||
let channels: Vec<String> = db
|
||||
|
|
@ -45,11 +45,11 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
.collect();
|
||||
|
||||
let privacy = if bot.private { " (private)" } else { "" };
|
||||
ctx.notice(me, from.uid, format!("Bot \x02{}\x02 — {}@{}{privacy}", bot.nick, bot.user, bot.host));
|
||||
ctx.notice(me, from.uid, format!(" Real name: {}", bot.gecos));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Bot \x02{nick}\x02 — {user}@{host}{privacy}", nick = bot.nick, user = bot.user, host = bot.host, privacy = privacy));
|
||||
ctx.notice(me, from.uid, t!(ctx, " Real name: {gecos}", gecos = bot.gecos));
|
||||
if channels.is_empty() {
|
||||
ctx.notice(me, from.uid, " Not assigned to any channel.");
|
||||
} else {
|
||||
ctx.notice(me, from.uid, format!(" Serving {} channel(s): {}", channels.len(), channels.join(", ")));
|
||||
ctx.notice(me, from.uid, t!(ctx, " Serving {count} channel(s): {list}", count = channels.len(), list = channels.join(", ")));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Kicker, Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Kicker, Sender, ServiceCtx, Store};
|
||||
|
||||
// KICK <#channel> <type> {ON|OFF} [params]: configure the bot's kickers.
|
||||
// Types: CAPS [min [percent]], BOLDS, COLORS, UNDERLINES, REVERSES, ITALICS,
|
||||
|
|
@ -16,8 +16,8 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
if kind.eq_ignore_ascii_case("TTB") {
|
||||
match args.get(3).and_then(|s| s.parse::<u16>().ok()) {
|
||||
Some(n) => match db.set_ttb(chan, n) {
|
||||
Ok(()) if n == 0 => ctx.notice(me, from.uid, format!("The bot will only kick (not ban) in \x02{chan}\x02.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("The bot will ban a user after \x02{n}\x02 kick(s) in \x02{chan}\x02.")),
|
||||
Ok(()) if n == 0 => ctx.notice(me, from.uid, t!(ctx, "The bot will only kick (not ban) in \x02{chan}\x02.", chan = chan)),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "The bot will ban a user after \x02{n}\x02 kick(s) in \x02{chan}\x02.", n = n, chan = chan)),
|
||||
Err(_) => reg_error(me, from, chan, ctx),
|
||||
},
|
||||
None => ctx.notice(me, from.uid, "Syntax: KICK <#channel> TTB <number>"),
|
||||
|
|
@ -34,7 +34,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
}
|
||||
let text = args[3..].join(" ");
|
||||
match db.kicker_test(chan, &text) {
|
||||
Some(reason) => ctx.notice(me, from.uid, format!("That line \x02would be kicked\x02: {reason}")),
|
||||
Some(reason) => ctx.notice(me, from.uid, t!(ctx, "That line \x02would be kicked\x02: {reason}", reason = reason)),
|
||||
None => ctx.notice(me, from.uid, "That line trips no content kicker (flood/repeat depend on timing and repetition, so they aren't tested here)."),
|
||||
}
|
||||
return;
|
||||
|
|
@ -55,7 +55,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
let min = args.get(4).and_then(|s| s.parse::<u16>().ok()).unwrap_or(0);
|
||||
let percent = args.get(5).and_then(|s| s.parse::<u16>().ok()).filter(|p| (1..=100).contains(p)).unwrap_or(0);
|
||||
match db.set_caps_kicker(chan, min, percent) {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("The bot will now kick for \x02caps\x02 in \x02{chan}\x02.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "The bot will now kick for \x02caps\x02 in \x02{chan}\x02.", chan = chan)),
|
||||
Err(_) => reg_error(me, from, chan, ctx),
|
||||
}
|
||||
} else {
|
||||
|
|
@ -68,7 +68,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
let lines = args.get(4).and_then(|s| s.parse::<u16>().ok()).unwrap_or(0);
|
||||
let secs = args.get(5).and_then(|s| s.parse::<u16>().ok()).unwrap_or(0);
|
||||
match db.set_flood_kicker(chan, lines, secs) {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("The bot will now kick for \x02flooding\x02 in \x02{chan}\x02.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "The bot will now kick for \x02flooding\x02 in \x02{chan}\x02.", chan = chan)),
|
||||
Err(_) => reg_error(me, from, chan, ctx),
|
||||
}
|
||||
} else {
|
||||
|
|
@ -80,7 +80,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
if on {
|
||||
let times = args.get(4).and_then(|s| s.parse::<u16>().ok()).unwrap_or(0);
|
||||
match db.set_repeat_kicker(chan, times) {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("The bot will now kick for \x02repeating\x02 in \x02{chan}\x02.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "The bot will now kick for \x02repeating\x02 in \x02{chan}\x02.", chan = chan)),
|
||||
Err(_) => reg_error(me, from, chan, ctx),
|
||||
}
|
||||
} else {
|
||||
|
|
@ -100,7 +100,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
"DONTKICKOPS" => Kicker::DontKickOps,
|
||||
"DONTKICKVOICES" => Kicker::DontKickVoices,
|
||||
other => {
|
||||
ctx.notice(me, from.uid, format!("Unknown kicker \x02{other}\x02. Try CAPS, FLOOD, REPEAT, BADWORDS, BOLDS, COLORS, UNDERLINES, REVERSES, ITALICS, WARN, DONTKICKOPS or DONTKICKVOICES."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Unknown kicker \x02{other}\x02. Try CAPS, FLOOD, REPEAT, BADWORDS, BOLDS, COLORS, UNDERLINES, REVERSES, ITALICS, WARN, DONTKICKOPS or DONTKICKVOICES.", other = other));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
|
@ -126,18 +126,18 @@ fn label(kicker: Kicker) -> &'static str {
|
|||
|
||||
fn toggle(me: &str, from: &Sender, chan: &str, kicker: Kicker, on: bool, ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
match db.set_kicker(chan, kicker, on) {
|
||||
Ok(()) if kicker == Kicker::DontKickOps && on => ctx.notice(me, from.uid, format!("The bot will no longer kick channel operators in \x02{chan}\x02.")),
|
||||
Ok(()) if kicker == Kicker::DontKickOps => ctx.notice(me, from.uid, format!("The bot may again kick channel operators in \x02{chan}\x02.")),
|
||||
Ok(()) if kicker == Kicker::DontKickVoices && on => ctx.notice(me, from.uid, format!("The bot will no longer kick voiced users in \x02{chan}\x02.")),
|
||||
Ok(()) if kicker == Kicker::DontKickVoices => ctx.notice(me, from.uid, format!("The bot may again kick voiced users in \x02{chan}\x02.")),
|
||||
Ok(()) if kicker == Kicker::Warn && on => ctx.notice(me, from.uid, format!("The bot will warn once before the first kick in \x02{chan}\x02.")),
|
||||
Ok(()) if kicker == Kicker::Warn => ctx.notice(me, from.uid, format!("The bot will kick without warning in \x02{chan}\x02.")),
|
||||
Ok(()) if on => ctx.notice(me, from.uid, format!("The \x02{}\x02 kicker is now on in \x02{chan}\x02.", label(kicker))),
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("The \x02{}\x02 kicker is now off in \x02{chan}\x02.", label(kicker))),
|
||||
Ok(()) if kicker == Kicker::DontKickOps && on => ctx.notice(me, from.uid, t!(ctx, "The bot will no longer kick channel operators in \x02{chan}\x02.", chan = chan)),
|
||||
Ok(()) if kicker == Kicker::DontKickOps => ctx.notice(me, from.uid, t!(ctx, "The bot may again kick channel operators in \x02{chan}\x02.", chan = chan)),
|
||||
Ok(()) if kicker == Kicker::DontKickVoices && on => ctx.notice(me, from.uid, t!(ctx, "The bot will no longer kick voiced users in \x02{chan}\x02.", chan = chan)),
|
||||
Ok(()) if kicker == Kicker::DontKickVoices => ctx.notice(me, from.uid, t!(ctx, "The bot may again kick voiced users in \x02{chan}\x02.", chan = chan)),
|
||||
Ok(()) if kicker == Kicker::Warn && on => ctx.notice(me, from.uid, t!(ctx, "The bot will warn once before the first kick in \x02{chan}\x02.", chan = chan)),
|
||||
Ok(()) if kicker == Kicker::Warn => ctx.notice(me, from.uid, t!(ctx, "The bot will kick without warning in \x02{chan}\x02.", chan = chan)),
|
||||
Ok(()) if on => ctx.notice(me, from.uid, t!(ctx, "The \x02{kicker}\x02 kicker is now on in \x02{chan}\x02.", kicker = label(kicker), chan = chan)),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "The \x02{kicker}\x02 kicker is now off in \x02{chan}\x02.", kicker = label(kicker), chan = chan)),
|
||||
Err(_) => reg_error(me, from, chan, ctx),
|
||||
}
|
||||
}
|
||||
|
||||
fn reg_error(me: &str, from: &Sender, chan: &str, ctx: &mut ServiceCtx) {
|
||||
ctx.notice(me, from.uid, format!("Couldn't update \x02{chan}\x02 — please try again in a moment."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Couldn't update \x02{chan}\x02 — please try again in a moment.", chan = chan));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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::{HelpEntry, NetView, Priv, Sender, Service, ServiceCtx, Store};
|
||||
use echo_api::{t, HelpEntry, NetView, Priv, Sender, Service, ServiceCtx, Store};
|
||||
|
||||
#[path = "bot.rs"]
|
||||
mod bot;
|
||||
|
|
@ -31,11 +31,11 @@ mod trigger;
|
|||
// founder or a services admin. Notices and returns false on failure.
|
||||
fn require_channel_admin(me: &str, from: &Sender, chan: &str, ctx: &mut ServiceCtx, db: &dyn Store) -> bool {
|
||||
let Some(founder) = db.channel(chan).map(|c| c.founder) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{chan}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 isn't registered.", chan = chan));
|
||||
return false;
|
||||
};
|
||||
if from.account != Some(founder.as_str()) && !from.privs.has(Priv::Admin) {
|
||||
ctx.notice(me, from.uid, format!("Only \x02{chan}\x02's founder can change that."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Only \x02{chan}\x02's founder can change that.", chan = chan));
|
||||
return false;
|
||||
}
|
||||
true
|
||||
|
|
@ -95,7 +95,7 @@ impl Service for BotServ {
|
|||
Some("COPY") => copy::handle(me, from, args, ctx, db),
|
||||
Some("TRIGGER") => trigger::handle(me, from, args, ctx, db),
|
||||
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.")),
|
||||
Some(other) => ctx.notice(me, from.uid, t!(ctx, "I don't know the command \x02{other}\x02. Try \x02HELP\x02.", other = other)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{NetView, Priv, Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, NetView, Priv, Sender, ServiceCtx, Store};
|
||||
|
||||
// SAY <#channel> <text> — make the channel's assigned bot say something.
|
||||
// ACT <#channel> <text> — the same, as a CTCP ACTION (/me).
|
||||
|
|
@ -6,27 +6,27 @@ use echo_api::{NetView, Priv, Sender, ServiceCtx, Store};
|
|||
pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net: &dyn NetView, db: &dyn Store, action: bool) {
|
||||
let verb = if action { "ACT" } else { "SAY" };
|
||||
if args.len() < 3 {
|
||||
ctx.notice(me, from.uid, format!("Syntax: {verb} <#channel> <text>"));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Syntax: {verb} <#channel> <text>", verb = verb));
|
||||
return;
|
||||
}
|
||||
let chan = args[1];
|
||||
let text = args[2..].join(" ");
|
||||
|
||||
let Some(info) = db.channel(chan) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{chan}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 isn't registered.", chan = chan));
|
||||
return;
|
||||
};
|
||||
let allowed = from.privs.has(Priv::Admin) || from.account.is_some_and(|a| info.is_op(a));
|
||||
if !allowed {
|
||||
ctx.notice(me, from.uid, format!("Access denied — you need operator access in \x02{chan}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Access denied — you need operator access in \x02{chan}\x02.", chan = chan));
|
||||
return;
|
||||
}
|
||||
let Some(bot) = info.assigned_bot else {
|
||||
ctx.notice(me, from.uid, format!("\x02{chan}\x02 has no bot assigned. Assign one with \x02ASSIGN\x02 {chan} <bot>."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 has no bot assigned. Assign one with \x02ASSIGN\x02 {chan} <bot>.", chan = chan));
|
||||
return;
|
||||
};
|
||||
let Some(botuid) = net.uid_by_nick(&bot) else {
|
||||
ctx.notice(me, from.uid, format!("Bot \x02{bot}\x02 isn't on the network right now."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Bot \x02{bot}\x02 isn't on the network right now.", bot = bot));
|
||||
return;
|
||||
};
|
||||
let botuid = botuid.to_string();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{parse_duration, ChanSetting, Priv, Sender, ServiceCtx, Store};
|
||||
use echo_api::{parse_duration, t, ChanSetting, Priv, Sender, ServiceCtx, Store};
|
||||
|
||||
// SET <#channel> <option> <value>: per-channel bot options (founder-or-admin) —
|
||||
// GREET <on|off>, BANEXPIRE <duration|off>, NOBOT <on|off>. Also
|
||||
|
|
@ -24,14 +24,14 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
return;
|
||||
}
|
||||
(false, _) => {
|
||||
ctx.notice(me, from.uid, format!("Unknown bot option \x02{option}\x02. Available: \x02PRIVATE\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Unknown bot option \x02{option}\x02. Available: \x02PRIVATE\x02.", option = option));
|
||||
return;
|
||||
}
|
||||
};
|
||||
match db.bot_set_private(target, on) {
|
||||
Ok(true) if on => ctx.notice(me, from.uid, format!("Bot \x02{target}\x02 is now private (operators only).")),
|
||||
Ok(true) => ctx.notice(me, from.uid, format!("Bot \x02{target}\x02 is now public.")),
|
||||
Ok(false) => ctx.notice(me, from.uid, format!("There's no bot named \x02{target}\x02.")),
|
||||
Ok(true) if on => ctx.notice(me, from.uid, t!(ctx, "Bot \x02{nick}\x02 is now private (operators only).", nick = target)),
|
||||
Ok(true) => ctx.notice(me, from.uid, t!(ctx, "Bot \x02{nick}\x02 is now public.", nick = target)),
|
||||
Ok(false) => ctx.notice(me, from.uid, t!(ctx, "There's no bot named \x02{nick}\x02.", nick = target)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
return;
|
||||
|
|
@ -46,8 +46,8 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
if option.eq_ignore_ascii_case("VOTEKICK") {
|
||||
match args.get(3).and_then(|s| s.parse::<u16>().ok()) {
|
||||
Some(n) => match db.set_votekick(chan, n) {
|
||||
Ok(()) if n == 0 => ctx.notice(me, from.uid, format!("\x02!votekick\x02 is now disabled in \x02{chan}\x02.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("\x02{n}\x02 vote(s) will now carry a \x02!votekick\x02/\x02!voteban\x02 in \x02{chan}\x02.")),
|
||||
Ok(()) if n == 0 => ctx.notice(me, from.uid, t!(ctx, "\x02!votekick\x02 is now disabled in \x02{chan}\x02.", chan = chan)),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "\x02{n}\x02 vote(s) will now carry a \x02!votekick\x02/\x02!voteban\x02 in \x02{chan}\x02.", n = n, chan = chan)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
},
|
||||
None => ctx.notice(me, from.uid, "Syntax: SET <#channel> VOTEKICK <number> (0 to disable)"),
|
||||
|
|
@ -67,14 +67,14 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
match parse_duration(arg) {
|
||||
Some(s) => s.min(u32::MAX as u64) as u32,
|
||||
None => {
|
||||
ctx.notice(me, from.uid, format!("\x02{arg}\x02 isn't a valid duration. Try 30m, 2h, 7d or \x02off\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{arg}\x02 isn't a valid duration. Try 30m, 2h, 7d or \x02off\x02.", arg = arg));
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
match db.set_ban_expire(chan, secs) {
|
||||
Ok(()) if secs == 0 => ctx.notice(me, from.uid, format!("Kicker bans in \x02{chan}\x02 won't expire automatically.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("Kicker bans in \x02{chan}\x02 will expire after \x02{arg}\x02.")),
|
||||
Ok(()) if secs == 0 => ctx.notice(me, from.uid, t!(ctx, "Kicker bans in \x02{chan}\x02 won't expire automatically.", chan = chan)),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Kicker bans in \x02{chan}\x02 will expire after \x02{arg}\x02.", chan = chan, arg = arg)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
return;
|
||||
|
|
@ -90,15 +90,15 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
};
|
||||
match option.to_ascii_uppercase().as_str() {
|
||||
"GREET" => match db.set_channel_setting(chan, ChanSetting::BotGreet, on) {
|
||||
Ok(()) if on => ctx.notice(me, from.uid, format!("Greet messages are now \x02on\x02 in \x02{chan}\x02.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("Greet messages are now \x02off\x02 in \x02{chan}\x02.")),
|
||||
Ok(()) if on => ctx.notice(me, from.uid, t!(ctx, "Greet messages are now \x02on\x02 in \x02{chan}\x02.", chan = chan)),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Greet messages are now \x02off\x02 in \x02{chan}\x02.", chan = chan)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
},
|
||||
"NOBOT" => match db.set_channel_setting(chan, ChanSetting::NoBot, on) {
|
||||
Ok(()) if on => ctx.notice(me, from.uid, format!("Only operators may (un)assign a bot in \x02{chan}\x02 now.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("The founder may (un)assign a bot in \x02{chan}\x02 again.")),
|
||||
Ok(()) if on => ctx.notice(me, from.uid, t!(ctx, "Only operators may (un)assign a bot in \x02{chan}\x02 now.", chan = chan)),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "The founder may (un)assign a bot in \x02{chan}\x02 again.", chan = chan)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
},
|
||||
other => ctx.notice(me, from.uid, format!("Unknown option \x02{other}\x02. Available: \x02GREET\x02, \x02BANEXPIRE\x02, \x02NOBOT\x02.")),
|
||||
other => ctx.notice(me, from.uid, t!(ctx, "Unknown option \x02{other}\x02. Available: \x02GREET\x02, \x02BANEXPIRE\x02, \x02NOBOT\x02.", other = other)),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{ChanError, Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, ChanError, Sender, ServiceCtx, Store};
|
||||
|
||||
// TRIGGER <#channel> ADD <regex>|<response> | DEL <num> | LIST | CLEAR: manage
|
||||
// the channel's auto-responses. When a line matches <regex>, the assigned bot
|
||||
|
|
@ -35,9 +35,9 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
return;
|
||||
}
|
||||
match db.trigger_add(chan, pattern, response, cooldown) {
|
||||
Ok(true) => ctx.notice(me, from.uid, format!("Added a trigger to \x02{chan}\x02.")),
|
||||
Ok(true) => ctx.notice(me, from.uid, t!(ctx, "Added a trigger to \x02{chan}\x02.", chan = chan)),
|
||||
Ok(false) => ctx.notice(me, from.uid, "That pattern already has a trigger."),
|
||||
Err(ChanError::InvalidPattern) => ctx.notice(me, from.uid, format!("\x02{pattern}\x02 isn't a valid regular expression.")),
|
||||
Err(ChanError::InvalidPattern) => ctx.notice(me, from.uid, t!(ctx, "\x02{pattern}\x02 isn't a valid regular expression.", pattern = pattern)),
|
||||
Err(_) => reg_error(me, from, chan, ctx),
|
||||
}
|
||||
}
|
||||
|
|
@ -47,31 +47,31 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
return;
|
||||
};
|
||||
match db.trigger_del(chan, n) {
|
||||
Ok(true) => ctx.notice(me, from.uid, format!("Trigger #\x02{n}\x02 removed from \x02{chan}\x02.")),
|
||||
Ok(false) => ctx.notice(me, from.uid, format!("\x02{chan}\x02 has no trigger #\x02{n}\x02.")),
|
||||
Ok(true) => ctx.notice(me, from.uid, t!(ctx, "Trigger #\x02{n}\x02 removed from \x02{chan}\x02.", n = n, chan = chan)),
|
||||
Ok(false) => ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 has no trigger #\x02{n}\x02.", chan = chan, n = n)),
|
||||
Err(_) => reg_error(me, from, chan, ctx),
|
||||
}
|
||||
}
|
||||
Some("CLEAR") => match db.trigger_clear(chan) {
|
||||
Ok(n) => ctx.notice(me, from.uid, format!("Cleared \x02{n}\x02 trigger(s) from \x02{chan}\x02.")),
|
||||
Ok(n) => ctx.notice(me, from.uid, t!(ctx, "Cleared \x02{n}\x02 trigger(s) from \x02{chan}\x02.", n = n, chan = chan)),
|
||||
Err(_) => reg_error(me, from, chan, ctx),
|
||||
},
|
||||
None | Some("LIST") => {
|
||||
let triggers = db.triggers(chan);
|
||||
if triggers.is_empty() {
|
||||
ctx.notice(me, from.uid, format!("\x02{chan}\x02 has no triggers."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 has no triggers.", chan = chan));
|
||||
return;
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("Triggers for \x02{chan}\x02 ({}):", triggers.len()));
|
||||
for (i, t) in triggers.iter().enumerate() {
|
||||
let cd = if t.cooldown > 0 { format!(" (cooldown {}s)", t.cooldown) } else { String::new() };
|
||||
ctx.notice(me, from.uid, format!(" {}. {} \x02→\x02 {}{cd}", i + 1, t.pattern, t.response));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Triggers for \x02{chan}\x02 ({count}):", chan = chan, count = triggers.len()));
|
||||
for (i, tr) in triggers.iter().enumerate() {
|
||||
let cd = if tr.cooldown > 0 { t!(ctx, " (cooldown {secs}s)", secs = tr.cooldown) } else { String::new() };
|
||||
ctx.notice(me, from.uid, t!(ctx, " {num}. {pattern} \x02→\x02 {response}{cd}", num = i + 1, pattern = tr.pattern, response = tr.response, cd = cd));
|
||||
}
|
||||
}
|
||||
Some(other) => ctx.notice(me, from.uid, format!("Unknown TRIGGER command \x02{other}\x02. Use ADD, DEL, LIST or CLEAR.")),
|
||||
Some(other) => ctx.notice(me, from.uid, t!(ctx, "Unknown TRIGGER command \x02{other}\x02. Use ADD, DEL, LIST or CLEAR.", other = other)),
|
||||
}
|
||||
}
|
||||
|
||||
fn reg_error(me: &str, from: &Sender, chan: &str, ctx: &mut ServiceCtx) {
|
||||
ctx.notice(me, from.uid, format!("Couldn't update \x02{chan}\x02 — please try again in a moment."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Couldn't update \x02{chan}\x02 — please try again in a moment.", chan = chan));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use echo_api::Store;
|
||||
use echo_api::{Sender, ServiceCtx};
|
||||
use echo_api::t;
|
||||
|
||||
// The named tiers ACCESS ADD accepts, high to low; the granular FLAGS command
|
||||
// covers anything finer. Kept in step with the XOP presets in `level_caps`.
|
||||
|
|
@ -13,12 +14,12 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
};
|
||||
match args.get(2).map(|s| s.to_ascii_uppercase()).as_deref() {
|
||||
None | Some("LIST") => match db.channel(chan) {
|
||||
None => ctx.notice(me, from.uid, format!("\x02{chan}\x02 isn't registered.")),
|
||||
None => ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 isn't registered.", chan = chan)),
|
||||
Some(info) => {
|
||||
ctx.notice(me, from.uid, format!("Access list for \x02{}\x02:", info.name));
|
||||
ctx.notice(me, from.uid, format!(" \x02{}\x02 (founder)", info.founder));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Access list for \x02{name}\x02:", name = info.name));
|
||||
ctx.notice(me, from.uid, t!(ctx, " \x02{founder}\x02 (founder)", founder = info.founder));
|
||||
for a in &info.access {
|
||||
ctx.notice(me, from.uid, format!(" \x02{}\x02 ({})", a.account, a.level));
|
||||
ctx.notice(me, from.uid, t!(ctx, " \x02{account}\x02 ({level})", account = a.account, level = a.level));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -36,7 +37,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
return;
|
||||
}
|
||||
match db.access_add(chan, account, &level) {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("Added \x02{account}\x02 to \x02{chan}\x02 as \x02{level}\x02.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Added \x02{account}\x02 to \x02{chan}\x02 as \x02{level}\x02.", account = account, chan = chan, level = level)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
@ -49,8 +50,8 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
return;
|
||||
}
|
||||
match db.access_del(chan, account) {
|
||||
Ok(true) => ctx.notice(me, from.uid, format!("Removed \x02{account}\x02 from \x02{chan}\x02.")),
|
||||
Ok(false) => ctx.notice(me, from.uid, format!("\x02{account}\x02 has no access to \x02{chan}\x02.")),
|
||||
Ok(true) => ctx.notice(me, from.uid, t!(ctx, "Removed \x02{account}\x02 from \x02{chan}\x02.", account = account, chan = chan)),
|
||||
Ok(false) => ctx.notice(me, from.uid, t!(ctx, "\x02{account}\x02 has no access to \x02{chan}\x02.", account = account, chan = chan)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
@ -62,11 +63,11 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
fn is_founder(me: &str, from: &Sender, chan: &str, ctx: &mut ServiceCtx, db: &dyn Store) -> bool {
|
||||
match db.channel(chan) {
|
||||
None => {
|
||||
ctx.notice(me, from.uid, format!("\x02{chan}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 isn't registered.", chan = chan));
|
||||
false
|
||||
}
|
||||
Some(info) if from.account != Some(info.founder.as_str()) => {
|
||||
ctx.notice(me, from.uid, format!("Only \x02{chan}\x02's founder can change access."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Only \x02{chan}\x02's founder can change access.", chan = chan));
|
||||
false
|
||||
}
|
||||
// Founder, but a staff-suspended channel is frozen — no access changes.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use echo_api::Store;
|
||||
use echo_api::{Sender, ServiceCtx};
|
||||
use echo_api::t;
|
||||
|
||||
// AKICK <#channel> ADD <mask> [reason] | DEL <mask> | LIST | CLEAR
|
||||
// A mask is a nick!user@host glob or any extban the ircd advertises (named or by
|
||||
|
|
@ -14,12 +15,12 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
};
|
||||
match args.get(2).map(|s| s.to_ascii_uppercase()).as_deref() {
|
||||
None | Some("LIST") => match db.channel(chan) {
|
||||
None => ctx.notice(me, from.uid, format!("\x02{chan}\x02 isn't registered.")),
|
||||
Some(info) if info.akick.is_empty() => ctx.notice(me, from.uid, format!("\x02{chan}\x02 has an empty auto-kick list.")),
|
||||
None => ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 isn't registered.", chan = chan)),
|
||||
Some(info) if info.akick.is_empty() => ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 has an empty auto-kick list.", chan = chan)),
|
||||
Some(info) => {
|
||||
ctx.notice(me, from.uid, format!("Auto-kick list for \x02{}\x02:", info.name));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Auto-kick list for \x02{name}\x02:", name = info.name));
|
||||
for k in &info.akick {
|
||||
ctx.notice(me, from.uid, format!(" \x02{}\x02 ({})", k.mask, k.reason));
|
||||
ctx.notice(me, from.uid, t!(ctx, " \x02{mask}\x02 ({reason})", mask = k.mask, reason = k.reason));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -37,25 +38,25 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
echo_api::AkickMask::Host(_) => {}
|
||||
echo_api::AkickMask::Ext(eb, _) => {
|
||||
if !db.extban_offered(eb.name) {
|
||||
ctx.notice(me, from.uid, format!("This network's ircd doesn't offer the \x02{}\x02 extban.", eb.name));
|
||||
ctx.notice(me, from.uid, t!(ctx, "This network's ircd doesn't offer the \x02{name}\x02 extban.", name = eb.name));
|
||||
return;
|
||||
}
|
||||
if !db.extban_enabled(eb.name) {
|
||||
ctx.notice(me, from.uid, format!("The \x02{}\x02 extban isn't enabled on this network.", eb.name));
|
||||
ctx.notice(me, from.uid, t!(ctx, "The \x02{name}\x02 extban isn't enabled on this network.", name = eb.name));
|
||||
return;
|
||||
}
|
||||
}
|
||||
echo_api::AkickMask::Unknown(token) => match db.extban_lookup(token) {
|
||||
None => {
|
||||
ctx.notice(me, from.uid, format!("\x02{token}\x02 isn't a host mask or an extban this network offers."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{token}\x02 isn't a host mask or an extban this network offers.", token = token));
|
||||
return;
|
||||
}
|
||||
Some(cap) if cap.acting => {
|
||||
ctx.notice(me, from.uid, format!("\x02{}\x02 is an acting extban; auto-kick needs a matching extban or a host mask.", cap.name));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{name}\x02 is an acting extban; auto-kick needs a matching extban or a host mask.", name = cap.name));
|
||||
return;
|
||||
}
|
||||
Some(cap) if !db.extban_enabled(&cap.name) => {
|
||||
ctx.notice(me, from.uid, format!("The \x02{}\x02 extban isn't enabled on this network.", cap.name));
|
||||
ctx.notice(me, from.uid, t!(ctx, "The \x02{name}\x02 extban isn't enabled on this network.", name = cap.name));
|
||||
return;
|
||||
}
|
||||
Some(_) => {}
|
||||
|
|
@ -73,7 +74,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
if echo_api::ircd_enforced(mask) {
|
||||
ctx.channel_mode(me, chan, &format!("+b {mask}"));
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("Added \x02{mask}\x02 to \x02{chan}\x02's auto-kick list."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Added \x02{mask}\x02 to \x02{chan}\x02's auto-kick list.", mask = mask, chan = chan));
|
||||
}
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
|
|
@ -92,9 +93,9 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
if echo_api::ircd_enforced(mask) {
|
||||
ctx.channel_mode(me, chan, &format!("-b {mask}"));
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("Removed \x02{mask}\x02 from \x02{chan}\x02's auto-kick list."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Removed \x02{mask}\x02 from \x02{chan}\x02's auto-kick list.", mask = mask, chan = chan));
|
||||
}
|
||||
Ok(false) => ctx.notice(me, from.uid, format!("\x02{mask}\x02 isn't on \x02{chan}\x02's auto-kick list.")),
|
||||
Ok(false) => ctx.notice(me, from.uid, t!(ctx, "\x02{mask}\x02 isn't on \x02{chan}\x02's auto-kick list.", mask = mask, chan = chan)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
@ -110,7 +111,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
ctx.channel_mode(me, chan, &format!("-b {mask}"));
|
||||
}
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("Cleared \x02{}\x02 entr{} from \x02{chan}\x02's auto-kick list.", masks.len(), if masks.len() == 1 { "y" } else { "ies" }));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Cleared \x02{count}\x02 entr{suffix} from \x02{chan}\x02's auto-kick list.", count = masks.len(), suffix = if masks.len() == 1 { "y" } else { "ies" }, chan = chan));
|
||||
}
|
||||
_ => ctx.notice(me, from.uid, "Syntax: AKICK <#channel> ADD <mask> [reason] | DEL <mask> | LIST | CLEAR"),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use echo_api::Store;
|
||||
use echo_api::{Sender, ServiceCtx};
|
||||
use echo_api::NetView;
|
||||
use echo_api::t;
|
||||
|
||||
// BAN <#channel> <nick> [reason]: ban *!*@host and kick the user.
|
||||
pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net: &dyn NetView, db: &dyn Store) {
|
||||
|
|
@ -12,7 +13,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
return;
|
||||
}
|
||||
let Some(target) = net.uid_by_nick(nick).map(str::to_string) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{nick}\x02 isn't here."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{nick}\x02 isn't here.", nick = nick));
|
||||
return;
|
||||
};
|
||||
if super::peace_blocks(me, from, chan, &target, ctx, net, db) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use echo_api::Store;
|
||||
use echo_api::{Sender, ServiceCtx};
|
||||
use echo_api::t;
|
||||
|
||||
// CLONE <source> <target>: copy a channel's settings (mode lock, access,
|
||||
// auto-kick, description, entry message) into another. Founder of both.
|
||||
|
|
@ -34,5 +35,5 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
if let Some(info) = db.channel(dest) {
|
||||
ctx.channel_mode(me, dest, &info.lock_modes());
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("Copied \x02{src}\x02's settings to \x02{dest}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Copied \x02{src}\x02's settings to \x02{dest}\x02.", src = src, dest = dest));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use echo_api::Store;
|
||||
use echo_api::{status_mode, Sender, ServiceCtx};
|
||||
use echo_api::NetView;
|
||||
use echo_api::t;
|
||||
|
||||
// ENFORCE <#channel>: re-apply the channel's settings to everyone present —
|
||||
// the mode lock, access status modes, and the auto-kick list.
|
||||
|
|
@ -35,5 +36,5 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
}
|
||||
}
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("Re-applied \x02{chan}\x02's settings to everyone present."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Re-applied \x02{chan}\x02's settings to everyone present.", chan = chan));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use echo_api::Store;
|
||||
use echo_api::{Sender, ServiceCtx};
|
||||
use echo_api::t;
|
||||
|
||||
// ENTRYMSG <#channel> [CLEAR | <text>]: message noticed to users as they join.
|
||||
// With no argument, show the current message.
|
||||
|
|
@ -10,16 +11,16 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
};
|
||||
match args.get(2) {
|
||||
None => match db.channel(chan) {
|
||||
None => ctx.notice(me, from.uid, format!("\x02{chan}\x02 isn't registered.")),
|
||||
Some(info) if info.entrymsg.is_empty() => ctx.notice(me, from.uid, format!("\x02{chan}\x02 has no entry message.")),
|
||||
Some(info) => ctx.notice(me, from.uid, format!("Entry message for \x02{chan}\x02: {}", info.entrymsg)),
|
||||
None => ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 isn't registered.", chan = chan)),
|
||||
Some(info) if info.entrymsg.is_empty() => ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 has no entry message.", chan = chan)),
|
||||
Some(info) => ctx.notice(me, from.uid, t!(ctx, "Entry message for \x02{chan}\x02: {msg}", chan = chan, msg = info.entrymsg)),
|
||||
},
|
||||
Some(&kw) if kw.eq_ignore_ascii_case("CLEAR") => {
|
||||
if !super::require_op(me, from, chan, ctx, db) {
|
||||
return;
|
||||
}
|
||||
match db.set_entrymsg(chan, "") {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("Entry message for \x02{chan}\x02 cleared.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Entry message for \x02{chan}\x02 cleared.", chan = chan)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
@ -28,7 +29,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
return;
|
||||
}
|
||||
match db.set_entrymsg(chan, &args[2..].join(" ")) {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("Entry message for \x02{chan}\x02 updated.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Entry message for \x02{chan}\x02 updated.", chan = chan)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use echo_api::{Flags, Sender, ServiceCtx, Store, ACCESS_FLAGS};
|
||||
use echo_api::t;
|
||||
|
||||
// FLAGS <#channel> [account [+/-flags]]: the granular access model. With no
|
||||
// account, list the access entries and their flags; with an account, show or
|
||||
|
|
@ -8,7 +9,7 @@ use echo_api::{Flags, Sender, ServiceCtx, Store, ACCESS_FLAGS};
|
|||
// preset ("op"/"sop"/…) or a raw flag string — resolves through `Flags`.
|
||||
pub fn handle(me: &str, from: &Sender, chan: &str, args: &[&str], ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
let Some(info) = db.channel(chan) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{chan}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 isn't registered.", chan = chan));
|
||||
return;
|
||||
};
|
||||
let is_founder = from.account == Some(info.founder.as_str());
|
||||
|
|
@ -17,15 +18,15 @@ pub fn handle(me: &str, from: &Sender, chan: &str, args: &[&str], ctx: &mut Serv
|
|||
// FLAGS <#chan> — list.
|
||||
let Some(&target) = args.get(2) else {
|
||||
if !is_founder && !caps.op {
|
||||
ctx.notice(me, from.uid, format!("You need access to \x02{chan}\x02 to view its flags."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "You need access to \x02{chan}\x02 to view its flags.", chan = chan));
|
||||
return;
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("Access flags for \x02{chan}\x02:"));
|
||||
ctx.notice(me, from.uid, format!(" \x02{}\x02 (founder): \x02f\x02", info.founder));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Access flags for \x02{chan}\x02:", chan = chan));
|
||||
ctx.notice(me, from.uid, t!(ctx, " \x02{founder}\x02 (founder): \x02f\x02", founder = info.founder));
|
||||
for a in &info.access {
|
||||
ctx.notice(me, from.uid, format!(" \x02{}\x02: \x02{}\x02", a.account, Flags::from_level(&a.level).to_letters()));
|
||||
ctx.notice(me, from.uid, t!(ctx, " \x02{account}\x02: \x02{flags}\x02", account = a.account, flags = Flags::from_level(&a.level).to_letters()));
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("End of flags ({} entr{}).", info.access.len() + 1, if info.access.is_empty() { "y" } else { "ies" }));
|
||||
ctx.notice(me, from.uid, t!(ctx, "End of flags ({count} entr{suffix}).", count = info.access.len() + 1, suffix = if info.access.is_empty() { "y" } else { "ies" }));
|
||||
return;
|
||||
};
|
||||
|
||||
|
|
@ -34,15 +35,15 @@ pub fn handle(me: &str, from: &Sender, chan: &str, args: &[&str], ctx: &mut Serv
|
|||
// FLAGS <#chan> <account> — show one.
|
||||
let Some(&delta) = args.get(3) else {
|
||||
match entry.as_deref().map(|l| Flags::from_level(l).to_letters()) {
|
||||
Some(f) if !f.is_empty() => ctx.notice(me, from.uid, format!("\x02{target}\x02 on \x02{chan}\x02: \x02{f}\x02")),
|
||||
_ => ctx.notice(me, from.uid, format!("\x02{target}\x02 has no access to \x02{chan}\x02.")),
|
||||
Some(f) if !f.is_empty() => ctx.notice(me, from.uid, t!(ctx, "\x02{target}\x02 on \x02{chan}\x02: \x02{flags}\x02", target = target, chan = chan, flags = f)),
|
||||
_ => ctx.notice(me, from.uid, t!(ctx, "\x02{target}\x02 has no access to \x02{chan}\x02.", target = target, chan = chan)),
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
// FLAGS <#chan> <account> <+/-flags> — modify.
|
||||
if !is_founder && !caps.access {
|
||||
ctx.notice(me, from.uid, format!("You need the founder or the \x02a\x02 flag to change access on \x02{chan}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "You need the founder or the \x02a\x02 flag to change access on \x02{chan}\x02.", chan = chan));
|
||||
return;
|
||||
}
|
||||
if super::suspended_block(me, from, chan, ctx, db) {
|
||||
|
|
@ -55,7 +56,7 @@ pub fn handle(me: &str, from: &Sender, chan: &str, args: &[&str], ctx: &mut Serv
|
|||
let updated = match Flags::from_level(entry.as_deref().unwrap_or("")).apply_delta(delta) {
|
||||
Ok(f) => f,
|
||||
Err(bad) => {
|
||||
ctx.notice(me, from.uid, format!("\x02{bad}\x02 isn't a valid flag. Valid flags: \x02{ACCESS_FLAGS}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{bad}\x02 isn't a valid flag. Valid flags: \x02{valid}\x02.", bad = bad, valid = ACCESS_FLAGS));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
|
@ -63,19 +64,19 @@ pub fn handle(me: &str, from: &Sender, chan: &str, args: &[&str], ctx: &mut Serv
|
|||
// `f` (founder/co-founder) — otherwise a delegate could mint a founder-equivalent
|
||||
// entry (Rank::Founder) and seize the channel.
|
||||
if !is_founder && updated.has(echo_api::Flag::Founder) {
|
||||
ctx.notice(me, from.uid, format!("Only the founder can grant the \x02f\x02 flag on \x02{chan}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Only the founder can grant the \x02f\x02 flag on \x02{chan}\x02.", chan = chan));
|
||||
return;
|
||||
}
|
||||
if updated.is_empty() {
|
||||
match db.access_del(chan, target) {
|
||||
Ok(true) => ctx.notice(me, from.uid, format!("Cleared \x02{target}\x02's access to \x02{chan}\x02.")),
|
||||
_ => ctx.notice(me, from.uid, format!("\x02{target}\x02 had no access to \x02{chan}\x02.")),
|
||||
Ok(true) => ctx.notice(me, from.uid, t!(ctx, "Cleared \x02{target}\x02's access to \x02{chan}\x02.", target = target, chan = chan)),
|
||||
_ => ctx.notice(me, from.uid, t!(ctx, "\x02{target}\x02 had no access to \x02{chan}\x02.", target = target, chan = chan)),
|
||||
}
|
||||
return;
|
||||
}
|
||||
let letters = updated.to_letters();
|
||||
match db.access_add(chan, target, &letters) {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("\x02{target}\x02 on \x02{chan}\x02 now holds \x02{letters}\x02.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "\x02{target}\x02 on \x02{chan}\x02 now holds \x02{letters}\x02.", target = target, chan = chan, letters = letters)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use echo_api::Store;
|
||||
use echo_api::{Sender, ServiceCtx};
|
||||
use echo_api::NetView;
|
||||
use echo_api::t;
|
||||
|
||||
// GETKEY <#channel>: report the channel key (+k), for ops who need to let
|
||||
// someone in.
|
||||
|
|
@ -13,7 +14,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
return;
|
||||
}
|
||||
match net.channel_key(chan) {
|
||||
Some(key) => ctx.notice(me, from.uid, format!("Key for \x02{chan}\x02 is \x02{key}\x02.")),
|
||||
None => ctx.notice(me, from.uid, format!("\x02{chan}\x02 has no key set.")),
|
||||
Some(key) => ctx.notice(me, from.uid, t!(ctx, "Key for \x02{chan}\x02 is \x02{key}\x02.", chan = chan, key = key)),
|
||||
None => ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 has no key set.", chan = chan)),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use echo_api::Store;
|
||||
use echo_api::{Sender, ServiceCtx};
|
||||
use echo_api::NetView;
|
||||
use echo_api::t;
|
||||
|
||||
// INVITE <#channel> [nick]: invite a user (self if no nick) into the channel.
|
||||
pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net: &dyn NetView, db: &dyn Store) {
|
||||
|
|
@ -15,12 +16,12 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
Some(&nick) => match net.uid_by_nick(nick) {
|
||||
Some(u) => u.to_string(),
|
||||
None => {
|
||||
ctx.notice(me, from.uid, format!("\x02{nick}\x02 isn't here."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{nick}\x02 isn't here.", nick = nick));
|
||||
return;
|
||||
}
|
||||
},
|
||||
None => from.uid.to_string(),
|
||||
};
|
||||
ctx.invite(me, &target, chan);
|
||||
ctx.notice(me, from.uid, format!("Invited to \x02{chan}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Invited to \x02{chan}\x02.", chan = chan));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use echo_api::Store;
|
||||
use echo_api::{Sender, ServiceCtx};
|
||||
use echo_api::NetView;
|
||||
use echo_api::t;
|
||||
|
||||
// KICK <#channel> <nick> [reason]: kick a user.
|
||||
pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net: &dyn NetView, db: &dyn Store) {
|
||||
|
|
@ -12,7 +13,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
return;
|
||||
}
|
||||
let Some(target) = net.uid_by_nick(nick) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{nick}\x02 isn't here."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{nick}\x02 isn't here.", nick = nick));
|
||||
return;
|
||||
};
|
||||
if super::peace_blocks(me, from, chan, target, ctx, net, db) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use echo_api::Store;
|
||||
use echo_api::{AccessRole, LevelCap, Sender, ServiceCtx};
|
||||
use echo_api::t;
|
||||
|
||||
// LEVELS <#channel> [SET <capability> <tier> | RESET <capability>]
|
||||
// Tune which access tier holds each channel capability (OP, TOPIC, INVITE, ACCESS).
|
||||
|
|
@ -20,16 +21,16 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
|
||||
fn list(me: &str, from: &Sender, chan: &str, ctx: &mut ServiceCtx, db: &dyn Store) {
|
||||
let Some(info) = db.channel(chan) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{chan}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 isn't registered.", chan = chan));
|
||||
return;
|
||||
};
|
||||
ctx.notice(me, from.uid, format!("Access levels for \x02{}\x02 (capability → minimum tier):", info.name));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Access levels for \x02{name}\x02 (capability → minimum tier):", name = info.name));
|
||||
for cap in LevelCap::ALL {
|
||||
let (tier, tag) = match info.levels.iter().find(|(c, _)| *c == cap) {
|
||||
Some((_, role)) => (*role, " (custom)"),
|
||||
None => (cap.default_role(), ""),
|
||||
};
|
||||
ctx.notice(me, from.uid, format!(" \x02{}\x02 — {}{}", cap.name(), tier_word(tier), tag));
|
||||
ctx.notice(me, from.uid, t!(ctx, " \x02{cap}\x02 — {tier}{tag}", cap = cap.name(), tier = tier_word(tier), tag = tag));
|
||||
}
|
||||
ctx.notice(me, from.uid, "Founder: \x02LEVELS <#chan> SET <capability> <tier>\x02 grants it to that tier and above; \x02RESET\x02 restores the default.");
|
||||
}
|
||||
|
|
@ -58,7 +59,7 @@ fn set(me: &str, from: &Sender, chan: &str, rest: &[&str], ctx: &mut ServiceCtx,
|
|||
}
|
||||
};
|
||||
match db.level_set(chan, cap.name(), tier_word(role)) {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("\x02{}\x02 on \x02{chan}\x02 is now held by \x02{}\x02 and above.", cap.name(), tier_word(role))),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "\x02{cap}\x02 on \x02{chan}\x02 is now held by \x02{tier}\x02 and above.", cap = cap.name(), chan = chan, tier = tier_word(role))),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
@ -76,8 +77,8 @@ fn reset(me: &str, from: &Sender, chan: &str, cap_arg: Option<&str>, ctx: &mut S
|
|||
return;
|
||||
};
|
||||
match db.level_reset(chan, cap.name()) {
|
||||
Ok(true) => ctx.notice(me, from.uid, format!("\x02{}\x02 on \x02{chan}\x02 is back to its default tier (\x02{}\x02).", cap.name(), tier_word(cap.default_role()))),
|
||||
Ok(false) => ctx.notice(me, from.uid, format!("\x02{}\x02 on \x02{chan}\x02 has no custom level.", cap.name())),
|
||||
Ok(true) => ctx.notice(me, from.uid, t!(ctx, "\x02{cap}\x02 on \x02{chan}\x02 is back to its default tier (\x02{tier}\x02).", cap = cap.name(), chan = chan, tier = tier_word(cap.default_role()))),
|
||||
Ok(false) => ctx.notice(me, from.uid, t!(ctx, "\x02{cap}\x02 on \x02{chan}\x02 has no custom level.", cap = cap.name(), chan = chan)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use echo_api::{ChanError, ChannelView, ForbidKind, Priv, Store};
|
||||
use echo_api::{HelpEntry, Sender, Service, ServiceCtx};
|
||||
use echo_api::NetView;
|
||||
use echo_api::t;
|
||||
|
||||
#[path = "mode.rs"]
|
||||
mod mode;
|
||||
|
|
@ -141,11 +142,11 @@ impl Service for ChanServ {
|
|||
};
|
||||
// You can only register a channel you actually control right now.
|
||||
if !net.is_op(chan, from.uid) {
|
||||
ctx.notice(me, from.uid, format!("You must be a channel operator (\x02@\x02) in \x02{chan}\x02 to register it."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "You must be a channel operator (\x02@\x02) in \x02{chan}\x02 to register it.", chan = chan));
|
||||
return;
|
||||
}
|
||||
if let Some(reason) = db.is_forbidden(ForbidKind::Chan, chan) {
|
||||
ctx.notice(me, from.uid, format!("\x02{chan}\x02 can't be registered: {reason}"));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 can't be registered: {reason}", chan = chan, reason = reason));
|
||||
return;
|
||||
}
|
||||
// Refuse a look-alike / mixed-script channel name (e.g. a Cyrillic
|
||||
|
|
@ -161,15 +162,15 @@ impl Service for ChanServ {
|
|||
Ok(()) => {
|
||||
ctx.channel_mode(me, chan, "+r"); // mark the channel registered
|
||||
ctx.count("chanserv.register");
|
||||
ctx.notice(me, from.uid, format!("\x02{chan}\x02 is now registered and you are its founder. Enjoy!"));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 is now registered and you are its founder. Enjoy!", chan = chan));
|
||||
// Auto-assign the network's default bot, if one is set (BotServ AUTOASSIGN).
|
||||
if let Some(bot) = db.default_bot() {
|
||||
if db.assign_bot(chan, &bot).is_ok() {
|
||||
ctx.notice(me, from.uid, format!("Assigned \x02{bot}\x02 to \x02{chan}\x02. Change it with \x02/msg BotServ ASSIGN\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Assigned \x02{bot}\x02 to \x02{chan}\x02. Change it with \x02/msg BotServ ASSIGN\x02.", bot = bot, chan = chan));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(ChanError::Exists) => ctx.notice(me, from.uid, format!("\x02{chan}\x02 is already registered. Try \x02INFO {chan}\x02 to see who owns it.")),
|
||||
Err(ChanError::Exists) => ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 is already registered. Try \x02INFO {chan}\x02 to see who owns it.", chan = chan)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
@ -180,20 +181,20 @@ impl Service for ChanServ {
|
|||
};
|
||||
match db.channel(chan) {
|
||||
Some(info) => {
|
||||
ctx.notice(me, from.uid, format!("Information for \x02{}\x02:", info.name));
|
||||
ctx.notice(me, from.uid, format!(" Founder : \x02{}\x02", info.founder));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Information for \x02{name}\x02:", name = info.name));
|
||||
ctx.notice(me, from.uid, t!(ctx, " Founder : \x02{founder}\x02", founder = info.founder));
|
||||
if !info.desc.is_empty() {
|
||||
ctx.notice(me, from.uid, format!(" Description: {}", info.desc));
|
||||
ctx.notice(me, from.uid, t!(ctx, " Description: {desc}", desc = info.desc));
|
||||
}
|
||||
if !info.url.is_empty() {
|
||||
ctx.notice(me, from.uid, format!(" URL : {}", info.url));
|
||||
ctx.notice(me, from.uid, t!(ctx, " URL : {url}", url = info.url));
|
||||
}
|
||||
if !info.email.is_empty() {
|
||||
ctx.notice(me, from.uid, format!(" Email : {}", info.email));
|
||||
ctx.notice(me, from.uid, t!(ctx, " Email : {email}", email = info.email));
|
||||
}
|
||||
ctx.notice(me, from.uid, format!(" Registered : {}", echo_api::human_time(info.ts)));
|
||||
ctx.notice(me, from.uid, t!(ctx, " Registered : {when}", when = echo_api::human_time(info.ts)));
|
||||
if let Some(s) = db.channel_suspension(chan) {
|
||||
ctx.notice(me, from.uid, format!(" Suspended : by \x02{}\x02 — {}", s.by, s.reason));
|
||||
ctx.notice(me, from.uid, t!(ctx, " Suspended : by \x02{by}\x02 — {reason}", by = s.by, reason = s.reason));
|
||||
}
|
||||
let mut opts = Vec::new();
|
||||
if info.signkick { opts.push("SIGNKICK"); }
|
||||
|
|
@ -203,16 +204,16 @@ impl Service for ChanServ {
|
|||
if info.keeptopic { opts.push("KEEPTOPIC"); }
|
||||
if info.topiclock { opts.push("TOPICLOCK"); }
|
||||
if !opts.is_empty() {
|
||||
ctx.notice(me, from.uid, format!(" Options : {}", opts.join(", ")));
|
||||
ctx.notice(me, from.uid, t!(ctx, " Options : {options}", options = opts.join(", ")));
|
||||
}
|
||||
// A staff note is shown to operators only.
|
||||
if from.privs.has(Priv::Auspex) {
|
||||
if let Some(note) = db.channel_note(chan) {
|
||||
ctx.notice(me, from.uid, format!(" Staff note : {note}"));
|
||||
ctx.notice(me, from.uid, t!(ctx, " Staff note : {note}", note = note));
|
||||
}
|
||||
}
|
||||
}
|
||||
None => ctx.notice(me, from.uid, format!("\x02{chan}\x02 isn't registered.")),
|
||||
None => ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 isn't registered.", chan = chan)),
|
||||
}
|
||||
}
|
||||
Some("DROP") => {
|
||||
|
|
@ -224,12 +225,12 @@ impl Service for ChanServ {
|
|||
let founder = match db.channel(chan) {
|
||||
Some(info) => info.founder.clone(),
|
||||
None => {
|
||||
ctx.notice(me, from.uid, format!("\x02{chan}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 isn't registered.", chan = chan));
|
||||
return;
|
||||
}
|
||||
};
|
||||
if from.account != Some(founder.as_str()) {
|
||||
ctx.notice(me, from.uid, format!("Only \x02{chan}\x02's founder can drop it."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Only \x02{chan}\x02's founder can drop it.", chan = chan));
|
||||
return;
|
||||
}
|
||||
if suspended_block(me, from, chan, ctx, db) {
|
||||
|
|
@ -239,7 +240,7 @@ impl Service for ChanServ {
|
|||
Ok(()) => {
|
||||
ctx.channel_mode(me, chan, "-r"); // no longer registered
|
||||
ctx.count("chanserv.drop");
|
||||
ctx.notice(me, from.uid, format!("\x02{chan}\x02 has been dropped and is no longer registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 has been dropped and is no longer registered.", chan = chan));
|
||||
}
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
|
|
@ -252,11 +253,11 @@ impl Service for ChanServ {
|
|||
// No modes given: show the current lock.
|
||||
if args.len() <= 2 {
|
||||
match db.channel(chan) {
|
||||
None => ctx.notice(me, from.uid, format!("\x02{chan}\x02 isn't registered.")),
|
||||
None => ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 isn't registered.", chan = chan)),
|
||||
Some(info) if info.lock_on.is_empty() && info.lock_off.is_empty() => {
|
||||
ctx.notice(me, from.uid, format!("\x02{}\x02 has no mode lock set.", info.name));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{name}\x02 has no mode lock set.", name = info.name));
|
||||
}
|
||||
Some(info) => ctx.notice(me, from.uid, format!("Mode lock for \x02{}\x02: \x02{}\x02", info.name, show_mlock(&info))),
|
||||
Some(info) => ctx.notice(me, from.uid, t!(ctx, "Mode lock for \x02{name}\x02: \x02{lock}\x02", name = info.name, lock = show_mlock(&info))),
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -264,12 +265,12 @@ impl Service for ChanServ {
|
|||
let founder = match db.channel(chan) {
|
||||
Some(info) => info.founder.clone(),
|
||||
None => {
|
||||
ctx.notice(me, from.uid, format!("\x02{chan}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 isn't registered.", chan = chan));
|
||||
return;
|
||||
}
|
||||
};
|
||||
if from.account != Some(founder.as_str()) {
|
||||
ctx.notice(me, from.uid, format!("Only \x02{chan}\x02's founder can set its mode lock."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Only \x02{chan}\x02's founder can set its mode lock.", chan = chan));
|
||||
return;
|
||||
}
|
||||
if suspended_block(me, from, chan, ctx, db) {
|
||||
|
|
@ -281,7 +282,7 @@ impl Service for ChanServ {
|
|||
if let Some(info) = db.channel(chan) {
|
||||
ctx.channel_mode(me, chan, &info.lock_modes()); // apply it now
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("Mode lock for \x02{chan}\x02 updated."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Mode lock for \x02{chan}\x02 updated.", chan = chan));
|
||||
}
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
|
|
@ -334,7 +335,7 @@ impl Service for ChanServ {
|
|||
Some("HELP") => echo_api::help(me, from, ctx, BLURB, TOPICS, args.get(1).copied()),
|
||||
// Direct `/msg ChanServ FOO` earns this reply; the fantasy router
|
||||
// gates on COMMANDS first so an in-channel `!foo` stays silent.
|
||||
Some(other) => ctx.notice(me, from.uid, format!("I don't know the command \x02{other}\x02. Try \x02HELP\x02.")),
|
||||
Some(other) => ctx.notice(me, from.uid, t!(ctx, "I don't know the command \x02{other}\x02. Try \x02HELP\x02.", other = other)),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
|
@ -347,12 +348,12 @@ fn require_founder(me: &str, from: &Sender, chan: &str, ctx: &mut ServiceCtx, db
|
|||
}
|
||||
match db.channel(chan) {
|
||||
None => {
|
||||
ctx.notice(me, from.uid, format!("\x02{chan}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 isn't registered.", chan = chan));
|
||||
false
|
||||
}
|
||||
Some(info) if from.account == Some(info.founder.as_str()) => true,
|
||||
_ => {
|
||||
ctx.notice(me, from.uid, format!("Only \x02{chan}\x02's founder can do that."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Only \x02{chan}\x02's founder can do that.", chan = chan));
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
@ -365,12 +366,12 @@ fn require_op(me: &str, from: &Sender, chan: &str, ctx: &mut ServiceCtx, db: &dy
|
|||
}
|
||||
match (from.account, db.channel(chan)) {
|
||||
(_, None) => {
|
||||
ctx.notice(me, from.uid, format!("\x02{chan}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 isn't registered.", chan = chan));
|
||||
false
|
||||
}
|
||||
(Some(acc), Some(info)) if info.is_op(acc) => true,
|
||||
_ => {
|
||||
ctx.notice(me, from.uid, format!("You need operator access to \x02{chan}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "You need operator access to \x02{chan}\x02.", chan = chan));
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
@ -379,7 +380,7 @@ fn require_op(me: &str, from: &Sender, chan: &str, ctx: &mut ServiceCtx, db: &dy
|
|||
// A suspended channel is frozen: ChanServ won't manage it (returns true + notices).
|
||||
fn suspended_block(me: &str, from: &Sender, chan: &str, ctx: &mut ServiceCtx, db: &dyn Store) -> bool {
|
||||
if db.is_channel_suspended(chan) {
|
||||
ctx.notice(me, from.uid, format!("\x02{chan}\x02 is suspended by network staff and can't be managed right now."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 is suspended by network staff and can't be managed right now.", chan = chan));
|
||||
return true;
|
||||
}
|
||||
false
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use echo_api::Store;
|
||||
use echo_api::{Sender, ServiceCtx};
|
||||
use echo_api::t;
|
||||
|
||||
// LIST: show all registered channels.
|
||||
pub fn handle(me: &str, from: &Sender, _args: &[&str], ctx: &mut ServiceCtx, db: &dyn Store) {
|
||||
|
|
@ -10,8 +11,8 @@ pub fn handle(me: &str, from: &Sender, _args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
return;
|
||||
}
|
||||
names.sort_unstable();
|
||||
ctx.notice(me, from.uid, format!("Registered channels ({}):", names.len()));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Registered channels ({count}):", count = names.len()));
|
||||
for n in names {
|
||||
ctx.notice(me, from.uid, format!(" \x02{n}\x02"));
|
||||
ctx.notice(me, from.uid, t!(ctx, " \x02{n}\x02", n = n));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use echo_api::Store;
|
||||
use echo_api::{Sender, ServiceCtx};
|
||||
use echo_api::t;
|
||||
|
||||
// MODE <#channel> <modes>: the founder sets channel modes via ChanServ. Extban
|
||||
// arguments on the ban-family list modes (+b/+e/+I) are held to the same
|
||||
|
|
@ -16,12 +17,12 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
let founder = match db.channel(chan) {
|
||||
Some(info) => info.founder.clone(),
|
||||
None => {
|
||||
ctx.notice(me, from.uid, format!("\x02{chan}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 isn't registered.", chan = chan));
|
||||
return;
|
||||
}
|
||||
};
|
||||
if from.account != Some(founder.as_str()) {
|
||||
ctx.notice(me, from.uid, format!("Only \x02{chan}\x02's founder can change its modes."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Only \x02{chan}\x02's founder can change its modes.", chan = chan));
|
||||
return;
|
||||
}
|
||||
if super::suspended_block(me, from, chan, ctx, db) {
|
||||
|
|
@ -32,18 +33,18 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
let core = mask.strip_prefix('!').unwrap_or(mask); // extbans may be inverted
|
||||
if let echo_api::AkickMask::Ext(eb, _) = echo_api::AkickMask::parse(core) {
|
||||
if !db.extban_offered(eb.name) {
|
||||
ctx.notice(me, from.uid, format!("This network's ircd doesn't offer the \x02{}\x02 extban.", eb.name));
|
||||
ctx.notice(me, from.uid, t!(ctx, "This network's ircd doesn't offer the \x02{name}\x02 extban.", name = eb.name));
|
||||
return;
|
||||
}
|
||||
if !db.extban_enabled(eb.name) {
|
||||
ctx.notice(me, from.uid, format!("The \x02{}\x02 extban isn't enabled on this network.", eb.name));
|
||||
ctx.notice(me, from.uid, t!(ctx, "The \x02{name}\x02 extban isn't enabled on this network.", name = eb.name));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
let modes = args[2..].join(" ");
|
||||
ctx.channel_mode(me, chan, &modes);
|
||||
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));
|
||||
}
|
||||
|
||||
// The parameters attached to +b/-b/+e/-e/+I/-I in a `<modes> [params...]` change.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use echo_api::{Priv, Sender, ServiceCtx, Store};
|
||||
use echo_api::t;
|
||||
|
||||
// NOEXPIRE <#channel> {ON|OFF}: pin a channel so inactivity-expiry never drops
|
||||
// it (or lift the pin). Oper-only (Priv::Admin).
|
||||
|
|
@ -12,13 +13,13 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
return;
|
||||
};
|
||||
if db.channel(chan).is_none() {
|
||||
ctx.notice(me, from.uid, format!("\x02{chan}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 isn't registered.", chan = chan));
|
||||
return;
|
||||
}
|
||||
match db.set_channel_noexpire(chan, on) {
|
||||
Ok(true) if on => ctx.notice(me, from.uid, format!("\x02{chan}\x02 will no longer expire.")),
|
||||
Ok(true) => ctx.notice(me, from.uid, format!("\x02{chan}\x02 can expire from inactivity again.")),
|
||||
Ok(false) => ctx.notice(me, from.uid, format!("\x02{chan}\x02 was already set that way.")),
|
||||
Ok(true) if on => ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 will no longer expire.", chan = chan)),
|
||||
Ok(true) => ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 can expire from inactivity again.", chan = chan)),
|
||||
Ok(false) => ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 was already set that way.", chan = chan)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use echo_api::Store;
|
||||
use echo_api::{Sender, ServiceCtx};
|
||||
use echo_api::NetView;
|
||||
use echo_api::t;
|
||||
|
||||
// OP/DEOP/VOICE/DEVOICE <#channel> [nick]: set a status mode on a user (self if
|
||||
// no nick given). `mode` is the mode to apply, e.g. "+o".
|
||||
|
|
@ -22,7 +23,7 @@ pub fn handle(me: &str, from: &Sender, mode: &str, args: &[&str], ctx: &mut Serv
|
|||
Some(&nick) => match net.uid_by_nick(nick) {
|
||||
Some(u) => u.to_string(),
|
||||
None => {
|
||||
ctx.notice(me, from.uid, format!("\x02{nick}\x02 isn't here."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{nick}\x02 isn't here.", nick = nick));
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use echo_api::{Sender, ServiceCtx};
|
||||
use echo_api::NetView;
|
||||
use echo_api::t;
|
||||
|
||||
// SEEN <nick> — network-wide: when a nick was last seen, and doing what.
|
||||
// SEEN <#channel> <nick> — channel-scoped: when last active there, and their last
|
||||
|
|
@ -15,26 +16,26 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
// Present in THIS channel right now — online elsewhere doesn't count.
|
||||
let here = net.uid_by_nick(nick).is_some_and(|uid| net.channel_members(chan).iter().any(|m| m == uid));
|
||||
if here {
|
||||
ctx.notice(me, from.uid, format!("{}: \x02{nick}\x02 is here right now.", from.nick));
|
||||
ctx.notice(me, from.uid, t!(ctx, "{who}: \x02{nick}\x02 is here right now.", who = from.nick, nick = nick));
|
||||
return;
|
||||
}
|
||||
match net.channel_seen(chan, nick) {
|
||||
Some(s) => ctx.notice(me, from.uid, format!(
|
||||
"{}: \x02{}\x02 was last seen on \x02{chan}\x02 {}, last saying: {}",
|
||||
from.nick, s.nick, echo_api::human_time(s.ts), s.msg
|
||||
Some(s) => ctx.notice(me, from.uid, t!(ctx,
|
||||
"{who}: \x02{nick}\x02 was last seen on \x02{chan}\x02 {when}, last saying: {msg}",
|
||||
who = from.nick, nick = s.nick, chan = chan, when = echo_api::human_time(s.ts), msg = s.msg
|
||||
)),
|
||||
None => ctx.notice(me, from.uid, format!("{}: I have no record of \x02{nick}\x02 talking in \x02{chan}\x02.", from.nick)),
|
||||
None => ctx.notice(me, from.uid, t!(ctx, "{who}: I have no record of \x02{nick}\x02 talking in \x02{chan}\x02.", who = from.nick, nick = nick, chan = chan)),
|
||||
}
|
||||
}
|
||||
// Network-wide.
|
||||
Some(&nick) => {
|
||||
if net.uid_by_nick(nick).is_some() {
|
||||
ctx.notice(me, from.uid, format!("\x02{nick}\x02 is currently online."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{nick}\x02 is currently online.", nick = nick));
|
||||
return;
|
||||
}
|
||||
match net.last_seen(nick) {
|
||||
Some(s) => ctx.notice(me, from.uid, format!("\x02{}\x02 was last seen {} ({}).", s.nick, echo_api::human_time(s.ts), s.what)),
|
||||
None => ctx.notice(me, from.uid, format!("I have no record of \x02{nick}\x02.")),
|
||||
Some(s) => ctx.notice(me, from.uid, t!(ctx, "\x02{nick}\x02 was last seen {when} ({what}).", nick = s.nick, when = echo_api::human_time(s.ts), what = s.what)),
|
||||
None => ctx.notice(me, from.uid, t!(ctx, "I have no record of \x02{nick}\x02.", nick = nick)),
|
||||
}
|
||||
}
|
||||
None => ctx.notice(me, from.uid, "Syntax: SEEN <nick> | SEEN <#channel> <nick>"),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use echo_api::{ChanSetting, Sender, ServiceCtx, Store};
|
||||
use echo_api::t;
|
||||
|
||||
// SET <#channel> FOUNDER <account> | DESC <text>: founder-only channel settings.
|
||||
pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
|
|
@ -8,13 +9,13 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
};
|
||||
let founder = match db.channel(chan) {
|
||||
None => {
|
||||
ctx.notice(me, from.uid, format!("\x02{chan}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 isn't registered.", chan = chan));
|
||||
return;
|
||||
}
|
||||
Some(info) => info.founder.clone(),
|
||||
};
|
||||
if from.account != Some(founder.as_str()) {
|
||||
ctx.notice(me, from.uid, format!("Only \x02{chan}\x02's founder can change its settings."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Only \x02{chan}\x02's founder can change its settings.", chan = chan));
|
||||
return;
|
||||
}
|
||||
if super::suspended_block(me, from, chan, ctx, db) {
|
||||
|
|
@ -27,11 +28,11 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
return;
|
||||
};
|
||||
if !db.exists(account) {
|
||||
ctx.notice(me, from.uid, format!("\x02{account}\x02 isn't a registered account."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{account}\x02 isn't a registered account.", account = account));
|
||||
return;
|
||||
}
|
||||
match db.set_founder(chan, account) {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("Founder of \x02{chan}\x02 transferred to \x02{account}\x02.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Founder of \x02{chan}\x02 transferred to \x02{account}\x02.", chan = chan, account = account)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
@ -39,7 +40,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
match args.get(3) {
|
||||
Some(&acct) if !acct.eq_ignore_ascii_case("OFF") => {
|
||||
if !db.exists(acct) {
|
||||
ctx.notice(me, from.uid, format!("\x02{acct}\x02 isn't a registered account."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{account}\x02 isn't a registered account.", account = acct));
|
||||
return;
|
||||
}
|
||||
if acct.eq_ignore_ascii_case(&founder) {
|
||||
|
|
@ -47,12 +48,12 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
return;
|
||||
}
|
||||
match db.set_successor(chan, Some(acct)) {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("Successor of \x02{chan}\x02 set to \x02{acct}\x02. They inherit it if your account is dropped or expires.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Successor of \x02{chan}\x02 set to \x02{acct}\x02. They inherit it if your account is dropped or expires.", chan = chan, acct = acct)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
_ => match db.set_successor(chan, None) {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("Successor of \x02{chan}\x02 cleared.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Successor of \x02{chan}\x02 cleared.", chan = chan)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
},
|
||||
}
|
||||
|
|
@ -60,23 +61,23 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
Some("DESC") => {
|
||||
let desc = if args.len() > 3 { args[3..].join(" ") } else { String::new() };
|
||||
match db.set_desc(chan, &desc) {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("Description for \x02{chan}\x02 updated.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Description for \x02{chan}\x02 updated.", chan = chan)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
Some("URL") => {
|
||||
let url = if args.len() > 3 { args[3..].join(" ") } else { String::new() };
|
||||
match db.set_url(chan, &url) {
|
||||
Ok(()) if url.is_empty() => ctx.notice(me, from.uid, format!("URL for \x02{chan}\x02 cleared.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("URL for \x02{chan}\x02 updated.")),
|
||||
Ok(()) if url.is_empty() => ctx.notice(me, from.uid, t!(ctx, "URL for \x02{chan}\x02 cleared.", chan = chan)),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "URL for \x02{chan}\x02 updated.", chan = chan)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
Some("EMAIL") => {
|
||||
let email = args.get(3).copied().unwrap_or("");
|
||||
match db.set_channel_email(chan, email) {
|
||||
Ok(()) if email.is_empty() => ctx.notice(me, from.uid, format!("Contact email for \x02{chan}\x02 cleared.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("Contact email for \x02{chan}\x02 updated.")),
|
||||
Ok(()) if email.is_empty() => ctx.notice(me, from.uid, t!(ctx, "Contact email for \x02{chan}\x02 cleared.", chan = chan)),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Contact email for \x02{chan}\x02 updated.", chan = chan)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
@ -115,12 +116,12 @@ fn toggle(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &mut dyn Store, cha
|
|||
Some("ON") => true,
|
||||
Some("OFF") => false,
|
||||
_ => {
|
||||
ctx.notice(me, from.uid, format!("Syntax: SET <#channel> {name} {{ON|OFF}}"));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Syntax: SET <#channel> {name} {ON|OFF}", name = name));
|
||||
return;
|
||||
}
|
||||
};
|
||||
match db.set_channel_setting(chan, setting, on) {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("\x02{name}\x02 for \x02{chan}\x02 is now \x02{}\x02.", if on { "on" } else { "off" })),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "\x02{name}\x02 for \x02{chan}\x02 is now \x02{state}\x02.", name = name, chan = chan, state = if on { "on" } else { "off" })),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use echo_api::Store;
|
||||
use echo_api::{access_role, Sender, ServiceCtx};
|
||||
use echo_api::NetView;
|
||||
use echo_api::t;
|
||||
|
||||
// STATUS <#channel> [nick]: show a user's access level (self if no nick).
|
||||
pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net: &dyn NetView, db: &dyn Store) {
|
||||
|
|
@ -9,7 +10,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
return;
|
||||
};
|
||||
let Some(info) = db.channel(chan) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{chan}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 isn't registered.", chan = chan));
|
||||
return;
|
||||
};
|
||||
let (label, account) = match args.get(2) {
|
||||
|
|
@ -26,5 +27,5 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
None => "none",
|
||||
},
|
||||
};
|
||||
ctx.notice(me, from.uid, format!("\x02{label}\x02 access on \x02{chan}\x02: \x02{level}\x02"));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{label}\x02 access on \x02{chan}\x02: \x02{level}\x02", label = label, chan = chan, level = level));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use echo_api::{parse_duration, NetView, Priv, Sender, ServiceCtx, Store};
|
||||
use echo_api::t;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
// SUSPEND <#channel> [+expiry] [reason] / UNSUSPEND <#channel>: freeze or unfreeze
|
||||
|
|
@ -15,14 +16,14 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
return;
|
||||
};
|
||||
if db.channel(chan).is_none() {
|
||||
ctx.notice(me, from.uid, format!("\x02{chan}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 isn't registered.", chan = chan));
|
||||
return;
|
||||
}
|
||||
|
||||
if !suspending {
|
||||
match db.unsuspend_channel(chan) {
|
||||
Ok(true) => ctx.notice(me, from.uid, format!("\x02{chan}\x02 is no longer suspended.")),
|
||||
Ok(false) => ctx.notice(me, from.uid, format!("\x02{chan}\x02 isn't suspended.")),
|
||||
Ok(true) => ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 is no longer suspended.", chan = chan)),
|
||||
Ok(false) => ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 isn't suspended.", chan = chan)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
return;
|
||||
|
|
@ -42,7 +43,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
ctx.kick(me, chan, &uid, &reason);
|
||||
}
|
||||
let expiry = if expires.is_some() { " (with expiry)" } else { "" };
|
||||
ctx.notice(me, from.uid, format!("\x02{chan}\x02 is now suspended{expiry}."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 is now suspended{expiry}.", chan = chan, expiry = expiry));
|
||||
}
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use echo_api::Store;
|
||||
use echo_api::{Sender, ServiceCtx};
|
||||
use echo_api::t;
|
||||
|
||||
// TOPIC <#channel> <text>: set the channel topic (empty clears it).
|
||||
pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
|
|
@ -16,5 +17,5 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
// stored topic stays stale and KEEPTOPIC/TOPICLOCK restore the old one on
|
||||
// recreation/restart. Ignore NoChannel (require_op already proved it exists).
|
||||
let _ = db.set_channel_topic(chan, &text);
|
||||
ctx.notice(me, from.uid, format!("Topic for \x02{chan}\x02 updated."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Topic for \x02{chan}\x02 updated.", chan = chan));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use echo_api::Store;
|
||||
use echo_api::{Sender, ServiceCtx};
|
||||
use echo_api::NetView;
|
||||
use echo_api::t;
|
||||
|
||||
// UNBAN <#channel> [nick]: remove the *!*@host ban of a user (self if no nick).
|
||||
pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net: &dyn NetView, db: &dyn Store) {
|
||||
|
|
@ -18,5 +19,5 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
.unwrap_or_else(|| from.uid.to_string());
|
||||
let host = net.host_of(&target).unwrap_or("*");
|
||||
ctx.channel_mode(me, chan, &format!("-b *!*@{host}"));
|
||||
ctx.notice(me, from.uid, format!("Cleared the *!*@{host} ban on \x02{chan}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Cleared the *!*@{host} ban on \x02{chan}\x02.", host = host, chan = chan));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use echo_api::{status_mode, NetView, Sender, ServiceCtx, Store};
|
||||
use echo_api::t;
|
||||
|
||||
// UP <#channel>: (re)apply the status mode your access entitles you to.
|
||||
// DOWN <#channel>: drop your channel status modes. `up` selects which.
|
||||
|
|
@ -12,24 +13,24 @@ pub fn handle(me: &str, from: &Sender, up: bool, args: &[&str], ctx: &mut Servic
|
|||
return;
|
||||
};
|
||||
let Some(info) = db.channel(chan) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{chan}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 isn't registered.", chan = chan));
|
||||
return;
|
||||
};
|
||||
if !net.channel_members(chan).iter().any(|u| u.as_str() == from.uid) {
|
||||
ctx.notice(me, from.uid, format!("You're not in \x02{chan}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "You're not in \x02{chan}\x02.", chan = chan));
|
||||
return;
|
||||
}
|
||||
if up {
|
||||
match info.join_mode(account) {
|
||||
Some(mode) => {
|
||||
ctx.channel_mode(me, chan, &status_mode(mode, from.uid));
|
||||
ctx.notice(me, from.uid, format!("Your status in \x02{chan}\x02 has been applied."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Your status in \x02{chan}\x02 has been applied.", chan = chan));
|
||||
}
|
||||
None => ctx.notice(me, from.uid, format!("You have no status access in \x02{chan}\x02.")),
|
||||
None => ctx.notice(me, from.uid, t!(ctx, "You have no status access in \x02{chan}\x02.", chan = chan)),
|
||||
}
|
||||
} else {
|
||||
// Strip owner, admin, op, halfop and voice; the ircd ignores any you don't hold.
|
||||
ctx.channel_mode(me, chan, &status_mode("-qaohv", from.uid));
|
||||
ctx.notice(me, from.uid, format!("Your status in \x02{chan}\x02 has been removed."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Your status in \x02{chan}\x02 has been removed.", chan = chan));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,52 +1,53 @@
|
|||
use echo_api::{access_role, Sender, ServiceCtx, Store};
|
||||
use echo_api::t;
|
||||
|
||||
// SOP/AOP/HOP/VOP <#channel> ADD <account> | DEL <account> | LIST — tiered
|
||||
// shortcuts over the access list. `level` is the tier they map to ("sop", "op",
|
||||
// "halfop", "voice"); `word` is the tier the user typed ("SOP".."VOP").
|
||||
pub fn handle(me: &str, from: &Sender, word: &str, level: &str, args: &[&str], ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
let Some(&chan) = args.get(1) else {
|
||||
ctx.notice(me, from.uid, format!("Syntax: {word} <#channel> ADD <account> | DEL <account> | LIST"));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Syntax: {word} <#channel> ADD <account> | DEL <account> | LIST", word = word));
|
||||
return;
|
||||
};
|
||||
match args.get(2).map(|s| s.to_ascii_uppercase()).as_deref() {
|
||||
None | Some("LIST") => match db.channel(chan) {
|
||||
None => ctx.notice(me, from.uid, format!("\x02{chan}\x02 isn't registered.")),
|
||||
None => ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 isn't registered.", chan = chan)),
|
||||
Some(info) => {
|
||||
ctx.notice(me, from.uid, format!("{word} list for \x02{}\x02:", info.name));
|
||||
ctx.notice(me, from.uid, t!(ctx, "{word} list for \x02{name}\x02:", word = word, name = info.name));
|
||||
// List by resolved tier, not the raw level string, so an entry set
|
||||
// via granular FLAGS appears under the same tier a XOP ADD would.
|
||||
for a in info.access.iter().filter(|a| access_role(&a.level).xop_word() == Some(word)) {
|
||||
ctx.notice(me, from.uid, format!(" \x02{}\x02", a.account));
|
||||
ctx.notice(me, from.uid, t!(ctx, " \x02{account}\x02", account = a.account));
|
||||
}
|
||||
}
|
||||
},
|
||||
Some("ADD") => {
|
||||
let Some(&account) = args.get(3) else {
|
||||
ctx.notice(me, from.uid, format!("Syntax: {word} <#channel> ADD <account>"));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Syntax: {word} <#channel> ADD <account>", word = word));
|
||||
return;
|
||||
};
|
||||
if !super::require_founder(me, from, chan, ctx, db) {
|
||||
return;
|
||||
}
|
||||
match db.access_add(chan, account, level) {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("Added \x02{account}\x02 to \x02{chan}\x02's {word} list.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Added \x02{account}\x02 to \x02{chan}\x02's {word} list.", account = account, chan = chan, word = word)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
Some("DEL") => {
|
||||
let Some(&account) = args.get(3) else {
|
||||
ctx.notice(me, from.uid, format!("Syntax: {word} <#channel> DEL <account>"));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Syntax: {word} <#channel> DEL <account>", word = word));
|
||||
return;
|
||||
};
|
||||
if !super::require_founder(me, from, chan, ctx, db) {
|
||||
return;
|
||||
}
|
||||
match db.access_del(chan, account) {
|
||||
Ok(true) => ctx.notice(me, from.uid, format!("Removed \x02{account}\x02 from \x02{chan}\x02's {word} list.")),
|
||||
Ok(false) => ctx.notice(me, from.uid, format!("\x02{account}\x02 has no access to \x02{chan}\x02.")),
|
||||
Ok(true) => ctx.notice(me, from.uid, t!(ctx, "Removed \x02{account}\x02 from \x02{chan}\x02's {word} list.", account = account, chan = chan, word = word)),
|
||||
Ok(false) => ctx.notice(me, from.uid, t!(ctx, "\x02{account}\x02 has no access to \x02{chan}\x02.", account = account, chan = chan)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
_ => ctx.notice(me, from.uid, format!("Syntax: {word} <#channel> ADD <account> | DEL <account> | LIST")),
|
||||
_ => ctx.notice(me, from.uid, t!(ctx, "Syntax: {word} <#channel> ADD <account> | DEL <account> | LIST", word = word)),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use echo_api::{HelpEntry, NetAction, NetView, Sender, Service, ServiceCtx, Store};
|
||||
use echo_api::{t, HelpEntry, NetAction, NetView, Sender, Service, ServiceCtx, Store};
|
||||
|
||||
#[path = "chess.rs"]
|
||||
mod chess;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Sender, ServiceCtx, Store};
|
||||
|
||||
// ADD <!group> <account>: add a plain member (no flags). Needs founder or `f`.
|
||||
pub fn handle(me: &str, from: &Sender, name: Option<&str>, target: Option<&str>, ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
|
|
@ -8,19 +8,19 @@ pub fn handle(me: &str, from: &Sender, name: Option<&str>, target: Option<&str>,
|
|||
return;
|
||||
};
|
||||
let Some(g) = db.group(name) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{name}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{name}\x02 isn't registered.", name = name));
|
||||
return;
|
||||
};
|
||||
if !super::can_manage(&g, acc) {
|
||||
ctx.notice(me, from.uid, format!("You need the founder or the \x02f\x02 flag to manage \x02{name}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "You need the founder or the \x02f\x02 flag to manage \x02{name}\x02.", name = name));
|
||||
return;
|
||||
}
|
||||
let Some(canonical) = db.resolve_account(target).map(str::to_string) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{target}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{target}\x02 isn't registered.", target = target));
|
||||
return;
|
||||
};
|
||||
match db.group_set_flags(name, &canonical, "") {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("Added \x02{canonical}\x02 to \x02{name}\x02.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Added \x02{canonical}\x02 to \x02{name}\x02.", canonical = canonical, name = name)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Sender, ServiceCtx, Store};
|
||||
|
||||
// DEL <!group> <account>: remove a member. Needs founder or the `f` flag.
|
||||
pub fn handle(me: &str, from: &Sender, name: Option<&str>, target: Option<&str>, ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
|
|
@ -8,16 +8,16 @@ pub fn handle(me: &str, from: &Sender, name: Option<&str>, target: Option<&str>,
|
|||
return;
|
||||
};
|
||||
let Some(g) = db.group(name) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{name}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{name}\x02 isn't registered.", name = name));
|
||||
return;
|
||||
};
|
||||
if !super::can_manage(&g, acc) {
|
||||
ctx.notice(me, from.uid, format!("You need the founder or the \x02f\x02 flag to manage \x02{name}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "You need the founder or the \x02f\x02 flag to manage \x02{name}\x02.", name = name));
|
||||
return;
|
||||
}
|
||||
match db.group_del_member(name, target) {
|
||||
Ok(true) => ctx.notice(me, from.uid, format!("Removed \x02{target}\x02 from \x02{name}\x02.")),
|
||||
Ok(false) => ctx.notice(me, from.uid, format!("\x02{target}\x02 isn't in \x02{name}\x02.")),
|
||||
Ok(true) => ctx.notice(me, from.uid, t!(ctx, "Removed \x02{target}\x02 from \x02{name}\x02.", target = target, name = name)),
|
||||
Ok(false) => ctx.notice(me, from.uid, t!(ctx, "\x02{target}\x02 isn't in \x02{name}\x02.", target = target, name = name)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Sender, ServiceCtx, Store};
|
||||
|
||||
// DROP <!group>: delete a group. Founder only.
|
||||
pub fn handle(me: &str, from: &Sender, name: Option<&str>, ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
|
|
@ -8,15 +8,15 @@ pub fn handle(me: &str, from: &Sender, name: Option<&str>, ctx: &mut ServiceCtx,
|
|||
return;
|
||||
};
|
||||
let Some(g) = db.group(name) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{name}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{name}\x02 isn't registered.", name = name));
|
||||
return;
|
||||
};
|
||||
if !g.founder.eq_ignore_ascii_case(acc) {
|
||||
ctx.notice(me, from.uid, format!("Only \x02{name}\x02's founder can drop it."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Only \x02{name}\x02's founder can drop it.", name = name));
|
||||
return;
|
||||
}
|
||||
match db.group_drop(name) {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("Group \x02{name}\x02 has been dropped.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Group \x02{name}\x02 has been dropped.", name = name)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{GroupFlags, Sender, ServiceCtx, Store, GROUP_FLAGS};
|
||||
use echo_api::{t, GroupFlags, Sender, ServiceCtx, Store, GROUP_FLAGS};
|
||||
|
||||
// FLAGS <!group> [account [+/-flags]]: list, show, or change group-access flags.
|
||||
// Listing/showing is open; changing needs the founder or the `f` flag.
|
||||
|
|
@ -9,15 +9,15 @@ pub fn handle(me: &str, from: &Sender, name: Option<&str>, target: Option<&str>,
|
|||
return;
|
||||
};
|
||||
let Some(g) = db.group(name) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{name}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{name}\x02 isn't registered.", name = name));
|
||||
return;
|
||||
};
|
||||
// List.
|
||||
let Some(target) = target else {
|
||||
ctx.notice(me, from.uid, format!("Flags for \x02{}\x02:", g.name));
|
||||
ctx.notice(me, from.uid, format!(" \x02{}\x02 (founder): \x02F\x02", g.founder));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Flags for \x02{name}\x02:", name = g.name));
|
||||
ctx.notice(me, from.uid, t!(ctx, " \x02{founder}\x02 (founder): \x02F\x02", founder = g.founder));
|
||||
for m in &g.members {
|
||||
ctx.notice(me, from.uid, format!(" \x02{}\x02: \x02{}\x02", m.account, if m.flags.is_empty() { "(member)" } else { &m.flags }));
|
||||
ctx.notice(me, from.uid, t!(ctx, " \x02{account}\x02: \x02{flags}\x02", account = m.account, flags = if m.flags.is_empty() { "(member)" } else { &m.flags }));
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
|
@ -25,30 +25,30 @@ pub fn handle(me: &str, from: &Sender, name: Option<&str>, target: Option<&str>,
|
|||
// Show one.
|
||||
let Some(delta) = delta else {
|
||||
match current {
|
||||
Some(f) => ctx.notice(me, from.uid, format!("\x02{target}\x02 in \x02{name}\x02: \x02{}\x02", if f.is_empty() { "(member)" } else { &f })),
|
||||
None => ctx.notice(me, from.uid, format!("\x02{target}\x02 isn't in \x02{name}\x02.")),
|
||||
Some(f) => ctx.notice(me, from.uid, t!(ctx, "\x02{target}\x02 in \x02{name}\x02: \x02{flags}\x02", target = target, name = name, flags = if f.is_empty() { "(member)" } else { &f })),
|
||||
None => ctx.notice(me, from.uid, t!(ctx, "\x02{target}\x02 isn't in \x02{name}\x02.", target = target, name = name)),
|
||||
}
|
||||
return;
|
||||
};
|
||||
// Change — needs founder or f flag.
|
||||
if !super::can_manage(&g, acc) {
|
||||
ctx.notice(me, from.uid, format!("You need the founder or the \x02f\x02 flag to change \x02{name}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "You need the founder or the \x02f\x02 flag to change \x02{name}\x02.", name = name));
|
||||
return;
|
||||
}
|
||||
let Some(canonical) = db.resolve_account(target).map(str::to_string) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{target}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{target}\x02 isn't registered.", target = target));
|
||||
return;
|
||||
};
|
||||
let updated = match GroupFlags::parse(current.as_deref().unwrap_or("")).apply_delta(delta) {
|
||||
Ok(f) => f,
|
||||
Err(bad) => {
|
||||
ctx.notice(me, from.uid, format!("\x02{bad}\x02 isn't a valid group flag. Valid: \x02{GROUP_FLAGS}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{bad}\x02 isn't a valid group flag. Valid: \x02{valid}\x02.", bad = bad, valid = GROUP_FLAGS));
|
||||
return;
|
||||
}
|
||||
};
|
||||
let letters = updated.to_letters();
|
||||
match db.group_set_flags(name, &canonical, &letters) {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("\x02{canonical}\x02 in \x02{name}\x02 now holds \x02{}\x02.", if letters.is_empty() { "(member)" } else { &letters })),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "\x02{canonical}\x02 in \x02{name}\x02 now holds \x02{flags}\x02.", canonical = canonical, name = name, flags = if letters.is_empty() { "(member)" } else { &letters })),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Sender, ServiceCtx, Store};
|
||||
|
||||
// INFO <!group>: show a group's founder and member count.
|
||||
pub fn handle(me: &str, from: &Sender, name: Option<&str>, ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
|
|
@ -7,10 +7,10 @@ pub fn handle(me: &str, from: &Sender, name: Option<&str>, ctx: &mut ServiceCtx,
|
|||
return;
|
||||
};
|
||||
let Some(g) = db.group(name) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{name}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{name}\x02 isn't registered.", name = name));
|
||||
return;
|
||||
};
|
||||
ctx.notice(me, from.uid, format!("Information for \x02{}\x02:", g.name));
|
||||
ctx.notice(me, from.uid, format!(" Founder : \x02{}\x02", g.founder));
|
||||
ctx.notice(me, from.uid, format!(" Members : {}", g.members.len()));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Information for \x02{name}\x02:", name = g.name));
|
||||
ctx.notice(me, from.uid, t!(ctx, " Founder : \x02{founder}\x02", founder = g.founder));
|
||||
ctx.notice(me, from.uid, t!(ctx, " Members : {count}", count = g.members.len()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
//! `lib.rs` holds the dispatcher and the two shared guards; each command lives
|
||||
//! in its own file.
|
||||
|
||||
use echo_api::{HelpEntry, NetView, Sender, Service, ServiceCtx, Store};
|
||||
use echo_api::{t, HelpEntry, NetView, Sender, Service, ServiceCtx, Store};
|
||||
|
||||
#[path = "register.rs"]
|
||||
mod register;
|
||||
|
|
@ -67,7 +67,7 @@ impl Service for GroupServ {
|
|||
Some("DEL") => del::handle(me, from, args.get(1).copied(), args.get(2).copied(), ctx, db),
|
||||
Some("FLAGS") => flags::handle(me, from, args.get(1).copied(), args.get(2).copied(), args.get(3).copied(), ctx, db),
|
||||
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 \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::{Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Sender, ServiceCtx, Store};
|
||||
|
||||
// LIST: operators see every group; others see the ones they belong to.
|
||||
pub fn handle(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
|
|
@ -14,7 +14,7 @@ pub fn handle(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &mut dyn Store)
|
|||
return;
|
||||
}
|
||||
for n in &names {
|
||||
ctx.notice(me, from.uid, format!(" \x02{n}\x02"));
|
||||
ctx.notice(me, from.uid, t!(ctx, " \x02{n}\x02", n = n));
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("End of list ({} group(s)).", names.len()));
|
||||
ctx.notice(me, from.uid, t!(ctx, "End of list ({count} group(s)).", count = names.len()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Sender, ServiceCtx, Store};
|
||||
|
||||
// REGISTER <!group>: create a group with you as its founder.
|
||||
pub fn handle(me: &str, from: &Sender, name: Option<&str>, ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
|
|
@ -29,13 +29,13 @@ pub fn handle(me: &str, from: &Sender, name: Option<&str>, ctx: &mut ServiceCtx,
|
|||
// (mirrors NickServ's grouped-nick and AJOIN caps).
|
||||
const MAX_GROUPS: usize = 25;
|
||||
if db.groups_founded(acc) >= MAX_GROUPS {
|
||||
ctx.notice(me, from.uid, format!("You've reached the maximum of {MAX_GROUPS} registered groups."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "You've reached the maximum of {max} registered groups.", max = MAX_GROUPS));
|
||||
return;
|
||||
}
|
||||
let acc = acc.to_string();
|
||||
match db.group_register(name, &acc) {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("Group \x02{name}\x02 registered — you're the founder.")),
|
||||
Err(echo_api::ChanError::Exists) => ctx.notice(me, from.uid, format!("\x02{name}\x02 is already registered.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Group \x02{name}\x02 registered — you're the founder.", name = name)),
|
||||
Err(echo_api::ChanError::Exists) => ctx.notice(me, from.uid, t!(ctx, "\x02{name}\x02 is already registered.", name = name)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Sender, ServiceCtx, Store};
|
||||
|
||||
// CANCEL: withdraw your own newest open ticket.
|
||||
pub fn handle(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
|
|
@ -15,7 +15,7 @@ pub fn handle(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &mut dyn Store)
|
|||
match mine {
|
||||
Some(t) => {
|
||||
db.help_close(t.id);
|
||||
ctx.notice(me, from.uid, format!("Your request \x02#{}\x02 has been cancelled.", t.id));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Your request \x02#{id}\x02 has been cancelled.", id = t.id));
|
||||
}
|
||||
None => ctx.notice(me, from.uid, "You have no open request to cancel."),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Sender, ServiceCtx, Store};
|
||||
|
||||
// CLOSE <id> (aka RESOLVE): resolve a ticket.
|
||||
pub fn handle(me: &str, from: &Sender, id: Option<&str>, ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
|
|
@ -10,8 +10,8 @@ pub fn handle(me: &str, from: &Sender, id: Option<&str>, ctx: &mut ServiceCtx, d
|
|||
return;
|
||||
};
|
||||
if db.help_close(n) {
|
||||
ctx.notice(me, from.uid, format!("Ticket \x02#{n}\x02 closed."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Ticket \x02#{id}\x02 closed.", id = n));
|
||||
} else {
|
||||
ctx.notice(me, from.uid, format!("Ticket \x02#{n}\x02 isn't open (or doesn't exist)."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Ticket \x02#{id}\x02 isn't open (or doesn't exist).", id = n));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Sender, ServiceCtx, Store};
|
||||
|
||||
// A friendly one-liner (with an example) for each extban, keyed by its ircd name.
|
||||
// The letter and whether it's offered come live from the ircd's CAPAB set, so this
|
||||
|
|
@ -53,8 +53,14 @@ pub fn handle(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &dyn Store) {
|
|||
let line = |ctx: &mut ServiceCtx, e: &echo_api::ExtbanCap| {
|
||||
let letter = e.letter.map_or_else(|| "-".to_string(), |c| c.to_string());
|
||||
match DESC.iter().find(|(n, _)| n.eq_ignore_ascii_case(&e.name)) {
|
||||
Some((_, desc)) => say(ctx, format!(" \x02{}\x02 (\x02{}\x02) — {}", e.name, letter, desc)),
|
||||
None => say(ctx, format!(" \x02{}\x02 (\x02{}\x02)", e.name, letter)),
|
||||
Some((_, desc)) => {
|
||||
let s = t!(ctx, " \x02{name}\x02 (\x02{letter}\x02) — {desc}", name = e.name, letter = letter, desc = desc);
|
||||
say(ctx, s)
|
||||
}
|
||||
None => {
|
||||
let s = t!(ctx, " \x02{name}\x02 (\x02{letter}\x02)", name = e.name, letter = letter);
|
||||
say(ctx, s)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -62,7 +68,8 @@ pub fn handle(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &dyn Store) {
|
|||
// We haven't learned the ircd's set yet — show the reference from DESC.
|
||||
say(ctx, "\x02Extban types\x02 (your network's exact set is confirmed on link):".to_string());
|
||||
for (name, desc) in DESC {
|
||||
say(ctx, format!(" \x02{name}\x02 — {desc}"));
|
||||
let s = t!(ctx, " \x02{name}\x02 — {desc}", name = name, desc = desc);
|
||||
say(ctx, s);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
//! `lib.rs` holds the dispatcher and the shared guard/claim helpers; each
|
||||
//! command lives in its own file.
|
||||
|
||||
use echo_api::{HelpEntry, NetView, Sender, Service, ServiceCtx, Store};
|
||||
use echo_api::{t, HelpEntry, NetView, Sender, Service, ServiceCtx, Store};
|
||||
|
||||
#[path = "request.rs"]
|
||||
mod request;
|
||||
|
|
@ -78,7 +78,7 @@ impl Service for HelpServ {
|
|||
None => {
|
||||
echo_api::help(me, from, ctx, BLURB, TOPICS, other);
|
||||
if other.is_none() {
|
||||
ctx.notice(me, from.uid, format!("For a service's own commands, type \x02HELP <service>\x02. Services: {}.", net.help_services().join(", ")));
|
||||
ctx.notice(me, from.uid, t!(ctx, "For a service's own commands, type \x02HELP <service>\x02. Services: {services}.", services = net.help_services().join(", ")));
|
||||
ctx.notice(me, from.uid, "For the extended ban types you can use, type \x02HELP EXTBANS\x02.");
|
||||
}
|
||||
}
|
||||
|
|
@ -87,7 +87,7 @@ impl Service for HelpServ {
|
|||
// Not a HelpServ command — maybe the name of a service to get help for.
|
||||
Some(_) => match net.service_help(args[0]) {
|
||||
Some((blurb, topics)) => echo_api::help(me, from, ctx, blurb, topics, args.get(1).copied()),
|
||||
None => ctx.notice(me, from.uid, format!("I don't know \x02{}\x02. Try \x02REQUEST\x02 <message>, \x02HELP\x02, or a service name (e.g. \x02NickServ\x02).", args[0])),
|
||||
None => ctx.notice(me, from.uid, t!(ctx, "I don't know \x02{name}\x02. Try \x02REQUEST\x02 <message>, \x02HELP\x02, or a service name (e.g. \x02NickServ\x02).", name = args[0])),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -103,8 +103,8 @@ fn take_id(me: &str, from: &Sender, id: u64, ctx: &mut ServiceCtx, db: &mut dyn
|
|||
let handler = from.account.unwrap_or(from.nick);
|
||||
if db.help_take(id, handler) {
|
||||
let msg = db.help_ticket(id).map(|t| t.message).unwrap_or_default();
|
||||
ctx.notice(me, from.uid, format!("You took ticket \x02#{id}\x02: {msg}"));
|
||||
ctx.notice(me, from.uid, t!(ctx, "You took ticket \x02#{id}\x02: {msg}", id = id, msg = msg));
|
||||
} else {
|
||||
ctx.notice(me, from.uid, format!("Ticket \x02#{id}\x02 isn't open (or doesn't exist)."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Ticket \x02#{id}\x02 isn't open (or doesn't exist).", id = id));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Sender, ServiceCtx, Store};
|
||||
|
||||
// LIST [ALL]: operators list the open queue (or every ticket with ALL).
|
||||
pub fn handle(me: &str, from: &Sender, arg: Option<&str>, ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
|
|
@ -13,12 +13,12 @@ pub fn handle(me: &str, from: &Sender, arg: Option<&str>, ctx: &mut ServiceCtx,
|
|||
}
|
||||
for t in &tickets {
|
||||
let state = match (&t.handler, t.open) {
|
||||
(_, false) => " (closed)".to_string(),
|
||||
(Some(h), true) => format!(" (taken by {h})"),
|
||||
(_, false) => t!(ctx, " (closed)"),
|
||||
(Some(h), true) => t!(ctx, " (taken by {handler})", handler = h),
|
||||
(None, true) => String::new(),
|
||||
};
|
||||
let short: String = t.message.chars().take(60).collect();
|
||||
ctx.notice(me, from.uid, format!("\x02#{}\x02 {} — {}{}", t.id, t.requester, short, state));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02#{id}\x02 {requester} — {short}{state}", id = t.id, requester = t.requester, short = short, state = state));
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("{} ticket(s). \x02VIEW\x02 <id> for detail.", tickets.len()));
|
||||
ctx.notice(me, from.uid, t!(ctx, "{count} ticket(s). \x02VIEW\x02 <id> for detail.", count = tickets.len()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{NetView, Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, NetView, Sender, ServiceCtx, Store};
|
||||
|
||||
// REQUEST <message> (aka HELPME): open a help-desk ticket for the staff.
|
||||
pub fn handle(me: &str, from: &Sender, rest: &[&str], ctx: &mut ServiceCtx, net: &dyn NetView, db: &mut dyn Store) {
|
||||
|
|
@ -11,7 +11,7 @@ pub fn handle(me: &str, from: &Sender, rest: &[&str], ctx: &mut ServiceCtx, net:
|
|||
// Rate-limit on the real host, not the spoofable nick (see REPORT).
|
||||
let cooldown_key = net.host_of(from.uid).unwrap_or(from.uid);
|
||||
match db.help_request(requester, cooldown_key, &message) {
|
||||
Some(id) => ctx.notice(me, from.uid, format!("Thanks — your request (\x02#{id}\x02) is in the queue. A staff member will be with you.")),
|
||||
Some(id) => ctx.notice(me, from.uid, t!(ctx, "Thanks — your request (\x02#{id}\x02) is in the queue. A staff member will be with you.", id = id)),
|
||||
None => ctx.notice(me, from.uid, "You just opened a ticket — please wait a moment before opening another."),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{human_time, Sender, ServiceCtx, Store};
|
||||
use echo_api::{human_time, t, Sender, ServiceCtx, Store};
|
||||
|
||||
// VIEW <id> (aka READ): operators read a ticket in full.
|
||||
pub fn handle(me: &str, from: &Sender, id: Option<&str>, ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
|
|
@ -9,12 +9,12 @@ pub fn handle(me: &str, from: &Sender, id: Option<&str>, ctx: &mut ServiceCtx, d
|
|||
ctx.notice(me, from.uid, "No such ticket. Syntax: VIEW <id>");
|
||||
return;
|
||||
};
|
||||
let state = if !t.open { "closed" } else if t.handler.is_some() { "taken" } else { "open" };
|
||||
ctx.notice(me, from.uid, format!("Ticket \x02#{}\x02 ({state}):", t.id));
|
||||
ctx.notice(me, from.uid, format!(" From : \x02{}\x02", t.requester));
|
||||
let state = if !t.open { t!(ctx, "closed") } else if t.handler.is_some() { t!(ctx, "taken") } else { t!(ctx, "open") };
|
||||
ctx.notice(me, from.uid, t!(ctx, "Ticket \x02#{id}\x02 ({state}):", id = t.id, state = state));
|
||||
ctx.notice(me, from.uid, t!(ctx, " From : \x02{requester}\x02", requester = t.requester));
|
||||
if let Some(h) = &t.handler {
|
||||
ctx.notice(me, from.uid, format!(" Handler : \x02{h}\x02"));
|
||||
ctx.notice(me, from.uid, t!(ctx, " Handler : \x02{handler}\x02", handler = h));
|
||||
}
|
||||
ctx.notice(me, from.uid, format!(" Opened : {}", human_time(t.ts)));
|
||||
ctx.notice(me, from.uid, format!(" Message : {}", t.message));
|
||||
ctx.notice(me, from.uid, t!(ctx, " Opened : {when}", when = human_time(t.ts)));
|
||||
ctx.notice(me, from.uid, t!(ctx, " Message : {msg}", msg = t.message));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{NetView, Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, NetView, Sender, ServiceCtx, Store};
|
||||
|
||||
// ACTIVATE <account> / REJECT <account>: approve a pending vhost request (setting
|
||||
// and applying it) or turn it down. Operators only.
|
||||
|
|
@ -14,30 +14,30 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
let host = match db.take_vhost_request(account) {
|
||||
Ok(Some(h)) => h,
|
||||
Ok(None) => {
|
||||
ctx.notice(me, from.uid, format!("\x02{account}\x02 has no pending vhost request."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{account}\x02 has no pending vhost request.", account = account));
|
||||
return;
|
||||
}
|
||||
Err(_) => {
|
||||
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;
|
||||
}
|
||||
};
|
||||
if !activate {
|
||||
ctx.notice(me, from.uid, format!("Rejected \x02{account}\x02's vhost request."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Rejected \x02{account}\x02's vhost request.", account = account));
|
||||
return;
|
||||
}
|
||||
// Re-check the requested host now (another account may have taken it since).
|
||||
let host = match super::prepare_vhost(&host, account, db) {
|
||||
let host = match super::prepare_vhost(&host, account, db, ctx) {
|
||||
Ok(h) => h,
|
||||
Err(msg) => {
|
||||
ctx.notice(me, from.uid, format!("Can't activate: {msg}"));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Can't activate: {msg}", msg = msg));
|
||||
return;
|
||||
}
|
||||
};
|
||||
// The forbidden list may have grown since the request was filed; a user's
|
||||
// request must still obey it (an operator's own SET is a deliberate override).
|
||||
if db.vhost_is_forbidden(&host) {
|
||||
ctx.notice(me, from.uid, format!("Can't activate: \x02{host}\x02 is on the forbidden list."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Can't activate: \x02{host}\x02 is on the forbidden list.", host = host));
|
||||
return;
|
||||
}
|
||||
match db.set_vhost(account, &host, from.nick, None) {
|
||||
|
|
@ -45,7 +45,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
for uid in net.uids_logged_into(account) {
|
||||
ctx.apply_vhost(&uid, &host);
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("Activated vhost \x02{host}\x02 for \x02{account}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Activated vhost \x02{host}\x02 for \x02{account}\x02.", host = host, account = account));
|
||||
}
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Sender, ServiceCtx, Store};
|
||||
|
||||
// DEFAULT: give yourself the auto-vhost from the network template, with your
|
||||
// account name substituted for $account.
|
||||
|
|
@ -18,7 +18,7 @@ pub fn handle(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &mut dyn Store)
|
|||
return;
|
||||
}
|
||||
let generated = template.replace("$account", &label);
|
||||
let host = match super::prepare_vhost(&generated, account, db) {
|
||||
let host = match super::prepare_vhost(&generated, account, db, ctx) {
|
||||
Ok(h) if !db.vhost_is_forbidden(&h) => h,
|
||||
_ => {
|
||||
ctx.notice(me, from.uid, "Sorry, a vhost couldn't be generated for your account.");
|
||||
|
|
@ -28,7 +28,7 @@ pub fn handle(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &mut dyn Store)
|
|||
match db.set_vhost(account, &host, "template", None) {
|
||||
Ok(()) => {
|
||||
ctx.apply_vhost(from.uid, &host);
|
||||
ctx.notice(me, from.uid, format!("You now have the vhost \x02{host}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "You now have the vhost \x02{host}\x02.", host = host));
|
||||
}
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{NetView, Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, NetView, Sender, ServiceCtx, Store};
|
||||
|
||||
// DEL <account>: remove an account's vhost, restoring the normal host on any
|
||||
// online sessions. Operators only.
|
||||
|
|
@ -27,9 +27,9 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
}
|
||||
}
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("Vhost for \x02{account}\x02 removed."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Vhost for \x02{account}\x02 removed.", account = account));
|
||||
}
|
||||
Ok(false) => ctx.notice(me, from.uid, format!("\x02{account}\x02 has no vhost.")),
|
||||
Err(_) => ctx.notice(me, from.uid, format!("\x02{account}\x02 isn't registered.")),
|
||||
Ok(false) => ctx.notice(me, from.uid, t!(ctx, "\x02{account}\x02 has no vhost.", account = account)),
|
||||
Err(_) => ctx.notice(me, from.uid, t!(ctx, "\x02{account}\x02 isn't registered.", account = account)),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Sender, ServiceCtx, Store};
|
||||
|
||||
// FORBID <pattern>: block user-requested vhosts matching this regex (operators),
|
||||
// e.g. (?i)(oper|admin|staff|services) to stop impersonation.
|
||||
|
|
@ -12,9 +12,9 @@ pub fn add(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: &mu
|
|||
}
|
||||
let pattern = args[1..].join(" ");
|
||||
match db.vhost_forbid_add(&pattern) {
|
||||
Ok(true) => ctx.notice(me, from.uid, format!("Forbidden vhost pattern added: {pattern}")),
|
||||
Ok(true) => ctx.notice(me, from.uid, t!(ctx, "Forbidden vhost pattern added: {pattern}", pattern = pattern)),
|
||||
Ok(false) => ctx.notice(me, from.uid, "That pattern is already forbidden."),
|
||||
Err(_) => ctx.notice(me, from.uid, format!("\x02{pattern}\x02 isn't a valid regular expression.")),
|
||||
Err(_) => ctx.notice(me, from.uid, t!(ctx, "\x02{pattern}\x02 isn't a valid regular expression.", pattern = pattern)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -28,9 +28,9 @@ pub fn list(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &dyn Store) {
|
|||
ctx.notice(me, from.uid, "No vhost patterns are forbidden.");
|
||||
return;
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("Forbidden vhost patterns ({}):", forbidden.len()));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Forbidden vhost patterns ({count}):", count = forbidden.len()));
|
||||
for (i, p) in forbidden.iter().enumerate() {
|
||||
ctx.notice(me, from.uid, format!(" {}. {p}", i + 1));
|
||||
ctx.notice(me, from.uid, t!(ctx, " {n}. {p}", n = i + 1, p = p));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -44,8 +44,8 @@ pub fn del(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: &mu
|
|||
return;
|
||||
};
|
||||
match db.vhost_forbid_del(n) {
|
||||
Ok(Some(pattern)) => ctx.notice(me, from.uid, format!("Removed forbidden pattern: {pattern}")),
|
||||
Ok(None) => ctx.notice(me, from.uid, format!("There's no forbidden pattern #\x02{n}\x02.")),
|
||||
Ok(Some(pattern)) => ctx.notice(me, from.uid, t!(ctx, "Removed forbidden pattern: {pattern}", pattern = pattern)),
|
||||
Ok(None) => ctx.notice(me, from.uid, t!(ctx, "There's no forbidden pattern #\x02{n}\x02.", n = n)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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::{HelpEntry, NetView, Priv, Sender, Service, ServiceCtx, Store};
|
||||
use echo_api::{t, HelpEntry, NetView, Priv, Sender, Service, ServiceCtx, Store};
|
||||
|
||||
#[path = "on.rs"]
|
||||
mod on;
|
||||
|
|
@ -96,7 +96,7 @@ impl Service for HostServ {
|
|||
Some("TEMPLATE") => template::handle(me, from, args, ctx, db),
|
||||
Some("DEFAULT") => default::handle(me, from, ctx, db),
|
||||
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.")),
|
||||
Some(other) => ctx.notice(me, from.uid, t!(ctx, "I don't know the command \x02{other}\x02. Try \x02HELP\x02.", other = other)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -125,13 +125,13 @@ pub(crate) fn normalize_vhost(spec: &str) -> String {
|
|||
|
||||
// Normalise a requested vhost and check it's valid and not already another
|
||||
// account's. Returns the canonical spec to store, or a message to show.
|
||||
pub(crate) fn prepare_vhost(spec: &str, account: &str, db: &dyn Store) -> Result<String, String> {
|
||||
pub(crate) fn prepare_vhost(spec: &str, account: &str, db: &dyn Store, ctx: &ServiceCtx) -> Result<String, String> {
|
||||
let host = normalize_vhost(spec);
|
||||
if !valid_vhost(&host) {
|
||||
return Err(format!("\x02{host}\x02 isn't a valid host (letters, digits, hyphens and dots)."));
|
||||
return Err(t!(ctx, "\x02{host}\x02 isn't a valid host (letters, digits, hyphens and dots).", host = host));
|
||||
}
|
||||
if db.vhost_owner(&host).is_some_and(|owner| !owner.eq_ignore_ascii_case(account)) {
|
||||
return Err(format!("\x02{host}\x02 is already in use. Please choose another."));
|
||||
return Err(t!(ctx, "\x02{host}\x02 is already in use. Please choose another.", host = host));
|
||||
}
|
||||
Ok(host)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Sender, ServiceCtx, Store};
|
||||
|
||||
// LIST: every account with an assigned vhost. Operators only.
|
||||
pub fn handle(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &dyn Store) {
|
||||
|
|
@ -10,9 +10,9 @@ pub fn handle(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &dyn Store) {
|
|||
ctx.notice(me, from.uid, "No vhosts have been assigned.");
|
||||
return;
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("Assigned vhosts ({}):", vhosts.len()));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Assigned vhosts ({count}):", count = vhosts.len()));
|
||||
for v in &vhosts {
|
||||
let temp = if v.expires.is_some() { ", temporary" } else { "" };
|
||||
ctx.notice(me, from.uid, format!(" \x02{}\x02 — {} (by {}{temp})", v.account, v.host, v.setter));
|
||||
let temp = if v.expires.is_some() { t!(ctx, ", temporary") } else { String::new() };
|
||||
ctx.notice(me, from.uid, t!(ctx, " \x02{account}\x02 — {host} (by {setter}{temp})", account = v.account, host = v.host, setter = v.setter, temp = temp));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Sender, ServiceCtx, Store};
|
||||
|
||||
// OFFER <host>: add a vhost to the self-serve menu (operators).
|
||||
pub fn add(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
|
|
@ -10,11 +10,11 @@ pub fn add(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: &mu
|
|||
return;
|
||||
};
|
||||
if !super::valid_vhost(host) {
|
||||
ctx.notice(me, from.uid, format!("\x02{host}\x02 isn't a valid host."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{host}\x02 isn't a valid host.", host = host));
|
||||
return;
|
||||
}
|
||||
match db.vhost_offer_add(host) {
|
||||
Ok(true) => ctx.notice(me, from.uid, format!("\x02{host}\x02 is now on the vhost menu.")),
|
||||
Ok(true) => ctx.notice(me, from.uid, t!(ctx, "\x02{host}\x02 is now on the vhost menu.", host = host)),
|
||||
Ok(false) => ctx.notice(me, from.uid, "That vhost is already on the menu."),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
|
|
@ -27,9 +27,9 @@ pub fn list(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &dyn Store) {
|
|||
ctx.notice(me, from.uid, "No vhosts are on offer.");
|
||||
return;
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("Vhosts on offer ({}):", offers.len()));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Vhosts on offer ({count}):", count = offers.len()));
|
||||
for (i, host) in offers.iter().enumerate() {
|
||||
ctx.notice(me, from.uid, format!(" {}. {host}", i + 1));
|
||||
ctx.notice(me, from.uid, t!(ctx, " {n}. {host}", n = i + 1, host = host));
|
||||
}
|
||||
ctx.notice(me, from.uid, "Take one with \x02TAKE\x02 <number>.");
|
||||
}
|
||||
|
|
@ -44,8 +44,8 @@ pub fn del(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: &mu
|
|||
return;
|
||||
};
|
||||
match db.vhost_offer_del(n) {
|
||||
Ok(Some(host)) => ctx.notice(me, from.uid, format!("Removed \x02{host}\x02 from the menu.")),
|
||||
Ok(None) => ctx.notice(me, from.uid, format!("There's no offer #\x02{n}\x02.")),
|
||||
Ok(Some(host)) => ctx.notice(me, from.uid, t!(ctx, "Removed \x02{host}\x02 from the menu.", host = host)),
|
||||
Ok(None) => ctx.notice(me, from.uid, t!(ctx, "There's no offer #\x02{n}\x02.", n = n)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Sender, ServiceCtx, Store};
|
||||
|
||||
// ON: activate the vhost assigned to your account.
|
||||
pub fn handle(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &dyn Store) {
|
||||
|
|
@ -9,7 +9,7 @@ pub fn handle(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &dyn Store) {
|
|||
match db.vhost(account) {
|
||||
Some(v) => {
|
||||
ctx.apply_vhost(from.uid, &v.host);
|
||||
ctx.notice(me, from.uid, format!("Your vhost \x02{}\x02 is now active.", v.host));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Your vhost \x02{host}\x02 is now active.", host = v.host));
|
||||
}
|
||||
None => ctx.notice(me, from.uid, "You have no vhost assigned."),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Sender, ServiceCtx, Store};
|
||||
|
||||
// REQUEST <host>: ask for a vhost, to be approved by an operator.
|
||||
pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
|
|
@ -10,7 +10,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
ctx.notice(me, from.uid, "Syntax: REQUEST <host>");
|
||||
return;
|
||||
};
|
||||
let host = match super::prepare_vhost(host, account, db) {
|
||||
let host = match super::prepare_vhost(host, account, db, ctx) {
|
||||
Ok(h) => h,
|
||||
Err(msg) => {
|
||||
ctx.notice(me, from.uid, msg);
|
||||
|
|
@ -18,16 +18,16 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
}
|
||||
};
|
||||
if db.vhost_is_forbidden(&host) {
|
||||
ctx.notice(me, from.uid, format!("\x02{host}\x02 isn't allowed here. Please choose another."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{host}\x02 isn't allowed here. Please choose another.", host = host));
|
||||
return;
|
||||
}
|
||||
let wait = db.vhost_request_wait(account);
|
||||
if wait > 0 {
|
||||
ctx.notice(me, from.uid, format!("Please wait \x02{wait}\x02s before requesting another vhost."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Please wait \x02{wait}\x02s before requesting another vhost.", wait = wait));
|
||||
return;
|
||||
}
|
||||
match db.request_vhost(account, &host) {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("Requested vhost \x02{host}\x02 — an operator will review it.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Requested vhost \x02{host}\x02 — an operator will review it.", host = host)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{parse_duration, NetView, Sender, ServiceCtx, Store};
|
||||
use echo_api::{parse_duration, t, NetView, Sender, ServiceCtx, Store};
|
||||
|
||||
// SET <account> <host> [duration]: assign a vhost to an account, applying it at
|
||||
// once to online sessions. An optional duration (e.g. 30d) makes it temporary.
|
||||
|
|
@ -12,10 +12,10 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
return;
|
||||
};
|
||||
if db.account(account).is_none() {
|
||||
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;
|
||||
}
|
||||
let host = match super::prepare_vhost(host, account, db) {
|
||||
let host = match super::prepare_vhost(host, account, db, ctx) {
|
||||
Ok(h) => h,
|
||||
Err(msg) => {
|
||||
ctx.notice(me, from.uid, msg);
|
||||
|
|
@ -28,8 +28,8 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
for uid in net.uids_logged_into(account) {
|
||||
ctx.apply_vhost(&uid, &host);
|
||||
}
|
||||
let when = args.get(3).filter(|_| ttl.is_some()).map(|d| format!(" (expires in {d})")).unwrap_or_default();
|
||||
ctx.notice(me, from.uid, format!("Vhost \x02{host}\x02 assigned to \x02{account}\x02{when}."));
|
||||
let when = args.get(3).filter(|_| ttl.is_some()).map(|d| t!(ctx, " (expires in {d})", d = d)).unwrap_or_default();
|
||||
ctx.notice(me, from.uid, t!(ctx, "Vhost \x02{host}\x02 assigned to \x02{account}\x02{when}.", host = host, account = account, when = when));
|
||||
}
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Sender, ServiceCtx, Store};
|
||||
|
||||
// TAKE <number>: assign yourself the vhost offered at that position on the menu.
|
||||
pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
|
|
@ -11,10 +11,10 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
return;
|
||||
};
|
||||
let Some(offer) = n.checked_sub(1).and_then(|i| db.vhost_offers().into_iter().nth(i)) else {
|
||||
ctx.notice(me, from.uid, format!("There's no offer #\x02{n}\x02. See \x02OFFERLIST\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "There's no offer #\x02{n}\x02. See \x02OFFERLIST\x02.", n = n));
|
||||
return;
|
||||
};
|
||||
let host = match super::prepare_vhost(&offer, account, db) {
|
||||
let host = match super::prepare_vhost(&offer, account, db, ctx) {
|
||||
Ok(h) => h,
|
||||
Err(msg) => {
|
||||
ctx.notice(me, from.uid, msg);
|
||||
|
|
@ -24,13 +24,13 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
// A menu offer can outlive a later FORBID; re-check at grant time like the
|
||||
// request/approve/default paths, or a stale offer bypasses the impersonation guard.
|
||||
if db.vhost_is_forbidden(&host) {
|
||||
ctx.notice(me, from.uid, format!("\x02{host}\x02 isn't allowed here. Please choose another."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{host}\x02 isn't allowed here. Please choose another.", host = host));
|
||||
return;
|
||||
}
|
||||
match db.set_vhost(account, &host, "offer", None) {
|
||||
Ok(()) => {
|
||||
ctx.apply_vhost(from.uid, &host);
|
||||
ctx.notice(me, from.uid, format!("You now have the vhost \x02{host}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "You now have the vhost \x02{host}\x02.", host = host));
|
||||
}
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Sender, ServiceCtx, Store};
|
||||
|
||||
// TEMPLATE [<pattern>]: show the auto-vhost template, or (operators) set it.
|
||||
// Use $account for the requester's sanitised account name, e.g.
|
||||
|
|
@ -6,7 +6,7 @@ use echo_api::{Sender, ServiceCtx, Store};
|
|||
pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
match args.get(1) {
|
||||
None => match db.vhost_template() {
|
||||
Some(t) => ctx.notice(me, from.uid, format!("Auto-vhost template: \x02{t}\x02. Users apply it with \x02DEFAULT\x02.")),
|
||||
Some(tmpl) => ctx.notice(me, from.uid, t!(ctx, "Auto-vhost template: \x02{tmpl}\x02. Users apply it with \x02DEFAULT\x02.", tmpl = tmpl)),
|
||||
None => ctx.notice(me, from.uid, "No auto-vhost template is set."),
|
||||
},
|
||||
Some(&arg) => {
|
||||
|
|
@ -24,7 +24,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
return;
|
||||
}
|
||||
match db.set_vhost_template(Some(template.clone())) {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("Auto-vhost template set to \x02{template}\x02.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Auto-vhost template set to \x02{template}\x02.", template = template)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Sender, ServiceCtx, Store};
|
||||
|
||||
// WAITING: pending vhost requests awaiting approval. Operators only.
|
||||
pub fn handle(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &dyn Store) {
|
||||
|
|
@ -10,9 +10,9 @@ pub fn handle(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &dyn Store) {
|
|||
ctx.notice(me, from.uid, "No vhost requests are waiting.");
|
||||
return;
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("Pending vhost requests ({}):", requests.len()));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Pending vhost requests ({count}):", count = requests.len()));
|
||||
for (account, host) in &requests {
|
||||
ctx.notice(me, from.uid, format!(" \x02{account}\x02 — {host}"));
|
||||
ctx.notice(me, from.uid, t!(ctx, " \x02{account}\x02 — {host}", account = account, host = host));
|
||||
}
|
||||
ctx.notice(me, from.uid, "Approve with \x02ACTIVATE\x02 <account> or turn down with \x02REJECT\x02 <account>.");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{NewsKind, Priv, Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, NewsKind, Priv, Sender, ServiceCtx, Store};
|
||||
|
||||
// DEL/ODEL <number>: remove a bulletin by its listed position. Admin only.
|
||||
pub fn handle(me: &str, from: &Sender, kind: NewsKind, num: Option<&str>, ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
|
|
@ -8,14 +8,14 @@ pub fn handle(me: &str, from: &Sender, kind: NewsKind, num: Option<&str>, ctx: &
|
|||
}
|
||||
let which = if kind == super::OPER { "oper" } else { "public" };
|
||||
let Some(n) = num.and_then(|n| n.parse::<usize>().ok()) else {
|
||||
ctx.notice(me, from.uid, format!("Syntax: {} <number>", if kind == super::OPER { "ODEL" } else { "DEL" }));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Syntax: {cmd} <number>", cmd = if kind == super::OPER { "ODEL" } else { "DEL" }));
|
||||
return;
|
||||
};
|
||||
match db.news(kind).get(n.wrapping_sub(1)) {
|
||||
Some(item) if n >= 1 => {
|
||||
db.news_del(item.id);
|
||||
ctx.notice(me, from.uid, format!("Removed \x02{which}\x02 bulletin \x02{n}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Removed \x02{which}\x02 bulletin \x02{n}\x02.", which = which, n = n));
|
||||
}
|
||||
_ => ctx.notice(me, from.uid, format!("There's no \x02{which}\x02 bulletin \x02{n}\x02.")),
|
||||
_ => ctx.notice(me, from.uid, t!(ctx, "There's no \x02{which}\x02 bulletin \x02{n}\x02.", which = which, n = n)),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
//! and any operator may OLIST. `lib.rs` holds the dispatcher; each command
|
||||
//! (parameterised by bulletin kind) lives in its own file.
|
||||
|
||||
use echo_api::{HelpEntry, NetView, NewsKind, Sender, Service, ServiceCtx, Store};
|
||||
use echo_api::{t, HelpEntry, NetView, NewsKind, Sender, Service, ServiceCtx, Store};
|
||||
|
||||
#[path = "post.rs"]
|
||||
mod post;
|
||||
|
|
@ -64,7 +64,7 @@ impl Service for InfoServ {
|
|||
// Oper bulletins are for operators only.
|
||||
Some("OLIST") => list::handle(me, from, OPER, true, ctx, db),
|
||||
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 \x02{other}\x02. Try \x02LIST\x02 or \x02HELP\x02.")),
|
||||
Some(other) => ctx.notice(me, from.uid, t!(ctx, "I don't know \x02{other}\x02. Try \x02LIST\x02 or \x02HELP\x02.", other = other)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{NewsKind, Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, NewsKind, Sender, ServiceCtx, Store};
|
||||
|
||||
// LIST/OLIST: show the bulletins of a kind. Public is open; oper is oper-only.
|
||||
pub fn handle(me: &str, from: &Sender, kind: NewsKind, oper_only: bool, ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
|
|
@ -9,11 +9,11 @@ pub fn handle(me: &str, from: &Sender, kind: NewsKind, oper_only: bool, ctx: &mu
|
|||
let items = db.news(kind);
|
||||
let which = if kind == super::OPER { "oper" } else { "public" };
|
||||
if items.is_empty() {
|
||||
ctx.notice(me, from.uid, format!("There are no {which} bulletins."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "There are no {which} bulletins.", which = which));
|
||||
return;
|
||||
}
|
||||
for (i, item) in items.iter().enumerate() {
|
||||
ctx.notice(me, from.uid, format!("{}. {} — by {}", i + 1, item.text, item.setter));
|
||||
ctx.notice(me, from.uid, t!(ctx, "{num}. {text} — by {by}", num = i + 1, text = item.text, by = item.setter));
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("End of {which} bulletins ({} shown).", items.len()));
|
||||
ctx.notice(me, from.uid, t!(ctx, "End of {which} bulletins ({count} shown).", which = which, count = items.len()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{NewsKind, Priv, Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, NewsKind, Priv, Sender, ServiceCtx, Store};
|
||||
|
||||
// POST/OPOST <message>: add a bulletin (public or oper). Admin only.
|
||||
pub fn handle(me: &str, from: &Sender, kind: NewsKind, rest: &[&str], ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
|
|
@ -8,11 +8,11 @@ pub fn handle(me: &str, from: &Sender, kind: NewsKind, rest: &[&str], ctx: &mut
|
|||
}
|
||||
let message = rest.join(" ");
|
||||
if message.trim().is_empty() {
|
||||
ctx.notice(me, from.uid, format!("Syntax: {} <message>", if kind == super::OPER { "OPOST" } else { "POST" }));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Syntax: {cmd} <message>", cmd = if kind == super::OPER { "OPOST" } else { "POST" }));
|
||||
return;
|
||||
}
|
||||
let setter = from.account.unwrap_or(from.nick);
|
||||
db.news_add(kind, &message, setter);
|
||||
let which = if kind == super::OPER { "oper" } else { "public" };
|
||||
ctx.notice(me, from.uid, format!("Added a \x02{which}\x02 bulletin."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Added a \x02{which}\x02 bulletin.", which = which));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Sender, ServiceCtx, Store};
|
||||
|
||||
// CANCEL <nick>: recall the last unread memo you sent to <nick>. A memo the
|
||||
// recipient has already read can't be recalled.
|
||||
|
|
@ -8,12 +8,12 @@ pub fn handle(me: &str, from: &Sender, account: &str, args: &[&str], ctx: &mut S
|
|||
return;
|
||||
};
|
||||
let Some(dest) = db.resolve_account(target).map(str::to_string) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{target}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{target}\x02 isn't registered.", target = target));
|
||||
return;
|
||||
};
|
||||
if db.memo_cancel(&dest, account) {
|
||||
ctx.notice(me, from.uid, format!("Your last unread memo to \x02{target}\x02 has been cancelled."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Your last unread memo to \x02{target}\x02 has been cancelled.", target = target));
|
||||
} else {
|
||||
ctx.notice(me, from.uid, format!("You have no unread memo to \x02{target}\x02 to cancel."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "You have no unread memo to \x02{target}\x02 to cancel.", target = target));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{human_time, Sender, ServiceCtx, Store};
|
||||
use echo_api::{human_time, t, Sender, ServiceCtx, Store};
|
||||
|
||||
// CHECK <nick>: has the last memo you sent to <nick> been read yet?
|
||||
pub fn handle(me: &str, from: &Sender, account: &str, args: &[&str], ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
|
|
@ -7,12 +7,12 @@ pub fn handle(me: &str, from: &Sender, account: &str, args: &[&str], ctx: &mut S
|
|||
return;
|
||||
};
|
||||
let Some(dest) = db.resolve_account(target).map(str::to_string) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{target}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{target}\x02 isn't registered.", target = target));
|
||||
return;
|
||||
};
|
||||
match db.memo_check(&dest, account) {
|
||||
Some((true, ts)) => ctx.notice(me, from.uid, format!("Your last memo to \x02{target}\x02 (sent {}) has been \x02read\x02.", human_time(ts))),
|
||||
Some((false, ts)) => ctx.notice(me, from.uid, format!("Your last memo to \x02{target}\x02 (sent {}) has \x02not\x02 been read yet.", human_time(ts))),
|
||||
None => ctx.notice(me, from.uid, format!("You have no memo on record to \x02{target}\x02.")),
|
||||
Some((true, ts)) => ctx.notice(me, from.uid, t!(ctx, "Your last memo to \x02{target}\x02 (sent {when}) has been \x02read\x02.", target = target, when = human_time(ts))),
|
||||
Some((false, ts)) => ctx.notice(me, from.uid, t!(ctx, "Your last memo to \x02{target}\x02 (sent {when}) has \x02not\x02 been read yet.", target = target, when = human_time(ts))),
|
||||
None => ctx.notice(me, from.uid, t!(ctx, "You have no memo on record to \x02{target}\x02.", target = target)),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Sender, ServiceCtx, Store};
|
||||
|
||||
// DEL <num>|ALL: remove a memo (or the whole mailbox).
|
||||
pub fn handle(me: &str, from: &Sender, account: &str, args: &[&str], ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
|
|
@ -9,7 +9,7 @@ pub fn handle(me: &str, from: &Sender, account: &str, args: &[&str], ctx: &mut S
|
|||
for i in (0..count).rev() {
|
||||
db.memo_del(account, i);
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("Deleted all \x02{count}\x02 memo(s)."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Deleted all \x02{count}\x02 memo(s).", count = count));
|
||||
}
|
||||
Some(numstr) => {
|
||||
let Some(n) = numstr.parse::<usize>().ok().filter(|n| *n >= 1) else {
|
||||
|
|
@ -17,9 +17,9 @@ pub fn handle(me: &str, from: &Sender, account: &str, args: &[&str], ctx: &mut S
|
|||
return;
|
||||
};
|
||||
if db.memo_del(account, n - 1) {
|
||||
ctx.notice(me, from.uid, format!("Memo #\x02{n}\x02 deleted."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Memo #\x02{n}\x02 deleted.", n = n));
|
||||
} else {
|
||||
ctx.notice(me, from.uid, format!("You have no memo #\x02{n}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "You have no memo #\x02{n}\x02.", n = n));
|
||||
}
|
||||
}
|
||||
None => ctx.notice(me, from.uid, "Syntax: DEL <num>|ALL"),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Sender, ServiceCtx, Store};
|
||||
|
||||
// IGNORE ADD <nick> | DEL <nick> | LIST: manage your memo-ignore list. Memos
|
||||
// from an ignored account are silently dropped.
|
||||
|
|
@ -14,13 +14,13 @@ pub fn handle(me: &str, from: &Sender, account: &str, args: &[&str], ctx: &mut S
|
|||
// bound — mirrors AJOIN/CERT; unthrottled for identified users otherwise.
|
||||
const MAX_IGNORE: usize = 50;
|
||||
if db.memo_ignores(account).len() >= MAX_IGNORE {
|
||||
ctx.notice(me, from.uid, format!("Your memo-ignore list is full (max {MAX_IGNORE})."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Your memo-ignore list is full (max {max}).", max = MAX_IGNORE));
|
||||
return;
|
||||
}
|
||||
if db.memo_ignore_add(account, &target) {
|
||||
ctx.notice(me, from.uid, format!("Now ignoring memos from \x02{target}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Now ignoring memos from \x02{target}\x02.", target = target));
|
||||
} else {
|
||||
ctx.notice(me, from.uid, format!("You're already ignoring \x02{target}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "You're already ignoring \x02{target}\x02.", target = target));
|
||||
}
|
||||
}
|
||||
Some("DEL") | Some("REMOVE") => {
|
||||
|
|
@ -30,9 +30,9 @@ pub fn handle(me: &str, from: &Sender, account: &str, args: &[&str], ctx: &mut S
|
|||
};
|
||||
let target = db.resolve_account(nick).map(str::to_string).unwrap_or_else(|| nick.to_string());
|
||||
if db.memo_ignore_del(account, &target) {
|
||||
ctx.notice(me, from.uid, format!("No longer ignoring \x02{target}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "No longer ignoring \x02{target}\x02.", target = target));
|
||||
} else {
|
||||
ctx.notice(me, from.uid, format!("You weren't ignoring \x02{target}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "You weren't ignoring \x02{target}\x02.", target = target));
|
||||
}
|
||||
}
|
||||
Some("LIST") | None => {
|
||||
|
|
@ -40,7 +40,7 @@ pub fn handle(me: &str, from: &Sender, account: &str, args: &[&str], ctx: &mut S
|
|||
if list.is_empty() {
|
||||
ctx.notice(me, from.uid, "Your memo-ignore list is empty.");
|
||||
} else {
|
||||
ctx.notice(me, from.uid, format!("You're ignoring memos from: {}", list.join(", ")));
|
||||
ctx.notice(me, from.uid, t!(ctx, "You're ignoring memos from: {names}", names = list.join(", ")));
|
||||
}
|
||||
}
|
||||
_ => ctx.notice(me, from.uid, "Syntax: IGNORE ADD <nick> | DEL <nick> | LIST"),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Sender, ServiceCtx, Store};
|
||||
|
||||
// INFO: a summary of your mailbox (total, unread, capacity).
|
||||
pub fn handle(me: &str, from: &Sender, account: &str, ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
|
|
@ -7,6 +7,6 @@ pub fn handle(me: &str, from: &Sender, account: &str, ctx: &mut ServiceCtx, db:
|
|||
ctx.notice(
|
||||
me,
|
||||
from.uid,
|
||||
format!("You have \x02{total}\x02 memo(s), \x02{unread}\x02 unread, of a maximum \x02{}\x02.", super::MAX_MEMOS),
|
||||
t!(ctx, "You have \x02{total}\x02 memo(s), \x02{unread}\x02 unread, of a maximum \x02{max}\x02.", total = total, unread = unread, max = super::MAX_MEMOS),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
//! account data. `lib.rs` holds the dispatcher; each command lives in its own
|
||||
//! file, matching NickServ/ChanServ.
|
||||
|
||||
use echo_api::{HelpEntry, NetView, Sender, Service, ServiceCtx, Store};
|
||||
use echo_api::{t, HelpEntry, NetView, Sender, Service, ServiceCtx, Store};
|
||||
|
||||
#[path = "send.rs"]
|
||||
mod send;
|
||||
|
|
@ -93,7 +93,7 @@ impl Service for MemoServ {
|
|||
Some("STAFF") => staff::handle(me, from, account, args, ctx, net, db),
|
||||
Some("IGNORE") => ignore::handle(me, from, account, args, ctx, db),
|
||||
Some("SET") => set::handle(me, from, account, args, ctx, db),
|
||||
Some(other) => ctx.notice(me, from.uid, format!("I don't know the command \x02{other}\x02. Try \x02HELP\x02.")),
|
||||
Some(other) => ctx.notice(me, from.uid, t!(ctx, "I don't know the command \x02{other}\x02. Try \x02HELP\x02.", other = other)),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{human_time, Sender, ServiceCtx, Store};
|
||||
use echo_api::{human_time, t, Sender, ServiceCtx, Store};
|
||||
|
||||
// LIST: show every memo with a one-line preview; \x02*\x02 marks unread.
|
||||
pub fn handle(me: &str, from: &Sender, account: &str, ctx: &mut ServiceCtx, db: &dyn Store) {
|
||||
|
|
@ -8,10 +8,10 @@ pub fn handle(me: &str, from: &Sender, account: &str, ctx: &mut ServiceCtx, db:
|
|||
return;
|
||||
}
|
||||
let unread = memos.iter().filter(|m| !m.read).count();
|
||||
ctx.notice(me, from.uid, format!("Your memos ({} total, {unread} new). \x02*\x02 marks unread:", memos.len()));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Your memos ({total} total, {unread} new). \x02*\x02 marks unread:", total = memos.len(), unread = unread));
|
||||
for (i, m) in memos.iter().enumerate() {
|
||||
let flag = if m.read { ' ' } else { '*' };
|
||||
ctx.notice(me, from.uid, format!(" {}{flag} from \x02{}\x02 ({}): {}", i + 1, m.from, human_time(m.ts), preview(&m.text)));
|
||||
ctx.notice(me, from.uid, t!(ctx, " {num}{flag} from \x02{from}\x02 ({when}): {preview}", num = i + 1, flag = flag, from = m.from, when = human_time(m.ts), preview = preview(&m.text)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{human_time, MemoView, Sender, ServiceCtx, Store};
|
||||
use echo_api::{human_time, t, MemoView, Sender, ServiceCtx, Store};
|
||||
|
||||
// READ <num>|NEW|ALL: display memos and mark them read.
|
||||
pub fn handle(me: &str, from: &Sender, account: &str, args: &[&str], ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
|
|
@ -33,7 +33,7 @@ pub fn handle(me: &str, from: &Sender, account: &str, args: &[&str], ctx: &mut S
|
|||
show(me, from, n - 1, &m, ctx);
|
||||
send_receipt(account, &m, db);
|
||||
}
|
||||
None => ctx.notice(me, from.uid, format!("You have no memo #\x02{n}\x02.")),
|
||||
None => ctx.notice(me, from.uid, t!(ctx, "You have no memo #\x02{n}\x02.", n = n)),
|
||||
}
|
||||
}
|
||||
None => ctx.notice(me, from.uid, "Syntax: READ <num>|NEW|ALL"),
|
||||
|
|
@ -41,8 +41,8 @@ pub fn handle(me: &str, from: &Sender, account: &str, args: &[&str], ctx: &mut S
|
|||
}
|
||||
|
||||
fn show(me: &str, from: &Sender, index: usize, m: &MemoView, ctx: &mut ServiceCtx) {
|
||||
ctx.notice(me, from.uid, format!("Memo #\x02{}\x02 from \x02{}\x02 ({}):", index + 1, m.from, human_time(m.ts)));
|
||||
ctx.notice(me, from.uid, format!(" {}", m.text));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Memo #\x02{num}\x02 from \x02{from}\x02 ({when}):", num = index + 1, from = m.from, when = human_time(m.ts)));
|
||||
ctx.notice(me, from.uid, t!(ctx, " {text}", text = m.text));
|
||||
}
|
||||
|
||||
// If this was an unread RSEND memo, memo the sender that it's now been read.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Sender, ServiceCtx, Store};
|
||||
|
||||
use super::MAX_MEMOS;
|
||||
|
||||
|
|
@ -7,28 +7,28 @@ use super::MAX_MEMOS;
|
|||
pub fn handle(me: &str, from: &Sender, account: &str, args: &[&str], ctx: &mut ServiceCtx, db: &mut dyn Store, receipt: bool) {
|
||||
let cmd = if receipt { "RSEND" } else { "SEND" };
|
||||
if args.len() < 3 {
|
||||
ctx.notice(me, from.uid, format!("Syntax: {cmd} <nick> <text>"));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Syntax: {cmd} <nick> <text>", cmd = cmd));
|
||||
return;
|
||||
}
|
||||
let target = args[1];
|
||||
let text = args[2..].join(" ");
|
||||
let Some(dest) = db.resolve_account(target).map(str::to_string) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{target}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{target}\x02 isn't registered.", target = target));
|
||||
return;
|
||||
};
|
||||
let limit = db.memo_limit_of(&dest).unwrap_or(MAX_MEMOS as u32) as usize;
|
||||
if db.memo_list(&dest).len() >= limit {
|
||||
ctx.notice(me, from.uid, format!("\x02{target}\x02's mailbox is full — they'll need to clear some memos first."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{target}\x02's mailbox is full — they'll need to clear some memos first.", target = target));
|
||||
return;
|
||||
}
|
||||
// If the recipient is ignoring the sender, drop it silently — the sender is
|
||||
// told it was sent, so the ignore isn't revealed.
|
||||
if db.memo_is_ignored(&dest, account) {
|
||||
ctx.notice(me, from.uid, format!("Memo sent to \x02{target}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Memo sent to \x02{target}\x02.", target = target));
|
||||
return;
|
||||
}
|
||||
match db.memo_send(&dest, account, &text, receipt) {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("Memo sent to \x02{target}\x02.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Memo sent to \x02{target}\x02.", target = target)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Priv, Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Priv, Sender, ServiceCtx, Store};
|
||||
|
||||
// SENDALL <text>: leave a memo on every registered account. Admin-only, for
|
||||
// network-wide announcements that persist until read.
|
||||
|
|
@ -20,5 +20,5 @@ pub fn handle(me: &str, from: &Sender, account: &str, args: &[&str], ctx: &mut S
|
|||
sent += 1;
|
||||
}
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("Memo sent to \x02{sent}\x02 account(s)."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Memo sent to \x02{sent}\x02 account(s).", sent = sent));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, Sender, ServiceCtx, Store};
|
||||
|
||||
// SET NOTIFY {ON|OFF} | SET LIMIT <n>|NONE: your MemoServ preferences.
|
||||
pub fn handle(me: &str, from: &Sender, account: &str, args: &[&str], ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
|
|
@ -13,7 +13,7 @@ pub fn handle(me: &str, from: &Sender, account: &str, args: &[&str], ctx: &mut S
|
|||
}
|
||||
};
|
||||
db.set_memo_notify(account, on);
|
||||
ctx.notice(me, from.uid, format!("New-memo notification is now \x02{}\x02.", if on { "on" } else { "off" }));
|
||||
ctx.notice(me, from.uid, t!(ctx, "New-memo notification is now \x02{state}\x02.", state = if on { "on" } else { "off" }));
|
||||
}
|
||||
Some("LIMIT") => {
|
||||
let limit = match args.get(2) {
|
||||
|
|
@ -22,15 +22,15 @@ pub fn handle(me: &str, from: &Sender, account: &str, args: &[&str], ctx: &mut S
|
|||
Some(&n) => match n.parse::<u32>() {
|
||||
Ok(v) if v <= super::MAX_MEMOS as u32 => Some(v),
|
||||
_ => {
|
||||
ctx.notice(me, from.uid, format!("The limit must be a number from 0 to {}, or NONE.", super::MAX_MEMOS));
|
||||
ctx.notice(me, from.uid, t!(ctx, "The limit must be a number from 0 to {max}, or NONE.", max = super::MAX_MEMOS));
|
||||
return;
|
||||
}
|
||||
},
|
||||
};
|
||||
db.set_memo_limit(account, limit);
|
||||
match limit {
|
||||
Some(v) => ctx.notice(me, from.uid, format!("Your mailbox limit is now \x02{v}\x02.")),
|
||||
None => ctx.notice(me, from.uid, format!("Your mailbox limit is now the network default (\x02{}\x02).", super::MAX_MEMOS)),
|
||||
Some(v) => ctx.notice(me, from.uid, t!(ctx, "Your mailbox limit is now \x02{v}\x02.", v = v)),
|
||||
None => ctx.notice(me, from.uid, t!(ctx, "Your mailbox limit is now the network default (\x02{max}\x02).", max = super::MAX_MEMOS)),
|
||||
}
|
||||
}
|
||||
_ => ctx.notice(me, from.uid, "Syntax: SET NOTIFY {ON|OFF} | SET LIMIT <n>|NONE"),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use echo_api::{NetView, Priv, Sender, ServiceCtx, Store};
|
||||
use echo_api::{t, NetView, Priv, Sender, ServiceCtx, Store};
|
||||
|
||||
// STAFF <text>: leave a memo on every operator's account. Admin-only, for
|
||||
// notes to the staff team that persist until read. Recipients are the union of
|
||||
|
|
@ -29,5 +29,5 @@ pub fn handle(me: &str, from: &Sender, account: &str, args: &[&str], ctx: &mut S
|
|||
sent += 1;
|
||||
}
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("Memo sent to \x02{sent}\x02 operator(s)."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Memo sent to \x02{sent}\x02 operator(s).", sent = sent));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use echo_api::Store;
|
||||
use echo_api::{Sender, ServiceCtx};
|
||||
use echo_api::t;
|
||||
|
||||
// A sane cap so a runaway list can't bloat an account or flood a user on identify.
|
||||
const MAX_AJOIN: usize = 25;
|
||||
|
|
@ -23,12 +24,12 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
}
|
||||
let key = args.get(3).copied().unwrap_or("");
|
||||
if db.ajoin_list(account).len() >= MAX_AJOIN {
|
||||
ctx.notice(me, from.uid, format!("Your auto-join list is full (max {MAX_AJOIN})."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Your auto-join list is full (max {max}).", max = MAX_AJOIN));
|
||||
return;
|
||||
}
|
||||
match db.ajoin_add(account, channel, key) {
|
||||
Ok(true) => ctx.notice(me, from.uid, format!("Added \x02{channel}\x02 to your auto-join list.")),
|
||||
Ok(false) => ctx.notice(me, from.uid, format!("Updated the key for \x02{channel}\x02 on your auto-join list.")),
|
||||
Ok(true) => ctx.notice(me, from.uid, t!(ctx, "Added \x02{channel}\x02 to your auto-join list.", channel = channel)),
|
||||
Ok(false) => ctx.notice(me, from.uid, t!(ctx, "Updated the key for \x02{channel}\x02 on your auto-join list.", channel = channel)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
@ -38,8 +39,8 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
return;
|
||||
};
|
||||
match db.ajoin_del(account, channel) {
|
||||
Ok(true) => ctx.notice(me, from.uid, format!("Removed \x02{channel}\x02 from your auto-join list.")),
|
||||
Ok(false) => ctx.notice(me, from.uid, format!("\x02{channel}\x02 isn't on your auto-join list.")),
|
||||
Ok(true) => ctx.notice(me, from.uid, t!(ctx, "Removed \x02{channel}\x02 from your auto-join list.", channel = channel)),
|
||||
Ok(false) => ctx.notice(me, from.uid, t!(ctx, "\x02{channel}\x02 isn't on your auto-join list.", channel = channel)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
@ -49,15 +50,15 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
ctx.notice(me, from.uid, "Your auto-join list is empty. Add channels with \x02AJOIN ADD <#channel>\x02.");
|
||||
return;
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("Your auto-join list ({}):", list.len()));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Your auto-join list ({count}):", count = list.len()));
|
||||
for e in &list {
|
||||
if e.key.is_empty() {
|
||||
ctx.notice(me, from.uid, format!(" \x02{}\x02", e.channel));
|
||||
ctx.notice(me, from.uid, t!(ctx, " \x02{channel}\x02", channel = e.channel));
|
||||
} else {
|
||||
ctx.notice(me, from.uid, format!(" \x02{}\x02 (key: {})", e.channel, e.key));
|
||||
ctx.notice(me, from.uid, t!(ctx, " \x02{channel}\x02 (key: {key})", channel = e.channel, key = e.key));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(other) => ctx.notice(me, from.uid, format!("Unknown AJOIN command \x02{other}\x02. Use \x02ADD\x02, \x02DEL\x02 or \x02LIST\x02.")),
|
||||
Some(other) => ctx.notice(me, from.uid, t!(ctx, "Unknown AJOIN command \x02{other}\x02. Use \x02ADD\x02, \x02DEL\x02 or \x02LIST\x02.", other = other)),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use echo_api::{access_role, Sender, ServiceCtx, Store};
|
||||
use echo_api::t;
|
||||
|
||||
// ALIST: list the channels the sender's account founds or has access on.
|
||||
pub fn handle(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &dyn Store) {
|
||||
|
|
@ -21,8 +22,8 @@ pub fn handle(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &dyn Store) {
|
|||
return;
|
||||
}
|
||||
rows.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
ctx.notice(me, from.uid, format!("Channels you have access on ({}):", rows.len()));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Channels you have access on ({count}):", count = rows.len()));
|
||||
for (chan, role) in rows {
|
||||
ctx.notice(me, from.uid, format!(" \x02{chan}\x02 ({role})"));
|
||||
ctx.notice(me, from.uid, t!(ctx, " \x02{chan}\x02 ({role})", chan = chan, role = role));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use echo_api::{CertError, Store};
|
||||
use echo_api::{Sender, ServiceCtx};
|
||||
use echo_api::t;
|
||||
|
||||
// CERT ADD|DEL|LIST [password] [fingerprint]: manage the TLS certificate
|
||||
// fingerprints that may log in to your account via SASL EXTERNAL. An identified
|
||||
|
|
@ -16,7 +17,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
if fps.is_empty() {
|
||||
ctx.notice(me, from.uid, "No certificate fingerprints are registered to your account.");
|
||||
} else {
|
||||
ctx.notice(me, from.uid, format!("Registered fingerprints: {}", fps.join(", ")));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Registered fingerprints: {list}", list = fps.join(", ")));
|
||||
}
|
||||
}
|
||||
Some("ADD") => {
|
||||
|
|
@ -28,7 +29,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
return;
|
||||
};
|
||||
match db.certfp_add(&account, fp) {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("Added fingerprint \x02{fp}\x02. You can now log in with SASL EXTERNAL.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Added fingerprint \x02{fp}\x02. You can now log in with SASL EXTERNAL.", fp = fp)),
|
||||
Err(CertError::InUse) => ctx.notice(me, from.uid, "That fingerprint is already registered."),
|
||||
Err(CertError::Invalid) => ctx.notice(me, from.uid, "That does not look like a certificate fingerprint."),
|
||||
Err(CertError::Full) => ctx.notice(me, from.uid, "You have too many fingerprints registered. Remove one first."),
|
||||
|
|
@ -44,7 +45,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
return;
|
||||
};
|
||||
match db.certfp_del(&account, fp) {
|
||||
Ok(true) => ctx.notice(me, from.uid, format!("Removed fingerprint \x02{fp}\x02.")),
|
||||
Ok(true) => ctx.notice(me, from.uid, t!(ctx, "Removed fingerprint \x02{fp}\x02.", fp = fp)),
|
||||
Ok(false) => ctx.notice(me, from.uid, "No such fingerprint is registered to your account."),
|
||||
Err(_) => ctx.notice(me, from.uid, "Could not remove the fingerprint, please try again later."),
|
||||
}
|
||||
|
|
@ -75,7 +76,7 @@ fn resolve<'a>(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &mut dyn Store
|
|||
// verify can't be spammed to stall the engine.
|
||||
fn verify(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &mut dyn Store, password: &str) -> Option<String> {
|
||||
if let Some(secs) = db.auth_lockout(from.nick) {
|
||||
ctx.notice(me, from.uid, format!("Too many failed attempts. Please wait {secs}s and try again."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Too many failed attempts. Please wait {secs}s and try again.", secs = secs));
|
||||
return None;
|
||||
}
|
||||
match db.authenticate(from.nick, password).map(str::to_string) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use echo_api::{CodeKind, Store};
|
||||
use echo_api::{Sender, ServiceCtx};
|
||||
use echo_api::t;
|
||||
|
||||
// CONFIRM <code>: confirm your account's email with the code you were emailed.
|
||||
pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: &mut dyn Store) {
|
||||
|
|
@ -12,7 +13,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
return;
|
||||
};
|
||||
if db.is_verified(&account) {
|
||||
ctx.notice(me, from.uid, format!("\x02{account}\x02 is already confirmed."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{account}\x02 is already confirmed.", account = account));
|
||||
return;
|
||||
}
|
||||
if !db.take_code(&account, CodeKind::Confirm, code) {
|
||||
|
|
@ -20,7 +21,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
return;
|
||||
}
|
||||
match db.verify_account(&account) {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("\x02{account}\x02 is now confirmed. Thanks!")),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "\x02{account}\x02 is now confirmed. Thanks!", account = account)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use echo_api::Store;
|
||||
use echo_api::{Sender, ServiceCtx};
|
||||
use echo_api::NetView;
|
||||
use echo_api::t;
|
||||
|
||||
// DROP <password>: delete your account. Re-authenticates as confirmation, releases
|
||||
// and drops the channels you found, and logs you out.
|
||||
|
|
@ -16,7 +17,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
// Throttle the confirmation verify like IDENTIFY so the ~1s check can't be
|
||||
// spammed to stall the engine, and record the attempt.
|
||||
if let Some(secs) = db.auth_lockout(account) {
|
||||
ctx.notice(me, from.uid, format!("Too many failed attempts. Please wait {secs}s and try again."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Too many failed attempts. Please wait {secs}s and try again.", secs = secs));
|
||||
return;
|
||||
}
|
||||
if db.authenticate(account, password).is_none() {
|
||||
|
|
@ -34,12 +35,12 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
for uid in net.uids_logged_into(account) {
|
||||
ctx.logout(&uid);
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("Your account \x02{account}\x02 has been dropped."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Your account \x02{account}\x02 has been dropped.", account = account));
|
||||
if !dropped.is_empty() {
|
||||
ctx.notice(me, from.uid, format!("Channels released: {}.", dropped.join(", ")));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Channels released: {list}.", list = dropped.join(", ")));
|
||||
}
|
||||
if !inherited.is_empty() {
|
||||
let list: Vec<String> = inherited.iter().map(|(c, s)| format!("{c} -> {s}")).collect();
|
||||
ctx.notice(me, from.uid, format!("Channels passed to their successor: {}.", list.join(", ")));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Channels passed to their successor: {list}.", list = list.join(", ")));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use echo_api::{Priv, Sender, ServiceCtx, Store};
|
||||
use echo_api::t;
|
||||
|
||||
// GETEMAIL <email>: list accounts registered with a matching email. Oper-only.
|
||||
pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: &dyn Store) {
|
||||
|
|
@ -12,8 +13,8 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
};
|
||||
let accounts = db.accounts_by_email(pattern);
|
||||
if accounts.is_empty() {
|
||||
ctx.notice(me, from.uid, format!("No accounts use an email matching \x02{pattern}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "No accounts use an email matching \x02{pattern}\x02.", pattern = pattern));
|
||||
return;
|
||||
}
|
||||
ctx.notice(me, from.uid, format!("Accounts with email matching \x02{pattern}\x02: {}", accounts.join(", ")));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Accounts with email matching \x02{pattern}\x02: {list}", pattern = pattern, list = accounts.join(", ")));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use echo_api::Store;
|
||||
use echo_api::{Sender, ServiceCtx};
|
||||
use echo_api::NetView;
|
||||
use echo_api::t;
|
||||
|
||||
// GHOST/RECOVER <nick> [password]: rename off a session using a nick you own,
|
||||
// either by being identified to its account or giving that account's password.
|
||||
|
|
@ -11,11 +12,11 @@ use echo_api::NetView;
|
|||
pub fn handle(me: &str, guest_nick: &str, guest_seq: &mut u32, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net: &dyn NetView, db: &mut dyn Store, regain: bool) {
|
||||
let cmd = if regain { "RECOVER" } else { "GHOST" };
|
||||
let Some(&target) = args.get(1) else {
|
||||
ctx.notice(me, from.uid, format!("Syntax: {cmd} <nick> [password]"));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Syntax: {cmd} <nick> [password]", cmd = cmd));
|
||||
return;
|
||||
};
|
||||
let Some(account) = db.resolve_account(target).map(str::to_string) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{target}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{target}\x02 isn't registered.", target = target));
|
||||
return;
|
||||
};
|
||||
// Ownership by an identified session, or by password — throttled and recorded
|
||||
|
|
@ -25,7 +26,7 @@ pub fn handle(me: &str, guest_nick: &str, guest_seq: &mut u32, from: &Sender, ar
|
|||
true
|
||||
} else if let Some(&pw) = args.get(2) {
|
||||
if let Some(secs) = db.auth_lockout(&account) {
|
||||
ctx.notice(me, from.uid, format!("Too many failed attempts. Please wait {secs}s and try again."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Too many failed attempts. Please wait {secs}s and try again.", secs = secs));
|
||||
return;
|
||||
}
|
||||
let ok = db.authenticate(&account, pw).is_some();
|
||||
|
|
@ -38,7 +39,7 @@ pub fn handle(me: &str, guest_nick: &str, guest_seq: &mut u32, from: &Sender, ar
|
|||
false
|
||||
};
|
||||
if !owns {
|
||||
ctx.notice(me, from.uid, format!("Access denied. Identify to \x02{account}\x02 or give its password."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Access denied. Identify to \x02{account}\x02 or give its password.", account = account));
|
||||
return;
|
||||
}
|
||||
// Free the nick if someone else is holding it.
|
||||
|
|
@ -50,10 +51,10 @@ pub fn handle(me: &str, guest_nick: &str, guest_seq: &mut u32, from: &Sender, ar
|
|||
Some(ghost) => {
|
||||
let guest = echo_api::next_guest_nick(guest_nick, guest_seq, net, &*db);
|
||||
ctx.force_nick(&ghost, &guest);
|
||||
ctx.notice(me, from.uid, format!("\x02{target}\x02 has been freed."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{target}\x02 has been freed.", target = target));
|
||||
}
|
||||
None if !regain => {
|
||||
ctx.notice(me, from.uid, format!("Nobody is using \x02{target}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Nobody is using \x02{target}\x02.", target = target));
|
||||
return;
|
||||
}
|
||||
None => {} // already free — RECOVER will simply regain it
|
||||
|
|
@ -61,6 +62,6 @@ pub fn handle(me: &str, guest_nick: &str, guest_seq: &mut u32, from: &Sender, ar
|
|||
// RECOVER puts the caller back onto the nick (queued after any guest-rename).
|
||||
if regain {
|
||||
ctx.force_nick(from.uid, target);
|
||||
ctx.notice(me, from.uid, format!("You have regained \x02{target}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "You have regained \x02{target}\x02.", target = target));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use echo_api::Store;
|
||||
use echo_api::{Sender, ServiceCtx};
|
||||
use echo_api::t;
|
||||
|
||||
// GLIST: list the nicks grouped to your account.
|
||||
pub fn handle(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &dyn Store) {
|
||||
|
|
@ -9,9 +10,9 @@ pub fn handle(me: &str, from: &Sender, ctx: &mut ServiceCtx, db: &dyn Store) {
|
|||
};
|
||||
let mut nicks = db.grouped_nicks(account);
|
||||
nicks.sort();
|
||||
ctx.notice(me, from.uid, format!("Nicks grouped to \x02{account}\x02:"));
|
||||
ctx.notice(me, from.uid, format!(" \x02{account}\x02 (main)"));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Nicks grouped to \x02{account}\x02:", account = account));
|
||||
ctx.notice(me, from.uid, t!(ctx, " \x02{account}\x02 (main)", account = account));
|
||||
for n in nicks {
|
||||
ctx.notice(me, from.uid, format!(" \x02{n}\x02"));
|
||||
ctx.notice(me, from.uid, t!(ctx, " \x02{n}\x02", n = n));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use echo_api::Store;
|
||||
use echo_api::{Sender, ServiceCtx};
|
||||
use echo_api::t;
|
||||
|
||||
// GROUP <account> <password>: link your current nick to an existing account, so
|
||||
// you can identify to it under this nick too.
|
||||
|
|
@ -12,7 +13,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
// password-guessing oracle against any account (and each ~1s verify blocks the
|
||||
// engine). Also feeds the auth audit feed.
|
||||
if let Some(secs) = db.auth_lockout(account) {
|
||||
ctx.notice(me, from.uid, format!("Too many failed attempts. Please wait {secs}s and try again."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Too many failed attempts. Please wait {secs}s and try again.", secs = secs));
|
||||
return;
|
||||
}
|
||||
let Some(canonical) = db.authenticate(account, password).map(str::to_string) else {
|
||||
|
|
@ -23,7 +24,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
};
|
||||
db.note_auth(account, true);
|
||||
if db.account(from.nick).is_some() {
|
||||
ctx.notice(me, from.uid, format!("\x02{}\x02 is itself a registered account.", from.nick));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{nick}\x02 is itself a registered account.", nick = from.nick));
|
||||
return;
|
||||
}
|
||||
// Guard the namespace like REGISTER does: grouping RESERVES from.nick (SET KILL
|
||||
|
|
@ -36,16 +37,16 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
}
|
||||
}
|
||||
if db.is_forbidden(echo_api::ForbidKind::Nick, from.nick).is_some() {
|
||||
ctx.notice(me, from.uid, format!("The nick \x02{}\x02 is reserved and can't be grouped.", from.nick));
|
||||
ctx.notice(me, from.uid, t!(ctx, "The nick \x02{nick}\x02 is reserved and can't be grouped.", nick = from.nick));
|
||||
return;
|
||||
}
|
||||
const MAX_GROUPED: usize = 25;
|
||||
if db.grouped_nicks(&canonical).len() >= MAX_GROUPED {
|
||||
ctx.notice(me, from.uid, format!("\x02{canonical}\x02 already has the maximum of {MAX_GROUPED} grouped nicks."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{canonical}\x02 already has the maximum of {max} grouped nicks.", canonical = canonical, max = MAX_GROUPED));
|
||||
return;
|
||||
}
|
||||
match db.group_nick(from.nick, &canonical) {
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("Your nick \x02{}\x02 is now grouped to \x02{canonical}\x02.", from.nick)),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Your nick \x02{nick}\x02 is now grouped to \x02{canonical}\x02.", nick = from.nick, canonical = canonical)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use echo_api::{human_time, AuthThen, Store};
|
||||
use echo_api::{Sender, ServiceCtx};
|
||||
use echo_api::t;
|
||||
|
||||
// IDENTIFY [account] <password>: log in. The account defaults to the current
|
||||
// nick, so both the bare-password and account+password forms work.
|
||||
|
|
@ -14,7 +15,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
};
|
||||
// Distinguish an unregistered account from a wrong password.
|
||||
if !db.exists(account_name) {
|
||||
ctx.fail(me, from.uid, "IDENTIFY", "ACCOUNT_NOT_REGISTERED", format!("\x02{account_name}\x02 isn't registered."));
|
||||
ctx.fail(me, from.uid, "IDENTIFY", "ACCOUNT_NOT_REGISTERED", t!(ctx, "\x02{account_name}\x02 isn't registered.", account_name = account_name));
|
||||
return;
|
||||
}
|
||||
// A suspended account can't be logged into (checked before the password so it
|
||||
|
|
@ -23,16 +24,16 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
if let Some(acc) = db.resolve_account(account_name).map(str::to_string) {
|
||||
if db.is_suspended(&acc) {
|
||||
if let Some(s) = db.suspension(&acc) {
|
||||
let mut msg = format!("\x02{acc}\x02 is suspended, so you can't log in to it right now. It was suspended by \x02{}\x02 on {}", s.by, human_time(s.ts));
|
||||
let mut msg = t!(ctx, "\x02{acc}\x02 is suspended, so you can't log in to it right now. It was suspended by \x02{by}\x02 on {when}", acc = acc, by = s.by, when = human_time(s.ts));
|
||||
if s.reason.trim().is_empty() {
|
||||
msg.push('.');
|
||||
} else {
|
||||
msg.push_str(&format!(" — reason: {}", s.reason));
|
||||
msg.push_str(&t!(ctx, " — reason: {reason}", reason = s.reason));
|
||||
}
|
||||
if let Some(exp) = s.expires {
|
||||
msg.push_str(&format!(" The suspension is due to lift on {}.", human_time(exp)));
|
||||
msg.push_str(&t!(ctx, " The suspension is due to lift on {when}.", when = human_time(exp)));
|
||||
}
|
||||
msg.push_str(" If you think this is a mistake, please contact the network staff.");
|
||||
msg.push_str(&t!(ctx, " If you think this is a mistake, please contact the network staff."));
|
||||
ctx.fail(me, from.uid, "IDENTIFY", "ACCOUNT_SUSPENDED", msg);
|
||||
}
|
||||
return;
|
||||
|
|
@ -40,7 +41,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
}
|
||||
// Refuse while throttled, so a password can't be brute-forced.
|
||||
if let Some(secs) = db.auth_lockout(account_name) {
|
||||
ctx.fail(me, from.uid, "IDENTIFY", "RATE_LIMITED", format!("Too many failed attempts. Please wait {secs}s and try again."));
|
||||
ctx.fail(me, from.uid, "IDENTIFY", "RATE_LIMITED", t!(ctx, "Too many failed attempts. Please wait {secs}s and try again.", secs = secs));
|
||||
return;
|
||||
}
|
||||
// Fetch the verifier cheaply and hand the (~1s) PBKDF2 verify to the engine to
|
||||
|
|
@ -59,7 +60,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
Some((account, verifier)) => {
|
||||
// Already identified to this account: skip the (wasted) verify.
|
||||
if from.account == Some(account.as_str()) {
|
||||
ctx.notice(me, from.uid, format!("You're already identified as \x02{}\x02.", account));
|
||||
ctx.notice(me, from.uid, t!(ctx, "You're already identified as \x02{account}\x02.", account = account));
|
||||
return;
|
||||
}
|
||||
ctx.defer_authenticate(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use echo_api::{human_time, NetView, Priv, Store};
|
||||
use echo_api::{Sender, ServiceCtx};
|
||||
use echo_api::t;
|
||||
|
||||
// INFO [account]: show an account's registration details. The email and other
|
||||
// private fields are shown to the account's own owner, or to an oper with the
|
||||
|
|
@ -7,13 +8,13 @@ use echo_api::{Sender, ServiceCtx};
|
|||
pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net: &dyn NetView, db: &dyn Store) {
|
||||
let name = args.get(1).copied().unwrap_or(from.nick);
|
||||
let Some(acct) = db.account(name) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{name}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{name}\x02 isn't registered.", name = name));
|
||||
return;
|
||||
};
|
||||
let is_owner = from.account == Some(acct.name.as_str());
|
||||
let privileged = is_owner || from.privs.has(Priv::Auspex);
|
||||
ctx.notice(me, from.uid, format!("Information for \x02{}\x02:", acct.name));
|
||||
ctx.notice(me, from.uid, format!(" Registered : {}", human_time(acct.ts)));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Information for \x02{name}\x02:", name = acct.name));
|
||||
ctx.notice(me, from.uid, t!(ctx, " Registered : {when}", when = human_time(acct.ts)));
|
||||
// Last-seen is public by default; SET HIDE STATUS keeps it to owner and opers.
|
||||
if privileged || !acct.hide_status {
|
||||
let last_seen = if net.uids_logged_into(&acct.name).is_empty() {
|
||||
|
|
@ -21,30 +22,30 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
} else {
|
||||
"now (online)".to_string()
|
||||
};
|
||||
ctx.notice(me, from.uid, format!(" Last seen : {last_seen}"));
|
||||
ctx.notice(me, from.uid, t!(ctx, " Last seen : {last_seen}", last_seen = last_seen));
|
||||
}
|
||||
// A greet is public — the bot shows it in-channel to everyone anyway.
|
||||
if !acct.greet.is_empty() {
|
||||
ctx.notice(me, from.uid, format!(" Greet : {}", acct.greet));
|
||||
ctx.notice(me, from.uid, t!(ctx, " Greet : {greet}", greet = acct.greet));
|
||||
}
|
||||
if privileged {
|
||||
if let Some(s) = db.suspension(&acct.name) {
|
||||
ctx.notice(me, from.uid, format!(" Suspended : by \x02{}\x02 — {}", s.by, s.reason));
|
||||
ctx.notice(me, from.uid, t!(ctx, " Suspended : by \x02{by}\x02 — {reason}", by = s.by, reason = s.reason));
|
||||
}
|
||||
match &acct.email {
|
||||
Some(email) if acct.verified => ctx.notice(me, from.uid, format!(" Email : {email}")),
|
||||
Some(email) => ctx.notice(me, from.uid, format!(" Email : {email} (unconfirmed)")),
|
||||
Some(email) if acct.verified => ctx.notice(me, from.uid, t!(ctx, " Email : {email}", email = email)),
|
||||
Some(email) => ctx.notice(me, from.uid, t!(ctx, " Email : {email} (unconfirmed)", email = email)),
|
||||
None => ctx.notice(me, from.uid, " Email : (none set)"),
|
||||
}
|
||||
let ajoin = db.ajoin_list(&acct.name);
|
||||
if !ajoin.is_empty() {
|
||||
ctx.notice(me, from.uid, format!(" Auto-join : {} channel(s) — see \x02AJOIN LIST\x02", ajoin.len()));
|
||||
ctx.notice(me, from.uid, t!(ctx, " Auto-join : {count} channel(s) — see \x02AJOIN LIST\x02", count = ajoin.len()));
|
||||
}
|
||||
}
|
||||
// A staff note is for operators' eyes only, never the account's owner.
|
||||
if from.privs.has(Priv::Auspex) {
|
||||
if let Some(note) = db.account_note(&acct.name) {
|
||||
ctx.notice(me, from.uid, format!(" Staff note : {note}"));
|
||||
ctx.notice(me, from.uid, t!(ctx, " Staff note : {note}", note = note));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ impl Service for NickServ {
|
|||
Some("LIST") => list::handle(me, from, args, ctx, db),
|
||||
Some("GETEMAIL") => getemail::handle(me, from, args, ctx, db),
|
||||
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.")),
|
||||
Some(other) => ctx.notice(me, from.uid, echo_api::t!(ctx, "I don't know the command \x02{other}\x02. Try \x02HELP\x02.", other = other)),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use echo_api::{human_time, Priv, Sender, ServiceCtx, Store};
|
||||
use echo_api::t;
|
||||
|
||||
// A cap so a broad glob can't flood the requesting oper.
|
||||
const MAX_SHOWN: usize = 100;
|
||||
|
|
@ -13,11 +14,11 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
let mut matches = db.accounts_matching(pattern);
|
||||
matches.sort_by_key(|a| a.name.to_ascii_lowercase());
|
||||
|
||||
ctx.notice(me, from.uid, format!("Accounts matching \x02{pattern}\x02:"));
|
||||
ctx.notice(me, from.uid, t!(ctx, "Accounts matching \x02{pattern}\x02:", pattern = pattern));
|
||||
for a in matches.iter().take(MAX_SHOWN) {
|
||||
let flag = if a.verified { "" } else { " (unconfirmed)" };
|
||||
ctx.notice(me, from.uid, format!(" \x02{}\x02 registered {}{}", a.name, human_time(a.ts), flag));
|
||||
ctx.notice(me, from.uid, t!(ctx, " \x02{name}\x02 registered {when}{flag}", name = a.name, when = human_time(a.ts), flag = flag));
|
||||
}
|
||||
let more = if matches.len() > MAX_SHOWN { format!(", showing the first {MAX_SHOWN}") } else { String::new() };
|
||||
ctx.notice(me, from.uid, format!("End of list — {} match(es){more}.", matches.len()));
|
||||
let more = if matches.len() > MAX_SHOWN { t!(ctx, ", showing the first {max}", max = MAX_SHOWN) } else { String::new() };
|
||||
ctx.notice(me, from.uid, t!(ctx, "End of list — {count} match(es){more}.", count = matches.len(), more = more));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use echo_api::{NetView, Sender, ServiceCtx, Store};
|
||||
use echo_api::t;
|
||||
|
||||
// LOGOUT: log out and rename to a guest nick (prefix + a per-logout sequence).
|
||||
pub fn handle(me: &str, guest_nick: &str, guest_seq: &mut u32, from: &Sender, ctx: &mut ServiceCtx, net: &dyn NetView, db: &dyn Store) {
|
||||
|
|
@ -9,5 +10,5 @@ pub fn handle(me: &str, guest_nick: &str, guest_seq: &mut u32, from: &Sender, ct
|
|||
let guest = echo_api::next_guest_nick(guest_nick, guest_seq, net, db);
|
||||
ctx.logout(from.uid);
|
||||
ctx.force_nick(from.uid, &guest);
|
||||
ctx.notice(me, from.uid, format!("You're now logged out. Your nick is now \x02{}\x02.", guest));
|
||||
ctx.notice(me, from.uid, t!(ctx, "You're now logged out. Your nick is now \x02{guest}\x02.", guest = guest));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use echo_api::{Priv, Sender, ServiceCtx, Store};
|
||||
use echo_api::t;
|
||||
|
||||
// NOEXPIRE <account> {ON|OFF}: pin an account so inactivity-expiry never drops
|
||||
// it (or lift the pin). Oper-only (Priv::Admin) — protecting a record from
|
||||
|
|
@ -13,13 +14,13 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
return;
|
||||
};
|
||||
let Some(account) = db.resolve_account(target).map(str::to_string) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{target}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{target}\x02 isn't registered.", target = target));
|
||||
return;
|
||||
};
|
||||
match db.set_account_noexpire(&account, on) {
|
||||
Ok(true) if on => ctx.notice(me, from.uid, format!("\x02{account}\x02 will no longer expire.")),
|
||||
Ok(true) => ctx.notice(me, from.uid, format!("\x02{account}\x02 can expire from inactivity again.")),
|
||||
Ok(false) => ctx.notice(me, from.uid, format!("\x02{account}\x02 was already set that way.")),
|
||||
Ok(true) if on => ctx.notice(me, from.uid, t!(ctx, "\x02{account}\x02 will no longer expire.", account = account)),
|
||||
Ok(true) => ctx.notice(me, from.uid, t!(ctx, "\x02{account}\x02 can expire from inactivity again.", account = account)),
|
||||
Ok(false) => ctx.notice(me, from.uid, t!(ctx, "\x02{account}\x02 was already set that way.", account = account)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use echo_api::{CodeKind, Store};
|
||||
use echo_api::{Sender, ServiceCtx};
|
||||
use echo_api::t;
|
||||
|
||||
// RESETPASS <account>: email a reset code to the address on file.
|
||||
// RESETPASS <account> <code> <newpassword>: complete the reset with that code.
|
||||
|
|
@ -11,7 +12,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
match (args.get(1), args.get(2), args.get(3)) {
|
||||
(Some(&name), None, None) => {
|
||||
let Some(canonical) = db.resolve_account(name).map(str::to_string) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{name}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{name}\x02 isn't registered.", name = name));
|
||||
return;
|
||||
};
|
||||
let Some(email) = db.account(&canonical).and_then(|a| a.email.clone()) else {
|
||||
|
|
@ -20,17 +21,17 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
};
|
||||
let wait = db.code_issue_wait(&canonical);
|
||||
if wait > 0 {
|
||||
ctx.notice(me, from.uid, format!("A code was just sent — please wait \x02{wait}\x02s before requesting another."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "A code was just sent — please wait \x02{wait}\x02s before requesting another.", wait = wait));
|
||||
return;
|
||||
}
|
||||
let code = db.issue_code(&canonical, CodeKind::Reset);
|
||||
let mail = echo_api::email::reset(db.email_brand(), db.email_accent(), db.email_logo(), &canonical, &code);
|
||||
ctx.send_email(email, mail.subject, mail.text, Some(mail.html));
|
||||
ctx.notice(me, from.uid, format!("A reset code has been emailed to the address on file for \x02{canonical}\x02."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "A reset code has been emailed to the address on file for \x02{canonical}\x02.", canonical = canonical));
|
||||
}
|
||||
(Some(&name), Some(&code), Some(&newpass)) => {
|
||||
let Some(canonical) = db.resolve_account(name).map(str::to_string) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{name}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{name}\x02 isn't registered.", name = name));
|
||||
return;
|
||||
};
|
||||
if !db.take_code(&canonical, CodeKind::Reset, code) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use echo_api::{Priv, Sender, ServiceCtx, Store};
|
||||
use echo_api::t;
|
||||
|
||||
// SASET <account> <option> [value]: the operator counterpart of SET, editing
|
||||
// another account's settings. Oper-only (Priv::Admin). Mirrors the self-service
|
||||
|
|
@ -13,7 +14,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
return;
|
||||
};
|
||||
let Some(account) = db.resolve_account(target).map(str::to_string) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{target}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{target}\x02 isn't registered.", target = target));
|
||||
return;
|
||||
};
|
||||
// Credential/identity fields are owned by the website in external mode.
|
||||
|
|
@ -38,8 +39,8 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
let email = args.get(3).map(|s| s.to_string());
|
||||
let cleared = email.is_none();
|
||||
match db.set_email(&account, email) {
|
||||
Ok(()) if cleared => ctx.notice(me, from.uid, format!("Email for \x02{account}\x02 cleared.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("Email for \x02{account}\x02 updated.")),
|
||||
Ok(()) if cleared => ctx.notice(me, from.uid, t!(ctx, "Email for \x02{account}\x02 cleared.", account = account)),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Email for \x02{account}\x02 updated.", account = account)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
@ -47,8 +48,8 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
let greet = if args.len() > 3 { args[3..].join(" ") } else { String::new() };
|
||||
let cleared = greet.is_empty();
|
||||
match db.set_greet(&account, &greet) {
|
||||
Ok(()) if cleared => ctx.notice(me, from.uid, format!("Greet for \x02{account}\x02 cleared.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("Greet for \x02{account}\x02 is now: {greet}")),
|
||||
Ok(()) if cleared => ctx.notice(me, from.uid, t!(ctx, "Greet for \x02{account}\x02 cleared.", account = account)),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Greet for \x02{account}\x02 is now: {greet}", account = account, greet = greet)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,8 +41,8 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
}
|
||||
}
|
||||
match db.set_email(account, email) {
|
||||
Ok(()) if cleared => ctx.notice(me, from.uid, format!("Email for \x02{account}\x02 cleared.")),
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("Email for \x02{account}\x02 updated.")),
|
||||
Ok(()) if cleared => ctx.notice(me, from.uid, t!(ctx, "Email for \x02{account}\x02 cleared.", account = account)),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Email for \x02{account}\x02 updated.", account = account)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
@ -75,7 +75,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
Some("STATUS") | Some("USERMASK") => {
|
||||
let Some(on) = args.get(3).and_then(|s| parse_toggle(s)) else {
|
||||
let state = if db.account_hides_status(account) { "ON" } else { "OFF" };
|
||||
ctx.notice(me, from.uid, format!("HIDE STATUS is \x02{state}\x02. Syntax: SET HIDE STATUS {{ON|OFF}}"));
|
||||
ctx.notice(me, from.uid, t!(ctx, "HIDE STATUS is \x02{state}\x02. Syntax: SET HIDE STATUS {ON|OFF}", state = state));
|
||||
return;
|
||||
};
|
||||
match db.set_account_hide_status(account, on) {
|
||||
|
|
@ -97,7 +97,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
};
|
||||
let Some(on) = on else {
|
||||
let state = if db.account_wants_protect(account) { "ON" } else { "OFF" };
|
||||
ctx.notice(me, from.uid, format!("KILL (nick protection) is \x02{state}\x02. Syntax: SET KILL {{ON|OFF}}"));
|
||||
ctx.notice(me, from.uid, t!(ctx, "KILL (nick protection) is \x02{state}\x02. Syntax: SET KILL {ON|OFF}", state = state));
|
||||
return;
|
||||
};
|
||||
match db.set_account_kill(account, on) {
|
||||
|
|
@ -109,7 +109,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
Some("AUTOOP") => {
|
||||
let Some(on) = args.get(2).and_then(|s| parse_toggle(s)) else {
|
||||
let state = if db.account_wants_autoop(account) { "ON" } else { "OFF" };
|
||||
ctx.notice(me, from.uid, format!("AUTOOP is \x02{state}\x02. Syntax: SET AUTOOP {{ON|OFF}}"));
|
||||
ctx.notice(me, from.uid, t!(ctx, "AUTOOP is \x02{state}\x02. Syntax: SET AUTOOP {ON|OFF}", state = state));
|
||||
return;
|
||||
};
|
||||
match db.set_account_autoop(account, on) {
|
||||
|
|
@ -121,7 +121,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
Some("SNOTICE") | Some("SNOTICES") => {
|
||||
let Some(on) = args.get(2).and_then(|s| parse_toggle(s)) else {
|
||||
let state = if db.account_wants_snotice(account) { "ON" } else { "OFF" };
|
||||
ctx.notice(me, from.uid, format!("SNOTICE is \x02{state}\x02. Syntax: SET SNOTICE {{ON|OFF}}"));
|
||||
ctx.notice(me, from.uid, t!(ctx, "SNOTICE is \x02{state}\x02. Syntax: SET SNOTICE {ON|OFF}", state = state));
|
||||
return;
|
||||
};
|
||||
match db.set_account_snotice(account, on) {
|
||||
|
|
@ -136,7 +136,7 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
|
|||
let cleared = greet.is_empty();
|
||||
match db.set_greet(account, &greet) {
|
||||
Ok(()) if cleared => ctx.notice(me, from.uid, "Your greet has been cleared."),
|
||||
Ok(()) => ctx.notice(me, from.uid, format!("Your greet is now: {greet}")),
|
||||
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Your greet is now: {greet}", greet = greet)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use echo_api::{parse_duration, NetView, Priv, Sender, ServiceCtx, Store};
|
||||
use echo_api::t;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
// SUSPEND <account> [+expiry] [reason] / UNSUSPEND <account>: freeze or unfreeze
|
||||
|
|
@ -15,14 +16,14 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
return;
|
||||
};
|
||||
let Some(account) = db.resolve_account(target).map(str::to_string) else {
|
||||
ctx.notice(me, from.uid, format!("\x02{target}\x02 isn't registered."));
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{target}\x02 isn't registered.", target = target));
|
||||
return;
|
||||
};
|
||||
|
||||
if !suspending {
|
||||
match db.unsuspend_account(&account) {
|
||||
Ok(true) => ctx.notice(me, from.uid, format!("\x02{account}\x02 is no longer suspended.")),
|
||||
Ok(false) => ctx.notice(me, from.uid, format!("\x02{account}\x02 isn't suspended.")),
|
||||
Ok(true) => ctx.notice(me, from.uid, t!(ctx, "\x02{account}\x02 is no longer suspended.", account = account)),
|
||||
Ok(false) => ctx.notice(me, from.uid, t!(ctx, "\x02{account}\x02 isn't suspended.", account = account)),
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
return;
|
||||
|
|
@ -42,8 +43,8 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net:
|
|||
for uid in net.uids_logged_into(&account) {
|
||||
ctx.logout(&uid);
|
||||
}
|
||||
let expiry = if expires.is_some() { " (with expiry)" } else { "" };
|
||||
ctx.notice(me, from.uid, format!("\x02{account}\x02 is now suspended{expiry}."));
|
||||
let expiry = if expires.is_some() { t!(ctx, " (with expiry)") } else { String::new() };
|
||||
ctx.notice(me, from.uid, t!(ctx, "\x02{account}\x02 is now suspended{expiry}.", account = account, expiry = expiry));
|
||||
}
|
||||
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue