9 Commits

Author SHA1 Message Date
Logan Cusano
57aaf04119 Initial migration to jasmine
- Still bugs in tests from migration
2024-05-26 20:41:03 -04:00
dc988a41c1 Merge pull request '#10-RSS-feed-manager' (#14) from #10-RSS-feed-manager into main
Some checks failed
DRB Tests / drb_mocha_tests (push) Successful in 43s
release-tag / release-image (push) Failing after 28s
Reviewed-on: logan/DRB-Server#14
2024-05-26 19:41:08 -04:00
Logan Cusano
43dfa7b5ad RSS improvements
All checks were successful
DRB Tests / drb_mocha_tests (pull_request) Successful in 31s
- Updated rss discord command name scheme
- Implemented new sourceManager for handling feed sources
- Added wrappers to delete/get feed sources by title
2024-05-26 01:26:12 -04:00
Logan Cusano
8f56fd9b92 Updated default ping command with AC defaults 2024-05-26 01:23:33 -04:00
Logan Cusano
702e291fcb #5 improve the debugger
- Update the logic
- Will now check to make sure the given file exists before writing
2024-05-26 01:22:47 -04:00
Logan Cusano
2ab5a181bd #5 replace all console.logs with debugger
All checks were successful
DRB Tests / drb_mocha_tests (pull_request) Successful in 32s
2024-05-25 23:52:18 -04:00
Logan Cusano
81a215f048 Implement auto-building on push to main
All checks were successful
DRB Tests / drb_mocha_tests (pull_request) Successful in 29s
2024-05-22 02:44:56 -04:00
Logan Cusano
4831bb817c #5 Improved logging on mongo handler core
All checks were successful
DRB Tests / drb_mocha_tests (pull_request) Successful in 37s
2024-05-22 02:20:52 -04:00
Logan Cusano
429ddd3333 Code improvements
- Better error handling
- More output
- Better logic
2024-05-22 02:15:56 -04:00
31 changed files with 1061 additions and 325 deletions

View File

@@ -0,0 +1,56 @@
name: release-tag
on:
push:
branches:
- main
jobs:
release-image:
runs-on: ubuntu-latest
container:
image: catthehacker/ubuntu:act-latest
env:
DOCKER_ORG: teacup
DOCKER_LATEST: nightly
RUNNER_TOOL_CACHE: /toolcache
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker BuildX
uses: docker/setup-buildx-action@v2
with: # replace it with your local IP
config-inline: |
[registry."${{ secrets.LOCAL_GITEA_IP}}:3000"]
http = true
insecure = true
- name: Login to DockerHub
uses: docker/login-action@v2
with:
registry: ${{ secrets.LOCAL_GITEA_IP}}:3000 # replace it with your local IP
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Get Meta
id: meta
run: |
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
echo REPO_VERSION=$(git describe --tags --always | sed 's/^v//') >> $GITHUB_OUTPUT
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
file: ./Dockerfile
platforms: |
linux/amd64
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 }}

View File

@@ -1,8 +1,10 @@
// addons/addon1/index.js // addons/addon1/index.js
import { DebugBuilder } from "../../modules/debugger.mjs";
const log = new DebugBuilder("server", "server");
// Function called by the main application to initialize the addon // Function called by the main application to initialize the addon
export function initialize(nodeIo, config) { export function initialize(nodeIo, config) {
console.log(`Initializing ${config.name}`); log.INFO(`Initializing ${config.name}`);
// Call other functions within the addon module // Call other functions within the addon module
registerSocketEvents(nodeIo, config); registerSocketEvents(nodeIo, config);
@@ -12,6 +14,6 @@ export function initialize(nodeIo, config) {
// Function to register Socket.IO event handlers // Function to register Socket.IO event handlers
function registerSocketEvents(nodeIo, config) { function registerSocketEvents(nodeIo, config) {
nodeIo.on(config.options.eventName, (data) => { nodeIo.on(config.options.eventName, (data) => {
console.log(`Received event "${config.options.eventName}" from client:`, data); log.DEBUG(`Received event "${config.options.eventName}" from client:`, data);
}); });
} }

View File

@@ -1,3 +1,5 @@
import { DebugBuilder } from "../../modules/debugger.mjs";
const log = new DebugBuilder("server", "discordBot.command.join");
import { SlashCommandBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; import { SlashCommandBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js';
import { requestNodeJoinSystem, checkIfNodeIsConnectedToVC, checkIfNodeHasOpenDiscordClient, getNodeCurrentListeningSystem } from '../../modules/socketServerWrappers.mjs'; import { requestNodeJoinSystem, checkIfNodeIsConnectedToVC, checkIfNodeHasOpenDiscordClient, getNodeCurrentListeningSystem } from '../../modules/socketServerWrappers.mjs';
import { getSystemsByNuid, getAllSystems, getSystemByName } from '../../modules/mongo-wrappers/mongoSystemsWrappers.mjs'; import { getSystemsByNuid, getAllSystems, getSystemByName } from '../../modules/mongo-wrappers/mongoSystemsWrappers.mjs';
@@ -27,7 +29,7 @@ export async function autocomplete(nodeIo, interaction) {
const choices = await getAllSystems(); const choices = await getAllSystems();
const filtered = choices.filter(choice => choice.name.startsWith(focusedValue)); const filtered = choices.filter(choice => choice.name.startsWith(focusedValue));
console.log(focusedValue, choices, filtered); log.DEBUG(focusedValue, choices, filtered);
await interaction.respond( await interaction.respond(
filtered.map(choice => ({ name: choice.name, value: choice.name })), filtered.map(choice => ({ name: choice.name, value: choice.name })),
@@ -44,7 +46,7 @@ export async function execute(nodeIo, interaction) {
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 }) } 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 // Grab the channel if the user is connected to VC
const channelToJoin = interaction.member.voice.channel; const channelToJoin = interaction.member.voice.channel;
console.log(`The user '${interaction.member.id}' is in the voice channel '${channelToJoin}'`); log.INFO(`The user '${interaction.member.id}' is in the voice channel '${channelToJoin}'`);
// Get the selected system option from the command interaction // Get the selected system option from the command interaction
const selectedSystem = interaction.options.getString('system'); const selectedSystem = interaction.options.getString('system');
@@ -58,11 +60,11 @@ export async function execute(nodeIo, interaction) {
const openSocket = await nodeIo.sockets.sockets.get(selectedNodeSocketId); const openSocket = await nodeIo.sockets.sockets.get(selectedNodeSocketId);
// Get the open ID for this connection\ // Get the open ID for this connection\
const discordTokens = await getAvailableTokensInGuild(nodeIo, interaction.guild.id); const discordTokens = await getAvailableTokensInGuild(nodeIo, interaction.guild.id);
console.log("Available discord tokens: ", discordTokens); log.DEBUG("Available discord tokens: ", discordTokens);
if (discordTokens.length >= 1) { if (discordTokens.length >= 1) {
// TODO - Implement a method to have preferred tokens (bot users) for specific systems // TODO - Implement a method to have preferred tokens (bot users) for specific systems
console.log("Joining selected open socket:", selectedNodeSocketId, system.name, channelToJoin.id, openSocket.node.name, discordTokens[0].token); 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 // Ask the node to join the selected channel and system
await requestNodeJoinSystem(openSocket, system.name, channelToJoin.id, discordTokens[0].token); await requestNodeJoinSystem(openSocket, system.name, channelToJoin.id, discordTokens[0].token);
@@ -74,7 +76,7 @@ export async function execute(nodeIo, interaction) {
// Get all open socket nodes // Get all open socket nodes
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
console.log("All open sockets: ", openSockets); log.DEBUG("All open sockets: ", openSockets);
var availableNodes = []; var availableNodes = [];
// Check each open socket to see if the node has the requested system // Check each open socket to see if the node has the requested system
@@ -85,14 +87,14 @@ export async function execute(nodeIo, interaction) {
if (hasOpenClient) { if (hasOpenClient) {
let currentSystem = await getNodeCurrentListeningSystem(openSocket); let currentSystem = await getNodeCurrentListeningSystem(openSocket);
if (currentSystem != system.name) { if (currentSystem != system.name) {
console.log("Node is listening to a different system than requested", openSocket.node.name); log.INFO("Node is listening to a different system than requested", openSocket.node.name);
return; return;
} }
} }
// Check if the bot has an open voice connection in the requested server already // 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); const connected = await checkIfNodeIsConnectedToVC(nodeIo, interaction.guild.id, openSocket.node.nuid);
console.log("Connected:", connected); log.INFO("Connected:", connected);
if (!connected) { if (!connected) {
// Check if this node has the requested system, if so add it to the availble array // Check if this node has the requested system, if so add it to the availble array
if (system.nodes.includes(openSocket.node.nuid)) { if (system.nodes.includes(openSocket.node.nuid)) {
@@ -102,7 +104,7 @@ export async function execute(nodeIo, interaction) {
})); }));
console.log("Availble nodes:", availableNodes.map(socket => socket.node.name)); 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 there are no available nodes, let the user know there are none available
if (availableNodes.length == 0) { if (availableNodes.length == 0) {

View File

@@ -1,3 +1,5 @@
import { DebugBuilder } from "../../modules/debugger.mjs";
const log = new DebugBuilder("server", "discordBot.command.leave");
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from 'discord.js';
import { requestBotLeaveServer, getSocketIdByNuid } from '../../modules/socketServerWrappers.mjs'; import { requestBotLeaveServer, getSocketIdByNuid } from '../../modules/socketServerWrappers.mjs';
import { checkOnlineBotsInGuild } from '../modules/wrappers.mjs' import { checkOnlineBotsInGuild } from '../modules/wrappers.mjs'
@@ -25,11 +27,11 @@ export async function autocomplete(nodeIo, interaction) {
const focusedValue = interaction.options.getFocused(); const focusedValue = interaction.options.getFocused();
const choices = (await checkOnlineBotsInGuild(nodeIo, interaction.guild.id)); const choices = (await checkOnlineBotsInGuild(nodeIo, interaction.guild.id));
console.log(choices); 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 => choice = {name: choice.name, value: choice.nuid});
console.log(focusedValue, choices, filtered); log.DEBUG(focusedValue, choices, filtered);
await interaction.respond(filtered); await interaction.respond(filtered);
} }
@@ -44,7 +46,7 @@ export async function execute(nodeIo, interaction) {
// Get the requested bot // Get the requested bot
const selectedNode = interaction.options.getString('bot'); const selectedNode = interaction.options.getString('bot');
const socket = await getSocketIdByNuid(nodeIo, selectedNode); const socket = await getSocketIdByNuid(nodeIo, selectedNode);
console.log("All open sockets:", socket, selectedNode); log.DEBUG("All open sockets:", socket, selectedNode);
await requestBotLeaveServer(socket, interaction.guild.id); await requestBotLeaveServer(socket, interaction.guild.id);
//await interaction.reply(`**Online Sockets: '${sockets}'**`); //await interaction.reply(`**Online Sockets: '${sockets}'**`);
await interaction.editReply(`Ok <@${interaction.member.id}>, the bot is leaving shortly`); await interaction.editReply(`Ok <@${interaction.member.id}>, the bot is leaving shortly`);

View File

@@ -1,3 +1,5 @@
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 // Exporting data property that contains the command structure for discord including any params
@@ -17,10 +19,10 @@ export const deferInitialReply = false; // If we the initial reply in discord sh
/* /*
export async function autocomplete(nodeIo, interaction) { export async function autocomplete(nodeIo, interaction) {
const focusedValue = interaction.options.getFocused(); const focusedValue = interaction.options.getFocused();
const choices = []; const choices = []; // The array to be filled with the autocorrect values
const filtered = choices.filter(choice => choice.name.startsWith(focusedValue)); const filtered = choices.filter(choice => choice.name.startsWith(focusedValue));
console.log(focusedValue, choices, filtered); log.INFO(focusedValue, choices, filtered);
await interaction.respond(filtered); await interaction.respond(filtered.map(choice => ({name: choice.name, value: choice.name})));
} }
*/ */
@@ -32,7 +34,7 @@ export async function autocomplete(nodeIo, interaction) {
export const execute = async (nodeIo, interaction) => { export const execute = async (nodeIo, interaction) => {
try { try {
const sockets = await nodeIo.allSockets(); const sockets = await nodeIo.allSockets();
console.log("All open sockets: ",sockets); log.DEBUG("All open sockets: ",sockets);
//await interaction.reply(`**Online Sockets: '${sockets}'**`); //await interaction.reply(`**Online Sockets: '${sockets}'**`);
await interaction.reply('**Pong.**'); await interaction.reply('**Pong.**');
//await interaction.channel.send('**Pong.**'); //await interaction.channel.send('**Pong.**');

View File

@@ -1,12 +1,12 @@
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from 'discord.js';
import { DebugBuilder } from "../../modules/debugger.mjs"; import { DebugBuilder } from "../../modules/debugger.mjs";
import { addSource } from '../../rss-manager/feedHandler.mjs' import { addSource } from '../../rss-manager/sourceManager.mjs'
const log = new DebugBuilder("server", "add"); const log = new DebugBuilder("server", "discordBot.command.rssAdd");
// Exporting data property that contains the command structure for discord including any params // Exporting data property that contains the command structure for discord including any params
export const data = new SlashCommandBuilder() export const data = new SlashCommandBuilder()
.setName('add') .setName('rss-add')
.setDescription('Add RSS Source') .setDescription('Add RSS Source')
.addStringOption(option => .addStringOption(option =>
option.setName('title') option.setName('title')
@@ -22,7 +22,7 @@ export const data = new SlashCommandBuilder()
.setRequired(false)) .setRequired(false))
// Exporting other properties // Exporting other properties
export const example = "/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. 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.
/** /**
@@ -37,7 +37,7 @@ export async function autocomplete(nodeIo, interaction) {
const focusedValue = interaction.options.getFocused(); const focusedValue = interaction.options.getFocused();
const choices = []; const choices = [];
const filtered = choices.filter(choice => choice.name.startsWith(focusedValue)); const filtered = choices.filter(choice => choice.name.startsWith(focusedValue));
console.log(focusedValue, choices, filtered); log.DEBUG(focusedValue, choices, filtered);
await interaction.respond(filtered); await interaction.respond(filtered);
} }
*/ */
@@ -59,7 +59,7 @@ export const execute = async (nodeIo, interaction) => {
log.DEBUG("Result from adding entry", result); log.DEBUG("Result from adding entry", result);
if (result) { if (result) {
interaction.reply(`Adding ${title} to the list of RSS sources`); interaction.reply(`Successfully added ${title} to the list of RSS sources`);
} else { } else {
interaction.reply(`${title} already exists in the list of RSS sources`); interaction.reply(`${title} already exists in the list of RSS sources`);
} }

View File

@@ -0,0 +1,58 @@
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'
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')
.setRequired(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
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 = await getAllFeeds() ?? [];
log.INFO("RSS Remove Choices:", choices);
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 })));
}
/**
* 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 {
var title = interaction.options.getString('title');
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}'`);
return;
}
interaction.editReply(`${title} was successfully removed from the RSS sources.`)
} catch (err) {
log.ERROR(err)
interaction.editReply(err.toString());
}
}

View File

@@ -1,13 +1,15 @@
import { DebugBuilder } from "../../modules/debugger.mjs";
const log = new DebugBuilder("server", "discordBot.command.rssTrigger");
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from 'discord.js';
import { updateFeeds } from '../../rss-manager/feedHandler.mjs' import { updateFeeds } from '../../rss-manager/feedHandler.mjs'
// Exporting data property that contains the command structure for discord including any params // Exporting data property that contains the command structure for discord including any params
export const data = new SlashCommandBuilder() export const data = new SlashCommandBuilder()
.setName('trigger-rss') .setName('rss-trigger')
.setDescription('Manually triggers an RSS feed update'); .setDescription('Manually triggers an RSS feed update');
// Exporting other properties // Exporting other properties
export const example = "/trigger-rss"; // An example of how the command would be run in discord chat, this will be used for the help command 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
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. 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.
/** /**
@@ -20,7 +22,7 @@ export async function autocomplete(nodeIo, interaction) {
const focusedValue = interaction.options.getFocused(); const focusedValue = interaction.options.getFocused();
const choices = []; const choices = [];
const filtered = choices.filter(choice => choice.name.startsWith(focusedValue)); const filtered = choices.filter(choice => choice.name.startsWith(focusedValue));
console.log(focusedValue, choices, filtered); log.INFO(focusedValue, choices, filtered);
await interaction.respond(filtered); await interaction.respond(filtered);
} }
*/ */
@@ -33,7 +35,6 @@ export async function autocomplete(nodeIo, interaction) {
export const execute = async (nodeIo, interaction) => { export const execute = async (nodeIo, interaction) => {
try { try {
//const sockets = await nodeIo.allSockets(); //const sockets = await nodeIo.allSockets();
//console.log("All open sockets: ", sockets);
//await interaction.reply(`**Online Sockets: '${sockets}'**`); //await interaction.reply(`**Online Sockets: '${sockets}'**`);
await interaction.reply('Triggering RSS update'); await interaction.reply('Triggering RSS update');
await updateFeeds(interaction.client); await updateFeeds(interaction.client);

View File

@@ -1,3 +1,5 @@
import { DebugBuilder } from "../../modules/debugger.mjs";
const log = new DebugBuilder("server", "discordBot.command.update");
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from 'discord.js';
import { requestNodeUpdate } from '../../modules/socketServerWrappers.mjs'; import { requestNodeUpdate } from '../../modules/socketServerWrappers.mjs';
@@ -18,7 +20,7 @@ export const deferInitialReply = false; // If we the initial reply in discord sh
export const execute = async (nodeIo, interaction) => { export const execute = async (nodeIo, interaction) => {
try { 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
console.log("All open sockets: ", openSockets); log.DEBUG("All open sockets: ", openSockets);
// Check each open socket to see if the node has the requested system // 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 => {

View File

@@ -1,14 +1,19 @@
import { DebugBuilder } from "../modules/debugger.mjs";
import { Client, GatewayIntentBits, Collection } from 'discord.js'; import { Client, GatewayIntentBits, Collection } from 'discord.js';
import { registerActiveCommands, unregisterAllCommands } from './modules/registerCommands.mjs' import { registerActiveCommands, unregisterAllCommands } from './modules/registerCommands.mjs'
import { RSSController } from '../rss-manager/rssController.mjs'
import { join, dirname } from 'path'; import { join, dirname } from 'path';
import { readdirSync } from 'fs'; import { readdirSync } from 'fs';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import dotenv from 'dotenv';
dotenv.config()
const log = new DebugBuilder("server", "discordBot");
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename); const __dirname = dirname(__filename);
import dotenv from 'dotenv';
dotenv.config()
/** /**
* Add the enabled commands to the bot to be used by users in discord * Add the enabled commands to the bot to be used by users in discord
@@ -25,18 +30,18 @@ export const addEnabledCommands = async (serverClient, _commandsPath = "./comman
for (const file of commandFiles) { for (const file of commandFiles) {
const filePath = await join(commandsPath, file); const filePath = await join(commandsPath, file);
console.log(`Adding enabled command: ${filePath}`); log.INFO(`Adding enabled command: ${filePath}`);
await import(`file://${filePath}`).then(command => { await import(`file://${filePath}`).then(command => {
if (command.data instanceof Promise) { if (command.data instanceof Promise) {
command.data.then(async (builder) => { command.data.then(async (builder) => {
command.data = builder; command.data = builder;
console.log("Importing command: ", command.data.name, command); log.DEBUG("Importing command: ", command.data.name, command);
// Set a new item in the Collection // Set a new item in the Collection
// With the key as the command name and the value as the exported module // With the key as the command name and the value as the exported module
serverClient.commands.set(command.data.name, command); serverClient.commands.set(command.data.name, command);
}); });
} else { } else {
console.log("Importing command: ", command.data.name, command); log.DEBUG("Importing command: ", command.data.name, command);
// Set a new item in the Collection // Set a new item in the Collection
// With the key as the command name and the value as the exported module // With the key as the command name and the value as the exported module
serverClient.commands.set(command.data.name, command); serverClient.commands.set(command.data.name, command);
@@ -61,9 +66,9 @@ export function addEnabledEventListeners(serverClient, _eventsPath = "./events")
for (const file of eventFiles) { for (const file of eventFiles) {
const filePath = join(eventsPath, file); const filePath = join(eventsPath, file);
console.log(`Adding enabled event listener: ${filePath}`); log.INFO(`Adding enabled event listener: ${filePath}`);
import(`file://${filePath}`).then(event => { import(`file://${filePath}`).then(event => {
console.log("Adding event: ", event); log.DEBUG("Adding event: ", event);
if (event.once) { if (event.once) {
serverClient.once(event.name, (...args) => event.execute(serverClient.nodeIo, ...args)); serverClient.once(event.name, (...args) => event.execute(serverClient.nodeIo, ...args));
} else { } else {
@@ -78,15 +83,21 @@ export const serverClient = new Client({ intents: [GatewayIntentBits.Guilds, Gat
// Run when the bot is ready // Run when the bot is ready
serverClient.on('ready', async () => { serverClient.on('ready', async () => {
console.log(`Logged in as ${serverClient.user.tag}!`); log.INFO(`Logged in as ${serverClient.user.tag}!`);
// Add and register commands // Add and register commands
await addEnabledCommands(serverClient); await addEnabledCommands(serverClient);
// Config the discord bot with events // Config the discord bot with events
await addEnabledEventListeners(serverClient); await addEnabledEventListeners(serverClient);
// Start the RSS Controller
serverClient.RSSController = await new RSSController(serverClient);
serverClient.RSSController.start();
log.INFO("RSS Controller:", serverClient.RSSController);
}); });
// Startup the discord bot // Startup the discord bot
console.log(`Logging into discord with ID: ${process.env.DISCORD_TOKEN}`); log.INFO(`Logging into discord with ID: ${process.env.DISCORD_TOKEN}`);
serverClient.login(process.env.DISCORD_TOKEN); serverClient.login(process.env.DISCORD_TOKEN);

View File

@@ -1,14 +1,16 @@
import { DebugBuilder } from "../../modules/debugger.mjs";
const log = new DebugBuilder("server", "discordBot.events.interactionCreate");
import { Events } from 'discord.js'; import { Events } from 'discord.js';
export const name = Events.InteractionCreate; export const name = Events.InteractionCreate;
export async function execute(nodeIo, interaction) { export async function execute(nodeIo, interaction) {
const command = interaction.client.commands.get(interaction.commandName); const command = interaction.client.commands.get(interaction.commandName);
console.log("Interaction created for command: ", command); log.INFO("Interaction created for command: ", command);
// Execute autocomplete if the user is checking autocomplete // Execute autocomplete if the user is checking autocomplete
if (interaction.isAutocomplete()) { if (interaction.isAutocomplete()) {
console.log("Running autocomplete for command: ", command.data.name); log.INFO("Running autocomplete for command: ", command.data.name);
return await command.autocomplete(nodeIo, interaction); return await command.autocomplete(nodeIo, interaction);
} }
@@ -20,7 +22,7 @@ export async function execute(nodeIo, interaction) {
return; return;
} }
console.log(`${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 // Defer the initial reply if the command has the parameter set
if (command.deferInitialReply) { if (command.deferInitialReply) {

View File

@@ -1,3 +1,5 @@
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'; import dotenv from 'dotenv';
@@ -15,21 +17,21 @@ export const registerActiveCommands = async (serverClient) => {
// and deploy your commands! // and deploy your commands!
guildIDs.forEach(guild => { guildIDs.forEach(guild => {
console.log("Deploying commands for: ", guild.id); log.INFO("Deploying commands for: ", guild.id);
console.log("Commands", commands); log.DEBUG("Commands", commands);
(async () => { (async () => {
try { try {
console.log(`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 // The put method is used to fully refresh all commands in the guild with the current set
const data = await rest.put( const data = await rest.put(
Routes.applicationGuildCommands(clientId, guild.id), Routes.applicationGuildCommands(clientId, guild.id),
{ body: commands }, { body: commands },
); );
console.log(`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) { } catch (error) {
// And of course, make sure you catch and log any errors! // And of course, make sure you catch and log any errors!
console.log("ERROR Deploying commands: ", error, "Body from error: ", commands); log.ERROR("ERROR Deploying commands: ", error, "Body from error: ", commands);
} }
})() })()
}) })
@@ -47,20 +49,20 @@ export const unregisterAllCommands = async (serverClient) => {
const rest = new REST({ version: '10' }).setToken(discordToken); const rest = new REST({ version: '10' }).setToken(discordToken);
guildIDs.forEach(guild => { guildIDs.forEach(guild => {
console.log("Removing commands for: ", clientId, guild.id); log.INFO("Removing commands for: ", clientId, guild.id);
(async () => { (async () => {
try { try {
console.log(`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 // The put method is used to fully refresh all commands in the guild with the current set
const data = await rest.put( const data = await rest.put(
Routes.applicationGuildCommands(clientId, guild.id), Routes.applicationGuildCommands(clientId, guild.id),
{ body: commands }, { body: commands },
); );
console.log(`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) { } catch (error) {
// And of course, make sure you catch and log any errors! // And of course, make sure you catch and log any errors!
console.log("ERROR removing commands: ", error, "Body from error: ", commands); log.ERROR("ERROR removing commands: ", error, "Body from error: ", commands);
} }
})() })()
}) })
@@ -74,10 +76,10 @@ export const unregisterAllCommands = async (serverClient) => {
*/ */
export const refreshActiveCommandsWrapper = async (serverClient) => { export const refreshActiveCommandsWrapper = async (serverClient) => {
// Remove all commands // Remove all commands
console.log("Removing/Unregistering all commands from all connected servers/guilds"); log.INFO("Removing/Unregistering all commands from all connected servers/guilds");
await unregisterAllCommands(serverClient); await unregisterAllCommands(serverClient);
// Deploy the active commands // Deploy the active commands
console.log("Adding commands to all connected servers/guilds"); log.INFO("Adding commands to all connected servers/guilds");
await registerActiveCommands(serverClient); await registerActiveCommands(serverClient);
return; return;
} }

View File

@@ -7,12 +7,12 @@ import { config } from 'dotenv';
// Load environment variables // Load environment variables
config(); config();
const log = new DebugBuilder("server", "libUtils"); const log = new DebugBuilder("server", "discordBot.modules.rssWrappers");
const imageRegex = /(http(s?):)([/|.|\w|\s|-])*((\.(?:jpg|gif|png|webm))|(\/gallery\/(?:[/|.|\w|\s|-])*))/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; const youtubeVideoRegex = /((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube(-nocookie)?\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)/g;
export class EmmeliaEmbedBuilder extends EmbedBuilder { export class DRBEmbedBuilder extends EmbedBuilder {
constructor() { constructor() {
super(); super();
this.setTimestamp(); this.setTimestamp();
@@ -22,80 +22,66 @@ export class EmmeliaEmbedBuilder extends EmbedBuilder {
export const sendPost = (post, source, channel) => { export const sendPost = (post, source, channel) => {
log.DEBUG("Sending post from source: ", post, source); log.DEBUG("Sending post from source: ", post, source);
const postTitle = String(post.title).substring(0, 150); const postTitle = String(post.title).substring(0, 150);
const postLink = post.link; const postLink = post.link;
let postContent; let postContent = `*This post has no content* [Direct Link](${post.link})`;
if (post.content) { if (post.content || post['content:encoded']) {
// Reset the content parameter with the encoded parameter const content = post['content:encoded'] ?? post.content;
post.content = parse(post['content:encoded'] ?? post.content); const parsedContent = parse(content);
// Get the post content and trim it to length or add a placeholder if necessary let postText = parsedContent.text.trim();
let postText = String(post.content.text);
if (postText.length >= 3800) postText = `${postText.slice(0, 3800).substring(0, Math.min(postText.length, postText.lastIndexOf(" ")))} [...](${post.link})`; if (postText.length >= 3800) {
else if (postText.length === 0) postText = `*This post has no content* [Direct Link](${post.link})`; postText = `${postText.slice(0, 3800).substring(0, postText.lastIndexOf(" "))} [...](${post.link})`;
} else if (postText.length === 0) {
postText = `*This post has no content* [Direct Link](${post.link})`;
}
postContent = postText; postContent = postText;
} else {
postContent = `*This post has no content* [Direct Link](${post.link})`; // Check for embedded YouTube videos and add the first four as links
const ytVideos = content.match(youtubeVideoRegex);
if (ytVideos) {
ytVideos.slice(0, 4).forEach((ytVideo) => {
if (ytVideo.includes("embed")) ytVideo = ytVideo.replace("embed/", "watch?v=");
postContent += `\nEmbedded Video from Post: [YouTube](${ytVideo})`;
});
} }
// Check for embedded youtube videos and add the first four as links // Extract the first image link if available
const ytVideos = String(post.content).match(youtubeVideoRegex); const imageLinks = parsedContent.querySelectorAll("a")
if (ytVideos) { .map(link => link.getAttribute("href"))
for (let ytVideo of ytVideos.slice(0, 4)) { .filter(href => href && href.match(imageRegex));
// If the video is an embed, replace the embed to make it watchable
if (ytVideo.includes("embed")) ytVideo = ytVideo.replace("embed/", "watch?v="); if (imageLinks.length > 0) {
postContent += `\nEmbeded Video from Post: [YouTube](${ytVideo})`; post.image = imageLinks[0];
} }
} }
log.DEBUG("Post content: ", postContent);
const postId = post.postId; const postId = post.postId;
if (!post.pubDate) post.pubDate = Date.now(); const postPubDate = new Date(post.pubDate || Date.now()).toISOString();
const postPubDate = new Date(post.pubDate).toISOString();
const postSourceLink = source.title; const postSourceLink = source.title;
let postImage = post.image ?? undefined; const postImage = post.image;
if (!postImage) { log.DEBUG("Post content: ", postContent);
if (post.content) {
const linksInPost = post.content.querySelectorAll("a");
if (linksInPost) {
log.DEBUG("Found links in post:", linksInPost);
for (const link of linksInPost) {
// Check to see if this link is a youtube video that was already found, if so skip it
if (ytVideos?.includes(link)) continue;
const images = String(link.getAttribute("href")).match(imageRegex);
log.DEBUG("Images found in post:", images);
if (images) {
postImage = images[0];
}
}
}
}
}
log.DEBUG("Sending an RSS post to discord", postTitle, postId, postContent);
try { try {
const rssMessage = new EmmeliaEmbedBuilder() const rssMessage = new DRBEmbedBuilder()
.setColor(0x0099FF) .setColor(0x0099FF)
.setTitle(postTitle) .setTitle(postTitle)
.setURL(postLink) .setURL(postLink)
.addFields({ name: 'Source', value: postSourceLink, inline: true }) .addFields({ name: 'Source', value: postSourceLink, inline: true })
.addFields({ name: 'Published', value: postPubDate, inline: true }); .addFields({ name: 'Published', value: postPubDate, inline: true });
// TODO - If there is more than one image, create a canvas and post the created canvas
if (postImage) { if (postImage) {
log.DEBUG("Image from post:", postImage); log.DEBUG("Image from post:", postImage);
rssMessage.setImage(postImage); rssMessage.setImage(postImage);
} }
// Add the main content if it's present
postContent = postContent.slice(0, 4090).trim(); postContent = postContent.slice(0, 4090).trim();
if (postContent) rssMessage.setDescription(postContent); if (postContent) rssMessage.setDescription(postContent);
const channelResponse = rssMessage; const channelResponse = channel.send({ embeds: [rssMessage] });
//const channelResponse = channel.send({ embeds: [rssMessage] });
log.DEBUG("Channel send response", channelResponse); log.DEBUG("Channel send response", channelResponse);

View File

@@ -1,3 +1,5 @@
import { DebugBuilder } from "../../modules/debugger.mjs";
const log = new DebugBuilder("server", "discordBot.modules.wrappers");
import { checkIfNodeIsConnectedToVC, getNodeDiscordID, getNodeDiscordUsername } from '../../modules/socketServerWrappers.mjs'; import { checkIfNodeIsConnectedToVC, getNodeDiscordID, getNodeDiscordUsername } from '../../modules/socketServerWrappers.mjs';
import { getAllDiscordIDs } from '../../modules/mongo-wrappers/mongoDiscordIDWrappers.mjs' import { getAllDiscordIDs } from '../../modules/mongo-wrappers/mongoDiscordIDWrappers.mjs'
@@ -8,7 +10,7 @@ export const checkOnlineBotsInGuild = async (nodeIo, guildId) => {
await Promise.all(openSockets.map(async openSocket => { await Promise.all(openSockets.map(async openSocket => {
openSocket = await nodeIo.sockets.sockets.get(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);
console.log("Connected:", connected); log.INFO("Connected:", connected);
if (connected) { if (connected) {
const username = await getNodeDiscordUsername(openSocket, guildId); const username = await getNodeDiscordUsername(openSocket, guildId);
const discordID = await getNodeDiscordID(openSocket); const discordID = await getNodeDiscordID(openSocket);
@@ -33,8 +35,8 @@ export const checkOnlineBotsInGuild = async (nodeIo, guildId) => {
]); ]);
// Use the results of both promises here // Use the results of both promises here
console.log("Available Discord IDs:", discordIDs); log.INFO("Available Discord IDs:", discordIDs);
console.log("Online bots in the guild:", onlineBots); log.INFO("Online bots in the guild:", onlineBots);
// Filter any discordIDs that are not active // 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));

View File

@@ -1,3 +1,5 @@
import { DebugBuilder } from "../modules/debugger.mjs";
const log = new DebugBuilder("server", "addonManager");
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
@@ -20,9 +22,9 @@ export const loadAddons = async (nodeIo) => {
if (addonConfig.enabled) { if (addonConfig.enabled) {
const addonIndexPath = path.join(addonsDir, addonDir.name, 'index.js'); const addonIndexPath = path.join(addonsDir, addonDir.name, 'index.js');
import(`file://${addonIndexPath}`).then(addonModule => { import(`file://${addonIndexPath}`).then(addonModule => {
console.log("Loading addon: ", addonModule); log.DEBUG("Loading addon: ", addonModule);
addonModule.initialize(nodeIo, addonConfig); addonModule.initialize(nodeIo, addonConfig);
console.log(`Addon ${addonConfig.name} loaded.`); log.DEBUG(`Addon ${addonConfig.name} loaded.`);
}); });
} }
} }

View File

@@ -1,25 +1,35 @@
// Import necessary modules // Import necessary modules
import debug from 'debug'; import debug from 'debug';
import { config } from 'dotenv'; import { config } from 'dotenv';
import { writeFile } from 'fs'; config();
import { promises as fs } from 'fs';
import { join, dirname } from 'path';
import { inspect } from 'util'; import { inspect } from 'util';
// Load environment variables /**
config(); * Write a given message to the log file
* @param {any} logMessage The message to write to the log file
const logLocation = process.env.LOG_LOCATION; * @param {string} appName The app name that created the log entry
*/
const writeToLog = async (logMessage, appName) => { const writeToLog = async (logMessage, appName) => {
const logLocation = join(process.env.LOG_LOCATION ?? `./logs/${appName}.log`);
// Ensure the log directory exists
try {
await fs.mkdir(dirname(logLocation), { recursive: true });
} catch (err) {
console.error(err);
}
// Ensure the message is a string
logMessage = `${String(logMessage)}\n`; logMessage = `${String(logMessage)}\n`;
writeFile( // Write to the file
logLocation ?? `./${appName}.log`, try {
logMessage, await fs.writeFile(logLocation, logMessage, { encoding: 'utf-8', flag: 'a+' });
{ encoding: "utf-8", flag: 'a+' }, } catch (err) {
(err) => { console.error(err);
if (err) console.error(err);
} }
);
}; };
/** /**
@@ -30,37 +40,31 @@ const writeToLog = async (logMessage, appName) => {
*/ */
export class DebugBuilder { export class DebugBuilder {
constructor(appName, fileName) { constructor(appName, fileName) {
this.INFO = (...messageParts) => { const buildLogger = (level) => (...messageParts) => {
const _info = debug(`${appName}:${fileName}:INFO`); const logger = debug(`${appName}:${fileName}:${level}`);
_info(messageParts); logger(messageParts);
writeToLog(`${new Date().toLocaleString('en-US', { timeZone: 'America/New_York' })} - ${appName}:${fileName}:INFO\t-\t${messageParts.map(messagePart => inspect(messagePart))}`, appName);
}; 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(' ')}`;
this.DEBUG = (...messageParts) => {
const _debug = debug(`${appName}:${fileName}:DEBUG`); // Write to console
_debug(messageParts); console.log(message);
writeToLog(`${new Date().toLocaleString('en-US', { timeZone: 'America/New_York' })} - ${appName}:${fileName}:DEBUG\t-\t${messageParts.map(messagePart => inspect(messagePart))}`, appName);
}; // Write to logfile
writeToLog(message, appName);
this.VERBOSE = (...messageParts) => {
const _verbose = debug(`${appName}:${fileName}:VERBOSE`);
_verbose(messageParts);
writeToLog(`${new Date().toLocaleString('en-US', { timeZone: 'America/New_York' })} - ${appName}:${fileName}:VERBOSE\t-\t${messageParts.map(messagePart => inspect(messagePart))}`, appName);
};
this.WARN = (...messageParts) => {
const _warn = debug(`${appName}:${fileName}:WARNING`);
_warn(messageParts);
writeToLog(`${new Date().toLocaleString('en-US', { timeZone: 'America/New_York' })} - ${appName}:${fileName}:WARNING\t-\t${messageParts.map(messagePart => inspect(messagePart))}`, appName);
}; };
this.INFO = buildLogger('INFO');
this.DEBUG = buildLogger('DEBUG');
this.VERBOSE = buildLogger('VERBOSE');
this.WARN = buildLogger('WARNING');
this.ERROR = (...messageParts) => { this.ERROR = (...messageParts) => {
const _error = debug(`${appName}:${fileName}:ERROR`); buildLogger('ERROR')(...messageParts);
_error(messageParts);
writeToLog(`${new Date().toLocaleString('en-US', { timeZone: 'America/New_York' })} - ${appName}:${fileName}:ERROR\t-\t${messageParts.map(messagePart => inspect(messagePart))}`, appName);
if (process.env.EXIT_ON_ERROR && process.env.EXIT_ON_ERROR > 0) { if (process.env.EXIT_ON_ERROR && process.env.EXIT_ON_ERROR > 0) {
writeToLog("!--- EXITING ---!", appName); writeToLog("!--- EXITING ---!", appName);
setTimeout(() => process.exit(), process.env.EXIT_ON_ERROR_DELAY ?? 0); const exitDelay = parseInt(process.env.EXIT_ON_ERROR_DELAY, 10) || 0;
setTimeout(() => process.exit(1), exitDelay);
} }
}; };
} }

View File

@@ -1,3 +1,5 @@
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';
@@ -8,7 +10,7 @@ export const createDiscordID = async (discordID) => {
const insertedId = await insertDocument(collectionName, discordID); const insertedId = await insertDocument(collectionName, discordID);
return insertedId; return insertedId;
} catch (error) { } catch (error) {
console.error('Error creating Discord ID:', error); log.ERROR('Error creating Discord ID:', error);
throw error; throw error;
} }
}; };
@@ -19,7 +21,7 @@ export const getAllDiscordIDs = async () => {
const discordIDs = await getDocuments(collectionName); const discordIDs = await getDocuments(collectionName);
return discordIDs; return discordIDs;
} catch (error) { } catch (error) {
console.error('Error getting all Discord IDs:', error); log.ERROR('Error getting all Discord IDs:', error);
throw error; throw error;
} }
}; };
@@ -37,7 +39,7 @@ export const getDiscordID = async (identifier) => {
}); });
return discordID; return discordID;
} catch (error) { } catch (error) {
console.error('Error getting Discord ID:', error); log.ERROR('Error getting Discord ID:', error);
throw error; throw error;
} finally { } finally {
// Close the connection // Close the connection
@@ -56,10 +58,10 @@ export const updateDiscordID = async (identifier, updatedFields) => {
{ discord_id: identifier } { discord_id: identifier }
] ]
}, { $set: updatedFields }); }, { $set: updatedFields });
console.log('Discord ID updated:', result.modifiedCount); log.INFO('Discord ID updated:', result.modifiedCount);
return result.modifiedCount; return result.modifiedCount;
} catch (error) { } catch (error) {
console.error('Error updating Discord ID:', error); log.ERROR('Error updating Discord ID:', error);
throw error; throw error;
} finally { } finally {
// Close the connection // Close the connection
@@ -78,10 +80,10 @@ export const deleteDiscordID = async (identifier) => {
{ discord_id: identifier } { discord_id: identifier }
] ]
}); });
console.log('Discord ID deleted:', result.deletedCount); log.INFO('Discord ID deleted:', result.deletedCount);
return result.deletedCount; return result.deletedCount;
} catch (error) { } catch (error) {
console.error('Error deleting Discord ID:', error); log.ERROR('Error deleting Discord ID:', error);
throw error; throw error;
} finally { } finally {
// Close the connection // Close the connection

View File

@@ -1,3 +1,5 @@
import { DebugBuilder } from "../../modules/debugger.mjs";
const log = new DebugBuilder("server", "mongoFeedsWrappers");
import { import {
insertDocument, insertDocument,
getDocuments, getDocuments,
@@ -15,7 +17,7 @@ import {
const insertedId = await insertDocument(feedCollectionName, feed); const insertedId = await insertDocument(feedCollectionName, feed);
return insertedId; return insertedId;
} catch (error) { } catch (error) {
console.error('Error creating feed:', error); log.ERROR('Error creating feed:', error);
throw error; throw error;
} }
}; };
@@ -26,7 +28,7 @@ import {
const feeds = await getDocuments(feedCollectionName); const feeds = await getDocuments(feedCollectionName);
return feeds; return feeds;
} catch (error) { } catch (error) {
console.error('Error getting all feeds:', error); log.ERROR('Error getting all feeds:', error);
throw error; throw error;
} }
}; };
@@ -37,7 +39,18 @@ import {
const feed = await getDocumentByField(feedCollectionName, 'link', link); const feed = await getDocumentByField(feedCollectionName, 'link', link);
return feed; return feed;
} catch (error) { } catch (error) {
console.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) => {
try {
const feed = await getDocumentByField(feedCollectionName, 'title', title);
return feed;
} catch (error) {
log.ERROR('Error getting feed by link:', error);
throw error; throw error;
} }
}; };
@@ -48,7 +61,7 @@ import {
const modifiedCount = await updateDocumentByField(feedCollectionName, 'link', link, updatedFields); const modifiedCount = await updateDocumentByField(feedCollectionName, 'link', link, updatedFields);
return modifiedCount; return modifiedCount;
} catch (error) { } catch (error) {
console.error('Error updating feed by link:', error); log.ERROR('Error updating feed by link:', error);
throw error; throw error;
} }
}; };
@@ -59,7 +72,18 @@ import {
const deletedCount = await deleteDocumentByField(feedCollectionName, 'link', link); const deletedCount = await deleteDocumentByField(feedCollectionName, 'link', link);
return deletedCount; return deletedCount;
} catch (error) { } catch (error) {
console.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) => {
try {
const deletedCount = await deleteDocumentByField(feedCollectionName, 'title', title);
return deletedCount;
} catch (error) {
log.ERROR('Error deleting feed by link:', error);
throw error; throw error;
} }
}; };
@@ -70,7 +94,7 @@ import {
const insertedId = await insertDocument(postCollectionName, post); const insertedId = await insertDocument(postCollectionName, post);
return insertedId; return insertedId;
} catch (error) { } catch (error) {
console.error('Error creating post:', error); log.ERROR('Error creating post:', error);
throw error; throw error;
} }
}; };
@@ -81,7 +105,7 @@ import {
const post = await getDocumentByField(postCollectionName, 'postId', postId); const post = await getDocumentByField(postCollectionName, 'postId', postId);
return post; return post;
} catch (error) { } catch (error) {
console.error('Error getting post by postId:', error); log.ERROR('Error getting post by postId:', error);
throw error; throw error;
} }
}; };

View File

@@ -1,5 +1,7 @@
// Import necessary modules // Import necessary modules
import { MongoClient } from 'mongodb'; import { MongoClient } from 'mongodb';
import { DebugBuilder } from '../debugger.mjs';
const log = new DebugBuilder("server", 'mongoHandler');
import dotenv from 'dotenv'; import dotenv from 'dotenv';
dotenv.config() dotenv.config()
@@ -21,10 +23,11 @@ export const connectToDatabase = async () => {
// Function to insert a document into the collection // Function to insert a document into the collection
export const insertDocument = async (collectionName, document) => { export const insertDocument = async (collectionName, document) => {
const db = await connectToDatabase(); const db = await connectToDatabase();
log.DEBUG("Inserting document:", collectionName, document);
try { try {
const collection = db.db().collection(collectionName); const collection = db.db().collection(collectionName);
const result = await collection.insertOne(document); const result = await collection.insertOne(document);
console.log('Document inserted:', result.insertedId); log.DEBUG('Document inserted:', result.insertedId);
return result.insertedId; return result.insertedId;
} catch (error) { } catch (error) {
console.error('Error inserting document:', error); console.error('Error inserting document:', error);
@@ -37,11 +40,12 @@ export const insertDocument = async (collectionName, document) => {
// Function to retrieve documents from the collection // Function to retrieve documents from the collection
export const getDocuments = async (collectionName) => { export const getDocuments = async (collectionName) => {
log.DEBUG("Getting all documents:", collectionName);
const db = await connectToDatabase(); const db = await connectToDatabase();
try { try {
const collection = db.db().collection(collectionName); const collection = db.db().collection(collectionName);
const documents = await collection.find({}).toArray(); const documents = await collection.find({}).toArray();
console.log('Documents retrieved:', documents); log.DEBUG('Documents retrieved:', documents);
return documents; return documents;
} catch (error) { } catch (error) {
console.error('Error retrieving documents:', error); console.error('Error retrieving documents:', error);
@@ -54,6 +58,7 @@ export const getDocuments = async (collectionName) => {
// Function to retrieve a document by a specific field // Function to retrieve a document by a specific field
export const getDocumentByField = async (collectionName, field, value) => { export const getDocumentByField = async (collectionName, field, value) => {
log.DEBUG("Getting document by field:", collectionName, field, value);
const db = await connectToDatabase(); const db = await connectToDatabase();
try { try {
const collection = db.db().collection(collectionName); const collection = db.db().collection(collectionName);
@@ -69,11 +74,12 @@ export const getDocumentByField = async (collectionName, field, value) => {
// Function to update a document by a specific field // Function to update a document by a specific field
export const updateDocumentByField = async (collectionName, field, value, updatedFields) => { export const updateDocumentByField = async (collectionName, field, value, updatedFields) => {
log.DEBUG("Update document by field:", collectionName, field, value, updatedFields);
const db = await connectToDatabase(); const db = await connectToDatabase();
try { try {
const collection = db.db().collection(collectionName); const collection = db.db().collection(collectionName);
const result = await collection.updateOne({ [field]: value }, { $set: updatedFields }); const result = await collection.updateOne({ [field]: value }, { $set: updatedFields });
console.log('Document updated:', result.modifiedCount); log.DEBUG('Document updated:', result.modifiedCount);
return result.modifiedCount; return result.modifiedCount;
} catch (error) { } catch (error) {
console.error('Error updating document:', error); console.error('Error updating document:', error);
@@ -85,11 +91,12 @@ export const updateDocumentByField = async (collectionName, field, value, update
// Function to delete a document by a specific field // Function to delete a document by a specific field
export const deleteDocumentByField = async (collectionName, field, value) => { export const deleteDocumentByField = async (collectionName, field, value) => {
log.DEBUG("Delete document by field:", collectionName, field, value);
const db = await connectToDatabase(); const db = await connectToDatabase();
try { try {
const collection = db.db().collection(collectionName); const collection = db.db().collection(collectionName);
const result = await collection.deleteOne({ [field]: value }); const result = await collection.deleteOne({ [field]: value });
console.log('Document deleted:', result.deletedCount); log.DEBUG('Document deleted:', result.deletedCount);
return result.deletedCount; return result.deletedCount;
} catch (error) { } catch (error) {
console.error('Error deleting document:', error); console.error('Error deleting document:', error);

View File

@@ -1,3 +1,5 @@
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';
@@ -8,7 +10,7 @@ export const createNode = async (node) => {
const insertedId = await insertDocument(collectionName, node); const insertedId = await insertDocument(collectionName, node);
return insertedId; return insertedId;
} catch (error) { } catch (error) {
console.error('Error creating node:', error); log.ERROR('Error creating node:', error);
throw error; throw error;
} }
}; };
@@ -19,7 +21,7 @@ export const getAllNodes = async () => {
const nodes = await getDocuments(collectionName); const nodes = await getDocuments(collectionName);
return nodes; return nodes;
} catch (error) { } catch (error) {
console.error('Error getting all nodes:', error); log.ERROR('Error getting all nodes:', error);
throw error; throw error;
} }
}; };
@@ -32,7 +34,7 @@ export const getNodeByNuid = async (nuid) => {
const node = await collection.findOne({ nuid }); const node = await collection.findOne({ nuid });
return node; return node;
} catch (error) { } catch (error) {
console.error('Error getting node by NUID:', error); log.ERROR('Error getting node by NUID:', error);
throw error; throw error;
} finally { } finally {
// Close the connection // Close the connection
@@ -46,10 +48,10 @@ export const updateNodeByNuid = async (nuid, updatedFields) => {
try { try {
const collection = db.db().collection(collectionName); const collection = db.db().collection(collectionName);
const result = await collection.updateOne({ nuid }, { $set: updatedFields }); const result = await collection.updateOne({ nuid }, { $set: updatedFields });
console.log('Node updated:', result.modifiedCount); log.INFO('Node updated:', result.modifiedCount);
return result.modifiedCount; return result.modifiedCount;
} catch (error) { } catch (error) {
console.error('Error updating node by NUID:', error); log.ERROR('Error updating node by NUID:', error);
throw error; throw error;
} finally { } finally {
// Close the connection // Close the connection
@@ -63,10 +65,10 @@ export const deleteNodeByNuid = async (nuid) => {
try { try {
const collection = db.db().collection(collectionName); const collection = db.db().collection(collectionName);
const result = await collection.deleteOne({ nuid }); const result = await collection.deleteOne({ nuid });
console.log('Node deleted:', result.deletedCount); log.INFO('Node deleted:', result.deletedCount);
return result.deletedCount; return result.deletedCount;
} catch (error) { } catch (error) {
console.error('Error deleting node by NUID:', error); log.ERROR('Error deleting node by NUID:', error);
throw error; throw error;
} finally { } finally {
// Close the connection // Close the connection

View File

@@ -1,3 +1,5 @@
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';
@@ -21,7 +23,7 @@ export const createSystem = async (name, system, nuid) => {
const insertedId = await insertDocument(collectionName, system); const insertedId = await insertDocument(collectionName, system);
return insertedId; return insertedId;
} catch (error) { } catch (error) {
console.error('Error creating system:', error); log.ERROR('Error creating system:', error);
throw error; throw error;
} }
}; };
@@ -32,7 +34,7 @@ export const getAllSystems = async () => {
const systems = await getDocuments(collectionName); const systems = await getDocuments(collectionName);
return systems; return systems;
} catch (error) { } catch (error) {
console.error('Error getting all systems:', error); log.ERROR('Error getting all systems:', error);
throw error; throw error;
} }
}; };
@@ -45,7 +47,7 @@ export const getSystemByName = async (name) => {
const system = await collection.findOne({ name }); const system = await collection.findOne({ name });
return system; return system;
} catch (error) { } catch (error) {
console.error('Error getting system by name:', error); log.ERROR('Error getting system by name:', error);
throw error; throw error;
} finally { } finally {
// Close the connection // Close the connection
@@ -65,7 +67,7 @@ export const getSystemsByNuid = async (nuid) => {
return systems; return systems;
} catch (error) { } catch (error) {
console.error('Error finding entries:', error); log.ERROR('Error finding entries:', error);
throw error; throw error;
} finally { } finally {
// Close the connection // Close the connection
@@ -82,10 +84,10 @@ export const updateSystemByName = async (name, updatedSystem) => {
try { try {
const collection = db.db().collection(collectionName); const collection = db.db().collection(collectionName);
const result = await collection.updateOne({ name }, { $set: updatedSystem }); const result = await collection.updateOne({ name }, { $set: updatedSystem });
console.log('System updated:', result.modifiedCount); log.INFO('System updated:', result.modifiedCount);
return result.modifiedCount; return result.modifiedCount;
} catch (error) { } catch (error) {
console.error('Error updating system by name:', error); log.ERROR('Error updating system by name:', error);
throw error; throw error;
} finally { } finally {
// Close the connection // Close the connection
@@ -99,10 +101,10 @@ export const deleteSystemByName = async (name) => {
try { try {
const collection = db.db().collection(collectionName); const collection = db.db().collection(collectionName);
const result = await collection.deleteOne({ name }); const result = await collection.deleteOne({ name });
console.log('System deleted:', result.deletedCount); log.INFO('System deleted:', result.deletedCount);
return result.deletedCount; return result.deletedCount;
} catch (error) { } catch (error) {
console.error('Error deleting system by name:', error); log.ERROR('Error deleting system by name:', error);
throw error; throw error;
} finally { } finally {
// Close the connection // Close the connection

View File

@@ -1,3 +1,5 @@
import { DebugBuilder } from "../modules/debugger.mjs";
const log = new DebugBuilder("server", "socketServer");
import express from 'express'; import express from 'express';
import { createServer } from 'node:http'; import { createServer } from 'node:http';
import { Server } from 'socket.io'; import { Server } from 'socket.io';
@@ -15,7 +17,7 @@ app.get('/', (req, res) => {
}); });
nodeIo.on('connection', (socket) => { nodeIo.on('connection', (socket) => {
console.log('a user connected', socket.id); log.INFO('a user connected', socket.id);
socket.on('node-login', async (data) => { socket.on('node-login', async (data) => {
await nodeLoginWrapper(data, socket); await nodeLoginWrapper(data, socket);

View File

@@ -1,3 +1,5 @@
import { DebugBuilder } from "../modules/debugger.mjs";
const log = new DebugBuilder("server", "socketServerWrappers");
import { createNode, getNodeByNuid, updateNodeByNuid } from "./mongo-wrappers/mongoNodesWrappers.mjs" import { createNode, getNodeByNuid, updateNodeByNuid } from "./mongo-wrappers/mongoNodesWrappers.mjs"
import { createSystem, getSystemByName, updateSystemByName, getSystemsByNuid, deleteSystemByName } from "./mongo-wrappers/mongoSystemsWrappers.mjs" import { createSystem, getSystemByName, updateSystemByName, getSystemsByNuid, deleteSystemByName } from "./mongo-wrappers/mongoSystemsWrappers.mjs"
@@ -22,17 +24,16 @@ const sendNodeCommand = async (socket, command, data) => {
* @returns {any} * @returns {any}
*/ */
export const nodeLoginWrapper = async (data, socket) => { export const nodeLoginWrapper = async (data, socket) => {
console.log(`Login requested from node: ${data.nuid}`, data); log.INFO(`Login requested from node: ${data.nuid}`, data);
// Check to see if node exists // Check to see if node exists
var node = await getNodeByNuid(data.nuid); var node = await getNodeByNuid(data.nuid);
console.log("After grabbing", node);
if (!node) { if (!node) {
const insertedId = await createNode(data); const insertedId = await createNode(data);
console.log("Added new node to the database:", insertedId); log.DEBUG("Added new node to the database:", insertedId);
} else { } else {
// Check for updates // Check for updates
const updatedNode = await updateNodeByNuid(data.nuid, data) const updatedNode = await updateNodeByNuid(data.nuid, data)
console.log("Updated node:", updatedNode); log.DEBUG("Updated node:", updatedNode);
} }
node = await getNodeByNuid(data.nuid); node = await getNodeByNuid(data.nuid);
@@ -59,7 +60,7 @@ export const nodeDisconnectWrapper = async (socketId) => {
* @returns {any} * @returns {any}
*/ */
export const nodeUpdateWrapper = async (nodeData) => { export const nodeUpdateWrapper = async (nodeData) => {
console.log("Data update sent by node: ", nodeData); log.DEBUG("Data update sent by node: ", nodeData);
const updateResults = await updateNodeByNuid(nodeData.nuid, nodeData); const updateResults = await updateNodeByNuid(nodeData.nuid, nodeData);
return; return;
} }
@@ -70,10 +71,10 @@ export const nodeUpdateWrapper = async (nodeData) => {
* @param {object} nearbySystems The nearby systems object passed from the node to be updated * @param {object} nearbySystems The nearby systems object passed from the node to be updated
*/ */
export const nearbySystemsUpdateWraper = async (nuid, nearbySystems) => { export const nearbySystemsUpdateWraper = async (nuid, nearbySystems) => {
console.log("System updates sent by node: ", nuid, nearbySystems); log.DEBUG("System updates sent by node: ", nuid, nearbySystems);
// Check to see if the node removed any systems // Check to see if the node removed any systems
const existingSystems = await getSystemsByNuid(nuid); const existingSystems = await getSystemsByNuid(nuid);
console.log("Existing systems:", existingSystems); log.DEBUG("Existing systems:", existingSystems);
if (existingSystems !== nearbySystems) { if (existingSystems !== nearbySystems) {
for (const existingSystem of existingSystems) { for (const existingSystem of existingSystems) {
if (existingSystem.name in nearbySystems) { if (existingSystem.name in nearbySystems) {
@@ -81,17 +82,17 @@ export const nearbySystemsUpdateWraper = async (nuid, nearbySystems) => {
continue; continue;
} }
console.log("System exists that was not given by node", existingSystem); log.DEBUG("System exists that was not given by node", existingSystem);
// Check if this node was the only node on this system // 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 // Remove the system if so
console.log("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); await deleteSystemByName(existingSystem.name);
} else { } else {
// Remove the node from the array if there are other nodes with this system // Remove the node from the array if there are other nodes with this system
console.log("Other nodes found on this system, removing the given NUID"); 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);
console.log(existingSystem); log.DEBUG(existingSystem);
await updateSystemByName(existingSystem.name, existingSystem); await updateSystemByName(existingSystem.name, existingSystem);
} }
} }
@@ -111,7 +112,7 @@ export const nearbySystemsUpdateWraper = async (nuid, nearbySystems) => {
existingSystem.nodes.push(nuid); existingSystem.nodes.push(nuid);
// Update the system with the added node // Update the system with the added node
const updateResults = await updateSystemByName(nearbySystem, existingSystem); const updateResults = await updateSystemByName(nearbySystem, existingSystem);
if (updateResults) console.log("System updated", nearbySystem); if (updateResults) log.INFO("System updated", nearbySystem);
} }
} else { } else {
// The systems are not the same // The systems are not the same
@@ -125,13 +126,13 @@ export const nearbySystemsUpdateWraper = async (nuid, nearbySystems) => {
// Update the system with the added node // Update the system with the added node
const updateResults = await updateSystemByName(nearbySystem, nearbySystems[nearbySystem]); const updateResults = await updateSystemByName(nearbySystem, nearbySystems[nearbySystem]);
if (updateResults) console.log("System updated", nearbySystem); if (updateResults) log.INFO("System updated", nearbySystem);
} }
} }
else { else {
// Create a new system // Create a new system
const newSystem = await createSystem(nearbySystem, nearbySystems[nearbySystem], nuid); const newSystem = await createSystem(nearbySystem, nearbySystems[nearbySystem], nuid);
console.log("New system created", nearbySystem, newSystem); log.INFO("New system created", nearbySystem, newSystem);
} }
} }
return; return;
@@ -146,7 +147,7 @@ export const nearbySystemsUpdateWraper = async (nuid, nearbySystems) => {
export const getSocketIdByNuid = async (nodeIo, nuid) => { export const getSocketIdByNuid = async (nodeIo, nuid) => {
const openSockets = await nodeIo.allSockets(); const openSockets = await nodeIo.allSockets();
for (const openSocketId of openSockets) { for (const openSocketId of openSockets) {
console.log(openSockets) log.DEBUG(openSockets)
const openSocket = await nodeIo.sockets.sockets.get(openSocketId); const openSocket = await nodeIo.sockets.sockets.get(openSocketId);
if (openSocket.node.nuid == nuid) if (openSocket.node.nuid == nuid)
return openSocket; return openSocket;
@@ -171,10 +172,10 @@ export const getAllSocketsConnectedToVC = async (nodeIo, guildId) => {
await new Promise((res) => { await new Promise((res) => {
openSocket.emit('node-check-connected-status', guildId, (status) => { openSocket.emit('node-check-connected-status', guildId, (status) => {
if (status) { if (status) {
console.log("Socket is connected to VC:", openSocket.node.name, status); log.INFO("Socket is connected to VC:", openSocket.node.name, status);
socketsConnectedToVC.push(openSocket); socketsConnectedToVC.push(openSocket);
} else { } else {
console.log("Socket is NOT connected to VC:", openSocket.node.name); log.INFO("Socket is NOT connected to VC:", openSocket.node.name);
} }
res(); res();
}) })
@@ -196,10 +197,10 @@ export const checkIfNodeHasOpenDiscordClient = async (openSocket) => {
await new Promise((res) => { await new Promise((res) => {
openSocket.emit('node-check-discord-open-client', (status) => { openSocket.emit('node-check-discord-open-client', (status) => {
if (status) { if (status) {
console.log("Socket has an open discord client:", openSocket.node.name, status); log.INFO("Socket has an open discord client:", openSocket.node.name, status);
hasOpenDiscordClient = true; hasOpenDiscordClient = true;
} else { } else {
console.log("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(); res();
}) })
@@ -217,10 +218,10 @@ export const getNodeCurrentListeningSystem = async (openSocket) => {
await new Promise((res) => { await new Promise((res) => {
openSocket.emit('node-check-current-system', (system) => { openSocket.emit('node-check-current-system', (system) => {
if (system) { if (system) {
console.log("Socket is listening to system:", openSocket.node.name, system); log.INFO("Socket is listening to system:", openSocket.node.name, system);
currentSystem = system; currentSystem = system;
} else { } else {
console.log("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(); res();
}) })
@@ -307,9 +308,9 @@ export const requestBotLeaveServer = async (socket, guildId) => {
export const requestNodeUpdate = async (socket) => { export const requestNodeUpdate = async (socket) => {
await sendNodeCommand(socket, 'node-update', (status) => { await sendNodeCommand(socket, 'node-update', (status) => {
if (status) { if (status) {
console.log("Node is out of date, updating now", socket.node.name); log.INFO("Node is out of date, updating now", socket.node.name);
} else { } else {
console.log("Node is up to date", socket.node.name); log.INFO("Node is up to date", socket.node.name);
} }
}); });
} }

View File

@@ -4,7 +4,7 @@
"description": "", "description": "",
"main": "server.js", "main": "server.js",
"scripts": { "scripts": {
"test": "mocha --timeout 5000", "test": "jasmine",
"start": "node server.js" "start": "node server.js"
}, },
"author": "Logan Cusano", "author": "Logan Cusano",

View File

@@ -1,13 +1,17 @@
import { createFeed, getAllFeeds, getFeedByLink, updateFeedByLink, deleteFeedByLink, createPost, getPostByPostId } from '../modules/mongo-wrappers/mongoFeedsWrappers.mjs'; import { getAllFeeds, deleteFeedByLink, createPost, getPostByPostId } from '../modules/mongo-wrappers/mongoFeedsWrappers.mjs';
import crypto from 'crypto'; import crypto from 'crypto';
import { sendPost } from '../discordBot/modules/rssWrappers.mjs'; import { sendPost } from '../discordBot/modules/rssWrappers.mjs';
import { DebugBuilder } from "../modules/debugger.mjs"; import { DebugBuilder } from "../modules/debugger.mjs";
import { removeSource } from './sourceManager.mjs'
import UserAgent from "user-agents"; import UserAgent from "user-agents";
import Parser from 'rss-parser';
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();
// Initiate the parser
import Parser from 'rss-parser';
const parser = new Parser({ const parser = new Parser({
headers: { headers: {
'User-Agent': process.env.USER_AGENT_STRING, 'User-Agent': process.env.USER_AGENT_STRING,
@@ -16,13 +20,16 @@ const parser = new Parser({
}); });
const log = new DebugBuilder("server", "feedHandler"); const log = new DebugBuilder("server", "feedHandler");
const runningPostsToRemove = {}; // Assuming this is a global state variable
const sourceFailureLimit = 5; // Define your source failure limit here
export const returnHash = (...stringsIncluded) => { export const returnHash = (...stringsIncluded) => {
return crypto.createHash('sha1').update(`${stringsIncluded.join("-<<??//\\\\??>>-")}`).digest("base64"); return crypto.createHash('sha1').update(stringsIncluded.join("-<<??//\\\\??>>-")).digest("base64");
}; };
/**
* Update the active RSS feeds and send any new posts to their discord channels
* @param {any} client The discord client to send posts with
* @returns {any}
*/
export const updateFeeds = async (client) => { export const updateFeeds = async (client) => {
if (!client) throw new Error("Client object not passed"); if (!client) throw new Error("Client object not passed");
@@ -30,127 +37,57 @@ export const updateFeeds = async (client) => {
const records = await getAllFeeds(); const records = await getAllFeeds();
const sourcePromiseArray = records.map(async (source) => { const sourcePromiseArray = records.map(async (source) => {
log.DEBUG('Record title:', source.title); log.DEBUG('Processing source:', source.title);
log.DEBUG('Record link:', source.link);
log.DEBUG('Record category:', source.category);
log.DEBUG('Record guild ID:', source.guild_id);
log.DEBUG('Record channel ID:', source.channel_id);
try { try {
const parsedFeed = await parser.parseURL(source.link); const parsedFeed = await parser.parseURL(source.link);
if (parsedFeed?.items) { if (parsedFeed?.items) {
await Promise.all(parsedFeed.items.reverse().map(async (post) => { await Promise.all(parsedFeed.items.reverse().map(async (post) => {
log.DEBUG("Parsed Source Keys", Object.keys(post), post?.title); log.DEBUG("Processing post:", post.title);
log.VERBOSE("Post from feed:", post);
if (!post.title || !post.link) throw new Error("Missing information from the post"); if (!post.title || !post.link) throw new Error("Missing title or link in the post");
if (!post.content || !post['content:encoded']) log.WARN("There is no content for post:", post.title); 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); const existingRecord = await getPostByPostId(post.postId);
log.DEBUG("Existing post record:", existingRecord);
if (!existingRecord) { if (!existingRecord) {
const channel = client.channels.cache.get(source.channel_id); const channel = client.channels.cache.get(source.channel_id);
const sendResults = await sendPost(post, source, channel); const sendResults = await sendPost(post, source, channel);
if (!sendResults) throw new Error("Failed to send post");
if (!sendResults) throw new Error("No sending results from sending a post"); log.DEBUG("Saving post to database:", post.title, source.channel_id);
log.DEBUG("Saving post to database:", sendResults, post.title, source.channel_id); const postToSave = {
//await createPost(post); title: post.title,
log.DEBUG("Saved post:", post); link: post.link,
pubDate: post.pubDate,
author: post.author,
contentSnippet: post.contentSnippet,
id: post.id,
isoDate: post.isoDate,
postId: post.postId
};
await createPost(postToSave);
log.DEBUG("Post saved:", postToSave);
} }
})); }));
} else { } else {
await deleteFeedByLink(source.link); await deleteFeedByLink(source.link);
} }
} catch (err) { } catch (err) {
log.ERROR("Parser Error:", source, err); log.ERROR("Error processing source:", source.title, err);
await removeSource(source.link); await removeSource(source.link);
throw err; throw err;
} }
}); });
await Promise.all(sourcePromiseArray); await Promise.all(sourcePromiseArray);
log.DEBUG("All sources finished"); log.DEBUG("All sources processed");
} catch (error) { } catch (error) {
log.ERROR("Error updating feeds:", error); log.ERROR("Error updating feeds:", error);
throw error; throw error;
} }
}; };
/**
* Adds or updates new source URL to configured storage.
* @param {string} title - Title/Name of the RSS feed.
* @param {string} link - URL of RSS feed.
* @param {string} category - Category of RSS feed.
* @param {string} guildId - Guild ID of RSS feed.
* @param {string} channelId - Channel ID of RSS feed.
* @param {function} callback - Callback function.
*/
export const addSource = async (title, link, category, guildId, channelId, callback) => {
try {
const feed = {
title,
link,
category,
guild_id: guildId,
channel_id: channelId
};
const record = await createFeed(feed);
log.DEBUG("Record ID:", record);
return callback(null, record);
} catch (err) {
log.ERROR("Error in create:", err);
return callback(err, null);
}
};
/**
* Wrapper for feeds that cause errors. By default it will wait over a day for the source to come back online before deleting it.
* @param {string} sourceURL - The URL of the feed source causing issues.
*/
export const removeSource = async (sourceURL) => {
log.INFO("Removing source URL:", sourceURL);
if (!runningPostsToRemove[sourceURL]) {
runningPostsToRemove[sourceURL] = { count: 1, timestamp: Date.now(), ignoredAttempts: 0 };
return;
}
const backoffDateTimeDifference = (Date.now() - runningPostsToRemove[sourceURL].timestamp);
const backoffWaitTime = (runningPostsToRemove[sourceURL].count * 30000);
log.DEBUG("Datetime", runningPostsToRemove[sourceURL], backoffDateTimeDifference, backoffWaitTime);
if (backoffDateTimeDifference <= backoffWaitTime) {
runningPostsToRemove[sourceURL].ignoredAttempts += 1;
return;
}
if (runningPostsToRemove[sourceURL].count < sourceFailureLimit) {
runningPostsToRemove[sourceURL].count += 1;
runningPostsToRemove[sourceURL].timestamp = Date.now();
return;
}
try {
const record = await getFeedByLink(sourceURL);
if (!record) {
log.ERROR("No source returned from feedStorage");
return;
}
const results = await deleteFeedByLink(sourceURL);
if (!results) {
log.WARN("No results from remove entry");
return;
}
log.DEBUG("Source exceeded the limit of retries and has been removed", sourceURL);
} catch (err) {
log.ERROR("Error removing source from feedStorage", err);
}
};

View File

@@ -1,37 +1,49 @@
//Will handle updating feeds in all channels // Will handle updating feeds in all channels
import { DebugBuilder } from "../modules/debugger.mjs"; import { DebugBuilder } from "../modules/debugger.mjs";
import { updateFeeds } from "./feedHandler.mjs"; import { updateFeeds } from "./feedHandler.mjs";
import dotenv from 'dotenv'; import dotenv from 'dotenv';
dotenv.config() dotenv.config();
const log = new DebugBuilder("server", "rssController"); const log = new DebugBuilder("server", "rssController");
const refreshInterval = process.env.RSS_REFRESH_INTERVAL ?? 300000; const refreshInterval = parseInt(process.env.RSS_REFRESH_INTERVAL) || 300000;
export class RSSController { export class RSSController {
constructor(client) { constructor(client) {
this.client = client; this.client = client;
this.intervalId = null;
} }
async start() { async start() {
// Wait for the refresh period before starting RSS feeds, so the rest of the bot can start try {
await new Promise(resolve => setTimeout(resolve, refreshInterval));
log.INFO("Starting RSS Controller"); log.INFO("Starting RSS Controller");
// Get initial feeds before starting the infinite loop // Get initial feeds before starting the interval loop
await updateFeeds(this.client);
while(true) {
// Wait for the refresh interval, then wait for the posts to return, then wait a quarter of the refresh interval to make sure everything is cleared up
await new Promise(resolve => setTimeout(resolve, refreshInterval));
await this.collectLatestPosts(); await this.collectLatestPosts();
await new Promise(resolve => setTimeout(resolve, refreshInterval / 4));
// Start the interval loop for updating feeds
this.intervalId = setInterval(async () => {
await this.collectLatestPosts();
}, refreshInterval);
} catch (error) {
log.ERROR(`Failed to start RSS Controller: ${error.message}`);
}
}
async stop() {
if (this.intervalId) {
clearInterval(this.intervalId);
log.INFO("RSS Controller stopped");
} }
} }
async collectLatestPosts() { async collectLatestPosts() {
try {
log.INFO("Updating sources"); log.INFO("Updating sources");
await updateFeeds(this.client); await updateFeeds(this.client);
} catch (error) {
log.ERROR(`Error updating feeds: ${error.message}`);
}
} }
} }

View File

@@ -0,0 +1,75 @@
import { createFeed, getFeedByLink, deleteFeedByLink } from '../modules/mongo-wrappers/mongoFeedsWrappers.mjs';
class SourceManager {
constructor(sourceFailureLimit) {
this.sourceFailureLimit = sourceFailureLimit;
this.runningSourcesToRemove = {};
}
async removeSource(sourceURL) {
log.INFO(`Removing source: ${sourceURL}`);
const currentTime = Date.now();
const sourceData = this.runningSourcesToRemove[sourceURL];
if (!sourceData) {
this.runningSourcesToRemove[sourceURL] = { count: 1, timestamp: currentTime, ignoredAttempts: 0 };
return;
}
const elapsedTimeSinceLastAttempt = currentTime - sourceData.timestamp;
const waitTime = sourceData.count * 30000;
if (elapsedTimeSinceLastAttempt <= waitTime) {
sourceData.ignoredAttempts += 1;
return;
}
if (sourceData.count < this.sourceFailureLimit) {
sourceData.count += 1;
sourceData.timestamp = currentTime;
return;
}
try {
const record = await getFeedByLink(sourceURL);
if (!record) {
log.ERROR(`Source not found in storage: ${sourceURL}`);
return;
}
const results = await deleteFeedByLink(sourceURL);
if (!results) {
log.WARN(`Failed to remove source: ${sourceURL}`);
return;
}
log.DEBUG(`Source removed after exceeding failure limit: ${sourceURL}`);
// Optionally, clean up the entry from runningSourcesToRemove
delete this.runningSourcesToRemove[sourceURL];
} catch (err) {
log.ERROR(`Error removing source from storage: ${sourceURL}`, err);
}
}
async addSource(title, link, category, guildId, channelId, callback) {
try {
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);
} catch (err) {
log.ERROR("Error adding source:", err);
if (callback) callback(err, null);
}
}
}
// 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);

View File

@@ -1,3 +1,5 @@
import { DebugBuilder } from "./modules/debugger.mjs";
const log = new DebugBuilder("server", "server");
import { nodeIo, app, server } from './modules/socketServer.mjs'; import { nodeIo, app, server } from './modules/socketServer.mjs';
import { loadAddons } from './modules/addonManager.mjs'; import { loadAddons } from './modules/addonManager.mjs';
import { serverClient, addEnabledEventListeners } from './discordBot/discordBot.mjs'; import { serverClient, addEnabledEventListeners } from './discordBot/discordBot.mjs';
@@ -7,7 +9,7 @@ dotenv.config()
// Startup the node server // Startup the node server
server.listen(process.env.SERVER_PORT || 3000, () => { server.listen(process.env.SERVER_PORT || 3000, () => {
console.log(`server running at http://localhost:${process.env.SERVER_PORT}`); log.INFO(`server running at http://localhost:${process.env.SERVER_PORT}`);
}); });
// Add objects to the others // Add objects to the others

13
spec/support/jasmine.json Normal file
View File

@@ -0,0 +1,13 @@
{
"spec_dir": "spec",
"spec_files": [
"**/*[sS]pec.?(m)js"
],
"helpers": [
"helpers/**/*.?(m)js"
],
"env": {
"stopSpecOnExpectationFailure": false,
"random": true
}
}

View File

@@ -0,0 +1,29 @@
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
});
});

View File

@@ -0,0 +1,494 @@
// 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();
});
});
});