Skip to content

Commit 2a3fc9c

Browse files
committed
IRCv3 no-implicit-names support
1 parent 55e1e4b commit 2a3fc9c

File tree

5 files changed

+168
-53
lines changed

5 files changed

+168
-53
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Added:
2222
- Settings to reroute direct PRIVMSG and/or NOTICE messages to another buffer (`servers.<name>.reroute.query` and `servers.<name>.reroute.notice`)
2323
- `channel-context` support
2424
- Expanded `tooltips` setting to allow hiding auto-complete tooltips
25+
- `no-implicit-names` support
2526

2627
Changed:
2728

data/src/capabilities.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ pub enum Capability {
3333
MessageTags,
3434
Multiline,
3535
MultiPrefix,
36+
NoImplicitNames,
3637
ReadMarker,
3738
Sasl,
3839
ServerTime,
@@ -60,6 +61,9 @@ impl FromStr for Capability {
6061
"labeled-response" => Ok(Self::LabeledResponse),
6162
"message-tags" => Ok(Self::MessageTags),
6263
"multi-prefix" => Ok(Self::MultiPrefix),
64+
"no-implicit-names" => Ok(Self::NoImplicitNames),
65+
// TODO(quaff): remove `draft/no-implicit-names` support when ergo & soju have both been upgraded
66+
"draft/no-implicit-names" => Ok(Self::NoImplicitNames),
6367
"server-time" => Ok(Self::ServerTime),
6468
"setname" => Ok(Self::Setname),
6569
"soju.im/bouncer-networks" => Ok(Self::BouncerNetworks),
@@ -345,6 +349,19 @@ impl Capabilities {
345349
requested.push("sasl");
346350
}
347351

352+
if self.pending.contains("no-implicit-names")
353+
&& !self.acknowledged(Capability::NoImplicitNames)
354+
{
355+
requested.push("no-implicit-names");
356+
}
357+
358+
// TODO(quaff): remove `draft/no-implicit-names` support when ergo & soju have both been upgraded
359+
if self.pending.contains("draft/no-implicit-names")
360+
&& !self.acknowledged(Capability::NoImplicitNames)
361+
{
362+
requested.push("draft/no-implicit-names");
363+
}
364+
348365
if let Some(multiline) = self
349366
.pending
350367
.iter()

data/src/client.rs

Lines changed: 123 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1733,10 +1733,28 @@ impl Client {
17331733
.iter()
17341734
.any(|who_poll| who_poll.channel == target_channel)
17351735
{
1736-
self.who_polls.push_back(WhoPoll {
1736+
let mut who_poll = WhoPoll {
17371737
channel: target_channel.clone(),
17381738
status: WhoStatus::Joined,
1739-
});
1739+
};
1740+
1741+
if self
1742+
.capabilities
1743+
.acknowledged(Capability::NoImplicitNames)
1744+
{
1745+
let message = prepare_who_polls(
1746+
&self.isupport,
1747+
&self.capabilities,
1748+
&mut who_poll,
1749+
);
1750+
self.send(
1751+
None,
1752+
message.into(),
1753+
TokenPriority::High,
1754+
);
1755+
}
1756+
1757+
self.who_polls.push_back(who_poll);
17401758
}
17411759

17421760
if !self.mode_requests.iter().any(|mode_request| {
@@ -1810,6 +1828,10 @@ impl Client {
18101828
}
18111829
Command::Numeric(RPL_WHOREPLY, args) => {
18121830
let channel = ok!(args.get(1));
1831+
let user = ok!(args.get(2));
1832+
let host = ok!(args.get(3));
1833+
let nick = ok!(args.get(5));
1834+
let flags = ok!(args.get(6));
18131835

18141836
let casemapping = self.casemapping();
18151837

@@ -1825,8 +1847,8 @@ impl Client {
18251847
self.chanmap.get_mut(&target_channel)
18261848
{
18271849
client_channel.update_user_away(
1828-
ok!(args.get(5)),
1829-
ok!(args.get(6)),
1850+
nick,
1851+
flags,
18301852
casemapping,
18311853
);
18321854

@@ -1844,6 +1866,20 @@ impl Client {
18441866
self.server
18451867
);
18461868
}
1869+
1870+
if let Ok(mut user) = User::parse_from_whoreply(
1871+
nick,
1872+
flags,
1873+
user,
1874+
host,
1875+
casemapping,
1876+
isupport::get_prefix(&self.isupport),
1877+
) {
1878+
if flags.starts_with('G') {
1879+
user.update_away(true);
1880+
}
1881+
client_channel.users.insert(user);
1882+
}
18471883
}
18481884

18491885
if !user_request {
@@ -1865,7 +1901,13 @@ impl Client {
18651901
}
18661902
}
18671903
Command::Numeric(RPL_WHOSPCRPL, args) => {
1904+
let token = ok!(args.get(1));
18681905
let channel = ok!(args.get(2));
1906+
let user = ok!(args.get(3));
1907+
let host = ok!(args.get(4));
1908+
let nick = ok!(args.get(5));
1909+
let flags = ok!(args.get(6));
1910+
let account = args.get(7);
18691911

18701912
let casemapping = self.casemapping();
18711913

@@ -1890,8 +1932,7 @@ impl Client {
18901932
_,
18911933
Some(request_token),
18921934
) if matches!(source, WhoSource::Poll) => {
1893-
if let Ok(token) =
1894-
ok!(args.get(1)).parse::<WhoToken>()
1935+
if let Ok(token) = token.parse::<WhoToken>()
18951936
&& *request_token == token
18961937
{
18971938
who_poll.status = WhoStatus::Receiving(
@@ -1926,36 +1967,49 @@ impl Client {
19261967
&who_poll.status
19271968
{
19281969
// Check token to ~ensure reply is to poll request
1929-
if let Ok(token) =
1930-
ok!(args.get(1)).parse::<WhoToken>()
1931-
{
1970+
if let Ok(token) = token.parse::<WhoToken>() {
19321971
if token == WhoXPollParameters::Default.token()
19331972
{
19341973
client_channel.update_user_away(
1935-
ok!(args.get(3)),
1936-
ok!(args.get(4)),
1974+
nick,
1975+
flags,
19371976
casemapping,
19381977
);
19391978
} else if token
19401979
== WhoXPollParameters::WithAccountName
19411980
.token()
19421981
{
1943-
let user = ok!(args.get(3));
1944-
19451982
client_channel.update_user_away(
1946-
user,
1947-
ok!(args.get(4)),
1983+
nick,
1984+
flags,
19481985
casemapping,
19491986
);
19501987

19511988
client_channel.update_user_accountname(
1952-
user,
1953-
ok!(args.get(5)),
1989+
nick,
1990+
ok!(account),
19541991
casemapping,
19551992
);
19561993
}
19571994
}
19581995
}
1996+
1997+
if let Ok(mut user) = User::parse_from_whoreply(
1998+
nick,
1999+
flags,
2000+
user,
2001+
host,
2002+
casemapping,
2003+
isupport::get_prefix(&self.isupport),
2004+
) {
2005+
if let Some(account) = account {
2006+
user = user.with_accountname(account);
2007+
}
2008+
if flags.starts_with('G') {
2009+
user.update_away(true);
2010+
}
2011+
client_channel.users.insert(user);
2012+
}
19592013
}
19602014

19612015
if !user_request {
@@ -2012,8 +2066,17 @@ impl Client {
20122066
matches!(who_poll.status, WhoStatus::Received)
20132067
}))
20142068
{
2015-
self.who_polls[pos].status =
2016-
WhoStatus::Waiting(Instant::now());
2069+
if !self
2070+
.capabilities
2071+
.acknowledged(Capability::NoImplicitNames)
2072+
|| matches!(
2073+
self.who_polls[pos].status,
2074+
WhoStatus::Received
2075+
)
2076+
{
2077+
self.who_polls[pos].status =
2078+
WhoStatus::Waiting(Instant::now());
2079+
}
20172080

20182081
if pos != 0
20192082
&& let Some(who_poll) =
@@ -3916,6 +3979,9 @@ impl Client {
39163979
let request = match &who_poll.status {
39173980
WhoStatus::Joined => {
39183981
(self.capabilities.acknowledged(Capability::AwayNotify)
3982+
|| self
3983+
.capabilities
3984+
.acknowledged(Capability::NoImplicitNames)
39193985
|| self.config.who_poll_enabled)
39203986
.then_some(Request::Poll)
39213987
}
@@ -3961,38 +4027,11 @@ impl Client {
39614027
}
39624028
);
39634029

3964-
let message =
3965-
if self.isupport.contains_key(&isupport::Kind::WHOX) {
3966-
let whox_params = if self
3967-
.capabilities
3968-
.acknowledged(Capability::AccountNotify)
3969-
{
3970-
WhoXPollParameters::WithAccountName
3971-
} else {
3972-
WhoXPollParameters::Default
3973-
};
3974-
3975-
who_poll.status = WhoStatus::Requested(
3976-
WhoSource::Poll,
3977-
Instant::now(),
3978-
Some(whox_params.token()),
3979-
);
3980-
3981-
command!(
3982-
"WHO",
3983-
who_poll.channel.to_string(),
3984-
whox_params.fields().to_string(),
3985-
whox_params.token().to_owned()
3986-
)
3987-
} else {
3988-
who_poll.status = WhoStatus::Requested(
3989-
WhoSource::Poll,
3990-
Instant::now(),
3991-
None,
3992-
);
3993-
3994-
command!("WHO", who_poll.channel.to_string())
3995-
};
4030+
let message = prepare_who_polls(
4031+
&self.isupport,
4032+
&self.capabilities,
4033+
who_poll,
4034+
);
39964035

39974036
self.send(None, message.into(), TokenPriority::Low);
39984037
}
@@ -4163,6 +4202,39 @@ fn compare_channels_default(chantypes: &[char], a: &str, b: &str) -> Ordering {
41634202
a.cmp(b)
41644203
}
41654204

4205+
fn prepare_who_polls(
4206+
isupport: &HashMap<isupport::Kind, isupport::Parameter>,
4207+
capabilities: &Capabilities,
4208+
who_poll: &mut WhoPoll,
4209+
) -> irc::proto::Message {
4210+
if isupport.contains_key(&isupport::Kind::WHOX) {
4211+
let whox_params =
4212+
if capabilities.acknowledged(Capability::AccountNotify) {
4213+
WhoXPollParameters::WithAccountName
4214+
} else {
4215+
WhoXPollParameters::Default
4216+
};
4217+
4218+
who_poll.status = WhoStatus::Requested(
4219+
WhoSource::Poll,
4220+
Instant::now(),
4221+
Some(whox_params.token()),
4222+
);
4223+
4224+
command!(
4225+
"WHO",
4226+
who_poll.channel.to_string(),
4227+
whox_params.fields().to_string(),
4228+
whox_params.token().to_owned()
4229+
)
4230+
} else {
4231+
who_poll.status =
4232+
WhoStatus::Requested(WhoSource::Poll, Instant::now(), None);
4233+
4234+
command!("WHO", who_poll.channel.to_string())
4235+
}
4236+
}
4237+
41664238
fn compare_channels(
41674239
config: &config::server::Server,
41684240
chantypes: &[char],

data/src/isupport.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,8 +1134,8 @@ pub enum WhoXPollParameters {
11341134
impl WhoXPollParameters {
11351135
pub fn fields(&self) -> &'static str {
11361136
match self {
1137-
WhoXPollParameters::Default => "tcnf",
1138-
WhoXPollParameters::WithAccountName => "tcnfa",
1137+
WhoXPollParameters::Default => "tcnfuh",
1138+
WhoXPollParameters::WithAccountName => "tcnfuha",
11391139
}
11401140
}
11411141

data/src/user.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,31 @@ impl User {
424424
away: false,
425425
})
426426
}
427+
428+
pub fn parse_from_whoreply(
429+
nick: &String,
430+
flags: &str,
431+
user: &String,
432+
host: &String,
433+
casemapping: isupport::CaseMap,
434+
prefix: Option<&[isupport::PrefixMap]>,
435+
) -> Result<Self, ParseUserError> {
436+
let access_level: String = flags[1..]
437+
.chars()
438+
.filter(|c| {
439+
if let Some(prefix) = prefix {
440+
prefix.iter().any(|p| p.prefix == *c)
441+
} else {
442+
AccessLevel::try_from(*c).is_ok()
443+
}
444+
})
445+
.collect();
446+
User::parse(
447+
format!("{access_level}{nick}!{user}@{host}").as_str(),
448+
casemapping,
449+
prefix,
450+
)
451+
}
427452
}
428453

429454
fn parse_user_names(

0 commit comments

Comments
 (0)