Major update

- Working client server interactions
- Can create radio config
- Needs radio testing
This commit is contained in:
Logan Cusano
2023-02-18 20:41:43 -05:00
parent c8c75e8b37
commit ce072d9287
25 changed files with 1114 additions and 39 deletions

1
Server/.env Normal file
View File

@@ -0,0 +1 @@
DEBUG="server:*";

20
Server/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,20 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}\\bin\\www",
"env": {
"DEBUG":"*"
}
}
]
}

View File

@@ -6,6 +6,7 @@
var app = require('../app');
// Debug
const debug = require('debug')('server');
const { DebugBuilder } = require("../utilities/debugBuilder.js");
const log = new DebugBuilder("server", "www");
var http = require('http');
@@ -89,4 +90,5 @@ function onListening() {
? 'pipe ' + addr
: 'port ' + addr.port;
log.DEBUG('Listening on ' + bind);
debug("testing");
}

View File

@@ -0,0 +1,5 @@
const discordConfig = {
channelID: '367396189529833476'
}
module.exports = discordConfig;

View File

@@ -0,0 +1,129 @@
// Config
const discordConfig = require("../config/discordConfig");
// Debug
const { DebugBuilder } = require("../utilities/debugBuilder.js");
const log = new DebugBuilder("server", "adminController");
// Utilities
const mysqlHandler = require("../utilities/mysqlHandler");
const utils = require("../utilities/utils");
const requests = require("../utilities/httpRequests");
/** Get the presets of all online nodes, can be used for functions
*
* @param callback Callback function
* @returns {*} A list of the systems online
*/
async function getPresetsOfOnlineNodes(callback) {
mysqlHandler.getOnlineNodes((onlineNodes) => {
let systems = {};
onlineNodes.forEach(onlineNode => {
systems[onlineNode.id] = utils.BufferToJson(onlineNode.nearbySystems);
});
callback(systems);
});
}
async function requestNodeListenToPreset(preset, nodeId, callback) {
mysqlHandler.getNodeInfoFromId(nodeId, (nodeObject) =>{
reqOptions = new requests.requestOptions("/bot/join", "POST", nodeObject.ip, nodeObject.port);
requests.sendHttpRequest(reqOptions, JSON.stringify({
"channelID": discordConfig.channelID,
"presetName": preset
}), (responseObject) => {
callback(responseObject)
});
})
}
async function getNodeBotStatus(nodeId, callback) {
mysqlHandler.getNodeInfoFromId(nodeId, (nodeObject) =>{
reqOptions = new requests.requestOptions("/bot/status", "GET", nodeObject.ip, nodeObject.port, undefined, 5);
requests.sendHttpRequest(reqOptions, JSON.stringify({}), (responseObject) => {
if (responseObject === false) {
// Bot is joined
}
else {
// Bot is free
}
callback(responseObject);
});
});
}
async function requestNodeLeaveServer(nodeId, callback) {
getNodeBotStatus(nodeId, (responseObject) => {
if (responseObject === false) {
// Bot is joined
mysqlHandler.getNodeInfoFromId(nodeId, (nodeObject) =>{
reqOptions = new requests.requestOptions("/bot/leave", "POST", nodeObject.ip, nodeObject.port);
requests.sendHttpRequest(reqOptions, JSON.stringify({}), (responseObject) => {
callback(responseObject);
});
});
}
else {
// Bot is free
callback(false);
}
})
}
/** Return to requests for the presets of all online nodes, cannot be used in functions
*
* @param {*} req Express request parameter
* @param {*} res Express response parameter
*/
exports.getAvailablePresets = async (req, res) => {
await getPresetsOfOnlineNodes((systems) => {
res.status(200).json({
"systemsOnline": systems
});
})
}
/** Request a node to join the server listening to a specific preset
*
* @param {*} req Express request parameter
* @var {*} req.body.preset The preset to join (REQ)
* @var {*} req.body.nodeId The specific node to join (OPT/REQ if more than one node has the preset)
* @param {*} res Express response parameter
*/
exports.joinPreset = async (req, res) => {
if (!req.body.preset) return res.status(400).json("No preset specified");
await getPresetsOfOnlineNodes((systems) => {
const systemsWithSelectedPreset = Object.values(systems).filter(nodePresets => nodePresets.includes(req.body.preset)).length
if (!systemsWithSelectedPreset) return res.status(400).json("No system online with that preset");
if (systemsWithSelectedPreset > 1) {
if (!req.body.nodeId) return res.status(175).json("Multiple locations with the selected channel, please specify a nodeID (nodeId)")
requestNodeListenToPreset(req.body.preset, req.body.nodeId, (responseObject) => {
if (responseObject === false) return res.status(400).json("Timeout reached");
return res.sendStatus(responseObject.statusCode);
});
}
else {
let nodeId;
if (!req.body.nodeId) nodeId = utils.getKeyByArrayValue(systems, req.body.preset);
else nodeId = req.body.nodeId;
requestNodeListenToPreset(req.body.preset, nodeId, (responseObject) => {
if (responseObject === false) return res.status(400).json("Timeout reached");
return res.sendStatus(responseObject.statusCode);
});
}
});
}
/** Request a node to join the server listening to a specific preset
*
* @param {*} req Express request parameter
* @param {*} res Express response parameter
*/
exports.leaveServer = async (req, res) => {
if (!req.body.nodeId) return res.status(400).json("No nodeID specified");
requestNodeLeaveServer(req.body.nodeId, (responseObject) => {
if (responseObject === false) return res.status(400).json("Bot not joined to server");
return res.sendStatus(responseObject.statusCode);
});
}

View File

@@ -0,0 +1,150 @@
//Config
import { getTOKEN, getGuildID, getApplicationID } from './utilities/configHandler.js.js';
// Commands
import ping from './commands/ping.js';
import join from './commands/join.js.js';
import leave from './commands/leave.js.js';
import status from './commands/status.js.js';
// Debug
import ModuleDebugBuilder from "./utilities/moduleDebugBuilder.js.js";
const log = new ModuleDebugBuilder("bot", "app");
// Modules
import { Client, GatewayIntentBits } from 'discord.js';
// Utilities
import registerCommands from './utilities/registerCommands.js.js';
/**
* Host Process Object Builder
*
* This constructor is used to easily construct responses to the host process
*/
class HPOB {
/**
* Build an object to be passed to the host process
* @param command The command to that was run ("Status", "Join", "Leave", "ChgPreSet")
* @param response The response from the command that was run
*/
constructor(command = "Status"||"Join"||"Leave"||"ChgPreSet", response) {
this.cmd = command;
this.msg = response;
}
}
// Create the Discord client
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
GatewayIntentBits.GuildVoiceStates
]
});
/**
* When the parent process sends a message, this will interpret the message and act accordingly
*
* DRB IPC Message Structure:
* msg.cmd = The command keyword; Commands covered on the server side
* msg.params = An array containing the parameters for the command
*
*/
process.on('message', (msg) => {
log.DEBUG('IPC Message: ', msg);
const guildID = getGuilds()[0];
log.DEBUG("Guild Name: ", getGuildNameFromID(guildID));
switch (msg.cmd) {
// Check the status of the bot
case "Status":
log.INFO("Status command run from IPC");
status({guildID: guildID, callback: (statusObj) => {
log.DEBUG("Status Object string: ", statusObj);
if (!statusObj.voiceConnection) return process.send(new HPOB("Status", "VDISCONN"));
}});
break;
// Check the params for a server ID and if so join the server
case "Join":
log.INFO("Join command run from IPC");
join({guildID: guildID, guildObj: client.guilds.cache.get(guildID), channelID: msg.params.channelID, callback: () => {
process.send(new HPOB("Join", "AIDS"));
}})
break;
// Check to see if the bot is in a server and if so leave
case "Leave":
log.INFO("Leave command run from IPC");
leave({guildID: guildID, callback: (response) => {
process.send(new HPOB("Leave", response));
}});
break;
default:
// Command doesn't exist
log.INFO("Unknown command run from IPC");
break;
}
})
// When the client is connected and ready
client.on('ready', () =>{
log.INFO(`${client.user.tag} is ready`)
process.send({'msg': "INIT READY"});
});
/*
* Saved For later
client.on('messageCreate', (message) => {
log.DEBUG(`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: interaction });
break;
case "leave":
leave({ interaction: interaction });
break;
case "status":
status({ interaction: interaction });
break;
default:
interaction.reply({ content: 'Command not found, try one that exists', fetchReply: true })
.then((message) => log.DEBUG(`Reply sent with content ${message.content}`))
.catch((err) => log.ERROR(err));
}
}
})
function loginBot(){
client.login(getTOKEN());
}
function getGuilds() {
return client.guilds.cache.map(guild => guild.id)
}
function getGuildNameFromID(guildID) {
return client.guilds.cache.map((guild) => {
if (guild.id === guildID) return guild.name;
})[0]
}
function main(){
registerCommands(() => {
loginBot();
});
}
main();
//module.exports = client;

View File

@@ -0,0 +1,6 @@
// Utilities
import { replyToInteraction } from '../utilities/messageHandler.js.js';
export default function ping(interaction) {
return replyToInteraction(interaction, "Pong! I have Aids and now you do too!");
}

View File

@@ -0,0 +1,7 @@
{
"TOKEN": "OTQzNzQyMDQwMjU1MTE1MzA0.Yg3eRA.ZxEbRr55xahjfaUmPY8pmS-RHTY",
"ApplicationID": "943742040255115304",
"GuildID": "367396189529833472",
"DeviceID": "5",
"DeviceName": "VoiceMeeter Aux Output (VB-Audi"
}

View File

@@ -4,17 +4,16 @@ const log = new DebugBuilder("server", "admin");
// Modules
var express = require('express');
var router = express.Router();
var adminController = require("../controllers/adminController");
/* GET */
router.get('/', (req, res) => {
res.send('GET request to the admin')
})
router.get('/presets', adminController.getAvailablePresets);
/* POST */
router.post('/', (req, res) => {
log.DEBUG(req.body);
res.send('POST request to the post')
})
router.post('/join', adminController.joinPreset);
/* POST */
router.post('/leave', adminController.leaveServer);
module.exports = router;

View File

@@ -0,0 +1,68 @@
// Debug
const { DebugBuilder } = require("../utilities/debugBuilder.js");
const log = new DebugBuilder("server", "httpRequests");
// Modules
const http = require("http");
exports.requestOptions = class requestOptions {
/**
* Construct an HTTP request
* @param {*} method Method of request to use [GET, POST]
* @param {*} headers Headers of the request, not required but will get filled with default values if not set
* @param {*} hostname The destination of the request
* @param {*} port The port for the destination, will use 3001 by default
*/
constructor(path, method, hostname, port = 3001, headers = undefined, timeout = undefined) {
this.hostname = hostname;
this.path = path;
this.port = port;
this.method = method;
this.timeout = timeout;
if (method === "POST"){
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){
log.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": data
};
try {
responseObject.body = JSON.parse(responseObject.body)
}
catch (err) {
}
log.DEBUG("Response Object: ", responseObject);
callback(responseObject);
})
}).on('error', err => {
log.ERROR('Error: ', err.message)
// TODO need to handle if the server is down
})
if (requestOptions.timeout) {
req.setTimeout(requestOptions.timeout, () => {
callback(false);
});
}
// Write the data to the request and send it
req.write(data)
req.end()
}

View File

@@ -13,7 +13,9 @@ const nodesTable = `${databaseConfig.database_database}.nodes`;
connection.connect()
// Get all nodes the server knows about regardless of status
/** Get all nodes the server knows about regardless of status
* @param {*} callback Callback function
*/
exports.getAllNodes = (callback) => {
const sqlQuery = `SELECT * FROM ${nodesTable}`
runSQL(sqlQuery, (rows) => {
@@ -21,7 +23,9 @@ exports.getAllNodes = (callback) => {
})
}
// Get all nodes that have the online status set true (are online)
/** Get all nodes that have the online status set true (are online)
* @param callback Callback function
*/
exports.getOnlineNodes = (callback) => {
const sqlQuery = `SELECT * FROM ${nodesTable} WHERE online = 1;`
runSQL(sqlQuery, (rows) => {
@@ -29,7 +33,10 @@ exports.getOnlineNodes = (callback) => {
})
}
// Get info on a node based on ID
/** Get info on a node based on ID
* @param nodeId The ID of the node
* @param callback Callback function
*/
exports.getNodeInfoFromId = (nodeId, callback) => {
const sqlQuery = `SELECT * FROM ${nodesTable} WHERE id = ${nodeId}`
runSQL(sqlQuery, (rows) => {
@@ -39,7 +46,10 @@ exports.getNodeInfoFromId = (nodeId, callback) => {
})
}
// Add a new node to the DB
/** Add a new node to the DB
* @param nodeObject Node information object
* @param callback Callback function
*/
exports.addNewNode = (nodeObject, callback) => {
if (!nodeObject.name) throw new Error("No name provided");
const name = nodeObject.name,
@@ -55,7 +65,10 @@ exports.addNewNode = (nodeObject, callback) => {
})
}
// Update the known info on a node
/** Update the known info on a node
* @param nodeObject Node information object
* @param callback Callback function
*/
exports.updateNodeInfo = (nodeObject, callback) => {
const name = nodeObject.name,
ip = nodeObject.ip,

View File

@@ -6,4 +6,14 @@ exports.JsonToBuffer = (jsonObject) => {
// Convert a buffer from the DB to JSON object
exports.BufferToJson = (buffer) => {
return JSON.parse(buffer.toString());
}
}
/** Find a key in an object by its value
*
* @param {*} object The object to search
* @param {*} value The value to search the arrays in the object for
* @returns The key of the object that contains the value
*/
exports.getKeyByArrayValue = (object, value) => {
return Object.keys(object).find(key => object[key].includes(value));
}