1 Commits

Author SHA1 Message Date
072edc1880 chore(deps): update rust crate ipnet to 2.12.0
Some checks failed
continuous-integration/drone/push Build is failing
2026-03-04 00:20:24 +00:00
8 changed files with 30 additions and 183 deletions

View File

@@ -14,15 +14,13 @@ use matrix_sdk::deserialized_responses::{TimelineEvent, TimelineEventKind};
use matrix_sdk::media::MediaEventContent; use matrix_sdk::media::MediaEventContent;
use matrix_sdk::room::MessagesOptions; use matrix_sdk::room::MessagesOptions;
use matrix_sdk::room::edit::EditedContent; use matrix_sdk::room::edit::EditedContent;
use matrix_sdk::room::reply::{EnforceThread, Reply};
use matrix_sdk::ruma::api::client::filter::RoomEventFilter; use matrix_sdk::ruma::api::client::filter::RoomEventFilter;
use matrix_sdk::ruma::api::client::receipt::create_receipt::v3::ReceiptType; use matrix_sdk::ruma::api::client::receipt::create_receipt::v3::ReceiptType;
use matrix_sdk::ruma::events::reaction::ReactionEventContent; use matrix_sdk::ruma::events::reaction::ReactionEventContent;
use matrix_sdk::ruma::events::receipt::ReceiptThread; use matrix_sdk::ruma::events::receipt::ReceiptThread;
use matrix_sdk::ruma::events::relation::{Annotation, InReplyTo}; use matrix_sdk::ruma::events::relation::Annotation;
use matrix_sdk::ruma::events::room::message::{ use matrix_sdk::ruma::events::room::message::{
MessageType, Relation, RoomMessageEvent, RoomMessageEventContent, MessageType, RoomMessageEvent, RoomMessageEventContent, RoomMessageEventContentWithoutRelation,
RoomMessageEventContentWithoutRelation,
}; };
use matrix_sdk::ruma::events::{AnyMessageLikeEvent, AnyTimelineEvent}; use matrix_sdk::ruma::events::{AnyMessageLikeEvent, AnyTimelineEvent};
use matrix_sdk::ruma::{MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId, RoomId, UInt}; use matrix_sdk::ruma::{MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedUserId, RoomId, UInt};
@@ -124,8 +122,6 @@ pub async fn get_for_room(
#[derive(Deserialize)] #[derive(Deserialize)]
struct SendTextMessageRequest { struct SendTextMessageRequest {
content: String, content: String,
#[serde(skip_serializing_if = "Option::is_none")]
in_reply_to: Option<OwnedEventId>,
} }
pub async fn send_text_message( pub async fn send_text_message(
@@ -138,15 +134,8 @@ pub async fn send_text_message(
return Ok(HttpResponse::NotFound().json("Room not found!")); return Ok(HttpResponse::NotFound().json("Room not found!"));
}; };
let mut evt = RoomMessageEventContent::text_plain(req.content); room.send(RoomMessageEventContent::text_plain(req.content))
.await?;
if let Some(event_id) = req.in_reply_to {
evt.relates_to = Some(Relation::Reply {
in_reply_to: InReplyTo::new(event_id),
})
};
room.send(evt).await?;
Ok(HttpResponse::Accepted().finish()) Ok(HttpResponse::Accepted().finish())
} }
@@ -157,16 +146,9 @@ pub struct SendFileForm {
file: actix_multipart::form::tempfile::TempFile, file: actix_multipart::form::tempfile::TempFile,
} }
#[derive(serde::Deserialize)]
pub struct SendFileQuery {
#[serde(skip_serializing_if = "Option::is_none")]
in_reply_to: Option<OwnedEventId>,
}
pub async fn send_file( pub async fn send_file(
client: MatrixClientExtractor, client: MatrixClientExtractor,
path: web::Path<RoomIdInPath>, path: web::Path<RoomIdInPath>,
query: web::Query<SendFileQuery>,
req: HttpRequest, req: HttpRequest,
) -> HttpResult { ) -> HttpResult {
let Some(payload) = client.auth.payload else { let Some(payload) = client.auth.payload else {
@@ -200,17 +182,8 @@ pub async fn send_file(
return Ok(HttpResponse::BadRequest().json("File content type must be specified!")); return Ok(HttpResponse::BadRequest().json("File content type must be specified!"));
}; };
let mut config = AttachmentConfig::new();
if let Some(event_id) = query.0.in_reply_to {
config.reply = Some(Reply {
event_id,
enforce_thread: EnforceThread::MaybeThreaded,
})
}
// Do send the file // Do send the file
room.send_attachment(file_name, mime_type, buff, config) room.send_attachment(file_name, mime_type, buff, AttachmentConfig::new())
.await?; .await?;
Ok(HttpResponse::Accepted().finish()) Ok(HttpResponse::Accepted().finish())
@@ -221,25 +194,6 @@ pub struct EventIdInPath {
pub(crate) event_id: OwnedEventId, pub(crate) event_id: OwnedEventId,
} }
/// Get a single event information
pub async fn get_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!"));
};
let event = room.load_or_fetch_event(&event_path.event_id, None).await?;
Ok(match event.kind {
TimelineEventKind::Decrypted(dec) => HttpResponse::Ok().json(dec.event),
TimelineEventKind::UnableToDecrypt { event, .. }
| TimelineEventKind::PlainText { event } => HttpResponse::Ok().json(event),
})
}
pub async fn set_text_content( pub async fn set_text_content(
client: MatrixClientExtractor, client: MatrixClientExtractor,
path: web::Path<RoomIdInPath>, path: web::Path<RoomIdInPath>,

View File

@@ -190,10 +190,6 @@ async fn main() -> std::io::Result<()> {
"/api/matrix/room/{room_id}/send_file", "/api/matrix/room/{room_id}/send_file",
web::post().to(matrix_event_controller::send_file), web::post().to(matrix_event_controller::send_file),
) )
.route(
"/api/matrix/room/{room_id}/event/{event_id}",
web::get().to(matrix_event_controller::get_event),
)
.route( .route(
"/api/matrix/room/{room_id}/event/{event_id}/set_text_content", "/api/matrix/room/{room_id}/event/{event_id}/set_text_content",
web::post().to(matrix_event_controller::set_text_content), web::post().to(matrix_event_controller::set_text_content),

View File

@@ -5,7 +5,6 @@
<link rel="icon" type="image/svg+xml" href="/favicon.png" /> <link rel="icon" type="image/svg+xml" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MatrixGW</title> <title>MatrixGW</title>
<style>body {background-color: black;}</style>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View File

@@ -37,7 +37,7 @@
"@vitejs/plugin-react": "^5.1.4", "@vitejs/plugin-react": "^5.1.4",
"eslint": "^9.39.3", "eslint": "^9.39.3",
"eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2", "eslint-plugin-react-refresh": "^0.4.26",
"globals": "^17.4.0", "globals": "^17.4.0",
"typescript": "~5.9.3", "typescript": "~5.9.3",
"typescript-eslint": "^8.56.1", "typescript-eslint": "^8.56.1",

View File

@@ -96,33 +96,23 @@ export class MatrixApiEvent {
/** /**
* Send text message * Send text message
*/ */
static async SendTextMessage( static async SendTextMessage(room: Room, content: string): Promise<void> {
room: Room,
content: string,
in_reply_to?: string | undefined,
): Promise<void> {
await APIClient.exec({ await APIClient.exec({
method: "POST", method: "POST",
uri: `/matrix/room/${room.id}/send_text_message`, uri: `/matrix/room/${room.id}/send_text_message`,
jsonData: { content, in_reply_to }, jsonData: { content },
}); });
} }
/** /**
* Send file message * Send file message
*/ */
static async SendFileMessage( static async SendFileMessage(room: Room, file: Blob): Promise<void> {
room: Room,
file: Blob,
inReplyTo?: string | undefined,
): Promise<void> {
const formData = new FormData(); const formData = new FormData();
formData.set("file", file); formData.set("file", file);
await APIClient.exec({ await APIClient.exec({
method: "POST", method: "POST",
uri: uri: `/matrix/room/${room.id}/send_file`,
`/matrix/room/${room.id}/send_file?` +
(inReplyTo ? `in_reply_to=${inReplyTo}` : ""),
formData, formData,
}); });
} }

View File

@@ -2,7 +2,6 @@ import AddReactionIcon from "@mui/icons-material/AddReaction";
import DeleteIcon from "@mui/icons-material/Delete"; import DeleteIcon from "@mui/icons-material/Delete";
import DownloadIcon from "@mui/icons-material/Download"; import DownloadIcon from "@mui/icons-material/Download";
import EditIcon from "@mui/icons-material/Edit"; import EditIcon from "@mui/icons-material/Edit";
import ReplyIcon from "@mui/icons-material/Reply";
import { import {
Box, Box,
Button, Button,
@@ -41,7 +40,6 @@ export function RoomMessagesList(p: {
room: Room; room: Room;
users: UsersMap; users: UsersMap;
manager: RoomEventsManager; manager: RoomEventsManager;
onRequestReplyToMessage: (evt: Message) => void;
}): React.ReactElement { }): React.ReactElement {
const snackbar = useSnackbar(); const snackbar = useSnackbar();
@@ -65,7 +63,7 @@ export function RoomMessagesList(p: {
try { try {
const older = await MatrixApiEvent.GetRoomEvents( const older = await MatrixApiEvent.GetRoomEvents(
p.room, p.room,
p.manager.endToken, p.manager.endToken
); );
p.manager.processNewEvents(older); p.manager.processNewEvents(older);
} catch (e) { } catch (e) {
@@ -178,7 +176,6 @@ export function RoomMessagesList(p: {
p.manager.messages.find((s) => s.event_id === m.inReplyTo)) || p.manager.messages.find((s) => s.event_id === m.inReplyTo)) ||
undefined undefined
} }
onRequestReplyToMessage={() => p.onRequestReplyToMessage(m)}
/> />
))} ))}
@@ -195,7 +192,6 @@ function RoomMessage(p: {
firstMessageOfDay: boolean; firstMessageOfDay: boolean;
receipts?: Receipt[]; receipts?: Receipt[];
repliedMessage?: Message; repliedMessage?: Message;
onRequestReplyToMessage: () => void;
}): React.ReactElement { }): React.ReactElement {
const theme = useTheme(); const theme = useTheme();
const user = useUserInfo(); const user = useUserInfo();
@@ -235,7 +231,7 @@ function RoomMessage(p: {
await MatrixApiEvent.SetTextMessageContent( await MatrixApiEvent.SetTextMessageContent(
p.room, p.room,
p.message.event_id, p.message.event_id,
editMessage!, editMessage!
); );
setEditMessage(undefined); setEditMessage(undefined);
} catch (e) { } catch (e) {
@@ -263,7 +259,7 @@ function RoomMessage(p: {
const handleToggleReaction = async ( const handleToggleReaction = async (
key: string, key: string,
reaction: MessageReaction | undefined, reaction: MessageReaction | undefined
) => { ) => {
try { try {
if (!reaction) if (!reaction)
@@ -343,7 +339,7 @@ function RoomMessage(p: {
{p.repliedMessage && repliedMsgSender && ( {p.repliedMessage && repliedMsgSender && (
<div <div
style={{ style={{
display: "flex", display: "inline-flex",
alignItems: "center", alignItems: "center",
borderLeft: "1px red solid", borderLeft: "1px red solid",
paddingLeft: "10px", paddingLeft: "10px",
@@ -364,7 +360,7 @@ function RoomMessage(p: {
src={MatrixApiEvent.GetEventFileURL( src={MatrixApiEvent.GetEventFileURL(
p.room, p.room,
p.message.event_id, p.message.event_id,
true, true
)} )}
style={{ style={{
maxWidth: "200px", maxWidth: "200px",
@@ -379,7 +375,7 @@ function RoomMessage(p: {
src={MatrixApiEvent.GetEventFileURL( src={MatrixApiEvent.GetEventFileURL(
p.room, p.room,
p.message.event_id, p.message.event_id,
false, false
)} )}
/> />
</audio> </audio>
@@ -392,7 +388,7 @@ function RoomMessage(p: {
src={MatrixApiEvent.GetEventFileURL( src={MatrixApiEvent.GetEventFileURL(
p.room, p.room,
p.message.event_id, p.message.event_id,
false, false
)} )}
/> />
</video> </video>
@@ -404,7 +400,7 @@ function RoomMessage(p: {
href={MatrixApiEvent.GetEventFileURL( href={MatrixApiEvent.GetEventFileURL(
p.room, p.room,
p.message.event_id, p.message.event_id,
false, false
)} )}
target="_blank" target="_blank"
rel="noopener" rel="noopener"
@@ -462,10 +458,6 @@ function RoomMessage(p: {
<Button onClick={handleAddReaction}> <Button onClick={handleAddReaction}>
<AddReactionIcon /> <AddReactionIcon />
</Button> </Button>
{/* Reply to message */}
<Button onClick={p.onRequestReplyToMessage}>
<ReplyIcon />
</Button>
{/* Edit text message */} {/* Edit text message */}
{p.message.account === user.info.matrix_user_id && {p.message.account === user.info.matrix_user_id &&
!p.message.file && ( !p.message.file && (
@@ -487,7 +479,7 @@ function RoomMessage(p: {
{[...p.message.reactions.keys()].map((r) => { {[...p.message.reactions.keys()].map((r) => {
const reactions = p.message.reactions.get(r)!; const reactions = p.message.reactions.get(r)!;
const userReaction = reactions.find( const userReaction = reactions.find(
(r) => r.account === user.info.matrix_user_id, (r) => r.account === user.info.matrix_user_id
); );
return ( return (
<Tooltip <Tooltip
@@ -545,7 +537,7 @@ function RoomMessage(p: {
src={MatrixApiEvent.GetEventFileURL( src={MatrixApiEvent.GetEventFileURL(
p.room, p.room,
p.message.event_id, p.message.event_id,
false, false
)} )}
/> />
</Dialog> </Dialog>
@@ -615,7 +607,7 @@ function ReactionButton(p: {
p.message.reactions p.message.reactions
.get(p.emojiKey) .get(p.emojiKey)
?.find( ?.find(
(r) => r.key === p.emojiKey && r.account === user.info.matrix_user_id, (r) => r.key === p.emojiKey && r.account === user.info.matrix_user_id
) !== undefined ) !== undefined
) )
return <></>; return <></>;

View File

@@ -3,7 +3,7 @@ import { MatrixApiEvent } from "../../api/matrix/MatrixApiEvent";
import type { UsersMap } from "../../api/matrix/MatrixApiProfile"; import type { UsersMap } from "../../api/matrix/MatrixApiProfile";
import type { Room } from "../../api/matrix/MatrixApiRoom"; import type { Room } from "../../api/matrix/MatrixApiRoom";
import { useSnackbar } from "../../hooks/contexts_provider/SnackbarProvider"; import { useSnackbar } from "../../hooks/contexts_provider/SnackbarProvider";
import { type Message, RoomEventsManager } from "../../utils/RoomEventsManager"; import { RoomEventsManager } from "../../utils/RoomEventsManager";
import { RoomMessagesList } from "./RoomMessagesList"; import { RoomMessagesList } from "./RoomMessagesList";
import { SendMessageForm } from "./SendMessageForm"; import { SendMessageForm } from "./SendMessageForm";
import { TypingNotice } from "./TypingNotice"; import { TypingNotice } from "./TypingNotice";
@@ -17,10 +17,6 @@ export function RoomWidget(p: {
const receiptId = React.useRef<string | undefined>(undefined); const receiptId = React.useRef<string | undefined>(undefined);
const [currMessageReply, setCurrMessageReply] = React.useState<
Message | undefined
>();
const handleRoomClick = async () => { const handleRoomClick = async () => {
if (p.manager.messages.length === 0) return; if (p.manager.messages.length === 0) return;
const latest = p.manager.messages[p.manager.messages.length - 1]; const latest = p.manager.messages[p.manager.messages.length - 1];
@@ -40,13 +36,9 @@ export function RoomWidget(p: {
style={{ display: "flex", flexDirection: "column", flex: 1 }} style={{ display: "flex", flexDirection: "column", flex: 1 }}
onClick={handleRoomClick} onClick={handleRoomClick}
> >
<RoomMessagesList {...p} onRequestReplyToMessage={setCurrMessageReply} /> <RoomMessagesList {...p} />
<TypingNotice {...p} /> <TypingNotice {...p} />
<SendMessageForm <SendMessageForm {...p} />
{...p}
currMessageReply={currMessageReply}
setCurrReplyToMessage={setCurrMessageReply}
/>
</div> </div>
); );
} }

View File

@@ -1,38 +1,21 @@
import AddReactionIcon from "@mui/icons-material/AddReaction";
import AttachFileIcon from "@mui/icons-material/AttachFile"; import AttachFileIcon from "@mui/icons-material/AttachFile";
import CloseIcon from "@mui/icons-material/Close";
import ReplyIcon from "@mui/icons-material/Reply";
import SendIcon from "@mui/icons-material/Send"; import SendIcon from "@mui/icons-material/Send";
import { import { IconButton, TextField, Tooltip } from "@mui/material";
Button,
Dialog,
IconButton,
Paper,
TextField,
Tooltip,
} from "@mui/material";
import EmojiPicker, { EmojiStyle, Theme } from "emoji-picker-react";
import React, { type SyntheticEvent } from "react"; import React, { type SyntheticEvent } from "react";
import { MatrixApiEvent } from "../../api/matrix/MatrixApiEvent"; import { MatrixApiEvent } from "../../api/matrix/MatrixApiEvent";
import type { Room } from "../../api/matrix/MatrixApiRoom"; import type { Room } from "../../api/matrix/MatrixApiRoom";
import { ServerApi } from "../../api/ServerApi";
import { useAlert } from "../../hooks/contexts_provider/AlertDialogProvider"; import { useAlert } from "../../hooks/contexts_provider/AlertDialogProvider";
import { useLoadingMessage } from "../../hooks/contexts_provider/LoadingMessageProvider"; import { useLoadingMessage } from "../../hooks/contexts_provider/LoadingMessageProvider";
import { useSnackbar } from "../../hooks/contexts_provider/SnackbarProvider";
import { selectFileToUpload } from "../../utils/FilesUtils"; import { selectFileToUpload } from "../../utils/FilesUtils";
import type { Message } from "../../utils/RoomEventsManager"; import { ServerApi } from "../../api/ServerApi";
import { useSnackbar } from "../../hooks/contexts_provider/SnackbarProvider";
export function SendMessageForm(p: { export function SendMessageForm(p: { room: Room }): React.ReactElement {
room: Room;
currMessageReply?: Message;
setCurrReplyToMessage: (msg: Message | undefined) => void;
}): React.ReactElement {
const loadingMessage = useLoadingMessage(); const loadingMessage = useLoadingMessage();
const alert = useAlert(); const alert = useAlert();
const snackbar = useSnackbar(); const snackbar = useSnackbar();
const [text, setText] = React.useState(""); const [text, setText] = React.useState("");
const [pickReaction, setPickReaction] = React.useState(false);
const handleTextSubmit = async (e: SyntheticEvent) => { const handleTextSubmit = async (e: SyntheticEvent) => {
e.preventDefault(); e.preventDefault();
@@ -42,15 +25,9 @@ export function SendMessageForm(p: {
loadingMessage.show("Sending message..."); loadingMessage.show("Sending message...");
try { try {
await MatrixApiEvent.SendTextMessage( await MatrixApiEvent.SendTextMessage(p.room, text);
p.room,
text,
p.currMessageReply?.event_id,
);
setText(""); setText("");
p.setCurrReplyToMessage(undefined);
} catch (e) { } catch (e) {
console.error(`Failed to send message! ${e}`); console.error(`Failed to send message! ${e}`);
alert(`Failed to send message! ${e}`); alert(`Failed to send message! ${e}`);
@@ -59,13 +36,6 @@ export function SendMessageForm(p: {
} }
}; };
const handleAddReaction = () => setPickReaction(true);
const handleCancelAddReaction = () => setPickReaction(false);
const handleSelectEmoji = async (key: string) => {
setText((t) => t + key);
setPickReaction(false);
};
const handleFileSubmit = async () => { const handleFileSubmit = async () => {
try { try {
const file = await selectFileToUpload({ const file = await selectFileToUpload({
@@ -75,15 +45,9 @@ export function SendMessageForm(p: {
if (!file) return; if (!file) return;
loadingMessage.show("Uploading file..."); loadingMessage.show("Uploading file...");
await MatrixApiEvent.SendFileMessage( await MatrixApiEvent.SendFileMessage(p.room, file);
p.room,
file,
p.currMessageReply?.event_id,
);
snackbar("The file was successfully uploaded!"); snackbar("The file was successfully uploaded!");
p.setCurrReplyToMessage(undefined);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
alert(`Failed to upload file! ${e}`); alert(`Failed to upload file! ${e}`);
@@ -94,31 +58,6 @@ export function SendMessageForm(p: {
return ( return (
<form onSubmit={handleTextSubmit}> <form onSubmit={handleTextSubmit}>
{/* Show replied message content */}
{p.currMessageReply && (
<Paper
variant="outlined"
style={{
display: "flex",
padding: "5px 10px",
justifyContent: "space-between",
alignItems: "center",
}}
>
<ReplyIcon />
<span style={{ flex: 1, marginLeft: "10px" }}>
{p.currMessageReply.content}
</span>
<Button
size="large"
onClick={() => p.setCurrReplyToMessage(undefined)}
>
<CloseIcon fontSize="inherit" />
</Button>
</Paper>
)}
{/* Input form */}
<div <div
style={{ style={{
padding: "10px", padding: "10px",
@@ -137,12 +76,6 @@ export function SendMessageForm(p: {
onChange={(e) => setText(e.target.value)} onChange={(e) => setText(e.target.value)}
/> />
<span style={{ width: "10px" }}></span> <span style={{ width: "10px" }}></span>
<Tooltip title="Add a reaction">
<IconButton size="small" onClick={handleAddReaction}>
<AddReactionIcon />
</IconButton>
</Tooltip>
<span style={{ width: "10px" }}></span>
<Tooltip title="Send a file"> <Tooltip title="Send a file">
<IconButton size="small" onClick={handleFileSubmit}> <IconButton size="small" onClick={handleFileSubmit}>
<AttachFileIcon /> <AttachFileIcon />
@@ -157,15 +90,6 @@ export function SendMessageForm(p: {
<SendIcon /> <SendIcon />
</IconButton> </IconButton>
</div> </div>
{/* Pick reaction dialog */}
<Dialog open={pickReaction} onClose={handleCancelAddReaction}>
<EmojiPicker
emojiStyle={EmojiStyle.NATIVE}
theme={Theme.AUTO}
onEmojiClick={(emoji) => handleSelectEmoji(emoji.emoji)}
/>
</Dialog>
</form> </form>
); );
} }