Thumbnail

steew/belltoll.git

Clone URL: https://git.buni.party/steew/belltoll.git

commit f82c903e5ab4b2749cdd0c9350e7c93c8706c8a7 Author: Daniel PĂ©rez <steew@psi.my.domain> Date: Tue Jan 06 23:07:34 2026 +0000 Formatting, started working on config file diff --git a/.gitignore b/.gitignore index 2f7896d..f8df8af 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +13 @@  target/ +*~ +config.ini \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 54a025c..4f1b3a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -626 +627 @@ dependencies = [   "env",   "futures-util",   "irc", + "rust-ini",   "serenity",   "tokio",  ] @@ -1816 +18226 @@ dependencies = [   "syn 1.0.109",  ]   +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "tiny-keccak", +] +  [[package]]  name = "core-foundation"  version = "0.9.4" @@ -2306 +25112 @@ version = "0.8.21"  source = "registry+https://github.com/rust-lang/crates.io-index"  checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"   +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +  [[package]]  name = "crypto-common"  version = "0.1.7" @@ -2916 +31815 @@ dependencies = [   "syn 2.0.113",  ]   +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] +  [[package]]  name = "encoding"  version = "0.2.33" @@ -10666 +110216 @@ dependencies = [   "vcpkg",  ]   +[[package]] +name = "ordered-multimap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +dependencies = [ + "dlv-list", + "hashbrown 0.14.5", +] +  [[package]]  name = "parking_lot"  version = "0.12.5" @@ -13716 +141716 @@ dependencies = [   "windows-sys 0.52.0",  ]   +[[package]] +name = "rust-ini" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7" +dependencies = [ + "cfg-if", + "ordered-multimap", +] +  [[package]]  name = "rustc-hash"  version = "2.1.1" @@ -18496 +190515 @@ dependencies = [   "time-core",  ]   +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] +  [[package]]  name = "tinystr"  version = "0.8.2" diff --git a/Cargo.toml b/Cargo.toml index 266b5c9..ff2260e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75 +76 @@ edition = "2024"  env = "1.0.1"  futures-util = "0.3.31"  irc = "1.1.0" +rust-ini = "0.21.3"  serenity = "0.12.5"  tokio = { version = "1.49.0", features = ["rt-multi-thread"] } diff --git a/src/main.rs b/src/main.rs index d9b1451..05aeb81 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17 +110 @@ +use std::collections::HashMap;  use std::collections::VecDeque; +use std::str::FromStr;  use std::sync::Arc;    use futures_util::StreamExt; +use ini::Ini;  use irc::client::Sender;  use irc::client::data::Config;  use irc::proto::Command; @@ -127 +156 @@ use serenity::all::Message;  use serenity::all::Ready;  use serenity::all::Webhook;  use serenity::async_trait; -use serenity::model::webhook;  use serenity::prelude::*;  use tokio::spawn;  use tokio::sync::Notify; @@ -208 +228 @@ use tokio::sync::Notify;  #[derive(Debug)]  enum RelayDirection { INVALID, - IRC2DIS, - DIS2IRC, + IRC2DIS(String), + DIS2IRC(ChannelId),  }    struct RelayMessage { @@ -466 +4822 @@ impl Default for RelayNotify { }  }   +struct RelayAssoc { + // stores the Discord - IRC channel bridge associations + bridge_assoc: HashMap<ChannelId, Vec<String>>, + // stores the webhook URL for the discord channels + chid_webhook_assoc: HashMap<ChannelId, String>, +} + +impl Default for RelayAssoc { + fn default() -> Self { + RelayAssoc { + bridge_assoc: HashMap::new(), + chid_webhook_assoc: HashMap::new() + } + } +} +  impl Default for RelayMessage { fn default() -> Self { RelayMessage { @@ -907 +1087 @@ impl EventHandler for Handler { // create a new message with the received discord message contents let mut new_message = RelayMessage::default(); new_message.contents = msg.content; - new_message.direction = RelayDirection::DIS2IRC; + new_message.direction = RelayDirection::DIS2IRC(msg.channel_id); new_message.author = msg.author.name; // push the pending message to the relay buffer relay_buffer.pending_relay_messages.push_back(new_message); @@ -1136 +1317 @@ async fn relay_consumer( http: Arc<Http>, webhook: Webhook, sender: Sender, + assoc: RelayAssoc  ) { loop { // await for new relay pending events @@ -12713 +14613 @@ async fn relay_consumer( pending.contents, pending.direction ); match pending.direction { - RelayDirection::IRC2DIS => { + RelayDirection::IRC2DIS(chan) => { // let chanid = ChannelId::new(591954698664149044); // chanid.say(http.clone(), pending.contents).await.unwrap(); let builder = ExecuteWebhook::new().content(pending.contents).username(pending.author); webhook.execute(&http, false, builder).await.expect("Could not execute webhook."); } - RelayDirection::DIS2IRC => { + RelayDirection::DIS2IRC(chan) => { let unpingable_name = pending.author.clone(); let (first, rest) = unpingable_name.split_at(1); let mut unpingable_name = String::new(); @@ -14939 +16879 @@ async fn relay_consumer( }  }   +async fn irc_producer( + mut irc_client: irc::client::Client, + buffer_reference: Arc<RwLock<MessageBuffer>>, + notify: Arc<RelayNotify> +) { + let mut irc_stream: irc::client::ClientStream = irc_client.stream().unwrap(); + + while let Ok(Some(message)) = irc_stream.next().await.transpose() { + let msg_clone = message.clone(); + match message.command { + Command::PRIVMSG(_, contents) => { + { + let uname = msg_clone.source_nickname().unwrap(); + let mut buffer = buffer_reference.write().await; + let mut new_message = RelayMessage::default(); + new_message.contents = contents; + new_message.direction = RelayDirection::IRC2DIS(String::from_str(msg_clone.response_target().unwrap()).unwrap()); + new_message.author.push_str(uname); + buffer.pending_relay_messages.push_back(new_message); + notify.notify.notify_one(); + } + println!("Added IRC message to buffer"); + } + _ => {} + } + } + +}  #[tokio::main]  async fn main() { - let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment"); + // Shared data initialization + // ====================================================================================== + let buffer_reference = Arc::new(RwLock::new(MessageBuffer::default())); + let notify = Arc::new(RelayNotify::default()); + + let discord_notify = notify.clone(); + let discord_buffer_reference = buffer_reference.clone(); + let irc_notify = notify.clone(); + let irc_buffer_reference = buffer_reference.clone();   + // Discord initialization + // ====================================================================================== + let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment"); + let intents = GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT; let mut discord_client = serenity::Client::builder(&token, intents) .event_handler(Handler) .await .expect("Err creating client");   - let buffer_reference = Arc::new(RwLock::new(MessageBuffer::default())); - let notify = Arc::new(RelayNotify::default()); - // set the arbitrary data mutex as write { let mut data = discord_client.data.write().await; - data.insert::<MessageBuffer>(buffer_reference.clone()); - data.insert::<RelayNotify>(notify.clone()); + data.insert::<MessageBuffer>(discord_buffer_reference); + data.insert::<RelayNotify>(discord_notify); } - + + // shared http sender for the discord client let shared_http = discord_client.http.clone(); - let shared_notify = notify.clone(); - let shared_buffer_reference = buffer_reference.clone(); - - let webhook = Webhook::from_url(&shared_http, "").await.unwrap(); - - // spawn discord client + + // spawn discord client async thread let _client_handle = spawn(async move { if let Err(why) = discord_client.start().await { eprintln!("Client error: {why:?}"); } });   + // TODO: remove and read from file + let webhook = Webhook::from_url(&shared_http, + "").await.unwrap(); + + // ====================================================================================== + // IRC initialization let config = Config { nickname: Some("belltoll".to_owned()), server: Some("irc.libera.chat".to_owned()), @@ -18940 +24846 @@ async fn main() { ..Default::default() };   - let mut irc_client = irc::client::Client::from_config(config).await.unwrap(); + let irc_client = irc::client::Client::from_config(config).await.unwrap(); irc_client.identify().unwrap(); + let irc_sender = irc_client.sender().clone();   - let mut irc_stream: irc::client::ClientStream = irc_client.stream().unwrap(); + let _irc_handle = spawn(async move { + irc_producer(irc_client, irc_buffer_reference, irc_notify).await; + }); + // Configuration file read for channel bridge associations + // ====================================================================================== + let ini_conf_file = "config.ini"; + let ini = Ini::load_from_file(ini_conf_file).expect("A config.ini file is needed"); + + let mut assoc = RelayAssoc::default(); + + match ini.section(Some("assoc")) { + Some(section_contents) => { + for (d_channel, i_channel) in section_contents.iter() { + let chid = u64::from_str(d_channel).expect("Channel ID is not valid! {d_channel}"); + let irc_chan = String::from_str(i_channel).expect("Channel name is not valid! {i_channel}"); + let mut ircs = Vec::new(); + ircs.push(irc_chan); + assoc.bridge_assoc.insert(ChannelId::new(chid), ircs); + } + }, + None => { panic!("Expected an [assoc] section with bridge associations.") }, + } + println!("{:?}", assoc.bridge_assoc.keys()); + println!("{:?}", assoc.bridge_assoc.values());   - // spawn relay consumer + // ====================================================================================== + // Relay consumer thread spawn let _relay_handle = spawn(async move { relay_consumer( - shared_buffer_reference, - shared_notify, + buffer_reference, + notify, shared_http, webhook, - irc_client.sender(), + irc_sender, + assoc ) .await; }); - - while let Ok(Some(message)) = irc_stream.next().await.transpose() { - let msg_clone = message.clone(); - match message.command { - Command::PRIVMSG(_, contents) => { - { - let uname = msg_clone.source_nickname().unwrap(); - let mut buffer = buffer_reference.write().await; - let mut new_message = RelayMessage::default(); - new_message.contents = contents; - new_message.direction = RelayDirection::IRC2DIS; - new_message.author.push_str(uname); - buffer.pending_relay_messages.push_back(new_message); - notify.notify.notify_one(); - } - println!("Added IRC message to buffer"); - } - _ => {} - } - }  }