- Working commands
- Keeps track of open connections
This commit is contained in:
Logan Cusano
2023-06-10 22:16:39 -04:00
parent 041e0d485d
commit e8d68b2da7
6 changed files with 379 additions and 41 deletions

View File

@@ -3,7 +3,7 @@ const { customSlashCommandBuilder } = require('../utilities/customSlashCommandBu
const { DebugBuilder } = require("../utilities/debugBuilder");
const { getMembersInRole, getAllClientIds } = require("../utilities/utils");
const { requestOptions, sendHttpRequest } = require("../utilities/httpRequests");
const { getOnlineNodes, updateNodeInfo } = require("../utilities/mysqlHandler");
const { getOnlineNodes, updateNodeInfo, addNodeConnection } = require("../utilities/mysqlHandler");
// Global Vars
const log = new DebugBuilder("server", "join");
@@ -47,37 +47,45 @@ async function joinServerWrapper(presetName, channelId, clientIdsUsed) {
var selectedClientId;
if (typeof clientIdsUsed === 'string') {
if (Object.keys(availableClientIds).includes(clientIdsUsed)) selectedClientId = availableClientIds[clientIdsUsed];
for (const availableClientId of availableClientIds) {
if (availableClientId.discordId != clientIdsUsed ) selectedClientId = availableClientId;
}
}
else {
log.DEBUG("Client IDs Used: ", clientIdsUsed.keys());
for (const usedClientId of clientIdsUsed.keys()) {
log.DEBUG("Used Client ID: ", usedClientId);
if (Object.keys(availableClientIds).includes(usedClientId)) {
delete availableClientIds[usedClientId];
}
availableClientIds = availableClientIds.filter(cid => cid.discordId != usedClientId);
}
log.DEBUG("Available Client IDs: ", availableClientIds);
if (!Object.keys(availableClientIds).length > 0) return log.ERROR("All client ID have been used, consider swapping one of the curretly joined bots or adding more Client IDs to the pool.")
selectedClientId = availableClientIds[Object.keys(availableClientIds)[0]];
selectedClientId = availableClientIds[0];
}
const selectedNode = nodesCurrentlyAvailable[0];
const reqOptions = new requestOptions("/bot/join", "POST", selectedNode.ip, selectedNode.port);
sendHttpRequest(reqOptions, JSON.stringify({
const postObject = {
"channelId": channelId,
"clientId": selectedClientId.id,
"clientId": selectedClientId.clientId,
"presetName": presetName
}), async (responseObj) => {
};
log.INFO("Post Object: ", postObject);
sendHttpRequest(reqOptions, JSON.stringify(postObject), async (responseObj) => {
log.VERBOSE("Response Object from node ", selectedNode, responseObj);
if (!responseObj || !responseObj.statusCode == 200) return false;
// Node has connected to discord
// Updating node Object in DB
selectedNode.connected = true;
const updatedNode = await updateNodeInfo(selectedNode)
const updatedNode = await updateNodeInfo(selectedNode);
log.DEBUG("Updated Node: ", updatedNode);
// Adding a new node connection
const nodeConnection = await addNodeConnection(selectedNode, selectedClientId);
log.DEBUG("Node Connection: ", nodeConnection);
});
}
exports.joinServerWrapper = joinServerWrapper;

83
Server/commands/leave.js Normal file
View File

@@ -0,0 +1,83 @@
// Modules
const { customSlashCommandBuilder } = require('../utilities/customSlashCommandBuilder');
const { DebugBuilder } = require("../utilities/debugBuilder");
const { getAllClientIds, getKeyByArrayValue } = require("../utilities/utils");
const { requestOptions, sendHttpRequest } = require("../utilities/httpRequests");
const { checkNodeConnectionByClientId, removeNodeConnectionByNodeId, updateNodeInfo, getConnectedNodes, getAllConnections } = require('../utilities/mysqlHandler');
// Global Vars
const log = new DebugBuilder("server", "leave");
const logAC = new DebugBuilder("server", "leave_autocorrect");
async function leaveServerWrapper(clientIdObject) {
if (!clientIdObject.clientId || !clientIdObject.name) return log.ERROR("Tried to leave server without client ID and/or Name");
const node = await checkNodeConnectionByClientId(clientIdObject);
reqOptions = new requestOptions("/bot/leave", "POST", node.ip, node.port);
const responseObj = await new Promise((recordResolve, recordReject) => {
sendHttpRequest(reqOptions, JSON.stringify({}), async (responseObj) => {
recordResolve(responseObj);
});
});
log.VERBOSE("Response Object from node ", node, responseObj);
if (!responseObj || !responseObj.statusCode == 202) return false;
// Node has disconnected from discord
// Updating the node object in the DB
node.connected = false;
const updatedNode = await updateNodeInfo(node)
log.DEBUG("Updated Node: ", updatedNode);
// Removing the node connection from the DB
const removedConnection = removeNodeConnectionByNodeId(node.id);
log.DEBUG("Removed Node Connection: ", removedConnection);
return;
}
exports.leaveServerWrapper = leaveServerWrapper;
module.exports = {
data: new customSlashCommandBuilder()
.setName('leave')
.setDescription('Disconnect a bot from the server')
.addStringOption(option =>
option.setName("bot")
.setDescription("The bot to disconnect from the server")
.setAutocomplete(true)),
example: "leave",
isPrivileged: false,
requiresTokens: false,
defaultTokenUsage: 0,
deferInitialReply: true,
async autocomplete(interaction) {
const focusedValue = interaction.options.getFocused();
const connections = await getAllConnections();
const filtered = connections.filter(conn => String(conn.clientObject.name).startsWith(focusedValue)).map(conn => conn.clientObject.name);
logAC.DEBUG("Focused Value: ", focusedValue, connections, filtered);
await interaction.respond(
filtered.map(option => ({ name: option, value: option })),
);
},
async execute(interaction) {
try{
const guildId = interaction.guild.id;
const botName = interaction.options.getString('bot');
log.DEBUG("Bot Name: ", botName)
const clinetIds = await getAllClientIds();
log.DEBUG("Client names: ", clinetIds);
const clientDiscordId = getKeyByArrayValue(clinetIds, {'name': botName});
log.DEBUG("Selected bot: ", clinetIds[clientDiscordId]);
// Need to create a table in DB to keep track of what bots have what IDs or an endpoint on the clients to return what ID they are running with
await leaveServerWrapper(clinetIds[clientDiscordId]);
await interaction.editReply(`**${clinetIds[clientDiscordId].name}** has been disconnected`); // This will reply to the initial interaction
//await interaction.channel.send('**word.**'); // This will send a message to the channel of the interaction outside of the initial reply
}catch(err){
log.ERROR(err)
//await interaction.reply(err.toString());
}
}
}

View File

@@ -9,6 +9,6 @@ const log = new DebugBuilder("server", "messageCreate");
module.exports = {
name: Events.MessageCreate,
async execute(interaction) {
await linkCop(interaction);
//await linkCop(interaction);
},
};

View File

@@ -1,9 +1,9 @@
require('dotenv').config();
const mysql = require('mysql');
const utils = require('./utils');
const { nodeObject } = require("./recordHelper");
const { nodeObject, clientObject, connectionObject } = require("./recordHelper");
const { DebugBuilder } = require("../utilities/debugBuilder");
const { BufferToJson } = require("../utilities/utils");
const { BufferToJson, getClientObjectByClientID } = require("../utilities/utils");
const log = new DebugBuilder("server", "mysSQLHandler");
@@ -15,6 +15,7 @@ const connection = mysql.createPool({
});
const nodesTable = `${process.env.NODE_DB_NAME}.nodes`;
const nodeConnectionsTable = `${process.env.NODE_DB_NAME}.node_connections`;
/**
* Return a node object from a single SQL row
@@ -31,7 +32,8 @@ function returnNodeObjectFromRow(row) {
_location: row.location,
_nearbySystems: BufferToJson(row.nearbySystems),
_online: (row.online === 1) ? true : false,
_connected: (row.connected === 1) ? true : false
_connected: (row.connected === 1) ? true : false,
_connection: (row.connection) ? row.connection : null,
});
}
@@ -53,6 +55,21 @@ function returnNodeObjectFromRows(rows) {
return rows;
}
/**
* Returns a connection object from an SQL row
*
* @param {*} row The SQL row to convert to a connection object
* @returns {connectionObject}
*/
async function returnConnectionObjectFromRow(row) {
log.DEBUG("Connection row: ", row);
return new connectionObject({
_connection_id: row.connection_id,
_node: await getNodeInfoFromId(row.id),
_client_object: await getClientObjectByClientID(row.discord_client_id)
});
}
/** Get all nodes the server knows about regardless of status
* @param {*} callback Callback function
*/
@@ -71,8 +88,7 @@ exports.getAllNodes = (callback) => {
* @returns
*/
exports.getAllNodesSync = async () => {
const sqlQuery = `SELECT * FROM ${nodesTable}`
var returnObjects = [];
const sqlQuery = `SELECT * FROM ${nodesTable}`
const rows = await runSQL(sqlQuery);
console.log("Rows: ", rows);
@@ -94,20 +110,26 @@ exports.getOnlineNodes = (callback) => {
* @param nodeId The ID of the node
* @param callback Callback function
*/
exports.getNodeInfoFromId = (nodeId, callback) => {
async function getNodeInfoFromId(nodeId, callback = undefined) {
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
return callback(returnNodeObjectFromRow(rows[0]));
})
const sqlResponse = await new Promise((recordResolve, recordReject) => {
runSQL(sqlQuery, (rows) => {
recordResolve(rows);
})
});
// Call back the first (and theoretically only) row
// Specify 0 so downstream functions don't have to worry about it
return (callback) ? callback(returnNodeObjectFromRow(sqlResponse[0])) : returnNodeObjectFromRow(sqlResponse[0]);
}
exports.getNodeInfoFromId = getNodeInfoFromId
/** Add a new node to the DB
* @param nodeObject Node information object
* @param callback Callback function
*/
exports.addNewNode = (nodeObject, callback) => {
exports.addNewNode = async (nodeObject, callback) => {
if (!nodeObject.name) throw new Error("No name provided");
const name = nodeObject.name,
ip = nodeObject.ip,
@@ -118,16 +140,22 @@ exports.addNewNode = (nodeObject, callback) => {
connected = 0;
const sqlQuery = `INSERT INTO ${nodesTable} (name, ip, port, location, nearbySystems, online, connected) VALUES ('${name}', '${ip}', ${port}, '${location}', '${nearbySystems}', ${online}, ${connected})`;
runSQL(sqlQuery, (rows) => {
return callback(returnNodeObjectFromRows(rows));
})
const sqlResponse = await new Promise((recordResolve, recordReject) => {
runSQL(sqlQuery, (rows) => {
recordResolve(rows);
})
});
// Call back the first (and theoretically only) row
// Specify 0 so downstream functions don't have to worry about it
return (callback) ? callback(returnNodeObjectFromRow(sqlResponse)) : returnNodeObjectFromRow(sqlResponse);
}
/** Update the known info on a node
* @param nodeObject Node information object
* @param callback Callback function
*/
exports.updateNodeInfo = (nodeObject, callback = undefined) => {
exports.updateNodeInfo = async (nodeObject, callback = undefined) => {
if(!nodeObject.id) throw new Error("Attempted to updated node without providing ID", nodeObject);
const name = nodeObject.name,
ip = nodeObject.ip,
@@ -175,21 +203,167 @@ exports.updateNodeInfo = (nodeObject, callback = undefined) => {
sqlQuery = `${sqlQuery} WHERE id = ${nodeObject.id};`
runSQL(sqlQuery, (rows) => {
if (rows.affectedRows === 1) return (callback) ? callback(true) : true;
else return (callback) ? callback(returnNodeObjectFromRows(rows)) : returnNodeObjectFromRows(rows);
})
const sqlResponse = await new Promise((recordResolve, recordReject) => {
runSQL(sqlQuery, (rows) => {
recordResolve(rows);
})
});
if (sqlResponse.affectedRows === 1) return (callback) ? callback(true) : true;
else return (callback) ? callback(returnNodeObjectFromRows(sqlResponse)) : returnNodeObjectFromRows(sqlResponse);
}
/**
* Add a new connection to the DB when a bot has been connected to the server
*
* @param {*} nodeObject The node object that is being used for this connection
* @param {*} clientId The client ID Object being used for this connection
* @param {*} callback [OPTIONAL] The callback function to be called with the results, will return otherwise
*/
exports.addNodeConnection = (nodeObject, clientObject, callback = undefined) => {
if (!nodeObject.id || !clientObject.clientId) throw new Error("Tried to add a connection without a client and/or node ID");
const sqlQuery = `INSERT INTO ${nodeConnectionsTable} (id, discord_client_id) VALUES (${nodeObject.id}, '${clientObject.clientId}')`;
const sqlResponse = new Promise((recordResolve, recordReject) => {
runSQL(sqlQuery, (rows) => {
recordResolve(rows);
})
});
if (!sqlResponse) throw new Error("No result from added connection");
return (callback) ? callback(true) : true;
}
/**
* Check what node is connected with a given client ID object
*
* @param {*} clientId The client ID object used to search for a connected node
* @param {*} callback [OPTIONAL] The callback function to be called with the results, return will be used otherwise
*/
exports.checkNodeConnectionByClientId = async (clientId, callback = undefined) => {
if (!clientId.clientId) throw new Error("Tried to check a connection without a client ID");
const sqlQuery = `SELECT * FROM ${nodeConnectionsTable} WHERE discord_client_id = '${clientId.clientId}'`;
const sqlResponse = await new Promise((recordResolve, recordReject) => {
runSQL(sqlQuery, (rows) => {
recordResolve(rows);
})
});
log.VERBOSE("SQL Response from checking connection: ", sqlResponse);
if (!sqlResponse) return (callback) ? callback(undefined) : undefined;
const newNodeObject = await getNodeInfoFromId(sqlResponse[0].id);
log.DEBUG("Node Object from SQL Response: ", newNodeObject);
return (callback) ? callback(newNodeObject) : newNodeObject;
}
/**
* Get a connection by node ID
*
* @param {*} nodeId The ID to search for a connection with
* @param {*} callback [OPTIONAL] The callback function to be called with the results, return will be used otherwise
* @returns {connectionObject}
*/
exports.getConnectionByNodeId = async (nodeId, callback = undefined) => {
const sqlQuery = `SELECT * FROM ${nodeConnectionsTable} WHERE id = '${nodeId}'`;
const sqlResponse = await new Promise((recordResolve, recordReject) => {
runSQL(sqlQuery, (rows) => {
recordResolve(rows);
})
});
log.VERBOSE("SQL Response from checking connection: ", sqlResponse);
if (!sqlResponse) return (callback) ? callback(undefined) : undefined;
const newConnectionObject = await returnConnectionObjectFromRow(sqlResponse)
log.DEBUG("Connection Object from SQL Response: ", newConnectionObject);
return (callback) ? callback(newConnectionObject) : newConnectionObject;
}
/**
* Remove a node connection by the node
*
* @param {*} nodeId The node ID of the node to remove connections of
* @param {*} callback [OPTIONAL] The callback function to callback with the results, return will be used otherwise
* @returns
*/
exports.removeNodeConnectionByNodeId = async (nodeId, callback = undefined) => {
const sqlQuery = `DELETE FROM ${nodeConnectionsTable} WHERE id = '${nodeId}'`;
const sqlResponse = await new Promise((recordResolve, recordReject) => {
runSQL(sqlQuery, (rows) => {
recordResolve(rows);
})
});
log.VERBOSE("SQL Response from removing connection: ", sqlResponse);
if (!sqlResponse) return (callback) ? callback(undefined) : undefined;
return (callback) ? callback(sqlResponse) : sqlResponse;
}
/**
* Gets all connected nodes
*
* @param {*} callback [OPTIONAL] The callback function to callback with the results, return will be used otherwise
* @returns {nodeObject}
*/
exports.getConnectedNodes = async (callback = undefined) => {
const sqlQuery = `SELECT * FROM ${nodeConnectionsTable}`;
const sqlResponse = await new Promise((recordResolve, recordReject) => {
runSQL(sqlQuery, (rows) => {
recordResolve(rows);
})
});
log.VERBOSE("SQL Response from checking connection: ", sqlResponse);
if (!sqlResponse) return (callback) ? callback(undefined) : undefined;
var nodeObjects = []
for (const row of sqlResponse) {
const newNodeObject = await getNodeInfoFromId(row.id);
log.DEBUG("Node Object from SQL Response: ", newNodeObject);
nodeObjects.push(newNodeObject);
}
return (callback) ? callback(nodeObjects) : nodeObjects;
}
/**
* Returns all connections
*
* @param {*} callback [OPTIONAL] The callback function to callback with the results, return will be used otherwise
* @returns {connectionObject}
*/
exports.getAllConnections = async (callback = undefined) => {
const sqlQuery = `SELECT * FROM ${nodeConnectionsTable}`;
const sqlResponse = await new Promise((recordResolve, recordReject) => {
runSQL(sqlQuery, (rows) => {
recordResolve(rows);
})
});
log.VERBOSE("SQL Response from checking connection: ", sqlResponse);
if (!sqlResponse) return (callback) ? callback(undefined) : undefined;
var connectionObjects = []
for (const row of sqlResponse) {
connectionObjects.push(await returnConnectionObjectFromRow(row));
}
return (callback) ? callback(connectionObjects) : connectionObjects;
}
// Function to run and handle SQL errors
function runSQL(sqlQuery, callback, error = (err) => {
function runSQL(sqlQuery, callback = undefined, error = (err) => {
console.log(err);
throw err;
}) {
return connection.query(sqlQuery, (err, rows) => {
connection.query(sqlQuery, (err, rows) => {
if (err) return error(err);
//console.log('The rows are:', rows);
return callback(rows);
//console.log('The rows are:', rows);
return (callback) ? callback(rows) : rows
})
}

View File

@@ -111,9 +111,10 @@ class nodeObject {
* @param {*} param0._location The physical location of the node
* @param {*} param0._online True/False if the node is online or offline
* @param {*} param0._connected True/False if the bot is connected to discord or not
* @param {*} param0._connection The connection Object associated with the node, null if not checked, undefined if none exists
* @param {*} param0._nearbySystems An object array of nearby systems
*/
constructor({ _id = null, _name = null, _ip = null, _port = null, _location = null, _nearbySystems = null, _online = null, _connected = null }) {
constructor({ _id = null, _name = null, _ip = null, _port = null, _location = null, _nearbySystems = null, _online = null, _connected = null, _connection = null }) {
this.id = _id;
this.name = _name;
this.ip = _ip;
@@ -122,7 +123,46 @@ class nodeObject {
this.nearbySystems = _nearbySystems;
this.online = _online;
this.connected = _connected;
this.connection = _connection;
}
}
exports.nodeObject = nodeObject;
exports.nodeObject = nodeObject;
/**
* This object represents a discord bot's client information
*/
class clientObject {
/**
*
* @param {*} param0._discord_id The discord id from the node, as seen when right clicking -> copy ID
* @param {*} param0._name The name of the bot associated with the IDs
* @param {*} param0._client_id The client ID of the bot needed to connect to Discord
*/
constructor({_discord_id = null, _name = null, _client_id = null,}) {
this.discordId = _discord_id;
this.name = _name;
this.clientId = _client_id;
}
}
exports.clientObject = clientObject;
/**
* This object represents a discord node connection
*/
class connectionObject {
/**
*
* @param {*} param0._connection_id The connection ID associated with the connection in the database
* @param {*} param0._node The node associated with the connection
* @param {*} param0._client_object The client object associated with the connection
*/
constructor({_connection_id = null, _node = null, _client_object}) {
this.connectionId = _connection_id;
this.node = _node;
this.clientObject = _client_object;
}
}
exports.connectionObject = connectionObject;

View File

@@ -1,5 +1,6 @@
// Debug
const { DebugBuilder } = require("../utilities/debugBuilder");
const { clientObject } = require("./recordHelper");
const { readFileSync } = require('fs');
const log = new DebugBuilder("server", "utils");
const path = require('path');
@@ -32,8 +33,11 @@ exports.SanitizePresetName = (presetName) => {
*/
exports.getMembersInRole = async (interaction, roleName = "Bots" ) => {
log.DEBUG("Fetching all members");
await interaction.guild.members.fetch() //cache all members in the server
const role = await interaction.guild.roles.cache.find(role => role.name === roleName); //the role to check
var guild = await interaction.client.guilds.fetch({ guild: interaction.guild.id, cache: false }); //cache all members in the server
await guild.members.fetch({cache: false});
await guild.roles.fetch({cache: false});
log.VERBOSE("Guild: ", guild);
const role = await guild.roles.cache.find(role => role.name === roleName); //the role to check
log.DEBUG("Role to check members from: ", role);
log.DEBUG("Members of role: ", role.members);
@@ -58,7 +62,9 @@ exports.getMembersInRole = async (interaction, roleName = "Bots" ) => {
* @returns The key of the object that contains the value
*/
exports.getKeyByArrayValue = (object, value) => {
return Object.keys(object).find(key => object[key].includes(value));
if (typeof value == "string") return Object.keys(object).find(key => object[key].includes(value));
const valueKey = Object.keys(value)[0];
return Object.keys(object).find(key => (object[key][valueKey] == value[valueKey]));
}
/**
@@ -82,5 +88,32 @@ exports.isJsonString = (str) => {
* @returns Object of Client IDs
*/
exports.getAllClientIds = () => {
return Object(JSON.parse(readFileSync(path.resolve(__dirname, '../clientIds.json'))));
const jsonClientIds = JSON.parse(readFileSync(path.resolve(__dirname, '../clientIds.json')));
var clientObjects = [];
for (const jsonClientId of Object.keys(jsonClientIds)){
clientObjects.push(new clientObject({
_discord_id: jsonClientId,
_name: jsonClientIds[jsonClientId].name,
_client_id: jsonClientIds[jsonClientId].id
}))
}
return clientObjects;
}
/**
* Gets a client object froma discord client ID
*
* @param {*} clientId The discord client ID to get the client object of
* @returns {clientObject|undefined}
*/
exports.getClientObjectByClientID = (clientId) => {
const clientObjects = this.getAllClientIds();
log.DEBUG("All client IDs: ", clientObjects);
for (const clientObject of clientObjects){
if (clientObject.clientId == clientId) {
log.DEBUG("Found client ID from given ID: ", clientObject);
return clientObject
}
}
return undefined
}