Compare commits
49 Commits
automated-
...
9f2ed48caf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f2ed48caf | ||
|
|
3f42d60efc | ||
|
|
cf9f48dfa7 | ||
|
|
bde7dbce45 | ||
|
|
a8e96ab5dc | ||
|
|
628fd80710 | ||
|
|
edb7ec41b1 | ||
|
|
0be5b059da | ||
|
|
46989942d8 | ||
|
|
ab929489b0 | ||
|
|
cf49ac414a | ||
|
|
94374b4d45 | ||
|
|
d18ffd4c11 | ||
|
|
a56c19a466 | ||
|
|
f4886f9fc5 | ||
|
|
e324ee1738 | ||
|
|
117cbea67f | ||
|
|
5cd47378d6 | ||
|
|
7e502eee7f | ||
|
|
6fbec1f7e2 | ||
|
|
a4da1fac1c | ||
|
|
64e1598a75 | ||
|
|
f6919e23bb | ||
|
|
39bb2fd2b6 | ||
|
|
8eee33271a | ||
|
|
b9bd732e6c | ||
|
|
49ef84d01b | ||
|
|
1c47b1141a | ||
|
|
450b7d3219 | ||
|
|
aac86d5d71 | ||
|
|
a7d1f4e6b4 | ||
|
|
b51300d878 | ||
|
|
f29459aadb | ||
|
|
2cd5eee940 | ||
|
|
24296c2ae4 | ||
|
|
db065c3ef0 | ||
|
|
697025ec1e | ||
|
|
3350b9f191 | ||
|
|
961a7cc2bd | ||
|
|
2c3cc18474 | ||
|
|
424d5ae749 | ||
|
|
5c86185ef5 | ||
|
|
e6de0f4453 | ||
|
|
e8cfca1d8d | ||
|
|
dce0086fdb | ||
|
|
ad45d8f0ea | ||
|
|
2c5cf3dac0 | ||
|
|
a3223b716e | ||
|
|
7a246f9e2a |
@@ -16,23 +16,23 @@ jobs:
|
||||
RUNNER_TOOL_CACHE: /toolcache
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker BuildX
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with: # replace it with your local IP
|
||||
config-inline: |
|
||||
[registry."${{ secrets.LOCAL_GITEA_IP}}:3000"]
|
||||
http = true
|
||||
insecure = true
|
||||
[registry."git.vpn.cusano.net"]
|
||||
http = false
|
||||
insecure = false
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ secrets.LOCAL_GITEA_IP}}:3000 # replace it with your local IP
|
||||
registry: git.vpn.cusano.net # replace it with your local IP
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
@@ -52,5 +52,5 @@ jobs:
|
||||
linux/arm64
|
||||
push: true
|
||||
tags: | # replace it with your local IP and tags
|
||||
${{ secrets.LOCAL_GITEA_IP}}:3000/${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}
|
||||
${{ secrets.LOCAL_GITEA_IP}}:3000/${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}
|
||||
git.vpn.cusano.net/${{ vars.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}
|
||||
git.vpn.cusano.net/${{ vars.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}
|
||||
46
.gitea/workflows/DRBv3_docs.yaml
Normal file
46
.gitea/workflows/DRBv3_docs.yaml
Normal file
@@ -0,0 +1,46 @@
|
||||
name: Update Wiki from JSDoc
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
# TODO - REMOVE AFTER TESTING
|
||||
pull_request:
|
||||
branches:
|
||||
- "*"
|
||||
# schedule:
|
||||
# - cron: '0 0 * * 1' # Every Monday at midnight (UTC)
|
||||
|
||||
jobs:
|
||||
update-wiki:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Generate JSDoc
|
||||
run: npx jsdoc -c jsdoc.json -d docs
|
||||
|
||||
- name: Checkout the wiki repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: logan/drb-server.wiki # Replace with your wiki repository
|
||||
path: wiki
|
||||
|
||||
- name: Update wiki
|
||||
run: |
|
||||
cp -r docs/* wiki/
|
||||
cd wiki
|
||||
git config user.name "gitea-actions"
|
||||
git config user.email "gitea-actions@cusano.net"
|
||||
git add .
|
||||
git commit -m "Update wiki from JSDoc"
|
||||
git push
|
||||
28
.gitea/workflows/DRBv3_linting.yaml
Normal file
28
.gitea/workflows/DRBv3_linting.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
name: Lint JavaScript/Node.js
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- "*"
|
||||
|
||||
jobs:
|
||||
lint-js:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22' # Use your preferred Node.js version
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Lint JavaScript/Node.js
|
||||
run: npm run lint
|
||||
@@ -3,7 +3,7 @@ name: DRB Tests
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- "*"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -302,3 +302,6 @@ op25/
|
||||
|
||||
# Ignore any local run scripts for development
|
||||
*.bat
|
||||
|
||||
# Ignore the auto-generated docs folder
|
||||
/docs
|
||||
@@ -15,7 +15,7 @@ RUN npm install
|
||||
COPY . .
|
||||
|
||||
# Expose the port on which your Node.js application will run
|
||||
EXPOSE 3000
|
||||
EXPOSE 3420
|
||||
|
||||
# Command to run the Node.js application
|
||||
CMD ["node", "."]
|
||||
|
||||
@@ -14,6 +14,9 @@ export function initialize(nodeIo, config) {
|
||||
// Function to register Socket.IO event handlers
|
||||
function registerSocketEvents(nodeIo, config) {
|
||||
nodeIo.on(config.options.eventName, (data) => {
|
||||
log.DEBUG(`Received event "${config.options.eventName}" from client:`, data);
|
||||
log.DEBUG(
|
||||
`Received event "${config.options.eventName}" from client:`,
|
||||
data,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
57
discordBot/addons/gptInteraction.mjs
Normal file
57
discordBot/addons/gptInteraction.mjs
Normal file
@@ -0,0 +1,57 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "discordBot.addons.gptInteraction");
|
||||
import { gptHandler } from "../modules/gptHandler.mjs";
|
||||
|
||||
export const gptInteraction = async (nodeIo, message) => {
|
||||
let conversation = [];
|
||||
|
||||
let prevMessages = await message.channel.messages.fetch({ limit: 10 });
|
||||
prevMessages.reverse();
|
||||
|
||||
prevMessages.forEach((msg) => {
|
||||
// Check if the message was sent within the last 24 hours
|
||||
if (new Date().getTime() - msg.createdTimestamp > 24 * 60 * 60 * 1000) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if it's from a bot other than the server
|
||||
if (msg.author.bot && msg.author.id !== nodeIo.serverClient.user.id) return;
|
||||
|
||||
const username = msg.author.username
|
||||
.replace(/\s+/g, "_")
|
||||
.replace(/[^\w\s]/gi, "");
|
||||
|
||||
if (msg.author.id === nodeIo.serverClient.user.id) {
|
||||
conversation.push({
|
||||
role: "assistant",
|
||||
//name: msg.author.id,
|
||||
content: msg.content,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
conversation.push({
|
||||
role: "user",
|
||||
//name: msg.author.id,
|
||||
content: msg.content.replace(`<@${nodeIo.serverClient.user.id}>`, ""),
|
||||
});
|
||||
});
|
||||
const response = await gptHandler(conversation);
|
||||
if (response) {
|
||||
const responseMessage = response;
|
||||
const chunkSize = 2500;
|
||||
|
||||
for (let i = 0; i < responseMessage.length; i += chunkSize) {
|
||||
const chunk = responseMessage.substring(i, i + chunkSize);
|
||||
|
||||
log.DEBUG("Sending message chunk:", chunk);
|
||||
|
||||
await message.reply(chunk);
|
||||
}
|
||||
} else {
|
||||
message.channel.send(
|
||||
"Sorry, I encountered an error while processing your request.",
|
||||
);
|
||||
}
|
||||
};
|
||||
105
discordBot/addons/linkCop.mjs
Normal file
105
discordBot/addons/linkCop.mjs
Normal file
@@ -0,0 +1,105 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "discordBot.addons.linkCop");
|
||||
import { gptHandler } from "../modules/gptHandler.mjs";
|
||||
import dotenv from "dotenv";
|
||||
import { getGuildConfig } from "../../modules/mongo-wrappers/mongoConfigWrappers.mjs";
|
||||
dotenv.config();
|
||||
|
||||
const linkRegExp = /http[s]?:\/\/\S+/g;
|
||||
|
||||
export const linkCop = async (nodeIo, message) => {
|
||||
// Set the channel IDs based on the guild the message was sent in
|
||||
const approvedLinksChannel =
|
||||
(await getGuildConfig(message.guild.id, "approvedLinksChannel")) ||
|
||||
"767303243285790721";
|
||||
const restrictedChannelIds = await getGuildConfig(
|
||||
message.guild.id,
|
||||
"restrictedChannelIds",
|
||||
);
|
||||
|
||||
// Check if the message was sent in an restricted channel
|
||||
if (
|
||||
message.channel.id == approvedLinksChannel ||
|
||||
!Array.isArray(restrictedChannelIds) ||
|
||||
Array.isArray(restrictedChannelIds) ||
|
||||
!restrictedChannelIds.includes(message.channel.id)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if there are URLs in the sent message
|
||||
const urls = [...message.content.matchAll(linkRegExp)];
|
||||
log.DEBUG("Parsed URLs from message:", urls);
|
||||
|
||||
if (!urls || urls.length === 0) return false;
|
||||
log.INFO("Found URLs: ", urls);
|
||||
|
||||
let conversation = [];
|
||||
|
||||
let prevMessages = await message.channel.messages.fetch({ limit: 2 });
|
||||
prevMessages.reverse();
|
||||
|
||||
prevMessages.forEach((msg) => {
|
||||
// Check if the message was sent within the last 5 minutes
|
||||
if (new Date().getTime() - msg.createdTimestamp > 5 * 60 * 1000) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if it's from a bot other than the server
|
||||
if (msg.author.bot && msg.author.id !== nodeIo.serverClient.user.id) return;
|
||||
|
||||
const username = msg.author.username
|
||||
.replace(/\s+/g, "_")
|
||||
.replace(/[^\w\s]/gi, "");
|
||||
|
||||
if (msg.author.id === nodeIo.serverClient.user.id) {
|
||||
conversation.push({
|
||||
role: "assistant",
|
||||
//name: msg.author.id,
|
||||
content: msg.content,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
conversation.push({
|
||||
role: "user",
|
||||
//name: msg.author.id,
|
||||
content: msg.content.replace(`<@${nodeIo.serverClient.user.id}>`, ""),
|
||||
});
|
||||
});
|
||||
|
||||
conversation.push({
|
||||
role: "assistant",
|
||||
content: `There has been a link posted to a channel that links are not allowed in. The above messages are from the channel that links are not allowed including the message with the link. The message with the link is going to be deleted and moved to the '#links' channels. You are replying to the message with the link to let the user know.`,
|
||||
});
|
||||
|
||||
const response = await gptHandler(conversation);
|
||||
|
||||
if (response) {
|
||||
const responseMessage = response;
|
||||
const chunkSize = 2000;
|
||||
|
||||
for (let i = 0; i < responseMessage.length; i += chunkSize) {
|
||||
const chunk = responseMessage.substring(i, i + chunkSize);
|
||||
|
||||
log.DEBUG("Sending message chunk:", chunk);
|
||||
|
||||
await message.reply(chunk);
|
||||
}
|
||||
|
||||
const messageContent = {
|
||||
author: message.author,
|
||||
content: `<@${message.author.id}> - ${String(message.content)}`,
|
||||
channelId: message.channelId,
|
||||
links: urls,
|
||||
};
|
||||
|
||||
await message.delete();
|
||||
log.DEBUG("Message content: ", messageContent);
|
||||
|
||||
message.client.channels.cache
|
||||
.get(approvedLinksChannel)
|
||||
.send(messageContent);
|
||||
}
|
||||
};
|
||||
52
discordBot/commands/connections.mjs
Normal file
52
discordBot/commands/connections.mjs
Normal file
@@ -0,0 +1,52 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "discordBot.command.ping");
|
||||
import { SlashCommandBuilder } from "discord.js";
|
||||
|
||||
// Exporting data property that contains the command structure for discord including any params
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName("connections")
|
||||
.setDescription("Check to see what bots are online.");
|
||||
|
||||
// Exporting other properties
|
||||
export const example = "/connections"; // 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 = []; // The array to be filled with the autocorrect values
|
||||
const filtered = choices.filter(choice => choice.name.startsWith(focusedValue));
|
||||
log.INFO(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 const execute = async (nodeIo, interaction) => {
|
||||
try {
|
||||
const sockets = await nodeIo.allSockets();
|
||||
log.DEBUG("All open sockets: ", sockets);
|
||||
let socketMessage = "";
|
||||
|
||||
// Create the message for discord with each socket on a new line
|
||||
sockets.forEach((socket) => {
|
||||
socketMessage += `\n${socket}`;
|
||||
});
|
||||
|
||||
await interaction.reply(`**Online Sockets: '${socketMessage}'**`);
|
||||
//await interaction.reply('**Pong.**');
|
||||
//await interaction.channel.send('**Pong.**');
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
// await interaction.reply(err.toString());
|
||||
}
|
||||
};
|
||||
@@ -1,19 +1,29 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
import { SlashCommandBuilder } from "discord.js";
|
||||
import {
|
||||
joinNode,
|
||||
getAvailableNodes,
|
||||
promptNodeSelection,
|
||||
getUserVoiceChannel,
|
||||
} from "../modules/wrappers.mjs";
|
||||
import {
|
||||
getAllSystems,
|
||||
getSystemByName,
|
||||
} from "../../modules/mongo-wrappers/mongoSystemsWrappers.mjs";
|
||||
|
||||
const log = new DebugBuilder("server", "discordBot.command.join");
|
||||
import { SlashCommandBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js';
|
||||
import { requestNodeJoinSystem, checkIfNodeIsConnectedToVC, checkIfNodeHasOpenDiscordClient, getNodeCurrentListeningSystem } from '../../modules/socketServerWrappers.mjs';
|
||||
import { getSystemsByNuid, getAllSystems, getSystemByName } from '../../modules/mongo-wrappers/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')
|
||||
.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));
|
||||
.setAutocomplete(true),
|
||||
);
|
||||
|
||||
// Exporting other properties
|
||||
export const example = "/join";
|
||||
@@ -27,130 +37,93 @@ export const deferInitialReply = true;
|
||||
export async function autocomplete(nodeIo, interaction) {
|
||||
const focusedValue = interaction.options.getFocused();
|
||||
const choices = await getAllSystems();
|
||||
const filtered = choices.filter(choice => choice.name.startsWith(focusedValue));
|
||||
const filtered = choices.filter((choice) =>
|
||||
choice.name.startsWith(focusedValue),
|
||||
);
|
||||
|
||||
log.DEBUG(focusedValue, choices, filtered);
|
||||
|
||||
try {
|
||||
await interaction.respond(
|
||||
filtered.map(choice => ({ name: choice.name, value: choice.name })),
|
||||
filtered.map((choice) => ({ name: choice.name, value: choice.name })),
|
||||
);
|
||||
} catch (e) {
|
||||
log.WARN("Autocomplete interaction failure", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The function to run when the command is called by a discord user
|
||||
* Handle join command execution
|
||||
* @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;
|
||||
log.INFO(`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);
|
||||
// Validate user is in a voice channel
|
||||
const channelToJoin = getUserVoiceChannel(interaction);
|
||||
if (!channelToJoin) return;
|
||||
|
||||
// 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 discordTokens = await getAvailableTokensInGuild(nodeIo, interaction.guild.id);
|
||||
log.DEBUG("Available discord tokens: ", discordTokens);
|
||||
// Get the selected system
|
||||
const selectedSystemName = interaction.options.getString("system");
|
||||
const system = await getSystemByName(selectedSystemName);
|
||||
|
||||
if (discordTokens.length >= 1) {
|
||||
// TODO - Implement a method to have preferred tokens (bot users) for specific systems
|
||||
log.INFO("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
|
||||
log.DEBUG("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) {
|
||||
log.INFO("Node is listening to a different system than requested", openSocket.node.name);
|
||||
// Check if there was a system found by the given system name
|
||||
if (!system) {
|
||||
await interaction.editReply({
|
||||
content: `System '${selectedSystemName}' not found.`,
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the available nodes for this system
|
||||
const availableNodes = await getAvailableNodes(
|
||||
nodeIo,
|
||||
interaction.guild.id,
|
||||
system,
|
||||
);
|
||||
|
||||
// Check if there are available nodes
|
||||
if (availableNodes.length === 0) {
|
||||
// If not, let the user know
|
||||
await interaction.editReply(
|
||||
`<@${interaction.member.id}>, the selected system has no available nodes`,
|
||||
);
|
||||
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);
|
||||
log.INFO("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);
|
||||
}
|
||||
// If there is one available node, request that node join
|
||||
if (availableNodes.length === 1) {
|
||||
await joinNode(
|
||||
nodeIo,
|
||||
interaction,
|
||||
availableNodes[0].id,
|
||||
system,
|
||||
channelToJoin,
|
||||
);
|
||||
}
|
||||
|
||||
}));
|
||||
|
||||
log.DEBUG("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: [] });
|
||||
}
|
||||
// If there are more than one available, prompt the user for their selected node
|
||||
else {
|
||||
await promptNodeSelection(
|
||||
interaction,
|
||||
availableNodes,
|
||||
async (selectedNode) => {
|
||||
await joinNode(
|
||||
nodeIo,
|
||||
interaction,
|
||||
selectedNode,
|
||||
system,
|
||||
channelToJoin,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
// await interaction.reply(err.toString());
|
||||
log.ERROR(err);
|
||||
await interaction.editReply({
|
||||
content: `An error occurred: ${err.message}`,
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,24 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
import { SlashCommandBuilder } from "discord.js";
|
||||
import {
|
||||
requestBotLeaveServer,
|
||||
getSocketIdByNuid,
|
||||
} from "../../modules/socketServerWrappers.mjs";
|
||||
import { checkOnlineBotsInGuild } from "../modules/wrappers.mjs";
|
||||
|
||||
const log = new DebugBuilder("server", "discordBot.command.leave");
|
||||
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')
|
||||
.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));;
|
||||
.setAutocomplete(true),
|
||||
);
|
||||
|
||||
// Exporting other properties
|
||||
export const example = "/leave *{Bot Name}*";
|
||||
@@ -25,15 +31,21 @@ export const deferInitialReply = true;
|
||||
*/
|
||||
export async function autocomplete(nodeIo, interaction) {
|
||||
const focusedValue = interaction.options.getFocused();
|
||||
const choices = (await checkOnlineBotsInGuild(nodeIo, interaction.guild.id));
|
||||
const choices = await checkOnlineBotsInGuild(nodeIo, interaction.guild.id);
|
||||
|
||||
log.DEBUG(choices);
|
||||
|
||||
const filtered = choices.filter(choice => choice.name.startsWith(focusedValue)).map(choice => choice = {name: choice.name, value: choice.nuid});
|
||||
const filtered = choices
|
||||
.filter((choice) => choice.name.startsWith(focusedValue))
|
||||
.map((choice) => ({ name: choice.name, value: choice.nuid }));
|
||||
|
||||
log.DEBUG(focusedValue, choices, filtered);
|
||||
|
||||
try {
|
||||
await interaction.respond(filtered);
|
||||
} catch (e) {
|
||||
log.WARN("Autocomplete interaction failure", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,16 +55,27 @@ export async function autocomplete(nodeIo, interaction) {
|
||||
*/
|
||||
export async function execute(nodeIo, interaction) {
|
||||
try {
|
||||
// Get the requested bot
|
||||
const selectedNode = interaction.options.getString('bot');
|
||||
const selectedNode = interaction.options.getString("bot");
|
||||
const socket = await getSocketIdByNuid(nodeIo, selectedNode);
|
||||
log.DEBUG("All open sockets:", socket, selectedNode);
|
||||
|
||||
if (!socket) {
|
||||
await interaction.editReply({
|
||||
content: `Bot '${selectedNode}' not found or not connected.`,
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
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.**');
|
||||
|
||||
await interaction.editReply(
|
||||
`Ok <@${interaction.member.id}>, the bot is leaving shortly.`,
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
// await interaction.reply(err.toString());
|
||||
log.ERROR("Failed to disconnect bot:", err);
|
||||
await interaction.editReply({
|
||||
content: `An error occurred: ${err.message}`,
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "discordBot.command.ping");
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
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!');
|
||||
.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
|
||||
@@ -34,12 +34,12 @@ export async function autocomplete(nodeIo, interaction) {
|
||||
export const execute = async (nodeIo, interaction) => {
|
||||
try {
|
||||
const sockets = await nodeIo.allSockets();
|
||||
log.DEBUG("All open sockets: ",sockets);
|
||||
log.DEBUG("All open sockets: ", sockets);
|
||||
//await interaction.reply(`**Online Sockets: '${sockets}'**`);
|
||||
await interaction.reply('**Pong.**');
|
||||
await interaction.reply("**Pong.**");
|
||||
//await interaction.channel.send('**Pong.**');
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
// await interaction.reply(err.toString());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,28 +1,34 @@
|
||||
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import { SlashCommandBuilder } from "discord.js";
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
import { addSource } from '../../rss-manager/sourceManager.mjs'
|
||||
import { addSource } from "../../rss-manager/sourceManager.mjs";
|
||||
const log = new DebugBuilder("server", "discordBot.command.rssAdd");
|
||||
|
||||
// Exporting data property that contains the command structure for discord including any params
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName('rss-add')
|
||||
.setDescription('Add RSS Source')
|
||||
.addStringOption(option =>
|
||||
option.setName('title')
|
||||
.setDescription('The title of the RSS feed')
|
||||
.setRequired(true))
|
||||
.addStringOption(option =>
|
||||
option.setName('link')
|
||||
.setDescription('The link to the RSS feed')
|
||||
.setRequired(true))
|
||||
.addStringOption(option =>
|
||||
option.setName('category')
|
||||
.setName("rss-add")
|
||||
.setDescription("Add RSS Source")
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("title")
|
||||
.setDescription("The title of the RSS feed")
|
||||
.setRequired(true),
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("link")
|
||||
.setDescription("The link to the RSS feed")
|
||||
.setRequired(true),
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("category")
|
||||
.setDescription('The category for the RSS feed *("ALL" by default")*')
|
||||
.setRequired(false))
|
||||
.setRequired(false),
|
||||
);
|
||||
|
||||
// Exporting other properties
|
||||
export const example = "/rss-add [title] [https://domain.com/feed.xml] [category]"; // An example of how the command would be run in discord chat, this will be used for the help command
|
||||
export const example =
|
||||
"/rss-add [title] [https://domain.com/feed.xml] [category]"; // 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.
|
||||
|
||||
/**
|
||||
@@ -49,23 +55,37 @@ export async function autocomplete(nodeIo, interaction) {
|
||||
*/
|
||||
export const execute = async (nodeIo, interaction) => {
|
||||
try {
|
||||
var title = interaction.options.getString('title');
|
||||
var link = interaction.options.getString('link');
|
||||
var category = interaction.options.getString('category');
|
||||
var title = interaction.options.getString("title");
|
||||
var link = interaction.options.getString("link");
|
||||
var category = interaction.options.getString("category");
|
||||
|
||||
if (!category) category = "ALL";
|
||||
await interaction.reply(
|
||||
`Adding ${title} to the list of RSS sources, please wait...`,
|
||||
);
|
||||
|
||||
await addSource(title, link, category, interaction.guildId, interaction.channelId, (err, result) => {
|
||||
await addSource(
|
||||
title,
|
||||
link,
|
||||
category,
|
||||
interaction.guildId,
|
||||
interaction.channelId,
|
||||
(err, result) => {
|
||||
log.DEBUG("Result from adding entry", result);
|
||||
|
||||
if (result) {
|
||||
interaction.reply(`Successfully added ${title} to the list of RSS sources`);
|
||||
interaction.editReply(
|
||||
`Successfully added ${title} to the list of RSS sources`,
|
||||
);
|
||||
} else {
|
||||
interaction.reply(`${title} already exists in the list of RSS sources`);
|
||||
interaction.editReply(
|
||||
`${title} already exists in the list of RSS sources`,
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
log.ERROR(err)
|
||||
await interaction.reply(err.toString());
|
||||
log.ERROR(err);
|
||||
await interaction.editReply(err.toString());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import { SlashCommandBuilder } from "discord.js";
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
import { removeSource } from '../../rss-manager/sourceManager.mjs'
|
||||
import { getAllFeeds, deleteFeedByTitle } from '../../modules/mongo-wrappers/mongoFeedsWrappers.mjs'
|
||||
import {
|
||||
getAllFeeds,
|
||||
deleteFeedByTitle,
|
||||
} from "../../modules/mongo-wrappers/mongoFeedsWrappers.mjs";
|
||||
const log = new DebugBuilder("server", "discordBot.command.rssRemove");
|
||||
|
||||
// Exporting data property that contains the command structure for discord including any params
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName('rss-remove')
|
||||
.setDescription('Add RSS Source')
|
||||
.addStringOption(option =>
|
||||
option.setName('title')
|
||||
.setDescription('The title of the RSS feed')
|
||||
.setName("rss-remove")
|
||||
.setDescription("Add RSS Source")
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("title")
|
||||
.setDescription("The title of the RSS feed")
|
||||
.setRequired(true)
|
||||
.setAutocomplete(true))
|
||||
.setAutocomplete(true),
|
||||
);
|
||||
|
||||
// Exporting other properties
|
||||
export const example = "/rss-remove [title]"; // An example of how the command would be run in discord chat, this will be used for the help command
|
||||
@@ -27,11 +30,15 @@ export const deferInitialReply = false; // If we the initial reply in discord sh
|
||||
|
||||
export async function autocomplete(nodeIo, interaction) {
|
||||
const focusedValue = interaction.options.getFocused();
|
||||
const choices = await getAllFeeds() ?? [];
|
||||
const choices = (await getAllFeeds()) ?? [];
|
||||
log.INFO("RSS Remove Choices:", choices);
|
||||
const filtered = choices.filter(choice => choice.title.startsWith(focusedValue));
|
||||
const filtered = choices.filter((choice) =>
|
||||
choice.title.startsWith(focusedValue),
|
||||
);
|
||||
log.DEBUG(focusedValue, choices, filtered);
|
||||
await interaction.respond(filtered.map(choice => ({ name: choice.title, value: choice.title })));
|
||||
await interaction.respond(
|
||||
filtered.map((choice) => ({ name: choice.title, value: choice.title })),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,18 +48,22 @@ export async function autocomplete(nodeIo, interaction) {
|
||||
*/
|
||||
export const execute = async (nodeIo, interaction) => {
|
||||
try {
|
||||
var title = interaction.options.getString('title');
|
||||
interaction.reply(`Removing ${title} from the list of RSS sources, please wait...`);
|
||||
var title = interaction.options.getString("title");
|
||||
await interaction.reply(
|
||||
`Removing ${title} from the list of RSS sources, please wait...`,
|
||||
);
|
||||
|
||||
const results = await deleteFeedByTitle(title);
|
||||
if (!results) {
|
||||
log.WARN(`Failed to remove source: ${title}`);
|
||||
interaction.editReply(`Failed to remove source: '${title}'`);
|
||||
await interaction.editReply(`Failed to remove source: '${title}'`);
|
||||
return;
|
||||
}
|
||||
interaction.editReply(`${title} was successfully removed from the RSS sources.`)
|
||||
await interaction.editReply(
|
||||
`${title} was successfully removed from the RSS sources.`,
|
||||
);
|
||||
} catch (err) {
|
||||
log.ERROR(err)
|
||||
interaction.editReply(err.toString());
|
||||
log.ERROR(err);
|
||||
await interaction.editReply(err.toString());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "discordBot.command.rssTrigger");
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import { updateFeeds } from '../../rss-manager/feedHandler.mjs'
|
||||
import { SlashCommandBuilder } from "discord.js";
|
||||
import { updateFeeds } from "../../rss-manager/feedHandler.mjs";
|
||||
|
||||
// Exporting data property that contains the command structure for discord including any params
|
||||
export const data = new SlashCommandBuilder()
|
||||
.setName('rss-trigger')
|
||||
.setDescription('Manually triggers an RSS feed update');
|
||||
.setName("rss-trigger")
|
||||
.setDescription("Manually triggers an RSS feed update");
|
||||
|
||||
// Exporting other properties
|
||||
export const example = "/rss-trigger"; // An example of how the command would be run in discord chat, this will be used for the help command
|
||||
@@ -36,11 +36,12 @@ export const execute = async (nodeIo, interaction) => {
|
||||
try {
|
||||
//const sockets = await nodeIo.allSockets();
|
||||
//await interaction.reply(`**Online Sockets: '${sockets}'**`);
|
||||
await interaction.reply('Triggering RSS update');
|
||||
await interaction.reply("Triggering RSS update");
|
||||
await updateFeeds(interaction.client);
|
||||
await interaction.editReply("RSS Update Completed");
|
||||
//await interaction.channel.send('**Pong.**');
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
// await interaction.reply(err.toString());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "discordBot.command.update");
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import { requestNodeUpdate } from '../../modules/socketServerWrappers.mjs';
|
||||
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');
|
||||
.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
|
||||
@@ -19,19 +19,21 @@ export const deferInitialReply = false; // If we the initial reply in discord sh
|
||||
*/
|
||||
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
|
||||
const openSockets = [...(await nodeIo.allSockets())]; // TODO - Filter the returned nodes to only nodes that have the radio capability
|
||||
log.DEBUG("All open sockets: ", openSockets);
|
||||
|
||||
// Check each open socket to see if the node has the requested system
|
||||
await Promise.all(openSockets.map(openSocket => {
|
||||
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.reply("All nodes have been requested to update");
|
||||
//await interaction.channel.send('**Pong.**');
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
// await interaction.reply(err.toString());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { DebugBuilder } from "../modules/debugger.mjs";
|
||||
import { Client, GatewayIntentBits, Collection } from 'discord.js';
|
||||
import { registerActiveCommands, unregisterAllCommands } from './modules/registerCommands.mjs'
|
||||
import { RSSController } from '../rss-manager/rssController.mjs'
|
||||
import { join, dirname } from 'path';
|
||||
import { readdirSync } from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { Client, GatewayIntentBits, Collection } from "discord.js";
|
||||
import { registerActiveCommands } from "./modules/registerCommands.mjs";
|
||||
import { RSSController } from "../rss-manager/rssController.mjs";
|
||||
import { join, dirname } from "path";
|
||||
import { readdirSync } from "fs";
|
||||
import { fileURLToPath } from "url";
|
||||
import PresenceManager from "./modules/presenceManager.mjs";
|
||||
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config()
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
const log = new DebugBuilder("server", "discordBot");
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
|
||||
/**
|
||||
* 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')
|
||||
@@ -22,16 +22,21 @@ const __dirname = dirname(__filename);
|
||||
* @param {any} _commandsPath="./commands"
|
||||
* @returns {any}
|
||||
*/
|
||||
export const addEnabledCommands = async (serverClient, _commandsPath = "./commands") => {
|
||||
export const addEnabledCommands = async (
|
||||
serverClient,
|
||||
_commandsPath = "./commands",
|
||||
) => {
|
||||
// Setup commands for the Discord bot
|
||||
serverClient.commands = new Collection();
|
||||
const commandsPath = join(__dirname, _commandsPath);
|
||||
const commandFiles = readdirSync(commandsPath).filter(file => file.endsWith('.mjs'));
|
||||
const commandFiles = readdirSync(commandsPath).filter((file) =>
|
||||
file.endsWith(".mjs"),
|
||||
);
|
||||
|
||||
for (const file of commandFiles) {
|
||||
const filePath = await join(commandsPath, file);
|
||||
log.INFO(`Adding enabled command: ${filePath}`);
|
||||
await import(`file://${filePath}`).then(command => {
|
||||
await import(`file://${filePath}`).then((command) => {
|
||||
if (command.data instanceof Promise) {
|
||||
command.data.then(async (builder) => {
|
||||
command.data = builder;
|
||||
@@ -46,12 +51,12 @@ export const addEnabledCommands = async (serverClient, _commandsPath = "./comman
|
||||
// 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
|
||||
@@ -60,31 +65,53 @@ export const addEnabledCommands = async (serverClient, _commandsPath = "./comman
|
||||
* @param {any} _eventsPath="./events"
|
||||
* @returns {any}
|
||||
*/
|
||||
export function addEnabledEventListeners(serverClient, _eventsPath = "./events") {
|
||||
export function addEnabledEventListeners(
|
||||
serverClient,
|
||||
_eventsPath = "./events",
|
||||
) {
|
||||
const eventsPath = join(__dirname, _eventsPath);
|
||||
const eventFiles = readdirSync(eventsPath).filter(file => file.endsWith('.mjs'));
|
||||
const eventFiles = readdirSync(eventsPath).filter((file) =>
|
||||
file.endsWith(".mjs"),
|
||||
);
|
||||
|
||||
for (const file of eventFiles) {
|
||||
const filePath = join(eventsPath, file);
|
||||
log.INFO(`Adding enabled event listener: ${filePath}`);
|
||||
import(`file://${filePath}`).then(event => {
|
||||
import(`file://${filePath}`).then((event) => {
|
||||
log.DEBUG("Adding event: ", event);
|
||||
if (event.once) {
|
||||
serverClient.once(event.name, (...args) => event.execute(serverClient.nodeIo, ...args));
|
||||
serverClient.once(event.name, (...args) =>
|
||||
event.execute(serverClient.nodeIo, ...args),
|
||||
);
|
||||
} else {
|
||||
serverClient.on(event.name, (...args) => event.execute(serverClient.nodeIo, ...args));
|
||||
serverClient.on(event.name, (...args) =>
|
||||
event.execute(serverClient.nodeIo, ...args),
|
||||
);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// The discord client
|
||||
export const serverClient = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates] });
|
||||
export const serverClient = new Client({
|
||||
intents: [
|
||||
GatewayIntentBits.Guilds,
|
||||
GatewayIntentBits.GuildVoiceStates,
|
||||
GatewayIntentBits.GuildMessages,
|
||||
GatewayIntentBits.MessageContent,
|
||||
GatewayIntentBits.GuildMembers,
|
||||
GatewayIntentBits.GuildPresences,
|
||||
],
|
||||
});
|
||||
|
||||
// Run when the bot is ready
|
||||
serverClient.on('ready', async () => {
|
||||
serverClient.on("ready", async () => {
|
||||
log.INFO(`Logged in as ${serverClient.user.tag}!`);
|
||||
|
||||
// Set the presence to default
|
||||
const pm = new PresenceManager(serverClient);
|
||||
await pm.resetToDefault();
|
||||
|
||||
// Add and register commands
|
||||
await addEnabledCommands(serverClient);
|
||||
|
||||
|
||||
19
discordBot/events/guildCreate.mjs
Normal file
19
discordBot/events/guildCreate.mjs
Normal file
@@ -0,0 +1,19 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "discordBot.events.guildCreate");
|
||||
import { Events } from "discord.js";
|
||||
import {
|
||||
addEnabledCommands,
|
||||
addEnabledEventListeners,
|
||||
} from "../discordBot.mjs";
|
||||
|
||||
export const name = Events.GuildMemberAdd;
|
||||
|
||||
export async function execute(nodeIo, guild) {
|
||||
log.INFO("Bot has joined a new server", guild);
|
||||
|
||||
log.DEBUG("Refreshing commands enabled");
|
||||
await addEnabledCommands(nodeIo.serverClient);
|
||||
|
||||
log.DEBUG("Refreshing events enabled");
|
||||
await addEnabledEventListeners(nodeIo.serverClient);
|
||||
}
|
||||
36
discordBot/events/guildMemberAdd.mjs
Normal file
36
discordBot/events/guildMemberAdd.mjs
Normal file
@@ -0,0 +1,36 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "discordBot.events.guildMemberAdd");
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
import { Events } from "discord.js";
|
||||
import { gptHandler } from "../modules/gptHandler.mjs";
|
||||
import { getGuildConfig } from "../../modules/mongo-wrappers/mongoConfigWrappers.mjs";
|
||||
|
||||
export const name = Events.GuildMemberAdd;
|
||||
|
||||
export async function execute(nodeIo, member) {
|
||||
const welcomeChannel = await getGuildConfig(
|
||||
message.guild.id,
|
||||
"welcomeChannelId",
|
||||
);
|
||||
log.INFO("New user joined the server", member);
|
||||
let conversation = [];
|
||||
conversation.push({
|
||||
role: "assistant",
|
||||
content: `A new user has joined the server. Their name is '<@${member.id}>'. Please welcome them to the server and remind them about the rules.`,
|
||||
});
|
||||
|
||||
const response = await gptHandler(conversation);
|
||||
if (response) {
|
||||
const responseMessage = response.choices[0].message.content;
|
||||
const chunkSize = 2500;
|
||||
|
||||
for (let i = 0; i < responseMessage.length; i += chunkSize) {
|
||||
const chunk = responseMessage.substring(i, i + chunkSize);
|
||||
|
||||
log.DEBUG("Sending message chunk:", chunk);
|
||||
|
||||
await nodeIo.serverClient.channels.cache.get(welcomeChannel).send(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "discordBot.events.interactionCreate");
|
||||
import { Events } from 'discord.js';
|
||||
import { Events } from "discord.js";
|
||||
import PresenceManager from "../modules/presenceManager.mjs";
|
||||
|
||||
export const name = Events.InteractionCreate;
|
||||
|
||||
@@ -8,6 +9,10 @@ export async function execute(nodeIo, interaction) {
|
||||
const command = interaction.client.commands.get(interaction.commandName);
|
||||
log.INFO("Interaction created for command: ", command);
|
||||
|
||||
// Set the presence for handling interaction
|
||||
const interactionPm = new PresenceManager(interaction.client);
|
||||
await interactionPm.setPresence("online", "PLAYING", "handling interaction");
|
||||
|
||||
// Execute autocomplete if the user is checking autocomplete
|
||||
if (interaction.isAutocomplete()) {
|
||||
log.INFO("Running autocomplete for command: ", command.data.name);
|
||||
@@ -22,7 +27,9 @@ export async function execute(nodeIo, interaction) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.INFO(`${interaction.member.user} is running '${interaction.commandName}'`);
|
||||
log.INFO(
|
||||
`${interaction.member.user} is running '${interaction.commandName}'`,
|
||||
);
|
||||
|
||||
// Defer the initial reply if the command has the parameter set
|
||||
if (command.deferInitialReply) {
|
||||
@@ -31,4 +38,7 @@ export async function execute(nodeIo, interaction) {
|
||||
|
||||
// Execute the command
|
||||
command.execute(nodeIo, interaction);
|
||||
|
||||
// Reset the presence
|
||||
await interactionPm.resetToDefault();
|
||||
}
|
||||
55
discordBot/events/messageCreate.mjs
Normal file
55
discordBot/events/messageCreate.mjs
Normal file
@@ -0,0 +1,55 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "discordBot.events.messageCreate");
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
import { Events } from "discord.js";
|
||||
import { gptInteraction } from "../addons/gptInteraction.mjs";
|
||||
import { linkCop } from "../addons/linkCop.mjs";
|
||||
import PresenceManager from "../modules/presenceManager.mjs";
|
||||
import { getGuildConfig } from "../../modules/mongo-wrappers/mongoConfigWrappers.mjs";
|
||||
|
||||
export const name = Events.MessageCreate;
|
||||
|
||||
export async function execute(nodeIo, message) {
|
||||
// Get the ignored channels from the server config
|
||||
const IGNORED_CHANNELS = await getGuildConfig(
|
||||
message.guild.id,
|
||||
"ignoredChannels",
|
||||
);
|
||||
|
||||
// Ignore ignored channels
|
||||
if (
|
||||
!Array.isArray(IGNORED_CHANNELS) ||
|
||||
(Array.isArray(IGNORED_CHANNELS) &&
|
||||
IGNORED_CHANNELS.includes(message.channel.id))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore messages from a bot
|
||||
if (message.author.bot) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.INFO("Message create", message);
|
||||
|
||||
// Set presence for reading message
|
||||
const messagePm = new PresenceManager(message.client);
|
||||
await messagePm.setPresence("online", "WATCHING", "latest messages");
|
||||
|
||||
// Check if the message mentions the bot
|
||||
if (message.mentions.users.has(nodeIo.serverClient.user.id)) {
|
||||
const interaction = await gptInteraction(nodeIo, message);
|
||||
|
||||
// Reset the presence
|
||||
await messagePm.resetToDefault();
|
||||
|
||||
return interaction;
|
||||
}
|
||||
|
||||
// Check if the message contains a link in a channel it shouldn't
|
||||
await linkCop(nodeIo, message);
|
||||
|
||||
// Reset the presence
|
||||
await messagePm.resetToDefault();
|
||||
}
|
||||
117
discordBot/modules/gptHandler.mjs
Normal file
117
discordBot/modules/gptHandler.mjs
Normal file
@@ -0,0 +1,117 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "discordBot.modules.gptHandler");
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
import { OpenAI } from "openai";
|
||||
import { EventEmitter } from "events";
|
||||
|
||||
const openai = new OpenAI(process.env.OPENAI_API_KEY);
|
||||
|
||||
const assistant = await openai.beta.assistants.create({
|
||||
name: "Emmelia",
|
||||
instructions: process.env.DRB_SERVER_INITIAL_PROMPT,
|
||||
model: "gpt-4o",
|
||||
});
|
||||
|
||||
class EventHandler extends EventEmitter {
|
||||
constructor(client) {
|
||||
super();
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
async onEvent(event) {
|
||||
try {
|
||||
console.log(event);
|
||||
// Retrieve events that are denoted with 'requires_action'
|
||||
// since these will have our tool_calls
|
||||
if (event.event === "thread.run.requires_action") {
|
||||
await this.handleRequiresAction(
|
||||
event.data,
|
||||
event.data.id,
|
||||
event.data.thread_id,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error handling event:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async handleRequiresAction(data, runId, threadId) {
|
||||
try {
|
||||
const toolOutputs =
|
||||
data.required_action.submit_tool_outputs.tool_calls.map((toolCall) => {
|
||||
// Call the function
|
||||
switch (toolCall.function.name) {
|
||||
case "getCurrentTemperature":
|
||||
return {
|
||||
tool_call_id: toolCall.id,
|
||||
output: "57",
|
||||
};
|
||||
}
|
||||
});
|
||||
// Submit all the tool outputs at the same time
|
||||
await this.submitToolOutputs(toolOutputs, runId, threadId);
|
||||
} catch (error) {
|
||||
console.error("Error processing required action:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async submitToolOutputs(toolOutputs, runId, threadId) {
|
||||
try {
|
||||
// Use the submitToolOutputsStream helper
|
||||
const stream = this.client.beta.threads.runs.submitToolOutputsStream(
|
||||
threadId,
|
||||
runId,
|
||||
{ tool_outputs: toolOutputs },
|
||||
);
|
||||
for await (const event of stream) {
|
||||
this.emit("event", event);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error submitting tool outputs:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const eventHandler = new EventHandler(openai);
|
||||
eventHandler.on("event", eventHandler.onEvent.bind(eventHandler));
|
||||
|
||||
export const gptHandler = async (additionalMessages) => {
|
||||
const thread = await openai.beta.threads.create();
|
||||
|
||||
// Add the additional messages to the conversation
|
||||
for (const msgObj of additionalMessages) {
|
||||
await openai.beta.threads.messages.create(thread.id, msgObj);
|
||||
}
|
||||
|
||||
log.DEBUG("AI Conversation:", thread);
|
||||
|
||||
// Run the thread to get a response
|
||||
try {
|
||||
const stream = await openai.beta.threads.runs.stream(
|
||||
thread.id,
|
||||
{ assistant_id: assistant.id },
|
||||
eventHandler,
|
||||
);
|
||||
|
||||
for await (const event of stream) {
|
||||
eventHandler.emit("event", event);
|
||||
}
|
||||
|
||||
let response;
|
||||
const messages = await openai.beta.threads.messages.list(thread.id);
|
||||
response = messages.data[0].content[0].text.value;
|
||||
|
||||
log.DEBUG("AI Response:", response);
|
||||
|
||||
if (!response) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("Error generating response:", error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
112
discordBot/modules/presenceManager.mjs
Normal file
112
discordBot/modules/presenceManager.mjs
Normal file
@@ -0,0 +1,112 @@
|
||||
import {
|
||||
getConfig,
|
||||
setConfig,
|
||||
} from "../../modules/mongo-wrappers/mongoConfigWrappers.mjs";
|
||||
import { ActivityType, PresenceUpdateStatus, Client } from "discord.js";
|
||||
|
||||
class PresenceManager {
|
||||
/**
|
||||
* Creates an instance of PresenceManager.
|
||||
* @param {Client} client - The Discord client instance.
|
||||
*/
|
||||
constructor(client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bot's presence.
|
||||
* @param {"online"|"idle"|"dnd"} status - The status of the bot (online, idle, dnd).
|
||||
* @param {"PLAYING"|"STREAMING"|"LISTENING"|"WATCHING"|"COMPETING"} activityType - The type of activity.
|
||||
* @param {string} activityName - The name of the activity.
|
||||
* @param {string} [url=null] - The URL for STREAMING activity type (optional).
|
||||
*/
|
||||
async setPresence(status, activityType, activityName, url = null) {
|
||||
const activityOptions = {
|
||||
type: this.convertActivityType(activityType),
|
||||
name: activityName,
|
||||
};
|
||||
|
||||
if (activityType.toUpperCase() === "STREAMING" && url) {
|
||||
activityOptions.url = url;
|
||||
}
|
||||
|
||||
await this.client.user.setPresence({
|
||||
status: this.convertStatus(status),
|
||||
activities: [activityOptions],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the bot's presence to the default state.
|
||||
*/
|
||||
async resetToDefault() {
|
||||
let defaultPresence = await getConfig("presence");
|
||||
|
||||
if (!defaultPresence) {
|
||||
defaultPresence = {
|
||||
status: "idle",
|
||||
activities: [
|
||||
{
|
||||
name: "your commands",
|
||||
type: "LISTENING",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
await setConfig("presence", defaultPresence);
|
||||
}
|
||||
|
||||
console.log("Default Presence:", defaultPresence);
|
||||
|
||||
// Update your bot's presence using this configuration
|
||||
await this.setPresence(
|
||||
defaultPresence.status,
|
||||
defaultPresence.activities[0].type,
|
||||
defaultPresence.activities[0].name,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string activity type to the corresponding ActivityType enum.
|
||||
* @param {string} activityType - The activity type string.
|
||||
* @returns {ActivityType} - The corresponding ActivityType enum.
|
||||
*/
|
||||
convertActivityType(activityType) {
|
||||
switch (activityType.toUpperCase()) {
|
||||
case "PLAYING":
|
||||
return ActivityType.Playing;
|
||||
case "STREAMING":
|
||||
return ActivityType.Streaming;
|
||||
case "LISTENING":
|
||||
return ActivityType.Listening;
|
||||
case "WATCHING":
|
||||
return ActivityType.Watching;
|
||||
case "COMPETING":
|
||||
return ActivityType.Competing;
|
||||
default:
|
||||
throw new Error("Invalid activity type");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string status to the corresponding PresenceUpdateStatus enum.
|
||||
* @param {string} status - The status string.
|
||||
* @returns {PresenceUpdateStatus} - The corresponding PresenceUpdateStatus enum.
|
||||
*/
|
||||
convertStatus(status) {
|
||||
switch (status.toLowerCase()) {
|
||||
case "online":
|
||||
return PresenceUpdateStatus.Online;
|
||||
case "idle":
|
||||
return PresenceUpdateStatus.Idle;
|
||||
case "dnd":
|
||||
return PresenceUpdateStatus.DoNotDisturb;
|
||||
case "invisible":
|
||||
return PresenceUpdateStatus.Invisible;
|
||||
default:
|
||||
throw new Error("Invalid status");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default PresenceManager;
|
||||
@@ -1,40 +1,51 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "discordBot.modules.registerCommands");
|
||||
import { REST, Routes } from 'discord.js';
|
||||
import { REST, Routes } from "discord.js";
|
||||
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config()
|
||||
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());
|
||||
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);
|
||||
const rest = new REST({ version: "10" }).setToken(discordToken);
|
||||
|
||||
// and deploy your commands!
|
||||
guildIDs.forEach(guild => {
|
||||
guildIDs.forEach((guild) => {
|
||||
log.INFO("Deploying commands for: ", guild.id);
|
||||
log.DEBUG("Commands", commands);
|
||||
(async () => {
|
||||
try {
|
||||
log.DEBUG(`Started refreshing application (/) commands for guild ID: ${guild.id}.`);
|
||||
log.DEBUG(
|
||||
`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 },
|
||||
);
|
||||
|
||||
log.DEBUG(`Successfully reloaded ${data.length} application (/) commands for guild ID: ${guild.id}.`);
|
||||
log.DEBUG(
|
||||
`Successfully reloaded ${data.length} application (/) commands for guild ID: ${guild.id}.`,
|
||||
);
|
||||
} catch (error) {
|
||||
// And of course, make sure you catch and log any errors!
|
||||
log.ERROR("ERROR Deploying commands: ", error, "Body from error: ", commands);
|
||||
log.ERROR(
|
||||
"ERROR Deploying commands: ",
|
||||
error,
|
||||
"Body from error: ",
|
||||
commands,
|
||||
);
|
||||
}
|
||||
})()
|
||||
})
|
||||
})();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -47,27 +58,35 @@ export const unregisterAllCommands = async (serverClient) => {
|
||||
const clientId = serverClient.user.id;
|
||||
commands = [];
|
||||
|
||||
const rest = new REST({ version: '10' }).setToken(discordToken);
|
||||
guildIDs.forEach(guild => {
|
||||
const rest = new REST({ version: "10" }).setToken(discordToken);
|
||||
guildIDs.forEach((guild) => {
|
||||
log.INFO("Removing commands for: ", clientId, guild.id);
|
||||
(async () => {
|
||||
try {
|
||||
log.DEBUG(`Started removal of ${commands.length} application (/) commands for guild ID: ${guild.id}.`);
|
||||
log.DEBUG(
|
||||
`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 },
|
||||
);
|
||||
|
||||
log.DEBUG(`Successfully removed ${data.length} application (/) commands for guild ID: ${guild.id}.`);
|
||||
log.DEBUG(
|
||||
`Successfully removed ${data.length} application (/) commands for guild ID: ${guild.id}.`,
|
||||
);
|
||||
} catch (error) {
|
||||
// And of course, make sure you catch and log any errors!
|
||||
log.ERROR("ERROR removing commands: ", error, "Body from error: ", commands);
|
||||
log.ERROR(
|
||||
"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
|
||||
@@ -76,10 +95,12 @@ export const unregisterAllCommands = async (serverClient) => {
|
||||
*/
|
||||
export const refreshActiveCommandsWrapper = async (serverClient) => {
|
||||
// Remove all commands
|
||||
log.INFO("Removing/Unregistering all commands from all connected servers/guilds");
|
||||
log.INFO(
|
||||
"Removing/Unregistering all commands from all connected servers/guilds",
|
||||
);
|
||||
await unregisterAllCommands(serverClient);
|
||||
// Deploy the active commands
|
||||
log.INFO("Adding commands to all connected servers/guilds");
|
||||
await registerActiveCommands(serverClient);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
// Import necessary modules
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
import { EmbedBuilder } from "discord.js";
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
import { parse } from "node-html-parser";
|
||||
import { config } from 'dotenv';
|
||||
import { config } from "dotenv";
|
||||
|
||||
// Load environment variables
|
||||
config();
|
||||
|
||||
const log = new DebugBuilder("server", "discordBot.modules.rssWrappers");
|
||||
|
||||
const imageRegex = /(http(s?):)([/|.|\w|\s|-])*((\.(?:jpg|gif|png|webm))|(\/gallery\/(?:[/|.|\w|\s|-])*))/g;
|
||||
const youtubeVideoRegex = /((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube(-nocookie)?\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)/g;
|
||||
const imageRegex =
|
||||
/(http(s?):)([/|.|\w|\s|-])*((\.(?:jpg|gif|png|webm))|(\/gallery\/(?:[/|.|\w|\s|-])*))/g;
|
||||
const youtubeVideoRegex =
|
||||
/((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube(-nocookie)?\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)/g;
|
||||
|
||||
export class DRBEmbedBuilder extends EmbedBuilder {
|
||||
constructor() {
|
||||
super();
|
||||
this.setTimestamp();
|
||||
this.setFooter({ text: 'Brought to you by Emmelia.' });
|
||||
this.setFooter({ text: "Brought to you by Emmelia." });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +29,8 @@ export const sendPost = (post, source, channel) => {
|
||||
const postLink = post.link;
|
||||
let postContent = `*This post has no content* [Direct Link](${post.link})`;
|
||||
|
||||
if (post.content || post['content:encoded']) {
|
||||
const content = post['content:encoded'] ?? post.content;
|
||||
if (post.content || post["content:encoded"]) {
|
||||
const content = post["content:encoded"] ?? post.content;
|
||||
const parsedContent = parse(content);
|
||||
let postText = parsedContent.text.trim();
|
||||
|
||||
@@ -43,15 +45,17 @@ export const sendPost = (post, source, channel) => {
|
||||
const ytVideos = content.match(youtubeVideoRegex);
|
||||
if (ytVideos) {
|
||||
ytVideos.slice(0, 4).forEach((ytVideo) => {
|
||||
if (ytVideo.includes("embed")) ytVideo = ytVideo.replace("embed/", "watch?v=");
|
||||
if (ytVideo.includes("embed"))
|
||||
ytVideo = ytVideo.replace("embed/", "watch?v=");
|
||||
postContent += `\nEmbedded Video from Post: [YouTube](${ytVideo})`;
|
||||
});
|
||||
}
|
||||
|
||||
// Extract the first image link if available
|
||||
const imageLinks = parsedContent.querySelectorAll("a")
|
||||
.map(link => link.getAttribute("href"))
|
||||
.filter(href => href && href.match(imageRegex));
|
||||
const imageLinks = parsedContent
|
||||
.querySelectorAll("a")
|
||||
.map((link) => link.getAttribute("href"))
|
||||
.filter((href) => href && href.match(imageRegex));
|
||||
|
||||
if (imageLinks.length > 0) {
|
||||
post.image = imageLinks[0];
|
||||
@@ -67,11 +71,11 @@ export const sendPost = (post, source, channel) => {
|
||||
|
||||
try {
|
||||
const rssMessage = new DRBEmbedBuilder()
|
||||
.setColor(0x0099FF)
|
||||
.setColor(0x0099ff)
|
||||
.setTitle(postTitle)
|
||||
.setURL(postLink)
|
||||
.addFields({ name: 'Source', value: postSourceLink, inline: true })
|
||||
.addFields({ name: 'Published', value: postPubDate, inline: true });
|
||||
.addFields({ name: "Source", value: postSourceLink, inline: true })
|
||||
.addFields({ name: "Published", value: postPubDate, inline: true });
|
||||
|
||||
if (postImage) {
|
||||
log.DEBUG("Image from post:", postImage);
|
||||
@@ -87,7 +91,14 @@ export const sendPost = (post, source, channel) => {
|
||||
|
||||
return channelResponse;
|
||||
} catch (err) {
|
||||
log.ERROR("Error sending message: ", postTitle, postId, postContent, postPubDate, err);
|
||||
log.ERROR(
|
||||
"Error sending message: ",
|
||||
postTitle,
|
||||
postId,
|
||||
postContent,
|
||||
postPubDate,
|
||||
err,
|
||||
);
|
||||
return err;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,15 +1,27 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "discordBot.modules.wrappers");
|
||||
import { checkIfNodeIsConnectedToVC, getNodeDiscordID, getNodeDiscordUsername } from '../../modules/socketServerWrappers.mjs';
|
||||
import { getAllDiscordIDs } from '../../modules/mongo-wrappers/mongoDiscordIDWrappers.mjs'
|
||||
|
||||
import {
|
||||
checkIfNodeIsConnectedToVC,
|
||||
getNodeDiscordID,
|
||||
getNodeDiscordUsername,
|
||||
checkIfNodeHasOpenDiscordClient,
|
||||
getNodeCurrentListeningSystem,
|
||||
requestNodeJoinSystem,
|
||||
} from "../../modules/socketServerWrappers.mjs";
|
||||
import { getAllDiscordIDs } from "../../modules/mongo-wrappers/mongoDiscordIDWrappers.mjs";
|
||||
import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
|
||||
|
||||
export const checkOnlineBotsInGuild = async (nodeIo, guildId) => {
|
||||
let onlineBots = [];
|
||||
const openSockets = [...await nodeIo.allSockets()];
|
||||
await Promise.all(openSockets.map(async openSocket => {
|
||||
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);
|
||||
const connected = await checkIfNodeIsConnectedToVC(
|
||||
nodeIo,
|
||||
guildId,
|
||||
openSocket.node.nuid,
|
||||
);
|
||||
log.INFO("Connected:", connected);
|
||||
if (connected) {
|
||||
const username = await getNodeDiscordUsername(openSocket, guildId);
|
||||
@@ -17,21 +29,21 @@ export const checkOnlineBotsInGuild = async (nodeIo, guildId) => {
|
||||
onlineBots.push({
|
||||
name: username,
|
||||
discord_id: discordID,
|
||||
nuid: openSocket.node.nuid
|
||||
nuid: openSocket.node.nuid,
|
||||
});
|
||||
}
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
return onlineBots;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const getAvailableTokensInGuild = async (nodeIo, guildId) => {
|
||||
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
|
||||
checkOnlineBotsInGuild(nodeIo, guildId), // Check online bots in the guild
|
||||
]);
|
||||
|
||||
// Use the results of both promises here
|
||||
@@ -39,12 +51,189 @@ export const checkOnlineBotsInGuild = async (nodeIo, guildId) => {
|
||||
log.INFO("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));
|
||||
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);
|
||||
console.error("Error getting available tokens in guild:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the nodes with given system that are available to be used within a given server
|
||||
* @param {any} nodeIo The nodeIO object contained in the discord server object
|
||||
* @param {any} guildId The guild ID to search in
|
||||
* @param {any} system The system to filter the nodes by
|
||||
* @returns {any}
|
||||
*/
|
||||
export const getAvailableNodes = async (nodeIo, guildId, system) => {
|
||||
// Get all open socket nodes
|
||||
const openSockets = [...(await nodeIo.allSockets())]; // TODO - Filter the returned nodes to only nodes that have the radio capability
|
||||
log.DEBUG("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) {
|
||||
log.INFO(
|
||||
"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,
|
||||
guildId,
|
||||
openSocket.node.nuid,
|
||||
);
|
||||
log.INFO("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);
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
log.DEBUG(
|
||||
"Availble nodes:",
|
||||
availableNodes.map((socket) => socket.node.name),
|
||||
);
|
||||
|
||||
return availableNodes;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the voice channel the user is currently in.
|
||||
* @param {any} interaction - The interaction object.
|
||||
* @returns {any} - The voice channel object, or null if the user is not in a voice channel.
|
||||
*/
|
||||
export const getUserVoiceChannel = (interaction) => {
|
||||
if (!interaction.member.voice.channel) {
|
||||
interaction.editReply({
|
||||
content: `<@${interaction.member.id}>, you need to enter a voice channel before using this command`,
|
||||
ephemeral: true,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
return interaction.member.voice.channel;
|
||||
};
|
||||
|
||||
/**
|
||||
* Joins a node to a specified system and voice channel.
|
||||
* @param {any} nodeIo - The nodeIO server for manipulation of sockets.
|
||||
* @param {any} interaction - The interaction object.
|
||||
* @param {string} nodeId - The ID of the node to join.
|
||||
* @param {any} system - The system object to join.
|
||||
* @param {any} channel - The voice channel to join.
|
||||
*/
|
||||
export const joinNode = async (
|
||||
nodeIo,
|
||||
interaction,
|
||||
nodeId,
|
||||
system,
|
||||
channel,
|
||||
) => {
|
||||
try {
|
||||
const openSocket = await nodeIo.sockets.sockets.get(nodeId);
|
||||
const discordTokens = await getAvailableTokensInGuild(
|
||||
nodeIo,
|
||||
interaction.guild.id,
|
||||
);
|
||||
|
||||
if (discordTokens.length === 0) {
|
||||
await interaction.editReply({
|
||||
content: `<@${interaction.member.id}>, there are no free bots available.`,
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
log.INFO(
|
||||
"Joining node:",
|
||||
nodeId,
|
||||
system.name,
|
||||
channel.id,
|
||||
openSocket.node.name,
|
||||
discordTokens[0].token,
|
||||
);
|
||||
await requestNodeJoinSystem(
|
||||
openSocket,
|
||||
system.name,
|
||||
channel.id,
|
||||
discordTokens[0].token,
|
||||
);
|
||||
|
||||
await interaction.editReply({
|
||||
content: `<@${interaction.member.id}>, a bot will join your channel listening to '${system.name}' shortly.`,
|
||||
ephemeral: true,
|
||||
});
|
||||
} catch (err) {
|
||||
log.ERROR("Failed to join node:", err);
|
||||
await interaction.editReply({
|
||||
content: `<@${interaction.member.id}>, an error occurred while joining the node: ${err.message}`,
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Prompts the user to select a node from available nodes.
|
||||
* @param {any} interaction - The interaction object.
|
||||
* @param {Array} availableNodes - The list of available nodes.
|
||||
* @param {Function} onNodeSelected - Callback function to handle the selected node.
|
||||
*/
|
||||
export const promptNodeSelection = async (
|
||||
interaction,
|
||||
availableNodes,
|
||||
onNodeSelected,
|
||||
) => {
|
||||
const nodeSelectionButtons = availableNodes.map((node) =>
|
||||
new ButtonBuilder()
|
||||
.setCustomId(node.id)
|
||||
.setLabel(node.node.name)
|
||||
.setStyle(ButtonStyle.Primary),
|
||||
);
|
||||
|
||||
const actionRow = new ActionRowBuilder().addComponents(nodeSelectionButtons);
|
||||
|
||||
const response = await interaction.editReply({
|
||||
content: `<@${interaction.member.id}>, please select the Node you would like to join with this system:`,
|
||||
components: [actionRow],
|
||||
ephemeral: true,
|
||||
});
|
||||
|
||||
const collectorFilter = (i) => i.user.id === interaction.user.id;
|
||||
try {
|
||||
const selectedNode = await response.awaitMessageComponent({
|
||||
filter: collectorFilter,
|
||||
time: 60_000,
|
||||
});
|
||||
await onNodeSelected(selectedNode.customId);
|
||||
} catch (e) {
|
||||
log.ERROR("Node selection timeout:", e);
|
||||
await interaction.editReply({
|
||||
content: "Confirmation not received within 1 minute, cancelling.",
|
||||
components: [],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
47
eslint.config.mjs
Normal file
47
eslint.config.mjs
Normal file
@@ -0,0 +1,47 @@
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { FlatCompat } from "@eslint/eslintrc";
|
||||
import mjs from "@eslint/js";
|
||||
import prettierConfig from "eslint-config-prettier";
|
||||
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
|
||||
import unusedImports from "eslint-plugin-unused-imports";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: mjs.configs.recommended,
|
||||
});
|
||||
|
||||
export default [
|
||||
// Apply ESLint recommended settings first
|
||||
...compat.extends().map((config) => ({
|
||||
...config,
|
||||
files: ["**/*.mjs", "**/*.js", "**/*.cjs"],
|
||||
})),
|
||||
|
||||
// Custom rules and plugin configuration
|
||||
{
|
||||
plugins: {
|
||||
"unused-imports": unusedImports,
|
||||
},
|
||||
rules: {
|
||||
// Custom rules here
|
||||
"no-console": "warn",
|
||||
"no-unused-vars": "off", // or "@typescript-eslint/no-unused-vars": "off",
|
||||
"unused-imports/no-unused-imports": "error",
|
||||
"unused-imports/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
vars: "all",
|
||||
varsIgnorePattern: "^_",
|
||||
args: "after-used",
|
||||
argsIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
"prettier/prettier": "warn", // Integrate prettier
|
||||
},
|
||||
},
|
||||
prettierConfig, // Turns off all ESLint rules that have the potential to interfere with Prettier rules.
|
||||
eslintPluginPrettierRecommended,
|
||||
];
|
||||
14
jsdoc.json
Normal file
14
jsdoc.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"source": {
|
||||
"include": ["addons", "discordBot", "modules", "rss-manager", "test"],
|
||||
"exclude": ["node_modules"],
|
||||
"includePattern": ".+\\.[mc]?js(doc|x)?$",
|
||||
"excludePattern": "(^|\\/|\\\\)_"
|
||||
},
|
||||
"opts": {
|
||||
"destination": "./docs",
|
||||
"recurse": true,
|
||||
"template": "node_modules/better-docs"
|
||||
}
|
||||
}
|
||||
|
||||
7
makefile
7
makefile
@@ -20,6 +20,13 @@ run:
|
||||
-e SERVER_PORT=${SERVER_PORT} \
|
||||
-e MONGO_URL=${MONGO_URL} \
|
||||
-e DISCORD_TOKEN=${DISCORD_TOKEN} \
|
||||
-e RSS_REFRESH_INTERVAL=${RSS_REFRESH_INTERVAL} \
|
||||
-e WELCOME_CHANNEL_ID=${WELCOME_CHANNEL_ID} \
|
||||
-e IGNORED_CHANNEL_IDS=${IGNORED_CHANNEL_IDS} \
|
||||
-e LINKCOP_RESTRICTED_CHANNEL_IDS=${LINKCOP_RESTRICTED_CHANNEL_IDS} \
|
||||
-e DRB_SERVER_INITIAL_PROMPT=${DRB_SERVER_INITIAL_PROMPT} \
|
||||
-e OPENAI_API_KEY=${OPENAI_API_KEY} \
|
||||
-e LOG_LOCATION="./logs/server.log" \
|
||||
-p ${SERVER_PORT}:${SERVER_PORT} \
|
||||
--name=drb \
|
||||
$(DOCKER_IMAGE_NAME)
|
||||
@@ -1,27 +1,39 @@
|
||||
import { DebugBuilder } from "../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "addonManager");
|
||||
import { fileURLToPath } from 'url';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
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');
|
||||
const addonsDir = path.join(__dirname, "../addons");
|
||||
|
||||
// Read the directory containing addon modules
|
||||
const addonDirectories = await fs.readdirSync(addonsDir, { withFileTypes: true });
|
||||
const addonDirectories = await fs.readdirSync(addonsDir, {
|
||||
withFileTypes: true,
|
||||
});
|
||||
|
||||
addonDirectories.forEach(addonDir => {
|
||||
addonDirectories.forEach((addonDir) => {
|
||||
if (addonDir.isDirectory()) {
|
||||
const addonConfigPath = path.join(addonsDir, addonDir.name, 'config.json');
|
||||
const addonConfigPath = path.join(
|
||||
addonsDir,
|
||||
addonDir.name,
|
||||
"config.json",
|
||||
);
|
||||
if (fs.existsSync(addonConfigPath)) {
|
||||
const addonConfig = JSON.parse(fs.readFileSync(addonConfigPath, 'utf-8'));
|
||||
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 => {
|
||||
const addonIndexPath = path.join(
|
||||
addonsDir,
|
||||
addonDir.name,
|
||||
"index.js",
|
||||
);
|
||||
import(`file://${addonIndexPath}`).then((addonModule) => {
|
||||
log.DEBUG("Loading addon: ", addonModule);
|
||||
addonModule.initialize(nodeIo, addonConfig);
|
||||
log.DEBUG(`Addon ${addonConfig.name} loaded.`);
|
||||
@@ -30,4 +42,4 @@ export const loadAddons = async (nodeIo) => {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// Import necessary modules
|
||||
import debug from 'debug';
|
||||
import { config } from 'dotenv';
|
||||
import debug from "debug";
|
||||
import { config } from "dotenv";
|
||||
config();
|
||||
import { promises as fs } from 'fs';
|
||||
import { join, dirname } from 'path';
|
||||
import { inspect } from 'util';
|
||||
import { promises as fs } from "fs";
|
||||
import { join, dirname } from "path";
|
||||
import { inspect } from "util";
|
||||
|
||||
/**
|
||||
* Write a given message to the log file
|
||||
@@ -26,7 +26,10 @@ const writeToLog = async (logMessage, appName) => {
|
||||
|
||||
// Write to the file
|
||||
try {
|
||||
await fs.writeFile(logLocation, logMessage, { encoding: 'utf-8', flag: 'a+' });
|
||||
await fs.writeFile(logLocation, logMessage, {
|
||||
encoding: "utf-8",
|
||||
flag: "a+",
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
@@ -40,12 +43,16 @@ const writeToLog = async (logMessage, appName) => {
|
||||
*/
|
||||
export class DebugBuilder {
|
||||
constructor(appName, fileName) {
|
||||
const buildLogger = (level) => (...messageParts) => {
|
||||
const buildLogger =
|
||||
(level) =>
|
||||
(...messageParts) => {
|
||||
const logger = debug(`${appName}:${fileName}:${level}`);
|
||||
logger(messageParts);
|
||||
|
||||
const timeStamp = new Date().toLocaleString('en-US', { timeZone: 'America/New_York' });
|
||||
const message = `${timeStamp} - ${appName}:${fileName}:${level}\t-\t${messageParts.map(part => inspect(part)).join(' ')}`;
|
||||
const timeStamp = new Date().toLocaleString("en-US", {
|
||||
timeZone: "America/New_York",
|
||||
});
|
||||
const message = `${timeStamp} - ${appName}:${fileName}:${level}\t-\t${messageParts.map((part) => inspect(part)).join(" ")}`;
|
||||
|
||||
// Write to console
|
||||
console.log(message);
|
||||
@@ -54,12 +61,12 @@ export class DebugBuilder {
|
||||
writeToLog(message, appName);
|
||||
};
|
||||
|
||||
this.INFO = buildLogger('INFO');
|
||||
this.DEBUG = buildLogger('DEBUG');
|
||||
this.VERBOSE = buildLogger('VERBOSE');
|
||||
this.WARN = buildLogger('WARNING');
|
||||
this.INFO = buildLogger("INFO");
|
||||
this.DEBUG = buildLogger("DEBUG");
|
||||
this.VERBOSE = buildLogger("VERBOSE");
|
||||
this.WARN = buildLogger("WARNING");
|
||||
this.ERROR = (...messageParts) => {
|
||||
buildLogger('ERROR')(...messageParts);
|
||||
buildLogger("ERROR")(...messageParts);
|
||||
|
||||
if (process.env.EXIT_ON_ERROR && process.env.EXIT_ON_ERROR > 0) {
|
||||
writeToLog("!--- EXITING ---!", appName);
|
||||
|
||||
113
modules/mongo-wrappers/mongoConfigWrappers.mjs
Normal file
113
modules/mongo-wrappers/mongoConfigWrappers.mjs
Normal file
@@ -0,0 +1,113 @@
|
||||
import {
|
||||
getDocumentByField,
|
||||
deleteDocumentByField,
|
||||
getDocumentByFields,
|
||||
upsertDocumentByField,
|
||||
deleteDocumentByFields,
|
||||
upsertDocumentByFields,
|
||||
} from "./mongoHandler.mjs"; // Import your MongoDB handlers
|
||||
import { DebugBuilder } from "../debugger.mjs";
|
||||
|
||||
const log = new DebugBuilder("server", "mongoConfigWrappers");
|
||||
|
||||
const collectionName = "configurations";
|
||||
|
||||
// Function to get a configuration by key
|
||||
export const getConfig = async (key) => {
|
||||
try {
|
||||
const config = await getDocumentByField(collectionName, "key", key);
|
||||
log.DEBUG(`Configuration for key "${key}" retrieved:`, config);
|
||||
return config ? config[key] : null; // Return null if no configuration is found
|
||||
} catch (error) {
|
||||
log.ERROR("Error retrieving configuration:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Function to set a configuration by key
|
||||
export const setConfig = async (key, value) => {
|
||||
// Set the config object
|
||||
value = { key: value };
|
||||
try {
|
||||
const result = await upsertDocumentByField(
|
||||
collectionName,
|
||||
"key",
|
||||
key,
|
||||
value,
|
||||
);
|
||||
log.DEBUG(`Configuration for key "${key}" set:`, value, result);
|
||||
return result > 0 ? key : null; // Return key if updated successfully, otherwise null
|
||||
} catch (error) {
|
||||
log.ERROR("Error setting configuration:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Function to delete a configuration by key (optional)
|
||||
export const deleteConfig = async (key) => {
|
||||
try {
|
||||
const result = await deleteDocumentByField(collectionName, "key", key);
|
||||
log.DEBUG(`Configuration for key "${key}" deleted:`, result);
|
||||
return result; // Return the count of deleted documents
|
||||
} catch (error) {
|
||||
log.ERROR("Error deleting configuration:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Function to get a configuration by key for a specific guild
|
||||
export const getGuildConfig = async (guildId, key) => {
|
||||
try {
|
||||
const config = await getDocumentByFields(
|
||||
collectionName,
|
||||
["guild", Number(guildId)],
|
||||
["key", key],
|
||||
);
|
||||
log.DEBUG(
|
||||
`Guild ${guildId} configuration for key "${key}" retrieved:`,
|
||||
config,
|
||||
);
|
||||
return config ? config[key] : null; // Return null if no configuration is found
|
||||
} catch (error) {
|
||||
log.ERROR("Error retrieving guild configuration:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Function to set a configuration by key for a specific guild
|
||||
export const setGuildConfig = async (guildId, key, value) => {
|
||||
// Set the config object
|
||||
value = { key: value };
|
||||
try {
|
||||
const result = await upsertDocumentByFields(
|
||||
collectionName,
|
||||
value,
|
||||
["guild", Number(guildId)],
|
||||
["key", key],
|
||||
);
|
||||
log.DEBUG(`Guild ${guildId} configuration for key "${key}" set:`, value);
|
||||
return result > 0 ? key : null; // Return key if updated successfully, otherwise null
|
||||
} catch (error) {
|
||||
log.ERROR("Error setting guild configuration:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Function to delete a configuration by key for a specific guild (optional)
|
||||
export const deleteGuildConfig = async (guildId, key) => {
|
||||
try {
|
||||
const result = await deleteDocumentByFields(
|
||||
collectionName,
|
||||
["guild", Number(guildId)],
|
||||
["key", key],
|
||||
);
|
||||
log.DEBUG(
|
||||
`Guild ${guildId} configuration for key "${key}" deleted:`,
|
||||
result,
|
||||
);
|
||||
return result; // Return the count of deleted documents
|
||||
} catch (error) {
|
||||
log.ERROR("Error deleting guild configuration:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -1,8 +1,12 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "mongoDiscordIDWrappers");
|
||||
import { insertDocument, getDocuments, connectToDatabase } from "./mongoHandler.mjs";
|
||||
import {
|
||||
insertDocument,
|
||||
getDocuments,
|
||||
connectToDatabase,
|
||||
} from "./mongoHandler.mjs";
|
||||
|
||||
const collectionName = 'discord-ids';
|
||||
const collectionName = "discord-ids";
|
||||
|
||||
// Wrapper for inserting a Discord ID
|
||||
export const createDiscordID = async (discordID) => {
|
||||
@@ -10,7 +14,7 @@ export const createDiscordID = async (discordID) => {
|
||||
const insertedId = await insertDocument(collectionName, discordID);
|
||||
return insertedId;
|
||||
} catch (error) {
|
||||
log.ERROR('Error creating Discord ID:', error);
|
||||
log.ERROR("Error creating Discord ID:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -21,7 +25,7 @@ export const getAllDiscordIDs = async () => {
|
||||
const discordIDs = await getDocuments(collectionName);
|
||||
return discordIDs;
|
||||
} catch (error) {
|
||||
log.ERROR('Error getting all Discord IDs:', error);
|
||||
log.ERROR("Error getting all Discord IDs:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -32,14 +36,11 @@ export const getDiscordID = async (identifier) => {
|
||||
try {
|
||||
const collection = db.db().collection(collectionName);
|
||||
const discordID = await collection.findOne({
|
||||
$or: [
|
||||
{ name: identifier },
|
||||
{ discord_id: identifier }
|
||||
]
|
||||
$or: [{ name: identifier }, { discord_id: identifier }],
|
||||
});
|
||||
return discordID;
|
||||
} catch (error) {
|
||||
log.ERROR('Error getting Discord ID:', error);
|
||||
log.ERROR("Error getting Discord ID:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
// Close the connection
|
||||
@@ -52,16 +53,16 @@ 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 });
|
||||
log.INFO('Discord ID updated:', result.modifiedCount);
|
||||
const result = await collection.updateOne(
|
||||
{
|
||||
$or: [{ name: identifier }, { discord_id: identifier }],
|
||||
},
|
||||
{ $set: updatedFields },
|
||||
);
|
||||
log.INFO("Discord ID updated:", result.modifiedCount);
|
||||
return result.modifiedCount;
|
||||
} catch (error) {
|
||||
log.ERROR('Error updating Discord ID:', error);
|
||||
log.ERROR("Error updating Discord ID:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
// Close the connection
|
||||
@@ -75,15 +76,12 @@ export const deleteDiscordID = async (identifier) => {
|
||||
try {
|
||||
const collection = db.db().collection(collectionName);
|
||||
const result = await collection.deleteOne({
|
||||
$or: [
|
||||
{ name: identifier },
|
||||
{ discord_id: identifier }
|
||||
]
|
||||
$or: [{ name: identifier }, { discord_id: identifier }],
|
||||
});
|
||||
log.INFO('Discord ID deleted:', result.deletedCount);
|
||||
log.INFO("Discord ID deleted:", result.deletedCount);
|
||||
return result.deletedCount;
|
||||
} catch (error) {
|
||||
log.ERROR('Error deleting Discord ID:', error);
|
||||
log.ERROR("Error deleting Discord ID:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
// Close the connection
|
||||
|
||||
@@ -6,107 +6,119 @@ import {
|
||||
getDocumentByField,
|
||||
updateDocumentByField,
|
||||
deleteDocumentByField,
|
||||
} from "./mongoHandler.mjs";
|
||||
} from "./mongoHandler.mjs";
|
||||
|
||||
const feedCollectionName = 'feeds';
|
||||
const postCollectionName = 'posts';
|
||||
const feedCollectionName = "feeds";
|
||||
const postCollectionName = "posts";
|
||||
|
||||
// Wrapper for inserting a feed
|
||||
export const createFeed = async (feed) => {
|
||||
// Wrapper for inserting a feed
|
||||
export const createFeed = async (feed) => {
|
||||
try {
|
||||
const insertedId = await insertDocument(feedCollectionName, feed);
|
||||
return insertedId;
|
||||
} catch (error) {
|
||||
log.ERROR('Error creating feed:', error);
|
||||
log.ERROR("Error creating feed:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Wrapper for retrieving all feeds
|
||||
export const getAllFeeds = async () => {
|
||||
// Wrapper for retrieving all feeds
|
||||
export const getAllFeeds = async () => {
|
||||
try {
|
||||
const feeds = await getDocuments(feedCollectionName);
|
||||
return feeds;
|
||||
} catch (error) {
|
||||
log.ERROR('Error getting all feeds:', error);
|
||||
log.ERROR("Error getting all feeds:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Wrapper for retrieving a feed by link
|
||||
export const getFeedByLink = async (link) => {
|
||||
// Wrapper for retrieving a feed by link
|
||||
export const getFeedByLink = async (link) => {
|
||||
try {
|
||||
const feed = await getDocumentByField(feedCollectionName, 'link', link);
|
||||
const feed = await getDocumentByField(feedCollectionName, "link", link);
|
||||
return feed;
|
||||
} catch (error) {
|
||||
log.ERROR('Error getting feed by link:', error);
|
||||
log.ERROR("Error getting feed by link:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Wrapper for retrieving a feed by the title
|
||||
export const getFeedByTitle = async (title) => {
|
||||
// Wrapper for retrieving a feed by the title
|
||||
export const getFeedByTitle = async (title) => {
|
||||
try {
|
||||
const feed = await getDocumentByField(feedCollectionName, 'title', title);
|
||||
const feed = await getDocumentByField(feedCollectionName, "title", title);
|
||||
return feed;
|
||||
} catch (error) {
|
||||
log.ERROR('Error getting feed by link:', error);
|
||||
log.ERROR("Error getting feed by link:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Wrapper for updating a feed by link
|
||||
export const updateFeedByLink = async (link, updatedFields) => {
|
||||
// Wrapper for updating a feed by link
|
||||
export const updateFeedByLink = async (link, updatedFields) => {
|
||||
try {
|
||||
const modifiedCount = await updateDocumentByField(feedCollectionName, 'link', link, updatedFields);
|
||||
const modifiedCount = await updateDocumentByField(
|
||||
feedCollectionName,
|
||||
"link",
|
||||
link,
|
||||
updatedFields,
|
||||
);
|
||||
return modifiedCount;
|
||||
} catch (error) {
|
||||
log.ERROR('Error updating feed by link:', error);
|
||||
log.ERROR("Error updating feed by link:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Wrapper for deleting a feed by link
|
||||
export const deleteFeedByLink = async (link) => {
|
||||
// Wrapper for deleting a feed by link
|
||||
export const deleteFeedByLink = async (link) => {
|
||||
try {
|
||||
const deletedCount = await deleteDocumentByField(feedCollectionName, 'link', link);
|
||||
const deletedCount = await deleteDocumentByField(
|
||||
feedCollectionName,
|
||||
"link",
|
||||
link,
|
||||
);
|
||||
return deletedCount;
|
||||
} catch (error) {
|
||||
log.ERROR('Error deleting feed by link:', error);
|
||||
log.ERROR("Error deleting feed by link:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Wrapper for deleting a feed by title
|
||||
export const deleteFeedByTitle = async (title) => {
|
||||
// Wrapper for deleting a feed by title
|
||||
export const deleteFeedByTitle = async (title) => {
|
||||
try {
|
||||
const deletedCount = await deleteDocumentByField(feedCollectionName, 'title', title);
|
||||
const deletedCount = await deleteDocumentByField(
|
||||
feedCollectionName,
|
||||
"title",
|
||||
title,
|
||||
);
|
||||
return deletedCount;
|
||||
} catch (error) {
|
||||
log.ERROR('Error deleting feed by link:', error);
|
||||
log.ERROR("Error deleting feed by link:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Wrapper for inserting a post
|
||||
export const createPost = async (post) => {
|
||||
// Wrapper for inserting a post
|
||||
export const createPost = async (post) => {
|
||||
try {
|
||||
const insertedId = await insertDocument(postCollectionName, post);
|
||||
return insertedId;
|
||||
} catch (error) {
|
||||
log.ERROR('Error creating post:', error);
|
||||
log.ERROR("Error creating post:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Wrapper for retrieving a post by postId
|
||||
export const getPostByPostId = async (postId) => {
|
||||
// Wrapper for retrieving a post by postId
|
||||
export const getPostByPostId = async (postId) => {
|
||||
try {
|
||||
const post = await getDocumentByField(postCollectionName, 'postId', postId);
|
||||
const post = await getDocumentByField(postCollectionName, "postId", postId);
|
||||
return post;
|
||||
} catch (error) {
|
||||
log.ERROR('Error getting post by postId:', error);
|
||||
log.ERROR("Error getting post by postId:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// Import necessary modules
|
||||
import { MongoClient } from 'mongodb';
|
||||
import { DebugBuilder } from '../debugger.mjs';
|
||||
const log = new DebugBuilder("server", 'mongoHandler');
|
||||
import { MongoClient } from "mongodb";
|
||||
import { DebugBuilder } from "../debugger.mjs";
|
||||
const log = new DebugBuilder("server", "mongoHandler");
|
||||
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config()
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
// MongoDB connection URI
|
||||
const uri = process.env.MONGO_URL;
|
||||
@@ -15,7 +15,7 @@ export const connectToDatabase = async () => {
|
||||
const client = await MongoClient.connect(uri);
|
||||
return client;
|
||||
} catch (error) {
|
||||
console.error('Error connecting to the database:', error);
|
||||
console.error("Error connecting to the database:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -27,10 +27,10 @@ export const insertDocument = async (collectionName, document) => {
|
||||
try {
|
||||
const collection = db.db().collection(collectionName);
|
||||
const result = await collection.insertOne(document);
|
||||
log.DEBUG('Document inserted:', result.insertedId);
|
||||
log.DEBUG("Document inserted:", result.insertedId);
|
||||
return result.insertedId;
|
||||
} catch (error) {
|
||||
console.error('Error inserting document:', error);
|
||||
console.error("Error inserting document:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
// Close the connection
|
||||
@@ -45,10 +45,10 @@ export const getDocuments = async (collectionName) => {
|
||||
try {
|
||||
const collection = db.db().collection(collectionName);
|
||||
const documents = await collection.find({}).toArray();
|
||||
log.DEBUG('Documents retrieved:', documents);
|
||||
log.DEBUG("Documents retrieved:", documents);
|
||||
return documents;
|
||||
} catch (error) {
|
||||
console.error('Error retrieving documents:', error);
|
||||
console.error("Error retrieving documents:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
// Close the connection
|
||||
@@ -59,13 +59,29 @@ export const getDocuments = async (collectionName) => {
|
||||
// Function to retrieve a document by a specific field
|
||||
export const getDocumentByField = async (collectionName, field, value) => {
|
||||
log.DEBUG("Getting document by field:", collectionName, field, value);
|
||||
return await getDocumentByFields(collectionName, [field, value]);
|
||||
};
|
||||
|
||||
// Function to retrieve a document by multiple fields
|
||||
export const getDocumentByFields = async (
|
||||
collectionName,
|
||||
...fieldValuePairs
|
||||
) => {
|
||||
log.DEBUG("Getting document by fields:", collectionName, fieldValuePairs);
|
||||
const db = await connectToDatabase();
|
||||
try {
|
||||
const collection = db.db().collection(collectionName);
|
||||
const document = await collection.findOne({ [field]: value });
|
||||
|
||||
// Convert the fieldValuePairs array into an object
|
||||
const query = fieldValuePairs.reduce((acc, [field, value]) => {
|
||||
acc[field] = value;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const document = await collection.findOne(query);
|
||||
return document;
|
||||
} catch (error) {
|
||||
console.error('Error retrieving document:', error);
|
||||
console.error("Error retrieving document:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
await db.close();
|
||||
@@ -73,16 +89,102 @@ export const getDocumentByField = async (collectionName, field, value) => {
|
||||
};
|
||||
|
||||
// Function to update a document by a specific field
|
||||
export const updateDocumentByField = async (collectionName, field, value, updatedFields) => {
|
||||
log.DEBUG("Update document by field:", collectionName, field, value, updatedFields);
|
||||
export const upsertDocumentByField = async (
|
||||
collectionName,
|
||||
field,
|
||||
value,
|
||||
updatedFields,
|
||||
) => {
|
||||
log.DEBUG(
|
||||
"Upsert document by field:",
|
||||
collectionName,
|
||||
field,
|
||||
value,
|
||||
updatedFields,
|
||||
);
|
||||
return await updateDocumentByFields(
|
||||
collectionName,
|
||||
updatedFields,
|
||||
{ upsert: true },
|
||||
[field, value],
|
||||
);
|
||||
};
|
||||
|
||||
// Function to update a document by a specific field
|
||||
export const upsertDocumentByFields = async (
|
||||
collectionName,
|
||||
updatedFields,
|
||||
...fieldValuePairs
|
||||
) => {
|
||||
log.DEBUG(
|
||||
"Upsert document by fields:",
|
||||
collectionName,
|
||||
updatedFields,
|
||||
fieldValuePairs,
|
||||
);
|
||||
return await updateDocumentByFields(
|
||||
collectionName,
|
||||
updatedFields,
|
||||
{ upsert: true },
|
||||
fieldValuePairs,
|
||||
);
|
||||
};
|
||||
|
||||
// Function to update a document by a specific field
|
||||
export const updateDocumentByField = async (
|
||||
collectionName,
|
||||
field,
|
||||
value,
|
||||
updatedFields,
|
||||
options = null,
|
||||
) => {
|
||||
log.DEBUG(
|
||||
"Update document by field:",
|
||||
collectionName,
|
||||
field,
|
||||
value,
|
||||
updatedFields,
|
||||
options,
|
||||
);
|
||||
return await updateDocumentByFields(collectionName, updatedFields, options, [
|
||||
field,
|
||||
value,
|
||||
]);
|
||||
};
|
||||
|
||||
// Function to update a document by multiple fields
|
||||
export const updateDocumentByFields = async (
|
||||
collectionName,
|
||||
updatedFields,
|
||||
options,
|
||||
...fieldValuePairs
|
||||
) => {
|
||||
log.DEBUG(
|
||||
"Update document by fields:",
|
||||
collectionName,
|
||||
updatedFields,
|
||||
options,
|
||||
fieldValuePairs,
|
||||
);
|
||||
const db = await connectToDatabase();
|
||||
try {
|
||||
const collection = db.db().collection(collectionName);
|
||||
const result = await collection.updateOne({ [field]: value }, { $set: updatedFields });
|
||||
log.DEBUG('Document updated:', result.modifiedCount);
|
||||
|
||||
// Convert the fieldValuePairs array into an object
|
||||
const query = fieldValuePairs.reduce((acc, [field, value]) => {
|
||||
acc[field] = value;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const result = await collection.updateOne(
|
||||
query,
|
||||
{ $set: updatedFields },
|
||||
options,
|
||||
);
|
||||
log.DEBUG("Document updated:", result.modifiedCount);
|
||||
return result.modifiedCount;
|
||||
} catch (error) {
|
||||
console.error('Error updating document:', error);
|
||||
console.error("Error updating document:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
await db.close();
|
||||
@@ -92,14 +194,30 @@ export const updateDocumentByField = async (collectionName, field, value, update
|
||||
// Function to delete a document by a specific field
|
||||
export const deleteDocumentByField = async (collectionName, field, value) => {
|
||||
log.DEBUG("Delete document by field:", collectionName, field, value);
|
||||
return await deleteDocumentByFields(collectionName, [field, value]);
|
||||
};
|
||||
|
||||
// Function to delete a document by multiple fields
|
||||
export const deleteDocumentByFields = async (
|
||||
collectionName,
|
||||
...fieldValuePairs
|
||||
) => {
|
||||
log.DEBUG("Delete document by fields:", collectionName, fieldValuePairs);
|
||||
const db = await connectToDatabase();
|
||||
try {
|
||||
const collection = db.db().collection(collectionName);
|
||||
const result = await collection.deleteOne({ [field]: value });
|
||||
log.DEBUG('Document deleted:', result.deletedCount);
|
||||
|
||||
// Convert the fieldValuePairs array into an object
|
||||
const query = fieldValuePairs.reduce((acc, [field, value]) => {
|
||||
acc[field] = value;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const result = await collection.deleteOne(query);
|
||||
log.DEBUG("Document deleted:", result.deletedCount);
|
||||
return result.deletedCount;
|
||||
} catch (error) {
|
||||
console.error('Error deleting document:', error);
|
||||
console.error("Error deleting document:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
await db.close();
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "mongoNodesWrappers");
|
||||
import { insertDocument, getDocuments, connectToDatabase } from "./mongoHandler.mjs";
|
||||
import {
|
||||
insertDocument,
|
||||
getDocuments,
|
||||
connectToDatabase,
|
||||
} from "./mongoHandler.mjs";
|
||||
|
||||
const collectionName = 'nodes';
|
||||
const collectionName = "nodes";
|
||||
|
||||
// Wrapper for inserting a node
|
||||
export const createNode = async (node) => {
|
||||
@@ -10,7 +14,7 @@ export const createNode = async (node) => {
|
||||
const insertedId = await insertDocument(collectionName, node);
|
||||
return insertedId;
|
||||
} catch (error) {
|
||||
log.ERROR('Error creating node:', error);
|
||||
log.ERROR("Error creating node:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -21,7 +25,7 @@ export const getAllNodes = async () => {
|
||||
const nodes = await getDocuments(collectionName);
|
||||
return nodes;
|
||||
} catch (error) {
|
||||
log.ERROR('Error getting all nodes:', error);
|
||||
log.ERROR("Error getting all nodes:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -34,7 +38,7 @@ export const getNodeByNuid = async (nuid) => {
|
||||
const node = await collection.findOne({ nuid });
|
||||
return node;
|
||||
} catch (error) {
|
||||
log.ERROR('Error getting node by NUID:', error);
|
||||
log.ERROR("Error getting node by NUID:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
// Close the connection
|
||||
@@ -47,11 +51,14 @@ 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 });
|
||||
log.INFO('Node updated:', result.modifiedCount);
|
||||
const result = await collection.updateOne(
|
||||
{ nuid },
|
||||
{ $set: updatedFields },
|
||||
);
|
||||
log.INFO("Node updated:", result.modifiedCount);
|
||||
return result.modifiedCount;
|
||||
} catch (error) {
|
||||
log.ERROR('Error updating node by NUID:', error);
|
||||
log.ERROR("Error updating node by NUID:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
// Close the connection
|
||||
@@ -65,10 +72,10 @@ export const deleteNodeByNuid = async (nuid) => {
|
||||
try {
|
||||
const collection = db.db().collection(collectionName);
|
||||
const result = await collection.deleteOne({ nuid });
|
||||
log.INFO('Node deleted:', result.deletedCount);
|
||||
log.INFO("Node deleted:", result.deletedCount);
|
||||
return result.deletedCount;
|
||||
} catch (error) {
|
||||
log.ERROR('Error deleting node by NUID:', error);
|
||||
log.ERROR("Error deleting node by NUID:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
// Close the connection
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "mongoSystemsWrappers");
|
||||
import { insertDocument, getDocuments, connectToDatabase } from "./mongoHandler.mjs";
|
||||
import {
|
||||
insertDocument,
|
||||
getDocuments,
|
||||
connectToDatabase,
|
||||
} from "./mongoHandler.mjs";
|
||||
|
||||
const collectionName = 'radio-systems';
|
||||
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) => {
|
||||
@@ -19,11 +22,11 @@ export const createSystem = async (name, system, nuid) => {
|
||||
// Add the NUID of the node that created this system
|
||||
system.nodes = [nuid];
|
||||
// Add the name of the system
|
||||
system.name = name
|
||||
system.name = name;
|
||||
const insertedId = await insertDocument(collectionName, system);
|
||||
return insertedId;
|
||||
} catch (error) {
|
||||
log.ERROR('Error creating system:', error);
|
||||
log.ERROR("Error creating system:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -34,7 +37,7 @@ export const getAllSystems = async () => {
|
||||
const systems = await getDocuments(collectionName);
|
||||
return systems;
|
||||
} catch (error) {
|
||||
log.ERROR('Error getting all systems:', error);
|
||||
log.ERROR("Error getting all systems:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -47,7 +50,7 @@ export const getSystemByName = async (name) => {
|
||||
const system = await collection.findOne({ name });
|
||||
return system;
|
||||
} catch (error) {
|
||||
log.ERROR('Error getting system by name:', error);
|
||||
log.ERROR("Error getting system by name:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
// Close the connection
|
||||
@@ -67,7 +70,7 @@ export const getSystemsByNuid = async (nuid) => {
|
||||
|
||||
return systems;
|
||||
} catch (error) {
|
||||
log.ERROR('Error finding entries:', error);
|
||||
log.ERROR("Error finding entries:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
// Close the connection
|
||||
@@ -83,11 +86,14 @@ export const updateSystemByName = async (name, updatedSystem) => {
|
||||
const db = await connectToDatabase();
|
||||
try {
|
||||
const collection = db.db().collection(collectionName);
|
||||
const result = await collection.updateOne({ name }, { $set: updatedSystem });
|
||||
log.INFO('System updated:', result.modifiedCount);
|
||||
const result = await collection.updateOne(
|
||||
{ name },
|
||||
{ $set: updatedSystem },
|
||||
);
|
||||
log.INFO("System updated:", result.modifiedCount);
|
||||
return result.modifiedCount;
|
||||
} catch (error) {
|
||||
log.ERROR('Error updating system by name:', error);
|
||||
log.ERROR("Error updating system by name:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
// Close the connection
|
||||
@@ -101,10 +107,10 @@ export const deleteSystemByName = async (name) => {
|
||||
try {
|
||||
const collection = db.db().collection(collectionName);
|
||||
const result = await collection.deleteOne({ name });
|
||||
log.INFO('System deleted:', result.deletedCount);
|
||||
log.INFO("System deleted:", result.deletedCount);
|
||||
return result.deletedCount;
|
||||
} catch (error) {
|
||||
log.ERROR('Error deleting system by name:', error);
|
||||
log.ERROR("Error deleting system by name:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
// Close the connection
|
||||
|
||||
@@ -1,41 +1,47 @@
|
||||
import { DebugBuilder } from "../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "socketServer");
|
||||
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";
|
||||
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.use(morgan("tiny"));
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.send('<h1>Hello world</h1>');
|
||||
app.get("/", (req, res) => {
|
||||
res.send("<h1>Hello world</h1>");
|
||||
});
|
||||
|
||||
nodeIo.on('connection', (socket) => {
|
||||
log.INFO('a user connected', socket.id);
|
||||
nodeIo.on("connection", (socket) => {
|
||||
log.INFO("a user connected", socket.id);
|
||||
|
||||
socket.on('node-login', async (data) => {
|
||||
socket.on("node-login", async (data) => {
|
||||
await nodeLoginWrapper(data, socket);
|
||||
await socket.emit('node-login-successful');
|
||||
})
|
||||
await socket.emit("node-login-successful");
|
||||
});
|
||||
|
||||
socket.on('node-update', async (data) => {
|
||||
socket.on("node-update", async (data) => {
|
||||
let tempPromises = [];
|
||||
tempPromises.push(nodeUpdateWrapper(data.node));
|
||||
tempPromises.push(nearbySystemsUpdateWraper(data.node.nuid, data.nearbySystems));
|
||||
tempPromises.push(
|
||||
nearbySystemsUpdateWraper(data.node.nuid, data.nearbySystems),
|
||||
);
|
||||
|
||||
await Promise.all(tempPromises);
|
||||
|
||||
await socket.emit('node-update-successful')
|
||||
})
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
nodeDisconnectWrapper(socket.id);
|
||||
await socket.emit("node-update-successful");
|
||||
});
|
||||
|
||||
socket.on("disconnect", () => {
|
||||
nodeDisconnectWrapper(socket.id);
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,17 @@
|
||||
import { DebugBuilder } from "../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "socketServerWrappers");
|
||||
import { createNode, getNodeByNuid, updateNodeByNuid } from "./mongo-wrappers/mongoNodesWrappers.mjs"
|
||||
import { createSystem, getSystemByName, updateSystemByName, getSystemsByNuid, deleteSystemByName } from "./mongo-wrappers/mongoSystemsWrappers.mjs"
|
||||
import {
|
||||
createNode,
|
||||
getNodeByNuid,
|
||||
updateNodeByNuid,
|
||||
} from "./mongo-wrappers/mongoNodesWrappers.mjs";
|
||||
import {
|
||||
createSystem,
|
||||
getSystemByName,
|
||||
updateSystemByName,
|
||||
getSystemsByNuid,
|
||||
deleteSystemByName,
|
||||
} from "./mongo-wrappers/mongoSystemsWrappers.mjs";
|
||||
|
||||
/**
|
||||
* Description
|
||||
@@ -15,7 +25,7 @@ const sendNodeCommand = async (socket, command, data) => {
|
||||
// TODO - Check to see if the socket is alive?
|
||||
// TODO - Validate the given data
|
||||
socket.emit(command, data);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Log the node into the network
|
||||
@@ -32,7 +42,7 @@ export const nodeLoginWrapper = async (data, socket) => {
|
||||
log.DEBUG("Added new node to the database:", insertedId);
|
||||
} else {
|
||||
// Check for updates
|
||||
const updatedNode = await updateNodeByNuid(data.nuid, data)
|
||||
const updatedNode = await updateNodeByNuid(data.nuid, data);
|
||||
log.DEBUG("Updated node:", updatedNode);
|
||||
}
|
||||
|
||||
@@ -42,7 +52,7 @@ export const nodeLoginWrapper = async (data, socket) => {
|
||||
socket.node = node;
|
||||
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnect the client from the server
|
||||
@@ -52,7 +62,7 @@ export const nodeLoginWrapper = async (data, socket) => {
|
||||
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
|
||||
@@ -63,7 +73,7 @@ export const nodeUpdateWrapper = async (nodeData) => {
|
||||
log.DEBUG("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
|
||||
@@ -84,14 +94,18 @@ export const nearbySystemsUpdateWraper = async (nuid, nearbySystems) => {
|
||||
|
||||
log.DEBUG("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) {
|
||||
if (existingSystem.nodes.filter((node) => node !== nuid).length === 0) {
|
||||
// Remove the system if so
|
||||
log.INFO("Given node was the only node on this system, removing the system...");
|
||||
log.INFO(
|
||||
"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
|
||||
log.INFO("Other nodes found on this system, removing the given NUID");
|
||||
existingSystem.nodes = existingSystem.nodes.filter(node => node !== nuid);
|
||||
existingSystem.nodes = existingSystem.nodes.filter(
|
||||
(node) => node !== nuid,
|
||||
);
|
||||
log.DEBUG(existingSystem);
|
||||
await updateSystemByName(existingSystem.name, existingSystem);
|
||||
}
|
||||
@@ -104,14 +118,20 @@ export const nearbySystemsUpdateWraper = async (nuid, nearbySystems) => {
|
||||
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)) {
|
||||
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);
|
||||
const updateResults = await updateSystemByName(
|
||||
nearbySystem,
|
||||
existingSystem,
|
||||
);
|
||||
if (updateResults) log.INFO("System updated", nearbySystem);
|
||||
}
|
||||
} else {
|
||||
@@ -125,19 +145,24 @@ export const nearbySystemsUpdateWraper = async (nuid, nearbySystems) => {
|
||||
}
|
||||
|
||||
// Update the system with the added node
|
||||
const updateResults = await updateSystemByName(nearbySystem, nearbySystems[nearbySystem]);
|
||||
const updateResults = await updateSystemByName(
|
||||
nearbySystem,
|
||||
nearbySystems[nearbySystem],
|
||||
);
|
||||
if (updateResults) log.INFO("System updated", nearbySystem);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Create a new system
|
||||
const newSystem = await createSystem(nearbySystem, nearbySystems[nearbySystem], nuid);
|
||||
const newSystem = await createSystem(
|
||||
nearbySystem,
|
||||
nearbySystems[nearbySystem],
|
||||
nuid,
|
||||
);
|
||||
log.INFO("New system created", nearbySystem, newSystem);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the open socket connection ID for a node from the NUID
|
||||
@@ -147,13 +172,12 @@ export const nearbySystemsUpdateWraper = async (nuid, nearbySystems) => {
|
||||
export const getSocketIdByNuid = async (nodeIo, nuid) => {
|
||||
const openSockets = await nodeIo.allSockets();
|
||||
for (const openSocketId of openSockets) {
|
||||
log.DEBUG(openSockets)
|
||||
log.DEBUG(openSockets);
|
||||
const openSocket = await nodeIo.sockets.sockets.get(openSocketId);
|
||||
if (openSocket.node.nuid == nuid)
|
||||
return openSocket;
|
||||
if (openSocket.node.nuid == nuid) return openSocket;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all nodes that are connected to a voice channel
|
||||
@@ -164,27 +188,32 @@ export const getSocketIdByNuid = async (nodeIo, nuid) => {
|
||||
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
|
||||
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 => {
|
||||
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) => {
|
||||
openSocket.emit("node-check-connected-status", guildId, (status) => {
|
||||
if (status) {
|
||||
log.INFO("Socket is connected to VC:", openSocket.node.name, status);
|
||||
log.INFO(
|
||||
"Socket is connected to VC:",
|
||||
openSocket.node.name,
|
||||
status,
|
||||
);
|
||||
socketsConnectedToVC.push(openSocket);
|
||||
} else {
|
||||
log.INFO("Socket is NOT connected to VC:", openSocket.node.name);
|
||||
}
|
||||
res();
|
||||
})
|
||||
});
|
||||
}));
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
return socketsConnectedToVC;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the given node has an open discord client
|
||||
@@ -195,19 +224,30 @@ 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) => {
|
||||
log.INFO(
|
||||
"Checking if socket has an open connection:",
|
||||
openSocket.node.name,
|
||||
);
|
||||
openSocket.emit("node-check-discord-open-client", (status) => {
|
||||
if (status) {
|
||||
log.INFO("Socket has an open discord client:", openSocket.node.name, status);
|
||||
log.INFO(
|
||||
"Socket has an open discord client:",
|
||||
openSocket.node.name,
|
||||
status,
|
||||
);
|
||||
hasOpenDiscordClient = true;
|
||||
} else {
|
||||
log.INFO("Socket does NOT have an open discord client:", openSocket.node.name);
|
||||
log.INFO(
|
||||
"Socket does NOT have an open discord client:",
|
||||
openSocket.node.name,
|
||||
);
|
||||
}
|
||||
res();
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
return hasOpenDiscordClient;
|
||||
}
|
||||
};
|
||||
|
||||
export const getNodeCurrentListeningSystem = async (openSocket) => {
|
||||
const hasOpenClient = checkIfNodeHasOpenDiscordClient(openSocket);
|
||||
@@ -216,19 +256,30 @@ export const getNodeCurrentListeningSystem = async (openSocket) => {
|
||||
// check what system the socket is listening to
|
||||
let currentSystem = undefined;
|
||||
await new Promise((res) => {
|
||||
openSocket.emit('node-check-current-system', (system) => {
|
||||
log.INFO(
|
||||
"Checking system node is currently listening to:",
|
||||
openSocket.node.name,
|
||||
);
|
||||
openSocket.emit("node-check-current-system", (system) => {
|
||||
if (system) {
|
||||
log.INFO("Socket is listening to system:", openSocket.node.name, system);
|
||||
log.INFO(
|
||||
"Socket is listening to system:",
|
||||
openSocket.node.name,
|
||||
system,
|
||||
);
|
||||
currentSystem = system;
|
||||
} else {
|
||||
log.INFO("Socket is not currently listening to a system:", openSocket.node.name);
|
||||
log.INFO(
|
||||
"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
|
||||
@@ -237,14 +288,17 @@ export const getNodeCurrentListeningSystem = async (openSocket) => {
|
||||
* @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);
|
||||
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
|
||||
@@ -254,11 +308,11 @@ export const checkIfNodeIsConnectedToVC = async (nodeIo, guildId, nuid) => {
|
||||
*/
|
||||
export const getNodeDiscordUsername = async (socket, guildId) => {
|
||||
return await new Promise((res) => {
|
||||
socket.emit('node-get-discord-username', guildId, (username) => {
|
||||
socket.emit("node-get-discord-username", guildId, (username) => {
|
||||
res(username);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the discord ID from a given socket
|
||||
@@ -267,11 +321,11 @@ export const getNodeDiscordUsername = async (socket, guildId) => {
|
||||
*/
|
||||
export const getNodeDiscordID = async (socket) => {
|
||||
return await new Promise((res) => {
|
||||
socket.emit('node-get-discord-id', (discordID) => {
|
||||
socket.emit("node-get-discord-id", (discordID) => {
|
||||
res(discordID);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Request a given socket node to join a given voice channel
|
||||
@@ -279,16 +333,21 @@ export const getNodeDiscordID = async (socket) => {
|
||||
* @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") => {
|
||||
export const requestNodeJoinSystem = async (
|
||||
socket,
|
||||
systemName,
|
||||
discordChanelId,
|
||||
discordToken = "MTE5NjAwNTM2ODYzNjExMjk3Nw.GuCMXg.24iNNofNNumq46FIj68zMe9RmQgugAgfrvelEA",
|
||||
) => {
|
||||
// Join the system
|
||||
const joinData = {
|
||||
'clientID': discordToken,
|
||||
'channelID': discordChanelId,
|
||||
'system': systemName
|
||||
}
|
||||
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
|
||||
@@ -298,19 +357,18 @@ export const requestNodeJoinSystem = async (socket, systemName, discordChanelId,
|
||||
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) => {
|
||||
await sendNodeCommand(socket, "node-update", (status) => {
|
||||
if (status) {
|
||||
log.INFO("Node is out of date, updating now", socket.node.name);
|
||||
} else {
|
||||
log.INFO("Node is up to date", socket.node.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
3990
package-lock.json
generated
3990
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
27
package.json
27
package.json
@@ -4,26 +4,37 @@
|
||||
"description": "",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"test": "jasmine",
|
||||
"docs": "jsdoc -c jsdoc.json -d docs -t ./node_modules/better-docs",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint --fix .",
|
||||
"test": "mocha --timeout 5000",
|
||||
"start": "node server.js"
|
||||
},
|
||||
"author": "Logan Cusano",
|
||||
"license": "ISC",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"chai": "^5.1.0",
|
||||
"better-docs": "^2.7.3",
|
||||
"chai": "^5.1.1",
|
||||
"eslint": "^9.9.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-unused-imports": "^4.1.3",
|
||||
"jsdoc": "^4.0.3",
|
||||
"mocha": "^10.4.0",
|
||||
"prettier": "^3.3.3",
|
||||
"socket.io-client": "^4.7.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"discord.js": "^14.14.1",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"mongodb": "^6.3.0",
|
||||
"discord.js": "^14.15.2",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2",
|
||||
"mongodb": "^6.7.0",
|
||||
"morgan": "^1.10.0",
|
||||
"node-html-parser": "^6.1.13",
|
||||
"openai": "^4.47.3",
|
||||
"rss-parser": "^3.13.0",
|
||||
"socket.io": "^4.7.2",
|
||||
"user-agents": "^1.1.208"
|
||||
"socket.io": "^4.7.5",
|
||||
"user-agents": "^1.1.222"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,37 @@
|
||||
import { getAllFeeds, deleteFeedByLink, createPost, getPostByPostId } from '../modules/mongo-wrappers/mongoFeedsWrappers.mjs';
|
||||
import crypto from 'crypto';
|
||||
import { sendPost } from '../discordBot/modules/rssWrappers.mjs';
|
||||
import {
|
||||
getAllFeeds,
|
||||
deleteFeedByLink,
|
||||
createPost,
|
||||
getPostByPostId,
|
||||
} from "../modules/mongo-wrappers/mongoFeedsWrappers.mjs";
|
||||
import crypto from "crypto";
|
||||
import { sendPost } from "../discordBot/modules/rssWrappers.mjs";
|
||||
import { DebugBuilder } from "../modules/debugger.mjs";
|
||||
import { removeSource } from './sourceManager.mjs'
|
||||
import { removeSource } from "./sourceManager.mjs";
|
||||
import UserAgent from "user-agents";
|
||||
import Parser from 'rss-parser';
|
||||
import Parser from "rss-parser";
|
||||
import PresenceManager from "../discordBot/modules/presenceManager.mjs";
|
||||
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config()
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
// Initialize the User-Agent string
|
||||
process.env.USER_AGENT_STRING = new UserAgent({ platform: 'Win32' }).toString();
|
||||
process.env.USER_AGENT_STRING = new UserAgent({ platform: "Win32" }).toString();
|
||||
|
||||
const parser = new Parser({
|
||||
headers: {
|
||||
'User-Agent': process.env.USER_AGENT_STRING,
|
||||
"Accept": "application/rss+xml,application/xhtml+xml,application/xml"
|
||||
}
|
||||
"User-Agent": process.env.USER_AGENT_STRING,
|
||||
Accept: "application/rss+xml,application/xhtml+xml,application/xml",
|
||||
},
|
||||
});
|
||||
|
||||
const log = new DebugBuilder("server", "feedHandler");
|
||||
|
||||
export const returnHash = (...stringsIncluded) => {
|
||||
return crypto.createHash('sha1').update(stringsIncluded.join("-<<??//\\\\??>>-")).digest("base64");
|
||||
return crypto
|
||||
.createHash("sha1")
|
||||
.update(stringsIncluded.join("-<<??//\\\\??>>-"))
|
||||
.digest("base64");
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -33,23 +42,34 @@ export const returnHash = (...stringsIncluded) => {
|
||||
export const updateFeeds = async (client) => {
|
||||
if (!client) throw new Error("Client object not passed");
|
||||
|
||||
// Setup presence manager
|
||||
const feedPm = new PresenceManager(client);
|
||||
await feedPm.setPresence("online", "WATCHING", "for RSS feed updates");
|
||||
|
||||
try {
|
||||
const records = await getAllFeeds();
|
||||
|
||||
const sourcePromiseArray = records.map(async (source) => {
|
||||
log.DEBUG('Processing source:', source.title);
|
||||
log.DEBUG("Processing source:", source.title);
|
||||
|
||||
try {
|
||||
const parsedFeed = await parser.parseURL(source.link);
|
||||
|
||||
if (parsedFeed?.items) {
|
||||
await Promise.all(parsedFeed.items.reverse().map(async (post) => {
|
||||
await Promise.all(
|
||||
parsedFeed.items.reverse().map(async (post) => {
|
||||
log.DEBUG("Processing post:", post.title);
|
||||
|
||||
if (!post.title || !post.link) throw new Error("Missing title or link in the post");
|
||||
if (!post.content && !post['content:encoded']) log.WARN("No content for post:", post.title);
|
||||
if (!post.title || !post.link)
|
||||
throw new Error("Missing title or link in the post");
|
||||
if (!post.content && !post["content:encoded"])
|
||||
log.WARN("No content for post:", post.title);
|
||||
|
||||
post.postId = post.postId ?? post.guid ?? post.id ?? returnHash(post.title, post.link, post.pubDate ?? Date.now());
|
||||
post.postId =
|
||||
post.postId ??
|
||||
post.guid ??
|
||||
post.id ??
|
||||
returnHash(post.title, post.link, post.pubDate ?? Date.now());
|
||||
|
||||
const existingRecord = await getPostByPostId(post.postId);
|
||||
if (!existingRecord) {
|
||||
@@ -57,7 +77,11 @@ export const updateFeeds = async (client) => {
|
||||
const sendResults = await sendPost(post, source, channel);
|
||||
if (!sendResults) throw new Error("Failed to send post");
|
||||
|
||||
log.DEBUG("Saving post to database:", post.title, source.channel_id);
|
||||
log.DEBUG(
|
||||
"Saving post to database:",
|
||||
post.title,
|
||||
source.channel_id,
|
||||
);
|
||||
|
||||
const postToSave = {
|
||||
title: post.title,
|
||||
@@ -67,13 +91,14 @@ export const updateFeeds = async (client) => {
|
||||
contentSnippet: post.contentSnippet,
|
||||
id: post.id,
|
||||
isoDate: post.isoDate,
|
||||
postId: post.postId
|
||||
postId: post.postId,
|
||||
};
|
||||
|
||||
await createPost(postToSave);
|
||||
log.DEBUG("Post saved:", postToSave);
|
||||
}
|
||||
}));
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
await deleteFeedByLink(source.link);
|
||||
}
|
||||
@@ -86,6 +111,7 @@ export const updateFeeds = async (client) => {
|
||||
|
||||
await Promise.all(sourcePromiseArray);
|
||||
log.DEBUG("All sources processed");
|
||||
await feedPm.resetToDefault();
|
||||
} catch (error) {
|
||||
log.ERROR("Error updating feeds:", error);
|
||||
throw error;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { DebugBuilder } from "../modules/debugger.mjs";
|
||||
import { updateFeeds } from "./feedHandler.mjs";
|
||||
import dotenv from 'dotenv';
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
const log = new DebugBuilder("server", "rssController");
|
||||
@@ -25,7 +25,6 @@ export class RSSController {
|
||||
this.intervalId = setInterval(async () => {
|
||||
await this.collectLatestPosts();
|
||||
}, refreshInterval);
|
||||
|
||||
} catch (error) {
|
||||
log.ERROR(`Failed to start RSS Controller: ${error.message}`);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { createFeed, getFeedByLink, deleteFeedByLink } from '../modules/mongo-wrappers/mongoFeedsWrappers.mjs';
|
||||
import { DebugBuilder } from "../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "sourceManager");
|
||||
import {
|
||||
createFeed,
|
||||
getFeedByLink,
|
||||
deleteFeedByLink,
|
||||
} from "../modules/mongo-wrappers/mongoFeedsWrappers.mjs";
|
||||
|
||||
class SourceManager {
|
||||
constructor(sourceFailureLimit) {
|
||||
@@ -13,7 +19,11 @@ class SourceManager {
|
||||
const sourceData = this.runningSourcesToRemove[sourceURL];
|
||||
|
||||
if (!sourceData) {
|
||||
this.runningSourcesToRemove[sourceURL] = { count: 1, timestamp: currentTime, ignoredAttempts: 0 };
|
||||
this.runningSourcesToRemove[sourceURL] = {
|
||||
count: 1,
|
||||
timestamp: currentTime,
|
||||
ignoredAttempts: 0,
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -54,7 +64,13 @@ class SourceManager {
|
||||
|
||||
async addSource(title, link, category, guildId, channelId, callback) {
|
||||
try {
|
||||
const feed = { title, link, category, guild_id: guildId, channel_id: channelId };
|
||||
const feed = {
|
||||
title,
|
||||
link,
|
||||
category,
|
||||
guild_id: guildId,
|
||||
channel_id: channelId,
|
||||
};
|
||||
const record = await createFeed(feed);
|
||||
log.DEBUG("Source added:", record);
|
||||
if (callback) callback(null, record);
|
||||
@@ -65,11 +81,12 @@ class SourceManager {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Create a default instance of SourceManager
|
||||
const defaultSourceManager = new SourceManager();
|
||||
|
||||
// Export the class and default instance methods
|
||||
export { SourceManager };
|
||||
export const addSource = defaultSourceManager.addSource.bind(defaultSourceManager);
|
||||
export const removeSource = defaultSourceManager.removeSource.bind(defaultSourceManager);
|
||||
export const addSource =
|
||||
defaultSourceManager.addSource.bind(defaultSourceManager);
|
||||
export const removeSource =
|
||||
defaultSourceManager.removeSource.bind(defaultSourceManager);
|
||||
|
||||
10
server.js
10
server.js
@@ -1,11 +1,11 @@
|
||||
import { DebugBuilder } from "./modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "server");
|
||||
import { nodeIo, app, server } from './modules/socketServer.mjs';
|
||||
import { loadAddons } from './modules/addonManager.mjs';
|
||||
import { serverClient, addEnabledEventListeners } from './discordBot/discordBot.mjs';
|
||||
import { nodeIo, server } from "./modules/socketServer.mjs";
|
||||
import { loadAddons } from "./modules/addonManager.mjs";
|
||||
import { serverClient } from "./discordBot/discordBot.mjs";
|
||||
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config()
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
// Startup the node server
|
||||
server.listen(process.env.SERVER_PORT || 3000, () => {
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"spec_dir": "spec",
|
||||
"spec_files": [
|
||||
"**/*[sS]pec.?(m)js"
|
||||
],
|
||||
"helpers": [
|
||||
"helpers/**/*.?(m)js"
|
||||
],
|
||||
"env": {
|
||||
"stopSpecOnExpectationFailure": false,
|
||||
"random": true
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import * as feedHandler from '../../rss-manager/feedHandler.mjs';
|
||||
import * as mw from '../../modules/mongo-wrappers/mongoFeedsWrappers.mjs';
|
||||
import * as drw from '../../discordBot/modules/rssWrappers.mjs';
|
||||
|
||||
describe('feedHandler', () => {
|
||||
it('should call updateFeeds', async () => {
|
||||
// Spy on the updateFeeds function
|
||||
const feedsSpy = spyOn(mw, 'getAllFeeds').and.stub();
|
||||
const sendPostSpy = spyOn(drw, 'sendPost').and.stub();
|
||||
|
||||
// Call the function that triggers updateFeeds
|
||||
// For example:
|
||||
// someFunctionThatCallsUpdateFeeds();
|
||||
console.log(await spyOn(feedHandler, 'updateFeeds').and.callThrough({
|
||||
channels: {
|
||||
cache: {
|
||||
get: () => ([{
|
||||
// Stub methods or properties of the channel object as needed for testing
|
||||
}])
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Add your expectations here to ensure updateFeeds was called
|
||||
expect(feedsSpy).toHaveBeenCalled();
|
||||
expect(sendPostSpy).toHaveBeenCalled();
|
||||
// Add more specific expectations if needed
|
||||
});
|
||||
});
|
||||
@@ -1,494 +0,0 @@
|
||||
// Import necessary modules for testing
|
||||
import ioClient from 'socket.io-client';
|
||||
import { deleteNodeByNuid, getNodeByNuid } from '../../modules/mongo-wrappers/mongoNodesWrappers.mjs';
|
||||
import { deleteSystemByName, getSystemByName } from '../../modules/mongo-wrappers/mongoSystemsWrappers.mjs';
|
||||
import { nodeDisconnectWrapper, checkIfNodeHasOpenDiscordClient, getNodeCurrentListeningSystem, checkIfNodeIsConnectedToVC, getNodeDiscordUsername, getNodeDiscordID, requestBotLeaveServer, requestNodeJoinSystem, requestNodeUpdate } from '../../modules/socketServerWrappers.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"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
describe('Socket Server - Core Tests', () => {
|
||||
// Start the Socket.IO server before running tests
|
||||
let clientSocket; // The socket client
|
||||
let serverClientSocket // The open client socket on the server
|
||||
beforeAll(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
|
||||
afterAll(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])
|
||||
});
|
||||
// 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).toEqual(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['_id']).toBeDefined(); // Check if _id property exists
|
||||
expect(addedNode['nuid']).toEqual(localNodeConfig.node.nuid);
|
||||
expect(addedNode['name']).toEqual(localNodeConfig.node.name);
|
||||
expect(addedNode['location']).toEqual(localNodeConfig.node.location);
|
||||
expect(addedNode['capabilities']).toEqual(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['_id']).toBeDefined(); // Check if _id property exists
|
||||
expect(existingNode['nuid']).toEqual(localNodeConfig.node.nuid);
|
||||
expect(existingNode['name']).toEqual(localNodeConfig.node.name);
|
||||
expect(existingNode['location']).toEqual(localNodeConfig.node.location);
|
||||
expect(existingNode['capabilities']).toEqual(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['_id']).toBeDefined(); // Check if _id property exists
|
||||
expect(updatedNode['nuid']).toEqual(updatedLocalNodeConfig.node.nuid);
|
||||
expect(updatedNode['name']).toEqual(updatedLocalNodeConfig.node.name);
|
||||
expect(updatedNode['location']).toEqual(updatedLocalNodeConfig.node.location);
|
||||
expect(updatedNode['capabilities']).toEqual(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['_id']).toBeDefined(); // Check if _id property exists
|
||||
expect(existingNode['nuid']).toEqual(updatedLocalNodeConfig.node.nuid);
|
||||
expect(existingNode['name']).toEqual(updatedLocalNodeConfig.node.name);
|
||||
expect(existingNode['location']).toEqual(updatedLocalNodeConfig.node.location);
|
||||
expect(existingNode['capabilities']).toEqual(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).toEqual(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['_id']).toBeDefined(); // Check if _id property exists
|
||||
expect(updatedNode['nuid']).toEqual(updatedLocalNodeConfig.node.nuid);
|
||||
expect(updatedNode['name']).toEqual(updatedLocalNodeConfig.node.name);
|
||||
expect(updatedNode['location']).toEqual(updatedLocalNodeConfig.node.location);
|
||||
expect(updatedNode['capabilities']).toEqual(updatedLocalNodeConfig.node.capabilities);
|
||||
|
||||
// Get the updated system
|
||||
const addedSystem = await getSystemByName("Testing P25 System Name");
|
||||
|
||||
console.log("Added system:", addedSystem);
|
||||
|
||||
expect(addedSystem['_id']).toBeDefined(); // Check if _id property exists
|
||||
expect(addedSystem['nodes']).toBeDefined(); // Check if nodes property exists
|
||||
expect(addedSystem.nodes).toEqual(updatedLocalNodeConfig.node.nuid) // Check if this node ID is in the nodes array
|
||||
expect(addedSystem['frequencies']).toEqual(updatedLocalNodeConfig.nearbySystems['Testing P25 System Name'].frequencies);
|
||||
expect(addedSystem['mode']).toEqual(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['_id']).toBeDefined(); // Check if _id property exists
|
||||
expect(existingNode['nuid']).toEqual(updatedLocalNodeConfig.node.nuid);
|
||||
expect(existingNode['name']).toEqual(updatedLocalNodeConfig.node.name);
|
||||
expect(existingNode['location']).toEqual(updatedLocalNodeConfig.node.location);
|
||||
expect(existingNode['capabilities']).toEqual(updatedLocalNodeConfig.node.capabilities);
|
||||
|
||||
// Get the updated system
|
||||
const existingSystem = await getSystemByName("Testing P25 System Name");
|
||||
expect(existingSystem['_id']).toBeDefined(); // Check if _id property exists
|
||||
expect(existingSystem['nodes']).toBeDefined(); // Check if nodes property exists
|
||||
expect(existingSystem.nodes).toContain(updatedLocalNodeConfig.node.nuid); // Check if this node ID is in the nodes array
|
||||
expect(existingSystem['frequencies']).toEqual(updatedLocalNodeConfig.nearbySystems['Testing P25 System Name'].frequencies);
|
||||
expect(existingSystem['mode']).toEqual(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['_id']).toBeDefined(); // Check if _id property exists
|
||||
expect(updatedNode['nuid']).toEqual(localNodeConfig.node.nuid);
|
||||
expect(updatedNode['name']).toEqual(localNodeConfig.node.name);
|
||||
expect(updatedNode['location']).toEqual(localNodeConfig.node.location);
|
||||
expect(updatedNode['capabilities']).toEqual(localNodeConfig.node.capabilities);
|
||||
|
||||
// Get the updated system
|
||||
const updatedSystem = await getSystemByName("Testing P25 System Name");
|
||||
|
||||
console.log("Updated system:", updatedSystem);
|
||||
|
||||
expect(updatedSystem['_id']).toBeDefined(); // Check if _id property exists
|
||||
expect(updatedSystem['nodes']).toBeDefined(); // Check if nodes property exists
|
||||
expect(updatedSystem.nodes).toContain(localNodeConfig.node.nuid); // Check if this node ID is in the nodes array
|
||||
expect(updatedSystem['frequencies']).toEqual(localNodeConfig.nearbySystems['Testing P25 System Name'].frequencies);
|
||||
expect(updatedSystem['mode']).toEqual(localNodeConfig.nearbySystems['Testing P25 System Name'].mode);
|
||||
});
|
||||
});
|
||||
|
||||
// Test getNodeCurrentListeningSystem
|
||||
describe('Get Node Current Listening System', () => {
|
||||
it('should correctly determine if the node is connected to a voice channel', async () => {
|
||||
// Simulate that the client socket is listening to a system
|
||||
const isConnectedToVC = true;
|
||||
const guildId = 'mockGuildId';
|
||||
|
||||
// Emit the event to the server and wait for the response
|
||||
const nodeReply = new Promise((resolve) => {
|
||||
clientSocket.once('node-check-connected-status', (passedGuildId, callback) => {
|
||||
// Check if the passed guild ID matches the expected guild ID
|
||||
expect(passedGuildId).toEqual(guildId);
|
||||
// Simulate receiving the connection status from the client
|
||||
callback(isConnectedToVC);
|
||||
});
|
||||
|
||||
// Call the function to check if the node is connected to a voice channel
|
||||
const response = checkIfNodeIsConnectedToVC(nodeIo, guildId, localNodeConfig.node.nuid);
|
||||
resolve(response);
|
||||
});
|
||||
|
||||
// Wait for the promise to resolve
|
||||
const response = await nodeReply;
|
||||
|
||||
// Assert that the response matches the expected connection status
|
||||
expect(response).toEqual(isConnectedToVC);
|
||||
});
|
||||
});
|
||||
|
||||
// Test checkIfNodeIsConnectedToVC
|
||||
describe('Check if Node is Connected to VC', () => {
|
||||
it('Should correctly determine if the node is connected to a voice channel', async () => {
|
||||
// Simulate that the client socket is listening to a system
|
||||
const isConnectedToVC = true;
|
||||
const guildId = 'mockGuildId';
|
||||
|
||||
// Emit the event to the server and wait for the response
|
||||
const nodeReply = new Promise((resolve) => {
|
||||
clientSocket.once('node-check-connected-status', (passedGuildId, callback) => {
|
||||
// Check if the passed guild ID matches the expected guild ID
|
||||
expect(passedGuildId).to.equal(guildId);
|
||||
// Simulate receiving the connection status from the client
|
||||
callback(isConnectedToVC);
|
||||
});
|
||||
|
||||
// Call the function to check if the node is connected to a voice channel
|
||||
const response = checkIfNodeIsConnectedToVC(nodeIo, guildId, localNodeConfig.node.nuid);
|
||||
resolve(response);
|
||||
});
|
||||
|
||||
// Wait for the promise to resolve
|
||||
const response = await nodeReply;
|
||||
|
||||
// Assert that the response matches the expected connection status
|
||||
expect(response).to.equal(isConnectedToVC);
|
||||
});
|
||||
});
|
||||
|
||||
// Test checkIfNodeHasOpenDiscordClient
|
||||
describe('Check if Node has an open discord client', () => {
|
||||
it('should correctly determine if the node has an open Discord client', async () => {
|
||||
const isDiscordOpen = true;
|
||||
|
||||
// Emit the event to the server and wait for the response
|
||||
const nodeReply = new Promise((resolve) => {
|
||||
clientSocket.once('node-check-discord-open-client', (callback) => {
|
||||
// Simulate receiving the client status from the client
|
||||
callback(isDiscordOpen);
|
||||
});
|
||||
|
||||
// Call the function to check if the node has an open Discord client
|
||||
const response = checkIfNodeHasOpenDiscordClient(serverClientSocket);
|
||||
resolve(response);
|
||||
});
|
||||
|
||||
// Wait for the promise to resolve
|
||||
const response = await nodeReply;
|
||||
|
||||
// Assert that the response matches the expected client status
|
||||
expect(response).toEqual(isDiscordOpen);
|
||||
});
|
||||
});
|
||||
|
||||
// Test getNodeDiscordUsername
|
||||
describe('Get the discord username from the client', () => {
|
||||
it('should request the username from a specific client', async () => {
|
||||
const discordUsername = "Test Discord Username";
|
||||
const guildId = 'mockGuildId';
|
||||
|
||||
// Emit the event to the server and wait for the response
|
||||
const nodeReply = new Promise((resolve) => {
|
||||
clientSocket.once('node-get-discord-username', (passedGuildId, callback) => {
|
||||
// Check if the passed guild ID matches the expected guild ID
|
||||
expect(passedGuildId).toEqual(guildId);
|
||||
// Simulate receiving the username from the client
|
||||
callback(discordUsername);
|
||||
});
|
||||
|
||||
// Call the function to get the Discord username
|
||||
const username = getNodeDiscordUsername(nodeIo, guildId, localNodeConfig.node.nuid);
|
||||
resolve(username);
|
||||
});
|
||||
|
||||
// Wait for the promise to resolve
|
||||
const username = await nodeReply;
|
||||
|
||||
// Assert that the username matches the expected username
|
||||
expect(username).toEqual(discordUsername);
|
||||
});
|
||||
});
|
||||
|
||||
// Test getNodeDiscordID
|
||||
describe('Get the discord ID from the client', () => {
|
||||
it('Should get the ID from the client', async () => {
|
||||
// Mocked Discord ID
|
||||
const discordId = "mockDiscordID";
|
||||
|
||||
// Emit the event to the server and wait for the response
|
||||
const nodeReply = new Promise((resolve) => {
|
||||
// Listen for the 'node-get-discord-id' event from the server
|
||||
clientSocket.once('node-get-discord-id', (callback) => {
|
||||
// Simulate receiving the Discord ID from the client
|
||||
callback(discordId);
|
||||
});
|
||||
|
||||
// Call the function to get the Discord ID
|
||||
const response = getNodeDiscordID(serverClientSocket);
|
||||
resolve(response);
|
||||
});
|
||||
|
||||
// Wait for the promise to resolve
|
||||
const response = await nodeReply;
|
||||
|
||||
// Assert that the response matches the expected Discord ID
|
||||
expect(response).toEqual(discordId);
|
||||
});
|
||||
});
|
||||
|
||||
// Test requestNodeJoinSystem
|
||||
describe('Request Node Join System', () => {
|
||||
it('Should send a request to the node to join a system', async () => {
|
||||
const systemName = 'mockSystemName';
|
||||
const channelId = 'mockChannelId';
|
||||
const token = 'mockToken';
|
||||
|
||||
// Emit the event to the server and wait for the response
|
||||
await new Promise(async (resolve) => {
|
||||
clientSocket.once('node-join', (joinData) => {
|
||||
// Check if the passed system ID matches the expected system ID
|
||||
expect(joinData.clientID).toEqual(token);
|
||||
expect(joinData.channelID).toEqual(channelId);
|
||||
expect(joinData.system).toEqual(systemName);
|
||||
// Simulate receiving a success callback from the client
|
||||
resolve();
|
||||
});
|
||||
|
||||
// Call the function to request joining a system
|
||||
requestNodeJoinSystem(serverClientSocket, systemName, channelId, token);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Test requestNodeLeaveSystem
|
||||
describe('Request Node Leave System', () => {
|
||||
it('Should send a request to the node to leave a given server', async () => {
|
||||
const guildId = 'mockGuildId';
|
||||
|
||||
// Emit the event to the server and wait for the response
|
||||
await new Promise(async (resolve) => {
|
||||
clientSocket.once('node-leave', (passedGuildId) => {
|
||||
// Check if the passed system ID matches the expected system ID
|
||||
expect(passedGuildId).toEqual(guildId);
|
||||
// Simulate receiving a success callback from the client
|
||||
resolve();
|
||||
});
|
||||
|
||||
// Call the function to request joining a system
|
||||
requestBotLeaveServer(serverClientSocket, guildId);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Test requestNodeUpdate
|
||||
describe('Request Node Update', () => {
|
||||
it('Should send the node a request to check for an update', async () => {
|
||||
// Emit the event to the server and wait for the response
|
||||
await new Promise((resolve) => {
|
||||
clientSocket.once('node-request-update', (callback) => {
|
||||
// Simulate an out of date request
|
||||
expect(callback).toBeDefined();
|
||||
callback(true);
|
||||
});
|
||||
|
||||
// Call the function to request updating node information
|
||||
const response = requestNodeUpdate(serverClientSocket);
|
||||
resolve(response);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Test nodeDisconnectWrapper
|
||||
describe('Node Disconnect Wrapper', () => {
|
||||
it('Should disconnect the node and trigger cleanup actions', async () => {
|
||||
// Mock the socket ID
|
||||
const socketId = 'mockSocketId';
|
||||
|
||||
// Call the nodeDisconnectWrapper function
|
||||
const result = await nodeDisconnectWrapper(socketId);
|
||||
|
||||
// Assert that the result is as expected (if any)
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,39 +1,50 @@
|
||||
// Import necessary modules for testing
|
||||
import { expect } from 'chai';
|
||||
import ioClient from 'socket.io-client';
|
||||
import { deleteNodeByNuid, getNodeByNuid } from '../modules/mongo-wrappers/mongoNodesWrappers.mjs';
|
||||
import { deleteSystemByName, getSystemByName } from '../modules/mongo-wrappers/mongoSystemsWrappers.mjs';
|
||||
import { nodeDisconnectWrapper, checkIfNodeHasOpenDiscordClient, getNodeCurrentListeningSystem, checkIfNodeIsConnectedToVC, getNodeDiscordUsername, getNodeDiscordID, requestBotLeaveServer, requestNodeJoinSystem, requestNodeUpdate } from '../modules/socketServerWrappers.mjs';
|
||||
import { nodeIo } from '../modules/socketServer.mjs';
|
||||
import { expect } from "chai";
|
||||
import ioClient from "socket.io-client";
|
||||
import {
|
||||
deleteNodeByNuid,
|
||||
getNodeByNuid,
|
||||
} from "../modules/mongo-wrappers/mongoNodesWrappers.mjs";
|
||||
import {
|
||||
deleteSystemByName,
|
||||
getSystemByName,
|
||||
} from "../modules/mongo-wrappers/mongoSystemsWrappers.mjs";
|
||||
import {
|
||||
nodeDisconnectWrapper,
|
||||
checkIfNodeHasOpenDiscordClient,
|
||||
getNodeCurrentListeningSystem,
|
||||
checkIfNodeIsConnectedToVC,
|
||||
getNodeDiscordUsername,
|
||||
getNodeDiscordID,
|
||||
requestBotLeaveServer,
|
||||
requestNodeJoinSystem,
|
||||
requestNodeUpdate,
|
||||
} from "../modules/socketServerWrappers.mjs";
|
||||
import { nodeIo } from "../modules/socketServer.mjs";
|
||||
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config()
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
process.env.SERVER_PORT = 6000
|
||||
process.env.SERVER_PORT = 6000;
|
||||
|
||||
// Define necessary variables for testing, such as mocked database connections or socket instances
|
||||
const localNodeConfig = {
|
||||
serverIp: 'localhost',
|
||||
serverIp: "localhost",
|
||||
serverPort: process.env.SERVER_PORT,
|
||||
node: {
|
||||
nuid: "4f29a6340901a12affc87047c0ac16b01b92496c460c880a2459abe8c7928374",
|
||||
name: "testyv7",
|
||||
location: "china",
|
||||
capabilities: ["radio"]
|
||||
capabilities: ["radio"],
|
||||
},
|
||||
nearbySystems: {
|
||||
"Testing P25 System Name": {
|
||||
"frequencies": [
|
||||
155344000,
|
||||
155444000,
|
||||
155555000,
|
||||
155588550
|
||||
],
|
||||
"mode": "p25",
|
||||
"trunkFile": "trunk.tsv",
|
||||
"whitelistFile": "whitelist.tsv"
|
||||
}
|
||||
}
|
||||
frequencies: [155344000, 155444000, 155555000, 155588550],
|
||||
mode: "p25",
|
||||
trunkFile: "trunk.tsv",
|
||||
whitelistFile: "whitelist.tsv",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const updatedLocalNodeConfig = {
|
||||
@@ -41,39 +52,39 @@ const updatedLocalNodeConfig = {
|
||||
nuid: localNodeConfig.node.nuid,
|
||||
name: "updatedName",
|
||||
location: "updatedLocation",
|
||||
capabilities: ["radio", "weather"] // Updated capabilities
|
||||
capabilities: ["radio", "weather"], // Updated capabilities
|
||||
},
|
||||
nearbySystems: {
|
||||
"Testing P25 System Name": {
|
||||
"frequencies": [
|
||||
155444000,
|
||||
155555000,
|
||||
155500000
|
||||
],
|
||||
"mode": "p25",
|
||||
"trunkFile": "trunk2.tsv",
|
||||
"whitelistFile": "whitelist2.tsv"
|
||||
}
|
||||
}
|
||||
frequencies: [155444000, 155555000, 155500000],
|
||||
mode: "p25",
|
||||
trunkFile: "trunk2.tsv",
|
||||
whitelistFile: "whitelist2.tsv",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('Socket Server - Core Tests', () => {
|
||||
describe("Socket Server - Core Tests", () => {
|
||||
// Start the Socket.IO server before running tests
|
||||
let clientSocket; // The socket client
|
||||
let serverClientSocket // The open client socket on the server
|
||||
before(done => {
|
||||
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}`);
|
||||
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}`);
|
||||
clientSocket = ioClient.connect(
|
||||
`http://localhost:${process.env.SERVER_PORT}`,
|
||||
);
|
||||
|
||||
nodeIo.on('connection', (socket) => {
|
||||
nodeIo.on("connection", (socket) => {
|
||||
serverClientSocket = socket;
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// Close the Socket.IO server after running tests
|
||||
@@ -86,11 +97,11 @@ describe('Socket Server - Core Tests', () => {
|
||||
|
||||
// Remove the test data
|
||||
deleteNodeByNuid(localNodeConfig.node.nuid); // Delete the user
|
||||
deleteSystemByName(Object.keys(localNodeConfig.nearbySystems)[0])
|
||||
deleteSystemByName(Object.keys(localNodeConfig.nearbySystems)[0]);
|
||||
});
|
||||
// Test Node Login functionality
|
||||
describe('Node Login', () => {
|
||||
it('Should add a new node if it does not exist', async () => {
|
||||
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);
|
||||
@@ -99,8 +110,8 @@ describe('Socket Server - Core Tests', () => {
|
||||
expect(existingNode).to.be.null;
|
||||
|
||||
// Wait for the update
|
||||
const node_login = new Promise(res => {
|
||||
clientSocket.on('node-login-successful', async () => {
|
||||
const node_login = new Promise((res) => {
|
||||
clientSocket.on("node-login-successful", async () => {
|
||||
res();
|
||||
});
|
||||
});
|
||||
@@ -118,27 +129,39 @@ describe('Socket Server - Core Tests', () => {
|
||||
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 () => {
|
||||
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);
|
||||
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 () => {
|
||||
const node_login = new Promise((res) => {
|
||||
clientSocket.on("node-login-successful", async () => {
|
||||
res();
|
||||
});
|
||||
});
|
||||
@@ -156,28 +179,52 @@ describe('Socket Server - Core Tests', () => {
|
||||
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);
|
||||
})
|
||||
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 () => {
|
||||
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);
|
||||
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");
|
||||
@@ -186,8 +233,8 @@ describe('Socket Server - Core Tests', () => {
|
||||
expect(existsingSystem).to.be.null;
|
||||
|
||||
// Wait for the update
|
||||
const node_system_update = new Promise(res => {
|
||||
clientSocket.on('node-update-successful', async () => {
|
||||
const node_system_update = new Promise((res) => {
|
||||
clientSocket.on("node-update-successful", async () => {
|
||||
res();
|
||||
});
|
||||
});
|
||||
@@ -205,47 +252,84 @@ describe('Socket Server - Core Tests', () => {
|
||||
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);
|
||||
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);
|
||||
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 () => {
|
||||
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);
|
||||
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);
|
||||
|
||||
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 () => {
|
||||
const node_update = new Promise((res) => {
|
||||
clientSocket.on("node-update-successful", async () => {
|
||||
res();
|
||||
});
|
||||
});
|
||||
@@ -261,35 +345,46 @@ describe('Socket Server - Core Tests', () => {
|
||||
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);
|
||||
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);
|
||||
|
||||
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,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Test getNodeCurrentListeningSystem
|
||||
describe('Get Node Current Listening System', () => {
|
||||
it('Should return the current listening system for the node', async () => {
|
||||
describe("Get Node Current Listening System", () => {
|
||||
it("Should return the current listening system for the node", async () => {
|
||||
// Simulate that the client socket is listening to a system
|
||||
const listeningSystem = 'Testing P25 System Name';
|
||||
const listeningSystem = "Testing P25 System Name";
|
||||
|
||||
// Emit the event to the server and wait for the response
|
||||
const nodeReply = new Promise((resolve) => {
|
||||
clientSocket.once('node-check-current-system', (callback) => {
|
||||
clientSocket.once("node-check-current-system", (callback) => {
|
||||
// Simulate receiving the current listening system from the client
|
||||
callback(listeningSystem);
|
||||
});
|
||||
@@ -308,23 +403,30 @@ describe('Socket Server - Core Tests', () => {
|
||||
});
|
||||
|
||||
// Test checkIfNodeIsConnectedToVC
|
||||
describe('Check if Node is Connected to VC', () => {
|
||||
it('Should correctly determine if the node is connected to a voice channel', async () => {
|
||||
describe("Check if Node is Connected to VC", () => {
|
||||
it("Should correctly determine if the node is connected to a voice channel", async () => {
|
||||
// Simulate that the client socket is listening to a system
|
||||
const isConnectedToVC = true;
|
||||
const guildId = 'mockGuildId';
|
||||
const guildId = "mockGuildId";
|
||||
|
||||
// Emit the event to the server and wait for the response
|
||||
const nodeReply = new Promise((resolve) => {
|
||||
clientSocket.once('node-check-connected-status', (passedGuildId, callback) => {
|
||||
clientSocket.once(
|
||||
"node-check-connected-status",
|
||||
(passedGuildId, callback) => {
|
||||
// Check if the passed guild ID matches the expected guild ID
|
||||
expect(passedGuildId).to.equal(guildId);
|
||||
// Simulate receiving the connection status from the client
|
||||
callback(isConnectedToVC);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Call the function to check if the node is connected to a voice channel
|
||||
const response = checkIfNodeIsConnectedToVC(nodeIo, guildId, localNodeConfig.node.nuid);
|
||||
const response = checkIfNodeIsConnectedToVC(
|
||||
nodeIo,
|
||||
guildId,
|
||||
localNodeConfig.node.nuid,
|
||||
);
|
||||
resolve(response);
|
||||
});
|
||||
|
||||
@@ -337,13 +439,13 @@ describe('Socket Server - Core Tests', () => {
|
||||
});
|
||||
|
||||
// Test checkIfNodeHasOpenDiscordClient
|
||||
describe('Check if Node has an open discord client', () => {
|
||||
it('Should correctly determine if the node has an open Discord client', async () => {
|
||||
describe("Check if Node has an open discord client", () => {
|
||||
it("Should correctly determine if the node has an open Discord client", async () => {
|
||||
const isDiscordOpen = true;
|
||||
|
||||
// Emit the event to the server and wait for the response
|
||||
const nodeReply = new Promise((resolve) => {
|
||||
clientSocket.once('node-check-discord-open-client', (callback) => {
|
||||
clientSocket.once("node-check-discord-open-client", (callback) => {
|
||||
// Simulate receiving the client status from the client
|
||||
callback(isDiscordOpen);
|
||||
});
|
||||
@@ -362,19 +464,22 @@ describe('Socket Server - Core Tests', () => {
|
||||
});
|
||||
|
||||
// Test getNodeDiscordUsername
|
||||
describe('Get the discord username from the client', () => {
|
||||
it('Should request the username from a specific client', async () => {
|
||||
describe("Get the discord username from the client", () => {
|
||||
it("Should request the username from a specific client", async () => {
|
||||
const discordUsername = "Test Discord Username";
|
||||
const guildId = 'mockGuildId';
|
||||
const guildId = "mockGuildId";
|
||||
|
||||
// Emit the event to the server and wait for the response
|
||||
const nodeReply = new Promise((resolve) => {
|
||||
clientSocket.once('node-get-discord-username', (passedGuildId, callback) => {
|
||||
clientSocket.once(
|
||||
"node-get-discord-username",
|
||||
(passedGuildId, callback) => {
|
||||
// Check if the passed guild ID matches the expected guild ID
|
||||
expect(passedGuildId).to.equal(guildId);
|
||||
// Simulate receiving the Discord username from the client
|
||||
callback(discordUsername);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Call the function to get the Discord username
|
||||
const response = getNodeDiscordUsername(serverClientSocket, guildId);
|
||||
@@ -390,15 +495,15 @@ describe('Socket Server - Core Tests', () => {
|
||||
});
|
||||
|
||||
// Test getNodeDiscordID
|
||||
describe('Get the discord ID from the client', () => {
|
||||
it('Should get the ID from the client', async () => {
|
||||
describe("Get the discord ID from the client", () => {
|
||||
it("Should get the ID from the client", async () => {
|
||||
// Mocked Discord ID
|
||||
const discordId = "mockDiscordID";
|
||||
|
||||
// Emit the event to the server and wait for the response
|
||||
const nodeReply = new Promise((resolve) => {
|
||||
// Listen for the 'node-get-discord-id' event from the server
|
||||
clientSocket.once('node-get-discord-id', (callback) => {
|
||||
clientSocket.once("node-get-discord-id", (callback) => {
|
||||
// Simulate receiving the Discord ID from the client
|
||||
callback(discordId);
|
||||
});
|
||||
@@ -417,15 +522,15 @@ describe('Socket Server - Core Tests', () => {
|
||||
});
|
||||
|
||||
// Test requestNodeJoinSystem
|
||||
describe('Request Node Join System', () => {
|
||||
it('Should send a request to the node to join a system', async () => {
|
||||
const systemName = 'mockSystemName';
|
||||
const channelId = 'mockChannelId';
|
||||
const token = 'mockToken';
|
||||
describe("Request Node Join System", () => {
|
||||
it("Should send a request to the node to join a system", async () => {
|
||||
const systemName = "mockSystemName";
|
||||
const channelId = "mockChannelId";
|
||||
const token = "mockToken";
|
||||
|
||||
// Emit the event to the server and wait for the response
|
||||
await new Promise(async (resolve) => {
|
||||
clientSocket.once('node-join', (joinData) => {
|
||||
clientSocket.once("node-join", (joinData) => {
|
||||
// Check if the passed system ID matches the expected system ID
|
||||
expect(joinData.clientID).to.equal(token);
|
||||
expect(joinData.channelID).to.equal(channelId);
|
||||
@@ -441,13 +546,13 @@ describe('Socket Server - Core Tests', () => {
|
||||
});
|
||||
|
||||
// Test requestNodeLeaveSystem
|
||||
describe('Request Node Leave System', () => {
|
||||
it('Should send a request to the node to leave a given server', async () => {
|
||||
const guildId = 'mockGuildId';
|
||||
describe("Request Node Leave System", () => {
|
||||
it("Should send a request to the node to leave a given server", async () => {
|
||||
const guildId = "mockGuildId";
|
||||
|
||||
// Emit the event to the server and wait for the response
|
||||
await new Promise(async (resolve) => {
|
||||
clientSocket.once('node-leave', (passedGuildId) => {
|
||||
clientSocket.once("node-leave", (passedGuildId) => {
|
||||
// Check if the passed system ID matches the expected system ID
|
||||
expect(passedGuildId).to.equal(guildId);
|
||||
// Simulate receiving a success callback from the client
|
||||
@@ -461,11 +566,11 @@ describe('Socket Server - Core Tests', () => {
|
||||
});
|
||||
|
||||
// Test requestNodeUpdate
|
||||
describe('Request Node Update', () => {
|
||||
it('Should send the node a request to check for an update', async () => {
|
||||
describe("Request Node Update", () => {
|
||||
it("Should send the node a request to check for an update", async () => {
|
||||
// Emit the event to the server and wait for the response
|
||||
await new Promise((resolve) => {
|
||||
clientSocket.once('node-request-update', (callback) => {
|
||||
clientSocket.once("node-request-update", (callback) => {
|
||||
// Simulate an out of date request
|
||||
expect(callback);
|
||||
callback(true);
|
||||
@@ -479,10 +584,10 @@ describe('Socket Server - Core Tests', () => {
|
||||
});
|
||||
|
||||
// Test nodeDisconnectWrapper
|
||||
describe('Node Disconnect Wrapper', () => {
|
||||
it('Should disconnect the node and trigger cleanup actions', async () => {
|
||||
describe("Node Disconnect Wrapper", () => {
|
||||
it("Should disconnect the node and trigger cleanup actions", async () => {
|
||||
// Mock the socket ID
|
||||
const socketId = 'mockSocketId';
|
||||
const socketId = "mockSocketId";
|
||||
|
||||
// Call the nodeDisconnectWrapper function
|
||||
const result = await nodeDisconnectWrapper(socketId);
|
||||
|
||||
Reference in New Issue
Block a user