phoebe/services/phoebe-discord/src/sender.rs

236 lines
6.9 KiB
Rust

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<Webhook> {
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<serde_json::Value> {
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<AttachmentType<'_>> {
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<ChatMessageReference> {
let channel_id = destination_channel.id.parse::<ChannelId>()?;
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::<u64>().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::<ChannelId>()?;
let message_id: MessageId = message.message_id.parse::<u64>()?.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::<ChannelId>()?;
let message_id: MessageId = prev_origin.message_id.parse::<u64>()?.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(())
}