repeat channel mode (+K <n>): block recently-repeated lines
This commit is contained in:
parent
668f2735b2
commit
1eca745092
4 changed files with 84 additions and 9 deletions
|
|
@ -19,6 +19,7 @@ pub struct Member {
|
||||||
pub halfop: bool, // +h (%)
|
pub halfop: bool, // +h (%)
|
||||||
pub voice: bool, // +v (+)
|
pub voice: bool, // +v (+)
|
||||||
pub joined: u64, // unix ts this member joined (for +d delaymsg; 0 = unknown)
|
pub joined: u64, // unix ts this member joined (for +d delaymsg; 0 = unknown)
|
||||||
|
pub recent_msgs: Vec<String>, // +K repeat: this member's last few lines here
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prefix ranks, high→low — gate who may grant a prefix / kick whom.
|
/// Prefix ranks, high→low — gate who may grant a prefix / kick whom.
|
||||||
|
|
@ -157,6 +158,7 @@ pub struct ChanModes {
|
||||||
pub kicknorejoin: Option<u32>, // +J <secs> — block rejoin for N secs after a kick
|
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 opmoderated: bool, // +U — unprivileged users' messages go to ops only
|
||||||
pub delaymsg: Option<u32>, // +d <secs> — new joiners can't speak for N secs
|
pub delaymsg: Option<u32>, // +d <secs> — new joiners can't speak for N secs
|
||||||
|
pub repeat: Option<u32>, // +K <n> — block a line repeated within your last n
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChanModes {
|
impl ChanModes {
|
||||||
|
|
|
||||||
|
|
@ -361,6 +361,37 @@ pub(crate) fn deliver(s: &mut Server, uid: Uid, params: &[String], notice: bool)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// +K repeat — reject a line the sender just repeated; else remember it (ops exempt)
|
||||||
|
if let Some(n) = s.channels.get(&key).and_then(|c| c.modes.repeat) {
|
||||||
|
if s.rank(uid, &key) < RANK_HALFOP {
|
||||||
|
let repeated = s
|
||||||
|
.channels
|
||||||
|
.get(&key)
|
||||||
|
.and_then(|c| c.members.get(&uid))
|
||||||
|
.map(|m| m.recent_msgs.iter().any(|p| p == &body))
|
||||||
|
.unwrap_or(false);
|
||||||
|
if repeated {
|
||||||
|
if !notice {
|
||||||
|
s.numeric(
|
||||||
|
uid,
|
||||||
|
ERR_CANNOTSENDTOCHAN,
|
||||||
|
&format!("{target} :Cannot send to channel (+K: repeated message)"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return CmdResult::Fail;
|
||||||
|
}
|
||||||
|
if let Some(m) = s
|
||||||
|
.channels
|
||||||
|
.get_mut(&key)
|
||||||
|
.and_then(|c| c.members.get_mut(&uid))
|
||||||
|
{
|
||||||
|
m.recent_msgs.push(body.clone());
|
||||||
|
while m.recent_msgs.len() > n as usize {
|
||||||
|
m.recent_msgs.remove(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// deliver to every member except the sender and +D (deaf) users, tagging
|
// deliver to every member except the sender and +D (deaf) users, tagging
|
||||||
// per-recipient (server-time + any client-only tags on the line)
|
// per-recipient (server-time + any client-only tags on the line)
|
||||||
let line = format!(":{prefix} {cmd} {target} :{body}");
|
let line = format!(":{prefix} {cmd} {target} :{body}");
|
||||||
|
|
|
||||||
44
src/mode.rs
44
src/mode.rs
|
|
@ -91,6 +91,7 @@ static CHAN_MODES: &[&(dyn ChanMode + Sync)] = &[
|
||||||
&KICKNOREJOIN,
|
&KICKNOREJOIN,
|
||||||
&OPMODERATED,
|
&OPMODERATED,
|
||||||
&DELAYMSG,
|
&DELAYMSG,
|
||||||
|
&REPEAT,
|
||||||
];
|
];
|
||||||
|
|
||||||
// --- prefix modes (+q/+a/+o/+h/+v): a per-member rank, needs a nick ----------
|
// --- prefix modes (+q/+a/+o/+h/+v): a per-member rank, needs a nick ----------
|
||||||
|
|
@ -952,6 +953,47 @@ impl ChanMode for DelayMsgMode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// +K `<n>` — block a message identical to one of the sender's previous `<n>`
|
||||||
|
/// lines in this channel (InspIRCd `m_repeat`, simplified). Ops are exempt.
|
||||||
|
struct RepeatMode;
|
||||||
|
static REPEAT: RepeatMode = RepeatMode;
|
||||||
|
impl ChanMode for RepeatMode {
|
||||||
|
fn letter(&self) -> char {
|
||||||
|
'K'
|
||||||
|
}
|
||||||
|
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(n) = param
|
||||||
|
.and_then(|p| p.parse::<u32>().ok())
|
||||||
|
.filter(|&n| n >= 1)
|
||||||
|
else {
|
||||||
|
return Applied::No;
|
||||||
|
};
|
||||||
|
let n = n.min(20);
|
||||||
|
if let Some(c) = s.channels.get_mut(key) {
|
||||||
|
c.modes.repeat = Some(n);
|
||||||
|
}
|
||||||
|
Applied::Yes(Some(n.to_string()))
|
||||||
|
} else {
|
||||||
|
if let Some(c) = s.channels.get_mut(key) {
|
||||||
|
c.modes.repeat = None;
|
||||||
|
}
|
||||||
|
Applied::Yes(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// === user modes ============================================================
|
// === user modes ============================================================
|
||||||
|
|
||||||
/// A user mode (+i/+w/+o) — same handler-object shape as [`ChanMode`], and the
|
/// A user mode (+i/+w/+o) — same handler-object shape as [`ChanMode`], and the
|
||||||
|
|
@ -1192,7 +1234,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn registry_covers_all_channel_modes() {
|
fn registry_covers_all_channel_modes() {
|
||||||
for c in "qaohvbeIklmntiszpONCTcSRMfjFLgGuBQAPJUd".chars() {
|
for c in "qaohvbeIklmntiszpONCTcSRMfjFLgGuBQAPJUdK".chars() {
|
||||||
assert!(chan_mode(c).is_some(), "missing handler for +{c}");
|
assert!(chan_mode(c).is_some(), "missing handler for +{c}");
|
||||||
}
|
}
|
||||||
assert!(chan_mode('y').is_none());
|
assert!(chan_mode('y').is_none());
|
||||||
|
|
|
||||||
|
|
@ -418,7 +418,7 @@ impl Server {
|
||||||
uid,
|
uid,
|
||||||
RPL_MYINFO,
|
RPL_MYINFO,
|
||||||
&format!(
|
&format!(
|
||||||
"{} echoircd-{VERSION} iowxsgBDIHrRzWc qaohvbeIklimnpstzCTcSNORMfjFLgGuBQAPJUd",
|
"{} echoircd-{VERSION} iowxsgBDIHrRzWc qaohvbeIklimnpstzCTcSNORMfjFLgGuBQAPJUdK",
|
||||||
self.name
|
self.name
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -426,7 +426,7 @@ impl Server {
|
||||||
uid,
|
uid,
|
||||||
RPL_ISUPPORT,
|
RPL_ISUPPORT,
|
||||||
&format!(
|
&format!(
|
||||||
"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",
|
"CHANTYPES=# PREFIX=(qaohv)~&@%+ CHANMODES=beIg,k,lfjFLHBJdK,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
|
self.network
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue