diff --git a/README.md b/README.md index 0ab7233..777a585 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ Vanderwarker Family XMPP Bot. [![NPM](https://nodei.co/npm/vxmppb.png?compact=true)](https://nodei.co/npm/vxmppb/) [![Known Vulnerabilities](https://snyk.io/test/npm/vxmppb/badge.svg)](https://snyk.io/test/npm/vxmppb) + +![docker](https://img.shields.io/docker/pulls/vxmppb/vxmppb.svg) ![npm](https://img.shields.io/npm/dt/vxmppb) diff --git a/config.ini.dist b/config.ini.dist index d71d41e..3c8d9bd 100644 --- a/config.ini.dist +++ b/config.ini.dist @@ -5,6 +5,7 @@ callfrom = callto = callerID = context = +enabled = host = password = port = @@ -17,19 +18,22 @@ password = port = [gotify] +api_url = +enabled = key = message = priority = title = -api_url = [mastodon] token = toot = api_url = user = +enabled = [mqtt] +enabled = message = password = server = @@ -42,6 +46,7 @@ bright_url = color_url = status_url = toggle_url = +reauth_url [unlocks] api_url = @@ -65,5 +70,6 @@ key = muc_name = muc_password = muc_server = +server_type = [friends] diff --git a/index.js b/index.js index 6e2669b..4ff83cb 100755 --- a/index.js +++ b/index.js @@ -22,6 +22,7 @@ try { const asterisk_callerID = config.asterisk.callerID; const asterisk_callto = config.asterisk.callerto; const asterisk_context = config.asterisk.context; + const calls_enabled = config.asterisk.enabled; const asterisk_host = config.asterisk.host; const asterisk_password = config.asterisk.password; const asterisk_port = config.asterisk.port; @@ -35,6 +36,7 @@ try { // Gotify config const gotify_api_url = config.gotify.api_url; + const gotify_enabled = config.gotify.enabled; const gotify_key = config.gotify.key; const gotify_message = config.gotify.message; const gotify_priority = config.gotify.priority; @@ -42,11 +44,13 @@ try { // Mastodon config const mastodon_api_url = config.mastodon.api_url; + const mastodon_enabled = config.mastodon.enabled; const mastodon_token = config.mastodon.token; const mastodon_toot = config.mastodon.toot; const mastodon_user = config.mastodon.user; // MQTT config + const mqtt_enabled = config.mqtt.enabled; const mqtt_message = config.mqtt.message; const mqtt_password = config.mqtt.password; const mqtt_server = config.mqtt.server; @@ -59,6 +63,7 @@ try { const tuya_color_url = config.tuya.color_url; const tuya_status_url = config.tuya.status_url; const tuya_toggle_url = config.tuya.toggle_url; + const tuya_reauth_url = config.tuya.reauth_url; // Unlocks API config const unlocks_api_url = config.unlocks.api_url; @@ -82,6 +87,7 @@ try { const xmpp_muc_name = config.xmpp.muc_name; const xmpp_muc_password = config.xmpp.muc_password; const xmpp_muc_server = config.xmpp.muc_server; + const xmpp_server_type = config.xmpp.server_type; // setup private command message let pc = 'This is a private command. Please see https://wiki.vanderwarker.family/doku.php?id=code:vxmppb:start for your own instance!'; @@ -109,42 +115,70 @@ try { // setup friends list const friends = Object.keys(config.friends); - - xmpp.on('online', function(data, to) { + + xmpp.on('online', function (data, to) { // once we're online, we'll send a message alerting xmpp admins console.log('Connected with JID: ' + bot_jid); - xmpp.send(user_jid, "[ONLINE]", false); + //xmpp.send(user_jid, "[ONLINE]", false); // send mqtt online message - client.publish(mqtt_topic, mqtt_message); + if (mqtt_enabled === "1"){ + client.publish(mqtt_topic, mqtt_message); + } // toot online message -/* - M.post('statuses', { - status: `${mastodon_toot}` - }); -*/ - + if (mastodon_enabled === "1"){ + M.post('statuses', { + status: `${mastodon_toot}` + }); + } }); // wait for incoming messages - xmpp.on('chat', function(from, message) { - // check for debugging + xmpp.on('chat', function (from, message) { + // check if user enabled asterisk + function call(call_data) { + if (calls_enabled === '1') { + ami.action({ + 'action': 'originate', + 'channel': 'SIP/' + asterisk_callfrom, + 'context': asterisk_context, + 'callerID': asterisk_callerID, + 'data': 'googletts.agi,' + call_data, + 'application': 'agi' + }); + } + } + // check is user enabled debugging function debug() { if (debugging === '1') { - console.log('-------------'); + console.log('[DEBUG]'); console.log(message); console.log(from); - console.log('-------------'); }; } - // setup gotify url + // http request errors + function resError(error, res) { + if (!error && res.statusCode == 401) { + console.log('Invalid credentials'); + xmpp.send(from, 'Invalid credentials'); + } + if (!error && res.statusCode == 404) { + console.log('Record not found'); + xmpp.send(from, 'Record not found'); + } + if (!error && res.statusCode == 500) { + console.log('Server error.'); + xmpp.send(from, 'Server error. Please ping admin'); + } + }; + // setup gotify url let gotifyURL = `${gotify_api_url}/message?token=${gotify_key}&message=${from} ${gotify_message}&title=${gotify_title}&priority=${gotify_priority}`; let gotifyEUrl = encodeURI(gotifyURL); - // HELP + // !help reply if (message === '!help') { xmpp.send(from, 'https://wiki.vanderwarker.family/doku.php?id=code:vxmppb:commands'); debug(); } - // UNLOCKS + // !unlocks reply else if (message === '!unlocks') { if (from != user_jid) { xmpp.send(from, pc); @@ -159,7 +193,44 @@ try { weekday[5] = "Friday"; weekday[6] = "Saturday"; var n = weekday[d.getDay()]; - // get latest unlocks + // get latest unlocks + request(unlocks_api_url + "?columns=" + n, (error, res, body) => { + if (error) { + return console.log(error) + }; + // check for valid api response + if (!error && res.statusCode == 200) { + var unlockBody = new RegExp(':(.*)}'); + var unlockBodyMatch = body.match(unlockBody); + // setup unlocks message + const unlockFinalMessage = user_name + " has " + unlockBodyMatch[1] + " unlocks for " + n; + xmpp.send(from, unlockFinalMessage); + // call user with unlocks if asterisk enabled + call(unlockFinalMessage); + } + // check for http errors + resError(error, res); + }); + // output xmpp, and request errors if debugging is enabled. + debug(); + } + } + // Toot Unlocks + else if (message === '!tootunlocks') { + if (from != user_jid) { + xmpp.send(from, pc); + } else { + var d = new Date(); + var weekday = new Array(7); + weekday[0] = "Sunday"; + weekday[1] = "Monday"; + weekday[2] = "Tuesday"; + weekday[3] = "Wednesday"; + weekday[4] = "Thursday"; + weekday[5] = "Friday"; + weekday[6] = "Saturday"; + var n = weekday[d.getDay()]; + // get latest unlocks request(unlocks_api_url + "?columns=" + n, (error, res, body) => { if (error) { return console.log(error) @@ -168,79 +239,24 @@ try { if (!error && res.statusCode == 200) { var unlockBody = new RegExp(':(.*)}'); var unlockBodyMatch = body.match(unlockBody); - const unlockFinalMessage = user_name + " has " + unlockBodyMatch[1] + " unlocks for " + n; - xmpp.send(from, unlockFinalMessage); + const unlockFinalMessage = "@" + config.mastodon.user + " has " + unlockBodyMatch[1] + " unlocks for " + n; + M.post('statuses', { + status: unlockFinalMessage + }); debug(); } - if (!error && res.statusCode == 401) { - console.log('Invalid credentials'); - xmpp.send(from, 'Invalid credentials'); - } - if (!error && res.statusCode == 404) { - console.log('Record not found'); - xmpp.send(from, 'Record not found'); - } - if (!error && res.statusCode == 500) { - console.log('Server error.'); - xmpp.send(from, 'Server error. Please ping admin'); - } + resError(error, res); }); debug(); } } - // Toot Unlocks - else if (message === '!tootunlocks') { - if (from != user_jid) { - xmpp.send(from, pc); - } else { - var d = new Date(); - var weekday = new Array(7); - weekday[0] = "Sunday"; - weekday[1] = "Monday"; - weekday[2] = "Tuesday"; - weekday[3] = "Wednesday"; - weekday[4] = "Thursday"; - weekday[5] = "Friday"; - weekday[6] = "Saturday"; - var n = weekday[d.getDay()]; - // get latest unlocks - request(unlocks_api_url + "?columns=" + n, (error, res, body) => { - if (error) { - return console.log(error) - }; - - if (!error && res.statusCode == 200) { - var unlockBody = new RegExp(':(.*)}'); - var unlockBodyMatch = body.match(unlockBody); - const unlockFinalMessage = "@" + config.mastodon.user + " has " + unlockBodyMatch[1] + " unlocks for " + n; - M.post('statuses', { - status: unlockFinalMessage - }); - debug(); - } - if (!error && res.statusCode == 401) { - console.log('Invalid credentials'); - xmpp.send(from, 'Invalid credentials'); - } - if (!error && res.statusCode == 404) { - console.log('Record not found'); - xmpp.send(from, 'Record not found'); - } - if (!error && res.statusCode == 500) { - console.log('Server error.'); - xmpp.send(from, 'Server error. Please ping admin'); - } - }); - debug(); - } - } - + // Tuya light control else if (message === '!lights') { if (from != user_jid) { xmpp.send(from, pc); } else { - // get current light status + // get current light status request(tuya_status_url, (error, res, body) => { if (error) { return console.log(error) @@ -260,18 +276,24 @@ try { console.log("Tuya API: Unknown reply!"); } } - if (!error && res.statusCode == 401) { - console.log('Invalid credentials'); - xmpp.send(from, 'Invalid credentials'); - } - if (!error && res.statusCode == 404) { - console.log('Record not found'); - xmpp.send(from, 'Record not found'); - } - if (!error && res.statusCode == 500) { - console.log('Server error.'); - xmpp.send(from, 'Server error. Please !ping'); + resError(error, res); + }); + debug(); + } + } else if (message === '!lightsauth') { + if (from != user_jid) { + xmpp.send(from, pc); + } else { + // refresh tokens for Tuya API + request(tuya_reauth_url, (error, res, body) => { + if (error) { + return console.log(error) + }; + + if (!error && res.statusCode == 200) { + xmpp.send(from, 'Refreshing Tuya API auth keys'); } + resError(error, res); }); debug(); } @@ -279,7 +301,7 @@ try { if (from != user_jid) { xmpp.send(from, pc); } else { - // turn on light + // turn on light request(tuya_toggle_url + "?stat=true&devname=" + tuya_name, (error, res, body) => { if (error) { return console.log(error) @@ -290,18 +312,7 @@ try { xmpp.send(from, stat); console.log(stat); } - if (!error && res.statusCode == 401) { - console.log('Invalid credentials'); - xmpp.send(from, 'Invalid credentials'); - } - if (!error && res.statusCode == 404) { - console.log('Record not found'); - xmpp.send(from, 'Record not found'); - } - if (!error && res.statusCode == 500) { - console.log('Server error.'); - xmpp.send(from, 'Server error. Please ping admin'); - } + resError(error, res); }); debug(); } @@ -309,29 +320,17 @@ try { if (from != user_jid) { xmpp.send(from, pc); } else { - // turn off light + // turn off light request(tuya_toggle_url + "?stat=false&devname=" + tuya_name, (error, res, body) => { if (error) { return console.log(error) }; - if (!error && res.statusCode == 200) { const stat = tuya_name + " has been turned off"; xmpp.send(from, stat); console.log(stat); } - if (!error && res.statusCode == 401) { - console.log('Invalid credentials'); - xmpp.send(from, 'Invalid credentials'); - } - if (!error && res.statusCode == 404) { - console.log('Record not found'); - xmpp.send(from, 'Record not found'); - } - if (!error && res.statusCode == 500) { - console.log('Server error.'); - xmpp.send(from, 'Server error. Please ping admin'); - } + resError(error, res); }); debug(); } @@ -341,28 +340,16 @@ try { } else { var per = message.substring(message.indexOf("brightness") + 10); var perc = (per * 10) - // get brightness of light + // get brightness of light request(tuya_bright_url + "?brite=" + perc + "&devname=" + tuya_name, (error, res, body) => { if (error) { return console.log(error) }; if (!error && res.statusCode == 200) { - xmpp.send(from, tuya_name + "set to " + per + "%"); - console.log(tuya_name + "set to " + per + "%"); - } - if (!error && res.statusCode == 401) { - console.log('Invalid credentials'); - xmpp.send(from, 'Invalid credentials'); - } - if (!error && res.statusCode == 404) { - console.log('Record not found'); - xmpp.send(from, 'Record not found'); - } - if (!error && res.statusCode == 500) { - console.log('Server error.'); - xmpp.send(from, 'Server error. Please ping admin'); + xmpp.send(from, tuya_name + " set to " + per + "%"); } + resError(error, res); }); if (config.debug === '1') { console.log('lights turned to ' + per + "%"); @@ -372,7 +359,7 @@ try { if (from != user_jid) { xmpp.send(from, pc); } else { - // change light color + // change light color request(tuya_toggle_url + "?stat=true&devname=" + tuya_name, (error, res, body) => {}) var color = message.substring(message.indexOf("color") + 6); var colorRgb = rgbcolor(color); @@ -390,18 +377,7 @@ try { xmpp.send(from, tuya_name + "set to " + color); console.log(tuya_name + "set to " + color); } - if (!error && res.statusCode == 401) { - console.log('Invalid credentials'); - xmpp.send(from, 'Invalid credentials'); - } - if (!error && res.statusCode == 404) { - console.log('Record not found'); - xmpp.send(from, 'Record not found'); - } - if (!error && res.statusCode == 500) { - console.log('Server error.'); - xmpp.send(from, 'Server error. Please ping admin'); - } + resError(error, res); }); if (config.debug === '1') { console.log(tuya_name + "set to " + color); @@ -410,27 +386,21 @@ try { } // Ping a user to let them know you would like to communicate else if (message === '!ping') { - // send xmpp ping message to message author + // send xmpp ping message to message author xmpp.send(from, "Pinging " + user_name + "! Please stand by."); - // send xmpp ping message to admin + // send xmpp ping message to admin xmpp.send(user_jid, from + " has pinged you!"); - // send mqtt ping message + // send mqtt ping message client.publish(mqtt_topic, from + ' has pinged you!'); - // set gotify ping message to admin - request.post(gotifyEUrl, function(err, response, body) { + // set gotify ping message to admin + request.post(gotifyEUrl, function (err, response, body) { if (err) { console.log('error:', err); } }); - // call admin alerting of authors ping - ami.action({ - 'action': 'originate', - 'channel': 'SIP/' + asterisk_callfrom, - 'context': asterisk_context, - 'callerID': asterisk_callerID, - 'data': 'googletts.agi, \"hello. ' + from + ' has pinged you!\"', - 'application': 'agi' - }); + // call admin alerting of authors ping + call_data = "hello. " + from + " has pinged you!" + call(call_data); debug(); } @@ -439,35 +409,24 @@ try { if (from != user_jid) { xmpp.send(from, pc); } else { - // check current weather - request(weather_url, function(err, response, body) { + // check current weather + request(weather_url, function (err, response, body) { if (err) { console.log('error:', err); } else { let w = JSON.parse(body); - let wm = weather_city_name + " Weather: \r\n Tempature: " + w.main.temp + " degrees. \r\n Conditions: " + w.weather[0].main + "\r\n Pressure: " + - w.main.pressure + " \r\n Wind Direction: " + getDirection(w.wind.deg) + "\r\n Wind Speed: " + w.wind.speed + " m.p.h. \r\n"; - let wmc = weather_city_name + " Weather: Tempature: " + w.main.temp + " degrees. Conditions: " + w.weather[0].main + ". Pressure: " + + let wm = weather_city_name + " Weather: Tempature: " + w.main.temp + " degrees. Conditions: " + w.weather[0].main + ". Pressure: " + w.main.pressure + ". Wind Direction: " + getDirection(w.wind.deg) + ". Wind Speed: " + w.wind.speed + " miles per hour \r\n"; - // send weather to mqtt + // send weather to mqtt client.publish('weather/temp', '' + w.main.temp + ''); client.publish('weather/conditions', '' + w.weather[0].main + ''); client.publish('weather/pressure', '' + w.main.pressure + ''); client.publish('weather/wind/direction', '' + getDirection(w.wind.deg) + ''); client.publish('weather/wind/speed', '' + w.wind.speed + ''); - // send weather to xmpp admin + // send weather to xmpp admin xmpp.send(from, wm); - // call admin with latest weather report - /* - ami.action({ - 'action':'originate', - 'channel':'SIP/' + asterisk_callfrom, - 'context': asterisk_context, - 'callerID' : asterisk_callerID, - 'data': 'googletts.agi,' + wmc, - 'application': 'agi' - }); - */ + // call admin with latest weather report + call(wm); } }); debug(); @@ -481,17 +440,17 @@ try { } - // Share-A-Command which was #13, but kinda turned into a different command - // This will generate a private channel, and add the users in the friends - // secion from config.ini - // Please not this section requires you to have an Ejabberd server. + // Share-A-Command which was #13, but kinda turned into a different command + // This will generate a private channel, and add the users in the friends + // secion from config.ini + // Please note this section requires you to have an Ejabberd server. // I am welcome to pull requests for other servers (maybe set type in config.ini) - else if (message === '!meet') { if (from != user_jid) { xmpp.send(from, pc); } else { - // Create private XMPP room + if (xmpp_server_type === "ejabberd"){ + // Create private XMPP room const createRoom = { method: 'POST', url: xmpp_api_url + '/create_room_with_opts', @@ -521,18 +480,18 @@ try { }, json: true }; - // send request to create room - request(createRoom, function(error, response, body) { + // send request to create room + request(createRoom, function (error, response, body) { if (error) throw new Error(error); console.log(body); }); - // wait 2 seconds until room has been created (http issues) + // wait 2 seconds until room has been created (http issues) setTimeout(() => { - // loop through friends, authorize users, and send invites + // loop through friends, authorize users, and send invites for (const key in config.friends) { - // send xmpp message to friends alerting of new room + // send xmpp message to friends alerting of new room xmpp.send(key, "Meeting started"); - // authorize users + // authorize users const addMember = { method: 'POST', url: xmpp_api_url + 'set_room_affiliation', @@ -548,7 +507,7 @@ try { }, json: true }; - // invite users + // invite users const inviteMember = { method: 'POST', url: xmpp_api_url + '/send_direct_invitation', @@ -565,18 +524,22 @@ try { }, json: true }; - request(addMember, function(error, response, body) { + request(addMember, function (error, response, body) { if (error) throw new Error(error); console.log(body); }); - request(inviteMember, function(error, response, body) { + request(inviteMember, function (error, response, body) { if (error) throw new Error(error); console.log(body); }); } }, 2000); debug(); - } + xmpp.send(from, "Alright. Starting a new meeting: " + xmpp_muc_name + "@" + xmpp_muc_server + " and inviting friends"); + } else { + xmpp.send(from, "Ejabberd is the only supported server at the moment.") + xmpp.send(from, "Please see https://git.vanderwarker.family/vxmppb/vxmppb/issues/20") + }} } // End meeting @@ -584,7 +547,7 @@ try { if (from != user_jid) { xmpp.send(from, pc); } else { - // end meeting (delete private xmpp room) + // end meeting (delete private xmpp room) const endMeeting = { method: 'POST', url: xmpp_api_url + '/destroy_room', @@ -598,13 +561,13 @@ try { }, json: true }; - // send room delete request - request(endMeeting, function(error, response, body) { + // send room delete request + request(endMeeting, function (error, response, body) { if (error) throw new Error(error); console.log(body); }); for (const key in config.friends) { - // alert friends meeting has ended + // alert friends meeting has ended xmpp.send(key, "Meeting ended"); } } @@ -615,12 +578,12 @@ try { else if (message === "sudo shutdown now -h") { if (from != user_jid) { xmpp.send(from, from + " is not in the sudoers file. This incident will be reported."); - console.log(from + ' tried to shut me down!'); + console.log(from + ' tried to shut me down!'); } else { - // send shutdown message + // send shutdown message xmpp.send(from, "Alrighty, shutting down. Goodbye!"); debug(); - // kill bot + // kill bot process.exit(1); } } @@ -630,8 +593,8 @@ try { if (from != user_jid) { xmpp.send(from, pc); } else { - // get user_jid vcard (profile info) - xmpp.getVCard(from, function(vcard) { + // get user_jid vcard (profile info) + xmpp.getVCard(from, function (vcard) { xmpp.send(from, vcard); console.log(vcard) }) @@ -646,7 +609,7 @@ try { }); // Catch and log XMPP errors - xmpp.on('error', function(err) { + xmpp.on('error', function (err) { console.error(err); }); @@ -660,24 +623,30 @@ try { host: bot_hostname, port: bot_port }); - // subscribe to friends - xmpp.on('subscribe', function(from) { - if (from in config.friends == true) { - console.log('accepting subsciption request from ' + from); - // accept pending subscription requests - xmpp.acceptSubscription(from); - // check for incoming subscription requests - xmpp.getRoster(); - // subscribe to any incoming subscription requests - console.log('requesting subscription to ' + from); - xmpp.subscribe(from); - } else { - console.log('denied subscription request from: ' + from + " as they are not in the friends section of config.ini"); - } - }); + // subscribe to friends + xmpp.on('subscribe', function (from) { + if (from in config.friends == true) { + console.log('accepting subsciption request from ' + from); + // accept pending subscription requests + xmpp.acceptSubscription(from); + // check for incoming subscription requests + xmpp.getRoster(); + // subscribe to any incoming subscription requests + console.log('requesting subscription to ' + from); + xmpp.subscribe(from); + } else { + console.log('denied subscription request from: ' + from + " as they are not in the friends section of config.ini"); + } + }); + + process.on('SIGINT', function() { + console.log("\r\n SigInt receieved. Shutdown eminent!") + xmpp.send(user_jid, "SigInt receieved. Shutdown eminent!") + process.exit(); + }); } catch (e) { console.log("Please create a config.ini"); console.log("See https://wiki.vanderwarker.family/doku.php?id=code:vxmppb:install:start"); console.log("and choose your prefered install method."); -} +} \ No newline at end of file diff --git a/package.json b/package.json index 145499b..aaae7a2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vxmppb", "description": "vanderwarker.family XMPP Bot", - "version": "1.0.0", + "version": "1.1.0", "keywords": [ "xmpp", "bot", @@ -35,9 +35,11 @@ "type": "git", "url": "https://git.vanderwarker.family/vxmppb/vxmppb.git" }, - "license": { - "type": "MIT" - }, + "licenses": [ + { + "type": "The MIT License", + "url": "http://www.opensource.org/licenses/mit-license.php" + }], "homepage": "https://wiki.vanderwarker.family/doku.php?id=code:vxmppb:start", "scripts": { "test": "echo \"No tests available. Please consider helping with a pull request!\" && exit 0"