commit 0174333c36996162472a117add1ecdf495a34092
Author: Daniel Pérez <steew@psi.my.domain>
Date: Fri Jan 09 17:13:52 2026 +0000
diff --git a/Cargo.lock b/Cargo.lock
index 4f1b3a2..9214729 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -646 +647 @@ dependencies = [
"irc",
"rust-ini",
"serenity",
+ "sha1",
"tokio",
]
diff --git a/Cargo.toml b/Cargo.toml
index ff2260e..966af60 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -94 +95 @@ futures-util = "0.3.31"
irc = "1.1.0"
rust-ini = "0.21.3"
serenity = "0.12.5"
+sha1 = "0.10.6"
tokio = { version = "1.49.0", features = ["rt-multi-thread"] }
diff --git a/src/main.rs b/src/main.rs
index 8509a0e..0e04c27 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -18 +111 @@
+use std::collections::HashMap;
use std::str::FromStr;
use std::sync::Arc;
+use futures_util::StreamExt;
use ini::Ini;
use irc::client::data::Config;
+use irc::proto::Command;
use serenity::all::ChannelId;
use serenity::all::Webhook;
use serenity::prelude::*;
@@ -476 +509 @@ async fn main() {
+ let shared_cache = discord_client.cache.clone();
+
+ let avatars: HashMap<String, String> = HashMap::new();
@@ -5523 +616 @@ async fn main() {
- // TODO: remove and read from file
- // ======================================================================================
- // IRC initialization
- let config = Config {
- nickname: Some("belltoll".to_owned()),
- server: Some("irc.libera.chat".to_owned()),
- channels: vec!["##steew".to_owned()],
- ..Default::default()
- };
-
- let irc_client = irc::client::Client::from_config(config).await.unwrap();
- irc_client.identify().unwrap();
- let irc_sender = irc_client.sender().clone();
-
- let _irc_handle = spawn(async move {
- irc_producer(irc_client, irc_buffer_reference, irc_notify).await;
- });
@@ -867 +759 @@ async fn main() {
- assoc.bridge_assoc.insert(ChannelId::new(chid), ircs.clone());
+ let chid = ChannelId::new(chid);
+ assoc.bridge_assoc.insert(chid, ircs.clone());
+ // =========================
@@ -1086 +9931 @@ async fn main() {
+ println!("Avatar URLs:");
+ println!("{:?}", avatars.values());
+
+ // ======================================================================================
+ // IRC initialization
+ let config = Config {
+ nickname: Some("belltoll".to_owned()),
+ server: Some("irc.libera.chat".to_owned()),
+ channels: vec!["##steew".to_owned()],
+ ..Default::default()
+ };
+
+ let irc_client = irc::client::Client::from_config(config).await.unwrap();
+ irc_client.identify().unwrap();
+ let irc_sender = irc_client.sender().clone();
+
+ for v in assoc.bridge_assoc.values() {
+ for c in v.iter() {
+ irc_client.send_join(c).expect("Could not join channel {c}");
+ }
+ }
+
+ let _irc_handle = spawn(async move {
+ irc_producer(irc_client, irc_buffer_reference, irc_notify).await;
+ });
@@ -1177 +1338 @@ async fn main() {
- assoc
+ assoc,
+ avatars
diff --git a/src/relay.rs b/src/relay.rs
index ab7048c..f779a65 100644
--- a/src/relay.rs
+++ b/src/relay.rs
@@ -311 +314 @@ use std::collections::VecDeque;
use std::sync::Arc;
use irc::client::Sender;
+use serenity::all::Cache;
use serenity::all::ChannelId;
use serenity::all::ExecuteWebhook;
use serenity::all::Http;
use serenity::all::Webhook;
+use serenity::builder;
use serenity::prelude::*;
+use sha1::{Sha1, Digest};
use tokio::sync::Notify;
#[derive(Debug)]
@@ -1710 +2019 @@ pub enum RelayDirection {
}
+pub enum MessageType {
+ Normal,
+ // discord reply, containing the u64 message id that it references, and the author name.
+ ReplyDiscord((String, String)),
+ // irc reply, containing the message base64 hash it references.
+ ReplyIrc(String)
+}
+
pub struct RelayMessage {
- pub author: String
+ pub author: String,
+ pub message_type: MessageType
}
pub struct MessageBuffer {
@@ -827 +948 @@ impl Default for RelayMessage {
- author: String::new()
+ author: String::new(),
+ message_type: MessageType::Normal
}
@@ -10313 +11633 @@ impl TypeMapKey for RelayNotify {
}
+fn format_name(name: &str) -> String {
+ // XMPP RFC 3174 implementation
+ // get hash
+ let mut hasher = Sha1::new();
+ hasher.update(name);
+ let result = hasher.finalize();
+ // obtain the last 16 bits
+ let last16 = result.last_chunk::<2>().unwrap();
+ let last16_float = f32::from(((last16[0] as u16) << 8) | (last16[1] as u16));
+ let hue = last16_float * 360.0 / 65535.0;
+ let hue = hue.ceil();
+ let hue: u32 = hue.to_bits() % 87;
+ println!("{hue}");
+ // insert invisible character to prevent pings
+ let (split_name_first, split_name_rest) = name.split_at(1);
+
+ format!("\x03{hue:02}{split_name_first}{split_name_rest}\x03")
+}
+
pub async fn relay_consumer(
- assoc: RelayAssoc
+ assoc: RelayAssoc,
+ avatars: HashMap<String, String>
) {
@@ -13131 +16445 @@ pub async fn relay_consumer(
- let builder = ExecuteWebhook::new().content(pending.contents).username(pending.author);
+ let builder: ExecuteWebhook;
+ let avatar_url: Option<String> = None;
+ if let Some(avatar_url) = avatar_url {
+ println!("{avatar_url}");
+ builder = ExecuteWebhook::new().content(pending.contents).username(pending.author).avatar_url(avatar_url);
+ } else {
+ builder = ExecuteWebhook::new().content(pending.contents).username(pending.author);
+ }
+
- let unpingable_name = pending.author.clone();
- let (first, rest) = unpingable_name.split_at(1);
- let mut unpingable_name = String::new();
- unpingable_name.push(char::from_u32(0x03).unwrap());
- unpingable_name.push_str("04");
- unpingable_name.push_str(first);
- unpingable_name.push_str("");
- unpingable_name.push_str(rest);
- unpingable_name.push(char::from_u32(0x03).unwrap());
-
+ let source_name = format_name(&pending.author);
-
- let response = format!("<{}>: {}", unpingable_name, pending.contents);
+ let response;
+ let message_contents = pending.contents;
+ match pending.message_type {
+ MessageType::ReplyDiscord(tuple) => {
+ let target_reply_user = format_name(&tuple.1);
+ let origin_contents = tuple.0;
+ response = format!("<{source_name} replying to: {target_reply_user}> \"{origin_contents}\"\r\n\t↪ {message_contents}");
+ },
+ MessageType::Normal => {
+ response = format!("<{source_name}> {message_contents}");
+ },
+ _ => {
+ // found invalid message type here!!
+ println!("Invalid reply type found.");
+ return;
+ }
+ }
- _ => { panic!("Found no target to send the message to!") }
+ _ => { println!("Found no target to send the message to!") }
diff --git a/src/relay_discord.rs b/src/relay_discord.rs
index 1afabb2..00ea812 100644
--- a/src/relay_discord.rs
+++ b/src/relay_discord.rs
@@ -13 +15 @@
+use std::hash::Hash;
+
use serenity::all::Message;
use serenity::all::Ready;
use serenity::async_trait;
@@ -147 +169 @@ impl EventHandler for Handler {
- if msg.author.bot {
+ println!("author id: {}, cache id: {}", msg.author.name, ctx.cache.current_user().name);
+ if let Some(_) = msg.webhook_id {
+ println!("Same discord author as the bot");
@@ -2510 +2913 @@ impl EventHandler for Handler {
+ // check if message is a reply to also relay it
+ if let Some(reply) = msg.referenced_message {
+ new_message.message_type = MessageType::ReplyDiscord((reply.content, reply.author.name));
+ }
- println!("Added discord message to buffer.");
diff --git a/src/relay_irc.rs b/src/relay_irc.rs
index 2129a0f..3eb8c07 100644
--- a/src/relay_irc.rs
+++ b/src/relay_irc.rs
@@ -197 +1913 @@ pub async fn irc_producer(
+ println!("IRC!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+ println!("username: {uname}, current nick: {}", irc_client.current_nickname());
+ if uname.eq_ignore_ascii_case(irc_client.current_nickname()) {
+ println!("Ignoring message from myself!");
+ return;
+ };