diff --git a/Cargo.lock b/Cargo.lock index 77918d8..9e19e8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,22 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "abridged" +version = "0.1.0" +dependencies = [ + "async-trait", + "env_logger", + "futures", + "irc", + "log", + "matrix-sdk", + "serde", + "serenity", + "tokio", + "toml 0.7.4", +] + [[package]] name = "adler" version = "1.0.2" @@ -123,9 +139,9 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ - "proc-macro2 1.0.56", + "proc-macro2 1.0.59", "quote 1.0.27", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -134,9 +150,9 @@ version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ - "proc-macro2 1.0.56", + "proc-macro2 1.0.59", "quote 1.0.27", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -200,9 +216,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f1e31e207a6b8fb791a38ea3105e6cb541f55e4d029902d3039a4ad07cc4105" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "base64ct" @@ -259,9 +275,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.12.2" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "byteorder" @@ -483,7 +499,7 @@ checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.56", + "proc-macro2 1.0.59", "quote 1.0.27", "strsim", "syn 1.0.109", @@ -539,7 +555,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f91d4cfa921f1c05904dc3c57b4a32c38aed3340cce209f3a6fd1478babafc4" dependencies = [ "darling", - "proc-macro2 1.0.56", + "proc-macro2 1.0.59", "quote 1.0.27", "syn 1.0.109", ] @@ -586,9 +602,9 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ - "proc-macro2 1.0.56", + "proc-macro2 1.0.59", "quote 1.0.27", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -860,9 +876,9 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ - "proc-macro2 1.0.56", + "proc-macro2 1.0.59", "quote 1.0.27", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -1205,9 +1221,9 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi 0.3.1", "libc", @@ -1593,22 +1609,6 @@ dependencies = [ "windows-sys 0.45.0", ] -[[package]] -name = "multibridge" -version = "0.1.0" -dependencies = [ - "async-trait", - "env_logger", - "futures", - "irc", - "log", - "matrix-sdk", - "serde", - "serenity", - "tokio", - "toml 0.7.4", -] - [[package]] name = "native-tls" version = "0.2.11" @@ -1689,9 +1689,9 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ - "proc-macro2 1.0.56", + "proc-macro2 1.0.59", "quote 1.0.27", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -1813,9 +1813,9 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" dependencies = [ - "proc-macro2 1.0.56", + "proc-macro2 1.0.59", "quote 1.0.27", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -1884,9 +1884,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" dependencies = [ "unicode-ident", ] @@ -1909,7 +1909,7 @@ checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", "itertools", - "proc-macro2 1.0.56", + "proc-macro2 1.0.59", "quote 1.0.27", "syn 1.0.109", ] @@ -1929,7 +1929,7 @@ version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" dependencies = [ - "proc-macro2 1.0.56", + "proc-macro2 1.0.59", ] [[package]] @@ -2138,9 +2138,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.1" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390" dependencies = [ "aho-corasick", "memchr", @@ -2149,9 +2149,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] name = "reqwest" @@ -2159,7 +2159,7 @@ version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" dependencies = [ - "base64 0.21.1", + "base64 0.21.2", "bytes", "encoding_rs", "futures-core", @@ -2304,7 +2304,7 @@ checksum = "0f82e91eb61cd86d9287303133ee55b54618eccb75a522cc22a42c15f5bda340" dependencies = [ "once_cell", "proc-macro-crate", - "proc-macro2 1.0.56", + "proc-macro2 1.0.59", "quote 1.0.27", "ruma-identifiers-validation", "serde", @@ -2369,7 +2369,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64 0.21.1", + "base64 0.21.2", ] [[package]] @@ -2480,9 +2480,9 @@ version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" dependencies = [ - "proc-macro2 1.0.56", + "proc-macro2 1.0.59", "quote 1.0.27", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -2673,18 +2673,18 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.56", + "proc-macro2 1.0.59", "quote 1.0.27", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.15" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" dependencies = [ - "proc-macro2 1.0.56", + "proc-macro2 1.0.59", "quote 1.0.27", "unicode-ident", ] @@ -2726,9 +2726,9 @@ version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ - "proc-macro2 1.0.56", + "proc-macro2 1.0.59", "quote 1.0.27", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -2807,9 +2807,9 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ - "proc-macro2 1.0.56", + "proc-macro2 1.0.59", "quote 1.0.27", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -2925,9 +2925,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.19.9" +version = "0.19.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d964908cec0d030b812013af25a0e57fddfadb1e066ecc6681d86253129d4f" +checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" dependencies = [ "indexmap", "serde", @@ -2961,9 +2961,9 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ - "proc-macro2 1.0.56", + "proc-macro2 1.0.59", "quote 1.0.27", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -3032,9 +3032,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-normalization" @@ -3199,9 +3199,9 @@ dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2 1.0.56", + "proc-macro2 1.0.59", "quote 1.0.27", - "syn 2.0.15", + "syn 2.0.16", "wasm-bindgen-shared", ] @@ -3233,9 +3233,9 @@ version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" dependencies = [ - "proc-macro2 1.0.56", + "proc-macro2 1.0.59", "quote 1.0.27", - "syn 2.0.15", + "syn 2.0.16", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3560,7 +3560,7 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ - "proc-macro2 1.0.56", + "proc-macro2 1.0.59", "quote 1.0.27", - "syn 2.0.15", + "syn 2.0.16", ] diff --git a/Cargo.toml b/Cargo.toml index 1e004d2..ca1ee97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,9 @@ [package] -name = "multibridge" +name = "abridged" version = "0.1.0" edition = "2021" -description = "Bridging between IRC, Discord, and Matrix" +description = "Configurable and fault-tolerant bridge for Discord, Matrix, and IRC" +readme = "README.md" [features] # platforms diff --git a/README.md b/README.md index e69de29..c49eea5 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,29 @@ +# Abridged + +Abridged is a configurable and fault-tolerant bridge for Discord, IRC, and Matrix. + +## Features + +- Bridge between multiple IRC servers and multiple Discord/Matrix bots +- Automatically and independently restart bridge nodes on error +- Feature flags to enable/disable each supported platform + +## Configuration + +An example configuration is provided in [example-config.toml](example-config.toml). The path to the configuration should be passed as the first command-line argument. + +## Building + +```sh +cargo build --release +``` + +The following feature flags are enabled: +- `irc` - enable IRC support +- `discord` - enable Discord support +- `matrix` - enable Matrix support +- `matrix-e2ee` - enable end-to-end encryption support for Matrix +- `tls-rustls` use rustls for TLS +- `tls-native` use the native TLS library (probably OpenSSL) for TLS + +`tls-rustls` and `tls-native` are mutually exclusive. By default, all platforms and platform-specific features are enabled and rustls is used for TLS. diff --git a/example-config.toml b/example-config.toml index 3e76b5e..78611cb 100644 --- a/example-config.toml +++ b/example-config.toml @@ -29,6 +29,7 @@ platform = "discord" [nodes.cccc.config] token = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.ABCDEF.0123456789abcdefghijklmnopqrstuvwxyzab" +webhooks = { "123456789012345678" = "9123456789123456789" } [nodes.cccc.links] "123456789012345678" = "example-link" diff --git a/src/bridge_discord/bridge.rs b/src/bridge_discord/bridge.rs index 2b5c4ea..e8e102c 100644 --- a/src/bridge_discord/bridge.rs +++ b/src/bridge_discord/bridge.rs @@ -37,12 +37,11 @@ impl Task for DiscordTask { let bridge_handler = async move { loop { let msg = rx.recv().await?; - handle_bridge_msg(msg, id, &self.links, http.as_ref()).await?; + self.handle_bridge_msg(msg, id, http.as_ref()).await?; } }; - debug!("{id}: built client, starting"); - + debug!("{id}: entering event loop"); tokio::select! { result = bridge_handler => result, result = client.start() => result.map_err(|e| e.into()), @@ -50,18 +49,29 @@ impl Task for DiscordTask { } } -async fn handle_bridge_msg(msg: BMessage, id: Id, links: &Linkmap, http: &Http) -> TaskResult { - let content = format!("<{}> {}", msg.author, msg.content); +impl DiscordTask { + async fn handle_bridge_msg(&self, msg: BMessage, id: Id, http: &Http) -> TaskResult { + let content = format!("<{}> {}", msg.author, msg.content); - for channel in links.get_channels(&msg.link) { - if msg.origin.0 == id && msg.origin.1 == channel.to_string() { - continue + for channel in self.links.get_channels(&msg.link) { + if msg.origin.0 == id && msg.origin.1 == channel.to_string() { + continue + } + + debug!("{id}: bridging message from {:?}/{:?} to {channel:?}", msg.origin, msg.link); + if let Some(hook) = self.config.webhooks.get(channel) { + let hook = http.get_webhook(*hook.as_u64()).await?; + 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("")) + }).await?; + } else { + channel.say(http, &content).await?; + } } - - debug!("{id}: bridging message from {:?}/{:?} to {channel:?}", msg.origin, msg.link); - channel.say(http, &content).await?; - } - Ok(()) + Ok(()) + } } struct Handler { @@ -73,10 +83,11 @@ struct Handler { #[async_trait] impl EventHandler for Handler { async fn message(&self, ctx: Context, msg: Message) { - if msg.author.id == ctx.cache.current_user_id() { + if msg.author.bot { return } + let avatar = msg.author.avatar_url(); let Some(link) = self.links.get_link(&msg.channel_id) else { return }; let author = msg.author.nick_in(ctx.http, msg.guild_id.expect("failed to get guild ID")) .await.unwrap_or(msg.author.name); @@ -88,6 +99,7 @@ impl EventHandler for Handler { link: link.clone(), author, content: msg.content, + avatar, }).expect("failed to broadcast message"); } diff --git a/src/bridge_discord/config.rs b/src/bridge_discord/config.rs index d0c55a6..fc873dc 100644 --- a/src/bridge_discord/config.rs +++ b/src/bridge_discord/config.rs @@ -1,6 +1,11 @@ +use std::collections::HashMap; + use serde::Deserialize; +use serenity::model::prelude::{WebhookId, ChannelId}; #[derive(Debug, Deserialize)] pub struct DiscordConfig { pub token: String, + #[serde(default)] + pub webhooks: HashMap, } diff --git a/src/bridge_irc/bridge.rs b/src/bridge_irc/bridge.rs index f4d1ebd..123596f 100644 --- a/src/bridge_irc/bridge.rs +++ b/src/bridge_irc/bridge.rs @@ -41,6 +41,7 @@ impl IrcTask { link: link.clone(), author: author.to_owned(), content: message.clone(), + avatar: None, })?; } Ok(()) diff --git a/src/bridge_irc/config.rs b/src/bridge_irc/config.rs index 2019c89..ffd55ac 100644 --- a/src/bridge_irc/config.rs +++ b/src/bridge_irc/config.rs @@ -1,10 +1,16 @@ use serde::Deserialize; +const fn default_port() -> u16 { 6667 } +const fn default_tls() -> bool { true } + #[derive(Clone, Debug, Deserialize)] pub struct IrcConfig { pub server: String, + #[serde(default="default_port")] pub port: u16, + #[serde(default="default_tls")] pub tls: bool, pub nick: String, + #[serde(default)] pub alt_nicks: Vec, } diff --git a/src/bridge_matrix/bridge.rs b/src/bridge_matrix/bridge.rs index 0a086f7..80c4605 100644 --- a/src/bridge_matrix/bridge.rs +++ b/src/bridge_matrix/bridge.rs @@ -4,7 +4,7 @@ use async_trait::async_trait; use futures::StreamExt; use log::debug; use matrix_sdk::{Client, config::SyncSettings, event_handler::Ctx, room::Room}; -use matrix_sdk::ruma::RoomId; +use matrix_sdk::ruma::{RoomId, MxcUri}; use matrix_sdk::ruma::events::room::message::{SyncRoomMessageEvent, RoomMessageEventContent}; use serde::Deserialize; use tokio::select; @@ -65,6 +65,23 @@ impl Task for MatrixTask { } } +async fn mxc_thumbnail(client: &Client, mxc: &MxcUri, width: u32, height: u32) -> Option { + let mut hs = client.homeserver().await; + + let (server_name, media_id) = mxc.parts().ok()?; + + hs.path_segments_mut().ok()? + .extend(["_matrix", "media", "v3", "thumbnail"]) + .push(server_name.as_str()) + .push(media_id); + + hs.query_pairs_mut() + .append_pair("width", &width.to_string()) + .append_pair("height", &height.to_string()); + + Some(hs.to_string()) +} + async fn handle_matrix_msg(ev: SyncRoomMessageEvent, client: Client, room: Room, ctx: Ctx>) -> TaskResult { if Some(ev.sender()) == client.user_id() { return Ok(()) } let SyncRoomMessageEvent::Original(ev) = ev else { return Ok(()) }; @@ -72,13 +89,19 @@ async fn handle_matrix_msg(ev: SyncRoomMessageEvent, client: Client, room: Room, let Some(link) = ctx.rooms.get_link(room.room_id().as_str()) else { return Ok(()) }; let body = ev.content.body(); + let avatar = match sender.avatar_url() { + None => None, + Some(u) => mxc_thumbnail(&client, u, 128, 128).await, + }; + println!("AVATAR: {:?}", avatar); - debug!("{}: broadcasting message from {:?} to {:?}", ctx.id, room, link); + debug!("{}: broadcasting message from {:?} ({}) to {:?}", ctx.id, room.name(), room.room_id(), link); ctx.tx.send(Message { origin: (ctx.id, room.room_id().to_string()), link: link.clone(), author: sender.name().to_owned(), content: body.to_owned(), + avatar, })?; Ok(()) } @@ -96,7 +119,7 @@ async fn handle_bridge_msg(id: Id, client: &Client, links: &Linkmap, msg let Ok(room) = <&RoomId>::try_from(room.as_str()) else { continue }; let Some(Room::Joined(room)) = client.get_room(room) else { continue }; - debug!("{id}: bridging message from {:?}/{:?} to {room:?}", msg.origin, msg.link); + debug!("{id}: bridging message from {:?}/{:?} to {:?} ({})", msg.origin, msg.link, room.name(), room.room_id()); room.send(content.clone(), None).await?; } Ok(()) diff --git a/src/main.rs b/src/main.rs index 6496017..a043c75 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,12 +32,9 @@ async fn main() -> ExitCode { let cmd_name = args.next() .unwrap_or_else(|| env!("CARGO_PKG_NAME").to_owned()); - let conf_filename = match args.next() { - Some(f) => f, - None => { - eprintln!("Usage: {cmd_name} "); - return ExitCode::FAILURE - } + let Some(conf_filename) = args.next() else { + eprintln!("Usage: {cmd_name} "); + return ExitCode::FAILURE }; debug!("reading config file"); diff --git a/src/supervisor.rs b/src/supervisor.rs index 15fcf4a..13e81a4 100644 --- a/src/supervisor.rs +++ b/src/supervisor.rs @@ -37,6 +37,7 @@ pub struct Message { pub link: Link, pub author: String, pub content: String, + pub avatar: Option, } pub type Sender = tokio::sync::broadcast::Sender;