Compare commits
96 Commits
1df2027a29
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4d07db766 | ||
|
|
03c940e07c | ||
|
|
fee40dd609 | ||
|
|
5671503594 | ||
|
|
0fd511cfaf | ||
|
|
9d0aa0191f | ||
|
|
9ce8928d82 | ||
|
|
abadcf5cb5 | ||
|
|
4b18df9e2c | ||
|
|
26a8d8a172 | ||
|
|
26f3493c8f | ||
|
|
fd261ef015 | ||
|
|
91201c3527 | ||
|
|
dfb4c6afa1 | ||
|
|
3b8e70208a | ||
|
|
99b60bf02c | ||
|
|
831317b9f0 | ||
|
|
2477b10900 | ||
|
|
67c29f1d72 | ||
|
|
91ed4fb1dc | ||
|
|
8ea02d1c0b | ||
|
|
b15ac7c973 | ||
|
|
c2c90019d7 | ||
|
|
686ddc8a0f | ||
|
|
463ccc1bd5 | ||
|
|
14a2b4a1b9 | ||
|
|
0c1f6cd867 | ||
|
|
7175487d77 | ||
|
|
f0eac45955 | ||
|
|
c81dce99e2 | ||
|
|
38cb1054e0 | ||
|
|
12441c5c6d | ||
|
|
1c1b071bd7 | ||
|
|
961c5c19e2 | ||
|
|
49e52d8944 | ||
|
|
63ccfa70d3 | ||
|
|
4bb8038a1d | ||
|
|
a353b9adbb | ||
|
|
bf69e93e29 | ||
|
|
6cae18e70c | ||
|
|
e7229322e4 | ||
|
|
34aa5d17dc | ||
|
|
565fd5af37 | ||
|
|
f04154d361 | ||
|
|
383663e980 | ||
|
|
7b21d4601f | ||
|
|
d0c2fcc8eb | ||
|
|
9ba90af464 | ||
|
|
6e8681e52d | ||
|
|
8ba1ed36d8 | ||
|
|
3074e88963 | ||
|
|
737b493b23 | ||
|
|
539dbd9518 | ||
|
|
59bfdbe143 | ||
|
|
560ed401cf | ||
|
|
7b91667414 | ||
|
|
fd9b6d9d1c | ||
|
|
3aae427249 | ||
|
|
bc4c8f72d0 | ||
|
|
cc4e5e762d | ||
|
|
31cedb2e9c | ||
|
|
61a616ec6b | ||
|
|
d7b7b04f78 | ||
|
|
2c9383824e | ||
|
|
cebd316939 | ||
|
|
af19db8e17 | ||
|
|
45b9a62c64 | ||
|
|
8c3164029f | ||
|
|
bec4072837 | ||
|
|
ac82b0efd0 | ||
|
|
854c73cc4e | ||
|
|
238fe6a254 | ||
|
|
96d9c38425 | ||
|
|
4df3de4d4a | ||
|
|
461b449194 | ||
|
|
757fdfb3b2 | ||
|
|
9bc80887ce | ||
|
|
0ce0f72ed3 | ||
|
|
49ae941e83 | ||
|
|
8e73659855 | ||
|
|
0a76804490 | ||
| 9ad24ca8ec | |||
| 7676e883a5 | |||
| 86a71d3d6f | |||
| 1dd53ffc84 | |||
| fc31026304 | |||
| 8a4b7685d2 | |||
|
|
49c1a1d724 | ||
|
|
a423417949 | ||
|
|
2fb0bc8920 | ||
| 38470bd788 | |||
| 8995f8b372 | |||
| a6c26d61da | |||
| ab0f94baf8 | |||
|
|
62c00eec09 | ||
|
|
ea63abcb93 |
32
.gitea/workflows/DRBv3-Client_Setup_tests.yaml.disabled
Normal file
32
.gitea/workflows/DRBv3-Client_Setup_tests.yaml.disabled
Normal file
@@ -0,0 +1,32 @@
|
||||
name: DRB Build Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
drb_test_setup:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Test setup script
|
||||
working-directory: "./client"
|
||||
run: |
|
||||
sudo useradd -m -s /bin/bash pi
|
||||
sudo bash setup.sh --test
|
||||
|
||||
- name: Test running client node
|
||||
working-directory: "./client"
|
||||
run: |
|
||||
bash serviceStart.sh
|
||||
24
.gitea/workflows/DRBv3_server_build.yaml
Normal file
24
.gitea/workflows/DRBv3_server_build.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
name: DRB Server Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
env:
|
||||
NODE_ENV: production
|
||||
SERVER_PORT: 3000
|
||||
MONGO_URL: ${{ secrets.MONGO_URL }}
|
||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
|
||||
jobs:
|
||||
drb_server_build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build the latest code
|
||||
working-directory: './server'
|
||||
run: make build
|
||||
81
.gitea/workflows/DRBv3_tests.yaml
Normal file
81
.gitea/workflows/DRBv3_tests.yaml
Normal file
@@ -0,0 +1,81 @@
|
||||
name: DRB Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
env:
|
||||
NODE_ENV: development
|
||||
MONGO_INITDB_ROOT_USERNAME: admin
|
||||
MONGO_INITDB_ROOT_PASSWORD: admin
|
||||
MONGO_INITDB_DATABASE: drb
|
||||
SERVER_PORT: 3000
|
||||
MONGO_URL: "mongodb://mongodb:27017/drb"
|
||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
TEST_GUILD_ID: ${{ secrets.TEST_GUILD_ID }}
|
||||
TEST_CLIENT_TOKEN: ${{ secrets.TEST_CLIENT_TOKEN }}
|
||||
TEST_CHANNEL_ID: ${{ secrets.TEST_CHANNEL_ID }}
|
||||
TEST_SYSTEM: ${{ secrets.TEST_SYSTEM }}
|
||||
EXPECTED_CLIENT_ID: ${{ secrets.TEST_CLIENT_ID }}
|
||||
EXPECTED_USERNAME: ${{ secrets.EXPECTED_USERNAME }}
|
||||
PDAB_PORT: ${{ vars.PDAB_PORT }}
|
||||
|
||||
jobs:
|
||||
drb_mocha_tests:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
mongodb:
|
||||
image: mongo:latest
|
||||
ports:
|
||||
- 27017:27017
|
||||
options: >-
|
||||
--health-cmd mongo
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Remove package-lock.json (Server)
|
||||
working-directory: "./server"
|
||||
run: rm package-lock.json
|
||||
|
||||
- name: Install Node-GYP (Server)
|
||||
working-directory: "./server"
|
||||
run: npm install -g node-gyp
|
||||
|
||||
- name: Install dependencies (Server)
|
||||
working-directory: "./server"
|
||||
run: npm install
|
||||
|
||||
- name: Remove package-lock.json (Client)
|
||||
working-directory: "./client"
|
||||
run: rm package-lock.json
|
||||
|
||||
- name: Install dependencies (Client)
|
||||
working-directory: "./client"
|
||||
run: npm install
|
||||
|
||||
- run: echo "Node has finished installing dependencies"
|
||||
|
||||
- name: Run Server tests
|
||||
working-directory: "./server"
|
||||
run: npm test
|
||||
|
||||
- name: Run Client tests
|
||||
working-directory: "./client"
|
||||
run: npm test
|
||||
|
||||
- run: echo "Completed the DRB tests"
|
||||
@@ -1,30 +0,0 @@
|
||||
name: Run Socket Server Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- all
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: './server'
|
||||
run: npm install
|
||||
run: echo "Node has finished installing dependencies"
|
||||
|
||||
- name: Run tests
|
||||
working-directory: './server'
|
||||
run: npm test
|
||||
run: echo "Completed the tests"
|
||||
@@ -2,6 +2,7 @@ import { generateUniqueID } from './modules/baseUtils.mjs';
|
||||
import { updateId } from './modules/updateConfig.mjs';
|
||||
import { ClientNodeConfig } from './modules/clientObjectDefinitions.mjs';
|
||||
import { initSocketConnection } from './modules/socketClient.mjs';
|
||||
import { checkForUpdates } from './modules/selfUpdater.mjs'
|
||||
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config()
|
||||
@@ -9,6 +10,9 @@ dotenv.config()
|
||||
var localNodeConfig = new ClientNodeConfig({})
|
||||
|
||||
async function boot() {
|
||||
// Check if there have been any updates
|
||||
await checkForUpdates();
|
||||
|
||||
if (localNodeConfig.node.nuid === undefined || localNodeConfig.node.nuid === '' || localNodeConfig.node.nuid === '0' || localNodeConfig.node.nuid === 0) {
|
||||
// Run the first time boot sequence
|
||||
await firstTimeBoot();
|
||||
|
||||
1
client/discordAudioBot/pdab
Submodule
1
client/discordAudioBot/pdab
Submodule
Submodule client/discordAudioBot/pdab added at fc6c114473
@@ -3,13 +3,30 @@ import express from 'express';
|
||||
import http from 'http';
|
||||
import { Server } from 'socket.io';
|
||||
import { launchProcess } from '../modules/subprocessHandler.mjs';
|
||||
import { join, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config()
|
||||
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
const io = new Server(server);
|
||||
let pdabProcess = false;
|
||||
|
||||
export const initDiscordBotClient = (clientId, callback) => {
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
let botCallback;
|
||||
|
||||
export const initDiscordBotClient = (clientId, callback, runPDAB = true) => {
|
||||
botCallback = callback;
|
||||
|
||||
if (runPDAB) launchProcess("python", [join(__dirname, "./pdab/main.py"), process.env.AUDIO_DEVICE_ID, clientId, port], false, join(__dirname, "./pdab"));
|
||||
pdabProcess = true; // TODO - Make this more dynamic
|
||||
}
|
||||
|
||||
export const startPdabSocketServer = () => {
|
||||
const port = process.env.PDAB_PORT || 3000;
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
@@ -22,18 +39,24 @@ export const initDiscordBotClient = (clientId, callback) => {
|
||||
// Listen for the discord client ready event
|
||||
socket.on('discord_ready', (message) => {
|
||||
console.log("Message from local client", message);
|
||||
callback();
|
||||
botCallback();
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(port, async () => {
|
||||
console.log(`Server is running on port ${port}`);
|
||||
|
||||
launchProcess("python", ["./discordAduioBot/pdab/main.py", process.env.AUDIO_DEVICE_ID, clientId, port], false);
|
||||
pdabProcess = true; // TODO - Make this more dynamic
|
||||
});
|
||||
return
|
||||
}
|
||||
|
||||
export const closePdabSocketServer = () => {
|
||||
if (io.sockets && io.sockets.length > 0) {
|
||||
io.sockets.forEach(socket => {
|
||||
socket.destroy();
|
||||
})
|
||||
}
|
||||
return io.close();
|
||||
}
|
||||
|
||||
// Function to emit a command to join a voice channel
|
||||
export const connectToChannel = (channelId) => {
|
||||
@@ -46,11 +69,20 @@ export const connectToChannel = (channelId) => {
|
||||
};
|
||||
|
||||
// Function to emit a command to leave a voice channel
|
||||
export const leaveVoiceChannel = (guildId) => {
|
||||
return new Promise((res) => {
|
||||
io.timeout(25000).emit('leave_server', { guild_id: guildId }, (status, clientRemainsOpen) => {
|
||||
export const leaveVoiceChannel = async (guildId) => {
|
||||
return await new Promise((res) => {
|
||||
io.timeout(25000).emit('leave_server', { guildId: guildId }, (status, clientRemainsOpen) => {
|
||||
console.log("Discord client remains open?", clientRemainsOpen);
|
||||
res(clientRemainsOpen)
|
||||
res(clientRemainsOpen[0])
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Set the presense of the discord client
|
||||
export const setDiscordClientPrsense = (system) => {
|
||||
return new Promise((res) => {
|
||||
io.timeout(25000).emit('set_system', { system: system }, (status) => {
|
||||
res();
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -62,7 +94,7 @@ export const checkIfConnectedToVC = async (guildId) => {
|
||||
if (!pdabProcess) return false;
|
||||
|
||||
return await new Promise((res) => {
|
||||
io.timeout(25000).emit('check_discord_vc_connected', { guild_id: guildId }, (status, result) => {
|
||||
io.timeout(25000).emit('check_discord_vc_connected', { guildId: guildId }, (status, result) => {
|
||||
console.log(`Discord VC connected for guild ${guildId}: ${result}`);
|
||||
res((result[0]));
|
||||
});
|
||||
@@ -71,7 +103,7 @@ export const checkIfConnectedToVC = async (guildId) => {
|
||||
|
||||
export const requestDiscordUsername = (guildId) => {
|
||||
return new Promise((res) => {
|
||||
io.timeout(25000).emit('request_discord_username', { guild_id: guildId }, (status, result) => {
|
||||
io.timeout(25000).emit('request_discord_username', { guildId: guildId }, (status, result) => {
|
||||
console.log(`Discord username: ${result[0]}`);
|
||||
res(result[0]);
|
||||
});
|
||||
@@ -95,3 +127,12 @@ export const requestDiscordID = () => {
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
export const requestDiscordClientClose = () => {
|
||||
return new Promise((res) => {
|
||||
io.timeout(25000).emit('request_client_close');
|
||||
pdabProcess = false;
|
||||
res();
|
||||
});
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { connectToChannel, leaveVoiceChannel, checkIfConnectedToVC, initDiscordBotClient, requestDiscordUsername, requestDiscordID } from './pdabHandler.mjs';
|
||||
import { connectToChannel, leaveVoiceChannel, checkIfConnectedToVC, initDiscordBotClient, requestDiscordUsername, requestDiscordID, requestDiscordClientClose, closePdabSocketServer, setDiscordClientPrsense, startPdabSocketServer } from './pdabHandler.mjs';
|
||||
import { openOP25, closeOP25 } from '../op25Handler/op25Handler.mjs';
|
||||
|
||||
let activeDiscordClient = undefined;
|
||||
@@ -8,16 +8,26 @@ let activeDiscordClient = undefined;
|
||||
* @param {object} joinData The object containing all the information to join the server
|
||||
*/
|
||||
export const joinDiscordVC = async (joinData) => {
|
||||
console.log("Join requested: ", joinData)
|
||||
console.log("Join requested: ", joinData);
|
||||
const connection = await new Promise(async (res) => {
|
||||
// Check if a client already exists
|
||||
console.log("Checking if there is a client open");
|
||||
if (!await checkIfClientIsOpen()) {
|
||||
console.log("There is no open client, starting it now");
|
||||
await startPdabSocketServer();
|
||||
// Open an instance of OP25
|
||||
console.log("Starting OP25")
|
||||
openOP25(joinData.system);
|
||||
|
||||
// Open a new client and join the requested channel with the requested ID
|
||||
initDiscordBotClient(joinData.clientID, () => {
|
||||
// Open an instance of OP25
|
||||
// TODO DELTE comment DEV ONLY openOP25(joinData.system);
|
||||
console.log("Started PDAB");
|
||||
|
||||
console.log("Setting the presense of the bot");
|
||||
setDiscordClientPrsense(joinData.system);
|
||||
|
||||
// Add the client object to the IO instance
|
||||
console.log("Connecting to channel")
|
||||
connectToChannel(joinData.channelID, (connectionStatus) => {
|
||||
console.log("Bot Connected to VC:", connectionStatus);
|
||||
res(connectionStatus);
|
||||
@@ -25,7 +35,9 @@ export const joinDiscordVC = async (joinData) => {
|
||||
});
|
||||
} else {
|
||||
// Join the requested channel with the requested ID
|
||||
// Add the client object to the IO instance
|
||||
console.log("There is an open client");
|
||||
|
||||
console.log("Connecting to channel")
|
||||
const connection = connectToChannel(joinData.channelID);
|
||||
console.log("Bot Connected to VC::");
|
||||
res(connection);
|
||||
@@ -42,14 +54,18 @@ export const joinDiscordVC = async (joinData) => {
|
||||
export const leaveDiscordVC = async (guildId) => {
|
||||
console.log("Leave requested");
|
||||
if (await checkIfConnectedToVC(guildId)) {
|
||||
await leaveVoiceChannel(guildId, async (clientRemainsOpen) => {
|
||||
const clientRemainsOpen = await leaveVoiceChannel(guildId);
|
||||
console.log("Client should remain open: ", clientRemainsOpen);
|
||||
if (!clientRemainsOpen) {
|
||||
console.log("There are no open VC connections");
|
||||
// TODO DELETE comment DEV ONLY await closeOP25();
|
||||
await closeOP25();
|
||||
|
||||
// TODO Close the python client
|
||||
// Close the python client
|
||||
await requestDiscordClientClose();
|
||||
|
||||
// Close the IPC server
|
||||
await closePdabSocketServer();
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { spawn } from "child_process";
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config()
|
||||
|
||||
/**
|
||||
* Object to store references to spawned processes.
|
||||
@@ -12,18 +14,42 @@ const runningProcesses = {};
|
||||
* @param {string[]} args - The arguments to pass to the process.
|
||||
* @param {boolean} waitForClose - Set this to wait to return until the process exits
|
||||
*/
|
||||
export const launchProcess = (processName, args, waitForClose=false) => {
|
||||
export const launchProcess = (processName, args, waitForClose = false, pcwd = undefined) => {
|
||||
if (!runningProcesses[processName]) {
|
||||
const childProcess = spawn(processName, args);
|
||||
let childProcess;
|
||||
if (pcwd) {
|
||||
childProcess = spawn(processName, args, { cwd: pcwd });
|
||||
}
|
||||
else {
|
||||
childProcess = spawn(processName, args);
|
||||
}
|
||||
|
||||
// Store reference to the spawned process
|
||||
runningProcesses[processName] = childProcess;
|
||||
|
||||
// Output the process output in development
|
||||
var scriptOutput = "";
|
||||
|
||||
// Get the stdout from the child process
|
||||
childProcess.stdout.setEncoding('utf8');
|
||||
childProcess.stdout.on('data', (data) => {
|
||||
if (process.env.NODE_ENV === "development") console.log(`Data from ${processName}:`, data);
|
||||
scriptOutput += data.toString();
|
||||
});
|
||||
|
||||
// Get the stderr from the child process
|
||||
childProcess.stderr.setEncoding('utf8');
|
||||
childProcess.stderr.on('data', (data) => {
|
||||
if (process.env.NODE_ENV === "development") console.log(`Data from ${processName}:`, data);
|
||||
scriptOutput += data.toString();
|
||||
})
|
||||
|
||||
let code = new Promise(res => {
|
||||
childProcess.on('exit', (code, signal) => {
|
||||
// Remove reference to the process when it exits
|
||||
delete runningProcesses[processName];
|
||||
console.log(`${processName} process exited with code ${code} and signal ${signal}`);
|
||||
console.log("Child process console output: ", scriptOutput);
|
||||
res(code);
|
||||
})
|
||||
});
|
||||
|
||||
865
client/package-lock.json
generated
865
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@
|
||||
"description": "",
|
||||
"main": "client.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"test": "mocha --timeout 10000"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
@@ -24,6 +24,9 @@
|
||||
"socket.io-client": "^4.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai-http": "^4.4.0",
|
||||
"chai": "^5.1.0",
|
||||
"mocha": "^10.4.0",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Install client package updates
|
||||
npm install
|
||||
|
||||
# Install OP25 Updates
|
||||
#cd ./op25
|
||||
#bash rebuild.sh
|
||||
|
||||
# Check for PDAB updates
|
||||
cd ../discordAudioBot/pdab
|
||||
git pull
|
||||
@@ -16,23 +16,45 @@ if [[ ! -f "$(pwd)/package.json" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check to make sure the pi user exists
|
||||
if ! id "pi" &>/dev/null; then
|
||||
echo "Error: User pi does not exist."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
####------------------- Functions
|
||||
# Function to prompt user for input with a specific message and store the result in a variable
|
||||
prompt_user() {
|
||||
if [[ "$TEST_MODE" == "true" ]]; then
|
||||
echo "TESTING" # Use the pre-set value
|
||||
else
|
||||
read -p "$1: " input
|
||||
echo "$input"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to prompt user for capabilities options and store the result in a variable
|
||||
prompt_capabilities() {
|
||||
if [[ "$TEST_MODE" == "true" ]]; then
|
||||
echo "radio" # Use the pre-set value
|
||||
else
|
||||
default_capabilities="radio" # Default value
|
||||
read -p "Select CLIENT_CAPABILITIES (comma-separated, default: $default_capabilities): " capabilities
|
||||
capabilities="${capabilities:-$default_capabilities}" # Use default value if input is empty
|
||||
echo "$capabilities"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to prompt user for nearby systems details
|
||||
prompt_nearby_system() {
|
||||
if [[ "$TEST_MODE" == "true" ]]; then
|
||||
echo "\"TESTING-Node\": {
|
||||
\"frequencies\": [\"$(echo "155750000,154750000,156555550" | sed 's/,/","/g')\"],
|
||||
\"mode\": \"p25\",
|
||||
\"trunkFile\": \"testing_trunk.tsv\",
|
||||
\"whitelistFile\": \"testing_whitelist.tsv\"
|
||||
}," # Use the pre-set value
|
||||
else
|
||||
local system_name=""
|
||||
local frequencies=""
|
||||
local mode=""
|
||||
@@ -54,8 +76,16 @@ prompt_nearby_system() {
|
||||
\"trunkFile\": \"$trunk_file\",
|
||||
\"whitelistFile\": \"$whitelist_file\"
|
||||
},"
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if test mode is enabled
|
||||
if [[ "$1" == "--test" ]]; then
|
||||
TEST_MODE="true"
|
||||
else
|
||||
TEST_MODE="false"
|
||||
fi
|
||||
|
||||
# Install Node Repo
|
||||
# Get the CPU architecture
|
||||
cpu_arch=$(uname -m)
|
||||
@@ -68,6 +98,7 @@ if [[ "$cpu_arch" == "armv6"* ]]; then
|
||||
echo "----- CPU Architecture is ARMv6 or compatible. -----"
|
||||
echo "----- CPU Architectre is not compatible with dependencies of this project, please use a newer CPU architecture -----"
|
||||
exit
|
||||
fi
|
||||
|
||||
curl -fsSL https://deb.nodesource.com/setup_current.x | sudo -E bash -
|
||||
|
||||
@@ -77,7 +108,18 @@ apt upgrade -y
|
||||
|
||||
# Install the necessary packages
|
||||
echo "Installing dependencies..."
|
||||
apt install -y nodejs portaudio19-dev libportaudio2 libpulse-dev pulseaudio apulse git ffmpeg git
|
||||
apt install -y \
|
||||
nodejs \
|
||||
libasound-dev \
|
||||
portaudio19-dev \
|
||||
libportaudio2 \
|
||||
libpulse-dev \
|
||||
pulseaudio \
|
||||
apulse \
|
||||
git \
|
||||
ffmpeg \
|
||||
python3 \
|
||||
python3-pip
|
||||
|
||||
echo "Setting up Pulse Audio"
|
||||
|
||||
@@ -104,13 +146,21 @@ usermod -aG pulse-access pi
|
||||
|
||||
# Enable the PulseAudio service
|
||||
systemctl enable PulseAudio.service
|
||||
systemctl stop PulseAudio.service
|
||||
|
||||
####------------------- Install and setup node
|
||||
# Run npm install to install dependencies listed in package.json
|
||||
echo "Installing npm dependencies..."
|
||||
npm install
|
||||
|
||||
# Get rid of PEP 668
|
||||
rm -rf /usr/lib/python3.11/EMTERNALL-MANAGED # Not sure if this was an attrocious fat finger or if this is needed, doesn't throw an error, so...
|
||||
rm -rf /usr/lib/python3.11/EXTERNALLY-MANAGED
|
||||
|
||||
# Getting the Python DAB
|
||||
echo "Installing PDAB and Dependencies"
|
||||
git clone -b DRBv3 https://git.vpn.cusano.net/logan/Python-Discord-Audio-Bot.git ./discordAudioBot/pdab
|
||||
pip3 install -r ./discordAudioBot/pdab/requirements.txt
|
||||
|
||||
# Generate .env file
|
||||
echo "Creating the config .env file..."
|
||||
echo "# Client Config" > .env
|
||||
@@ -138,6 +188,11 @@ echo "OP25_FULL_PATH=$op25_full_path" >> .env
|
||||
echo "" >> .env
|
||||
echo "# Core config, DO NOT TOUCH UNLESS YOU KNOW WHAT YOU ARE DOING" >> .env
|
||||
echo "CONFIG_PATH=./config/radioPresets.json" >> .env
|
||||
runuser -l pi -c 'python3 ./discordAudioBot/pdab/getDevices.py'
|
||||
audio_device_id=$(prompt_user "Enter the ID of the 'input' audio device you would like to use (most often 'default')")
|
||||
echo "AUDIO_DEVICE_ID=$audio_device_id" >> .env
|
||||
echo "PDAB_PORT=3110" >> .env
|
||||
echo "NODE_ENV=production" >> .env
|
||||
|
||||
echo ".env file generated successfully."
|
||||
|
||||
@@ -183,14 +238,13 @@ echo "$service_content" > /etc/systemd/system/discord-radio-bot.service
|
||||
# Reload systemd daemon
|
||||
systemctl daemon-reload
|
||||
systemctl enable discord-radio-bot.service
|
||||
systemctl stop discord-radio-bot.service
|
||||
|
||||
echo "\n\n\t\tDiscord Client Node install completed!\n\n"
|
||||
|
||||
####------------------- OP25 Installation
|
||||
# Clone OP25 from the git repository
|
||||
echo "Cloning OP25 from the git repository..."
|
||||
git clone https://github.com/boatbod/op25.git
|
||||
git clone -b gr310 https://github.com/boatbod/op25.git
|
||||
|
||||
# Navigate to the OP25 directory
|
||||
ogPwd=$(pwd)
|
||||
@@ -226,7 +280,6 @@ echo "$service_content" > /etc/systemd/system/op25-multi_rx.service
|
||||
|
||||
# Reload systemd daemon
|
||||
systemctl daemon-reload
|
||||
systemctl stop op25-multi_rx.service
|
||||
|
||||
# Install OP25 using the provided installation script
|
||||
echo "Installing OP25..."
|
||||
@@ -251,7 +304,7 @@ confirm="${confirm,,}"
|
||||
#echo "To configure the app, please go to http://$nodeIP:$nodePort" # TODO - uncomment when webapp is built
|
||||
echo "Thank you for joining the network!"
|
||||
|
||||
if [[ "$confirm" != "y" && "$confirm" != "yes" ]]; then
|
||||
if [[ "$confirm" == "y" && "$confirm" == "yes" ]]; then
|
||||
# Prompt user to press any key before rebooting
|
||||
read -rsp $'System will now reboot, press any key to continue or Ctrl+C to cancel...\n' -n1 key
|
||||
echo "Rebooting..."
|
||||
|
||||
194
client/test/pdabHandler.test.js
Normal file
194
client/test/pdabHandler.test.js
Normal file
@@ -0,0 +1,194 @@
|
||||
// Import necessary modules for testing
|
||||
import { expect } from 'chai'
|
||||
import io from 'socket.io-client';
|
||||
const ioClient = io;
|
||||
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config()
|
||||
|
||||
import { initDiscordBotClient, connectToChannel, leaveVoiceChannel, checkIfConnectedToVC, requestDiscordUsername, requestDiscordID, requestDiscordClientClose, closePdabSocketServer } from '../discordAudioBot/pdabHandler.mjs';
|
||||
|
||||
let socket;
|
||||
|
||||
before(async done => {
|
||||
// Any setup needed before tests
|
||||
done();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await closePdabSocketServer();
|
||||
})
|
||||
|
||||
describe('Socket Server Tests', done => {
|
||||
after(async () => {
|
||||
// Any teardown needed after tests
|
||||
try {
|
||||
await socket.close();
|
||||
}
|
||||
catch {
|
||||
console.log("Socket already closed");
|
||||
}
|
||||
console.log('Closing PDAB Socker Server');
|
||||
});
|
||||
|
||||
it('Should open a socket server and callback when the client is connected and ready', done => {
|
||||
const clientId = process.env.TEST_CLIENT_TOKEN;
|
||||
|
||||
const callback = () => {
|
||||
done();
|
||||
};
|
||||
|
||||
initDiscordBotClient(clientId, callback, false);
|
||||
|
||||
socket = ioClient.connect(`http://localhost:${process.env.PDAB_PORT}`);
|
||||
|
||||
socket.on('connect', () => {
|
||||
socket.emit('discord_ready')
|
||||
});
|
||||
});
|
||||
|
||||
it('Should emit command for and return status from join server', async () => {
|
||||
socket.on('join_server', (data, callback) => {
|
||||
console.log('Join data from server:', data);
|
||||
expect(data).to.deep.equal({ channelId: process.env.TEST_CHANNEL_ID });
|
||||
callback(true, true);
|
||||
})
|
||||
|
||||
// Simulate emitting 'join_server' event
|
||||
const status = await connectToChannel(process.env.TEST_CHANNEL_ID);
|
||||
|
||||
// Check the server sent the expected info
|
||||
|
||||
// Assert the status returned from the server
|
||||
expect(status).to.be.true;
|
||||
});
|
||||
|
||||
it('Should emit command for and return open status for leave server', async () => {
|
||||
socket.on('leave_server', (data, callback) => {
|
||||
console.log('Leave data from server:', data);
|
||||
expect(data).to.deep.equal({ guildId: process.env.TEST_GUILD_ID });
|
||||
callback(false, false);
|
||||
});
|
||||
|
||||
// Simulate emitting 'leave_server' event
|
||||
const openStatus = await leaveVoiceChannel(process.env.TEST_GUILD_ID);
|
||||
|
||||
// Assert the open status returned from the server
|
||||
expect(openStatus).to.be.false;
|
||||
});
|
||||
|
||||
it('Should emit command for and return status if connected to voice channel', async () => {
|
||||
socket.on('check_discord_vc_connected', (data, callback) => {
|
||||
console.log('Client Check data:', data);
|
||||
expect(data).to.deep.equal({ guildId: process.env.TEST_GUILD_ID });
|
||||
callback(true, true);
|
||||
});
|
||||
|
||||
// Simulate emitting 'check_discord_vc_connected' event
|
||||
const isConnected = await checkIfConnectedToVC(process.env.TEST_GUILD_ID);
|
||||
|
||||
// Assert the connection status returned from the server
|
||||
expect(isConnected).to.be.true;
|
||||
});
|
||||
|
||||
it('Should emit command for and return username for request discord username', async () => {
|
||||
socket.on('request_discord_username', (data, callback) => {
|
||||
console.log('Username Check data:', data);
|
||||
expect(data).to.deep.equal({ guildId: process.env.TEST_GUILD_ID });
|
||||
callback(process.env.EXPECTED_USERNAME);
|
||||
});
|
||||
// Simulate emitting 'request_discord_username' event
|
||||
const username = await requestDiscordUsername(process.env.TEST_GUILD_ID);
|
||||
|
||||
// Assert the username returned from the server
|
||||
expect(username).to.equal(process.env.EXPECTED_USERNAME);
|
||||
});
|
||||
|
||||
it('Should emit command for and return discord client ID', async () => {
|
||||
socket.on('request_discord_id', (callback) => {
|
||||
callback(process.env.EXPECTED_CLIENT_ID);
|
||||
});
|
||||
// Simulate emitting 'request_discord_id' event
|
||||
const clientId = await requestDiscordID();
|
||||
|
||||
// Assert the client ID returned from the server
|
||||
expect(clientId).to.equal(process.env.EXPECTED_CLIENT_ID);
|
||||
});
|
||||
|
||||
it('Should emit command for discord client to close', done => {
|
||||
socket.on('request_client_close', async () => {
|
||||
await socket.close();
|
||||
done()
|
||||
})
|
||||
// Simulate emitting 'request_client_close' event
|
||||
requestDiscordClientClose();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Socket Client & Python IPC Tests', done => {
|
||||
it('Should open a socket server and callback when the client is connected and ready', done => {
|
||||
let clientConnected = false;
|
||||
const clientId = process.env.TEST_CLIENT_TOKEN;
|
||||
|
||||
const callback = () => {
|
||||
clientConnected = true;
|
||||
expect(clientConnected).to.be.true;
|
||||
|
||||
done();
|
||||
};
|
||||
|
||||
initDiscordBotClient(clientId, callback);
|
||||
|
||||
});
|
||||
|
||||
it('Should emit command for and return status from join server', async () => {
|
||||
// Simulate emitting 'join_server' event
|
||||
const status = await connectToChannel(process.env.TEST_CHANNEL_ID);
|
||||
|
||||
// Check the server sent the expected info
|
||||
|
||||
// Assert the status returned from the server
|
||||
expect(status).to.be.true;
|
||||
});
|
||||
|
||||
it('Should emit command for and return status if connected to voice channel', async () => {
|
||||
// Simulate emitting 'check_discord_vc_connected' event
|
||||
const isConnected = await checkIfConnectedToVC(process.env.TEST_GUILD_ID);
|
||||
|
||||
// Assert the connection status returned from the server
|
||||
expect(isConnected).to.be.true;
|
||||
});
|
||||
|
||||
it('Should emit command for and return username for request discord username', async () => {
|
||||
// Simulate emitting 'request_discord_username' event
|
||||
const username = await requestDiscordUsername(process.env.TEST_GUILD_ID);
|
||||
|
||||
// Assert the username returned from the server
|
||||
expect(username).to.equal(process.env.EXPECTED_USERNAME);
|
||||
});
|
||||
|
||||
it('Should emit command for and return discord client ID', async () => {
|
||||
// Simulate emitting 'request_discord_id' event
|
||||
const clientId = await requestDiscordID();
|
||||
|
||||
console.log("type of client id", typeof clientId, typeof process.env.EXPECTED_CLIENT_ID);
|
||||
|
||||
// Assert the client ID returned from the server
|
||||
expect(clientId).to.equal(Number(process.env.EXPECTED_CLIENT_ID));
|
||||
});
|
||||
|
||||
it('Should emit command for and return open status for leave server', async () => {
|
||||
// Simulate emitting 'leave_server' event
|
||||
const openStatus = await leaveVoiceChannel(process.env.TEST_GUILD_ID);
|
||||
|
||||
// Assert the open status returned from the server
|
||||
expect(openStatus).to.be.false;
|
||||
});
|
||||
|
||||
it('Should emit command for discord client to close', async () => {
|
||||
// Simulate emitting 'request_client_close' event
|
||||
await requestDiscordClientClose();
|
||||
});
|
||||
|
||||
});
|
||||
40
client/test/pdabWrappers.test.js.bak
Normal file
40
client/test/pdabWrappers.test.js.bak
Normal file
@@ -0,0 +1,40 @@
|
||||
import { use } from 'chai';
|
||||
import chaiHttp from 'chai-http';
|
||||
const chai = use(chaiHttp)
|
||||
chai.should();
|
||||
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config()
|
||||
|
||||
import { joinDiscordVC, leaveDiscordVC } from '../discordAudioBot/pdabWrappers.mjs'
|
||||
|
||||
before(async () => {
|
||||
// Any setup needed before tests
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
// Any teardown needed after tests
|
||||
});
|
||||
|
||||
describe('PDAB Wrapper Tests', () => {
|
||||
it('Should open the discord bot, and join the first server when requested', async () => {
|
||||
// Test case
|
||||
const joinData = {
|
||||
channelID: process.env.TEST_CHANNEL_ID,
|
||||
clientID: process.env.TEST_CLIENT_TOKEN,
|
||||
system: process.env.TEST_SYSTEM,
|
||||
};
|
||||
const connection = await joinDiscordVC(joinData);
|
||||
console.log("Connection:", connection);
|
||||
});
|
||||
|
||||
it('Should open OP25', async () => {
|
||||
const res = await chai.request('http://localhost:8081').get('/');
|
||||
expect(res).to.have.status(200); // Assuming 200 is the expected status code
|
||||
// Add more assertions if needed
|
||||
})
|
||||
|
||||
it("Should disconnect from the discord server", async () => {
|
||||
await leaveDiscordVC(process.env.TEST_GUILD_ID);
|
||||
})
|
||||
});
|
||||
21
server/Dockerfile
Normal file
21
server/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", "."]
|
||||
@@ -41,9 +41,10 @@ export async function autocomplete(nodeIo, interaction) {
|
||||
*/
|
||||
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 use the command`, ephemeral: true }) }
|
||||
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');
|
||||
@@ -56,13 +57,19 @@ export async function execute(nodeIo, interaction) {
|
||||
const joinSelectedNode = async (selectedNodeSocketId) => {
|
||||
const openSocket = await nodeIo.sockets.sockets.get(selectedNodeSocketId);
|
||||
// Get the open ID for this connection\
|
||||
const discordToken = await getAvailableTokensInGuild(nodeIo, interaction.guild.id);
|
||||
// TODO - Implement a method to have preferred tokens (bot users) for specific systems
|
||||
const ss = await getAvailableTokensInGuild(nodeIo, interaction.guild.id);
|
||||
console.log("Available discord tokens: ", discordTokens);
|
||||
|
||||
console.log("Joining selected open socket:", selectedNodeSocketId, system.name, channelToJoin.id, openSocket.node.name, discordToken);
|
||||
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, discordToken[0].token);
|
||||
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
|
||||
|
||||
@@ -74,7 +74,7 @@ export function addEnabledEventListeners(serverClient, _eventsPath = "./events")
|
||||
}
|
||||
|
||||
// The discord client
|
||||
export const serverClient = new Client({ intents: [GatewayIntentBits.Guilds] });
|
||||
export const serverClient = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates] });
|
||||
|
||||
// Run when the bot is ready
|
||||
serverClient.on('ready', async () => {
|
||||
|
||||
@@ -37,10 +37,7 @@ export const checkOnlineBotsInGuild = async (nodeIo, guildId) => {
|
||||
console.log("Online bots in the guild:", onlineBots);
|
||||
|
||||
// Filter any discordIDs that are not active
|
||||
var availableDiscordIDs = discordIDs.filter(discordID => discordID.active == true);
|
||||
|
||||
// Filter out discordIDs that are not found in onlineBots
|
||||
availableDiscordIDs = availableDiscordIDs.filter(discordID => !onlineBots.some(bot => bot.discord_id === discordID.discord_id));
|
||||
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;
|
||||
|
||||
25
server/makefile
Normal file
25
server/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 -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)
|
||||
@@ -17,13 +17,19 @@ app.get('/', (req, res) => {
|
||||
nodeIo.on('connection', (socket) => {
|
||||
console.log('a user connected', socket.id);
|
||||
|
||||
socket.on('node-login', (data) => {
|
||||
nodeLoginWrapper(data, socket);
|
||||
socket.on('node-login', async (data) => {
|
||||
await nodeLoginWrapper(data, socket);
|
||||
await socket.emit('node-login-successful');
|
||||
})
|
||||
|
||||
socket.on('node-update', (data) => {
|
||||
nodeUpdateWrapper(data.node);
|
||||
nearbySystemsUpdateWraper(data.node.nuid, data.nearbySystems)
|
||||
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', () => {
|
||||
@@ -31,8 +37,3 @@ nodeIo.on('connection', (socket) => {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// Startup the node server
|
||||
server.listen(3000, () => {
|
||||
console.log('server running at http://localhost:3000');
|
||||
});
|
||||
@@ -28,15 +28,17 @@ export const nodeLoginWrapper = async (data, socket) => {
|
||||
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);
|
||||
} else {
|
||||
// Check for updates
|
||||
const updatedNode = await updateNodeByNuid(data.nuid, data)
|
||||
console.log("Updated node:", updatedNode);
|
||||
}
|
||||
// Check for updates if so
|
||||
// Check for System updates
|
||||
|
||||
node = await getNodeByNuid(data.nuid);
|
||||
|
||||
// Add the socket/node connection
|
||||
socket.node = node;
|
||||
//socket.id = node.nuid;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
980
server/package-lock.json
generated
980
server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,15 +4,16 @@
|
||||
"description": "",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"test": "mocha --timeout 5000",
|
||||
"start": "node server.js"
|
||||
},
|
||||
"author": "Logan Cusano",
|
||||
"license": "ISC",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"mocha": "^8.4.0",
|
||||
"chai": "^4.3.4"
|
||||
"chai": "^5.1.0",
|
||||
"mocha": "^10.4.0",
|
||||
"socket.io-client": "^4.7.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"discord.js": "^14.14.1",
|
||||
|
||||
@@ -5,6 +5,11 @@ import { serverClient, addEnabledEventListeners } from './discordBot/discordBot.
|
||||
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;
|
||||
|
||||
290
server/test/socketServerWrappers.test.js
Normal file
290
server/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
|
||||
});
|
||||
})
|
||||
});
|
||||
@@ -1,76 +0,0 @@
|
||||
import { expect } from 'chai';
|
||||
import { createNode, getNodeByNuid, updateNodeByNuid } from '../modules/mongoNodesWrappers'; // Import necessary functions from your wrappers
|
||||
import { nodeLoginWrapper, nodeUpdateWrapper, nearbySystemsUpdateWraper } from '../modules/socketServerWrappers'; // Import the functions you want to test
|
||||
import { ClientNodeObject, ClientNodeConfig } from '../../client/modules/clientObjectDefinitions.mjs'; // Import the objects definitions
|
||||
|
||||
describe('Socket Server Wrappers', function() {
|
||||
describe('nodeLoginWrapper', function() {
|
||||
it('should create a new node if it does not exist', async function() {
|
||||
const socket = {
|
||||
node: undefined, // Initialize a socket with no node
|
||||
id: 'mockSocketId' // Mock socket id
|
||||
};
|
||||
|
||||
// Mock data using ClientNodeConfig object
|
||||
const config = new ClientNodeConfig({
|
||||
_nuid: 'mockNuid',
|
||||
_name: 'mockName',
|
||||
_location: 'mockLocation',
|
||||
_capabilities: { /* mock capabilities */ }
|
||||
});
|
||||
|
||||
await nodeLoginWrapper(config, socket);
|
||||
|
||||
// Check if the node was created
|
||||
const createdNode = await getNodeByNuid('mockNuid');
|
||||
expect(createdNode).to.exist;
|
||||
});
|
||||
|
||||
// Add more test cases as needed
|
||||
});
|
||||
|
||||
describe('nodeUpdateWrapper', function() {
|
||||
it('should update the node data in the database', async function() {
|
||||
// Mock node data using ClientNodeObject
|
||||
const nodeData = new ClientNodeObject({
|
||||
_nuid: 'mockNuid',
|
||||
_name: 'mockName',
|
||||
_location: 'mockLocation',
|
||||
_capabilities: { /* mock capabilities */ }
|
||||
});
|
||||
|
||||
await nodeUpdateWrapper(nodeData);
|
||||
|
||||
// Check if the node was updated in the database
|
||||
const updatedNode = await getNodeByNuid('mockNuid');
|
||||
// Assert that the node has been updated with the new data
|
||||
expect(updatedNode).to.deep.include({
|
||||
nuid: 'mockNuid',
|
||||
name: 'mockName',
|
||||
location: 'mockLocation',
|
||||
capabilities: { /* mock capabilities */ }
|
||||
});
|
||||
});
|
||||
|
||||
// Add more test cases as needed
|
||||
});
|
||||
|
||||
describe('nearbySystemsUpdateWraper', function() {
|
||||
it('should update nearby systems in the database', async function() {
|
||||
// Mock nearby systems data
|
||||
const nearbySystems = {
|
||||
// Mock nearby systems data
|
||||
};
|
||||
const nuid = 'mockNuid'; // Mock nuid
|
||||
|
||||
await nearbySystemsUpdateWraper(nuid, nearbySystems);
|
||||
|
||||
// Perform assertions to ensure nearby systems are updated correctly
|
||||
// You may need to check the database for updated nearby systems
|
||||
});
|
||||
|
||||
// Add more test cases as needed
|
||||
});
|
||||
|
||||
// Add more describe blocks for other functions and their respective test cases
|
||||
});
|
||||
Reference in New Issue
Block a user