Functional joining and leaving

- Needs to be tested on multiple servers
- Needs to be tested with multiple nodes
#1 #9
This commit is contained in:
Logan Cusano
2024-02-18 20:05:10 -05:00
parent 6bc09df824
commit 42784f1852
7 changed files with 210 additions and 42 deletions

View File

@@ -97,6 +97,10 @@ export async function checkIfConnectedToVC(guildId) {
return connection
}
export const getVoiceConnectionFromGuild = async (guildId) => {
return getVoiceConnection(guildId);
}
export async function initDiscordBotClient(token, systemName, readyCallback) {
const client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.MessageContent],
@@ -104,10 +108,16 @@ export async function initDiscordBotClient(token, systemName, readyCallback) {
client.on(Events.ClientReady, () => {
console.log('discord.js client is ready!');
// Attach the recorder to the VC connection
attachRecorder();
// Set the activity of the bot user
client.user.setPresence({
activities: [{ name: `${systemName}`, type: ActivityType.Listening }],
});
//
readyCallback(client);
});

View File

@@ -1,49 +1,61 @@
import { io } from "socket.io-client";
import { connectToChannel, initDiscordBotClient, getVoiceChannelFromID, checkIfConnectedToVC } from '../discordAudioBot/dab.mjs';
import { logIntoServerWrapper, sendNodeUpdateWrapper } from "./socketClientWrappers.mjs";
import { connectToChannel, initDiscordBotClient, getVoiceChannelFromID, checkIfConnectedToVC, getVoiceConnectionFromGuild } from '../discordAudioBot/dab.mjs';
import { logIntoServerWrapper, sendNodeUpdateWrapper, nodeCheckStatus } from "./socketClientWrappers.mjs";
/**
* Initialize the socket connection with the server, this will handle disconnects within itself
* @param {Object} localNodeConfig The local node config object
* @returns {any}
*/
export const initSocketConnection = async (localNodeConfig) => {
const serverEndpoint = `http://${localNodeConfig.serverIp}:${localNodeConfig.serverPort}` || 'http://localhost:3000'; // Adjust the server endpoint
const socket = io.connect(serverEndpoint);
const discordClients = {};
socket.on('connect', async () => {
console.log('Connected to the server');
await logIntoServerWrapper(socket, localNodeConfig);
});
socket.on('node-join', async (joinData) => {
socket.on('node-join', async (joinData) => {
console.log("Join requested: ", joinData)
// TODO - Implement logic to control OP25 for the requested channel/system
// Join the requested channel with the requested ID
initDiscordBotClient(joinData.clientID, joinData.system, client => {
getVoiceChannelFromID(client, joinData.channelID).then(vc => {
// Add the client object to the IO instance
discordClients[vc.guild.id] = client;
const connection = connectToChannel(vc);
console.log("Bot Connected to VC");
})
});
})
});
});
socket.on('node-leave', async () => {
socket.on('node-leave', async (guildId) => {
console.log("Leave requested");
const connection = await getVoiceConnection(myVoiceChannel.guild.id);
if (connection) {
console.log("There is an open VC connection, closing it now");
connection.destroy();
if (await checkIfConnectedToVC(guildId)) {
const connection = await getVoiceConnectionFromGuild(guildId);
if (connection) {
console.log("There is an open VC connection, closing it now");
// Destroy the open VC connection
connection.destroy();
// Remove the client from the socket connection
delete discordClients[guildId];
}
}
});
socket.on('node-check-connected-status', async (guildId, socketCallback) => {
console.log("Requested status check");
if (await checkIfConnectedToVC(guildId)) {
console.log("There is an open VC connection");
socketCallback(true);
} else {
socketCallback(false);
}
socket.on('node-get-discord-username', async (guildId, socketCallback) => {
console.log("Requested username");
socketCallback(discordClients[guildId].user.username);
});
socket.on('node-check-connected-status', nodeCheckStatus);
socket.on('disconnect', () => {
console.log('Disconnected from the server');
});

View File

@@ -1,11 +1,60 @@
import { checkIfConnectedToVC } from '../discordAudioBot/dab.mjs';
/**
* Wrapper to log into the server
* @param {any} socket The socket connection with the server
* @param {object} localNodeConfig The local node object
* @returns {any}
*/
export const logIntoServerWrapper = async (socket, localNodeConfig) => {
// Log into the server
socket.emit("node-login", localNodeConfig.node);
// Send an update to the server
sendNodeUpdateWrapper(socket, localNodeConfig);
}
/**
* Send the server an update
* @param {any} socket The socket connection with the server
* @param {object} localNodeConfig The local node object
*/
export const sendNodeUpdateWrapper = async (socket, localNodeConfig) => {
socket.emit('node-update', {
'node': localNodeConfig.node,
'nearbySystems': localNodeConfig.nearbySystems
});
}
export const nodeJoinServer = async (joinData) => {
console.log("Join requested: ", joinData)
// TODO - Implement logic to control OP25 for the requested channel/system
// Join the requested channel with the requested ID
initDiscordBotClient(joinData.clientID, joinData.system, client => {
getVoiceChannelFromID(client, joinData.channelID).then(vc => {
// Add the client object to the IO instance
discordClients[vc.guild.id] = client;
const connection = connectToChannel(vc);
console.log("Bot Connected to VC");
})
});
}
/**
* Check if the bot is connected to a discord VC in the given server
* @param {string} guildId The guild id to check the connection status in
* @param {any} socketCallback The callback function to return the result to
* @returns {any}
*/
export const nodeCheckStatus = async (guildId, socketCallback) => {
console.log("Requested status check");
if (await checkIfConnectedToVC(guildId)) {
console.log("There is an open VC connection");
socketCallback(true);
} else {
socketCallback(false);
}
}

View File

@@ -12,7 +12,16 @@ export const data = new SlashCommandBuilder()
.setRequired(true)
.setAutocomplete(true));
export async function autocomplete(interaction) {
// 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));
@@ -24,14 +33,14 @@ export async function autocomplete(interaction) {
);
}
// Exporting other properties
export const example = "/join";
export const deferInitialReply = true;
// Exporting execute function
/**
* 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.reply({ content: 'You need to enter a voice channel before use the command', ephemeral: true }) }
if (!interaction.member.voice.channel) { return await interaction.reply({ content: `<@${interaction.member.id}>, you need to enter a voice channel before use the command`, ephemeral: true }) }
// Grab the channel if the user is connected to VC
const channelToJoin = interaction.member.voice.channel;
@@ -74,7 +83,7 @@ export async function execute(nodeIo, interaction) {
// 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("The selected system has no available nodes");
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
@@ -94,7 +103,7 @@ export async function execute(nodeIo, interaction) {
// Reply to the user with the button prompts
const response = await interaction.editReply({
content: "Please select the Node you would like to join with this system",
content: `<@${interaction.member.id}>, Please select the Node you would like to join with this system`,
components: [actionRow]
});

View File

@@ -0,0 +1,67 @@
import { SlashCommandBuilder } from 'discord.js';
import { checkIfNodeIsConnectedToVC, requestBotLeaveServer, getNodeDiscordUsername, getSocketIdByNuid } from '../../modules/socketServerWrappers.mjs';
// Exporting data property
export const data = new SlashCommandBuilder()
.setName('leave')
.setDescription('Disconnect a bot from the server')
.addStringOption(system =>
system.setName('bot')
.setDescription('The bot you would like to disconnect')
.setRequired(true)
.setAutocomplete(true));;
// Exporting other properties
export const example = "/leave *{Bot Name}*";
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 = [];
const openSockets = [...await nodeIo.allSockets()];
await Promise.all(openSockets.map(async openSocket => {
openSocket = await nodeIo.sockets.sockets.get(openSocket);
const connected = await checkIfNodeIsConnectedToVC(nodeIo, interaction.guild.id, openSocket.node.nuid);
console.log("Connected:", connected);
if (connected) {
const username = await getNodeDiscordUsername(openSocket, interaction.guild.id);
choices.push({
name: username,
value: openSocket.node.nuid
});
}
}));
const filtered = choices.filter(choice => choice.name.startsWith(focusedValue));
console.log(focusedValue, choices, filtered);
await interaction.respond(filtered);
}
/**
* 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) {
try {
// Get the requested bot
const selectedNode = interaction.options.getString('bot');
const socket = await getSocketIdByNuid(nodeIo, selectedNode);
console.log("All open sockets:", socket, selectedNode);
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.channel.send('**Pong.**');
} catch (err) {
console.error(err);
// await interaction.reply(err.toString());
}
}

View File

@@ -9,7 +9,7 @@ export async function execute(nodeIo, interaction) {
// Execute autocomplete if the user is checking autocomplete
if (interaction.isAutocomplete()) {
console.log("Running autocomplete for command: ", command.data.name);
return await command.autocomplete(interaction);
return await command.autocomplete(nodeIo, interaction);
}
// Check if the interaction is a command

View File

@@ -66,7 +66,6 @@ export const nodeUpdateWrapper = async (nodeData) => {
* Wrapper to update the systems from the nearbySystems object passed from clients
* @param {string} nuid The NUID of the node that sent the update
* @param {object} nearbySystems The nearby systems object passed from the node to be updated
* @returns {any}
*/
export const nearbySystemsUpdateWraper = async (nuid, nearbySystems) => {
console.log("System updates sent by node: ", nuid, nearbySystems);
@@ -142,9 +141,12 @@ export const nearbySystemsUpdateWraper = async (nuid, nearbySystems) => {
* @param {string} nuid The NUID to find within the open sockets
* @returns {string|null} Will return the open socket ID or NULL
*/
const getSocketIdByNuid = async (nodeIo, nuid) => {
for (const openSocket in await nodeIo.allSockets()) {
if (openSockets[openSocket] == nuid)
export const getSocketIdByNuid = async (nodeIo, nuid) => {
const openSockets = await nodeIo.allSockets();
for (const openSocketId of openSockets) {
console.log(openSockets)
const openSocket = await nodeIo.sockets.sockets.get(openSocketId);
if (openSocket.node.nuid == nuid)
return openSocket;
}
return null;
@@ -152,9 +154,9 @@ const getSocketIdByNuid = async (nodeIo, nuid) => {
/**
* Get all nodes that are connected to a voice channel
* @param {any} nodeIo
* @param {any} guildId The guild ID string for the guild we are looking in
* @returns {any}
* @param {any} nodeIo The nodeIo object that contains the IO server
* @param {string} guildId The guild ID string for the guild we are looking in
* @returns {Array} The sockets connected to VC in a given server
*/
export const getAllSocketsConnectedToVC = async (nodeIo, guildId) => {
// Get all open socket nodes
@@ -167,7 +169,7 @@ export const getAllSocketsConnectedToVC = async (nodeIo, guildId) => {
await new Promise((res) => {
openSocket.emit('node-check-connected-status', guildId, (status) => {
if (status) {
console.log("Socket is connected to VC:", openSocket.node.name);
console.log("Socket is connected to VC:", openSocket.node.name, status);
socketsConnectedToVC.push(openSocket);
} else {
console.log("Socket is NOT connected to VC:", openSocket.node.name);
@@ -183,8 +185,8 @@ export const getAllSocketsConnectedToVC = async (nodeIo, guildId) => {
/**
* Wrapper to check if the given NUID is connected to a VC
* @param {any} nodeIo The nodeIo object that contains the IO server
* @param {any} nuid The NUID string that we would like to find in the open socket connections
* @returns {any}
* @param {string} nuid The NUID string that we would like to find in the open socket connections
* @returns {boolean} If the node is connected to VC in the given server
*/
export const checkIfNodeIsConnectedToVC = async (nodeIo, guildId, nuid) => {
const socketsConnectedToVC = await getAllSocketsConnectedToVC(nodeIo, guildId);
@@ -196,12 +198,25 @@ export const checkIfNodeIsConnectedToVC = async (nodeIo, guildId, nuid) => {
return false;
}
/**
* Get the discord username from a given socket
* @param {any} socket The socket object of the node to check the username of
* * @param {string} guildId The guild ID to check the username in
* @returns {string} The username of the bot in the requested server
*/
export const getNodeDiscordUsername = async (socket, guildId) => {
return await new Promise((res) => {
socket.emit('node-get-discord-username', guildId, (username) => {
res(username);
});
});
}
/**
* Request a given socket node to join a given voice channel
* @param {any} socket The socket object of the node the request should be sent to
* @param {any} systemName The system preset name that we would like to listen to
* @param {any} discordChanelId The Discord channel ID to join the listening bot to
* @returns {any}
* @param {string} discordChanelId The Discord channel ID to join the listening bot to
*/
export const requestNodeJoinSystem = async (socket, systemName, discordChanelId) => {
// Check for open client IDs
@@ -214,6 +229,12 @@ export const requestNodeJoinSystem = async (socket, systemName, discordChanelId)
await sendNodeCommand(socket, "node-join", joinData);
}
export const requestBotLeave = async () => {
/**
* Request a given socket node to leave VC in a given server
* @param {any} socket The socket object of the node the request should be sent to
* @param {string} guildId The guild ID to disconnect the socket node from
*/
export const requestBotLeaveServer = async (socket, guildId) => {
// Send the command to the node
await sendNodeCommand(socket, "node-leave", guildId);
}