From ef29b5e51f3b9d248fdb16d6f9721e5bc9e58739 Mon Sep 17 00:00:00 2001 From: Jiralite <33201955+Jiralite@users.noreply.github.com> Date: Wed, 12 Oct 2022 11:27:02 +0100 Subject: [PATCH] chore: Redirect voice examples to the new repository (#8737) --- packages/voice/.eslintignore | 1 - packages/voice/README.md | 5 + packages/voice/examples/README.md | 7 - packages/voice/examples/UNLICENSE | 24 --- packages/voice/examples/basic/README.md | 91 ----------- packages/voice/examples/basic/adapter.ts | 73 --------- .../voice/examples/basic/basic-example.ts | 153 ------------------ packages/voice/examples/radio-bot/.gitignore | 2 - packages/voice/examples/radio-bot/README.md | 64 -------- .../examples/radio-bot/config.example.json | 6 - packages/voice/examples/radio-bot/index.js | 104 ------------ .../voice/examples/radio-bot/package.json | 32 ---- .../voice/examples/recorder/.eslintrc.json | 7 - packages/voice/examples/recorder/.gitignore | 4 - packages/voice/examples/recorder/README.md | 23 --- .../voice/examples/recorder/auth.example.json | 3 - packages/voice/examples/recorder/package.json | 28 ---- .../examples/recorder/recordings/.gitkeep | 0 packages/voice/examples/recorder/src/bot.ts | 50 ------ .../recorder/src/createListeningStream.ts | 42 ----- .../voice/examples/recorder/src/deploy.ts | 27 ---- .../examples/recorder/src/interactions.ts | 92 ----------- .../examples/recorder/tsconfig.eslint.json | 3 - .../voice/examples/recorder/tsconfig.json | 14 -- 24 files changed, 5 insertions(+), 850 deletions(-) delete mode 100644 packages/voice/examples/README.md delete mode 100644 packages/voice/examples/UNLICENSE delete mode 100644 packages/voice/examples/basic/README.md delete mode 100644 packages/voice/examples/basic/adapter.ts delete mode 100644 packages/voice/examples/basic/basic-example.ts delete mode 100644 packages/voice/examples/radio-bot/.gitignore delete mode 100644 packages/voice/examples/radio-bot/README.md delete mode 100644 packages/voice/examples/radio-bot/config.example.json delete mode 100644 packages/voice/examples/radio-bot/index.js delete mode 100644 packages/voice/examples/radio-bot/package.json delete mode 100644 packages/voice/examples/recorder/.eslintrc.json delete mode 100644 packages/voice/examples/recorder/.gitignore delete mode 100644 packages/voice/examples/recorder/README.md delete mode 100644 packages/voice/examples/recorder/auth.example.json delete mode 100644 packages/voice/examples/recorder/package.json delete mode 100644 packages/voice/examples/recorder/recordings/.gitkeep delete mode 100644 packages/voice/examples/recorder/src/bot.ts delete mode 100644 packages/voice/examples/recorder/src/createListeningStream.ts delete mode 100644 packages/voice/examples/recorder/src/deploy.ts delete mode 100644 packages/voice/examples/recorder/src/interactions.ts delete mode 100644 packages/voice/examples/recorder/tsconfig.eslint.json delete mode 100644 packages/voice/examples/recorder/tsconfig.json diff --git a/packages/voice/.eslintignore b/packages/voice/.eslintignore index 8be4eac61..cd4efd8e5 100644 --- a/packages/voice/.eslintignore +++ b/packages/voice/.eslintignore @@ -1,2 +1 @@ *.d.ts -examples diff --git a/packages/voice/README.md b/packages/voice/README.md index 301d9a1f3..6e0121187 100644 --- a/packages/voice/README.md +++ b/packages/voice/README.md @@ -64,6 +64,10 @@ try installing another. - [`FFmpeg`](https://ffmpeg.org/) (installed and added to environment) - `ffmpeg-static`: ^4.2.7 (npm install) +## Examples + +The [voice-examples][voice-examples] repository contains examples on how to use this package. Feel free to check them out if you need a nudge in the right direction. + ## Links - [Website][website] ([source][website-source]) @@ -99,3 +103,4 @@ nudge in the right direction, please don't hesitate to join our official [discor [npm]: https://www.npmjs.com/package/@discordjs/voice [related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries [contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md +[voice-examples]: https://github.com/discordjs/voice-examples diff --git a/packages/voice/examples/README.md b/packages/voice/examples/README.md deleted file mode 100644 index 81617f38e..000000000 --- a/packages/voice/examples/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Examples - -| Example | Description | -| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [Basic](./basic) | A simple "Hello World" TypeScript example that plays an mp3 file. Notably, it works with discord.js v12 and so it also contains an example of creating an adapter | -| [Radio Bot](./radio-bot) | A fun JavaScript example of what you can create using @discordjs/voice. A radio bot that plays output from your speakers in a Discord voice channel | -| [Recorder](./recorder) | An example of using voice receive to create a bot that can record audio from users | diff --git a/packages/voice/examples/UNLICENSE b/packages/voice/examples/UNLICENSE deleted file mode 100644 index 68a49daad..000000000 --- a/packages/voice/examples/UNLICENSE +++ /dev/null @@ -1,24 +0,0 @@ -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to diff --git a/packages/voice/examples/basic/README.md b/packages/voice/examples/basic/README.md deleted file mode 100644 index 70dafdcdf..000000000 --- a/packages/voice/examples/basic/README.md +++ /dev/null @@ -1,91 +0,0 @@ -# Basic Example - -This example will demonstrate how to join a voice channel and play resources, with some best practice -assistance on making sure you aren't waiting indefinitely for things to happen. - -To achieve this, the example sets some fairly arbitrary time constraints for things such as joining -voice channels and audio becoming available. - -## Code snippet - -This code snippet doesn't include any comments for brevity. If you want to see the full source code, -check the other files in this folder! - -```ts -import { Client, VoiceChannel, Intents } from 'discord.js'; -import { - joinVoiceChannel, - createAudioPlayer, - createAudioResource, - entersState, - StreamType, - AudioPlayerStatus, - VoiceConnectionStatus, -} from '@discordjs/voice'; -import { createDiscordJSAdapter } from './adapter'; - -const player = createAudioPlayer(); - -function playSong() { - const resource = createAudioResource('https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3', { - inputType: StreamType.Arbitrary, - }); - - player.play(resource); - - return entersState(player, AudioPlayerStatus.Playing, 5e3); -} - -async function connectToChannel(channel: VoiceChannel) { - const connection = joinVoiceChannel({ - channelId: channel.id, - guildId: channel.guild.id, - adapterCreator: createDiscordJSAdapter(channel), - }); - - try { - await entersState(connection, VoiceConnectionStatus.Ready, 30e3); - return connection; - } catch (error) { - connection.destroy(); - throw error; - } -} - -const client = new Client({ - ws: { intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES, Intents.FLAGS.GUILD_VOICE_STATES] }, -}); - -client.login('token here'); - -client.on('ready', async () => { - console.log('Discord.js client is ready!'); - - try { - await playSong(); - console.log('Song is ready to play!'); - } catch (error) { - console.error(error); - } -}); - -client.on('message', async (message) => { - if (!message.guild) return; - - if (message.content === '-join') { - const channel = message.member?.voice.channel; - - if (channel) { - try { - const connection = await connectToChannel(channel); - connection.subscribe(player); - message.reply('Playing now!'); - } catch (error) { - console.error(error); - } - } else { - message.reply('Join a voice channel then try again!'); - } - } -}); -``` diff --git a/packages/voice/examples/basic/adapter.ts b/packages/voice/examples/basic/adapter.ts deleted file mode 100644 index 6e7b1bf0c..000000000 --- a/packages/voice/examples/basic/adapter.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { DiscordGatewayAdapterCreator, DiscordGatewayAdapterLibraryMethods } from '../../'; -import { Snowflake, Client, Guild, VoiceBasedChannel, Status, Events } from 'discord.js'; -import { - GatewayDispatchEvents, - GatewayVoiceServerUpdateDispatchData, - GatewayVoiceStateUpdateDispatchData, -} from 'discord-api-types/v9'; - -const adapters = new Map(); -const trackedClients = new Set(); - -/** - * Tracks a Discord.js client, listening to VOICE_SERVER_UPDATE and VOICE_STATE_UPDATE events - * - * @param client - The Discord.js Client to track - */ -function trackClient(client: Client) { - if (trackedClients.has(client)) return; - trackedClients.add(client); - client.ws.on(GatewayDispatchEvents.VoiceServerUpdate, (payload: GatewayVoiceServerUpdateDispatchData) => { - adapters.get(payload.guild_id)?.onVoiceServerUpdate(payload); - }); - client.ws.on(GatewayDispatchEvents.VoiceStateUpdate, (payload: GatewayVoiceStateUpdateDispatchData) => { - if (payload.guild_id && payload.session_id && payload.user_id === client.user?.id) { - adapters.get(payload.guild_id)?.onVoiceStateUpdate(payload); - } - }); - client.on(Events.ShardDisconnect, (_, shardId) => { - const guilds = trackedShards.get(shardId); - if (guilds) { - for (const guildID of guilds.values()) { - adapters.get(guildID)?.destroy(); - } - } - trackedShards.delete(shardId); - }); -} - -const trackedShards = new Map>(); - -function trackGuild(guild: Guild) { - let guilds = trackedShards.get(guild.shardId); - if (!guilds) { - guilds = new Set(); - trackedShards.set(guild.shardId, guilds); - } - guilds.add(guild.id); -} - -/** - * Creates an adapter for a Voice Channel. - * - * @param channel - The channel to create the adapter for - */ -export function createDiscordJSAdapter(channel: VoiceBasedChannel): DiscordGatewayAdapterCreator { - return (methods) => { - adapters.set(channel.guild.id, methods); - trackClient(channel.client); - trackGuild(channel.guild); - return { - sendPayload(data) { - if (channel.guild.shard.status === Status.Ready) { - channel.guild.shard.send(data); - return true; - } - return false; - }, - destroy() { - return adapters.delete(channel.guild.id); - }, - }; - }; -} diff --git a/packages/voice/examples/basic/basic-example.ts b/packages/voice/examples/basic/basic-example.ts deleted file mode 100644 index 00222e956..000000000 --- a/packages/voice/examples/basic/basic-example.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { Client, VoiceBasedChannel, VoiceChannel } from 'discord.js'; -import { - joinVoiceChannel, - createAudioPlayer, - createAudioResource, - entersState, - StreamType, - AudioPlayerStatus, - VoiceConnectionStatus, -} from '@discordjs/voice'; -import { createDiscordJSAdapter } from './adapter'; -import { GatewayIntentBits } from 'discord-api-types/v9'; - -/** - * In this example, we are creating a single audio player that plays to a number of voice channels. - * The audio player will play a single track. - */ - -/** - * Create the audio player. We will use this for all of our connections. - */ -const player = createAudioPlayer(); - -function playSong() { - /** - * Here we are creating an audio resource using a sample song freely available online - * (see https://www.soundhelix.com/audio-examples) - * - * We specify an arbitrary inputType. This means that we aren't too sure what the format of - * the input is, and that we'd like to have this converted into a format we can use. If we - * were using an Ogg or WebM source, then we could change this value. However, for now we - * will leave this as arbitrary. - */ - const resource = createAudioResource('https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3', { - inputType: StreamType.Arbitrary, - }); - - /** - * We will now play this to the audio player. By default, the audio player will not play until - * at least one voice connection is subscribed to it, so it is fine to attach our resource to the - * audio player this early. - */ - player.play(resource); - - /** - * Here we are using a helper function. It will resolve if the player enters the Playing - * state within 5 seconds, otherwise it will reject with an error. - */ - return entersState(player, AudioPlayerStatus.Playing, 5e3); -} - -async function connectToChannel(channel: VoiceBasedChannel) { - /** - * Here, we try to establish a connection to a voice channel. If we're already connected - * to this voice channel, @discordjs/voice will just return the existing connection for us! - */ - const connection = joinVoiceChannel({ - channelId: channel.id, - guildId: channel.guild.id, - adapterCreator: createDiscordJSAdapter(channel), - }); - - /** - * If we're dealing with a connection that isn't yet Ready, we can set a reasonable - * time limit before giving up. In this example, we give the voice connection 30 seconds - * to enter the ready state before giving up. - */ - try { - /** - * Allow ourselves 30 seconds to join the voice channel. If we do not join within then, - * an error is thrown. - */ - await entersState(connection, VoiceConnectionStatus.Ready, 30e3); - /** - * At this point, the voice connection is ready within 30 seconds! This means we can - * start playing audio in the voice channel. We return the connection so it can be - * used by the caller. - */ - return connection; - } catch (error) { - /** - * At this point, the voice connection has not entered the Ready state. We should make - * sure to destroy it, and propagate the error by throwing it, so that the calling function - * is aware that we failed to connect to the channel. - */ - connection.destroy(); - throw error; - } -} - -/** - * Main code - * ========= - * Here we will implement the helper functions that we have defined above. - */ - -const client = new Client({ - intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildVoiceStates], -}); - -void client.login('token here'); - -client.on('ready', async () => { - console.log('Discord.js client is ready!'); - - /** - * Try to get our song ready to play for when the bot joins a voice channel - */ - try { - await playSong(); - console.log('Song is ready to play!'); - } catch (error) { - /** - * The song isn't ready to play for some reason :( - */ - console.error(error); - } -}); - -client.on('messageCreate', async (message) => { - if (!message.guild) return; - - if (message.content === '-join') { - const channel = message.member?.voice.channel; - - if (channel) { - /** - * The user is in a voice channel, try to connect. - */ - try { - const connection = await connectToChannel(channel); - - /** - * We have successfully connected! Now we can subscribe our connection to - * the player. This means that the player will play audio in the user's - * voice channel. - */ - connection.subscribe(player); - await message.reply('Playing now!'); - } catch (error) { - /** - * Unable to connect to the voice channel within 30 seconds :( - */ - console.error(error); - } - } else { - /** - * The user is not in a voice channel. - */ - void message.reply('Join a voice channel then try again!'); - } - } -}); diff --git a/packages/voice/examples/radio-bot/.gitignore b/packages/voice/examples/radio-bot/.gitignore deleted file mode 100644 index dba742256..000000000 --- a/packages/voice/examples/radio-bot/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -config.json -package-lock.json diff --git a/packages/voice/examples/radio-bot/README.md b/packages/voice/examples/radio-bot/README.md deleted file mode 100644 index 69d0b090b..000000000 --- a/packages/voice/examples/radio-bot/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# Discord Radio Bot 🎧 - -A proof-of-concept radio bot that uses @discordjs/voice and discord.js. Streams audio from an audio output hardware device on your computer over a Discord voice channel. - -**Works on:** - -- Linux (via PulseAudio `pulse`) -- Windows (via DirectShow `dshow`) - -## Usage - -```bash -# Clone the main @discordjs/voice repo, then install dependencies and build -$ npm install -$ npm run build - -# Enter this example's directory, create a config file and start! -$ cd examples/radio-bot -$ npm install -$ nano config.json -$ npm start - -# Join a voice channel in Discord, then send "-join" -``` - -## Configuring on Windows via `dshow` - -Run `ffmpeg -list_devices true -f dshow -i dummy` and observe output containing something similar: - -``` -DirectShow audio devices - "Stereo Mix (Realtek(R) Audio)" - Alternative name "@device_cm_{ID1}\wave_{ID2}" -``` - -For example, playing the above device will mirror audio from the speaker output of your machine. Your `config.json` should then be considered like so: - -```json -{ - "token": "discord_bot_token", - "device": "Stereo Mix (Realtek(R) Audio)", - "type": "dshow", - "maxTransmissionGap": 5000 -} -``` - -## Configuring on Linux via `pulse` - -Run `pactl list short sources` and observe output containing something similar: - -``` -5 alsa_output.pci.3.analog-stereo.monitor module-alsa-card.c s16le 2ch 44100Hz IDLE -``` - -Then configure your `config.json` with the device you'd like to use: - -```json -{ - "token": "discord_bot_token", - "device": "alsa_output.pci.3.analog-stereo.monitor", - "type": "pulse", - "maxTransmissionGap": 5000 -} -``` diff --git a/packages/voice/examples/radio-bot/config.example.json b/packages/voice/examples/radio-bot/config.example.json deleted file mode 100644 index 6aa3fed98..000000000 --- a/packages/voice/examples/radio-bot/config.example.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "token": "discord_bot_token", - "device": "audio_hw_device_id", - "type": "pulse", - "maxTransmissionGap": 5000 -} diff --git a/packages/voice/examples/radio-bot/index.js b/packages/voice/examples/radio-bot/index.js deleted file mode 100644 index 672dbb6a6..000000000 --- a/packages/voice/examples/radio-bot/index.js +++ /dev/null @@ -1,104 +0,0 @@ -require('module-alias/register'); - -const { Client } = require('discord.js'); -const prism = require('prism-media'); -const config = require('./config.json'); -const { - NoSubscriberBehavior, - StreamType, - createAudioPlayer, - createAudioResource, - entersState, - AudioPlayerStatus, - VoiceConnectionStatus, - joinVoiceChannel, -} = require('@discordjs/voice'); - -const player = createAudioPlayer({ - behaviors: { - noSubscriber: NoSubscriberBehavior.Play, - maxMissedFrames: Math.round(config.maxTransmissionGap / 20), - }, -}); - -player.on('stateChange', (oldState, newState) => { - if (oldState.status === AudioPlayerStatus.Idle && newState.status === AudioPlayerStatus.Playing) { - console.log('Playing audio output on audio player'); - } else if (newState.status === AudioPlayerStatus.Idle) { - console.log('Playback has stopped. Attempting to restart.'); - attachRecorder(); - } -}); - -function attachRecorder() { - player.play( - createAudioResource( - new prism.FFmpeg({ - args: [ - '-analyzeduration', - '0', - '-loglevel', - '0', - '-f', - config.type, - '-i', - config.type === 'dshow' ? `audio=${config.device}` : config.device, - '-acodec', - 'libopus', - '-f', - 'opus', - '-ar', - '48000', - '-ac', - '2', - ], - }), - { - inputType: StreamType.OggOpus, - }, - ), - ); - console.log('Attached recorder - ready to go!'); -} - -async function connectToChannel(channel) { - const connection = joinVoiceChannel({ - channelId: channel.id, - guildId: channel.guild.id, - adapterCreator: channel.guild.voiceAdapterCreator, - }); - try { - await entersState(connection, VoiceConnectionStatus.Ready, 30_000); - return connection; - } catch (error) { - connection.destroy(); - throw error; - } -} - -const client = new Client({ intents: ['GUILDS', 'GUILD_MESSAGES', 'GUILD_VOICE_STATES'] }); - -client.on('ready', async () => { - console.log('discord.js client is ready!'); - attachRecorder(); -}); - -client.on('messageCreate', async (message) => { - if (!message.guild) return; - if (message.content === '-join') { - const channel = message.member?.voice.channel; - if (channel) { - try { - const connection = await connectToChannel(channel); - connection.subscribe(player); - await message.reply('Playing now!'); - } catch (error) { - console.error(error); - } - } else { - await message.reply('Join a voice channel then try again!'); - } - } -}); - -void client.login(config.token); diff --git a/packages/voice/examples/radio-bot/package.json b/packages/voice/examples/radio-bot/package.json deleted file mode 100644 index 1cafafad5..000000000 --- a/packages/voice/examples/radio-bot/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "discord-radio-bot", - "version": "1.0.0", - "description": "A proof-of-concept radio bot for @discordjs/voice", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "start": "node index.js" - }, - "keywords": [ - "discord", - "radio", - "bot", - "audio", - "speakers", - "hardware", - "dj" - ], - "author": "Amish Shah ", - "license": "MIT", - "dependencies": { - "@discordjs/voice": "file:../../", - "discord.js": "^13.7.0", - "libsodium-wrappers": "^0.7.9", - "module-alias": "^2.2.2", - "prism-media": "^1.3.1" - }, - "_moduleAliases": { - "@root": ".", - "@discordjs/voice": "../../", - "libsodium-wrappers": "./node_modules/libsodium-wrappers" - } -} diff --git a/packages/voice/examples/recorder/.eslintrc.json b/packages/voice/examples/recorder/.eslintrc.json deleted file mode 100644 index ab8cc9fa2..000000000 --- a/packages/voice/examples/recorder/.eslintrc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "root": true, - "extends": "../../.eslintrc.json", - "parserOptions": { - "project": "./tsconfig.eslint.json" - } -} diff --git a/packages/voice/examples/recorder/.gitignore b/packages/voice/examples/recorder/.gitignore deleted file mode 100644 index 789c0675a..000000000 --- a/packages/voice/examples/recorder/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -package-lock.json -auth.json -tsconfig.tsbuildinfo -recordings/*.ogg diff --git a/packages/voice/examples/recorder/README.md b/packages/voice/examples/recorder/README.md deleted file mode 100644 index 397542d53..000000000 --- a/packages/voice/examples/recorder/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# 👂 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 -``` diff --git a/packages/voice/examples/recorder/auth.example.json b/packages/voice/examples/recorder/auth.example.json deleted file mode 100644 index 34e3fca00..000000000 --- a/packages/voice/examples/recorder/auth.example.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "token": "Your Discord bot token here" -} diff --git a/packages/voice/examples/recorder/package.json b/packages/voice/examples/recorder/package.json deleted file mode 100644 index 47ce68f27..000000000 --- a/packages/voice/examples/recorder/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "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 .", - "build": "tsc", - "build:check": "tsc --noEmit --incremental false" - }, - "author": "Amish Shah ", - "license": "MIT", - "dependencies": { - "@discordjs/opus": "^0.8.0", - "discord-api-types": "^0.33.3", - "discord.js": "^13.8.0", - "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.7.4" - } -} diff --git a/packages/voice/examples/recorder/recordings/.gitkeep b/packages/voice/examples/recorder/recordings/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/voice/examples/recorder/src/bot.ts b/packages/voice/examples/recorder/src/bot.ts deleted file mode 100644 index 8e7555008..000000000 --- a/packages/voice/examples/recorder/src/bot.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { getVoiceConnection } from '@discordjs/voice'; -import { GatewayIntentBits } from 'discord-api-types/v9'; -import Discord, { Interaction, Constants } from 'discord.js'; -import { deploy } from './deploy'; -import { interactionHandlers } from './interactions'; - -const { token } = require('../auth.json') as { token: string }; - -const client = new Discord.Client({ - intents: [GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildMessages, GatewayIntentBits.Guilds], -}); - -const { Events } = Constants; - -client.on(Events.CLIENT_READY, () => console.log('Ready!')); - -client.on(Events.MESSAGE_CREATE, 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(); - -client.on(Events.INTERACTION_CREATE, 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(Events.ERROR, console.warn); - -void client.login(token); diff --git a/packages/voice/examples/recorder/src/createListeningStream.ts b/packages/voice/examples/recorder/src/createListeningStream.ts deleted file mode 100644 index 60e659b04..000000000 --- a/packages/voice/examples/recorder/src/createListeningStream.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { createWriteStream } from 'node:fs'; -import { pipeline } from 'node:stream'; -import { EndBehaviorType, VoiceReceiver } from '@discordjs/voice'; -import type { User } from 'discord.js'; -import * as prism from 'prism-media'; - -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: 1000, - }, - }); - - 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}`); - } - }); -} diff --git a/packages/voice/examples/recorder/src/deploy.ts b/packages/voice/examples/recorder/src/deploy.ts deleted file mode 100644 index ce1166ae9..000000000 --- a/packages/voice/examples/recorder/src/deploy.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ApplicationCommandOptionType } from 'discord-api-types/v9'; -import type { 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: ApplicationCommandOptionType.User, - description: 'The user to record', - required: true, - }, - ], - }, - { - name: 'leave', - description: 'Leave the voice channel', - }, - ]); -}; diff --git a/packages/voice/examples/recorder/src/interactions.ts b/packages/voice/examples/recorder/src/interactions.ts deleted file mode 100644 index 55610ca2b..000000000 --- a/packages/voice/examples/recorder/src/interactions.ts +++ /dev/null @@ -1,92 +0,0 @@ -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, - 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, - 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, - _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, - client: Client, - connection?: VoiceConnection, - ) => Promise ->(); -interactionHandlers.set('join', join); -interactionHandlers.set('record', record); -interactionHandlers.set('leave', leave); diff --git a/packages/voice/examples/recorder/tsconfig.eslint.json b/packages/voice/examples/recorder/tsconfig.eslint.json deleted file mode 100644 index ea6be8e9a..000000000 --- a/packages/voice/examples/recorder/tsconfig.eslint.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "./tsconfig.json" -} diff --git a/packages/voice/examples/recorder/tsconfig.json b/packages/voice/examples/recorder/tsconfig.json deleted file mode 100644 index cadce793d..000000000 --- a/packages/voice/examples/recorder/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "baseUrl": ".", - "outDir": "dist", - "skipLibCheck": true, - "paths": { - "@discordjs/voice": ["../../"], - "libsodium-wrappers": ["./node_modules/libsodium-wrappers"] - } - }, - "include": ["src/*.ts"], - "exclude": [""] -}