// Config require('dotenv').config(); // Debug const { DebugBuilder } = require("../utilities/debugBuilder.js"); const log = new DebugBuilder("server", "adminController"); // Utilities const { getAllClientIds } = require("../utilities/utils"); const { getOnlineNodes, updateNodeInfo, addNodeConnection, getConnectionByNodeId, getNodeInfoFromId, checkNodeConnectionByClientId, removeNodeConnectionByNodeId } = require("../utilities/mysqlHandler"); const { requestOptions, sendHttpRequest } = 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) { getOnlineNodes((onlineNodes) => { return callback(onlineNodes); }); } async function getNodeBotStatus(nodeId, callback) { getNodeInfoFromId(nodeId, (nodeObject) =>{ reqOptions = new requestOptions("/bot/status", "GET", nodeObject.ip, nodeObject.port, undefined, 5); sendHttpRequest(reqOptions, JSON.stringify({}), (responseObject) => { if (responseObject === false) { // Bot is joined } else { // Bot is free } return callback(responseObject); }); }); } /** 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) * @var {*} req.body.clientId The ID of the client that we want to join with * @var {*} req.body.channelId The channel Id of the discord channel to join * @param {*} res Express response parameter */ exports.joinPreset = async (req, res) => { if (!req.body.preset) return res.status(400).json("No preset specified"); if (!req.body.nodeId) return res.status(400).json("No node ID specified"); if (!req.body.clientId) return res.status(400).json("No client ID specified"); if (!req.body.channelId) return res.status(400).json("No channel ID specified"); const preset = req.body.preset; const nodeId = req.body.nodeId; const clientId = req.body.clientId; const channelId = req.body.channelId; const joinedClient = await joinServerWrapper(preset, channelId, clientId, nodeId); if (!joinedClient) return res.send(400).json("No joined client"); return res.status(200).json(joinedClient); } /** Request a node to join the server listening to a specific preset * * @param {*} req Express request parameter * @param {*} res Express response parameter * @var {*} req.body.nodeId The ID of the node to disconnect */ exports.leaveServer = async (req, res) => { if (!req.body.nodeId) return res.status(400).json("No node ID specified"); const nodeId = req.body.nodeId; const currentConnection = await getConnectionByNodeId(nodeId); log.DEBUG("Current Connection for node: ", currentConnection); if (!currentConnection) return res.status(400).json("Node is not connected") await leaveServerWrapper(currentConnection.clientObject) return res.status(200).json(currentConnection.clientObject.name); } /** * * This wrapper will check if there is an available node with the requested preset and if so checks for an available client ID to join with * * @param {*} presetName The preset name to listen to on the client * @param {*} channelId The channel ID to join the bot to * @param {*} connections EITHER A collection of clients that are currently connected OR a single discord client ID (NOT dev portal ID) that should be used to join the server with * @param {number} nodeId [OPTIONAL] The node ID to join with (will join with another node if given node is not available) * @returns */ async function joinServerWrapper(presetName, channelId, connections, nodeId = 0) { // Get nodes online var onlineNodes = await new Promise((recordResolve, recordReject) => { getOnlineNodes((nodeRows) => { recordResolve(nodeRows); }); }); // Check which nodes have the selected preset onlineNodes = onlineNodes.filter(node => node.presets.includes(presetName)); log.DEBUG("Filtered Online Nodes: ", onlineNodes); // Check if any nodes with this preset are available var nodesCurrentlyAvailable = []; for (const node of onlineNodes) { const currentConnection = await getConnectionByNodeId(node.id); log.DEBUG("Checking to see if there is a connection for Node: ", node, currentConnection); if(!currentConnection) nodesCurrentlyAvailable.push(node); } log.DEBUG("Nodes Currently Available: ", nodesCurrentlyAvailable); // If not, let the user know if (!nodesCurrentlyAvailable.length > 0) return Error("All nodes with this channel are unavailable, consider swapping one of the currently joined bots."); // If so, join with the first node var availableClientIds = await getAllClientIds(); log.DEBUG("All clients: ", Object.keys(availableClientIds)); var selectedClientId; if (typeof connections === 'string') { for (const availableClientId of availableClientIds) { if (availableClientId.discordId != connections ) selectedClientId = availableClientId; } } else { log.DEBUG("Open connections: ", connections); for (const connection of connections) { log.DEBUG("Used Client ID: ", connection); availableClientIds = availableClientIds.filter(cid => cid.discordId != connection.clientObject.discordId); } 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[0]; } let selectedNode; if (nodeId > 0) { for(const availableNode of nodesCurrentlyAvailable){ if (availableNode.id == nodeId) selectedNode = availableNode; } } if (!selectedNode) selectedNode = nodesCurrentlyAvailable[0]; const reqOptions = new requestOptions("/bot/join", "POST", selectedNode.ip, selectedNode.port); const postObject = { "channelId": channelId, "clientId": selectedClientId.clientId, "presetName": presetName }; 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 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); }); return selectedClientId; } exports.joinServerWrapper = joinServerWrapper; /** * * @param {*} clientIdObject The client ID object for the node to leave the server. Either 'clientId'||'name' can be set. * @returns */ 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 discor // Removing the node connection from the DB const removedConnection = removeNodeConnectionByNodeId(node.id); log.DEBUG("Removed Node Connection: ", removedConnection); return; } exports.leaveServerWrapper = leaveServerWrapper;