import { DebugBuilder } from "../../modules/debugger.mjs"; const log = new DebugBuilder("server", "discordBot.modules.wrappers"); import { checkIfNodeIsConnectedToVC, getNodeDiscordID, getNodeDiscordUsername, checkIfNodeHasOpenDiscordClient, getNodeCurrentListeningSystem, requestNodeJoinSystem, } from "../../modules/socketServerWrappers.mjs"; import { getAllDiscordIDs } from "../../modules/mongo-wrappers/mongoDiscordIDWrappers.mjs"; import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js"; export const checkOnlineBotsInGuild = async (nodeIo, guildId) => { let onlineBots = []; const openSockets = [...(await nodeIo.allSockets())]; await Promise.all( openSockets.map(async (openSocket) => { openSocket = await nodeIo.sockets.sockets.get(openSocket); const connected = await checkIfNodeIsConnectedToVC( nodeIo, guildId, openSocket.node.nuid, ); log.INFO("Connected:", connected); if (connected) { const username = await getNodeDiscordUsername(openSocket, guildId); const discordID = await getNodeDiscordID(openSocket); onlineBots.push({ name: username, discord_id: discordID, nuid: openSocket.node.nuid, }); } }), ); return onlineBots; }; export const getAvailableTokensInGuild = async (nodeIo, guildId) => { try { // Execute both asynchronous functions concurrently const [discordIDs, onlineBots] = await Promise.all([ getAllDiscordIDs(), // Fetch all Discord IDs checkOnlineBotsInGuild(nodeIo, guildId), // Check online bots in the guild ]); // Use the results of both promises here log.INFO("Available Discord IDs:", discordIDs); log.INFO("Online bots in the guild:", onlineBots); // Filter any discordIDs that are not active const availableDiscordIDs = discordIDs .filter((discordID) => discordID.active == true) .filter( (discordID) => !onlineBots.some( (bot) => Number(bot.discord_id) == discordID.discord_id, ), ); // Return the unavailable discordIDs return availableDiscordIDs; } catch (error) { console.error("Error getting available tokens in guild:", error); throw error; } }; /** * Get the nodes with given system that are available to be used within a given server * @param {any} nodeIo The nodeIO object contained in the discord server object * @param {any} guildId The guild ID to search in * @param {any} system The system to filter the nodes by * @returns {any} */ export const getAvailableNodes = async (nodeIo, guildId, system) => { // Get all open socket nodes const openSockets = [...(await nodeIo.allSockets())]; // TODO - Filter the returned nodes to only nodes that have the radio capability log.DEBUG("All open sockets: ", openSockets); var availableNodes = []; // Check each open socket to see if the node has the requested system await Promise.all( openSockets.map(async (openSocket) => { openSocket = await nodeIo.sockets.sockets.get(openSocket); // Check if the node has an existing open client (meaning the radio is already being listened to) const hasOpenClient = await checkIfNodeHasOpenDiscordClient(openSocket); if (hasOpenClient) { let currentSystem = await getNodeCurrentListeningSystem(openSocket); if (currentSystem != system.name) { log.INFO( "Node is listening to a different system than requested", openSocket.node.name, ); return; } } // Check if the bot has an open voice connection in the requested server already const connected = await checkIfNodeIsConnectedToVC( nodeIo, guildId, openSocket.node.nuid, ); log.INFO("Connected:", connected); if (!connected) { // Check if this node has the requested system, if so add it to the availble array if (system.nodes.includes(openSocket.node.nuid)) { availableNodes.push(openSocket); } } }), ); log.DEBUG( "Availble nodes:", availableNodes.map((socket) => socket.node.name), ); return availableNodes; }; /** * Gets the voice channel the user is currently in. * @param {any} interaction - The interaction object. * @returns {any} - The voice channel object, or null if the user is not in a voice channel. */ export const getUserVoiceChannel = (interaction) => { if (!interaction.member.voice.channel) { interaction.editReply({ content: `<@${interaction.member.id}>, you need to enter a voice channel before using this command`, ephemeral: true, }); return null; } return interaction.member.voice.channel; }; /** * Joins a node to a specified system and voice channel. * @param {any} nodeIo - The nodeIO server for manipulation of sockets. * @param {any} interaction - The interaction object. * @param {string} nodeId - The ID of the node to join. * @param {any} system - The system object to join. * @param {any} channel - The voice channel to join. */ export const joinNode = async ( nodeIo, interaction, nodeId, system, channel, ) => { try { const openSocket = await nodeIo.sockets.sockets.get(nodeId); const discordTokens = await getAvailableTokensInGuild( nodeIo, interaction.guild.id, ); if (discordTokens.length === 0) { await interaction.editReply({ content: `<@${interaction.member.id}>, there are no free bots available.`, ephemeral: true, }); return; } log.INFO( "Joining node:", nodeId, system.name, channel.id, openSocket.node.name, discordTokens[0].token, ); await requestNodeJoinSystem( openSocket, system.name, channel.id, discordTokens[0].token, ); await interaction.editReply({ content: `<@${interaction.member.id}>, a bot will join your channel listening to '${system.name}' shortly.`, ephemeral: true, }); } catch (err) { log.ERROR("Failed to join node:", err); await interaction.editReply({ content: `<@${interaction.member.id}>, an error occurred while joining the node: ${err.message}`, ephemeral: true, }); } }; /** * Prompts the user to select a node from available nodes. * @param {any} interaction - The interaction object. * @param {Array} availableNodes - The list of available nodes. * @param {Function} onNodeSelected - Callback function to handle the selected node. */ export const promptNodeSelection = async ( interaction, availableNodes, onNodeSelected, ) => { const nodeSelectionButtons = availableNodes.map((node) => new ButtonBuilder() .setCustomId(node.id) .setLabel(node.node.name) .setStyle(ButtonStyle.Primary), ); const actionRow = new ActionRowBuilder().addComponents(nodeSelectionButtons); const response = await interaction.editReply({ content: `<@${interaction.member.id}>, please select the Node you would like to join with this system:`, components: [actionRow], ephemeral: true, }); const collectorFilter = (i) => i.user.id === interaction.user.id; try { const selectedNode = await response.awaitMessageComponent({ filter: collectorFilter, time: 60_000, }); await onNodeSelected(selectedNode.customId); } catch (e) { log.ERROR("Node selection timeout:", e); await interaction.editReply({ content: "Confirmation not received within 1 minute, cancelling.", components: [], }); } };