commit 0e6d4840a45d1427dff29f94fa930aabf1531896 Author: stephen Date: Sun Jun 20 09:54:50 2021 -0400 :new: :fire: diff --git a/.gitea/ISSUE_TEMPLATE.md b/.gitea/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..9ba6531 --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE.md @@ -0,0 +1,16 @@ +## Expected Behavior + + +## Actual Behavior + + +## Steps to Reproduce the Problem +1. +2. +3. + +## Specifications + + - Version: + - Platform: + - Subsystem: diff --git a/.gitea/PULL_REQUEST_TEMPLATE.md b/.gitea/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..4030f6f --- /dev/null +++ b/.gitea/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,7 @@ +Fixes # + +## Proposed Changes + + - + - + - diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b81b531 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules +package-lock.json +config.ini +build/ +.npmignore +npm-debug.log diff --git a/README.md b/README.md new file mode 100644 index 0000000..a2fad0d --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# vxmppb + +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) + +![npm](https://img.shields.io/npm/dt/vxmppb) + +### :book: How To Install +You can install using: + - [npm](https://npmjs.com/package/vxmppb) + - [Wiki](https://wiki.vanderwarker.family/doku.php?id=code:vxmppb:install) + +### :1234: Commands +[See here](https://wiki.vanderwarker.family/doku.php?id=code:vxmppb:commands) + +### :bug: Bugs +[See here](https://git.vanderwarker.family/vxmppb/vxmppb/issues) diff --git a/config.ini.dist b/config.ini.dist new file mode 100644 index 0000000..b274dc7 --- /dev/null +++ b/config.ini.dist @@ -0,0 +1,34 @@ +api_pass = +api_url = +api_user = +app_port = +asterisk_callfrom = +asterisk_callerID = +asterisk_context = +asterisk_host = +asterisk_password = +asterisk_port = +asterisk_user = +bot_hostname = +bot_jid = +bot_password = +city_code = +city_name = +gotify_api_key = +gotify_message = +gotify_priority = +gotify_title = +gotify_url = +mastodon_key = +mastodon_toot = +mastodon_url = +mqtt_message = +mqtt_password = +mqtt_server = +mqtt_topic = +mqtt_user = +rss_url = +user_jid = +user_name = +weather_api_key = +weather_units = diff --git a/index.js b/index.js new file mode 100644 index 0000000..6a1efee --- /dev/null +++ b/index.js @@ -0,0 +1,263 @@ +const chalk = require('chalk'); +const d2d = require('degrees-to-direction'); +const express = require('express') +const app = express(); +const fs = require('fs'); +const ini = require('ini'); +const mastodon = require('mastodon'); +const mqtt = require('mqtt'); +const request = require('request'); +let Parser = require('rss-parser'); +let parser = new Parser(); +const xmpp = require('simple-xmpp'); + +const config = ini.parse(fs.readFileSync('./config.ini', 'utf-8')); +const api_pass = config.api_pass; +const api_url = config.api_url; +const api_user = config.api_user; +const app_port = config.app_port; +const asterisk_callfrom = config.asterisk_callfrom; +const asterisk_callerID = config.asterisk_callerID; +const asterisk_context = config.asterisk_context; +const asterisk_host = config.asterisk_host; +const asterisk_password = config.asterisk_password; +const asterisk_port = config.asterisk_port; +const asterisk_user = config.asterisk_user; +const bot_hostname = config.bot_hostname; +const bot_jid = config.bot_jid; +const bot_password = config.bot_password; +const city_code = config.city_code; +const city_name = config.city_name; +const gotify_api_key = config.gotify_api_key; +const gotify_message = config.gotify_message; +const gotify_priority = config.gotify_priority; +const gotify_title = config.gotify_title; +const gotify_url = config.gotify_url; +const mastodon_token = config.mastodon_token; +const mastodon_toot = config.mastodon_toot; +const mastodon_url = config.mastodon_url; +const mqtt_message = config.mqtt_message; +const mqtt_password = config.mqtt_password; +const mqtt_server = config.mqtt_server; +const mqtt_topic = config.mqtt_topic; +const mqtt_user = config.mqtt_user; +const rpc_url = config.rpc_url; +const user_jid = config.user_jid; +const user_name = config.user_name; +const vfc_url = config.vfc_url; +const weather_api_key = config.weather_api_key; +const weather_units = config.weather_units; + +var ami = new require('asterisk-manager')(asterisk_port, asterisk_host, asterisk_user, asterisk_password, false); +let weather_url = `https://api.openweathermap.org/data/2.5/weather?id=${city_code}&appid=${weather_api_key}&units=${weather_units}` +var client = mqtt.connect('tcp://' + mqtt_user + ':' + mqtt_password + '@' + mqtt_server); +var M = new mastodon({ + access_token: `${mastodon_token}`, + timeout_ms: 60*1000, + api_url: `${mastodon_url}`, +}) +// Once we're online, we'll send a message alerting admins +xmpp.on('online', function(data, to) { + console.log('Connected with JID: ' + data.jid.user); + xmpp.send(user_jid, "[ONLINE]", false); + client.publish(mqtt_topic, mqtt_message); + M.post('statuses', { status: `${mastodon_toot}` }); + app.get('/', (req, res) => res.send("Bot Online!

Bot Online!

")); + app.listen(app_port, () => console.log(`Listening on ${app_port}!`)); +}); + +xmpp.on('chat', function(from, message) { + + // HELP + if (message === 'help' || message === 'Help') { + xmpp.send(from, 'https://vanderwarker.family/help/xmpp#chatbot'); + console.log(chalk.blue('help')); + } + + // UNLOCKS + else if (message === 'unlocks' || message === 'Unlocks') { + 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()]; + + request(api_url + "?columns=" + n, (error, res, body) => { + if (error) { + return chalk.red(console.log(error)) + }; + + if (!error && res.statusCode == 200) { + var ulb = new RegExp(':(.*)}'); + var ule = body.match(ulb); + const ur = user_name + " has " + ule[1] + " unlocks for " + n; + xmpp.send(from, ur); + console.log(chalk.yellow(ur)); + } + if (!error && res.statusCode == 401) { + console.log(chalk.red('Invalid credentials')); + xmpp.send(from, 'Invalid credentials'); + } + if (!error && res.statusCode == 404) { + console.log(chalk.red('Record not found')); + xmpp.send(from, 'Record not found'); + } + if (!error && res.statusCode == 500) { + console.log(chalk.red('Server error.')); + xmpp.send(from, 'Server error. Please ping admin'); + } + }); + console.log(chalk.blue('unlocks')); + } + + // Send VFC from coinbase to Pixel2 (Metamask) + else if (message === "vfc" || message === "Vfc") { + ami.action({ + 'action':'originate', + 'channel':'SIP/' + asterisk_callfrom, + 'context': asterisk_context, + 'callerID' : asterisk_callerID, + 'exten' : '832', + 'priority' : '1', + }); + request(vfc_url, (error, res, body) => { + if (error) { + return chalk.red(console.log(error)) + }; + + if (!error && res.statusCode == 200) { + const balance = user_name + " has " + res.body + " VFC"; + xmpp.send(from, balance); + console.log(chalk.yellow(balance)); + } + }); + console.log(chalk.blue('vfc')); + } + + // Get latest toots (RSS) + else if (message === "rss" || message === "Rss") { + (async () => { + let feed = await parser.parseURL(rss_url); + // console.log(feed.title); + feed.items.forEach(item => { + console.log(item.title + ':' + item.link) + xmpp.send(from, item.title) + }); + })(); + } + + // PING + else if (message === "ping" || message === 'Ping') { + xmpp.send(from, "Pinging " + user_name + "! Please stand by."); + xmpp.send(user_jid, from + " has pinged you!"); + client.publish(mqtt_topic, from + ' has pinged you!'); + ami.action({ + 'action':'originate', + 'channel':'SIP/' + asterisk_callfrom, + 'context': asterisk_context, + 'callerID' : asterisk_callerID, + 'data': 'googletts.agi, \"hello. ' + from + ' has pinged you!\"', + 'application': 'agi' + }); + console.log(chalk.blue('ping')); + let guf = `${gotify_url}/message?token=${gotify_api_key}&message=${from} ${gotify_message}&title=${gotify_title}&priority=${gotify_priority}`; + let gufe = encodeURI(guf); + request.post(gufe, function (err, response, body) { + if(err){ + console.log(chalk.red('error:', err)); + } + }); + } + + // WEATHER + else if (message === "weather" || message === 'Weather') { + xmpp.send(from, "Checking weather. Please hold...\r\n \r\n"); + request(weather_url, function (err, response, body) { + if(err){ + console.log(chalk.red('error:', err)); + } else { + let w = JSON.parse(body); + let wm = 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: " + d2d(w.wind.deg) + "\r\n Wind Speed: " + w.wind.speed + " m.p.h. \r\n"; + let wmc = city_name + " Weather: Tempature: " + w.main.temp + " degrees. Conditions: " + w.weather[0].main + ". Pressure: " + + w.main.pressure + ". Wind Direction: " + d2d(w.wind.deg) + ". Wind Speed: " + w.wind.speed + " miles per hour \r\n"; + console.log(chalk.yellow(wm)); + 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', 'd2d(w.wind.deg)'); + client.publish('weather/wind/speed', 'w.wind.speed'); + xmpp.send(from, wm); + /* + ami.action({ + 'action':'originate', + 'channel':'SIP/' + asterisk_callfrom, + 'context': asterisk_context, + 'callerID' : asterisk_callerID, + 'data': 'googletts.agi,' + wmc, + 'application': 'agi' + }); + */ + } + }); + console.log(chalk.blue('weather')); + } + + // BYE + else if (message === 'bye' || message === "Bye" && from != user_jid) { + xmpp.send(from, "Bye, have a beautiful time!"); + if (from === user_jid || message === "!bye") { + xmpp.send(from, "Alrighty, shutting down. Goodbye!"); + console.log(chalk.red('Admin shutdown.')); + process.exit(1) + } + console.log(chalk.blue('bye')); + } + + // VCARD (WIP) + else if (message === 'vcard' && from === user_jid){ + xmpp.getVCard(from, function (vcard) { + xmpp.send(from, vcard); + console.log(chalk.yellow(vcard)) + }) + } + + // AUTO REPLY (ALL OTHER MESSAGES) + else { + xmpp.send(from, "This is an auto replying bot. Reply \"help\" for more info."); + console.log(chalk.blue('autoreply')); + } +}); + +// CATCH XMPP ERRORS +xmpp.on('error', function(err) { +console.error(err); +}); + + +// EXTENDING XEPS (WIP) +/* +xmpp.on('chatstate', function(from, state) { +console.log(chalk.yellow(state)); +}); + +xmpp.on('buddy', function(jid, state, statusText, resource) { +console.log(chalk.yellow(state + statusText + resource)); +}); +*/ + +// CONNECT TO XMPP! +xmpp.connect({ + jid: bot_jid, + password: bot_password, + host: bot_hostname, + port: 5222 +}); + +// CHECK FOR NEW INCOMING REQUESTS +console.log(chalk.yellow(xmpp.getRoster())); diff --git a/package.json b/package.json new file mode 100644 index 0000000..0792ba0 --- /dev/null +++ b/package.json @@ -0,0 +1,49 @@ +{ + "name": "vxmppb", + "description": "vanderwarker.family XMPP Bot", + "version": "0.1.1", + "keywords": [ + "xmpp", + "bot", + "sms", + "autoreply", + "asterisk", + "rss", + "mqtt", + "gotify", + "push notifications" + ], + "maintainers": [ + { + "name": "Stephen Vanderwarker", + "email": "stephen@vanderwarker.family", + "web": "https://stephen.vanderwarker.family" + } + ], + "dependencies": { + "asterisk-manager": "0.1.16", + "chalk": "2.4.2", + "degrees-to-direction": "1.1.1", + "express": "4.17.1", + "ini": "2.0.0", + "mastodon": "1.2.2", + "mqtt": "4.2.6", + "request": "2.88.0", + "rss-parser": "3.12.0", + "simple-xmpp": "2.3.1" + }, + "bugs": { + "url": "https://git.vanderwarker.family/vxmppb/vxmppb/issues" + }, + "repository": { + "type": "git", + "url": "https://git.vanderwarker.family/vxmppb/vxmppb.git" + }, + "license": { + "type": "MIT" + }, + "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" + } +}