156 lines
7.4 KiB
JavaScript
156 lines
7.4 KiB
JavaScript
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());
|
|
}
|
|
} |