2021-06-22 05:21:16 +00:00
|
|
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
|
|
|
import { Room } from "matrix-js-sdk/src/models/room";
|
|
|
|
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
|
|
|
import { exportOptions, exportTypes } from "./exportUtils";
|
|
|
|
import { decryptFile } from "../DecryptFile";
|
|
|
|
import { mediaFromContent } from "../../customisations/Media";
|
|
|
|
import { formatFullDateNoDay } from "../../DateUtils";
|
2021-05-24 15:18:13 +00:00
|
|
|
|
2021-06-23 06:28:50 +00:00
|
|
|
type FileStream = {
|
|
|
|
name: string,
|
|
|
|
stream(): ReadableStream,
|
|
|
|
};
|
|
|
|
|
2021-06-03 08:09:14 +00:00
|
|
|
export default abstract class Exporter {
|
2021-06-23 06:28:50 +00:00
|
|
|
protected files: FileStream[];
|
2021-06-11 06:34:05 +00:00
|
|
|
protected constructor(
|
|
|
|
protected room: Room,
|
|
|
|
protected exportType: exportTypes,
|
|
|
|
protected exportOptions?: exportOptions,
|
2021-06-23 06:28:50 +00:00
|
|
|
) {
|
|
|
|
this.files = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
protected addFile = (filePath: string, blob: Blob) => {
|
|
|
|
const file = {
|
|
|
|
name: filePath,
|
|
|
|
stream: () => blob.stream(),
|
|
|
|
}
|
|
|
|
this.files.push(file);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected pumpToFileStream = async (reader: ReadableStreamDefaultReader, writer: WritableStreamDefaultWriter) => {
|
|
|
|
const res = await reader.read();
|
|
|
|
if (res.done) await writer.close();
|
|
|
|
else {
|
|
|
|
await writer.write(res.value);
|
|
|
|
await this.pumpToFileStream(reader, writer)
|
|
|
|
}
|
|
|
|
}
|
2021-06-03 07:51:56 +00:00
|
|
|
|
2021-06-07 06:04:03 +00:00
|
|
|
protected setEventMetadata = (event: MatrixEvent) => {
|
|
|
|
const client = MatrixClientPeg.get();
|
|
|
|
const roomState = client.getRoom(this.room.roomId).currentState;
|
|
|
|
event.sender = roomState.getSentinelMember(
|
|
|
|
event.getSender(),
|
|
|
|
);
|
|
|
|
if (event.getType() === "m.room.member") {
|
|
|
|
event.target = roomState.getSentinelMember(
|
|
|
|
event.getStateKey(),
|
|
|
|
);
|
2021-06-04 09:38:17 +00:00
|
|
|
}
|
2021-06-07 06:04:03 +00:00
|
|
|
return event;
|
2021-06-04 09:38:17 +00:00
|
|
|
}
|
|
|
|
|
2021-06-11 06:34:05 +00:00
|
|
|
protected getLimit = () => {
|
|
|
|
let limit: number;
|
|
|
|
switch (this.exportType) {
|
|
|
|
case exportTypes.LAST_N_MESSAGES:
|
|
|
|
limit = this.exportOptions.numberOfMessages;
|
|
|
|
break;
|
|
|
|
case exportTypes.TIMELINE:
|
|
|
|
limit = 40;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
limit = Number.MAX_VALUE;
|
|
|
|
}
|
|
|
|
return limit;
|
|
|
|
}
|
|
|
|
|
2021-06-04 09:38:17 +00:00
|
|
|
protected getRequiredEvents = async () : Promise<MatrixEvent[]> => {
|
|
|
|
const client = MatrixClientPeg.get();
|
2021-06-07 06:04:03 +00:00
|
|
|
const eventMapper = client.getEventMapper();
|
2021-06-04 09:38:17 +00:00
|
|
|
|
|
|
|
let prevToken: string|null = null;
|
2021-06-11 06:34:05 +00:00
|
|
|
let limit = this.getLimit();
|
2021-06-04 09:38:17 +00:00
|
|
|
let events: MatrixEvent[] = [];
|
2021-06-07 06:04:03 +00:00
|
|
|
|
2021-06-04 09:38:17 +00:00
|
|
|
while (limit) {
|
2021-06-10 06:23:41 +00:00
|
|
|
const eventsPerCrawl = Math.min(limit, 1000);
|
2021-06-05 05:57:08 +00:00
|
|
|
const res: any = await client.createMessagesRequest(this.room.roomId, prevToken, eventsPerCrawl, "b");
|
2021-06-04 09:38:17 +00:00
|
|
|
|
|
|
|
if (res.chunk.length === 0) break;
|
|
|
|
|
2021-06-10 06:23:41 +00:00
|
|
|
limit -= res.chunk.length;
|
2021-06-04 09:38:17 +00:00
|
|
|
|
|
|
|
const matrixEvents: MatrixEvent[] = res.chunk.map(eventMapper);
|
|
|
|
|
2021-06-11 06:34:05 +00:00
|
|
|
for (const mxEv of matrixEvents) {
|
|
|
|
if (this.exportOptions.startDate && mxEv.getTs() < this.exportOptions.startDate) {
|
|
|
|
// Once the last message received is older than the start date, we break out of both the loops
|
|
|
|
limit = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
events.push(mxEv);
|
|
|
|
}
|
2021-06-04 09:38:17 +00:00
|
|
|
|
|
|
|
prevToken = res.end;
|
|
|
|
}
|
2021-06-08 13:07:36 +00:00
|
|
|
// Reverse the events so that we preserve the order
|
2021-06-07 06:04:03 +00:00
|
|
|
events = events.reverse();
|
2021-06-04 09:38:17 +00:00
|
|
|
|
|
|
|
const decryptionPromises = events
|
|
|
|
.filter(event => event.isEncrypted())
|
|
|
|
.map(event => {
|
|
|
|
return client.decryptEventIfNeeded(event, {
|
2021-06-07 06:04:03 +00:00
|
|
|
isRetry: true,
|
|
|
|
emit: false,
|
2021-06-04 09:38:17 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2021-06-08 13:07:14 +00:00
|
|
|
// Wait for all the events to get decrypted.
|
2021-06-04 09:38:17 +00:00
|
|
|
await Promise.all(decryptionPromises);
|
|
|
|
|
2021-06-07 09:17:27 +00:00
|
|
|
for (let i = 0; i < events.length; i++) this.setEventMetadata(events[i]);
|
2021-06-04 09:38:17 +00:00
|
|
|
|
2021-06-07 06:04:03 +00:00
|
|
|
return events;
|
2021-06-04 09:38:17 +00:00
|
|
|
}
|
|
|
|
|
2021-06-14 12:36:40 +00:00
|
|
|
protected async getMediaBlob(event: MatrixEvent) {
|
|
|
|
let blob: Blob;
|
|
|
|
try {
|
|
|
|
const isEncrypted = event.isEncrypted();
|
|
|
|
const content = event.getContent();
|
|
|
|
const shouldDecrypt = isEncrypted && !content.hasOwnProperty("org.matrix.msc1767.file")
|
|
|
|
&& event.getType() !== "m.sticker";
|
|
|
|
if (shouldDecrypt) {
|
|
|
|
blob = await decryptFile(content.file);
|
|
|
|
} else {
|
|
|
|
const media = mediaFromContent(event.getContent());
|
|
|
|
const image = await fetch(media.srcHttp);
|
|
|
|
blob = await image.blob();
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
console.log("Error decrypting media");
|
|
|
|
}
|
|
|
|
return blob;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected splitFileName(file: string) {
|
|
|
|
const lastDot = file.lastIndexOf('.');
|
|
|
|
if (lastDot === -1) return [file, ""];
|
|
|
|
const fileName = file.slice(0, lastDot);
|
|
|
|
const ext = file.slice(lastDot + 1);
|
|
|
|
return [fileName, '.' + ext];
|
|
|
|
}
|
|
|
|
|
|
|
|
protected getFilePath(event: MatrixEvent) {
|
|
|
|
const mediaType = event.getContent().msgtype;
|
|
|
|
let fileDirectory: string;
|
|
|
|
switch (mediaType) {
|
|
|
|
case "m.image":
|
|
|
|
fileDirectory = "images";
|
|
|
|
break;
|
|
|
|
case "m.video":
|
|
|
|
fileDirectory = "videos";
|
|
|
|
break;
|
|
|
|
case "m.audio":
|
|
|
|
fileDirectory = "audio";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
fileDirectory = event.getType() === "m.sticker" ? "stickers" : "files";
|
|
|
|
}
|
|
|
|
const fileDate = formatFullDateNoDay(new Date(event.getTs()));
|
|
|
|
const [fileName, fileExt] = this.splitFileName(event.getContent().body);
|
|
|
|
return fileDirectory + "/" + fileName + '-' + fileDate + fileExt;
|
|
|
|
}
|
|
|
|
|
2021-06-22 07:20:15 +00:00
|
|
|
protected isReply(event: MatrixEvent) {
|
|
|
|
const isEncrypted = event.isEncrypted();
|
|
|
|
// If encrypted, in_reply_to lies in event.event.content
|
|
|
|
const content = isEncrypted ? event.event.content : event.getContent();
|
|
|
|
const relatesTo = content["m.relates_to"];
|
2021-06-22 05:19:14 +00:00
|
|
|
return !!(relatesTo && relatesTo["m.in_reply_to"]);
|
|
|
|
}
|
|
|
|
|
2021-06-14 12:36:40 +00:00
|
|
|
protected isAttachment(mxEv: MatrixEvent) {
|
|
|
|
const attachmentTypes = ["m.sticker", "m.image", "m.file", "m.video", "m.audio"];
|
|
|
|
return mxEv.getType() === attachmentTypes[0] || attachmentTypes.includes(mxEv.getContent().msgtype);
|
|
|
|
}
|
|
|
|
|
2021-06-17 05:16:08 +00:00
|
|
|
abstract export(): Promise<any>;
|
2021-05-24 15:18:13 +00:00
|
|
|
}
|