Skip to content

Commit

Permalink
add CHANTYPES support
Browse files Browse the repository at this point in the history
  • Loading branch information
4e554c4c committed Oct 18, 2024
1 parent 7112152 commit 4aec042
Show file tree
Hide file tree
Showing 10 changed files with 81 additions and 41 deletions.
52 changes: 36 additions & 16 deletions data/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,17 @@ impl Client {
}
}

fn start_reroute(&self, command: &Command) -> bool {
use Command::*;

if let MODE(target, _, _) = command {
!self.is_channel(target)
} else {
matches!(command, WHO(..) | WHOIS(..) | WHOWAS(..))
}
}


fn send(&mut self, buffer: &Buffer, mut message: message::Encoded) {
if self.supports_labels {
use proto::Tag;
Expand All @@ -200,7 +211,7 @@ impl Client {
}];
}

self.reroute_responses_to = start_reroute(&message.command).then(|| buffer.clone());
self.reroute_responses_to = self.start_reroute(&message.command).then(|| buffer.clone());

if let Err(e) = self.handle.try_send(message.into()) {
log::warn!("Error sending message: {e}");
Expand Down Expand Up @@ -890,7 +901,7 @@ impl Client {
Command::Numeric(RPL_WHOREPLY, args) => {
let target = args.get(1)?;

if proto::is_channel(target) {
if self.is_channel(target) {
if let Some(channel) = self.chanmap.get_mut(target) {
channel.update_user_away(args.get(5)?, args.get(6)?);

Expand All @@ -909,7 +920,7 @@ impl Client {
Command::Numeric(RPL_WHOSPCRPL, args) => {
let target = args.get(2)?;

if proto::is_channel(target) {
if self.is_channel(target) {
if let Some(channel) = self.chanmap.get_mut(target) {
channel.update_user_away(args.get(3)?, args.get(4)?);

Expand Down Expand Up @@ -941,7 +952,7 @@ impl Client {
Command::Numeric(RPL_ENDOFWHO, args) => {
let target = args.get(1)?;

if proto::is_channel(target) {
if self.is_channel(target) {
if let Some(channel) = self.chanmap.get_mut(target) {
if matches!(channel.last_who, Some(WhoStatus::Receiving(_))) {
channel.last_who = Some(WhoStatus::Done(Instant::now()));
Expand Down Expand Up @@ -989,7 +1000,7 @@ impl Client {
}
}
Command::MODE(target, Some(modes), Some(args)) => {
if proto::is_channel(target) {
if self.is_channel(target) {
let modes = mode::parse::<mode::Channel>(modes, args);

if let Some(channel) = self.chanmap.get_mut(target) {
Expand Down Expand Up @@ -1047,7 +1058,7 @@ impl Client {
Command::Numeric(RPL_ENDOFNAMES, args) => {
let target = args.get(1)?;

if proto::is_channel(target) {
if self.is_channel(target) {
if let Some(channel) = self.chanmap.get_mut(target) {
if !channel.names_init {
channel.names_init = true;
Expand Down Expand Up @@ -1389,6 +1400,19 @@ impl Client {
}
}
}

pub fn chantypes<'a>(&'a self) -> &'a [char] {
self.isupport.get(&isupport::Kind::CHANTYPES).and_then(|chantypes| {
let isupport::Parameter::CHANTYPES(types) = chantypes else {
unreachable!("Corruption in isupport table.")
};
types.as_deref()
}).unwrap_or(proto::DEFAULT_CHANNEL_PREFIXES)
}

pub fn is_channel(&self, target: &str) -> bool {
proto::is_channel(target, self.chantypes())
}
}

#[derive(Debug)]
Expand Down Expand Up @@ -1543,6 +1567,12 @@ impl Map {
.unwrap_or_default()
}

pub fn get_chantypes<'a>(&'a self, server: &Server) -> &'a [char] {
self.client(server)
.map(|client| client.chantypes())
.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 @@ -1631,16 +1661,6 @@ fn remove_tag(key: &str, tags: &mut Vec<irc::proto::Tag>) -> Option<String> {
.value
}

fn start_reroute(command: &Command) -> bool {
use Command::*;

if let MODE(target, _, _) = command {
!proto::is_channel(target)
} else {
matches!(command, WHO(..) | WHOIS(..) | WHOWAS(..))
}
}

fn stop_reroute(command: &Command) -> bool {
use command::Numeric::*;

Expand Down
3 changes: 2 additions & 1 deletion data/src/history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ impl From<String> for Kind {

impl From<&str> for Kind {
fn from(target: &str) -> Self {
if proto::is_channel(target) {
// XXX not sure how to fix this in a way that will preserve on-disk history
if proto::is_channel(target, proto::DEFAULT_CHANNEL_PREFIXES) {
Kind::Channel(target.to_string())
} else {
Kind::Query(target.to_string().into())
Expand Down
3 changes: 2 additions & 1 deletion data/src/history/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,10 +208,11 @@ impl Manager {
input: Input,
user: User,
channel_users: &[User],
chantypes: &[char],
) -> Vec<impl Future<Output = Message>> {
let mut tasks = vec![];

if let Some(messages) = input.messages(user, channel_users) {
if let Some(messages) = input.messages(user, channel_users, chantypes) {
for message in messages {
tasks.extend(self.record_message(input.server(), message));
}
Expand Down
4 changes: 2 additions & 2 deletions data/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ impl Input {
self.buffer.server()
}

pub fn messages(&self, user: User, channel_users: &[User]) -> Option<Vec<Message>> {
pub fn messages(&self, user: User, channel_users: &[User], chantypes: &[char]) -> Option<Vec<Message>> {
let to_target = |target: &str, source| {
if let Some((prefix, channel)) = proto::parse_channel_from_target(target) {
if let Some((prefix, channel)) = proto::parse_channel_from_target(target, chantypes) {
Some(message::Target::Channel {
channel,
source,
Expand Down
13 changes: 7 additions & 6 deletions data/src/isupport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub enum Kind {
AWAYLEN,
CHANLIMIT,
CHANNELLEN,
CHANTYPES,
CNOTICE,
CPRIVMSG,
ELIST,
Expand Down Expand Up @@ -139,12 +140,11 @@ impl FromStr for Operation {
parse_required_positive_integer(value)?,
))),
"CHANTYPES" => {
if value.is_empty() {
let chars = value.chars().collect::<Vec<_>>();
if chars.is_empty() {
Ok(Operation::Add(Parameter::CHANTYPES(None)))
} else if value.chars().all(|c| proto::CHANNEL_PREFIXES.contains(&c)) {
Ok(Operation::Add(Parameter::CHANTYPES(Some(
value.to_string(),
))))
} else if chars.iter().all(|c| proto::CHANNEL_PREFIXES.contains(&c)) {
Ok(Operation::Add(Parameter::CHANTYPES(Some(chars))))
} else {
Err("value must only contain channel types if specified")
}
Expand Down Expand Up @@ -485,6 +485,7 @@ impl Operation {
"AWAYLEN" => Some(Kind::AWAYLEN),
"CHANLIMIT" => Some(Kind::CHANLIMIT),
"CHANNELLEN" => Some(Kind::CHANNELLEN),
"CHANTYPES" => Some(Kind::CHANTYPES),
"CNOTICE" => Some(Kind::CNOTICE),
"CPRIVMSG" => Some(Kind::CPRIVMSG),
"ELIST" => Some(Kind::ELIST),
Expand Down Expand Up @@ -526,7 +527,7 @@ pub enum Parameter {
CHANLIMIT(Vec<ChannelLimit>),
CHANMODES(Vec<ChannelMode>),
CHANNELLEN(u16),
CHANTYPES(Option<String>),
CHANTYPES(Option<Vec<char>>),
CHATHISTORY(u16),
CLIENTTAGDENY(Vec<ClientOnlyTags>),
CLIENTVER(u16, u16),
Expand Down
14 changes: 9 additions & 5 deletions data/src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ impl Message {
config: &'a Config,
resolve_attributes: impl Fn(&User, &str) -> Option<User>,
channel_users: impl Fn(&str) -> &'a [User],
chantypes: &[char]
) -> Option<Message> {
let server_time = server_time(&encoded);
let id = message_id(&encoded);
Expand All @@ -187,8 +188,9 @@ impl Message {
config,
&resolve_attributes,
&channel_users,
chantypes,
)?;
let target = target(encoded, &our_nick, &resolve_attributes)?;
let target = target(encoded, &our_nick, &resolve_attributes, chantypes)?;

Some(Message {
received_at: Posix::now(),
Expand Down Expand Up @@ -534,14 +536,15 @@ fn target(
message: Encoded,
our_nick: &Nick,
resolve_attributes: &dyn Fn(&User, &str) -> Option<User>,
chantypes: &[char],
) -> Option<Target> {
use proto::command::Numeric::*;

let user = message.user();

match message.0.command {
// Channel
Command::MODE(target, ..) if proto::is_channel(&target) => Some(Target::Channel {
Command::MODE(target, ..) if proto::is_channel(&target, chantypes) => Some(Target::Channel {
channel: target,
source: source::Source::Server(None),
prefix: None,
Expand Down Expand Up @@ -605,7 +608,7 @@ fn target(
}
};

match (proto::parse_channel_from_target(&target), user) {
match (proto::parse_channel_from_target(&target, chantypes), user) {
(Some((prefix, channel)), Some(user)) => {
let source = source(resolve_attributes(&user, &channel).unwrap_or(user));
Some(Target::Channel {
Expand Down Expand Up @@ -639,7 +642,7 @@ fn target(
}
};

match (proto::parse_channel_from_target(&target), user) {
match (proto::parse_channel_from_target(&target, chantypes), user) {
(Some((prefix, channel)), Some(user)) => {
let source = source(resolve_attributes(&user, &channel).unwrap_or(user));
Some(Target::Channel {
Expand Down Expand Up @@ -757,6 +760,7 @@ fn content<'a>(
config: &Config,
resolve_attributes: &dyn Fn(&User, &str) -> Option<User>,
channel_users: &dyn Fn(&str) -> &'a [User],
chantypes: &[char],
) -> Option<Content> {
use irc::proto::command::Numeric::*;

Expand Down Expand Up @@ -826,7 +830,7 @@ fn content<'a>(
&[],
))
}
Command::MODE(target, modes, args) if proto::is_channel(target) => {
Command::MODE(target, modes, args) if proto::is_channel(target, chantypes) => {
let raw_user = message.user()?;
let with_access_levels = config.buffer.nickname.show_access_levels;
let user = resolve_attributes(&raw_user, target)
Expand Down
22 changes: 14 additions & 8 deletions irc/proto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,32 +49,38 @@ pub fn command(command: &str, parameters: Vec<String>) -> Message {
}

/// Reference: https://defs.ircdocs.horse/defs/chantypes
pub const CHANNEL_PREFIXES: [char; 4] = ['#', '&', '+', '!'];
pub const CHANNEL_PREFIXES: &[char] = &['#', '&', '+', '!'];

/// Reference: https://defs.ircdocs.horse/defs/chantypes
///
/// Channel types which should be used if the CHANTYPES ISUPPORT is not returned
pub const DEFAULT_CHANNEL_PREFIXES: &[char] = &['#', '&'];

/// https://modern.ircdocs.horse/#channels
///
/// Channel names are strings (beginning with specified prefix characters). Apart from the requirement of
/// the first character being a valid channel type prefix character; the only restriction on a channel name
/// is that it may not contain any spaces (' ', 0x20), a control G / BELL ('^G', 0x07), or a comma (',', 0x2C)
/// (which is used as a list item separator by the protocol).
pub const CHANNEL_BLACKLIST_CHARS: [char; 3] = [',', '\u{07}', ','];
pub const CHANNEL_BLACKLIST_CHARS: &[char] = &[',', '\u{07}', ','];

pub fn is_channel(target: &str) -> bool {
target.starts_with(CHANNEL_PREFIXES) && !target.contains(CHANNEL_BLACKLIST_CHARS)
pub fn is_channel(target: &str, chantypes: &[char]) -> bool {
target.starts_with(chantypes) && !target.contains(CHANNEL_BLACKLIST_CHARS)
}

// Reference: https://defs.ircdocs.horse/defs/chanmembers
pub const CHANNEL_MEMBERSHIP_PREFIXES: [char; 6] = ['~', '&', '!', '@', '%', '+'];
pub const CHANNEL_MEMBERSHIP_PREFIXES: &[char] = &['~', '&', '!', '@', '%', '+'];

pub fn parse_channel_from_target(target: &str) -> Option<(Option<char>, String)> {
pub fn parse_channel_from_target(target: &str, chantypes: &[char]) -> Option<(Option<char>, String)> {
if target.starts_with(CHANNEL_MEMBERSHIP_PREFIXES) {
let channel = target.strip_prefix(CHANNEL_MEMBERSHIP_PREFIXES)?;

if is_channel(channel) {
if is_channel(channel, chantypes) {
return Some((target.chars().next(), channel.to_string()));
}
}

if is_channel(target) {
if is_channel(target, chantypes) {
Some((None, target.to_string()))
} else {
None
Expand Down
3 changes: 2 additions & 1 deletion src/buffer/input_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ impl State {
if let Some(nick) = clients.nickname(buffer.server()) {
let mut user = nick.to_owned().into();
let mut channel_users = &[][..];
let chantypes = clients.get_chantypes(buffer.server());

// Resolve our attributes if sending this message in a channel
if let Buffer::Channel(server, channel) = &buffer {
Expand All @@ -198,7 +199,7 @@ impl State {

history_task = Task::batch(
history
.record_input(input, user, channel_users)
.record_input(input, user, channel_users, chantypes)
.into_iter()
.map(Task::future),
);
Expand Down
5 changes: 5 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,8 @@ impl Halloy {
self.clients.get_channel_users(&server, channel)
};

let chantypes = self.clients.get_chantypes(&server);

match event {
data::client::Event::Single(encoded, our_nick) => {
if let Some(message) = data::Message::received(
Expand All @@ -511,6 +513,7 @@ impl Halloy {
&self.config,
resolve_user_attributes,
channel_users,
chantypes,
) {
commands.push(
dashboard
Expand All @@ -526,6 +529,7 @@ impl Halloy {
&self.config,
resolve_user_attributes,
channel_users,
chantypes,
) {
commands.push(
dashboard
Expand Down Expand Up @@ -642,6 +646,7 @@ impl Halloy {
&self.config,
resolve_user_attributes,
channel_users,
chantypes,
) {
commands.push(
dashboard
Expand Down
3 changes: 2 additions & 1 deletion src/screen/dashboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ impl Dashboard {
{
let mut user = nick.to_owned().into();
let mut channel_users = &[][..];
let chantypes = clients.get_chantypes(buffer.server());

// Resolve our attributes if sending this message in a channel
if let data::Buffer::Channel(server, channel) =
Expand All @@ -224,7 +225,7 @@ impl Dashboard {
}

if let Some(messages) =
input.messages(user, channel_users)
input.messages(user, channel_users, chantypes)
{
let mut tasks = vec![task];

Expand Down

0 comments on commit 4aec042

Please sign in to comment.