Major updates to core

This commit is contained in:
Logan Cusano
2024-02-10 15:10:35 -05:00
parent d563021866
commit 9c46792959
22 changed files with 977 additions and 156 deletions

View File

@@ -1,7 +1,7 @@
import { generateUniqueID } from './modules/baseUtils.mjs';
import { updateId } from './modules/updateConfig.mjs';
import { ClientNodeConfig } from './modules/clientObjectDefinitions.mjs';
import { initSocketConnection, initSocketListeners, sendNodeUpdate } from './modules/socketClient.mjs';
import { initSocketConnection } from './modules/socketClient.mjs';
import dotenv from 'dotenv';
dotenv.config()
@@ -9,7 +9,7 @@ dotenv.config()
var localNodeConfig = new ClientNodeConfig({})
async function boot() {
if (localNodeConfig.node.id === undefined || localNodeConfig.node.id === '' || localNodeConfig.node.id === 0) {
if (localNodeConfig.node.nuid === undefined || localNodeConfig.node.nuid === '' || localNodeConfig.node.nuid === 0) {
// Run the first time boot sequence
await firstTimeBoot();
}
@@ -25,10 +25,10 @@ async function boot() {
async function firstTimeBoot() {
// Generate a new ID for the node
localNodeConfig.node.id = generateUniqueID();
console.log(`Generated a new unique ID for this node: '${localNodeConfig.node.id}'`);
console.log(`Generated a new unique ID for this node: '${localNodeConfig.node.nuid}'`);
// Update the config with the new ID
updateId(localNodeConfig.node.id);
updateId(localNodeConfig.node.nuid);
console.log("Updated the config with the new node ID");
// TODO - Create the config file with the ID given and replace the update above
// TODO - Check if the system is linux or windows and set the 'type' param in DAB
@@ -41,7 +41,5 @@ async function firstTimeBoot() {
// Boot the client application
boot().then((openSocket) => {
initSocketListeners(openSocket, localNodeConfig);
//console.log(openSocket, "Open socket");
setTimeout(() => {sendNodeUpdate(openSocket);}, 2500);
console.log(openSocket, "Booted Sucessfully");
})

View File

@@ -78,6 +78,7 @@ export async function connectToChannel(channel) {
});
try {
await entersState(connection, VoiceConnectionStatus.Ready, 30_000);
await connection.subscribe(player);
return connection;
} catch (error) {
connection.destroy();
@@ -100,6 +101,8 @@ export async function initDiscordBotClient(token, readyCallback){
readyCallback(client);
});
/* on event create
// TODO - Implement methods for discord users to interact directly with the bots for realtime info (last talked ID/TG, etc.)
client.on(Events.MessageCreate, async (message) => {
if (!message.guild) return;
console.log(`New Message:`, message.content);
@@ -118,6 +121,7 @@ export async function initDiscordBotClient(token, readyCallback){
}
}
});
*/
client.login(token);
}

View File

@@ -1,5 +1,7 @@
import { networkInterfaces } from 'os';
import { createHash, randomBytes } from 'crypto';
import { promises, constants } from 'fs';
import { dirname } from "path";
/**
* Check to see if the input is a valid JSON string
@@ -7,7 +9,7 @@ import { createHash, randomBytes } from 'crypto';
* @param {*} str The string to check for valud JSON
* @returns {true|false}
*/
export function isJsonString (str) {
export const isJsonString = (str) => {
try {
JSON.parse(str);
} catch (e) {
@@ -16,12 +18,25 @@ export function isJsonString (str) {
return true;
}
/**
* Check to see if a path exists, creating it if it does not
* @returns {undefined}
*/
export const ensureDirectoryExists = async (filePath) => {
const directory = dirname(filePath);
try {
await promises.access(directory, constants.F_OK);
} catch (error) {
await promises.mkdir(directory, { recursive: true });
}
};
/**
* Generate a unique ID for a given device, this will also be unique on the same device at a different time.
* @returns {string} A generated unique ID
*/
export function generateUniqueID () {
export const generateUniqueID = () => {
const netInterfaces = networkInterfaces();
let macAddress = '';

View File

@@ -1,3 +1,5 @@
import { getAllPresets } from "./radioPresetHandler.mjs";
import dotenv from 'dotenv';
dotenv.config()
@@ -7,33 +9,43 @@ dotenv.config()
export class ClientNodeObject {
/**
*
* @param {string} param0._id The ID of the node (Assigned by the client on first boot)
* @param {string} param0._nuid The ID of the node (Assigned by the client on first boot)
* @param {string} param0._name The name of the node (Assigned by the user)
* @param {string} param0._location The physical location of the node (Assigned by the user)
* @param {object} param0._nearbySystems An object array of nearby systems (Assigned by the user)
* @param {string} param0._location The physical location of the node (Assigned by the user)
* @param {object} param0._capabilities The capabilities of this node (Assigned by the user, and determined by the hardware)
*/
constructor({ _id = undefined, _name = undefined, _location = undefined, _nearbySystems = undefined}) {
this.id = _id;
constructor({ _nuid = undefined, _name = undefined, _location = undefined, _capabilities = undefined }) {
this.nuid = _nuid;
this.name = _name;
this.location = _location;
this.nearbySystems = _nearbySystems;
this.location = _location;
this.capabilities = _capabilities
}
}
/** The configuration object for the node */
export class ClientNodeConfig {
/**
*
* @param {string} param0._nuid The ID of the node (Assigned by the client on first boot)
* @param {string} param0._name The name of the node (Assigned by the user)
* @param {string} param0._location The physical location of the node (Assigned by the user)
* @param {object} param0._nearbySystems An object array of nearby systems (Assigned by the user)
* @param {object} param0._capabilities The capabilities of this node (Assigned by the user, and determined by the hardware)
*/
constructor({
_id = process.env.CLIENT_ID,
_name = process.env.CLIENT_NAME,
_location = process.env.CLIENT_LOCATION,
_nearbySystems = undefined, // TODO - Get the presets when loading the config instead of having to do it after the fact
_serverIp = process.env.SERVER_IP,
_serverPort = process.env.SERVER_PORT,
_nuid = process.env.CLIENT_NUID,
_name = process.env.CLIENT_NAME,
_location = process.env.CLIENT_LOCATION,
_nearbySystems = getAllPresets(),
_capabilities = process.env.CLIENT_CAPABILITIES.split(", "),
_serverIp = process.env.SERVER_IP,
_serverPort = process.env.SERVER_PORT,
}) {
this.node = new ClientNodeObject({
_id:_id, _name:_name, _location:_location, _nearbySystems:_nearbySystems
_nuid: _nuid, _name: _name, _location: _location, _capabilities: _capabilities
});
this.nearbySystems = _nearbySystems;
this.serverIp = _serverIp;
this.serverPort = _serverPort;
}

View File

@@ -1,22 +1,27 @@
// Debug
const { DebugBuilder } = require("../utilities/debugBuilder.js");
const log = new DebugBuilder("client", "updatePresets");
// Modules
const fs = require('fs');
const path = require("path");
const converter = require("convert-units");
import { writeFile, existsSync, readFileSync } from 'fs';
import { resolve } from "path";
import { ensureDirectoryExists } from "./baseUtils.mjs";
import convert_units from "convert-units";
const { converter } = convert_units;
import dotenv from 'dotenv';
dotenv.config()
const configFilePath = process.env.CONFIG_PATH;
/**
* 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) {
log.DEBUG(`${__dirname}`);
fs.writeFile("./config/radioPresets.json", JSON.stringify(presets), (err) => {
const writePresets = async (presets, callback = undefined) => {
console.log(`${__dirname}`);
await ensureDirectoryExists(configFilePath);
writeFile(configFilePath, JSON.stringify(presets), (err) => {
// Error checking
if (err) throw err;
log.DEBUG("Write Complete");
console.log("Write Complete");
if (callback) callback(); else return
});
}
@@ -26,14 +31,14 @@ function writePresets(presets, callback = undefined) {
* @param frequenciesArray
* @returns {*[]}
*/
function sanitizeFrequencies(frequenciesArray) {
const sanitizeFrequencies = async (frequenciesArray) => {
let sanitizedFrequencyArray = [];
for (const freq of frequenciesArray) {
sanitizedFrequencyArray.push(convertFrequencyToHertz(freq));
}
log.DEBUG("Sanitized Frequency Array", sanitizedFrequencyArray);
console.log("Sanitized Frequency Array", sanitizedFrequencyArray);
return sanitizedFrequencyArray;
}
@@ -42,23 +47,23 @@ function sanitizeFrequencies(frequenciesArray) {
* @param frequency Could be a string, number or float,
* @returns {number|number|*} Return the value to be saved in Hz format ("154.875"MHz format = "154875000")
*/
function convertFrequencyToHertz(frequency){
const convertFrequencyToHertz = async (frequency) => {
// check if the passed value is a number
if(typeof frequency == 'number' && !isNaN(frequency)){
if (typeof frequency == 'number' && !isNaN(frequency)) {
if (Number.isInteger(frequency)) {
log.DEBUG(`${frequency} is an integer.`);
console.log(`${frequency} is an integer.`);
// Check to see if the frequency has the correct length
if (frequency >= 1000000) return frequency
if (frequency >= 100 && frequency <= 999) return frequency * 1000000
log.WARN("Frequency hasn't matched filters: ", frequency);
console.log("Frequency hasn't matched filters: ", frequency);
}
else {
log.DEBUG(`${frequency} is a float value.`);
console.log(`${frequency} is a float value.`);
// Convert to a string to remove the decimal in place and then correct the length
return parseInt(converter(frequency).from("MHz").to("Hz"));
}
} else {
log.DEBUG(`${frequency} is not a number`);
console.log(`${frequency} is not a number`);
frequency = convertFrequencyToHertz(parseFloat(frequency));
return parseInt(frequency)
@@ -69,10 +74,10 @@ function convertFrequencyToHertz(frequency){
* Gets the saved presets and returns a preset object
* @returns {any} The object containing the different systems the bot is near
*/
function getPresets() {
const presetDir = path.resolve("./config/radioPresets.json");
log.DEBUG(`Getting presets from directory: '${presetDir}'`);
if (fs.existsSync(presetDir)) return JSON.parse(fs.readFileSync(presetDir));
export const getAllPresets = () => {
const presetDir = resolve(configFilePath);
console.log(`Getting presets from directory: '${presetDir}'`);
if (existsSync(presetDir)) return JSON.parse(readFileSync(presetDir));
else return {};
}
@@ -86,15 +91,15 @@ function getPresets() {
* @param {string} trunkFile The file that contains all trunking information (if applicable to the selected listening mode)
* @param {string} whitelistFile The file that contains the whitelisted talkgroups [optional]
*/
function addNewPreset(systemName, frequencies, mode, callback, trunkFile = undefined, whitelistFile = undefined) {
export const addNewPreset = (systemName, frequencies, mode, callback, trunkFile = undefined, whitelistFile = undefined) => {
const presets = this.getPresets();
// Create the preset for the new system
presets[systemName] = {
"frequencies": sanitizeFrequencies(frequencies),
"mode": mode,
"trunkFile": trunkFile ?? "none",
"whitelistFile": whitelistFile ?? "none"
}
"frequencies": sanitizeFrequencies(frequencies),
"mode": mode,
"trunkFile": trunkFile ?? "none",
"whitelistFile": whitelistFile ?? "none"
}
// Write the changes to the preset config file
writePresets(presets, callback);
}
@@ -109,15 +114,15 @@ function addNewPreset(systemName, frequencies, mode, callback, trunkFile = undef
* @param {string} trunkFile The file that contains all trunking information (if applicable to the selected listening mode)
* @param {string} whitelistFile The file that contains the whitelisted talkgroups [optional]
*/
function updatePreset(systemName, callback, { frequencies = undefined, mode = undefined, trunkFile = undefined, whitelistFile = undefined }) {
export const updatePreset = (systemName, callback, { frequencies = undefined, mode = undefined, trunkFile = undefined, whitelistFile = 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 && sanitizeFrequencies(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";
if(whitelistFile && whitelistFile !== presets[systemName].whitelistFile || whitelistFile === "") presets[systemName].whitelistFile = whitelistFile ?? "none";
if (frequencies && sanitizeFrequencies(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";
if (whitelistFile && whitelistFile !== presets[systemName].whitelistFile || whitelistFile === "") presets[systemName].whitelistFile = whitelistFile ?? "none";
// Write the changes
writePresets(presets, callback);
}
@@ -129,14 +134,11 @@ function updatePreset(systemName, callback, { frequencies = undefined, mode = un
* @param {string} systemName The name of the system being modified
* @param {function} callback The callback function to be called when the function completes
*/
function removePreset(systemName, callback) {
export const removePreset = (systemName, callback) => {
const presets = this.getPresets();
// Check if a system name was passed
if (systemName in presets) {
delete presets[systemName];
writePresets(presets, callback);
}
}
}

View File

@@ -1,34 +1,28 @@
import { io } from "socket.io-client";
import { connectToChannel, initDiscordBotClient, getVoiceChannelFromID } from '../discordAudioBot/dab.mjs';
import { logIntoServerWrapper, sendNodeUpdateWrapper } from "./socketClientWrappers.mjs";
export function initSocketConnection(localNodeConfig) {
export const initSocketConnection = async (localNodeConfig) => {
const serverEndpoint = `http://${localNodeConfig.serverIp}:${localNodeConfig.serverPort}` || 'http://localhost:3000'; // Adjust the server endpoint
const socket = io.connect(serverEndpoint);
return socket;
}
export function initSocketListeners(socket, localNodeConfig) {
socket.on('connect', () => {
socket.on('connect', async () => {
console.log('Connected to the server');
logIntoServer(socket, localNodeConfig.node);
await logIntoServerWrapper(socket, localNodeConfig);
});
socket.on('node-join', async (joinData) => {
console.log("Join requested: ", joinData)
// TODO - Implement logic to control OP25 for the requested channel
// TODO - Implement logic to control OP25 for the requested channel/system
// Join the requested channel with the requested ID
initDiscordBotClient(joinData.clientID, client => {
console.log("Client:", client);
initDiscordBotClient(joinData.clientID, client => {
getVoiceChannelFromID(client, joinData.channelID).then(vc => {
console.log("Voice Channel:", vc);
console.log("Voice Channel Guild:", vc.Guild);
const connection = connectToChannel(vc);
console.log("Bot Connected to VC");
})
});
console.log("All done?");
});
});
socket.on('node-leave', () => {
@@ -39,12 +33,5 @@ export function initSocketListeners(socket, localNodeConfig) {
console.log('Disconnected from the server');
});
}
export function logIntoServer(socket, nodeData) {
socket.emit("node-login", nodeData);
}
export function sendNodeUpdate(socket, nodeData) {
socket.emit('node-update', nodeData);
return socket;
}

View File

@@ -0,0 +1,11 @@
export const logIntoServerWrapper = async (socket, localNodeConfig) => {
socket.emit("node-login", localNodeConfig.node);
sendNodeUpdateWrapper(socket, localNodeConfig);
}
export const sendNodeUpdateWrapper = async (socket, localNodeConfig) => {
socket.emit('node-update', {
'node': localNodeConfig.node,
'nearbySystems': localNodeConfig.nearbySystems
});
}

View File

@@ -17,8 +17,8 @@ class Options {
* @param updatedId The updated ID assigned to the node
*/
export function updateId (updatedId) {
updateConfig('CLIENT_ID', updatedId);
process.env.CLIENT_ID = updatedId;
updateConfig('CLIENT_NUID', updatedId);
process.env.CLIENT_NUID = updatedId;
console.log("Updated ID to: ", updatedId);
}
@@ -41,7 +41,7 @@ export function updateClientConfig (runningConfig, newConfigObject) {
if (configKeys.includes("id")) {
if (runningConfig.id != newConfigObject.id) {
this.updateId(newConfigObject.id);
updatedKeys.push({ 'CLIENT_ID': newConfigObject.id });
updatedKeys.push({ 'CLIENT_NUID': newConfigObject.id });
}
}
if (configKeys.includes("name")) {

164
client/package-lock.json generated
View File

@@ -10,6 +10,7 @@
"license": "ISC",
"dependencies": {
"@discordjs/voice": "^0.16.1",
"convert-units": "^2.3.4",
"discord.js": "^14.14.1",
"dotenv": "^16.3.1",
"libsodium-wrappers": "^0.7.13",
@@ -321,6 +322,15 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/convert-units": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/convert-units/-/convert-units-2.3.4.tgz",
"integrity": "sha512-ERHfdA0UhHJp1IpwE6PnFJx8LqG7B1ZjJ20UvVCmopEnVCfER68Tbe3kvN63dLbYXDA2xFWRE6zd4Wsf0w7POg==",
"dependencies": {
"lodash.foreach": "2.3.x",
"lodash.keys": "2.3.x"
}
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -522,11 +532,165 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash._basebind": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._basebind/-/lodash._basebind-2.3.0.tgz",
"integrity": "sha512-SHqM7YCuJ+BeGTs7lqpWnmdHEeF4MWxS3dksJctHFNxR81FXPOzA4bS5Vs5CpcGTkBpM8FCl+YEbQEblRw8ABg==",
"dependencies": {
"lodash._basecreate": "~2.3.0",
"lodash._setbinddata": "~2.3.0",
"lodash.isobject": "~2.3.0"
}
},
"node_modules/lodash._basecreate": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-2.3.0.tgz",
"integrity": "sha512-vwZaWldZwS2y9b99D8i9+WtgiZXbHKsBsMrpxJEqTsNW20NhJo5W8PBQkeQO9CmxuqEYn8UkMnfEM2MMT4cVrw==",
"dependencies": {
"lodash._renative": "~2.3.0",
"lodash.isobject": "~2.3.0",
"lodash.noop": "~2.3.0"
}
},
"node_modules/lodash._basecreatecallback": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._basecreatecallback/-/lodash._basecreatecallback-2.3.0.tgz",
"integrity": "sha512-Ev+pDzzfVfgbiucpXijconLGRBar7/+KNCf05kSnk4CmdDVhAy1RdbU9efCJ/o9GXI08JdUGwZ+5QJ3QX3kj0g==",
"dependencies": {
"lodash._setbinddata": "~2.3.0",
"lodash.bind": "~2.3.0",
"lodash.identity": "~2.3.0",
"lodash.support": "~2.3.0"
}
},
"node_modules/lodash._basecreatewrapper": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._basecreatewrapper/-/lodash._basecreatewrapper-2.3.0.tgz",
"integrity": "sha512-YLycQ7k8AB9Wc1EOvLNxuRWcqipDkMXq2GCgnLWQR6qtgTb3gY3LELzEpnFshrEO4LOLs+R2EpcY+uCOZaLQ8Q==",
"dependencies": {
"lodash._basecreate": "~2.3.0",
"lodash._setbinddata": "~2.3.0",
"lodash._slice": "~2.3.0",
"lodash.isobject": "~2.3.0"
}
},
"node_modules/lodash._createwrapper": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._createwrapper/-/lodash._createwrapper-2.3.0.tgz",
"integrity": "sha512-XjaI/rzg9W+WO4WJDQ+PRlHD5sAMJ1RhJLuT65cBxLCb1kIYs4U20jqvTDGAWyVT3c34GYiLd9AreHYuB/8yJA==",
"dependencies": {
"lodash._basebind": "~2.3.0",
"lodash._basecreatewrapper": "~2.3.0",
"lodash.isfunction": "~2.3.0"
}
},
"node_modules/lodash._objecttypes": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.3.0.tgz",
"integrity": "sha512-jbA6QyHt9cw3BzvbWzIcnU3Z12jSneT6xBgz3Y782CJsN1tV5aTBKrFo2B4AkeHBNaxSrbPYZZpi1Lwj3xjdtg=="
},
"node_modules/lodash._renative": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._renative/-/lodash._renative-2.3.0.tgz",
"integrity": "sha512-v44MRirqYqZGK/h5UKoVqXWF2L+LUiLTU+Ogu5rHRVWJUA1uWIlHaMpG8f/OA8j++BzPMQij9+erXHtgFcbuwg=="
},
"node_modules/lodash._setbinddata": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._setbinddata/-/lodash._setbinddata-2.3.0.tgz",
"integrity": "sha512-xMFfbF7dL+sFtrdE49uHFmfpBAEwlFtfgMp86nQRlAF6aizYL+3MTbnYMKJSkP1W501PhsgiBED5kBbZd8kR2g==",
"dependencies": {
"lodash._renative": "~2.3.0",
"lodash.noop": "~2.3.0"
}
},
"node_modules/lodash._shimkeys": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._shimkeys/-/lodash._shimkeys-2.3.0.tgz",
"integrity": "sha512-9Iuyi7TiWMGa/9+2rqEE+Zwye4b/U2w7Saw6UX1h6Xs88mEER+uz9FZcEBPKMVKsad9Pw5GNAcIBRnW2jNpneQ==",
"dependencies": {
"lodash._objecttypes": "~2.3.0"
}
},
"node_modules/lodash._slice": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash._slice/-/lodash._slice-2.3.0.tgz",
"integrity": "sha512-7C61GhzRUv36gTafr+RIb+AomCAYsSATEoK4OP0VkNBcwvsM022Z22AVgqjjzikeNO1U29LzsJZDvLbiNPUYvA=="
},
"node_modules/lodash.bind": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-2.3.0.tgz",
"integrity": "sha512-goakyOo+FMN8lttMPnZ0UNlr5RlzX4IrUXyTJPT2A0tGCMXySupond9wzvDqTvVmYTcQjIKGrj8naJDS2xWAlQ==",
"dependencies": {
"lodash._createwrapper": "~2.3.0",
"lodash._renative": "~2.3.0",
"lodash._slice": "~2.3.0"
}
},
"node_modules/lodash.foreach": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-2.3.0.tgz",
"integrity": "sha512-yLnyptVRJd0//AbGp480grgQG9iaDIV5uOgSbpurRy1dYybPbjNTLQ3FyLEQ84buVLPG7jyaiyvpzgfOutRB3Q==",
"dependencies": {
"lodash._basecreatecallback": "~2.3.0",
"lodash.forown": "~2.3.0"
}
},
"node_modules/lodash.forown": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.forown/-/lodash.forown-2.3.0.tgz",
"integrity": "sha512-dUnCsuQTtq3Y7bxPNoEEqjJjPL2ftLtcz2PTeRKvhbpdM514AvnqCjewHGsm/W+dwspIwa14KoWEZeizJ7smxA==",
"dependencies": {
"lodash._basecreatecallback": "~2.3.0",
"lodash._objecttypes": "~2.3.0",
"lodash.keys": "~2.3.0"
}
},
"node_modules/lodash.identity": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.identity/-/lodash.identity-2.3.0.tgz",
"integrity": "sha512-NYJ2r2cwy3tkx/saqbIZEX6oQUzjWTnGRu7d/zmBjMCZos3eHBxCpbvWFWSetv8jFVrptsp6EbWjzNgBKhUoOA=="
},
"node_modules/lodash.isfunction": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-2.3.0.tgz",
"integrity": "sha512-X5lteBYlCrVO7Qc00fxP8W90fzRp6Ax9XcHANmU3OsZHdSyIVZ9ZlX5QTTpRq8aGY+9I5Rmd0UTzTIIyWPugEQ=="
},
"node_modules/lodash.isobject": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.3.0.tgz",
"integrity": "sha512-jo1pfV61C4TE8BfEzqaHj6EIKiSkFANJrB6yscwuCJMSRw5tbqjk4Gv7nJzk4Z6nFKobZjGZ8Qd41vmnwgeQqQ==",
"dependencies": {
"lodash._objecttypes": "~2.3.0"
}
},
"node_modules/lodash.keys": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.3.0.tgz",
"integrity": "sha512-c0UW0ffqMxSCtoVbmVt2lERJLkEqgoOn2ejPsWXzr0ZrqRbl3uruGgwHzhtqXxi6K/ei3Ey7zimOqSwXgzazPg==",
"dependencies": {
"lodash._renative": "~2.3.0",
"lodash._shimkeys": "~2.3.0",
"lodash.isobject": "~2.3.0"
}
},
"node_modules/lodash.noop": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.noop/-/lodash.noop-2.3.0.tgz",
"integrity": "sha512-NpSm8HRm1WkBBWHUveDukLF4Kfb5P5E3fjHc9Qre9A11nNubozLWD2wH3UBTZbu+KSuX8aSUvy9b+PUyEceJ8g=="
},
"node_modules/lodash.snakecase": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
"integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="
},
"node_modules/lodash.support": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lodash.support/-/lodash.support-2.3.0.tgz",
"integrity": "sha512-etc7VWbB0U3Iya8ixj2xy4sDBN3jvPX7ODi8iXtn4KkkjNpdngrdc7Vlt5jub/Vgqx6/dWtp7Ml9awhCQPYKGQ==",
"dependencies": {
"lodash._renative": "~2.3.0"
}
},
"node_modules/magic-bytes.js": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.7.0.tgz",

View File

@@ -12,6 +12,7 @@
"type": "module",
"dependencies": {
"@discordjs/voice": "^0.16.1",
"convert-units": "^2.3.4",
"discord.js": "^14.14.1",
"dotenv": "^16.3.1",
"libsodium-wrappers": "^0.7.13",

View File

@@ -0,0 +1,25 @@
import { SlashCommandBuilder } from 'discord.js';
// Exporting data property
export const data = new SlashCommandBuilder()
.setName('join')
.setDescription('Replies with your input!');
// Exporting other properties
export const example = "/join";
export const deferInitialReply = false;
// Exporting execute function
export async function execute(nodeIo, interaction) {
try {
const sockets = await nodeIo.allSockets();
console.log("All open sockets: ",sockets);
console.log("Open Socket: ", sockets.values(), nodeIo.sockets.sockets.get(sockets[0]))
//await interaction.reply(`**Online Sockets: '${sockets}'**`);
await interaction.reply('**Pong.**');
//await interaction.channel.send('**Pong.**');
} catch (err) {
console.error(err);
// await interaction.reply(err.toString());
}
}

View File

@@ -1,29 +1,32 @@
import { Client, GatewayIntentBits, Collection } from 'discord.js';
import { registerActiveCommands, unregisterAllCommands } from './modules/registerCommands.mjs'
import { join, dirname } from 'path';
import { readdirSync } from 'fs';
import { fileURLToPath } from 'url';
import { Collection } from 'discord.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
import dotenv from 'dotenv';
dotenv.config()
/**
* Add the enabled commands to the bot to be used by users in discord
* (commands that end in '.mjs' will be enabled, to disable just remove the extension or replace with '.mjs.disabled')
* @param {any} serverClient
* @param {any} nodeIo
* @param {any} _commandsPath="./commands"
* @returns {any}
*/
export function addEnabledCommands(serverClient, nodeIo, _commandsPath = "../commands") {
export const addEnabledCommands = async (serverClient, _commandsPath = "./commands") => {
// Setup commands for the Discord bot
serverClient.commands = new Collection();
const commandsPath = join(__dirname, _commandsPath);
const commandFiles = readdirSync(commandsPath).filter(file => file.endsWith('.mjs'));
for (const file of commandFiles) {
const filePath = join(commandsPath, file);
const filePath = await join(commandsPath, file);
console.log(`Adding enabled command: ${filePath}`);
import(`file://${filePath}`).then(command => {
await import(`file://${filePath}`).then(command => {
if (command.data instanceof Promise) {
command.data.then(async (builder) => {
command.data = builder;
@@ -40,17 +43,19 @@ export function addEnabledCommands(serverClient, nodeIo, _commandsPath = "../com
}
})
}
// Register the commands currently in use by the bot
await registerActiveCommands(serverClient);
}
/**
* Add the enabled event listeners to the bot
* (events that end in '.mjs' will be enabled, to disable just remove the extension or replace with '.mjs.disabled')
* @param {any} serverClient
* @param {any} nodeIo
* @param {any} _eventsPath="./events"
* @returns {any}
*/
export function addEnabledEventListeners(serverClient, nodeIo, _eventsPath = "../events") {
export function addEnabledEventListeners(serverClient, _eventsPath = "./events") {
const eventsPath = join(__dirname, _eventsPath);
const eventFiles = readdirSync(eventsPath).filter(file => file.endsWith('.mjs'));
@@ -60,10 +65,28 @@ export function addEnabledEventListeners(serverClient, nodeIo, _eventsPath = "..
import(`file://${filePath}`).then(event => {
console.log("Adding event: ", event);
if (event.once) {
serverClient.once(event.name, (...args) => event.execute(nodeIo, ...args));
serverClient.once(event.name, (...args) => event.execute(serverClient.nodeIo, ...args));
} else {
serverClient.on(event.name, (...args) => event.execute(nodeIo, ...args));
serverClient.on(event.name, (...args) => event.execute(serverClient.nodeIo, ...args));
}
})
}
}
}
// The discord client
export const serverClient = new Client({ intents: [GatewayIntentBits.Guilds] });
// Run when the bot is ready
serverClient.on('ready', async () => {
console.log(`Logged in as ${serverClient.user.tag}!`);
// Add and register commands
await addEnabledCommands(serverClient);
// Config the discord bot with events
await addEnabledEventListeners(serverClient);
});
// Startup the discord bot
console.log(`Logging into discord with ID: ${process.env.DISCORD_TOKEN}`);
serverClient.login(process.env.DISCORD_TOKEN);

View File

@@ -0,0 +1,83 @@
import { REST, Routes } from 'discord.js';
import dotenv from 'dotenv';
dotenv.config()
const discordToken = process.env.DISCORD_TOKEN;
export const registerActiveCommands = async (serverClient) => {
const guildIDs = serverClient.guilds.cache;
const clientId = serverClient.user.id;
const commands = await serverClient.commands.map(command => command = command.data.toJSON());
// Construct and prepare an instance of the REST module
const rest = new REST({ version: '10' }).setToken(discordToken);
// and deploy your commands!
guildIDs.forEach(guild => {
console.log("Deploying commands for: ", guild.id);
console.log("Commands", commands);
(async () => {
try {
console.log(`Started refreshing application (/) commands for guild ID: ${guild.id}.`);
// The put method is used to fully refresh all commands in the guild with the current set
const data = await rest.put(
Routes.applicationGuildCommands(clientId, guild.id),
{ body: commands },
);
console.log(`Successfully reloaded ${data.length} application (/) commands for guild ID: ${guild.id}.`);
} catch (error) {
// And of course, make sure you catch and log any errors!
console.log("ERROR Deploying commands: ", error, "Body from error: ", commands);
}
})()
})
};
/**
* Remove all commands for a given bot in a given guild
*
* @param {any} serverClient The discord bot client
*/
export const unregisterAllCommands = async (serverClient) => {
const guildIDs = serverClient.guilds.cache;
const clientId = serverClient.user.id;
commands = [];
const rest = new REST({ version: '10' }).setToken(discordToken);
guildIDs.forEach(guild => {
console.log("Removing commands for: ", clientId, guild.id);
(async () => {
try {
console.log(`Started removal of ${commands.length} application (/) commands for guild ID: ${guild.id}.`);
// The put method is used to fully refresh all commands in the guild with the current set
const data = await rest.put(
Routes.applicationGuildCommands(clientId, guild.id),
{ body: commands },
);
console.log(`Successfully removed ${data.length} application (/) commands for guild ID: ${guild.id}.`);
} catch (error) {
// And of course, make sure you catch and log any errors!
console.log("ERROR removing commands: ", error, "Body from error: ", commands);
}
})()
})
}
/**
* This named wrapper will remove all commands and then re-add the commands back, effectively refreshing them
* @param {any} serverClient The discord bot client object
* @returns {any}
*/
export const refreshActiveCommandsWrapper = async (serverClient) => {
// Remove all commands
console.log("Removing/Unregistering all commands from all connected servers/guilds");
await unregisterAllCommands(serverClient);
// Deploy the active commands
console.log("Adding commands to all connected servers/guilds");
await registerActiveCommands(serverClient);
return;
}

View File

@@ -1,11 +0,0 @@
import { Client, GatewayIntentBits } from 'discord.js';
//import { deployActiveCommands } from '../discordBot/modules/deployCommands.mjs'
import dotenv from 'dotenv';
dotenv.config()
export const serverClient = new Client({ intents: [GatewayIntentBits.Guilds] });
serverClient.on('ready', () => {
console.log(`Logged in as ${serverClient.user.tag}!`);
});

View File

@@ -0,0 +1,53 @@
// Import necessary modules
import { MongoClient } from 'mongodb';
import dotenv from 'dotenv';
dotenv.config()
// MongoDB connection URI
const uri = process.env.MONGO_URL;
// Function to connect to the database
export const connectToDatabase = async () => {
try {
const client = await MongoClient.connect(uri);
return client;
} catch (error) {
console.error('Error connecting to the database:', error);
throw error;
}
};
// Function to insert a document into the collection
export const insertDocument = async (collectionName, document) => {
const db = await connectToDatabase();
try {
const collection = db.db().collection(collectionName);
const result = await collection.insertOne(document);
console.log('Document inserted:', result.insertedId);
return result.insertedId;
} catch (error) {
console.error('Error inserting document:', error);
throw error;
} finally {
// Close the connection
await db.close();
}
};
// Function to retrieve documents from the collection
export const getDocuments = async (collectionName) => {
const db = await connectToDatabase();
try {
const collection = db.db().collection(collectionName);
const documents = await collection.find({}).toArray();
console.log('Documents retrieved:', documents);
return documents;
} catch (error) {
console.error('Error retrieving documents:', error);
throw error;
} finally {
// Close the connection
await db.close();
}
};

View File

@@ -0,0 +1,75 @@
import { insertDocument, getDocuments, connectToDatabase } from "./mongoHandler.mjs";
const collectionName = 'nodes';
// Wrapper for inserting a node
export const createNode = async (node) => {
try {
const insertedId = await insertDocument(collectionName, node);
return insertedId;
} catch (error) {
console.error('Error creating node:', error);
throw error;
}
};
// Wrapper for retrieving all nodes
export const getAllNodes = async () => {
try {
const nodes = await getDocuments(collectionName);
return nodes;
} catch (error) {
console.error('Error getting all nodes:', error);
throw error;
}
};
// Wrapper for retrieving a node by NUID
export const getNodeByNuid = async (nuid) => {
const db = await connectToDatabase();
try {
const collection = db.db().collection(collectionName);
const node = await collection.findOne({ nuid });
return node;
} catch (error) {
console.error('Error getting node by NUID:', error);
throw error;
} finally {
// Close the connection
await db.close();
}
};
// Wrapper for updating a node by NUID
export const updateNodeByNuid = async (nuid, updatedFields) => {
const db = await connectToDatabase();
try {
const collection = db.db().collection(collectionName);
const result = await collection.updateOne({ nuid }, { $set: updatedFields });
console.log('Node updated:', result.modifiedCount);
return result.modifiedCount;
} catch (error) {
console.error('Error updating node by NUID:', error);
throw error;
} finally {
// Close the connection
await db.close();
}
};
// Wrapper for deleting a node by NUID
export const deleteNodeByNuid = async (nuid) => {
const db = await connectToDatabase();
try {
const collection = db.db().collection(collectionName);
const result = await collection.deleteOne({ nuid });
console.log('Node deleted:', result.deletedCount);
return result.deletedCount;
} catch (error) {
console.error('Error deleting node by NUID:', error);
throw error;
} finally {
// Close the connection
await db.close();
}
};

View File

@@ -0,0 +1,111 @@
import { insertDocument, getDocuments, connectToDatabase } from "./mongoHandler.mjs";
const collectionName = 'radio-systems';
// Local wrapper to remove any local files from radio systems
const removeLocalFilesFromsystem = async (system) => {
if (system.trunkFile) delete system.trunkFile;
if (system.whitelistFile) delete system.whitelistFile;
}
// Wrapper for inserting a system
export const createSystem = async (name, system, nuid) => {
try {
// Remove any local files
await removeLocalFilesFromsystem(system);
// Add the NUID of the node that created this system
system.nodes = [nuid];
// Add the name of the system
system.name = name
const insertedId = await insertDocument(collectionName, system);
return insertedId;
} catch (error) {
console.error('Error creating system:', error);
throw error;
}
};
// Wrapper for retrieving all systems
export const getAllSystems = async () => {
try {
const systems = await getDocuments(collectionName);
return systems;
} catch (error) {
console.error('Error getting all systems:', error);
throw error;
}
};
// Wrapper for retrieving a system by name
export const getSystemByName = async (name) => {
const db = await connectToDatabase();
try {
const collection = db.db().collection(collectionName);
const system = await collection.findOne({ name });
return system;
} catch (error) {
console.error('Error getting system by name:', error);
throw error;
} finally {
// Close the connection
await db.close();
}
};
// Wrapper to get all systems from a given node
export const getSystemsByNuid = async (nuid) => {
const db = await connectToDatabase();
try {
const collection = db.db().collection(collectionName);
// Query for documents where the 'nodes' array contains the given nodeID
const query = { nodes: nuid };
const systems = await collection.find(query).toArray();
return systems;
} catch (error) {
console.error('Error finding entries:', error);
throw error;
} finally {
// Close the connection
await db.close();
}
};
// Wrapper for updating a system by name
export const updateSystemByName = async (name, updatedSystem) => {
// Remove any local files
await removeLocalFilesFromsystem(updatedSystem);
const db = await connectToDatabase();
try {
const collection = db.db().collection(collectionName);
const result = await collection.updateOne({ name }, { $set: updatedSystem });
console.log('System updated:', result.modifiedCount);
return result.modifiedCount;
} catch (error) {
console.error('Error updating system by name:', error);
throw error;
} finally {
// Close the connection
await db.close();
}
};
// Wrapper for deleting a system by name
export const deleteSystemByName = async (name) => {
const db = await connectToDatabase();
try {
const collection = db.db().collection(collectionName);
const result = await collection.deleteOne({ name });
console.log('System deleted:', result.deletedCount);
return result.deletedCount;
} catch (error) {
console.error('Error deleting system by name:', error);
throw error;
} finally {
// Close the connection
await db.close();
}
};

View File

@@ -2,6 +2,7 @@ import express from 'express';
import { createServer } from 'node:http';
import { Server } from 'socket.io';
import morgan from 'morgan';
import { nodeLoginWrapper, nodeUpdateWrapper, nodeDisconnectWrapper, nearbySystemsUpdateWraper } from "./socketServerWrappers.mjs";
export const app = express();
export const server = createServer(app);
@@ -17,48 +18,21 @@ nodeIo.on('connection', (socket) => {
console.log('a user connected', socket.id);
socket.on('node-login', (data) => {
nodeLoginWrapper(data);
nodeLoginWrapper(data, socket);
})
socket.on('node-update', (data) => {
updateNodeData(data);
nodeUpdateWrapper(data.node);
nearbySystemsUpdateWraper(data.node.nuid, data.nearbySystems)
})
socket.on('disconnect', () => {
console.log('user disconnected');
nodeDisconnectWrapper(socket.id);
});
// Test commands
setTimeout(() => {
const joinData = {
'clientID': "MTE5NjAwNTM2ODYzNjExMjk3Nw.GuCMXg.24iNNofNNumq46FIj68zMe9RmQgugAgfrvelEA",
'channelID': "367396189529833476",
'preset': ""
}
sendNodeCommand(socket, "node-join", joinData);
}, 2500)
//setTimeout(() => { sendNodeCommand(socket, "node-leave", {}); }, 3500)
});
function sendNodeCommand(socket, command, data) {
// TODO - Check to see if the command exists
// TODO - Check to see if the socket is alive?
// TODO - Validate the given data
socket.emit(command, data);
}
function loginNode() {
}
function registerNode() {
}
function updateNodeData(data) {
console.log("Data update sent by node: ", data);
}
function nodeLoginWrapper(data) {
console.log(`Login requested from node: ${data.id}`, data);
}
// Startup the node server
server.listen(3000, () => {
console.log('server running at http://localhost:3000');
});

View File

@@ -0,0 +1,167 @@
import { createNode, getNodeByNuid, updateNodeByNuid } from "./mongoNodesWrappers.mjs"
import { createSystem, getSystemByName, updateSystemByName, getSystemsByNuid, deleteSystemByName } from "./mongoSystemsWrappers.mjs"
/**
* Description
* @param {any} socket
* @param {any} command
* @param {any} data
* @returns {any}
*/
const sendNodeCommand = async (socket, command, data) => {
// TODO - Check to see if the command exists
// TODO - Check to see if the socket is alive?
// TODO - Validate the given data
socket.emit(command, data);
}
/**
* Log the node into the network
* @param {object} data The data sent from the node
* @param {any} socket The socket the node is connected from
* @returns {any}
*/
export const nodeLoginWrapper = async (data, socket) => {
console.log(`Login requested from node: ${data.nuid}`, data);
// Check to see if node exists
var node = await getNodeByNuid(data.nuid);
console.log("After grabbing", node);
if (!node) {
const insertedId = await createNode(data);
node = await getNodeByNuid(data.nuid);
console.log("Added new node to the database:", insertedId);
}
// Check for updates if so
// Check for System updates
// Add the socket/node connection
socket.node = node;
socket.id = node.nuid;
return;
}
/**
* Disconnect the client from the server
* @param {string} socketId The socket ID that was disconnected
* @returns {any}
*/
export const nodeDisconnectWrapper = async (socketId) => {
return;
}
/**
* Update node data in the database
* @param {object} nodeData The data object sent from the node
* @returns {any}
*/
export const nodeUpdateWrapper = async (nodeData) => {
console.log("Data update sent by node: ", nodeData);
const updateResults = await updateNodeByNuid(nodeData.nuid, nodeData);
return;
}
/**
* Wrapper to update the systems from the nearbySystems object passed from clients
* @param {string} nuid The NUID of the node that sent the update
* @param {object} nearbySystems The nearby systems object passed from the node to be updated
* @returns {any}
*/
export const nearbySystemsUpdateWraper = async (nuid, nearbySystems) => {
console.log("System updates sent by node: ", nuid, nearbySystems);
// Check to see if the node removed any systems
const existingSystems = await getSystemsByNuid(nuid);
console.log("Existing systems:", existingSystems);
if (existingSystems !== nearbySystems) {
for (const existingSystem of existingSystems) {
if (existingSystem.name in nearbySystems) {
// Skip this system if it's in the given systems update
continue;
}
console.log("System exists that was not given by node", existingSystem);
// Check if this node was the only node on this system
if (existingSystem.nodes.filter(node => node !== nuid).length === 0) {
// Remove the system if so
console.log("Given node was the only node on this system, removing the system...");
await deleteSystemByName(existingSystem.name);
} else {
// Remove the node from the array if there are other nodes with this system
console.log("Other nodes found on this system, removing the given NUID");
existingSystem.nodes = existingSystem.nodes.filter(node => node !== nuid);
console.log(existingSystem);
await updateSystemByName(existingSystem.name, existingSystem);
}
}
}
// Add and update the given systems
for (const nearbySystem in nearbySystems) {
// Check if the system exists already on another node
const existingSystem = await getSystemByName(nearbySystem);
if (existingSystem) {
// Verify the frequencies match (to make sure the name isn't just the same)
console.log(existingSystem.frequencies, nearbySystems[nearbySystem].frequencies, (JSON.stringify(existingSystem.frequencies) === JSON.stringify(nearbySystems[nearbySystem].frequencies)));
if (JSON.stringify(existingSystem.frequencies) === JSON.stringify(nearbySystems[nearbySystem].frequencies)) {
// The systems are the same
// Check if the current node is listed in the nodes, if not add it
if (!existingSystem.nodes.includes(nuid)) {
existingSystem.nodes.push(nuid);
// Update the system with the added node
const updateResults = await updateSystemByName(nearbySystem, existingSystem);
if (updateResults) console.log("System updated", nearbySystem);
}
} else {
// The systems are not the same
// TODO - Implement logic to handle if system names match, but they are for different frequencies or have additional freqs
// Check if the current node is listed in the nodes, if not add it
if (!existingSystem.nodes.includes(nuid)) {
existingSystem.nodes.push(nuid);
nearbySystems[nearbySystem].nodes = existingSystem.nodes;
}
// Update the system with the added node
const updateResults = await updateSystemByName(nearbySystem, nearbySystems[nearbySystem]);
if (updateResults) console.log("System updated", nearbySystem);
}
}
else {
// Create a new system
const newSystem = await createSystem(nearbySystem, nearbySystems[nearbySystem], nuid);
console.log("New system created", nearbySystem, newSystem);
}
}
return;
}
/**
* Get the open socket connection ID for a node from the NUID
* @param {string} nuid The NUID to find within the open sockets
* @returns {string|null} Will return the open socket ID or NULL
*/
const getSocketIdByNuid = async (nuid) => {
for (const openSocket in openSockets) {
if (openSockets[openSocket] == nuid)
return openSocket;
}
return null;
}
const requestNodeJoinPreset = async () => {
// Check for System updates
// Test commands
setTimeout(() => {
const joinData = {
'clientID': "MTE5NjAwNTM2ODYzNjExMjk3Nw.GuCMXg.24iNNofNNumq46FIj68zMe9RmQgugAgfrvelEA",
'channelID': "367396189529833476",
'preset': ""
}
sendNodeCommand(socket, "node-join", joinData);
}, 2500)
}

136
server/package-lock.json generated
View File

@@ -12,6 +12,7 @@
"discord.js": "^14.14.1",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"mongodb": "^6.3.0",
"morgan": "^1.10.0",
"socket.io": "^4.7.2"
}
@@ -142,6 +143,14 @@
"node": ">=14"
}
},
"node_modules/@mongodb-js/saslprep": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.4.tgz",
"integrity": "sha512-8zJ8N1x51xo9hwPh6AWnKdLGEC5N3lDa6kms1YHmFBoRhTpJR6HG8wWk0td1MVCu9cD4YBrvjZEtd5Obw0Fbnw==",
"dependencies": {
"sparse-bitfield": "^3.0.3"
}
},
"node_modules/@sapphire/async-queue": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.1.tgz",
@@ -198,6 +207,19 @@
"undici-types": "~5.26.4"
}
},
"node_modules/@types/webidl-conversions": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
"integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="
},
"node_modules/@types/whatwg-url": {
"version": "11.0.4",
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.4.tgz",
"integrity": "sha512-lXCmTWSHJvf0TRSO58nm978b8HJ/EdsSsEKLd3ODHFjo+3VGAyyTp4v50nWvwtzBxSMQrVOK7tcuN0zGPLICMw==",
"dependencies": {
"@types/webidl-conversions": "*"
}
},
"node_modules/@types/ws": {
"version": "8.5.9",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.9.tgz",
@@ -279,6 +301,14 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/bson": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/bson/-/bson-6.2.0.tgz",
"integrity": "sha512-ID1cI+7bazPDyL9wYy9GaQ8gEEohWvcUl/Yf0dIdutJxnmInEEyCsb4awy/OiBfall7zBA179Pahi3vCdFze3Q==",
"engines": {
"node": ">=16.20.1"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -743,6 +773,11 @@
"node": ">= 0.6"
}
},
"node_modules/memory-pager": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="
},
"node_modules/merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
@@ -786,6 +821,60 @@
"node": ">= 0.6"
}
},
"node_modules/mongodb": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.3.0.tgz",
"integrity": "sha512-tt0KuGjGtLUhLoU263+xvQmPHEGTw5LbcNC73EoFRYgSHwZt5tsoJC110hDyO1kjQzpgNrpdcSza9PknWN4LrA==",
"dependencies": {
"@mongodb-js/saslprep": "^1.1.0",
"bson": "^6.2.0",
"mongodb-connection-string-url": "^3.0.0"
},
"engines": {
"node": ">=16.20.1"
},
"peerDependencies": {
"@aws-sdk/credential-providers": "^3.188.0",
"@mongodb-js/zstd": "^1.1.0",
"gcp-metadata": "^5.2.0",
"kerberos": "^2.0.1",
"mongodb-client-encryption": ">=6.0.0 <7",
"snappy": "^7.2.2",
"socks": "^2.7.1"
},
"peerDependenciesMeta": {
"@aws-sdk/credential-providers": {
"optional": true
},
"@mongodb-js/zstd": {
"optional": true
},
"gcp-metadata": {
"optional": true
},
"kerberos": {
"optional": true
},
"mongodb-client-encryption": {
"optional": true
},
"snappy": {
"optional": true
},
"socks": {
"optional": true
}
}
},
"node_modules/mongodb-connection-string-url": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.0.tgz",
"integrity": "sha512-t1Vf+m1I5hC2M5RJx/7AtxgABy1cZmIPQRMXw+gEIPn/cZNF3Oiy+l0UIypUwVB5trcWHq3crg2g3uAR9aAwsQ==",
"dependencies": {
"@types/whatwg-url": "^11.0.2",
"whatwg-url": "^13.0.0"
}
},
"node_modules/morgan": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
@@ -885,6 +974,14 @@
"node": ">= 0.10"
}
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"engines": {
"node": ">=6"
}
},
"node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
@@ -1098,6 +1195,14 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/sparse-bitfield": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
"integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
"dependencies": {
"memory-pager": "^1.0.2"
}
},
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@@ -1114,6 +1219,17 @@
"node": ">=0.6"
}
},
"node_modules/tr46": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz",
"integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==",
"dependencies": {
"punycode": "^2.3.0"
},
"engines": {
"node": ">=14"
}
},
"node_modules/ts-mixer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.3.tgz",
@@ -1176,6 +1292,26 @@
"node": ">= 0.8"
}
},
"node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
"engines": {
"node": ">=12"
}
},
"node_modules/whatwg-url": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz",
"integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==",
"dependencies": {
"tr46": "^4.1.1",
"webidl-conversions": "^7.0.0"
},
"engines": {
"node": ">=16"
}
},
"node_modules/ws": {
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",

View File

@@ -14,6 +14,7 @@
"discord.js": "^14.14.1",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"mongodb": "^6.3.0",
"morgan": "^1.10.0",
"socket.io": "^4.7.2"
}

View File

@@ -1,19 +1,9 @@
import { nodeIo, app, server } from './modules/socketServer.mjs';
import { addEnabledCommands, addEnabledEventListeners } from './discordBot/modules/discordBotUtils.mjs'
import { serverClient } from './modules/discordBot.mjs';
import { serverClient, addEnabledEventListeners } from './discordBot/discordBot.mjs';
import dotenv from 'dotenv';
dotenv.config()
// Startup the node server
server.listen(3000, () => {
console.log('server running at http://localhost:3000');
});
// Config the discord bot with commands and events
await addEnabledEventListeners(serverClient, nodeIo);
await addEnabledCommands(serverClient, nodeIo);
// Startup the discord bot
console.log(`Logging into discord with ID: ${process.env.DISCORD_TOKEN}`);
serverClient.login(process.env.DISCORD_TOKEN);
// Add objects to the others
serverClient.nodeIo = nodeIo;
nodeIo.serverClient = serverClient;