Use webhooks where possible when interacting with Discord
parent
a0d7b59a33
commit
f3dce91862
|
@ -1695,6 +1695,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"matrix-sdk",
|
"matrix-sdk",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"serenity",
|
"serenity",
|
||||||
"sled",
|
"sled",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -2479,9 +2480,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.67"
|
version = "1.0.68"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a7f9e390c27c3c0ce8bc5d725f6e4d30a29d26659494aa4b17535f7522c5c950"
|
checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
|
|
|
@ -16,6 +16,7 @@ env_logger = "0.9.0"
|
||||||
html-escape = "0.2.9"
|
html-escape = "0.2.9"
|
||||||
html5ever = "0.25.1"
|
html5ever = "0.25.1"
|
||||||
kuchiki = "0.8.1"
|
kuchiki = "0.8.1"
|
||||||
|
serde_json = "1.0.68"
|
||||||
|
|
||||||
[dependencies.serenity]
|
[dependencies.serenity]
|
||||||
version = "0.10.9"
|
version = "0.10.9"
|
||||||
|
|
|
@ -176,6 +176,10 @@ impl Bridgers {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_message(&self, message: SentMessage) {
|
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()];
|
let mut related_messages = vec![message.source.clone()];
|
||||||
|
|
||||||
if let Some(discord) = self.discord.lock().unwrap().borrow().as_ref() {
|
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) {
|
pub async fn delete_message(&self, message: DeletedMessage) {
|
||||||
if let Some(related_messages) = self.get_related_messages(&message.reference) {
|
if let Some(related_messages) = self.get_related_messages(&message.reference) {
|
||||||
for related_message in related_messages.iter() {
|
for related_message in related_messages.iter() {
|
||||||
// TODO: What do we want to do with the success / failure ???
|
|
||||||
|
|
||||||
match related_message {
|
match related_message {
|
||||||
MessageReference::Discord(channel_id, message_id) => {
|
MessageReference::Discord(channel_id, message_id) => {
|
||||||
if let Some(discord) = self.discord.lock().unwrap().borrow().as_ref() {
|
if let Some(discord) = self.discord.lock().unwrap().borrow().as_ref() {
|
||||||
|
|
157
src/discord.rs
157
src/discord.rs
|
@ -24,20 +24,28 @@ struct DiscordHandler {
|
||||||
event_tx: mpsc::UnboundedSender<MessageEvent>,
|
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]
|
#[async_trait]
|
||||||
impl EventHandler for DiscordHandler {
|
impl EventHandler for DiscordHandler {
|
||||||
async fn ready(&self, ctx: Context, _ready: Ready) {
|
async fn ready(&self, ctx: Context, _ready: Ready) {
|
||||||
let _ = self.ctx_tx.send(ctx);
|
let _ = self.ctx_tx.send(ctx);
|
||||||
info!("Discord ready!");
|
info!("Discord ready!");
|
||||||
|
|
||||||
// TODO: Scan for channels to link
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn message(&self, ctx: Context, message: Message) {
|
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 let Some(target) = message.content.strip_prefix("phoebe!link ") {
|
||||||
if message
|
if message
|
||||||
.member(&ctx)
|
.member(&ctx)
|
||||||
|
@ -72,12 +80,7 @@ impl EventHandler for DiscordHandler {
|
||||||
let _ = self.event_tx.send(MessageEvent::Send(SentMessage {
|
let _ = self.event_tx.send(MessageEvent::Send(SentMessage {
|
||||||
source: message_ref,
|
source: message_ref,
|
||||||
content,
|
content,
|
||||||
author: MessageAuthor {
|
author: get_message_author(&ctx, &message).await,
|
||||||
display_name: message
|
|
||||||
.author_nick(&ctx)
|
|
||||||
.await
|
|
||||||
.unwrap_or(message.author.name),
|
|
||||||
},
|
|
||||||
replies_to,
|
replies_to,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -104,12 +107,7 @@ impl EventHandler for DiscordHandler {
|
||||||
let _ = self.event_tx.send(MessageEvent::Edit(EditedMessage {
|
let _ = self.event_tx.send(MessageEvent::Edit(EditedMessage {
|
||||||
replacing: message_ref,
|
replacing: message_ref,
|
||||||
content,
|
content,
|
||||||
author: MessageAuthor {
|
author: get_message_author(&ctx, &new_message).await,
|
||||||
display_name: new_message
|
|
||||||
.author_nick(&ctx)
|
|
||||||
.await
|
|
||||||
.unwrap_or(new_message.author.name),
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(
|
pub async fn forward_to_discord(
|
||||||
discord_ctx: &Context,
|
discord_ctx: &Context,
|
||||||
channel: ChannelId,
|
channel: ChannelId,
|
||||||
message: &SentMessage,
|
message: &SentMessage,
|
||||||
reply: Option<(u64, u64)>,
|
reply: Option<(u64, u64)>,
|
||||||
) -> Option<MessageReference> {
|
) -> 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
|
channel
|
||||||
.send_message(&discord_ctx, |m| {
|
.send_message(discord_ctx, |m| {
|
||||||
let b = m.content(format_discord(&message.content));
|
let content = format_discord(&message.content);
|
||||||
|
|
||||||
if let Some((channel_id, message_id)) = reply {
|
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 {
|
} else {
|
||||||
b
|
m.content(&content)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
@ -156,6 +251,17 @@ pub async fn edit_on_discord(
|
||||||
message_id: MessageId,
|
message_id: MessageId,
|
||||||
message: &EditedMessage,
|
message: &EditedMessage,
|
||||||
) -> Option<MessageReference> {
|
) -> 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
|
channel_id
|
||||||
.edit_message(&discord_ctx, &message_id, |m| {
|
.edit_message(&discord_ctx, &message_id, |m| {
|
||||||
m.content(format_discord(&message.content))
|
m.content(format_discord(&message.content))
|
||||||
|
@ -172,6 +278,13 @@ pub async fn delete_on_discord(
|
||||||
message_id: MessageId,
|
message_id: MessageId,
|
||||||
_message: &DeletedMessage,
|
_message: &DeletedMessage,
|
||||||
) -> bool {
|
) -> 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
|
channel_id
|
||||||
.delete_message(&discord_ctx, &message_id)
|
.delete_message(&discord_ctx, &message_id)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -81,7 +81,6 @@ fn _find_content(event: &AnySyncRoomEvent) -> Option<AnyMessageEventContent> {
|
||||||
|
|
||||||
struct MatrixHandler {
|
struct MatrixHandler {
|
||||||
message_tx: mpsc::UnboundedSender<MessageEvent>,
|
message_tx: mpsc::UnboundedSender<MessageEvent>,
|
||||||
current_user_id: UserId,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MatrixHandler {
|
impl MatrixHandler {
|
||||||
|
@ -92,6 +91,19 @@ impl MatrixHandler {
|
||||||
.display_name()
|
.display_name()
|
||||||
.unwrap_or_else(|| sender.name())
|
.unwrap_or_else(|| sender.name())
|
||||||
.to_string(),
|
.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 {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -138,10 +150,6 @@ async fn on_room_message_event(
|
||||||
event: SyncMessageEvent<MessageEventContent>,
|
event: SyncMessageEvent<MessageEventContent>,
|
||||||
room: Room,
|
room: Room,
|
||||||
) {
|
) {
|
||||||
if event.sender == ctx.current_user_id {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Room::Joined(room) = room {
|
if let Room::Joined(room) = room {
|
||||||
if let Some(Relation::Replacement(replacement)) = &event.content.relates_to {
|
if let Some(Relation::Replacement(replacement)) = &event.content.relates_to {
|
||||||
on_message_edited(ctx, &event, room, replacement).await;
|
on_message_edited(ctx, &event, room, replacement).await;
|
||||||
|
@ -363,12 +371,7 @@ pub async fn create_matrix_client(
|
||||||
info!("Matrix starting…");
|
info!("Matrix starting…");
|
||||||
client.sync_once(SyncSettings::default()).await.unwrap();
|
client.sync_once(SyncSettings::default()).await.unwrap();
|
||||||
|
|
||||||
let current_user_id = client.user_id().await.unwrap();
|
let event_handler = Arc::new(MatrixHandler { message_tx });
|
||||||
|
|
||||||
let event_handler = Arc::new(MatrixHandler {
|
|
||||||
message_tx,
|
|
||||||
current_user_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
let on_msg_ctx = event_handler.clone();
|
let on_msg_ctx = event_handler.clone();
|
||||||
client
|
client
|
||||||
|
|
|
@ -10,6 +10,8 @@ pub enum MessageReference {
|
||||||
|
|
||||||
pub struct MessageAuthor {
|
pub struct MessageAuthor {
|
||||||
pub display_name: String,
|
pub display_name: String,
|
||||||
|
pub avatar_url: String,
|
||||||
|
pub service_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SentMessage {
|
pub struct SentMessage {
|
||||||
|
|
Loading…
Reference in New Issue