implement quotes

This commit is contained in:
Sorunome 2020-04-25 11:38:50 +02:00
parent 9054d7a18f
commit 3f161b27cb
No known key found for this signature in database
GPG Key ID: B19471D07FC9BE9C
6 changed files with 112 additions and 18 deletions

6
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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));

View File

@ -52,8 +52,12 @@ export class MatrixMessageParser {
const inner = this.walkChildNodes(nodeHtml);
return `<a href="${escapeHtml(href)}">${inner}</a>`;
}
case "blockquote":
return `<quote>${this.walkChildNodes(nodeHtml)}</quote>`;
case "wrap":
return this.walkChildNodes(nodeHtml);
case "mx-reply": // disgard replies
return "";
default:
if (!nodeHtml.tagName) {
return this.walkChildNodes(nodeHtml);

View File

@ -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 = `<quote author="${author}" authorname="${authorname}" timestamp="${timestamp}" ` +
`conversation="${conversationId}" messageid="${escapeHtml(eventId)}">` +
`<legacyquote>[${timestamp}] ${authorname}: </legacyquote>${contents}<legacyquote>
&lt;&lt;&lt; </legacyquote></quote>`;
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("<quote")) {
// okay, we might have a reply...
const $ = cheerio.load(msg);
const quote = $("quote");
const messageid = quote.attr("messageid");
if (messageid) {
const sendQuoteMsg = this.skypeMessageParser.parse(msg, { noQuotes: true });
await this.puppet.sendReply(params, messageid, sendQuoteMsg);
return;
}
}
let sendMsg: IMessageEvent;
if (rich) {
sendMsg = this.skypeMessageParser.parse(msg);
@ -557,7 +625,7 @@ export class Skype {
}
let sendMsg: IMessageEvent;
if (rich) {
sendMsg = this.skypeMessageParser.parse(msg);
sendMsg = this.skypeMessageParser.parse(msg, { noQuotes: msg.trim().startsWith("<quote") });
} else {
sendMsg = {
body: msg,

View File

@ -17,23 +17,30 @@ import * as escapeHtml from "escape-html";
import { IMessageEvent } from "mx-puppet-bridge";
import * as emoji from "node-emoji";
interface ISkypeMessageParserOpts {
noQuotes?: boolean;
}
export class SkypeMessageParser {
public parse(msg: string): IMessageEvent {
public parse(msg: string, opts: ISkypeMessageParserOpts = {}): IMessageEvent {
opts = Object.assign({
noQuotes: false,
}, opts);
const nodes = Parser.parse(`<wrap>${msg}</wrap>`, {
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: `<em>${child.formattedBody}</em>`,
};
}
case "b": {
const child = this.walkChildNodes(nodeHtml);
const child = this.walkChildNodes(nodeHtml, opts);
return {
body: `*${child.body}*`,
formattedBody: `<strong>${child.formattedBody}</strong>`,
};
}
case "s": {
const child = this.walkChildNodes(nodeHtml);
const child = this.walkChildNodes(nodeHtml, opts);
return {
body: `~${child.body}~`,
formattedBody: `<del>${child.formattedBody}</del>`,
};
}
case "pre": {
const child = this.walkChildNodes(nodeHtml);
const child = this.walkChildNodes(nodeHtml, opts);
return {
body: `{code}${child.body}{code}`,
formattedBody: `<code>${child.formattedBody}</code>`,
@ -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: `<a href="${escapeHtml(href)}">${child.formattedBody}</a>`,
};
}
case "quote": {
if (opts.noQuotes) {
return {
body: "",
formattedBody: "",
};
}
const child = this.walkChildNodes(nodeHtml, opts);
return {
body: `> ${child.body}\n`,
formattedBody: `<blockquote>${child.formattedBody}<br> - ${nodeHtml.attributes.authorname}</blockquote>`,
};
}
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 {