Skip to content

Commit

Permalink
Merge pull request #469 from squidowl/feat/accountname
Browse files Browse the repository at this point in the history
`account-notify` and `extended-join` Support
  • Loading branch information
casperstorm authored Aug 4, 2024
2 parents 0b7f9c6 + deb2be7 commit 3cb7873
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 6 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Added:

- Small icon in sidemenu when a new release is available
- Enable support for IRCv3 `chghost`
- Enable support for IRCv3 `chghost`, `account-notify`, and `extended-join`

Removed:

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ Halloy is also available from [Flathub](https://flathub.org/apps/org.squidowl.ha
* [`WHOX`](https://ircv3.net/specs/extensions/whox)
* [`UTF8ONLY`](https://ircv3.net/specs/extensions/utf8-only)
* [chghost](https://ircv3.net/specs/extensions/chghost)
* [account-notify](https://ircv3.net/specs/extensions/account-notify)
* [extended-join](https://ircv3.net/specs/extensions/extended-join)
* 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 @@ -19,6 +19,8 @@
* [`WHOX`](https://ircv3.net/specs/extensions/whox)
* [`UTF8ONLY`](https://ircv3.net/specs/extensions/utf8-only)
* [chghost](https://ircv3.net/specs/extensions/chghost)
* [account-notify](https://ircv3.net/specs/extensions/account-notify)
* [extended-join](https://ircv3.net/specs/extensions/extended-join)
* SASL support
* DCC Send
* Keyboard shortcuts
Expand Down
124 changes: 120 additions & 4 deletions data/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ pub struct Client {
listed_caps: Vec<String>,
supports_labels: bool,
supports_away_notify: bool,
supports_account_notify: bool,
supports_extended_join: bool,
highlight_blackout: HighlightBlackout,
registration_required_channels: Vec<String>,
isupport: HashMap<isupport::Kind, isupport::Parameter>,
Expand Down Expand Up @@ -144,6 +146,8 @@ impl Client {
listed_caps: vec![],
supports_labels: false,
supports_away_notify: false,
supports_account_notify: false,
supports_extended_join: false,
highlight_blackout: HighlightBlackout::Blackout(Instant::now()),
registration_required_channels: vec![],
isupport: HashMap::new(),
Expand Down Expand Up @@ -333,6 +337,13 @@ impl Client {
if contains("chghost") {
requested.push("chghost");
}
if contains("account-notify") {
requested.push("account-notify");

if contains("extended-join") {
requested.push("extended-join");
}
}
if contains("batch") {
requested.push("batch");
}
Expand Down Expand Up @@ -376,6 +387,12 @@ impl Client {
if caps.contains(&"away-notify") {
self.supports_away_notify = true;
}
if caps.contains(&"account-notify") {
self.supports_account_notify = true;
}
if caps.contains(&"extended-join") {
self.supports_extended_join = true;
}

let supports_sasl = caps.iter().any(|cap| cap.contains("sasl"));

Expand Down Expand Up @@ -428,6 +445,15 @@ impl Client {
if newly_contains("chghost") {
requested.push("chghost");
}
if contains("account-notify") || newly_contains("account-notify") {
if newly_contains("account-notify") {
requested.push("account-notify");
}

if newly_contains("extended-join") {
requested.push("extended-join");
}
}
if newly_contains("batch") {
requested.push("batch");
}
Expand Down Expand Up @@ -465,6 +491,12 @@ impl Client {
if del_caps.contains(&"away-notify") {
self.supports_away_notify = false;
}
if del_caps.contains(&"account-notify") {
self.supports_account_notify = false;
}
if del_caps.contains(&"extended-join") {
self.supports_extended_join = false;
}

self.listed_caps
.retain(|cap| !del_caps.iter().any(|del_cap| del_cap == cap));
Expand All @@ -478,7 +510,7 @@ impl Client {
let _ = self.handle.try_send(command!("CAP", "END"));
}
}
Command::Numeric(RPL_LOGGEDIN, _) => {
Command::Numeric(RPL_LOGGEDIN, args) => {
log::info!("[{}] logged in", self.server);

if !self.registration_required_channels.is_empty() {
Expand All @@ -491,6 +523,31 @@ impl Client {

self.registration_required_channels.clear();
}

if !self.supports_account_notify {
let accountname = args.first()?;

let old_user = User::from(self.nickname().to_owned());

self.chanmap.values_mut().for_each(|channel| {
if let Some(user) = channel.users.take(&old_user) {
channel.users.insert(user.with_accountname(accountname));
}
});
}
}
Command::Numeric(RPL_LOGGEDOUT, _) => {
log::info!("[{}] logged out", self.server);

if !self.supports_account_notify {
let old_user = User::from(self.nickname().to_owned());

self.chanmap.values_mut().for_each(|channel| {
if let Some(user) = channel.users.take(&old_user) {
channel.users.insert(user.with_accountname("*"));
}
});
}
}
Command::PRIVMSG(channel, text) | Command::NOTICE(channel, text) => {
if let Some(user) = message.user() {
Expand Down Expand Up @@ -738,7 +795,7 @@ impl Client {
channel.users.remove(&user);
}
}
Command::JOIN(channel, _) => {
Command::JOIN(channel, accountname) => {
let user = message.user()?;

if user.nickname() == self.nickname() {
Expand All @@ -747,12 +804,19 @@ impl Client {
if let Some(state) = self.chanmap.get_mut(channel) {
// Sends WHO to get away state on users.
if self.isupport.contains_key(&isupport::Kind::WHOX) {
let fields = if self.supports_account_notify {
"tcnfa"
} else {
"tcnf"
};

let _ = self.handle.try_send(command!(
"WHO",
channel,
"tcnf",
fields,
isupport::WHO_POLL_TOKEN.to_owned()
));

state.last_who = Some(WhoStatus::Requested(
Instant::now(),
Some(isupport::WHO_POLL_TOKEN),
Expand All @@ -764,6 +828,14 @@ impl Client {
log::debug!("[{}] {channel} - WHO requested", self.server);
}
} else if let Some(channel) = self.chanmap.get_mut(channel) {
let user = if self.supports_extended_join {
accountname.as_ref().map_or(user.clone(), |accountname| {
user.with_accountname(accountname)
})
} else {
user
};

channel.users.insert(user);
}
}
Expand Down Expand Up @@ -802,6 +874,12 @@ impl Client {
if let Some(channel) = self.chanmap.get_mut(target) {
channel.update_user_away(args.get(3)?, args.get(4)?);

if self.supports_account_notify {
if let (Some(user), Some(accountname)) = (args.get(3), args.get(5)) {
channel.update_user_accountname(user, accountname);
}
}

if let Ok(token) = args.get(1)?.parse::<isupport::WhoToken>() {
if let Some(WhoStatus::Requested(_, Some(request_token))) =
channel.last_who
Expand Down Expand Up @@ -1020,6 +1098,29 @@ impl Client {
Command::TAGMSG(_) => {
return None;
}
Command::ACCOUNT(accountname) => {
let old_user = message.user()?;

self.chanmap.values_mut().for_each(|channel| {
if let Some(user) = channel.users.take(&old_user) {
channel.users.insert(user.with_accountname(accountname));
}
});

if old_user.nickname() == self.nickname()
&& accountname != "*"
&& !self.registration_required_channels.is_empty()
{
for message in group_joins(
&self.registration_required_channels,
&self.config.channel_keys,
) {
let _ = self.handle.try_send(message);
}

self.registration_required_channels.clear();
}
}
Command::CHGHOST(new_username, new_hostname) => {
let old_user = message.user()?;

Expand Down Expand Up @@ -1136,12 +1237,19 @@ impl Client {

if let Some(request) = request {
if self.isupport.contains_key(&isupport::Kind::WHOX) {
let fields = if self.supports_account_notify {
"tcnfa"
} else {
"tcnf"
};

let _ = self.handle.try_send(command!(
"WHO",
channel,
"tcnf",
fields,
isupport::WHO_POLL_TOKEN.to_owned()
));

state.last_who = Some(WhoStatus::Requested(
Instant::now(),
Some(isupport::WHO_POLL_TOKEN),
Expand Down Expand Up @@ -1438,6 +1546,14 @@ impl Channel {
}
}
}

pub fn update_user_accountname(&mut self, user: &str, accountname: &str) {
let user = User::from(Nick::from(user));

if let Some(user) = self.users.take(&user) {
self.users.insert(user.with_accountname(accountname));
}
}
}

#[derive(Default, Debug, Clone)]
Expand Down
1 change: 1 addition & 0 deletions data/src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,7 @@ fn target(
| Command::USERHOST(_)
| Command::CAP(_, _, _, _)
| Command::AUTHENTICATE(_)
| Command::ACCOUNT(_)
| Command::BATCH(_, _)
| Command::CHGHOST(_, _)
| Command::CNOTICE(_, _, _)
Expand Down
21 changes: 21 additions & 0 deletions data/src/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub struct User {
nickname: Nick,
username: Option<String>,
hostname: Option<String>,
accountname: Option<String>,
access_levels: HashSet<AccessLevel>,
away: bool,
}
Expand Down Expand Up @@ -93,6 +94,7 @@ impl<'a> TryFrom<&'a str> for User {
nickname: Nick::from(nickname),
username,
hostname,
accountname: None,
access_levels,
away: false,
})
Expand All @@ -118,6 +120,7 @@ impl From<Nick> for User {
nickname,
username: None,
hostname: None,
accountname: None,
access_levels: HashSet::default(),
away: false,
}
Expand Down Expand Up @@ -157,6 +160,10 @@ impl User {
self.hostname.as_deref()
}

pub fn accountname(&self) -> Option<&str> {
self.accountname.as_deref()
}

pub fn with_nickname(self, nickname: Nick) -> Self {
Self { nickname, ..self }
}
Expand All @@ -169,6 +176,19 @@ impl User {
}
}

pub fn with_accountname(self, accountname: &str) -> Self {
let accountname = if accountname == "*" || accountname == "0" {
None
} else {
Some(accountname.to_string())
};

Self {
accountname,
..self
}
}

pub fn highest_access_level(&self) -> AccessLevel {
self.access_levels
.iter()
Expand Down Expand Up @@ -221,6 +241,7 @@ impl From<proto::User> for User {
nickname: Nick::from(user.nickname),
username: user.username,
hostname: user.hostname,
accountname: None,
access_levels: HashSet::default(),
away: false,
}
Expand Down
8 changes: 7 additions & 1 deletion irc/proto/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ pub enum Command {
ERROR(String),

/* Channel Operations */
/// <channel>{,<channel>} [<key>{,<key>}]
/// <channel>{,<channel>} [<key>{,<key>}] (send)
/// <channel>{,<channel>} [<accountname>] (receive [extended-join])
JOIN(String, Option<String>),
/// <channel>{,<channel>} [<reason>]
PART(String, Option<String>),
Expand Down Expand Up @@ -94,6 +95,8 @@ pub enum Command {
/// <text>
WALLOPS(String),

/// <accountname>
ACCOUNT(String),
/* IRC extensions */
BATCH(String, Vec<String>),
/// <new_username> <new_hostname>
Expand Down Expand Up @@ -194,6 +197,7 @@ impl Command {
"LINKS" => LINKS,
"USERHOST" => USERHOST(params.collect()),
"WALLOPS" if len > 0 => WALLOPS(req!()),
"ACCOUNT" if len > 0 => ACCOUNT(req!()),
"BATCH" if len > 0 => BATCH(req!(), params.collect()),
"CHGHOST" if len > 1 => CHGHOST(req!(), req!()),
"CNOTICE" if len > 2 => CNOTICE(req!(), req!(), req!()),
Expand Down Expand Up @@ -249,6 +253,7 @@ impl Command {
Command::LINKS => vec![],
Command::USERHOST(params) => params,
Command::WALLOPS(a) => vec![a],
Command::ACCOUNT(a) => vec![a],
Command::BATCH(a, rest) => std::iter::once(a).chain(rest).collect(),
Command::CHGHOST(a, b) => vec![a, b],
Command::CNOTICE(a, b, c) => vec![a, b, c],
Expand Down Expand Up @@ -306,6 +311,7 @@ impl Command {
LINKS => "LINKS".to_string(),
USERHOST(_) => "USERHOST".to_string(),
WALLOPS(_) => "WALLOPS".to_string(),
ACCOUNT(_) => "ACCOUNT".to_string(),
BATCH(_, _) => "BATCH".to_string(),
CHGHOST(_, _) => "CHGHOST".to_string(),
CNOTICE(_, _, _) => "CNOTICE".to_string(),
Expand Down

0 comments on commit 3cb7873

Please sign in to comment.