const sdkPromise = import("matrix-js-sdk"); module.exports = function(RED) { function MatrixSendImage(n) { RED.nodes.createNode(this, n); var node = this; this.name = n.name; this.server = RED.nodes.getNode(n.server); this.roomId = n.roomId; this.messageType = n.messageType; this.messageFormat = n.messageFormat; this.replaceMessage = n.replaceMessage; this.message = n.message; this.threadReplyType = n.threadReplyType || null; this.threadReplyValue = n.threadReplyValue || null; // taken from https://github.com/matrix-org/synapse/blob/master/synapse/push/mailer.py this.allowedTags = [ "font", // custom to matrix for IRC-style font coloring "del", // for markdown // deliberately no h1/h2 to stop people shouting. "h3", "h4", "h5", "h6", "blockquote", "p", "a", "ul", "ol", "nl", "li", "b", "i", "u", "strong", "em", "strike", "code", "hr", "br", "div", "table", "thead", "caption", "tbody", "tr", "th", "td", "pre", ]; if (!node.server) { node.warn("No configuration node"); return; } node.server.register(node); node.status({ fill: "red", shape: "ring", text: "disconnected" }); node.server.on("disconnected", function(){ node.status({ fill: "red", shape: "ring", text: "disconnected" }); }); node.server.on("connected", function() { node.status({ fill: "green", shape: "ring", text: "connected" }); }); // Make the input handler async so we can await the dynamic import. node.on("input", async function (msg) { // Await the SDK import and get the RelationType constant. const sdk = await sdkPromise; const RelationType = sdk.RelationType; function getToValue(msg, type, property) { let value = property; if (type === "msg") { value = RED.util.getMessageProperty(msg, property); } else if (type === 'flow' || type === 'global') { try { value = RED.util.evaluateNodeProperty(property, type, node, msg); } catch(e2) { throw new Error("Invalid value evaluation"); } } else if(type === "bool") { value = (property === 'true'); } else if(type === "num") { value = Number(property); } return value; } let msgType = node.messageType, msgFormat = node.messageFormat, threadReply = getToValue(msg, node.threadReplyType, node.threadReplyValue); if (!node.server || !node.server.matrixClient) { node.warn("No matrix server selected"); return; } if(!node.server.isConnected()) { node.error("Matrix server connection is currently closed", msg); node.send([null, msg]); return; } msg.topic = node.roomId || msg.topic; if(!msg.topic) { node.warn("Room must be specified in msg.topic or in configuration"); return; } let payload = n.message || msg.payload; if(!payload) { node.error('msg.payload must be defined or the message configured on the node.', msg); return; } let content = null; if (typeof payload === 'object') { content = payload; } else { if (msgType === 'msg.type') { if (!msg.type) { node.error("msg.type is set to be passed in via msg.type but was not defined", msg); return; } msgType = msg.type; } if (msgFormat === 'msg.format') { if (!Object.hasOwn(msg, 'format')) { node.error("Message format is set to be passed in via msg.format but was not defined", msg); return; } msgFormat = msg.format; } content = { msgtype: msgType, body: payload.toString() }; if (msgFormat === 'html') { content.format = "org.matrix.custom.html"; content.formatted_body = (typeof msg.formatted_payload !== 'undefined' && msg.formatted_payload) ? msg.formatted_payload.toString() : payload.toString(); } if ((node.replaceMessage || msg.replace) && msg.eventId) { content['m.new_content'] = { msgtype: content.msgtype, body: content.body }; if ('format' in content) { content['m.new_content']['format'] = content['format']; } if ('formatted_body' in content) { content['m.new_content']['formatted_body'] = content['formatted_body']; } content['m.relates_to'] = { rel_type: RelationType.Replace, event_id: msg.eventId }; content['body'] = ' * ' + content['body']; } else if (threadReply) { // If incoming message is a reply to a thread we fetch the thread parent from m.relates_to, // otherwise fallback to msg.eventId. let threadParent = (msg?.content?.['m.relates_to']?.rel_type === RelationType.Thread ? msg?.content?.['m.relates_to']?.event_id : null) || msg.eventId; if (threadParent) { content["m.relates_to"] = { "rel_type": RelationType.Thread, "event_id": threadParent, "is_falling_back": true, }; if (msg.eventId !== threadParent) { content["m.relates_to"]["m.in_reply_to"] = { "event_id": msg.eventId }; } } } } node.server.matrixClient.sendMessage(msg.topic, content) .then(function(e) { node.log("Message sent: " + payload); msg.eventId = e.event_id; node.log(JSON.stringify(e)); node.send([msg, null]); }) .catch(function(e){ node.error("Error sending message: " + e, {}); msg.error = e; node.send([null, msg]); }); }); node.on("close", function() { node.server.deregister(node); }); } RED.nodes.registerType("matrix-send-message", MatrixSendImage); };