Notify or memo the affected user on channel access, XOP, founder, and successor changes

This commit is contained in:
Jean Chevronnet 2026-07-20 19:34:08 +00:00
parent 6078cdf83d
commit 6e53cb751a
No known key found for this signature in database
13 changed files with 152 additions and 29 deletions

View file

@ -1,5 +1,4 @@
use echo_api::Store;
use echo_api::{Sender, ServiceCtx};
use echo_api::{NetView, Sender, ServiceCtx, Store};
use echo_api::t;
// The named tiers ACCESS ADD accepts, high to low; the granular FLAGS command
@ -7,7 +6,7 @@ use echo_api::t;
const TIERS: [&str; 4] = ["sop", "op", "halfop", "voice"];
// ACCESS <#channel> LIST | ADD <account> <sop|op|halfop|voice> | DEL <account>
pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: &mut dyn Store) {
pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net: &dyn NetView, db: &mut dyn Store) {
let Some(&chan) = args.get(1) else {
ctx.notice(me, from.uid, "Syntax: ACCESS <#channel> LIST | ADD <account|!group> <sop|op|halfop|voice> | DEL <account|!group>");
return;
@ -37,7 +36,11 @@ 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, t!(ctx, "Added \x02{account}\x02 to \x02{chan}\x02 as \x02{level}\x02.", account = account, chan = chan, level = level)),
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));
let by = from.account.unwrap_or(from.nick);
echo_api::notify_or_memo(ctx, net, db, me, by, account, "\x02{by}\x02 added you to \x02{chan}\x02 as \x02{level}\x02.", &[("by", by.to_string()), ("chan", chan.to_string()), ("level", level.clone())]);
}
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
}
}
@ -50,7 +53,11 @@ 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, t!(ctx, "Removed \x02{account}\x02 from \x02{chan}\x02.", account = account, chan = chan)),
Ok(true) => {
ctx.notice(me, from.uid, t!(ctx, "Removed \x02{account}\x02 from \x02{chan}\x02.", account = account, chan = chan));
let by = from.account.unwrap_or(from.nick);
echo_api::notify_or_memo(ctx, net, db, me, by, account, "\x02{by}\x02 removed your access to \x02{chan}\x02.", &[("by", by.to_string()), ("chan", chan.to_string())]);
}
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."),
}

View file

@ -1,4 +1,4 @@
use echo_api::{Flags, Sender, ServiceCtx, Store, ACCESS_FLAGS};
use echo_api::{Flags, NetView, Sender, ServiceCtx, Store, ACCESS_FLAGS};
use echo_api::t;
// FLAGS <#channel> [account [+/-flags]]: the granular access model. With no
@ -7,7 +7,7 @@ use echo_api::t;
// t topic i invite a access-list s settings g greet. Viewing needs op access;
// changing needs the founder or the \x02a\x02 flag. Every stored level — a tier
// 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) {
pub fn handle(me: &str, from: &Sender, chan: &str, args: &[&str], ctx: &mut ServiceCtx, net: &dyn NetView, db: &mut dyn Store) {
let Some(info) = db.channel(chan) else {
ctx.notice(me, from.uid, t!(ctx, "\x02{chan}\x02 isn't registered.", chan = chan));
return;
@ -76,7 +76,11 @@ pub fn handle(me: &str, from: &Sender, chan: &str, args: &[&str], ctx: &mut Serv
}
let letters = updated.to_letters();
match db.access_add(chan, target, &letters) {
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)),
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));
let by = from.account.unwrap_or(from.nick);
echo_api::notify_or_memo(ctx, net, db, me, by, target, "\x02{by}\x02 set your access on \x02{chan}\x02 to \x02{letters}\x02.", &[("by", by.to_string()), ("chan", chan.to_string()), ("letters", letters.clone())]);
}
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
}
}

View file

@ -288,13 +288,13 @@ impl Service for ChanServ {
}
}
Some("MODE") => mode::handle(me, from, args, ctx, db),
Some("ACCESS") => access::handle(me, from, args, ctx, db),
Some("ACCESS") => access::handle(me, from, args, ctx, net, db),
Some("FLAGS") => {
let Some(&chan) = args.get(1) else {
ctx.notice(me, from.uid, "Syntax: FLAGS <#channel> [account [+/-flags]]");
return;
};
flags::handle(me, from, chan, args, ctx, db);
flags::handle(me, from, chan, args, ctx, net, db);
}
Some("OP") => op::handle(me, from, "+o", args, ctx, net, db),
Some("DEOP") => op::handle(me, from, "-o", args, ctx, net, db),
@ -320,7 +320,7 @@ impl Service for ChanServ {
Some("UNSUSPEND") => suspend::handle(me, from, args, ctx, net, db, false),
Some("NOEXPIRE") => noexpire::handle(me, from, args, ctx, db),
Some("LIST") => list::handle(me, from, args, ctx, db),
Some("SET") => set::handle(me, from, args, ctx, db),
Some("SET") => set::handle(me, from, args, ctx, net, db),
Some("ENTRYMSG") => entrymsg::handle(me, from, args, ctx, db),
Some("GETKEY") => getkey::handle(me, from, args, ctx, net, db),
Some("SEEN") => seen::handle(me, from, args, ctx, net),
@ -328,10 +328,10 @@ impl Service for ChanServ {
// the mode lock and akick list, so SYNC is the same handler.
Some("ENFORCE") | Some("SYNC") => enforce::handle(me, from, args, ctx, net, db),
Some("CLONE") => clone::handle(me, from, args, ctx, db),
Some("SOP") => xop::handle(me, from, "SOP", "sop", args, ctx, db),
Some("AOP") => xop::handle(me, from, "AOP", "op", args, ctx, db),
Some("HOP") => xop::handle(me, from, "HOP", "halfop", args, ctx, db),
Some("VOP") => xop::handle(me, from, "VOP", "voice", args, ctx, db),
Some("SOP") => xop::handle(me, from, "SOP", "sop", args, ctx, net, db),
Some("AOP") => xop::handle(me, from, "AOP", "op", args, ctx, net, db),
Some("HOP") => xop::handle(me, from, "HOP", "halfop", args, ctx, net, db),
Some("VOP") => xop::handle(me, from, "VOP", "voice", args, ctx, net, db),
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.

View file

@ -1,8 +1,8 @@
use echo_api::{ChanSetting, Sender, ServiceCtx, Store};
use echo_api::{ChanSetting, NetView, 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) {
pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net: &dyn NetView, db: &mut dyn Store) {
let Some(&chan) = args.get(1) else {
ctx.notice(me, from.uid, "Syntax: SET <#channel> FOUNDER <account> | DESC <text>");
return;
@ -32,7 +32,11 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db:
return;
}
match db.set_founder(chan, account) {
Ok(()) => ctx.notice(me, from.uid, t!(ctx, "Founder of \x02{chan}\x02 transferred to \x02{account}\x02.", chan = chan, account = account)),
Ok(()) => {
ctx.notice(me, from.uid, t!(ctx, "Founder of \x02{chan}\x02 transferred to \x02{account}\x02.", chan = chan, account = account));
let by = from.account.unwrap_or(from.nick);
echo_api::notify_or_memo(ctx, net, db, me, by, account, "\x02{by}\x02 transferred the \x02{chan}\x02 founder to you.", &[("by", by.to_string()), ("chan", chan.to_string())]);
}
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
}
}
@ -48,7 +52,11 @@ 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, 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)),
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));
let by = from.account.unwrap_or(from.nick);
echo_api::notify_or_memo(ctx, net, db, me, by, acct, "\x02{by}\x02 set you as successor of \x02{chan}\x02.", &[("by", by.to_string()), ("chan", chan.to_string())]);
}
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
}
}

View file

@ -1,10 +1,11 @@
use echo_api::{access_role, Sender, ServiceCtx, Store};
use echo_api::{access_role, NetView, 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) {
#[allow(clippy::too_many_arguments)]
pub fn handle(me: &str, from: &Sender, word: &str, level: &str, args: &[&str], ctx: &mut ServiceCtx, net: &dyn NetView, db: &mut dyn Store) {
let Some(&chan) = args.get(1) else {
ctx.notice(me, from.uid, t!(ctx, "Syntax: {word} <#channel> ADD <account> | DEL <account> | LIST", word = word));
return;
@ -30,7 +31,11 @@ pub fn handle(me: &str, from: &Sender, word: &str, level: &str, args: &[&str], c
return;
}
match db.access_add(chan, account, level) {
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)),
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));
let by = from.account.unwrap_or(from.nick);
echo_api::notify_or_memo(ctx, net, db, me, by, account, "\x02{by}\x02 added you to \x02{chan}\x02's {word} list.", &[("by", by.to_string()), ("chan", chan.to_string()), ("word", word.to_string())]);
}
Err(_) => ctx.notice(me, from.uid, "Sorry, that didn't work. Please try again in a moment."),
}
}
@ -43,7 +48,11 @@ pub fn handle(me: &str, from: &Sender, word: &str, level: &str, args: &[&str], c
return;
}
match db.access_del(chan, account) {
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(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));
let by = from.account.unwrap_or(from.nick);
echo_api::notify_or_memo(ctx, net, db, me, by, account, "\x02{by}\x02 removed you from \x02{chan}\x02's {word} list.", &[("by", by.to_string()), ("chan", chan.to_string()), ("word", word.to_string())]);
}
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."),
}