From a66b4ee61e2462e69627e8d0a01aab8e20d92a0f Mon Sep 17 00:00:00 2001 From: Simon Schuster Date: Wed, 19 Mar 2025 21:26:35 +0100 Subject: [PATCH 1/8] Autojoin IRC channels on connect Some clients do not remember and autojoin previously connected channels. Sending JOINs for these channels on connect helps to rediscover these channels. --- src/ircd/mod.rs | 3 +++ src/ircd/proto.rs | 23 +++++++++++++++++++++++ src/matrix/room_mappings.rs | 16 ++++++++-------- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/ircd/mod.rs b/src/ircd/mod.rs index f99af5f..40766c6 100644 --- a/src/ircd/mod.rs +++ b/src/ircd/mod.rs @@ -90,6 +90,9 @@ async fn handle_client(mut stream: Framed) -> Result<()> { .irc() .send_privmsg("matrirc", &matrirc.irc().nick, "okay") .await?; + + proto::join_channels(&matrirc).await?; + if let Err(e) = proto::ircd_sync_read(reader_stream, reader_matrirc).await { info!("irc read task failed: {:?}", e); } diff --git a/src/ircd/proto.rs b/src/ircd/proto.rs index 0c4c2ad..b27862c 100644 --- a/src/ircd/proto.rs +++ b/src/ircd/proto.rs @@ -162,6 +162,29 @@ pub async fn ircd_sync_write( Ok(()) } +pub async fn join_channels( + matrirc: &Matrirc +) -> Result<()> { + let irc = matrirc.irc(); + let matrix = matrirc.matrix(); + let mapping = matrirc.mappings(); + + for joined in matrix.joined_rooms() { + if joined.is_tombstoned() { + trace!( + "Skipping tombstoned {}", + joined + .name() + .unwrap_or_else(|| joined.room_id().to_string()) + ); + continue; + } + let roomtarget = mapping.try_room_target(&joined).await?; + roomtarget.join_chan(&irc).await; + } + Ok(()) +} + pub async fn ircd_sync_read( mut reader: SplitStream>, matrirc: Matrirc, diff --git a/src/matrix/room_mappings.rs b/src/matrix/room_mappings.rs index ee17e42..47ed92c 100644 --- a/src/matrix/room_mappings.rs +++ b/src/matrix/room_mappings.rs @@ -56,8 +56,8 @@ pub struct RoomTarget { inner: Arc>, } -#[derive(Debug, PartialEq)] -enum RoomTargetType { +#[derive(Debug, PartialEq, Clone)] +pub enum RoomTargetType { /// room maps to a query e.g. single other member (or alone!) Query, /// room maps to a chan, and irc side has it joined @@ -214,17 +214,17 @@ impl RoomTarget { self.inner.read().await.target.clone() } - async fn join_chan(&self, irc: &IrcClient) -> bool { + pub async fn join_chan(&self, irc: &IrcClient) -> bool { let mut lock = self.inner.write().await; - match &lock.target_type { - RoomTargetType::LeftChan => (), - RoomTargetType::Query => (), + let prefix = match &lock.target_type { + RoomTargetType::LeftChan => "", + RoomTargetType::Query => "#", // got raced or already joined RoomTargetType::JoiningChan => return false, RoomTargetType::Chan => return false, }; lock.target_type = RoomTargetType::JoiningChan; - let chan = format!("#{}", lock.target); + let chan = format!("{}{}", prefix, lock.target); drop(lock); // we need to initate the join before getting members in another task @@ -458,7 +458,7 @@ impl Mappings { // long enough to check for deduplicate and it's a bit of a mess; it could be done // with a more generic 'insert_free_target' that takes a couple of callbacks but // it's just not worth it - async fn try_room_target(&self, room: &Room) -> Result { + pub async fn try_room_target(&self, room: &Room) -> Result { // happy case first if let Some(target) = self.inner.read().await.rooms.get(room.room_id()) { return Ok(target.clone()); From d28597742cbf708c875ec47ee496f2939be76d6b Mon Sep 17 00:00:00 2001 From: Simon Schuster Date: Fri, 28 Mar 2025 20:07:52 +0100 Subject: [PATCH 2/8] run rustfmt --- src/ircd/proto.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ircd/proto.rs b/src/ircd/proto.rs index b27862c..21b1be9 100644 --- a/src/ircd/proto.rs +++ b/src/ircd/proto.rs @@ -162,9 +162,7 @@ pub async fn ircd_sync_write( Ok(()) } -pub async fn join_channels( - matrirc: &Matrirc -) -> Result<()> { +pub async fn join_channels(matrirc: &Matrirc) -> Result<()> { let irc = matrirc.irc(); let matrix = matrirc.matrix(); let mapping = matrirc.mappings(); From 0ff270ca27e9928055e4699f8a6ce1ef67f66222 Mon Sep 17 00:00:00 2001 From: Simon Schuster Date: Fri, 28 Mar 2025 20:09:16 +0100 Subject: [PATCH 3/8] delay proto::join_channels until after initial room sync in matrix --- src/ircd/mod.rs | 2 -- src/matrix/mod.rs | 9 ++++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/ircd/mod.rs b/src/ircd/mod.rs index 40766c6..68fe34b 100644 --- a/src/ircd/mod.rs +++ b/src/ircd/mod.rs @@ -91,8 +91,6 @@ async fn handle_client(mut stream: Framed) -> Result<()> { .send_privmsg("matrirc", &matrirc.irc().nick, "okay") .await?; - proto::join_channels(&matrirc).await?; - if let Err(e) = proto::ircd_sync_read(reader_stream, reader_matrirc).await { info!("irc read task failed: {:?}", e); } diff --git a/src/matrix/mod.rs b/src/matrix/mod.rs index 562c9d8..ba4b072 100644 --- a/src/matrix/mod.rs +++ b/src/matrix/mod.rs @@ -2,6 +2,7 @@ use anyhow::Result; use log::warn; use matrix_sdk::{config::SyncSettings, LoopCtrl}; +use crate::ircd::proto; use crate::matrirc::{Matrirc, Running}; mod invite; @@ -38,7 +39,13 @@ pub async fn matrix_sync(matrirc: Matrirc) -> Result<()> { // XXX send to irc Ok(LoopCtrl::Break) } else { - Ok(LoopCtrl::Continue) + match proto::join_channels(loop_matrirc).await { + Ok(_) => Ok(LoopCtrl::Continue), + Err(e) => { + warn!("Failed to autojoin rooms in IRC: {}", e); + Ok(LoopCtrl::Break) + } + } } } Running::Continue => Ok(LoopCtrl::Continue), From 9dbc1dece823eeeb602149635b585bd380244ace Mon Sep 17 00:00:00 2001 From: Simon Schuster Date: Fri, 28 Mar 2025 20:09:49 +0100 Subject: [PATCH 4/8] fix clippy::needless_borrow warning --- src/ircd/proto.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ircd/proto.rs b/src/ircd/proto.rs index 21b1be9..5a2240b 100644 --- a/src/ircd/proto.rs +++ b/src/ircd/proto.rs @@ -178,7 +178,7 @@ pub async fn join_channels(matrirc: &Matrirc) -> Result<()> { continue; } let roomtarget = mapping.try_room_target(&joined).await?; - roomtarget.join_chan(&irc).await; + roomtarget.join_chan(irc).await; } Ok(()) } From 8242cd03e7fa2e1ed45294e540563b26af6be3b3 Mon Sep 17 00:00:00 2001 From: Simon Schuster Date: Fri, 28 Mar 2025 20:10:00 +0100 Subject: [PATCH 5/8] Fix assignment of channelprefix between Queries and LeftChan --- src/matrix/room_mappings.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/matrix/room_mappings.rs b/src/matrix/room_mappings.rs index 47ed92c..cd8aae8 100644 --- a/src/matrix/room_mappings.rs +++ b/src/matrix/room_mappings.rs @@ -217,8 +217,8 @@ impl RoomTarget { pub async fn join_chan(&self, irc: &IrcClient) -> bool { let mut lock = self.inner.write().await; let prefix = match &lock.target_type { - RoomTargetType::LeftChan => "", - RoomTargetType::Query => "#", + RoomTargetType::LeftChan => "#", + RoomTargetType::Query => "", // got raced or already joined RoomTargetType::JoiningChan => return false, RoomTargetType::Chan => return false, From 8d45d48a56049438b63b256d2e9a1261323d9e77 Mon Sep 17 00:00:00 2001 From: Simon Schuster Date: Sun, 30 Mar 2025 17:10:53 +0200 Subject: [PATCH 6/8] Add RoomTarget::target_type --- src/matrix/room_mappings.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/matrix/room_mappings.rs b/src/matrix/room_mappings.rs index cd8aae8..422d182 100644 --- a/src/matrix/room_mappings.rs +++ b/src/matrix/room_mappings.rs @@ -213,6 +213,9 @@ impl RoomTarget { pub async fn target(&self) -> String { self.inner.read().await.target.clone() } + pub async fn target_type(&self) -> RoomTargetType { + self.inner.read().await.target_type.clone() + } pub async fn join_chan(&self, irc: &IrcClient) -> bool { let mut lock = self.inner.write().await; From 9cf2761ab16a32ecb02fd53168187f16c79623b7 Mon Sep 17 00:00:00 2001 From: Simon Schuster Date: Mon, 31 Mar 2025 19:30:48 +0200 Subject: [PATCH 7/8] Add --autojoin option to select whether to autojoin channels and queries --- Cargo.toml | 2 +- src/args.rs | 22 ++++++++++++++++++++++ src/ircd/proto.rs | 21 ++++++++++++++++++++- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 83882ef..02a2e76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ matrix-sdk = { version = "0.8", features = ["anyhow", "sso-login"] } percent-encoding = "2.3.1" rand_core = { version = "0.6", features = ["getrandom"] } regex = "1.8" -serde = "1.0" +serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tokio = { version = "1.0.0", features = ["full"] } tokio-util = { version = "0.7", features = ["codec"] } diff --git a/src/args.rs b/src/args.rs index 9d223a3..bf2b01c 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,7 +1,26 @@ use clap::Parser; use lazy_static::lazy_static; +use serde::Deserialize; use std::net::SocketAddr; +#[derive(clap::ValueEnum, Clone, Debug, PartialEq, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum AutoJoinOptions { + None, + Queries, + Channels, + All, +} + +impl AutoJoinOptions { + pub fn join_queries(&self) -> bool { + [AutoJoinOptions::Queries, AutoJoinOptions::All].contains(self) + } + pub fn join_channels(&self) -> bool { + [AutoJoinOptions::Channels, AutoJoinOptions::All].contains(self) + } +} + #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] pub struct Args { @@ -19,6 +38,9 @@ pub struct Args { #[arg(long, default_value = None)] pub media_url: Option, + + #[arg(long, value_enum, default_value_t = AutoJoinOptions::None)] + pub autojoin: AutoJoinOptions, } pub fn args() -> &'static Args { diff --git a/src/ircd/proto.rs b/src/ircd/proto.rs index 5a2240b..a73e9e8 100644 --- a/src/ircd/proto.rs +++ b/src/ircd/proto.rs @@ -10,6 +10,8 @@ use tokio::net::TcpStream; use tokio::sync::mpsc; use tokio_util::codec::Framed; +use crate::args::{args, AutoJoinOptions}; +use crate::matrix::room_mappings::RoomTargetType; use crate::{matrirc::Matrirc, matrix::MatrixMessageType}; /// it's a bit of a pain to redo the work twice for notice/privmsg, @@ -163,6 +165,10 @@ pub async fn ircd_sync_write( } pub async fn join_channels(matrirc: &Matrirc) -> Result<()> { + if args().autojoin == AutoJoinOptions::None { + return Ok(()); + } + let irc = matrirc.irc(); let matrix = matrirc.matrix(); let mapping = matrirc.mappings(); @@ -178,7 +184,20 @@ pub async fn join_channels(matrirc: &Matrirc) -> Result<()> { continue; } let roomtarget = mapping.try_room_target(&joined).await?; - roomtarget.join_chan(irc).await; + let chantype = roomtarget.target_type().await; + if [RoomTargetType::Chan, RoomTargetType::LeftChan].contains(&chantype) + && args().autojoin.join_channels() + { + roomtarget.join_chan(irc).await; + } else if chantype == RoomTargetType::Query && args().autojoin.join_queries() { + let _ = irc + .send(privmsg( + roomtarget.target().await, + &irc.nick, + "* ", + )) + .await; + } } Ok(()) } From 4933a745bb5003a0d01a9da989e106acd64821ca Mon Sep 17 00:00:00 2001 From: Simon Schuster Date: Sat, 3 May 2025 21:08:20 +0200 Subject: [PATCH 8/8] Option: Add query-users to matirc channel --- src/ircd/login.rs | 17 +++++++++++++++++ src/ircd/proto.rs | 24 ++++++++++++------------ 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/ircd/login.rs b/src/ircd/login.rs index 0c693ae..72d59e2 100644 --- a/src/ircd/login.rs +++ b/src/ircd/login.rs @@ -1,5 +1,6 @@ use anyhow::{Context, Error, Result}; use irc::{client::prelude::Command, proto::IrcCodec}; +use crate::ircd::proto::{join, raw_msg}; use log::{debug, info, trace, warn}; use tokio::net::TcpStream; use tokio::sync::oneshot; @@ -53,6 +54,22 @@ pub async fn auth_loop( ))) .await?; info!("Processing login from {}!{}", nick, user); + // Promote matric to chan + let matrircchan = "matrirc".to_string(); + stream + .send(join( + Some(format!("{}!{}@matrirc", nick, user)), + "matrirc".to_string(), + )) + .await?; + stream + .send(join( + Some(format!("{}!{}@matrirc", nick, user)), + "matrirc".to_string(), + )) + .await?; + stream.send(raw_msg(format!(":matrirc 353 {} = {} :@matrirc", nick, matrircchan))).await?; + stream.send(raw_msg(format!(":matrirc 366 {} {} :End", nick, matrircchan))).await?; let client = match state::login(&nick, &pass)? { Some(session) => matrix_restore_session(stream, &nick, &pass, session).await?, None => matrix_login_loop(stream, &nick, &pass).await?, diff --git a/src/ircd/proto.rs b/src/ircd/proto.rs index a73e9e8..90e7826 100644 --- a/src/ircd/proto.rs +++ b/src/ircd/proto.rs @@ -165,10 +165,6 @@ pub async fn ircd_sync_write( } pub async fn join_channels(matrirc: &Matrirc) -> Result<()> { - if args().autojoin == AutoJoinOptions::None { - return Ok(()); - } - let irc = matrirc.irc(); let matrix = matrirc.matrix(); let mapping = matrirc.mappings(); @@ -189,14 +185,18 @@ pub async fn join_channels(matrirc: &Matrirc) -> Result<()> { && args().autojoin.join_channels() { roomtarget.join_chan(irc).await; - } else if chantype == RoomTargetType::Query && args().autojoin.join_queries() { - let _ = irc - .send(privmsg( - roomtarget.target().await, - &irc.nick, - "* ", - )) - .await; + } else if chantype == RoomTargetType::Query { + let name = roomtarget.target().await; + let _ = irc.send(join(Some(format!("{}!{}@matrirc", name, name)), "matrirc".to_string())).await?; + if args().autojoin.join_queries() { + let _ = irc + .send(privmsg( + name, + &irc.nick, + "* ", + )) + .await; + } } } Ok(())