From dc47ec918b5bf672af8ca2ce82aa2c3730b883e2 Mon Sep 17 00:00:00 2001 From: TriMill Date: Sat, 10 Jun 2023 22:35:48 -0400 Subject: [PATCH] things --- Cargo.lock | 36 ++++++ Cargo.toml | 5 +- src/bridge_discord/bridge.rs | 5 +- src/bridge_discord/config.rs | 8 +- src/bridge_irc/bridge.rs | 1 + src/bridge_irc/config.rs | 8 ++ src/bridge_irc_sp/bridge.rs | 192 +++++++++++++++++++++++++++++++ src/bridge_irc_sp/config.rs | 33 ++++++ src/bridge_irc_sp/message.rs | 217 +++++++++++++++++++++++++++++++++++ src/bridge_irc_sp/mod.rs | 7 ++ src/bridge_irc_sp/network.rs | 71 ++++++++++++ src/bridge_matrix/bridge.rs | 3 + src/bridge_matrix/config.rs | 8 ++ src/config.rs | 6 +- src/main.rs | 2 + src/supervisor.rs | 3 +- 16 files changed, 600 insertions(+), 5 deletions(-) create mode 100644 src/bridge_irc_sp/bridge.rs create mode 100644 src/bridge_irc_sp/config.rs create mode 100644 src/bridge_irc_sp/message.rs create mode 100644 src/bridge_irc_sp/mod.rs create mode 100644 src/bridge_irc_sp/network.rs diff --git a/Cargo.lock b/Cargo.lock index 9e19e8f..1505472 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,6 +14,8 @@ dependencies = [ "matrix-sdk", "serde", "serenity", + "strum", + "strum_macros", "tokio", "toml 0.7.4", ] @@ -1010,6 +1012,12 @@ dependencies = [ "ahash", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.2.6" @@ -2382,6 +2390,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" + [[package]] name = "ryu" version = "1.0.13" @@ -2650,6 +2664,28 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck", + "proc-macro2 1.0.59", + "quote 1.0.27", + "rustversion", + "syn 1.0.109", +] + [[package]] name = "subtle" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index ca1ee97..5a5d7a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ readme = "README.md" [features] # platforms irc = ["dep:irc"] +irc-sp = ["tokio/net"] matrix = ["dep:matrix-sdk"] discord = ["dep:serenity"] # tls @@ -16,7 +17,7 @@ tls-rustls = ["irc?/tls-rust", "matrix-sdk?/rustls-tls", "serenity?/rustls_bac # extra matrix-e2ee = ["matrix", "matrix-sdk?/e2e-encryption"] # default -default = ["tls-rustls", "irc", "discord", "matrix", "matrix-e2ee"] +default = ["tls-rustls", "irc", "irc-sp", "discord", "matrix", "matrix-e2ee"] [dependencies] async-trait = "0.1" @@ -26,6 +27,8 @@ log = "0.4" serde = { version = "1.0", features = ["derive", "rc"] } tokio = { version = "1.28", features = ["rt-multi-thread", "macros", "time", "sync"] } toml = "0.7" +strum_macros = "0.24" +strum = { version = "0.24", features = ["derive"] } [dependencies.irc] optional = true diff --git a/src/bridge_discord/bridge.rs b/src/bridge_discord/bridge.rs index e8e102c..bd0068c 100644 --- a/src/bridge_discord/bridge.rs +++ b/src/bridge_discord/bridge.rs @@ -21,6 +21,7 @@ impl Task for DiscordTask { async fn start(&self, id: Id, tx: Sender, mut rx: Receiver) -> TaskResult { let handler = Handler { links: self.links.clone(), + suffix: self.config.suffix.clone(), tx, id, }; @@ -64,7 +65,7 @@ impl DiscordTask { hook.execute(http, false, |hook| { hook.username(&msg.author) .content(&msg.content) - .avatar_url(msg.avatar.as_ref().map(|s| s.as_str()).unwrap_or("")) + .avatar_url(msg.avatar.as_deref().unwrap_or("")) }).await?; } else { channel.say(http, &content).await?; @@ -76,6 +77,7 @@ impl DiscordTask { struct Handler { id: Id, + suffix: Arc, links: Arc>, tx: Sender, } @@ -98,6 +100,7 @@ impl EventHandler for Handler { origin: (self.id, msg.channel_id.to_string()), link: link.clone(), author, + suffix: self.suffix.clone(), content: msg.content, avatar, }).expect("failed to broadcast message"); diff --git a/src/bridge_discord/config.rs b/src/bridge_discord/config.rs index fc873dc..23ade32 100644 --- a/src/bridge_discord/config.rs +++ b/src/bridge_discord/config.rs @@ -1,11 +1,17 @@ -use std::collections::HashMap; +use std::{collections::HashMap, sync::Arc}; use serde::Deserialize; use serenity::model::prelude::{WebhookId, ChannelId}; +fn default_suffix() -> Arc { + "[d]".into() +} + #[derive(Debug, Deserialize)] pub struct DiscordConfig { pub token: String, #[serde(default)] pub webhooks: HashMap, + #[serde(default="default_suffix")] + pub suffix: Arc, } diff --git a/src/bridge_irc/bridge.rs b/src/bridge_irc/bridge.rs index 123596f..4a1b113 100644 --- a/src/bridge_irc/bridge.rs +++ b/src/bridge_irc/bridge.rs @@ -40,6 +40,7 @@ impl IrcTask { origin: (id, channel.clone()), link: link.clone(), author: author.to_owned(), + suffix: self.config.suffix.clone(), content: message.clone(), avatar: None, })?; diff --git a/src/bridge_irc/config.rs b/src/bridge_irc/config.rs index ffd55ac..0c5ed1f 100644 --- a/src/bridge_irc/config.rs +++ b/src/bridge_irc/config.rs @@ -1,7 +1,13 @@ +use std::sync::Arc; + use serde::Deserialize; const fn default_port() -> u16 { 6667 } const fn default_tls() -> bool { true } +fn default_suffix() -> Arc { + "[i]".into() +} + #[derive(Clone, Debug, Deserialize)] pub struct IrcConfig { @@ -13,4 +19,6 @@ pub struct IrcConfig { pub nick: String, #[serde(default)] pub alt_nicks: Vec, + #[serde(default="default_suffix")] + pub suffix: Arc, } diff --git a/src/bridge_irc_sp/bridge.rs b/src/bridge_irc_sp/bridge.rs new file mode 100644 index 0000000..f65cc73 --- /dev/null +++ b/src/bridge_irc_sp/bridge.rs @@ -0,0 +1,192 @@ +use std::collections::{HashSet, HashMap}; + +use async_trait::async_trait; +use log::{debug, warn}; +use serde::Deserialize; +use tokio::{net::TcpStream, io::{BufReader, BufWriter, AsyncWrite}, select}; + +use crate::{supervisor::{Id, TaskResult, Sender, Task, Receiver}, linkmap::Linkmap}; +use crate::supervisor::Message as BMessage; + +use super::{IrcSpConfig, message::{Message, Command}, network::{MessageReader, MessageWriter}}; + +#[derive(Debug, Deserialize)] +pub struct IrcSpTask { + config: IrcSpConfig, + links: Linkmap, +} + +#[async_trait] +impl Task for IrcSpTask { + async fn start(&self, id: Id, tx: Sender, mut rx: Receiver) -> TaskResult { + let stream = TcpStream::connect((self.config.peer_host.as_ref(), self.config.peer_port)).await?; + let (reader, writer) = stream.into_split(); + let mut reader = MessageReader::new(BufReader::new(reader)); + + let mut ctx = Context { + id, + links: &self.links, + cfg: &self.config, + writer: MessageWriter::new(BufWriter::new(writer)), + tx, + servers: HashSet::from([self.config.server_name.clone()]), + nicks: HashSet::from([self.config.user_nick.clone()]), + local_users: HashSet::from([self.config.user_nick.clone()]), + }; + + + debug!("{id}: identifying"); + + ctx.writer.write_msg(Message::new(Command::PASS, vec![&ctx.cfg.peer_password, "0210", "abridged|"])).await?; + ctx.writer.write_msg(Message::new(Command::SERVER, vec![&ctx.cfg.server_name, ":Abridged"])).await?; + ctx.writer.flush().await?; + + debug!("{id}: checking peer identification"); + + let passmsg = reader.read_message().await?; + + if passmsg.command != Command::PASS { + return Err("Invalid handshake, expected PASS".into()) + } + + if passmsg.params.first() != Some(&ctx.cfg.password.as_str()) { + return Err("Configured local password does not match password from peer".into()) + } + + ctx.writer.write_msg(Message::with_prefix( + &ctx.cfg.server_name, + Command::NICK, + vec![&ctx.cfg.user_nick, "0", &ctx.cfg.user_nick, "server", "1", "+", &ctx.cfg.user_nick] + )).await?; + + for channel in self.links.iter_channels() { + ctx.writer.write_msg(Message::with_prefix( + &ctx.cfg.user_nick, + Command::JOIN, + vec![channel], + )).await?; + } + ctx.writer.flush().await?; + + debug!("{id}: entering event loop"); + loop { select! { + msg = reader.read_message() => handle_irc_msg(msg?, &mut ctx).await?, + msg = rx.recv() => handle_bridge_msg(msg?, &mut ctx).await?, + }} + } +} + +struct Context<'a, W: AsyncWrite + Unpin> { + id: Id, + links: &'a Linkmap, + cfg: &'a IrcSpConfig, + writer: MessageWriter, + tx: Sender, + servers: HashSet, + nicks: HashSet, + local_users: HashMap<(String, &str), String>, +} + +const MALFORMED: &str = "Malformed IRC message"; + +async fn handle_bridge_msg(msg: BMessage, ctx: &mut Context<'_, impl AsyncWrite + Unpin>) -> TaskResult { + let key = &(msg.author, msg.suffix.as_ref()); + let nick = match ctx.local_users.get(&key) { + Some(nick) => nick, + None => { + let nick = msg.author + msg.suffix.as_ref(); + ctx.writer.write_msg(Message::with_prefix( + &ctx.cfg.server_name, + Command::NICK, + vec![&nick, "0", &nick, "server", "1", "+", &nick] + )).await?; + ctx.local_users.insert(todo!(), nick.clone()); + ctx.nicks.insert(nick.clone()); + &nick + } + }; + for channel in ctx.links.get_channels(&msg.link) { + if msg.origin.0 == ctx.id && &msg.origin.1 == channel { + continue + } + ctx.writer.write_msg(Message::with_prefix( + &nick, + Command::PRIVMSG, + vec![channel, &msg.content] + )).await?; + ctx.writer.flush().await?; + } + Ok(()) +} + +async fn handle_irc_msg<'a>(msg: Message<'a>, ctx: &mut Context<'_, impl AsyncWrite + Unpin>) -> TaskResult { + debug!("irc message: {:?}", msg); + match msg.command { + Command::PASS => todo!(), + + Command::SERVER => { + let name = *msg.params.first().ok_or(MALFORMED)?; + debug!("linked server {}", name); + ctx.servers.insert(name.to_owned()); + } + + Command::NICK => { + let prefix = msg.prefix.ok_or(MALFORMED)?; + let name = *msg.params.first().ok_or(MALFORMED)?; + if ctx.nicks.contains(prefix) { + ctx.nicks.remove(prefix); + debug!("updated nick for {} to {}", prefix, name); + } else { + debug!("connecting user {}", name); + } + ctx.nicks.insert(name.to_owned()); + }, + + Command::PING => { + let prefix = msg.prefix.ok_or(MALFORMED)?; + let params = match msg.params.first() { + Some(m) => vec![prefix, m], + None => vec![prefix], + }; + ctx.writer.write_msg(Message::with_prefix(&ctx.cfg.server_name, Command::PONG, params)).await?; + ctx.writer.flush().await?; + } + + Command::PRIVMSG => { + let channel = msg.params.first().ok_or(MALFORMED)?.to_string(); + if let Some(link) = ctx.links.get_link(&channel) { + let author = msg.prefix.ok_or(MALFORMED)?.to_owned(); + let content = msg.params.get(1).ok_or(MALFORMED)?.to_string(); + ctx.tx.send(BMessage { + origin: (ctx.id, channel.clone()), + link: link.clone(), + author, + suffix: ctx.cfg.suffix.clone(), + content, + avatar: None, + })?; + } + } + + Command::ERROR => { + warn!("Error: {:?}", msg.params.first()); + } + + _ => (), + } + Ok(()) +} + +/* ++ PASS A_pitowoVsjRgZ 0210 proxy| ++ SERVER c.trimill.xyz :Server Info Text +- :trimill.xyz PASS C_kTNGh3SlfpJ5 0210 proxy| +- :trimill.xyz SERVER trimill.xyz 1 :Server Info Text +- :trimill.xyz NICK trimill 1 trimill server 1 + :trimill +- :trimill.xyz NJOIN #test2 :@trimill +- :trimill.xyz NJOIN #test :@trimill +- :trimill.xyz PING :trimill.xyz ++ :c.trimill.xyz PING :c.trimill.xyz ++ :c.trimill.xyz PONG trimill.xyz :trimill.xyz +- :trimill.xyz PONG c.trimill.xyz :c.trimill.xyz + */ diff --git a/src/bridge_irc_sp/config.rs b/src/bridge_irc_sp/config.rs new file mode 100644 index 0000000..ffa06dc --- /dev/null +++ b/src/bridge_irc_sp/config.rs @@ -0,0 +1,33 @@ +use std::sync::Arc; + +use serde::Deserialize; + +const fn default_port() -> u16 { 6667 } +const fn default_nick_len() -> u32 { 9 } +fn default_user_nick() -> String { + "abridged".to_owned() +} +fn default_suffix() -> Arc { + "[i]".into() +} + +#[derive(Clone, Debug, Deserialize)] +pub struct IrcSpConfig { + pub server_name: String, + + pub peer_host: String, + #[serde(default="default_port")] + pub peer_port: u16, + + pub password: String, + pub peer_password: String, + + #[serde(default="default_user_nick")] + pub user_nick: String, + + #[serde(default="default_nick_len")] + pub max_nick_len: u32, + + #[serde(default="default_suffix")] + pub suffix: Arc, +} diff --git a/src/bridge_irc_sp/message.rs b/src/bridge_irc_sp/message.rs new file mode 100644 index 0000000..ddd7ba2 --- /dev/null +++ b/src/bridge_irc_sp/message.rs @@ -0,0 +1,217 @@ +use std::{str::{Chars, FromStr}, iter::Peekable, ops::Range, fmt}; + +use strum_macros::{EnumString, Display}; + +#[allow(clippy::upper_case_acronyms)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumString, Display)] +pub enum Command { + PASS, + SERVER, + NICK, + NJOIN, + PING, + PONG, + MODE, + JOIN, + PRIVMSG, + ERROR, + #[strum(disabled)] + Number(u16), +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Message<'a> { + pub prefix: Option<&'a str>, + pub command: Command, + pub params: Vec<&'a str>, +} + +impl<'a> Message<'a> { + pub fn new(command: Command, params: Vec<&'a str>) -> Self { + Self { + prefix: None, + command, + params, + } + } + + pub fn with_prefix(prefix: &'a str, command: Command, params: Vec<&'a str>) -> Self { + Self { + prefix: Some(prefix), + command, + params, + } + } +} + +impl<'a> fmt::Display for Message<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(prefix) = self.prefix { + write!(f, ":{} ", prefix)?; + } + write!(f, "{}", self.command)?; + let paramc = self.params.len(); + if paramc > 0 { + for param in &self.params[..(paramc-1)] { + write!(f, " {}", param)?; + } + write!(f, " :{}", &self.params[paramc-1])?; + } + Ok(()) + } +} + +#[derive(Debug)] +pub struct ParseError { + msg: String, + span: Range, +} + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}: {}", self.span, self.msg) + } +} + +impl std::error::Error for ParseError {} + +struct Parser<'a> { + src: &'a str, + chars: Peekable>, + pos: usize, +} + +impl<'a> Parser<'a> { + fn next(&mut self) -> Option { + self.chars + .next() + .map(|c| { + self.pos += 1; + c + }) + } + + fn peek(&mut self) -> Option { + self.chars.peek().copied() + } + + fn read_word(&mut self) -> &'a str { + let start = self.pos; + while !matches!(self.peek(), Some(' ') | Some('\n') | Some('\r') | None) { + self.next(); + } + &self.src[start..self.pos] + } + + fn parse(&mut self) -> Result, ParseError> { + let prefix = if let Some(':') = self.peek() { + self.next(); + let p = self.read_word(); + match self.peek() { + Some(' ') => { self.next(); }, + Some(c) => return Err(ParseError { + msg: format!("expected space, found '{c}'"), + span: self.pos..(self.pos+1), + }), + None => return Err(ParseError { + msg: "expected space, found EOF".to_owned(), + span: self.pos..(self.pos+1) + }) + } + Some(p) + } else { + None + }; + let command = self.parse_command()?; + let mut params = Vec::new(); + while self.peek().is_some() { + params.push(self.parse_param()?); + } + Ok(Message { + prefix, + command, + params, + }) + } + + fn parse_param(&mut self) -> Result<&'a str, ParseError> { + let Some(' ') = self.peek() else { + let pos = self.pos; + return Err(ParseError { + msg: format!("expected space, found '{}'", self.next().unwrap()), + span: pos..(pos+1), + }) + }; + self.next(); + if let Some(':') = self.peek() { + self.next(); + let start = self.pos; + while self.next().is_some() {} + Ok(&self.src[start..]) + } else { + let start = self.pos; + while !matches!(self.peek(), Some(' ') | Some('\n') | Some('\r') | None) { + self.next(); + } + Ok(&self.src[start..self.pos]) + } + } + + fn parse_command(&mut self) -> Result { + let start = self.pos; + let name = self.read_word(); + if name.len() == 3 { + if let Ok(n) = name.parse::() { + return Ok(Command::Number(n)) + } + } + Command::from_str(name) + .map_err(|_| ParseError { + msg: format!("unknown command: {name}"), + span: start..self.pos + }) + } +} + +pub fn parse(msg: &str) -> Result { + let mut p = Parser { + src: msg, + chars: msg.chars().peekable(), + pos: 0, + }; + p.parse() +} + +#[cfg(test)] +mod tests { + use super::{parse, Message, Command, ParseError}; + + #[test] + fn test() -> Result<(), ParseError> { + assert_eq!( + parse(":example.org PONG irc.example.com :irc.example.com")?, + Message { + prefix: Some("example.org"), + command: Command::PONG, + params: vec!["irc.example.com", "irc.example.com"], + } + ); + assert_eq!( + parse("PASS AAAABBBB 0210-IRC+ ngIRCd|26.1:CHLMSXZ PZ")?, + Message { + prefix: None, + command: Command::PASS, + params: vec!["AAAABBBB", "0210-IRC+", "ngIRCd|26.1:CHLMSXZ", "PZ"], + } + ); + assert_eq!( + parse(":example.org SERVER example.org 1 :Server Info Text")?, + Message { + prefix: Some("example.org"), + command: Command::SERVER, + params: vec!["example.org", "1", "Server Info Text"], + } + ); + Ok(()) + } +} diff --git a/src/bridge_irc_sp/mod.rs b/src/bridge_irc_sp/mod.rs new file mode 100644 index 0000000..4c8fe42 --- /dev/null +++ b/src/bridge_irc_sp/mod.rs @@ -0,0 +1,7 @@ +mod config; +mod bridge; +mod network; +mod message; + +pub use config::IrcSpConfig; +pub use bridge::IrcSpTask; diff --git a/src/bridge_irc_sp/network.rs b/src/bridge_irc_sp/network.rs new file mode 100644 index 0000000..d6478fc --- /dev/null +++ b/src/bridge_irc_sp/network.rs @@ -0,0 +1,71 @@ +use std::{fmt, io}; + +use tokio::io::{Lines, AsyncBufRead, AsyncBufReadExt, AsyncWrite, AsyncWriteExt}; +use log::debug; + +use super::message::{Message, parse, ParseError}; + +#[derive(Debug)] +pub enum ReadMessageError { + String(&'static str), + ParseError(ParseError), + Io(std::io::Error), +} + +impl fmt::Display for ReadMessageError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ReadMessageError::String(e) => write!(f, "{e}"), + ReadMessageError::ParseError(e) => write!(f, "{e}"), + ReadMessageError::Io(e) => write!(f, "{e}"), + } + } +} +impl std::error::Error for ReadMessageError {} + + +pub struct MessageReader { + reader: Lines, + buf: String, +} + +impl MessageReader { + pub fn new(reader: R) -> Self { + Self { + reader: reader.lines(), + buf: String::with_capacity(0), + } + } + + pub async fn read_message(&mut self) -> Result { + match self.reader.next_line().await { + Ok(Some(line)) => { + self.buf = line; + parse(&self.buf).map_err(ReadMessageError::ParseError) + } + Ok(None) => Err(ReadMessageError::String("Lost connection to peer server")), + Err(e) => Err(ReadMessageError::Io(e)), + } + } +} + +pub struct MessageWriter { + writer: W, +} + +impl MessageWriter { + pub fn new(writer: W) -> Self { + Self { writer } + } + + pub async fn write_msg<'a>(&mut self, msg: Message<'a>) -> Result<(), io::Error> { + let msg = format!("{}\r\n", msg); + debug!("sending message: {:?}", msg); + self.writer.write_all(msg.as_bytes()).await + } + + pub async fn flush(&mut self) -> Result<(), io::Error> { + self.writer.flush().await + } + +} diff --git a/src/bridge_matrix/bridge.rs b/src/bridge_matrix/bridge.rs index c6cc876..1ddcefd 100644 --- a/src/bridge_matrix/bridge.rs +++ b/src/bridge_matrix/bridge.rs @@ -22,6 +22,7 @@ pub struct MatrixTask { struct Context { id: Id, tx: Sender, + suffix: Arc, rooms: Arc>, } @@ -41,6 +42,7 @@ impl Task for MatrixTask { client.add_event_handler_context(Arc::new(Context { id, tx, + suffix: self.config.suffix.clone(), rooms: self.links.clone() })); client.add_event_handler(handle_matrix_msg); @@ -99,6 +101,7 @@ async fn handle_matrix_msg(ev: SyncRoomMessageEvent, client: Client, room: Room, origin: (ctx.id, room.room_id().to_string()), link: link.clone(), author: sender.name().to_owned(), + suffix: ctx.suffix.clone(), content: body.to_owned(), avatar, })?; diff --git a/src/bridge_matrix/config.rs b/src/bridge_matrix/config.rs index 61ce86e..b88ec66 100644 --- a/src/bridge_matrix/config.rs +++ b/src/bridge_matrix/config.rs @@ -1,8 +1,16 @@ +use std::sync::Arc; + use matrix_sdk::ruma::OwnedUserId; use serde::Deserialize; +fn default_suffix() -> Arc { + "[m]".into() +} + #[derive(Debug, Deserialize)] pub struct MatrixConfig { pub user: OwnedUserId, pub password: String, + #[serde(default="default_suffix")] + pub suffix: Arc } diff --git a/src/config.rs b/src/config.rs index 6b6db20..83fe4c9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,10 +7,12 @@ use crate::supervisor::Task; #[derive(Debug, Deserialize)] #[serde(tag="platform")] -#[serde(rename_all="snake_case")] +#[serde(rename_all="kebab-case")] pub enum Node { #[cfg(feature="irc")] Irc(crate::bridge_irc::IrcTask), + #[cfg(feature="irc-sp")] + IrcSp(crate::bridge_irc_sp::IrcSpTask), #[cfg(feature="matrix")] Matrix(crate::bridge_matrix::MatrixTask), #[cfg(feature="discord")] @@ -22,6 +24,8 @@ impl Node { match self { #[cfg(feature="irc")] Node::Irc(t) => Box::new(t), + #[cfg(feature="irc-sp")] + Node::IrcSp(t) => Box::new(t), #[cfg(feature="matrix")] Node::Matrix(t) => Box::new(t), #[cfg(feature="discord")] diff --git a/src/main.rs b/src/main.rs index a043c75..e1eb96a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,8 @@ mod config; #[cfg(feature="irc")] mod bridge_irc; +#[cfg(feature="irc-sp")] +mod bridge_irc_sp; #[cfg(feature="matrix")] mod bridge_matrix; #[cfg(feature="discord")] diff --git a/src/supervisor.rs b/src/supervisor.rs index 13e81a4..6d2785c 100644 --- a/src/supervisor.rs +++ b/src/supervisor.rs @@ -1,4 +1,4 @@ -use std::{fmt, error::Error, time::Duration, any::Any, panic::AssertUnwindSafe, collections::HashMap}; +use std::{fmt, error::Error, time::Duration, any::Any, panic::AssertUnwindSafe, collections::HashMap, sync::Arc}; use async_trait::async_trait; use futures::{stream::FuturesUnordered, StreamExt, FutureExt}; @@ -36,6 +36,7 @@ pub struct Message { pub origin: (Id, String), pub link: Link, pub author: String, + pub suffix: Arc, pub content: String, pub avatar: Option, }