2021-09-14 04:15:37 +00:00
|
|
|
use std::sync::Arc;
|
2021-09-13 18:52:42 +00:00
|
|
|
|
2021-08-12 09:47:38 +00:00
|
|
|
use matrix_sdk::{
|
2021-09-16 03:29:00 +00:00
|
|
|
config::ClientConfig,
|
2021-09-12 23:06:11 +00:00
|
|
|
room::{Joined, Room},
|
2021-08-12 09:47:38 +00:00
|
|
|
ruma::{
|
2021-09-14 04:15:37 +00:00
|
|
|
api,
|
2021-08-12 09:47:38 +00:00
|
|
|
events::{
|
2021-09-14 04:15:37 +00:00
|
|
|
self,
|
2021-09-10 04:44:00 +00:00
|
|
|
room::{
|
2021-09-12 23:06:11 +00:00
|
|
|
message::{
|
|
|
|
FormattedBody, MessageEventContent, MessageFormat, MessageType, Relation,
|
2021-09-14 04:15:37 +00:00
|
|
|
Replacement,
|
2021-09-12 23:06:11 +00:00
|
|
|
},
|
2021-09-16 03:29:00 +00:00
|
|
|
redaction::{RedactionEventContent, SyncRedactionEvent},
|
2021-09-10 04:44:00 +00:00
|
|
|
},
|
2021-09-13 19:41:55 +00:00
|
|
|
AnyMessageEvent, AnyMessageEventContent, AnyRoomEvent, AnySyncRoomEvent,
|
|
|
|
SyncMessageEvent,
|
2021-08-12 09:47:38 +00:00
|
|
|
},
|
2021-09-14 04:15:37 +00:00
|
|
|
UserId,
|
2021-08-12 09:47:38 +00:00
|
|
|
},
|
|
|
|
};
|
2021-09-14 04:15:37 +00:00
|
|
|
pub use matrix_sdk::{
|
2021-09-16 03:29:00 +00:00
|
|
|
config::SyncSettings,
|
2021-09-14 04:15:37 +00:00
|
|
|
ruma::{EventId, RoomId},
|
|
|
|
Client,
|
|
|
|
};
|
2021-08-12 09:47:38 +00:00
|
|
|
|
2021-09-12 02:59:13 +00:00
|
|
|
use log::info;
|
2021-09-10 04:44:00 +00:00
|
|
|
use tokio::sync::mpsc;
|
|
|
|
use url::Url;
|
2021-08-12 09:47:38 +00:00
|
|
|
|
2021-09-12 02:59:13 +00:00
|
|
|
use crate::{
|
2021-09-12 23:06:11 +00:00
|
|
|
message_ast::{
|
|
|
|
convert_matrix, convert_plain, format_discord, format_matrix, MessageComponent,
|
|
|
|
MessageContent,
|
|
|
|
},
|
2021-09-16 03:29:00 +00:00
|
|
|
messages::{
|
|
|
|
DeletedMessage, EditedMessage, MessageAuthor, MessageEvent, MessageReference, SentMessage,
|
|
|
|
},
|
2021-09-12 02:59:13 +00:00
|
|
|
};
|
2021-09-10 04:44:00 +00:00
|
|
|
|
|
|
|
impl From<(&RoomId, &EventId)> for MessageReference {
|
|
|
|
fn from((room_id, event_id): (&RoomId, &EventId)) -> Self {
|
|
|
|
let room_string = room_id.as_str().to_string();
|
|
|
|
let event_string = event_id.as_str().to_string();
|
2021-08-12 09:47:38 +00:00
|
|
|
|
|
|
|
Self::Matrix(room_string, event_string)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-10 04:44:00 +00:00
|
|
|
fn _find_content(event: &AnySyncRoomEvent) -> Option<AnyMessageEventContent> {
|
|
|
|
match event {
|
|
|
|
AnySyncRoomEvent::Message(message) => Some(message.content()),
|
|
|
|
AnySyncRoomEvent::RedactedMessage(message) => {
|
|
|
|
if let Some(ref redaction_event) = message.unsigned().redacted_because {
|
|
|
|
Some(AnyMessageEventContent::RoomRedaction(
|
|
|
|
redaction_event.content.clone(),
|
|
|
|
))
|
|
|
|
} else {
|
|
|
|
Some(AnyMessageEventContent::RoomRedaction(
|
|
|
|
RedactionEventContent::new(),
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
AnySyncRoomEvent::RedactedState(state) => {
|
|
|
|
if let Some(ref redaction_event) = state.unsigned().redacted_because {
|
|
|
|
Some(AnyMessageEventContent::RoomRedaction(
|
|
|
|
redaction_event.content.clone(),
|
|
|
|
))
|
|
|
|
} else {
|
|
|
|
Some(AnyMessageEventContent::RoomRedaction(
|
|
|
|
RedactionEventContent::new(),
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct MatrixHandler {
|
2021-09-13 02:08:35 +00:00
|
|
|
message_tx: mpsc::UnboundedSender<MessageEvent>,
|
2021-09-12 02:59:13 +00:00
|
|
|
current_user_id: UserId,
|
2021-09-10 04:44:00 +00:00
|
|
|
}
|
2021-08-12 09:47:38 +00:00
|
|
|
|
2021-09-12 23:06:11 +00:00
|
|
|
impl MatrixHandler {
|
|
|
|
async fn get_message_author(&self, room: &Joined, user_id: &UserId) -> Option<MessageAuthor> {
|
|
|
|
if let Ok(Some(sender)) = room.get_member(user_id).await {
|
|
|
|
Some(MessageAuthor {
|
|
|
|
display_name: sender
|
|
|
|
.display_name()
|
|
|
|
.unwrap_or_else(|| sender.name())
|
|
|
|
.to_string(),
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_content(&self, body: &str, formatted_body: &Option<FormattedBody>) -> MessageContent {
|
|
|
|
if let Some(html) = formatted_body
|
|
|
|
.as_ref()
|
|
|
|
.filter(|f| f.format == MessageFormat::Html)
|
|
|
|
.map(|f| &f.body)
|
|
|
|
{
|
|
|
|
convert_matrix(html)
|
|
|
|
} else {
|
|
|
|
convert_plain(body)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-14 04:15:37 +00:00
|
|
|
async fn get_room_message_event(
|
|
|
|
room: &Joined,
|
|
|
|
event_id: &EventId,
|
|
|
|
) -> Option<events::MessageEvent<MessageEventContent>> {
|
|
|
|
let event = room
|
|
|
|
.event(api::client::r0::room::get_room_event::Request::new(
|
|
|
|
room.room_id(),
|
|
|
|
event_id,
|
|
|
|
))
|
|
|
|
.await
|
|
|
|
.ok()?
|
|
|
|
.event
|
|
|
|
.deserialize()
|
|
|
|
.ok()?;
|
|
|
|
|
|
|
|
if let AnyRoomEvent::Message(AnyMessageEvent::RoomMessage(message_event)) = event {
|
|
|
|
Some(message_event)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-13 18:52:42 +00:00
|
|
|
async fn on_room_message_event(
|
|
|
|
ctx: Arc<MatrixHandler>,
|
|
|
|
event: SyncMessageEvent<MessageEventContent>,
|
|
|
|
room: Room,
|
|
|
|
) {
|
|
|
|
if event.sender == ctx.current_user_id {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Room::Joined(room) = room {
|
2021-09-14 04:15:37 +00:00
|
|
|
if let Some(Relation::Replacement(replacement)) = &event.content.relates_to {
|
|
|
|
on_message_edited(ctx, &event, room, replacement).await;
|
|
|
|
} else {
|
|
|
|
on_message_sent(ctx, &event, room).await;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-09-13 18:52:42 +00:00
|
|
|
|
2021-09-14 04:15:37 +00:00
|
|
|
async fn on_message_sent(
|
|
|
|
ctx: Arc<MatrixHandler>,
|
|
|
|
event: &SyncMessageEvent<MessageEventContent>,
|
|
|
|
room: Joined,
|
|
|
|
) {
|
|
|
|
let message_ref = MessageReference::from((room.room_id(), &event.event_id));
|
2021-09-13 18:52:42 +00:00
|
|
|
|
2021-09-14 04:15:37 +00:00
|
|
|
if let Some(author) = ctx.get_message_author(&room, &event.sender).await {
|
|
|
|
let message_event = match &event.content.msgtype {
|
2021-09-13 18:52:42 +00:00
|
|
|
MessageType::Text(text) => {
|
|
|
|
let content = ctx.get_content(&text.body, &text.formatted);
|
2021-09-10 04:44:00 +00:00
|
|
|
|
2021-09-14 04:15:37 +00:00
|
|
|
let replies_to =
|
|
|
|
if let Some(Relation::Reply { in_reply_to }) = &event.content.relates_to {
|
|
|
|
Some(MessageReference::from((
|
|
|
|
room.room_id(),
|
|
|
|
&in_reply_to.event_id,
|
|
|
|
)))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
Some(MessageEvent::Send(SentMessage {
|
|
|
|
source: message_ref,
|
|
|
|
content,
|
|
|
|
author,
|
|
|
|
replies_to,
|
|
|
|
}))
|
2021-09-13 18:52:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
MessageType::Emote(emote) => {
|
|
|
|
let mut content = ctx.get_content(&emote.body, &emote.formatted);
|
|
|
|
content.insert(0, MessageComponent::Plain("* ".to_string()));
|
2021-09-12 23:06:11 +00:00
|
|
|
|
2021-09-14 04:15:37 +00:00
|
|
|
Some(MessageEvent::Send(SentMessage {
|
|
|
|
source: message_ref,
|
|
|
|
content,
|
|
|
|
author,
|
|
|
|
replies_to: None,
|
|
|
|
}))
|
2021-09-13 18:52:42 +00:00
|
|
|
}
|
2021-09-12 02:59:13 +00:00
|
|
|
|
2021-09-13 18:52:42 +00:00
|
|
|
// TODO: Handle reactions, uploads (audio, video, image, file), and any other types of event
|
2021-09-14 04:15:37 +00:00
|
|
|
_ => None,
|
2021-09-13 18:52:42 +00:00
|
|
|
};
|
2021-09-12 23:06:11 +00:00
|
|
|
|
2021-09-14 04:15:37 +00:00
|
|
|
if let Some(e) = message_event {
|
|
|
|
let _ = ctx.message_tx.send(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let _ = room.read_receipt(&event.event_id).await;
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn on_message_edited(
|
|
|
|
ctx: Arc<MatrixHandler>,
|
|
|
|
event: &SyncMessageEvent<MessageEventContent>,
|
|
|
|
room: Joined,
|
|
|
|
replacement: &Replacement,
|
|
|
|
) {
|
|
|
|
let message_ref = MessageReference::from((room.room_id(), &replacement.event_id));
|
|
|
|
|
|
|
|
if let MessageType::Text(text) = &replacement.new_content.msgtype {
|
|
|
|
let content = ctx.get_content(&text.body, &text.formatted);
|
|
|
|
|
|
|
|
if let Some(author) = ctx.get_message_author(&room, &event.sender).await {
|
|
|
|
let _ = ctx.message_tx.send(MessageEvent::Edit(EditedMessage {
|
|
|
|
replacing: message_ref,
|
|
|
|
content,
|
|
|
|
author,
|
|
|
|
}));
|
|
|
|
}
|
2021-08-12 09:47:38 +00:00
|
|
|
}
|
|
|
|
}
|
2021-09-10 04:44:00 +00:00
|
|
|
|
2021-09-16 03:29:00 +00:00
|
|
|
async fn on_redact_event(ctx: Arc<MatrixHandler>, event: SyncRedactionEvent, room: Room) {
|
|
|
|
let message_ref = MessageReference::from((room.room_id(), &event.redacts));
|
|
|
|
|
|
|
|
let _ = ctx.message_tx.send(MessageEvent::Delete(DeletedMessage {
|
|
|
|
reference: message_ref,
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2021-09-12 05:45:46 +00:00
|
|
|
pub async fn forward_to_matrix(
|
|
|
|
client: &Client,
|
|
|
|
room_id: RoomId,
|
|
|
|
message: &SentMessage,
|
2021-09-14 04:15:37 +00:00
|
|
|
replying_to: Option<EventId>,
|
2021-09-12 05:45:46 +00:00
|
|
|
) -> Option<MessageReference> {
|
|
|
|
if let Some(room) = client.get_joined_room(&room_id) {
|
2021-09-14 04:15:37 +00:00
|
|
|
let replied_message_event = if let Some(event_id) = replying_to {
|
|
|
|
get_room_message_event(&room, &event_id).await
|
2021-09-13 19:41:55 +00:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
let content = if let Some(replied_message_event) = &replied_message_event {
|
|
|
|
MessageEventContent::text_reply_html(
|
|
|
|
format_discord(&message.content),
|
|
|
|
format_matrix(&message.content),
|
|
|
|
replied_message_event,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
MessageEventContent::text_html(
|
|
|
|
format_discord(&message.content),
|
|
|
|
format_matrix(&message.content),
|
2021-09-12 05:45:46 +00:00
|
|
|
)
|
2021-09-13 19:41:55 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let event = room
|
|
|
|
.send(AnyMessageEventContent::RoomMessage(content), None)
|
2021-09-12 05:45:46 +00:00
|
|
|
.await
|
|
|
|
.ok()?;
|
|
|
|
|
|
|
|
return Some(MessageReference::from((&room_id, &event.event_id)));
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2021-09-14 04:15:37 +00:00
|
|
|
pub async fn edit_on_matrix(
|
|
|
|
client: &Client,
|
|
|
|
room_id: RoomId,
|
|
|
|
event_id: EventId,
|
|
|
|
message: &EditedMessage,
|
|
|
|
) -> Option<MessageReference> {
|
|
|
|
if let Some(room) = client.get_joined_room(&room_id) {
|
|
|
|
if let Some(original_message_event) = get_room_message_event(&room, &event_id).await {
|
|
|
|
if let Some(Relation::Replacement(_)) = &original_message_event.content.relates_to {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
let replied_message_event = if let Some(Relation::Reply { in_reply_to }) =
|
|
|
|
&original_message_event.content.relates_to
|
|
|
|
{
|
|
|
|
get_room_message_event(&room, &in_reply_to.event_id).await
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
let plain_reply = format_discord(&message.content);
|
|
|
|
let html_reply = format_matrix(&message.content);
|
|
|
|
|
|
|
|
let mut edit_content = if let Some(replied_message_event) = &replied_message_event {
|
|
|
|
MessageEventContent::text_reply_html(
|
|
|
|
format!("* {}", &plain_reply),
|
|
|
|
format!("* {}", html_reply),
|
|
|
|
replied_message_event,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
MessageEventContent::text_html(
|
|
|
|
format!("* {}", &plain_reply),
|
|
|
|
format!("* {}", html_reply),
|
|
|
|
)
|
|
|
|
};
|
|
|
|
|
|
|
|
let basic_content = if let Some(replied_message_event) = &replied_message_event {
|
|
|
|
MessageEventContent::text_reply_html(plain_reply, html_reply, replied_message_event)
|
|
|
|
} else {
|
|
|
|
MessageEventContent::text_html(plain_reply, html_reply)
|
|
|
|
};
|
|
|
|
|
|
|
|
edit_content.relates_to = Some(Relation::Replacement(Replacement::new(
|
|
|
|
event_id,
|
|
|
|
Box::new(basic_content),
|
|
|
|
)));
|
|
|
|
|
|
|
|
let new_event = room
|
|
|
|
.send(AnyMessageEventContent::RoomMessage(edit_content), None)
|
|
|
|
.await
|
|
|
|
.ok()?;
|
|
|
|
|
|
|
|
return Some(MessageReference::from((&room_id, &new_event.event_id)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2021-09-16 03:29:00 +00:00
|
|
|
pub async fn delete_on_matrix(
|
|
|
|
client: &Client,
|
|
|
|
room_id: RoomId,
|
|
|
|
event_id: EventId,
|
|
|
|
_message: &DeletedMessage,
|
|
|
|
) -> bool {
|
|
|
|
if let Some(room) = client.get_joined_room(&room_id) {
|
|
|
|
room.redact(&event_id, None, None).await.is_ok()
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-10 04:44:00 +00:00
|
|
|
pub async fn create_matrix_client(
|
|
|
|
homeserver_url: String,
|
|
|
|
username: String,
|
|
|
|
password: String,
|
2021-09-13 02:08:35 +00:00
|
|
|
message_tx: mpsc::UnboundedSender<MessageEvent>,
|
2021-09-10 04:44:00 +00:00
|
|
|
) -> Client {
|
2021-09-12 09:54:41 +00:00
|
|
|
let client_config = ClientConfig::new().store_path("./data/matrix_state");
|
2021-09-10 04:44:00 +00:00
|
|
|
|
|
|
|
let homeserver_url =
|
|
|
|
Url::parse(&homeserver_url).expect("Failed to parse the matrix homeserver URL");
|
|
|
|
let client = Client::new_with_config(homeserver_url, client_config).unwrap();
|
|
|
|
|
2021-09-12 02:59:13 +00:00
|
|
|
info!("Matrix logging in…");
|
2021-09-10 04:44:00 +00:00
|
|
|
client
|
|
|
|
.login(&username, &password, None, Some("phoebe"))
|
|
|
|
.await
|
|
|
|
.expect("Failed to log in");
|
|
|
|
|
2021-09-12 02:59:13 +00:00
|
|
|
info!("Matrix starting…");
|
2021-09-10 04:44:00 +00:00
|
|
|
client.sync_once(SyncSettings::default()).await.unwrap();
|
|
|
|
|
2021-09-12 02:59:13 +00:00
|
|
|
let current_user_id = client.user_id().await.unwrap();
|
|
|
|
|
2021-09-13 18:52:42 +00:00
|
|
|
let event_handler = Arc::new(MatrixHandler {
|
2021-09-12 02:59:13 +00:00
|
|
|
message_tx,
|
|
|
|
current_user_id,
|
2021-09-13 18:52:42 +00:00
|
|
|
});
|
|
|
|
|
2021-09-16 03:29:00 +00:00
|
|
|
let on_msg_ctx = event_handler.clone();
|
|
|
|
client
|
|
|
|
.register_event_handler(move |ev, room| on_room_message_event(on_msg_ctx.clone(), ev, room))
|
|
|
|
.await;
|
|
|
|
|
|
|
|
let on_redact_ctx = event_handler.clone();
|
2021-09-13 18:52:42 +00:00
|
|
|
client
|
2021-09-16 03:29:00 +00:00
|
|
|
.register_event_handler(move |ev, room| on_redact_event(on_redact_ctx.clone(), ev, room))
|
2021-09-13 18:52:42 +00:00
|
|
|
.await;
|
2021-09-10 04:44:00 +00:00
|
|
|
|
|
|
|
client
|
|
|
|
}
|