Compare commits
13 Commits
fa2f28207e
...
feature/#1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77deb3ba2b | ||
|
|
f4475dc9d7 | ||
|
|
c4650a9e99 | ||
|
|
f5e119d845 | ||
|
|
e8d68b2da7 | ||
|
|
041e0d485d | ||
|
|
fc11324714 | ||
|
|
c6c048c919 | ||
|
|
8ab611836b | ||
| 7d8ad68e27 | |||
| 200ca9c926 | |||
|
|
ff8e86cc3a | ||
|
|
6b12c3e3df |
@@ -1,18 +1,10 @@
|
||||
DEBUG="client:*"
|
||||
# Bot Config
|
||||
# Discord Bot Token
|
||||
TOKEN=""
|
||||
# Discord Bot Application ID
|
||||
APPLICATION_ID=""
|
||||
# Default Guild ID
|
||||
GUILD_ID=""
|
||||
|
||||
# Audio Config
|
||||
AUDIO_DEVICE_ID=""
|
||||
AUDIO_DEVICE_NAME=""
|
||||
|
||||
# Client Config
|
||||
CLIENT_ID=
|
||||
CLIENT_ID=0
|
||||
CLIENT_NAME=""
|
||||
CLIENT_IP=""
|
||||
CLIENT_PORT=3010
|
||||
|
||||
@@ -24,7 +24,7 @@ exports.getStatus = (req, res) => {
|
||||
* Start the bot and join the server and preset specified
|
||||
*/
|
||||
exports.joinServer = async (req, res) => {
|
||||
if (!req.body.clientId || !req.body.channelId) return res.send("500").json({"message": "You must include the client ID (discord token), channel ID (The discord ID of the channel to connect to)"});
|
||||
if (!req.body.clientId || !req.body.channelId) return res.status(500).json({"message": "You must include the client ID (discord token), channel ID (The discord ID of the channel to connect to)"});
|
||||
const deviceId = process.env.AUDIO_DEVICE_ID;
|
||||
const channelId = req.body.channelId;
|
||||
const clientId = req.body.clientId;
|
||||
|
||||
@@ -13,6 +13,9 @@ ls -ld $SCRIPT_DIR | awk '{print $3}' >> ./config/installerName
|
||||
useradd -M RadioNode
|
||||
usermod -s -L RadioNode
|
||||
|
||||
# Create the .env file from the example
|
||||
cp $SCRIPT_DIR/.env.example $SCRIPT_DIR/.env
|
||||
|
||||
# Change the ownership of the directory to the service user
|
||||
chown RadioNode -R $SCRIPT_DIR
|
||||
|
||||
@@ -27,7 +30,7 @@ apt-get update
|
||||
apt-get upgrade -y
|
||||
|
||||
# Install the necessary packages
|
||||
apt-get install -y nodejs npm libopus-dev gcc make portaudio19-dev libportaudio2 libpulse-dev pulseaudio apulse python3 pip
|
||||
apt-get install -y nodejs portaudio19-dev libportaudio2 libpulse-dev pulseaudio apulse python3 python3-pip
|
||||
|
||||
# Ensure pulse audio is running
|
||||
pulseaudio
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
// Modules
|
||||
const { customSlashCommandBuilder } = require('../utilities/customSlashCommandBuilder');
|
||||
const { DebugBuilder } = require("../utilities/debugBuilder");
|
||||
const { BufferToJson, getMembersInRole, getKeyByArrayValue } = require("../utilities/utils");
|
||||
const { getMembersInRole, getAllClientIds } = require("../utilities/utils");
|
||||
const { requestOptions, sendHttpRequest } = require("../utilities/httpRequests");
|
||||
const { readFileSync } = require('fs');
|
||||
const { getOnlineNodes, getNodeInfoFromId, updateNodeInfo } = require("../utilities/mysqlHandler");
|
||||
const path = require('path');
|
||||
const { getOnlineNodes, updateNodeInfo, addNodeConnection, getConnectionByNodeId } = require("../utilities/mysqlHandler");
|
||||
|
||||
// Global Vars
|
||||
const log = new DebugBuilder("server", "join");
|
||||
@@ -25,61 +23,66 @@ async function joinServerWrapper(presetName, channelId, clientIdsUsed) {
|
||||
recordResolve(nodeRows);
|
||||
});
|
||||
});
|
||||
|
||||
// Check which nodes have the selected preset
|
||||
onlineNodes = onlineNodes.filter(node => node.nearbySystems.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 reqOptions = new requestOptions("/bot/status", "GET", node.ip, node.port);
|
||||
await new Promise(resolve => sendHttpRequest(reqOptions, "", (responseObj) => {
|
||||
if (!responseObj || !responseObj.statusCode == 200) return resolve(false);
|
||||
log.VERBOSE("Response Object from node ", node, responseObj);
|
||||
nodesCurrentlyAvailable.push(node);
|
||||
resolve(true);
|
||||
}));
|
||||
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 Object(JSON.parse(readFileSync(path.resolve(__dirname, '../clientIds.json'))));
|
||||
var availableClientIds = await getAllClientIds();
|
||||
log.DEBUG("All clients: ", Object.keys(availableClientIds));
|
||||
|
||||
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
|
||||
selectedNode.connected = true;
|
||||
const updatedNode = await updateNodeInfo(selectedNode)
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
exports.joinServerWrapper = joinServerWrapper;
|
||||
@@ -98,15 +101,16 @@ module.exports = {
|
||||
try{
|
||||
const guildId = interaction.guild.id;
|
||||
const presetName = interaction.options.getString('preset');
|
||||
if (!interaction.member.voice.channel.id) return interaction.editReply(`You need to be in a voice channel, ${interaction.user}`)
|
||||
const channelId = interaction.member.voice.channel.id;
|
||||
log.DEBUG(`Join requested by: ${interaction.user.username}, to: '${presetName}', in channel: ${channelId} / ${guildId}`);
|
||||
await interaction.editReply('**Pong.**');
|
||||
|
||||
const onlineBots = await getMembersInRole(interaction);
|
||||
|
||||
log.DEBUG("Online Bots: ", onlineBots);
|
||||
|
||||
await joinServerWrapper(presetName, channelId, onlineBots.online);
|
||||
await interaction.editReply('**Pong.**');
|
||||
//await interaction.channel.send('**Pong.**'); // This will send a message to the channel of the interaction outside of the initial reply
|
||||
}catch(err){
|
||||
log.ERROR(err)
|
||||
|
||||
78
Server/commands/leave.js
Normal file
78
Server/commands/leave.js
Normal file
@@ -0,0 +1,78 @@
|
||||
// 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 discor
|
||||
// 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)
|
||||
.setRequired(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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,9 @@ module.exports = {
|
||||
requiresTokens: false,
|
||||
defaultTokenUsage: 0,
|
||||
deferInitialReply: false,
|
||||
/*async autocomplete(interaction) {
|
||||
const focusedValue = interaction.options.getFocused();
|
||||
},*/
|
||||
async execute(interaction) {
|
||||
try{
|
||||
await interaction.channel.send('**Pong.**'); // TODO - Add insults as the response to this command
|
||||
|
||||
@@ -8,45 +8,53 @@ const log = new DebugBuilder("server", "interactionCreate");
|
||||
module.exports = {
|
||||
name: Events.InteractionCreate,
|
||||
async execute(interaction) {
|
||||
const command = interaction.client.commands.get(interaction.commandName);
|
||||
log.VERBOSE("Interaction for command: ", command);
|
||||
|
||||
// Execute autocomplete if the user is checking autocomplete
|
||||
if (interaction.isAutocomplete()) {
|
||||
log.DEBUG("Running autocomplete for command: ", command.data.name);
|
||||
return await command.autocomplete(interaction);
|
||||
}
|
||||
|
||||
// Check if the interaction is a command
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
const command = interaction.client.commands.get(interaction.commandName);
|
||||
if (!command) {
|
||||
log.ERROR(`No command matching ${interaction.commandName} was found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!command) {
|
||||
log.ERROR(`No command matching ${interaction.commandName} was found.`);
|
||||
return;
|
||||
}
|
||||
log.DEBUG(`${interaction.member.user} is running '${interaction.commandName}'`);
|
||||
|
||||
log.DEBUG(`${interaction.member.user} is running '${interaction.commandName}'`);
|
||||
|
||||
await authorizeCommand(interaction, command, async () => {
|
||||
await authorizeTokenUsage(interaction, command, undefined, async () => {
|
||||
try {
|
||||
if (command.deferInitialReply) {
|
||||
try {
|
||||
if (interaction.options.getBool('public') && interaction.options.getBool('public') == false) await interaction.deferReply({ ephemeral: true });
|
||||
else await interaction.deferReply({ ephemeral: false });
|
||||
}
|
||||
catch (err) {
|
||||
if (err instanceof TypeError) {
|
||||
// The public option doesn't exist in this command
|
||||
await interaction.deferReply({ ephemeral: false });
|
||||
} else {
|
||||
throw err;
|
||||
await authorizeCommand(interaction, command, async () => {
|
||||
await authorizeTokenUsage(interaction, command, undefined, async () => {
|
||||
try {
|
||||
if (command.deferInitialReply) {
|
||||
try {
|
||||
if (interaction.options.getBool('public') && interaction.options.getBool('public') == false) await interaction.deferReply({ ephemeral: true });
|
||||
else await interaction.deferReply({ ephemeral: false });
|
||||
}
|
||||
catch (err) {
|
||||
if (err instanceof TypeError) {
|
||||
// The public option doesn't exist in this command
|
||||
await interaction.deferReply({ ephemeral: false });
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
command.execute(interaction);
|
||||
} catch (error) {
|
||||
log.ERROR(error);
|
||||
if (interaction.replied || interaction.deferred) {
|
||||
interaction.followUp({ content: 'There was an error while executing this command!', ephemeral: true });
|
||||
} else {
|
||||
interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
|
||||
}
|
||||
}
|
||||
command.execute(interaction);
|
||||
} catch (error) {
|
||||
log.ERROR(error);
|
||||
if (interaction.replied || interaction.deferred) {
|
||||
interaction.followUp({ content: 'There was an error while executing this command!', ephemeral: true });
|
||||
} else {
|
||||
interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
})
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -9,6 +9,6 @@ const log = new DebugBuilder("server", "messageCreate");
|
||||
module.exports = {
|
||||
name: Events.MessageCreate,
|
||||
async execute(interaction) {
|
||||
await linkCop(interaction);
|
||||
//await linkCop(interaction);
|
||||
},
|
||||
};
|
||||
@@ -32,17 +32,38 @@ var runningPostsToRemove = [{
|
||||
}]
|
||||
*/
|
||||
var runningPostsToRemove = {};
|
||||
const sourceFailureLimit = process.env.SOURCE_FAILURE_LIMIT ?? 3;
|
||||
const sourceFailureLimit = process.env.SOURCE_FAILURE_LIMIT ?? 15;
|
||||
|
||||
/**
|
||||
* Wrapper for feeds that cause errors. By default it will wait over a day for the source to come back online before deleting it.
|
||||
*
|
||||
* @param {*} sourceURL
|
||||
* @param {string} sourceURL The URL of the feed source causing issues
|
||||
*/
|
||||
exports.removeSource = function removeSource(sourceURL) {
|
||||
log.INFO("Removing source URL: ", sourceURL);
|
||||
if (!sourceURL in runningPostsToRemove) {runningPostsToRemove[sourceURL] = 1; return;}
|
||||
// Check to see if this is the first time this source has been attempted
|
||||
if (!Object.keys(runningPostsToRemove).includes(sourceURL)) {
|
||||
runningPostsToRemove[sourceURL] = { count: 1, timestamp: Date.now(), ignoredAttempts: 0 };
|
||||
return;
|
||||
}
|
||||
|
||||
if (runningPostsToRemove[sourceURL] < sourceFailureLimit) {runningPostsToRemove[sourceURL] += 1; return;}
|
||||
const backoffDateTimeDifference = (Date.now() - new Date(runningPostsToRemove[sourceURL].timestamp));
|
||||
const backoffWaitTime = (runningPostsToRemove[sourceURL].count * 30000);
|
||||
|
||||
log.DEBUG("Datetime", runningPostsToRemove[sourceURL], backoffDateTimeDifference, backoffWaitTime);
|
||||
|
||||
// Check to see if the last error occurred within the backoff period or if we should try again
|
||||
if (backoffDateTimeDifference <= backoffWaitTime) {
|
||||
runningPostsToRemove[sourceURL].ignoredAttempts +=1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Increase the retry counter
|
||||
if (runningPostsToRemove[sourceURL].count < sourceFailureLimit) {
|
||||
runningPostsToRemove[sourceURL].count += 1;
|
||||
runningPostsToRemove[sourceURL].timestamp = Date.now();
|
||||
return;
|
||||
}
|
||||
|
||||
feedStorage.getRecordBy('link', sourceURL, (err, record) => {
|
||||
if (err) log.ERROR("Error getting record from feedStorage", err);
|
||||
@@ -62,13 +83,14 @@ exports.removeSource = function removeSource(sourceURL) {
|
||||
/**
|
||||
* Unset a source URL from deletion if the source has not already been deleted
|
||||
* @param {*} sourceURL The source URL to be unset from deletion
|
||||
* @returns {*}
|
||||
*/
|
||||
exports.unsetRemoveSource = function unsetRemoveSource(sourceURL) {
|
||||
log.INFO("Unsetting source URL from deletion (if not already deleted): ", sourceURL);
|
||||
if (!sourceURL in runningPostsToRemove) return;
|
||||
if (!Object.keys(runningPostsToRemove).includes(sourceURL)) return;
|
||||
|
||||
if (runningPostsToRemove[sourceURL] > sourceFailureLimit) return delete runningPostsToRemove[sourceURL];
|
||||
delete runningPostsToRemove[sourceURL];
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
40
Server/modules/radioRecordingScraper/recordings_spider.py
Normal file
40
Server/modules/radioRecordingScraper/recordings_spider.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import scrapy
|
||||
from scrapy.crawler import CrawlerProcess
|
||||
|
||||
class RecordingSpider(scrapy.Spider):
|
||||
name = "recording-scraper"
|
||||
start_urls = [
|
||||
'https://radio.vpn.cusano.net/sdr/transmissions',
|
||||
]
|
||||
|
||||
def parse(self, response):
|
||||
print("ASDASDD")
|
||||
print(response)
|
||||
for row in response.css("tr"):
|
||||
if row.css('td.py-1'):
|
||||
links = row.css('a')
|
||||
rows = row.css('td.py-1')
|
||||
print(row)
|
||||
yield {
|
||||
'device': rows[0],
|
||||
'date': rows[1],
|
||||
'duration': rows[2],
|
||||
"frequency": rows[3],
|
||||
"link": links[0].attrib["href"],
|
||||
}
|
||||
|
||||
next_page_url = response.css("a.page-link > a::attr(href)").extract_first()
|
||||
if next_page_url is not None:
|
||||
yield scrapy.Request(response.urljoin(next_page_url))
|
||||
|
||||
|
||||
process = CrawlerProcess(
|
||||
settings={
|
||||
"FEEDS": {
|
||||
"items.json": {"format": "json"},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
process.crawl(RecordingSpider)
|
||||
process.start() # the script will block here until the crawling is finished
|
||||
3
Server/modules/radioRecordingScraper/requirements.txt
Normal file
3
Server/modules/radioRecordingScraper/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
scrapy
|
||||
fake-useragent
|
||||
beautifulsoup4
|
||||
@@ -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,6 @@ function returnNodeObjectFromRow(row) {
|
||||
_location: row.location,
|
||||
_nearbySystems: BufferToJson(row.nearbySystems),
|
||||
_online: (row.online === 1) ? true : false,
|
||||
_connected: (row.connected === 1) ? true : false
|
||||
});
|
||||
}
|
||||
|
||||
@@ -53,6 +53,22 @@ 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) {
|
||||
if (Array.isArray(row)) row = row[0]
|
||||
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
|
||||
*/
|
||||
@@ -72,7 +88,6 @@ exports.getAllNodes = (callback) => {
|
||||
*/
|
||||
exports.getAllNodesSync = async () => {
|
||||
const sqlQuery = `SELECT * FROM ${nodesTable}`
|
||||
var returnObjects = [];
|
||||
const rows = await runSQL(sqlQuery);
|
||||
|
||||
console.log("Rows: ", rows);
|
||||
@@ -94,20 +109,28 @@ exports.getOnlineNodes = (callback) => {
|
||||
* @param nodeId The ID of the node
|
||||
* @param callback Callback function
|
||||
*/
|
||||
exports.getNodeInfoFromId = (nodeId, callback) => {
|
||||
async function getNodeInfoFromId(nodeId, callback = undefined) {
|
||||
if (!nodeId) throw new Error("No node ID given when trying to fetch node");
|
||||
log.DEBUG("Getting node from ID: ", nodeId);
|
||||
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,23 +141,28 @@ 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,
|
||||
port = nodeObject.port,
|
||||
location = nodeObject.location,
|
||||
online = nodeObject.online,
|
||||
connected = nodeObject.connected;
|
||||
online = nodeObject.online
|
||||
let queryParams = [],
|
||||
nearbySystems = nodeObject.nearbySystems;
|
||||
|
||||
@@ -150,10 +178,6 @@ exports.updateNodeInfo = (nodeObject, callback = undefined) => {
|
||||
if (online || online === 1) queryParams.push(`online = 1`);
|
||||
else queryParams.push(`online = 0`);
|
||||
}
|
||||
if (typeof connected === "boolean" || typeof connected === "number") {
|
||||
if (online || online === 1) queryParams.push(`connected = 1`);
|
||||
else queryParams.push(`connected = 0`);
|
||||
}
|
||||
|
||||
let sqlQuery = `UPDATE ${nodesTable} SET`
|
||||
if (!queryParams || queryParams.length === 0) return (callback) ? callback(undefined) : undefined;
|
||||
@@ -175,21 +199,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 | sqlResponse.length == 0) 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);
|
||||
return (callback) ? callback(rows) : rows
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }) {
|
||||
this.id = _id;
|
||||
this.name = _name;
|
||||
this.ip = _ip;
|
||||
@@ -121,8 +122,45 @@ class nodeObject {
|
||||
this.location = _location;
|
||||
this.nearbySystems = _nearbySystems;
|
||||
this.online = _online;
|
||||
this.connected = _connected;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -1,6 +1,9 @@
|
||||
// Debug
|
||||
const { DebugBuilder } = require("../utilities/debugBuilder");
|
||||
const { clientObject } = require("./recordHelper");
|
||||
const { readFileSync } = require('fs');
|
||||
const log = new DebugBuilder("server", "utils");
|
||||
const path = require('path');
|
||||
|
||||
// Convert a JSON object to a buffer for the DB
|
||||
exports.JsonToBuffer = (jsonObject) => {
|
||||
@@ -30,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);
|
||||
|
||||
@@ -56,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]));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,3 +81,39 @@ exports.isJsonString = (str) => {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all client IDs from the saved JSON file
|
||||
*
|
||||
* @returns Object of Client IDs
|
||||
*/
|
||||
exports.getAllClientIds = () => {
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user