kicknorejoin channel mode (+J <secs>)
This commit is contained in:
parent
8dc51a281b
commit
37ea5d277d
5 changed files with 66 additions and 3 deletions
|
|
@ -152,6 +152,7 @@ pub struct ChanModes {
|
||||||
pub nokicks: bool, // +Q — KICK is disabled on the channel
|
pub nokicks: bool, // +Q — KICK is disabled on the channel
|
||||||
pub allowinvite: bool, // +A — any member (not just ops) may INVITE
|
pub allowinvite: bool, // +A — any member (not just ops) may INVITE
|
||||||
pub permanent: bool, // +P — channel persists with zero members
|
pub permanent: bool, // +P — channel persists with zero members
|
||||||
|
pub kicknorejoin: Option<u32>, // +J <secs> — block rejoin for N secs after a kick
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChanModes {
|
impl ChanModes {
|
||||||
|
|
@ -278,6 +279,7 @@ pub struct Channel {
|
||||||
pub joinflood_until: u64, // +j locked out until this unix ts
|
pub joinflood_until: u64, // +j locked out until this unix ts
|
||||||
pub nickflood_hits: Vec<u64>, // +F recent nick-change times
|
pub nickflood_hits: Vec<u64>, // +F recent nick-change times
|
||||||
pub nickflood_until: u64, // +F locked out until this unix ts
|
pub nickflood_until: u64, // +F locked out until this unix ts
|
||||||
|
pub recent_kicks: HashMap<Uid, u64>, // +J uid -> unix ts of last kick (rejoin delay)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Channel {
|
impl Channel {
|
||||||
|
|
@ -304,6 +306,7 @@ impl Channel {
|
||||||
joinflood_until: 0,
|
joinflood_until: 0,
|
||||||
nickflood_hits: Vec::new(),
|
nickflood_hits: Vec::new(),
|
||||||
nickflood_until: 0,
|
nickflood_until: 0,
|
||||||
|
recent_kicks: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -450,6 +453,19 @@ impl Server {
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// +J <secs> — can't rejoin within N seconds of being kicked
|
||||||
|
if let Some(secs) = ch.modes.kicknorejoin {
|
||||||
|
if let Some(&kt) = ch.recent_kicks.get(&uid) {
|
||||||
|
if now().saturating_sub(kt) < secs as u64 {
|
||||||
|
self.numeric(
|
||||||
|
uid,
|
||||||
|
ERR_DELAYREJOIN,
|
||||||
|
&format!("{name} :You must wait {secs}s after a kick to rejoin (+J)"),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// +l full — with +L redirect, bounce the user to the target instead
|
// +l full — with +L redirect, bounce the user to the target instead
|
||||||
if let Some(ch) = self.channels.get(&key) {
|
if let Some(ch) = self.channels.get(&key) {
|
||||||
|
|
@ -505,6 +521,7 @@ impl Server {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
ch.invites.remove(&uid); // consume any pending invite
|
ch.invites.remove(&uid); // consume any pending invite
|
||||||
|
ch.recent_kicks.remove(&uid); // they got back in; clear any +J rejoin timer
|
||||||
if let Some(u) = self.users.get_mut(&uid) {
|
if let Some(u) = self.users.get_mut(&uid) {
|
||||||
u.channels.insert(key.clone());
|
u.channels.insert(key.clone());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -451,6 +451,9 @@ impl Command for Kick {
|
||||||
s.propagate_from_user(uid, &format!("KICK {chan} {victim} :{reason}")); // tell links
|
s.propagate_from_user(uid, &format!("KICK {chan} {victim} :{reason}")); // tell links
|
||||||
if let Some(ch) = s.channels.get_mut(&key) {
|
if let Some(ch) = s.channels.get_mut(&key) {
|
||||||
ch.members.remove(&tuid);
|
ch.members.remove(&tuid);
|
||||||
|
if ch.modes.kicknorejoin.is_some() {
|
||||||
|
ch.recent_kicks.insert(tuid, now()); // +J rejoin-delay clock
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if let Some(u) = s.users.get_mut(&tuid) {
|
if let Some(u) = s.users.get_mut(&tuid) {
|
||||||
u.channels.remove(&key);
|
u.channels.remove(&key);
|
||||||
|
|
|
||||||
44
src/mode.rs
44
src/mode.rs
|
|
@ -88,6 +88,7 @@ static CHAN_MODES: &[&(dyn ChanMode + Sync)] = &[
|
||||||
&NOKICKS,
|
&NOKICKS,
|
||||||
&ALLOWINVITE,
|
&ALLOWINVITE,
|
||||||
&PERMANENT,
|
&PERMANENT,
|
||||||
|
&KICKNOREJOIN,
|
||||||
];
|
];
|
||||||
|
|
||||||
// --- 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 ----------
|
||||||
|
|
@ -859,6 +860,47 @@ impl ChanMode for AntiCapsMode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// +J `<secs>` — after being kicked, a user can't rejoin for `<secs>` seconds
|
||||||
|
/// (InspIRCd `m_kicknorejoin`). Enforced in `Server::join`.
|
||||||
|
struct KickNoRejoinMode;
|
||||||
|
static KICKNOREJOIN: KickNoRejoinMode = KickNoRejoinMode;
|
||||||
|
impl ChanMode for KickNoRejoinMode {
|
||||||
|
fn letter(&self) -> char {
|
||||||
|
'J'
|
||||||
|
}
|
||||||
|
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; // needs a positive seconds value
|
||||||
|
};
|
||||||
|
let secs = secs.min(3600);
|
||||||
|
if let Some(c) = s.channels.get_mut(key) {
|
||||||
|
c.modes.kicknorejoin = Some(secs);
|
||||||
|
}
|
||||||
|
Applied::Yes(Some(secs.to_string()))
|
||||||
|
} else {
|
||||||
|
if let Some(c) = s.channels.get_mut(key) {
|
||||||
|
c.modes.kicknorejoin = 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
|
||||||
|
|
@ -1083,7 +1125,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn registry_covers_all_channel_modes() {
|
fn registry_covers_all_channel_modes() {
|
||||||
for c in "qaohvbeIklmntiszpONCTcSRMfjFLgGuBQAP".chars() {
|
for c in "qaohvbeIklmntiszpONCTcSRMfjFLgGuBQAPJ".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());
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ pub const ERR_NOSUCHSERVER: u16 = 402;
|
||||||
pub const ERR_WASNOSUCHNICK: u16 = 406;
|
pub const ERR_WASNOSUCHNICK: u16 = 406;
|
||||||
pub const ERR_UNAVAILRESOURCE: u16 = 437; // channel temporarily unavailable (+j)
|
pub const ERR_UNAVAILRESOURCE: u16 = 437; // channel temporarily unavailable (+j)
|
||||||
pub const ERR_LINKCHANNEL: u16 = 470; // +L — you were redirected to another channel
|
pub const ERR_LINKCHANNEL: u16 = 470; // +L — you were redirected to another channel
|
||||||
|
pub const ERR_DELAYREJOIN: u16 = 495; // +J — must wait before rejoining after a kick
|
||||||
pub const RPL_ENDOFSPAMFILTER: u16 = 940; // end of the +g word-filter list
|
pub const RPL_ENDOFSPAMFILTER: u16 = 940; // end of the +g word-filter list
|
||||||
pub const RPL_SPAMFILTER: u16 = 941; // one +g word-filter entry
|
pub const RPL_SPAMFILTER: u16 = 941; // one +g word-filter entry
|
||||||
pub const RPL_KNOCK: u16 = 710; // channel gets the knock
|
pub const RPL_KNOCK: u16 = 710; // channel gets the knock
|
||||||
|
|
|
||||||
|
|
@ -409,7 +409,7 @@ impl Server {
|
||||||
uid,
|
uid,
|
||||||
RPL_MYINFO,
|
RPL_MYINFO,
|
||||||
&format!(
|
&format!(
|
||||||
"{} echoircd-{VERSION} iowxsgBDIHrRz qaohvbeIklimnpstzCTcSNORMfjFLgGuBQAP",
|
"{} echoircd-{VERSION} iowxsgBDIHrRz qaohvbeIklimnpstzCTcSNORMfjFLgGuBQAPJ",
|
||||||
self.name
|
self.name
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -417,7 +417,7 @@ impl Server {
|
||||||
uid,
|
uid,
|
||||||
RPL_ISUPPORT,
|
RPL_ISUPPORT,
|
||||||
&format!(
|
&format!(
|
||||||
"CHANTYPES=# PREFIX=(qaohv)~&@%+ CHANMODES=beIg,k,lfjFLHB,ACGMNOPQRSTcimnpstuz 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,lfjFLHBJ,ACGMNOPQRSTcimnpstuz 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