Compare commits
1 Commits
b9bd732e6c
...
automated-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57aaf04119 |
@@ -1,26 +0,0 @@
|
|||||||
name: Lint JavaScript/Node.js
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lint-js:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout Code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Set up Node.js
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: '16' # Use your preferred Node.js version
|
|
||||||
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: npm install
|
|
||||||
|
|
||||||
- name: Lint JavaScript/Node.js
|
|
||||||
run: npm run lint
|
|
||||||
@@ -25,14 +25,14 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
with: # replace it with your local IP
|
with: # replace it with your local IP
|
||||||
config-inline: |
|
config-inline: |
|
||||||
[registry."git.vpn.cusano.net"]
|
[registry."${{ secrets.LOCAL_GITEA_IP}}:3000"]
|
||||||
http = false
|
http = true
|
||||||
insecure = false
|
insecure = true
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
registry: git.vpn.cusano.net # replace it with your local IP
|
registry: ${{ secrets.LOCAL_GITEA_IP}}:3000 # replace it with your local IP
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
@@ -52,5 +52,5 @@ jobs:
|
|||||||
linux/arm64
|
linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: | # replace it with your local IP and tags
|
tags: | # replace it with your local IP and tags
|
||||||
git.vpn.cusano.net/${{ vars.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 }}:${{ steps.meta.outputs.REPO_VERSION }}
|
||||||
git.vpn.cusano.net/${{ vars.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}
|
${{ secrets.LOCAL_GITEA_IP}}:3000/${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}
|
||||||
@@ -15,7 +15,7 @@ RUN npm install
|
|||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Expose the port on which your Node.js application will run
|
# Expose the port on which your Node.js application will run
|
||||||
EXPOSE 3420
|
EXPOSE 3000
|
||||||
|
|
||||||
# Command to run the Node.js application
|
# Command to run the Node.js application
|
||||||
CMD ["node", "."]
|
CMD ["node", "."]
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
|
||||||
const log = new DebugBuilder("server", "discordBot.addons.gptInteraction");
|
|
||||||
import { gptHandler } from "../modules/gptHandler.mjs";
|
|
||||||
|
|
||||||
export const gptInteraction = async (nodeIo, message) => {
|
|
||||||
let conversation = [];
|
|
||||||
|
|
||||||
let prevMessages = await message.channel.messages.fetch({ limit: 10 });
|
|
||||||
prevMessages.reverse();
|
|
||||||
|
|
||||||
prevMessages.forEach((msg) => {
|
|
||||||
// Check if the message was sent within the last 24 hours
|
|
||||||
if (new Date().getTime() - msg.createdTimestamp > (24 * 60 * 60 * 1000)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if it's from a bot other than the server
|
|
||||||
if (msg.author.bot && msg.author.id !== nodeIo.serverClient.user.id) return;
|
|
||||||
|
|
||||||
const username = msg.author.username.replace(/\s+/g, '_').replace(/[^\w\s]/gi, '');
|
|
||||||
|
|
||||||
if (msg.author.id === nodeIo.serverClient.user.id) {
|
|
||||||
conversation.push({
|
|
||||||
role: 'assistant',
|
|
||||||
//name: msg.author.id,
|
|
||||||
content: msg.content,
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
conversation.push({
|
|
||||||
role: 'user',
|
|
||||||
//name: msg.author.id,
|
|
||||||
content: msg.content.replace(`<@${nodeIo.serverClient.user.id}>`, ''),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const response = await gptHandler(conversation);
|
|
||||||
if (response) {
|
|
||||||
const responseMessage = response;
|
|
||||||
const chunkSize = 2500;
|
|
||||||
|
|
||||||
for (let i = 0; i < responseMessage.length; i += chunkSize) {
|
|
||||||
const chunk = responseMessage.substring(i, i + chunkSize);
|
|
||||||
|
|
||||||
log.DEBUG("Sending message chunk:", chunk);
|
|
||||||
|
|
||||||
await message.reply(chunk);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
message.channel.send('Sorry, I encountered an error while processing your request.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
|
||||||
const log = new DebugBuilder("server", "discordBot.addons.linkCop");
|
|
||||||
import { gptHandler } from "../modules/gptHandler.mjs";
|
|
||||||
import dotenv from 'dotenv';
|
|
||||||
dotenv.config();
|
|
||||||
|
|
||||||
const approvedLinksChannel = "767303243285790721";
|
|
||||||
const restrictedChannelIds = process.env.LINKCOP_RESTRICTED_CHANNEL_IDS.split(',');
|
|
||||||
|
|
||||||
const linkRegExp = /(?:http[s]?:\/\/)?(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)/g
|
|
||||||
|
|
||||||
export const linkCop = async (nodeIo, message) => {
|
|
||||||
if (message.channel.id == approvedLinksChannel || !restrictedChannelIds.includes(message.channel.id)) return false;
|
|
||||||
|
|
||||||
const urls = String(message.content).matchAll(linkRegExp);
|
|
||||||
if (!urls || urls.length === 0) return false;
|
|
||||||
log.DEBUG("Found URLs: ", urls);
|
|
||||||
|
|
||||||
let conversation = [];
|
|
||||||
|
|
||||||
let prevMessages = await message.channel.messages.fetch({ limit: 2 });
|
|
||||||
prevMessages.reverse();
|
|
||||||
|
|
||||||
prevMessages.forEach((msg) => {
|
|
||||||
// Check if the message was sent within the last 5 minutes
|
|
||||||
if (new Date().getTime() - msg.createdTimestamp > (5 * 60 * 1000)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if it's from a bot other than the server
|
|
||||||
if (msg.author.bot && msg.author.id !== nodeIo.serverClient.user.id) return;
|
|
||||||
|
|
||||||
const username = msg.author.username.replace(/\s+/g, '_').replace(/[^\w\s]/gi, '');
|
|
||||||
|
|
||||||
if (msg.author.id === nodeIo.serverClient.user.id) {
|
|
||||||
conversation.push({
|
|
||||||
role: 'assistant',
|
|
||||||
//name: msg.author.id,
|
|
||||||
content: msg.content,
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
conversation.push({
|
|
||||||
role: 'user',
|
|
||||||
//name: msg.author.id,
|
|
||||||
content: msg.content.replace(`<@${nodeIo.serverClient.user.id}>`, ''),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
conversation.push({
|
|
||||||
role: 'assistant',
|
|
||||||
content: `There has been a link posted to a channel that links are not allowed in. The above messages are from the channel that links are not allowed including the message with the link. The message with the link is going to be deleted and moved to the '#links' channels. You are replying to the message with the link to let the user know.`
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await gptHandler(conversation);
|
|
||||||
|
|
||||||
if (response) {
|
|
||||||
const responseMessage = response;
|
|
||||||
const chunkSize = 2000;
|
|
||||||
|
|
||||||
for (let i = 0; i < responseMessage.length; i += chunkSize) {
|
|
||||||
const chunk = responseMessage.substring(i, i + chunkSize);
|
|
||||||
|
|
||||||
log.DEBUG("Sending message chunk:", chunk);
|
|
||||||
|
|
||||||
await message.reply(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
const messageContent = {
|
|
||||||
'author': message.author,
|
|
||||||
'content': `<@${message.author.id}> - ${String(message.content)}`,
|
|
||||||
'channelId': message.channelId,
|
|
||||||
'links': urls
|
|
||||||
}
|
|
||||||
|
|
||||||
await message.delete();
|
|
||||||
log.DEBUG("Message content: ", messageContent);
|
|
||||||
|
|
||||||
message.client.channels.cache.get(approvedLinksChannel).send(messageContent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
|
||||||
const log = new DebugBuilder("server", "discordBot.command.ping");
|
|
||||||
import { SlashCommandBuilder } from 'discord.js';
|
|
||||||
|
|
||||||
// Exporting data property that contains the command structure for discord including any params
|
|
||||||
export const data = new SlashCommandBuilder()
|
|
||||||
.setName('connections')
|
|
||||||
.setDescription('Check to see what bots are online.');
|
|
||||||
|
|
||||||
// Exporting other properties
|
|
||||||
export const example = "/connections"; // An example of how the command would be run in discord chat, this will be used for the help command
|
|
||||||
export const deferInitialReply = false; // If we the initial reply in discord should be deferred. This gives extra time to respond, however the method of replying is different.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function to give the user auto-reply suggestions
|
|
||||||
* @param {any} nodeIo The nodeIO server for manipulation of sockets
|
|
||||||
* @param {any} interaction The interaction object
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
export async function autocomplete(nodeIo, interaction) {
|
|
||||||
const focusedValue = interaction.options.getFocused();
|
|
||||||
const choices = []; // The array to be filled with the autocorrect values
|
|
||||||
const filtered = choices.filter(choice => choice.name.startsWith(focusedValue));
|
|
||||||
log.INFO(focusedValue, choices, filtered);
|
|
||||||
await interaction.respond(filtered.map(choice => ({name: choice.name, value: choice.name})));
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The function to run when the command is called by a discord user
|
|
||||||
* @param {any} nodeIo The nodeIO server for manipulation of sockets
|
|
||||||
* @param {any} interaction The interaction object
|
|
||||||
*/
|
|
||||||
export const execute = async (nodeIo, interaction) => {
|
|
||||||
try {
|
|
||||||
const sockets = await nodeIo.allSockets();
|
|
||||||
log.DEBUG("All open sockets: ",sockets);
|
|
||||||
let socketMessage = "";
|
|
||||||
|
|
||||||
// Create the message for discord with each socket on a new line
|
|
||||||
sockets.forEach(socket => {
|
|
||||||
socketMessage += `\n${socket}`
|
|
||||||
});
|
|
||||||
|
|
||||||
await interaction.reply(`**Online Sockets: '${socketMessage}'**`);
|
|
||||||
//await interaction.reply('**Pong.**');
|
|
||||||
//await interaction.channel.send('**Pong.**');
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
// await interaction.reply(err.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||||
import { SlashCommandBuilder } from 'discord.js';
|
|
||||||
import { joinNode, getAvailableNodes, promptNodeSelection, getUserVoiceChannel } from '../modules/wrappers.mjs';
|
|
||||||
import { getAllSystems, getSystemByName } from '../../modules/mongo-wrappers/mongoSystemsWrappers.mjs';
|
|
||||||
|
|
||||||
const log = new DebugBuilder("server", "discordBot.command.join");
|
const log = new DebugBuilder("server", "discordBot.command.join");
|
||||||
|
import { SlashCommandBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js';
|
||||||
|
import { requestNodeJoinSystem, checkIfNodeIsConnectedToVC, checkIfNodeHasOpenDiscordClient, getNodeCurrentListeningSystem } from '../../modules/socketServerWrappers.mjs';
|
||||||
|
import { getSystemsByNuid, getAllSystems, getSystemByName } from '../../modules/mongo-wrappers/mongoSystemsWrappers.mjs';
|
||||||
|
import { getAvailableTokensInGuild } from '../modules/wrappers.mjs';
|
||||||
|
|
||||||
// Exporting data property
|
// Exporting data property
|
||||||
export const data = new SlashCommandBuilder()
|
export const data = new SlashCommandBuilder()
|
||||||
@@ -13,8 +13,7 @@ export const data = new SlashCommandBuilder()
|
|||||||
system.setName('system')
|
system.setName('system')
|
||||||
.setDescription('The radio system you would like to listen to')
|
.setDescription('The radio system you would like to listen to')
|
||||||
.setRequired(true)
|
.setRequired(true)
|
||||||
.setAutocomplete(true)
|
.setAutocomplete(true));
|
||||||
);
|
|
||||||
|
|
||||||
// Exporting other properties
|
// Exporting other properties
|
||||||
export const example = "/join";
|
export const example = "/join";
|
||||||
@@ -32,62 +31,126 @@ export async function autocomplete(nodeIo, interaction) {
|
|||||||
|
|
||||||
log.DEBUG(focusedValue, choices, filtered);
|
log.DEBUG(focusedValue, choices, filtered);
|
||||||
|
|
||||||
try {
|
|
||||||
await interaction.respond(
|
await interaction.respond(
|
||||||
filtered.map(choice => ({ name: choice.name, value: choice.name }))
|
filtered.map(choice => ({ name: choice.name, value: choice.name })),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
log.WARN("Autocomplete interaction failure", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle join command execution
|
* 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} nodeIo The nodeIO server for manipulation of sockets
|
||||||
* @param {any} interaction The interaction object
|
* @param {any} interaction The interaction object
|
||||||
*/
|
*/
|
||||||
export async function execute(nodeIo, interaction) {
|
export async function execute(nodeIo, interaction) {
|
||||||
|
// Check if the user is in a VC
|
||||||
|
if (!interaction.member.voice.channel) { return await interaction.editReply({ content: `<@${interaction.member.id}>, you need to enter a voice channel before you use this command`, ephemeral: true }) }
|
||||||
|
// Grab the channel if the user is connected to VC
|
||||||
|
const channelToJoin = interaction.member.voice.channel;
|
||||||
|
log.INFO(`The user '${interaction.member.id}' is in the voice channel '${channelToJoin}'`);
|
||||||
|
|
||||||
|
// Get the selected system option from the command interaction
|
||||||
|
const selectedSystem = interaction.options.getString('system');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Validate user is in a voice channel
|
// Get the selected system object from the DB
|
||||||
const channelToJoin = getUserVoiceChannel(interaction);
|
const system = await getSystemByName(selectedSystem);
|
||||||
if (!channelToJoin) return;
|
|
||||||
|
|
||||||
// Get the selected system
|
// Function wrapper to request the selected/only node to join the selected system
|
||||||
const selectedSystemName = interaction.options.getString('system');
|
const joinSelectedNode = async (selectedNodeSocketId) => {
|
||||||
const system = await getSystemByName(selectedSystemName);
|
const openSocket = await nodeIo.sockets.sockets.get(selectedNodeSocketId);
|
||||||
|
// Get the open ID for this connection\
|
||||||
|
const discordTokens = await getAvailableTokensInGuild(nodeIo, interaction.guild.id);
|
||||||
|
log.DEBUG("Available discord tokens: ", discordTokens);
|
||||||
|
|
||||||
// Check if there was a system found by the given system name
|
if (discordTokens.length >= 1) {
|
||||||
if (!system) {
|
// TODO - Implement a method to have preferred tokens (bot users) for specific systems
|
||||||
await interaction.editReply({ content: `System '${selectedSystemName}' not found.`, ephemeral: true });
|
log.INFO("Joining selected open socket:", selectedNodeSocketId, system.name, channelToJoin.id, openSocket.node.name, discordTokens[0].token);
|
||||||
return;
|
|
||||||
|
// Ask the node to join the selected channel and system
|
||||||
|
await requestNodeJoinSystem(openSocket, system.name, channelToJoin.id, discordTokens[0].token);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the available nodes for this system
|
|
||||||
const availableNodes = await getAvailableNodes(nodeIo, interaction.guild.id, system);
|
|
||||||
|
|
||||||
// Check if there are available nodes
|
|
||||||
if (availableNodes.length === 0) {
|
|
||||||
// If not, let the user know
|
|
||||||
await interaction.editReply(`<@${interaction.member.id}>, the selected system has no available nodes`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is one available node, request that node join
|
|
||||||
if (availableNodes.length === 1) {
|
|
||||||
await joinNode(nodeIo, interaction, availableNodes[0].id, system, channelToJoin);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there are more than one available, prompt the user for their selected node
|
|
||||||
else {
|
else {
|
||||||
await promptNodeSelection(interaction, availableNodes, async selectedNode => {
|
return await interaction.editReply({ content: `<@${interaction.member.id}>, there are no free bots. Free up or create a new bot ID (discord app) to listen to this system.`, ephemeral: true })
|
||||||
await joinNode(nodeIo, interaction, selectedNode, system, channelToJoin);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (err) {
|
// Get all open socket nodes
|
||||||
log.ERROR(err);
|
const openSockets = [...await nodeIo.allSockets()]; // TODO - Filter the returned nodes to only nodes that have the radio capability
|
||||||
await interaction.editReply({ content: `An error occurred: ${err.message}`, ephemeral: true });
|
log.DEBUG("All open sockets: ", openSockets);
|
||||||
|
|
||||||
|
var availableNodes = [];
|
||||||
|
// Check each open socket to see if the node has the requested system
|
||||||
|
await Promise.all(openSockets.map(async openSocket => {
|
||||||
|
openSocket = await nodeIo.sockets.sockets.get(openSocket);
|
||||||
|
// Check if the node has an existing open client (meaning the radio is already being listened to)
|
||||||
|
const hasOpenClient = await checkIfNodeHasOpenDiscordClient(openSocket);
|
||||||
|
if (hasOpenClient) {
|
||||||
|
let currentSystem = await getNodeCurrentListeningSystem(openSocket);
|
||||||
|
if (currentSystem != system.name) {
|
||||||
|
log.INFO("Node is listening to a different system than requested", openSocket.node.name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the bot has an open voice connection in the requested server already
|
||||||
|
const connected = await checkIfNodeIsConnectedToVC(nodeIo, interaction.guild.id, openSocket.node.nuid);
|
||||||
|
log.INFO("Connected:", connected);
|
||||||
|
if (!connected) {
|
||||||
|
// Check if this node has the requested system, if so add it to the availble array
|
||||||
|
if (system.nodes.includes(openSocket.node.nuid)) {
|
||||||
|
availableNodes.push(openSocket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
log.DEBUG("Availble nodes:", availableNodes.map(socket => socket.node.name));
|
||||||
|
|
||||||
|
// If there are no available nodes, let the user know there are none available
|
||||||
|
if (availableNodes.length == 0) {
|
||||||
|
// There are no nodes availble for the requested system
|
||||||
|
return await interaction.editReply(`<@${interaction.member.id}>, the selected system has no available nodes`);
|
||||||
|
} else if (availableNodes.length == 1) {
|
||||||
|
// There is only one node available for the requested system
|
||||||
|
// Request the node to join
|
||||||
|
await joinSelectedNode(availableNodes[0].id);
|
||||||
|
// Let the user know
|
||||||
|
await interaction.editReply({ content: `Ok <@${interaction.member.id}>, a bot will join your channel listening to *'${system.name}'* shortly`, components: [] });
|
||||||
|
} else if (availableNodes.length > 1) {
|
||||||
|
// There is more than one node availble for the requested system
|
||||||
|
const nodeSelectionButtons = []
|
||||||
|
|
||||||
|
// Create a button for each available node
|
||||||
|
for (const availableNode of availableNodes) {
|
||||||
|
nodeSelectionButtons.push(new ButtonBuilder().setCustomId(availableNode.id).setLabel(availableNode.node.name).setStyle(ButtonStyle.Primary));
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionRow = new ActionRowBuilder().addComponents(nodeSelectionButtons);
|
||||||
|
|
||||||
|
// Reply to the user with the button prompts
|
||||||
|
const response = await interaction.editReply({
|
||||||
|
content: `<@${interaction.member.id}>, Please select the Node you would like to join with this system`,
|
||||||
|
components: [actionRow]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Make sure the responding selection is from the user who initiated the command
|
||||||
|
const collectorFilter = i => i.user.id === interaction.user.id;
|
||||||
|
|
||||||
|
// Wait for the confirmation from the user on which node to join
|
||||||
|
try {
|
||||||
|
const selectedNode = await response.awaitMessageComponent({ filter: collectorFilter, time: 60_000 });
|
||||||
|
// Run the local wrapper to listen to the selected node
|
||||||
|
await joinSelectedNode(selectedNode.customId);
|
||||||
|
// Let the user know
|
||||||
|
await selectedNodeConfirmation.update({ content: `Ok <@${interaction.member.id}>, a bot will join your channel listening to *'${system.name}'*`, components: [] });
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
// Timeout the prompt if the user doesn't interact with it
|
||||||
|
await interaction.editReply({ content: 'Confirmation not received within 1 minute, cancelling', components: [] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
// await interaction.reply(err.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
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'
|
||||||
|
|
||||||
const log = new DebugBuilder("server", "discordBot.command.leave");
|
|
||||||
|
|
||||||
// Exporting data property
|
// Exporting data property
|
||||||
export const data = new SlashCommandBuilder()
|
export const data = new SlashCommandBuilder()
|
||||||
@@ -13,8 +12,7 @@ export const data = new SlashCommandBuilder()
|
|||||||
system.setName('bot')
|
system.setName('bot')
|
||||||
.setDescription('The bot you would like to disconnect')
|
.setDescription('The bot you would like to disconnect')
|
||||||
.setRequired(true)
|
.setRequired(true)
|
||||||
.setAutocomplete(true)
|
.setAutocomplete(true));;
|
||||||
);
|
|
||||||
|
|
||||||
// Exporting other properties
|
// Exporting other properties
|
||||||
export const example = "/leave *{Bot Name}*";
|
export const example = "/leave *{Bot Name}*";
|
||||||
@@ -27,22 +25,15 @@ export const deferInitialReply = true;
|
|||||||
*/
|
*/
|
||||||
export async function autocomplete(nodeIo, interaction) {
|
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));
|
||||||
|
|
||||||
log.DEBUG(choices);
|
log.DEBUG(choices);
|
||||||
|
|
||||||
const filtered = choices
|
const filtered = choices.filter(choice => choice.name.startsWith(focusedValue)).map(choice => choice = {name: choice.name, value: choice.nuid});
|
||||||
.filter(choice => choice.name.startsWith(focusedValue))
|
|
||||||
.map(choice => ({ name: choice.name, value: choice.nuid }));
|
|
||||||
|
|
||||||
log.DEBUG(focusedValue, choices, filtered);
|
log.DEBUG(focusedValue, choices, filtered);
|
||||||
|
|
||||||
try{
|
|
||||||
await interaction.respond(filtered);
|
await interaction.respond(filtered);
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
log.WARN("Autocomplete interaction failure", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,19 +43,16 @@ export async function autocomplete(nodeIo, interaction) {
|
|||||||
*/
|
*/
|
||||||
export async function execute(nodeIo, interaction) {
|
export async function execute(nodeIo, interaction) {
|
||||||
try {
|
try {
|
||||||
|
// 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);
|
||||||
|
log.DEBUG("All open sockets:", socket, selectedNode);
|
||||||
if (!socket) {
|
|
||||||
await interaction.editReply({ content: `Bot '${selectedNode}' not found or not connected.`, ephemeral: true });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await requestBotLeaveServer(socket, interaction.guild.id);
|
await requestBotLeaveServer(socket, interaction.guild.id);
|
||||||
|
//await interaction.reply(`**Online Sockets: '${sockets}'**`);
|
||||||
await interaction.editReply(`Ok <@${interaction.member.id}>, the bot is leaving shortly.`);
|
await interaction.editReply(`Ok <@${interaction.member.id}>, the bot is leaving shortly`);
|
||||||
|
//await interaction.channel.send('**Pong.**');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.ERROR("Failed to disconnect bot:", err);
|
console.error(err);
|
||||||
await interaction.editReply({ content: `An error occurred: ${err.message}`, ephemeral: true });
|
// await interaction.reply(err.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,19 +54,18 @@ export const execute = async (nodeIo, interaction) => {
|
|||||||
var category = interaction.options.getString('category');
|
var category = interaction.options.getString('category');
|
||||||
|
|
||||||
if (!category) category = "ALL";
|
if (!category) category = "ALL";
|
||||||
await interaction.reply(`Adding ${title} to the list of RSS sources, please wait...`);
|
|
||||||
|
|
||||||
await addSource(title, link, category, interaction.guildId, interaction.channelId, (err, result) => {
|
await addSource(title, link, category, interaction.guildId, interaction.channelId, (err, result) => {
|
||||||
log.DEBUG("Result from adding entry", result);
|
log.DEBUG("Result from adding entry", result);
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
interaction.editReply(`Successfully added ${title} to the list of RSS sources`);
|
interaction.reply(`Successfully added ${title} to the list of RSS sources`);
|
||||||
} else {
|
} else {
|
||||||
interaction.editReply(`${title} already exists in the list of RSS sources`);
|
interaction.reply(`${title} already exists in the list of RSS sources`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.ERROR(err)
|
log.ERROR(err)
|
||||||
await interaction.editReply(err.toString());
|
await interaction.reply(err.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,17 +42,17 @@ export async function autocomplete(nodeIo, interaction) {
|
|||||||
export const execute = async (nodeIo, interaction) => {
|
export const execute = async (nodeIo, interaction) => {
|
||||||
try {
|
try {
|
||||||
var title = interaction.options.getString('title');
|
var title = interaction.options.getString('title');
|
||||||
await interaction.reply(`Removing ${title} from the list of RSS sources, please wait...`);
|
interaction.reply(`Removing ${title} from the list of RSS sources, please wait...`);
|
||||||
|
|
||||||
const results = await deleteFeedByTitle(title);
|
const results = await deleteFeedByTitle(title);
|
||||||
if (!results) {
|
if (!results) {
|
||||||
log.WARN(`Failed to remove source: ${title}`);
|
log.WARN(`Failed to remove source: ${title}`);
|
||||||
await interaction.editReply(`Failed to remove source: '${title}'`);
|
interaction.editReply(`Failed to remove source: '${title}'`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await interaction.editReply(`${title} was successfully removed from the RSS sources.`)
|
interaction.editReply(`${title} was successfully removed from the RSS sources.`)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.ERROR(err)
|
log.ERROR(err)
|
||||||
await interaction.editReply(err.toString());
|
interaction.editReply(err.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,6 @@ export const execute = async (nodeIo, interaction) => {
|
|||||||
//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);
|
||||||
await interaction.editReply('RSS Update Completed');
|
|
||||||
//await interaction.channel.send('**Pong.**');
|
//await interaction.channel.send('**Pong.**');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export function addEnabledEventListeners(serverClient, _eventsPath = "./events")
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The discord client
|
// The discord client
|
||||||
export const serverClient = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildPresences] });
|
export const serverClient = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates] });
|
||||||
|
|
||||||
// Run when the bot is ready
|
// Run when the bot is ready
|
||||||
serverClient.on('ready', async () => {
|
serverClient.on('ready', async () => {
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
|
||||||
const log = new DebugBuilder("server", "discordBot.events.guildCreate");
|
|
||||||
import { Events } from 'discord.js';
|
|
||||||
import { addEnabledCommands, addEnabledEventListeners } from "../discordBot.mjs";
|
|
||||||
|
|
||||||
export const name = Events.GuildMemberAdd;
|
|
||||||
|
|
||||||
export async function execute(nodeIo, guild) {
|
|
||||||
log.INFO("Bot has joined a new server", guild);
|
|
||||||
|
|
||||||
log.DEBUG("Refreshing commands enabled");
|
|
||||||
await addEnabledCommands(nodeIo.serverClient);
|
|
||||||
|
|
||||||
log.DEBUG("Refreshing events enabled");
|
|
||||||
await addEnabledEventListeners(nodeIo.serverClient);
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
|
||||||
const log = new DebugBuilder("server", "discordBot.events.guildMemberAdd");
|
|
||||||
import dotenv from 'dotenv';
|
|
||||||
dotenv.config();
|
|
||||||
import { Events } from 'discord.js';
|
|
||||||
import { gptHandler } from "../modules/gptHandler.mjs";
|
|
||||||
|
|
||||||
const welcomeChannel = process.env.WELCOME_CHANNEL_ID; // TODO - Need to add a DB section for server configs so it's not static to one server
|
|
||||||
|
|
||||||
export const name = Events.GuildMemberAdd;
|
|
||||||
|
|
||||||
export async function execute(nodeIo, member) {
|
|
||||||
log.INFO("New user joined the server", member);
|
|
||||||
let conversation = [];
|
|
||||||
conversation.push({
|
|
||||||
role: 'assistant',
|
|
||||||
content: `A new user has joined the server. Their name is '<@${member.id}>'. Please welcome them to the server and remind them about the rules.`
|
|
||||||
})
|
|
||||||
|
|
||||||
const response = await gptHandler(conversation);
|
|
||||||
if (response) {
|
|
||||||
const responseMessage = response.choices[0].message.content;
|
|
||||||
const chunkSize = 2500;
|
|
||||||
|
|
||||||
for (let i = 0; i < responseMessage.length; i += chunkSize) {
|
|
||||||
const chunk = responseMessage.substring(i, i + chunkSize);
|
|
||||||
|
|
||||||
log.DEBUG("Sending message chunk:", chunk);
|
|
||||||
|
|
||||||
await nodeIo.serverClient.channels.cache.get(welcomeChannel).send(chunk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
|
||||||
const log = new DebugBuilder("server", "discordBot.events.messageCreate");
|
|
||||||
import dotenv from 'dotenv';
|
|
||||||
dotenv.config();
|
|
||||||
import { Events } from 'discord.js';
|
|
||||||
import { gptInteraction } from '../addons/gptInteraction.mjs';
|
|
||||||
import { linkCop } from '../addons/linkCop.mjs';
|
|
||||||
|
|
||||||
const IGNORED_CHANNELS = process.env.IGNORED_CHANNEL_IDS.split(',');
|
|
||||||
|
|
||||||
export const name = Events.MessageCreate;
|
|
||||||
|
|
||||||
export async function execute(nodeIo, message) {
|
|
||||||
// Ignore ignored channels
|
|
||||||
if (IGNORED_CHANNELS.includes(message.channel.id)) return;
|
|
||||||
|
|
||||||
// Ignore messages from a bot
|
|
||||||
if (message.author.bot) return;
|
|
||||||
|
|
||||||
log.INFO("Message create", message);
|
|
||||||
|
|
||||||
// Check if the message mentions the bot
|
|
||||||
if (message.mentions.users.has(nodeIo.serverClient.user.id)) {
|
|
||||||
return await gptInteraction(nodeIo, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the message contains a link in a channel it shouldn't
|
|
||||||
if (await linkCop(nodeIo, message)) return;
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
|
||||||
const log = new DebugBuilder("server", "discordBot.modules.gptHandler");
|
|
||||||
import dotenv from 'dotenv';
|
|
||||||
dotenv.config();
|
|
||||||
|
|
||||||
import { OpenAI } from 'openai';
|
|
||||||
import { EventEmitter } from 'events';
|
|
||||||
|
|
||||||
const openai = new OpenAI(process.env.OPENAI_API_KEY);
|
|
||||||
|
|
||||||
const assistant = await openai.beta.assistants.create({
|
|
||||||
name: "Emmelia",
|
|
||||||
instructions: process.env.DRB_SERVER_INITIAL_PROMPT,
|
|
||||||
model: "gpt-4o",
|
|
||||||
});
|
|
||||||
|
|
||||||
class EventHandler extends EventEmitter {
|
|
||||||
constructor(client) {
|
|
||||||
super();
|
|
||||||
this.client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
async onEvent(event) {
|
|
||||||
try {
|
|
||||||
console.log(event);
|
|
||||||
// Retrieve events that are denoted with 'requires_action'
|
|
||||||
// since these will have our tool_calls
|
|
||||||
if (event.event === "thread.run.requires_action") {
|
|
||||||
await this.handleRequiresAction(
|
|
||||||
event.data,
|
|
||||||
event.data.id,
|
|
||||||
event.data.thread_id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error handling event:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleRequiresAction(data, runId, threadId) {
|
|
||||||
try {
|
|
||||||
const toolOutputs =
|
|
||||||
data.required_action.submit_tool_outputs.tool_calls.map((toolCall) => {
|
|
||||||
// Call the function
|
|
||||||
switch (toolCall.function.name) {
|
|
||||||
case "getCurrentTemperature": return {
|
|
||||||
tool_call_id: toolCall.id,
|
|
||||||
output: "57",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Submit all the tool outputs at the same time
|
|
||||||
await this.submitToolOutputs(toolOutputs, runId, threadId);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error processing required action:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async submitToolOutputs(toolOutputs, runId, threadId) {
|
|
||||||
try {
|
|
||||||
// Use the submitToolOutputsStream helper
|
|
||||||
const stream = this.client.beta.threads.runs.submitToolOutputsStream(
|
|
||||||
threadId,
|
|
||||||
runId,
|
|
||||||
{ tool_outputs: toolOutputs },
|
|
||||||
);
|
|
||||||
for await (const event of stream) {
|
|
||||||
this.emit("event", event);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error submitting tool outputs:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const eventHandler = new EventHandler(openai);
|
|
||||||
eventHandler.on("event", eventHandler.onEvent.bind(eventHandler));
|
|
||||||
|
|
||||||
export const gptHandler = async (additionalMessages) => {
|
|
||||||
const thread = await openai.beta.threads.create();
|
|
||||||
|
|
||||||
// Add the additional messages to the conversation
|
|
||||||
for (const msgObj of additionalMessages) {
|
|
||||||
await openai.beta.threads.messages.create(
|
|
||||||
thread.id,
|
|
||||||
msgObj
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
log.DEBUG("AI Conversation:", thread);
|
|
||||||
|
|
||||||
// Run the thread to get a response
|
|
||||||
try {
|
|
||||||
const stream = await openai.beta.threads.runs.stream(
|
|
||||||
thread.id,
|
|
||||||
{ assistant_id: assistant.id },
|
|
||||||
eventHandler,
|
|
||||||
);
|
|
||||||
|
|
||||||
for await (const event of stream) {
|
|
||||||
eventHandler.emit("event", event);
|
|
||||||
}
|
|
||||||
|
|
||||||
let response;
|
|
||||||
const messages = await openai.beta.threads.messages.list(
|
|
||||||
thread.id
|
|
||||||
);
|
|
||||||
response = messages.data[0].content[0].text.value;
|
|
||||||
|
|
||||||
log.DEBUG("AI Response:", response);
|
|
||||||
|
|
||||||
if (!response) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error generating response:', error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||||
const log = new DebugBuilder("server", "discordBot.modules.wrappers");
|
const log = new DebugBuilder("server", "discordBot.modules.wrappers");
|
||||||
import { checkIfNodeIsConnectedToVC, getNodeDiscordID, getNodeDiscordUsername, checkIfNodeHasOpenDiscordClient, getNodeCurrentListeningSystem, requestNodeJoinSystem } 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'
|
||||||
import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const checkOnlineBotsInGuild = async (nodeIo, guildId) => {
|
export const checkOnlineBotsInGuild = async (nodeIo, guildId) => {
|
||||||
@@ -25,10 +23,10 @@ export const checkOnlineBotsInGuild = async (nodeIo, guildId) => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
return onlineBots;
|
return onlineBots;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const getAvailableTokensInGuild = async (nodeIo, guildId) => {
|
export const getAvailableTokensInGuild = async (nodeIo, guildId) => {
|
||||||
try {
|
try {
|
||||||
// Execute both asynchronous functions concurrently
|
// Execute both asynchronous functions concurrently
|
||||||
const [discordIDs, onlineBots] = await Promise.all([
|
const [discordIDs, onlineBots] = await Promise.all([
|
||||||
@@ -50,118 +48,3 @@ export const getAvailableTokensInGuild = async (nodeIo, guildId) => {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the nodes with given system that are available to be used within a given server
|
|
||||||
* @param {any} nodeIo The nodeIO object contained in the discord server object
|
|
||||||
* @param {any} guildId The guild ID to search in
|
|
||||||
* @param {any} system The system to filter the nodes by
|
|
||||||
* @returns {any}
|
|
||||||
*/
|
|
||||||
export const getAvailableNodes = async (nodeIo, guildId, system) => {
|
|
||||||
// Get all open socket nodes
|
|
||||||
const openSockets = [...await nodeIo.allSockets()]; // TODO - Filter the returned nodes to only nodes that have the radio capability
|
|
||||||
log.DEBUG("All open sockets: ", openSockets);
|
|
||||||
|
|
||||||
var availableNodes = [];
|
|
||||||
// Check each open socket to see if the node has the requested system
|
|
||||||
await Promise.all(openSockets.map(async openSocket => {
|
|
||||||
openSocket = await nodeIo.sockets.sockets.get(openSocket);
|
|
||||||
// Check if the node has an existing open client (meaning the radio is already being listened to)
|
|
||||||
const hasOpenClient = await checkIfNodeHasOpenDiscordClient(openSocket);
|
|
||||||
if (hasOpenClient) {
|
|
||||||
let currentSystem = await getNodeCurrentListeningSystem(openSocket);
|
|
||||||
if (currentSystem != system.name) {
|
|
||||||
log.INFO("Node is listening to a different system than requested", openSocket.node.name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the bot has an open voice connection in the requested server already
|
|
||||||
const connected = await checkIfNodeIsConnectedToVC(nodeIo, guildId, openSocket.node.nuid);
|
|
||||||
log.INFO("Connected:", connected);
|
|
||||||
if (!connected) {
|
|
||||||
// Check if this node has the requested system, if so add it to the availble array
|
|
||||||
if (system.nodes.includes(openSocket.node.nuid)) {
|
|
||||||
availableNodes.push(openSocket);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}));
|
|
||||||
|
|
||||||
log.DEBUG("Availble nodes:", availableNodes.map(socket => socket.node.name));
|
|
||||||
|
|
||||||
return availableNodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the voice channel the user is currently in.
|
|
||||||
* @param {any} interaction - The interaction object.
|
|
||||||
* @returns {any} - The voice channel object, or null if the user is not in a voice channel.
|
|
||||||
*/
|
|
||||||
export const getUserVoiceChannel = (interaction) => {
|
|
||||||
if (!interaction.member.voice.channel) {
|
|
||||||
interaction.editReply({ content: `<@${interaction.member.id}>, you need to enter a voice channel before using this command`, ephemeral: true });
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return interaction.member.voice.channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Joins a node to a specified system and voice channel.
|
|
||||||
* @param {any} nodeIo - The nodeIO server for manipulation of sockets.
|
|
||||||
* @param {any} interaction - The interaction object.
|
|
||||||
* @param {string} nodeId - The ID of the node to join.
|
|
||||||
* @param {any} system - The system object to join.
|
|
||||||
* @param {any} channel - The voice channel to join.
|
|
||||||
*/
|
|
||||||
export const joinNode = async (nodeIo, interaction, nodeId, system, channel) => {
|
|
||||||
try {
|
|
||||||
const openSocket = await nodeIo.sockets.sockets.get(nodeId);
|
|
||||||
const discordTokens = await getAvailableTokensInGuild(nodeIo, interaction.guild.id);
|
|
||||||
|
|
||||||
if (discordTokens.length === 0) {
|
|
||||||
await interaction.editReply({ content: `<@${interaction.member.id}>, there are no free bots available.`, ephemeral: true });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.INFO("Joining node:", nodeId, system.name, channel.id, openSocket.node.name, discordTokens[0].token);
|
|
||||||
await requestNodeJoinSystem(openSocket, system.name, channel.id, discordTokens[0].token);
|
|
||||||
|
|
||||||
await interaction.editReply({ content: `<@${interaction.member.id}>, a bot will join your channel listening to '${system.name}' shortly.`, ephemeral: true });
|
|
||||||
} catch (err) {
|
|
||||||
log.ERROR("Failed to join node:", err);
|
|
||||||
await interaction.editReply({ content: `<@${interaction.member.id}>, an error occurred while joining the node: ${err.message}`, ephemeral: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prompts the user to select a node from available nodes.
|
|
||||||
* @param {any} interaction - The interaction object.
|
|
||||||
* @param {Array} availableNodes - The list of available nodes.
|
|
||||||
* @param {Function} onNodeSelected - Callback function to handle the selected node.
|
|
||||||
*/
|
|
||||||
export const promptNodeSelection = async (interaction, availableNodes, onNodeSelected) => {
|
|
||||||
const nodeSelectionButtons = availableNodes.map(node =>
|
|
||||||
new ButtonBuilder().setCustomId(node.id).setLabel(node.node.name).setStyle(ButtonStyle.Primary)
|
|
||||||
);
|
|
||||||
|
|
||||||
const actionRow = new ActionRowBuilder().addComponents(nodeSelectionButtons);
|
|
||||||
|
|
||||||
const response = await interaction.editReply({
|
|
||||||
content: `<@${interaction.member.id}>, please select the Node you would like to join with this system:`,
|
|
||||||
components: [actionRow],
|
|
||||||
ephemeral: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const collectorFilter = i => i.user.id === interaction.user.id;
|
|
||||||
try {
|
|
||||||
const selectedNode = await response.awaitMessageComponent({ filter: collectorFilter, time: 60_000 });
|
|
||||||
await onNodeSelected(selectedNode.customId);
|
|
||||||
} catch (e) {
|
|
||||||
log.ERROR("Node selection timeout:", e);
|
|
||||||
await interaction.editReply({ content: 'Confirmation not received within 1 minute, cancelling.', components: [] });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
7
makefile
7
makefile
@@ -20,13 +20,6 @@ run:
|
|||||||
-e SERVER_PORT=${SERVER_PORT} \
|
-e SERVER_PORT=${SERVER_PORT} \
|
||||||
-e MONGO_URL=${MONGO_URL} \
|
-e MONGO_URL=${MONGO_URL} \
|
||||||
-e DISCORD_TOKEN=${DISCORD_TOKEN} \
|
-e DISCORD_TOKEN=${DISCORD_TOKEN} \
|
||||||
-e RSS_REFRESH_INTERVAL=${RSS_REFRESH_INTERVAL} \
|
|
||||||
-e WELCOME_CHANNEL_ID=${WELCOME_CHANNEL_ID} \
|
|
||||||
-e IGNORED_CHANNEL_IDS=${IGNORED_CHANNEL_IDS} \
|
|
||||||
-e LINKCOP_RESTRICTED_CHANNEL_IDS=${LINKCOP_RESTRICTED_CHANNEL_IDS} \
|
|
||||||
-e DRB_SERVER_INITIAL_PROMPT=${DRB_SERVER_INITIAL_PROMPT} \
|
|
||||||
-e OPENAI_API_KEY=${OPENAI_API_KEY} \
|
|
||||||
-e LOG_LOCATION="./logs/server.log" \
|
|
||||||
-p ${SERVER_PORT}:${SERVER_PORT} \
|
-p ${SERVER_PORT}:${SERVER_PORT} \
|
||||||
--name=drb \
|
--name=drb \
|
||||||
$(DOCKER_IMAGE_NAME)
|
$(DOCKER_IMAGE_NAME)
|
||||||
@@ -195,7 +195,6 @@ export const checkIfNodeHasOpenDiscordClient = async (openSocket) => {
|
|||||||
// Check the open socket to see if the node has an open discord client
|
// Check the open socket to see if the node has an open discord client
|
||||||
let hasOpenDiscordClient = false;
|
let hasOpenDiscordClient = false;
|
||||||
await new Promise((res) => {
|
await new Promise((res) => {
|
||||||
log.INFO("Checking if socket has an open connection:", openSocket.node.name)
|
|
||||||
openSocket.emit('node-check-discord-open-client', (status) => {
|
openSocket.emit('node-check-discord-open-client', (status) => {
|
||||||
if (status) {
|
if (status) {
|
||||||
log.INFO("Socket has an open discord client:", openSocket.node.name, status);
|
log.INFO("Socket has an open discord client:", openSocket.node.name, status);
|
||||||
@@ -217,7 +216,6 @@ export const getNodeCurrentListeningSystem = async (openSocket) => {
|
|||||||
// check what system the socket is listening to
|
// check what system the socket is listening to
|
||||||
let currentSystem = undefined;
|
let currentSystem = undefined;
|
||||||
await new Promise((res) => {
|
await new Promise((res) => {
|
||||||
log.INFO("Checking system node is currently listening to:", openSocket.node.name)
|
|
||||||
openSocket.emit('node-check-current-system', (system) => {
|
openSocket.emit('node-check-current-system', (system) => {
|
||||||
if (system) {
|
if (system) {
|
||||||
log.INFO("Socket is listening to system:", openSocket.node.name, system);
|
log.INFO("Socket is listening to system:", openSocket.node.name, system);
|
||||||
|
|||||||
829
package-lock.json
generated
829
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
18
package.json
18
package.json
@@ -4,28 +4,26 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint . --ext .js,.mjs",
|
"test": "jasmine",
|
||||||
"test": "mocha --timeout 5000",
|
|
||||||
"start": "node server.js"
|
"start": "node server.js"
|
||||||
},
|
},
|
||||||
"author": "Logan Cusano",
|
"author": "Logan Cusano",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"chai": "^5.1.1",
|
"chai": "^5.1.0",
|
||||||
"mocha": "^10.4.0",
|
"mocha": "^10.4.0",
|
||||||
"socket.io-client": "^4.7.5"
|
"socket.io-client": "^4.7.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"discord.js": "^14.15.2",
|
"discord.js": "^14.14.1",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.3.1",
|
||||||
"express": "^4.19.2",
|
"express": "^4.18.2",
|
||||||
"mongodb": "^6.7.0",
|
"mongodb": "^6.3.0",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"node-html-parser": "^6.1.13",
|
"node-html-parser": "^6.1.13",
|
||||||
"openai": "^4.47.3",
|
|
||||||
"rss-parser": "^3.13.0",
|
"rss-parser": "^3.13.0",
|
||||||
"socket.io": "^4.7.5",
|
"socket.io": "^4.7.2",
|
||||||
"user-agents": "^1.1.222"
|
"user-agents": "^1.1.208"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { DebugBuilder } from "../modules/debugger.mjs";
|
|
||||||
const log = new DebugBuilder("server", "sourceManager");
|
|
||||||
import { createFeed, getFeedByLink, deleteFeedByLink } from '../modules/mongo-wrappers/mongoFeedsWrappers.mjs';
|
import { createFeed, getFeedByLink, deleteFeedByLink } from '../modules/mongo-wrappers/mongoFeedsWrappers.mjs';
|
||||||
|
|
||||||
class SourceManager {
|
class SourceManager {
|
||||||
|
|||||||
13
spec/support/jasmine.json
Normal file
13
spec/support/jasmine.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"spec_dir": "spec",
|
||||||
|
"spec_files": [
|
||||||
|
"**/*[sS]pec.?(m)js"
|
||||||
|
],
|
||||||
|
"helpers": [
|
||||||
|
"helpers/**/*.?(m)js"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"stopSpecOnExpectationFailure": false,
|
||||||
|
"random": true
|
||||||
|
}
|
||||||
|
}
|
||||||
29
spec/test/rssManager.spec.js.b
Normal file
29
spec/test/rssManager.spec.js.b
Normal 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
|
||||||
|
});
|
||||||
|
});
|
||||||
494
spec/test/socketServerWrappers.spec.js
Normal file
494
spec/test/socketServerWrappers.spec.js
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user