diff --git a/Cargo.lock b/Cargo.lock index 0a0ef5b..64d3c17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1695,6 +1695,7 @@ dependencies = [ "log", "matrix-sdk", "serde", + "serde_json", "serenity", "sled", "tokio", @@ -2479,9 +2480,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.67" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7f9e390c27c3c0ce8bc5d725f6e4d30a29d26659494aa4b17535f7522c5c950" +checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8" dependencies = [ "itoa", "ryu", diff --git a/Cargo.toml b/Cargo.toml index e9cce53..c9f4f97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ env_logger = "0.9.0" html-escape = "0.2.9" html5ever = "0.25.1" kuchiki = "0.8.1" +serde_json = "1.0.68" [dependencies.serenity] version = "0.10.9" diff --git a/src/bridgers.rs b/src/bridgers.rs index 79c85a1..6db2c31 100644 --- a/src/bridgers.rs +++ b/src/bridgers.rs @@ -176,6 +176,10 @@ impl Bridgers { } pub async fn send_message(&self, message: SentMessage) { + if self.get_related_messages(&message.source).is_some() { + return; + } + let mut related_messages = vec![message.source.clone()]; if let Some(discord) = self.discord.lock().unwrap().borrow().as_ref() { @@ -250,8 +254,6 @@ impl Bridgers { pub async fn delete_message(&self, message: DeletedMessage) { if let Some(related_messages) = self.get_related_messages(&message.reference) { for related_message in related_messages.iter() { - // TODO: What do we want to do with the success / failure ??? - match related_message { MessageReference::Discord(channel_id, message_id) => { if let Some(discord) = self.discord.lock().unwrap().borrow().as_ref() { diff --git a/src/discord.rs b/src/discord.rs index 52afe56..127f8cd 100644 --- a/src/discord.rs +++ b/src/discord.rs @@ -24,20 +24,28 @@ struct DiscordHandler { event_tx: mpsc::UnboundedSender, } +async fn get_message_author(ctx: &Context, message: &Message) -> MessageAuthor { + MessageAuthor { + display_name: message + .author_nick(&ctx) + .await + .unwrap_or_else(|| message.author.name.clone()), + avatar_url: message + .author + .static_avatar_url() + .unwrap_or_else(|| message.author.default_avatar_url()), + service_name: "discord".to_string(), + } +} + #[async_trait] impl EventHandler for DiscordHandler { async fn ready(&self, ctx: Context, _ready: Ready) { let _ = self.ctx_tx.send(ctx); info!("Discord ready!"); - - // TODO: Scan for channels to link } async fn message(&self, ctx: Context, message: Message) { - if message.author.id == ctx.cache.current_user_id().await { - return; - } - if let Some(target) = message.content.strip_prefix("phoebe!link ") { if message .member(&ctx) @@ -72,12 +80,7 @@ impl EventHandler for DiscordHandler { let _ = self.event_tx.send(MessageEvent::Send(SentMessage { source: message_ref, content, - author: MessageAuthor { - display_name: message - .author_nick(&ctx) - .await - .unwrap_or(message.author.name), - }, + author: get_message_author(&ctx, &message).await, replies_to, })); } @@ -104,12 +107,7 @@ impl EventHandler for DiscordHandler { let _ = self.event_tx.send(MessageEvent::Edit(EditedMessage { replacing: message_ref, content, - author: MessageAuthor { - display_name: new_message - .author_nick(&ctx) - .await - .unwrap_or(new_message.author.name), - }, + author: get_message_author(&ctx, &new_message).await, })); } } @@ -128,20 +126,117 @@ impl EventHandler for DiscordHandler { } } +async fn get_webhook_for_channel(discord_ctx: &Context, channel: &ChannelId) -> Option { + if let Ok(webhooks) = channel.webhooks(discord_ctx).await { + for webhook in webhooks { + if matches!(webhook.name.as_deref(), Some("phoebe")) { + return Some(webhook); + } + } + } + + None +} + +async fn get_or_create_webhook_for_channel( + discord_ctx: &Context, + channel: &ChannelId, +) -> Option { + if let Some(webhook) = get_webhook_for_channel(discord_ctx, channel).await { + return Some(webhook); + } + + if let Ok(webhook) = channel.create_webhook(discord_ctx, "phoebe").await { + return Some(webhook); + } + + None +} + +async fn create_webhook_reply_embeds( + discord_ctx: &Context, + reply: Option<(u64, u64)>, +) -> Vec { + if let Some((channel_id, message_id)) = reply { + if let Ok(replied_message) = ChannelId(channel_id) + .message(discord_ctx, MessageId(message_id)) + .await + { + let replied_author_name = format!( + "{} ↩️", + replied_message + .author_nick(discord_ctx) + .await + .as_ref() + .unwrap_or(&replied_message.author.name) + ); + + let reply_description = format!( + "**[Reply to:]({})**\n{}", + replied_message.id.link( + ChannelId(channel_id), + discord_ctx + .cache + .guild_channel(channel_id) + .await + .map(|gc| gc.guild_id) + ), + &replied_message.content + ); + + return vec![Embed::fake(|e| { + e.author(|a| { + a.icon_url( + &replied_message + .author + .static_avatar_url() + .unwrap_or_else(|| replied_message.author.default_avatar_url()), + ) + .name(replied_author_name) + }) + .description(reply_description) + })]; + } + } + + vec![] +} + pub async fn forward_to_discord( discord_ctx: &Context, channel: ChannelId, message: &SentMessage, reply: Option<(u64, u64)>, ) -> Option { + if let Some(webhook) = get_or_create_webhook_for_channel(discord_ctx, &channel).await { + let reply_embeds = create_webhook_reply_embeds(discord_ctx, reply).await; + + return webhook + .execute(discord_ctx, true, |w| { + w.content(format_discord(&message.content)) + .username(format!( + "{} ({})", + &message.author.display_name, &message.author.service_name + )) + .avatar_url(&message.author.avatar_url) + .embeds(reply_embeds) + }) + .await + .ok() + .flatten() + .as_ref() + .map(MessageReference::from); + } + channel - .send_message(&discord_ctx, |m| { - let b = m.content(format_discord(&message.content)); + .send_message(discord_ctx, |m| { + let content = format_discord(&message.content); if let Some((channel_id, message_id)) = reply { - b.reference_message((ChannelId(channel_id), MessageId(message_id))) + m.content(&content) + .reference_message((ChannelId(channel_id), MessageId(message_id))) } else { - b + m.content(&content) } }) .await @@ -156,6 +251,17 @@ pub async fn edit_on_discord( message_id: MessageId, message: &EditedMessage, ) -> Option { + if let Some(webhook) = get_or_create_webhook_for_channel(discord_ctx, &channel_id).await { + return webhook + .edit_message(discord_ctx, message_id, |w| { + w.content(format_discord(&message.content)) + }) + .await + .as_ref() + .ok() + .map(MessageReference::from); + } + channel_id .edit_message(&discord_ctx, &message_id, |m| { m.content(format_discord(&message.content)) @@ -172,6 +278,13 @@ pub async fn delete_on_discord( message_id: MessageId, _message: &DeletedMessage, ) -> bool { + if let Some(webhook) = get_or_create_webhook_for_channel(discord_ctx, &channel_id).await { + return webhook + .delete_message(discord_ctx, message_id) + .await + .is_ok(); + } + channel_id .delete_message(&discord_ctx, &message_id) .await diff --git a/src/matrix.rs b/src/matrix.rs index c7ca5fb..e6a847a 100644 --- a/src/matrix.rs +++ b/src/matrix.rs @@ -81,7 +81,6 @@ fn _find_content(event: &AnySyncRoomEvent) -> Option { struct MatrixHandler { message_tx: mpsc::UnboundedSender, - current_user_id: UserId, } impl MatrixHandler { @@ -92,6 +91,19 @@ impl MatrixHandler { .display_name() .unwrap_or_else(|| sender.name()) .to_string(), + avatar_url: sender + .avatar_url() + .map(|u| { + format!( + "https://matrix.org/_matrix/media/r0/thumbnail/{}?width=500&height=500", + u.as_str().to_string().replace("mxc://", "") + ) + }) + .unwrap_or_else(|| { + "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" + .to_string() + }), + service_name: "matrix".to_string(), }) } else { None @@ -138,10 +150,6 @@ async fn on_room_message_event( event: SyncMessageEvent, room: Room, ) { - if event.sender == ctx.current_user_id { - return; - } - if let Room::Joined(room) = room { if let Some(Relation::Replacement(replacement)) = &event.content.relates_to { on_message_edited(ctx, &event, room, replacement).await; @@ -363,12 +371,7 @@ pub async fn create_matrix_client( info!("Matrix starting…"); client.sync_once(SyncSettings::default()).await.unwrap(); - let current_user_id = client.user_id().await.unwrap(); - - let event_handler = Arc::new(MatrixHandler { - message_tx, - current_user_id, - }); + let event_handler = Arc::new(MatrixHandler { message_tx }); let on_msg_ctx = event_handler.clone(); client diff --git a/src/messages.rs b/src/messages.rs index 3b05b0e..cab7692 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -10,6 +10,8 @@ pub enum MessageReference { pub struct MessageAuthor { pub display_name: String, + pub avatar_url: String, + pub service_name: String, } pub struct SentMessage {