├── LICENSE.md ├── README.md ├── commandResponses ├── skip.mp3 └── stop.mp3 ├── commands ├── bassboost.js ├── clearqueue.js ├── control.js ├── help.js ├── karaoke.js ├── language.js ├── nightcore.js ├── pause.js ├── ping.js ├── play.js ├── resume.js ├── skip.js ├── stop.js ├── surrounding.js ├── tremolo.js ├── vaporwave.js ├── vibrato.js └── volume.js ├── databases └── main │ └── INFO.txt ├── events ├── messageCreate.js └── ready.js ├── example.env ├── extenders ├── antiCrash.js └── clientVariables.js ├── index.js ├── languages ├── cn.js ├── de.js ├── en.js └── fr.js ├── package.json ├── playerFunctions.js ├── temp └── INFO.txt ├── utils ├── botUtils.js ├── constants │ ├── Color.js │ ├── Emojis.js │ ├── Status.js │ ├── clientData.js │ └── settingsData.js ├── language.js ├── playerFunctions.js └── speechHandler.js └── yarn.lock /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Tomato6966 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Voice Controlled Discord Bot 3 | 4 | This repository is for the 5th Hackathon of Documatic. 5 | With that Project I am aiming to place `#1` and win 50€ 6 | 7 | ![Banner](https://imgur.com/ZCuO0KT.gif) 8 | 9 | # About 10 | 11 | It is a voice controlled Discord Bot, means you (mainly) do not use prefix / Slash Commands, no! You will be using your VOICE. 12 | 13 | Ofc. you somehow need to parse that audio, for that I am using https://wit.ai which is a free speech-to-text ai, which you need to train! 14 | That means it might sometimes not return what you say... but I tested it over 19 months on another Project, and there it works 100% everytime, even multilangual now.. 15 | 16 | It is a Music Bot, as other interactions are not that optimal, like kick / ban, you can't control that with voice 100% sure... 17 | 18 | [Check out the **Show-off and Tutorial Video**](https://github.com/Tomato6966/voice-controlled-discord-bot/blob/main/README.md#explanation-and-show-off-video) 19 | 20 | ![image](https://user-images.githubusercontent.com/68145571/182658779-1638aed0-10e3-4c23-b95d-1f7e36d8fc82.png) 21 | 22 | # System-Requirements 23 | - **Idle Load**: **`17mb Ram`** 24 | - **High Load**: **`25-30mb Ram`** 25 | - **Highest Memory Load** *with HEAPS for 2 Days runtime*: **`97mb Ram`** 26 | - **JS-Engine:** [nodejs v16.10 or later](https://nodejs.org) 27 | - **RUST (cmake):** [latest](https://www.rust-lang.org/tools/install) 28 | - **FFMPEG:** `npm i ffmpeg-static ffmpeg` & `apt-get install -y ffmpeg` 29 | 30 | | Recommended-CPU | Recommended-RAM | 31 | |--|--| 32 | | 1 CORE | 250mb Ram | 33 | 34 | ### Example Usage 35 | 36 | >![image](https://user-images.githubusercontent.com/68145571/182658298-f079f132-29ad-4259-8328-d9c1ebfad280.png) 37 | 38 | # How to use the Bot? | [Check the Video](https://github.com/Tomato6966/voice-controlled-discord-bot/blob/main/README.md#explanation-and-show-off-video) 39 | 0. *[Join the testing Server](https://discord.gg/TWRJH6ACvR) - Prefix: `v!`* / [self-host it!](https://github.com/Tomato6966/voice-controlled-discord-bot/blob/main/README.md#self-hosting) 40 | 1. Join a Discord Voice-Channel, *in a Server, where the Bot is in!* 41 | 2. Type in a Text-Channel `v!control` or `@Bot control` 42 | a. *Now it'll only listen and be controlable by YOU* 43 | 3. Speak your COMMANDS and add a KEYWORD in beforehand. 44 | a. **Examples**: 45 | i. `bot play shape of you` 46 | ii. `bot play thunder` 47 | iii. `bot play despacito` 48 | iv. `bot nightcore` *=Audio-Filter* 49 | v. `bot skip` 50 | vi. `bot stop` 51 | 4. Tipps to get understood more often! 52 | a. Make sure to talk in a normal speed and loudness, do not scream or errape 53 | b. Also Make sure to Reduce background noices, speak clear and fluently to be "recogniced" by the bot pretty well! 54 | 55 | # Self-Hosting 56 | 1. Download the repo | [Click-here](https://github.com/Tomato6966/voice-controlled-discord-bot/archive/refs/heads/main.zip) 57 | - or: `git clone https://github.com/Tomato6966/voice-controlled-discord-bot` 58 | 2. Rename `example.env` to `.env` and fill out the variables *(get wit.ai `Server Access Token` from [wit.ai](https://wit.ai))* 59 | 3. `yarn install` / `npm install` (make sure you have `rust` & `nodejs` on your system) 60 | 4. Type `node index.js` / `npm start` 61 | 62 | ## Want to add more music commands? 63 | 64 | Take a look at my Light-Music-Bot Project, which is similar to this one (for the music system) https://github.com/Tomato6966/light-music-bot 65 | 66 | # Resources used (modules & credits) 67 | - `node-fetch@2` for api Calls 68 | - `discord.js@latest` as my Discord-Bot-Wrapper 69 | - `@discordjs/voice`, `@discordjs/opus`, `discord-ytdl-core`, `ytdl-core`, `youtube-sr`, `ffmpeg`, `libsodium-wrappers` for the Music System (Its similar to [my light-music-bot](https://github.com/Tomato6966/light-music-bot)) 70 | - `ffmpeg`, `prism-media`, `node-crc` for parsing / Piping / Transforming Audio Streams, -Buffers and -Files. 71 | - `dotenv` for allowing to use .env ENVIRONMENT Variables 72 | 73 | # Explanation and show off Video 74 | 75 | https://user-images.githubusercontent.com/68145571/181937812-b8602b54-5f68-48cf-b85d-c076092b606a.mp4 76 | 77 | # ~~ToDo~~ 78 | 79 | - ✅ (28/07/2022 20:17) Change Message Responses from the voice-command, to VOICE-RESPONSES (Bot speaks it) 80 | - Announce Songs: "Now playing XYZ" 81 | - also for Skip: "Skipping the current Track" 82 | - ✅ (27/07/2022 18:23) Make the structure better, and fix all bugs 83 | - ✅ (27/07/2022 18:23) Add all to a .env file, and create an example.env file 84 | - ✅ (27/07/2022 18:23) Adjust the Settings 85 | - ✅ (27/07/2022 18:23) Make it multi langual without any modules 86 | - ✅ (27/07/2022 18:23) Try to make it even faster ;) 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /commandResponses/skip.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tomato6966/voice-controlled-discord-bot/cee54f011edaf5ea92be0e0d5e406909ac793782/commandResponses/skip.mp3 -------------------------------------------------------------------------------- /commandResponses/stop.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tomato6966/voice-controlled-discord-bot/cee54f011edaf5ea92be0e0d5e406909ac793782/commandResponses/stop.mp3 -------------------------------------------------------------------------------- /commands/bassboost.js: -------------------------------------------------------------------------------- 1 | const { getVoiceConnection, createAudioPlayer, NoSubscriberBehavior } = require("@discordjs/voice"); 2 | const { PermissionsBitField } = require("discord.js"); 3 | const { joinVoiceChannelUtil, playSong, createQueue, createSong, leaveVoiceChannel, getResource } = require("../utils/playerFunctions.js"); 4 | const { parseAudioData } = require("../utils/speechHandler.js"); 5 | const { default: YouTube } = require('youtube-sr'); 6 | const { translate } = require("../utils/language.js"); 7 | module.exports = { 8 | name: "bassboost", 9 | aliases: ["bass"], 10 | description: "Toggles a Bassboost effect!", 11 | execute: async (client, args, user, channel, voiceChannel, message, prefix) => { 12 | const oldConnection = getVoiceConnection(channel.guild.id); 13 | if(!oldConnection) return channel.send({ 14 | content: translate(client, channel.guild.id, "NOT_CONNECTED") 15 | }).catch(() => null); 16 | 17 | const queue = client.queues.get(channel.guild.id); // get the queue 18 | if(!queue) { 19 | return channel.send({ 20 | content: translate(client, channel.guild.id, "NOTHING_PLAYING") 21 | }).catch(() => null); 22 | } 23 | 24 | 25 | // 7 dbs 26 | const filterToChange = "Bassboost" 27 | queue.effects[`${filterToChange.toLowerCase()}`] = queue.effects[`${filterToChange.toLowerCase()}`] === 0 ? 7 : 0; 28 | 29 | // change the Basslevel 30 | queue.filtersChanged = true; 31 | const curPos = oldConnection.state.subscription.player.state.resource.playbackDuration; 32 | oldConnection.state.subscription.player.stop(); 33 | oldConnection.state.subscription.player.play(getResource(queue, queue.tracks[0].id, curPos)); 34 | 35 | return channel.send({ 36 | content: translate(client, channel.guildId, "FILTER", queue.effects[`${filterToChange.toLowerCase()}`] !== 0, filterToChange) 37 | }).catch(() => null); 38 | } 39 | } -------------------------------------------------------------------------------- /commands/clearqueue.js: -------------------------------------------------------------------------------- 1 | const { getVoiceConnection } = require("@discordjs/voice"); 2 | const { PermissionsBitField } = require("discord.js"); 3 | const { joinVoiceChannelUtil, playSong, createQueue, createSong } = require("../utils/playerFunctions.js"); 4 | const { parseAudioData } = require("../utils/speechHandler.js"); 5 | const { default: YouTube } = require('youtube-sr'); 6 | const { translate } = require("../utils/language.js"); 7 | module.exports = { 8 | name: "clearqueue", 9 | description: "Clears the current queue", 10 | execute: async (client, args, user, channel, voiceChannel, message, prefix) => { 11 | const oldConnection = getVoiceConnection(channel.guild.id); 12 | if(!oldConnection) return channel.send({ 13 | content: translate(client, channel.guild.id, "NOT_CONNECTED") 14 | }).catch(() => null); 15 | 16 | const queue = client.queues.get(channel.guild.id); // get the queue 17 | if(!queue) { 18 | return channel.send({ 19 | content: translate(client, channel.guild.id, "NOTHING_PLAYING") 20 | }).catch(() => null); 21 | } 22 | // no new songs (and no current) 23 | queue.tracks = [ queue.tracks[0] ]; 24 | // skip the track 25 | 26 | return channel.send({ 27 | content: translate(client, channel.guild.id, "CLEAREDQUEUE") 28 | }).catch(() => null); 29 | } 30 | } -------------------------------------------------------------------------------- /commands/control.js: -------------------------------------------------------------------------------- 1 | const { getVoiceConnection } = require("@discordjs/voice"); 2 | const { PermissionsBitField } = require("discord.js"); 3 | const { joinVoiceChannelUtil } = require("../utils/playerFunctions.js"); 4 | const { parseAudioData } = require("../utils/speechHandler.js"); 5 | const { settings, Emojis } = require("../utils/constants/settingsData.js"); 6 | const { translate } = require("../utils/language.js"); 7 | const justListened = new Map(); 8 | 9 | module.exports = { 10 | name: "control", 11 | description: "Take audio-control of the Bot", 12 | execute: async (client, args, user, channel, voiceChannel, message, prefix) => { 13 | try { 14 | // User's VOice Channel Connection 15 | const { channel } = message.member.voice; 16 | // if not connected, return an error 17 | if (!channel) return message.reply({ content: translate(client, message.guildId, "JOIN_VC") }).catch(console.warn); 18 | 19 | // receive the Bot's connection 20 | const oldConnection = getVoiceConnection(message.guildId); 21 | // if the bot is connected already return error 22 | if (oldConnection) return message.reply({ content: translate(client, message.guildId, "ALREADY_CONNECTED", oldConnection.joinConfig.channelId) }).catch(() => null); 23 | 24 | // missing Permission for CONNECT 25 | if (!channel.permissionsFor(client.user.id)?.has(PermissionsBitField.Flags.Connect)) return message.reply({ content: translate(client, message.guildId, "MISSING_PERMS", "__CONNECT__") }).catch(() => null); 26 | // missing Permission for SPEAK 27 | if (!channel.permissionsFor(client.user.id)?.has(PermissionsBitField.Flags.Speak)) return message.reply({ content: translate(client, message.guildId, "MISSING_PERMS", "__SPEAK__") }).catch(() => null); 28 | 29 | //join in a voice connection 30 | const voiceConnection = await joinVoiceChannelUtil(client, channel); 31 | if (!voiceConnection) return message.reply({ content: translate(client, message.guildId, "COULD_NOT_JOIN", channel.id) }).catch(() => null); 32 | 33 | client.listenAbleUsers.add(message.author.id); 34 | 35 | //STARTE ZUHÖRER 36 | voiceConnection.receiver.speaking.on("start", async (userId) => { 37 | // if it's an invalid User, or the user is not allowed anymore, or still is on cooldown WAIT 38 | if (!client.listenAbleUsers.has(userId) || userId !== message.author.id || IsOnCooldown(userId, settings.listeningCooldown)) return 39 | // use and parse the audio data from the user 40 | parseAudioData(client, voiceConnection, message.author, message.channel) 41 | }) 42 | 43 | //ERROR LOGGER 44 | voiceConnection.receiver.speaking.on('disconnect', async (e) => { 45 | if (e) console.error(e); 46 | client.listenAbleUsers.delete(message.author.id); 47 | }); 48 | 49 | message.reply({ 50 | content: translate(client, message.guildId, "CONTROLLING", client.commands.filter(c => !settings.prefixCommands.includes(c.name)).map(x => `\`${x.name}\``).join(",")) 51 | }).catch(() => null); 52 | } catch (e) { 53 | console.error(e); 54 | message.reply({ content: `${Emojis.cross.str} Could not connect. **Reason**: \`\`\`${e.message || e}`.substring(0, 1950) + `\`\`\`` }).catch(() => null); 55 | } 56 | } 57 | } 58 | 59 | function IsOnCooldown(userId, time = 5_000) { 60 | const justListenedTime = justListened.get(userId); 61 | 62 | if (justListenedTime && Date.now() - justListenedTime < time) return true; 63 | 64 | justListened.set(userId, Date.now()); 65 | 66 | return false; 67 | } -------------------------------------------------------------------------------- /commands/help.js: -------------------------------------------------------------------------------- 1 | const { getVoiceConnection } = require("@discordjs/voice"); 2 | const { PermissionsBitField } = require("discord.js"); 3 | const { translate } = require("../utils/language.js"); 4 | const { joinVoiceChannelUtil } = require("../utils/playerFunctions.js"); 5 | const { parseAudioData } = require("../utils/speechHandler.js"); 6 | module.exports = { 7 | name: "help", 8 | description: "Information about me", 9 | execute: async (client, args, user, channel, voiceChannel, message, prefix) => { 10 | channel.send({ 11 | content: translate(client, channel.guild.id, "HELP", prefix || process.env.DEFAULTPREFIX) 12 | }).catch(() => null); 13 | } 14 | } -------------------------------------------------------------------------------- /commands/karaoke.js: -------------------------------------------------------------------------------- 1 | const { getVoiceConnection, createAudioPlayer, NoSubscriberBehavior } = require("@discordjs/voice"); 2 | const { PermissionsBitField } = require("discord.js"); 3 | const { joinVoiceChannelUtil, playSong, createQueue, createSong, leaveVoiceChannel, getResource } = require("../utils/playerFunctions.js"); 4 | const { parseAudioData } = require("../utils/speechHandler.js"); 5 | const { default: YouTube } = require('youtube-sr'); 6 | const { translate } = require("../utils/language.js"); 7 | module.exports = { 8 | name: "karaoke", 9 | description: "Toggles a Karaoke effect!", 10 | execute: async (client, args, user, channel, voiceChannel, message, prefix) => { 11 | const oldConnection = getVoiceConnection(channel.guild.id); 12 | if(!oldConnection) return channel.send({ 13 | content: translate(client, channel.guild.id, "NOT_CONNECTED") 14 | }).catch(() => null); 15 | 16 | const queue = client.queues.get(channel.guild.id); // get the queue 17 | if(!queue) { 18 | return channel.send({ 19 | content: translate(client, channel.guild.id, "NOTHING_PLAYING") 20 | }).catch(() => null); 21 | } 22 | 23 | const filterToChange = "Karaoke" 24 | queue.effects[`${filterToChange.toLowerCase()}`] = !queue.effects[`${filterToChange.toLowerCase()}`]; 25 | 26 | // changed a filter 27 | queue.filtersChanged = true; 28 | const curPos = oldConnection.state.subscription.player.state.resource.playbackDuration; 29 | oldConnection.state.subscription.player.stop(); 30 | oldConnection.state.subscription.player.play(getResource(queue, queue.tracks[0].id, curPos)); 31 | 32 | return channel.send({ 33 | content: translate(client, channel.guildId, "FILTER", !!queue.effects[`${filterToChange.toLowerCase()}`], filterToChange) 34 | }).catch(() => null); 35 | } 36 | } -------------------------------------------------------------------------------- /commands/language.js: -------------------------------------------------------------------------------- 1 | const { ActionRowBuilder, ButtonBuilder, ButtonStyle, PermissionsBitField } = require("discord.js"); 2 | const { languageStrings, languages, translate } = require("../utils/language"); 3 | module.exports = { 4 | name: "language", 5 | description: "Change the Bot's language", 6 | execute: async (client, args, user, channel, voiceChannel, message, prefix) => { 7 | if(!message.member.permissions.has(PermissionsBitField.Flags.Administrator)) { 8 | return message.reply({ 9 | content: translate(client, message.guildId, "MISSING_PERMS") 10 | }) 11 | } 12 | 13 | client.db.ensure(message.guildId, { language: "en" }); 14 | const lang = client.db.get(message.guildId, "language") ?? "en"; 15 | const langs = Object.entries(languageStrings); 16 | const allcomps = [ ]; 17 | for(let i = 0; i<= langs.length + 5; i+=5) { 18 | if(langs.length > i) { 19 | allcomps.push(new ActionRowBuilder().addComponents(langs.slice(i, i+5).map(([l, data]) => 20 | new ButtonBuilder().setStyle(lang == l ? ButtonStyle.Primary : ButtonStyle.Secondary).setDisabled(lang == l).setLabel(data.text).setEmoji(data.emoji).setCustomId(`language_${l}`) 21 | ))); 22 | } 23 | } 24 | message.reply({ 25 | content: `Pick your wished Language`, 26 | components: allcomps.slice(0, 5) 27 | }).then(msg => { 28 | var collector = msg.createMessageComponentCollector({ filter: (i) => i.user.id == message.author.id, time: 60_000 }); 29 | collector.on('collect', async i => { 30 | const newlang = i.customId.split("_")[1] 31 | client.db.set(message.guildId, newlang, "language"); 32 | i.update({ 33 | components: [], 34 | content: translate(client, message.guildId, "LANGUAGE", `${languageStrings[newlang].emoji} ${languageStrings[newlang].text}`) 35 | }); 36 | }) 37 | collector.on("end", collected => { 38 | if(collected.size === 0) msg.edit({ 39 | components: [], 40 | content: translate(client, message.guildId, "TIME_ENDED") 41 | }).catch(() => null) 42 | }) 43 | }) 44 | } 45 | } -------------------------------------------------------------------------------- /commands/nightcore.js: -------------------------------------------------------------------------------- 1 | const { getVoiceConnection, createAudioPlayer, NoSubscriberBehavior } = require("@discordjs/voice"); 2 | const { PermissionsBitField } = require("discord.js"); 3 | const { joinVoiceChannelUtil, playSong, createQueue, createSong, leaveVoiceChannel, getResource } = require("../utils/playerFunctions.js"); 4 | const { parseAudioData } = require("../utils/speechHandler.js"); 5 | const { default: YouTube } = require('youtube-sr'); 6 | const { translate } = require("../utils/language.js"); 7 | module.exports = { 8 | name: "nightcore", 9 | description: "Toggles a Nightcore effect!", 10 | aliases: ["night"], 11 | execute: async (client, args, user, channel, voiceChannel, message, prefix) => { 12 | const oldConnection = getVoiceConnection(channel.guild.id); 13 | if(!oldConnection) return channel.send({ 14 | content: translate(client, channel.guild.id, "NOT_CONNECTED") 15 | }).catch(() => null); 16 | 17 | const queue = client.queues.get(channel.guild.id); // get the queue 18 | if(!queue) { 19 | return channel.send({ 20 | content: translate(client, channel.guild.id, "NOTHING_PLAYING") 21 | }).catch(() => null); 22 | } 23 | 24 | const filterToChange = "Nightcore" 25 | queue.effects[`${filterToChange.toLowerCase()}`] = !queue.effects[`${filterToChange.toLowerCase()}`]; 26 | 27 | // changed a filter 28 | queue.filtersChanged = true; 29 | const curPos = oldConnection.state.subscription.player.state.resource.playbackDuration; 30 | oldConnection.state.subscription.player.stop(); 31 | oldConnection.state.subscription.player.play(getResource(queue, queue.tracks[0].id, curPos)); 32 | 33 | return channel.send({ 34 | content: translate(client, channel.guildId, "FILTER", !!queue.effects[`${filterToChange.toLowerCase()}`], filterToChange) 35 | }).catch(() => null); 36 | } 37 | } -------------------------------------------------------------------------------- /commands/pause.js: -------------------------------------------------------------------------------- 1 | const { getVoiceConnection } = require("@discordjs/voice"); 2 | const { PermissionsBitField } = require("discord.js"); 3 | const { joinVoiceChannelUtil, playSong, createQueue, createSong } = require("../utils/playerFunctions.js"); 4 | const { parseAudioData } = require("../utils/speechHandler.js"); 5 | const { default: YouTube } = require('youtube-sr'); 6 | const { translate } = require("../utils/language.js"); 7 | module.exports = { 8 | name: "pause", 9 | description: "Pauses the current Track", 10 | execute: async (client, args, user, channel, voiceChannel, message, prefix) => { 11 | const oldConnection = getVoiceConnection(channel.guild.id); 12 | if(!oldConnection) return channel.send({ 13 | content: translate(client, channel.guild.id, "NOT_CONNECTED") 14 | }).catch(() => null); 15 | 16 | const queue = client.queues.get(channel.guild.id); // get the queue 17 | if(!queue) { 18 | return channel.send({ 19 | content: translate(client, channel.guild.id, "NOTHING_PLAYING") 20 | }).catch(() => null); 21 | } 22 | 23 | // if already paused 24 | if(queue.paused) return channel.send({ 25 | content: translate(client, channel.guild.id, "NOT_RESUMED") 26 | }).catch(() => null); 27 | 28 | queue.paused = true; 29 | 30 | // skip the track 31 | oldConnection.state.subscription.player.pause(); 32 | 33 | return channel.send({ 34 | content: translate(client, channel.guild.id, "PAUSED") 35 | }).catch(() => null); 36 | } 37 | } -------------------------------------------------------------------------------- /commands/ping.js: -------------------------------------------------------------------------------- 1 | const { getVoiceConnection } = require("@discordjs/voice"); 2 | const { PermissionsBitField } = require("discord.js"); 3 | const { translate } = require("../utils/language.js"); 4 | const { joinVoiceChannelUtil } = require("../utils/playerFunctions.js"); 5 | const { parseAudioData } = require("../utils/speechHandler.js"); 6 | module.exports = { 7 | name: "ping", 8 | description: "Responses with the Api-Ws-Ping", 9 | execute: async (client, args, user, channel, voiceChannel, message, prefix) => { 10 | channel.send({ 11 | content: translate(client, channel.guild.id, "PING", client.ws.ping) 12 | }).catch(() => null); 13 | } 14 | } -------------------------------------------------------------------------------- /commands/play.js: -------------------------------------------------------------------------------- 1 | const { playSong, createQueue, createSong, queuePos } = require("../utils/playerFunctions.js"); 2 | const { default: YouTube } = require('youtube-sr'); 3 | const { getVoiceConnection } = require("@discordjs/voice"); 4 | const { translate } = require("../utils/language.js"); 5 | module.exports = { 6 | name: "play", 7 | description: "Play a song", 8 | aliases: ["place"], 9 | execute: async (client, args, user, channel, voiceChannel, message, prefix) => { 10 | const oldConnection = getVoiceConnection(channel.guild.id); 11 | if(!oldConnection) return channel.send({ 12 | content: translate(client, channel.guild.id, "NOT_CONNECTED") 13 | }).catch(() => null); 14 | 15 | const track = args.join(" "); 16 | if(!args[0]) return channel.send(`👎 Please add the wished Music via saying: \`voice play \``).catch(() => null); 17 | // Regexpressions for testing the search string 18 | const youtubRegex = /^(https?:\/\/)?(www\.)?(m\.|music\.)?(youtube\.com|youtu\.?be)\/.+$/gi; 19 | const playlistRegex = /^.*(list=)([^#\&\?]*).*/gi; 20 | const songRegex = /^.*(watch\?v=)([^#\&\?]*).*/gi; 21 | // variables for song, and playlist 22 | let song = null; 23 | let playlist = null; 24 | // Use the regex expressions 25 | const isYT = youtubRegex.exec(track); 26 | const isSong = songRegex.exec(track); 27 | const isList = playlistRegex.exec(track) 28 | 29 | try { 30 | // try to play the requested song 31 | const m = await channel.send(`🔍 *Searching **${track}** ...*`).catch(() => null); 32 | // get the queue 33 | let queue = client.queues.get(channel.guild.id); 34 | // get song from the link 35 | if(isYT && isSong && !isList) { 36 | song = await YouTube.getVideo(track); 37 | } 38 | // get playlist from the link 39 | else if(isYT && !isSong && isList) { 40 | playlist = await YouTube.getPlaylist(track).then(playlist => playlist.fetch()); 41 | } 42 | // get playlist & song from the link 43 | else if(isYT && isSong && isList) { 44 | song = await YouTube.getVideo(`https://www.youtube.com/watch?v=${isSong[2]}`); 45 | playlist = await YouTube.getPlaylist(`https://www.youtube.com/playlist?list=${isList[2]}`).then(playlist => playlist.fetch()); 46 | } 47 | // otherwise search for it 48 | else { 49 | song = await YouTube.searchOne(track); 50 | } 51 | 52 | if(!song && !playlist) return m.edit(`❌ **Failed looking up for ${track}!**`); 53 | /* FOR NO PLAYLIST REQUESTS */ 54 | if(!playlist) { 55 | // if there is no queue create one and start playing 56 | if(!queue || queue.tracks.length == 0) { 57 | // Add the playlist songs to the queue 58 | const newQueue = createQueue(song, user, channel.id, voiceChannel.id) 59 | client.queues.set(channel.guild.id, newQueue) 60 | // play the song in the voice channel 61 | await playSong(client, voiceChannel, song); 62 | // edit the loading message 63 | return m.edit(`▶️ **Now Playing: __${song.title}__** - \`${song.durationFormatted}\``).catch(() => null); 64 | } 65 | // Add the song to the queue 66 | queue.tracks.push(createSong(song, user)) 67 | // edit the loading message 68 | return m.edit(`👍 **Queued at \`${queuePos(queue.tracks.length - 1)}\`: __${song.title}__** - \`${song.durationFormatted}\``).catch(() => null); 69 | } 70 | /* FOR PLAYLIST REQUEST */ 71 | else { 72 | // get the song, or the first playlist song 73 | song = song ? song : playlist.videos[0]; 74 | // remove the song which got added 75 | const index = playlist.videos.findIndex(s => s.id == song.id) || 0; 76 | playlist.videos.splice(index, 1) 77 | // if there is no queue create one and start playing 78 | if(!queue || queue.tracks.length == 0) { 79 | // Add the playlist songs to the queue 80 | const newQueue = createQueue(song, user, channel.id, voiceChannel.id) 81 | playlist.videos.forEach(song => newQueue.tracks.push(createSong(song, user))) 82 | client.queues.set(channel.guild.id, newQueue) 83 | // play the song in the voice channel 84 | await playSong(client, voiceChannel, song); 85 | // edit the loading message 86 | return m.edit(`▶️ **Now Playing: __${song.title}__** - \`${song.durationFormatted}\`\n> **Added \`${playlist.videos.length - 1} Songs\` from the Playlist:**\n> __**${playlist.title}**__`).catch(() => null); 87 | } 88 | // Add the playlist songs to the queue 89 | playlist.videos.forEach(song => queue.tracks.push(createSong(song, user))) 90 | // edit the loading message 91 | return m.edit(`👍 **Queued at \`${queuePos(queue.tracks.length - (playlist.videos.length - 1))}\`: __${song.title}__** - \`${song.durationFormatted}\`\n> **Added \`${playlist.videos.length - 1} Songs\` from the Playlist:**\n> __**${playlist.title}**__`).catch(() => null); 92 | } 93 | 94 | } catch (e){ console.error(e); 95 | return channel.send(`❌ Could not play the Song because: \`\`\`${e.message || e}`.substr(0, 1950) + `\`\`\``).catch(() => null); 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /commands/resume.js: -------------------------------------------------------------------------------- 1 | const { getVoiceConnection } = require("@discordjs/voice"); 2 | const { PermissionsBitField } = require("discord.js"); 3 | const { joinVoiceChannelUtil, playSong, createQueue, createSong } = require("../utils/playerFunctions.js"); 4 | const { parseAudioData } = require("../utils/speechHandler.js"); 5 | const { default: YouTube } = require('youtube-sr'); 6 | const { translate } = require("../utils/language.js"); 7 | module.exports = { 8 | name: "resume", 9 | description: "Resumes the current song", 10 | execute: async (client, args, user, channel, voiceChannel, message, prefix) => { 11 | const oldConnection = getVoiceConnection(channel.guild.id); 12 | if(!oldConnection) return channel.send({ 13 | content: translate(client, channel.guild.id, "NOT_CONNECTED") 14 | }).catch(() => null); 15 | 16 | const queue = client.queues.get(channel.guild.id); // get the queue 17 | if(!queue) { 18 | return channel.send({ 19 | content: translate(client, channel.guild.id, "NOTHING_PLAYING") 20 | }).catch(() => null); 21 | } 22 | // if already paused 23 | if(!queue.paused) return channel.send({ 24 | content: translate(client, channel.guild.id, "NOT_PAUSED") 25 | }).catch(() => null); 26 | 27 | queue.paused = false; 28 | 29 | // skip the track 30 | oldConnection.state.subscription.player.unpause(); 31 | 32 | return channel.send({ 33 | content: translate(client, channel.guild.id, "RESUMED") 34 | }).catch(() => null); 35 | } 36 | } -------------------------------------------------------------------------------- /commands/skip.js: -------------------------------------------------------------------------------- 1 | const { getVoiceConnection } = require("@discordjs/voice"); 2 | const { PermissionsBitField } = require("discord.js"); 3 | const { joinVoiceChannelUtil, playSong, createQueue, createSong } = require("../utils/playerFunctions.js"); 4 | const { parseAudioData } = require("../utils/speechHandler.js"); 5 | const { default: YouTube } = require('youtube-sr'); 6 | const { translate } = require("../utils/language.js"); 7 | module.exports = { 8 | name: "skip", 9 | description: "Skips the current song", 10 | execute: async (client, args, user, channel, voiceChannel, message, prefix) => { 11 | const oldConnection = getVoiceConnection(channel.guild.id); 12 | if(!oldConnection) return channel.send({ 13 | content: translate(client, channel.guild.id, "NOT_CONNECTED") 14 | }).catch(() => null); 15 | 16 | const queue = client.queues.get(channel.guild.id); // get the queue 17 | if(!queue) { 18 | return channel.send({ 19 | content: translate(client, channel.guild.id, "NOTHING_PLAYING") 20 | }).catch(() => null); 21 | } 22 | // no new songs (and no current) 23 | if(!queue.tracks || queue.tracks.length <= 1) { 24 | return channel.send({ 25 | content: translate(client, channel.guild.id, "NOTHING_TO_SKIP") 26 | }).catch(() => null); 27 | } 28 | queue.skipped = true; 29 | 30 | queue.tracks.splice(1, 0, client.commandResponses.get("skip")); 31 | // skip the track 32 | oldConnection.state.subscription.player.stop(); 33 | 34 | return channel.send({ 35 | content: translate(client, channel.guild.id, "SKIPPED") 36 | }).catch(() => null); 37 | } 38 | } -------------------------------------------------------------------------------- /commands/stop.js: -------------------------------------------------------------------------------- 1 | const { getVoiceConnection, createAudioPlayer, NoSubscriberBehavior } = require("@discordjs/voice"); 2 | const { PermissionsBitField } = require("discord.js"); 3 | const { joinVoiceChannelUtil, playSong, createQueue, createSong, leaveVoiceChannel } = require("../utils/playerFunctions.js"); 4 | const { parseAudioData } = require("../utils/speechHandler.js"); 5 | const { default: YouTube } = require('youtube-sr'); 6 | const { translate } = require("../utils/language.js"); 7 | module.exports = { 8 | name: "stop", 9 | description: "Stops the Player and listener!", 10 | execute: async (client, args, user, channel, voiceChannel, message, prefix) => { 11 | const oldConnection = getVoiceConnection(channel.guild.id); 12 | if(!oldConnection) return channel.send({ 13 | content: translate(client, channel.guild.id, "NOT_CONNECTED") 14 | }).catch(() => null); 15 | 16 | const queue = client.queues.get(channel.guild.id); // get the queue 17 | if(!queue) { 18 | return channel.send({ 19 | content: translate(client, channel.guild.id, "NOTHING_PLAYING") 20 | }).catch(() => null); 21 | } 22 | // no new songs (and no current) 23 | queue.tracks = [client.commandResponses.get("stop")]; 24 | // set the queue to stopped 25 | queue.stopped = true; 26 | // skip the track 27 | oldConnection.state.subscription.player.stop(); 28 | 29 | // response will be sent, when the queue is stopped, aka no need for sending it in here.. 30 | //return channel.send({ 31 | // content: translate(client, channel.guild.id, "STOPPED") 32 | //}).catch(() => null); 33 | } 34 | } -------------------------------------------------------------------------------- /commands/surrounding.js: -------------------------------------------------------------------------------- 1 | const { getVoiceConnection, createAudioPlayer, NoSubscriberBehavior } = require("@discordjs/voice"); 2 | const { PermissionsBitField } = require("discord.js"); 3 | const { joinVoiceChannelUtil, playSong, createQueue, createSong, leaveVoiceChannel, getResource } = require("../utils/playerFunctions.js"); 4 | const { parseAudioData } = require("../utils/speechHandler.js"); 5 | const { default: YouTube } = require('youtube-sr'); 6 | const { translate } = require("../utils/language.js"); 7 | module.exports = { 8 | name: "surrounding", 9 | description: "Toggles a Surrounding effect!", 10 | execute: async (client, args, user, channel, voiceChannel, message, prefix) => { 11 | const oldConnection = getVoiceConnection(channel.guild.id); 12 | if(!oldConnection) return channel.send({ 13 | content: translate(client, channel.guild.id, "NOT_CONNECTED") 14 | }).catch(() => null); 15 | 16 | const queue = client.queues.get(channel.guild.id); // get the queue 17 | if(!queue) { 18 | return channel.send({ 19 | content: translate(client, channel.guild.id, "NOTHING_PLAYING") 20 | }).catch(() => null); 21 | } 22 | 23 | const filterToChange = "Surrounding" 24 | queue.effects[`${filterToChange.toLowerCase()}`] = !queue.effects[`${filterToChange.toLowerCase()}`]; 25 | 26 | // changed a filter 27 | queue.filtersChanged = true; 28 | const curPos = oldConnection.state.subscription.player.state.resource.playbackDuration; 29 | oldConnection.state.subscription.player.stop(); 30 | oldConnection.state.subscription.player.play(getResource(queue, queue.tracks[0].id, curPos)); 31 | 32 | return channel.send({ 33 | content: translate(client, channel.guildId, "FILTER", !!queue.effects[`${filterToChange.toLowerCase()}`], filterToChange) 34 | }).catch(() => null); 35 | } 36 | } -------------------------------------------------------------------------------- /commands/tremolo.js: -------------------------------------------------------------------------------- 1 | const { getVoiceConnection, createAudioPlayer, NoSubscriberBehavior } = require("@discordjs/voice"); 2 | const { PermissionsBitField } = require("discord.js"); 3 | const { joinVoiceChannelUtil, playSong, createQueue, createSong, leaveVoiceChannel, getResource } = require("../utils/playerFunctions.js"); 4 | const { parseAudioData } = require("../utils/speechHandler.js"); 5 | const { default: YouTube } = require('youtube-sr'); 6 | const { translate } = require("../utils/language.js"); 7 | module.exports = { 8 | name: "tremolo", 9 | description: "Toggles a Tremolo effect!", 10 | execute: async (client, args, user, channel, voiceChannel, message, prefix) => { 11 | const oldConnection = getVoiceConnection(channel.guild.id); 12 | if(!oldConnection) return channel.send({ 13 | content: translate(client, channel.guild.id, "NOT_CONNECTED") 14 | }).catch(() => null); 15 | 16 | const queue = client.queues.get(channel.guild.id); // get the queue 17 | if(!queue) { 18 | return channel.send({ 19 | content: translate(client, channel.guild.id, "NOTHING_PLAYING") 20 | }).catch(() => null); 21 | } 22 | 23 | const filterToChange = "Tremolo" 24 | queue.effects[`${filterToChange.toLowerCase()}`] = !queue.effects[`${filterToChange.toLowerCase()}`]; 25 | 26 | // changed a filter 27 | queue.filtersChanged = true; 28 | const curPos = oldConnection.state.subscription.player.state.resource.playbackDuration; 29 | oldConnection.state.subscription.player.stop(); 30 | oldConnection.state.subscription.player.play(getResource(queue, queue.tracks[0].id, curPos)); 31 | 32 | return channel.send({ 33 | content: translate(client, channel.guildId, "FILTER", !!queue.effects[`${filterToChange.toLowerCase()}`], filterToChange) 34 | }).catch(() => null); 35 | } 36 | } -------------------------------------------------------------------------------- /commands/vaporwave.js: -------------------------------------------------------------------------------- 1 | const { getVoiceConnection, createAudioPlayer, NoSubscriberBehavior } = require("@discordjs/voice"); 2 | const { PermissionsBitField } = require("discord.js"); 3 | const { joinVoiceChannelUtil, playSong, createQueue, createSong, leaveVoiceChannel, getResource } = require("../utils/playerFunctions.js"); 4 | const { parseAudioData } = require("../utils/speechHandler.js"); 5 | const { default: YouTube } = require('youtube-sr'); 6 | const { translate } = require("../utils/language.js"); 7 | module.exports = { 8 | name: "vaporwave", 9 | description: "Toggles a Vaporwave effect!", 10 | execute: async (client, args, user, channel, voiceChannel, message, prefix) => { 11 | const oldConnection = getVoiceConnection(channel.guild.id); 12 | if(!oldConnection) return channel.send({ 13 | content: translate(client, channel.guild.id, "NOT_CONNECTED") 14 | }).catch(() => null); 15 | 16 | const queue = client.queues.get(channel.guild.id); // get the queue 17 | if(!queue) { 18 | return channel.send({ 19 | content: translate(client, channel.guild.id, "NOTHING_PLAYING") 20 | }).catch(() => null); 21 | } 22 | 23 | const filterToChange = "Vaporwave" 24 | queue.effects[`${filterToChange.toLowerCase()}`] = !queue.effects[`${filterToChange.toLowerCase()}`]; 25 | 26 | // changed a filter 27 | queue.filtersChanged = true; 28 | const curPos = oldConnection.state.subscription.player.state.resource.playbackDuration; 29 | oldConnection.state.subscription.player.stop(); 30 | oldConnection.state.subscription.player.play(getResource(queue, queue.tracks[0].id, curPos)); 31 | 32 | return channel.send({ 33 | content: translate(client, channel.guildId, "FILTER", !!queue.effects[`${filterToChange.toLowerCase()}`], filterToChange) 34 | }).catch(() => null); 35 | } 36 | } -------------------------------------------------------------------------------- /commands/vibrato.js: -------------------------------------------------------------------------------- 1 | const { getVoiceConnection, createAudioPlayer, NoSubscriberBehavior } = require("@discordjs/voice"); 2 | const { PermissionsBitField } = require("discord.js"); 3 | const { joinVoiceChannelUtil, playSong, createQueue, createSong, leaveVoiceChannel, getResource } = require("../utils/playerFunctions.js"); 4 | const { parseAudioData } = require("../utils/speechHandler.js"); 5 | const { default: YouTube } = require('youtube-sr'); 6 | const { translate } = require("../utils/language.js"); 7 | module.exports = { 8 | name: "vibrato", 9 | description: "Toggles a Vibrato effect!", 10 | execute: async (client, args, user, channel, voiceChannel, message, prefix) => { 11 | const oldConnection = getVoiceConnection(channel.guild.id); 12 | if(!oldConnection) return channel.send({ 13 | content: translate(client, channel.guild.id, "NOT_CONNECTED") 14 | }).catch(() => null); 15 | 16 | const queue = client.queues.get(channel.guild.id); // get the queue 17 | if(!queue) { 18 | return channel.send({ 19 | content: translate(client, channel.guild.id, "NOTHING_PLAYING") 20 | }).catch(() => null); 21 | } 22 | 23 | const filterToChange = "Vibrato" 24 | queue.effects[`${filterToChange.toLowerCase()}`] = !queue.effects[`${filterToChange.toLowerCase()}`]; 25 | 26 | // changed a filter 27 | queue.filtersChanged = true; 28 | const curPos = oldConnection.state.subscription.player.state.resource.playbackDuration; 29 | oldConnection.state.subscription.player.stop(); 30 | oldConnection.state.subscription.player.play(getResource(queue, queue.tracks[0].id, curPos)); 31 | 32 | return channel.send({ 33 | content: translate(client, channel.guildId, "FILTER", !!queue.effects[`${filterToChange.toLowerCase()}`], filterToChange) 34 | }).catch(() => null); 35 | } 36 | } -------------------------------------------------------------------------------- /commands/volume.js: -------------------------------------------------------------------------------- 1 | const { playSong, createQueue, createSong, queuePos } = require("../utils/playerFunctions.js"); 2 | const { default: YouTube } = require('youtube-sr'); 3 | const { getVoiceConnection } = require("@discordjs/voice"); 4 | const { translate } = require("../utils/language.js"); 5 | module.exports = { 6 | name: "volume", 7 | description: "Changes the Volume", 8 | execute: async (client, args, user, channel, voiceChannel, message, prefix) => { 9 | const oldConnection = getVoiceConnection(channel.guild.id); 10 | if(!oldConnection) return channel.send({ 11 | content: translate(client, channel.guild.id, "NOT_CONNECTED") 12 | }).catch(() => null); 13 | 14 | const queue = client.queues.get(channel.guild.id); // get the queue 15 | if(!queue) { 16 | return channel.send({ 17 | content: translate(client, channel.guild.id, "NOTHING_PLAYING") 18 | }).catch(() => null); 19 | } 20 | if(!args[0] || isNaN(Number(args[0])) || Number(args[0]) < 0 || Number(args[0]) > 150) { 21 | return channel.send({ 22 | content: translate(client, channel.guild.id, "INVALID_VOL") 23 | }).catch(() => null); 24 | } 25 | queue.volume = Number(args[0]); 26 | 27 | // change the volume 28 | oldConnection.state.subscription.player.state.resource.volume.setVolume(queue.volume / 100); 29 | 30 | return channel.send({ 31 | content: translate(client, channel.guild.id, "VOLUME", queue.volume) 32 | }).catch(() => null); 33 | } 34 | } -------------------------------------------------------------------------------- /databases/main/INFO.txt: -------------------------------------------------------------------------------- 1 | PLACEHOLDER -------------------------------------------------------------------------------- /events/messageCreate.js: -------------------------------------------------------------------------------- 1 | const { settings } = require("../utils/constants/settingsData.js"); 2 | const { translate } = require("../utils/language.js"); 3 | const { escapeRegex } = require("../utils/botUtils.js"); 4 | module.exports = async client => { 5 | const validMessageCommands = [...settings.prefixCommands, "ping"]; 6 | // The Event 7 | client.on("messageCreate", async (message) => { 8 | if(!message.guild || message.author.bot) return; 9 | // ensure the Database 10 | client.db.ensure(message.guildId, { prefix: (process.env.DEFAULTPREFIX ?? "!") }); 11 | // get the database prefix 12 | const prefix = client.db.get(message.guildId, "prefix") 13 | // prefix regex for matching the message content 14 | const prefixRegex = new RegExp(`^(<@!?${client.user.id}>|${escapeRegex(prefix)})\\s*`); 15 | // if it's not a command, then return 16 | if (!prefixRegex.test(message.content)) return; 17 | // get the used Prefix ("@bot" / "!") 18 | const [, usedPrefix] = message.content.match(prefixRegex); 19 | // format the message content for cmd and args 20 | const [ cmdName, ...args ] = message.content.slice(usedPrefix.length).trim().split(/ +/g); 21 | // if no commandName input, but a bot ping, return info 22 | if (!cmdName?.length) { 23 | if (usedPrefix.includes(client.user.id)) return message.reply({ 24 | content: translate(client, message.guild, "PREFIXINFO", prefix) 25 | }).catch(() => null); 26 | return; 27 | } 28 | // find the right command 29 | const command = client.commands.get(cmdName?.toLowerCase()) || client.commands.find(c => !!c.aliases?.includes(cmdName?.toLowerCase())) 30 | 31 | // execute the command, if it's valid 32 | if(command) { 33 | if(!validMessageCommands.includes(command.name)) { 34 | if(client.listenAbleUsers.size > 0 && !client.listenAbleUsers.has(message.author.id)) { 35 | return message.reply({ 36 | content: translate(client, message.guild, "NOT_CONTROLLING", prefix) 37 | }) 38 | } 39 | } 40 | // client, args, user, channel, voiceChannel, message, prefix 41 | command.execute(client, args, message.author, message.channel, message.member.voice.channel, message, prefix); 42 | } 43 | }); 44 | } -------------------------------------------------------------------------------- /events/ready.js: -------------------------------------------------------------------------------- 1 | const { Status } = require("../utils/constants/settingsData.js"); 2 | 3 | module.exports = async client => { 4 | client.on("ready", () => { 5 | let statusCounter = 0; 6 | setInterval(() => { 7 | client.user.setActivity(Status.activities[statusCounter++]); 8 | if(statusCounter >= Status.activities.length - 1) statusCounter = 0; 9 | setTimeout(() => { 10 | const ram = (process.memoryUsage().heapUsed/1024/1024).toFixed(0) + "mb"; 11 | const rssRam = (process.memoryUsage().rss/1024/1024).toFixed(0) + "mb"; 12 | console.table({ram, rssRam}) 13 | }, 500); 14 | }, Status.editInterval); 15 | 16 | console.table({ 17 | State: ` -- READY -- `, 18 | Bot: client.user.tag, 19 | Id: client.user.id, 20 | Guilds: client.guilds.cache.size, 21 | Members: client.guilds.cache.map(g => g.memberCount).reduce((a,b) => a+b,0), 22 | }); 23 | }); 24 | } -------------------------------------------------------------------------------- /example.env: -------------------------------------------------------------------------------- 1 | # SENSITIVE DATA 2 | DISCORD_TOKEN="" 3 | YOUTUBE_LOGIN_COOKIE="" 4 | WIT_AI_ACCESS_TOKEN="" 5 | 6 | # Settings 7 | DEFAULTPREFIX="v!" 8 | DEFAULT_LANGUAGE="en" -------------------------------------------------------------------------------- /extenders/antiCrash.js: -------------------------------------------------------------------------------- 1 | module.exports = client => { 2 | process.on('unhandledRejection', (reason, p) => { 3 | console.log(' [antiCrash] :: Unhandled Rejection/Catch'); 4 | console.log(reason, p); 5 | }); 6 | process.on("uncaughtException", (err, origin) => { 7 | console.log(' [antiCrash] :: Uncaught Exception/Catch'); 8 | console.log(err, origin); 9 | }) 10 | process.on('uncaughtExceptionMonitor', (err, origin) => { 11 | console.log(' [antiCrash] :: Uncaught Exception/Catch (MONITOR)'); 12 | console.log(err, origin); 13 | }); 14 | process.on('multipleResolves', (type, promise, reason) => { 15 | //console.log(' [antiCrash] :: Multiple Resolves'); 16 | //console.log(type, promise, reason); 17 | }); 18 | } -------------------------------------------------------------------------------- /extenders/clientVariables.js: -------------------------------------------------------------------------------- 1 | const { Collection } = require("discord.js"); 2 | const Enmap = require("enmap"); 3 | module.exports = async client => { 4 | client.commands = new Collection(); 5 | client.queues = new Collection(); 6 | client.commandResponses = new Collection(); 7 | client.listenAbleUsers = new Set(); // add delete size 8 | client.db = new Enmap({ 9 | name: "maindb", 10 | dataDir: "./databases/main" 11 | }); 12 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { createAudioResource } = require("@discordjs/voice"); 2 | const { Client } = require("discord.js"); 3 | const { readdirSync } = require("fs"); 4 | const { clientData } = require("./utils/constants/clientData.js"); 5 | require("dotenv").config(); 6 | 7 | const client = new Client(clientData); 8 | 9 | // load modules 10 | readdirSync(`./extenders`).filter(x => x.endsWith(".js")).forEach(fileName => require(`./extenders/${fileName}`)(client)); 11 | readdirSync(`./events`).filter(x => x.endsWith(".js")).forEach(fileName => require(`./events/${fileName}`)(client)); 12 | readdirSync(`./commands`).filter(x => x.endsWith(".js")).forEach(fileName => client.commands.set(fileName.toLowerCase().replace(".js", ""), require(`./commands/${fileName}`))) 13 | readdirSync(`./commandResponses`).filter( x => x.endsWith(".mp3")).forEach(fileName => client.commandResponses.set(fileName.toLowerCase().replace(".mp3", ""), {title: "infoActionAudio", resource: createAudioResource(`./commandResponses/${fileName}`)})) 14 | // Log in with the process.env.DISCORD_TOKEN Variable 15 | client.login(); -------------------------------------------------------------------------------- /languages/cn.js: -------------------------------------------------------------------------------- 1 | const { Emojis } = require("../utils/constants/settingsData.js"); 2 | const { newLiner, parseChannelMention, parseUserMention } = require("../utils/botUtils.js"); 3 | module.exports = { 4 | MISSING_PERMS: `${Emojis.cross.str} 您缺少此命令的权限`, 5 | 6 | PREFIXINFO: (prefix) => `${Emojis.check.str} **我这里的前缀是\`${prefix}\`**`, 7 | 8 | LANGUAGE: (newlangstring) => `${Emojis.check.str} 将语言更改为 **${newlangstring}**`, 9 | TIME_ENDED: `Time ran out`, 10 | 11 | 12 | JOIN_VC: `${Emojis.warn.str} **请先加入频道**`, 13 | ALREADY_CONNECTED: (channelId) => `${Emojis.denied.str} **我已经连接了 ${parseChannelMention(channelId)}**!`, 14 | MISSING_PERMS: (permString) => `${Emojis.denied.str} **我在您的语音频道中缺少 “${permString}” 的权限!**`, 15 | 16 | COULD_NOT_JOIN: (channelId) => `${Emojis.cross.str} **我无法连接到 ${parseChannelMention(channelId)}。**`, 17 | HELP: (prefix) => { 18 | return newLiner( 19 | `${Emojis.check.str} **帮助 | 关于我!**`, 20 | `> **我的前缀是: \`${prefix}\`**`, 21 | `*我是一个具有高品质和许多功能的语音控制音乐机器人!*`, 22 | `> 要开始,只需在聊天中输入 \`${prefix}control\``, 23 | `> 它会告诉你该怎么做,但**简单地说**:`, 24 | `> 只需说出您想要执行的命令!`, 25 | `**请注意:** 在每个命令之前,您必须添加一个关键字! 有效的是:`, 26 | `> \`bot\` / \`voice\` / \`speech\` / \`client\``, 27 | ) 28 | }, 29 | CONTROLLING: (possibleCommands) => { 30 | return newLiner( 31 | `${Emojis.check.str} **您现在正在控制机器人!**`, 32 | `__可能的命令:__`, 33 | `> ${possibleCommands}`, 34 | `__如何执行命令?__`, 35 | `> *说出它,说出一个关键字,然后说出命令和查询! (用英语讲)*`, 36 | `**示例**:\`\`\`bot play shape of you\nbot skip\nbot stop\nbot nightcore\nbot play no diggity\nbot play believer\`\`\``, 37 | `**不要停顿**:`, 38 | `> -) 不要停顿`, 39 | `> -) 无背景噪音`, 40 | `> -) 语音通常相当快速和清晰`, 41 | `> -) 确保没有人说话(甚至不是机器人)`, 42 | `\n> *你仍然可以像往常一样使用我的命令!*`, 43 | ); 44 | }, 45 | PING: (ping) => `🏓 我的 **API-RESPONSE-TIME** 是 **${ping}ms**`, 46 | NOWLISTENING: (usertag, time) => `👂 **现在正在收听 ${usertag}**\n> *可以获取下一个输入 *`, 47 | QUEUE_EMPTY: (time) => `${Emojis.empty.str} **队列为空**\n> 我将离开频道 `, 48 | LEFT_VC: `👋 **Left the VoiceChannel**`, 49 | NOT_CONNECTED: `${Emojis.denied.str} **我没有连接到某个地方!**`, 50 | NOTHING_PLAYING: `${Emojis.denied.str} **现在没有播放**`, 51 | NOTHING_TO_SKIP: `${Emojis.denied.str} **没有什么可跳过的**`, 52 | SKIPPED: `${Emojis.skip.str} **成功跳过赛道**`, 53 | STOPPED: `${Emojis.stop.str} **成功停止播放并清除队列。**`, 54 | NOT_CONTROLLING: (prefix) => `${Emojis.cross.str} **您不是通过 \`${prefix}control\` 控制机器人的人**`, 55 | FILTER: (state, filter) => `🎚 **成功 ${state ? "添加" : "移除"} \`${filter}\` 过滤器。**`, 56 | 57 | INVALID_VOL: `${Emojis.cross.str} **无效/未添加音量!**\n> 在 \`0\` 和 \`150\` 之间添加一个百分比!`, 58 | VOLUME: (vol) => `${Emojis.check.str} **将音量更改为 ${vol}%**`, 59 | 60 | RESUMED: `▶️ **成功恢复赛道**`, 61 | NOT_PAUSED: `👎 **曲目未暂停**`, 62 | PAUSED: `⏸️ **已成功暂停曲目**`, 63 | NOT_RESUMED: `👎 **曲目已暂停**`, 64 | 65 | CLEAREDQUEUE: `🪣 **成功清空队列.**`, 66 | } -------------------------------------------------------------------------------- /languages/de.js: -------------------------------------------------------------------------------- 1 | const { Emojis } = require("../utils/constants/settingsData.js"); 2 | const { newLiner, parseChannelMention, parseUserMention } = require("../utils/botUtils.js"); 3 | module.exports = { 4 | MISSING_PERMS: `${Emojis.cross.str} Dir fehlt die Berechtigung diesen Befehl auszuführen`, 5 | 6 | PREFIXINFO: (prefix) => `${Emojis.check.str} **Mein Prefix lautet: \`${prefix}\`**`, 7 | 8 | LANGUAGE: (newlangstring) => `${Emojis.check.str} Sprache auf **${newlangstring}** geändert!`, 9 | TIME_ENDED: `${Emojis.timer.str} Zeit abgelaufen...`, 10 | 11 | JOIN_VC: `${Emojis.warn.str} **Bitte Verbinde dich mit einem Voice-Channel**`, 12 | ALREADY_CONNECTED: (channelId) => `${Emojis.denied.str} **Ich bin schon in ${parseChannelMention(channelId)} verbunden**!`, 13 | MISSING_PERMS: (permString) => `${Emojis.denied.str} **Mir fehlen die Berechtigung für "${permString}" in deinem Voice-Channel!**`, 14 | 15 | COULD_NOT_JOIN: (channelId) => `${Emojis.cross.str} **Ich konnte ${parseChannelMention(channelId)} nicht beitreten.**`, 16 | HELP: (prefix) => { 17 | return newLiner( 18 | `${Emojis.check.str} **HILFE | ÜBER MICH!**`, 19 | `> **Mein Präfix ist: \`${prefix}\`**`, 20 | `*Ich bin ein sprachgesteuerter Musik-Bot mit hoher Qualität und vielen Features!*`, 21 | `> Um zu beginnen, geben Sie einfach \`${prefix}control\` in den Chat ein`, 22 | `> Es wird Ihnen sagen, was zu tun ist, aber **einfach gesagt**:`, 23 | `> Sagen Sie einfach die Befehle, die Sie ausführen möchten!`, 24 | `**BITTE BEACHTEN:** Vor jedem Befehl müssen Sie ein Schlüsselwort hinzufügen! Gültige sind:`, 25 | `> \`bot\` / \`voice\` / \`speech\` / \`client\``, 26 | ) 27 | }, 28 | CONTROLLING: (possibleCommands) => { 29 | return newLiner( 30 | `${Emojis.check.str} **Du kontrollierst mich nun!**`, 31 | `__Mögliche Befehle:__`, 32 | `> ${possibleCommands}`, 33 | `__Wie führst du einen aus?__`, 34 | `> *Sag es einfach, sprich ein KEYWORD (voice, speech) und danach den BEFEHL mit deiner Anfrage aus! (in English)*`, 35 | `**Zum Beispiel**: \`\`\`bot play shape of you\nbot skip\nbot stop\nbot nightcore\nbot play no diggity\nbot play believer\`\`\``, 36 | `**Tipps, dass ich dich besser verstehe**:`, 37 | `> -) Mach keine Sprechpausen`, 38 | `> -) Minimiere Hintergrundgeräusche`, 39 | `> -) Sprich normal, tendenziell schnell und deutlich`, 40 | `> -) Stelle sicher, dass sonst niemand ausser dir spricht`, 41 | `\n> *Du kannst mich aber normal mit Commands noch verwenden!*`, 42 | ); 43 | }, 44 | PING: (ping) => `🏓 Meine **API-Antwort-Zeit** ist **${ping}ms**`, 45 | NOWLISTENING: (usertag, time) => `👂 **Höre nun ${usertag} zu!**\n> *Nächstes Input kann passieren!*`, 46 | QUEUE_EMPTY: (time) => `${Emojis.empty.str} **Queue ist nun leer**\n> Ich werde den Channel in verlassen`, 47 | LEFT_VC: `👋 **Talk verlassen**`, 48 | NOT_CONNECTED: `${Emojis.denied.str} **Ich bin niergends verbunden.**`, 49 | NOTHING_PLAYING: `${Emojis.denied.str} **Ich spiele zurzeit nichts ab.**`, 50 | NOTHING_TO_SKIP: `${Emojis.denied.str} **Es gibt nichts zum überspringen**`, 51 | SKIPPED: `${Emojis.skip.str} **Erfolgreich den Song übersprungen**`, 52 | STOPPED: `${Emojis.stop.str} **Erfolgreich gestoppt und die Queue gelöscht.**`, 53 | NOT_CONTROLLING: (prefix) => `${Emojis.cross.str} **Du kontrollierst nicht den Bot mit: \`${prefix}control\`**`, 54 | FILTER: (state, filter) => `🎚 **Der Filter \`${filter}\` wurde erfolgreich ${state ? "hinzugefügt" : "entfernt"}.**`, 55 | INVALID_VOL: `${Emojis.cross.str} **Keine / Falsche Lautstärke!**\n> Füge eine Lautstärke zw. \`0\` und \`150\` % hinzu!`, 56 | VOLUME: (vol) => `${Emojis.check.str} **Lautstärke auf ${vol}% geändert**`, 57 | RESUMED: `▶️ **Erfolgreich den Song forgesetzt**`, 58 | NOT_PAUSED: `👎 **Song ist nicht pausiert**`, 59 | PAUSED: `⏸️ **Erfolgreich den Song pausiert**`, 60 | NOT_RESUMED: `👎 **Song ist schon pausiert**`, 61 | CLEAREDQUEUE: `🪣 **Erfolgreich die Queue gelöscht.**`, 62 | } -------------------------------------------------------------------------------- /languages/en.js: -------------------------------------------------------------------------------- 1 | const { Emojis } = require("../utils/constants/settingsData.js"); 2 | const { newLiner, parseChannelMention, parseUserMention } = require("../utils/botUtils.js"); 3 | module.exports = { 4 | MISSING_PERMS: `${Emojis.cross.str} You are missing Permissions for this Command`, 5 | 6 | PREFIXINFO: (prefix) => `${Emojis.check.str} **My Prefix here is \`${prefix}\`**`, 7 | 8 | LANGUAGE: (newlangstring) => `${Emojis.check.str} Changed the Language to **${newlangstring}**`, 9 | TIME_ENDED: `Time ran out`, 10 | 11 | 12 | JOIN_VC: `${Emojis.warn.str} **Please join a Channel first**`, 13 | ALREADY_CONNECTED: (channelId) => `${Emojis.denied.str} **I'm already connected in ${parseChannelMention(channelId)}**!`, 14 | MISSING_PERMS: (permString) => `${Emojis.denied.str} **I'm missing the Permission to "${permString}" in your Voice-Channel!**`, 15 | 16 | COULD_NOT_JOIN: (channelId) => `${Emojis.cross.str} **I could not connect to ${parseChannelMention(channelId)}.**`, 17 | HELP: (prefix) => { 18 | return newLiner( 19 | `${Emojis.check.str} **HELP | ABOUT ME!**`, 20 | `> **My Prefix is: \`${prefix}\`**`, 21 | `*I'm a voice-controlled-music-bot with high quality and many features!*`, 22 | `> To get started, simply type \`${prefix}control\` in the Chat`, 23 | `> It will tell you what to do, but **simply said**:`, 24 | `> Just say the commands you want to do!`, 25 | `**PLEASE MIND:** Before each command, you must add a Keyword! Valid ones are:`, 26 | `> \`bot\` / \`voice\` / \`speech\` / \`client\``, 27 | ) 28 | }, 29 | CONTROLLING: (possibleCommands) => { 30 | return newLiner( 31 | `${Emojis.check.str} **You are now controlling the Bot!**`, 32 | `__Possible Commands:__`, 33 | `> ${possibleCommands}`, 34 | `__How to execute a Command?__`, 35 | `> *Say it, by saying a Keyword and then the Command and Query! (in English)*`, 36 | `**Examples**: \`\`\`bot play shape of you\nbot skip\nbot stop\nbot nightcore\nbot play no diggity\nbot play believer\`\`\``, 37 | `**Tipps to be understood:**`, 38 | `> -) Don't make pauses`, 39 | `> -) No backgroundnoises`, 40 | `> -) Speach Normally rather fast and Clear`, 41 | `> -) Make sure that NOONE Speaks (not even a BOT)`, 42 | `\n> *You can still use me with commands as normal!*`, 43 | ); 44 | }, 45 | PING: (ping) => `🏓 My **API-RESPONSE-TIME** is **${ping}ms**`, 46 | NOWLISTENING: (usertag, time) => `👂 **Now listening to ${usertag}**\n> *Next input can be taken *`, 47 | QUEUE_EMPTY: (time) => `${Emojis.empty.str} **Queue got empty**\n> I will leave the Channel `, 48 | LEFT_VC: `👋 **Left the VoiceChannel**`, 49 | NOT_CONNECTED: `${Emojis.denied.str} **I'm not connected somewhere!**`, 50 | NOTHING_PLAYING: `${Emojis.denied.str} **Nothing playing right now**`, 51 | NOTHING_TO_SKIP: `${Emojis.denied.str} **Nothing to skip**`, 52 | SKIPPED: `${Emojis.skip.str} **Successfully skipped the Track**`, 53 | STOPPED: `${Emojis.stop.str} **Successfully stopped playing and cleared the Queue.**`, 54 | NOT_CONTROLLING: (prefix) => `${Emojis.cross.str} **You are not the one Controlling the Bot via \`${prefix}control\`**`, 55 | FILTER: (state, filter) => `🎚 **Successfully ${state ? "added" : "removed"} the \`${filter}\` Filter.**`, 56 | 57 | INVALID_VOL: `${Emojis.cross.str} **Invalid / No Volume Added!**\n> Add a percentage between \`0\` and \`150\`!`, 58 | VOLUME: (vol) => `${Emojis.check.str} **Changed the Volume to ${vol}%**`, 59 | 60 | RESUMED: `▶️ **Successfully resumed the Track**`, 61 | NOT_PAUSED: `👎 **Track is not paused**`, 62 | PAUSED: `⏸️ **Successfully paused the Track**`, 63 | NOT_RESUMED: `👎 **Track already paused**`, 64 | 65 | CLEAREDQUEUE: `🪣 **Successfully cleared the Queue.**`, 66 | } -------------------------------------------------------------------------------- /languages/fr.js: -------------------------------------------------------------------------------- 1 | const { Emojis } = require("../utils/constants/settingsData.js"); 2 | const { newLiner, parseChannelMention, parseUserMention } = require("../utils/botUtils.js"); 3 | module.exports = { 4 | MISSING_PERMS: `${Emojis.cross.str} Il vous manque des autorisations pour cette commande`, 5 | 6 | PREFIXINFO: (prefix) => `${Emojis.check.str} **Mon préfixe ici est \`${prefix}\`**`, 7 | 8 | LANGUAGE: (newlangstring) => `${Emojis.check.str} Changement de la langue en **${newlangstring}**`, 9 | TIME_ENDED: `Time ran out`, 10 | 11 | 12 | JOIN_VC: `${Emojis.warn.str} **Veuillez d'abord rejoindre une chaîne**`, 13 | ALREADY_CONNECTED: (channelId) => `${Emojis.denied.str} **Je suis déjà connecté à ${parseChannelMention(channelId)}**!`, 14 | MISSING_PERMS: (permString) => `${Emojis.denied.str} **Il me manque l'autorisation de "${permString}" dans votre canal vocal!**`, 15 | 16 | COULD_NOT_JOIN: (channelId) => `${Emojis.cross.str} **Je n'ai pas pu me connecter à ${parseChannelMention(channelId)}.**`, 17 | HELP: (prefix) => { 18 | return newLiner( 19 | `${Emojis.check.str} **AIDE | À PROPOS DE MOI!**`, 20 | `> **Mon préfixe est : \`${prefix}\`**`, 21 | `*Je suis un robot musical à commande vocale avec une haute qualité et de nombreuses fonctionnalités!*`, 22 | `> Pour commencer, tapez simplement \`${prefix}control\` dans le chat`, 23 | `> Il vous dira quoi faire, mais **a simplement dit**:`, 24 | `> Dites simplement les commandes que vous voulez faire!`, 25 | `**ATTENTION:** Avant chaque commande, vous devez ajouter un mot-clé! Les valides sont :`, 26 | `> \`bot\` / \`voice\` / \`speech\` / \`client\``, 27 | ) 28 | }, 29 | CONTROLLING: (possibleCommands) => { 30 | return newLiner( 31 | `${Emojis.check.str} **Vous contrôlez maintenant le Bot!**`, 32 | `__Commandes possibles:__`, 33 | `> ${possibleCommands}`, 34 | `__Comment exécuter une commande?__`, 35 | `> *Dites-le en prononçant un mot-clé, puis la commande et la requête! (En anglais)*`, 36 | `**Exemple**: \`\`\`bot play shape of you\nbot skip\nbot stop\nbot nightcore\nbot play no diggity\nbot play believer\`\`\``, 37 | `**Conseils pour être compris:**`, 38 | `> -) Ne faites pas de pause`, 39 | `> -) Pas de bruit de fond`, 40 | `> -) Discours Normalement plutôt rapide et clair`, 41 | `> -) Assurez-vous que PERSONNE ne parle (pas même un BOT)`, 42 | `\n> *Vous pouvez toujours m'utiliser avec des commandes comme d'habitude!*`, 43 | ); 44 | }, 45 | PING: (ping) => `🏓 Mon **API-RESPONSE-TIME** est de **${ping}ms**`, 46 | NOWLISTENING: (usertag, time) => `👂 **Écoutez maintenant ${usertag}**\n> *La prochaine entrée peut être prise *`, 47 | QUEUE_EMPTY: (time) => `${Emojis.empty.str} **La file d'attente est vide**\n> Je vais quitter la chaîne `, 48 | LEFT_VC: `👋 **A quitté le VoiceChannel**`, 49 | NOT_CONNECTED: `${Emojis.denied.str} **Je ne suis pas connecté quelque part!**`, 50 | NOTHING_PLAYING: `${Emojis.denied.str} **Rien en cours de lecture pour le moment**`, 51 | NOTHING_TO_SKIP: `${Emojis.denied.str} **Rien à sauter**`, 52 | SKIPPED: `${Emojis.skip.str} **A sauté la piste avec succès**`, 53 | STOPPED: `${Emojis.stop.str} **Arrêt de la lecture réussi et effacement de la file d'attente.**`, 54 | NOT_CONTROLLING: (prefix) => `${Emojis.cross.str} **Vous n'êtes pas celui qui contrôle le bot via \`${prefix}control\`**`, 55 | FILTER: (state, filter) => `🎚 **${state ? "ajouté" : "supprimé"} le \`${filtre}\` Filtre.**`, 56 | 57 | INVALID_VOL: `${Emojis.cross.str} **Invalide / Aucun volume ajouté!**\n> Ajoutez un pourcentage entre \`0\` et \`150\` !`, 58 | VOLUME: (vol) => `${Emojis.check.str} **Modification du volume en${vol}%**`, 59 | 60 | RESUMED: `▶️ **Reprise réussie de la piste**`, 61 | NOT_PAUSED: `👎 **La piste n'est pas en pause**`, 62 | PAUSED: `⏸️ **Mise en pause réussie de la piste**`, 63 | NOT_RESUMED: `👎 **Piste déjà en pause**`, 64 | 65 | CLEAREDQUEUE: `🪣 **La file d'attente a été effacée avec succès.**`, 66 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@discordjs/opus": "^0.8.0", 4 | "@discordjs/voice": "^0.11.0", 5 | "discord-ytdl-core": "^5.0.4", 6 | "discord.js": "^14.0.3", 7 | "dotenv": "^16.0.1", 8 | "enmap": "^5.9.0", 9 | "ffmpeg": "^0.0.4", 10 | "ffmpeg-static": "^5.0.2", 11 | "libsodium-wrappers": "^0.7.9", 12 | "node-crc": "^1.3.2", 13 | "node-fetch": "^2.6.7", 14 | "prism-media": "^2.0.0-alpha.0", 15 | "youtube-sr": "^4.2.0", 16 | "ytdl-core": "^4.11.0" 17 | }, 18 | "name": "voice-controlled-discord-bot", 19 | "version": "1.0.0", 20 | "description": "A Voice Controlled Discord (Music) Bot", 21 | "main": "index.js", 22 | "scripts": { 23 | "test": "echo \"Error: no test specified\" && exit 1", 24 | "start": "node index.js" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/Tomato6966/voice-controlled-discord-bot.git" 29 | }, 30 | "keywords": [ 31 | "discord", 32 | "bot", 33 | "music", 34 | "voice", 35 | "controlled" 36 | ], 37 | "author": "Tomato#6966", 38 | "license": "MIT", 39 | "bugs": { 40 | "url": "https://github.com/Tomato6966/voice-controlled-discord-bot/issues" 41 | }, 42 | "homepage": "https://github.com/Tomato6966/voice-controlled-discord-bot#readme" 43 | } 44 | -------------------------------------------------------------------------------- /playerFunctions.js: -------------------------------------------------------------------------------- 1 | // Import Packages 2 | const dcYtdl = require("discord-ytdl-core"); 3 | const { Client, VoiceChannel, ChannelType, User } = require("discord.js") 4 | const { entersState, joinVoiceChannel, getVoiceConnection, VoiceConnectionStatus, createAudioResource, createAudioPlayer, NoSubscriberBehavior, VoiceConnection, AudioPlayer, AudioResource } = require("@discordjs/voice"); 5 | // require settingsData 6 | const { Color, settings } = require("./constants/settingsData"); 7 | const { translate } = require("./language"); 8 | const { msUnix, delay } = require("./botUtils"); 9 | const { EmbedBuilder } = require("discord.js"); 10 | 11 | /** 12 | * An array of valid voice channel types the bot can connect to 13 | */ 14 | const validVCTypes = [ChannelType.GuildVoice, ChannelType.GuildStageVoice]; 15 | 16 | /** 17 | * Joins a Voice-Channel 18 | * @param {Client} client 19 | * @param {VoiceChannel} channel 20 | * @returns {Promise} Voiceconnection 21 | */ 22 | const joinVoiceChannelUtil = async (client, channel) => { 23 | return new Promise(async (res, rej) => { 24 | if(!validVCTypes.includes(channel.type)) return rej("Channel is not a Voice / Stage Channel"); 25 | // create a new connection 26 | const newConnection = joinVoiceChannel({ 27 | channelId: channel.id, 28 | guildId: channel.guild.id, 29 | adapterCreator: channel.guild.voiceAdapterCreator, 30 | selfDeaf: false, 31 | }); 32 | // set the voicestate as rady 33 | await entersState(newConnection, VoiceConnectionStatus.Ready, 20e3); 34 | // voiceconnection handlings 35 | newConnection.on(VoiceConnectionStatus.Disconnected, async (oldState, newState) => { 36 | try { 37 | await Promise.race([ 38 | entersState(newConnection, VoiceConnectionStatus.Signalling, 5_000), 39 | entersState(newConnection, VoiceConnectionStatus.Connecting, 5_000), 40 | ]); 41 | // if no error, then it was a swich 42 | } catch (error) { 43 | newConnection.destroy(); 44 | } 45 | }); 46 | // delete the queue on channel leave 47 | newConnection.on(VoiceConnectionStatus.Destroyed, () => client.queues.delete(channel.guild.id)) 48 | 49 | // making the client a speaker, if in a stage channel 50 | if(channel.type === ChannelType.GuildStageVoice) await channel.guild.members?.me?.voice?.setSuppressed(true) 51 | 52 | return res(newConnection); 53 | }) 54 | }; 55 | 56 | /** 57 | * Format < 100 Numbers 58 | * @param {number} t 59 | * @returns 60 | */ 61 | const m2 = (t) => { 62 | return parseInt(t) < 10 ? `0${t}` : `${t}` 63 | } 64 | 65 | /** 66 | * Format < 1000 Numbers 67 | * @param {number} t 68 | * @returns 69 | */ 70 | const m3 = (t) => { 71 | return parseInt(t) < 100 ? `0${m2(t)}` : `${t}` 72 | } 73 | 74 | /** 75 | * Formats a duration from ms to human-readable 76 | * @param {number} ms 77 | * @returns Formatted Duration in min:sec 78 | */ 79 | const formatDuration = (ms) => { 80 | const sec = parseInt(ms / 1000 % 60); 81 | const min = parseInt(ms / (1000*60) % 60); 82 | const hrs = parseInt(ms / (1000*60*60) % 24); 83 | if(sec >= 60) sec = 0; 84 | if(min >= 60) min = 0; 85 | if(hrs > 1) return `${m2(hrs)}:${m2(min)}:${m2(sec)}` 86 | return `${m2(min)}:${m2(sec)}` 87 | } 88 | 89 | /** 90 | * Format a link 91 | * @param {string} ID 92 | * @param {string} prefix "www"|"music" 93 | * @returns YoutubeWatch link based on id 94 | */ 95 | const getYTLink = (ID, prefix="music") => { 96 | return `https://${prefix}.youtube.com/watch?v=${ID}`; 97 | } 98 | 99 | /** 100 | * Leaves the voice channel of the guild 101 | * @param {VoiceChannel} channel 102 | * @returns 103 | */ 104 | const leaveVoiceChannel = async (channel) => { 105 | return new Promise(async (res, rej) => { 106 | const oldConnection = getVoiceConnection(channel.guild.id); 107 | if (oldConnection) { 108 | if (oldConnection.joinConfig.channelId != channel.id) return rej("We aren't in the same channel!") 109 | try { 110 | oldConnection.destroy(); 111 | await delay(250); 112 | return res(true) 113 | } catch (e) { 114 | return rej(e) 115 | } 116 | } else { 117 | return rej("I'm not connected somwhere.") 118 | } 119 | }) 120 | 121 | } 122 | 123 | /** 124 | * Creates a Audio Resource Stream, with FFMPEG Filters (if the queue has them) 125 | * @param {object} queue 126 | * @param {string} songInfoId 127 | * @param {?number} seekTime 128 | * @returns {AudioResource} 129 | */ 130 | const getResource = (queue, songInfoId, seekTime = 0) => { 131 | let Qargs = ""; 132 | let effects = queue?.effects || { 133 | bassboost: 4, 134 | speed: 1, 135 | } 136 | if(effects.normalizer) Qargs += `,dynaudnorm=f=200`; 137 | if(effects.bassboost) Qargs += `,bass=g=${effects.bassboost}` 138 | if(effects.speed) Qargs += `,atempo=${effects.speed}` 139 | if(effects["3d"]) Qargs += `,apulsator=hz=0.03` 140 | if(effects.subboost) Qargs += `,asubboost` 141 | if(effects.mcompand) Qargs += `,mcompand` 142 | if(effects.haas) Qargs += `,haas` 143 | if(effects.gate) Qargs += `,agate` 144 | if(effects.karaoke) Qargs += `,stereotools=mlev=0.03` 145 | if(effects.flanger) Qargs += `,flanger` 146 | if(effects.pulsator) Qargs += `,apulsator=hz=1` 147 | if(effects.surrounding) Qargs += `,surround` 148 | if(effects.vaporwave) Qargs += `,aresample=48000,asetrate=48000*0.8` 149 | if(effects.nightcore) Qargs += `,aresample=48000,asetrate=48000*1.5` 150 | if(effects.phaser) Qargs += `,aphaser=in_gain=0.4` 151 | if(effects.tremolo) Qargs += `,tremolo` 152 | if(effects.vibrato) Qargs += `,vibrato=f=6.5` 153 | if(effects.reverse) Qargs += `,areverse` 154 | if(effects.treble) Qargs += `,treble=g=5` 155 | if(Qargs.startsWith(",")) Qargs = Qargs.substring(1) 156 | const requestOpts = { 157 | filter: "audioonly", 158 | fmt: "mp3", 159 | highWaterMark: 1 << 62, 160 | liveBuffer: 1 << 62, 161 | dlChunkSize: 0, 162 | seek: Math.floor(seekTime / 1000), 163 | bitrate: queue?.bitrate || 128, 164 | quality: "lowestaudio", 165 | encoderArgs: Qargs ? ["-af", Qargs ] : ['-af', 'bass=g=6,dynaudnorm=f=200'] // queue.filters 166 | }; 167 | if(process.env.YOUTUBE_LOGIN_COOKIE && process.env.YOUTUBE_LOGIN_COOKIE.length > 10) { 168 | requestOpts.requestOptions = { 169 | headers: { 170 | cookie: process.env.YOUTUBE_LOGIN_COOKIE, 171 | } 172 | } 173 | } 174 | const resource = createAudioResource(dcYtdl(getYTLink(songInfoId), requestOpts), { 175 | inlineVolume: true 176 | }); 177 | const volume = queue && queue.volume && queue.volume <= 150 && queue.volume >= 1 ? (queue.volume / 100) : 0.15; // queue.volume / 100; 178 | resource.volume.setVolume(volume); 179 | resource.playbackDuration = seekTime; 180 | return resource; 181 | } 182 | 183 | /** 184 | * Plays a song in a voice channel if possible, else it's adding it 185 | * @param {Client} client 186 | * @param {VoiceChannel} channel 187 | * @param {object} songInfo 188 | * @returns 189 | */ 190 | const playSong = async (client, channel, songInfo) => { 191 | return new Promise(async (res, rej) => { 192 | const oldConnection = getVoiceConnection(channel.guild.id); 193 | if (oldConnection) { 194 | if (oldConnection.joinConfig.channelId != channel.id) return rej("We aren't in the same channel!") 195 | try { 196 | const curQueue = client.queues.get(channel.guild.id); 197 | 198 | const player = createAudioPlayer({ 199 | behaviors: { 200 | noSubscriber: NoSubscriberBehavior.Stop, 201 | }, 202 | }); 203 | oldConnection.subscribe(player); 204 | 205 | const resource = getResource(curQueue, songInfo.id); 206 | // play the resource 207 | player.play(resource); 208 | 209 | // When the player plays a new song 210 | player.on("playing", (player) => { 211 | const queue = client.queues.get(channel.guild.id); 212 | // if filters changed, don't send something 213 | if(queue && queue.filtersChanged) { 214 | queue.filtersChanged = false; 215 | } else { 216 | sendQueueUpdate(client, channel.guild.id); 217 | } 218 | 219 | }); 220 | // When the player goes on idle 221 | player.on("idle", () => { 222 | const queue = client.queues.get(channel.guild.id); 223 | handleQueue(client, player, queue) 224 | }) 225 | // when an error happens 226 | player.on('error', error => { 227 | console.error(error); 228 | const queue = client.queues.get(channel.guild.id); 229 | handleQueue(client, player, queue) 230 | }); 231 | 232 | return res(songInfo); 233 | } catch (e) { 234 | return rej(e) 235 | } 236 | } else { 237 | return rej("I'm not connected somwhere.") 238 | } 239 | }) 240 | 241 | } 242 | 243 | /** 244 | * handles the queue (skipping, stopping, looping) 245 | * @param {Client} client 246 | * @param {AudioPlayer} player 247 | * @param {object} queue 248 | * @returns {void} 249 | */ 250 | async function handleQueue(client, player, queue) { 251 | if(queue && !queue.filtersChanged) { 252 | try { 253 | player.stop() 254 | if(queue && queue.tracks && queue.tracks.length > 1) { 255 | queue.previous = queue.tracks[0]; 256 | if(queue.trackloop && !queue.skipped) { 257 | if(queue.paused) queue.paused = false; 258 | if(queue.tracks[0]?.resource) player.play(createAudioResource(queue.tracks[0].resource)) 259 | else player.play(getResource(queue, queue.tracks[0].id)) 260 | } else if(queue.queueloop && !queue.skipped) { 261 | const skipped = queue.tracks.shift(); 262 | queue.tracks.push(skipped); 263 | if(queue.paused) queue.paused = false; 264 | if(queue.tracks[0]?.resource) player.play(createAudioResource(queue.tracks[0].resource)) 265 | else player.play(getResource(queue, queue.tracks[0].id)); 266 | } else { 267 | if(queue.skipped) queue.skipped = false; 268 | if(queue.paused) queue.paused = false; 269 | queue.tracks.shift(); 270 | if(queue.tracks[0]?.resource) player.play(createAudioResource(queue.tracks[0].resource)); 271 | else player.play(getResource(queue, queue.tracks[0].id)); 272 | } 273 | } else if(queue && queue.tracks && queue.tracks.length <= 1) { // removes the nowplaying, if no upcoming and ends it 274 | queue.previous = queue.tracks[0]; 275 | if(queue.trackloop || queue.queueloop && !queue.skipped) { 276 | if(queue.tracks[0]?.resource) player.play(createAudioResource(queue.tracks[0].resource)) 277 | else player.play(getResource(queue, queue.tracks[0].id)); 278 | } else { 279 | if(queue.skipped) queue.skipped = false; 280 | if(queue.stopped) { 281 | // if there is a voice-response resource, play it 282 | if(queue.tracks[0]?.resource) { 283 | const track = queue.tracks.shift(); 284 | if(track?.resource) return player.play(createAudioResource(track.resource)); 285 | } 286 | 287 | // get the bot's voice Connection 288 | const meChannel = client.guilds.cache.get(queue.guildId)?.members?.me?.voice?.channel; 289 | if(meChannel) leaveVoiceChannel(meChannel); 290 | else { // else fetch the voicechannel and leave it 291 | const vc = await client.channels.fetch(queue.voiceChannel).catch(() => null); 292 | if(vc) leaveVoiceChannel(vc); 293 | } 294 | 295 | // send a status update if possible 296 | const textChannel = await client.channels.fetch(queue.textChannel).catch(() => null); 297 | if(textChannel) textChannel.send({ 298 | content: translate(client, textChannel.guild.id, "STOPPED") 299 | }).catch(() => null); 300 | return; 301 | } 302 | queue.tracks = []; 303 | // Queue Empty, do this 304 | const textChannel = await client.channels.fetch(queue.textChannel).catch(() => null); 305 | if(textChannel) { 306 | textChannel.send({ 307 | content: translate(client, textChannel.guildId, "QUEUE_EMPTY", msUnix(Date.now() + settings.leaveEmptyVC)) 308 | }).catch(console.warn) 309 | } 310 | setTimeout(async () => { 311 | const nqueue = client.queues.get(queue.guildId); 312 | if(!nqueue?.tracks?.length) { 313 | // get the bot's voice Connection 314 | const meChannel = client.guilds.cache.get(queue.guildId)?.members?.me?.voice?.channel; 315 | if(meChannel) leaveVoiceChannel(meChannel); 316 | else { // else fetch the voicechannel and leave it 317 | const vc = await client.channels.fetch(queue.voiceChannel).catch(() => null); 318 | if(vc) leaveVoiceChannel(vc); 319 | } 320 | } else console.log(nqueue); 321 | return; 322 | }, settings.leaveEmptyVC) 323 | } 324 | } else { 325 | // get the bot's voice Connection 326 | const meChannel = client.guilds.cache.get(queue.guildId)?.members?.me?.voice?.channel; 327 | if(meChannel) leaveVoiceChannel(meChannel); 328 | else { // else fetch the voicechannel and leave it 329 | const vc = await client.channels.fetch(queue.voiceChannel).catch(() => null); 330 | if(vc) leaveVoiceChannel(vc); 331 | } 332 | // send a queue textchannel update 333 | const textChannel = await client.channels.fetch(queue.textChannel).catch(() => null); 334 | if(textChannel) textChannel.send({ content: translate(client, textChannel.guildId, "LEFT_VC") }).catch(() => null); 335 | return; 336 | } 337 | } catch (e) { console.error(e) } 338 | } 339 | return; 340 | } 341 | 342 | /** 343 | * Sends a Queue Update to the queue channel 344 | * @param {Client} client 345 | * @param {string} guildId 346 | * @returns {void} 347 | */ 348 | const sendQueueUpdate = async (client, guildId) => { 349 | const queue = client.queues.get(guildId); 350 | if(!queue || !queue.tracks || queue.tracks.length == 0 || !queue.textChannel) return 351 | 352 | const textChannel = await client.channels.fetch(queue.textChannel).catch(() => null); 353 | if(!textChannel) return 354 | 355 | const song = queue.tracks[0]; 356 | const embed = new EmbedBuilder().setColor(Color.Main) 357 | .setURL(getYTLink(song.id)) 358 | .setTitle(`▶️ Now playing __${song.title}__`) 359 | .setFields([ 360 | { 361 | name: `**Duration:**`, inline: true, 362 | value: `> \`${song.durationFormatted}\``, 363 | }, 364 | { 365 | name: `**Requester:**`, inline: true, 366 | value: `> ${song.requester} \`${song.requester.tag}\`` 367 | }, 368 | { 369 | name: `**Artist:**`, inline: true, 370 | value: `> ${song.channel?.name ? `[${song.channel.name}](${song.channel.url})` : `\`Unknown\``}` 371 | } 372 | ]); 373 | if(song?.thumbnail?.url) embed.setThumbnail(`${song?.thumbnail?.url}`); 374 | 375 | textChannel.send({ 376 | embeds: [ embed ] 377 | }).catch(console.warn) 378 | return; 379 | } 380 | 381 | /** 382 | * Merge song and requester together 383 | * @param {*} song 384 | * @param {User} requester 385 | * @returns Object of song and requester 386 | */ 387 | const createSong = (song, requester) => { 388 | return { ...song, requester } 389 | } 390 | 391 | /** 392 | * Formats the song index to a readable string 393 | * @param {number} length 394 | * @returns String 395 | */ 396 | const queuePos = (length) => { 397 | const str = { 398 | 1: "st", 399 | 2: "nd", 400 | 3: "rd", 401 | } 402 | return `${length}${str[length % 10] ? str[length % 10] : "th"}` 403 | } 404 | 405 | /** 406 | * Creates a Queue Object for the voicechannel, textchannel and guildId 407 | * @param {*} song 408 | * @param {User} user 409 | * @param {string} channelId 410 | * @param {VoiceChannel} voiceChannel 411 | * @param {number} bitrate 412 | * @returns a Queue-Object 413 | */ 414 | const createQueue = (song, user, channelId, voiceChannel, bitrate = 128) => { 415 | return { 416 | guildId: voiceChannel.guildId, 417 | voiceChannel, 418 | textChannel: channelId, 419 | paused: false, 420 | skipped: false, 421 | effects: { 422 | bassboost: 0, 423 | subboost: false, 424 | mcompand: false, 425 | haas: false, 426 | gate: false, 427 | karaoke: false, 428 | flanger: false, 429 | pulsator: false, 430 | surrounding: false, 431 | "3d": false, 432 | vaporwave: false, 433 | nightcore: false, 434 | phaser: false, 435 | normalizer: false, 436 | speed: 1, 437 | tremolo: false, 438 | vibrato: false, 439 | reverse: false, 440 | treble: false, 441 | }, 442 | trackloop: false, 443 | queueloop: false, 444 | filtersChanged: false, 445 | volume: 15, // queue volume, between 1 and 100 446 | tracks: [ createSong(song, user) ], 447 | previous: undefined, 448 | creator: user, 449 | bitrate: bitrate 450 | } 451 | } 452 | module.exports = { 453 | validVCTypes, 454 | joinVoiceChannelUtil, 455 | m2, m3, formatDuration, getYTLink, leaveVoiceChannel, getResource, playSong, 456 | sendQueueUpdate, createSong, queuePos, createQueue 457 | } -------------------------------------------------------------------------------- /temp/INFO.txt: -------------------------------------------------------------------------------- 1 | PLACEHOLDER -------------------------------------------------------------------------------- /utils/botUtils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Formats mujltiple new-lines strings into 1 string (for better readability) 3 | * @param {...string} lines Each text-line 4 | * @returns string 5 | */ 6 | const newLiner = (...lines) => lines.join("\n"); 7 | 8 | /** 9 | * Formats and returns the valid Channel-mention, if it's undefined, then unknown Channel ofc. 10 | * @param {string|any} input ChannelId 11 | * @returns "mention"-String 12 | */ 13 | const parseChannelMention = (input) => typeof input === "string" && input.length > 5 ? `<#${input}>` : `#Unknown-Channel` 14 | 15 | /** 16 | * Formats and returns the valid User-mention, if it's undefined, then unknown User ofc. 17 | * @param {string|any} input User-Id 18 | * @returns "mention"-String 19 | */ 20 | const parseUserMention = (input) => typeof input === "string" && input.length > 5 ? `<@!${input}>` : `@Unknown-User` 21 | 22 | /** 23 | * Formats the message content and matches 24 | * @param {string} str String to replace the REGEX 25 | * @returns string 26 | */ 27 | const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, `\\$&`); 28 | 29 | /** 30 | * Blocks the process 31 | * @param {number} ms 32 | * @returns {Promise<*>} 33 | */ 34 | const delay = (ms) => new Promise(r => setTimeout(() => r(2)), ms); 35 | 36 | /** 37 | * Formats an MS Timestamp to an UNIX Timestamp 38 | * @param {number} number 39 | * @returns Number:UnixTimestamp 40 | */ 41 | const msUnix = (number) => Math.floor(number / 1000); 42 | 43 | /** 44 | * Formats a Username for letters and numbers. 45 | * @param {string} username 46 | * @returns Formatted Username, so it's valid for a filename 47 | */ 48 | const transformUsername = (username) => username.replace(/[^a-z0-9]/gi, '_').toLowerCase(); 49 | 50 | module.exports = { 51 | delay, 52 | newLiner, parseChannelMention, parseUserMention, escapeRegex, msUnix, transformUsername 53 | } -------------------------------------------------------------------------------- /utils/constants/Color.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Main: "#fb32cd", 3 | Red: "#ff0000" 4 | } -------------------------------------------------------------------------------- /utils/constants/Emojis.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | check: getEmoji("✅"), 3 | cross: getEmoji("❌"), 4 | denied: getEmoji("🚫"), 5 | warn: getEmoji("❗"), 6 | timer: getEmoji("🕛"), 7 | stop: getEmoji("🛑"), 8 | skip: getEmoji("⏭️"), 9 | empty: getEmoji("🤖"), 10 | } 11 | function getEmoji(str) { 12 | str = str.trim(); 13 | if(str.includes("<")) { 14 | const splitted = str.replace(">", "").replace("<", "").split(":").reverse().filter(Boolean); 15 | const id = `${splitted[0]}`; 16 | const name = `${splitted[1]}`; 17 | const animated = splitted[2] && splitted[2] == "a" ? true : false 18 | return { id, name, animated, str: `${str}` } 19 | } 20 | return { id: undefined, str: `${str}`, name: `${str}`, animated: false } 21 | } -------------------------------------------------------------------------------- /utils/constants/Status.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | state: "online", 3 | activities: [ 4 | { 5 | name: `!control --> speak`, 6 | type: "PLAYING", 7 | }, 8 | { 9 | name: `by Tomato#6966`, 10 | type: "PLAYING", 11 | }, 12 | ], 13 | editInterval: 30_000, 14 | }; -------------------------------------------------------------------------------- /utils/constants/clientData.js: -------------------------------------------------------------------------------- 1 | const { GatewayIntentBits, Partials } = require("discord.js"); 2 | const { Status } = require("./settingsData.js"); 3 | const clientData = { 4 | intents: [ 5 | GatewayIntentBits.Guilds, 6 | GatewayIntentBits.GuildVoiceStates, 7 | GatewayIntentBits.MessageContent, 8 | GatewayIntentBits.GuildMessages 9 | ], 10 | allowedMentions: { 11 | parse: ["roles", "users", /* "everyone" */], 12 | repliedUser: false, //set true if you want to ping the bot on reply messages 13 | }, 14 | failIfNotExists: false, 15 | partials: [Partials.Message, Partials.Channel, Partials.GuildMember, Partials.ThreadMember], 16 | presence: { activities: [Status.activities[0]], status: Status.state } 17 | }; 18 | 19 | module.exports = { clientData }; -------------------------------------------------------------------------------- /utils/constants/settingsData.js: -------------------------------------------------------------------------------- 1 | const Color = require("./Color.js") 2 | const Status = require("./Status.js"); 3 | const Emojis = require("./Emojis.js"); 4 | 5 | module.exports = { 6 | Status, 7 | Color, 8 | settings: { 9 | listeningCooldown: 3_500, 10 | prefixCommands: [ "control", "language", "prefix" ], 11 | leaveEmptyVC: 60_000, 12 | validVoiceKeyWords: ["bot","client","voice", `speech`, `beach`, `each`, `peach`] 13 | }, 14 | Emojis 15 | } 16 | -------------------------------------------------------------------------------- /utils/language.js: -------------------------------------------------------------------------------- 1 | const english = require("../languages/en.js"); 2 | const german = require("../languages/de.js"); 3 | const chinese = require("../languages/cn.js"); 4 | const french = require("../languages/fr.js"); 5 | 6 | const languages = { 7 | "en": english, 8 | "de": german, 9 | "cn": chinese, 10 | "fr": french, 11 | } 12 | const languageStrings = { 13 | "en" : {emoji: "🇬🇧", text: "English"}, 14 | "de" : {emoji: "🇩🇪", text: "German"}, 15 | "cn" : {emoji: "🇨🇳", text: "Chinese"}, 16 | "fr" : {emoji: "🇫🇷", text: "French"} 17 | } 18 | function getLanguage(client, guildId) { 19 | try { 20 | const defaultLang = process.env.DEFAULT_LANGUAGE ?? "en"; 21 | client.db.ensure(guildId, { language: defaultLang }); 22 | const lang = client.db.get(guildId, "language") ?? "en"; 23 | return Object.keys(languageStrings).includes(lang.trim()) ? lang.trim() : "en"; 24 | } catch { 25 | return "en" 26 | } 27 | } 28 | function translate(client, guildId, key, ...params) { 29 | if(typeof guildId !== "string") { 30 | try { 31 | if(guildId?.guild) guildId = guildId.guild.id 32 | else guildId = guildId.id; 33 | } catch{ } 34 | } 35 | const language = getLanguage(client, guildId); 36 | let value = languages[language][key]; 37 | // Was not able to be translated 38 | if (!value && language !== "en") value = languages.en[key] || key; 39 | if (Array.isArray(value)) return value.join("\n"); 40 | if (typeof value === "function") return value(...(params || [])); 41 | return value; 42 | } 43 | module.exports = { 44 | languages, languageStrings, translate, getLanguage 45 | } -------------------------------------------------------------------------------- /utils/playerFunctions.js: -------------------------------------------------------------------------------- 1 | // Import Packages 2 | const dcYtdl = require("discord-ytdl-core"); 3 | const { Client, VoiceChannel, ChannelType, User } = require("discord.js") 4 | const { entersState, joinVoiceChannel, getVoiceConnection, VoiceConnectionStatus, createAudioResource, createAudioPlayer, NoSubscriberBehavior, VoiceConnection, AudioPlayer, AudioResource } = require("@discordjs/voice"); 5 | // require settingsData 6 | const { Color, settings } = require("./constants/settingsData"); 7 | const { translate } = require("./language"); 8 | const { msUnix, delay } = require("./botUtils"); 9 | const { EmbedBuilder } = require("discord.js"); 10 | 11 | /** 12 | * An array of valid voice channel types the bot can connect to 13 | */ 14 | const validVCTypes = [ChannelType.GuildVoice, ChannelType.GuildStageVoice]; 15 | 16 | /** 17 | * Joins a Voice-Channel 18 | * @param {Client} client 19 | * @param {VoiceChannel} channel 20 | * @returns {Promise} Voiceconnection 21 | */ 22 | const joinVoiceChannelUtil = async (client, channel) => { 23 | return new Promise(async (res, rej) => { 24 | if(!validVCTypes.includes(channel.type)) return rej("Channel is not a Voice / Stage Channel"); 25 | // create a new connection 26 | const newConnection = joinVoiceChannel({ 27 | channelId: channel.id, 28 | guildId: channel.guild.id, 29 | adapterCreator: channel.guild.voiceAdapterCreator, 30 | selfDeaf: false, 31 | }); 32 | // set the voicestate as rady 33 | await entersState(newConnection, VoiceConnectionStatus.Ready, 20e3); 34 | // voiceconnection handlings 35 | newConnection.on(VoiceConnectionStatus.Disconnected, async (oldState, newState) => { 36 | try { 37 | await Promise.race([ 38 | entersState(newConnection, VoiceConnectionStatus.Signalling, 5_000), 39 | entersState(newConnection, VoiceConnectionStatus.Connecting, 5_000), 40 | ]); 41 | // if no error, then it was a swich 42 | } catch (error) { 43 | newConnection.destroy(); 44 | } 45 | }); 46 | // delete the queue on channel leave 47 | newConnection.on(VoiceConnectionStatus.Destroyed, () => client.queues.delete(channel.guild.id)) 48 | 49 | // making the client a speaker, if in a stage channel 50 | if(channel.type === ChannelType.GuildStageVoice) await channel.guild.members?.me?.voice?.setSuppressed(true) 51 | 52 | return res(newConnection); 53 | }) 54 | }; 55 | 56 | /** 57 | * Format < 100 Numbers 58 | * @param {number} t 59 | * @returns 60 | */ 61 | const m2 = (t) => { 62 | return parseInt(t) < 10 ? `0${t}` : `${t}` 63 | } 64 | 65 | /** 66 | * Format < 1000 Numbers 67 | * @param {number} t 68 | * @returns 69 | */ 70 | const m3 = (t) => { 71 | return parseInt(t) < 100 ? `0${m2(t)}` : `${t}` 72 | } 73 | 74 | /** 75 | * Formats a duration from ms to human-readable 76 | * @param {number} ms 77 | * @returns Formatted Duration in min:sec 78 | */ 79 | const formatDuration = (ms) => { 80 | const sec = parseInt(ms / 1000 % 60); 81 | const min = parseInt(ms / (1000*60) % 60); 82 | const hrs = parseInt(ms / (1000*60*60) % 24); 83 | if(sec >= 60) sec = 0; 84 | if(min >= 60) min = 0; 85 | if(hrs > 1) return `${m2(hrs)}:${m2(min)}:${m2(sec)}` 86 | return `${m2(min)}:${m2(sec)}` 87 | } 88 | 89 | /** 90 | * Format a link 91 | * @param {string} ID 92 | * @param {string} prefix "www"|"music" 93 | * @returns YoutubeWatch link based on id 94 | */ 95 | const getYTLink = (ID, prefix="www") => { 96 | return `https://${prefix}.youtube.com/watch?v=${ID}`; 97 | } 98 | 99 | /** 100 | * Leaves the voice channel of the guild 101 | * @param {VoiceChannel} channel 102 | * @returns 103 | */ 104 | const leaveVoiceChannel = async (channel) => { 105 | return new Promise(async (res, rej) => { 106 | const oldConnection = getVoiceConnection(channel.guild.id); 107 | if (oldConnection) { 108 | if (oldConnection.joinConfig.channelId != channel.id) return rej("We aren't in the same channel!") 109 | try { 110 | oldConnection.destroy(); 111 | await delay(250); 112 | return res(true) 113 | } catch (e) { 114 | return rej(e) 115 | } 116 | } else { 117 | return rej("I'm not connected somwhere.") 118 | } 119 | }) 120 | 121 | } 122 | 123 | /** 124 | * Creates a Audio Resource Stream, with FFMPEG Filters (if the queue has them) 125 | * @param {object} queue 126 | * @param {string} songInfoId 127 | * @param {?number} seekTime 128 | * @returns {AudioResource} 129 | */ 130 | const getResource = (queue, songInfoId, seekTime = 0) => { 131 | let Qargs = ""; 132 | let effects = queue?.effects || { 133 | bassboost: 4, 134 | speed: 1, 135 | } 136 | if(effects.normalizer) Qargs += `,dynaudnorm=f=200`; 137 | if(effects.bassboost) Qargs += `,bass=g=${effects.bassboost}` 138 | if(effects.speed) Qargs += `,atempo=${effects.speed}` 139 | if(effects["3d"]) Qargs += `,apulsator=hz=0.03` 140 | if(effects.subboost) Qargs += `,asubboost` 141 | if(effects.mcompand) Qargs += `,mcompand` 142 | if(effects.haas) Qargs += `,haas` 143 | if(effects.gate) Qargs += `,agate` 144 | if(effects.karaoke) Qargs += `,stereotools=mlev=0.03` 145 | if(effects.flanger) Qargs += `,flanger` 146 | if(effects.pulsator) Qargs += `,apulsator=hz=1` 147 | if(effects.surrounding) Qargs += `,surround` 148 | if(effects.vaporwave) Qargs += `,aresample=48000,asetrate=48000*0.8` 149 | if(effects.nightcore) Qargs += `,aresample=48000,asetrate=48000*1.5` 150 | if(effects.phaser) Qargs += `,aphaser=in_gain=0.4` 151 | if(effects.tremolo) Qargs += `,tremolo` 152 | if(effects.vibrato) Qargs += `,vibrato=f=6.5` 153 | if(effects.reverse) Qargs += `,areverse` 154 | if(effects.treble) Qargs += `,treble=g=5` 155 | if(Qargs.startsWith(",")) Qargs = Qargs.substring(1) 156 | const requestOpts = { 157 | filter: "audioonly", 158 | fmt: "mp3", 159 | highWaterMark: 1 << 62, 160 | liveBuffer: 1 << 62, 161 | dlChunkSize: 0, 162 | seek: Math.floor(seekTime / 1000), 163 | bitrate: queue?.bitrate || 128, 164 | quality: "lowestaudio", 165 | encoderArgs: Qargs ? ["-af", Qargs ] : ['-af', 'bass=g=6,dynaudnorm=f=200'] // queue.filters 166 | }; 167 | if(process.env.YOUTUBE_LOGIN_COOKIE && process.env.YOUTUBE_LOGIN_COOKIE.length > 10) { 168 | requestOpts.requestOptions = { 169 | headers: { 170 | cookie: process.env.YOUTUBE_LOGIN_COOKIE, 171 | } 172 | } 173 | } 174 | const resource = createAudioResource(dcYtdl(getYTLink(songInfoId), requestOpts), { 175 | inlineVolume: true 176 | }); 177 | const volume = queue && queue.volume && queue.volume <= 150 && queue.volume >= 1 ? (queue.volume / 100) : 0.15; // queue.volume / 100; 178 | resource.volume.setVolume(volume); 179 | resource.playbackDuration = seekTime; 180 | return resource; 181 | } 182 | 183 | /** 184 | * Plays a song in a voice channel if possible, else it's adding it 185 | * @param {Client} client 186 | * @param {VoiceChannel} channel 187 | * @param {object} songInfo 188 | * @returns 189 | */ 190 | const playSong = async (client, channel, songInfo) => { 191 | return new Promise(async (res, rej) => { 192 | const oldConnection = getVoiceConnection(channel.guild.id); 193 | if (oldConnection) { 194 | if (oldConnection.joinConfig.channelId != channel.id) return rej("We aren't in the same channel!") 195 | try { 196 | const curQueue = client.queues.get(channel.guild.id); 197 | 198 | const player = createAudioPlayer({ 199 | behaviors: { 200 | noSubscriber: NoSubscriberBehavior.Stop, 201 | }, 202 | }); 203 | oldConnection.subscribe(player); 204 | 205 | const resource = getResource(curQueue, songInfo.id); 206 | // play the resource 207 | player.play(resource); 208 | 209 | // When the player plays a new song 210 | player.on("playing", (player) => { 211 | const queue = client.queues.get(channel.guild.id); 212 | // if filters changed, don't send something 213 | if(queue && queue.filtersChanged) { 214 | queue.filtersChanged = false; 215 | } else { 216 | sendQueueUpdate(client, channel.guild.id); 217 | } 218 | 219 | }); 220 | // When the player goes on idle 221 | player.on("idle", () => { 222 | const queue = client.queues.get(channel.guild.id); 223 | handleQueue(client, player, queue) 224 | }) 225 | // when an error happens 226 | player.on('error', error => { 227 | console.error(error); 228 | const queue = client.queues.get(channel.guild.id); 229 | handleQueue(client, player, queue) 230 | }); 231 | 232 | return res(songInfo); 233 | } catch (e) { 234 | return rej(e) 235 | } 236 | } else { 237 | return rej("I'm not connected somwhere.") 238 | } 239 | }) 240 | 241 | } 242 | 243 | /** 244 | * handles the queue (skipping, stopping, looping) 245 | * @param {Client} client 246 | * @param {AudioPlayer} player 247 | * @param {object} queue 248 | * @returns {void} 249 | */ 250 | async function handleQueue(client, player, queue) { 251 | if(queue && !queue.filtersChanged) { 252 | try { 253 | player.stop() 254 | if(queue && queue.tracks && queue.tracks.length > 1) { 255 | queue.previous = queue.tracks[0]; 256 | if(queue.trackloop && !queue.skipped) { 257 | if(queue.paused) queue.paused = false; 258 | if(queue.tracks[0]?.resource) player.play(createAudioResource(queue.tracks[0].resource)) 259 | else player.play(getResource(queue, queue.tracks[0].id)) 260 | } else if(queue.queueloop && !queue.skipped) { 261 | const skipped = queue.tracks.shift(); 262 | queue.tracks.push(skipped); 263 | if(queue.paused) queue.paused = false; 264 | if(queue.tracks[0]?.resource) player.play(createAudioResource(queue.tracks[0].resource)) 265 | else player.play(getResource(queue, queue.tracks[0].id)); 266 | } else { 267 | if(queue.skipped) queue.skipped = false; 268 | if(queue.paused) queue.paused = false; 269 | queue.tracks.shift(); 270 | if(queue.tracks[0]?.resource) player.play(createAudioResource(queue.tracks[0].resource)); 271 | else player.play(getResource(queue, queue.tracks[0].id)); 272 | } 273 | } else if(queue && queue.tracks && queue.tracks.length <= 1) { // removes the nowplaying, if no upcoming and ends it 274 | queue.previous = queue.tracks[0]; 275 | if(queue.trackloop || queue.queueloop && !queue.skipped) { 276 | if(queue.tracks[0]?.resource) player.play(createAudioResource(queue.tracks[0].resource)) 277 | else player.play(getResource(queue, queue.tracks[0].id)); 278 | } else { 279 | if(queue.skipped) queue.skipped = false; 280 | if(queue.stopped) { 281 | // if there is a voice-response resource, play it 282 | if(queue.tracks[0]?.resource) { 283 | const track = queue.tracks.shift(); 284 | if(track?.resource) return player.play(createAudioResource(track.resource)); 285 | } 286 | 287 | // get the bot's voice Connection 288 | const meChannel = client.guilds.cache.get(queue.guildId)?.members?.me?.voice?.channel; 289 | if(meChannel) leaveVoiceChannel(meChannel); 290 | else { // else fetch the voicechannel and leave it 291 | const vc = await client.channels.fetch(queue.voiceChannel).catch(() => null); 292 | if(vc) leaveVoiceChannel(vc); 293 | } 294 | 295 | // send a status update if possible 296 | const textChannel = await client.channels.fetch(queue.textChannel).catch(() => null); 297 | if(textChannel) textChannel.send({ 298 | content: translate(client, textChannel.guild.id, "STOPPED") 299 | }).catch(() => null); 300 | return; 301 | } 302 | queue.tracks = []; 303 | // Queue Empty, do this 304 | const textChannel = await client.channels.fetch(queue.textChannel).catch(() => null); 305 | if(textChannel) { 306 | textChannel.send({ 307 | content: translate(client, textChannel.guildId, "QUEUE_EMPTY", msUnix(Date.now() + settings.leaveEmptyVC)) 308 | }).catch(console.warn) 309 | } 310 | setTimeout(async () => { 311 | const nqueue = client.queues.get(queue.guildId); 312 | if(!nqueue?.tracks?.length) { 313 | // get the bot's voice Connection 314 | const meChannel = client.guilds.cache.get(queue.guildId)?.members?.me?.voice?.channel; 315 | if(meChannel) leaveVoiceChannel(meChannel); 316 | else { // else fetch the voicechannel and leave it 317 | const vc = await client.channels.fetch(queue.voiceChannel).catch(() => null); 318 | if(vc) leaveVoiceChannel(vc); 319 | } 320 | } else console.log(nqueue); 321 | return; 322 | }, settings.leaveEmptyVC) 323 | } 324 | } else { 325 | // get the bot's voice Connection 326 | const meChannel = client.guilds.cache.get(queue.guildId)?.members?.me?.voice?.channel; 327 | if(meChannel) leaveVoiceChannel(meChannel); 328 | else { // else fetch the voicechannel and leave it 329 | const vc = await client.channels.fetch(queue.voiceChannel).catch(() => null); 330 | if(vc) leaveVoiceChannel(vc); 331 | } 332 | // send a queue textchannel update 333 | const textChannel = await client.channels.fetch(queue.textChannel).catch(() => null); 334 | if(textChannel) textChannel.send({ content: translate(client, textChannel.guildId, "LEFT_VC") }).catch(() => null); 335 | return; 336 | } 337 | } catch (e) { console.error(e) } 338 | } 339 | return; 340 | } 341 | 342 | /** 343 | * Sends a Queue Update to the queue channel 344 | * @param {Client} client 345 | * @param {string} guildId 346 | * @returns {void} 347 | */ 348 | const sendQueueUpdate = async (client, guildId) => { 349 | const queue = client.queues.get(guildId); 350 | if(!queue || !queue.tracks || queue.tracks.length == 0 || !queue.textChannel) return 351 | 352 | const textChannel = await client.channels.fetch(queue.textChannel).catch(() => null); 353 | if(!textChannel) return 354 | 355 | const song = queue.tracks[0]; 356 | const embed = new EmbedBuilder().setColor(Color.Main) 357 | .setURL(getYTLink(song.id)) 358 | .setFields([ 359 | { 360 | name: `**Duration:**`, 361 | value: `> \`${song.durationFormatted}\``, inline: true 362 | }, 363 | { 364 | name: `**Requester:**`, 365 | value: `> ${song.requester} \`${song.requester.tag}\``, inline: true 366 | } 367 | ]); 368 | if(song?.thumbnail?.url) embed.setThumbnail(`${song?.thumbnail?.url}`); 369 | 370 | textChannel.send({ 371 | embeds: [ embed ] 372 | }).catch(console.warn) 373 | return; 374 | } 375 | 376 | /** 377 | * Merge song and requester together 378 | * @param {*} song 379 | * @param {User} requester 380 | * @returns Object of song and requester 381 | */ 382 | const createSong = (song, requester) => { 383 | return { ...song, requester } 384 | } 385 | 386 | /** 387 | * Formats the song index to a readable string 388 | * @param {number} length 389 | * @returns String 390 | */ 391 | const queuePos = (length) => { 392 | const str = { 393 | 1: "st", 394 | 2: "nd", 395 | 3: "rd", 396 | } 397 | return `${length}${str[length % 10] ? str[length % 10] : "th"}` 398 | } 399 | 400 | /** 401 | * Creates a Queue Object for the voicechannel, textchannel and guildId 402 | * @param {*} song 403 | * @param {User} user 404 | * @param {string} channelId 405 | * @param {VoiceChannel} voiceChannel 406 | * @param {number} bitrate 407 | * @returns a Queue-Object 408 | */ 409 | const createQueue = (song, user, channelId, voiceChannel, bitrate = 128) => { 410 | return { 411 | guildId: voiceChannel.guildId, 412 | voiceChannel, 413 | textChannel: channelId, 414 | paused: false, 415 | skipped: false, 416 | effects: { 417 | bassboost: 0, 418 | subboost: false, 419 | mcompand: false, 420 | haas: false, 421 | gate: false, 422 | karaoke: false, 423 | flanger: false, 424 | pulsator: false, 425 | surrounding: false, 426 | "3d": false, 427 | vaporwave: false, 428 | nightcore: false, 429 | phaser: false, 430 | normalizer: false, 431 | speed: 1, 432 | tremolo: false, 433 | vibrato: false, 434 | reverse: false, 435 | treble: false, 436 | }, 437 | trackloop: false, 438 | queueloop: false, 439 | filtersChanged: false, 440 | volume: 15, // queue volume, between 1 and 100 441 | tracks: [ createSong(song, user) ], 442 | previous: undefined, 443 | creator: user, 444 | bitrate: bitrate 445 | } 446 | } 447 | module.exports = { 448 | validVCTypes, 449 | joinVoiceChannelUtil, 450 | m2, m3, formatDuration, getYTLink, leaveVoiceChannel, getResource, playSong, 451 | sendQueueUpdate, createSong, queuePos, createQueue 452 | } -------------------------------------------------------------------------------- /utils/speechHandler.js: -------------------------------------------------------------------------------- 1 | // Import Packages 2 | const { readFileSync, createWriteStream, unlinkSync, createReadStream } = require("fs"); 3 | const { EndBehaviorType } = require("@discordjs/voice"); 4 | const prism = require("prism-media"); 5 | const fetch = require('node-fetch'); 6 | const ffmpeg = require('ffmpeg'); 7 | const { pipeline } = require("node:stream"); 8 | const { AttachmentBuilder } = require("discord.js"); 9 | // util functions and settings 10 | const { msUnix, transformUsername, delay } = require("./botUtils"); 11 | const { settings, Emojis } = require("./constants/settingsData"); 12 | const { translate } = require("./language"); 13 | 14 | let witAI_lastcallTS = null; 15 | 16 | async function parseAudioData(client, VoiceConnection, user, channel) { 17 | // create the filename of it 18 | const filename = `${process.cwd()}/temp/${transformUsername(user.username)}_${Date.now()}.pcm` 19 | // then make a listenable audio stream, with the maximum highWaterMark (longest duration(s)) 20 | const audioStream = VoiceConnection.receiver.subscribe(user.id, { 21 | end: { 22 | behavior: EndBehaviorType.AfterSilence, 23 | duration: 1000, 24 | }, 25 | highWaterMark: 1 << 16 26 | }); 27 | // create an ogglogicalbitstream piper 28 | const oggStream = new prism.opus.OggLogicalBitstream({ 29 | opusHead: new prism.opus.OpusHead({ 30 | channelCount: 1, 31 | sampleRate: 48000, 32 | }), 33 | pageSizeControl: { 34 | maxPackets: 10, 35 | }, 36 | }); 37 | // and lastly the file write stream 38 | const out = createWriteStream(filename); 39 | 40 | // send a status update 41 | console.log(`👂 Started recording ${filename}`); 42 | const msg = await channel.send({ 43 | content: translate(client, channel.guild.id, "NOWLISTENING", user.tag, msUnix(Date.now() + 5_000)) 44 | }).catch(() => null); 45 | 46 | // pipe the audiostream, ogg stream and writestream together, once audiostream is finished 47 | pipeline(audioStream, oggStream, out, async (err) => { 48 | if (err) return console.warn(`❌ Error recording file ${filename} - ${err.message}`); 49 | 50 | console.log(`✅ Recorded ${filename}`); 51 | // TESTED - here we have a PCM File which when transformed to a .wav file is listen-able 52 | return await handlePCMFile(client, VoiceConnection, user, channel, msg, filename); 53 | }); 54 | } 55 | async function handlePCMFile(client, VoiceConnection, user, channel, msg, pcmFileName) { 56 | const mp3FileName = pcmFileName.replace(".pcm", ".mp3"); 57 | // convert the pcm file to an mp3 file 58 | await convertAudioFiles(pcmFileName, mp3FileName); 59 | // create a read stream of the wav file 60 | const mp3FileStream = createReadStream(mp3FileName); 61 | // try to do the text-to-speech 62 | try { 63 | // anti spam delay loop 64 | // ensure we do not send more than one request per second 65 | if (witAI_lastcallTS != null) { 66 | let now = Date.now(); 67 | let secCounter = 0; 68 | while (now - witAI_lastcallTS < 1000) { 69 | await delay(100); 70 | secCounter++; 71 | now = Date.now(); 72 | if(secCounter>=50) return; 73 | } 74 | } 75 | // set current witAI call 76 | witAI_lastcallTS = Date.now(); 77 | // "audio/raw;encoding=signed-integer;bits=16;rate=48k;endian=little" 78 | const output = await fetch('https://api.wit.ai/speech', { 79 | method: 'POST', 80 | headers: { 81 | 'Authorization': `Bearer ${process.env.WIT_AI_ACCESS_TOKEN}`, 82 | 'Content-Type': "audio/mpeg3", 83 | }, 84 | body: mp3FileStream, 85 | }).then(res => speechToText(res)).catch(console.error); 86 | 87 | // stop the mp3 file reading stream 88 | mp3FileStream.destroy(); 89 | 90 | // delete the temp files 91 | try { unlinkSync(pcmFileName); } catch { } 92 | try { unlinkSync(mp3FileName); } catch { } 93 | 94 | if(!output?.length) return; 95 | 96 | const [ keyWord, ...params ] = output.split(" "); 97 | 98 | if (keyWord && params[0] && settings.validVoiceKeyWords.some(x => x.toLowerCase() == keyWord.toLowerCase())) { 99 | return processCommandQuery(client, params, user, channel, VoiceConnection, msg, mp3FileName); 100 | } 101 | if(output === "hey" || output === undefined || !keyWord || !params[0]) { 102 | return await msg.edit({ 103 | content: `${Emojis.cross.str} **I could not understand you!**\n> Try to speak clearer and faster...` 104 | }).catch(console.warn); 105 | } 106 | return await msg.edit({ 107 | content: `${Emojis.cross.str} **INVALID-Input:**\n> \`\`\`${output}\`\`\`\n> Try to speak clearer and faster...`, 108 | }).catch(console.warn); 109 | } catch(e) { 110 | console.error(e); 111 | } 112 | } 113 | async function processCommandQuery(client, params, user, channel, VoiceConnection, msg, mp3FileName) { 114 | const [ commandName, ...args ] = params; 115 | 116 | await msg.edit({ 117 | content: `✅ **Your Command:**\n> \`\`\`${commandName} ${args.join(" ")}\`\`\``, 118 | }).catch(console.warn); 119 | 120 | const command = client.commands.get(commandName?.toLowerCase()) || client.commands.find(c => !!c.aliases?.includes(commandName?.toLowerCase())) 121 | if(command && command.name !== "control") command.execute(client, args, user, channel, await client.channels.fetch(VoiceConnection.joinConfig.channelId)); 122 | return; 123 | } 124 | 125 | // the api now returns Unspecific amount of CHUNKS of JSON DATA 126 | // step one : recieve it as a stream 127 | // step two : return the last chunk 128 | async function speechToText(res) { 129 | const wholeBody = await res.text(); 130 | const returnData = []; 131 | for(const thing of wholeBody.split("\n")) { 132 | if(thing.includes('"text":')) { 133 | try { //' "text": "...", ' 134 | const parsedData = JSON.parse(`{ ${thing.trim().replace('",', '"')} }`); 135 | if(parsedData?.text) { 136 | if(parsedData.text.endsWith("!") || parsedData.text.endsWith(".")) 137 | returnData.push(parsedData.text.substring(0, parsedData.text.length - 1)); 138 | else returnData.push(parsedData.text); 139 | } 140 | } catch (e) { 141 | console.warn(e) 142 | } 143 | } 144 | } 145 | const sorted = returnData.sort((a, b) => { 146 | if(a.length < b.length) return 1; 147 | if(a.length > b.length) return -1; 148 | return 0 149 | }); 150 | const output = sorted[0]?.split(", ")?.join(" ")?.toLowerCase(); 151 | if(output.startsWith("hey ")) return output.replace("hey ", ""); 152 | return output; 153 | } 154 | async function convertAudioFiles(infile, outfile) { 155 | return new Promise(r => { 156 | /* Create ffmpeg command to convert pcm to mp3 */ 157 | const processD = new ffmpeg(infile); 158 | processD.then(function (audio) { 159 | audio.fnExtractSoundToMP3(outfile, async function (e, file) { 160 | if(e) console.error(e); 161 | // make an .wav file out of the .mp3 file 162 | return r(outfile); // return the .wav file 163 | }); 164 | }, function (e) { 165 | if(e) console.error(e); 166 | }); 167 | }) 168 | } 169 | module.exports = { 170 | parseAudioData 171 | } 172 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@derhuerst/http-basic@^8.2.0": 6 | "integrity" "sha512-F9rL9k9Xjf5blCz8HsJRO4diy111cayL2vkY2XE4r4t3n0yPXVYy3KD3nJ1qbrSn9743UWSXH4IwuCa/HWlGFw==" 7 | "resolved" "https://registry.npmjs.org/@derhuerst/http-basic/-/http-basic-8.2.4.tgz" 8 | "version" "8.2.4" 9 | dependencies: 10 | "caseless" "^0.12.0" 11 | "concat-stream" "^2.0.0" 12 | "http-response-object" "^3.0.1" 13 | "parse-cache-control" "^1.0.1" 14 | 15 | "@discordjs/builders@^1.0.0": 16 | "integrity" "sha512-8y91ZfpOHubiGJu5tVyGI9tQCEyHZDTeqUWVcJd0dq7B96xIf84S0L4fwmD1k9zTe1eqEFSk0gc7BpY+FKn7Ww==" 17 | "resolved" "https://registry.npmjs.org/@discordjs/builders/-/builders-1.0.0.tgz" 18 | "version" "1.0.0" 19 | dependencies: 20 | "@sapphire/shapeshift" "^3.5.1" 21 | "discord-api-types" "^0.36.2" 22 | "fast-deep-equal" "^3.1.3" 23 | "ts-mixer" "^6.0.1" 24 | "tslib" "^2.4.0" 25 | 26 | "@discordjs/collection@^1.0.0": 27 | "integrity" "sha512-nAxDQYE5dNAzEGQ7HU20sujDsG5vLowUKCEqZkKUIlrXERZFTt/60zKUj/g4+AVCGeq+pXC5hivMaNtiC+PY5Q==" 28 | "resolved" "https://registry.npmjs.org/@discordjs/collection/-/collection-1.0.0.tgz" 29 | "version" "1.0.0" 30 | 31 | "@discordjs/node-pre-gyp@^0.4.4": 32 | "integrity" "sha512-x569MMtdk6jdGo2S58iiZoyv4p/N2Ju8Nh6vvzZb1wyouV7IE3VuU0hg2kqUmTfD0z6r4uD6acvMTuc+iA3f8g==" 33 | "resolved" "https://registry.npmjs.org/@discordjs/node-pre-gyp/-/node-pre-gyp-0.4.4.tgz" 34 | "version" "0.4.4" 35 | dependencies: 36 | "detect-libc" "^2.0.0" 37 | "https-proxy-agent" "^5.0.0" 38 | "make-dir" "^3.1.0" 39 | "node-fetch" "^2.6.7" 40 | "nopt" "^5.0.0" 41 | "npmlog" "^5.0.1" 42 | "rimraf" "^3.0.2" 43 | "semver" "^7.3.5" 44 | "tar" "^6.1.11" 45 | 46 | "@discordjs/opus@^0.8.0": 47 | "integrity" "sha512-uHE7OmHEmP8YM0yvsH3iSdacdeghO0qTkF0CIkV07Tg0qdyOLUVkoZHj5Zcpge9rC4qb/JvTS2xRgttSZLM43Q==" 48 | "resolved" "https://registry.npmjs.org/@discordjs/opus/-/opus-0.8.0.tgz" 49 | "version" "0.8.0" 50 | dependencies: 51 | "@discordjs/node-pre-gyp" "^0.4.4" 52 | "node-addon-api" "^5.0.0" 53 | 54 | "@discordjs/rest@^1.0.0": 55 | "integrity" "sha512-uDAvnE0P2a8axMdD4C51EGjvCRQ2HZk2Yxf6vHWZgIqG87D8DGKMPwmquIxrrB07MjV+rwci2ObU+mGhGP+bJg==" 56 | "resolved" "https://registry.npmjs.org/@discordjs/rest/-/rest-1.0.0.tgz" 57 | "version" "1.0.0" 58 | dependencies: 59 | "@discordjs/collection" "^1.0.0" 60 | "@sapphire/async-queue" "^1.3.2" 61 | "@sapphire/snowflake" "^3.2.2" 62 | "discord-api-types" "^0.36.2" 63 | "file-type" "^17.1.2" 64 | "tslib" "^2.4.0" 65 | "undici" "^5.7.0" 66 | 67 | "@discordjs/voice@^0.11.0": 68 | "integrity" "sha512-6+9cj1dxzBJm7WJ9qyG2XZZQ8rcLl6x2caW0C0OxuTtMLAaEDntpb6lqMTFiBg/rDc4Rd59g1w0gJmib33CuHw==" 69 | "resolved" "https://registry.npmjs.org/@discordjs/voice/-/voice-0.11.0.tgz" 70 | "version" "0.11.0" 71 | dependencies: 72 | "@types/ws" "^8.5.3" 73 | "discord-api-types" "^0.36.2" 74 | "prism-media" "^1.3.4" 75 | "tslib" "^2.4.0" 76 | "ws" "^8.8.1" 77 | 78 | "@sapphire/async-queue@^1.3.2": 79 | "integrity" "sha512-rUpMLATsoAMnlN3gecAcr9Ecnw1vG7zi5Xr+IX22YzRzi1k9PF9vKzoT8RuEJbiIszjcimu3rveqUnvwDopz8g==" 80 | "resolved" "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.3.2.tgz" 81 | "version" "1.3.2" 82 | 83 | "@sapphire/shapeshift@^3.5.1": 84 | "integrity" "sha512-7JFsW5IglyOIUQI1eE0g6h06D/Far6HqpcowRScgCiLSqTf3hhkPWCWotVTtVycnDCMYIwPeaw6IEPBomKC8pA==" 85 | "resolved" "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.5.1.tgz" 86 | "version" "3.5.1" 87 | dependencies: 88 | "fast-deep-equal" "^3.1.3" 89 | "lodash.uniqwith" "^4.5.0" 90 | 91 | "@sapphire/snowflake@^3.2.2": 92 | "integrity" "sha512-ula2O0kpSZtX9rKXNeQMrHwNd7E4jPDJYUXmEGTFdMRfyfMw+FPyh04oKMjAiDuOi64bYgVkOV3MjK+loImFhQ==" 93 | "resolved" "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.2.2.tgz" 94 | "version" "3.2.2" 95 | 96 | "@tokenizer/token@^0.3.0": 97 | "integrity" "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" 98 | "resolved" "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz" 99 | "version" "0.3.0" 100 | 101 | "@types/node@*": 102 | "integrity" "sha512-z+2vB6yDt1fNwKOeGbckpmirO+VBDuQqecXkgeIqDlaOtmKn6hPR/viQ8cxCfqLU4fTlvM3+YjM367TukWdxpg==" 103 | "resolved" "https://registry.npmjs.org/@types/node/-/node-18.6.1.tgz" 104 | "version" "18.6.1" 105 | 106 | "@types/node@^10.0.3": 107 | "integrity" "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==" 108 | "resolved" "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz" 109 | "version" "10.17.60" 110 | 111 | "@types/node@^15.6.1": 112 | "integrity" "sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A==" 113 | "resolved" "https://registry.npmjs.org/@types/node/-/node-15.14.9.tgz" 114 | "version" "15.14.9" 115 | 116 | "@types/ws@^8.5.3": 117 | "integrity" "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==" 118 | "resolved" "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz" 119 | "version" "8.5.3" 120 | dependencies: 121 | "@types/node" "*" 122 | 123 | "abbrev@1": 124 | "integrity" "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" 125 | "resolved" "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz" 126 | "version" "1.1.1" 127 | 128 | "agent-base@6": 129 | "integrity" "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==" 130 | "resolved" "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" 131 | "version" "6.0.2" 132 | dependencies: 133 | "debug" "4" 134 | 135 | "ansi-regex@^5.0.1": 136 | "integrity" "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" 137 | "resolved" "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" 138 | "version" "5.0.1" 139 | 140 | "aproba@^1.0.3 || ^2.0.0": 141 | "integrity" "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" 142 | "resolved" "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz" 143 | "version" "2.0.0" 144 | 145 | "are-we-there-yet@^2.0.0": 146 | "integrity" "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==" 147 | "resolved" "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz" 148 | "version" "2.0.0" 149 | dependencies: 150 | "delegates" "^1.0.0" 151 | "readable-stream" "^3.6.0" 152 | 153 | "balanced-match@^1.0.0": 154 | "integrity" "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 155 | "resolved" "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" 156 | "version" "1.0.2" 157 | 158 | "base64-js@^1.3.1": 159 | "integrity" "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" 160 | "resolved" "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" 161 | "version" "1.5.1" 162 | 163 | "better-sqlite3@^7.5.1": 164 | "integrity" "sha512-S5zIU1Hink2AH4xPsN0W43T1/AJ5jrPh7Oy07ocuW/AKYYY02GWzz9NH0nbSMn/gw6fDZ5jZ1QsHt1BXAwJ6Lg==" 165 | "resolved" "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-7.6.2.tgz" 166 | "version" "7.6.2" 167 | dependencies: 168 | "bindings" "^1.5.0" 169 | "prebuild-install" "^7.1.0" 170 | 171 | "bindings@^1.3.0", "bindings@^1.5.0": 172 | "integrity" "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==" 173 | "resolved" "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz" 174 | "version" "1.5.0" 175 | dependencies: 176 | "file-uri-to-path" "1.0.0" 177 | 178 | "bl@^4.0.3": 179 | "integrity" "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==" 180 | "resolved" "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz" 181 | "version" "4.1.0" 182 | dependencies: 183 | "buffer" "^5.5.0" 184 | "inherits" "^2.0.4" 185 | "readable-stream" "^3.4.0" 186 | 187 | "brace-expansion@^1.1.7": 188 | "integrity" "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==" 189 | "resolved" "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" 190 | "version" "1.1.11" 191 | dependencies: 192 | "balanced-match" "^1.0.0" 193 | "concat-map" "0.0.1" 194 | 195 | "buffer-from@^1.0.0": 196 | "integrity" "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" 197 | "resolved" "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" 198 | "version" "1.1.2" 199 | 200 | "buffer@^5.5.0": 201 | "integrity" "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==" 202 | "resolved" "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" 203 | "version" "5.7.1" 204 | dependencies: 205 | "base64-js" "^1.3.1" 206 | "ieee754" "^1.1.13" 207 | 208 | "caseless@^0.12.0": 209 | "integrity" "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" 210 | "resolved" "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz" 211 | "version" "0.12.0" 212 | 213 | "chownr@^1.1.1": 214 | "integrity" "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" 215 | "resolved" "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz" 216 | "version" "1.1.4" 217 | 218 | "chownr@^2.0.0": 219 | "integrity" "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" 220 | "resolved" "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz" 221 | "version" "2.0.0" 222 | 223 | "color-support@^1.1.2": 224 | "integrity" "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" 225 | "resolved" "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz" 226 | "version" "1.1.3" 227 | 228 | "concat-map@0.0.1": 229 | "integrity" "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" 230 | "resolved" "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" 231 | "version" "0.0.1" 232 | 233 | "concat-stream@^2.0.0": 234 | "integrity" "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==" 235 | "resolved" "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz" 236 | "version" "2.0.0" 237 | dependencies: 238 | "buffer-from" "^1.0.0" 239 | "inherits" "^2.0.3" 240 | "readable-stream" "^3.0.2" 241 | "typedarray" "^0.0.6" 242 | 243 | "console-control-strings@^1.0.0", "console-control-strings@^1.1.0": 244 | "integrity" "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" 245 | "resolved" "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz" 246 | "version" "1.1.0" 247 | 248 | "debug@4": 249 | "integrity" "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==" 250 | "resolved" "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" 251 | "version" "4.3.4" 252 | dependencies: 253 | "ms" "2.1.2" 254 | 255 | "decompress-response@^6.0.0": 256 | "integrity" "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==" 257 | "resolved" "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz" 258 | "version" "6.0.0" 259 | dependencies: 260 | "mimic-response" "^3.1.0" 261 | 262 | "deep-extend@^0.6.0": 263 | "integrity" "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" 264 | "resolved" "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz" 265 | "version" "0.6.0" 266 | 267 | "delegates@^1.0.0": 268 | "integrity" "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" 269 | "resolved" "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz" 270 | "version" "1.0.0" 271 | 272 | "detect-libc@^2.0.0": 273 | "integrity" "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==" 274 | "resolved" "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz" 275 | "version" "2.0.1" 276 | 277 | "discord-api-types@^0.36.2": 278 | "integrity" "sha512-bz/NDyG0KBo/tY14vSkrwQ/n3HKPf87a0WFW/1M9+tXYK+vp5Z5EksawfCWo2zkAc6o7CClc0eff1Pjrqznlwg==" 279 | "resolved" "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.36.3.tgz" 280 | "version" "0.36.3" 281 | 282 | "discord-ytdl-core@^5.0.4": 283 | "integrity" "sha512-O+G9wuCw5TERR9iHZFMYnYQbs/ZGudDc9cxa1OKSV5TdcBYrHGS1JqvE90ZvSQ6SmS4XvqtOf/Okls6yiGJ3sg==" 284 | "resolved" "https://registry.npmjs.org/discord-ytdl-core/-/discord-ytdl-core-5.0.4.tgz" 285 | "version" "5.0.4" 286 | dependencies: 287 | "prism-media" "^1.2.9" 288 | 289 | "discord.js@^14.0.3": 290 | "integrity" "sha512-wH/VQl4CqN8/+dcXEtYis1iurqxGlDpEe0O4CqH5FGqZGIjVpTdtK0STXXx7bVNX8MT/0GvLZLkmO/5gLDWZVg==" 291 | "resolved" "https://registry.npmjs.org/discord.js/-/discord.js-14.0.3.tgz" 292 | "version" "14.0.3" 293 | dependencies: 294 | "@discordjs/builders" "^1.0.0" 295 | "@discordjs/collection" "^1.0.0" 296 | "@discordjs/rest" "^1.0.0" 297 | "@sapphire/snowflake" "^3.2.2" 298 | "@types/ws" "^8.5.3" 299 | "discord-api-types" "^0.36.2" 300 | "fast-deep-equal" "^3.1.3" 301 | "lodash.snakecase" "^4.1.1" 302 | "tslib" "^2.4.0" 303 | "undici" "^5.8.0" 304 | "ws" "^8.8.1" 305 | 306 | "dotenv@^16.0.1": 307 | "integrity" "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==" 308 | "resolved" "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz" 309 | "version" "16.0.1" 310 | 311 | "duplex-child-process@^1.0.1": 312 | "integrity" "sha512-tWbt4tyioDjyK5nh+qicbdvBvNjSXsTUF5zKUwSauuKPg1mokjwn/HezwfvWhh6hXoLdgetY+ZlzU/sMwUMJkg==" 313 | "resolved" "https://registry.npmjs.org/duplex-child-process/-/duplex-child-process-1.0.1.tgz" 314 | "version" "1.0.1" 315 | 316 | "emoji-regex@^8.0.0": 317 | "integrity" "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" 318 | "resolved" "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" 319 | "version" "8.0.0" 320 | 321 | "end-of-stream@^1.1.0", "end-of-stream@^1.4.1": 322 | "integrity" "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==" 323 | "resolved" "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz" 324 | "version" "1.4.4" 325 | dependencies: 326 | "once" "^1.4.0" 327 | 328 | "enmap@^5.9.0": 329 | "integrity" "sha512-70bJNufWO2fE1BdDuPdtgyJGz+13EN3shOIvczi2riKKWOsSvBfXXHLzqpV0wpA+IOvSNxlSO+eoBc0pMwIXMw==" 330 | "resolved" "https://registry.npmjs.org/enmap/-/enmap-5.9.0.tgz" 331 | "version" "5.9.0" 332 | dependencies: 333 | "better-sqlite3" "^7.5.1" 334 | "lodash" "^4.17.21" 335 | "on-change" "^2.2.3" 336 | "serialize-javascript" "^6.0.0" 337 | 338 | "env-paths@^2.2.0": 339 | "integrity" "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==" 340 | "resolved" "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz" 341 | "version" "2.2.1" 342 | 343 | "expand-template@^2.0.3": 344 | "integrity" "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" 345 | "resolved" "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz" 346 | "version" "2.0.3" 347 | 348 | "fast-deep-equal@^3.1.3": 349 | "integrity" "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" 350 | "resolved" "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" 351 | "version" "3.1.3" 352 | 353 | "ffmpeg-static@^5.0.2", "ffmpeg-static@^5.0.2 || ^4.2.7 || ^3.0.0 || ^2.4.0": 354 | "integrity" "sha512-rYeA04AGbvxbUxov6Cn/KDKIzw2rmzwPlgHoqn837NZt0xPdOVA9mJMILz9IX27R45hhSlXS6nThk85XxDivLg==" 355 | "resolved" "https://registry.npmjs.org/ffmpeg-static/-/ffmpeg-static-5.0.2.tgz" 356 | "version" "5.0.2" 357 | dependencies: 358 | "@derhuerst/http-basic" "^8.2.0" 359 | "env-paths" "^2.2.0" 360 | "https-proxy-agent" "^5.0.0" 361 | "progress" "^2.0.3" 362 | 363 | "ffmpeg@^0.0.4": 364 | "integrity" "sha512-3TgWUJJlZGQn+crJFyhsO/oNeRRnGTy6GhgS98oUCIfZrOW5haPPV7DUfOm3xJcHr5q3TJpjk2GudPutrNisRA==" 365 | "resolved" "https://registry.npmjs.org/ffmpeg/-/ffmpeg-0.0.4.tgz" 366 | "version" "0.0.4" 367 | dependencies: 368 | "when" ">= 0.0.1" 369 | 370 | "file-type@^17.1.2": 371 | "integrity" "sha512-MFVSozBIhvnx2dkxlf+010Xqn6+ojlMUT9LXQiPNoOijgRtXNMghWdGK0u2o1RoCqzHoVsw65IL8ZBcQ4MhIrw==" 372 | "resolved" "https://registry.npmjs.org/file-type/-/file-type-17.1.3.tgz" 373 | "version" "17.1.3" 374 | dependencies: 375 | "readable-web-to-node-stream" "^3.0.2" 376 | "strtok3" "^7.0.0-alpha.7" 377 | "token-types" "^5.0.0-alpha.2" 378 | 379 | "file-uri-to-path@1.0.0": 380 | "integrity" "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" 381 | "resolved" "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz" 382 | "version" "1.0.0" 383 | 384 | "fs-constants@^1.0.0": 385 | "integrity" "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" 386 | "resolved" "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz" 387 | "version" "1.0.0" 388 | 389 | "fs-minipass@^2.0.0": 390 | "integrity" "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==" 391 | "resolved" "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz" 392 | "version" "2.1.0" 393 | dependencies: 394 | "minipass" "^3.0.0" 395 | 396 | "fs.realpath@^1.0.0": 397 | "integrity" "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" 398 | "resolved" "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" 399 | "version" "1.0.0" 400 | 401 | "gauge@^3.0.0": 402 | "integrity" "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==" 403 | "resolved" "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz" 404 | "version" "3.0.2" 405 | dependencies: 406 | "aproba" "^1.0.3 || ^2.0.0" 407 | "color-support" "^1.1.2" 408 | "console-control-strings" "^1.0.0" 409 | "has-unicode" "^2.0.1" 410 | "object-assign" "^4.1.1" 411 | "signal-exit" "^3.0.0" 412 | "string-width" "^4.2.3" 413 | "strip-ansi" "^6.0.1" 414 | "wide-align" "^1.1.2" 415 | 416 | "github-from-package@0.0.0": 417 | "integrity" "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" 418 | "resolved" "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz" 419 | "version" "0.0.0" 420 | 421 | "glob@^7.1.3": 422 | "integrity" "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==" 423 | "resolved" "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" 424 | "version" "7.2.3" 425 | dependencies: 426 | "fs.realpath" "^1.0.0" 427 | "inflight" "^1.0.4" 428 | "inherits" "2" 429 | "minimatch" "^3.1.1" 430 | "once" "^1.3.0" 431 | "path-is-absolute" "^1.0.0" 432 | 433 | "has-unicode@^2.0.1": 434 | "integrity" "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" 435 | "resolved" "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz" 436 | "version" "2.0.1" 437 | 438 | "http-response-object@^3.0.1": 439 | "integrity" "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==" 440 | "resolved" "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz" 441 | "version" "3.0.2" 442 | dependencies: 443 | "@types/node" "^10.0.3" 444 | 445 | "https-proxy-agent@^5.0.0": 446 | "integrity" "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==" 447 | "resolved" "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz" 448 | "version" "5.0.1" 449 | dependencies: 450 | "agent-base" "6" 451 | "debug" "4" 452 | 453 | "ieee754@^1.1.13", "ieee754@^1.2.1": 454 | "integrity" "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" 455 | "resolved" "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" 456 | "version" "1.2.1" 457 | 458 | "inflight@^1.0.4": 459 | "integrity" "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==" 460 | "resolved" "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" 461 | "version" "1.0.6" 462 | dependencies: 463 | "once" "^1.3.0" 464 | "wrappy" "1" 465 | 466 | "inherits@^2.0.3", "inherits@^2.0.4", "inherits@2": 467 | "integrity" "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 468 | "resolved" "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" 469 | "version" "2.0.4" 470 | 471 | "ini@~1.3.0": 472 | "integrity" "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" 473 | "resolved" "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" 474 | "version" "1.3.8" 475 | 476 | "is-fullwidth-code-point@^3.0.0": 477 | "integrity" "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" 478 | "resolved" "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" 479 | "version" "3.0.0" 480 | 481 | "libsodium-wrappers@^0.7.9": 482 | "integrity" "sha512-9HaAeBGk1nKTRFRHkt7nzxqCvnkWTjn1pdjKgcUnZxj0FyOP4CnhgFhMdrFfgNsukijBGyBLpP2m2uKT1vuWhQ==" 483 | "resolved" "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.9.tgz" 484 | "version" "0.7.9" 485 | dependencies: 486 | "libsodium" "^0.7.0" 487 | 488 | "libsodium@^0.7.0": 489 | "integrity" "sha512-eY+z7hDrDKxkAK+QKZVNv92A5KYkxfvIshtBJkmg5TSiCnYqZP3i9OO9whE79Pwgm4jGaoHgkM4ao/b9Cyu4zQ==" 490 | "resolved" "https://registry.npmjs.org/libsodium/-/libsodium-0.7.10.tgz" 491 | "version" "0.7.10" 492 | 493 | "lodash.snakecase@^4.1.1": 494 | "integrity" "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==" 495 | "resolved" "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz" 496 | "version" "4.1.1" 497 | 498 | "lodash.uniqwith@^4.5.0": 499 | "integrity" "sha512-7lYL8bLopMoy4CTICbxygAUq6CdRJ36vFc80DucPueUee+d5NBRxz3FdT9Pes/HEx5mPoT9jwnsEJWz1N7uq7Q==" 500 | "resolved" "https://registry.npmjs.org/lodash.uniqwith/-/lodash.uniqwith-4.5.0.tgz" 501 | "version" "4.5.0" 502 | 503 | "lodash@^4.17.21": 504 | "integrity" "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 505 | "resolved" "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" 506 | "version" "4.17.21" 507 | 508 | "lru-cache@^6.0.0": 509 | "integrity" "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==" 510 | "resolved" "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" 511 | "version" "6.0.0" 512 | dependencies: 513 | "yallist" "^4.0.0" 514 | 515 | "m3u8stream@^0.8.6": 516 | "integrity" "sha512-LZj8kIVf9KCphiHmH7sbFQTVe4tOemb202fWwvJwR9W5ENW/1hxJN6ksAWGhQgSBSa3jyWhnjKU1Fw1GaOdbyA==" 517 | "resolved" "https://registry.npmjs.org/m3u8stream/-/m3u8stream-0.8.6.tgz" 518 | "version" "0.8.6" 519 | dependencies: 520 | "miniget" "^4.2.2" 521 | "sax" "^1.2.4" 522 | 523 | "make-dir@^3.1.0": 524 | "integrity" "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==" 525 | "resolved" "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz" 526 | "version" "3.1.0" 527 | dependencies: 528 | "semver" "^6.0.0" 529 | 530 | "mimic-response@^3.1.0": 531 | "integrity" "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" 532 | "resolved" "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz" 533 | "version" "3.1.0" 534 | 535 | "miniget@^4.2.2": 536 | "integrity" "sha512-a7voNL1N5lDMxvTMExOkg+Fq89jM2vY8pAi9ZEWzZtfNmdfP6RXkvUtFnCAXoCv2T9k1v/fUJVaAEuepGcvLYA==" 537 | "resolved" "https://registry.npmjs.org/miniget/-/miniget-4.2.2.tgz" 538 | "version" "4.2.2" 539 | 540 | "minimatch@^3.1.1": 541 | "integrity" "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==" 542 | "resolved" "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" 543 | "version" "3.1.2" 544 | dependencies: 545 | "brace-expansion" "^1.1.7" 546 | 547 | "minimist@^1.2.0", "minimist@^1.2.3": 548 | "integrity" "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" 549 | "resolved" "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz" 550 | "version" "1.2.6" 551 | 552 | "minipass@^3.0.0": 553 | "integrity" "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==" 554 | "resolved" "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz" 555 | "version" "3.3.4" 556 | dependencies: 557 | "yallist" "^4.0.0" 558 | 559 | "minizlib@^2.1.1": 560 | "integrity" "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==" 561 | "resolved" "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz" 562 | "version" "2.1.2" 563 | dependencies: 564 | "minipass" "^3.0.0" 565 | "yallist" "^4.0.0" 566 | 567 | "mkdirp-classic@^0.5.2", "mkdirp-classic@^0.5.3": 568 | "integrity" "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" 569 | "resolved" "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz" 570 | "version" "0.5.3" 571 | 572 | "mkdirp@^1.0.3": 573 | "integrity" "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" 574 | "resolved" "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" 575 | "version" "1.0.4" 576 | 577 | "ms@2.1.2": 578 | "integrity" "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 579 | "resolved" "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" 580 | "version" "2.1.2" 581 | 582 | "napi-build-utils@^1.0.1": 583 | "integrity" "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" 584 | "resolved" "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz" 585 | "version" "1.0.2" 586 | 587 | "node-abi@^3.3.0": 588 | "integrity" "sha512-u4uAs/4Zzmp/jjsD9cyFYDXeISfUWaAVWshPmDZOFOv4Xl4SbzTXm53I04C2uRueYJ+0t5PEtLH/owbn2Npf/w==" 589 | "resolved" "https://registry.npmjs.org/node-abi/-/node-abi-3.22.0.tgz" 590 | "version" "3.22.0" 591 | dependencies: 592 | "semver" "^7.3.5" 593 | 594 | "node-addon-api@^5.0.0": 595 | "integrity" "sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==" 596 | "resolved" "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.0.0.tgz" 597 | "version" "5.0.0" 598 | 599 | "node-crc@^1.3.2": 600 | "integrity" "sha512-1ipluqUEawnH1SVUz3JvnHqHaKbjTW5Mz/4lnvSU4fEmvfw9NU4DcTtCU8j2atk9p4P1TzyDKjo7YxVIKGTGdg==" 601 | "resolved" "https://registry.npmjs.org/node-crc/-/node-crc-1.3.2.tgz" 602 | "version" "1.3.2" 603 | dependencies: 604 | "@types/node" "^15.6.1" 605 | "bindings" "^1.3.0" 606 | 607 | "node-fetch@^2.6.7": 608 | "integrity" "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==" 609 | "resolved" "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz" 610 | "version" "2.6.7" 611 | dependencies: 612 | "whatwg-url" "^5.0.0" 613 | 614 | "nopt@^5.0.0": 615 | "integrity" "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==" 616 | "resolved" "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz" 617 | "version" "5.0.0" 618 | dependencies: 619 | "abbrev" "1" 620 | 621 | "npmlog@^5.0.1": 622 | "integrity" "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==" 623 | "resolved" "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz" 624 | "version" "5.0.1" 625 | dependencies: 626 | "are-we-there-yet" "^2.0.0" 627 | "console-control-strings" "^1.1.0" 628 | "gauge" "^3.0.0" 629 | "set-blocking" "^2.0.0" 630 | 631 | "object-assign@^4.1.1": 632 | "integrity" "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" 633 | "resolved" "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" 634 | "version" "4.1.1" 635 | 636 | "on-change@^2.2.3": 637 | "integrity" "sha512-yx48YQW3XsMHYWJ5n8oOgonrxsIJJNn1fqE3QlQpYS/I6XHvzTARHzaVbwFyJoSaZ4g7UTZheaaxHVtFKcNXgg==" 638 | "resolved" "https://registry.npmjs.org/on-change/-/on-change-2.2.3.tgz" 639 | "version" "2.2.3" 640 | 641 | "once@^1.3.0", "once@^1.3.1", "once@^1.4.0": 642 | "integrity" "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==" 643 | "resolved" "https://registry.npmjs.org/once/-/once-1.4.0.tgz" 644 | "version" "1.4.0" 645 | dependencies: 646 | "wrappy" "1" 647 | 648 | "parse-cache-control@^1.0.1": 649 | "integrity" "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==" 650 | "resolved" "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz" 651 | "version" "1.0.1" 652 | 653 | "path-is-absolute@^1.0.0": 654 | "integrity" "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" 655 | "resolved" "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" 656 | "version" "1.0.1" 657 | 658 | "peek-readable@^5.0.0-alpha.5": 659 | "integrity" "sha512-pJohF/tDwV3ntnT5+EkUo4E700q/j/OCDuPxtM+5/kFGjyOai/sK4/We4Cy1MB2OiTQliWU5DxPvYIKQAdPqAA==" 660 | "resolved" "https://registry.npmjs.org/peek-readable/-/peek-readable-5.0.0-alpha.5.tgz" 661 | "version" "5.0.0-alpha.5" 662 | 663 | "prebuild-install@^7.1.0": 664 | "integrity" "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==" 665 | "resolved" "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz" 666 | "version" "7.1.1" 667 | dependencies: 668 | "detect-libc" "^2.0.0" 669 | "expand-template" "^2.0.3" 670 | "github-from-package" "0.0.0" 671 | "minimist" "^1.2.3" 672 | "mkdirp-classic" "^0.5.3" 673 | "napi-build-utils" "^1.0.1" 674 | "node-abi" "^3.3.0" 675 | "pump" "^3.0.0" 676 | "rc" "^1.2.7" 677 | "simple-get" "^4.0.0" 678 | "tar-fs" "^2.0.0" 679 | "tunnel-agent" "^0.6.0" 680 | 681 | "prism-media@^1.2.9": 682 | "integrity" "sha512-eW7LXORkTCQznZs+eqe9VjGOrLBxcBPXgNyHXMTSRVhphvd/RrxgIR7WaWt4fkLuhshcdT5KHL88LAfcvS3f5g==" 683 | "resolved" "https://registry.npmjs.org/prism-media/-/prism-media-1.3.4.tgz" 684 | "version" "1.3.4" 685 | 686 | "prism-media@^1.3.4": 687 | "integrity" "sha512-eW7LXORkTCQznZs+eqe9VjGOrLBxcBPXgNyHXMTSRVhphvd/RrxgIR7WaWt4fkLuhshcdT5KHL88LAfcvS3f5g==" 688 | "resolved" "https://registry.npmjs.org/prism-media/-/prism-media-1.3.4.tgz" 689 | "version" "1.3.4" 690 | 691 | "prism-media@^2.0.0-alpha.0": 692 | "integrity" "sha512-QL9rnO4xo0grgj7ptsA+AzSCYLirGWM4+ZcyboFmbkYHSgaXIESzHq/SXNizz2iHIfuM2og0cPhmSnTVMeFjKg==" 693 | "resolved" "https://registry.npmjs.org/prism-media/-/prism-media-2.0.0-alpha.0.tgz" 694 | "version" "2.0.0-alpha.0" 695 | dependencies: 696 | "duplex-child-process" "^1.0.1" 697 | 698 | "progress@^2.0.3": 699 | "integrity" "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" 700 | "resolved" "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz" 701 | "version" "2.0.3" 702 | 703 | "pump@^3.0.0": 704 | "integrity" "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==" 705 | "resolved" "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz" 706 | "version" "3.0.0" 707 | dependencies: 708 | "end-of-stream" "^1.1.0" 709 | "once" "^1.3.1" 710 | 711 | "randombytes@^2.1.0": 712 | "integrity" "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==" 713 | "resolved" "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz" 714 | "version" "2.1.0" 715 | dependencies: 716 | "safe-buffer" "^5.1.0" 717 | 718 | "rc@^1.2.7": 719 | "integrity" "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==" 720 | "resolved" "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz" 721 | "version" "1.2.8" 722 | dependencies: 723 | "deep-extend" "^0.6.0" 724 | "ini" "~1.3.0" 725 | "minimist" "^1.2.0" 726 | "strip-json-comments" "~2.0.1" 727 | 728 | "readable-stream@^3.0.2", "readable-stream@^3.1.1", "readable-stream@^3.4.0", "readable-stream@^3.6.0": 729 | "integrity" "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==" 730 | "resolved" "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz" 731 | "version" "3.6.0" 732 | dependencies: 733 | "inherits" "^2.0.3" 734 | "string_decoder" "^1.1.1" 735 | "util-deprecate" "^1.0.1" 736 | 737 | "readable-web-to-node-stream@^3.0.2": 738 | "integrity" "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==" 739 | "resolved" "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz" 740 | "version" "3.0.2" 741 | dependencies: 742 | "readable-stream" "^3.6.0" 743 | 744 | "rimraf@^3.0.2": 745 | "integrity" "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==" 746 | "resolved" "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" 747 | "version" "3.0.2" 748 | dependencies: 749 | "glob" "^7.1.3" 750 | 751 | "safe-buffer@^5.0.1", "safe-buffer@^5.1.0", "safe-buffer@~5.2.0": 752 | "integrity" "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 753 | "resolved" "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" 754 | "version" "5.2.1" 755 | 756 | "sax@^1.1.3", "sax@^1.2.4": 757 | "integrity" "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" 758 | "resolved" "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz" 759 | "version" "1.2.4" 760 | 761 | "semver@^6.0.0": 762 | "integrity" "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" 763 | "resolved" "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" 764 | "version" "6.3.0" 765 | 766 | "semver@^7.3.5": 767 | "integrity" "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==" 768 | "resolved" "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz" 769 | "version" "7.3.7" 770 | dependencies: 771 | "lru-cache" "^6.0.0" 772 | 773 | "serialize-javascript@^6.0.0": 774 | "integrity" "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==" 775 | "resolved" "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz" 776 | "version" "6.0.0" 777 | dependencies: 778 | "randombytes" "^2.1.0" 779 | 780 | "set-blocking@^2.0.0": 781 | "integrity" "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" 782 | "resolved" "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" 783 | "version" "2.0.0" 784 | 785 | "signal-exit@^3.0.0": 786 | "integrity" "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" 787 | "resolved" "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" 788 | "version" "3.0.7" 789 | 790 | "simple-concat@^1.0.0": 791 | "integrity" "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" 792 | "resolved" "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz" 793 | "version" "1.0.1" 794 | 795 | "simple-get@^4.0.0": 796 | "integrity" "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==" 797 | "resolved" "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz" 798 | "version" "4.0.1" 799 | dependencies: 800 | "decompress-response" "^6.0.0" 801 | "once" "^1.3.1" 802 | "simple-concat" "^1.0.0" 803 | 804 | "string_decoder@^1.1.1": 805 | "integrity" "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==" 806 | "resolved" "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" 807 | "version" "1.3.0" 808 | dependencies: 809 | "safe-buffer" "~5.2.0" 810 | 811 | "string-width@^1.0.2 || 2 || 3 || 4", "string-width@^4.2.3": 812 | "integrity" "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==" 813 | "resolved" "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" 814 | "version" "4.2.3" 815 | dependencies: 816 | "emoji-regex" "^8.0.0" 817 | "is-fullwidth-code-point" "^3.0.0" 818 | "strip-ansi" "^6.0.1" 819 | 820 | "strip-ansi@^6.0.1": 821 | "integrity" "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==" 822 | "resolved" "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" 823 | "version" "6.0.1" 824 | dependencies: 825 | "ansi-regex" "^5.0.1" 826 | 827 | "strip-json-comments@~2.0.1": 828 | "integrity" "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==" 829 | "resolved" "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" 830 | "version" "2.0.1" 831 | 832 | "strtok3@^7.0.0-alpha.7": 833 | "integrity" "sha512-G8WxjBFjTZ77toVElv1i7k3jCXNkBB14FVaZ/6LIOka/WGo4La5XHLrU7neFVLdKbXESZf4BejVKZu5maOmocA==" 834 | "resolved" "https://registry.npmjs.org/strtok3/-/strtok3-7.0.0-alpha.9.tgz" 835 | "version" "7.0.0-alpha.9" 836 | dependencies: 837 | "@tokenizer/token" "^0.3.0" 838 | "peek-readable" "^5.0.0-alpha.5" 839 | 840 | "tar-fs@^2.0.0": 841 | "integrity" "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==" 842 | "resolved" "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz" 843 | "version" "2.1.1" 844 | dependencies: 845 | "chownr" "^1.1.1" 846 | "mkdirp-classic" "^0.5.2" 847 | "pump" "^3.0.0" 848 | "tar-stream" "^2.1.4" 849 | 850 | "tar-stream@^2.1.4": 851 | "integrity" "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==" 852 | "resolved" "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz" 853 | "version" "2.2.0" 854 | dependencies: 855 | "bl" "^4.0.3" 856 | "end-of-stream" "^1.4.1" 857 | "fs-constants" "^1.0.0" 858 | "inherits" "^2.0.3" 859 | "readable-stream" "^3.1.1" 860 | 861 | "tar@^6.1.11": 862 | "integrity" "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==" 863 | "resolved" "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz" 864 | "version" "6.1.11" 865 | dependencies: 866 | "chownr" "^2.0.0" 867 | "fs-minipass" "^2.0.0" 868 | "minipass" "^3.0.0" 869 | "minizlib" "^2.1.1" 870 | "mkdirp" "^1.0.3" 871 | "yallist" "^4.0.0" 872 | 873 | "token-types@^5.0.0-alpha.2": 874 | "integrity" "sha512-EsG9UxAW4M6VATrEEjhPFTKEUi1OiJqTUMIZOGBN49fGxYjZB36k0p7to3HZSmWRoHm1QfZgrg3e02fpqAt5fQ==" 875 | "resolved" "https://registry.npmjs.org/token-types/-/token-types-5.0.0-alpha.2.tgz" 876 | "version" "5.0.0-alpha.2" 877 | dependencies: 878 | "@tokenizer/token" "^0.3.0" 879 | "ieee754" "^1.2.1" 880 | 881 | "tr46@~0.0.3": 882 | "integrity" "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" 883 | "resolved" "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" 884 | "version" "0.0.3" 885 | 886 | "ts-mixer@^6.0.1": 887 | "integrity" "sha512-hvE+ZYXuINrx6Ei6D6hz+PTim0Uf++dYbK9FFifLNwQj+RwKquhQpn868yZsCtJYiclZF1u8l6WZxxKi+vv7Rg==" 888 | "resolved" "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.1.tgz" 889 | "version" "6.0.1" 890 | 891 | "tslib@^2.4.0": 892 | "integrity" "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" 893 | "resolved" "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz" 894 | "version" "2.4.0" 895 | 896 | "tunnel-agent@^0.6.0": 897 | "integrity" "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==" 898 | "resolved" "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz" 899 | "version" "0.6.0" 900 | dependencies: 901 | "safe-buffer" "^5.0.1" 902 | 903 | "typedarray@^0.0.6": 904 | "integrity" "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" 905 | "resolved" "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" 906 | "version" "0.0.6" 907 | 908 | "undici@^5.7.0", "undici@^5.8.0": 909 | "integrity" "sha512-1F7Vtcez5w/LwH2G2tGnFIihuWUlc58YidwLiCv+jR2Z50x0tNXpRRw7eOIJ+GvqCqIkg9SB7NWAJ/T9TLfv8Q==" 910 | "resolved" "https://registry.npmjs.org/undici/-/undici-5.8.0.tgz" 911 | "version" "5.8.0" 912 | 913 | "util-deprecate@^1.0.1": 914 | "integrity" "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 915 | "resolved" "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" 916 | "version" "1.0.2" 917 | 918 | "webidl-conversions@^3.0.0": 919 | "integrity" "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" 920 | "resolved" "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" 921 | "version" "3.0.1" 922 | 923 | "whatwg-url@^5.0.0": 924 | "integrity" "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==" 925 | "resolved" "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" 926 | "version" "5.0.0" 927 | dependencies: 928 | "tr46" "~0.0.3" 929 | "webidl-conversions" "^3.0.0" 930 | 931 | "when@>= 0.0.1": 932 | "integrity" "sha512-5cZ7mecD3eYcMiCH4wtRPA5iFJZ50BJYDfckI5RRpQiktMiYTcn0ccLTZOvcbBume+1304fQztxeNzNS9Gvrnw==" 933 | "resolved" "https://registry.npmjs.org/when/-/when-3.7.8.tgz" 934 | "version" "3.7.8" 935 | 936 | "wide-align@^1.1.2": 937 | "integrity" "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==" 938 | "resolved" "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz" 939 | "version" "1.1.5" 940 | dependencies: 941 | "string-width" "^1.0.2 || 2 || 3 || 4" 942 | 943 | "wrappy@1": 944 | "integrity" "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" 945 | "resolved" "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" 946 | "version" "1.0.2" 947 | 948 | "ws@^8.8.1": 949 | "integrity" "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==" 950 | "resolved" "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz" 951 | "version" "8.8.1" 952 | 953 | "yallist@^4.0.0": 954 | "integrity" "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 955 | "resolved" "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" 956 | "version" "4.0.0" 957 | 958 | "youtube-sr@^4.2.0": 959 | "integrity" "sha512-GDHGrq/kVG1ZzFGBmjqC41m2Xz7aFGv0NY3NjxsHG4TVWT1PlEhxaUH/fhLKzBriZz/8IQcOflgdSLxW6+4i4g==" 960 | "resolved" "https://registry.npmjs.org/youtube-sr/-/youtube-sr-4.2.0.tgz" 961 | "version" "4.2.0" 962 | 963 | "ytdl-core@^4.11.0": 964 | "integrity" "sha512-Q3hCLiUA9AOGQXzPvno14GN+HgF9wsO1ZBHlj0COTcyxjIyFpWvMfii0UC4/cAbVaIjEdbWB71GdcGuc4J1Lmw==" 965 | "resolved" "https://registry.npmjs.org/ytdl-core/-/ytdl-core-4.11.0.tgz" 966 | "version" "4.11.0" 967 | dependencies: 968 | "m3u8stream" "^0.8.6" 969 | "miniget" "^4.2.2" 970 | "sax" "^1.1.3" 971 | --------------------------------------------------------------------------------