diff --git a/package-lock.json b/package-lock.json index f9293f3..23b84a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -98,9 +98,9 @@ } }, "@sorunome/skype-http": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@sorunome/skype-http/-/skype-http-1.5.1.tgz", - "integrity": "sha512-dJ0fMeTmtzTUWbXN3+SCth8C9XElhrEkzbp454xyo0vXYyoPMbIUYuWeEas1hyeYjaqI9PdgTOe5xI9y+qr9/g==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@sorunome/skype-http/-/skype-http-1.5.2.tgz", + "integrity": "sha512-NAxvVVHIi7G/9v5RbB3vkNqbgy2CB+puE6ZvwX1ha4+PCP5MTy5KZRfrhOpIVyTLmTEXS50USl/ixAek5RwLlA==", "requires": { "async-file": "^2.0.2", "big-integer": "^1.6.26", diff --git a/package.json b/package.json index c894493..c56f640 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ }, "author": "Sorunome", "dependencies": { - "@sorunome/skype-http": "^1.5.1", + "@sorunome/skype-http": "^1.5.2", "cheerio": "^1.0.0-rc.3", "command-line-args": "^5.1.1", "command-line-usage": "^5.0.5", diff --git a/src/index.ts b/src/index.ts index 193b903..2c4b7a5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -62,6 +62,7 @@ const protocol: IProtocolInformation = { audio: true, file: true, edit: true, + reply: true, globalNamespace: true, }, id: "skype", @@ -94,6 +95,7 @@ async function run() { puppet.on("puppetDelete", skype.deletePuppet.bind(skype)); puppet.on("message", skype.handleMatrixMessage.bind(skype)); puppet.on("edit", skype.handleMatrixEdit.bind(skype)); + puppet.on("reply", skype.handleMatrixReply.bind(skype)); puppet.on("redact", skype.handleMatrixRedact.bind(skype)); puppet.on("image", skype.handleMatrixImage.bind(skype)); puppet.on("audio", skype.handleMatrixAudio.bind(skype)); diff --git a/src/matrixmessageparser.ts b/src/matrixmessageparser.ts index 5a1be43..07e57c8 100644 --- a/src/matrixmessageparser.ts +++ b/src/matrixmessageparser.ts @@ -52,8 +52,12 @@ export class MatrixMessageParser { const inner = this.walkChildNodes(nodeHtml); return `${inner}`; } + case "blockquote": + return `${this.walkChildNodes(nodeHtml)}`; case "wrap": return this.walkChildNodes(nodeHtml); + case "mx-reply": // disgard replies + return ""; default: if (!nodeHtml.tagName) { return this.walkChildNodes(nodeHtml); diff --git a/src/skype.ts b/src/skype.ts index 5e52a8b..73fb03c 100644 --- a/src/skype.ts +++ b/src/skype.ts @@ -409,6 +409,63 @@ export class Skype { } } + public async handleMatrixReply(room: IRemoteRoom, eventId: string, data: IMessageEvent) { + const p = this.puppets[room.puppetId]; + if (!p) { + return; + } + log.info("Received reply from matrix"); + const conversation = await p.client.getConversation(room); + if (!conversation) { + log.warn(`Room ${room.roomId} not found!`); + return; + } + let msg: string; + if (data.formattedBody) { + msg = this.matrixMessageParser.parse(data.formattedBody); + } else { + msg = escapeHtml(data.body); + } + // now prepend the reply + const author = escapeHtml(p.client.username.substr(p.client.username.indexOf(":") + 1)); + const ownContact = await p.client.getContact(p.client.username); + const authorname = escapeHtml(ownContact ? ownContact.displayName : p.client.username); + const conversationId = escapeHtml(conversation.id); + const timestamp = Math.round(Number(eventId) / 1000).toString(); + const origEventId = (await this.puppet.eventSync.getMatrix(room.puppetId, eventId))[0]; + let contents = "blah"; + if (origEventId) { + const [realOrigEventId, roomId] = origEventId.split(";"); + try { + const client = (await this.puppet.roomSync.getRoomOp(roomId)) || this.puppet.botIntent.underlyingClient; + const evt = await client.getEvent(roomId, realOrigEventId); + if (evt && evt.content && typeof evt.content.body === "string") { + if (evt.content.formatted_body) { + contents = this.matrixMessageParser.parse(evt.content.formatted_body); + } else { + contents = escapeHtml(evt.content.body); + } + } + } catch (err) { + log.verbose("Event not found", err.body || err); + } + } + const quote = `` + + `[${timestamp}] ${authorname}: ${contents} + +<<< `; + msg = quote + msg; + const dedupeKey = `${room.puppetId};${room.roomId}`; + this.messageDeduplicator.lock(dedupeKey, p.client.username, msg); + const ret = await p.client.sendMessage(conversation.id, msg); + const newEventId = ret && ret.MessageId; + this.messageDeduplicator.unlock(dedupeKey, p.client.username, newEventId); + if (newEventId) { + await this.puppet.eventSync.insert(room.puppetId, data.eventId!, newEventId); + } + } + public async handleMatrixRedact(room: IRemoteRoom, eventId: string) { const p = this.puppets[room.puppetId]; if (!p) { @@ -514,6 +571,17 @@ export class Skype { log.silly("normal message dedupe"); return; } + if (rich && msg.trim().startsWith("${msg}`, { lowerCaseTagName: true, pre: true, }); - return this.walkNode(nodes); + return this.walkNode(nodes, opts); } - private walkChildNodes(node: Parser.Node): IMessageEvent { + private walkChildNodes(node: Parser.Node, opts: ISkypeMessageParserOpts): IMessageEvent { if (!node.childNodes.length) { return { body: "", formattedBody: "", }; } - return node.childNodes.map((n) => this.walkNode(n)).reduce((acc, curr) => { + return node.childNodes.map((n) => this.walkNode(n, opts)).reduce((acc, curr) => { return { body: acc.body + curr.body, formattedBody: acc.formattedBody! + curr.formattedBody!, @@ -48,35 +55,35 @@ export class SkypeMessageParser { }; } - private walkNode(node: Parser.Node): IMessageEvent { + private walkNode(node: Parser.Node, opts: ISkypeMessageParserOpts): IMessageEvent { if (node.nodeType === Parser.NodeType.TEXT_NODE) { return this.escape((node as Parser.TextNode).text); } else if (node.nodeType === Parser.NodeType.ELEMENT_NODE) { const nodeHtml = node as Parser.HTMLElement; switch (nodeHtml.tagName) { case "i": { - const child = this.walkChildNodes(nodeHtml); + const child = this.walkChildNodes(nodeHtml, opts); return { body: `_${child.body}_`, formattedBody: `${child.formattedBody}`, }; } case "b": { - const child = this.walkChildNodes(nodeHtml); + const child = this.walkChildNodes(nodeHtml, opts); return { body: `*${child.body}*`, formattedBody: `${child.formattedBody}`, }; } case "s": { - const child = this.walkChildNodes(nodeHtml); + const child = this.walkChildNodes(nodeHtml, opts); return { body: `~${child.body}~`, formattedBody: `${child.formattedBody}`, }; } case "pre": { - const child = this.walkChildNodes(nodeHtml); + const child = this.walkChildNodes(nodeHtml, opts); return { body: `{code}${child.body}{code}`, formattedBody: `${child.formattedBody}`, @@ -84,12 +91,25 @@ export class SkypeMessageParser { } case "a": { const href = nodeHtml.attributes.href; - const child = this.walkChildNodes(nodeHtml); + const child = this.walkChildNodes(nodeHtml, opts); return { body: child.body === href ? href : `[${child.body}](${href})`, formattedBody: `${child.formattedBody}`, }; } + case "quote": { + if (opts.noQuotes) { + return { + body: "", + formattedBody: "", + }; + } + const child = this.walkChildNodes(nodeHtml, opts); + return { + body: `> ${child.body}\n`, + formattedBody: `
${child.formattedBody}
- ${nodeHtml.attributes.authorname}
`, + }; + } case "ss": { // skype emoji const type = nodeHtml.attributes.type; @@ -164,14 +184,14 @@ export class SkypeMessageParser { formattedBody: `(${escapeHtml(type)})`, }; } - case "e_m": - // empty edit tag + case "e_m": // empty edit tag + case "legacyquote": // empty legacy quote tag return { body: "", formattedBody: "", }; default: - return this.walkChildNodes(nodeHtml); + return this.walkChildNodes(nodeHtml, opts); } } return {