import { MatrixClient } from "../MatrixClient";
import { IPreprocessor } from "./IPreprocessor";
import { EventKind, extractRequestError, LogService } from "..";
/**
* Metadata for a rich reply. Usually stored under the "mx_richreply"
* field of an event (at the top level).
* @category Preprocessors
* @see RichRepliesPreprocessor
*/
export interface IRichReplyMetadata {
/**
* If true, the preprocessor found some inconsistencies in the reply
* information that does not match the specification. For example,
* this may indicate that a reply was sent without an HTML component.
*/
wasLenient: boolean;
/**
* The event ID the event references. May be an empty string if
* wasLenient is true.
*/
parentEventId: string;
/**
* The fallback plain text the preprocessor found. May be an empty
* string if wasLenient is true. The prefix characters to indicate
* this is a fallback will have already been removed.
*/
fallbackPlainBody: string;
/**
* The fallback HTML the processor found. May be an empty string if
* wasLenient is true. The fallback structure will have already been
* removed, leaving just the original assumed HTML.
*/
fallbackHtmlBody: string;
/**
* The user ID that sent the parent event, as determined by the fallback
* text. This should not be relied upon for anything serious, and instead
* the preprocessor should be configured to fetch the real event to
* populate the realEvent property. May be an empty string if wasLenient
* is true.
*/
fallbackSender: string;
/**
* If the preprocessor is configured to fetch event content, this field
* will contain the event as reported by the homeserver. May be null if
* wasLenient is true.
*/
realEvent: any;
}
/**
* Processes rich replies found in events. This automatically strips
* the fallback representation from events, providing the information
* as a top level "mx_richreply" key. The "mx_richreply" property may
* be cast to the type IRichReplyMetadata.
* @category Preprocessors
*/
export class RichRepliesPreprocessor implements IPreprocessor {
/**
* Creates a new rich replies preprocessor.
* @param fetchRealEventContents If enabled, this preprocessor will
* attempt to get the real event contents and append them to the event
* information.
*/
public constructor(private fetchRealEventContents = false) {
}
public getSupportedEventTypes(): string[] {
return ["m.room.message"];
}
public async processEvent(event: any, client: MatrixClient, kind?: EventKind): Promise<any> {
if (kind && kind !== EventKind.RoomEvent) return;
if (!event["content"]) return;
if (!event["content"]["m.relates_to"]) return;
if (!event["content"]["m.relates_to"]["m.in_reply_to"]) return;
const parentEventId = event["content"]["m.relates_to"]["m.in_reply_to"]["event_id"];
if (!parentEventId) return;
let fallbackHtml = "";
let fallbackText = "";
let fallbackSender = "";
let realHtml = event["content"]["formatted_body"];
let realText = event["content"]["body"];
let lenient = false;
if (event["content"]["format"] !== "org.matrix.custom.html" || !event["content"]["formatted_body"]) {
lenient = true; // Not safe to parse: probably not HTML
} else {
const formattedBody = event["content"]["formatted_body"];
if (!formattedBody.startsWith("<mx-reply>") || formattedBody.indexOf("</mx-reply>") === -1) {
lenient = true; // Doesn't look like a reply
} else {
const parts = formattedBody.split("</mx-reply>");
const fbHtml = parts[0];
realHtml = parts[1];
const results = fbHtml.match(/<br[ ]*[/]{0,2}>(.*)<\/blockquote>\s*$/i);
if (!results) {
lenient = true;
} else {
fallbackHtml = results[1];
}
}
}
let processedFallback = false;
const body = event["content"]["body"] || "";
for (const line of body.split("\n")) {
if (line.startsWith("> ") && !processedFallback) {
fallbackText += line.substring(2) + "\n";
} else if (!processedFallback) {
realText = "";
processedFallback = true;
} else {
realText += line + "\n";
}
}
const firstFallbackLine = fallbackText.split("\n")[0];
const matches = firstFallbackLine.match(/<(@.*:.*)>/);
if (!matches) {
lenient = true;
} else {
fallbackSender = matches[1];
}
const metadata: IRichReplyMetadata = {
wasLenient: lenient,
fallbackHtmlBody: fallbackHtml ? fallbackHtml.trim() : "",
fallbackPlainBody: fallbackText ? fallbackText.trim() : "",
fallbackSender: fallbackSender ? fallbackSender.trim() : "",
parentEventId: parentEventId ? parentEventId.trim() : "",
realEvent: null,
};
if (this.fetchRealEventContents) {
try {
metadata.realEvent = await client.getEvent(event["room_id"], parentEventId);
} catch (e) {
LogService.error("RichRepliesPreprocessor", "Failed to fetch real event:");
LogService.error("RichRepliesPreprocessor", extractRequestError(e));
metadata.wasLenient = true; // failed to fetch event
}
}
event["mx_richreply"] = metadata;
event["content"]["body"] = realText.trim();
if (realHtml) event["content"]["formatted_body"] = realHtml.trim();
return event;
}
}
Source