Begin extended implementation

pull/21833/head
Jaiwanth 2021-06-04 15:08:17 +05:30
parent e7f0df7fcc
commit f32726d5ed
4 changed files with 145 additions and 20 deletions

View File

@ -32,8 +32,7 @@ import RoomTopic from "../elements/RoomTopic";
import RoomName from "../elements/RoomName"; import RoomName from "../elements/RoomName";
import {PlaceCallType} from "../../../CallHandler"; import {PlaceCallType} from "../../../CallHandler";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import exportConversationalHistory from '../../../utils/exportUtils/exportUtils'; import exportConversationalHistory, { exportTypes, exportFormats } from '../../../utils/exportUtils/exportUtils';
import { exportFormats, exportOptions } from '../../../utils/exportUtils/exportUtils';
@replaceableComponent("views.rooms.RoomHeader") @replaceableComponent("views.rooms.RoomHeader")
@ -121,7 +120,7 @@ export default class RoomHeader extends React.Component {
} }
_exportConvertionalHistory = async () => { _exportConvertionalHistory = async () => {
await exportConversationalHistory(this.props.room, exportFormats.HTML, exportOptions.TIMELINE); await exportConversationalHistory(this.props.room, exportFormats.HTML, exportTypes.LAST_N_MESSAGES, 30);
} }
render() { render() {

View File

@ -3,9 +3,11 @@ import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixClientPeg } from "../../MatrixClientPeg"; import { MatrixClientPeg } from "../../MatrixClientPeg";
import { TimelineWindow } from "matrix-js-sdk/src/timeline-window"; import { TimelineWindow } from "matrix-js-sdk/src/timeline-window";
import { arrayFastClone } from "../arrays"; import { arrayFastClone } from "../arrays";
import { exportTypes } from "./exportUtils";
import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
export default abstract class Exporter { export default abstract class Exporter {
constructor(protected room: Room) {} constructor(protected room: Room, protected exportType: exportTypes, protected numberOfEvents?: number) {}
protected getTimelineConversation = () : MatrixEvent[] => { protected getTimelineConversation = () : MatrixEvent[] => {
if (!this.room) return; if (!this.room) return;
@ -20,7 +22,7 @@ export default abstract class Exporter {
timelineWindow.load(null, 30); timelineWindow.load(null, 30);
const events = timelineWindow.getEvents(); const events: MatrixEvent[] = timelineWindow.getEvents();
// Clone and reverse the events so that we preserve the order // Clone and reverse the events so that we preserve the order
arrayFastClone(events) arrayFastClone(events)
@ -32,5 +34,121 @@ export default abstract class Exporter {
return events; return events;
}; };
protected eventToJson(ev) {
const jsonEvent = ev.toJSON();
const e = ev.isEncrypted() ? jsonEvent.decrypted : jsonEvent;
if (ev.isEncrypted()) {
e.curve25519Key = ev.getSenderKey();
e.ed25519Key = ev.getClaimedEd25519Key();
e.algorithm = ev.getWireContent().algorithm;
e.forwardingCurve25519KeyChain = ev.getForwardingCurve25519KeyChain();
} else {
delete e.curve25519Key;
delete e.ed25519Key;
delete e.algorithm;
delete e.forwardingCurve25519KeyChain;
}
return e;
}
protected getRequiredEvents = async () : Promise<MatrixEvent[]> => {
const client = MatrixClientPeg.get();
const eventMapper = client.getEventMapper({ preventReEmit: true });
let prevToken: string|null = null;
let limit = this.numberOfEvents || Number.MAX_VALUE;
let events: MatrixEvent[] = [];
const stateRes: any[] = [];
while (limit) {
const eventsPerCrawl = Math.min(limit, 100);
const res = await client._createMessagesRequest(this.room.roomId, prevToken, eventsPerCrawl, "b");
if (res.state) stateRes.push(...res.state);
if (res.chunk.length === 0) break;
limit -= eventsPerCrawl;
const matrixEvents: MatrixEvent[] = res.chunk.map(eventMapper);
matrixEvents.forEach(mxEv => events.push(mxEv));
prevToken = res.end;
}
events = events.reverse()
let stateEvents = [];
if (stateRes !== undefined) {
stateEvents = stateRes.map(eventMapper);
}
const profiles = {};
stateEvents.forEach(ev => {
if (ev.event.content &&
ev.event.content.membership === "join") {
profiles[ev.event.sender] = {
displayname: ev.event.content.displayname,
avatar_url: ev.event.content.avatar_url,
};
}
});
const decryptionPromises = events
.filter(event => event.isEncrypted())
.map(event => {
return client.decryptEventIfNeeded(event, {
isRetry: true,
emit: false,
});
});
// Let us wait for all the events to get decrypted.
await Promise.all(decryptionPromises);
const eventsWithProfile = events.map((ev) => {
const e = this.eventToJson(ev);
let profile: any = {};
if (e.sender in profiles) profile = profiles[e.sender];
const object = {
event: e,
profile: profile,
};
return object;
});
const matrixEvents = eventsWithProfile.map(e => {
const matrixEvent = eventMapper(e.event);
const member = new RoomMember(this.room.roomId, matrixEvent.getSender());
member.name = e.profile.displayname;
const memberEvent = eventMapper(
{
content: {
membership: "join",
avatar_url: e.profile.avatar_url,
displayname: e.profile.displayname,
},
type: "m.room.member",
event_id: matrixEvent.getId() + ":eventIndex",
room_id: matrixEvent.getRoomId(),
sender: matrixEvent.getSender(),
origin_server_ts: matrixEvent.getTs(),
state_key: matrixEvent.getSender(),
},
);
member.events.member = memberEvent;
matrixEvent.sender = member;
return matrixEvent;
});
return matrixEvents;
}
abstract export(): Promise<Blob>; abstract export(): Promise<Blob>;
} }

View File

@ -22,23 +22,24 @@ import BaseAvatar from "../../components/views/avatars/BaseAvatar";
import exportCSS from "./exportCSS"; import exportCSS from "./exportCSS";
import exportJS from "./exportJS"; import exportJS from "./exportJS";
import exportIcons from "./exportIcons"; import exportIcons from "./exportIcons";
import { exportTypes } from "./exportUtils";
export default class HTMLExporter extends Exporter { export default class HTMLExporter extends Exporter {
protected zip: JSZip; protected zip: JSZip;
protected avatars: Map<string, boolean>; protected avatars: Map<string, boolean>;
protected permalinkCreator: RoomPermalinkCreator; protected permalinkCreator: RoomPermalinkCreator;
constructor(room: Room) { constructor(room: Room, exportType: exportTypes, numberOfEvents?: number) {
super(room); super(room, exportType, numberOfEvents);
this.zip = new JSZip(); this.zip = new JSZip();
this.avatars = new Map<string, boolean>(); this.avatars = new Map<string, boolean>();
this.permalinkCreator = new RoomPermalinkCreator(this.room); this.permalinkCreator = new RoomPermalinkCreator(this.room);
} }
protected async getRoomAvatar(avatarSide: number) { protected async getRoomAvatar() {
let blob: Blob; let blob: Blob;
const avatarUrl = Avatar.avatarUrlForRoom(this.room, avatarSide, avatarSide, "crop"); const avatarUrl = Avatar.avatarUrlForRoom(this.room, 32, 32, "crop");
const avatarPath = `room/avatar${avatarSide}.png`; const avatarPath = "room.png";
if (avatarUrl) { if (avatarUrl) {
const image = await fetch(avatarUrl); const image = await fetch(avatarUrl);
blob = await image.blob(); blob = await image.blob();
@ -46,8 +47,8 @@ export default class HTMLExporter extends Exporter {
} }
const avatar = ( const avatar = (
<BaseAvatar <BaseAvatar
width={avatarSide} width={32}
height={avatarSide} height={32}
name={this.room.name} name={this.room.name}
title={this.room.name} title={this.room.name}
url={blob ? avatarPath : null} url={blob ? avatarPath : null}
@ -58,7 +59,7 @@ export default class HTMLExporter extends Exporter {
} }
protected async wrapHTML(content: string) { protected async wrapHTML(content: string) {
const roomAvatar32 = await this.getRoomAvatar(32); const roomAvatar = await this.getRoomAvatar();
const exportDate = formatFullDateNoDayNoTime(new Date()); const exportDate = formatFullDateNoDayNoTime(new Date());
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const creator = this.room.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender(); const creator = this.room.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender();
@ -81,7 +82,6 @@ export default class HTMLExporter extends Exporter {
}); });
const topicText = topic ? _t("Topic: %(topic)s", { topic }) : ""; const topicText = topic ? _t("Topic: %(topic)s", { topic }) : "";
const roomAvatar52 = await this.getRoomAvatar(52);
return ` return `
@ -108,7 +108,7 @@ export default class HTMLExporter extends Exporter {
<div class="mx_RoomHeader_wrapper" aria-owns="mx_RightPanel"> <div class="mx_RoomHeader_wrapper" aria-owns="mx_RightPanel">
<div class="mx_RoomHeader_avatar"> <div class="mx_RoomHeader_avatar">
<div class="mx_DecoratedRoomAvatar"> <div class="mx_DecoratedRoomAvatar">
${roomAvatar32} ${roomAvatar}
</div> </div>
</div> </div>
<div class="mx_RoomHeader_name"> <div class="mx_RoomHeader_name">
@ -143,9 +143,10 @@ export default class HTMLExporter extends Exporter {
role="list" role="list"
> >
<div class="mx_NewRoomIntro"> <div class="mx_NewRoomIntro">
${roomAvatar52} ${roomAvatar}
<h2> ${this.room.name} </h2> <h2> ${this.room.name} </h2>
<p> ${createdText} <br/><br/> ${exportedText} </p> <p> ${createdText} <br/><br/> ${exportedText} </p>
<br/>
<p> ${topicText} </p> <p> ${topicText} </p>
</div> </div>
${content} ${content}

View File

@ -1,4 +1,4 @@
import { Room } from 'matrix-js-sdk/src/models/room'; import { Room } from "matrix-js-sdk/src/models/room";
import HTMLExporter from "./HtmlExport"; import HTMLExporter from "./HtmlExport";
export enum exportFormats { export enum exportFormats {
@ -7,14 +7,21 @@ export enum exportFormats {
LOGS = "LOGS", LOGS = "LOGS",
} }
export enum exportOptions { export enum exportTypes {
TIMELINE = "TIMELINE", TIMELINE = "TIMELINE",
BEGINNING = "BEGINNING",
LAST_N_MESSAGES = "LAST_N_MESSAGES",
} }
const exportConversationalHistory = async (room: Room, format: string, options) => { const exportConversationalHistory = async (
room: Room,
format: string,
exportType: exportTypes,
numberOfEvents?: number,
) => {
switch (format) { switch (format) {
case exportFormats.HTML: case exportFormats.HTML:
await new HTMLExporter(room).export(); await new HTMLExporter(room, exportType, numberOfEvents).export();
break; break;
case exportFormats.JSON: case exportFormats.JSON:
break; break;