Compare commits

...

3 Commits

Author SHA1 Message Date
Logan Cusano
7983a45281 Improve service handler 2024-03-24 02:46:16 -04:00
Logan Cusano
fea7ed2c7f #16
- Server can now choose from IDs in the DB
- Implemented an active system to disable some IDs from being used
2024-03-24 02:45:34 -04:00
Logan Cusano
bee95ed999 #6
- Added a config system to the addons
- Added options in the config for dynamic variables
- Updated example and code to use new config and options
2024-03-10 04:50:16 -04:00
13 changed files with 276 additions and 79 deletions

View File

@@ -93,11 +93,32 @@ export const checkIfDiscordVCConnected = async (guildId) => {
* Get the username of the bot in a given guild
* (there may be a server nickname given to the bot in a certain guild)
* @param {string} guildId The guild id to check the connection status in
* @returns {string} The username of the bot in the given guild's CV
* @returns {string} The username of the bot in the given guild's VC
*/
export const getDiscordUsername = async (guildId) => {
console.log("Requested username");
if (activeDiscordClient) return (activeDiscordClient.user.username);
if (activeDiscordClient) {
// Fetch the guild
const guild = await client.guilds.fetch(guildId);
// Fetch the bot member in the guild
const botMember = await guild.members.fetch(client.user.id);
// Return bot's nickname if available, otherwise return username
return botMember.nickname || botMember.user.username;
}
else return (undefined);
}
/**
* Get the ID of the currently running bot
* @returns {string} The ID of the active client
*/
export const getDiscordID = async () => {
console.log("Requested username");
if (activeDiscordClient) {
return (activeDiscordClient.user.id);
}
else return (undefined);
}

View File

@@ -1,52 +1,58 @@
import { exec } from 'child_process';
/**
* Starts the given service from the command line.
* @param {string} serviceName The service name to be started. Ex: ... start "{serviceName}.service"
* @returns {Promise<void>}
* Executes a system command with error handling.
* @param {string} command The command to execute.
* @returns {Promise<{ stdout: string, stderr: string }>} A promise resolving to an object containing stdout and stderr.
*/
export const startService = async (serviceName) => {
const executeCommand = (command) => {
return new Promise((resolve, reject) => {
exec(`sudo systemctl start ${serviceName}.service`, (error, stdout, stderr) => {
exec(command, (error, stdout, stderr) => {
if (error) {
reject(error);
console.error(`Command failed with error: ${error.message}`);
resolve({ stdout, stderr });
} else {
resolve();
resolve({ stdout, stderr });
}
});
});
};
/**
* Starts the given service from the command line.
* @param {string} serviceName The service name to be started.
* @returns {Promise<void>}
*/
export const startService = async (serviceName) => {
try {
await executeCommand(`sudo systemctl start ${serviceName}.service`);
} catch (error) {
console.error(`Failed to start service: ${error.message}`);
}
};
/**
* Restarts the given service from the command line.
* @param {string} serviceName The service name to be restarted. Ex: ... restart "{serviceName}.service"
* @param {string} serviceName The service name to be restarted.
* @returns {Promise<void>}
*/
export const restartService = async (serviceName) => {
return new Promise((resolve, reject) => {
exec(`sudo systemctl restart ${serviceName}.service`, (error, stdout, stderr) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
try {
await executeCommand(`sudo systemctl restart ${serviceName}.service`);
} catch (error) {
console.error(`Failed to restart service: ${error.message}`);
}
};
/**
* Stops the given service from the command line.
* @param {string} serviceName The service name to be stopped. Ex: ... stop "{serviceName}.service"
* @param {string} serviceName The service name to be stopped.
* @returns {Promise<void>}
*/
export const stopService = async (serviceName) => {
return new Promise((resolve, reject) => {
exec(`sudo systemctl stop ${serviceName}.service`, (error, stdout, stderr) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
try {
await executeCommand(`sudo systemctl stop ${serviceName}.service`);
} catch (error) {
console.error(`Failed to stop service: ${error.message}`);
}
};

View File

@@ -1,5 +1,5 @@
import { io } from "socket.io-client";
import { logIntoServerWrapper, nodeCheckStatus, nodeJoinServer, nodeLeaveServer, nodeGetUsername, nodeCheckDiscordClientStatus, nodeCheckCurrentSystem, nodeUpdate } from "./socketClientWrappers.mjs";
import { logIntoServerWrapper, nodeCheckStatus, nodeJoinServer, nodeLeaveServer, nodeGetUsername, nodeCheckDiscordClientStatus, nodeCheckCurrentSystem, nodeUpdate, nodeGetDiscordID } from "./socketClientWrappers.mjs";
/**
* Initialize the socket connection with the server, this will handle disconnects within itself
@@ -36,6 +36,9 @@ export const initSocketConnection = async (localNodeConfig) => {
// Requested to get the discord username in a given guild
socket.on('node-get-discord-username', nodeGetUsername);
// Requested to get the ID of the active discord client
socket.on('node-get-discord-id', nodeGetDiscordID);
// Requested to check if the node is connected to VC in a given guild
socket.on('node-check-connected-status', nodeCheckStatus);

View File

@@ -1,4 +1,4 @@
import { checkIfDiscordVCConnected, joinDiscordVC, leaveDiscordVC, getDiscordUsername, checkIfClientIsOpen } from '../discordAudioBot/dabWrappers.mjs';
import { checkIfDiscordVCConnected, joinDiscordVC, leaveDiscordVC, getDiscordUsername, checkIfClientIsOpen, getDiscordID } from '../discordAudioBot/dabWrappers.mjs';
import { getCurrentSystem } from '../op25Handler/op25Handler.mjs';
import { checkForUpdates } from './selfUpdater.mjs';
@@ -80,6 +80,15 @@ export const nodeGetUsername = async (guildId, socketCallback) => {
socketCallback(await getDiscordUsername(guildId));
}
/**
* Get the ID of the active client
* @param {any} socketCallback The callback function to return the result to
* @callback {any}
*/
export const nodeGetDiscordID = async (socketCallback) => {
socketCallback(await getDiscordID());
}
/**
* Check if the local node has an open discord client in any server

View File

@@ -0,0 +1,7 @@
{
"name": "Addon 1",
"enabled": false,
"options": {
"eventName": "connection"
}
}

View File

@@ -0,0 +1,17 @@
// addons/addon1/index.js
// Function called by the main application to initialize the addon
export function initialize(nodeIo, config) {
console.log(`Initializing ${config.name}`);
// Call other functions within the addon module
registerSocketEvents(nodeIo, config);
// Call additional initialization functions if needed
}
// Function to register Socket.IO event handlers
function registerSocketEvents(nodeIo, config) {
nodeIo.on(config.options.eventName, (data) => {
console.log(`Received event "${config.options.eventName}" from client:`, data);
});
}

View File

@@ -1,17 +0,0 @@
// Function called by the main application to initialize the addon
export function initialize(io) {
console.log('Initializing addon1');
// Call other functions within the addon module
registerSocketEvents(io);
// Call additional initialization functions if needed
}
// Function to register Socket.IO event handlers
function registerSocketEvents(io) {
io.on('connection', (socket) => {
console.log('A client connected from addon1');
// Add more event handlers if needed
});
}

View File

@@ -1,6 +1,7 @@
import { SlashCommandBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js';
import { requestNodeJoinSystem, checkIfNodeIsConnectedToVC, checkIfNodeHasOpenDiscordClient, getNodeCurrentListeningSystem } from '../../modules/socketServerWrappers.mjs';
import { getSystemsByNuid, getAllSystems, getSystemByName } from '../../modules/mongoSystemsWrappers.mjs';
import { getAvailableTokensInGuild } from '../modules/wrappers.mjs';
// Exporting data property
export const data = new SlashCommandBuilder()
@@ -40,7 +41,7 @@ export async function autocomplete(nodeIo, interaction) {
*/
export async function execute(nodeIo, interaction) {
// Check if the user is in a VC
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 }) }
if (!interaction.member.voice.channel) { return await interaction.editReply({ 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;
@@ -54,10 +55,14 @@ export async function execute(nodeIo, interaction) {
// 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);
console.log("Joining selected open socket:", selectedNodeSocketId, system.name, channelToJoin.id, openSocket.node.name);
// Get the open ID for this connection\
const discordToken = await getAvailableTokensInGuild(nodeIo, interaction.guild.id);
// TODO - Implement a method to have preferred tokens (bot users) for specific systems
console.log("Joining selected open socket:", selectedNodeSocketId, system.name, channelToJoin.id, openSocket.node.name, discordToken);
// Ask the node to join the selected channel and system
await requestNodeJoinSystem(openSocket, system.name, channelToJoin.id);
await requestNodeJoinSystem(openSocket, system.name, channelToJoin.id, discordToken[0].token);
}
// Get all open socket nodes

View File

@@ -1,5 +1,6 @@
import { SlashCommandBuilder } from 'discord.js';
import { checkIfNodeIsConnectedToVC, requestBotLeaveServer, getNodeDiscordUsername, getSocketIdByNuid } from '../../modules/socketServerWrappers.mjs';
import { requestBotLeaveServer, getSocketIdByNuid } from '../../modules/socketServerWrappers.mjs';
import { checkOnlineBotsInGuild } from '../modules/wrappers.mjs'
// Exporting data property
export const data = new SlashCommandBuilder()
@@ -22,21 +23,7 @@ export const deferInitialReply = true;
*/
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 choices = (await checkOnlineBotsInGuild(nodeIo, interaction.guild.id)).map(choice => choice = {name, value: choice.nuid});
const filtered = choices.filter(choice => choice.name.startsWith(focusedValue));

View File

@@ -0,0 +1,51 @@
import { checkIfNodeIsConnectedToVC, getNodeDiscordID, getNodeDiscordUsername } from '../../modules/socketServerWrappers.mjs';
import { getAllDiscordIDs } from '../../modules/mongoDiscordIDWrappers.mjs'
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);
console.log("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
console.log("Available Discord IDs:", discordIDs);
console.log("Online bots in the guild:", onlineBots);
// Filter any discordIDs that are not active
var availableDiscordIDs = discordIDs.filter(discordID => discordID.active == true);
// Filter out discordIDs that are not found in onlineBots
availableDiscordIDs = availableDiscordIDs.filter(discordID => !onlineBots.some(bot => 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;
}
};

View File

@@ -14,13 +14,18 @@ export const loadAddons = async (nodeIo) => {
addonDirectories.forEach(addonDir => {
if (addonDir.isDirectory()) {
const addonPath = path.join(addonsDir, addonDir.name, 'index.js');
import(`file://${addonPath}`).then(addon => {
console.log("Loading addon: ", addon);
addon.initialize(nodeIo);
console.log(`Addon ${addonDir.name} loaded.`);
})
const addonConfigPath = path.join(addonsDir, addonDir.name, 'config.json');
if (fs.existsSync(addonConfigPath)) {
const addonConfig = JSON.parse(fs.readFileSync(addonConfigPath, 'utf-8'));
if (addonConfig.enabled) {
const addonIndexPath = path.join(addonsDir, addonDir.name, 'index.js');
import(`file://${addonIndexPath}`).then(addonModule => {
console.log("Loading addon: ", addonModule);
addonModule.initialize(nodeIo, addonConfig);
console.log(`Addon ${addonConfig.name} loaded.`);
});
}
}
}
});
}

View File

@@ -0,0 +1,90 @@
import { insertDocument, getDocuments, connectToDatabase } from "./mongoHandler.mjs";
const collectionName = 'discord-ids';
// Wrapper for inserting a Discord ID
export const createDiscordID = async (discordID) => {
try {
const insertedId = await insertDocument(collectionName, discordID);
return insertedId;
} catch (error) {
console.error('Error creating Discord ID:', error);
throw error;
}
};
// Wrapper for retrieving all Discord IDs
export const getAllDiscordIDs = async () => {
try {
const discordIDs = await getDocuments(collectionName);
return discordIDs;
} catch (error) {
console.error('Error getting all Discord IDs:', error);
throw error;
}
};
// Wrapper for retrieving a Discord ID by name or discord_id
export const getDiscordID = async (identifier) => {
const db = await connectToDatabase();
try {
const collection = db.db().collection(collectionName);
const discordID = await collection.findOne({
$or: [
{ name: identifier },
{ discord_id: identifier }
]
});
return discordID;
} catch (error) {
console.error('Error getting Discord ID:', error);
throw error;
} finally {
// Close the connection
await db.close();
}
};
// Wrapper for updating a Discord ID by name or discord_id
export const updateDiscordID = async (identifier, updatedFields) => {
const db = await connectToDatabase();
try {
const collection = db.db().collection(collectionName);
const result = await collection.updateOne({
$or: [
{ name: identifier },
{ discord_id: identifier }
]
}, { $set: updatedFields });
console.log('Discord ID updated:', result.modifiedCount);
return result.modifiedCount;
} catch (error) {
console.error('Error updating Discord ID:', error);
throw error;
} finally {
// Close the connection
await db.close();
}
};
// Wrapper for deleting a Discord ID by name or discord_id
export const deleteDiscordID = async (identifier) => {
const db = await connectToDatabase();
try {
const collection = db.db().collection(collectionName);
const result = await collection.deleteOne({
$or: [
{ name: identifier },
{ discord_id: identifier }
]
});
console.log('Discord ID deleted:', result.deletedCount);
return result.deletedCount;
} catch (error) {
console.error('Error deleting Discord ID:', error);
throw error;
} finally {
// Close the connection
await db.close();
}
};

View File

@@ -257,16 +257,29 @@ export const getNodeDiscordUsername = async (socket, guildId) => {
});
}
/**
* Get the discord ID from a given socket
* @param {any} socket The socket object of the node to check the ID of
* @returns {string} The ID of the bot
*/
export const getNodeDiscordID = async (socket) => {
return await new Promise((res) => {
socket.emit('node-get-discord-id', (discordID) => {
res(discordID);
});
});
}
/**
* 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 {string} discordChanelId The Discord channel ID to join the listening bot to
*/
export const requestNodeJoinSystem = async (socket, systemName, discordChanelId) => {
// Check for open client IDs
export const requestNodeJoinSystem = async (socket, systemName, discordChanelId, discordToken = "MTE5NjAwNTM2ODYzNjExMjk3Nw.GuCMXg.24iNNofNNumq46FIj68zMe9RmQgugAgfrvelEA") => {
// Join the system
const joinData = {
'clientID': "MTE5NjAwNTM2ODYzNjExMjk3Nw.GuCMXg.24iNNofNNumq46FIj68zMe9RmQgugAgfrvelEA",
'clientID': discordToken,
'channelID': discordChanelId,
'system': systemName
}