You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

343 lines
11 KiB
JavaScript

/*
* Copyright 2020 Netsyms Technologies.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
console.log("Starting up...");
import sdk from 'matrix-js-sdk';
import fs from 'fs';
import log4js from 'log4js';
import https from 'https';
import { fileURLToPath }
from 'url';
import { dirname }
from 'path';
import request from 'request';
import FileType from 'file-type';
// Load settings from config.json
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
let rawdata = fs.readFileSync(__dirname + '/config.json');
let settings = JSON.parse(rawdata);
console.log(__dirname + "/config.json loaded.");
var logger = log4js.getLogger();
logger.level = settings.loglevel;
logger.info("Log initialized.");
var initialsynccomplete = false;
function checkSMS() {
logger.debug("Checking SMS");
const options = {
hostname: 'smsapi.voxtelesys.net',
port: 443,
path: '/api/v1/msgs/inbound?unread_only=true',
method: 'GET',
headers: {
"Authorization": "Bearer " + settings.smsapikey
}
};
const req = https.request(options, res => {
res.on('data', d => {
try {
var json = JSON.parse(d);
var messages = json.results;
if (messages.length == 0) {
logger.debug("No new SMS messages.");
}
for (var i = 0; i < messages.length; i++) {
var msg = messages[i];
if (settings.smsonlyto.length > 0 && settings.smsonlyto.indexOf(msg.to) != -1) {
logger.info("Received SMS from " + msg.from + " for " + msg.to + ": " + msg.body);
createOrJoinSMSRoom(msg.from, function (roomid) {
sendMatrix(roomid, msg.body, msg.media);
});
} else {
logger.info("Received SMS from " + msg.from + " for " + msg.to + ", ignoring based on smsonlyto list.");
}
}
} catch (ex) {
logger.error(ex);
}
});
});
req.on('error', error => {
logger.warning(error);
});
req.end();
}
/**
* Join or create+join a room with alias #SMS_{tel}. If already joined, do nothing.
* @param {string} tel
* @param {function} callback function with the room ID as an argument.
* @returns {undefined}
*/
function createOrJoinSMSRoom(tel, callback) {
logger.debug("Checking if room #SMS_" + tel + ":" + settings.matrixdomain + " exists.");
client.getRoomIdForAlias("#SMS_" + tel + ":" + settings.matrixdomain).then((res) => {
logger.debug("Room #SMS_" + tel + ":" + settings.matrixdomain + " exists!");
var inRoom = false;
var rooms = client.getRooms();
for (var i = 0; i < rooms.length; i++) {
if (rooms[i].roomId == res.room_id) {
inRoom = true;
break;
}
}
if (inRoom) {
// we're already in the room, do nothing
logger.debug("Room #SMS_" + tel + ":" + settings.matrixdomain + " already joined.");
callback(res.room_id);
} else {
// not in the room, join it
client.joinRoom(res.room_id).then((room) => {
logger.debug("Room #SMS_" + tel + ":" + settings.matrixdomain + " joined.");
callback(room.room_id);
});
}
}).catch((err) => {
// room doesn't exist, create it
//logger.debug(err);
logger.debug("Room #SMS_" + tel + ":" + settings.matrixdomain + " does not exist. Creating it now.");
client.createRoom({
room_alias_name: "SMS_" + tel,
visibility: "private",
invite: settings.inviteusers,
name: "SMS: " + tel,
topic: "SMS conversation with " + tel
}).then((room) => {
logger.debug("Room #SMS_" + tel + ":" + settings.matrixdomain + " created with ID " + room.room_id);
// The first message or two we send doesn't go through unless we do this.
// It just spits out "Error sending event M_FORBIDDEN: Unknown room" instead.
createOrJoinSMSRoom(tel, callback);
});
});
}
function getAndUploadFile(url, callback) {
logger.info("Downloading MMS media " + url);
// download
request({url, encoding: null}, (err, resp, buffer) => {
FileType.fromBuffer(buffer).then(function (mimeobj) {
logger.debug(mimeobj);
// upload
logger.info("Uploading MMS media to Matrix " + url);
client.uploadContent(buffer, {
onlyContentUri: true,
rawResponse: false,
type: mimeobj.mime
}).then((res) => {
if (typeof callback == "function") {
callback(res, mimeobj.mime);
logger.info("Media URI: " + res);
}
}).catch((err) => {
if (typeof callback == "function") {
callback(false);
}
if (err.data.error == "Unknown room") {
return;
}
logger.error(err);
});
});
});
}
/**
* Send a message to a Matrix room.
* @param {string} roomid the room to post the message in.
* @param {string} body message content.
* @param {array} media Array of media URLs to download via HTTP(s) and send to Matrix.
* @param {function|undefined} callback passes true when successful, false on failure.
* @returns {undefined}
*/
function sendMatrix(roomid, body, media, callback) {
if (Array.isArray(media)) {
for (var i = 0; i < media.length; i++) {
getAndUploadFile(media[i], function (uri, mimetype) {
if (mimetype == "image/jpg" || mimetype == "image/jpeg" || mimetype == "image/png" || mimetype == "image/gif") {
var content = {
body: "Image",
msgtype: "m.image",
url: uri,
info: {
mimetype: mimetype
}
};
} else {
var content = {
body: "File",
msgtype: "m.file",
url: uri,
info: {
mimetype: mimetype
}
};
}
client.sendEvent(roomid, "m.room.message", content, "").then((res) => {
}).catch((err) => {
if (err.data.error == "Unknown room") {
return;
}
logger.error(err);
});
});
}
}
if (body != "") {
var content = {
body: body,
msgtype: "m.text"
};
client.sendEvent(roomid, "m.room.message", content, "").then((res) => {
if (typeof callback == "function") {
callback(true);
}
}).catch((err) => {
if (typeof callback == "function") {
callback(false);
}
if (err.data.error == "Unknown room") {
return;
}
logger.error(err);
});
} else {
callback(true);
}
}
function sendMatrixNotice(roomid, body, callback) {
var content = {
body: body,
msgtype: "m.notice"
};
client.sendEvent(roomid, "m.room.message", content, "").then((res) => {
if (typeof callback == "function") {
callback(true);
}
}).catch((err) => {
if (typeof callback == "function") {
callback(false);
}
if (err.data.error == "Unknown room") {
return;
}
logger.error(err);
});
}
/**
* Send a SMS to a phone number.
* @param {string} number
* @param {string} body message content.
* @param {function|undefined} callback passes true when successful, false on failure.
* @returns {undefined}
*/
function sendSMS(number, body, callback) {
logger.info("Sending SMS to " + number);
const data = JSON.stringify({
to: [number],
from: settings.smsfrom,
body: body
});
const options = {
hostname: 'smsapi.voxtelesys.net',
port: 443,
path: '/api/v1/sms',
method: 'POST',
headers: {
"Authorization": "Bearer " + settings.smsapikey,
"Content-Type": "application/json",
"Accept": "application/json"
}
}
const req = https.request(options, res => {
res.on('data', d => {
logger.debug(d.toString('utf8'));
});
});
req.on('error', error => {
logger.error(error);
callback(false);
});
req.write(data);
req.end();
if (typeof callback == "function") {
callback(true);
}
}
const client = sdk.createClient(settings.homeserver);
client.login("m.login.password", {"user": settings.matrixuser, "password": settings.matrixpass}).then((response) => {
client.startClient();
logger.info("Plugged into the matrix.");
client.once('sync', function (state, prevState, res) {
logger.debug("Initial sync complete (" + state + ")");
initialsynccomplete = true;
logger.info("Up and running.");
setInterval(checkSMS, settings.smsinterval * 1000);
checkSMS();
});
client.on("Room.timeline", function (event, room) {
if (!initialsynccomplete) {
return; // ignore anything while we were offline
}
if (event.getType() !== "m.room.message") {
return; // only use messages
}
if (client.getUserId() == event.getSender()) {
return; // skip own messages to prevent loop
}
if (event.getContent().body.startsWith("!sms")) {
// capture command to start room for new number
const matches = event.getContent().body.match(/([1-9]?[0-9]{10})/g);
if (matches.length == 1) {
var tel = matches[0];
if (tel.length == 10) {
// make it the full number
tel = "1" + tel;
}
logger.info("Got request to start new SMS conversation with " + tel + " from " + event.getSender() + ".");
sendMatrixNotice(event.getRoomId(), "Starting new conversation with " + tel);
createOrJoinSMSRoom(tel, function () {
return;
});
}
return;
}
//sendMatrix(room.roomId, "echo " + event.event.content.body);
const matches = room.name.match(/([1-9][0-9]+)/g);
if (matches.length == 1) {
var tel = matches[0];
logger.info("Got message for " + tel + " from " + event.getSender() + ", relaying.");
sendSMS(tel, event.getContent().body, function () {
client.sendReadReceipt(event, {});
});
}
});
});