delaymsg channel mode (+d <secs>)
This commit is contained in:
parent
f4c01e1bf8
commit
668f2735b2
4 changed files with 69 additions and 3 deletions
|
|
@ -18,6 +18,7 @@ pub struct Member {
|
|||
pub op: bool, // +o (@)
|
||||
pub halfop: bool, // +h (%)
|
||||
pub voice: bool, // +v (+)
|
||||
pub joined: u64, // unix ts this member joined (for +d delaymsg; 0 = unknown)
|
||||
}
|
||||
|
||||
/// Prefix ranks, high→low — gate who may grant a prefix / kick whom.
|
||||
|
|
@ -155,6 +156,7 @@ pub struct ChanModes {
|
|||
pub permanent: bool, // +P — channel persists with zero members
|
||||
pub kicknorejoin: Option<u32>, // +J <secs> — block rejoin for N secs after a kick
|
||||
pub opmoderated: bool, // +U — unprivileged users' messages go to ops only
|
||||
pub delaymsg: Option<u32>, // +d <secs> — new joiners can't speak for N secs
|
||||
}
|
||||
|
||||
impl ChanModes {
|
||||
|
|
@ -551,6 +553,7 @@ impl Server {
|
|||
uid,
|
||||
Member {
|
||||
op: is_new,
|
||||
joined: now(),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -216,6 +216,27 @@ pub(crate) fn deliver(s: &mut Server, uid: Uid, params: &[String], notice: bool)
|
|||
}
|
||||
return CmdResult::Fail;
|
||||
}
|
||||
// +d delaymsg — a just-joined unprivileged user must wait before speaking
|
||||
if let Some(secs) = s.channels.get(&key).and_then(|c| c.modes.delaymsg) {
|
||||
if s.rank(uid, &key) < RANK_VOICE {
|
||||
let joined = s
|
||||
.channels
|
||||
.get(&key)
|
||||
.and_then(|c| c.members.get(&uid))
|
||||
.map(|m| m.joined)
|
||||
.unwrap_or(0);
|
||||
if joined != 0 && crate::server::now().saturating_sub(joined) < secs as u64 {
|
||||
if !notice {
|
||||
s.numeric(
|
||||
uid,
|
||||
ERR_CANNOTSENDTOCHAN,
|
||||
&format!("{target} :You must wait {secs}s after joining to speak (+d)"),
|
||||
);
|
||||
}
|
||||
return CmdResult::Fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
// extban `m:` mute — matched users can't speak unless voiced-or-above
|
||||
if s.extban_active(uid, &key, 'm') && s.rank(uid, &key) < RANK_VOICE {
|
||||
if !notice {
|
||||
|
|
|
|||
44
src/mode.rs
44
src/mode.rs
|
|
@ -90,6 +90,7 @@ static CHAN_MODES: &[&(dyn ChanMode + Sync)] = &[
|
|||
&PERMANENT,
|
||||
&KICKNOREJOIN,
|
||||
&OPMODERATED,
|
||||
&DELAYMSG,
|
||||
];
|
||||
|
||||
// --- prefix modes (+q/+a/+o/+h/+v): a per-member rank, needs a nick ----------
|
||||
|
|
@ -910,6 +911,47 @@ impl ChanMode for KickNoRejoinMode {
|
|||
}
|
||||
}
|
||||
|
||||
/// +d `<secs>` — a newly-joined member can't speak for `<secs>` seconds (InspIRCd
|
||||
/// `m_delaymsg`). Enforced in the message path; voiced-or-above are exempt.
|
||||
struct DelayMsgMode;
|
||||
static DELAYMSG: DelayMsgMode = DelayMsgMode;
|
||||
impl ChanMode for DelayMsgMode {
|
||||
fn letter(&self) -> char {
|
||||
'd'
|
||||
}
|
||||
fn wants_param(&self, adding: bool) -> bool {
|
||||
adding
|
||||
}
|
||||
fn apply(
|
||||
&self,
|
||||
s: &mut Server,
|
||||
_chan: &str,
|
||||
key: &str,
|
||||
_uid: Uid,
|
||||
adding: bool,
|
||||
param: Option<&str>,
|
||||
) -> Applied {
|
||||
if adding {
|
||||
let Some(secs) = param
|
||||
.and_then(|p| p.parse::<u32>().ok())
|
||||
.filter(|&n| n >= 1)
|
||||
else {
|
||||
return Applied::No;
|
||||
};
|
||||
let secs = secs.min(3600);
|
||||
if let Some(c) = s.channels.get_mut(key) {
|
||||
c.modes.delaymsg = Some(secs);
|
||||
}
|
||||
Applied::Yes(Some(secs.to_string()))
|
||||
} else {
|
||||
if let Some(c) = s.channels.get_mut(key) {
|
||||
c.modes.delaymsg = None;
|
||||
}
|
||||
Applied::Yes(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === user modes ============================================================
|
||||
|
||||
/// A user mode (+i/+w/+o) — same handler-object shape as [`ChanMode`], and the
|
||||
|
|
@ -1150,7 +1192,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn registry_covers_all_channel_modes() {
|
||||
for c in "qaohvbeIklmntiszpONCTcSRMfjFLgGuBQAPJU".chars() {
|
||||
for c in "qaohvbeIklmntiszpONCTcSRMfjFLgGuBQAPJUd".chars() {
|
||||
assert!(chan_mode(c).is_some(), "missing handler for +{c}");
|
||||
}
|
||||
assert!(chan_mode('y').is_none());
|
||||
|
|
|
|||
|
|
@ -418,7 +418,7 @@ impl Server {
|
|||
uid,
|
||||
RPL_MYINFO,
|
||||
&format!(
|
||||
"{} echoircd-{VERSION} iowxsgBDIHrRzWc qaohvbeIklimnpstzCTcSNORMfjFLgGuBQAPJU",
|
||||
"{} echoircd-{VERSION} iowxsgBDIHrRzWc qaohvbeIklimnpstzCTcSNORMfjFLgGuBQAPJUd",
|
||||
self.name
|
||||
),
|
||||
);
|
||||
|
|
@ -426,7 +426,7 @@ impl Server {
|
|||
uid,
|
||||
RPL_ISUPPORT,
|
||||
&format!(
|
||||
"CHANTYPES=# PREFIX=(qaohv)~&@%+ CHANMODES=beIg,k,lfjFLHBJ,ACGMNOPQRSTUcimnpstuz EXTBAN=,cmn WATCH=128 MONITOR=128 SILENCE=32 CALLERID=g WHOX CHATHISTORY=256 MSGREFTYPES=timestamp,msgid UTF8ONLY CASEMAPPING=ascii NICKLEN=30 CHANNELLEN=50 NETWORK={} :are supported by this server",
|
||||
"CHANTYPES=# PREFIX=(qaohv)~&@%+ CHANMODES=beIg,k,lfjFLHBJd,ACGMNOPQRSTUcimnpstuz EXTBAN=,cmn WATCH=128 MONITOR=128 SILENCE=32 CALLERID=g WHOX CHATHISTORY=256 MSGREFTYPES=timestamp,msgid UTF8ONLY CASEMAPPING=ascii NICKLEN=30 CHANNELLEN=50 NETWORK={} :are supported by this server",
|
||||
self.network
|
||||
),
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue