AudioBot/src/index.ts

119 lines
4.6 KiB
TypeScript
Raw Normal View History

2024-01-25 22:03:22 -05:00
import {REST, Routes, Client, GatewayIntentBits, ChatInputCommandInteraction, Guild, GuildMember} from 'discord.js';
import {AudioPlayer, AudioPlayerStatus, StreamType, VoiceConnectionStatus, createAudioPlayer, createAudioResource, getVoiceConnection, joinVoiceChannel} from '@discordjs/voice';
import Commands from './commands.js';
import IConfig from './config.js';
import log from './log.js';
import {readFileSync} from 'fs';
import { ChildProcessWithoutNullStreams, exec, spawn } from 'child_process';
import { Readable } from 'stream';
import MemoryStream from 'memorystream';
log("INFO", "QEMU Discord Audio bot starting...");
const config : IConfig = JSON.parse(readFileSync('./config.json', 'utf8'));
const rest = new REST({version: '10'}).setToken(config.DiscordToken);
const client = new Client({intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates]});
var player : AudioPlayer = createAudioPlayer();
player.on('error', (error) => {
log("ERROR", `Audio player: ${error.message}`);
});
player.on(AudioPlayerStatus.Playing, () => log("INFO", "Audio playing"));
player.on(AudioPlayerStatus.Idle, () => {
log("INFO", "Audio idle");
stream.destroy();
if (audioprc) audioStream();
})
var vncname : string = "";
var audioprc : ChildProcessWithoutNullStreams | null = null;
var stream : MemoryStream;
client.on('ready', () => {
log("INFO", `Logged into discord as ${client.user?.tag}`);
});
client.on('interactionCreate', async i => {
if (!i.isChatInputCommand) return;
var cmd = i as ChatInputCommandInteraction;
switch (cmd.commandName) {
case "connect": {
await cmd.deferReply();
// kill the old process if it exists
if (audioprc != null) audioprc.kill("SIGTERM");
// get vnc address
var vm = config.VMs.find(vm => vm.name == cmd.options.getString('vm'));
if (!vm) {
await cmd.editReply("Invalid VM");
return;
}
var vncaddr = vm.vncaddr;
// get vnc name
var error = false;
await new Promise<void>((res, rej) => {
exec(`./bin/name ${vncaddr}`, (err, stdout, stderr) => {
if (err) {
log("ERROR", `Failed to get name of VM: ${err}`);
cmd.editReply("Failed to connect to VNC");
error = true;
} else vncname = stdout;
res();
});
})
if (error) return;
log("INFO", `Connecting to VNC ${vncname} at ${vncaddr}`);
// start the audio process
audioprc = spawn("./bin/audio", [vncaddr]);
audioprc.stderr.setEncoding('utf8');
audioprc.stderr.on('data', console.log);
audioprc.on('exit', (code) => {
audioprc = null;
log("INFO", `Audio process exited with code ${code}`);
});
audioStream();
// connect to voice channel if not already connected
var guild = cmd.guild!;
var con = getVoiceConnection(guild.id);
if (con && con.state.status == VoiceConnectionStatus.Ready) {
2024-01-25 22:03:22 -05:00
await cmd.editReply(`Connected to ${vncname}`);
return;
}
var channel = (cmd.member! as GuildMember).voice.channel;
if (!channel) {
await cmd.editReply(`Connected to ${vncname}`);
return;
}
con = joinVoiceChannel({
2024-01-25 22:03:22 -05:00
guildId: guild.id,
channelId: channel.id,
adapterCreator: guild.voiceAdapterCreator,
});
con.on(VoiceConnectionStatus.Ready, () => log("INFO", `Connected to voice channel ${channel!.name}`));
con.subscribe(player);
await cmd.editReply(`Connected to ${vncname}`);
break;
}
case "disconnect": {
var conn = getVoiceConnection(cmd.guild!.id);
if (conn == undefined) {
await cmd.reply("Not connected to a voice channel");
return;
}
conn.disconnect();
await cmd.reply("Disconnected");
}
}
});
async function main() {
log("INFO", "Publishing slash commands...");
2024-01-25 22:16:04 -05:00
await rest.put(Routes.applicationCommands(config.DiscordClientID), {body: Commands});
2024-01-25 22:03:22 -05:00
client.login(config.DiscordToken);
}
main();
function audioStream() {
if (!audioprc) return;
stream = new MemoryStream();
audioprc.stdout.pipe(stream);
player.play(createAudioResource(stream, {
inputType: StreamType.Raw,
inlineVolume: false
}));
}