Initial update to try and fix #1

This commit is contained in:
Logan Cusano
2023-03-26 15:03:35 -04:00
parent 403b533a33
commit b296da629b
14 changed files with 225 additions and 120 deletions

View File

@@ -1,58 +0,0 @@
// Debug
import ModuleDebugBuilder from "../utilities/moduleDebugBuilder.js";
const log = new ModuleDebugBuilder("bot", "join");
// Modules
import { joinVoiceChannel, VoiceConnectionStatus } 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
* @param {string||any} guildID The specified Guild ID if this function is run from the client instead of from an interaction in Discord
* @param {string||any} channelID The channel ID to join
* @param guild The guild object to be used to create a voice adapter
* @param {function} callback The callback that will be needed if this function is run with a Guild ID instead of an interaction
*/
export default async function join({interaction= undefined, guildID= undefined, channelID = undefined, guildObj = undefined, callback = undefined}){
if (interaction){
const voiceChannel = interaction.options.getChannel('voicechannel');
channelID = voiceChannel.id;
guildID = interaction.guildId;
guildObj = interaction.guild;
if (interaction) replyToInteraction(interaction, `Ok, Joining ${voiceChannel.name}`);
}
log.DEBUG("Channel ID: ", channelID)
log.DEBUG("Guild ID: ", guildID)
const voiceConnection = joinVoiceChannel({
channelId: channelID,
guildId: guildID,
adapterCreator: guildObj.voiceAdapterCreator,
selfMute: false,
selfDeaf: false,
});
const audioInstance = await createAudioInstance();
audioInstance.on('audio', (buffer) => {
buffer = Buffer.from(buffer);
log.DEBUG("Audio buffer: ", buffer);
const encoded = encoder.encode(buffer);
// TODO Add a function here to check the volume of either buffer and only play audio to discord when there is audio to be played
voiceConnection.playOpusPacket(encoded);
})
// Exit the audio handler when the bot disconnects
voiceConnection.on(VoiceConnectionStatus.Destroyed, () => {
audioInstance.quit();
})
if (guildID && callback) callback();
}

View File

@@ -1,31 +0,0 @@
import {getVoiceConnection} from "@discordjs/voice";
import {replyToInteraction} from "../utilities/messageHandler.js";
// Debug
//import debugBuilder from "../utilities/moduleDebugBuilder.js";
//const log = new debugBuilder("bot", "leave");
/**
* If in a voice channel for the specified guild, leave
*
* @param interaction Message interaction from discord
* @param guildID
* @param callback
*/
export default async function leave({interaction = undefined, guildID= undefined, callback = undefined}) {
if(interaction) {
guildID = interaction.guild.id;
}
const voiceConnection = getVoiceConnection(guildID);
let response;
if (!voiceConnection){
response = "Not in a voice channel."
if (interaction) return replyToInteraction(interaction, response);
else callback(response);
}
voiceConnection.destroy();
response = "Goodbye"
if (interaction) return replyToInteraction(interaction, response);
else callback(response);
}

View File

@@ -1,6 +0,0 @@
// Utilities
import { replyToInteraction } from '../utilities/messageHandler.js';
export default function ping(interaction) {
return replyToInteraction(interaction, "Pong! I have Aids and now you do too!");
}

View File

@@ -1,31 +0,0 @@
// Debug
import ModuleDebugBuilder from "../utilities/moduleDebugBuilder.js";
const log = new ModuleDebugBuilder("bot", "status");
// Modules
import {getVoiceConnection} from "@discordjs/voice";
// Utilities
import { replyToInteraction } from '../utilities/messageHandler.js';
export default async function status({interaction= undefined, guildID= undefined, callback = undefined}) {
//if (!interaction && !guildID) // Need error of sorts
if (interaction){
guildID = interaction.guild.id;
}
const voiceConnection = getVoiceConnection(guildID);
const statusObj = {
"guildID": guildID, "voiceConnection": voiceConnection
}
//log.DEBUG('Status Object: ', statusObj);
// get the status and return it accordingly (message reply / module)
if (interaction) {
return replyToInteraction(interaction, "Pong! I have Aids and now you do too!");
}
else {
callback(statusObj);
}
}

View File

@@ -1,7 +0,0 @@
{
"TOKEN": "OTQzNzQyMDQwMjU1MTE1MzA0.Yg3eRA.ZxEbRr55xahjfaUmPY8pmS-RHTY",
"ApplicationID": "943742040255115304",
"GuildID": "367396189529833472",
"DeviceID": "5",
"DeviceName": "VoiceMeeter Aux Output (VB-Audi"
}

View File

@@ -1,58 +0,0 @@
// Config
import { getDeviceID } from '../utilities/configHandler.js';
// Modules
import alsaInstance from 'alsa-capture';
import { returnAlsaDeviceObject } from "../utilities/executeConsoleCommands.js";
// Debug
import ModuleDebugBuilder from "../utilities/moduleDebugBuilder.js";
// Global Vars
const log = new ModuleDebugBuilder("bot", "audioController");
/**
* 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 async function confirmAudioDevice({deviceName = undefined, deviceId = undefined}){
const deviceList = await getAudioDevices();
if (!deviceName && !deviceId) throw new Error("No device given");
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 async function getAudioDevices(){
// Exec output contains both stderr and stdout outputs
const deviceList = await returnAlsaDeviceObject();
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 new portAudio.AudioIO
*/
export async function createAudioInstance() {
const selectedDevice = await 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
return new alsaInstance({
channels: 2,
format: "S16_BE",
rate: 48000,
device: selectedDevice.name ?? "default", // Use -1 or omit the deviceId to select the default device
periodSize: 100, //(48000 / 1000) * 20, //(48000 * 16 * 2) / 1000 * 20 // (48000 * (16 / 8) * 2) / 60 / 1000 * 20 //0.025 * 48000 / 2
periodTime: undefined,
// highwaterMark: 3840
});
}

View File

@@ -1,50 +0,0 @@
// Debug
import ModuleDebugBuilder from "./moduleDebugBuilder.js";
const log = new ModuleDebugBuilder("bot", "configHandler");
// Modules
import { readFileSync } from 'fs';
import path from "path";
export function getConfig() {
return JSON.parse(readFileSync(path.resolve("discord-bot/config/botConfig.json")));
}
export function getTOKEN() {
const parsedJSON = getConfig();
const token = parsedJSON.TOKEN;
log.DEBUG("Discord API Token: ", token)
return token;
}
export function getGuildID() {
const parsedJSON = getConfig();
const guildID = parsedJSON.GuildID;
log.DEBUG("Guild ID: ", guildID);
return guildID;
}
export function getApplicationID() {
const parsedJSON = getConfig();
const appID = parsedJSON.ApplicationID;
log.DEBUG("Application ID: ", appID);
return appID;
}
export function getDeviceID(){
const parsedJSON = getConfig();
const deviceID = parseInt(parsedJSON.DeviceID);
log.DEBUG("Device ID: ", deviceID);
return deviceID;
}
export function getDeviceName(){
const parsedJSON = getConfig();
const deviceName = parsedJSON.DeviceName;
log.DEBUG("Device Name: ", deviceName);
return deviceName;
}

View File

@@ -1,49 +0,0 @@
// Modules
import { promisify } from 'util';
import { exec } from "child_process";
// Debug
import ModuleDebugBuilder from "../utilities/moduleDebugBuilder.js";
// Global Vars
const log = new ModuleDebugBuilder("bot", "executeConsoleCommand");
const execCommand = promisify(exec);
export default async function executeAsyncConsoleCommand(consoleCommand) {
// Check to see if the command is a real command
// TODO needs to be improved
const acceptableCommands = [ "arecord -L" ];
if (!acceptableCommands.includes(consoleCommand)) {
log.WARN("Console command is not acceptable: ", consoleCommand);
return undefined;
}
log.DEBUG("Running console command: ", consoleCommand);
const tempOutput = await execCommand(consoleCommand);
const output = tempOutput.stdout.trim();
log.DEBUG("Executed Console Command Response: ", output)
// TODO add some error checking
return output;
}
export async function returnAlsaDeviceObject() {
const listAlsaDevicesCommand = "arecord -L";
const commandResponse = await executeAsyncConsoleCommand(listAlsaDevicesCommand);
const brokenCommand = String(commandResponse).split('\n');
var devices = [];
var i = 0;
for (const responseLine of brokenCommand) {
if (String(responseLine) && !String(responseLine).match(/^\s/g)) {
const tempDevice = {
id: i,
name: responseLine
}
devices.push(tempDevice);
i += 1;
}
}
return devices;
}

View File

@@ -1,9 +0,0 @@
// Debug
import ModuleDebugBuilder from "./moduleDebugBuilder.js";
const log = new ModuleDebugBuilder("bot", "messageHandler");
export function replyToInteraction(interaction, message){
interaction.reply({ content: message, fetchReply: true })
.then((message) => log.DEBUG(`Reply sent with content ${message.content}`))
.catch((err) => log.ERROR(err));
}