Skip to content

Instantly share code, notes, and snippets.

@CTK-WARRIOR
Last active June 13, 2024 10:07
Show Gist options
  • Save CTK-WARRIOR/dcf9bdeee01ddf2a6f6cf0004ebd20ff to your computer and use it in GitHub Desktop.
Save CTK-WARRIOR/dcf9bdeee01ddf2a6f6cf0004ebd20ff to your computer and use it in GitHub Desktop.
Record command using discord.js and discordjs/voice with node 17, watch video: https://www.youtube.com/watch?v=h7CC-8kTsGI
/* Required Modules */
const { entersState, joinVoiceChannel, VoiceConnectionStatus, EndBehaviorType } = require('@discordjs/voice');
const { createWriteStream } = require('node:fs');
const prism = require('prism-media');
const { pipeline } = require('node:stream');
const { Client, Intents, MessageAttachment, Collection } = require('discord.js');
const ffmpeg = require('ffmpeg');
const sleep = require('util').promisify(setTimeout);
const fs = require('fs');
/* Initialize Discord Client */
const client = new Client({
intents: [
Intents.FLAGS.GUILDS,
Intents.FLAGS.GUILD_MESSAGES,
Intents.FLAGS.GUILD_MEMBERS,
Intents.FLAGS.GUILD_VOICE_STATES
]
})
/* Collection to store voice state */
client.voiceManager = new Collection()
/* Ready event */
client.on("ready", () => {
console.log("Connected as", client.user.tag, "to discord!");
})
/* When message is sent*/
client.on('messageCreate', async (message) => {
/* If content starts with `!record` */
if (message.content.startsWith('!record')) {
/* If member do not have admin perms */
if (!message.member.permissions.has('ADMINISTRATOR')) return message.channel.send('You do not have permission to use this command.');
/* Get the voice channel the user is in */
const voiceChannel = message.member.voice.channel
/* Check if the bot is in voice channel */
let connection = client.voiceManager.get(message.channel.guild.id)
/* If the bot is not in voice channel */
if (!connection) {
/* if user is not in any voice channel then return the error message */
if(!voiceChannel) return message.channel.send("You must be in a voice channel to use this command!")
/* Join voice channel*/
connection = joinVoiceChannel({
channelId: voiceChannel.id,
guildId: voiceChannel.guild.id,
selfDeaf: false,
selfMute: true,
adapterCreator: voiceChannel.guild.voiceAdapterCreator,
});
/* Add voice state to collection */
client.voiceManager.set(message.channel.guild.id, connection);
await entersState(connection, VoiceConnectionStatus.Ready, 20e3);
const receiver = connection.receiver;
/* When user speaks in vc*/
receiver.speaking.on('start', (userId) => {
if(userId !== message.author.id) return;
/* create live stream to save audio */
createListeningStream(receiver, userId, client.users.cache.get(userId));
});
/* Return success message */
return message.channel.send(`🎙️ I am now recording ${voiceChannel.name}`);
/* If the bot is in voice channel */
} else if (connection) {
/* Send waiting message */
const msg = await message.channel.send("Please wait while I am preparing your recording...")
/* wait for 5 seconds */
await sleep(5000)
/* disconnect the bot from voice channel */
connection.destroy();
/* Remove voice state from collection */
client.voiceManager.delete(message.channel.guild.id)
const filename = `./recordings/${message.author.id}`;
/* Create ffmpeg command to convert pcm to mp3 */
const process = new ffmpeg(`${filename}.pcm`);
process.then(function (audio) {
audio.fnExtractSoundToMP3(`${filename}.mp3`, async function (error, file) {
//edit message with recording as attachment
await msg.edit({
content: `🔉 Here is your recording!`,
files: [new MessageAttachment(`./recordings/${message.author.id}.mp3`, 'recording.mp3')]
});
//delete both files
fs.unlinkSync(`${filename}.pcm`)
fs.unlinkSync(`${filename}.mp3`)
});
}, function (err) {
/* handle error by sending error message to discord */
return msg.edit(`❌ An error occurred while processing your recording: ${err.message}`);
});
}
}
})
client.login("BOT TOKEN")
//------------------------- F U N C T I O N S ----------------------//
/* Function to write audio to file (from discord.js example) */
function createListeningStream(receiver, userId, user) {
const opusStream = receiver.subscribe(userId, {
end: {
behavior: EndBehaviorType.AfterSilence,
duration: 100,
},
});
const oggStream = new prism.opus.OggLogicalBitstream({
opusHead: new prism.opus.OpusHead({
channelCount: 2,
sampleRate: 48000,
}),
pageSizeControl: {
maxPackets: 10,
},
});
const filename = `./recordings/${user.id}.pcm`;
const out = createWriteStream(filename, { flags: 'a' });
console.log(`👂 Started recording ${filename}`);
pipeline(opusStream, oggStream, out, (err) => {
if (err) {
console.warn(`❌ Error recording file ${filename} - ${err.message}`);
} else {
console.log(`✅ Recorded ${filename}`);
}
});
}
@CTK-WARRIOR
Copy link
Author

Install required package using

npm i @discordjs/opus@0.5.3 @discordjs/voice@0.8.0 discord.js@13.0.1 ffmpeg libsodium-wrappers@0.7.9 node-crc@1.3.2 prism-media@^2.0.0-alpha.0

@anshtyagi0
Copy link

Unable to install packages

@LMS5413
Copy link

LMS5413 commented Apr 30, 2022

TypeError: failed to downcast any to number

@fruit-jazz
Copy link

node:internal/process/promises:279
triggerUncaughtException(err, true /* fromPromise */);
^

[Error: ENOENT: no such file or directory, stat 'C:\Users\User\Desktop\SPBYSHI\recordings\682296526835548175.mp3'] {
errno: -4058,
code: 'ENOENT',
syscall: 'stat',
path: 'C:\Users\User\Desktop\SPBYSHI\recordings\682296526835548175.mp3'
}

@CTK-WARRIOR
Copy link
Author

Read this if you are facing any error: https://2vr8.short.gy/record

@namedotget
Copy link

namedotget commented Nov 11, 2022

TypeError: failed to downcast any to number

Locate the createListeningStream function and set the crc parameter as false for oggStream

const oggStream = new prism.opus.OggLogicalBitstream({ opusHead: new prism.opus.OpusHead({ channelCount: 2, sampleRate: 48000, }), pageSizeControl: { maxPackets: 10, }, crc: false, //ADD THIS !!!! });

@toddixon
Copy link

My audio recordings that get attached to the Discord chat maybe have some recognizable sounds at the beginning or end and sometimes some short blips in between otherwise no audio at all. I'm running the exact same record.js script as above, node version is v17.0.0 and these are all the installed packages:

record@1.0.0 /home/dev/DiscordBot/record
├── @discordjs/opus@0.5.3
├── @discordjs/voice@0.8.0
├── discord.js@13.0.1
├── ffmpeg@0.0.4
├── libsodium-wrappers@0.7.9
├── node-crc@1.3.2
└── prism-media@2.0.0-alpha.0

I'm not sure if anybody else has been experiencing this problem, just looking for any advice on possible solutions.

recording
recording1

@d4sein
Copy link

d4sein commented Mar 26, 2023

For those unable to get a clean audio, make sure you have ffmpeg installed on your machine, do NOT use "crc: false" on the oggStream because it needs to match with ffmpeg, and double check the dependencies. I adapted the implementation into my bot and its working.

My dependencies:

  "dependencies": {
    "@discordjs/opus": "0.5.3",
    "@discordjs/voice": "^0.15.0",
    "discord.js": "^14.8.0",
    "dotenv": "^16.0.3",
    "ffmpeg": "^0.0.4",
    "libsodium-wrappers": "0.7.9",
    "node-crc": "1.3.2",
    "openai": "^3.2.1",
    "prism-media": "2.0.0-alpha.0",
    "typescript": "^5.0.2"
  }

@APRILDAY23
Copy link

APRILDAY23 commented Apr 14, 2024

ream function and set the crc parameter as false for oggStream

node:internal/process/promises:279 triggerUncaughtException(err, true /* fromPromise */); ^

[Error: ENOENT: no such file or directory, stat 'C:\Users\User\Desktop\SPBYSHI\recordings\682296526835548175.mp3'] { errno: -4058, code: 'ENOENT', syscall: 'stat', path: 'C:\Users\User\Desktop\SPBYSHI\recordings\682296526835548175.mp3' }

did you manage to solve this in discord js v14? Getting the same error. I have ffmpeg installed on my pc and package as well

const { entersState, joinVoiceChannel, VoiceConnectionStatus, EndBehaviorType } = require('@discordjs/voice');
const { createWriteStream } = require('node:fs');
const prism = require('prism-media');
const { pipeline } = require('node:stream');
const ffmpeg = require('ffmpeg');
const sleep = require('util').promisify(setTimeout);

/* Collection to store voice state */
client.voiceManager = new Collection()


/* When message is sent*/
client.on('messageCreate', async (message) => {
    /* If content starts with `!record` */
    if (message.content.startsWith('!record')) {
        /* If member do not have admin perms */
        if (!message.member.permissions.has('ADMINISTRATOR')) return message.channel.send('You do not have permission to use this command.'); 
        /* Get the voice channel the user is in */
        const voiceChannel = message.member.voice.channel
        /* Check if the bot is in voice channel */
        let connection = client.voiceManager.get(message.channel.guild.id)

        /* If the bot is not in voice channel */
        if (!connection) {
            /* if user is not in any voice channel then return the error message */
            if(!voiceChannel) return message.channel.send("You must be in a voice channel to use this command!")

            /* Join voice channel*/
            connection = joinVoiceChannel({
                channelId: voiceChannel.id,
                guildId: voiceChannel.guild.id,
                selfDeaf: false,
                selfMute: true,
                adapterCreator: voiceChannel.guild.voiceAdapterCreator,
            });

            /* Add voice state to collection */
            client.voiceManager.set(message.channel.guild.id, connection);
            await entersState(connection, VoiceConnectionStatus.Ready, 20e3);
            const receiver = connection.receiver;

            /* When user speaks in vc*/
            receiver.speaking.on('start', (userId) => {
                if(userId !== message.author.id) return;
                /* create live stream to save audio */
                createListeningStream(receiver, userId, client.users.cache.get(userId));
            });

            /* Return success message */
            return message.channel.send(`🎙️ I am now recording ${voiceChannel.name}`);
        
            /* If the bot is in voice channel */
        } else if (connection) {
            /* Send waiting message */
            const msg = await message.channel.send("Please wait while I am preparing your recording...")
            /* wait for 5 seconds */
            await sleep(5000)

            /* disconnect the bot from voice channel */
            connection.destroy();

            /* Remove voice state from collection */
            client.voiceManager.delete(message.channel.guild.id)
            
            const filename = `./Recordings/${message.author.id}`;

            /* Create ffmpeg command to convert pcm to mp3 */
            const process = new ffmpeg(`${filename}.pcm`);
            process.then(function (audio) {
                audio.fnExtractSoundToMP3(`${filename}.mp3`, async function (error, file) {
                    //edit message with recording as attachment
                    await msg.edit({
                        content: `🔉 Here is your recording!`,
                        files: [
                            {
                                attachment: `${filename}.mp3`,
                                name: "recording.mp3",
                            },
                        ],
                    });

                    //delete both files
                    fs.unlinkSync(`${filename}.pcm`)
                    fs.unlinkSync(`${filename}.mp3`)
                });
            }, function (err) {
                /* handle error by sending error message to discord */
                return msg.edit(`❌ An error occurred while processing your recording: ${err.message}`);
            });

        }
    }
})

//------------------------- F U N C T I O N S ----------------------//

/* Function to write audio to file (from discord.js example) */
function createListeningStream(receiver, userId, user) {
  const opusStream = receiver.subscribe(userId, {
      end: {
          behavior: EndBehaviorType.AfterSilence,
          duration: 100,
      },
  });

  const filename = `./Recordings/${user.id}.pcm`;
  const out = createWriteStream(filename, { flags: 'a' });
  console.log(`👂 Started recording ${filename}`);

  const opusEncoder = new prism.opus.Encoder({
      rate: 48000,
      channels: 2,
      frameSize: 960,
      quality: 10,
      crc: false,
  });

  pipeline(opusStream, opusEncoder, out, (err) => {
      if (err) {
          console.warn(`❌ Error recording file ${filename} - ${err.message}`);
      } else {
          console.log(`✅ Recorded ${filename}`);
      }
  });
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment