mx-puppet-bridge
Advanced tools
Comparing version 0.0.40 to 0.0.41
/// <reference types="node" /> | ||
import { IStringFormatterVars } from "./structures/stringformatter"; | ||
import { MessageEvent, TextualMessageEventContent, FileMessageEventContent } from "@sorunome/matrix-bot-sdk"; | ||
declare type PuppetDataSingleType = string | number | boolean | IPuppetData | null | undefined; | ||
@@ -73,2 +74,11 @@ export interface IPuppetData { | ||
} | ||
export interface IEventInfo { | ||
message?: IMessageEvent; | ||
file?: IFileEvent; | ||
event: MessageEvent<TextualMessageEventContent> | MessageEvent<FileMessageEventContent>; | ||
user: ISendingUser; | ||
} | ||
export interface IReplyEvent extends IMessageEvent { | ||
reply: IEventInfo; | ||
} | ||
export interface IFileEvent { | ||
@@ -84,2 +94,3 @@ filename: string; | ||
url: string; | ||
type: string; | ||
eventId?: string; | ||
@@ -118,2 +129,3 @@ } | ||
mxid: string; | ||
user: IRemoteUser | null; | ||
} | ||
@@ -120,0 +132,0 @@ export declare type CreateUserHook = (user: IRemoteUser) => Promise<IRemoteUser | null>; |
import { PuppetBridge } from "./puppetbridge"; | ||
import { MatrixClient } from "@sorunome/matrix-bot-sdk"; | ||
import { IEventInfo } from "./interfaces"; | ||
export declare class MatrixEventHandler { | ||
@@ -7,2 +9,3 @@ private bridge; | ||
registerAppserviceEvents(): void; | ||
getEventInfo(roomId: string, eventId: string, client?: MatrixClient | null, sender?: string): Promise<IEventInfo | null>; | ||
private handleRoomEvent; | ||
@@ -15,3 +18,5 @@ private handleJoinEvent; | ||
private handleMessageEvent; | ||
private getFileEventData; | ||
private handleFileEvent; | ||
private getMessageEventData; | ||
private handleTextEvent; | ||
@@ -18,0 +23,0 @@ private handleInviteEvent; |
@@ -66,2 +66,41 @@ "use strict"; | ||
} | ||
getEventInfo(roomId, eventId, client, sender) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
try { | ||
if (!client) { | ||
client = yield this.bridge.roomSync.getRoomOp(roomId); | ||
} | ||
if (!client) { | ||
log.error(`Failed fetching event in room ${roomId}: no client`); | ||
return null; | ||
} | ||
const rawEvent = yield client.getEvent(roomId, eventId); | ||
if (!rawEvent) { | ||
return null; | ||
} | ||
const evt = new matrix_bot_sdk_1.MessageEvent(rawEvent); | ||
const info = { | ||
user: (yield this.getSendingUser(true, roomId, evt.sender, sender)), | ||
event: evt, | ||
}; | ||
if (["m.file", "m.image", "m.audio", "m.sticker", "m.video"].includes(this.getMessageType(evt))) { | ||
// file event | ||
const replyEvent = new matrix_bot_sdk_1.MessageEvent(evt.raw); | ||
info.event = replyEvent; | ||
info.file = this.getFileEventData(replyEvent); | ||
} | ||
else { | ||
// message event | ||
const replyEvent = new matrix_bot_sdk_1.MessageEvent(evt.raw); | ||
info.event = replyEvent; | ||
info.message = this.getMessageEventData(replyEvent); | ||
} | ||
return info; | ||
} | ||
catch (err) { | ||
log.error(`Event ${eventId} in room ${roomId} not found`, err.error || err.body || err); | ||
return null; | ||
} | ||
}); | ||
} | ||
handleRoomEvent(roomId, event) { | ||
@@ -304,2 +343,27 @@ return __awaiter(this, void 0, void 0, function* () { | ||
} | ||
getFileEventData(event) { | ||
const msgtype = this.getMessageType(event); | ||
const content = event.content; | ||
const url = this.bridge.getUrlFromMxc(content.url); | ||
const data = { | ||
filename: content.body || "", | ||
mxc: content.url, | ||
url, | ||
eventId: event.eventId, | ||
type: "file", | ||
}; | ||
if (content.info) { | ||
data.info = content.info; | ||
} | ||
data.type = { | ||
"m.image": "image", | ||
"m.audio": "audio", | ||
"m.video": "video", | ||
"m.sticker": "sticker", | ||
}[msgtype]; | ||
if (!data.type) { | ||
data.type = "file"; | ||
} | ||
return data; | ||
} | ||
handleFileEvent(roomId, room, puppetData, event) { | ||
@@ -309,22 +373,4 @@ return __awaiter(this, void 0, void 0, function* () { | ||
log.info(`Handling file event with msgtype ${msgtype}...`); | ||
const content = event.content; | ||
const url = this.bridge.getUrlFromMxc(content.url); | ||
const data = { | ||
filename: content.body, | ||
mxc: content.url, | ||
url, | ||
eventId: event.eventId, | ||
}; | ||
if (content.info) { | ||
data.info = content.info; | ||
} | ||
let emitEvent = { | ||
"m.image": "image", | ||
"m.audio": "audio", | ||
"m.video": "video", | ||
"m.sticker": "sticker", | ||
}[msgtype]; | ||
if (!emitEvent) { | ||
emitEvent = "file"; | ||
} | ||
const data = this.getFileEventData(event); | ||
const emitEvent = data.type; | ||
const asUser = yield this.getSendingUser(puppetData, roomId, event.sender); | ||
@@ -359,2 +405,16 @@ // alright, now determine fallbacks etc. | ||
} | ||
getMessageEventData(event) { | ||
const msgtype = this.getMessageType(event); | ||
const content = event.content; | ||
const msgData = { | ||
body: content.body || "", | ||
emote: msgtype === "m.emote", | ||
notice: msgtype === "m.notice", | ||
eventId: event.eventId, | ||
}; | ||
if (content.format) { | ||
msgData.formattedBody = content.formatted_body; | ||
} | ||
return msgData; | ||
} | ||
handleTextEvent(roomId, room, puppetData, event) { | ||
@@ -364,12 +424,3 @@ return __awaiter(this, void 0, void 0, function* () { | ||
log.info(`Handling text event with msgtype ${msgtype}...`); | ||
const content = event.content; | ||
const msgData = { | ||
body: content.body || "", | ||
emote: msgtype === "m.emote", | ||
notice: msgtype === "m.notice", | ||
eventId: event.eventId, | ||
}; | ||
if (content.format) { | ||
msgData.formattedBody = content.formatted_body; | ||
} | ||
const msgData = this.getMessageEventData(event); | ||
const relate = event.content["m.relates_to"]; // there is no relates_to interface yet :[ | ||
@@ -398,5 +449,12 @@ const asUser = yield this.getSendingUser(puppetData, roomId, event.sender); | ||
if (this.bridge.protocol.features.reply && (relate.rel_type === "m.in_reply_to" || relate["m.in_reply_to"])) { | ||
log.debug("Emitting reply event..."); | ||
this.bridge.emit("reply", room, relEvent, msgData, asUser, event); | ||
return; | ||
// okay, let's try to fetch the original event | ||
const info = yield this.getEventInfo(roomId, eventId, null, event.sender); | ||
if (info) { | ||
const replyData = Object.assign(msgData, { | ||
reply: info, | ||
}); | ||
log.debug("Emitting reply event..."); | ||
this.bridge.emit("reply", room, relEvent, replyData, asUser, event); | ||
return; | ||
} | ||
} | ||
@@ -598,8 +656,13 @@ if (relate.rel_type === "m.annotation") { | ||
} | ||
getSendingUser(puppetData, roomId, userId) { | ||
getSendingUser(puppetData, roomId, userId, sender) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
if (puppetData.type !== "relay") { | ||
if (!puppetData || (typeof puppetData !== "boolean" && puppetData.type !== "relay")) { | ||
return null; | ||
} | ||
const membership = yield this.getRoomMemberInfo(roomId, userId); | ||
let user = null; | ||
try { | ||
user = yield this.getUserParts(userId, sender || userId); | ||
} | ||
catch (_a) { } // ignore error | ||
if (!membership) { | ||
@@ -611,2 +674,3 @@ return { | ||
avatarUrl: null, | ||
user, | ||
}; | ||
@@ -625,2 +689,3 @@ } | ||
avatarUrl, | ||
user, | ||
}; | ||
@@ -627,0 +692,0 @@ }); |
@@ -24,3 +24,3 @@ /// <reference types="node" /> | ||
import { DelayedFunction } from "./structures/delayedfunction"; | ||
import { IPuppetBridgeRegOpts, IPuppetBridgeFeatures, IReceiveParams, IMessageEvent, IProtocolInformation, CreateRoomHook, CreateUserHook, CreateGroupHook, GetDescHook, BotHeaderMsgHook, GetDataFromStrHook, GetDmRoomIdHook, ListUsersHook, ListRoomsHook, IRemoteUser, IRemoteRoom, IRemoteGroup, IPuppetData, GetUserIdsInRoomHook, UserExistsHook, RoomExistsHook, GroupExistsHook, ResolveRoomIdHook } from "./interfaces"; | ||
import { IPuppetBridgeRegOpts, IPuppetBridgeFeatures, IReceiveParams, IMessageEvent, IProtocolInformation, CreateRoomHook, CreateUserHook, CreateGroupHook, GetDescHook, BotHeaderMsgHook, GetDataFromStrHook, GetDmRoomIdHook, ListUsersHook, ListRoomsHook, IRemoteUser, IRemoteRoom, IRemoteGroup, IPuppetData, GetUserIdsInRoomHook, UserExistsHook, RoomExistsHook, GroupExistsHook, ResolveRoomIdHook, IEventInfo } from "./interfaces"; | ||
export interface IPuppetBridgeHooks { | ||
@@ -255,3 +255,4 @@ createUser?: CreateUserHook; | ||
redactEvent(client: MatrixClient, roomId: string, eventId: string): Promise<void>; | ||
getEventInfo(roomId: string | IRemoteRoom, eventId: string, client?: MatrixClient): Promise<IEventInfo | null>; | ||
} | ||
export {}; |
@@ -772,3 +772,23 @@ "use strict"; | ||
} | ||
getEventInfo(roomId, eventId, client) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
let sender; | ||
if (typeof roomId !== "string") { | ||
const maybeRoomId = yield this.roomSync.maybeGetMxid(roomId); | ||
if (!maybeRoomId) { | ||
return null; | ||
} | ||
if (roomId.puppetId !== -1) { | ||
try { | ||
sender = yield this.provisioner.getMxid(roomId.puppetId); | ||
} | ||
catch (_a) { } | ||
} | ||
eventId = (yield this.eventSync.getMatrix(roomId, eventId))[0]; | ||
roomId = maybeRoomId; | ||
} | ||
return this.matrixEventHandler.getEventInfo(roomId, eventId, client, sender); | ||
}); | ||
} | ||
} | ||
exports.PuppetBridge = PuppetBridge; |
@@ -27,2 +27,3 @@ "use strict"; | ||
const timedcache_1 = require("./structures/timedcache"); | ||
const escapeHtml = require("escape-html"); | ||
const log = new log_1.Log("RemoteEventHandler"); | ||
@@ -88,5 +89,5 @@ // tslint:disable no-magic-numbers | ||
} | ||
const origEvents = yield this.bridge.eventSync.getMatrix(params.room, params.eventId); | ||
for (const origEvent of origEvents) { | ||
yield ret.client.sendReadReceipt(ret.mxid, origEvent.split(";")[0]); | ||
const origEventIdIds = yield this.bridge.eventSync.getMatrix(params.room, params.eventId); | ||
for (const origEventId of origEventIdIds) { | ||
yield ret.client.sendReadReceipt(ret.mxid, origEventId); | ||
} | ||
@@ -175,12 +176,12 @@ }); | ||
} | ||
const origEvents = yield this.bridge.eventSync.getMatrix(params.room, eventId); | ||
const origEventIdIds = yield this.bridge.eventSync.getMatrix(params.room, eventId); | ||
if (ix < 0) { | ||
// negative indexes are from the back | ||
ix = origEvents.length + ix; | ||
ix = origEventIdIds.length + ix; | ||
} | ||
if (ix >= origEvents.length) { | ||
if (ix >= origEventIdIds.length) { | ||
// sanity check on the index | ||
ix = 0; | ||
} | ||
const origEvent = origEvents[ix]; | ||
const origEventId = origEventIdIds[ix]; | ||
// this object is set to any-type as the interfaces don't do edits yet | ||
@@ -196,5 +197,5 @@ const send = { | ||
}; // tslint:disable-line no-any | ||
if (origEvent) { | ||
if (origEventId) { | ||
send["m.relates_to"] = { | ||
event_id: origEvent.split(";")[0], | ||
event_id: origEventId, | ||
rel_type: "m.replace", | ||
@@ -231,5 +232,5 @@ }; | ||
const { client, mxid } = yield this.prepareSend(params); | ||
const origEvents = yield this.bridge.eventSync.getMatrix(params.room, eventId); | ||
for (const origEvent of origEvents) { | ||
yield this.bridge.redactEvent(client, mxid, origEvent.split(";")[0]); | ||
const origEventIdIds = yield this.bridge.eventSync.getMatrix(params.room, eventId); | ||
for (const origEventId of origEventIdIds) { | ||
yield this.bridge.redactEvent(client, mxid, origEventId); | ||
} | ||
@@ -252,4 +253,4 @@ }); | ||
} | ||
const origEvents = yield this.bridge.eventSync.getMatrix(params.room, eventId); | ||
const origEvent = origEvents[0]; | ||
const origEventIds = yield this.bridge.eventSync.getMatrix(params.room, eventId); | ||
const origEventId = origEventIds[0]; | ||
// this send object needs to be any-type, as the interfaces don't do replies yet | ||
@@ -259,10 +260,57 @@ const send = { | ||
body: opts.body, | ||
format: "org.matrix.custom.html", | ||
formatted_body: opts.formattedBody ? opts.formattedBody : escapeHtml(opts.body).replace(/\n/g, "<br>"), | ||
source: this.bridge.protocol.id, | ||
}; // tslint:disable-line no-any | ||
if (origEvent) { | ||
if (opts.formattedBody) { | ||
send.format = "org.matrix.custom.html"; | ||
send.formatted_body = opts.formattedBody; | ||
} | ||
if (origEventId) { | ||
send["m.relates_to"] = { | ||
"m.in_reply_to": { | ||
event_id: origEvent.split(";")[0], | ||
event_id: origEventId, | ||
}, | ||
}; | ||
try { | ||
const info = yield this.bridge.getEventInfo(mxid, origEventId, client); | ||
if (info) { | ||
if (info.message) { | ||
if (!info.message.formattedBody) { | ||
info.message.formattedBody = escapeHtml(info.message.body).replace(/\n/g, "<br>"); | ||
} | ||
const bodyParts = info.message.body.split("\n"); | ||
bodyParts[0] = `${info.message.emote ? "* " : ""}<${info.user.mxid}> ${bodyParts[0]}`; | ||
send.body = `${bodyParts.map((l) => `> ${l}`).join("\n")}\n\n${send.body}`; | ||
const richHeader = `<mx-reply><blockquote> | ||
<a href="https://matrix.to/#/${mxid}/${origEventId}">In reply to</a> | ||
${info.message.emote ? "* " : ""}<a href="https://matrix.to/#/${info.user.mxid}">${info.user.mxid}</a> | ||
<br>${info.message.formattedBody} | ||
</blockquote></mx-reply>`; | ||
send.formatted_body = richHeader + send.formatted_body; | ||
} | ||
else if (info.file) { | ||
let msg = { | ||
image: "an image", | ||
audio: "an audio file", | ||
video: "a video", | ||
sticker: "a sticker", | ||
}[info.file.type]; | ||
if (!msg) { | ||
msg = "a file"; | ||
} | ||
const plainHeader = `> <${info.user.mxid}> sent ${msg}.\n\n`; | ||
send.body = plainHeader + send.body; | ||
const richHeader = `<mx-reply><blockquote> | ||
<a href="https://matrix.to/#/${mxid}/${origEventId}">In reply to</a> | ||
<a href="https://matrix.to/#/${info.user.mxid}">${info.user.mxid}</a> | ||
<br>sent ${msg}. | ||
</blockquote></mx-reply>`; | ||
send.formatted_body = richHeader + send.formatted_body; | ||
} | ||
} | ||
} | ||
catch (err) { | ||
log.warn("Failed to add reply fallback", err.error || err.body || err); | ||
} | ||
} | ||
@@ -272,6 +320,2 @@ else { | ||
} | ||
if (opts.formattedBody) { | ||
send.format = "org.matrix.custom.html"; | ||
send.formatted_body = opts.formattedBody; | ||
} | ||
if (params.externalUrl) { | ||
@@ -278,0 +322,0 @@ send.external_url = params.externalUrl; |
{ | ||
"name": "mx-puppet-bridge", | ||
"version": "0.0.40", | ||
"version": "0.0.41", | ||
"description": "Matrix Puppeting Bridge library", | ||
@@ -24,7 +24,8 @@ "repository": { | ||
"@sorunome/matrix-bot-sdk": "^0.5.3-2", | ||
"got": "^10.7.0", | ||
"better-sqlite3": "^6.0.1", | ||
"escape-html": "^1.0.3", | ||
"events": "^3.1.0", | ||
"expire-set": "^1.0.0", | ||
"file-type": "^12.4.2", | ||
"got": "^10.7.0", | ||
"hasha": "^5.2.0", | ||
@@ -31,0 +32,0 @@ "js-yaml": "^3.13.1", |
@@ -56,3 +56,4 @@ /* | ||
const result: string[] = []; | ||
const rows = await this.db.All("SELECT * FROM event_store WHERE puppet_id = $p AND room_id = $room AND matrix_id = $m", { | ||
const rows = await this.db.All( | ||
"SELECT * FROM event_store WHERE puppet_id = $p AND room_id = $room AND matrix_id = $m", { | ||
p: puppetId, | ||
@@ -59,0 +60,0 @@ room: roomId, |
@@ -15,2 +15,5 @@ /* | ||
import { IStringFormatterVars } from "./structures/stringformatter"; | ||
import { | ||
MessageEvent, TextualMessageEventContent, FileMessageEventContent, | ||
} from "@sorunome/matrix-bot-sdk"; | ||
@@ -112,2 +115,13 @@ type PuppetDataSingleType = string | number | boolean | IPuppetData | null | undefined; | ||
export interface IEventInfo { | ||
message?: IMessageEvent; | ||
file?: IFileEvent; | ||
event: MessageEvent<TextualMessageEventContent> | MessageEvent<FileMessageEventContent>; | ||
user: ISendingUser; | ||
} | ||
export interface IReplyEvent extends IMessageEvent { | ||
reply: IEventInfo; | ||
} | ||
export interface IFileEvent { | ||
@@ -123,2 +137,3 @@ filename: string; | ||
url: string; | ||
type: string; | ||
eventId?: string; | ||
@@ -163,2 +178,3 @@ } | ||
mxid: string; | ||
user: IRemoteUser | null; | ||
} | ||
@@ -165,0 +181,0 @@ |
@@ -18,5 +18,7 @@ /* | ||
MembershipEvent, RedactionEvent, RoomEvent, MessageEvent, FileMessageEventContent, TextualMessageEventContent, | ||
MembershipEventContent, RoomEventContent, MessageEventContent, | ||
MembershipEventContent, RoomEventContent, MessageEventContent, MatrixClient, | ||
} from "@sorunome/matrix-bot-sdk"; | ||
import { IFileEvent, IMessageEvent, IRemoteRoom, ISendingUser, IRemoteUser } from "./interfaces"; | ||
import { | ||
IFileEvent, IMessageEvent, IRemoteRoom, ISendingUser, IRemoteUser, IReplyEvent, IEventInfo, | ||
} from "./interfaces"; | ||
import * as escapeHtml from "escape-html"; | ||
@@ -68,2 +70,44 @@ import { IPuppet } from "./db/puppetstore"; | ||
public async getEventInfo( | ||
roomId: string, | ||
eventId: string, | ||
client?: MatrixClient | null, | ||
sender?: string, | ||
): Promise<IEventInfo | null> { | ||
try { | ||
if (!client) { | ||
client = await this.bridge.roomSync.getRoomOp(roomId); | ||
} | ||
if (!client) { | ||
log.error(`Failed fetching event in room ${roomId}: no client`); | ||
return null; | ||
} | ||
const rawEvent = await client.getEvent(roomId, eventId); | ||
if (!rawEvent) { | ||
return null; | ||
} | ||
const evt = new MessageEvent<MessageEventContent>(rawEvent); | ||
const info: IEventInfo = { | ||
user: (await this.getSendingUser(true, roomId, evt.sender, sender))!, | ||
event: evt, | ||
}; | ||
if (["m.file", "m.image", "m.audio", "m.sticker", "m.video"].includes(this.getMessageType(evt))) { | ||
// file event | ||
const replyEvent = new MessageEvent<FileMessageEventContent>(evt.raw); | ||
info.event = replyEvent; | ||
info.file = this.getFileEventData(replyEvent); | ||
} else { | ||
// message event | ||
const replyEvent = new MessageEvent<TextualMessageEventContent>(evt.raw); | ||
info.event = replyEvent; | ||
info.message = this.getMessageEventData(replyEvent); | ||
} | ||
return info; | ||
} catch (err) { | ||
log.error(`Event ${eventId} in room ${roomId} not found`, err.error || err.body || err); | ||
return null; | ||
} | ||
} | ||
private async handleRoomEvent(roomId: string, event: RoomEvent<RoomEventContent>) { | ||
@@ -304,17 +348,12 @@ if (event.type === "m.room.member") { | ||
private async handleFileEvent( | ||
roomId: string, | ||
room: IRemoteRoom, | ||
puppetData: IPuppet, | ||
event: MessageEvent<FileMessageEventContent>, | ||
) { | ||
private getFileEventData(event: MessageEvent<FileMessageEventContent>): IFileEvent { | ||
const msgtype = this.getMessageType(event); | ||
log.info(`Handling file event with msgtype ${msgtype}...`); | ||
const content = event.content; | ||
const url = this.bridge.getUrlFromMxc(content.url); | ||
const data: IFileEvent = { | ||
filename: content.body, | ||
filename: content.body || "", | ||
mxc: content.url, | ||
url, | ||
eventId: event.eventId, | ||
type: "file", | ||
}; | ||
@@ -324,3 +363,3 @@ if (content.info) { | ||
} | ||
let emitEvent = { | ||
data.type = { | ||
"m.image": "image", | ||
@@ -331,5 +370,18 @@ "m.audio": "audio", | ||
}[msgtype]; | ||
if (!emitEvent) { | ||
emitEvent = "file"; | ||
if (!data.type) { | ||
data.type = "file"; | ||
} | ||
return data; | ||
} | ||
private async handleFileEvent( | ||
roomId: string, | ||
room: IRemoteRoom, | ||
puppetData: IPuppet, | ||
event: MessageEvent<FileMessageEventContent>, | ||
) { | ||
const msgtype = this.getMessageType(event); | ||
log.info(`Handling file event with msgtype ${msgtype}...`); | ||
const data = this.getFileEventData(event); | ||
const emitEvent = data.type; | ||
const asUser = await this.getSendingUser(puppetData, roomId, event.sender); | ||
@@ -364,10 +416,4 @@ // alright, now determine fallbacks etc. | ||
private async handleTextEvent( | ||
roomId: string, | ||
room: IRemoteRoom, | ||
puppetData: IPuppet, | ||
event: MessageEvent<TextualMessageEventContent>, | ||
) { | ||
private getMessageEventData(event: MessageEvent<TextualMessageEventContent>): IMessageEvent { | ||
const msgtype = this.getMessageType(event); | ||
log.info(`Handling text event with msgtype ${msgtype}...`); | ||
const content = event.content; | ||
@@ -383,2 +429,14 @@ const msgData: IMessageEvent = { | ||
} | ||
return msgData; | ||
} | ||
private async handleTextEvent( | ||
roomId: string, | ||
room: IRemoteRoom, | ||
puppetData: IPuppet, | ||
event: MessageEvent<TextualMessageEventContent>, | ||
) { | ||
const msgtype = this.getMessageType(event); | ||
log.info(`Handling text event with msgtype ${msgtype}...`); | ||
const msgData = this.getMessageEventData(event); | ||
const relate = event.content["m.relates_to"]; // there is no relates_to interface yet :[ | ||
@@ -408,5 +466,12 @@ const asUser = await this.getSendingUser(puppetData, roomId, event.sender); | ||
if (this.bridge.protocol.features.reply && (relate.rel_type === "m.in_reply_to" || relate["m.in_reply_to"])) { | ||
log.debug("Emitting reply event..."); | ||
this.bridge.emit("reply", room, relEvent, msgData, asUser, event); | ||
return; | ||
// okay, let's try to fetch the original event | ||
const info = await this.getEventInfo(roomId, eventId, null, event.sender); | ||
if (info) { | ||
const replyData: IReplyEvent = Object.assign(msgData, { | ||
reply: info, | ||
}); | ||
log.debug("Emitting reply event..."); | ||
this.bridge.emit("reply", room, relEvent, replyData, asUser, event); | ||
return; | ||
} | ||
} | ||
@@ -606,7 +671,16 @@ if (relate.rel_type === "m.annotation") { | ||
private async getSendingUser(puppetData: IPuppet, roomId: string, userId: string): Promise<ISendingUser | null> { | ||
if (puppetData.type !== "relay") { | ||
private async getSendingUser( | ||
puppetData: IPuppet | boolean, | ||
roomId: string, | ||
userId: string, | ||
sender?: string, | ||
): Promise<ISendingUser | null> { | ||
if (!puppetData || (typeof puppetData !== "boolean" && puppetData.type !== "relay")) { | ||
return null; | ||
} | ||
const membership = await this.getRoomMemberInfo(roomId, userId); | ||
let user: IRemoteUser | null = null; | ||
try { | ||
user = await this.getUserParts(userId, sender || userId); | ||
} catch {} // ignore error | ||
if (!membership) { | ||
@@ -618,2 +692,3 @@ return { | ||
avatarUrl: null, | ||
user, | ||
}; | ||
@@ -632,2 +707,3 @@ } | ||
avatarUrl, | ||
user, | ||
}; | ||
@@ -634,0 +710,0 @@ } |
@@ -57,2 +57,3 @@ /* | ||
IRemoteGroup, IPuppetData, GetUserIdsInRoomHook, UserExistsHook, RoomExistsHook, GroupExistsHook, ResolveRoomIdHook, | ||
IEventInfo, | ||
} from "./interfaces"; | ||
@@ -834,2 +835,24 @@ | ||
} | ||
public async getEventInfo( | ||
roomId: string | IRemoteRoom, | ||
eventId: string, | ||
client?: MatrixClient, | ||
): Promise<IEventInfo | null> { | ||
let sender: string | undefined; | ||
if (typeof roomId !== "string") { | ||
const maybeRoomId = await this.roomSync.maybeGetMxid(roomId); | ||
if (!maybeRoomId) { | ||
return null; | ||
} | ||
if (roomId.puppetId !== -1) { | ||
try { | ||
sender = await this.provisioner.getMxid(roomId.puppetId); | ||
} catch {} | ||
} | ||
eventId = (await this.eventSync.getMatrix(roomId, eventId))[0]; | ||
roomId = maybeRoomId; | ||
} | ||
return this.matrixEventHandler.getEventInfo(roomId, eventId, client, sender); | ||
} | ||
} |
@@ -22,4 +22,5 @@ /* | ||
TextualMessageEventContent, FileMessageEventContent, FileWithThumbnailInfo, MatrixClient, DimensionalFileInfo, | ||
VideoFileInfo, TimedFileInfo, | ||
VideoFileInfo, TimedFileInfo, MessageEvent, MessageEventContent, | ||
} from "@sorunome/matrix-bot-sdk"; | ||
import * as escapeHtml from "escape-html"; | ||
@@ -94,5 +95,5 @@ const log = new Log("RemoteEventHandler"); | ||
} | ||
const origEvents = await this.bridge.eventSync.getMatrix(params.room, params.eventId); | ||
for (const origEvent of origEvents) { | ||
await ret.client.sendReadReceipt(ret.mxid, origEvent.split(";")[0]); | ||
const origEventIdIds = await this.bridge.eventSync.getMatrix(params.room, params.eventId); | ||
for (const origEventId of origEventIdIds) { | ||
await ret.client.sendReadReceipt(ret.mxid, origEventId); | ||
} | ||
@@ -175,12 +176,12 @@ } | ||
} | ||
const origEvents = await this.bridge.eventSync.getMatrix(params.room, eventId); | ||
const origEventIdIds = await this.bridge.eventSync.getMatrix(params.room, eventId); | ||
if (ix < 0) { | ||
// negative indexes are from the back | ||
ix = origEvents.length + ix; | ||
ix = origEventIdIds.length + ix; | ||
} | ||
if (ix >= origEvents.length) { | ||
if (ix >= origEventIdIds.length) { | ||
// sanity check on the index | ||
ix = 0; | ||
} | ||
const origEvent = origEvents[ix]; | ||
const origEventId = origEventIdIds[ix]; | ||
// this object is set to any-type as the interfaces don't do edits yet | ||
@@ -196,5 +197,5 @@ const send = { | ||
} as any; // tslint:disable-line no-any | ||
if (origEvent) { | ||
if (origEventId) { | ||
send["m.relates_to"] = { | ||
event_id: origEvent.split(";")[0], | ||
event_id: origEventId, | ||
rel_type: "m.replace", | ||
@@ -229,5 +230,5 @@ }; | ||
const { client, mxid } = await this.prepareSend(params); | ||
const origEvents = await this.bridge.eventSync.getMatrix(params.room, eventId); | ||
for (const origEvent of origEvents) { | ||
await this.bridge.redactEvent(client, mxid, origEvent.split(";")[0]); | ||
const origEventIdIds = await this.bridge.eventSync.getMatrix(params.room, eventId); | ||
for (const origEventId of origEventIdIds) { | ||
await this.bridge.redactEvent(client, mxid, origEventId); | ||
} | ||
@@ -248,4 +249,4 @@ } | ||
} | ||
const origEvents = await this.bridge.eventSync.getMatrix(params.room, eventId); | ||
const origEvent = origEvents[0]; | ||
const origEventIds = await this.bridge.eventSync.getMatrix(params.room, eventId); | ||
const origEventId = origEventIds[0]; | ||
// this send object needs to be any-type, as the interfaces don't do replies yet | ||
@@ -255,17 +256,58 @@ const send = { | ||
body: opts.body, | ||
format: "org.matrix.custom.html", | ||
formatted_body: opts.formattedBody ? opts.formattedBody : escapeHtml(opts.body).replace(/\n/g, "<br>"), | ||
source: this.bridge.protocol.id, | ||
} as any; // tslint:disable-line no-any | ||
if (origEvent) { | ||
if (opts.formattedBody) { | ||
send.format = "org.matrix.custom.html"; | ||
send.formatted_body = opts.formattedBody; | ||
} | ||
if (origEventId) { | ||
send["m.relates_to"] = { | ||
"m.in_reply_to": { | ||
event_id: origEvent.split(";")[0], | ||
event_id: origEventId, | ||
}, | ||
}; | ||
try { | ||
const info = await this.bridge.getEventInfo(mxid, origEventId, client); | ||
if (info) { | ||
if (info.message) { | ||
if (!info.message.formattedBody) { | ||
info.message.formattedBody = escapeHtml(info.message.body).replace(/\n/g, "<br>"); | ||
} | ||
const bodyParts = info.message.body.split("\n"); | ||
bodyParts[0] = `${info.message.emote ? "* " : ""}<${info.user.mxid}> ${bodyParts[0]}`; | ||
send.body = `${bodyParts.map((l) => `> ${l}`).join("\n")}\n\n${send.body}`; | ||
const richHeader = `<mx-reply><blockquote> | ||
<a href="https://matrix.to/#/${mxid}/${origEventId}">In reply to</a> | ||
${info.message.emote ? "* " : ""}<a href="https://matrix.to/#/${info.user.mxid}">${info.user.mxid}</a> | ||
<br>${info.message.formattedBody} | ||
</blockquote></mx-reply>`; | ||
send.formatted_body = richHeader + send.formatted_body; | ||
} else if (info.file) { | ||
let msg = { | ||
image: "an image", | ||
audio: "an audio file", | ||
video: "a video", | ||
sticker: "a sticker", | ||
}[info.file.type]; | ||
if (!msg) { | ||
msg = "a file"; | ||
} | ||
const plainHeader = `> <${info.user.mxid}> sent ${msg}.\n\n`; | ||
send.body = plainHeader + send.body; | ||
const richHeader = `<mx-reply><blockquote> | ||
<a href="https://matrix.to/#/${mxid}/${origEventId}">In reply to</a> | ||
<a href="https://matrix.to/#/${info.user.mxid}">${info.user.mxid}</a> | ||
<br>sent ${msg}. | ||
</blockquote></mx-reply>`; | ||
send.formatted_body = richHeader + send.formatted_body; | ||
} | ||
} | ||
} catch (err) { | ||
log.warn("Failed to add reply fallback", err.error || err.body || err); | ||
} | ||
} else { | ||
log.warn("Couldn't find event, sending as normal message..."); | ||
} | ||
if (opts.formattedBody) { | ||
send.format = "org.matrix.custom.html"; | ||
send.formatted_body = opts.formattedBody; | ||
} | ||
if (params.externalUrl) { | ||
@@ -272,0 +314,0 @@ send.external_url = params.externalUrl; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
790243
20182
14
+ Addedescape-html@^1.0.3