2020-11-05 22:39:11 -07:00
/ *
2022-06-09 12:51:49 -06:00
* Copyright 2020 - 2022 Netsyms Technologies .
2020-11-05 22:39:11 -07:00
* 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/.
* /
2021-07-12 21:18:18 -06:00
/ *
* Matrix really , really , really needs better SDK docs .
* /
2020-11-05 22:39:11 -07:00
console . log ( "Starting up..." ) ;
2023-09-05 22:16:44 -06:00
import * as sdk from 'matrix-js-sdk' ;
2023-09-12 19:09:43 -06:00
import matrixcs from 'matrix-js-sdk' ;
2020-11-05 22:39:11 -07:00
import fs from 'fs' ;
import log4js from 'log4js' ;
import https from 'https' ;
2022-02-03 18:10:21 -07:00
import { fileURLToPath } from 'url' ;
import { dirname } from 'path' ;
2021-06-14 17:02:41 -06:00
import request from 'request' ;
2021-06-14 17:22:51 -06:00
import FileType from 'file-type' ;
2022-02-03 19:38:32 -07:00
import express from 'express' ;
import bodyParser from 'body-parser' ;
2024-03-12 18:57:22 -06:00
import fetch from 'node-fetch' ;
2023-09-05 22:16:44 -06:00
2023-09-07 04:08:46 -06:00
// Save script start time for ignoring older messages
var boottimestamp = Date . now ( ) ;
2023-09-12 19:15:18 -06:00
var client ;
2023-09-12 19:09:43 -06:00
var _ _dirname ;
var _ _filename ;
var initialsynccomplete = false ;
// Init logging
var logger = log4js . getLogger ( ) ;
logger . level = "debug" ;
logger . info ( "Log initialized." ) ;
var settings = { } ;
/ * *
* Load settings from config . json
* /
function loadSettingsFile ( ) {
_ _filename = fileURLToPath ( import . meta . url ) ;
_ _dirname = dirname ( _ _filename ) ;
let rawdata = fs . readFileSync ( _ _dirname + '/config.json' ) ;
settings = JSON . parse ( rawdata ) ;
console . log ( _ _dirname + "/config.json loaded." ) ;
logger . level = settings . loglevel ;
}
loadSettingsFile ( ) ;
2023-09-05 22:16:44 -06:00
// https://github.com/matrix-org/matrix-js-sdk/issues/2415#issuecomment-1188812401
matrixcs . request ( request ) ;
2021-06-16 14:56:45 -06:00
/ * *
* From https : //github.com/stevekinney/node-phone-formatter
2021-06-16 15:00:57 -06:00
* @ param { string } phoneNumber
* @ param { string } formatString
* @ returns { string }
2021-06-16 14:56:45 -06:00
* /
function formatPhoneNumber ( phoneNumber , formatString ) {
2021-06-16 15:00:57 -06:00
phoneNumber = phoneNumber . replace (
/^[\+\d{1,3}\-\s]*\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/ ,
"$1$2$3"
) ;
2021-06-16 14:56:45 -06:00
for ( var i = 0 , l = phoneNumber . length ; i < l ; i ++ ) {
formatString = formatString . replace ( "N" , phoneNumber [ i ] ) ;
}
return formatString ;
}
2020-11-05 22:39:11 -07:00
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 : {
2021-06-16 14:42:32 -06:00
"Authorization" : "Bearer " + settings . smsapikey
2020-11-05 22:39:11 -07:00
}
} ;
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 ++ ) {
2022-12-29 22:01:03 -07:00
let msg = messages [ i ] ;
2021-06-16 14:03:52 -06:00
if ( settings . smsonlyto . length > 0 && settings . smsonlyto . indexOf ( msg . to ) != - 1 ) {
logger . info ( "Received SMS from " + msg . from + " for " + msg . to + ": " + msg . body ) ;
2021-06-16 14:56:45 -06:00
createOrJoinSMSRoom ( msg . from , msg . to , function ( roomid ) {
2021-06-16 14:03:52 -06:00
sendMatrix ( roomid , msg . body , msg . media ) ;
} ) ;
} else {
logger . info ( "Received SMS from " + msg . from + " for " + msg . to + ", ignoring based on smsonlyto list." ) ;
}
2020-11-05 22:39:11 -07:00
}
} catch ( ex ) {
logger . error ( ex ) ;
}
} ) ;
} ) ;
req . on ( 'error' , error => {
2023-09-07 04:14:12 -06:00
logger . warn ( error ) ;
2020-11-05 22:39:11 -07:00
} ) ;
req . end ( ) ;
}
2024-03-12 18:57:22 -06:00
/ * *
* Connect to the server and get the room ID and stuff
* @ param { type } alias
* @ returns { undefined }
* /
async function getRoomIDFromAlias ( alias ) {
var info = await client . getRoomIdForAlias ( alias ) ;
if ( typeof info . room _id != "undefined" ) {
return info ;
}
var response = await fetch ( settings . homeserver + "/_matrix/client/v3/directory/room/" + encodeURIComponent ( alias ) ) ;
if ( response . status != 200 ) {
throw new Error ( "Fetch returned invalid status code " + response . status ) ;
}
var json = await response . json ( ) ;
return json ;
}
2020-11-05 22:39:11 -07:00
/ * *
* Join or create + join a room with alias # SMS _ { tel } . If already joined , do nothing .
* @ param { string } tel
2021-06-16 14:56:45 -06:00
* @ param { string } ournumber the phone number Matrix messages will be sent from for this room
2020-11-05 22:39:11 -07:00
* @ param { function } callback function with the room ID as an argument .
* @ returns { undefined }
* /
2024-03-12 18:57:22 -06:00
async function createOrJoinSMSRoom ( tel , ournumber , callback ) {
2021-06-16 14:56:45 -06:00
var roomName = "#SMS_" + tel + "_" + ournumber + ":" + settings . matrixdomain ;
logger . debug ( "Checking if room " + roomName + " exists." ) ;
2024-03-12 18:57:22 -06:00
try {
var res = await getRoomIDFromAlias ( roomName ) ;
2024-03-12 18:58:55 -06:00
logger . debug ( JSON . stringify ( res ) ) ;
2021-06-16 14:56:45 -06:00
logger . debug ( "Room " + roomName + " exists!" ) ;
2020-11-05 22:39:11 -07:00
var inRoom = false ;
2024-03-12 18:57:22 -06:00
if ( ! res . error ) {
var rooms = ( await client . getJoinedRooms ( ) ) . joined _rooms ;
for ( var i = 0 ; i < rooms . length ; i ++ ) {
if ( rooms [ i ] == res . room _id ) {
inRoom = true ;
break ;
}
2020-11-05 22:39:11 -07:00
}
}
if ( inRoom ) {
// we're already in the room, do nothing
2021-06-16 14:56:45 -06:00
logger . debug ( "Room " + roomName + " already joined." ) ;
2021-07-12 21:09:59 -06:00
client . setRoomTag ( res . room _id , "u.matrix-bridge-voxtelesys-sms" , { tel : tel , ournumber : ournumber , order : 0.5 } ) ;
2020-11-05 22:39:11 -07:00
callback ( res . room _id ) ;
} else {
// not in the room, join it
client . joinRoom ( res . room _id ) . then ( ( room ) => {
2021-06-16 14:56:45 -06:00
logger . debug ( "Room " + roomName + " joined." ) ;
2021-07-12 21:09:59 -06:00
client . setRoomTag ( room . room _id , "u.matrix-bridge-voxtelesys-sms" , { tel : tel , ournumber : ournumber , order : 0.5 } ) ;
2024-03-12 18:57:22 -06:00
callback ( res . room _id ) ;
2020-11-05 22:39:11 -07:00
} ) ;
}
2021-07-12 20:30:00 -06:00
return ;
2024-03-12 18:57:22 -06:00
} catch ( err ) {
2020-11-05 22:39:11 -07:00
// room doesn't exist, create it
2021-07-12 20:30:00 -06:00
logger . debug ( err ) ;
2021-06-16 14:56:45 -06:00
logger . debug ( "Room " + roomName + " does not exist. Creating it now." ) ;
2021-07-10 19:51:26 -06:00
var userPowerLevels = { } ;
for ( var i = 0 ; i < settings . inviteusers . length ; i ++ ) {
userPowerLevels [ settings . inviteusers [ i ] ] = 50 ;
}
2023-09-12 19:17:26 -06:00
userPowerLevels [ settings [ "matrixuser" ] ] = 100 ;
2024-03-12 18:57:22 -06:00
try {
var room = await client . createRoom ( {
room _alias _name : "SMS_" + tel + "_" + ournumber ,
preset : "trusted_private_chat" ,
visibility : "private" ,
invite : settings . inviteusers ,
power _level _content _override : {
"events" : {
"m.room.name" : 50 ,
"m.room.power_levels" : 50 ,
"m.room.canonical_alias" : 100
} ,
"events_default" : 0 ,
"invite" : 50 ,
"kick" : 50 ,
"notifications" : {
"room" : 50
} ,
"redact" : 50 ,
"state_default" : 50 ,
"users" : userPowerLevels ,
"users_default" : 50
2021-07-10 19:51:26 -06:00
} ,
2024-03-12 18:57:22 -06:00
is _direct : true ,
name : formatPhoneNumber ( tel , "(NNN) NNN-NNNN" ) ,
topic : "SMS conversation with " + formatPhoneNumber ( tel , "(NNN) NNN-NNNN" ) + " (using " + formatPhoneNumber ( ournumber , "(NNN) NNN-NNNN" ) + ")"
} ) ;
2021-06-16 14:56:45 -06:00
logger . debug ( "Room" + roomName + " created with ID " + room . room _id ) ;
2020-11-05 22:39:11 -07:00
// 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.
2021-06-16 14:56:45 -06:00
createOrJoinSMSRoom ( tel , ournumber , callback ) ;
2024-03-12 18:57:22 -06:00
} catch ( errr ) {
2021-07-12 20:24:22 -06:00
logger . error ( "Could not create " + roomName + "." ) ;
2024-03-12 18:57:22 -06:00
logger . error ( errr ) ;
}
}
2020-11-05 22:39:11 -07:00
}
2021-06-14 17:00:21 -06:00
function getAndUploadFile ( url , callback ) {
logger . info ( "Downloading MMS media " + url ) ;
// download
request ( { url , encoding : null } , ( err , resp , buffer ) => {
2021-06-14 17:29:34 -06:00
FileType . fromBuffer ( buffer ) . then ( function ( mimeobj ) {
logger . debug ( mimeobj ) ;
2023-09-05 21:06:51 -06:00
if ( typeof mimeobj == "undefined" ) {
logger . error ( "Undefined media mimetype, not uploading to Matrix." ) ;
return ;
}
2021-06-14 17:29:34 -06:00
// 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 ) ;
} ) ;
2021-06-14 17:00:21 -06:00
} ) ;
} ) ;
}
2020-11-05 22:39:11 -07:00
/ * *
* Send a message to a Matrix room .
2024-03-12 18:57:22 -06:00
* @ param { string } room the room to post the message in .
2020-11-05 22:39:11 -07:00
* @ param { string } body message content .
2021-06-16 14:35:37 -06:00
* @ param { array } media Array of media URLs to download via HTTP ( s ) and send to Matrix .
2020-11-05 22:39:11 -07:00
* @ returns { undefined }
* /
2024-03-12 18:57:22 -06:00
async function sendMatrix ( room , body , media ) {
var roomid = room ;
if ( room . startsWith ( "#" ) ) {
try {
roomid = ( await getRoomIDFromAlias ( ) ) . room _id ;
logger . info ( "Translated alias " + room + " to room ID " + roomid ) ;
} catch ( err ) {
logger . error ( err ) ;
}
}
2021-06-14 17:22:51 -06:00
if ( Array . isArray ( media ) ) {
2021-06-14 17:00:21 -06:00
for ( var i = 0 ; i < media . length ; i ++ ) {
2024-03-12 18:57:22 -06:00
getAndUploadFile ( media [ i ] , async function ( uri , mimetype ) {
2021-06-15 18:14:18 -06:00
if ( mimetype == "image/jpg" || mimetype == "image/jpeg" || mimetype == "image/png" || mimetype == "image/gif" ) {
2021-06-14 17:22:51 -06:00
var content = {
2021-06-16 14:12:11 -06:00
body : "Image" ,
2021-06-14 17:22:51 -06:00
msgtype : "m.image" ,
url : uri ,
info : {
mimetype : mimetype
}
} ;
} else {
var content = {
2021-06-16 14:12:11 -06:00
body : "File" ,
2021-06-14 17:22:51 -06:00
msgtype : "m.file" ,
url : uri ,
info : {
mimetype : mimetype
}
} ;
2021-06-14 17:00:21 -06:00
}
2024-03-12 18:57:22 -06:00
try {
await client . sendEvent ( roomid , "m.room.message" , content , "" ) ;
return true ;
} catch ( err ) {
2021-06-14 17:00:21 -06:00
if ( err . data . error == "Unknown room" ) {
return ;
}
logger . error ( err ) ;
2024-03-12 18:57:22 -06:00
}
2021-06-14 17:00:21 -06:00
} ) ;
2021-06-14 16:46:19 -06:00
}
}
2020-11-05 22:39:11 -07:00
2021-06-16 14:12:11 -06:00
if ( body != "" ) {
var content = {
body : body ,
msgtype : "m.text"
2021-06-16 14:35:37 -06:00
} ;
2024-03-12 18:57:22 -06:00
try {
await client . sendEvent ( roomid , "m.room.message" , content , "" ) ;
return true ;
} catch ( err ) {
2021-06-16 14:12:11 -06:00
if ( err . data . error == "Unknown room" ) {
2024-03-12 18:57:22 -06:00
return false ;
2021-06-16 14:12:11 -06:00
}
logger . error ( err ) ;
2022-02-03 18:26:42 -07:00
}
2024-03-12 18:57:22 -06:00
} else {
return true ;
2021-06-16 14:12:11 -06:00
}
2020-11-05 22:39:11 -07:00
}
2021-06-16 14:35:37 -06:00
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 ) ;
} ) ;
}
2020-11-05 22:39:11 -07:00
/ * *
* 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 }
* /
2021-06-16 15:35:51 -06:00
function sendSMS ( number , from , body , callback ) {
2022-02-03 18:10:21 -07:00
if ( settings . googleverifiedsms ) {
// Use Google Verified SMS to add business branding to SMS message
}
2021-06-16 16:22:48 -06:00
logger . info ( "Sending SMS to " + number + " from " + from ) ;
2022-02-03 18:10:21 -07:00
var data = {
2020-11-05 22:39:11 -07:00
to : [ number ] ,
2021-06-16 15:35:51 -06:00
from : from ,
2020-11-05 22:39:11 -07:00
body : body
2022-02-03 18:10:21 -07:00
} ;
const jsondata = JSON . stringify ( data ) ;
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 ) ;
2020-11-05 22:39:11 -07:00
} ) ;
2022-02-03 18:10:21 -07:00
req . write ( jsondata ) ;
req . end ( ) ;
if ( typeof callback == "function" ) {
callback ( true ) ;
}
}
/ * *
* 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 sendMMS ( number , from , mediauri , mimetype , callback ) {
logger . info ( "Sending MMS to " + number + " from " + from ) ;
2022-02-03 18:26:42 -07:00
var urichunks = mediauri . split ( "/" ) ; // should result in something like [ "mxc:", "", "matrix.org", "mediaidhere90473473" ]
if ( urichunks . length < 4 ) {
logger . error ( "Invalid media uri" ) ;
2022-02-03 18:28:15 -07:00
if ( typeof callback == "function" ) {
callback ( false ) ;
}
2022-02-03 18:26:42 -07:00
return ;
}
2022-02-03 18:10:21 -07:00
var httpmediaurl = settings . mediaurlpath ;
2022-02-03 18:26:42 -07:00
httpmediaurl = httpmediaurl . replace ( "{{server-name}}" , urichunks [ 2 ] ) ;
httpmediaurl = httpmediaurl . replace ( "{{media-id}}" , urichunks [ 3 ] ) ;
2022-02-03 18:40:02 -07:00
var body = "" ;
var media = [ httpmediaurl ] ;
switch ( mimetype ) {
case "image/jpeg" :
case "image/png" :
case "image/gif" :
case "image/bmp" :
// These are likely to work, so don't include the URL
body = "" ;
break ;
default :
2022-02-03 18:47:45 -07:00
// Send link to content too
2022-02-03 18:40:02 -07:00
body = httpmediaurl ;
2022-02-03 18:47:45 -07:00
//media = [];
2022-02-03 18:40:02 -07:00
}
2022-02-03 18:10:21 -07:00
var data = {
to : [ number ] ,
from : from ,
2022-02-03 18:40:02 -07:00
body : body ,
media : media
2022-02-03 18:10:21 -07:00
} ;
const jsondata = JSON . stringify ( data ) ;
2020-11-05 22:39:11 -07:00
const options = {
hostname : 'smsapi.voxtelesys.net' ,
port : 443 ,
path : '/api/v1/sms' ,
method : 'POST' ,
headers : {
2021-06-16 14:42:32 -06:00
"Authorization" : "Bearer " + settings . smsapikey ,
2020-11-05 22:39:11 -07:00
"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 ) ;
2022-02-03 18:28:15 -07:00
if ( typeof callback == "function" ) {
callback ( false ) ;
}
2020-11-05 22:39:11 -07:00
} ) ;
2022-02-03 18:10:21 -07:00
req . write ( jsondata ) ;
2020-11-05 22:39:11 -07:00
req . end ( ) ;
if ( typeof callback == "function" ) {
callback ( true ) ;
}
}
2022-02-03 19:38:32 -07:00
function handleHTTPRequest ( req , res ) {
2022-02-03 18:17:11 -07:00
try {
2022-02-03 19:38:32 -07:00
logger . info ( "Got HTTP request: " + req . url ) ;
if ( req . url == "/webhook" ) {
2022-02-03 18:17:11 -07:00
try {
2022-02-03 19:38:32 -07:00
var msg = req . body ;
2022-02-03 18:10:21 -07:00
2022-02-03 18:17:11 -07:00
if ( msg . type == "mo" ) {
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 , msg . to , function ( roomid ) {
2024-03-12 18:57:22 -06:00
logger . info ( "Sending to room " + roomid ) ;
2022-02-03 18:17:11 -07:00
sendMatrix ( roomid , msg . body , msg . media ) ;
} ) ;
2022-02-03 19:38:32 -07:00
res . sendStatus ( 204 ) ;
res . end ( ) ;
2022-02-03 18:17:11 -07:00
} else {
logger . info ( "Received SMS from " + msg . from + " for " + msg . to + ", ignoring based on smsonlyto list." ) ;
2022-02-03 19:38:32 -07:00
res . sendStatus ( 403 ) ;
res . end ( ) ;
2022-02-03 18:17:11 -07:00
}
2022-02-03 18:10:21 -07:00
} else {
2022-02-03 19:38:32 -07:00
res . sendStatus ( 403 ) ;
res . end ( ) ;
2022-02-03 18:10:21 -07:00
}
2022-02-03 18:17:11 -07:00
} catch ( ex ) {
logger . error ( "Decoding webhook body: " + ex ) ;
2022-02-03 19:38:32 -07:00
logger . error ( req . body ) ;
res . sendStatus ( 500 ) ;
res . end ( ) ;
2022-02-03 18:10:21 -07:00
}
2022-02-03 18:17:11 -07:00
} else {
try {
2022-02-03 19:38:32 -07:00
res . sendStatus ( 404 ) ;
res . end ( ) ;
2022-02-03 18:17:11 -07:00
} catch ( err ) {
logger . error ( err ) ;
}
2022-02-03 18:10:21 -07:00
}
2022-02-03 18:17:11 -07:00
} catch ( exx ) {
logger . error ( exx ) ;
2022-02-03 18:10:21 -07:00
}
}
2023-09-12 19:09:43 -06:00
// Access token empty
if ( settings . matrixaccesstoken == false ) {
logger . error ( "Matrix access token not set." ) ;
if ( settings . matrixuser == false || settings . matrixpass == false ) {
logger . error ( "Config values for matrixuser and/or matrixpass are not valid." ) ;
process . exit ( 1 ) ;
2023-09-05 22:08:00 -06:00
}
2023-09-12 19:09:43 -06:00
logger . error ( "Attempting to fetch access token for you..." ) ;
2023-09-05 22:08:00 -06:00
try {
2023-09-12 19:09:43 -06:00
request ( {
method : "POST" ,
uri : settings . homeserver + "/_matrix/client/v3/login" ,
json : {
type : "m.login.password" ,
user : settings . matrixuser ,
password : settings . matrixpass
}
} , function ( error , resp , body ) {
if ( ! error && resp . statusCode == 200 ) {
if ( body . access _token ) {
logger . error ( "Got access token for you. Writing it to " + _ _dirname + "/config.json now." ) ;
settings . matrixaccesstoken = body . access _token ;
fs . writeFileSync ( _ _dirname + '/config.json' , JSON . stringify ( settings , null , 4 ) ) ;
logger . error ( "Exiting. Please restart me to load new config file." ) ;
process . exit ( ) ;
}
} else {
logger . error ( "Couldn't get access token. Get it yourself: curl -XPOST -d '{\"type\":\"m.login.password\", \"user\":\"" + settings . matrixuser + "\", \"password\":\"password here\"}' \"https://matrix.netsyms.net/_matrix/client/v3/login\"" ) ;
}
} ) ;
} catch ( ex ) {
logger . error ( "Couldn't get access token. Get it yourself: curl -XPOST -d '{\"type\":\"m.login.password\", \"user\":\"" + settings . matrixuser + "\", \"password\":\"password here\"}' \"https://matrix.netsyms.net/_matrix/client/v3/login\"" ) ;
}
} else {
2023-09-12 19:15:18 -06:00
client = sdk . createClient ( { baseUrl : settings . homeserver , userId : settings . matrixuser , accessToken : settings . matrixaccesstoken } ) ;
2023-09-12 19:09:43 -06:00
var httpserver = express ( ) ;
var jsonParser = bodyParser . json ( ) ;
client . startClient ( { initialSyncLimit : 10 } ) ;
logger . info ( "Plugged into the matrix." ) ;
2024-03-12 18:57:22 -06:00
client . once ( "sync" , function ( state , prevState , res ) {
2023-09-12 19:09:43 -06:00
logger . debug ( "Initial sync complete (" + state + ")" ) ;
initialsynccomplete = true ;
httpserver . post ( "*" , jsonParser , ( req , res ) => {
handleHTTPRequest ( req , res ) ;
} ) ;
httpserver . get ( "*" , ( req , res ) => {
handleHTTPRequest ( req , res ) ;
} ) ;
httpserver . listen ( settings . listenport , ( ) => {
logger . info ( "HTTP server listening on port " + settings . listenport ) ;
} ) ;
logger . info ( "Up and running." ) ;
if ( settings . smsinterval > 0 ) {
setInterval ( checkSMS , settings . smsinterval * 1000 ) ;
checkSMS ( ) ;
2023-09-07 03:59:19 -06:00
}
2023-09-12 19:09:43 -06:00
} ) ;
client . on ( "Room.timeline" , function ( event , room ) {
try {
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 . getTs ( ) < ( Date . now ( ) - 1000 * 60 * 60 * 8 ) ) {
// Ignore old events (8 hrs), they're probably duplicates or something.
logger . warn ( "Ignoring stale Matrix room event [" + event . getId ( ) + "]: older than 8 hours" ) ;
return ;
}
if ( event . getTs ( ) < boottimestamp ) {
logger . warn ( "Ignoring stale Matrix room event [" + event . getId ( ) + "]: event predates start of this program" ) ;
2023-09-05 21:14:48 -06:00
return ;
}
2023-09-12 19:09:43 -06:00
if ( event . getContent ( ) == null || typeof event . getContent ( ) . body == "undefined" || event . getContent ( ) . body == null ) {
// Apparently this can happen?
return ;
}
logger . debug ( "Got room message (event ID " + event . getId ( ) + ")" ) ;
if ( event . getContent ( ) . body . toLowerCase ( ) . startsWith ( "!sms" ) ) {
// capture command to start room for new number
const matches = event . getContent ( ) . body . match ( /([1-9]?[0-9]{10})/g ) ;
if ( matches == null ) {
return ;
2020-11-10 18:07:40 -07:00
}
2023-09-12 19:09:43 -06:00
if ( matches . length == 1 || matches . length == 2 ) {
var tel = matches [ 0 ] ;
var ournumber = settings . smsfrom ;
if ( tel . length == 10 ) {
2021-06-16 14:56:45 -06:00
// make it the full number
2023-09-12 19:09:43 -06:00
tel = "1" + tel ;
2023-09-05 21:50:26 -06:00
}
2023-09-12 19:09:43 -06:00
if ( matches . length == 2 ) {
ournumber = matches [ 1 ] ;
if ( ournumber . length == 10 ) {
// make it the full number
ournumber = "1" + ournumber ;
}
}
logger . info ( "Got request to start new SMS conversation with " + tel + " using " + ournumber + " from " + event . getSender ( ) + "." ) ;
sendMatrixNotice ( event . getRoomId ( ) , "Starting conversation with " + tel ) ;
createOrJoinSMSRoom ( tel , ournumber , function ( roomid ) {
//client.setRoomTag(roomid, "u.matrix-bridge-voxtelesys-sms", {tel: tel, ournumber: ournumber});
} ) ;
2021-06-16 14:56:45 -06:00
}
2023-09-12 19:09:43 -06:00
return ;
} else if ( event . getContent ( ) . body . toLowerCase ( ) . replace ( /\s/g , "" ) . startsWith ( "!sms" ) ) {
sendMatrixNotice ( event . getRoomId ( ) , "Malformed command detected, ignoring." ) ;
sendMatrixNotice ( event . getRoomId ( ) , "Hint: there aren't supposed to be any spaces before or in the \"!sms\" part." ) ;
return ;
2020-11-10 18:07:40 -07:00
}
2022-12-29 21:49:24 -07:00
2023-09-12 19:09:43 -06:00
if ( event . getContent ( ) . body . toLowerCase ( ) . startsWith ( "!fixusers" ) ) {
sendMatrixNotice ( event . getRoomId ( ) , "Inviting missing users across all SMS chats. You may need to run this command several times, there's a server limit to how many invites can be sent at once." ) ;
client . getJoinedRooms ( ) . then ( function ( rooms ) {
var roomlist = rooms . joined _rooms ;
for ( var i = 0 ; i < roomlist . length ; i ++ ) {
( function ( roomid ) {
client . getJoinedRoomMembers ( roomid ) . then ( function ( joined ) {
var members = Object . keys ( joined . joined ) ;
for ( var j = 0 ; j < settings . inviteusers . length ; j ++ ) {
if ( members . indexOf ( settings . inviteusers [ j ] ) == - 1 ) {
logger . info ( "Inviting missing user " + settings . inviteusers [ j ] + " to room " + roomid ) ;
client . invite ( roomid , settings . inviteusers [ j ] ) ;
}
2022-06-09 12:32:25 -06:00
}
2023-09-12 19:09:43 -06:00
} ) ;
} ) ( roomlist [ i ] ) ;
}
} ) ;
2022-06-09 12:32:25 -06:00
2023-09-12 19:09:43 -06:00
return ;
} else if ( event . getContent ( ) . body . toLowerCase ( ) . replace ( /\s/g , "" ) . startsWith ( "!fixusers" ) ) {
sendMatrixNotice ( event . getRoomId ( ) , "Malformed command detected, ignoring." ) ;
return ;
}
2022-12-29 21:51:38 -07:00
2023-09-12 19:09:43 -06:00
if ( event . getContent ( ) . body . toLowerCase ( ) . replace ( /\s/g , "" ) . startsWith ( "!" ) ) {
sendMatrixNotice ( event . getRoomId ( ) , "I'm sorry, but my programming forbids me from sending text messages that start with `!`." ) ;
return ;
}
2022-12-29 22:07:19 -07:00
2023-09-12 19:09:43 -06:00
var matches = room . name . match ( /SMS_([1-9][0-9]+)(?:_([1-9][0-9]+))?/g ) ;
if ( matches == null || ( matches . length != 1 && matches . length != 2 ) ) {
client . getRoomTags ( event . getRoomId ( ) ) . then ( ( response ) => {
console . log ( response ) ;
if ( typeof response . tags [ "u.matrix-bridge-voxtelesys-sms" ] != "undefined" ) {
var tel = response . tags [ "u.matrix-bridge-voxtelesys-sms" ] . tel ;
var from = response . tags [ "u.matrix-bridge-voxtelesys-sms" ] . ournumber ;
logger . info ( "Got message for " + tel + " from " + event . getSender ( ) + ", relaying to " + from + "." ) ;
switch ( event . getContent ( ) . msgtype ) {
case "m.image" :
case "m.file" :
case "m.video" :
sendMMS (
tel ,
from ,
event . getContent ( ) . url ,
event . getContent ( ) . info . mimetype ,
function ( ) {
client . sendReadReceipt ( event , "m.read" ) ;
} ) ;
break ;
case "m.text" :
default :
sendSMS (
tel ,
from ,
event . getContent ( ) . body ,
function ( ) {
client . sendReadReceipt ( event , "m.read" ) ;
} ) ;
break ;
}
} else if ( typeof response . tags [ "com.netsyms.matrix-bridge-voxtelesys.sms" ] != "undefined" ) {
var tel = response . tags [ "com.netsyms.matrix-bridge-voxtelesys.sms" ] . tel ;
var from = response . tags [ "com.netsyms.matrix-bridge-voxtelesys.sms" ] . ournumber ;
client . setRoomTag ( event . getRoomId ( ) , "u.matrix-bridge-voxtelesys-sms" , { tel : tel , ournumber : from , order : 0.5 } ) ;
logger . info ( "Got message for " + tel + " from " + event . getSender ( ) + ", relaying to " + from + "." ) ;
switch ( event . getContent ( ) . msgtype ) {
case "m.image" :
case "m.file" :
case "m.video" :
sendMMS (
tel ,
from ,
event . getContent ( ) . url ,
event . getContent ( ) . info . mimetype ,
function ( ) {
client . sendReadReceipt ( event , "m.read" ) ;
} ) ;
break ;
case "m.text" :
default :
sendSMS (
tel ,
from ,
event . getContent ( ) . body ,
function ( ) {
client . sendReadReceipt ( event , "m.read" ) ;
} ) ;
break ;
}
} else {
console . log ( response . tags ) ;
sendMatrixNotice ( room . roomId , "Error: couldn't determine correct number to send SMS from." ) ;
}
} ) ;
} else {
if ( matches . length == 1 ) {
var tel = matches [ 0 ] ;
logger . info ( "Got message for " + tel + " from " + event . getSender ( ) + ", relaying." ) ;
2022-02-03 18:10:21 -07:00
switch ( event . getContent ( ) . msgtype ) {
case "m.image" :
2022-02-03 18:41:58 -07:00
case "m.file" :
2022-02-03 18:47:45 -07:00
case "m.video" :
2023-09-12 19:09:43 -06:00
sendMMS ( tel , settings . smsfrom , event . getContent ( ) . url ,
2022-02-03 18:10:21 -07:00
event . getContent ( ) . info . mimetype ,
function ( ) {
2023-09-05 21:45:57 -06:00
client . sendReadReceipt ( event , "m.read" ) ;
2022-02-03 18:10:21 -07:00
} ) ;
break ;
case "m.text" :
default :
2023-09-12 19:09:43 -06:00
sendSMS ( tel , settings . smsfrom , event . getContent ( ) . body , function ( ) {
client . sendReadReceipt ( event , "m.read" ) ;
} ) ;
2022-02-03 18:10:21 -07:00
break ;
}
2023-09-12 19:09:43 -06:00
} else if ( matches . length == 2 ) {
var tel = matches [ 0 ] ;
var from = matches [ 1 ] ;
2021-07-12 21:15:51 -06:00
logger . info ( "Got message for " + tel + " from " + event . getSender ( ) + ", relaying to " + from + "." ) ;
2022-02-03 18:10:21 -07:00
switch ( event . getContent ( ) . msgtype ) {
case "m.image" :
2022-02-03 18:41:58 -07:00
case "m.file" :
2022-02-03 18:47:45 -07:00
case "m.video" :
2023-09-12 19:09:43 -06:00
sendMMS ( tel , from , event . getContent ( ) . url ,
2022-02-03 18:10:21 -07:00
event . getContent ( ) . info . mimetype ,
function ( ) {
2023-09-05 21:45:57 -06:00
client . sendReadReceipt ( event , "m.read" ) ;
2022-02-03 18:10:21 -07:00
} ) ;
break ;
case "m.text" :
default :
2023-09-12 19:09:43 -06:00
sendSMS ( tel , settings . smsfrom , event . getContent ( ) . body , function ( ) {
client . sendReadReceipt ( event , "m.read" ) ;
} ) ;
2022-02-03 18:10:21 -07:00
break ;
}
}
2021-07-12 20:15:47 -06:00
}
2023-09-12 19:09:43 -06:00
} catch ( ex ) {
logger . error ( "Error handling incoming event: " + ex ) ;
2020-11-05 22:39:11 -07:00
}
2023-09-12 19:09:43 -06:00
} ) ;
}