271 lines
8.8 KiB
Rust
271 lines
8.8 KiB
Rust
use crate::controllers::HttpResult;
|
|
use crate::controllers::matrix::matrix_media_controller;
|
|
use crate::controllers::matrix::matrix_media_controller::MediaQuery;
|
|
use crate::controllers::matrix::matrix_room_controller::RoomIdInPath;
|
|
use crate::extractors::matrix_client_extractor::MatrixClientExtractor;
|
|
use actix_web::dev::Payload;
|
|
use actix_web::{FromRequest, HttpRequest, HttpResponse, web};
|
|
use futures_util::{StreamExt, stream};
|
|
use matrix_sdk::Room;
|
|
use matrix_sdk::deserialized_responses::{TimelineEvent, TimelineEventKind};
|
|
use matrix_sdk::media::MediaEventContent;
|
|
use matrix_sdk::room::MessagesOptions;
|
|
use matrix_sdk::room::edit::EditedContent;
|
|
use matrix_sdk::ruma::events::reaction::ReactionEventContent;
|
|
use matrix_sdk::ruma::events::relation::Annotation;
|
|
use matrix_sdk::ruma::events::room::message::{
|
|
MessageType, RoomMessageEvent, RoomMessageEventContent, RoomMessageEventContentWithoutRelation,
|
|
};
|
|
use matrix_sdk::ruma::events::{AnyMessageLikeEvent, AnyTimelineEvent};
|
|
use matrix_sdk::ruma::{MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId, RoomId, UInt};
|
|
use serde::{Deserialize, Serialize};
|
|
use serde_json::value::RawValue;
|
|
|
|
#[derive(Serialize)]
|
|
pub struct APIEvent {
|
|
id: OwnedEventId,
|
|
time: MilliSecondsSinceUnixEpoch,
|
|
sender: OwnedUserId,
|
|
data: Box<RawValue>,
|
|
}
|
|
|
|
impl APIEvent {
|
|
pub async fn from_evt(msg: TimelineEvent, room_id: &RoomId) -> anyhow::Result<Self> {
|
|
let (event, raw) = match &msg.kind {
|
|
TimelineEventKind::Decrypted(d) => (d.event.deserialize()?, d.event.json()),
|
|
TimelineEventKind::UnableToDecrypt { event, .. }
|
|
| TimelineEventKind::PlainText { event } => (
|
|
event.deserialize()?.into_full_event(room_id.to_owned()),
|
|
event.json(),
|
|
),
|
|
};
|
|
|
|
Ok(Self {
|
|
id: event.event_id().to_owned(),
|
|
time: event.origin_server_ts(),
|
|
sender: event.sender().to_owned(),
|
|
data: raw.to_owned(),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
pub struct APIEventsList {
|
|
pub start: String,
|
|
pub end: Option<String>,
|
|
pub events: Vec<APIEvent>,
|
|
}
|
|
|
|
/// Get messages for a given room
|
|
pub(super) async fn get_events(
|
|
room: &Room,
|
|
limit: u32,
|
|
from: Option<&str>,
|
|
) -> anyhow::Result<APIEventsList> {
|
|
let mut msg_opts = MessagesOptions::backward();
|
|
msg_opts.from = from.map(str::to_string);
|
|
msg_opts.limit = UInt::from(limit);
|
|
|
|
let messages = room.messages(msg_opts).await?;
|
|
Ok(APIEventsList {
|
|
start: messages.start,
|
|
end: messages.end,
|
|
events: stream::iter(messages.chunk)
|
|
.then(async |msg| APIEvent::from_evt(msg, room.room_id()).await)
|
|
.collect::<Vec<_>>()
|
|
.await
|
|
.into_iter()
|
|
.collect::<Result<Vec<_>, _>>()?,
|
|
})
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
pub struct GetRoomEventsQuery {
|
|
#[serde(default)]
|
|
limit: Option<u32>,
|
|
#[serde(default)]
|
|
from: Option<String>,
|
|
}
|
|
|
|
/// Get the events for a room
|
|
pub async fn get_for_room(
|
|
client: MatrixClientExtractor,
|
|
path: web::Path<RoomIdInPath>,
|
|
query: web::Query<GetRoomEventsQuery>,
|
|
) -> HttpResult {
|
|
let Some(room) = client.client.client.get_room(&path.room_id) else {
|
|
return Ok(HttpResponse::NotFound().json("Room not found!"));
|
|
};
|
|
|
|
Ok(HttpResponse::Ok()
|
|
.json(get_events(&room, query.limit.unwrap_or(500), query.from.as_deref()).await?))
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct SendTextMessageRequest {
|
|
content: String,
|
|
}
|
|
|
|
pub async fn send_text_message(
|
|
client: MatrixClientExtractor,
|
|
path: web::Path<RoomIdInPath>,
|
|
) -> HttpResult {
|
|
let req = client.auth.decode_json_body::<SendTextMessageRequest>()?;
|
|
|
|
let Some(room) = client.client.client.get_room(&path.room_id) else {
|
|
return Ok(HttpResponse::NotFound().json("Room not found!"));
|
|
};
|
|
|
|
room.send(RoomMessageEventContent::text_plain(req.content))
|
|
.await?;
|
|
|
|
Ok(HttpResponse::Accepted().finish())
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
pub struct EventIdInPath {
|
|
pub(crate) event_id: OwnedEventId,
|
|
}
|
|
|
|
pub async fn set_text_content(
|
|
client: MatrixClientExtractor,
|
|
path: web::Path<RoomIdInPath>,
|
|
event_path: web::Path<EventIdInPath>,
|
|
) -> HttpResult {
|
|
let req = client.auth.decode_json_body::<SendTextMessageRequest>()?;
|
|
|
|
let Some(room) = client.client.client.get_room(&path.room_id) else {
|
|
return Ok(HttpResponse::NotFound().json("Room not found!"));
|
|
};
|
|
|
|
let edit_event = match room
|
|
.make_edit_event(
|
|
&event_path.event_id,
|
|
EditedContent::RoomMessage(RoomMessageEventContentWithoutRelation::text_plain(
|
|
req.content,
|
|
)),
|
|
)
|
|
.await
|
|
{
|
|
Ok(msg) => msg,
|
|
Err(e) => {
|
|
log::error!(
|
|
"Failed to created edit message event {}: {e}",
|
|
event_path.event_id
|
|
);
|
|
return Ok(HttpResponse::InternalServerError()
|
|
.json(format!("Failed to create edit message event! {e}")));
|
|
}
|
|
};
|
|
|
|
Ok(match room.send(edit_event).await {
|
|
Ok(_) => HttpResponse::Accepted().finish(),
|
|
Err(e) => {
|
|
log::error!("Failed to edit event message {}: {e}", event_path.event_id);
|
|
HttpResponse::InternalServerError().json(format!("Failed to edit event! {e}"))
|
|
}
|
|
})
|
|
}
|
|
|
|
pub async fn event_file(
|
|
req: HttpRequest,
|
|
client: MatrixClientExtractor,
|
|
path: web::Path<RoomIdInPath>,
|
|
event_path: web::Path<EventIdInPath>,
|
|
) -> HttpResult {
|
|
let query = web::Query::<MediaQuery>::from_request(&req, &mut Payload::None).await?;
|
|
|
|
let Some(room) = client.client.client.get_room(&path.room_id) else {
|
|
return Ok(HttpResponse::NotFound().json("Room not found!"));
|
|
};
|
|
|
|
let event = match room.load_or_fetch_event(&event_path.event_id, None).await {
|
|
Ok(event) => event,
|
|
Err(e) => {
|
|
log::error!("Failed to load event information! {e}");
|
|
return Ok(HttpResponse::InternalServerError()
|
|
.json(format!("Failed to load event information! {e}")));
|
|
}
|
|
};
|
|
|
|
let event = match event.kind {
|
|
TimelineEventKind::Decrypted(dec) => dec.event.deserialize()?,
|
|
TimelineEventKind::UnableToDecrypt { event, .. }
|
|
| TimelineEventKind::PlainText { event } => event
|
|
.deserialize()?
|
|
.into_full_event(room.room_id().to_owned()),
|
|
};
|
|
|
|
let AnyTimelineEvent::MessageLike(message) = event else {
|
|
return Ok(HttpResponse::BadRequest().json("Event is not message like!"));
|
|
};
|
|
|
|
let AnyMessageLikeEvent::RoomMessage(message) = message else {
|
|
return Ok(HttpResponse::BadRequest().json("Event is not a room message!"));
|
|
};
|
|
|
|
let RoomMessageEvent::Original(message) = message else {
|
|
return Ok(HttpResponse::BadRequest().json("Event has been redacted!"));
|
|
};
|
|
|
|
let (source, thumb_source) = match message.content.msgtype {
|
|
MessageType::Audio(c) => (c.source(), c.thumbnail_source()),
|
|
MessageType::File(c) => (c.source(), c.thumbnail_source()),
|
|
MessageType::Image(c) => (c.source(), c.thumbnail_source()),
|
|
MessageType::Location(c) => (c.source(), c.thumbnail_source()),
|
|
MessageType::Video(c) => (c.source(), c.thumbnail_source()),
|
|
_ => (None, None),
|
|
};
|
|
|
|
println!("{source:#?} {thumb_source:#?}");
|
|
|
|
let source = match (query.thumbnail, source, thumb_source) {
|
|
(false, Some(s), _) => s,
|
|
(true, _, Some(s)) => s,
|
|
_ => return Ok(HttpResponse::NotFound().json("Requested file not available!")),
|
|
};
|
|
|
|
matrix_media_controller::serve_media(req, source, false).await
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct EventReactionBody {
|
|
key: String,
|
|
}
|
|
|
|
pub async fn react_to_event(
|
|
client: MatrixClientExtractor,
|
|
path: web::Path<RoomIdInPath>,
|
|
event_path: web::Path<EventIdInPath>,
|
|
) -> HttpResult {
|
|
let body = client.auth.decode_json_body::<EventReactionBody>()?;
|
|
|
|
let Some(room) = client.client.client.get_room(&path.room_id) else {
|
|
return Ok(HttpResponse::NotFound().json("Room not found!"));
|
|
};
|
|
|
|
let annotation = Annotation::new(event_path.event_id.to_owned(), body.key.to_owned());
|
|
room.send(ReactionEventContent::from(annotation)).await?;
|
|
|
|
Ok(HttpResponse::Accepted().finish())
|
|
}
|
|
|
|
pub async fn redact_event(
|
|
client: MatrixClientExtractor,
|
|
path: web::Path<RoomIdInPath>,
|
|
event_path: web::Path<EventIdInPath>,
|
|
) -> HttpResult {
|
|
let Some(room) = client.client.client.get_room(&path.room_id) else {
|
|
return Ok(HttpResponse::NotFound().json("Room not found!"));
|
|
};
|
|
|
|
Ok(match room.redact(&event_path.event_id, None, None).await {
|
|
Ok(_) => HttpResponse::Accepted().finish(),
|
|
|
|
Err(e) => {
|
|
log::error!("Failed to redact event {}: {e}", event_path.event_id);
|
|
HttpResponse::InternalServerError().json(format!("Failed to redact event! {e}"))
|
|
}
|
|
})
|
|
}
|