forked from Elijah/AudioBot
119 lines
No EOL
4.7 KiB
TypeScript
119 lines
No EOL
4.7 KiB
TypeScript
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) {
|
|
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({
|
|
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...");
|
|
await rest.put(Routes.applicationCommands(config.DiscordClientID), {body: Commands});
|
|
client.login(config.DiscordToken);
|
|
}
|
|
main();
|
|
|
|
function audioStream() {
|
|
if (!audioprc) return;
|
|
stream = new MemoryStream();
|
|
audioprc.stdout.pipe(stream as unknown as NodeJS.WritableStream);
|
|
player.play(createAudioResource(stream, {
|
|
inputType: StreamType.Raw,
|
|
inlineVolume: false
|
|
}));
|
|
} |