diff --git a/Client/discord-bot/app.js b/Client/discord-bot/app.js index 1d11749..55b2146 100644 --- a/Client/discord-bot/app.js +++ b/Client/discord-bot/app.js @@ -1,11 +1,12 @@ //Config import { getTOKEN, getGuildID, getApplicationID } from './utilities/configHandler.js'; // Commands -import ping from './controllers/ping.js'; -import { join, leave } from './controllers/voiceController.js'; +import ping from './commands/ping.js'; +import join from './commands/join.js'; +import leave from './commands/leave.js'; // Debug -import Debug from 'debug'; -const debug = Debug("bot:app"); +import debugBuilder from "./utilities/debugBuilder.js"; +const log = new debugBuilder("bot", "app"); // Modules import { Client, GatewayIntentBits } from 'discord.js'; // Utilities @@ -23,15 +24,13 @@ const client = new Client({ // When the client is connected and ready client.on('ready', () =>{ - debug(`${client.user.tag} is ready`) - console.log(`${client.user.tag} is ready`) + log.INFO(`${client.user.tag} is ready`) }); /* * Saved For later client.on('messageCreate', (message) => { - debug(`Message Sent by: ${message.author.tag}\n\t'${message.content}'`); - console.log(`Message Sent by: ${message.author.tag}\n\t'${message.content}'`); + log.DEBUG(`Message Sent by: ${message.author.tag}\n\t'${message.content}'`); }); */ @@ -50,8 +49,8 @@ client.on('interactionCreate', (interaction) => { break; default: interaction.reply({ content: 'Command not found, try one that exists', fetchReply: true }) - .then((message) => console.log(`Reply sent with content ${message.content}`)) - .catch(console.error); + .then((message) => log.DEBUG(`Reply sent with content ${message.content}`)) + .catch((err) => log.ERROR(err)); } } }) diff --git a/Client/discord-bot/commands/join.js b/Client/discord-bot/commands/join.js new file mode 100644 index 0000000..496eba7 --- /dev/null +++ b/Client/discord-bot/commands/join.js @@ -0,0 +1,35 @@ +import {joinVoiceChannel} from "@discordjs/voice"; +import {replyToInteraction} from "../utilities/messageHandler.js"; +import {createAudioInstance} from "../controllers/audioController.js"; +import OpusEncoderPkg from "@discordjs/opus"; + +// Declare the encoder (module is incompatible modern import method) +const { OpusEncoder } = OpusEncoderPkg; +const encoder = new OpusEncoder(48000, 2); + +/** + * Join the specified voice channel + * + * @param interaction Message interaction from discord + */ +export default async function join(interaction){ + const voiceChannel = interaction.options.getChannel('voicechannel'); + const voiceConnection = joinVoiceChannel({ + channelId: voiceChannel.id, + guildId: interaction.guildId, + adapterCreator: interaction.guild.voiceAdapterCreator, + selfMute: false, + selfDeaf: false, + }); + replyToInteraction(interaction, `Ok, Joining ${voiceChannel.name}`); + + const audioInstance = createAudioInstance(); + + audioInstance.on('data', buffer => { + buffer = Buffer.from(buffer); + const encoded = encoder.encode(buffer); + voiceConnection.playOpusPacket(encoded); + }) + + audioInstance.start(); +} \ No newline at end of file diff --git a/Client/discord-bot/commands/leave.js b/Client/discord-bot/commands/leave.js new file mode 100644 index 0000000..3806770 --- /dev/null +++ b/Client/discord-bot/commands/leave.js @@ -0,0 +1,18 @@ +import {getVoiceConnection} from "@discordjs/voice"; +import {replyToInteraction} from "../utilities/messageHandler.js"; +// Debug +//import debugBuilder from "../utilities/debugBuilder.js"; +//const log = new debugBuilder("bot", "leave"); + +/** + * If in a voice channel for the specified guild, leave + * + * @param interaction Message interaction from discord + */ +export default async function leave(interaction){ + const guildId = interaction.guild.id; + const voiceConnection = getVoiceConnection(guildId); + if (!voiceConnection) return replyToInteraction(interaction, "Not in a voice channel."); + voiceConnection.destroy(); + return replyToInteraction(interaction, `Goodbye`); +} \ No newline at end of file diff --git a/Client/discord-bot/controllers/ping.js b/Client/discord-bot/commands/ping.js similarity index 100% rename from Client/discord-bot/controllers/ping.js rename to Client/discord-bot/commands/ping.js diff --git a/Client/discord-bot/controllers/audioController.js b/Client/discord-bot/controllers/audioController.js index e9fd66a..91dc75e 100644 --- a/Client/discord-bot/controllers/audioController.js +++ b/Client/discord-bot/controllers/audioController.js @@ -1,16 +1,31 @@ // Config -import { getDeviceID, getDeviceName } from '../utilities/configHandler.js' +import {getDeviceID} from '../utilities/configHandler.js' // Modules import portAudio from 'naudiodon'; -import {createAudioResource} from "@discordjs/voice"; +// Debug +import debugBuilder from "../utilities/debugBuilder.js"; +const log = new debugBuilder("bot", "audioController"); -export function getAudioDevice({deviceName = undefined, deviceId = undefined}){ +/** + * Checks to make sure the selected audio device is available and returns the device object (PortAudio Device Info) + * At least one option must be supplied, it will prefer ID to device name + * + * @param deviceName The name of the device being queried + * @param deviceId The ID of the device being queried + * @returns {unknown} + */ +export function confirmAudioDevice({deviceName = undefined, deviceId = undefined}){ const deviceList = getAudioDevices(); if (!deviceName && !deviceId) throw new Error("No device given"); - if (deviceName) return deviceList.find(device => device.name === deviceName); if (deviceId) return deviceList.find(device => device.id === deviceId); + if (deviceName) return deviceList.find(device => device.name === deviceName); } +/** + * Return a list of the audio devices connected with input channels + * + * @returns {unknown[]} + */ export function getAudioDevices(){ const deviceList = portAudio.getDevices().map((device) => { if (device.defaultSampleRate === 48000 || device.maxInputChannels > 0) { @@ -20,16 +35,21 @@ export function getAudioDevices(){ return null; } }).filter(Boolean); - console.log("Devices:", deviceList); + log.DEBUG("Device List: ", deviceList); return deviceList; } +/** + * Create and return the audio instance from the saved settings + * TODO Allow the client to save and load these settings dynamically + * + * @returns {IoStreamRead} + */ export function createAudioInstance() { - //const resource = createAudioResource(); - const selectedDevice = getAudioDevice({deviceId: getDeviceID()});//{deviceName: "VoiceMeeter VAIO3 Output (VB-Au"}); - console.log(selectedDevice); + const selectedDevice = confirmAudioDevice({deviceId: getDeviceID()});//{deviceName: "VoiceMeeter VAIO3 Output (VB-Au"}); + log.DEBUG("Device selected from config: ", selectedDevice); // Create an instance of AudioIO with outOptions (defaults are as below), which will return a WritableStream - const audioInstance = new portAudio.AudioIO({ + return new portAudio.AudioIO({ inOptions: { channelCount: 2, sampleFormat: portAudio.SampleFormat16Bit, @@ -40,6 +60,4 @@ export function createAudioInstance() { highwaterMark: 3840 }, }); - //audioInstance.start(); - return audioInstance; } \ No newline at end of file diff --git a/Client/discord-bot/controllers/voiceController.js b/Client/discord-bot/controllers/voiceController.js deleted file mode 100644 index f492865..0000000 --- a/Client/discord-bot/controllers/voiceController.js +++ /dev/null @@ -1,87 +0,0 @@ -// Modules -import { - joinVoiceChannel, - getVoiceConnection, - createAudioResource, - createAudioPlayer, - NoSubscriberBehavior, StreamType -} from '@discordjs/voice'; -import OpusEncoderPkg from "@discordjs/opus"; -const { OpusEncoder } = OpusEncoderPkg; -// Utilities -import { replyToInteraction } from '../utilities/messageHandler.js'; -import { createAudioInstance } from "./audioController.js"; - - -/** - * Join the specified voice channel - * - * @param interaction Message interaction from discord - */ -export function join(interaction){ - const voiceChannel = interaction.options.getChannel('voicechannel'); - const voiceConnection = joinVoiceChannel({ - channelId: voiceChannel.id, - guildId: interaction.guildId, - adapterCreator: interaction.guild.voiceAdapterCreator, - selfMute: false, - selfDeaf: false, - }); - replyToInteraction(interaction, `Ok, Joining ${voiceChannel.name}`); - - // Declare the encoder - const encoder = new OpusEncoder(48000, 2); - - const player = createAudioPlayer({ - behaviors: { - noSubscriber: NoSubscriberBehavior.Play, - }, - }); - - const audioInstance = createAudioInstance(); - - audioInstance.on('data', buffer => { - buffer = Buffer.from(buffer); - const encoded = encoder.encode(buffer); - voiceConnection.playOpusPacket(encoded); - }) - - audioInstance.start(); -} - -/** - * If in a voice channel for the specified guild, leave - * - * @param interaction Message interaction from discord - */ -export function leave(interaction){ - const guildId = interaction.guild.id; - const voiceConnection = getVoiceConnection(guildId); - if (!voiceConnection) return replyToInteraction(interaction, "Not in a voice channel."); - voiceConnection.destroy(); - return replyToInteraction(interaction, `Goodbye`); -} - -/* -* Brute forcing the required buffer size, 16 bit, 48KHz, 2ch = 480 Bytes (need to get frames still) - let notWorking = true; - let splitIndexInverse = 0 - while (notWorking) { - try { - const newBuffer = buffer.slice(buffer.length - splitIndexInverse); - console.log(timestamp ,newBuffer, "end") - const encoded = encoder.encode(newBuffer); - console.log("Working Buffer Length" ,newBuffer.length) - console.log("Working Buffer" ,newBuffer) - notWorking = false - break; - } catch (err){ - console.log(err) - if (splitIndexInverse >= buffer.length) { - notWorking = false - throw new Error("Shit fucked") - } - splitIndexInverse += 1 - } - } - */ \ No newline at end of file diff --git a/Client/discord-bot/utilities/configHandler.js b/Client/discord-bot/utilities/configHandler.js index 70c45f0..1c46179 100644 --- a/Client/discord-bot/utilities/configHandler.js +++ b/Client/discord-bot/utilities/configHandler.js @@ -1,4 +1,7 @@ import { readFileSync } from 'fs'; +// Debug +import debugBuilder from "./debugBuilder.js"; +const log = new debugBuilder("bot", "configHandler"); export function getConfig() { return JSON.parse(readFileSync("./config/botConfig.json")); @@ -6,31 +9,40 @@ export function getConfig() { export function getTOKEN() { const parsedJSON = getConfig(); - return parsedJSON.TOKEN; + const token = parsedJSON.TOKEN; + + log.DEBUG("Discord API Token: ", token) + return token; } export function getGuildID() { const parsedJSON = getConfig(); - parsedJSON.GuildID = BigInt(parsedJSON.GuildID); - //console.log("Guild ID: ", parsedJSON.GuildID); - return parsedJSON.GuildID; + const guildID = BigInt(parsedJSON.GuildID); + + log.DEBUG("Guild ID: ", guildID); + return guildID; } export function getApplicationID() { const parsedJSON = getConfig(); - parsedJSON.ApplicationID = BigInt(parsedJSON.ApplicationID); - //console.log("Application ID: ", parsedJSON.ApplicationID); - return parsedJSON.ApplicationID; + const appID = BigInt(parsedJSON.ApplicationID); + + log.DEBUG("Application ID: ", appID); + return appID; } export function getDeviceID(){ const parsedJSON = getConfig(); - //console.log("Device ID: ", parseInt(parsedJSON.DeviceID)); - return parseInt(parsedJSON.DeviceID); + const deviceID = parseInt(parsedJSON.DeviceID); + + log.DEBUG("Device ID: ", deviceID); + return deviceID; } export function getDeviceName(){ const parsedJSON = getConfig(); - //console.log("Device Name: ", parseInt(parsedJSON.DeviceName)); - return parsedJSON.DeviceName; + const deviceName = parsedJSON.DeviceName; + + log.DEBUG("Device Name: ", deviceName); + return deviceName; } \ No newline at end of file diff --git a/Client/discord-bot/utilities/debugBuilder.js b/Client/discord-bot/utilities/debugBuilder.js new file mode 100644 index 0000000..f957b0f --- /dev/null +++ b/Client/discord-bot/utilities/debugBuilder.js @@ -0,0 +1,17 @@ +// Debug +import Debug from 'debug'; + +export default class debugBuilder { + /** + * Create the different logging methods for a function + * Namespace template = ("[app]:[fileName]:['INFO', 'WARNING', 'DEBUG', 'ERROR']") + * @param {string} appName The name of the app to be used in the 'app' portion of the namespace + * @param {string} fileName The name of the file calling the builder to be used in the 'fileName' portion of the namespace + */ + constructor(appName, fileName) { + this.INFO = Debug(`${appName}:${fileName}:INFO`); + this.DEBUG = Debug(`${appName}:${fileName}:DEBUG`); + this.WARN = Debug(`${appName}:${fileName}:WARNING`); + this.ERROR = Debug(`${appName}:${fileName}::ERROR`); + } +} \ No newline at end of file diff --git a/Client/discord-bot/utilities/messageHandler.js b/Client/discord-bot/utilities/messageHandler.js index 053895e..33b84aa 100644 --- a/Client/discord-bot/utilities/messageHandler.js +++ b/Client/discord-bot/utilities/messageHandler.js @@ -1,5 +1,9 @@ +// Debug +import debugBuilder from "./debugBuilder.js"; +const log = new debugBuilder("bot", "messageHandler"); + export function replyToInteraction(interaction, message){ interaction.reply({ content: message, fetchReply: true }) - .then((message) => console.log(`Reply sent with content ${message.content}`)) - .catch(console.error); + .then((message) => log.DEBUG(`Reply sent with content ${message.content}`)) + .catch((err) => log.ERROR(err)); } \ No newline at end of file diff --git a/Client/discord-bot/utilities/registerCommands.js b/Client/discord-bot/utilities/registerCommands.js index 7dd11e6..0643b8b 100644 --- a/Client/discord-bot/utilities/registerCommands.js +++ b/Client/discord-bot/utilities/registerCommands.js @@ -2,6 +2,9 @@ import {SlashCommandBuilder} from "@discordjs/builders"; import {REST} from "@discordjs/rest"; import {getApplicationID, getGuildID, getTOKEN} from "./configHandler.js"; import { Routes, ChannelType } from "discord.js"; +// Debug +import debugBuilder from "./debugBuilder.js"; +const log = new debugBuilder("bot", "registerCommands"); const pingCommand = new SlashCommandBuilder() .setName("ping") @@ -38,8 +41,9 @@ export default async function registerCommands(callback){ await rest.put(Routes.applicationGuildCommands(clientID, guildID), { body: commands, }); + log.DEBUG("Successfully registered the following commands: ", commands) callback(); } catch (err) { - console.log(err); + log.ERROR(err); } } \ No newline at end of file