import { DebugBuilder } from "../../modules/debugger.mjs"; const log = new DebugBuilder("server", "discordBot.command.join"); import { SlashCommandBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; import { requestNodeJoinSystem, checkIfNodeIsConnectedToVC, checkIfNodeHasOpenDiscordClient, getNodeCurrentListeningSystem } from '../../modules/socketServerWrappers.mjs'; import { getSystemsByNuid, getAllSystems, getSystemByName } from '../../modules/mongo-wrappers/mongoSystemsWrappers.mjs'; import { getAvailableTokensInGuild } from '../modules/wrappers.mjs'; // Exporting data property export const data = new SlashCommandBuilder() .setName('join') .setDescription('Listen to the selected radio system in your channel') .addStringOption(system => system.setName('system') .setDescription('The radio system you would like to listen to') .setRequired(true) .setAutocomplete(true)); // Exporting other properties export const example = "/join"; export const deferInitialReply = true; /** * 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 getAllSystems(); const filtered = choices.filter(choice => choice.name.startsWith(focusedValue)); log.DEBUG(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 async function execute(nodeIo, interaction) { // Check if the user is in a VC if (!interaction.member.voice.channel) { return await interaction.editReply({ content: `<@${interaction.member.id}>, you need to enter a voice channel before you use this command`, ephemeral: true }) } // Grab the channel if the user is connected to VC const channelToJoin = interaction.member.voice.channel; log.INFO(`The user '${interaction.member.id}' is in the voice channel '${channelToJoin}'`); // Get the selected system option from the command interaction const selectedSystem = interaction.options.getString('system'); try { // Get the selected system object from the DB const system = await getSystemByName(selectedSystem); // Function wrapper to request the selected/only node to join the selected system const joinSelectedNode = async (selectedNodeSocketId) => { const openSocket = await nodeIo.sockets.sockets.get(selectedNodeSocketId); // Get the open ID for this connection\ const discordTokens = await getAvailableTokensInGuild(nodeIo, interaction.guild.id); log.DEBUG("Available discord tokens: ", discordTokens); if (discordTokens.length >= 1) { // TODO - Implement a method to have preferred tokens (bot users) for specific systems log.INFO("Joining selected open socket:", selectedNodeSocketId, system.name, channelToJoin.id, openSocket.node.name, discordTokens[0].token); // Ask the node to join the selected channel and system await requestNodeJoinSystem(openSocket, system.name, channelToJoin.id, discordTokens[0].token); } else { return await interaction.editReply({ content: `<@${interaction.member.id}>, there are no free bots. Free up or create a new bot ID (discord app) to listen to this system.`, ephemeral: true }) } } // Get all open socket nodes const openSockets = [...await nodeIo.allSockets()]; // TODO - Filter the returned nodes to only nodes that have the radio capability log.DEBUG("All open sockets: ", openSockets); var availableNodes = []; // Check each open socket to see if the node has the requested system await Promise.all(openSockets.map(async openSocket => { openSocket = await nodeIo.sockets.sockets.get(openSocket); // Check if the node has an existing open client (meaning the radio is already being listened to) const hasOpenClient = await checkIfNodeHasOpenDiscordClient(openSocket); if (hasOpenClient) { let currentSystem = await getNodeCurrentListeningSystem(openSocket); if (currentSystem != system.name) { log.INFO("Node is listening to a different system than requested", openSocket.node.name); 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()); } }