import Discord from "discord.js"; import __Core from "../../core"; import ApplicationCommand from "../../utils/typings/appCommand"; import { EndBehaviorType, entersState, joinVoiceChannel, VoiceConnectionStatus } from "@discordjs/voice"; import * as prism from "prism-media"; import path from "node:path"; import { createWriteStream } from "node:fs"; import { pipeline } from "node:stream"; export default { data: new Discord.SlashCommandBuilder() .setName("vc-recording") .setDescription("Controls the VC recording.") .addSubcommand(command => command.setName("start") .setDescription("Starts a new VC recording.") .addChannelOption(option => option.setName("channel") .setDescription("Target channel to start recording.") .addChannelTypes(Discord.ChannelType.GuildVoice) .setRequired(true) ) ) .addSubcommand(command => command.setName("stop") .setDescription("Stops a current VC recording.") /*.addStringOption(option => option.setName("recording_id") .setDescription("ID of the recording to stop.") .setRequired(true) )*/ ) .setDefaultMemberPermissions(Discord.PermissionFlagsBits.ModerateMembers), Dev: true, Disabled: false, async Run(bot: __Core, interaction: Discord.ChatInputCommandInteraction): Promise { const action = interaction.options.getSubcommand() as "start" | "stop"; const identifier = `${interaction.guild?.id}-${interaction.user.id}`; if (action === "start") { const channel = interaction.options.getChannel("channel") as Discord.VoiceChannel; const connection = joinVoiceChannel({ channelId: channel.id, guildId: interaction.guild!.id, selfDeaf: false, selfMute: true, adapterCreator: channel.guild.voiceAdapterCreator }); await interaction.reply({ ...bot.makeEmbed(`Joined ${channel}.`, "success"), ephemeral: true }); await entersState(connection, VoiceConnectionStatus.Ready, 20e3) const receiver = connection.receiver; connection.receiver.speaking.on("start", user_id => { const opusStream = receiver.subscribe(user_id, { end: { behavior: EndBehaviorType.AfterSilence, duration: 100 } }); const oggStream = new prism.opus.OggLogicalBitstream({ opusHead: new prism.opus.OpusHead({ channelCount: 2, sampleRate: 48000 }), pageSizeControl: { maxPackets: 10 } }); //Getting it to .pcm const file_name = path.join(__dirname, `../../recordings/${Date.now()}-${user_id}.pcm`); const out = createWriteStream(file_name, { flags: "a" }); if (!bot.collections._vcConnections.has(identifier)) { bot.collections._vcConnections.set(identifier, { audio_stream: opusStream, output_stream: out, connection: connection }) } console.info(`Started recording ${file_name}`); /* Type error on function name "pipeline": https://sourceb.in/CFLiPZlKLo */ pipeline(opusStream, oggStream, out, (err: NodeJS.ErrnoException | null) => { if (err) { console.warn(`Error recording file ${file_name} - ${err.message}`) } else console.info(`Recorded ${file_name}`); }); }); await interaction.editReply(bot.makeEmbed(`Started recording.`, "success")); } else { const recording = bot.collections._vcConnections.get(identifier); if (!recording) return interaction.reply({ ...bot.makeEmbed("Recording not found.", "danger"), ephemeral: true }); recording.audio_stream.destroy(); recording.output_stream.destroy(); recording.connection.destroy(); interaction.reply({ ...bot.makeEmbed("Recording stopped.", "success"), ephemeral: true }); } } }