Init WIP Bot
This commit is contained in:
47
Client/app.js
Normal file
47
Client/app.js
Normal file
@@ -0,0 +1,47 @@
|
||||
var createError = require('http-errors');
|
||||
var express = require('express');
|
||||
var path = require('path');
|
||||
var cookieParser = require('cookie-parser');
|
||||
var logger = require('morgan');
|
||||
|
||||
var indexRouter = require('./routes/index');
|
||||
var botController = require('./routes/bot');
|
||||
var clientController = require('./routes/client');
|
||||
|
||||
var app = express();
|
||||
|
||||
// view engine setup
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
app.set('view engine', 'ejs');
|
||||
|
||||
app.use(logger('dev'));
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: false }));
|
||||
app.use(cookieParser());
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
app.use('/', indexRouter);
|
||||
|
||||
// Discord bot control route
|
||||
app.use('/bot', botController);
|
||||
|
||||
// Local client control route
|
||||
app.use("/client", clientController);
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use(function(req, res, next) {
|
||||
next(createError(404));
|
||||
});
|
||||
|
||||
// error handler
|
||||
app.use(function(err, req, res, next) {
|
||||
// set locals, only providing error in development
|
||||
res.locals.message = err.message;
|
||||
res.locals.error = req.app.get('env') === 'development' ? err : {};
|
||||
|
||||
// render the error page
|
||||
res.status(err.status || 500);
|
||||
res.render('error');
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
74
Client/bin/www
Normal file
74
Client/bin/www
Normal file
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const app = require('../app');
|
||||
const debug = require('debug')('client:server');
|
||||
const http = require('http');
|
||||
const config = require('../config/clientConfig');
|
||||
const clientController = require('../controllers/clientController');
|
||||
|
||||
/**
|
||||
* Get port from environment and store in Express.
|
||||
*/
|
||||
|
||||
app.set('port', config.clientConfig.port);
|
||||
|
||||
/**
|
||||
* Create HTTP server.
|
||||
*/
|
||||
|
||||
const server = http.createServer(app);
|
||||
|
||||
/**
|
||||
* Listen on provided port, on all network interfaces.
|
||||
*/
|
||||
|
||||
server.listen(config.clientConfig.port);
|
||||
server.on('error', onError);
|
||||
server.on('listening', onListening);
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "error" event.
|
||||
*/
|
||||
|
||||
function onError(error) {
|
||||
if (error.syscall !== 'listen') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const bind = typeof port === 'string'
|
||||
? 'Pipe ' + port
|
||||
: 'Port ' + port;
|
||||
|
||||
// handle specific listen errors with friendly messages
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
console.error(bind + ' requires elevated privileges');
|
||||
process.exit(1);
|
||||
break;
|
||||
case 'EADDRINUSE':
|
||||
console.error(bind + ' is already in use');
|
||||
process.exit(1);
|
||||
break;
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "listening" event.
|
||||
*/
|
||||
|
||||
function onListening() {
|
||||
const addr = server.address();
|
||||
const bind = typeof addr === 'string'
|
||||
? 'pipe ' + addr
|
||||
: 'port ' + addr.port;
|
||||
debug('Listening on ' + bind);
|
||||
|
||||
// check in with the server to add this node or come back online
|
||||
clientController.checkIn();
|
||||
}
|
||||
17
Client/config/clientConfig.js
Normal file
17
Client/config/clientConfig.js
Normal file
@@ -0,0 +1,17 @@
|
||||
// Core config settings for the node, these are the settings that are checked with the server
|
||||
exports.clientConfig = {
|
||||
"id": 13,
|
||||
"name": "boilin balls in the hall",
|
||||
"ip": "172.16.100.150",
|
||||
"port": 3001,
|
||||
"location": "the house",
|
||||
"nearbySystems": ["Westchester Cty. Simulcast"],
|
||||
"online": true
|
||||
}
|
||||
|
||||
// Configuration for the connection to the server
|
||||
exports.serverConfig = {
|
||||
"ip": "127.0.0.1",
|
||||
"hostname": "localhost",
|
||||
"port": 3000
|
||||
}
|
||||
16
Client/config/core.js.default
Normal file
16
Client/config/core.js.default
Normal file
@@ -0,0 +1,16 @@
|
||||
// Core config settings for the node, these are the settings that are checked with the server
|
||||
exports.nodeConfig = {
|
||||
"id": 0,
|
||||
"name": "",
|
||||
"ip": "",
|
||||
"port": 0,
|
||||
"location": "",
|
||||
"nearbySystems": {
|
||||
"System Name": {
|
||||
"frequencies": [],
|
||||
"mode": "",
|
||||
"trunkFile": ""
|
||||
}
|
||||
},
|
||||
"online": false
|
||||
}
|
||||
3
Client/config/modes.js
Normal file
3
Client/config/modes.js
Normal file
@@ -0,0 +1,3 @@
|
||||
exports.digitalModes = ["p25"];
|
||||
|
||||
exports.analogModes = ["nbfm"];
|
||||
1
Client/config/radioPresets.json
Normal file
1
Client/config/radioPresets.json
Normal file
@@ -0,0 +1 @@
|
||||
{"Westchester Cty. Simulcast":{"frequencies":[470575000,470375000,470525000,470575000,470550000],"mode":"p25","trunkFile":"trunk.tsv"}}
|
||||
0
Client/controllers/botController.js
Normal file
0
Client/controllers/botController.js
Normal file
101
Client/controllers/clientController.js
Normal file
101
Client/controllers/clientController.js
Normal file
@@ -0,0 +1,101 @@
|
||||
// Debug output
|
||||
const debug = require('debug')('client:clientController');
|
||||
// Configs
|
||||
const config = require("../config/clientConfig");
|
||||
const modes = require("../config/modes");
|
||||
// Utilities
|
||||
const updateConfig = require("../utilities/updateConfig");
|
||||
const updatePreset = require("../utilities/updatePresets");
|
||||
const requests = require("../utilities/httpRequests");
|
||||
|
||||
/**
|
||||
* Check the body for the required fields to update or add a preset
|
||||
* @param req Express req from the endpoint controller
|
||||
* @param res Express res from the endpoint controller
|
||||
* @param callback The callback function to call when this function completes
|
||||
* @returns {*}
|
||||
*/
|
||||
function checkBodyForPresetFields(req, res, callback) {
|
||||
if (!req.body?.systemName) return res.status(403).json({"message": "No system in the request"});
|
||||
if (!req.body?.frequencies && Array.isArray(req.body.frequencies)) return res.status(403).json({"message": "No frequencies in the request or type is not an array"});
|
||||
if (!req.body?.mode && typeof req.body.mode === "string") return res.status(403).json({"message": "No mode in the request"});
|
||||
if (!req.body?.trunkFile) {
|
||||
if (modes.digitalModes.includes(req.body.mode)) return res.status(403).json({"message": "No trunk file in the request but digital mode specified. If you are not using a trunk file for this frequency make sure to specify 'none' for trunk file in the request"})
|
||||
// If there is a value keep it but if not, add nothing so the system can update that key (if needed)
|
||||
req.body.trunkFile = req.body.trunkFile ?? "none";
|
||||
}
|
||||
|
||||
return callback();
|
||||
}
|
||||
|
||||
/** Check in with the server
|
||||
* If the bot has a saved ID, check in with the server to update any information or just check back in
|
||||
* If the bot does not have a saved ID, it will attempt to request a new ID from the server
|
||||
*/
|
||||
exports.checkIn = async () => {
|
||||
let reqOptions;
|
||||
// Check if there is an ID found, if not add the node to the server. If there was an ID, check in with the server to make sure it has the correct information
|
||||
if (config.clientConfig.id === 0) {
|
||||
// ID was not found in the config, creating a new node
|
||||
reqOptions = new requests.requestOptions("/nodes/newNode", "POST");
|
||||
delete config.clientConfig.id;
|
||||
requests.sendHttpRequest(reqOptions, JSON.stringify(config.clientConfig), (responseObject) => {
|
||||
// Update the client's ID if the server accepted it
|
||||
if (responseObject.statusCode === 202) {
|
||||
config.clientConfig.id = responseObject.body.nodeId;
|
||||
updateConfig.updateId(responseObject.body.nodeId);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
// ID is in the config, checking in with the server
|
||||
reqOptions = new requests.requestOptions("/nodes/nodeCheckIn", "POST");
|
||||
requests.sendHttpRequest(reqOptions, JSON.stringify(config.clientConfig), (responseObject) => {
|
||||
if (responseObject.statusCode === 202) {
|
||||
// Server accepted an update
|
||||
}
|
||||
if (responseObject.statusCode === 200) {
|
||||
// Server accepted the response but there were no keys to be updated
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Controller for the /client/requestCheckIn endpoint
|
||||
* This is the endpoint wrapper to queue a check in
|
||||
*/
|
||||
exports.requestCheckIn = async (req, res) => {
|
||||
this.checkIn();
|
||||
return res.sendStatus(200);
|
||||
}
|
||||
|
||||
/** Controller for the /client/presets endpoint
|
||||
* This is the endpoint wrapper to get the presets object
|
||||
*/
|
||||
exports.getPresets = async (req, res) => {
|
||||
return res.status(200).json(updatePreset.getPresets());
|
||||
}
|
||||
|
||||
/** Controller for the /client/updatePreset endpoint
|
||||
* This is the endpoint wrapper to update the selected preset (must include the whole object for that preset otherwise it will be rejected)
|
||||
*/
|
||||
exports.updatePreset = async (req, res) => {
|
||||
checkBodyForPresetFields(req, res, () => {
|
||||
updatePreset.updatePreset(req.body.systemName, () => {
|
||||
return res.sendStatus(200);
|
||||
}, {frequencies: req.body.frequencies, mode: req.body.mode, trunkFile: req.body.trunkFile});
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new preset to the client
|
||||
*/
|
||||
exports.addNewPreset = async (req, res) => {
|
||||
checkBodyForPresetFields(req, res, () => {
|
||||
updatePreset.addNewPreset(req.body.systemName, req.body.frequencies, req.body.mode, () => {
|
||||
return res.sendStatus(200);
|
||||
}, req.body.trunkFile);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
69
Client/discord-bot/app.js
Normal file
69
Client/discord-bot/app.js
Normal file
@@ -0,0 +1,69 @@
|
||||
//Config
|
||||
import { getTOKEN, getGuildID, getApplicationID } from './utilities/configHandler.js';
|
||||
// Commands
|
||||
import ping from './controllers/ping.js';
|
||||
import { join, leave } from './controllers/voiceController.js';
|
||||
// Debug
|
||||
import Debug from 'debug';
|
||||
const debug = Debug("bot:app");
|
||||
// Modules
|
||||
import { Client, GatewayIntentBits } from 'discord.js';
|
||||
// Utilities
|
||||
import registerCommands from './utilities/registerCommands.js';
|
||||
|
||||
// Create the Discord client
|
||||
const client = new Client({
|
||||
intents: [
|
||||
GatewayIntentBits.Guilds,
|
||||
GatewayIntentBits.GuildMessages,
|
||||
GatewayIntentBits.MessageContent,
|
||||
GatewayIntentBits.GuildVoiceStates
|
||||
]
|
||||
});
|
||||
|
||||
// When the client is connected and ready
|
||||
client.on('ready', () =>{
|
||||
debug(`${client.user.tag} is ready`)
|
||||
console.log(`${client.user.tag} is ready`)
|
||||
});
|
||||
|
||||
/*
|
||||
* Saved For later
|
||||
client.on('messageCreate', (message) => {
|
||||
debug(`Message Sent by: ${message.author.tag}\n\t'${message.content}'`);
|
||||
console.log(`Message Sent by: ${message.author.tag}\n\t'${message.content}'`);
|
||||
});
|
||||
*/
|
||||
|
||||
// When a command is sent
|
||||
client.on('interactionCreate', (interaction) => {
|
||||
if (interaction.isChatInputCommand()){
|
||||
switch (interaction.commandName) {
|
||||
case "ping":
|
||||
ping(interaction);
|
||||
break;
|
||||
case "join":
|
||||
join(interaction);
|
||||
break;
|
||||
case "leave":
|
||||
leave(interaction);
|
||||
break;
|
||||
default:
|
||||
interaction.reply({ content: 'Command not found, try one that exists', fetchReply: true })
|
||||
.then((message) => console.log(`Reply sent with content ${message.content}`))
|
||||
.catch(console.error);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function loginBot(){
|
||||
client.login(getTOKEN());
|
||||
}
|
||||
|
||||
function main(){
|
||||
registerCommands(() => {
|
||||
loginBot();
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
7
Client/discord-bot/config/botConfig.json
Normal file
7
Client/discord-bot/config/botConfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"TOKEN": "OTQzNzQyMDQwMjU1MTE1MzA0.Yg3eRA.ZxEbRr55xahjfaUmPY8pmS-RHTY",
|
||||
"ApplicationID": "943742040255115304",
|
||||
"GuildID": "367396189529833472",
|
||||
"DeviceID": "25",
|
||||
"DeviceName": "VoiceMeeter Aux Output (VB-Audio VoiceMeeter AUX VAIO)"
|
||||
}
|
||||
37
Client/discord-bot/controllers/audioController.js
Normal file
37
Client/discord-bot/controllers/audioController.js
Normal file
@@ -0,0 +1,37 @@
|
||||
// Config
|
||||
import { getDeviceID, getDeviceName } from '../utilities/configHandler.js'
|
||||
// Modules
|
||||
import portAudio from 'naudiodon';
|
||||
import {createAudioResource} from "@discordjs/voice";
|
||||
|
||||
export function getAudioDevice({deviceName = undefined, deviceId = undefined}){
|
||||
const deviceList = getAudioDevices();
|
||||
if (!deviceName && !deviceId) throw new Error("No device given");
|
||||
if (deviceName) return deviceList.find(device => device.name === deviceName);
|
||||
if (deviceId) return deviceList.find(device => device.id === deviceId);
|
||||
}
|
||||
|
||||
export function getAudioDevices(){
|
||||
const deviceList = portAudio.getDevices();
|
||||
console.log("Devices:", deviceList);
|
||||
return deviceList;
|
||||
}
|
||||
|
||||
export function createAudioInstance() {
|
||||
//const resource = createAudioResource();
|
||||
const selectedDevice = getAudioDevice({deviceId: getDeviceID()});//{deviceName: "VoiceMeeter VAIO3 Output (VB-Au"});
|
||||
console.log(selectedDevice);
|
||||
// Create an instance of AudioIO with outOptions (defaults are as below), which will return a WritableStream
|
||||
const audioInstance = new portAudio.AudioIO({
|
||||
inOptions: {
|
||||
channelCount: 2,
|
||||
sampleFormat: portAudio.SampleFormat16Bit,
|
||||
sampleRate: 44100,
|
||||
deviceId: selectedDevice.id, // Use -1 or omit the deviceId to select the default device
|
||||
closeOnError: false, // Close the stream if an audio error is detected, if set false then just log the error
|
||||
framesPerBuffer: 0 // 44100 / 1000 * 120 / 2 // Get 120ms of audio
|
||||
}
|
||||
});
|
||||
//audioInstance.start();
|
||||
return audioInstance;
|
||||
}
|
||||
6
Client/discord-bot/controllers/ping.js
Normal file
6
Client/discord-bot/controllers/ping.js
Normal file
@@ -0,0 +1,6 @@
|
||||
// Utilities
|
||||
import { replyToInteraction } from '../utilities/messageHandler.js';
|
||||
|
||||
export default function ping(interaction) {
|
||||
return replyToInteraction(interaction, "Pong! I have Aids and now you do too!");
|
||||
}
|
||||
62
Client/discord-bot/controllers/voiceController.js
Normal file
62
Client/discord-bot/controllers/voiceController.js
Normal file
@@ -0,0 +1,62 @@
|
||||
// Modules
|
||||
import {
|
||||
joinVoiceChannel,
|
||||
getVoiceConnection,
|
||||
createAudioResource,
|
||||
createAudioPlayer,
|
||||
NoSubscriberBehavior, StreamType
|
||||
} from '@discordjs/voice';
|
||||
import OpusEncoderPkg from "@discordjs/opus";
|
||||
const { OpusEncoder } = OpusEncoderPkg;
|
||||
// Utilities
|
||||
import { replyToInteraction } from '../utilities/messageHandler.js';
|
||||
import { createAudioInstance } from "./audioController.js";
|
||||
|
||||
|
||||
/**
|
||||
* Join the specified voice channel
|
||||
*
|
||||
* @param interaction Message interaction from discord
|
||||
*/
|
||||
export function join(interaction){
|
||||
const voiceChannel = interaction.options.getChannel('voicechannel');
|
||||
const voiceConnection = joinVoiceChannel({
|
||||
channelId: voiceChannel.id,
|
||||
guildId: interaction.guildId,
|
||||
adapterCreator: interaction.guild.voiceAdapterCreator,
|
||||
selfMute: false,
|
||||
selfDeaf: false,
|
||||
});
|
||||
replyToInteraction(interaction, `Ok, Joining ${voiceChannel.name}`);
|
||||
|
||||
// Declare the encoder
|
||||
const encoder = new OpusEncoder(44100, 2);
|
||||
|
||||
const player = createAudioPlayer({
|
||||
behaviors: {
|
||||
noSubscriber: NoSubscriberBehavior.Play,
|
||||
},
|
||||
});
|
||||
|
||||
const audioInstance = createAudioInstance();
|
||||
const audioResource = createAudioResource(audioInstance, { inputType: StreamType.Raw });
|
||||
audioInstance.start();
|
||||
//audioInstance.on('data', buffer => {
|
||||
// Do on buffer event
|
||||
//})
|
||||
player.play(audioResource);
|
||||
voiceConnection.subscribe(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* If in a voice channel for the specified guild, leave
|
||||
*
|
||||
* @param interaction Message interaction from discord
|
||||
*/
|
||||
export function leave(interaction){
|
||||
const guildId = interaction.guild.id;
|
||||
const voiceConnection = getVoiceConnection(guildId);
|
||||
if (!voiceConnection) return replyToInteraction(interaction, "Not in a voice channel.");
|
||||
voiceConnection.destroy();
|
||||
return replyToInteraction(interaction, `Goodbye`);
|
||||
}
|
||||
2907
Client/discord-bot/package-lock.json
generated
Normal file
2907
Client/discord-bot/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
25
Client/discord-bot/package.json
Normal file
25
Client/discord-bot/package.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "discord-bot",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@discordjs/builders": "^1.4.0",
|
||||
"@discordjs/opus": "^0.9.0",
|
||||
"@discordjs/rest": "^1.4.0",
|
||||
"@discordjs/voice": "^0.14.0",
|
||||
"@mapbox/node-pre-gyp": "^1.0.10",
|
||||
"debug": "^4.3.4",
|
||||
"discord.js": "^14.7.1",
|
||||
"naudiodon": "^2.3.6",
|
||||
"node-gyp": "^9.3.0",
|
||||
"libsodium-wrappers": "^0.7.10"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
36
Client/discord-bot/utilities/configHandler.js
Normal file
36
Client/discord-bot/utilities/configHandler.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
export function getConfig() {
|
||||
return JSON.parse(readFileSync("./config/botConfig.json"));
|
||||
}
|
||||
|
||||
export function getTOKEN() {
|
||||
const parsedJSON = getConfig();
|
||||
return parsedJSON.TOKEN;
|
||||
}
|
||||
|
||||
export function getGuildID() {
|
||||
const parsedJSON = getConfig();
|
||||
parsedJSON.GuildID = BigInt(parsedJSON.GuildID);
|
||||
//console.log("Guild ID: ", parsedJSON.GuildID);
|
||||
return parsedJSON.GuildID;
|
||||
}
|
||||
|
||||
export function getApplicationID() {
|
||||
const parsedJSON = getConfig();
|
||||
parsedJSON.ApplicationID = BigInt(parsedJSON.ApplicationID);
|
||||
//console.log("Application ID: ", parsedJSON.ApplicationID);
|
||||
return parsedJSON.ApplicationID;
|
||||
}
|
||||
|
||||
export function getDeviceID(){
|
||||
const parsedJSON = getConfig();
|
||||
//console.log("Device ID: ", parseInt(parsedJSON.DeviceID));
|
||||
return parseInt(parsedJSON.DeviceID);
|
||||
}
|
||||
|
||||
export function getDeviceName(){
|
||||
const parsedJSON = getConfig();
|
||||
//console.log("Device Name: ", parseInt(parsedJSON.DeviceName));
|
||||
return parsedJSON.DeviceName;
|
||||
}
|
||||
5
Client/discord-bot/utilities/messageHandler.js
Normal file
5
Client/discord-bot/utilities/messageHandler.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export function replyToInteraction(interaction, message){
|
||||
interaction.reply({ content: message, fetchReply: true })
|
||||
.then((message) => console.log(`Reply sent with content ${message.content}`))
|
||||
.catch(console.error);
|
||||
}
|
||||
45
Client/discord-bot/utilities/registerCommands.js
Normal file
45
Client/discord-bot/utilities/registerCommands.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import {SlashCommandBuilder} from "@discordjs/builders";
|
||||
import {REST} from "@discordjs/rest";
|
||||
import {getApplicationID, getGuildID, getTOKEN} from "./configHandler.js";
|
||||
import { Routes, ChannelType } from "discord.js";
|
||||
|
||||
const pingCommand = new SlashCommandBuilder()
|
||||
.setName("ping")
|
||||
.setDescription("Confirm the bot is online")
|
||||
.toJSON();
|
||||
|
||||
const joinCommand = new SlashCommandBuilder()
|
||||
.setName('join')
|
||||
.setDescription('Joins a voice channel')
|
||||
.addChannelOption((option) => option
|
||||
.setName('voicechannel')
|
||||
.setDescription('The Channel to voiceController')
|
||||
.setRequired(false)
|
||||
.addChannelTypes(ChannelType.GuildVoice))
|
||||
.toJSON();
|
||||
|
||||
const leaveCommand = new SlashCommandBuilder()
|
||||
.setName("leave")
|
||||
.setDescription("Leave current voice channel")
|
||||
.toJSON();
|
||||
|
||||
export default async function registerCommands(callback){
|
||||
const commands = [
|
||||
pingCommand,
|
||||
joinCommand,
|
||||
leaveCommand
|
||||
];
|
||||
|
||||
try {
|
||||
const rest = new REST({ version: '10' }).setToken(getTOKEN());
|
||||
const clientID = getApplicationID();
|
||||
const guildID = getGuildID();
|
||||
|
||||
await rest.put(Routes.applicationGuildCommands(clientID, guildID), {
|
||||
body: commands,
|
||||
});
|
||||
callback();
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
1448
Client/package-lock.json
generated
Normal file
1448
Client/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
Client/package.json
Normal file
17
Client/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "client",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node ./bin/www"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie-parser": "~1.4.4",
|
||||
"debug": "~2.6.9",
|
||||
"ejs": "~2.6.1",
|
||||
"express": "~4.16.1",
|
||||
"http-errors": "~1.6.3",
|
||||
"morgan": "~1.9.1",
|
||||
"replace-in-file": "~6.3.5"
|
||||
}
|
||||
}
|
||||
8
Client/public/stylesheets/style.css
Normal file
8
Client/public/stylesheets/style.css
Normal file
@@ -0,0 +1,8 @@
|
||||
body {
|
||||
padding: 50px;
|
||||
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00B7FF;
|
||||
}
|
||||
39
Client/routes/bot.js
Normal file
39
Client/routes/bot.js
Normal file
@@ -0,0 +1,39 @@
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
/* GET users listing.
|
||||
router.get('/', function(req, res, next) {
|
||||
res.send('respond with a resource');
|
||||
});
|
||||
*/
|
||||
|
||||
/** GET bot status
|
||||
* Check to see if the bot is online and if so, if it is currently connected to anything
|
||||
*
|
||||
* The status of the bot: 200 = online, 202 = connected, 500 + JSON = encountered error
|
||||
* @returns status
|
||||
*/
|
||||
|
||||
/** POST bot join channel
|
||||
* Join the channel specified listening to the specified freq/mode
|
||||
*
|
||||
* @param req The request sent from the master
|
||||
* @param req.body.channelId The channel ID to join
|
||||
* @param req.body.presetName The name of the preset to start listening to
|
||||
*/
|
||||
|
||||
/** POST bot leave channel
|
||||
* Will leave the channel it is currently listening to if any, otherwise it will just return that it is not connected
|
||||
*
|
||||
* The status of the bot: 200 = no change, 202 = changed successfully, 500 + JSON = encountered error
|
||||
* @returns status
|
||||
*/
|
||||
|
||||
/** POST change bot preset
|
||||
* This will change the bot to the preset specified (if different from what is currently playing)
|
||||
*
|
||||
* The status of the bot: 200 = no change, 202 = changed successfully, 500 + JSON = encountered error
|
||||
* @returns status
|
||||
*/
|
||||
|
||||
module.exports = router;
|
||||
41
Client/routes/client.js
Normal file
41
Client/routes/client.js
Normal file
@@ -0,0 +1,41 @@
|
||||
// Modules
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
// Controllers
|
||||
const clientController = require("../controllers/clientController");
|
||||
|
||||
/** GET Request a check in from the client
|
||||
* Queue the client to check in with the server
|
||||
*
|
||||
* The status of the checkin request: 200 = Queued
|
||||
*/
|
||||
router.get('/requestCheckIn', clientController.requestCheckIn);
|
||||
|
||||
/** GET Object of all known presets
|
||||
* Query the client to get all the known presets
|
||||
*/
|
||||
router.get('/presets', clientController.getPresets);
|
||||
|
||||
/** POST Update to preset
|
||||
* Join the channel specified listening to the specified freq/mode
|
||||
*
|
||||
* @param req The request sent from the master
|
||||
* @param {string} req.body.systemName The name of the system to be updated
|
||||
* @param {Array} req.body.frequencies The frequencies array for the channel or channels to be listened to
|
||||
* @param {string} req.body.mode The listening mode for the SDR
|
||||
* @param {string} req.body.trunkFile If the listening mode is digital this can be set to identify the communications
|
||||
*/
|
||||
router.post('/updatePreset', clientController.updatePreset);
|
||||
|
||||
/** POST Add new preset
|
||||
* Join the channel specified listening to the specified freq/mode
|
||||
*
|
||||
* @param req The request sent from the master
|
||||
* @param {string} req.body.systemName The name of the system to be updated
|
||||
* @param {Array} req.body.frequencies The frequencies array for the channel or channels to be listened to
|
||||
* @param {string} req.body.mode The listening mode for the SDR
|
||||
* @param {string} req.body.trunkFile If the listening mode is digital this can be set to identify the communications
|
||||
*/
|
||||
router.post('/addPreset', clientController.addNewPreset);
|
||||
|
||||
module.exports = router;
|
||||
9
Client/routes/index.js
Normal file
9
Client/routes/index.js
Normal file
@@ -0,0 +1,9 @@
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
/* GET home page. */
|
||||
router.get('/', function(req, res, next) {
|
||||
res.render('index', { title: 'Express' });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
48
Client/utilities/httpRequests.js
Normal file
48
Client/utilities/httpRequests.js
Normal file
@@ -0,0 +1,48 @@
|
||||
// Debug
|
||||
const debug = require('debug')('client:httpRequests');
|
||||
// Config
|
||||
const config = require("../config/clientConfig");
|
||||
// Modules
|
||||
const http = require("http");
|
||||
|
||||
exports.requestOptions = class requestOptions {
|
||||
constructor(path, method, hostname = undefined, headers = undefined, port = undefined) {
|
||||
if (method === "POST"){
|
||||
this.hostname = hostname ?? config.serverConfig.hostname
|
||||
this.path = path
|
||||
this.port = port ?? config.serverConfig.port
|
||||
this.method = method
|
||||
this.headers = headers ?? {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the HTTP request to the server
|
||||
* @param requestOptions
|
||||
* @param data
|
||||
* @param callback
|
||||
*/
|
||||
exports.sendHttpRequest = function sendHttpRequest(requestOptions, data, callback){
|
||||
debug("Sending a request to: ", requestOptions.hostname, requestOptions.port)
|
||||
// Create the request
|
||||
const req = http.request(requestOptions, res => {
|
||||
res.on('data', (data) => {
|
||||
const responseObject = {
|
||||
"statusCode": res.statusCode,
|
||||
"body": JSON.parse(data)
|
||||
};
|
||||
debug("Response Object: ", responseObject);
|
||||
callback(responseObject);
|
||||
})
|
||||
}).on('error', err => {
|
||||
debug('Error: ', err.message)
|
||||
// TODO need to handle if the server is down
|
||||
})
|
||||
|
||||
// Write the data to the request and send it
|
||||
req.write(data)
|
||||
req.end()
|
||||
}
|
||||
41
Client/utilities/updateConfig.js
Normal file
41
Client/utilities/updateConfig.js
Normal file
@@ -0,0 +1,41 @@
|
||||
// Debug
|
||||
const debug = require('debug')('client:updateConfig');
|
||||
// Modules
|
||||
const replace = require('replace-in-file');
|
||||
|
||||
class Options {
|
||||
constructor(key, updatedValue) {
|
||||
this.files = "./config/clientConfig.js";
|
||||
// A regex of the line containing the key in the config file
|
||||
this.from = new RegExp(`"${key}": (.+),`, "g");
|
||||
// Check to see if the value is a string and needs to be wrapped in double quotes
|
||||
if (typeof updatedValue === "string") this.to = `"${key}": "${updatedValue}",`;
|
||||
else this.to = `"${key}": ${updatedValue},`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper to update the client's saved ID
|
||||
* @param updatedId The updated ID assigned to the bot
|
||||
*/
|
||||
exports.updateId = (updatedId) => {
|
||||
const options = new Options("id", updatedId);
|
||||
|
||||
updateConfigFile(options, (updatedFiles) => {
|
||||
// Do Something
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper to write changes to the file
|
||||
* @param options An instance of the Objects class specified to the key being updated
|
||||
* @param callback Callback when the files have been modified
|
||||
*/
|
||||
function updateConfigFile(options, callback){
|
||||
replace(options, (error, changedFiles) => {
|
||||
if (error) return console.error('Error occurred:', error);
|
||||
debug('Modified files:', changedFiles);
|
||||
callback(changedFiles);
|
||||
});
|
||||
}
|
||||
125
Client/utilities/updatePresets.js
Normal file
125
Client/utilities/updatePresets.js
Normal file
@@ -0,0 +1,125 @@
|
||||
// Debug
|
||||
const debug = require('debug')('client:updatePresets');
|
||||
// Modules
|
||||
const fs = require('fs');
|
||||
|
||||
/**
|
||||
* Write the given presets to the JSON file
|
||||
* @param presets The preset or presets to be written
|
||||
* @param {function} callback The function to be called when this wrapper completes
|
||||
*/
|
||||
function writePresets(presets, callback = undefined) {
|
||||
fs.writeFile("../config/radioPresets.json", JSON.stringify(presets), (err) => {
|
||||
// Error checking
|
||||
if (err) throw err;
|
||||
debug("Write Complete");
|
||||
if (callback) callback()
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper to ensure each value in the array is in Hz format
|
||||
* @param frequenciesArray
|
||||
* @returns {*[]}
|
||||
*/
|
||||
function sanitizeFrequencies(frequenciesArray) {
|
||||
let sanitizedFrequencyArray = [];
|
||||
|
||||
for (const freq of frequenciesArray) {
|
||||
sanitizedFrequencyArray.push(convertFrequencyToHertz(freq));
|
||||
}
|
||||
|
||||
debug(sanitizedFrequencyArray);
|
||||
return sanitizedFrequencyArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to convert a string or a float into the integer type needed to be saved
|
||||
* @param frequency Could be a string, number or float,
|
||||
* @returns {number|number|*} Return the value to be saved in Hz format ("154875000" = 154.875MHz)
|
||||
*/
|
||||
function convertFrequencyToHertz(frequency){
|
||||
// check if the passed value is a number
|
||||
if(typeof frequency == 'number' && !isNaN(frequency)){
|
||||
if (Number.isInteger(frequency)) {
|
||||
debug(`${frequency} is integer.`);
|
||||
// Check to see if the frequency has the correct length
|
||||
if (frequency.toString().length >= 7 && frequency.toString().length <= 9) return frequency
|
||||
}
|
||||
else {
|
||||
debug(`${frequency} is a float value.`);
|
||||
// Convert to a string to remove the decimal in place and then correct the length
|
||||
frequency = frequency.toString();
|
||||
// One extra digit checked for the '.' included in the string
|
||||
if (frequency.length >= 8 && frequency.length <= 10) return parseInt(frequency.replace(".", ""));
|
||||
else if (frequency.length <= 7) {
|
||||
// Check to see if the frequency is 1s, 10s or 100s of MHz
|
||||
let zerosToBeRemoved = 3 - frequency.split(".")[0].length;
|
||||
// Need to add more 0s since it was in MHz format
|
||||
let neededZeros = (9 - frequency.length) - zerosToBeRemoved;
|
||||
frequency = frequency.replace(".", "") + '0'.repeat(neededZeros)
|
||||
|
||||
return parseInt(frequency);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug(`${frequency} is not a number`);
|
||||
frequency = convertFrequencyToHertz(parseFloat(frequency));
|
||||
|
||||
return parseInt(frequency)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the saved presets and returns a preset object
|
||||
* @returns {any} The object containing the different systems the bot is near
|
||||
*/
|
||||
exports.getPresets = function getPresets() {
|
||||
return JSON.parse(fs.readFileSync("../config/radioPresets.json"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new preset to the radioPresets JSON file
|
||||
*
|
||||
* @param {string} systemName The name of the system being added
|
||||
* @param {Array} frequencies The frequency or frequencies the SDR should tune to for this system
|
||||
* @param {string} mode The listening mode the SDR should be using when listening to this frequency
|
||||
* @param {function} callback The callback function to call when completed
|
||||
* @param {string} trunkFile The file that contains all trunking information (if applicable to the selected listening mode)
|
||||
*/
|
||||
exports.addNewPreset = (systemName, frequencies, mode, callback, trunkFile = undefined) => {
|
||||
const presets = this.getPresets();
|
||||
// Create the preset for the new system
|
||||
presets[systemName] = {
|
||||
"frequencies": sanitizeFrequencies(frequencies),
|
||||
"mode": mode,
|
||||
"trunkFile": trunkFile ?? "none"
|
||||
}
|
||||
// Write the changes to the preset config file
|
||||
writePresets(presets, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the specified system
|
||||
*
|
||||
* @param {string} systemName The name of the system being modified
|
||||
* @param {function} callback The callback function to be called when the function completes
|
||||
* @param {Array} frequencies The frequency or frequencies the SDR should tune to for this system
|
||||
* @param {string} mode The listening mode the SDR should be using when listening to this frequency
|
||||
* @param {string} trunkFile The file that contains all trunking information (if applicable to the selected listening mode)
|
||||
*/
|
||||
exports.updatePreset = (systemName, callback, { frequencies = undefined, mode = undefined, trunkFile = undefined }) => {
|
||||
const presets = this.getPresets();
|
||||
// Check if a system name was passed
|
||||
if (systemName in presets) {
|
||||
// System name exists, checking to see if the keys are different
|
||||
if(frequencies && frequencies !== presets[systemName].frequencies) presets[systemName].frequencies = sanitizeFrequencies(frequencies);
|
||||
if(mode && mode !== presets[systemName].mode) presets[systemName].mode = mode;
|
||||
if(trunkFile && trunkFile !== presets[systemName].trunkFile || trunkFile === "") presets[systemName].trunkFile = trunkFile ?? "none";
|
||||
// Write the changes
|
||||
writePresets(presets, callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
3
Client/views/error.ejs
Normal file
3
Client/views/error.ejs
Normal file
@@ -0,0 +1,3 @@
|
||||
<h1><%= message %></h1>
|
||||
<h2><%= error.status %></h2>
|
||||
<pre><%= error.stack %></pre>
|
||||
11
Client/views/index.ejs
Normal file
11
Client/views/index.ejs
Normal file
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title><%= title %></title>
|
||||
<link rel='stylesheet' href='/stylesheets/style.css' />
|
||||
</head>
|
||||
<body>
|
||||
<h1><%= title %></h1>
|
||||
<p>Welcome to <%= title %></p>
|
||||
</body>
|
||||
</html>
|
||||
48
Server/app.js
Normal file
48
Server/app.js
Normal file
@@ -0,0 +1,48 @@
|
||||
var createError = require('http-errors');
|
||||
var express = require('express');
|
||||
var path = require('path');
|
||||
var cookieParser = require('cookie-parser');
|
||||
var logger = require('morgan');
|
||||
|
||||
var indexRouter = require('./routes/index');
|
||||
var nodesRouter = require('./routes/nodes');
|
||||
var adminRouter = require('./routes/admin');
|
||||
|
||||
var app = express();
|
||||
|
||||
// view engine setup
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
app.set('view engine', 'ejs');
|
||||
|
||||
app.use(logger('dev'));
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: false }));
|
||||
app.use(cookieParser());
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
// Web Interface
|
||||
app.use('/', indexRouter);
|
||||
|
||||
// Nodes API
|
||||
app.use('/nodes', nodesRouter);
|
||||
|
||||
// Admin API
|
||||
app.use('/admin', adminRouter);
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use((req, res, next) => {
|
||||
next(createError(404));
|
||||
});
|
||||
|
||||
// error handler
|
||||
app.use((err, req, res, next) => {
|
||||
// set locals, only providing error in development
|
||||
res.locals.message = err.message;
|
||||
res.locals.error = req.app.get('env') === 'development' ? err : {};
|
||||
|
||||
// render the error page
|
||||
res.status(err.status || 500);
|
||||
res.render('error');
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
90
Server/bin/www
Normal file
90
Server/bin/www
Normal file
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var app = require('../app');
|
||||
var debug = require('debug')('server:server');
|
||||
var http = require('http');
|
||||
|
||||
/**
|
||||
* Get port from environment and store in Express.
|
||||
*/
|
||||
|
||||
var port = normalizePort(process.env.PORT || '3000');
|
||||
app.set('port', port);
|
||||
|
||||
/**
|
||||
* Create HTTP server.
|
||||
*/
|
||||
|
||||
var server = http.createServer(app);
|
||||
|
||||
/**
|
||||
* Listen on provided port, on all network interfaces.
|
||||
*/
|
||||
|
||||
server.listen(port);
|
||||
server.on('error', onError);
|
||||
server.on('listening', onListening);
|
||||
|
||||
/**
|
||||
* Normalize a port into a number, string, or false.
|
||||
*/
|
||||
|
||||
function normalizePort(val) {
|
||||
var port = parseInt(val, 10);
|
||||
|
||||
if (isNaN(port)) {
|
||||
// named pipe
|
||||
return val;
|
||||
}
|
||||
|
||||
if (port >= 0) {
|
||||
// port number
|
||||
return port;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "error" event.
|
||||
*/
|
||||
|
||||
function onError(error) {
|
||||
if (error.syscall !== 'listen') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
var bind = typeof port === 'string'
|
||||
? 'Pipe ' + port
|
||||
: 'Port ' + port;
|
||||
|
||||
// handle specific listen errors with friendly messages
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
console.error(bind + ' requires elevated privileges');
|
||||
process.exit(1);
|
||||
break;
|
||||
case 'EADDRINUSE':
|
||||
console.error(bind + ' is already in use');
|
||||
process.exit(1);
|
||||
break;
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "listening" event.
|
||||
*/
|
||||
|
||||
function onListening() {
|
||||
var addr = server.address();
|
||||
var bind = typeof addr === 'string'
|
||||
? 'pipe ' + addr
|
||||
: 'port ' + addr.port;
|
||||
debug('Listening on ' + bind);
|
||||
}
|
||||
8
Server/config/databaseConfig.js
Normal file
8
Server/config/databaseConfig.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const databaseConfig = {
|
||||
database_host: '100.20.1.45',
|
||||
database_user: 'DRB_CNC',
|
||||
database_password: 'baMbC6IAl$Rn7$h0PS',
|
||||
database_database: 'DRB_CNC'
|
||||
}
|
||||
|
||||
module.exports = databaseConfig;
|
||||
76
Server/controllers/nodesController.js
Normal file
76
Server/controllers/nodesController.js
Normal file
@@ -0,0 +1,76 @@
|
||||
const mysqlHander = require("../mysqlHandler");
|
||||
const utils = require("../utils");
|
||||
|
||||
exports.listAllNodes = async (req, res) => {
|
||||
mysqlHander.getAllNodes((allNodes) => {
|
||||
res.status(200).json({
|
||||
"nodes_online": allNodes
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Add a new node to the
|
||||
exports.newNode = async (req, res) => {
|
||||
if (!req.body.name) return res.send(400)
|
||||
|
||||
try {
|
||||
// Try to add the new user with defaults if missing options
|
||||
mysqlHander.addNewNode({
|
||||
'name': req.body.name,
|
||||
'ip': req.body.ip ?? null,
|
||||
'port': req.body.port ?? null,
|
||||
'location': req.body.location ?? null,
|
||||
'nearbySystems': req.body.nearbySystems ?? null,
|
||||
'online': req.body.online ?? 0
|
||||
}, (queryResults) => {
|
||||
// Send back a success if the user has been added and the ID for the client to keep track of
|
||||
res.status(202).json({"nodeId": queryResults.insertId});
|
||||
})
|
||||
}
|
||||
catch (err) {
|
||||
// Catch any errors
|
||||
if (err === "No name provided") {
|
||||
return res.sendStatus(400);
|
||||
}
|
||||
else console.log(err)
|
||||
return res.sendStatus(500);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the known info for the node specified
|
||||
exports.getNodeInfo = async (req, res) => {
|
||||
if (!req.query.id) return res.status(400).json("No id specified");
|
||||
mysqlHander.getNodeInfoFromId(req.query.id, (nodeInfo) => {
|
||||
res.status(200).json(nodeInfo);
|
||||
})
|
||||
}
|
||||
|
||||
// Updates the information received from the client based on ID
|
||||
exports.nodeCheckIn = async (req, res) => {
|
||||
if (!req.body.id) return res.status(400).json("No id specified");
|
||||
mysqlHander.getNodeInfoFromId(req.body.id, (nodeInfo) => {
|
||||
let nodeObject = {};
|
||||
// Convert the DB systems buffer to a JSON object to be worked with
|
||||
nodeInfo.nearbySystems = utils.BufferToJson(nodeInfo.nearbySystems)
|
||||
// Convert the online status to a boolean to be worked with
|
||||
nodeInfo.online = nodeInfo.online !== 0;
|
||||
|
||||
if (req.body.name && req.body.name !== nodeInfo.name) nodeObject.name = req.body.name
|
||||
if (req.body.ip && req.body.ip !== nodeInfo.ip) nodeObject.ip = req.body.ip
|
||||
if (req.body.port && req.body.port !== nodeInfo.port) nodeObject.port = req.body.port
|
||||
if (req.body.location && req.body.location !== nodeInfo.location) nodeObject.location = req.body.location
|
||||
if (req.body.nearbySystems && JSON.stringify(req.body.nearbySystems) !== JSON.stringify(nodeInfo.nearbySystems)) nodeObject.nearbySystems = req.body.nearbySystems
|
||||
if (req.body.online && req.body.online !== nodeInfo.online) nodeObject.online = req.body.online
|
||||
|
||||
// If no changes are made tell the client
|
||||
if (Object.keys(nodeObject).length === 0) return res.status(200).json("No keys updated");
|
||||
|
||||
console.log("Updating the following keys for ID:", req.body.id, nodeObject);
|
||||
// Adding the ID key to the body so that the client can double-check their ID
|
||||
nodeObject.id = req.body.id;
|
||||
mysqlHander.updateNodeInfo(nodeObject, () => {
|
||||
return res.status(202).json({"updatedKeys": nodeObject});
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
121
Server/mysqlHandler.js
Normal file
121
Server/mysqlHandler.js
Normal file
@@ -0,0 +1,121 @@
|
||||
const mysql = require('mysql');
|
||||
const databaseConfig = require('./config/databaseConfig');
|
||||
const utils = require('./utils');
|
||||
|
||||
const connection = mysql.createConnection({
|
||||
host: databaseConfig.database_host,
|
||||
user: databaseConfig.database_user,
|
||||
password: databaseConfig.database_password,
|
||||
database: databaseConfig.database_database
|
||||
});
|
||||
|
||||
const nodesTable = `${databaseConfig.database_database}.nodes`;
|
||||
|
||||
connection.connect()
|
||||
|
||||
// Get all nodes the server knows about regardless of status
|
||||
exports.getAllNodes = (callback) => {
|
||||
const sqlQuery = `SELECT * FROM ${nodesTable}`
|
||||
runSQL(sqlQuery, (rows) => {
|
||||
callback(rows);
|
||||
})
|
||||
}
|
||||
|
||||
// Get all nodes that have the online status set true (are online)
|
||||
exports.getOnlineNodes = (callback) => {
|
||||
const sqlQuery = `SELECT * FROM ${nodesTable} WHERE online = 1;`
|
||||
runSQL(sqlQuery, (rows) => {
|
||||
callback(rows);
|
||||
})
|
||||
}
|
||||
|
||||
// Get info on a node based on ID
|
||||
exports.getNodeInfoFromId = (nodeId, callback) => {
|
||||
const sqlQuery = `SELECT * FROM ${nodesTable} WHERE id = ${nodeId}`
|
||||
runSQL(sqlQuery, (rows) => {
|
||||
// Call back the first (and theoretically only) row
|
||||
// Specify 0 so downstream functions don't have to worry about it
|
||||
callback(rows[0]);
|
||||
})
|
||||
}
|
||||
|
||||
// Add a new node to the DB
|
||||
exports.addNewNode = (nodeObject, callback) => {
|
||||
if (!nodeObject.name) throw new Error("No name provided");
|
||||
const name = nodeObject.name,
|
||||
ip = nodeObject.ip,
|
||||
port = nodeObject.port,
|
||||
location = nodeObject.location,
|
||||
nearbySystems = utils.JsonToBuffer(nodeObject.nearbySystems),
|
||||
online = nodeObject.online;
|
||||
const sqlQuery = `INSERT INTO ${nodesTable} (name, ip, port, location, nearbySystems, online) VALUES ('${name}', '${ip}', ${port}, '${location}', '${nearbySystems}', ${online})`;
|
||||
|
||||
runSQL(sqlQuery, (rows) => {
|
||||
callback(rows);
|
||||
})
|
||||
}
|
||||
|
||||
// Update the known info on a node
|
||||
exports.updateNodeInfo = (nodeObject, callback) => {
|
||||
const name = nodeObject.name,
|
||||
ip = nodeObject.ip,
|
||||
port = nodeObject.port,
|
||||
location = nodeObject.location,
|
||||
online = nodeObject.online;
|
||||
let queryParams = [],
|
||||
nearbySystems = nodeObject.nearbySystems;
|
||||
|
||||
if (name) queryParams.push(`name = '${name}'`);
|
||||
if (ip) queryParams.push(`ip = '${ip}'`);
|
||||
if (port) queryParams.push(`port = ${port}`);
|
||||
if (location) queryParams.push(`location = '${location}'`);
|
||||
if (nearbySystems) {
|
||||
nearbySystems = utils.JsonToBuffer(nearbySystems)
|
||||
queryParams.push(`nearbySystems = '${nearbySystems}'`);
|
||||
}
|
||||
if (typeof online === "boolean") {
|
||||
if (online) queryParams.push(`online = 1`);
|
||||
else queryParams.push(`online = 0`);
|
||||
}
|
||||
|
||||
let sqlQuery = `UPDATE ${nodesTable} SET`
|
||||
if (!queryParams || queryParams.length === 0) return callback(undefined);
|
||||
if (queryParams.length === 1) {
|
||||
sqlQuery = `${sqlQuery} ${queryParams[0]}`
|
||||
} else {
|
||||
let i = 0;
|
||||
for (const param of queryParams) {
|
||||
if (i === queryParams.length-1) {
|
||||
sqlQuery = `${sqlQuery} ${param}`
|
||||
i += 1;
|
||||
}
|
||||
else {
|
||||
sqlQuery = `${sqlQuery} ${param},`
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sqlQuery = `${sqlQuery} WHERE id = ${nodeObject.id};`
|
||||
|
||||
runSQL(sqlQuery, (rows) => {
|
||||
if (rows.affectedRows === 1) callback(true);
|
||||
else callback(rows);
|
||||
})
|
||||
}
|
||||
|
||||
// Function to run and handle SQL errors
|
||||
function runSQL(sqlQuery, callback, error = (err) => {
|
||||
console.log(err);
|
||||
throw err;
|
||||
}) {
|
||||
connection.query(sqlQuery, (err, rows) => {
|
||||
if (err) return error(err);
|
||||
//console.log('The rows are:', rows);
|
||||
return callback(rows);
|
||||
})
|
||||
}
|
||||
|
||||
exports.closeConnection = () => {
|
||||
connection.end()
|
||||
}
|
||||
1071
Server/package-lock.json
generated
Normal file
1071
Server/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
Server/package.json
Normal file
17
Server/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "server",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node ./bin/www"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie-parser": "~1.4.4",
|
||||
"debug": "~2.6.9",
|
||||
"ejs": "~2.6.1",
|
||||
"express": "~4.16.1",
|
||||
"http-errors": "~1.6.3",
|
||||
"morgan": "~1.9.1",
|
||||
"mysql": "^2.18.1"
|
||||
}
|
||||
}
|
||||
8
Server/public/stylesheets/style.css
Normal file
8
Server/public/stylesheets/style.css
Normal file
@@ -0,0 +1,8 @@
|
||||
body {
|
||||
padding: 50px;
|
||||
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00B7FF;
|
||||
}
|
||||
16
Server/routes/admin.js
Normal file
16
Server/routes/admin.js
Normal file
@@ -0,0 +1,16 @@
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
/* GET */
|
||||
router.get('/', (req, res) => {
|
||||
res.send('GET request to the admin')
|
||||
})
|
||||
|
||||
/* POST */
|
||||
router.post('/', (req, res) => {
|
||||
console.log(req.body);
|
||||
res.send('POST request to the post')
|
||||
})
|
||||
|
||||
|
||||
module.exports = router;
|
||||
9
Server/routes/index.js
Normal file
9
Server/routes/index.js
Normal file
@@ -0,0 +1,9 @@
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
/* GET home page. */
|
||||
router.get('/', (req, res, next) => {
|
||||
res.render('index', { title: 'Express' });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
31
Server/routes/nodes.js
Normal file
31
Server/routes/nodes.js
Normal file
@@ -0,0 +1,31 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const nodesController = require('../controllers/nodesController');
|
||||
|
||||
/* GET nodes the server knows */
|
||||
router.get('/', nodesController.listAllNodes);
|
||||
|
||||
// TODO Need to authenticate this request
|
||||
/* POST a new node to the server
|
||||
*
|
||||
* Will create a new DB entry for the node for the server to reference later
|
||||
* Req. body: {
|
||||
* "serverInfo": {"ip": "x.x.x.x", port: 0000}
|
||||
* }
|
||||
*
|
||||
* Will return a token for the client to reference when the bot is making requests
|
||||
* Res. body {
|
||||
* "serverToken": ""
|
||||
* }
|
||||
*/
|
||||
router.post('/newNode', nodesController.newNode);
|
||||
|
||||
// TODO Need to authenticate this request
|
||||
/* GET the information the server has on a particular node */
|
||||
router.get('/nodeInfo', nodesController.getNodeInfo);
|
||||
|
||||
// TODO Need to authenticate this request
|
||||
// Client checkin with the server to update information
|
||||
router.post('/nodeCheckIn', nodesController.nodeCheckIn);
|
||||
|
||||
module.exports = router;
|
||||
9
Server/utils.js
Normal file
9
Server/utils.js
Normal file
@@ -0,0 +1,9 @@
|
||||
// Convert a JSON object to a buffer for the DB
|
||||
exports.JsonToBuffer = (jsonObject) => {
|
||||
return Buffer.from(JSON.stringify(jsonObject))
|
||||
}
|
||||
|
||||
// Convert a buffer from the DB to JSON object
|
||||
exports.BufferToJson = (buffer) => {
|
||||
return JSON.parse(buffer.toString());
|
||||
}
|
||||
3
Server/views/error.ejs
Normal file
3
Server/views/error.ejs
Normal file
@@ -0,0 +1,3 @@
|
||||
<h1><%= message %></h1>
|
||||
<h2><%= error.status %></h2>
|
||||
<pre><%= error.stack %></pre>
|
||||
11
Server/views/index.ejs
Normal file
11
Server/views/index.ejs
Normal file
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title><%= title %></title>
|
||||
<link rel='stylesheet' href='/stylesheets/style.css' />
|
||||
</head>
|
||||
<body>
|
||||
<h1><%= title %></h1>
|
||||
<p>Welcome to <%= title %></p>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user