whox (354) + ircv3 standard-replies (fail/warn/note), account-tag, and +s/+p who/names member hiding
This commit is contained in:
parent
7e0449e2dd
commit
bcfed8a621
7 changed files with 233 additions and 53 deletions
|
|
@ -507,6 +507,19 @@ impl Server {
|
||||||
self.numeric(uid, RPL_ENDOFNAMES, &format!("{key} :End of /NAMES list"));
|
self.numeric(uid, RPL_ENDOFNAMES, &format!("{key} :End of /NAMES list"));
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
// +s (secret) / +p (private): members are hidden from non-members. Reply as
|
||||||
|
// if the channel were empty (opers still see it).
|
||||||
|
if (ch.modes.secret || ch.modes.private)
|
||||||
|
&& !ch.members.contains_key(&uid)
|
||||||
|
&& !self.is_oper(uid)
|
||||||
|
{
|
||||||
|
self.numeric(
|
||||||
|
uid,
|
||||||
|
RPL_ENDOFNAMES,
|
||||||
|
&format!("{} :End of /NAMES list", ch.name),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// multi-prefix → all prefixes; userhost-in-names → full nick!user@host
|
// multi-prefix → all prefixes; userhost-in-names → full nick!user@host
|
||||||
let (multi, uhost) = self
|
let (multi, uhost) = self
|
||||||
.users
|
.users
|
||||||
|
|
|
||||||
|
|
@ -39,17 +39,11 @@ impl Command for Knock {
|
||||||
}
|
}
|
||||||
let can = s.channels[&key].modes.invite_only && !s.is_member(uid, &key);
|
let can = s.channels[&key].modes.invite_only && !s.is_member(uid, &key);
|
||||||
if !can {
|
if !can {
|
||||||
let nick = s
|
s.fail(
|
||||||
.users
|
|
||||||
.get(&uid)
|
|
||||||
.map(|u| u.nick.clone())
|
|
||||||
.unwrap_or_default();
|
|
||||||
s.send(
|
|
||||||
uid,
|
uid,
|
||||||
format!(
|
"KNOCK",
|
||||||
":{} NOTICE {nick} :Can't KNOCK on {chan} (not invite-only, or you're on it)",
|
"CANNOT_KNOCK",
|
||||||
s.name
|
&format!("Can't KNOCK on {chan} (not invite-only, or you're on it)"),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
return CmdResult::Fail;
|
return CmdResult::Fail;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
use crate::command::{CmdResult, Command};
|
use crate::command::{CmdResult, Command};
|
||||||
use crate::numeric::*;
|
use crate::numeric::*;
|
||||||
use crate::server::{Server, VERSION};
|
use crate::server::{Server, VERSION};
|
||||||
|
use crate::users::User;
|
||||||
use crate::Uid;
|
use crate::Uid;
|
||||||
|
|
||||||
pub fn commands() -> Vec<Box<dyn Command>> {
|
pub fn commands() -> Vec<Box<dyn Command>> {
|
||||||
|
|
@ -214,14 +215,34 @@ impl Command for Who {
|
||||||
}
|
}
|
||||||
fn handle(&self, s: &mut Server, uid: Uid, params: &[String]) -> CmdResult {
|
fn handle(&self, s: &mut Server, uid: Uid, params: &[String]) -> CmdResult {
|
||||||
let target = ¶ms[0];
|
let target = ¶ms[0];
|
||||||
if target.starts_with('#') {
|
// WHOX: an options token containing '%' selects the reply fields (354).
|
||||||
let key = target.to_ascii_lowercase();
|
// `WHO <target> <filter>%<fields>[,<querytype>]`, e.g. `WHO #c %cuhnat,152`.
|
||||||
let multi = s
|
let whox = params
|
||||||
.users
|
.get(1)
|
||||||
.get(&uid)
|
.and_then(|o| o.split_once('%'))
|
||||||
.map(|u| u.caps.multi_prefix)
|
.map(|(_, spec)| {
|
||||||
.unwrap_or(false);
|
let (fields, qtype) = spec.split_once(',').unwrap_or((spec, ""));
|
||||||
let rows: Vec<(Uid, String, String)> = match s.channels.get(&key) {
|
(fields.to_string(), qtype.to_string())
|
||||||
|
});
|
||||||
|
let asker_oper = s.is_oper(uid);
|
||||||
|
let multi = s
|
||||||
|
.users
|
||||||
|
.get(&uid)
|
||||||
|
.map(|u| u.caps.multi_prefix)
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
// (uid, channel-name-or-"*", prefix-string) for each user to report
|
||||||
|
let rows: Vec<(Uid, String, String)> = if target.starts_with('#') {
|
||||||
|
match s.channels.get(&target.to_ascii_lowercase()) {
|
||||||
|
// +s/+p: don't reveal a secret/private channel's members to
|
||||||
|
// non-members (opers excepted)
|
||||||
|
Some(ch)
|
||||||
|
if (ch.modes.secret || ch.modes.private)
|
||||||
|
&& !ch.members.contains_key(&uid)
|
||||||
|
&& !asker_oper =>
|
||||||
|
{
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
Some(ch) => {
|
Some(ch) => {
|
||||||
let name = ch.name.clone();
|
let name = ch.name.clone();
|
||||||
ch.members
|
ch.members
|
||||||
|
|
@ -237,37 +258,131 @@ impl Command for Who {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
None => Vec::new(),
|
None => Vec::new(),
|
||||||
};
|
|
||||||
for (m, name, pfx) in rows {
|
|
||||||
if let Some(u) = s.users.get(&m) {
|
|
||||||
let row = format!(
|
|
||||||
"{name} {} {} {} {} H{pfx} :0 {}",
|
|
||||||
u.ident,
|
|
||||||
u.host_display(),
|
|
||||||
s.name,
|
|
||||||
u.nick,
|
|
||||||
u.realname
|
|
||||||
);
|
|
||||||
s.numeric(uid, RPL_WHOREPLY, &row);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if let Some(tuid) = s.find_nick(target) {
|
} else if let Some(tuid) = s.find_nick(target) {
|
||||||
let u = &s.users[&tuid];
|
vec![(tuid, "*".to_string(), String::new())]
|
||||||
let row = format!(
|
} else {
|
||||||
"* {} {} {} {} H :0 {}",
|
Vec::new()
|
||||||
u.ident,
|
};
|
||||||
u.host_display(),
|
|
||||||
s.name,
|
let now = crate::server::now();
|
||||||
u.nick,
|
for (m, chan, pfx) in rows {
|
||||||
u.realname
|
let (code, row) = {
|
||||||
);
|
let Some(u) = s.users.get(&m) else { continue };
|
||||||
s.numeric(uid, RPL_WHOREPLY, &row);
|
// flags: H (here) / G (gone/away), then * for opers, then prefixes
|
||||||
|
let mut flags = String::from(if u.flags.away.is_some() { "G" } else { "H" });
|
||||||
|
if u.flags.oper && (!u.flags.hideoper || asker_oper) {
|
||||||
|
flags.push('*');
|
||||||
|
}
|
||||||
|
flags.push_str(&pfx);
|
||||||
|
match &whox {
|
||||||
|
Some((fields, qtype)) => (
|
||||||
|
RPL_WHOSPCRPL,
|
||||||
|
whox_row(
|
||||||
|
&s.name,
|
||||||
|
u,
|
||||||
|
&chan,
|
||||||
|
&flags,
|
||||||
|
fields,
|
||||||
|
qtype,
|
||||||
|
asker_oper,
|
||||||
|
m == uid,
|
||||||
|
now,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
None => (
|
||||||
|
RPL_WHOREPLY,
|
||||||
|
format!(
|
||||||
|
"{chan} {} {} {} {} {flags} :0 {}",
|
||||||
|
u.ident,
|
||||||
|
u.host_display(),
|
||||||
|
s.name,
|
||||||
|
u.nick,
|
||||||
|
u.realname
|
||||||
|
),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
s.numeric(uid, code, &row);
|
||||||
}
|
}
|
||||||
s.numeric(uid, RPL_ENDOFWHO, &format!("{target} :End of /WHO list"));
|
s.numeric(uid, RPL_ENDOFWHO, &format!("{target} :End of /WHO list"));
|
||||||
CmdResult::Ok
|
CmdResult::Ok
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Build a WHOX (354) reply body: the requested `fields` in their fixed output
|
||||||
|
/// order (never the request order), realname always last. Unknown field letters
|
||||||
|
/// are ignored. The real IP (`i`) is shown only to opers or to the user
|
||||||
|
/// themselves, so host-cloaking isn't defeated.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn whox_row(
|
||||||
|
server: &str,
|
||||||
|
u: &User,
|
||||||
|
chan: &str,
|
||||||
|
flags: &str,
|
||||||
|
fields: &str,
|
||||||
|
qtype: &str,
|
||||||
|
asker_oper: bool,
|
||||||
|
is_self: bool,
|
||||||
|
now: u64,
|
||||||
|
) -> String {
|
||||||
|
let has = |c: char| fields.contains(c);
|
||||||
|
let mut parts: Vec<String> = Vec::new();
|
||||||
|
if has('t') {
|
||||||
|
parts.push(if qtype.is_empty() {
|
||||||
|
"0".to_string()
|
||||||
|
} else {
|
||||||
|
qtype.to_string()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if has('c') {
|
||||||
|
parts.push(chan.to_string());
|
||||||
|
}
|
||||||
|
if has('u') {
|
||||||
|
parts.push(u.ident.clone());
|
||||||
|
}
|
||||||
|
if has('i') {
|
||||||
|
parts.push(if asker_oper || is_self {
|
||||||
|
u.addr.ip().to_string()
|
||||||
|
} else {
|
||||||
|
"255.255.255.255".to_string()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if has('h') {
|
||||||
|
parts.push(u.host_display().to_string());
|
||||||
|
}
|
||||||
|
if has('s') {
|
||||||
|
parts.push(server.to_string());
|
||||||
|
}
|
||||||
|
if has('n') {
|
||||||
|
parts.push(u.nick.clone());
|
||||||
|
}
|
||||||
|
if has('f') {
|
||||||
|
parts.push(flags.to_string());
|
||||||
|
}
|
||||||
|
if has('d') {
|
||||||
|
parts.push("0".to_string()); // hopcount (local users)
|
||||||
|
}
|
||||||
|
if has('l') {
|
||||||
|
parts.push(now.saturating_sub(u.last_active).to_string()); // idle seconds
|
||||||
|
}
|
||||||
|
if has('a') {
|
||||||
|
parts.push(u.account.clone().unwrap_or_else(|| "0".to_string()));
|
||||||
|
}
|
||||||
|
if has('o') {
|
||||||
|
parts.push("n/a".to_string()); // channel op-level
|
||||||
|
}
|
||||||
|
let mut row = parts.join(" ");
|
||||||
|
if has('r') {
|
||||||
|
if !row.is_empty() {
|
||||||
|
row.push(' ');
|
||||||
|
}
|
||||||
|
row.push(':');
|
||||||
|
row.push_str(&u.realname);
|
||||||
|
}
|
||||||
|
row
|
||||||
|
}
|
||||||
|
|
||||||
struct Lusers;
|
struct Lusers;
|
||||||
impl Command for Lusers {
|
impl Command for Lusers {
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
|
|
|
||||||
|
|
@ -317,7 +317,7 @@ fn deliver(s: &mut Server, uid: Uid, params: &[String], notice: bool) -> CmdResu
|
||||||
if m == uid || s.users.get(&m).map(|u| u.flags.deaf).unwrap_or(false) {
|
if m == uid || s.users.get(&m).map(|u| u.flags.deaf).unwrap_or(false) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
s.send_tagged(m, &ctags, &msgid, &line);
|
s.send_tagged(m, uid, &ctags, &msgid, &line);
|
||||||
}
|
}
|
||||||
// echo-message: give the sender their own copy if they asked for one
|
// echo-message: give the sender their own copy if they asked for one
|
||||||
if s.users
|
if s.users
|
||||||
|
|
@ -325,7 +325,7 @@ fn deliver(s: &mut Server, uid: Uid, params: &[String], notice: bool) -> CmdResu
|
||||||
.map(|u| u.caps.echo_message)
|
.map(|u| u.caps.echo_message)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
s.send_tagged(uid, &ctags, &msgid, &line);
|
s.send_tagged(uid, uid, &ctags, &msgid, &line);
|
||||||
}
|
}
|
||||||
// propagate to linked servers that have members in this channel
|
// propagate to linked servers that have members in this channel
|
||||||
s.send_channel_to_links(uid, &key, target, cmd, &body);
|
s.send_channel_to_links(uid, &key, target, cmd, &body);
|
||||||
|
|
@ -427,14 +427,14 @@ fn deliver(s: &mut Server, uid: Uid, params: &[String], notice: bool) -> CmdResu
|
||||||
let ctags = s.line_ctags.clone();
|
let ctags = s.line_ctags.clone();
|
||||||
let msgid = s.next_msgid();
|
let msgid = s.next_msgid();
|
||||||
if !silenced {
|
if !silenced {
|
||||||
s.send_tagged(tuid, &ctags, &msgid, &pm);
|
s.send_tagged(tuid, uid, &ctags, &msgid, &pm);
|
||||||
}
|
}
|
||||||
if s.users
|
if s.users
|
||||||
.get(&uid)
|
.get(&uid)
|
||||||
.map(|u| u.caps.echo_message)
|
.map(|u| u.caps.echo_message)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
s.send_tagged(uid, &ctags, &msgid, &pm);
|
s.send_tagged(uid, uid, &ctags, &msgid, &pm);
|
||||||
}
|
}
|
||||||
// if the recipient is away, tell the sender (PRIVMSG only, not if silenced)
|
// if the recipient is away, tell the sender (PRIVMSG only, not if silenced)
|
||||||
if !notice && !silenced {
|
if !notice && !silenced {
|
||||||
|
|
@ -467,6 +467,7 @@ fn deliver(s: &mut Server, uid: Uid, params: &[String], notice: bool) -> CmdResu
|
||||||
let ctags = s.line_ctags.clone();
|
let ctags = s.line_ctags.clone();
|
||||||
let msgid = s.next_msgid();
|
let msgid = s.next_msgid();
|
||||||
s.send_tagged(
|
s.send_tagged(
|
||||||
|
uid,
|
||||||
uid,
|
uid,
|
||||||
&ctags,
|
&ctags,
|
||||||
&msgid,
|
&msgid,
|
||||||
|
|
@ -564,7 +565,7 @@ impl Command for TagMsg {
|
||||||
.map(|u| u.caps.message_tags)
|
.map(|u| u.caps.message_tags)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
s.send_tagged(m, &ctags, &msgid, &body);
|
s.send_tagged(m, uid, &ctags, &msgid, &body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let Some(tuid) = s.find_nick(target) {
|
} else if let Some(tuid) = s.find_nick(target) {
|
||||||
|
|
@ -573,14 +574,14 @@ impl Command for TagMsg {
|
||||||
.map(|u| u.caps.message_tags)
|
.map(|u| u.caps.message_tags)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
s.send_tagged(tuid, &ctags, &msgid, &body);
|
s.send_tagged(tuid, uid, &ctags, &msgid, &body);
|
||||||
}
|
}
|
||||||
if s.users
|
if s.users
|
||||||
.get(&uid)
|
.get(&uid)
|
||||||
.map(|u| u.caps.echo_message)
|
.map(|u| u.caps.echo_message)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
s.send_tagged(uid, &ctags, &msgid, &body);
|
s.send_tagged(uid, uid, &ctags, &msgid, &body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CmdResult::Ok
|
CmdResult::Ok
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,7 @@ pub const RPL_CREATIONTIME: u16 = 329;
|
||||||
pub const RPL_NOTOPIC: u16 = 331;
|
pub const RPL_NOTOPIC: u16 = 331;
|
||||||
pub const RPL_TOPIC: u16 = 332;
|
pub const RPL_TOPIC: u16 = 332;
|
||||||
pub const RPL_WHOREPLY: u16 = 352;
|
pub const RPL_WHOREPLY: u16 = 352;
|
||||||
|
pub const RPL_WHOSPCRPL: u16 = 354; // WHOX: field-selected WHO reply
|
||||||
pub const RPL_NAMREPLY: u16 = 353;
|
pub const RPL_NAMREPLY: u16 = 353;
|
||||||
pub const RPL_ENDOFNAMES: u16 = 366;
|
pub const RPL_ENDOFNAMES: u16 = 366;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -446,6 +446,46 @@ impl Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// IRCv3 standard reply (`FAIL`/`WARN`/`NOTE`): structured, machine-readable
|
||||||
|
/// command feedback. Sent in the `:server FAIL <command> <code> :<desc>` form
|
||||||
|
/// to clients that negotiated `standard-replies`; others get the description as
|
||||||
|
/// a plain server NOTICE so the human-readable text still reaches them.
|
||||||
|
pub fn fail(&self, uid: Uid, command: &str, code: &str, desc: &str) {
|
||||||
|
self.standard_reply(uid, "FAIL", command, code, desc);
|
||||||
|
}
|
||||||
|
pub fn warn(&self, uid: Uid, command: &str, code: &str, desc: &str) {
|
||||||
|
self.standard_reply(uid, "WARN", command, code, desc);
|
||||||
|
}
|
||||||
|
pub fn note(&self, uid: Uid, command: &str, code: &str, desc: &str) {
|
||||||
|
self.standard_reply(uid, "NOTE", command, code, desc);
|
||||||
|
}
|
||||||
|
fn standard_reply(&self, uid: Uid, kind: &str, command: &str, code: &str, desc: &str) {
|
||||||
|
let cap = self
|
||||||
|
.users
|
||||||
|
.get(&uid)
|
||||||
|
.map(|u| u.caps.standard_replies)
|
||||||
|
.unwrap_or(false);
|
||||||
|
if cap {
|
||||||
|
self.send(
|
||||||
|
uid,
|
||||||
|
format!(":{} {kind} {command} {code} :{desc}", self.name),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let nick = self
|
||||||
|
.users
|
||||||
|
.get(&uid)
|
||||||
|
.map(|u| {
|
||||||
|
if u.nick.is_empty() {
|
||||||
|
"*".to_string()
|
||||||
|
} else {
|
||||||
|
u.nick.clone()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| "*".to_string());
|
||||||
|
self.send(uid, format!(":{} NOTICE {nick} :{desc}", self.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Send a line to every member of a channel, optionally skipping one uid.
|
/// Send a line to every member of a channel, optionally skipping one uid.
|
||||||
pub fn to_channel(&self, key: &str, line: &str, except: Option<Uid>) {
|
pub fn to_channel(&self, key: &str, line: &str, except: Option<Uid>) {
|
||||||
if let Some(ch) = self.channels.get(key) {
|
if let Some(ch) = self.channels.get(key) {
|
||||||
|
|
@ -457,15 +497,23 @@ impl Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send a message body (`:prefix CMD …`) to `uid`, composing its IRCv3 tag
|
/// Send a message body (`:prefix CMD …`) from `src` to `uid`, composing its
|
||||||
/// prefix from that client's caps: `time=` (server-time) plus the client-only
|
/// IRCv3 tag prefix from *that recipient's* caps: `time=` (server-time),
|
||||||
/// tags `ctags` (message-tags). Used for PRIVMSG / NOTICE / TAGMSG delivery.
|
/// `account=` (account-tag, from the sender's login) plus the client-only tags
|
||||||
pub fn send_tagged(&self, uid: Uid, ctags: &str, msgid: &str, body: &str) {
|
/// `ctags` and `msgid` (message-tags). For PRIVMSG / NOTICE / TAGMSG delivery.
|
||||||
|
pub fn send_tagged(&self, uid: Uid, src: Uid, ctags: &str, msgid: &str, body: &str) {
|
||||||
if let Some(u) = self.users.get(&uid) {
|
if let Some(u) = self.users.get(&uid) {
|
||||||
let mut tags: Vec<String> = Vec::new();
|
let mut tags: Vec<String> = Vec::new();
|
||||||
if u.caps.server_time {
|
if u.caps.server_time {
|
||||||
tags.push(format!("time={}", iso_time(now())));
|
tags.push(format!("time={}", iso_time(now())));
|
||||||
}
|
}
|
||||||
|
// account-tag: label a message with the sender's services account, so
|
||||||
|
// recipients see who's authenticated without a separate WHOIS.
|
||||||
|
if u.caps.account_tag {
|
||||||
|
if let Some(acct) = self.users.get(&src).and_then(|su| su.account.as_deref()) {
|
||||||
|
tags.push(format!("account={acct}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
// msgid (IRCv3): a unique, server-assigned id per message so clients
|
// msgid (IRCv3): a unique, server-assigned id per message so clients
|
||||||
// can reference it (reactions, replies, redaction). Tag-only feature,
|
// can reference it (reactions, replies, redaction). Tag-only feature,
|
||||||
// so it goes to message-tags clients alongside any client `+`-tags.
|
// so it goes to message-tags clients alongside any client `+`-tags.
|
||||||
|
|
|
||||||
10
src/users.rs
10
src/users.rs
|
|
@ -93,6 +93,8 @@ pub const SUPPORTED_CAPS: &[&str] = &[
|
||||||
"invite-notify",
|
"invite-notify",
|
||||||
"setname",
|
"setname",
|
||||||
"extended-monitor",
|
"extended-monitor",
|
||||||
|
"account-tag",
|
||||||
|
"standard-replies",
|
||||||
"cap-notify",
|
"cap-notify",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -114,6 +116,8 @@ pub struct Caps {
|
||||||
pub invite_notify: bool,
|
pub invite_notify: bool,
|
||||||
pub setname: bool,
|
pub setname: bool,
|
||||||
pub extended_monitor: bool, // route away/account/chghost/setname for MONITOR targets
|
pub extended_monitor: bool, // route away/account/chghost/setname for MONITOR targets
|
||||||
|
pub account_tag: bool, // prepend account=<name> tag on messages from logged-in users
|
||||||
|
pub standard_replies: bool, // understands FAIL/WARN/NOTE structured replies
|
||||||
pub cap_notify: bool,
|
pub cap_notify: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -152,6 +156,8 @@ impl Caps {
|
||||||
"invite-notify" => self.invite_notify,
|
"invite-notify" => self.invite_notify,
|
||||||
"setname" => self.setname,
|
"setname" => self.setname,
|
||||||
"extended-monitor" => self.extended_monitor,
|
"extended-monitor" => self.extended_monitor,
|
||||||
|
"account-tag" => self.account_tag,
|
||||||
|
"standard-replies" => self.standard_replies,
|
||||||
"cap-notify" => self.cap_notify,
|
"cap-notify" => self.cap_notify,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
|
|
@ -173,6 +179,8 @@ impl Caps {
|
||||||
"invite-notify" => &mut self.invite_notify,
|
"invite-notify" => &mut self.invite_notify,
|
||||||
"setname" => &mut self.setname,
|
"setname" => &mut self.setname,
|
||||||
"extended-monitor" => &mut self.extended_monitor,
|
"extended-monitor" => &mut self.extended_monitor,
|
||||||
|
"account-tag" => &mut self.account_tag,
|
||||||
|
"standard-replies" => &mut self.standard_replies,
|
||||||
"cap-notify" => &mut self.cap_notify,
|
"cap-notify" => &mut self.cap_notify,
|
||||||
_ => return false,
|
_ => return false,
|
||||||
};
|
};
|
||||||
|
|
@ -370,7 +378,7 @@ impl Server {
|
||||||
uid,
|
uid,
|
||||||
RPL_ISUPPORT,
|
RPL_ISUPPORT,
|
||||||
&format!(
|
&format!(
|
||||||
"CHANTYPES=# PREFIX=(qaohv)~&@%+ CHANMODES=beIg,k,lfjFL,CGMNORSTcimnpstuz EXTBAN=,cmn WATCH=128 MONITOR=128 SILENCE=32 CALLERID=g CASEMAPPING=ascii NICKLEN=30 CHANNELLEN=50 NETWORK={} :are supported by this server",
|
"CHANTYPES=# PREFIX=(qaohv)~&@%+ CHANMODES=beIg,k,lfjFL,CGMNORSTcimnpstuz EXTBAN=,cmn WATCH=128 MONITOR=128 SILENCE=32 CALLERID=g WHOX 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