Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
22 changes: 22 additions & 0 deletions src/args.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -19,6 +38,9 @@ pub struct Args {

#[arg(long, default_value = None)]
pub media_url: Option<String>,

#[arg(long, value_enum, default_value_t = AutoJoinOptions::None)]
pub autojoin: AutoJoinOptions,
}

pub fn args() -> &'static Args {
Expand Down
17 changes: 17 additions & 0 deletions src/ircd/login.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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?,
Expand Down
1 change: 1 addition & 0 deletions src/ircd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ async fn handle_client(mut stream: Framed<TcpStream, IrcCodec>) -> Result<()> {
.irc()
.send_privmsg("matrirc", &matrirc.irc().nick, "okay")
.await?;

if let Err(e) = proto::ircd_sync_read(reader_stream, reader_matrirc).await {
info!("irc read task failed: {:?}", e);
}
Expand Down
40 changes: 40 additions & 0 deletions src/ircd/proto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use tokio::net::TcpStream;
use tokio::sync::mpsc;
use tokio_util::codec::Framed;

use crate::args::{args, AutoJoinOptions};
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AutoJoinOptions is unused since you've used the join_queries()/channels() methods

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,
Expand Down Expand Up @@ -162,6 +164,44 @@ 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?;
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 {
let name = roomtarget.target().await;
let _ = irc.send(join(Some(format!("{}!{}@matrirc", name, name)), "matrirc".to_string())).await?;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clippy warns on this on my machine: you're using ? so all that's left here is an Unit ();

Either the error is safe to error and there should be no ? or we should drop the let _ =

Looking around the rest of this loop I think we should be consistent: either we expect errors and we should perhaps continue; or keep going, or we should bail out with ? on the join queries case as well.

I think that irc.send has no reason to fail in a way that's recoverable, so I'd make drop the let _ and use ? below as well, but happy either way tbh

The other comment I have reading this diff is that the abstraction for join() is rather annoying when making someone join when we don't have a string ready for their nick!user@host triplet, so perhaps it's make sense to make a second join helper, but I'm happy to let you judge on that as well

if args().autojoin.join_queries() {
let _ = irc
.send(privmsg(
name,
&irc.nick,
"* <Resumed connection to matrirc>",
))
.await;
}
}
}
Ok(())
}

pub async fn ircd_sync_read(
mut reader: SplitStream<Framed<TcpStream, IrcCodec>>,
matrirc: Matrirc,
Expand Down
9 changes: 8 additions & 1 deletion src/matrix/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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),
Expand Down
19 changes: 11 additions & 8 deletions src/matrix/room_mappings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ pub struct RoomTarget {
inner: Arc<RwLock<RoomTargetInner>>,
}

#[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
Expand Down Expand Up @@ -213,18 +213,21 @@ 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()
}

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
Expand Down Expand Up @@ -458,7 +461,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<RoomTarget> {
pub async fn try_room_target(&self, room: &Room) -> Result<RoomTarget> {
// happy case first
if let Some(target) = self.inner.read().await.rooms.get(room.room_id()) {
return Ok(target.clone());
Expand Down