Skip to content

Commit

Permalink
Merge pull request #349 from andymandias/feat/isupport
Browse files Browse the repository at this point in the history
ISUPPORT Support
  • Loading branch information
tarkah authored Apr 30, 2024
2 parents 09d55ac + abf2f8c commit 6eb019b
Show file tree
Hide file tree
Showing 17 changed files with 1,592 additions and 65 deletions.
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ Added:
- Reload configuration file from within the application (<kbd>Ctrl</kbd> + <kbd>r</kbd> (macOS: <kbd>⌘</kbd> + <kbd>r</kbd>))
- Allow configuration of internal messages in buffer (see [buffer configuration](https://halloy.squidowl.org/configuration/buffer.html#bufferinternal_messages-section))
- User information added to context menu
- Support for IRCv3 CAP NEW and CAP DEL subcommands
- Enable support for IRCv3 `multi-prefix`
- Support for IRCv3 `CAP NEW` and `CAP DEL` subcommands
- Enable support for IRCv3 `multi-prefix`, `WHOX`, and `UTF8ONLY`
- Dynamic commands and tooltips added to command auto-completion via `ISUPPORT`
- Added support for `socks5` proxy configuration (see [proxy configuration](https://halloy.squidowl.org/configuration/proxy.html))
- Added support for `http` proxy configuration (see [proxy configuration](https://halloy.squidowl.org/configuration/proxy.html))

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Join **#halloy** on libera.chat if you have questions or looking for help.
* [sasl-3.1](https://ircv3.net/specs/extensions/sasl-3.1)
* [cap-notify](https://ircv3.net/specs/extensions/capability-negotiation.html#cap-notify)
* [multi-prefix](https://ircv3.net/specs/extensions/multi-prefix)
* [`WHOX`](https://ircv3.net/specs/extensions/whox)
* [`UTF8ONLY`](https://ircv3.net/specs/extensions/utf8-only)
* SASL support
* DCC Send
* Keyboard shortcuts
Expand Down
2 changes: 2 additions & 0 deletions book/src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
* [sasl-3.1](https://ircv3.net/specs/extensions/sasl-3.1)
* [cap-notify](https://ircv3.net/specs/extensions/capability-negotiation.html#cap-notify)
* [multi-prefix](https://ircv3.net/specs/extensions/multi-prefix)
* [`WHOX`](https://ircv3.net/specs/extensions/whox)
* [`UTF8ONLY`](https://ircv3.net/specs/extensions/utf8-only)
* SASL support
* DCC Send
* Keyboard shortcuts
Expand Down
166 changes: 141 additions & 25 deletions data/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::time::{Duration, Instant};
use crate::message::server_time;
use crate::time::Posix;
use crate::user::{Nick, NickRef};
use crate::{config, dcc, message, mode, Buffer, Server, User};
use crate::{config, dcc, isupport, message, mode, Buffer, Server, User};
use crate::{file_transfer, server};

const HIGHLIGHT_BLACKOUT_INTERVAL: Duration = Duration::from_secs(5);
Expand Down Expand Up @@ -88,6 +88,7 @@ pub struct Client {
supports_away_notify: bool,
highlight_blackout: HighlightBlackout,
registration_required_channels: Vec<String>,
isupport: HashMap<isupport::Kind, isupport::Parameter>,
}

impl fmt::Debug for Client {
Expand Down Expand Up @@ -137,6 +138,7 @@ impl Client {
supports_away_notify: false,
highlight_blackout: HighlightBlackout::Blackout(Instant::now()),
registration_required_channels: vec![],
isupport: HashMap::new(),
}
}

Expand Down Expand Up @@ -658,8 +660,21 @@ impl Client {

if let Some(state) = self.chanmap.get_mut(channel) {
// Sends WHO to get away state on users.
let _ = self.handle.try_send(command!("WHO", channel));
state.last_who = Some(WhoStatus::Requested(Instant::now()));
if self.isupport.get(&isupport::Kind::WHOX).is_some() {
let _ = self.handle.try_send(command!(
"WHO",
channel,
"tcnf",
isupport::WHO_POLL_TOKEN.to_owned()
));
state.last_who = Some(WhoStatus::Requested(
Instant::now(),
Some(isupport::WHO_POLL_TOKEN),
));
} else {
let _ = self.handle.try_send(command!("WHO", channel));
state.last_who = Some(WhoStatus::Requested(Instant::now(), None));
}
log::debug!("[{}] {channel} - WHO requested", self.server);
}
} else if let Some(channel) = self.chanmap.get_mut(channel) {
Expand All @@ -680,25 +695,36 @@ impl Client {

if proto::is_channel(target) {
if let Some(channel) = self.chanmap.get_mut(target) {
if matches!(channel.last_who, Some(WhoStatus::Requested(_)) | None) {
channel.last_who = Some(WhoStatus::Receiving);
channel.update_user_away(args.get(5)?, args.get(6)?);

if matches!(channel.last_who, Some(WhoStatus::Requested(_, None)) | None) {
channel.last_who = Some(WhoStatus::Receiving(None));
log::debug!("[{}] {target} - WHO receiving...", self.server);
// We requested, don't save to history
return None;
}
}
}
}
Command::Numeric(RPL_WHOSPCRPL, args) => {
let target = args.get(2)?;

// H = Here, G = gone (away)
let flags = args.get(6)?.chars().collect::<Vec<char>>();
let away = *(flags.first()?) == 'G';

let lookup = User::from(Nick::from(args[5].clone()));

if let Some(mut user) = channel.users.take(&lookup) {
user.update_away(away);
channel.users.insert(user);
}
if proto::is_channel(target) {
if let Some(channel) = self.chanmap.get_mut(target) {
channel.update_user_away(args.get(3)?, args.get(4)?);

// We requested, don't save to history
if matches!(channel.last_who, Some(WhoStatus::Receiving)) {
return None;
if let Ok(token) = args.get(1)?.parse::<isupport::WhoToken>() {
if let Some(WhoStatus::Requested(_, Some(request_token))) =
channel.last_who
{
if request_token == token {
channel.last_who =
Some(WhoStatus::Receiving(Some(request_token)));
log::debug!("[{}] {target} - WHO receiving...", self.server);
// We requested, don't save to history
return None;
}
}
}
}
}
Expand All @@ -708,7 +734,7 @@ impl Client {

if proto::is_channel(target) {
if let Some(channel) = self.chanmap.get_mut(target) {
if matches!(channel.last_who, Some(WhoStatus::Receiving)) {
if matches!(channel.last_who, Some(WhoStatus::Receiving(_))) {
channel.last_who = Some(WhoStatus::Done(Instant::now()));
log::debug!("[{}] {target} - WHO done", self.server);
return None;
Expand Down Expand Up @@ -846,6 +872,57 @@ impl Client {
self.registration_required_channels.push(channel.clone());
}
}
Command::Numeric(RPL_ISUPPORT, args) => {
let args_len = args.len();
args.iter().enumerate().skip(1).for_each(|(index, arg)| {
let operation = arg.parse::<isupport::Operation>();

match operation {
Ok(operation) => {
match operation {
isupport::Operation::Add(parameter) => {
if let Some(kind) = parameter.kind() {
log::info!(
"[{}] adding ISUPPORT parameter: {:?}",
self.server,
parameter
);
self.isupport.insert(kind, parameter);
} else {
log::debug!(
"[{}] ignoring ISUPPORT parameter: {:?}",
self.server,
parameter
);
}
}
isupport::Operation::Remove(_) => {
if let Some(kind) = operation.kind() {
log::info!(
"[{}] removing ISUPPORT parameter: {:?}",
self.server,
kind
);
self.isupport.remove(&kind);
}
}
};
}
Err(error) => {
if index != args_len - 1 {
log::debug!(
"[{}] unable to parse ISUPPORT parameter: {} ({})",
self.server,
arg,
error
)
}
}
}
});

return None;
}
_ => {}
}

Expand Down Expand Up @@ -929,15 +1006,28 @@ impl Client {
(now.duration_since(last) >= self.config.who_poll_interval)
.then_some(Request::Poll)
}
Some(WhoStatus::Requested(requested)) => (now.duration_since(requested)
Some(WhoStatus::Requested(requested, _)) => (now.duration_since(requested)
>= self.config.who_retry_interval)
.then_some(Request::Retry),
_ => None,
};

if let Some(request) = request {
let _ = self.handle.try_send(command!("WHO", channel));
state.last_who = Some(WhoStatus::Requested(Instant::now()));
if self.isupport.get(&isupport::Kind::WHOX).is_some() {
let _ = self.handle.try_send(command!(
"WHO",
channel,
"tcnf",
isupport::WHO_POLL_TOKEN.to_owned()
));
state.last_who = Some(WhoStatus::Requested(
Instant::now(),
Some(isupport::WHO_POLL_TOKEN),
));
} else {
let _ = self.handle.try_send(command!("WHO", channel));
state.last_who = Some(WhoStatus::Requested(Instant::now(), None));
}
log::debug!(
"[{}] {channel} - WHO {}",
self.server,
Expand Down Expand Up @@ -1067,6 +1157,12 @@ impl Map {
.unwrap_or_default()
}

pub fn get_isupport(&self, server: &Server) -> HashMap<isupport::Kind, isupport::Parameter> {
self.client(server)
.map(|client| client.isupport.clone())
.unwrap_or_default()
}

pub fn get_server_handle(&self, server: &Server) -> Option<&server::Handle> {
self.client(server).map(|client| &client.handle)
}
Expand Down Expand Up @@ -1196,17 +1292,37 @@ pub struct Channel {
pub names_init: bool,
}

impl Channel {
pub fn update_user_away(&mut self, user: &str, flags: &str) {
let user = User::from(Nick::from(user));

if let Some(away_flag) = flags.chars().next() {
// H = Here, G = gone (away)
let away = match away_flag {
'G' => true,
'H' => false,
_ => return,
};

if let Some(mut user) = self.users.take(&user) {
user.update_away(away);
self.users.insert(user);
}
}
}
}

#[derive(Default, Debug, Clone)]
pub struct Topic {
pub text: Option<String>,
pub who: Option<String>,
pub time: Option<DateTime<Utc>>,
}

#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone)]
pub enum WhoStatus {
Requested(Instant),
Receiving,
Requested(Instant, Option<isupport::WhoToken>),
Receiving(Option<isupport::WhoToken>),
Done(Instant),
}

Expand Down
Loading

0 comments on commit 6eb019b

Please sign in to comment.