Compare commits
3 Commits
7d4f48a446
...
7983a45281
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7983a45281 | ||
|
|
fea7ed2c7f | ||
|
|
bee95ed999 |
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
7
server/addons/example/config.json
Normal file
7
server/addons/example/config.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "Addon 1",
|
||||
"enabled": false,
|
||||
"options": {
|
||||
"eventName": "connection"
|
||||
}
|
||||
}
|
||||
17
server/addons/example/index.js
Normal file
17
server/addons/example/index.js
Normal 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);
|
||||
});
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
51
server/discordBot/modules/wrappers.mjs
Normal file
51
server/discordBot/modules/wrappers.mjs
Normal 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;
|
||||
}
|
||||
};
|
||||
@@ -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.`);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
90
server/modules/mongoDiscordIDWrappers.mjs
Normal file
90
server/modules/mongoDiscordIDWrappers.mjs
Normal 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();
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user