node-red-contrib-matrixbot/99-matrixbot.js
2020-11-09 06:55:12 +11:00

394 lines
14 KiB
JavaScript

module.exports = function (RED) {
"use strict";
var sdk = require("matrix-js-sdk");
var md = require("markdown-it")();
const fs = require('fs');
// --------------------------------------------------------------------------------------------
// The configuration node holds the configuration and credentials for all nodes.
function MatrixBotNode(config) {
RED.nodes.createNode(this, config);
// copy "this" object in case we need it in context of callbacks of other functions.
var node = this;
node.log("Initializing Matrix Bot node");
// Configuration options passed by Node Red
node.userId = config.userId;
node.room = config.room;
// TODO: Switch from configuration to credentials and check with if (this.credentials)
node.accessToken = config.accessToken;
node.matrixServerURL = config.matrixServerURL;
node.matrixClient = sdk.createClient({
baseUrl: node.matrixServerURL,
accessToken: node.accessToken,
userId: node.userId
});
// If no room is specified, join any room where we are invited
if (!node.room || node.room === "") {
node.matrixClient.on("RoomMember.membership", function (event, member) {
if (member.membership === "invite" && member.userId === node.userId) {
node.log("Trying to join room " + member.roomId);
node.matrixClient.joinRoom(member.roomId).then(function () {
node.log("Automatically accepted invitation to join room " + member.roomId);
}).catch(function (e) {
node.warn("Cannot join room (probably because I was kicked) " + member.roomId + ": " + e);
});
}
});
}
node.matrixClient.on("sync", function (state, prevState, data) {
switch (state) {
case "ERROR":
// update UI to say "Connection Lost"
node.warn("Connection to Matrix server lost");
node.updateConnectionState(false);
break;
case "SYNCING":
// update UI to remove any "Connection Lost" message
node.updateConnectionState(true);
break;
case "PREPARED":
// the client instance is ready to be queried.
node.log("Synchronized to Matrix server.");
if (node.room) {
node.log("Trying to join room " + node.room);
node.matrixClient.joinRoom(node.room, { syncRoom: false })
.then(function (joinedRoom) {
node.log("Joined " + node.room);
node.room = joinedRoom.roomId;
node.updateConnectionState(true);
}).catch(function (e) {
node.warn("Error joining " + node.room + ": " + e);
});
} else {
node.log("No room configured. Will only join rooms where I'm invited");
}
break;
}
});
node.log("Connecting to Matrix server...");
node.matrixClient.startClient();
// Called when the connection state may have changed
this.updateConnectionState = function (connected) {
if (node.connected !== connected) {
node.connected = connected;
if (connected) {
node.emit("connected");
} else {
node.emit("disconnected");
}
}
};
// When Node-RED updates nodes, disconnect from server to ensure a clean start
node.on("close", function (done) {
node.log("Matrix configuration node closing...");
if (node.matrixClient) {
node.log("Disconnecting from Matrix server...");
node.matrixClient.stopClient();
node.updateConnectionState(false);
}
done();
});
}
RED.nodes.registerType("matrix bot", MatrixBotNode);
// --------------------------------------------------------------------------------------------
// The output node sends a message to the chat.
function MatrixOutNode(config) {
RED.nodes.createNode(this, config);
// copy "this" object in case we need it in context of callbacks of other functions.
var node = this;
// Configuration options passed by Node Red
node.configNode = RED.nodes.getNode(config.bot);
node.configNode.on("connected", function () {
node.status({ fill: "green", shape: "ring", text: "connected" });
});
node.configNode.on("disconnected", function () {
node.status({ fill: "red", shape: "ring", text: "disconnected" });
});
this.on("input", function (msg) {
if (!node.configNode || !node.configNode.matrixClient) {
node.warn("No configuration");
return;
}
if (msg.payload) {
node.log("Sending message " + msg.payload);
var destRoom = "";
if (msg.roomId) {
destRoom = msg.roomId;
} else if (node.configNode.room) {
destRoom = node.configNode.room;
} else {
node.warn("Room must be specified in msg.roomId or in configuration");
return;
}
node.configNode.matrixClient.sendHtmlMessage(destRoom, msg.payload.toString(), md.render(msg.payload.toString()))
.then(function () {
node.log("Message sent: " + msg.payload);
}).catch(function (e) {
node.warn("Error sending message " + e);
});
} else {
node.warn("msg.payload is empty");
}
});
this.on("close", function (done) {
node.log("Matrix out node closing...");
done();
});
}
RED.nodes.registerType("matrix sender", MatrixOutNode);
// --------------------------------------------------------------------------------------------
// Tim: File Send node
function MatrixFileOutNode(config) {
RED.nodes.createNode(this, config);
const node = this;
node.configNode = RED.nodes.getNode(config.bot);
node.configNode.on('connected', () => {
node.status({ fill: 'green', shape: 'ring', text: 'connected' });
});
node.configNode.on('disconnected', () => {
node.status({ fill: 'red', shape: 'ring', text: 'disconnected' });
});
this.on('input', msg => {
if (!node.configNode || !node.configNode.matrixClient) {
node.warn('No configuration');
return;
}
if (msg.payload) {
node.log('Sending message ' + msg.payload);
let destRoom = '';
if (msg.roomId) {
destRoom = msg.roomId;
}
else if (node.configNode.room) {
destRoom = node.configNode.room;
}
else {
node.warn('Room must be specified in msg.roomId or in config');
return;
}
const filePath = msg.payload;
fs.readFile(filePath, (err, data) => {
if (err) {
node.warn('Error reading file', err);
return;
}
const tks = filePath.split('/');
const fname = tks[tks.length - 1];
const opts = {
rawResponse: false,
name: fname
}
node.configNode.matrixClient.uploadContent(data, opts).then(res => {
node.log('success uploading file', res);
console.log('Uploaded file', res);
const imageUri = res.content_uri;
console.log('imageUri', imageUri);
node.configNode.matrixClient.sendImageMessage(destRoom, imageUri, {}, 'image.png', () => {
console.log('image send func');
}).then(() => {
node.log('image sent success');
console.log('image sent success')
}).catch(e => {
node.warn('error sending img: ', e);
console.log('error sending image', e)
})
}).catch(err => {
node.warn('Error uploading file', err);
})
});
}
else {
node.warn('msg.payload empty')
}
});
this.on('close', done => {
node.log('matrix file out node closing...');
done();
});
}
RED.nodes.registerType("matrix file sender", MatrixFileOutNode);
// --------------------------------------------------------------------------------------------
// The input node receives messages from the chat.
function MatrixInNode(config) {
RED.nodes.createNode(this, config);
// copy "this" object in case we need it in context of callbacks of other functions.
var node = this;
node.configNode = RED.nodes.getNode(config.bot);
node.log("MatrixInNode initializing...");
if (!node.configNode) {
node.warn("No configuration node");
return;
}
node.status({ fill: "red", shape: "ring", text: "disconnected" });
node.configNode.on("disconnected", function () {
node.status({ fill: "red", shape: "ring", text: "disconnected" });
});
node.configNode.on("connected", function () {
node.status({ fill: "green", shape: "ring", text: "connected" });
node.configNode.matrixClient.on("Room.timeline", function (event, room, toStartOfTimeline, data) {
if (toStartOfTimeline) {
return; // don't print paginated results
}
if (event.getType() !== "m.room.message") {
return; // only keep messages
}
if (!event.getSender() || event.getSender() === node.configNode.userId) {
return; // ignore our own messages
}
if (!event.getUnsigned() || event.getUnsigned().age > 1000) {
return; // ignore old messages
}
// TODO process messages other than text
node.log(
// the room name will update with m.room.name events automatically
"Received chat message: (" + room.name + ") " + event.getSender() + " :: " + event.getContent().body
);
var msg = {
payload: event.getContent().body,
sender: event.getSender(),
roomId: room.roomId
};
node.send(msg);
});
});
this.on("close", function (done) {
node.log("Matrix in node closing...");
done();
});
}
RED.nodes.registerType("matrix receiver", MatrixInNode);
// --------------------------------------------------------------------------------------------
// The command node receives messages from the chat.
function MatrixCommandNode(config) {
RED.nodes.createNode(this, config);
// copy "this" object in case we need it in context of callbacks of other functions.
var node = this;
node.command = config.command;
node.configNode = RED.nodes.getNode(config.bot);
node.log("MatrixCommandNode initializing...");
if (!node.configNode) {
node.warn("No configuration node");
return;
}
node.status({ fill: "red", shape: "ring", text: "disconnected" });
node.configNode.on("disconnected", function () {
node.status({ fill: "red", shape: "ring", text: "disconnected" });
});
node.configNode.on("connected", function () {
node.status({ fill: "green", shape: "ring", text: "connected" });
node.configNode.matrixClient.on("Room.timeline", function (event, room, toStartOfTimeline, data) {
if (toStartOfTimeline) {
return; // don't print paginated results
}
if (event.getType() !== "m.room.message") {
return; // only keep messages
}
if (!event.getSender() || event.getSender() === node.configNode.userId) {
return; // ignore our own messages
}
if (!event.getUnsigned() || event.getUnsigned().age > 1000) {
return; // ignore old messages
}
// TODO process messages other than text
node.log(
// the room name will update with m.room.name events automatically
"Received chat message: (" + room.name + ") " + event.getSender() + " :: " + event.getContent().body
);
var message = event.getContent().body;
var tokens = message.split(" ");
if (tokens[0] == node.command) {
node.log("Recognized command " + node.command + " Processing...");
var remainingText = message.replace(node.command, "");
var msg = {
payload: remainingText,
sender: event.getSender(),
roomId: room.roomId,
originalMessage: message
};
node.send([msg, null]);
}
});
});
this.on("close", function (done) {
node.log("Matrix command node closing...");
done();
});
}
RED.nodes.registerType("matrix command", MatrixCommandNode);
}