chore: monorepo setup (#7175)

This commit is contained in:
Noel
2022-01-07 17:18:25 +01:00
committed by GitHub
parent 780b7ed39f
commit 16390efe6e
504 changed files with 25459 additions and 22830 deletions

View File

@@ -0,0 +1,7 @@
{
"root": true,
"extends": "../../.eslintrc.json",
"parserOptions": {
"project": "./tsconfig.eslint.json"
}
}

View File

@@ -0,0 +1,4 @@
package-lock.json
auth.json
tsconfig.tsbuildinfo
recordings/*.ogg

View 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
```

View File

@@ -0,0 +1,3 @@
{
"token": "Your Discord bot token here"
}

View 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"
}
}

View 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);

View File

@@ -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}`);
}
});
}

View 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',
},
]);
};

View 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);

View File

@@ -0,0 +1,3 @@
{
"extends": "./tsconfig.json"
}

View 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": [""]
}