use phoebe::{ attachments::attachment_to_url, mid_chat::{ChatAttachment, ChatMessage, ChatMessageEdit, ChatMessageReference, ChatReference}, prelude::*, }; use serenity::{http::AttachmentType, model::prelude::*, prelude::*}; use crate::{chat_conv, discord_reference, DiscordService}; async fn get_or_create_webhook_for_channel( discord: &mut DiscordService, 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); } } } if let Ok(webhook) = channel.create_webhook(&discord.ctx, "Phoebe").await { return Some(webhook); } None } async fn create_webhook_reply_embeds( discord_ctx: &Context, channel_id: ChannelId, message_id: MessageId, ) -> Vec { if let Ok(replied_message) = channel_id.message(discord_ctx, 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( 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![] } async fn create_discord_attachments(source: &'_ ChatMessage) -> Vec> { source .attachments .iter() .map(|a| match a { ChatAttachment::Online { url, media_type: Some(media_type), } => { if media_type.starts_with("image/") { AttachmentType::Image(url) } else { todo!("Handle non-image online attachment") } } ChatAttachment::Online { .. } => { todo!("Handle online attachment with no media_type") } ChatAttachment::InMemory { file_name, data, .. } => AttachmentType::Bytes { filename: file_name.clone(), data: data.into(), }, }) .collect() } pub async fn send_discord_message( discord: &mut DiscordService, source: &ChatMessage, destination_channel: ChatReference, ) -> Result { let channel_id = destination_channel.id.parse::()?; let discord_reply = if let Some(reply) = &source.replying { if let Some(reply_ref) = discord .lookup_message(reply, |r| future::ready(r.channel == destination_channel)) .await { assert_eq!(reply_ref.channel.service, "discord"); let channel_id: ChannelId = reply_ref.channel.id.parse().unwrap(); let message_id: MessageId = reply_ref.message_id.parse::().unwrap().into(); Some((channel_id, message_id)) } else { None } } else { None }; let files = create_discord_attachments(source).await; if let Some(webhook) = get_or_create_webhook_for_channel(&mut *discord, &channel_id).await { let reply_embeds = if let Some((channel, message)) = discord_reply { create_webhook_reply_embeds(&discord.ctx, channel, message).await } else { vec![] }; let avatar_url = attachment_to_url(&source.author.avatar).await; if let Some(sent_message) = webhook .execute(&discord.ctx, true, |w| { w.content(chat_conv::format(&source.content)) .username(format!( "{} ({})", &source.author.display_name, &source.author.reference.service )) .avatar_url(&avatar_url) .embeds(reply_embeds) .add_files(files.clone()) }) .await? { return Ok(ChatMessageReference::new( discord_reference(sent_message.channel_id), sent_message.id, )); } } let content = format!( "{} ({}): {}", source.author.display_name, source.author.reference.service, chat_conv::format(&source.content) ); let sent_message = channel_id .send_message(&discord.ctx, move |m| { let m = m.content(content); let m = m.add_files(files); if let Some(reply) = discord_reply { m.reference_message(reply) } else { m } }) .await?; Ok(ChatMessageReference::new( discord_reference(sent_message.channel_id), sent_message.id, )) } pub async fn delete_discord_message( discord: &mut DiscordService, message: &ChatMessageReference, ) -> Result<()> { let channel_id = message.channel.id.parse::()?; let message_id: MessageId = message.message_id.parse::()?.into(); if let Some(webhook) = get_or_create_webhook_for_channel(&mut *discord, &channel_id).await { if webhook .delete_message(&discord.ctx, message_id) .await .is_ok() { return Ok(()); } } channel_id.delete_message(&discord.ctx, message_id).await?; Ok(()) } pub async fn edit_discord_message( discord: &mut DiscordService, prev_origin: &ChatMessageReference, edit: &ChatMessageEdit, ) -> Result<()> { let channel_id = prev_origin.channel.id.parse::()?; let message_id: MessageId = prev_origin.message_id.parse::()?.into(); if let Some(webhook) = get_or_create_webhook_for_channel(&mut *discord, &channel_id).await { if webhook .edit_message(&discord.ctx, message_id, |w| { w.content(chat_conv::format(&edit.new_content)) }) .await .is_ok() { return Ok(()); } } let content = format!( "{} ({}): {}", edit.author.display_name, edit.author.reference.service, chat_conv::format(&edit.new_content) ); channel_id .edit_message(&discord.ctx, message_id, |m| m.content(content)) .await?; Ok(()) }