mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-14 10:33:30 +01:00
chore: monorepo setup (#7175)
This commit is contained in:
7
packages/voice/examples/recorder/.eslintrc.json
Normal file
7
packages/voice/examples/recorder/.eslintrc.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"root": true,
|
||||
"extends": "../../.eslintrc.json",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.eslint.json"
|
||||
}
|
||||
}
|
||||
4
packages/voice/examples/recorder/.gitignore
vendored
Normal file
4
packages/voice/examples/recorder/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
package-lock.json
|
||||
auth.json
|
||||
tsconfig.tsbuildinfo
|
||||
recordings/*.ogg
|
||||
23
packages/voice/examples/recorder/README.md
Normal file
23
packages/voice/examples/recorder/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# 👂 Recorder Bot
|
||||
|
||||
This example shows how you can use the voice receive functionality in @discordjs/voice to record users in voice channels
|
||||
and save the audio to local Ogg files.
|
||||
|
||||
## Usage
|
||||
|
||||
```sh-session
|
||||
# Clone the main repository, and then run:
|
||||
$ npm install
|
||||
$ npm run build
|
||||
|
||||
# Open this example and install dependencies
|
||||
$ cd examples/recorder
|
||||
$ npm install
|
||||
|
||||
# Set a bot token (see auth.example.json)
|
||||
$ cp auth.example.json auth.json
|
||||
$ nano auth.json
|
||||
|
||||
# Start the bot!
|
||||
$ npm start
|
||||
```
|
||||
3
packages/voice/examples/recorder/auth.example.json
Normal file
3
packages/voice/examples/recorder/auth.example.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"token": "Your Discord bot token here"
|
||||
}
|
||||
28
packages/voice/examples/recorder/package.json
Normal file
28
packages/voice/examples/recorder/package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "receiver-bot",
|
||||
"version": "0.0.1",
|
||||
"description": "An example receiver bot written using @discordjs/voice",
|
||||
"scripts": {
|
||||
"start": "npm run build && node -r tsconfig-paths/register dist/bot",
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"lint": "eslint src --ext .ts",
|
||||
"lint:fix": "eslint src --ext .ts --fix",
|
||||
"prettier": "prettier --write **/*.{ts,js,json,yml,yaml}",
|
||||
"build": "tsc",
|
||||
"build:check": "tsc --noEmit --incremental false"
|
||||
},
|
||||
"author": "Amish Shah <contact@shah.gg>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@discordjs/opus": "^0.5.3",
|
||||
"discord-api-types": "^0.22.0",
|
||||
"discord.js": "^13.0.1",
|
||||
"libsodium-wrappers": "^0.7.9",
|
||||
"node-crc": "^1.3.2",
|
||||
"prism-media": "^2.0.0-alpha.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsconfig-paths": "^3.10.1",
|
||||
"typescript": "~4.3.5"
|
||||
}
|
||||
}
|
||||
46
packages/voice/examples/recorder/src/bot.ts
Normal file
46
packages/voice/examples/recorder/src/bot.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import Discord, { Interaction } from 'discord.js';
|
||||
import { getVoiceConnection } from '@discordjs/voice';
|
||||
import { deploy } from './deploy';
|
||||
import { interactionHandlers } from './interactions';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
|
||||
const { token } = require('../auth.json');
|
||||
|
||||
const client = new Discord.Client({ intents: ['GUILD_VOICE_STATES', 'GUILD_MESSAGES', 'GUILDS'] });
|
||||
|
||||
client.on('ready', () => console.log('Ready!'));
|
||||
|
||||
client.on('messageCreate', async (message) => {
|
||||
if (!message.guild) return;
|
||||
if (!client.application?.owner) await client.application?.fetch();
|
||||
|
||||
if (message.content.toLowerCase() === '!deploy' && message.author.id === client.application?.owner?.id) {
|
||||
await deploy(message.guild);
|
||||
await message.reply('Deployed!');
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* The IDs of the users that can be recorded by the bot.
|
||||
*/
|
||||
const recordable = new Set<string>();
|
||||
|
||||
client.on('interactionCreate', async (interaction: Interaction) => {
|
||||
if (!interaction.isCommand() || !interaction.guildId) return;
|
||||
|
||||
const handler = interactionHandlers.get(interaction.commandName);
|
||||
|
||||
try {
|
||||
if (handler) {
|
||||
await handler(interaction, recordable, client, getVoiceConnection(interaction.guildId));
|
||||
} else {
|
||||
await interaction.reply('Unknown command');
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
}
|
||||
});
|
||||
|
||||
client.on('error', console.warn);
|
||||
|
||||
void client.login(token);
|
||||
@@ -0,0 +1,42 @@
|
||||
import { EndBehaviorType, VoiceReceiver } from '@discordjs/voice';
|
||||
import { User } from 'discord.js';
|
||||
import { createWriteStream } from 'node:fs';
|
||||
import prism from 'prism-media';
|
||||
import { pipeline } from 'node:stream';
|
||||
|
||||
function getDisplayName(userId: string, user?: User) {
|
||||
return user ? `${user.username}_${user.discriminator}` : userId;
|
||||
}
|
||||
|
||||
export function createListeningStream(receiver: VoiceReceiver, userId: string, user?: 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/${Date.now()}-${getDisplayName(userId, user)}.ogg`;
|
||||
|
||||
const out = createWriteStream(filename);
|
||||
|
||||
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}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
26
packages/voice/examples/recorder/src/deploy.ts
Normal file
26
packages/voice/examples/recorder/src/deploy.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Guild } from 'discord.js';
|
||||
|
||||
export const deploy = async (guild: Guild) => {
|
||||
await guild.commands.set([
|
||||
{
|
||||
name: 'join',
|
||||
description: 'Joins the voice channel that you are in',
|
||||
},
|
||||
{
|
||||
name: 'record',
|
||||
description: 'Enables recording for a user',
|
||||
options: [
|
||||
{
|
||||
name: 'speaker',
|
||||
type: 'USER' as const,
|
||||
description: 'The user to record',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'leave',
|
||||
description: 'Leave the voice channel',
|
||||
},
|
||||
]);
|
||||
};
|
||||
92
packages/voice/examples/recorder/src/interactions.ts
Normal file
92
packages/voice/examples/recorder/src/interactions.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { entersState, joinVoiceChannel, VoiceConnection, VoiceConnectionStatus } from '@discordjs/voice';
|
||||
import { Client, CommandInteraction, GuildMember, Snowflake } from 'discord.js';
|
||||
import { createListeningStream } from './createListeningStream';
|
||||
|
||||
async function join(
|
||||
interaction: CommandInteraction,
|
||||
recordable: Set<Snowflake>,
|
||||
client: Client,
|
||||
connection?: VoiceConnection,
|
||||
) {
|
||||
await interaction.deferReply();
|
||||
if (!connection) {
|
||||
if (interaction.member instanceof GuildMember && interaction.member.voice.channel) {
|
||||
const channel = interaction.member.voice.channel;
|
||||
connection = joinVoiceChannel({
|
||||
channelId: channel.id,
|
||||
guildId: channel.guild.id,
|
||||
selfDeaf: false,
|
||||
selfMute: true,
|
||||
adapterCreator: channel.guild.voiceAdapterCreator,
|
||||
});
|
||||
} else {
|
||||
await interaction.followUp('Join a voice channel and then try that again!');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await entersState(connection, VoiceConnectionStatus.Ready, 20e3);
|
||||
const receiver = connection.receiver;
|
||||
|
||||
receiver.speaking.on('start', (userId) => {
|
||||
if (recordable.has(userId)) {
|
||||
createListeningStream(receiver, userId, client.users.cache.get(userId));
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
await interaction.followUp('Failed to join voice channel within 20 seconds, please try again later!');
|
||||
}
|
||||
|
||||
await interaction.followUp('Ready!');
|
||||
}
|
||||
|
||||
async function record(
|
||||
interaction: CommandInteraction,
|
||||
recordable: Set<Snowflake>,
|
||||
client: Client,
|
||||
connection?: VoiceConnection,
|
||||
) {
|
||||
if (connection) {
|
||||
const userId = interaction.options.get('speaker')!.value! as Snowflake;
|
||||
recordable.add(userId);
|
||||
|
||||
const receiver = connection.receiver;
|
||||
if (connection.receiver.speaking.users.has(userId)) {
|
||||
createListeningStream(receiver, userId, client.users.cache.get(userId));
|
||||
}
|
||||
|
||||
await interaction.reply({ ephemeral: true, content: 'Listening!' });
|
||||
} else {
|
||||
await interaction.reply({ ephemeral: true, content: 'Join a voice channel and then try that again!' });
|
||||
}
|
||||
}
|
||||
|
||||
async function leave(
|
||||
interaction: CommandInteraction,
|
||||
recordable: Set<Snowflake>,
|
||||
client: Client,
|
||||
connection?: VoiceConnection,
|
||||
) {
|
||||
if (connection) {
|
||||
connection.destroy();
|
||||
recordable.clear();
|
||||
await interaction.reply({ ephemeral: true, content: 'Left the channel!' });
|
||||
} else {
|
||||
await interaction.reply({ ephemeral: true, content: 'Not playing in this server!' });
|
||||
}
|
||||
}
|
||||
|
||||
export const interactionHandlers = new Map<
|
||||
string,
|
||||
(
|
||||
interaction: CommandInteraction,
|
||||
recordable: Set<Snowflake>,
|
||||
client: Client,
|
||||
connection?: VoiceConnection,
|
||||
) => Promise<void>
|
||||
>();
|
||||
interactionHandlers.set('join', join);
|
||||
interactionHandlers.set('record', record);
|
||||
interactionHandlers.set('leave', leave);
|
||||
3
packages/voice/examples/recorder/tsconfig.eslint.json
Normal file
3
packages/voice/examples/recorder/tsconfig.eslint.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "./tsconfig.json"
|
||||
}
|
||||
13
packages/voice/examples/recorder/tsconfig.json
Normal file
13
packages/voice/examples/recorder/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"outDir": "dist",
|
||||
"paths": {
|
||||
"@discordjs/voice": ["../../"],
|
||||
"libsodium-wrappers": ["./node_modules/libsodium-wrappers"]
|
||||
}
|
||||
},
|
||||
"include": ["src/*.ts"],
|
||||
"exclude": [""]
|
||||
}
|
||||
Reference in New Issue
Block a user