Initial move
This commit is contained in:
301
.gitignore
vendored
Normal file
301
.gitignore
vendored
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
# ---> Node
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
# ---> Python
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# poetry
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
|
#poetry.lock
|
||||||
|
|
||||||
|
# pdm
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
|
#pdm.lock
|
||||||
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
|
# in version control.
|
||||||
|
# https://pdm.fming.dev/#use-with-ide
|
||||||
|
.pdm.toml
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
#.idea/
|
||||||
|
|
||||||
|
/.vscode
|
||||||
|
|
||||||
|
# Ignore the config dirs
|
||||||
|
config/
|
||||||
|
|
||||||
|
# Ignore the OP25 directory we will create
|
||||||
|
op25/
|
||||||
21
Dockerfile
Normal file
21
Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Use the official Node.js image as the base image
|
||||||
|
FROM node:20
|
||||||
|
|
||||||
|
# Set the working directory inside the container
|
||||||
|
WORKDIR /server
|
||||||
|
|
||||||
|
# Copy package.json and package-lock.json (if available) to the working directory
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN npm install -g node-gyp
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# Copy the rest of the application code to the working directory
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Expose the port on which your Node.js application will run
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Command to run the Node.js application
|
||||||
|
CMD ["node", "."]
|
||||||
7
addons/example/config.json
Normal file
7
addons/example/config.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "Addon 1",
|
||||||
|
"enabled": false,
|
||||||
|
"options": {
|
||||||
|
"eventName": "connection"
|
||||||
|
}
|
||||||
|
}
|
||||||
17
addons/example/index.js
Normal file
17
addons/example/index.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// addons/addon1/index.js
|
||||||
|
|
||||||
|
// Function called by the main application to initialize the addon
|
||||||
|
export function initialize(nodeIo, config) {
|
||||||
|
console.log(`Initializing ${config.name}`);
|
||||||
|
|
||||||
|
// Call other functions within the addon module
|
||||||
|
registerSocketEvents(nodeIo, config);
|
||||||
|
// Call additional initialization functions if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to register Socket.IO event handlers
|
||||||
|
function registerSocketEvents(nodeIo, config) {
|
||||||
|
nodeIo.on(config.options.eventName, (data) => {
|
||||||
|
console.log(`Received event "${config.options.eventName}" from client:`, data);
|
||||||
|
});
|
||||||
|
}
|
||||||
154
discordBot/commands/join.mjs
Normal file
154
discordBot/commands/join.mjs
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
import { SlashCommandBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js';
|
||||||
|
import { requestNodeJoinSystem, checkIfNodeIsConnectedToVC, checkIfNodeHasOpenDiscordClient, getNodeCurrentListeningSystem } from '../../modules/socketServerWrappers.mjs';
|
||||||
|
import { getSystemsByNuid, getAllSystems, getSystemByName } from '../../modules/mongoSystemsWrappers.mjs';
|
||||||
|
import { getAvailableTokensInGuild } from '../modules/wrappers.mjs';
|
||||||
|
|
||||||
|
// Exporting data property
|
||||||
|
export const data = new SlashCommandBuilder()
|
||||||
|
.setName('join')
|
||||||
|
.setDescription('Listen to the selected radio system in your channel')
|
||||||
|
.addStringOption(system =>
|
||||||
|
system.setName('system')
|
||||||
|
.setDescription('The radio system you would like to listen to')
|
||||||
|
.setRequired(true)
|
||||||
|
.setAutocomplete(true));
|
||||||
|
|
||||||
|
// Exporting other properties
|
||||||
|
export const example = "/join";
|
||||||
|
export const deferInitialReply = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to give the user auto-reply suggestions
|
||||||
|
* @param {any} nodeIo The nodeIO server for manipulation of sockets
|
||||||
|
* @param {any} interaction The interaction object
|
||||||
|
*/
|
||||||
|
export async function autocomplete(nodeIo, interaction) {
|
||||||
|
const focusedValue = interaction.options.getFocused();
|
||||||
|
const choices = await getAllSystems();
|
||||||
|
const filtered = choices.filter(choice => choice.name.startsWith(focusedValue));
|
||||||
|
|
||||||
|
console.log(focusedValue, choices, filtered);
|
||||||
|
|
||||||
|
await interaction.respond(
|
||||||
|
filtered.map(choice => ({ name: choice.name, value: choice.name })),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The function to run when the command is called by a discord user
|
||||||
|
* @param {any} nodeIo The nodeIO server for manipulation of sockets
|
||||||
|
* @param {any} interaction The interaction object
|
||||||
|
*/
|
||||||
|
export async function execute(nodeIo, interaction) {
|
||||||
|
// Check if the user is in a VC
|
||||||
|
if (!interaction.member.voice.channel) { return await interaction.editReply({ content: `<@${interaction.member.id}>, you need to enter a voice channel before you use this command`, ephemeral: true }) }
|
||||||
|
// Grab the channel if the user is connected to VC
|
||||||
|
const channelToJoin = interaction.member.voice.channel;
|
||||||
|
console.log(`The user '${interaction.member.id}' is in the voice channel '${channelToJoin}'`);
|
||||||
|
|
||||||
|
// Get the selected system option from the command interaction
|
||||||
|
const selectedSystem = interaction.options.getString('system');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get the selected system object from the DB
|
||||||
|
const system = await getSystemByName(selectedSystem);
|
||||||
|
|
||||||
|
// Function wrapper to request the selected/only node to join the selected system
|
||||||
|
const joinSelectedNode = async (selectedNodeSocketId) => {
|
||||||
|
const openSocket = await nodeIo.sockets.sockets.get(selectedNodeSocketId);
|
||||||
|
// Get the open ID for this connection\
|
||||||
|
const ss = await getAvailableTokensInGuild(nodeIo, interaction.guild.id);
|
||||||
|
console.log("Available discord tokens: ", discordTokens);
|
||||||
|
|
||||||
|
if (discordTokens.length >= 1) {
|
||||||
|
// TODO - Implement a method to have preferred tokens (bot users) for specific systems
|
||||||
|
console.log("Joining selected open socket:", selectedNodeSocketId, system.name, channelToJoin.id, openSocket.node.name, discordTokens[0].token);
|
||||||
|
|
||||||
|
// Ask the node to join the selected channel and system
|
||||||
|
await requestNodeJoinSystem(openSocket, system.name, channelToJoin.id, discordTokens[0].token);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return await interaction.editReply({ content: `<@${interaction.member.id}>, there are no free bots. Free up or create a new bot ID (discord app) to listen to this system.`, ephemeral: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all open socket nodes
|
||||||
|
const openSockets = [...await nodeIo.allSockets()]; // TODO - Filter the returned nodes to only nodes that have the radio capability
|
||||||
|
console.log("All open sockets: ", openSockets);
|
||||||
|
|
||||||
|
var availableNodes = [];
|
||||||
|
// Check each open socket to see if the node has the requested system
|
||||||
|
await Promise.all(openSockets.map(async openSocket => {
|
||||||
|
openSocket = await nodeIo.sockets.sockets.get(openSocket);
|
||||||
|
// Check if the node has an existing open client (meaning the radio is already being listened to)
|
||||||
|
const hasOpenClient = await checkIfNodeHasOpenDiscordClient(openSocket);
|
||||||
|
if (hasOpenClient) {
|
||||||
|
let currentSystem = await getNodeCurrentListeningSystem(openSocket);
|
||||||
|
if (currentSystem != system.name) {
|
||||||
|
console.log("Node is listening to a different system than requested", openSocket.node.name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the bot has an open voice connection in the requested server already
|
||||||
|
const connected = await checkIfNodeIsConnectedToVC(nodeIo, interaction.guild.id, openSocket.node.nuid);
|
||||||
|
console.log("Connected:", connected);
|
||||||
|
if (!connected) {
|
||||||
|
// Check if this node has the requested system, if so add it to the availble array
|
||||||
|
if (system.nodes.includes(openSocket.node.nuid)) {
|
||||||
|
availableNodes.push(openSocket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
console.log("Availble nodes:", availableNodes.map(socket => socket.node.name));
|
||||||
|
|
||||||
|
// If there are no available nodes, let the user know there are none available
|
||||||
|
if (availableNodes.length == 0) {
|
||||||
|
// There are no nodes availble for the requested system
|
||||||
|
return await interaction.editReply(`<@${interaction.member.id}>, the selected system has no available nodes`);
|
||||||
|
} else if (availableNodes.length == 1) {
|
||||||
|
// There is only one node available for the requested system
|
||||||
|
// Request the node to join
|
||||||
|
await joinSelectedNode(availableNodes[0].id);
|
||||||
|
// Let the user know
|
||||||
|
await interaction.editReply({ content: `Ok <@${interaction.member.id}>, a bot will join your channel listening to *'${system.name}'* shortly`, components: [] });
|
||||||
|
} else if (availableNodes.length > 1) {
|
||||||
|
// There is more than one node availble for the requested system
|
||||||
|
const nodeSelectionButtons = []
|
||||||
|
|
||||||
|
// Create a button for each available node
|
||||||
|
for (const availableNode of availableNodes) {
|
||||||
|
nodeSelectionButtons.push(new ButtonBuilder().setCustomId(availableNode.id).setLabel(availableNode.node.name).setStyle(ButtonStyle.Primary));
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionRow = new ActionRowBuilder().addComponents(nodeSelectionButtons);
|
||||||
|
|
||||||
|
// Reply to the user with the button prompts
|
||||||
|
const response = await interaction.editReply({
|
||||||
|
content: `<@${interaction.member.id}>, Please select the Node you would like to join with this system`,
|
||||||
|
components: [actionRow]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Make sure the responding selection is from the user who initiated the command
|
||||||
|
const collectorFilter = i => i.user.id === interaction.user.id;
|
||||||
|
|
||||||
|
// Wait for the confirmation from the user on which node to join
|
||||||
|
try {
|
||||||
|
const selectedNode = await response.awaitMessageComponent({ filter: collectorFilter, time: 60_000 });
|
||||||
|
// Run the local wrapper to listen to the selected node
|
||||||
|
await joinSelectedNode(selectedNode.customId);
|
||||||
|
// Let the user know
|
||||||
|
await selectedNodeConfirmation.update({ content: `Ok <@${interaction.member.id}>, a bot will join your channel listening to *'${system.name}'*`, components: [] });
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
// Timeout the prompt if the user doesn't interact with it
|
||||||
|
await interaction.editReply({ content: 'Confirmation not received within 1 minute, cancelling', components: [] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
// await interaction.reply(err.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
56
discordBot/commands/leave.mjs
Normal file
56
discordBot/commands/leave.mjs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
|
import { requestBotLeaveServer, getSocketIdByNuid } from '../../modules/socketServerWrappers.mjs';
|
||||||
|
import { checkOnlineBotsInGuild } from '../modules/wrappers.mjs'
|
||||||
|
|
||||||
|
// Exporting data property
|
||||||
|
export const data = new SlashCommandBuilder()
|
||||||
|
.setName('leave')
|
||||||
|
.setDescription('Disconnect a bot from the server')
|
||||||
|
.addStringOption(system =>
|
||||||
|
system.setName('bot')
|
||||||
|
.setDescription('The bot you would like to disconnect')
|
||||||
|
.setRequired(true)
|
||||||
|
.setAutocomplete(true));;
|
||||||
|
|
||||||
|
// Exporting other properties
|
||||||
|
export const example = "/leave *{Bot Name}*";
|
||||||
|
export const deferInitialReply = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to give the user auto-reply suggestions
|
||||||
|
* @param {any} nodeIo The nodeIO server for manipulation of sockets
|
||||||
|
* @param {any} interaction The interaction object
|
||||||
|
*/
|
||||||
|
export async function autocomplete(nodeIo, interaction) {
|
||||||
|
const focusedValue = interaction.options.getFocused();
|
||||||
|
const choices = (await checkOnlineBotsInGuild(nodeIo, interaction.guild.id));
|
||||||
|
|
||||||
|
console.log(choices);
|
||||||
|
|
||||||
|
const filtered = choices.filter(choice => choice.name.startsWith(focusedValue)).map(choice => choice = {name: choice.name, value: choice.nuid});
|
||||||
|
|
||||||
|
console.log(focusedValue, choices, filtered);
|
||||||
|
|
||||||
|
await interaction.respond(filtered);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The function to run when the command is called by a discord user
|
||||||
|
* @param {any} nodeIo The nodeIO server for manipulation of sockets
|
||||||
|
* @param {any} interaction The interaction object
|
||||||
|
*/
|
||||||
|
export async function execute(nodeIo, interaction) {
|
||||||
|
try {
|
||||||
|
// Get the requested bot
|
||||||
|
const selectedNode = interaction.options.getString('bot');
|
||||||
|
const socket = await getSocketIdByNuid(nodeIo, selectedNode);
|
||||||
|
console.log("All open sockets:", socket, selectedNode);
|
||||||
|
await requestBotLeaveServer(socket, interaction.guild.id);
|
||||||
|
//await interaction.reply(`**Online Sockets: '${sockets}'**`);
|
||||||
|
await interaction.editReply(`Ok <@${interaction.member.id}>, the bot is leaving shortly`);
|
||||||
|
//await interaction.channel.send('**Pong.**');
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
// await interaction.reply(err.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
43
discordBot/commands/ping.mjs
Normal file
43
discordBot/commands/ping.mjs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
|
|
||||||
|
// Exporting data property that contains the command structure for discord including any params
|
||||||
|
export const data = new SlashCommandBuilder()
|
||||||
|
.setName('ping')
|
||||||
|
.setDescription('Replies with your input!');
|
||||||
|
|
||||||
|
// Exporting other properties
|
||||||
|
export const example = "/ping"; // An example of how the command would be run in discord chat, this will be used for the help command
|
||||||
|
export const deferInitialReply = false; // If we the initial reply in discord should be deferred. This gives extra time to respond, however the method of replying is different.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to give the user auto-reply suggestions
|
||||||
|
* @param {any} nodeIo The nodeIO server for manipulation of sockets
|
||||||
|
* @param {any} interaction The interaction object
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
export async function autocomplete(nodeIo, interaction) {
|
||||||
|
const focusedValue = interaction.options.getFocused();
|
||||||
|
const choices = [];
|
||||||
|
const filtered = choices.filter(choice => choice.name.startsWith(focusedValue));
|
||||||
|
console.log(focusedValue, choices, filtered);
|
||||||
|
await interaction.respond(filtered);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The function to run when the command is called by a discord user
|
||||||
|
* @param {any} nodeIo The nodeIO server for manipulation of sockets
|
||||||
|
* @param {any} interaction The interaction object
|
||||||
|
*/
|
||||||
|
export const execute = async (nodeIo, interaction) => {
|
||||||
|
try {
|
||||||
|
const sockets = await nodeIo.allSockets();
|
||||||
|
console.log("All open sockets: ",sockets);
|
||||||
|
//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());
|
||||||
|
}
|
||||||
|
}
|
||||||
35
discordBot/commands/update.mjs
Normal file
35
discordBot/commands/update.mjs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
|
import { requestNodeUpdate } from '../../modules/socketServerWrappers.mjs';
|
||||||
|
|
||||||
|
// Exporting data property that contains the command structure for discord including any params
|
||||||
|
export const data = new SlashCommandBuilder()
|
||||||
|
.setName('update')
|
||||||
|
.setDescription('Updates all nodes currently logged on');
|
||||||
|
|
||||||
|
// Exporting other properties
|
||||||
|
export const example = "/update"; // An example of how the command would be run in discord chat, this will be used for the help command
|
||||||
|
export const deferInitialReply = false; // If we the initial reply in discord should be deferred. This gives extra time to respond, however the method of replying is different.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The function to run when the command is called by a discord user
|
||||||
|
* @param {any} nodeIo The nodeIO server for manipulation of sockets
|
||||||
|
* @param {any} interaction The interaction object
|
||||||
|
*/
|
||||||
|
export const execute = async (nodeIo, interaction) => {
|
||||||
|
try {
|
||||||
|
const openSockets = [...await nodeIo.allSockets()]; // TODO - Filter the returned nodes to only nodes that have the radio capability
|
||||||
|
console.log("All open sockets: ", openSockets);
|
||||||
|
|
||||||
|
// Check each open socket to see if the node has the requested system
|
||||||
|
await Promise.all(openSockets.map(openSocket => {
|
||||||
|
openSocket = nodeIo.sockets.sockets.get(openSocket);
|
||||||
|
requestNodeUpdate(openSocket);
|
||||||
|
}));
|
||||||
|
//await interaction.reply(`**Online Sockets: '${sockets}'**`);
|
||||||
|
await interaction.reply('All nodes have been requested to update');
|
||||||
|
//await interaction.channel.send('**Pong.**');
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
// await interaction.reply(err.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
92
discordBot/discordBot.mjs
Normal file
92
discordBot/discordBot.mjs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
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} _commandsPath="./commands"
|
||||||
|
* @returns {any}
|
||||||
|
*/
|
||||||
|
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 = await join(commandsPath, file);
|
||||||
|
console.log(`Adding enabled command: ${filePath}`);
|
||||||
|
await import(`file://${filePath}`).then(command => {
|
||||||
|
if (command.data instanceof Promise) {
|
||||||
|
command.data.then(async (builder) => {
|
||||||
|
command.data = builder;
|
||||||
|
console.log("Importing command: ", command.data.name, command);
|
||||||
|
// Set a new item in the Collection
|
||||||
|
// With the key as the command name and the value as the exported module
|
||||||
|
serverClient.commands.set(command.data.name, command);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log("Importing command: ", command.data.name, command);
|
||||||
|
// Set a new item in the Collection
|
||||||
|
// With the key as the command name and the value as the exported module
|
||||||
|
serverClient.commands.set(command.data.name, command);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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} _eventsPath="./events"
|
||||||
|
* @returns {any}
|
||||||
|
*/
|
||||||
|
export function addEnabledEventListeners(serverClient, _eventsPath = "./events") {
|
||||||
|
const eventsPath = join(__dirname, _eventsPath);
|
||||||
|
const eventFiles = readdirSync(eventsPath).filter(file => file.endsWith('.mjs'));
|
||||||
|
|
||||||
|
for (const file of eventFiles) {
|
||||||
|
const filePath = join(eventsPath, file);
|
||||||
|
console.log(`Adding enabled event listener: ${filePath}`);
|
||||||
|
import(`file://${filePath}`).then(event => {
|
||||||
|
console.log("Adding event: ", event);
|
||||||
|
if (event.once) {
|
||||||
|
serverClient.once(event.name, (...args) => event.execute(serverClient.nodeIo, ...args));
|
||||||
|
} else {
|
||||||
|
serverClient.on(event.name, (...args) => event.execute(serverClient.nodeIo, ...args));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The discord client
|
||||||
|
export const serverClient = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates] });
|
||||||
|
|
||||||
|
// 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);
|
||||||
32
discordBot/events/interactionCreate.mjs
Normal file
32
discordBot/events/interactionCreate.mjs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { Events } from 'discord.js';
|
||||||
|
|
||||||
|
export const name = Events.InteractionCreate;
|
||||||
|
|
||||||
|
export async function execute(nodeIo, interaction) {
|
||||||
|
const command = interaction.client.commands.get(interaction.commandName);
|
||||||
|
console.log("Interaction created for command: ", command);
|
||||||
|
|
||||||
|
// Execute autocomplete if the user is checking autocomplete
|
||||||
|
if (interaction.isAutocomplete()) {
|
||||||
|
console.log("Running autocomplete for command: ", command.data.name);
|
||||||
|
return await command.autocomplete(nodeIo, interaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the interaction is a command
|
||||||
|
if (!interaction.isChatInputCommand()) return;
|
||||||
|
|
||||||
|
if (!command) {
|
||||||
|
console.error(`No command matching ${interaction.commandName} was found.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`${interaction.member.user} is running '${interaction.commandName}'`);
|
||||||
|
|
||||||
|
// Defer the initial reply if the command has the parameter set
|
||||||
|
if (command.deferInitialReply) {
|
||||||
|
await interaction.deferReply();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the command
|
||||||
|
command.execute(nodeIo, interaction);
|
||||||
|
}
|
||||||
83
discordBot/modules/registerCommands.mjs
Normal file
83
discordBot/modules/registerCommands.mjs
Normal 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;
|
||||||
|
}
|
||||||
48
discordBot/modules/wrappers.mjs
Normal file
48
discordBot/modules/wrappers.mjs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { checkIfNodeIsConnectedToVC, getNodeDiscordID, getNodeDiscordUsername } from '../../modules/socketServerWrappers.mjs';
|
||||||
|
import { getAllDiscordIDs } from '../../modules/mongoDiscordIDWrappers.mjs'
|
||||||
|
|
||||||
|
|
||||||
|
export const checkOnlineBotsInGuild = async (nodeIo, guildId) => {
|
||||||
|
let onlineBots = [];
|
||||||
|
const openSockets = [...await nodeIo.allSockets()];
|
||||||
|
await Promise.all(openSockets.map(async openSocket => {
|
||||||
|
openSocket = await nodeIo.sockets.sockets.get(openSocket);
|
||||||
|
const connected = await checkIfNodeIsConnectedToVC(nodeIo, guildId, openSocket.node.nuid);
|
||||||
|
console.log("Connected:", connected);
|
||||||
|
if (connected) {
|
||||||
|
const username = await getNodeDiscordUsername(openSocket, guildId);
|
||||||
|
const discordID = await getNodeDiscordID(openSocket);
|
||||||
|
onlineBots.push({
|
||||||
|
name: username,
|
||||||
|
discord_id: discordID,
|
||||||
|
nuid: openSocket.node.nuid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
return onlineBots;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const getAvailableTokensInGuild = async (nodeIo, guildId) => {
|
||||||
|
try {
|
||||||
|
// Execute both asynchronous functions concurrently
|
||||||
|
const [discordIDs, onlineBots] = await Promise.all([
|
||||||
|
getAllDiscordIDs(), // Fetch all Discord IDs
|
||||||
|
checkOnlineBotsInGuild(nodeIo, guildId) // Check online bots in the guild
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Use the results of both promises here
|
||||||
|
console.log("Available Discord IDs:", discordIDs);
|
||||||
|
console.log("Online bots in the guild:", onlineBots);
|
||||||
|
|
||||||
|
// Filter any discordIDs that are not active
|
||||||
|
const availableDiscordIDs = discordIDs.filter(discordID => discordID.active == true).filter(discordID => !onlineBots.some(bot => Number(bot.discord_id) == discordID.discord_id));
|
||||||
|
|
||||||
|
// Return the unavailable discordIDs
|
||||||
|
return availableDiscordIDs;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting available tokens in guild:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
25
makefile
Normal file
25
makefile
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Define variables
|
||||||
|
DOCKER_IMAGE_NAME := drb-server
|
||||||
|
|
||||||
|
# Define targets and rules
|
||||||
|
.PHONY: clean build run
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@echo "Cleaning existing Docker images, containers, and builds..."
|
||||||
|
docker stop drb || true
|
||||||
|
docker rm drb || true
|
||||||
|
docker rmi $(DOCKER_IMAGE_NAME) || true
|
||||||
|
|
||||||
|
build:
|
||||||
|
@echo "Building Docker image..."
|
||||||
|
docker build -t $(DOCKER_IMAGE_NAME) .
|
||||||
|
|
||||||
|
run:
|
||||||
|
@echo "Running Docker container..."
|
||||||
|
docker run -d --rm -e NODE_ENV=${NODE_ENV} \
|
||||||
|
-e SERVER_PORT=${SERVER_PORT} \
|
||||||
|
-e MONGO_URL=${MONGO_URL} \
|
||||||
|
-e DISCORD_TOKEN=${DISCORD_TOKEN} \
|
||||||
|
-p ${SERVER_PORT}:${SERVER_PORT} \
|
||||||
|
--name=drb \
|
||||||
|
$(DOCKER_IMAGE_NAME)
|
||||||
31
modules/addonManager.mjs
Normal file
31
modules/addonManager.mjs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
// Function to load addons from the addons directory
|
||||||
|
export const loadAddons = async (nodeIo) => {
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
const addonsDir = path.join(__dirname, '../addons');
|
||||||
|
|
||||||
|
// Read the directory containing addon modules
|
||||||
|
const addonDirectories = await fs.readdirSync(addonsDir, { withFileTypes: true });
|
||||||
|
|
||||||
|
addonDirectories.forEach(addonDir => {
|
||||||
|
if (addonDir.isDirectory()) {
|
||||||
|
const addonConfigPath = path.join(addonsDir, addonDir.name, 'config.json');
|
||||||
|
if (fs.existsSync(addonConfigPath)) {
|
||||||
|
const addonConfig = JSON.parse(fs.readFileSync(addonConfigPath, 'utf-8'));
|
||||||
|
if (addonConfig.enabled) {
|
||||||
|
const addonIndexPath = path.join(addonsDir, addonDir.name, 'index.js');
|
||||||
|
import(`file://${addonIndexPath}`).then(addonModule => {
|
||||||
|
console.log("Loading addon: ", addonModule);
|
||||||
|
addonModule.initialize(nodeIo, addonConfig);
|
||||||
|
console.log(`Addon ${addonConfig.name} loaded.`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
90
modules/mongoDiscordIDWrappers.mjs
Normal file
90
modules/mongoDiscordIDWrappers.mjs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import { insertDocument, getDocuments, connectToDatabase } from "./mongoHandler.mjs";
|
||||||
|
|
||||||
|
const collectionName = 'discord-ids';
|
||||||
|
|
||||||
|
// Wrapper for inserting a Discord ID
|
||||||
|
export const createDiscordID = async (discordID) => {
|
||||||
|
try {
|
||||||
|
const insertedId = await insertDocument(collectionName, discordID);
|
||||||
|
return insertedId;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating Discord ID:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wrapper for retrieving all Discord IDs
|
||||||
|
export const getAllDiscordIDs = async () => {
|
||||||
|
try {
|
||||||
|
const discordIDs = await getDocuments(collectionName);
|
||||||
|
return discordIDs;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting all Discord IDs:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wrapper for retrieving a Discord ID by name or discord_id
|
||||||
|
export const getDiscordID = async (identifier) => {
|
||||||
|
const db = await connectToDatabase();
|
||||||
|
try {
|
||||||
|
const collection = db.db().collection(collectionName);
|
||||||
|
const discordID = await collection.findOne({
|
||||||
|
$or: [
|
||||||
|
{ name: identifier },
|
||||||
|
{ discord_id: identifier }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
return discordID;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting Discord ID:', error);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
// Close the connection
|
||||||
|
await db.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wrapper for updating a Discord ID by name or discord_id
|
||||||
|
export const updateDiscordID = async (identifier, updatedFields) => {
|
||||||
|
const db = await connectToDatabase();
|
||||||
|
try {
|
||||||
|
const collection = db.db().collection(collectionName);
|
||||||
|
const result = await collection.updateOne({
|
||||||
|
$or: [
|
||||||
|
{ name: identifier },
|
||||||
|
{ discord_id: identifier }
|
||||||
|
]
|
||||||
|
}, { $set: updatedFields });
|
||||||
|
console.log('Discord ID updated:', result.modifiedCount);
|
||||||
|
return result.modifiedCount;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating Discord ID:', error);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
// Close the connection
|
||||||
|
await db.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wrapper for deleting a Discord ID by name or discord_id
|
||||||
|
export const deleteDiscordID = async (identifier) => {
|
||||||
|
const db = await connectToDatabase();
|
||||||
|
try {
|
||||||
|
const collection = db.db().collection(collectionName);
|
||||||
|
const result = await collection.deleteOne({
|
||||||
|
$or: [
|
||||||
|
{ name: identifier },
|
||||||
|
{ discord_id: identifier }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
console.log('Discord ID deleted:', result.deletedCount);
|
||||||
|
return result.deletedCount;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting Discord ID:', error);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
// Close the connection
|
||||||
|
await db.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
53
modules/mongoHandler.mjs
Normal file
53
modules/mongoHandler.mjs
Normal 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();
|
||||||
|
}
|
||||||
|
};
|
||||||
75
modules/mongoNodesWrappers.mjs
Normal file
75
modules/mongoNodesWrappers.mjs
Normal 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();
|
||||||
|
}
|
||||||
|
};
|
||||||
111
modules/mongoSystemsWrappers.mjs
Normal file
111
modules/mongoSystemsWrappers.mjs
Normal 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();
|
||||||
|
}
|
||||||
|
};
|
||||||
39
modules/socketServer.mjs
Normal file
39
modules/socketServer.mjs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
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);
|
||||||
|
export const nodeIo = new Server(server);
|
||||||
|
|
||||||
|
app.use(morgan('tiny'));
|
||||||
|
|
||||||
|
app.get('/', (req, res) => {
|
||||||
|
res.send('<h1>Hello world</h1>');
|
||||||
|
});
|
||||||
|
|
||||||
|
nodeIo.on('connection', (socket) => {
|
||||||
|
console.log('a user connected', socket.id);
|
||||||
|
|
||||||
|
socket.on('node-login', async (data) => {
|
||||||
|
await nodeLoginWrapper(data, socket);
|
||||||
|
await socket.emit('node-login-successful');
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('node-update', async (data) => {
|
||||||
|
let tempPromises = [];
|
||||||
|
tempPromises.push(nodeUpdateWrapper(data.node));
|
||||||
|
tempPromises.push(nearbySystemsUpdateWraper(data.node.nuid, data.nearbySystems));
|
||||||
|
|
||||||
|
await Promise.all(tempPromises);
|
||||||
|
|
||||||
|
await socket.emit('node-update-successful')
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('disconnect', () => {
|
||||||
|
nodeDisconnectWrapper(socket.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
315
modules/socketServerWrappers.mjs
Normal file
315
modules/socketServerWrappers.mjs
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
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);
|
||||||
|
console.log("Added new node to the database:", insertedId);
|
||||||
|
} else {
|
||||||
|
// Check for updates
|
||||||
|
const updatedNode = await updateNodeByNuid(data.nuid, data)
|
||||||
|
console.log("Updated node:", updatedNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
node = await getNodeByNuid(data.nuid);
|
||||||
|
|
||||||
|
// Add the socket/node connection
|
||||||
|
socket.node = node;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect the client from the server
|
||||||
|
* @param {string} socketId The socket ID that was disconnected
|
||||||
|
* @returns {any}
|
||||||
|
*/
|
||||||
|
export const nodeDisconnectWrapper = async (socketId) => {
|
||||||
|
// TODO - Let any server know that a bot has disconnected if the bot was joined to vc? might not be worth cpu lol
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
export const getSocketIdByNuid = async (nodeIo, nuid) => {
|
||||||
|
const openSockets = await nodeIo.allSockets();
|
||||||
|
for (const openSocketId of openSockets) {
|
||||||
|
console.log(openSockets)
|
||||||
|
const openSocket = await nodeIo.sockets.sockets.get(openSocketId);
|
||||||
|
if (openSocket.node.nuid == nuid)
|
||||||
|
return openSocket;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all nodes that are connected to a voice channel
|
||||||
|
* @param {any} nodeIo The nodeIo object that contains the IO server
|
||||||
|
* @param {string} guildId The guild ID string for the guild we are looking in
|
||||||
|
* @returns {Array} The sockets connected to VC in a given server
|
||||||
|
*/
|
||||||
|
export const getAllSocketsConnectedToVC = async (nodeIo, guildId) => {
|
||||||
|
// Get all open socket nodes
|
||||||
|
// TODO - require a server guild to filter the results, ie this would be able to check what server the VCs the nodes are connected are in
|
||||||
|
const openSockets = [...await nodeIo.allSockets()]; // TODO - Filter the returned nodes to only nodes that have the radio capability
|
||||||
|
// Check each open socket to see if the node has the requested system
|
||||||
|
const socketsConnectedToVC = []
|
||||||
|
await Promise.all(openSockets.map(async openSocket => {
|
||||||
|
openSocket = await nodeIo.sockets.sockets.get(openSocket);
|
||||||
|
await new Promise((res) => {
|
||||||
|
openSocket.emit('node-check-connected-status', guildId, (status) => {
|
||||||
|
if (status) {
|
||||||
|
console.log("Socket is connected to VC:", openSocket.node.name, status);
|
||||||
|
socketsConnectedToVC.push(openSocket);
|
||||||
|
} else {
|
||||||
|
console.log("Socket is NOT connected to VC:", openSocket.node.name);
|
||||||
|
}
|
||||||
|
res();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
return socketsConnectedToVC;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given node has an open discord client
|
||||||
|
* @param {any} openSocket The open socket connection with the node to check
|
||||||
|
* @returns {boolean} If the given node has an open discord client or not
|
||||||
|
*/
|
||||||
|
export const checkIfNodeHasOpenDiscordClient = async (openSocket) => {
|
||||||
|
// Check the open socket to see if the node has an open discord client
|
||||||
|
let hasOpenDiscordClient = false;
|
||||||
|
await new Promise((res) => {
|
||||||
|
openSocket.emit('node-check-discord-open-client', (status) => {
|
||||||
|
if (status) {
|
||||||
|
console.log("Socket has an open discord client:", openSocket.node.name, status);
|
||||||
|
hasOpenDiscordClient = true;
|
||||||
|
} else {
|
||||||
|
console.log("Socket does NOT have an open discord client:", openSocket.node.name);
|
||||||
|
}
|
||||||
|
res();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
return hasOpenDiscordClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getNodeCurrentListeningSystem = async (openSocket) => {
|
||||||
|
const hasOpenClient = checkIfNodeHasOpenDiscordClient(openSocket);
|
||||||
|
if (!hasOpenClient) return undefined;
|
||||||
|
|
||||||
|
// check what system the socket is listening to
|
||||||
|
let currentSystem = undefined;
|
||||||
|
await new Promise((res) => {
|
||||||
|
openSocket.emit('node-check-current-system', (system) => {
|
||||||
|
if (system) {
|
||||||
|
console.log("Socket is listening to system:", openSocket.node.name, system);
|
||||||
|
currentSystem = system;
|
||||||
|
} else {
|
||||||
|
console.log("Socket is not currently listening to a system:", openSocket.node.name);
|
||||||
|
}
|
||||||
|
res();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
return currentSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper to check if the given NUID is connected to a VC
|
||||||
|
* @param {any} nodeIo The nodeIo object that contains the IO server
|
||||||
|
* @param {string} nuid The NUID string that we would like to find in the open socket connections
|
||||||
|
* @returns {boolean} If the node is connected to VC in the given server
|
||||||
|
*/
|
||||||
|
export const checkIfNodeIsConnectedToVC = async (nodeIo, guildId, nuid) => {
|
||||||
|
const socketsConnectedToVC = await getAllSocketsConnectedToVC(nodeIo, guildId);
|
||||||
|
for (const socket of socketsConnectedToVC) {
|
||||||
|
if (socket.node.nuid === nuid) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the discord username from a given socket
|
||||||
|
* @param {any} socket The socket object of the node to check the username of
|
||||||
|
* * @param {string} guildId The guild ID to check the username in
|
||||||
|
* @returns {string} The username of the bot in the requested server
|
||||||
|
*/
|
||||||
|
export const getNodeDiscordUsername = async (socket, guildId) => {
|
||||||
|
return await new Promise((res) => {
|
||||||
|
socket.emit('node-get-discord-username', guildId, (username) => {
|
||||||
|
res(username);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the discord ID from a given socket
|
||||||
|
* @param {any} socket The socket object of the node to check the ID of
|
||||||
|
* @returns {string} The ID of the bot
|
||||||
|
*/
|
||||||
|
export const getNodeDiscordID = async (socket) => {
|
||||||
|
return await new Promise((res) => {
|
||||||
|
socket.emit('node-get-discord-id', (discordID) => {
|
||||||
|
res(discordID);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request a given socket node to join a given voice channel
|
||||||
|
* @param {any} socket The socket object of the node the request should be sent to
|
||||||
|
* @param {any} systemName The system preset name that we would like to listen to
|
||||||
|
* @param {string} discordChanelId The Discord channel ID to join the listening bot to
|
||||||
|
*/
|
||||||
|
export const requestNodeJoinSystem = async (socket, systemName, discordChanelId, discordToken = "MTE5NjAwNTM2ODYzNjExMjk3Nw.GuCMXg.24iNNofNNumq46FIj68zMe9RmQgugAgfrvelEA") => {
|
||||||
|
// Join the system
|
||||||
|
const joinData = {
|
||||||
|
'clientID': discordToken,
|
||||||
|
'channelID': discordChanelId,
|
||||||
|
'system': systemName
|
||||||
|
}
|
||||||
|
// Send the command to the node
|
||||||
|
await sendNodeCommand(socket, "node-join", joinData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request a given socket node to leave VC in a given server
|
||||||
|
* @param {any} socket The socket object of the node the request should be sent to
|
||||||
|
* @param {string} guildId The guild ID to disconnect the socket node from
|
||||||
|
*/
|
||||||
|
export const requestBotLeaveServer = async (socket, guildId) => {
|
||||||
|
// Send the command to the node
|
||||||
|
await sendNodeCommand(socket, "node-leave", guildId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requset a given socket node to update themselves
|
||||||
|
* @param {any} socket The socket object of the node to request to update
|
||||||
|
*/
|
||||||
|
export const requestNodeUpdate = async (socket) => {
|
||||||
|
await sendNodeCommand(socket, 'node-update', (status) => {
|
||||||
|
if (status) {
|
||||||
|
console.log("Node is out of date, updating now", socket.node.name);
|
||||||
|
} else {
|
||||||
|
console.log("Node is up to date", socket.node.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
2316
package-lock.json
generated
Normal file
2316
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
package.json
Normal file
26
package.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "drb-server",
|
||||||
|
"version": "3.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "server.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "mocha --timeout 5000",
|
||||||
|
"start": "node server.js"
|
||||||
|
},
|
||||||
|
"author": "Logan Cusano",
|
||||||
|
"license": "ISC",
|
||||||
|
"type": "module",
|
||||||
|
"devDependencies": {
|
||||||
|
"chai": "^5.1.0",
|
||||||
|
"mocha": "^10.4.0",
|
||||||
|
"socket.io-client": "^4.7.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
18
server.js
Normal file
18
server.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { nodeIo, app, server } from './modules/socketServer.mjs';
|
||||||
|
import { loadAddons } from './modules/addonManager.mjs';
|
||||||
|
import { serverClient, addEnabledEventListeners } from './discordBot/discordBot.mjs';
|
||||||
|
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
dotenv.config()
|
||||||
|
|
||||||
|
// Startup the node server
|
||||||
|
server.listen(process.env.SERVER_PORT || 3000, () => {
|
||||||
|
console.log(`server running at http://localhost:${process.env.SERVER_PORT}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add objects to the others
|
||||||
|
serverClient.nodeIo = nodeIo;
|
||||||
|
nodeIo.serverClient = serverClient;
|
||||||
|
|
||||||
|
// Load the addons
|
||||||
|
loadAddons(nodeIo);
|
||||||
290
test/socketServerWrappers.test.js
Normal file
290
test/socketServerWrappers.test.js
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
// Import necessary modules for testing
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import ioClient from 'socket.io-client';
|
||||||
|
import { deleteNodeByNuid, getNodeByNuid } from '../modules/mongoNodesWrappers.mjs';
|
||||||
|
import { deleteSystemByName, getSystemByName } from '../modules/mongoSystemsWrappers.mjs';
|
||||||
|
import { nodeIo } from '../modules/socketServer.mjs';
|
||||||
|
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
dotenv.config()
|
||||||
|
|
||||||
|
process.env.SERVER_PORT = 6000
|
||||||
|
|
||||||
|
// Define necessary variables for testing, such as mocked database connections or socket instances
|
||||||
|
const localNodeConfig = {
|
||||||
|
serverIp: 'localhost',
|
||||||
|
serverPort: process.env.SERVER_PORT,
|
||||||
|
node: {
|
||||||
|
nuid: "4f29a6340901a12affc87047c0ac16b01b92496c460c880a2459abe8c7928374",
|
||||||
|
name: "testyv7",
|
||||||
|
location: "china",
|
||||||
|
capabilities: ["radio"]
|
||||||
|
},
|
||||||
|
nearbySystems: {
|
||||||
|
"Testing P25 System Name": {
|
||||||
|
"frequencies": [
|
||||||
|
155344000,
|
||||||
|
155444000,
|
||||||
|
155555000,
|
||||||
|
155588550
|
||||||
|
],
|
||||||
|
"mode": "p25",
|
||||||
|
"trunkFile": "trunk.tsv",
|
||||||
|
"whitelistFile": "whitelist.tsv"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatedLocalNodeConfig = {
|
||||||
|
node: {
|
||||||
|
nuid: localNodeConfig.node.nuid,
|
||||||
|
name: "updatedName",
|
||||||
|
location: "updatedLocation",
|
||||||
|
capabilities: ["radio", "weather"] // Updated capabilities
|
||||||
|
},
|
||||||
|
nearbySystems: {
|
||||||
|
"Testing P25 System Name": {
|
||||||
|
"frequencies": [
|
||||||
|
155444000,
|
||||||
|
155555000,
|
||||||
|
155500000
|
||||||
|
],
|
||||||
|
"mode": "p25",
|
||||||
|
"trunkFile": "trunk2.tsv",
|
||||||
|
"whitelistFile": "whitelist2.tsv"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start the Socket.IO server before running tests
|
||||||
|
let clientSocket; // The socket client
|
||||||
|
let serverClientSocket // The open client socket on the server
|
||||||
|
before(done => {
|
||||||
|
// Startup the node server
|
||||||
|
nodeIo.listen(process.env.SERVER_PORT || 3000, () => {
|
||||||
|
console.log(`server running at http://localhost:${process.env.SERVER_PORT}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Connect a client socket to the server
|
||||||
|
clientSocket = ioClient.connect(`http://localhost:${process.env.SERVER_PORT}`);
|
||||||
|
|
||||||
|
nodeIo.on('connection', (socket) => {
|
||||||
|
serverClientSocket = socket;
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close the Socket.IO server after running tests
|
||||||
|
after(async () => {
|
||||||
|
// Disconnect client socket
|
||||||
|
clientSocket.disconnect();
|
||||||
|
|
||||||
|
// Close the server
|
||||||
|
nodeIo.close();
|
||||||
|
|
||||||
|
// Remove the test data
|
||||||
|
deleteNodeByNuid(localNodeConfig.node.nuid); // Delete the user
|
||||||
|
deleteSystemByName(Object.keys(localNodeConfig.nearbySystems)[0])
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Node Core Server Tests', () => {
|
||||||
|
// Test Node Login functionality
|
||||||
|
describe('Node Login', () => {
|
||||||
|
it('Should add a new node if it does not exist', async () => {
|
||||||
|
// Simulate a node login request
|
||||||
|
// Use the getNodeByNuid mock function to simulate checking if node exists
|
||||||
|
const existingNode = await getNodeByNuid(localNodeConfig.node.nuid);
|
||||||
|
|
||||||
|
// Assert that existingNode is null before node login
|
||||||
|
expect(existingNode).to.be.null;
|
||||||
|
|
||||||
|
// Wait for the update
|
||||||
|
const node_login = new Promise(res => {
|
||||||
|
clientSocket.on('node-login-successful', async () => {
|
||||||
|
res();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Emit the login command
|
||||||
|
clientSocket.emit("node-login", localNodeConfig.node);
|
||||||
|
|
||||||
|
// Wait for the successful login event
|
||||||
|
await node_login;
|
||||||
|
|
||||||
|
// Now we need to check if the node is added to the database
|
||||||
|
// We can use getNodeByNuid again to verify if the node was added correctly
|
||||||
|
const addedNode = await getNodeByNuid(localNodeConfig.node.nuid);
|
||||||
|
|
||||||
|
console.log("Added Node:", addedNode);
|
||||||
|
|
||||||
|
// Assert that the node is added correctly
|
||||||
|
expect(addedNode).to.have.property('_id'); // Check if _id property exists
|
||||||
|
expect(addedNode).to.have.property('nuid', localNodeConfig.node.nuid);
|
||||||
|
expect(addedNode).to.have.property('name', localNodeConfig.node.name);
|
||||||
|
expect(addedNode).to.have.property('location', localNodeConfig.node.location);
|
||||||
|
expect(addedNode).to.have.deep.property('capabilities', localNodeConfig.node.capabilities);
|
||||||
|
})
|
||||||
|
it('Should update a node if it exists', async () => {
|
||||||
|
// Simulate a node login request
|
||||||
|
// Use the getNodeByNuid mock function to simulate checking if node exists
|
||||||
|
const existingNode = await getNodeByNuid(localNodeConfig.node.nuid);
|
||||||
|
|
||||||
|
// Assert that existingNode is matches the existing data before logging in
|
||||||
|
expect(existingNode).to.have.property('_id'); // Check if _id property exists
|
||||||
|
expect(existingNode).to.have.property('nuid', localNodeConfig.node.nuid);
|
||||||
|
expect(existingNode).to.have.property('name', localNodeConfig.node.name);
|
||||||
|
expect(existingNode).to.have.property('location', localNodeConfig.node.location);
|
||||||
|
expect(existingNode).to.have.deep.property('capabilities', localNodeConfig.node.capabilities);
|
||||||
|
|
||||||
|
// Wait for the update
|
||||||
|
const node_login = new Promise(res => {
|
||||||
|
clientSocket.on('node-login-successful', async () => {
|
||||||
|
res();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Emit the login command
|
||||||
|
clientSocket.emit("node-login", updatedLocalNodeConfig.node);
|
||||||
|
|
||||||
|
// Wait for the successful login event
|
||||||
|
await node_login;
|
||||||
|
|
||||||
|
// Now we need to check if the node is added to the database
|
||||||
|
// We can use getNodeByNuid again to verify if the node was added correctly
|
||||||
|
const updatedNode = await getNodeByNuid(localNodeConfig.node.nuid);
|
||||||
|
|
||||||
|
console.log("Updated Node:", updatedNode);
|
||||||
|
|
||||||
|
// Assert that the node is added correctly
|
||||||
|
expect(updatedNode).to.have.property('_id'); // Check if _id property exists
|
||||||
|
expect(updatedNode).to.have.property('nuid', updatedLocalNodeConfig.node.nuid);
|
||||||
|
expect(updatedNode).to.have.property('name', updatedLocalNodeConfig.node.name);
|
||||||
|
expect(updatedNode).to.have.property('location', updatedLocalNodeConfig.node.location);
|
||||||
|
expect(updatedNode).to.have.deep.property('capabilities', updatedLocalNodeConfig.node.capabilities);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test Node Update functionality
|
||||||
|
describe('Node Update', () => {
|
||||||
|
it('Should add a node\'s nearby systems', async () => {
|
||||||
|
// Simulate an update request sent from the client to the server
|
||||||
|
|
||||||
|
// Get the existing node in the database
|
||||||
|
const existingNode = await getNodeByNuid(localNodeConfig.node.nuid);
|
||||||
|
|
||||||
|
// Assert that existingNode matches the updatedLocalNodeConfig
|
||||||
|
expect(existingNode).to.have.property('_id'); // Check if _id property exists
|
||||||
|
expect(existingNode).to.have.property('nuid', updatedLocalNodeConfig.node.nuid);
|
||||||
|
expect(existingNode).to.have.property('name', updatedLocalNodeConfig.node.name);
|
||||||
|
expect(existingNode).to.have.property('location', updatedLocalNodeConfig.node.location);
|
||||||
|
expect(existingNode).to.have.deep.property('capabilities', updatedLocalNodeConfig.node.capabilities);
|
||||||
|
|
||||||
|
// Get the system from the DB
|
||||||
|
const existsingSystem = await getSystemByName("Testing P25 System Name");
|
||||||
|
|
||||||
|
// Assert that there is no existing system in the DB
|
||||||
|
expect(existsingSystem).to.be.null;
|
||||||
|
|
||||||
|
// Wait for the update
|
||||||
|
const node_system_update = new Promise(res => {
|
||||||
|
clientSocket.on('node-update-successful', async () => {
|
||||||
|
res();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Emit the update command
|
||||||
|
clientSocket.emit("node-update", updatedLocalNodeConfig);
|
||||||
|
|
||||||
|
// Wait for the successful update event
|
||||||
|
await node_system_update;
|
||||||
|
|
||||||
|
// Now we need to check if the system is added to the database
|
||||||
|
// We can use getNodeByNuid again to verify if the node was added correctly
|
||||||
|
const updatedNode = await getNodeByNuid(localNodeConfig.node.nuid);
|
||||||
|
|
||||||
|
console.log("Updated Node:", updatedNode);
|
||||||
|
|
||||||
|
// Assert that the node is added correctly
|
||||||
|
expect(updatedNode).to.have.property('_id'); // Check if _id property exists
|
||||||
|
expect(updatedNode).to.have.property('nuid', updatedLocalNodeConfig.node.nuid);
|
||||||
|
expect(updatedNode).to.have.property('name', updatedLocalNodeConfig.node.name);
|
||||||
|
expect(updatedNode).to.have.property('location', updatedLocalNodeConfig.node.location);
|
||||||
|
expect(updatedNode).to.have.deep.property('capabilities', updatedLocalNodeConfig.node.capabilities);
|
||||||
|
|
||||||
|
// Get the updated system
|
||||||
|
const addedSystem = await getSystemByName("Testing P25 System Name");
|
||||||
|
|
||||||
|
console.log("Added system:", addedSystem);
|
||||||
|
|
||||||
|
expect(addedSystem).to.have.property('_id'); // Check if _id property exists
|
||||||
|
expect(addedSystem).to.have.property('nodes'); // Check if nodes property exists
|
||||||
|
expect(addedSystem.nodes).to.include(updatedLocalNodeConfig.node.nuid) // Check if this node ID is in the nodes array
|
||||||
|
expect(addedSystem).to.have.deep.property('frequencies', updatedLocalNodeConfig.nearbySystems['Testing P25 System Name'].frequencies);
|
||||||
|
expect(addedSystem).to.have.property('mode', updatedLocalNodeConfig.nearbySystems['Testing P25 System Name'].mode);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should update a node and its nearby systems', async () => {
|
||||||
|
// Get the existing node in the database
|
||||||
|
const existingNode = await getNodeByNuid(localNodeConfig.node.nuid);
|
||||||
|
|
||||||
|
// Assert that existingNode matches the updatedLocalNodeConfig
|
||||||
|
expect(existingNode).to.have.property('_id'); // Check if _id property exists
|
||||||
|
expect(existingNode).to.have.property('nuid', updatedLocalNodeConfig.node.nuid);
|
||||||
|
expect(existingNode).to.have.property('name', updatedLocalNodeConfig.node.name);
|
||||||
|
expect(existingNode).to.have.property('location', updatedLocalNodeConfig.node.location);
|
||||||
|
expect(existingNode).to.have.deep.property('capabilities', updatedLocalNodeConfig.node.capabilities);
|
||||||
|
|
||||||
|
// Get the updated system
|
||||||
|
const existingSystem = await getSystemByName("Testing P25 System Name");
|
||||||
|
expect(existingSystem).to.have.property('_id'); // Check if _id property exists
|
||||||
|
expect(existingSystem).to.have.property('nodes'); // Check if nodes property exists
|
||||||
|
expect(existingSystem.nodes).to.include(updatedLocalNodeConfig.node.nuid) // Check if this node ID is in the nodes array
|
||||||
|
expect(existingSystem).to.have.deep.property('frequencies', updatedLocalNodeConfig.nearbySystems['Testing P25 System Name'].frequencies);
|
||||||
|
expect(existingSystem).to.have.property('mode', updatedLocalNodeConfig.nearbySystems['Testing P25 System Name'].mode);
|
||||||
|
|
||||||
|
|
||||||
|
// Wait for the update
|
||||||
|
const node_update = new Promise(res => {
|
||||||
|
clientSocket.on('node-update-successful', async () => {
|
||||||
|
res();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Emit the update command
|
||||||
|
clientSocket.emit("node-update", localNodeConfig);
|
||||||
|
|
||||||
|
// Wait for the successful update event
|
||||||
|
await node_update;
|
||||||
|
|
||||||
|
const updatedNode = await getNodeByNuid(localNodeConfig.node.nuid);
|
||||||
|
|
||||||
|
console.log("Updated Node:", updatedNode);
|
||||||
|
|
||||||
|
// Assert that the node is added correctly
|
||||||
|
expect(updatedNode).to.have.property('_id'); // Check if _id property exists
|
||||||
|
expect(updatedNode).to.have.property('nuid', localNodeConfig.node.nuid);
|
||||||
|
expect(updatedNode).to.have.property('name', localNodeConfig.node.name);
|
||||||
|
expect(updatedNode).to.have.property('location', localNodeConfig.node.location);
|
||||||
|
expect(updatedNode).to.have.deep.property('capabilities', localNodeConfig.node.capabilities);
|
||||||
|
|
||||||
|
// Get the updated system
|
||||||
|
const updatedSystem = await getSystemByName("Testing P25 System Name");
|
||||||
|
|
||||||
|
console.log("Updated system:", updatedSystem);
|
||||||
|
|
||||||
|
expect(updatedSystem).to.have.property('_id'); // Check if _id property exists
|
||||||
|
expect(updatedSystem).to.have.property('nodes'); // Check if nodes property exists
|
||||||
|
expect(updatedSystem.nodes).include(localNodeConfig.node.nuid) // Check if this node ID is in the nodes array
|
||||||
|
expect(updatedSystem).to.have.deep.property('frequencies', localNodeConfig.nearbySystems['Testing P25 System Name'].frequencies);
|
||||||
|
expect(updatedSystem).to.have.property('mode', localNodeConfig.nearbySystems['Testing P25 System Name'].mode);
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Node Disconnect', () => {
|
||||||
|
it('Should trigger cleanup actions upon socket disconnection', async () => {
|
||||||
|
// Write test code to simulate a socket disconnection
|
||||||
|
// Check if the appropriate cleanup actions are triggered
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user