modules: port ircv3_extended_isupport (draft/extended-isupport cap + ISUPPORT command, draft/isupport batch)
This commit is contained in:
parent
f48be3413d
commit
bda7eecfc2
4 changed files with 116 additions and 23 deletions
49
src/modules/extended_isupport.rs
Normal file
49
src/modules/extended_isupport.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
//! extended_isupport — the `draft/extended-isupport` capability. reverse's own
|
||||||
|
//! module. Normally ISUPPORT (005) is a one-shot at registration; with this cap a
|
||||||
|
//! client can send the `ISUPPORT` command any time to re-request the current tokens
|
||||||
|
//! (handy after a rehash changes them). If the client also has `batch`, the reply
|
||||||
|
//! is wrapped in a `draft/isupport` BATCH so the multi-line set arrives atomically —
|
||||||
|
//! the emission itself lives in `Server::send_isupport`, shared with the welcome burst.
|
||||||
|
//!
|
||||||
|
//! Behaviour reference: reverse's InspIRCd `m_ircv3_extended_isupport`. Original native Rust.
|
||||||
|
|
||||||
|
use crate::command::{CmdResult, Command};
|
||||||
|
use crate::numeric::ERR_UNKNOWNCOMMAND;
|
||||||
|
use crate::server::Server;
|
||||||
|
use crate::Uid;
|
||||||
|
|
||||||
|
pub fn commands() -> Vec<Box<dyn Command>> {
|
||||||
|
vec![Box::new(ISupportCmd)]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ISUPPORT — re-send the server's ISUPPORT tokens. Requires the
|
||||||
|
/// `draft/extended-isupport` cap; usable before registration completes.
|
||||||
|
struct ISupportCmd;
|
||||||
|
impl Command for ISupportCmd {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"ISUPPORT"
|
||||||
|
}
|
||||||
|
fn min_params(&self) -> usize {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
fn before_reg(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
fn handle(&self, s: &mut Server, uid: Uid, _params: &[String]) -> CmdResult {
|
||||||
|
let (has_cap, has_batch) = s
|
||||||
|
.users
|
||||||
|
.get(&uid)
|
||||||
|
.map(|u| (u.caps.ext_isupport, u.caps.batch))
|
||||||
|
.unwrap_or((false, false));
|
||||||
|
if !has_cap {
|
||||||
|
s.numeric(
|
||||||
|
uid,
|
||||||
|
ERR_UNKNOWNCOMMAND,
|
||||||
|
"ISUPPORT :You must request the draft/extended-isupport capability to use this command",
|
||||||
|
);
|
||||||
|
return CmdResult::Fail;
|
||||||
|
}
|
||||||
|
s.send_isupport(uid, has_batch);
|
||||||
|
CmdResult::Ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,6 +15,7 @@ pub mod connectban;
|
||||||
pub mod connflood;
|
pub mod connflood;
|
||||||
pub mod denychans;
|
pub mod denychans;
|
||||||
pub mod dnsbl;
|
pub mod dnsbl;
|
||||||
|
pub mod extended_isupport;
|
||||||
pub mod extjwt;
|
pub mod extjwt;
|
||||||
pub mod filehost;
|
pub mod filehost;
|
||||||
pub mod filter;
|
pub mod filter;
|
||||||
|
|
@ -97,5 +98,6 @@ pub fn module_commands() -> Vec<Box<dyn Command>> {
|
||||||
.chain(cloudflare_challenge::commands())
|
.chain(cloudflare_challenge::commands())
|
||||||
.chain(extjwt::commands())
|
.chain(extjwt::commands())
|
||||||
.chain(filehost::commands())
|
.chain(filehost::commands())
|
||||||
|
.chain(extended_isupport::commands())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -579,6 +579,59 @@ impl Server {
|
||||||
|
|
||||||
/// Send a numeric: `:server NNN <target> <rest>`. `<target>` is the client's
|
/// Send a numeric: `:server NNN <target> <rest>`. `<target>` is the client's
|
||||||
/// nick, or `*` before it has one.
|
/// nick, or `*` before it has one.
|
||||||
|
/// The ISUPPORT (005) token blocks this server advertises — the fixed set plus
|
||||||
|
/// the config-driven module tokens (ICON, FILEHOST). Each entry is a token block
|
||||||
|
/// without the trailing `:are supported by this server`. Shared by the welcome
|
||||||
|
/// burst and the `ISUPPORT` command (draft/extended-isupport).
|
||||||
|
pub fn isupport_lines(&self) -> Vec<String> {
|
||||||
|
let mut lines = vec![format!(
|
||||||
|
"CHANTYPES=# PREFIX=(qaohv)~&@%+ CHANMODES=beIgX,k,lfjFLHBJdK,ACDGMNOPQRSTUcimnpstuz EXTBAN=,cgjmnrsy WATCH=128 MONITOR=128 SILENCE=32 CALLERID=g WHOX CHATHISTORY=256 MSGREFTYPES=timestamp,msgid UTF8ONLY CASEMAPPING=ascii NICKLEN=30 CHANNELLEN=50 NETWORK={}",
|
||||||
|
self.network
|
||||||
|
)];
|
||||||
|
if let Some(tok) = crate::modules::network_icon::isupport(self) {
|
||||||
|
lines.push(tok);
|
||||||
|
}
|
||||||
|
if let Some(tok) = crate::modules::filehost::isupport(self) {
|
||||||
|
lines.push(tok);
|
||||||
|
}
|
||||||
|
lines
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emit the ISUPPORT numerics to `uid`. When `batched` (the client negotiated
|
||||||
|
/// `draft/extended-isupport` + `batch`), wrap them in a `draft/isupport` BATCH so
|
||||||
|
/// the multi-line set arrives atomically (InspIRCd's m_ircv3_extended_isupport).
|
||||||
|
pub fn send_isupport(&mut self, uid: Uid, batched: bool) {
|
||||||
|
let lines = self.isupport_lines();
|
||||||
|
if batched {
|
||||||
|
let nick = self
|
||||||
|
.users
|
||||||
|
.get(&uid)
|
||||||
|
.map(|u| u.nick.clone())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let bref = self.next_msgid().replace('-', "");
|
||||||
|
self.send(uid, format!(":{} BATCH +{bref} draft/isupport", self.name));
|
||||||
|
for l in &lines {
|
||||||
|
self.send(
|
||||||
|
uid,
|
||||||
|
format!(
|
||||||
|
"@batch={bref} :{} {:03} {nick} {l} :are supported by this server",
|
||||||
|
self.name,
|
||||||
|
crate::numeric::RPL_ISUPPORT
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self.send(uid, format!(":{} BATCH -{bref}", self.name));
|
||||||
|
} else {
|
||||||
|
for l in &lines {
|
||||||
|
self.numeric(
|
||||||
|
uid,
|
||||||
|
crate::numeric::RPL_ISUPPORT,
|
||||||
|
&format!("{l} :are supported by this server"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn numeric(&self, uid: Uid, code: u16, rest: &str) {
|
pub fn numeric(&self, uid: Uid, code: u16, rest: &str) {
|
||||||
let target = self
|
let target = self
|
||||||
.users
|
.users
|
||||||
|
|
|
||||||
35
src/users.rs
35
src/users.rs
|
|
@ -116,6 +116,7 @@ pub const SUPPORTED_CAPS: &[&str] = &[
|
||||||
"draft/multiline",
|
"draft/multiline",
|
||||||
"draft/account-registration",
|
"draft/account-registration",
|
||||||
"draft/json-log",
|
"draft/json-log",
|
||||||
|
"draft/extended-isupport",
|
||||||
"reverse.im/filehost",
|
"reverse.im/filehost",
|
||||||
"cap-notify",
|
"cap-notify",
|
||||||
];
|
];
|
||||||
|
|
@ -149,6 +150,7 @@ pub struct Caps {
|
||||||
pub multiline: bool, // draft/multiline — may send multiline message batches
|
pub multiline: bool, // draft/multiline — may send multiline message batches
|
||||||
pub acct_registration: bool, // draft/account-registration — REGISTER/VERIFY understood
|
pub acct_registration: bool, // draft/account-registration — REGISTER/VERIFY understood
|
||||||
pub json_log: bool, // draft/json-log — structured JSON tag on server notices
|
pub json_log: bool, // draft/json-log — structured JSON tag on server notices
|
||||||
|
pub ext_isupport: bool, // draft/extended-isupport — ISUPPORT command + batched 005
|
||||||
pub filehost: bool, // reverse.im/filehost — knows the file-host extension
|
pub filehost: bool, // reverse.im/filehost — knows the file-host extension
|
||||||
pub cap_notify: bool,
|
pub cap_notify: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -210,6 +212,7 @@ impl Caps {
|
||||||
"draft/multiline" => self.multiline,
|
"draft/multiline" => self.multiline,
|
||||||
"draft/account-registration" => self.acct_registration,
|
"draft/account-registration" => self.acct_registration,
|
||||||
"draft/json-log" => self.json_log,
|
"draft/json-log" => self.json_log,
|
||||||
|
"draft/extended-isupport" => self.ext_isupport,
|
||||||
"reverse.im/filehost" => self.filehost,
|
"reverse.im/filehost" => self.filehost,
|
||||||
"cap-notify" => self.cap_notify,
|
"cap-notify" => self.cap_notify,
|
||||||
_ => false,
|
_ => false,
|
||||||
|
|
@ -243,6 +246,7 @@ impl Caps {
|
||||||
"draft/multiline" => &mut self.multiline,
|
"draft/multiline" => &mut self.multiline,
|
||||||
"draft/account-registration" => &mut self.acct_registration,
|
"draft/account-registration" => &mut self.acct_registration,
|
||||||
"draft/json-log" => &mut self.json_log,
|
"draft/json-log" => &mut self.json_log,
|
||||||
|
"draft/extended-isupport" => &mut self.ext_isupport,
|
||||||
"reverse.im/filehost" => &mut self.filehost,
|
"reverse.im/filehost" => &mut self.filehost,
|
||||||
"cap-notify" => &mut self.cap_notify,
|
"cap-notify" => &mut self.cap_notify,
|
||||||
_ => return false,
|
_ => return false,
|
||||||
|
|
@ -460,29 +464,14 @@ impl Server {
|
||||||
self.name
|
self.name
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
self.numeric(
|
// ISUPPORT (005): the fixed set + config-driven module tokens (ICON/FILEHOST).
|
||||||
uid,
|
// draft/extended-isupport + batch clients get it wrapped in a draft/isupport batch.
|
||||||
RPL_ISUPPORT,
|
let batched = self
|
||||||
&format!(
|
.users
|
||||||
"CHANTYPES=# PREFIX=(qaohv)~&@%+ CHANMODES=beIgX,k,lfjFLHBJdK,ACDGMNOPQRSTUcimnpstuz EXTBAN=,cgjmnrsy 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",
|
.get(&uid)
|
||||||
self.network
|
.map(|u| u.caps.ext_isupport && u.caps.batch)
|
||||||
),
|
.unwrap_or(false);
|
||||||
);
|
self.send_isupport(uid, batched);
|
||||||
// ircv3_network_icon: advertise draft/ICON when configured
|
|
||||||
if let Some(tok) = crate::modules::network_icon::isupport(self) {
|
|
||||||
self.numeric(
|
|
||||||
uid,
|
|
||||||
RPL_ISUPPORT,
|
|
||||||
&format!("{tok} :are supported by this server"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(tok) = crate::modules::filehost::isupport(self) {
|
|
||||||
self.numeric(
|
|
||||||
uid,
|
|
||||||
RPL_ISUPPORT,
|
|
||||||
&format!("{tok} :are supported by this server"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
self.numeric(
|
self.numeric(
|
||||||
uid,
|
uid,
|
||||||
RPL_LUSERCLIENT,
|
RPL_LUSERCLIENT,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue