Use webhooks where possible when interacting with Discord

legacy
Charlotte Som 2021-09-28 02:32:53 +01:00
parent a0d7b59a33
commit f3dce91862
6 changed files with 159 additions and 37 deletions

5
Cargo.lock generated
View File

@ -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",

View File

@ -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"

View File

@ -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() {

View File

@ -24,20 +24,28 @@ struct DiscordHandler {
event_tx: mpsc::UnboundedSender<MessageEvent>,
}
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<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);
}
}
}
None
}
async fn get_or_create_webhook_for_channel(
discord_ctx: &Context,
channel: &ChannelId,
) -> Option<Webhook> {
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<serde_json::Value> {
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<MessageReference> {
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<MessageReference> {
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

View File

@ -81,7 +81,6 @@ fn _find_content(event: &AnySyncRoomEvent) -> Option<AnyMessageEventContent> {
struct MatrixHandler {
message_tx: mpsc::UnboundedSender<MessageEvent>,
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<MessageEventContent>,
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

View File

@ -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 {