diff --git a/apps/music-bot/.eslintrc.json b/apps/music-bot/.eslintrc.json deleted file mode 100644 index 6c408c9b21..0000000000 --- a/apps/music-bot/.eslintrc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "@sapphire", - "ignorePatterns": ["src/ignore"], - "rules": { - // "no-return-await": "off" - } -} \ No newline at end of file diff --git a/apps/music-bot/.gitattributes b/apps/music-bot/.gitattributes deleted file mode 100644 index af3ad12812..0000000000 --- a/apps/music-bot/.gitattributes +++ /dev/null @@ -1,4 +0,0 @@ -/.yarn/** linguist-vendored -/.yarn/releases/* binary -/.yarn/plugins/**/* binary -/.pnp.* binary linguist-generated diff --git a/apps/music-bot/.gitignore b/apps/music-bot/.gitignore deleted file mode 100644 index 99d2d23e5c..0000000000 --- a/apps/music-bot/.gitignore +++ /dev/null @@ -1,32 +0,0 @@ -# Ignore a blackhole and the folder for development -node_modules/ -.vs/ -.idea/ -*.iml - -# Yarn files -.yarn/* -!.yarn/cache -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/sdks -!.yarn/versions - -# Other -.DS_Store -dist/ - -# Ignore the config file (contains sensitive information such as tokens) -config.ts - -# Ignore heapsnapshot and log files -*.heapsnapshot -*.log - -# Environment variables -.env -.env.local -.env.development.local -.env.test.local -.env.production.local diff --git a/apps/music-bot/.prettierignore b/apps/music-bot/.prettierignore deleted file mode 100644 index 763301fc00..0000000000 --- a/apps/music-bot/.prettierignore +++ /dev/null @@ -1,2 +0,0 @@ -dist/ -node_modules/ \ No newline at end of file diff --git a/apps/music-bot/.sapphirerc.json b/apps/music-bot/.sapphirerc.json deleted file mode 100644 index b8d29e8af8..0000000000 --- a/apps/music-bot/.sapphirerc.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/sapphiredev/cli/main/templates/schemas/.sapphirerc.scheme.json", - "projectLanguage": "ts", - "locations": { - "base": "src", - "arguments": "arguments", - "commands": "commands", - "listeners": "listeners", - "preconditions": "preconditions" - }, - "customFileTemplates": { - "enabled": true, - "location": "templates" - } -} \ No newline at end of file diff --git a/apps/music-bot/README.md b/apps/music-bot/README.md deleted file mode 100644 index 135ea1aa9e..0000000000 --- a/apps/music-bot/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Music Bot - -## ⚠️ This bot is a fork of [itsauric/auricle-music-bot](https://github.com/itsauric/auricle-music-bot) - -> Commit: [8099da1](https://github.com/itsauric/auricle-music-bot/commit/8099da11ed316785acfaa5d3c326351786fb788c) - -This bot is intended to be used while testing discord-player in development. diff --git a/apps/music-bot/package.json b/apps/music-bot/package.json deleted file mode 100644 index a073cddc8e..0000000000 --- a/apps/music-bot/package.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "music-bot", - "version": "1.0.0", - "main": "dist/index.js", - "author": "Androz2091 ", - "license": "MIT", - "dependencies": { - "@discord-player/equalizer": "workspace:^", - "@discord-player/extractor": "workspace:^", - "@discord-player/utils": "workspace:^", - "@discordjs/opus": "^0.9.0", - "@distube/ytdl-core": "^4.13.3", - "@sapphire/discord.js-utilities": "^6.0.3", - "@sapphire/duration": "^1.0.0", - "@sapphire/framework": "^4.2.1", - "@sapphire/plugin-api": "^5.0.1", - "@sapphire/plugin-hmr": "^2.0.0", - "@sapphire/plugin-logger": "^3.0.1", - "@skyra/env-utilities": "^1.1.0", - "colorette": "^2.0.19", - "discord-api-types": "^0.37.35", - "discord-player": "workspace:^", - "mediaplex": "^0.0.9", - "opusscript": "^0.0.8", - "play-dl": "^1.9.7", - "youtube-ext": "^1.1.23" - }, - "devDependencies": { - "@sapphire/prettier-config": "^1.4.5", - "@sapphire/ts-config": "^3.3.4", - "@swc/cli": "^0.1.62", - "@swc/core": "^1.3.37", - "@types/node": "^18.14.6", - "@types/ws": "^8.5.4", - "npm-run-all": "^4.1.5", - "prettier": "^2.8.4", - "tsc-watch": "^6.0.0", - "tsup": "^7.2.0", - "tsx": "^3.12.7", - "typescript": "^5.2.2", - "vitest": "^0.34.6" - }, - "scripts": { - "build": "tsup", - "start": "tsup && node dist/index.js", - "dev": "tsx ./src/index.ts", - "format": "prettier --write \"src/**/*.ts\"" - }, - "prettier": "@sapphire/prettier-config" -} diff --git a/apps/music-bot/src/.env.example b/apps/music-bot/src/.env.example deleted file mode 100644 index 907340fbf7..0000000000 --- a/apps/music-bot/src/.env.example +++ /dev/null @@ -1,3 +0,0 @@ -DISCORD_TOKEN= -OWNERS= -YOUTUBE_COOKIE= \ No newline at end of file diff --git a/apps/music-bot/src/KarasuClient.ts b/apps/music-bot/src/KarasuClient.ts deleted file mode 100644 index cd5e8453fa..0000000000 --- a/apps/music-bot/src/KarasuClient.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { BucketScope, LogLevel, SapphireClient } from '@sapphire/framework'; -import { Player } from 'discord-player'; -import { GatewayIntentBits } from 'discord.js'; -import Emojis from './emojis'; -import { envParseArray } from '@skyra/env-utilities'; -import * as Permissions from './lib/perms'; -import path from 'path'; -import { mkdirSync, existsSync } from 'fs'; - -export class KarasuClient extends SapphireClient { - public player: Player; - public dev: typeof Emojis; - public perms: typeof Permissions; - public recordingPath = path.resolve(`${__dirname}/recordings`); - public constructor() { - super({ - disableMentionPrefix: true, - intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates], - defaultCooldown: { - filteredUsers: envParseArray('OWNERS'), - scope: BucketScope.User, - delay: 10_000, - limit: 2 - }, - logger: { - level: LogLevel.Debug - } - }); - this.dev = Emojis; - this.perms = Permissions; - - this.player = new Player(this as any, { - ytdlOptions: { - requestOptions: { - headers: { - cookie: process.env.YOUTUBE_COOKIE - } - } - }, - skipFFmpeg: true - }); - - // this.player.events.on('willPlayTrack', (_, __, config, done) => { - // config.dispatcherConfig = { - // ...config.dispatcherConfig, - // disableBiquad: true, - // disableEqualizer: true, - // disableFilters: true, - // disableResampler: true, - // disableVolume: true - // }; - - // done(); - // }); - - if (!existsSync(this.recordingPath)) - mkdirSync(this.recordingPath, { - recursive: true - }); - } -} - -declare module 'discord.js' { - interface Client { - readonly player: Player; - readonly perms: typeof Permissions; - readonly dev: typeof Emojis; - readonly recordingPath: string; - } -} diff --git a/apps/music-bot/src/commands/applemusic.ts b/apps/music-bot/src/commands/applemusic.ts deleted file mode 100644 index 9f873e338d..0000000000 --- a/apps/music-bot/src/commands/applemusic.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { Command } from '@sapphire/framework'; -import { QueryType } from 'discord-player'; -import type { GuildMember } from 'discord.js'; - -export class AppleMusicCommand extends Command { - public constructor(context: Command.Context, options: Command.Options) { - super(context, { - ...options, - description: 'Plays and enqueues track(s) of the query provided from apple music' - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => { - builder // - .setName(this.name) - .setDescription(this.description) - .addStringOption((option) => { - return option.setName('query').setDescription('A query of your choice').setRequired(true).setAutocomplete(true); - }); - }); - } - - public override async autocompleteRun(interaction: Command.AutocompleteInteraction) { - const query = interaction.options.getString('query'); - const results = await this.container.client.player.search(query!, { - searchEngine: QueryType.APPLE_MUSIC_SEARCH - }); - - return interaction.respond( - results.tracks.slice(0, 10).map((t) => ({ - name: t.title, - value: t.url - })) - ); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const member = interaction.member as GuildMember; - const permissions = this.container.client.perms.voice(interaction, this.container.client); - if (permissions.member()) return interaction.reply({ content: permissions.member(), ephemeral: true }); - if (permissions.client()) return interaction.reply({ content: permissions.client(), ephemeral: true }); - - const query = interaction.options.getString('query'); - - if (permissions.clientToMember()) return interaction.reply({ content: permissions.clientToMember(), ephemeral: true }); - - const results = await this.container.client.player.search(query!, { - searchEngine: QueryType.APPLE_MUSIC_SEARCH - }); - - if (!results.hasTracks()) - return interaction.reply({ - content: `${this.container.client.dev.error} | No tracks were found for your query`, - ephemeral: true - }); - - await interaction.deferReply(); - await interaction.editReply({ content: `⏳ | Loading ${results.playlist ? 'a playlist...' : 'a track...'}` }); - - try { - const res = await this.container.client.player.play(member.voice.channel!.id, results, { - nodeOptions: { - metadata: { - channel: interaction.channel, - client: interaction.guild?.members.me, - requestedBy: interaction.user.username - }, - leaveOnEmptyCooldown: 300000, - leaveOnEmpty: true, - leaveOnEnd: false - } - }); - - await interaction.editReply({ - content: `${this.container.client.dev.success} | Successfully enqueued${ - res.track.playlist ? ` **multiple tracks** from: **${res.track.playlist.title}**` : `: **${res.track.cleanTitle}**` - }` - }); - } catch (error: any) { - await interaction.editReply({ content: `${this.container.client.dev.error} | An error has occurred` }); - return console.log(error); - } - } -} diff --git a/apps/music-bot/src/commands/biquad.ts b/apps/music-bot/src/commands/biquad.ts deleted file mode 100644 index 788f50133c..0000000000 --- a/apps/music-bot/src/commands/biquad.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Command } from '@sapphire/framework'; -import { BiquadFilterType, useQueue } from 'discord-player'; -import type { APIApplicationCommandOptionChoice } from 'discord.js'; - -type SupportedBiquadFilters = keyof typeof BiquadFilterType | 'Off'; - -export class BiquadCommand extends Command { - public constructor(context: Command.Context, options: Command.Options) { - super(context, { - ...options, - description: 'The biquad filter that can be applied to tracks' - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - const biquadFilters = Object.keys(BiquadFilterType) - .filter((k) => typeof k[0] === 'string') - .map((m) => ({ - name: m, - value: m - })) as APIApplicationCommandOptionChoice[]; - - biquadFilters.unshift({ - name: 'Disable', - value: 'Off' - }); - - registry.registerChatInputCommand((builder) => { - builder // - .setName(this.name) - .setDescription(this.description) - .addStringOption((option) => - option - .setName('filter') - .setDescription('The biquad filter to use') - .addChoices(...biquadFilters) - .setRequired(true) - ) - .addNumberOption((option) => { - return option.setMinValue(-50).setMaxValue(50).setName('gain').setDescription('The dB gain value').setRequired(false); - }); - }); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const queue = useQueue(interaction.guild!.id); - const permissions = this.container.client.perms.voice(interaction, this.container.client); - const filter = interaction.options.getString('filter', true) as SupportedBiquadFilters; - const dB = interaction.options.getNumber('gain'); - - if (!queue) return interaction.reply({ content: `${this.container.client.dev.error} | I am **not** in a voice channel`, ephemeral: true }); - if (!queue.currentTrack) - return interaction.reply({ content: `${this.container.client.dev.error} | There is no track **currently** playing`, ephemeral: true }); - if (permissions.clientToMember()) return interaction.reply({ content: permissions.clientToMember(), ephemeral: true }); - - if (!queue.filters.biquad) - return interaction.reply({ - content: `${this.container.client.dev.error} | The biquad filter is **not available** to be used in this queue`, - ephemeral: true - }); - - if (filter === 'Off') { - queue.filters.biquad.disable(); - } else { - if (typeof dB === 'number') queue.filters.biquad.setGain(dB); - queue.filters.biquad.enable(); - queue.filters.biquad.setFilter(BiquadFilterType[filter]); - } - - return interaction.reply({ - content: `${this.container.client.dev.success} | **Biquad filter** set to: \`${filter}\`` - }); - } -} diff --git a/apps/music-bot/src/commands/clear.ts b/apps/music-bot/src/commands/clear.ts deleted file mode 100644 index dc7ff6b268..0000000000 --- a/apps/music-bot/src/commands/clear.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Command } from '@sapphire/framework'; -import { useQueue } from 'discord-player'; - -export class ClearCommand extends Command { - public constructor(context: Command.Context, options: Command.Options) { - super(context, { - ...options, - description: 'Clears the current queue and removes all enqueued tracks' - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => { - builder // - .setName(this.name) - .setDescription(this.description) - .addBooleanOption((option) => option.setName('history').setDescription('Clear the queue history')); - }); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const queue = useQueue(interaction.guild!.id); - const history = interaction.options.getBoolean('history'); - const permissions = this.container.client.perms.voice(interaction, this.container.client); - - if (!queue) return interaction.reply({ content: `${this.container.client.dev.error} | I am **not** in a voice channel`, ephemeral: true }); - if (!queue.tracks) - return interaction.reply({ content: `${this.container.client.dev.error} | There is **nothing** to clear`, ephemeral: true }); - if (permissions.clientToMember()) return interaction.reply({ content: permissions.clientToMember(), ephemeral: true }); - - queue.tracks.clear(); - if (history) queue.history.clear(); - return interaction.reply({ - content: `${this.container.client.dev.success} | I have **cleared** the queue` - }); - } -} diff --git a/apps/music-bot/src/commands/connect.ts b/apps/music-bot/src/commands/connect.ts deleted file mode 100644 index d74ff21c90..0000000000 --- a/apps/music-bot/src/commands/connect.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Command } from '@sapphire/framework'; -import { useMainPlayer, useQueue } from 'discord-player'; -import { GuildMember } from 'discord.js'; - -export class DisconnectCommand extends Command { - public constructor(context: Command.Context, options: Command.Options) { - super(context, { - ...options, - description: 'Connects the bot to the voice channel while also creating a new queue' - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => { - builder // - .setName(this.name) - .setDescription(this.description); - }); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - if (interaction.member instanceof GuildMember) { - const permissions = this.container.client.perms.voice(interaction, this.container.client); - if (permissions.member()) return interaction.reply({ content: permissions.member(), ephemeral: true }); - if (permissions.client()) return interaction.reply({ content: permissions.client(), ephemeral: true }); - const queue = useQueue(interaction.guild!.id); - const player = useMainPlayer(); - - if (queue) - return interaction.reply({ content: `${this.container.client.dev.error} | I am **already** in a voice channel`, ephemeral: true }); - - const newQueue = player?.queues.create(interaction.guild!.id, { - metadata: { - channel: interaction.channel, - client: interaction.guild?.members.me - }, - leaveOnEmptyCooldown: 300000, - leaveOnEmpty: true, - leaveOnEnd: false, - bufferingTimeout: 0, - volume: 10, - defaultFFmpegFilters: ['lofi', 'bassboost', 'normalizer'] - }); - await newQueue?.connect(interaction.member.voice.channel!.id); - return interaction.reply({ - content: `${this.container.client.dev.success} | I have **successfully connected** to the voice channel` - }); - } - } -} diff --git a/apps/music-bot/src/commands/disconnect.ts b/apps/music-bot/src/commands/disconnect.ts deleted file mode 100644 index 62c7219e39..0000000000 --- a/apps/music-bot/src/commands/disconnect.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Command } from '@sapphire/framework'; -import { useQueue } from 'discord-player'; - -export class DisconnectCommand extends Command { - public constructor(context: Command.Context, options: Command.Options) { - super(context, { - ...options, - description: 'Disconnects the bot from the voice channel and deletes the queue' - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => { - builder // - .setName(this.name) - .setDescription(this.description); - }); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const queue = useQueue(interaction.guild!.id); - const permissions = this.container.client.perms.voice(interaction, this.container.client); - - if (!queue) return interaction.reply({ content: `${this.container.client.dev.error} | I am **not** in a voice channel`, ephemeral: true }); - if (permissions.clientToMember()) return interaction.reply({ content: permissions.clientToMember(), ephemeral: true }); - - queue.delete(); - return interaction.reply({ - content: `${this.container.client.dev.success} | I have **successfully disconnected** from the voice channel` - }); - } -} diff --git a/apps/music-bot/src/commands/dsp.ts b/apps/music-bot/src/commands/dsp.ts deleted file mode 100644 index 94e6e116aa..0000000000 --- a/apps/music-bot/src/commands/dsp.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Command } from '@sapphire/framework'; -import { PCMAudioFilters, PCMFilters, useQueue } from 'discord-player'; - -export class PulsatorCommand extends Command { - public constructor(context: Command.Context, options: Command.Options) { - super(context, { - ...options, - description: 'The DSP filters that can be applied to tracks' - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => { - builder // - .setName(this.name) - .setDescription(this.description) - .addStringOption((option) => - option - .setName('filter') - .setDescription('The filter to toggle') - .addChoices( - ...Object.keys(PCMAudioFilters).map((m) => ({ - name: m, - value: m - })) - ) - .setRequired(true) - ); - }); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const queue = useQueue(interaction.guild!.id); - const permissions = this.container.client.perms.voice(interaction, this.container.client); - const filter = interaction.options.getString('filter') as PCMFilters; - - if (!queue) return interaction.reply({ content: `${this.container.client.dev.error} | I am **not** in a voice channel`, ephemeral: true }); - if (!queue.currentTrack) - return interaction.reply({ content: `${this.container.client.dev.error} | There is no track **currently** playing`, ephemeral: true }); - if (permissions.clientToMember()) return interaction.reply({ content: permissions.clientToMember(), ephemeral: true }); - - if (!queue.filters.filters) - return interaction.reply({ - content: `${this.container.client.dev.error} | The DSP filters are **not available** to be used in this queue`, - ephemeral: true - }); - - let ff = queue.filters.filters.filters; - if (ff.includes(filter)) { - ff = ff.filter((r) => r !== filter); - } else { - ff.push(filter); - } - - queue.filters.filters.setFilters(ff); - - return interaction.reply({ - content: `${this.container.client.dev.success} | **${filter}** filter has been **${ff.includes(filter) ? 'enabled' : 'disabled'}**` - }); - } -} diff --git a/apps/music-bot/src/commands/equalizer.ts b/apps/music-bot/src/commands/equalizer.ts deleted file mode 100644 index 264a3e87ee..0000000000 --- a/apps/music-bot/src/commands/equalizer.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Command } from '@sapphire/framework'; -import { EqualizerConfigurationPreset, useQueue } from 'discord-player'; - -export class EqualizerCommand extends Command { - public constructor(context: Command.Context, options: Command.Options) { - super(context, { - ...options, - description: 'The equalizer filter that can be applied to tracks' - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => { - builder // - .setName(this.name) - .setDescription(this.description) - .addStringOption((option) => - option - .setName('preset') - .setDescription('The equalizer filter to use') - .addChoices( - ...Object.keys(EqualizerConfigurationPreset).map((m) => ({ - name: m, - value: m - })) - ) - .setRequired(true) - ); - }); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const queue = useQueue(interaction.guild!.id); - const permissions = this.container.client.perms.voice(interaction, this.container.client); - const preset = interaction.options.getString('preset') as string; - - if (!queue) return interaction.reply({ content: `${this.container.client.dev.error} | I am **not** in a voice channel`, ephemeral: true }); - if (!queue.currentTrack) - return interaction.reply({ content: `${this.container.client.dev.error} | There is no track **currently** playing`, ephemeral: true }); - if (permissions.clientToMember()) return interaction.reply({ content: permissions.clientToMember(), ephemeral: true }); - - if (!queue.filters.equalizer) - return interaction.reply({ - content: `${this.container.client.dev.error} | The equalizer filter is **not available** to be used in this queue`, - ephemeral: true - }); - - queue.filters.equalizer.setEQ(EqualizerConfigurationPreset[preset]); - queue.filters.equalizer.enable(); - - return interaction.reply({ - content: `${this.container.client.dev.success} | **Equalizer** filter has been set to: **\`${preset}\`**` - }); - } -} diff --git a/apps/music-bot/src/commands/filters.ts b/apps/music-bot/src/commands/filters.ts deleted file mode 100644 index 74810b16d1..0000000000 --- a/apps/music-bot/src/commands/filters.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Command } from '@sapphire/framework'; -import { FiltersName, useQueue } from 'discord-player'; - -export class FiltersCommand extends Command { - public constructor(context: Command.Context, options: Command.Options) { - super(context, { - ...options, - description: 'The FFmpeg filters that can be applied to tracks' - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => { - builder // - .setName(this.name) - .setDescription(this.description) - .addStringOption((option) => - option - .setName('filter') - .setDescription('The FFmpeg filter to use') - .addChoices( - { name: 'Off', value: 'Off' }, - ...([ - { name: 'lofi', value: 'lofi' }, - { name: '8D', value: '8D' }, - { name: 'bassboost', value: 'bassboost' }, - { name: 'compressor', value: 'compressor' }, - { name: 'karaoke', value: 'karaoke' }, - { name: 'vibrato', value: 'vibrato' }, - { name: 'vaporwave', value: 'vaporwave' }, - { name: 'nightcore', value: 'nightcore' }, - { name: 'tremolo', value: 'tremolo' } - ] as { name: FiltersName; value: FiltersName }[]) - ) - .setRequired(true) - ); - }); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const queue = useQueue(interaction.guild!.id); - const permissions = this.container.client.perms.voice(interaction, this.container.client); - const filter = interaction.options.getString('filter') as FiltersName | 'Off'; - - if (!queue) return interaction.reply({ content: `${this.container.client.dev.error} | I am **not** in a voice channel`, ephemeral: true }); - if (!queue.currentTrack) - return interaction.reply({ content: `${this.container.client.dev.error} | There is no track **currently** playing`, ephemeral: true }); - if (permissions.clientToMember()) return interaction.reply({ content: permissions.clientToMember(), ephemeral: true }); - - if (!queue.filters.ffmpeg) - return interaction.reply({ - content: `${this.container.client.dev.error} | The FFmpeg filters are **not available** to be used in this queue`, - ephemeral: true - }); - - if (filter === 'Off') { - await queue.filters.ffmpeg.setFilters(false); - return interaction.reply({ - content: `${this.container.client.dev.success} | **Audio** filter has been **disabled**` - }); - } - - await queue.filters.ffmpeg.toggle(filter.includes('bassboost') ? ['bassboost', 'normalizer'] : filter); - - return interaction.reply({ - content: `${this.container.client.dev.success} | **${filter}** filter has been **${ - queue.filters.ffmpeg.isEnabled(filter) ? 'enabled' : 'disabled' - }**` - }); - } -} diff --git a/apps/music-bot/src/commands/history.ts b/apps/music-bot/src/commands/history.ts deleted file mode 100644 index e73f0e4666..0000000000 --- a/apps/music-bot/src/commands/history.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { PaginatedMessage } from '@sapphire/discord.js-utilities'; -import { Command } from '@sapphire/framework'; -import { useHistory, useQueue } from 'discord-player'; - -export class HistoryCommand extends Command { - public constructor(context: Command.Context, options: Command.Options) { - super(context, { - ...options, - description: 'Displays the queue history in an embed' - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => { - builder // - .setName(this.name) - .setDescription(this.description); - }); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const queue = useQueue(interaction.guild!.id); - const history = useHistory(interaction.guild!.id); - - if (!queue) return interaction.reply({ content: `${this.container.client.dev.error} | I am **not** in a voice channel`, ephemeral: true }); - if (!history?.tracks) - return interaction.reply({ - content: `${this.container.client.dev.error} | There is **no** queue history to **display**`, - ephemeral: true - }); - - let pagesNum = Math.ceil(queue.tracks.size / 5); - - if (pagesNum <= 0) { - pagesNum = 1; - } - - const tracks = history.tracks.map((track, idx) => `**${++idx})** [${track.cleanTitle}](${track.url})`); - - const paginatedMessage = new PaginatedMessage(); - - // handle error if pages exceed 25 pages - if (pagesNum > 25) pagesNum = 25; - - for (let i = 0; i < pagesNum; i++) { - const list = tracks.slice(i * 5, i * 5 + 5).join('\n'); - - paginatedMessage.addPageEmbed((embed) => - embed - .setColor('Red') - .setDescription( - `**Queue history** for **session** in **${queue.channel?.name}:**\n${ - list === '' ? '\n*• No more queued tracks*' : `\n${list}` - } - \n` - ) - .setFooter({ - text: `${queue.tracks.size} track(s) in queue` - }) - ); - } - - return paginatedMessage.run(interaction); - } -} diff --git a/apps/music-bot/src/commands/jump.ts b/apps/music-bot/src/commands/jump.ts deleted file mode 100644 index 9a82014c4b..0000000000 --- a/apps/music-bot/src/commands/jump.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { Command } from '@sapphire/framework'; -import { useQueue } from 'discord-player'; - -export class JumpCommand extends Command { - public constructor(context: Command.Context, options: Command.Options) { - super(context, { - ...options, - description: 'Jumps to the given track without removing any previous tracks' - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => { - builder // - .setName(this.name) - .setDescription(this.description) - .addIntegerOption((option) => - option.setName('track').setDescription('The track you want to jump to').setMinValue(1).setRequired(true).setAutocomplete(true) - ); - }); - } - - public override async autocompleteRun(interaction: Command.AutocompleteInteraction) { - const queue = useQueue(interaction.guild!.id); - const track = interaction.options.getInteger('track'); - const jump = queue?.tracks.at(track!); - const position = queue?.node.getTrackPosition(jump!); - - const tracks = queue!.tracks.map((t, idx) => ({ - name: t.title, - value: ++idx - })); - - if (jump?.title && !tracks.some((t) => t.name === jump.title)) { - tracks.unshift({ - name: jump.title, - value: position! - }); - } - - let slicedTracks = tracks.slice(0, 5); - if (track) { - slicedTracks = tracks.slice(track - 1, track + 4); - if (slicedTracks.length > 5) { - slicedTracks = slicedTracks.slice(0, 5); - } - } - - return interaction.respond(slicedTracks); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const queue = useQueue(interaction.guild!.id); - const permissions = this.container.client.perms.voice(interaction, this.container.client); - - if (!queue) return interaction.reply({ content: `${this.container.client.dev.error} | I am **not** in a voice channel`, ephemeral: true }); - if (!queue.tracks) - return interaction.reply({ content: `${this.container.client.dev.error} | There are **no tracks** to **jump** to`, ephemeral: true }); - if (permissions.clientToMember()) return interaction.reply({ content: permissions.clientToMember(), ephemeral: true }); - - const jump = interaction.options.getInteger('track')! - 1; - const trackResolvable = queue.tracks.at(jump!); - - if (!trackResolvable) - return interaction.reply({ content: `${this.container.client.dev.error} | The **requested track** doesn't **exist**`, ephemeral: true }); - - queue.node.jump(trackResolvable); - return interaction.reply({ - content: `⏩ | I have **jumped** to the track: **${trackResolvable.title}**` - }); - } -} diff --git a/apps/music-bot/src/commands/loop.ts b/apps/music-bot/src/commands/loop.ts deleted file mode 100644 index b808447c2f..0000000000 --- a/apps/music-bot/src/commands/loop.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Command } from '@sapphire/framework'; -import { QueueRepeatMode, useQueue } from 'discord-player'; - -const repeatModes = [ - { name: 'Off', value: QueueRepeatMode.OFF }, - { name: 'Track', value: QueueRepeatMode.TRACK }, - { name: 'Queue', value: QueueRepeatMode.QUEUE }, - { name: 'Autoplay', value: QueueRepeatMode.AUTOPLAY } -]; - -export class LoopCommand extends Command { - public constructor(context: Command.Context, options: Command.Options) { - super(context, { - ...options, - description: 'Loops the current playing track or the entire queue' - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => { - builder // - .setName(this.name) - .setDescription(this.description) - .addNumberOption((option) => - option - .setName('mode') - .setDescription('Choose a loop mode') - .setRequired(true) - .addChoices(...repeatModes) - ); - }); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const queue = useQueue(interaction.guild!.id); - const permissions = this.container.client.perms.voice(interaction, this.container.client); - - if (!queue) return interaction.reply({ content: `${this.container.client.dev.error} | I am **not** in a voice channel`, ephemeral: true }); - if (!queue.currentTrack) - return interaction.reply({ content: `${this.container.client.dev.error} | There is no track **currently** playing`, ephemeral: true }); - if (permissions.clientToMember()) return interaction.reply({ content: permissions.clientToMember(), ephemeral: true }); - - const mode = interaction.options.getNumber('mode', true); - const name = mode === QueueRepeatMode.OFF ? 'Looping' : repeatModes.find((m) => m.value === mode)?.name; - - queue.setRepeatMode(mode as QueueRepeatMode); - - return interaction.reply({ - content: `${this.container.client.dev.success} | **${name}** has been **${mode === queue.repeatMode ? 'enabled' : 'disabled'}**` - }); - } -} diff --git a/apps/music-bot/src/commands/lyrics.ts b/apps/music-bot/src/commands/lyrics.ts deleted file mode 100644 index 6017a9303b..0000000000 --- a/apps/music-bot/src/commands/lyrics.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { Command } from '@sapphire/framework'; -import { useQueue, useMainPlayer, Util } from 'discord-player'; -import { EmbedBuilder } from 'discord.js'; - -export class LyricsCommand extends Command { - public constructor(context: Command.Context, options: Command.Options) { - super(context, { - ...options, - description: 'Displays lyrics of the given track' - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => { - builder // - .setName(this.name) - .setDescription(this.description) - .addStringOption((option) => { - return option.setName('name').setDescription('The track of the lyrics to search').setRequired(false); - }); - }); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const player = useMainPlayer(); - const queue = useQueue(interaction.guild!.id); - const track = interaction.options.getString('name') || (queue?.currentTrack?.cleanTitle as string); - - await interaction.deferReply(); - - const results = await player.lyrics - .search({ - q: track - }) - .catch((e) => { - console.log(e); - }); - - const lyrics = results?.[0]; - - if (!lyrics?.syncedLyrics && !lyrics?.plainLyrics) - return interaction.editReply({ content: `${this.container.client.dev.error} | No lyrics found for this track` }); - - if (lyrics.syncedLyrics) { - const syncedLyrics = queue?.syncedLyrics(lyrics); - - syncedLyrics?.onChange(async (lyrics, timestamp) => { - await interaction.channel?.send({ - content: `[${Util.formatDuration(timestamp)}]: ${lyrics}` - }); - }); - - syncedLyrics?.subscribe(); - } - - const trimmedLyrics = (lyrics.plainLyrics || lyrics.syncedLyrics || '').substring(0, 1997); - - const embed = new EmbedBuilder() - .setTitle(lyrics.trackName) - .setAuthor({ - name: lyrics.artistName - }) - .setDescription(trimmedLyrics.length === 1997 ? `${trimmedLyrics}...` : trimmedLyrics) - .setColor('Yellow'); - - return interaction.editReply({ embeds: [embed] }); - } -} diff --git a/apps/music-bot/src/commands/nowplaying.ts b/apps/music-bot/src/commands/nowplaying.ts deleted file mode 100644 index f0380ca3c0..0000000000 --- a/apps/music-bot/src/commands/nowplaying.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Command } from '@sapphire/framework'; -import { useQueue, useTimeline } from 'discord-player'; -import { EmbedBuilder } from 'discord.js'; - -export class NowPlayingCommand extends Command { - public constructor(context: Command.Context, options: Command.Options) { - super(context, { - ...options, - description: 'Displays the current track in an embed' - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => { - builder // - .setName(this.name) - .setDescription(this.description); - }); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const queue = useQueue(interaction.guild!.id); - const timeline = useTimeline(interaction.guild!.id)!; - - if (!queue) return interaction.reply({ content: `${this.container.client.dev.error} | I am **not** in a voice channel`, ephemeral: true }); - if (!queue.currentTrack) - return interaction.reply({ content: `${this.container.client.dev.error} | There is no track **currently** playing`, ephemeral: true }); - - const track = queue.currentTrack; - - const embed = new EmbedBuilder() - .setAuthor({ - name: interaction.user.username, - iconURL: interaction.user.displayAvatarURL() - }) - .setColor('Red') - .setTitle('💿 Now Playing') - .setDescription(`[${track.cleanTitle}](${track.url})`) - .setThumbnail(track.thumbnail ?? interaction.user.displayAvatarURL()) - .addFields([ - { name: 'Author', value: track.author }, - { name: 'Progress', value: `${queue.node.createProgressBar()} (${timeline.timestamp?.progress}%)` }, - { name: 'Extractor', value: `\`${track.extractor?.identifier || 'N/A'}\`` } - ]) - .setFooter({ - text: `Ping: ${queue.ping}ms | Event Loop Lag: ${queue.player.eventLoopLag.toFixed(0)}ms` - }); - - return interaction.reply({ embeds: [embed] }); - } -} diff --git a/apps/music-bot/src/commands/pause.ts b/apps/music-bot/src/commands/pause.ts deleted file mode 100644 index 18b624409a..0000000000 --- a/apps/music-bot/src/commands/pause.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Command } from '@sapphire/framework'; -import { useQueue, useTimeline } from 'discord-player'; - -export class PauseCommand extends Command { - public constructor(context: Command.Context, options: Command.Options) { - super(context, { - ...options, - description: 'Pauses or resumes the current track' - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => { - builder // - .setName(this.name) - .setDescription(this.description); - }); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const queue = useQueue(interaction.guild!.id); - const timeline = useTimeline(interaction.guild!.id)!; - const permissions = this.container.client.perms.voice(interaction, this.container.client); - - if (!queue) return interaction.reply({ content: `${this.container.client.dev.error} | I am **not** in a voice channel`, ephemeral: true }); - if (!queue.currentTrack) - return interaction.reply({ content: `${this.container.client.dev.error} | There is no track **currently** playing`, ephemeral: true }); - if (permissions.clientToMember()) return interaction.reply({ content: permissions.clientToMember(), ephemeral: true }); - - timeline.paused ? timeline.resume() : timeline.pause(); - const state = timeline.paused; - return interaction.reply({ content: `${this.container.client.dev.success} | **Playback** has been **${state ? 'paused' : 'resumed'}**` }); - } -} diff --git a/apps/music-bot/src/commands/ping.ts b/apps/music-bot/src/commands/ping.ts deleted file mode 100644 index 3eb498a1d2..0000000000 --- a/apps/music-bot/src/commands/ping.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { isMessageInstance } from '@sapphire/discord.js-utilities'; -import { Command } from '@sapphire/framework'; -import { ApplicationCommandType } from 'discord.js'; - -export class PingCommand extends Command { - public constructor(context: Command.Context, options: Command.Options) { - super(context, { - ...options, - description: 'Returns the round trip and heartbeat' - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => { - builder // - .setName(this.name) - .setDescription(this.description); - }); - registry.registerContextMenuCommand((builder) => { - builder // - .setName(this.name) - .setType(ApplicationCommandType.Message); - }); - registry.registerContextMenuCommand((builder) => { - builder // - .setName(this.name) - .setType(ApplicationCommandType.User); - }); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const msg = await interaction.reply({ content: `Ping?`, fetchReply: true }); - - if (isMessageInstance(msg)) { - const diff = msg.createdTimestamp - interaction.createdTimestamp; - const ping = Math.round(this.container.client.ws.ping); - return interaction.editReply(`The round trip took **${diff}ms** and the heartbeat being **${ping}ms**`); - } - - return interaction.editReply('Failed to retrieve ping...'); - } - - // context menu command - public async contextMenuRun(interaction: Command.ContextMenuCommandInteraction) { - const msg = await interaction.reply({ content: `Ping?`, fetchReply: true }); - - if (isMessageInstance(msg)) { - const diff = msg.createdTimestamp - interaction.createdTimestamp; - const ping = Math.round(this.container.client.ws.ping); - return interaction.editReply(`The round trip took **${diff}ms** and the heartbeat being **${ping}ms**`); - } - - return interaction.editReply('Failed to retrieve ping...'); - } -} diff --git a/apps/music-bot/src/commands/play.ts b/apps/music-bot/src/commands/play.ts deleted file mode 100644 index 62343195e0..0000000000 --- a/apps/music-bot/src/commands/play.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { Command } from '@sapphire/framework'; -import { QueryType, useMainPlayer } from 'discord-player'; -import type { GuildMember } from 'discord.js'; - -export class PlayCommand extends Command { - public constructor(context: Command.Context, options: Command.Options) { - super(context, { - ...options, - description: 'Plays and enqueues track(s) of the query provided' - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => { - builder // - .setName(this.name) - .setDescription(this.description) - .addStringOption((option) => { - return option.setName('query').setDescription('A query of your choice').setRequired(true).setAutocomplete(true); - }); - }); - } - - public override async autocompleteRun(interaction: Command.AutocompleteInteraction) { - const query = interaction.options.getString('query'); - if (!query) return []; - - const player = useMainPlayer(); - - const results = await player!.search(query!, { - requestedBy: interaction.user, - fallbackSearchEngine: QueryType.YOUTUBE_SEARCH - }); - - let tracks; - tracks = results!.tracks - .map((t) => ({ - name: t.title, - value: t.url - })) - .slice(0, 10); - - if (results.playlist) { - tracks = results!.tracks - .map(() => ({ - name: `${results.playlist!.title} [playlist]`, - value: results.playlist!.url - })) - .slice(0, 1); - } - - return interaction.respond(tracks).catch(() => null); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const player = useMainPlayer(); - const member = interaction.member as GuildMember; - const permissions = this.container.client.perms.voice(interaction, this.container.client); - if (permissions.member()) return interaction.reply({ content: permissions.member(), ephemeral: true }); - if (permissions.client()) return interaction.reply({ content: permissions.client(), ephemeral: true }); - - const query = interaction.options.getString('query'); - - if (permissions.clientToMember()) return interaction.reply({ content: permissions.clientToMember(), ephemeral: true }); - await interaction.deferReply(); - const results = await player!.search(query!, { - requestedBy: interaction.user, - fallbackSearchEngine: QueryType.YOUTUBE_SEARCH - }); - - if (!results.hasTracks()) - return interaction.editReply({ - content: `${this.container.client.dev.error} | **No** tracks were found for your query` - }); - - try { - const res = await player!.play(member.voice.channel!.id, results, { - nodeOptions: { - metadata: { - channel: interaction.channel, - client: interaction.guild?.members.me, - requestedBy: interaction.user.username - }, - leaveOnEmptyCooldown: 300000, - leaveOnEmpty: true, - leaveOnEnd: false, - pauseOnEmpty: true, - bufferingTimeout: 0, - volume: 50 - // defaultFFmpegFilters: ['silenceremove'] - } - }); - - return interaction.editReply({ - content: `${this.container.client.dev.success} | Successfully enqueued${ - res.track.playlist ? ` **track(s)** from: **${res.track.playlist.title}**` : `: **${res.track.cleanTitle}**` - }` - }); - } catch (error: any) { - await interaction.editReply({ content: `${this.container.client.dev.error} | An **error** has occurred` }); - return console.log(error); - } - } -} diff --git a/apps/music-bot/src/commands/previous.ts b/apps/music-bot/src/commands/previous.ts deleted file mode 100644 index 536a208dd0..0000000000 --- a/apps/music-bot/src/commands/previous.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Command } from '@sapphire/framework'; -import { useHistory, useQueue } from 'discord-player'; - -export class PreviousCommand extends Command { - public constructor(context: Command.Context, options: Command.Options) { - super(context, { - ...options, - description: 'Plays the previous track' - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => { - builder // - .setName(this.name) - .setDescription(this.description); - }); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const queue = useQueue(interaction.guild!.id); - const history = useHistory(interaction.guild!.id); - const permissions = this.container.client.perms.voice(interaction, this.container.client); - - if (!queue) return interaction.reply({ content: `${this.container.client.dev.error} | I am **not** in a voice channel`, ephemeral: true }); - if (permissions.clientToMember()) return interaction.reply({ content: permissions.clientToMember(), ephemeral: true }); - - if (!history?.previousTrack) - return interaction.reply({ - content: `${this.container.client.dev.error} | There is **no** previous track in the **history**`, - ephemeral: true - }); - - await history.previous(); - return interaction.reply({ - content: `🔁 | I am **replaying** the previous track` - }); - } -} diff --git a/apps/music-bot/src/commands/queue.ts b/apps/music-bot/src/commands/queue.ts deleted file mode 100644 index e4a9975c85..0000000000 --- a/apps/music-bot/src/commands/queue.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { PaginatedMessage } from '@sapphire/discord.js-utilities'; -import { Command } from '@sapphire/framework'; -import { useQueue } from 'discord-player'; - -export class QueueCommand extends Command { - public constructor(context: Command.Context, options: Command.Options) { - super(context, { - ...options, - description: 'Displays the queue in an embed' - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => { - builder // - .setName(this.name) - .setDescription(this.description); - }); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const queue = useQueue(interaction.guild!.id); - - if (!queue) return interaction.reply({ content: `${this.container.client.dev.error} | I am **not** in a voice channel`, ephemeral: true }); - if (!queue.tracks || !queue.currentTrack) - return interaction.reply({ content: `${this.container.client.dev.error} | There is **no** queue to **display**`, ephemeral: true }); - - let pagesNum = Math.ceil(queue.tracks.size / 5); - if (pagesNum <= 0) pagesNum = 1; - - const tracks = queue.tracks.map((track, idx) => `**${++idx})** [${track.cleanTitle}](${track.url})`); - const paginatedMessage = new PaginatedMessage(); - - // handle error if pages exceed 25 pages - if (pagesNum > 25) pagesNum = 25; - for (let i = 0; i < pagesNum; i++) { - const list = tracks.slice(i * 5, i * 5 + 5).join('\n'); - - paginatedMessage.addPageEmbed((embed) => - embed - .setColor('Red') - .setDescription( - `**Queue** for **session** in **${queue.channel?.name}:**\n${list === '' ? '\n*• No more queued tracks*' : `\n${list}`} - \n**Now Playing:** [${queue.currentTrack?.title}](${queue.currentTrack?.url})\n` - ) - .setFooter({ - text: `${queue.tracks.size} track(s) in queue` - }) - ); - } - - return paginatedMessage.run(interaction); - } -} diff --git a/apps/music-bot/src/commands/record.ts b/apps/music-bot/src/commands/record.ts deleted file mode 100644 index 2f8bf5aab6..0000000000 --- a/apps/music-bot/src/commands/record.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { EndBehaviorType } from 'discord-voip'; -import { Command } from '@sapphire/framework'; -import type { GuildMember } from 'discord.js'; -import { createWriteStream } from 'fs'; - -export class RecordCommand extends Command { - public constructor(context: Command.Context, options: Command.Options) { - super(context, { - ...options, - description: 'Records and plays back the recording' - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => { - builder // - .setName(this.name) - .setDescription(this.description) - .addUserOption((option) => { - return option.setName('user').setRequired(false).setDescription('The user to record'); - }); - }); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const member = interaction.member as GuildMember; - const user = interaction.options.getUser('user'); - const target = user ? interaction.guild!.members.resolve(user) : interaction.guild!.members.resolve(member); - const permissions = this.container.client.perms.voice(interaction, this.container.client); - - if (permissions.member(target)) return interaction.reply({ content: permissions.member(target), ephemeral: true }); - if (permissions.client()) return interaction.reply({ content: permissions.client(), ephemeral: true }); - if (permissions.memberToMember(target)) return interaction.reply({ content: permissions.memberToMember(target), ephemeral: true }); - if (permissions.clientToMember()) return interaction.reply({ content: permissions.clientToMember(), ephemeral: true }); - - await interaction.deferReply(); - - const queue = this.container.client.player.nodes.create(interaction.guildId!, { - // just in case if someone decides to play music - metadata: { - channel: interaction.channel, - client: interaction.guild?.members.me, - requestedBy: interaction.user.username - }, - leaveOnEmptyCooldown: 300000, - leaveOnEmpty: true, - leaveOnEnd: false, - bufferingTimeout: 0 - }); - - try { - await queue.connect(member.voice.channelId!, { deaf: false }); - } catch { - return interaction.followUp('Failed to connect to your channel'); - } - - const stream = queue.voiceReceiver?.recordUser(target!.id, { - mode: 'pcm', - end: EndBehaviorType.AfterSilence - }); - if (!stream) return interaction.followUp('Failed to record that user'); - - stream.once('error', (err) => { - console.error(err); - if (interaction.isRepliable()) interaction.followUp('Something went wrong while recording!'); - queue.delete(); - }); - - const writer = stream.pipe(createWriteStream(`${this.container.client.recordingPath}/recording-${target!.id}.pcm`)); - writer.once('finish', () => { - if (interaction.isRepliable()) interaction.followUp(`Finished writing audio!`); - queue.delete(); - }); - - writer.once('error', (err) => { - console.error(err); - if (interaction.isRepliable()) interaction.followUp('Something went wrong while recording!'); - queue.delete(); - }); - } -} diff --git a/apps/music-bot/src/commands/remove.ts b/apps/music-bot/src/commands/remove.ts deleted file mode 100644 index 283875ad49..0000000000 --- a/apps/music-bot/src/commands/remove.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { Command } from '@sapphire/framework'; -import { useQueue } from 'discord-player'; - -export class removeCommand extends Command { - public constructor(context: Command.Context, options: Command.Options) { - super(context, { - ...options, - description: 'Removes the given track' - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => { - builder // - .setName(this.name) - .setDescription(this.description) - .addIntegerOption((option) => - option.setName('track').setDescription('The track you want to remove').setMinValue(1).setRequired(true).setAutocomplete(true) - ); - }); - } - - public override async autocompleteRun(interaction: Command.AutocompleteInteraction) { - const queue = useQueue(interaction.guild!.id); - const track = interaction.options.getInteger('track'); - const remove = queue?.tracks.at(track!); - const position = queue?.node.getTrackPosition(remove!); - - const tracks = queue!.tracks.map((t, idx) => ({ - name: t.title, - value: ++idx - })); - - if (remove?.title && !tracks.some((t) => t.name === remove.title)) { - tracks.unshift({ - name: remove.title, - value: position! - }); - } - - let slicedTracks = tracks.slice(0, 5); - if (track) { - slicedTracks = tracks.slice(track - 1, track + 4); - if (slicedTracks.length > 5) { - slicedTracks = slicedTracks.slice(0, 5); - } - } - - return interaction.respond(slicedTracks); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const queue = useQueue(interaction.guild!.id); - const permissions = this.container.client.perms.voice(interaction, this.container.client); - - if (!queue) return interaction.reply({ content: `${this.container.client.dev.error} | I am **not** in a voice channel`, ephemeral: true }); - if (!queue.tracks) - return interaction.reply({ content: `${this.container.client.dev.error} | There are **no tracks** to **remove**`, ephemeral: true }); - if (permissions.clientToMember()) return interaction.reply({ content: permissions.clientToMember(), ephemeral: true }); - - const remove = interaction.options.getInteger('track')! - 1; - const trackResolvable = queue.tracks.at(remove!); - - if (!trackResolvable) - return interaction.reply({ content: `${this.container.client.dev.error} | The **requested track** doesn't **exist**`, ephemeral: true }); - - queue.node.remove(trackResolvable); - return interaction.reply({ - content: `${this.container.client.dev.success} | I have **removed** the track: **${trackResolvable.title}**` - }); - } -} diff --git a/apps/music-bot/src/commands/shuffle.ts b/apps/music-bot/src/commands/shuffle.ts deleted file mode 100644 index d9de5c60fb..0000000000 --- a/apps/music-bot/src/commands/shuffle.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Command } from '@sapphire/framework'; -import { useQueue } from 'discord-player'; - -export class ShuffleCommand extends Command { - public constructor(context: Command.Context, options: Command.Options) { - super(context, { - ...options, - description: 'Shuffles the tracks in the queue' - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => { - builder // - .setName(this.name) - .setDescription(this.description); - }); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const queue = useQueue(interaction.guild!.id); - const permissions = this.container.client.perms.voice(interaction, this.container.client); - - if (!queue) return interaction.reply({ content: `${this.container.client.dev.error} | I am **not** in a voice channel`, ephemeral: true }); - if (permissions.clientToMember()) return interaction.reply({ content: permissions.clientToMember(), ephemeral: true }); - - if (queue.tracks.size < 2) - return interaction.reply({ - content: `${this.container.client.dev.error} | There are not **enough tracks** in queue to **shuffle**`, - ephemeral: true - }); - - // queue.tracks.shuffle(); - const status = queue.toggleShuffle(); - return interaction.reply({ content: `${this.container.client.dev.success} | I have **${status ? 'shuffled' : 'unshuffled'}** the queue` }); - } -} diff --git a/apps/music-bot/src/commands/skip.ts b/apps/music-bot/src/commands/skip.ts deleted file mode 100644 index ee8750faec..0000000000 --- a/apps/music-bot/src/commands/skip.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Command } from '@sapphire/framework'; -import { useQueue } from 'discord-player'; - -export class SkipCommand extends Command { - public constructor(context: Command.Context, options: Command.Options) { - super(context, { - ...options, - description: 'Skips the current track and automatically plays the next' - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => { - builder // - .setName(this.name) - .setDescription(this.description); - }); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const queue = useQueue(interaction.guild!.id); - const permissions = this.container.client.perms.voice(interaction, this.container.client); - - if (!queue) return interaction.reply({ content: `${this.container.client.dev.error} | I am **not** in a voice channel`, ephemeral: true }); - if (!queue.currentTrack) - return interaction.reply({ content: `${this.container.client.dev.error} | There is no track **currently** playing`, ephemeral: true }); - if (permissions.clientToMember()) return interaction.reply({ content: permissions.clientToMember(), ephemeral: true }); - - queue.node.skip(); - return interaction.reply({ - content: `⏩ | I have **skipped** to the next track` - }); - } -} diff --git a/apps/music-bot/src/commands/skipto.ts b/apps/music-bot/src/commands/skipto.ts deleted file mode 100644 index 7910dfc940..0000000000 --- a/apps/music-bot/src/commands/skipto.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { Command } from '@sapphire/framework'; -import { useQueue } from 'discord-player'; - -export class SkipToCommand extends Command { - public constructor(context: Command.Context, options: Command.Options) { - super(context, { - ...options, - description: 'Skips to the given track whilst removing previous tracks' - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => { - builder // - .setName(this.name) - .setDescription(this.description) - .addIntegerOption((option) => - option.setName('track').setDescription('The track you want to skip to').setMinValue(1).setRequired(true).setAutocomplete(true) - ); - }); - } - - public override async autocompleteRun(interaction: Command.AutocompleteInteraction) { - const queue = useQueue(interaction.guild!.id); - const track = interaction.options.getInteger('track'); - const skip = queue?.tracks.at(track!); - const position = queue?.node.getTrackPosition(skip!); - - const tracks = queue!.tracks.map((t, idx) => ({ - name: t.title, - value: ++idx - })); - - if (skip?.title && !tracks.some((t) => t.name === skip.title)) { - tracks.unshift({ - name: skip.title, - value: position! - }); - } - - let slicedTracks = tracks.slice(0, 5); - if (track) { - slicedTracks = tracks.slice(track - 1, track + 4); - if (slicedTracks.length > 5) { - slicedTracks = slicedTracks.slice(0, 5); - } - } - - return interaction.respond(slicedTracks); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const queue = useQueue(interaction.guild!.id); - const permissions = this.container.client.perms.voice(interaction, this.container.client); - - if (!queue) return interaction.reply({ content: `${this.container.client.dev.error} | I am **not** in a voice channel`, ephemeral: true }); - if (!queue.tracks) - return interaction.reply({ content: `${this.container.client.dev.error} | There are **no tracks** to **skip** to`, ephemeral: true }); - if (permissions.clientToMember()) return interaction.reply({ content: permissions.clientToMember(), ephemeral: true }); - - const skip = interaction.options.getInteger('track')! - 1; - const trackResolvable = queue.tracks.at(skip!); - - if (!trackResolvable) - return interaction.reply({ content: `${this.container.client.dev.error} | The **requested track** doesn't **exist**`, ephemeral: true }); - - queue.node.skipTo(trackResolvable); - return interaction.reply({ - content: `⏩ | I have **skipped** to the track: **${trackResolvable.title}**` - }); - } -} diff --git a/apps/music-bot/src/commands/soundcloud.ts b/apps/music-bot/src/commands/soundcloud.ts deleted file mode 100644 index 7ba6601496..0000000000 --- a/apps/music-bot/src/commands/soundcloud.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { Command } from '@sapphire/framework'; -import { QueryType } from 'discord-player'; -import type { GuildMember } from 'discord.js'; - -export class SoundcloudCommand extends Command { - public constructor(context: Command.Context, options: Command.Options) { - super(context, { - ...options, - description: 'Plays and enqueues track(s) of the query provided from Soundcloud' - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => { - builder // - .setName(this.name) - .setDescription(this.description) - .addStringOption((option) => { - return option.setName('query').setDescription('A query of your choice').setRequired(true).setAutocomplete(true); - }); - }); - } - - public override async autocompleteRun(interaction: Command.AutocompleteInteraction) { - const query = interaction.options.getString('query'); - const results = await this.container.client.player.search(query!, { - searchEngine: QueryType.SOUNDCLOUD_SEARCH - }); - - return interaction.respond( - results.tracks.slice(0, 10).map((t) => ({ - name: t.title, - value: t.url - })) - ); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const member = interaction.member as GuildMember; - const permissions = this.container.client.perms.voice(interaction, this.container.client); - if (permissions.member()) return interaction.reply({ content: permissions.member(), ephemeral: true }); - if (permissions.client()) return interaction.reply({ content: permissions.client(), ephemeral: true }); - - const query = interaction.options.getString('query'); - - if (permissions.clientToMember()) return interaction.reply({ content: permissions.clientToMember(), ephemeral: true }); - - const results = await this.container.client.player.search(query!, { - searchEngine: QueryType.SOUNDCLOUD_SEARCH - }); - - if (!results.hasTracks()) - return interaction.reply({ - content: `${this.container.client.dev.error} | No tracks were found for your query`, - ephemeral: true - }); - - await interaction.deferReply(); - await interaction.editReply({ content: `⏳ | Loading ${results.playlist ? 'a playlist...' : 'a track...'}` }); - - try { - const res = await this.container.client.player.play(member.voice.channel!.id, results, { - nodeOptions: { - metadata: { - channel: interaction.channel, - client: interaction.guild?.members.me, - requestedBy: interaction.user.username - }, - leaveOnEmptyCooldown: 300000, - leaveOnEmpty: true, - leaveOnEnd: false - } - }); - - await interaction.editReply({ - content: `${this.container.client.dev.success} | Successfully enqueued${ - res.track.playlist ? ` **multiple tracks** from: **${res.track.playlist.title}**` : `: **${res.track.cleanTitle}**` - }` - }); - } catch (error: any) { - await interaction.editReply({ content: `${this.container.client.dev.error} | An error has occurred` }); - return console.log(error); - } - } -} diff --git a/apps/music-bot/src/commands/spotify.ts b/apps/music-bot/src/commands/spotify.ts deleted file mode 100644 index 5ac229a108..0000000000 --- a/apps/music-bot/src/commands/spotify.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { Command } from '@sapphire/framework'; -import { QueryType } from 'discord-player'; -import type { GuildMember } from 'discord.js'; - -export class SpotifyCommand extends Command { - public constructor(context: Command.Context, options: Command.Options) { - super(context, { - ...options, - description: 'Plays and enqueues track(s) of the query provided from spotify' - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => { - builder // - .setName(this.name) - .setDescription(this.description) - .addStringOption((option) => { - return option.setName('query').setDescription('A query of your choice').setRequired(true).setAutocomplete(true); - }); - }); - } - - public override async autocompleteRun(interaction: Command.AutocompleteInteraction) { - const query = interaction.options.getString('query'); - const results = await this.container.client.player.search(query!, { - searchEngine: QueryType.SPOTIFY_SEARCH - }); - - return interaction.respond( - results.tracks.slice(0, 10).map((t) => ({ - name: t.title, - value: t.url - })) - ); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const member = interaction.member as GuildMember; - const permissions = this.container.client.perms.voice(interaction, this.container.client); - if (permissions.member()) return interaction.reply({ content: permissions.member(), ephemeral: true }); - if (permissions.client()) return interaction.reply({ content: permissions.client(), ephemeral: true }); - - const query = interaction.options.getString('query'); - - if (permissions.clientToMember()) return interaction.reply({ content: permissions.clientToMember(), ephemeral: true }); - - const results = await this.container.client.player.search(query!, { - searchEngine: QueryType.SPOTIFY_SEARCH - }); - - if (!results.hasTracks()) - return interaction.reply({ - content: `${this.container.client.dev.error} | No tracks were found for your query`, - ephemeral: true - }); - - await interaction.deferReply(); - await interaction.editReply({ content: `⏳ | Loading ${results.playlist ? 'a playlist...' : 'a track...'}` }); - - try { - const res = await this.container.client.player.play(member.voice.channel!.id, results, { - nodeOptions: { - metadata: { - channel: interaction.channel, - client: interaction.guild?.members.me, - requestedBy: interaction.user.username - }, - leaveOnEmptyCooldown: 300000, - leaveOnEmpty: true, - leaveOnEnd: false - } - }); - - await interaction.editReply({ - content: `${this.container.client.dev.success} | Successfully enqueued${ - res.track.playlist ? ` **multiple tracks** from: **${res.track.playlist.title}**` : `: **${res.track.cleanTitle}**` - }` - }); - } catch (error: any) { - await interaction.editReply({ content: `${this.container.client.dev.error} | An error has occurred` }); - return console.log(error); - } - } -} diff --git a/apps/music-bot/src/commands/volume.ts b/apps/music-bot/src/commands/volume.ts deleted file mode 100644 index 971089afde..0000000000 --- a/apps/music-bot/src/commands/volume.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Command } from '@sapphire/framework'; -import { useQueue, useTimeline } from 'discord-player'; - -export class VolumeCommand extends Command { - public constructor(context: Command.Context, options: Command.Options) { - super(context, { - ...options, - description: 'Changes the volume of the track and entire queue' - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => { - builder // - .setName(this.name) - .setDescription(this.description) - .addIntegerOption((option) => - option.setName('amount').setDescription('The amount of volume you want to change to').setMinValue(0).setMaxValue(100) - ); - }); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const queue = useQueue(interaction.guild!.id); - const timeline = useTimeline(interaction.guild!.id)!; - const permissions = this.container.client.perms.voice(interaction, this.container.client); - const volume = interaction.options.getInteger('amount'); - - if (!queue) return interaction.reply({ content: `${this.container.client.dev.error} | I am not in a voice channel`, ephemeral: true }); - if (!queue.currentTrack) - return interaction.reply({ content: `${this.container.client.dev.error} | There is no track **currently** playing`, ephemeral: true }); - if (!volume) return interaction.reply({ content: `🔊 | **Current** volume is **${timeline.volume}%**` }); - if (permissions.clientToMember()) return interaction.reply({ content: permissions.clientToMember(), ephemeral: true }); - - timeline.setVolume(volume!); - return interaction.reply({ - content: `${this.container.client.dev.success} | I **changed** the volume to: **${timeline.volume}%**` - }); - } -} diff --git a/apps/music-bot/src/emojis.ts b/apps/music-bot/src/emojis.ts deleted file mode 100644 index bade457f9c..0000000000 --- a/apps/music-bot/src/emojis.ts +++ /dev/null @@ -1,4 +0,0 @@ -export default { - success: '✅', - error: '❌' -}; diff --git a/apps/music-bot/src/index.ts b/apps/music-bot/src/index.ts deleted file mode 100644 index 6fd48eb7b8..0000000000 --- a/apps/music-bot/src/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { KarasuClient } from './KarasuClient'; -import './lib/setup'; - -const client = new KarasuClient(); - -const main = async () => { - try { - client.logger.info('Logging in...'); - await client.login(); - client.logger.info('Logged in!'); - } catch (error) { - client.logger.fatal(error); - client.destroy(); - process.exit(1); - } -}; - -void main(); diff --git a/apps/music-bot/src/lib/constants.ts b/apps/music-bot/src/lib/constants.ts deleted file mode 100644 index 11bba99300..0000000000 --- a/apps/music-bot/src/lib/constants.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { join } from 'path'; - -export const rootDir = join(__dirname, '..', '..'); -export const srcDir = join(rootDir, 'src'); - -export const RandomLoadingMessage = ['Computing...', 'Thinking...', 'Cooking some food', 'Give me a moment', 'Loading...']; diff --git a/apps/music-bot/src/lib/perms.ts b/apps/music-bot/src/lib/perms.ts deleted file mode 100644 index 9e4c99f16b..0000000000 --- a/apps/music-bot/src/lib/perms.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { PermissionsBitField } from 'discord.js'; -import type { KarasuClient } from '../KarasuClient'; - -export function voice(interaction, container: KarasuClient) { - function client() { - const resolved = new PermissionsBitField([ - PermissionsBitField.Flags.Connect, - PermissionsBitField.Flags.Speak, - PermissionsBitField.Flags.ViewChannel - ]); - const missingPerms = interaction.member.voice.channel.permissionsFor(interaction.guild!.members.me!).missing(resolved); - - if (missingPerms.length) - return `${container.dev.error} | I am missing the required voice channel permissions: \`${missingPerms.join(', ')}\``; - } - - function member(target?) { - if (target && !target.member.voice.channel) return `${container.dev.error} | ${target.displayName} is not in a voice channel.`; - if (!interaction.member.voice.channel) return `${container.dev.error} | You need to be in a voice channel.`; - } - - function memberToMember(target) { - if (interaction.member.voice.channelId !== target.member.voice.channelId) - return `${container.dev.error} | You are not in the same voice channel as the **target user**.`; - } - - function clientToMember() { - if (interaction.guild?.members.me?.voice.channelId && interaction.member.voice.channelId !== interaction.guild?.members.me?.voice.channelId) - return `${container.dev.error} | You are not in my voice channel`; - } - - return { client, member, memberToMember, clientToMember }; -} diff --git a/apps/music-bot/src/lib/setup.ts b/apps/music-bot/src/lib/setup.ts deleted file mode 100644 index 2b4db44511..0000000000 --- a/apps/music-bot/src/lib/setup.ts +++ /dev/null @@ -1,15 +0,0 @@ -import '@sapphire/plugin-api/register'; -import '@sapphire/plugin-hmr/register'; -import '@sapphire/plugin-logger/register'; -import { setup, type ArrayString } from '@skyra/env-utilities'; -import { join } from 'path'; - -const rootDir = join(__dirname, '..', '..'); -const srcDir = join(rootDir, 'src'); -setup({ path: join(srcDir, '.env') }); - -declare module '@skyra/env-utilities' { - interface Env { - OWNERS: ArrayString; - } -} diff --git a/apps/music-bot/src/lib/utils.ts b/apps/music-bot/src/lib/utils.ts deleted file mode 100644 index f66c93655e..0000000000 --- a/apps/music-bot/src/lib/utils.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { ChatInputCommandSuccessPayload, Command, ContextMenuCommandSuccessPayload, MessageCommandSuccessPayload } from '@sapphire/framework'; -import { container } from '@sapphire/framework'; -import { cyan } from 'colorette'; -import type { APIUser } from 'discord-api-types/v9'; -import { Track, TrackResolvable, useQueue } from 'discord-player'; -import type { Guild, User } from 'discord.js'; - -export function logSuccessCommand(payload: ContextMenuCommandSuccessPayload | ChatInputCommandSuccessPayload | MessageCommandSuccessPayload): void { - let successLoggerData: ReturnType; - - if ('interaction' in payload) { - successLoggerData = getSuccessLoggerData(payload.interaction.guild, payload.interaction.user, payload.command); - } else { - successLoggerData = getSuccessLoggerData(payload.message.guild, payload.message.author, payload.command); - } - - container.logger.debug(`${successLoggerData.shard} - ${successLoggerData.commandName} ${successLoggerData.author} ${successLoggerData.sentAt}`); -} - -export function getSuccessLoggerData(guild: Guild | null, user: User, command: Command) { - const shard = getShardInfo(guild?.shardId ?? 0); - const commandName = getCommandInfo(command); - const author = getAuthorInfo(user); - const sentAt = getGuildInfo(guild); - - return { shard, commandName, author, sentAt }; -} - -function getShardInfo(id: number) { - return `[${cyan(id.toString())}]`; -} - -function getCommandInfo(command: Command) { - return cyan(command.name); -} - -function getAuthorInfo(author: User | APIUser) { - return `${author.username}[${cyan(author.id)}]`; -} - -function getGuildInfo(guild: Guild | null) { - if (guild === null) return 'Direct Messages'; - return `${guild.name}[${cyan(guild.id)}]`; -} diff --git a/apps/music-bot/src/listeners/commands/chatInputCommands/chatInputCommandDenied.ts b/apps/music-bot/src/listeners/commands/chatInputCommands/chatInputCommandDenied.ts deleted file mode 100644 index f8e4fd6693..0000000000 --- a/apps/music-bot/src/listeners/commands/chatInputCommands/chatInputCommandDenied.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { DurationFormatter } from '@sapphire/duration'; -import type { ChatInputCommandDeniedPayload, Events } from '@sapphire/framework'; -import { Listener, UserError } from '@sapphire/framework'; - -export class UserEvent extends Listener { - public run({ identifier, context, message: content }: UserError, { interaction }: ChatInputCommandDeniedPayload) { - // `context: { silent: true }` should make UserError silent: - // Use cases for this are for example permissions error when running the `eval` command. - if (Reflect.get(Object(context), 'silent')) return; - - if (identifier === 'preconditionCooldown') { - const remaining = Reflect.get(Object(context), 'remaining'); - const ms = new DurationFormatter().format(remaining); - return interaction.reply({ - content: `Slow down! You must wait **${ms}** before using the \`${interaction.commandName}\` comand.`, - allowedMentions: { users: [interaction.user.id], roles: [] }, - ephemeral: true - }); - } - - return interaction.reply({ - content, - allowedMentions: { users: [interaction.user.id], roles: [] }, - ephemeral: true - }); - } -} diff --git a/apps/music-bot/src/listeners/commands/chatInputCommands/chatInputCommandSuccess.ts b/apps/music-bot/src/listeners/commands/chatInputCommands/chatInputCommandSuccess.ts deleted file mode 100644 index 84728aa988..0000000000 --- a/apps/music-bot/src/listeners/commands/chatInputCommands/chatInputCommandSuccess.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ChatInputCommandSuccessPayload, Listener, LogLevel } from '@sapphire/framework'; -import type { Logger } from '@sapphire/plugin-logger'; -import { logSuccessCommand } from '../../../lib/utils'; - -export class UserListener extends Listener { - public run(payload: ChatInputCommandSuccessPayload) { - logSuccessCommand(payload); - } - - public onLoad() { - this.enabled = (this.container.logger as Logger).level <= LogLevel.Debug; - return super.onLoad(); - } -} diff --git a/apps/music-bot/src/listeners/commands/contextMenuCommands/contextMenuCommandDenied.ts b/apps/music-bot/src/listeners/commands/contextMenuCommands/contextMenuCommandDenied.ts deleted file mode 100644 index 64b2d26f0e..0000000000 --- a/apps/music-bot/src/listeners/commands/contextMenuCommands/contextMenuCommandDenied.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { DurationFormatter } from '@sapphire/duration'; -import type { ContextMenuCommandDeniedPayload, Events } from '@sapphire/framework'; -import { Listener, UserError } from '@sapphire/framework'; - -export class UserEvent extends Listener { - public run({ identifier, context, message: content }: UserError, { interaction }: ContextMenuCommandDeniedPayload) { - // `context: { silent: true }` should make UserError silent: - // Use cases for this are for example permissions error when running the `eval` command. - if (Reflect.get(Object(context), 'silent')) return; - - if (identifier === 'preconditionCooldown') { - const remaining = Reflect.get(Object(context), 'remaining'); - const ms = new DurationFormatter().format(remaining); - return interaction.reply({ - content: `Slow down! You must wait **${ms}** before using the \`${interaction.commandName}\` comand.`, - allowedMentions: { users: [interaction.user.id], roles: [] }, - ephemeral: true - }); - } - - return interaction.reply({ - content, - allowedMentions: { users: [interaction.user.id], roles: [] }, - ephemeral: true - }); - } -} diff --git a/apps/music-bot/src/listeners/commands/contextMenuCommands/contextMenuCommandSuccess.ts b/apps/music-bot/src/listeners/commands/contextMenuCommands/contextMenuCommandSuccess.ts deleted file mode 100644 index 6b21930fb6..0000000000 --- a/apps/music-bot/src/listeners/commands/contextMenuCommands/contextMenuCommandSuccess.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ContextMenuCommandSuccessPayload, Listener, LogLevel } from '@sapphire/framework'; -import type { Logger } from '@sapphire/plugin-logger'; -import { logSuccessCommand } from '../../../lib/utils'; - -export class UserListener extends Listener { - public run(payload: ContextMenuCommandSuccessPayload) { - logSuccessCommand(payload); - } - - public onLoad() { - this.enabled = (this.container.logger as Logger).level <= LogLevel.Debug; - return super.onLoad(); - } -} diff --git a/apps/music-bot/src/listeners/player/audioTrackAdd.ts b/apps/music-bot/src/listeners/player/audioTrackAdd.ts deleted file mode 100644 index 697cf42c21..0000000000 --- a/apps/music-bot/src/listeners/player/audioTrackAdd.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { container, Listener } from '@sapphire/framework'; -import type { GuildQueue, Track } from 'discord-player'; -import { Client, GuildTextBasedChannel, PermissionsBitField } from 'discord.js'; - -export class PlayerEvent extends Listener { - public constructor(context: Listener.Context, options: Listener.Options) { - super(context, { - ...options, - emitter: container.client.player.events, - event: 'audioTrackAdd' - }); - } - - public run( - queue: GuildQueue<{ - channel: GuildTextBasedChannel; - client: Client; - }>, - track: Track - ) { - const resolved = new PermissionsBitField([PermissionsBitField.Flags.SendMessages, PermissionsBitField.Flags.ViewChannel]); - const missingPerms = queue.metadata.channel.permissionsFor(queue.metadata.client!.user)?.missing(resolved); - if (missingPerms?.length) return; - - return queue.metadata.channel.send({ - embeds: [ - { - title: 'Track Added!', - description: `🎵 | Track **${track.cleanTitle || 'Unknown Title'}** added to the queue!`, - color: 0xffaaaa, - footer: { - text: `Extractor: ${track.extractor?.identifier || 'N/A'}` - }, - thumbnail: { - url: track.thumbnail - } - } - ] - }); - } -} diff --git a/apps/music-bot/src/listeners/player/connectionDestroyed.ts b/apps/music-bot/src/listeners/player/connectionDestroyed.ts deleted file mode 100644 index 7d8cebafef..0000000000 --- a/apps/music-bot/src/listeners/player/connectionDestroyed.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { container, Listener } from '@sapphire/framework'; -import type { GuildQueue } from 'discord-player'; - -export class PlayerEvent extends Listener { - public constructor(context: Listener.Context, options: Listener.Options) { - super(context, { - ...options, - emitter: container.client.player.events, - event: 'connectionDestroyed' - }); - } - - public run(queue: GuildQueue) { - console.log(`Voice connection destroyed for ${queue.guild.name}`); - queue.delete(); - } -} diff --git a/apps/music-bot/src/listeners/player/connectionError.ts b/apps/music-bot/src/listeners/player/connectionError.ts deleted file mode 100644 index ed6103aa6e..0000000000 --- a/apps/music-bot/src/listeners/player/connectionError.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { container, Listener } from '@sapphire/framework'; - -export class PlayerEvent extends Listener { - public constructor(context: Listener.Context, options: Listener.Options) { - super(context, { - ...options, - emitter: container.client.player.events, - event: 'connectionError' - }); - } - - public run(queue, error) { - console.log(`[${queue.guild.name}] Error emitted from the connection: ${error.message}`); - } -} diff --git a/apps/music-bot/src/listeners/player/debug.ts b/apps/music-bot/src/listeners/player/debug.ts deleted file mode 100644 index e6d2b06e34..0000000000 --- a/apps/music-bot/src/listeners/player/debug.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { container, Listener } from '@sapphire/framework'; -import { cyanBright, gray } from 'colorette'; - -export class PlayerEvent extends Listener { - public constructor(context: Listener.Context, options: Listener.Options) { - super(context, { - ...options, - emitter: container.client.player.events, - event: 'debug' - }); - } - - public run(_queue, message) { - console.log(`[${cyanBright('DEBUG')}] ${gray(message)}\n`); - } -} diff --git a/apps/music-bot/src/listeners/player/disconnect.ts b/apps/music-bot/src/listeners/player/disconnect.ts deleted file mode 100644 index d00c929e35..0000000000 --- a/apps/music-bot/src/listeners/player/disconnect.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { container, Listener } from '@sapphire/framework'; -import { PermissionsBitField } from 'discord.js'; - -export class PlayerEvent extends Listener { - public constructor(context: Listener.Context, options: Listener.Options) { - super(context, { - ...options, - emitter: container.client.player.events, - event: 'disconnect' - }); - } - - public run(queue) { - const resolved = new PermissionsBitField([PermissionsBitField.Flags.SendMessages, PermissionsBitField.Flags.ViewChannel]); - const missingPerms = queue.metadata.channel.permissionsFor(queue.metadata.client).missing(resolved); - if (missingPerms.length) return; - - queue.metadata.channel - .send('I have been **manually disconnected** from the **voice channel**') - .then((m: { delete: () => void }) => setTimeout(() => m.delete(), 15000)); - } -} diff --git a/apps/music-bot/src/listeners/player/emptyChannel.ts b/apps/music-bot/src/listeners/player/emptyChannel.ts deleted file mode 100644 index cda467c381..0000000000 --- a/apps/music-bot/src/listeners/player/emptyChannel.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { container, Listener } from '@sapphire/framework'; -import { PermissionsBitField } from 'discord.js'; - -export class PlayerEvent extends Listener { - public constructor(context: Listener.Context, options: Listener.Options) { - super(context, { - ...options, - emitter: container.client.player.events, - event: 'emptyChannel' - }); - } - - public run(queue) { - const resolved = new PermissionsBitField([PermissionsBitField.Flags.SendMessages, PermissionsBitField.Flags.ViewChannel]); - const missingPerms = queue.metadata.channel.permissionsFor(queue.metadata.client).missing(resolved); - if (missingPerms.length) return; - - queue.metadata.channel - .send('I left the channel after **5 minutes** due to **channel inactivity**') - .then((m: { delete: () => void }) => setTimeout(() => m.delete(), 15000)); - } -} diff --git a/apps/music-bot/src/listeners/player/emptyQueue.ts b/apps/music-bot/src/listeners/player/emptyQueue.ts deleted file mode 100644 index 2f1e447494..0000000000 --- a/apps/music-bot/src/listeners/player/emptyQueue.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { container, Listener } from '@sapphire/framework'; -import { PermissionsBitField } from 'discord.js'; - -export class PlayerEvent extends Listener { - public constructor(context: Listener.Context, options: Listener.Options) { - super(context, { - ...options, - emitter: container.client.player.events, - event: 'emptyQueue' - }); - } - - public run(queue) { - const resolved = new PermissionsBitField([PermissionsBitField.Flags.SendMessages, PermissionsBitField.Flags.ViewChannel]); - const missingPerms = queue.metadata.channel.permissionsFor(queue.metadata.client).missing(resolved); - if (missingPerms.length) return; - - queue.metadata.channel.send('No more tracks left in the queue!').then((m: { delete: () => void }) => setTimeout(() => m.delete(), 7000)); - } -} diff --git a/apps/music-bot/src/listeners/player/error.ts b/apps/music-bot/src/listeners/player/error.ts deleted file mode 100644 index 9575768ccc..0000000000 --- a/apps/music-bot/src/listeners/player/error.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { container, Listener } from '@sapphire/framework'; - -export class PlayerEvent extends Listener { - public constructor(context: Listener.Context, options: Listener.Options) { - super(context, { - ...options, - emitter: container.client.player.events, - event: 'error' - }); - } - - public run(queue, error) { - console.log(`[${queue.guild.name}] Error emitted from the queue: ${error.message}`); - } -} diff --git a/apps/music-bot/src/listeners/player/masterDebug.ts b/apps/music-bot/src/listeners/player/masterDebug.ts deleted file mode 100644 index e3a93c662a..0000000000 --- a/apps/music-bot/src/listeners/player/masterDebug.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { container, Listener } from '@sapphire/framework'; -import { cyanBright, gray } from 'colorette'; - -export class PlayerEvent extends Listener { - public constructor(context: Listener.Context, options: Listener.Options) { - super(context, { - ...options, - emitter: container.client.player, - event: 'debug' - }); - } - - public run(message) { - console.log(`[${cyanBright('DEBUG')}] ${gray(message)}\n`); - } -} diff --git a/apps/music-bot/src/listeners/player/masterError.ts b/apps/music-bot/src/listeners/player/masterError.ts deleted file mode 100644 index dc25c94219..0000000000 --- a/apps/music-bot/src/listeners/player/masterError.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { container, Listener } from '@sapphire/framework'; - -export class PlayerEvent extends Listener { - public constructor(context: Listener.Context, options: Listener.Options) { - super(context, { - ...options, - emitter: container.client.player, - event: 'error' - }); - } - - public run(error) { - console.log(`Error emitted from player: ${error.message}`); - } -} diff --git a/apps/music-bot/src/listeners/player/playerError.ts b/apps/music-bot/src/listeners/player/playerError.ts deleted file mode 100644 index b4dd574d4a..0000000000 --- a/apps/music-bot/src/listeners/player/playerError.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { container, Listener } from '@sapphire/framework'; -import { PermissionsBitField } from 'discord.js'; - -export class PlayerEvent extends Listener { - public constructor(context: Listener.Context, options: Listener.Options) { - super(context, { - ...options, - emitter: container.client.player.events, - event: 'playerError' - }); - } - - public run(queue, error, track) { - const resolved = new PermissionsBitField([PermissionsBitField.Flags.SendMessages, PermissionsBitField.Flags.ViewChannel]); - const missingPerms = queue.metadata.channel.permissionsFor(queue.metadata.client).missing(resolved); - if (missingPerms.length) return; - - console.log(error); - - return queue.metadata.channel.send(`${queue.metadata.client.dev.error} | There was an error with **${track.cleanTitle}:**`); - } -} diff --git a/apps/music-bot/src/listeners/player/playerStart.ts b/apps/music-bot/src/listeners/player/playerStart.ts deleted file mode 100644 index 22830d1370..0000000000 --- a/apps/music-bot/src/listeners/player/playerStart.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { container, Listener } from '@sapphire/framework'; -import type { Track } from 'discord-player'; -import { PermissionsBitField } from 'discord.js'; - -export class PlayerEvent extends Listener { - public constructor(context: Listener.Context, options: Listener.Options) { - super(context, { - ...options, - emitter: container.client.player.events, - event: 'playerStart' - }); - } - - public run(queue, track: Track) { - console.log(track.metadata); - const resolved = new PermissionsBitField([PermissionsBitField.Flags.SendMessages, PermissionsBitField.Flags.ViewChannel]); - const missingPerms = queue.metadata.channel.permissionsFor(queue.metadata.client).missing(resolved); - if (missingPerms.length) return; - - return queue.metadata.channel.send({ - embeds: [ - { - title: 'Now Playing', - description: `🎵 | **${track.cleanTitle || 'Unknown Title'}**`, - color: 0xaaaaff, - footer: { - text: `Extractor: ${track.extractor?.identifier || 'N/A'}` - }, - thumbnail: { - url: track.thumbnail - } - } - ] - }); - } -} diff --git a/apps/music-bot/src/listeners/ready.ts b/apps/music-bot/src/listeners/ready.ts deleted file mode 100644 index ee27c88fa1..0000000000 --- a/apps/music-bot/src/listeners/ready.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Listener } from '@sapphire/framework'; -import { useMainPlayer } from 'discord-player'; - -export class UserEvent extends Listener { - public constructor(context: Listener.Context, options: Listener.Options) { - super(context, { - ...options, - once: true - }); - } - - public async run() { - console.log(`Logged in as ${this.container.client.user?.username}`); - - const player = useMainPlayer(); - if (player) { - // await player.extractors.loadDefault(/* (ext) => ext !== 'YouTubeExtractor' */); - // console.log(player.scanDeps()); - // await player.extractors.loadDefault((ext) => ext === 'YouTubeExtractor' || ext === 'SpotifyExtractor' || ext === 'AttachmentExtractor'); - await player.extractors.loadDefault(/* (ext) => ext !== 'YouTubeExtractor' */); - } - } -} diff --git a/apps/music-bot/src/preconditions/devOnly.ts b/apps/music-bot/src/preconditions/devOnly.ts deleted file mode 100644 index 5c2bf7de6c..0000000000 --- a/apps/music-bot/src/preconditions/devOnly.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { AllFlowsPrecondition } from '@sapphire/framework'; -import type { ChatInputCommandInteraction, ContextMenuCommandInteraction, Message, Snowflake } from 'discord.js'; -import { envParseArray } from '@skyra/env-utilities'; - -const OWNERS = envParseArray('OWNERS'); - -export class UserPrecondition extends AllFlowsPrecondition { - #message = 'This command can only be used by the owner.'; - - public override chatInputRun(interaction: ChatInputCommandInteraction) { - return this.doOwnerCheck(interaction.user.id); - } - - public override contextMenuRun(interaction: ContextMenuCommandInteraction) { - return this.doOwnerCheck(interaction.user.id); - } - - public override messageRun(message: Message) { - return this.doOwnerCheck(message.author.id); - } - - private doOwnerCheck(userId: Snowflake) { - return OWNERS.includes(userId) ? this.ok() : this.error({ message: this.#message }); - } -} - -declare module '@sapphire/framework' { - interface Preconditions { - devOnly: never; - } -} diff --git a/apps/music-bot/templates/command.ts.sapphire b/apps/music-bot/templates/command.ts.sapphire deleted file mode 100644 index 8c854de2ef..0000000000 --- a/apps/music-bot/templates/command.ts.sapphire +++ /dev/null @@ -1,26 +0,0 @@ -{ - "category": "commands" -} ---- -import { Command } from '@sapphire/framework'; - -export class {{name}}Command extends Command { - public constructor(context: Command.Context, options: Command.Options) { - super(context, { - ...options, - description: 'A command' - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => { - builder // - .setName(this.name) - .setDescription(this.description); - }); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - return interaction.reply({ content: `${interaction.commandName}`, ephemeral: true }); - } -} diff --git a/apps/music-bot/tsconfig.eslint.json b/apps/music-bot/tsconfig.eslint.json deleted file mode 100644 index c2f78f844e..0000000000 --- a/apps/music-bot/tsconfig.eslint.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "./tsconfig.json", - "include": ["src"] -} \ No newline at end of file diff --git a/apps/music-bot/tsconfig.json b/apps/music-bot/tsconfig.json deleted file mode 100644 index daa422f309..0000000000 --- a/apps/music-bot/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "@sapphire/ts-config", - "compilerOptions": { - "experimentalDecorators": true, - "rootDir": "src", - "outDir": "dist", - "tsBuildInfoFile": "dist/.tsbuildinfo", - "skipLibCheck": true, - "noImplicitAny": false, - "noImplicitReturns": false, - "ignoreDeprecations": "5.0" - }, - "include": ["src"] -} diff --git a/apps/music-bot/tsup.config.ts b/apps/music-bot/tsup.config.ts deleted file mode 100644 index 3d316a21ea..0000000000 --- a/apps/music-bot/tsup.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { defineConfig } from 'tsup'; - -export default defineConfig({ - dts: false, - clean: true, - format: ['cjs'], - bundle: false, - skipNodeModulesBundle: true, - keepNames: true, - minify: false, - silent: true, - entry: ['src'], - outDir: 'dist' -}); diff --git a/packages/adapter-local/LICENSE b/packages/adapter-local/LICENSE deleted file mode 100644 index fe07fc7364..0000000000 --- a/packages/adapter-local/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 Androz2091 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/packages/adapter-local/README.md b/packages/adapter-local/README.md deleted file mode 100644 index 9a301abd93..0000000000 --- a/packages/adapter-local/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# `@discord-player/adapter-local` - -Local PlayerNode adapter that provides interface to Discord Player - -## Installation - -```sh -$ yarn add @discord-player/adapter-local -``` \ No newline at end of file diff --git a/packages/adapter-local/package.json b/packages/adapter-local/package.json deleted file mode 100644 index ec8097e0c3..0000000000 --- a/packages/adapter-local/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "@discord-player/adapter-local", - "version": "0.1.0", - "description": "Discord Player local adapter", - "keywords": [ - "discord-player" - ], - "author": "twlite", - "homepage": "https://discord-player.js.org", - "license": "MIT", - "main": "dist/index.js", - "files": [ - "dist" - ], - "repository": { - "type": "git", - "url": "git+https://github.com/Androz2091/discord-player.git" - }, - "scripts": { - "build:check": "tsc --noEmit", - "build": "tsup" - }, - "bugs": { - "url": "https://github.com/Androz2091/discord-player/issues" - }, - "devDependencies": { - "@discord-player/tsconfig": "workspace:^", - "tsup": "^7.2.0", - "typescript": "^5.2.2", - "vitest": "^0.34.6" - } -} diff --git a/packages/adapter-local/src/index.ts b/packages/adapter-local/src/index.ts deleted file mode 100644 index 6b08400873..0000000000 --- a/packages/adapter-local/src/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export {}; - -// eslint-disable-next-line @typescript-eslint/no-inferrable-types -export const version: string = '[VI]{{inject}}[/VI]'; diff --git a/packages/adapter-local/tsconfig.json b/packages/adapter-local/tsconfig.json deleted file mode 100644 index 08981710e6..0000000000 --- a/packages/adapter-local/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "@discord-player/tsconfig/base.json", - "include": ["src/**/*"], - "exclude": ["node_modules"] -} \ No newline at end of file diff --git a/packages/adapter-local/tsup.config.ts b/packages/adapter-local/tsup.config.ts deleted file mode 100644 index e161ba4d4d..0000000000 --- a/packages/adapter-local/tsup.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { defineConfig } from '../../tsup.config'; -import { esbuildPluginVersionInjector } from 'esbuild-plugin-version-injector'; - -export default defineConfig({ - esbuildPlugins: [esbuildPluginVersionInjector()] -}); diff --git a/packages/adapter-remote/LICENSE b/packages/adapter-remote/LICENSE deleted file mode 100644 index fe07fc7364..0000000000 --- a/packages/adapter-remote/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 Androz2091 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/packages/adapter-remote/README.md b/packages/adapter-remote/README.md deleted file mode 100644 index 5661e3ca76..0000000000 --- a/packages/adapter-remote/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# `@discord-player/adapter-remote` - -Remote PlayerNode adapter that provides interface to Discord Player - -## Installation - -```sh -$ yarn add @discord-player/adapter-remote -``` \ No newline at end of file diff --git a/packages/adapter-remote/package.json b/packages/adapter-remote/package.json deleted file mode 100644 index a66b7156e6..0000000000 --- a/packages/adapter-remote/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "@discord-player/adapter-remote", - "version": "0.1.0", - "description": "Discord Player remote adapter", - "keywords": [ - "discord-player" - ], - "author": "twlite", - "homepage": "https://discord-player.js.org", - "license": "MIT", - "main": "dist/index.js", - "directories": { - "src": "src" - }, - "files": [ - "dist" - ], - "repository": { - "type": "git", - "url": "git+https://github.com/Androz2091/discord-player.git" - }, - "scripts": { - "build:check": "tsc --noEmit", - "build": "tsup" - }, - "bugs": { - "url": "https://github.com/Androz2091/discord-player/issues" - }, - "devDependencies": { - "@discord-player/tsconfig": "workspace:^", - "tsup": "^7.2.0", - "typescript": "^5.2.2", - "vitest": "^0.34.6" - } -} diff --git a/packages/adapter-remote/src/index.ts b/packages/adapter-remote/src/index.ts deleted file mode 100644 index 6b08400873..0000000000 --- a/packages/adapter-remote/src/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export {}; - -// eslint-disable-next-line @typescript-eslint/no-inferrable-types -export const version: string = '[VI]{{inject}}[/VI]'; diff --git a/packages/adapter-remote/tsconfig.json b/packages/adapter-remote/tsconfig.json deleted file mode 100644 index 08981710e6..0000000000 --- a/packages/adapter-remote/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "@discord-player/tsconfig/base.json", - "include": ["src/**/*"], - "exclude": ["node_modules"] -} \ No newline at end of file diff --git a/packages/adapter-remote/tsup.config.ts b/packages/adapter-remote/tsup.config.ts deleted file mode 100644 index e161ba4d4d..0000000000 --- a/packages/adapter-remote/tsup.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { defineConfig } from '../../tsup.config'; -import { esbuildPluginVersionInjector } from 'esbuild-plugin-version-injector'; - -export default defineConfig({ - esbuildPlugins: [esbuildPluginVersionInjector()] -}); diff --git a/packages/core/LICENSE b/packages/core/LICENSE deleted file mode 100644 index fe07fc7364..0000000000 --- a/packages/core/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 Androz2091 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/packages/core/README.md b/packages/core/README.md deleted file mode 100644 index 9113d96ad5..0000000000 --- a/packages/core/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# `@discord-player/core` - -Discord Player core components - -## Installation - -```sh -$ yarn add @discord-player/core -``` - -This library is internally used by `discord-player`. This library handles all the work related to voice and provides a way to communicate with nodes. \ No newline at end of file diff --git a/packages/core/package.json b/packages/core/package.json deleted file mode 100644 index b0f242743f..0000000000 --- a/packages/core/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "@discord-player/core", - "version": "0.1.0", - "description": "Discord Player core components", - "keywords": [ - "discord-player" - ], - "author": "Androz2091 ", - "homepage": "https://discord-player.js.org", - "license": "MIT", - "main": "dist/index.js", - "module": "dist/index.mjs", - "types": "dist/index.d.ts", - "directories": { - "dist": "dist", - "src": "src" - }, - "files": [ - "dist" - ], - "repository": { - "type": "git", - "url": "git+https://github.com/Androz2091/discord-player.git" - }, - "scripts": { - "build": "tsup", - "build:check": "tsc --noEmit" - }, - "bugs": { - "url": "https://github.com/Androz2091/discord-player/issues" - }, - "dependencies": { - "@discord-player/utils": "workspace:^", - "discord-api-types": "^0.37.2", - "discord-voip": "^0.1.2" - }, - "devDependencies": { - "@discord-player/tsconfig": "workspace:^", - "tsup": "^7.2.0", - "typescript": "^5.2.2", - "vitest": "^0.34.6" - } -} diff --git a/packages/core/src/classes/PlayerNodeManager.ts b/packages/core/src/classes/PlayerNodeManager.ts deleted file mode 100644 index 620dd819c8..0000000000 --- a/packages/core/src/classes/PlayerNodeManager.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { cpus } from 'node:os'; -import { Worker } from 'node:worker_threads'; -import { join } from 'node:path'; -import { Collection, EventEmitter } from '@discord-player/utils'; -import { WorkerEvents, WorkerOp } from '../utils/enums'; - -interface PlayerNodeConfig { - max?: number | 'auto'; - respawn?: boolean; -} - -interface BasicSubscription { - guild_id: string; - client_id: string; -} - -type WorkerResolvable = number | Worker; - -export interface PlayerNodeEvents { - error: (worker: Worker, error: Error) => Awaited; - message: (worker: Worker, message: unknown) => Awaited; - spawn: (worker: Worker) => Awaited; - debug: (message: string) => Awaited; - voiceStateUpdate: (worker: Worker, payload: any) => Awaited; - subscriptionCreate: (worker: Worker, payload: BasicSubscription) => Awaited; - subscriptionDelete: (worker: Worker, payload: BasicSubscription) => Awaited; -} - -export interface ServicePayload { - op: keyof typeof WorkerOp; - d: { - guild_id: string; - client_id: string; - } & T; -} - -export interface WorkerPayload { - t: keyof typeof WorkerEvents; - d: T; -} - -export class PlayerNodeManager extends EventEmitter { - public workers = new Collection(); - public constructor(public config: PlayerNodeConfig) { - super(); - } - - #debug(message: string) { - this.emit('debug', `[${this.constructor.name} | ${new Date().toLocaleString()}] ${message}`); - } - - public get maxThreads() { - const conf = this.config.max; - if (conf === 'auto') return cpus().length; - if (typeof conf !== 'number' || Number.isNaN(conf) || conf < 1 || !Number.isFinite(conf)) return 1; - return conf; - } - - public get spawnable() { - return this.workers.size < this.maxThreads; - } - - // TODO - public getLeastBusy() { - return; - } - - public send(workerRes: WorkerResolvable, data: ServicePayload) { - const worker = this.resolveWorker(workerRes); - if (!worker) throw new Error('Worker does not exist'); - this.#debug(`Sending ${JSON.stringify(data)} to thread ${worker.threadId}`); - worker.postMessage(data); - } - - public spawn() { - return new Promise((resolve) => { - if (!this.spawnable) return resolve(this.workers.random()!); - - const worker = new Worker(join(__dirname, '..', 'worker', 'worker.js')); - this.#debug(`Spawned worker at thread ${worker.threadId}`); - - worker.on('online', () => { - this.#debug(`worker ${worker.threadId} is online`); - this.workers.set(worker.threadId, worker); - this.emit('spawn', worker); - return resolve(worker); - }); - - worker.on('message', (message: WorkerPayload) => { - this.#debug(`Incoming message from worker ${worker.threadId}\n\n${JSON.stringify(message)}`); - switch (message.t) { - case WorkerEvents.VOICE_STATE_UPDATE: { - return this.emit('voiceStateUpdate', worker, message.d); - } - case WorkerEvents.ERROR: { - return this.emit('error', worker, new Error((message.d as any).message)); - } - case WorkerEvents.SUBSCRIPTION_CREATE: { - return this.emit('subscriptionCreate', worker, message.d as BasicSubscription); - } - case WorkerEvents.SUBSCRIPTION_DELETE: { - return this.emit('subscriptionDelete', worker, message.d as BasicSubscription); - } - default: { - return this.emit('message', worker, message); - } - } - }); - - worker.on('exit', () => { - this.#debug(`Worker terminated at thread ${worker.threadId}`); - this.workers.delete(worker.threadId); - }); - - worker.on('error', (error) => { - this.#debug(`Incoming error message from worker ${worker.threadId}\n\n${JSON.stringify(error)}`); - this.emit('error', worker, error); - }); - }); - } - - public resolveWorker(worker: WorkerResolvable) { - if (typeof worker === 'number') return this.workers.get(worker); - return this.workers.find((res) => res.threadId === worker.threadId); - } - - public async terminate(worker?: WorkerResolvable) { - if (worker) { - const internalWorker = this.resolveWorker(worker); - if (internalWorker) { - this.#debug(`Terminating worker ${internalWorker.threadId}...`); - await internalWorker.terminate(); - this.workers.delete(internalWorker.threadId); - } - } else { - for (const [id, thread] of this.workers) { - this.#debug(`Terminating worker ${thread.threadId}...`); - await thread.terminate(); - this.workers.delete(id); - } - } - } -} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts deleted file mode 100644 index fb547a9361..0000000000 --- a/packages/core/src/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './utils/enums'; -export * from './classes/PlayerNodeManager'; - -// eslint-disable-next-line @typescript-eslint/no-inferrable-types -export const version: string = '[VI]{{inject}}[/VI]'; diff --git a/packages/core/src/utils/clients.ts b/packages/core/src/utils/clients.ts deleted file mode 100644 index 6f28f3eebb..0000000000 --- a/packages/core/src/utils/clients.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Collection } from '@discord-player/utils'; -import type { SubscriptionClient } from '../worker/SubscriptionClient'; - -export const clients = new Collection(); diff --git a/packages/core/src/utils/enums.ts b/packages/core/src/utils/enums.ts deleted file mode 100644 index 6b54380f86..0000000000 --- a/packages/core/src/utils/enums.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { keyMirror } from '@discord-player/utils'; - -// prettier-ignore -export const WorkerOp = keyMirror([ - "JOIN_VOICE_CHANNEL", - "CREATE_SUBSCRIPTION", - "DELETE_SUBSCRIPTION", - "GATEWAY_PAYLOAD", - "PLAY" -]); - -// prettier-ignore -export const WorkerEvents = keyMirror([ - "SUBSCRIPTION_CREATE", - "SUBSCRIPTION_DELETE", - "VOICE_STATE_UPDATE", - "ERROR", - "CONNECTION_DESTROY" -]); diff --git a/packages/core/src/worker/AudioNode.ts b/packages/core/src/worker/AudioNode.ts deleted file mode 100644 index d0bf05bfae..0000000000 --- a/packages/core/src/worker/AudioNode.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { createAudioPlayer, createAudioResource, StreamType, VoiceConnection } from 'discord-voip'; - -export interface NodePlayerOptions { - query: string; - metadata: unknown; - initialVolume?: number; -} - -export class AudioNode { - public audioPlayer = createAudioPlayer(); - public constructor(public connection: VoiceConnection, public client: string) { - connection.subscribe(this.audioPlayer); - } - - public get guild() { - return this.connection.joinConfig.guildId; - } - - public get channel() { - return this.connection.joinConfig.channelId; - } - - public play(options: NodePlayerOptions) { - const resource = createAudioResource(options.query, { - inputType: StreamType.Arbitrary, - inlineVolume: typeof options.initialVolume === 'number', - metadata: options.metadata - }); - - if ('initialVolume' in options && resource.volume) { - resource.volume.setVolumeLogarithmic(options.initialVolume!); - } - - this.audioPlayer.play(resource); - } - - public destroy() { - this.connection.destroy(); - } -} diff --git a/packages/core/src/worker/SubscriptionClient.ts b/packages/core/src/worker/SubscriptionClient.ts deleted file mode 100644 index febeaf9e00..0000000000 --- a/packages/core/src/worker/SubscriptionClient.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Collection } from '@discord-player/utils'; -import { DiscordGatewayAdapterLibraryMethods, joinVoiceChannel } from 'discord-voip'; -import { WorkerEvents } from '../utils/enums'; -import { AudioNode } from './AudioNode'; -import { notify } from './notifier'; - -export interface SubscriptionPayload { - channelId: string; - guildId: string; - deafen?: boolean; -} - -export class SubscriptionClient { - public subscriptions = new Collection(); - public adapters = new Collection(); - public constructor(public clientId: string) {} - - public connect(config: SubscriptionPayload) { - const voiceConnection = joinVoiceChannel({ - channelId: config.channelId, - guildId: config.guildId, - selfDeaf: Boolean(config.deafen), - adapterCreator: (adapter) => { - this.adapters.set(config.guildId, adapter); - return { - sendPayload: (payload) => { - notify({ - t: WorkerEvents.VOICE_STATE_UPDATE, - d: payload - }); - return true; - }, - destroy: () => { - this.adapters.delete(config.guildId); - this.subscriptions.delete(config.guildId); - notify({ - t: WorkerEvents.CONNECTION_DESTROY, - d: { - client_id: this.clientId, - guild_id: config.guildId, - channel_id: config.channelId - } - }); - } - }; - } - }); - - this.subscriptions.set(voiceConnection.joinConfig.guildId, new AudioNode(voiceConnection, this.clientId)); - } - - public disconnect(config: Pick) { - const node = this.subscriptions.get(config.guildId); - if (node) { - node.connection.destroy(); - this.subscriptions.delete(config.guildId); - } - } - - public disconnectAll() { - for (const [id, node] of this.subscriptions) { - node.connection.destroy(); - this.subscriptions.delete(id); - } - } -} diff --git a/packages/core/src/worker/actions/CREATE_SUBSCRIPTION.ts b/packages/core/src/worker/actions/CREATE_SUBSCRIPTION.ts deleted file mode 100644 index 30d2c75d69..0000000000 --- a/packages/core/src/worker/actions/CREATE_SUBSCRIPTION.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ServicePayload } from '../../classes/PlayerNodeManager'; -import { WorkerEvents, WorkerOp } from '../../utils/enums'; -import { SubscriptionClient } from '../SubscriptionClient'; -import { BaseAction } from './base/BaseAction'; - -class CreateSubscription extends BaseAction { - public actionName = WorkerOp.CREATE_SUBSCRIPTION; - - public handle(data: ServicePayload) { - if (this.isSubscribed(data)) return; - const client = new SubscriptionClient(data.d.client_id); - this.setClient(data, client); - this.notify({ - t: WorkerEvents.SUBSCRIPTION_CREATE, - d: { - client_id: data.d.client_id, - guild_id: data.d.guild_id - } - }); - } -} - -export default new CreateSubscription(); diff --git a/packages/core/src/worker/actions/DELETE_SUBSCRIPTION.ts b/packages/core/src/worker/actions/DELETE_SUBSCRIPTION.ts deleted file mode 100644 index 28aa1fcfa2..0000000000 --- a/packages/core/src/worker/actions/DELETE_SUBSCRIPTION.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ServicePayload } from '../../classes/PlayerNodeManager'; -import { WorkerEvents, WorkerOp } from '../../utils/enums'; -import { BaseAction } from './base/BaseAction'; - -class DeleteSubscription extends BaseAction { - public actionName = WorkerOp.DELETE_SUBSCRIPTION; - - public handle(data: ServicePayload) { - const client = this.getClient(data); - if (client) { - client.disconnectAll(); - this.deleteClient(data); - this.notify({ - t: WorkerEvents.SUBSCRIPTION_DELETE, - d: { - client_id: client.clientId, - guild_id: data.d.guild_id - } - }); - } - } -} - -export default new DeleteSubscription(); diff --git a/packages/core/src/worker/actions/GATEWAY_PAYLOAD.ts b/packages/core/src/worker/actions/GATEWAY_PAYLOAD.ts deleted file mode 100644 index 3b75699b1f..0000000000 --- a/packages/core/src/worker/actions/GATEWAY_PAYLOAD.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ServicePayload } from '../../classes/PlayerNodeManager'; -import { WorkerOp } from '../../utils/enums'; -import { BaseAction } from './base/BaseAction'; -import { GatewayDispatchEvents } from 'discord-api-types/v10'; - -class JoinVoiceChannel extends BaseAction { - public actionName = WorkerOp.GATEWAY_PAYLOAD; - - public async handle(data: ServicePayload) { - const client = this.getClient(data); - if (!client) return; - const adapter = client.adapters.get(data.d.payload.d.guild_id); - if (!adapter) return; - const message = data.d.payload; - if (message.t === GatewayDispatchEvents.VoiceServerUpdate) { - adapter.onVoiceServerUpdate(message.d); - } else if (message.t === GatewayDispatchEvents.VoiceStateUpdate && message.d.session_id && message.d.user_id === client.clientId) { - adapter.onVoiceStateUpdate(message.d); - } - } -} - -export default new JoinVoiceChannel(); diff --git a/packages/core/src/worker/actions/JOIN_VOICE_CHANNEL.ts b/packages/core/src/worker/actions/JOIN_VOICE_CHANNEL.ts deleted file mode 100644 index 0c12a2ba65..0000000000 --- a/packages/core/src/worker/actions/JOIN_VOICE_CHANNEL.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ServicePayload } from '../../classes/PlayerNodeManager'; -import { WorkerOp } from '../../utils/enums'; -import { BaseAction } from './base/BaseAction'; - -export interface JoinPayload { - channel_id: string; - self_deaf?: boolean; -} - -class JoinVoiceChannel extends BaseAction { - public actionName = WorkerOp.JOIN_VOICE_CHANNEL; - - public async handle(data: ServicePayload) { - const client = this.getClient(data); - if (client) - await client.connect({ - channelId: data.d.channel_id, - guildId: data.d.guild_id, - deafen: data.d.self_deaf - }); - } -} - -export default new JoinVoiceChannel(); diff --git a/packages/core/src/worker/actions/PLAY.ts b/packages/core/src/worker/actions/PLAY.ts deleted file mode 100644 index 7760d14836..0000000000 --- a/packages/core/src/worker/actions/PLAY.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ServicePayload } from '../../classes/PlayerNodeManager'; -import { WorkerOp } from '../../utils/enums'; -import { BaseAction } from './base/BaseAction'; - -export interface PlayPayload { - query: string; - metadata: unknown; - initial_volume?: number; -} - -class Play extends BaseAction { - public actionName = WorkerOp.PLAY; - - public async handle(data: ServicePayload) { - const client = this.getClient(data); - if (!client) return; - const node = client.subscriptions.get(data.d.guild_id); - if (node) { - const { query, metadata, initial_volume } = data.d; - node.play({ query, metadata, initialVolume: initial_volume }); - } - } -} - -export default new Play(); diff --git a/packages/core/src/worker/actions/base/BaseAction.ts b/packages/core/src/worker/actions/base/BaseAction.ts deleted file mode 100644 index 447df3a2af..0000000000 --- a/packages/core/src/worker/actions/base/BaseAction.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { ServicePayload, WorkerPayload } from '../../../classes/PlayerNodeManager'; -import { clients } from '../../../utils/clients'; -import { WorkerOp } from '../../../utils/enums'; -import { notify } from '../../notifier'; -import { SubscriptionClient } from '../../SubscriptionClient'; - -export class BaseAction { - public clients = clients; - public actionName!: keyof typeof WorkerOp; - - public getClient(data: ServicePayload) { - return this.clients.get(data.d.client_id); - } - - public setClient(data: ServicePayload, client: SubscriptionClient) { - return this.clients.set(data.d.client_id, client); - } - - public deleteClient(data: ServicePayload) { - return this.clients.delete(data.d.client_id); - } - - public isSubscribed(data: ServicePayload) { - return this.clients.has(data.d.client_id); - } - - public handle(data: ServicePayload) {} - - public notify(data: WorkerPayload) { - notify(data); - } -} diff --git a/packages/core/src/worker/notifier.ts b/packages/core/src/worker/notifier.ts deleted file mode 100644 index cfdde3ee3b..0000000000 --- a/packages/core/src/worker/notifier.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { parentPort } from 'node:worker_threads'; -import { WorkerPayload } from '../classes/PlayerNodeManager'; - -export function notify(data: WorkerPayload) { - parentPort?.postMessage(data); -} diff --git a/packages/core/src/worker/worker.ts b/packages/core/src/worker/worker.ts deleted file mode 100644 index 45d18c8b26..0000000000 --- a/packages/core/src/worker/worker.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { ServicePayload } from '../classes/PlayerNodeManager'; -import { WorkerEvents } from '../utils/enums'; -import type { BaseAction } from './actions/base/BaseAction'; -import { notify } from './notifier'; -import { parentPort } from 'node:worker_threads'; - -parentPort?.on('message', async (message: ServicePayload) => { - const action = getAction(message.op); - if (action) { - try { - return void (await action.handle(message)); - } catch (e) { - return notify({ - t: WorkerEvents.ERROR, - d: { - message: `${(e as any).stack || e}` - } - }); - } - } -}); - -function getAction(op: string) { - try { - const action = require(`${__dirname}/actions/${op}`); - return (action.default || action) as BaseAction; - } catch { - return null; - } -} diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json deleted file mode 100644 index f19c9a585f..0000000000 --- a/packages/core/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "@discord-player/tsconfig/base.json", - "include": ["src/**/*"], - "compilerOptions": { - "esModuleInterop": true - } -} \ No newline at end of file diff --git a/packages/core/tsup.config.ts b/packages/core/tsup.config.ts deleted file mode 100644 index e161ba4d4d..0000000000 --- a/packages/core/tsup.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { defineConfig } from '../../tsup.config'; -import { esbuildPluginVersionInjector } from 'esbuild-plugin-version-injector'; - -export default defineConfig({ - esbuildPlugins: [esbuildPluginVersionInjector()] -}); diff --git a/packages/discord-player/package.json b/packages/discord-player/package.json index da3fd98fb2..cdb06d2fce 100644 --- a/packages/discord-player/package.json +++ b/packages/discord-player/package.json @@ -1,6 +1,6 @@ { "name": "discord-player", - "version": "6.6.10", + "version": "7.0.0-dev.0", "description": "Complete framework to facilitate music commands using discord.js", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -56,10 +56,8 @@ "@web-scrobbler/metadata-filter": "^3.1.0", "discord-voip": "^0.1.3", "ip": "^2.0.1", - "libsodium-wrappers": "^0.7.13" - }, - "peerDependencies": { - "@discord-player/extractor": "workspace:^" + "libsodium-wrappers": "^0.7.13", + "ws": "^8.17.0" }, "devDependencies": { "@discord-player/tsconfig": "workspace:^", @@ -78,4 +76,4 @@ "readmeFile": "./README.md", "tsconfig": "./tsconfig.json" } -} \ No newline at end of file +} diff --git a/packages/discord-player/src/DefaultVoiceStateHandler.ts b/packages/discord-player/src/DefaultVoiceStateHandler.ts deleted file mode 100644 index 70f97b8e79..0000000000 --- a/packages/discord-player/src/DefaultVoiceStateHandler.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { ChannelType, VoiceState } from 'discord.js'; -import { GuildQueue, GuildQueueEvent } from './queue'; -import { Player } from './Player'; -import { Util } from './utils/Util'; - -export async function defaultVoiceStateHandler(player: Player, queue: GuildQueue, oldState: VoiceState, newState: VoiceState) { - if (!queue || !queue.connection || !queue.channel) return; - - if (oldState.channelId && !newState.channelId && newState.member?.id === newState.guild.members.me?.id) { - try { - queue.delete(); - } catch { - /* noop */ - } - return void player.events.emit(GuildQueueEvent.disconnect, queue); - } - - if (queue.options.pauseOnEmpty) { - const isEmpty = Util.isVoiceEmpty(queue.channel); - - if (isEmpty) { - queue.node.setPaused(true); - Reflect.set(queue, '__pausedOnEmpty', true); - if (queue.hasDebugger) { - queue.debug('Voice channel is empty and options#pauseOnEmpty is true, pausing...'); - } - } else { - if (Reflect.get(queue, '__pausedOnEmpty')) { - queue.node.setPaused(false); - Reflect.set(queue, '__pausedOnEmpty', false); - if (queue.hasDebugger) { - queue.debug('Voice channel is not empty and options#pauseOnEmpty is true, resuming...'); - } - } - } - } - - if (!oldState.channelId && newState.channelId && newState.member?.id === newState.guild.members.me?.id) { - if (newState.serverMute != null && oldState.serverMute !== newState.serverMute) { - queue.node.setPaused(newState.serverMute); - } else if (newState.channel?.type === ChannelType.GuildStageVoice && newState.suppress != null && oldState.suppress !== newState.suppress) { - queue.node.setPaused(newState.suppress); - if (newState.suppress) { - newState.guild.members.me?.voice.setRequestToSpeak(true).catch(Util.noop); - } - } - } - - if (!newState.channelId && oldState.channelId === queue.channel.id) { - if (!Util.isVoiceEmpty(queue.channel)) return; - const timeout = setTimeout(() => { - if (!Util.isVoiceEmpty(queue.channel!)) return; - if (!player.nodes.has(queue.guild.id)) return; - if (queue.options.leaveOnEmpty) queue.delete(); - player.events.emit(GuildQueueEvent.emptyChannel, queue); - }, queue.options.leaveOnEmptyCooldown || 0).unref(); - queue.timeouts.set(`empty_${oldState.guild.id}`, timeout); - } - - if (newState.channelId && newState.channelId === queue.channel.id) { - const emptyTimeout = queue.timeouts.get(`empty_${oldState.guild.id}`); - const channelEmpty = Util.isVoiceEmpty(queue.channel); - if (!channelEmpty && emptyTimeout) { - clearTimeout(emptyTimeout); - queue.timeouts.delete(`empty_${oldState.guild.id}`); - player.events.emit(GuildQueueEvent.channelPopulate, queue); - } - } - - if (oldState.channelId && newState.channelId && oldState.channelId !== newState.channelId) { - if (newState.member?.id === newState.guild.members.me?.id) { - if (queue.connection && newState.member?.id === newState.guild.members.me?.id) queue.channel = newState.channel!; - const emptyTimeout = queue.timeouts.get(`empty_${oldState.guild.id}`); - const channelEmpty = Util.isVoiceEmpty(queue.channel); - if (!channelEmpty && emptyTimeout) { - clearTimeout(emptyTimeout); - queue.timeouts.delete(`empty_${oldState.guild.id}`); - player.events.emit(GuildQueueEvent.channelPopulate, queue); - } else { - const timeout = setTimeout(() => { - if (queue.connection && !Util.isVoiceEmpty(queue.channel!)) return; - if (!player.nodes.has(queue.guild.id)) return; - if (queue.options.leaveOnEmpty) queue.delete(); - player.events.emit(GuildQueueEvent.emptyChannel, queue); - }, queue.options.leaveOnEmptyCooldown || 0).unref(); - queue.timeouts.set(`empty_${oldState.guild.id}`, timeout); - } - } else { - if (newState.channelId !== queue.channel.id) { - const channelEmpty = Util.isVoiceEmpty(queue.channel!); - if (!channelEmpty) return; - if (queue.timeouts.has(`empty_${oldState.guild.id}`)) return; - const timeout = setTimeout(() => { - if (!Util.isVoiceEmpty(queue.channel!)) return; - if (!player.nodes.has(queue.guild.id)) return; - if (queue.options.leaveOnEmpty) queue.delete(); - player.events.emit(GuildQueueEvent.emptyChannel, queue); - }, queue.options.leaveOnEmptyCooldown || 0).unref(); - queue.timeouts.set(`empty_${oldState.guild.id}`, timeout); - } else { - const emptyTimeout = queue.timeouts.get(`empty_${oldState.guild.id}`); - const channelEmpty = Util.isVoiceEmpty(queue.channel!); - if (!channelEmpty && emptyTimeout) { - clearTimeout(emptyTimeout); - queue.timeouts.delete(`empty_${oldState.guild.id}`); - player.events.emit(GuildQueueEvent.channelPopulate, queue); - } - } - } - } -} diff --git a/packages/discord-player/src/Player.ts b/packages/discord-player/src/Player.ts index 446c7dd4e3..2ac871dae1 100644 --- a/packages/discord-player/src/Player.ts +++ b/packages/discord-player/src/Player.ts @@ -1,679 +1,15 @@ -import { FFmpeg } from '@discord-player/ffmpeg'; -import { Client, SnowflakeUtil, VoiceState, IntentsBitField, User, GuildVoiceChannelResolvable, version as djsVersion, Events } from 'discord.js'; -import { Playlist, Track, SearchResult } from './fabric'; -import { GuildQueueEvents, VoiceConnectConfig, GuildNodeCreateOptions, GuildNodeManager, GuildQueue, ResourcePlayOptions, GuildQueueEvent } from './queue'; -import { VoiceUtils } from './VoiceInterface/VoiceUtils'; -import { PlayerEvents, QueryType, SearchOptions, PlayerInitOptions, PlaylistInitData, SearchQueryType, PlayerEvent } from './types/types'; -import { QueryResolver, ResolvedQuery } from './utils/QueryResolver'; -import { Util } from './utils/Util'; -import { generateDependencyReport, version as dVoiceVersion } from 'discord-voip'; -import { ExtractorExecutionContext } from './extractors/ExtractorExecutionContext'; -import { BaseExtractor } from './extractors/BaseExtractor'; -import * as _internals from './utils/__internal__'; -import { QueryCache } from './utils/QueryCache'; -import { PlayerEventsEmitter } from './utils/PlayerEventsEmitter'; -import { Exceptions } from './errors'; -import { defaultVoiceStateHandler } from './DefaultVoiceStateHandler'; -import { IPRotator } from './utils/IPRotator'; -import { Context, createContext } from './hooks'; -import { HooksCtx } from './hooks/common'; -import { LrcLib } from './lrclib/LrcLib'; +import type { IAdapter } from './adapter'; +import { setPlayerAdapterContext } from './context'; -const kSingleton = Symbol('InstanceDiscordPlayerSingleton'); - -export interface PlayerNodeInitializationResult { - track: Track; - extractor: BaseExtractor | null; - searchResult: SearchResult; - queue: GuildQueue; -} - -export type TrackLike = string | Track | SearchResult | Track[] | Playlist; - -export interface PlayerNodeInitializerOptions extends SearchOptions { - nodeOptions?: GuildNodeCreateOptions; - connectionOptions?: VoiceConnectConfig; - audioPlayerOptions?: ResourcePlayOptions; - signal?: AbortSignal; - afterSearch?: (result: SearchResult) => Promise; -} - -export type VoiceStateHandler = (player: Player, queue: GuildQueue, oldVoiceState: VoiceState, newVoiceState: VoiceState) => Awaited; - -export class Player extends PlayerEventsEmitter { - #lastLatency = -1; - #voiceStateUpdateListener = this.handleVoiceState.bind(this); - #lagMonitorTimeout!: NodeJS.Timeout; - #lagMonitorInterval!: NodeJS.Timer; - #onVoiceStateUpdate: VoiceStateHandler = defaultVoiceStateHandler; - #hooksCtx: Context | null = null; - /** - * The version of discord-player - */ - public static readonly version: string = '[VI]{{inject}}[/VI]'; - public static _singletonKey = kSingleton; - /** - * The unique identifier of this player instance - */ - public readonly id = SnowflakeUtil.generate().toString(); - /** - * The discord.js client - */ - public readonly client!: Client; - /** - * The player options - */ - public readonly options!: PlayerInitOptions; - /** - * The player nodes (queue) manager - */ - public nodes = new GuildNodeManager(this); - /** - * The voice api utilities - */ - public readonly voiceUtils = new VoiceUtils(this); - /** - * The extractors manager - */ - public extractors = new ExtractorExecutionContext(this); - /** - * The player events channel - */ - public events = new PlayerEventsEmitter([GuildQueueEvent.Error, GuildQueueEvent.PlayerError]); - /** - * The route planner - */ - public routePlanner: IPRotator | null = null; - /** - * The player version - */ - public readonly version = Player.version; - /** - * The lyrics api - */ - public readonly lyrics = new LrcLib(this); - - /** - * Creates new Discord Player - * @param {Client} client The Discord Client - * @param {PlayerInitOptions} [options] The player init options - */ - public constructor(client: Client, options: PlayerInitOptions = {}) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (!options.ignoreInstance && kSingleton in Player) return (Player)[kSingleton] as Player; - - super([PlayerEvent.Error]); - - /** - * The discord.js client - * @type {Client} - */ - this.client = client; - - try { - if (!(client instanceof Client)) { - Util.warn( - `Client is not an instance of discord.js@${djsVersion} client, some things may not work correctly. This can happen due to corrupt dependencies or having multiple installations of discord.js.`, - 'InvalidClientInstance' - ); - } - - const ibf = this.client.options.intents instanceof IntentsBitField ? this.client.options.intents : new IntentsBitField(this.client.options.intents); - - if (!ibf.has(IntentsBitField.Flags.GuildVoiceStates)) { - Util.warn('client is missing "GuildVoiceStates" intent', 'InvalidIntentsBitField'); - } - } catch { - // noop - } - - this.options = { - lockVoiceStateHandler: false, - blockExtractors: [], - blockStreamFrom: [], - connectionTimeout: 20000, - lagMonitor: 30000, - queryCache: options.queryCache === null ? null : options.queryCache || new QueryCache(this), - useLegacyFFmpeg: false, - skipFFmpeg: true, - probeTimeout: 5000, - ...options, - ytdlOptions: { - highWaterMark: 1 << 25, - ...options.ytdlOptions - } - } as PlayerInitOptions; - - this.client.setMaxListeners(this.client.getMaxListeners() + 1); - this.client.on(Events.VoiceStateUpdate, this.#voiceStateUpdateListener); - - if (typeof this.options.lagMonitor === 'number' && this.options.lagMonitor > 0) { - this.#lagMonitorInterval = setInterval(() => { - const start = performance.now(); - this.#lagMonitorTimeout = setTimeout(() => { - this.#lastLatency = performance.now() - start; - if (this.hasDebugger) this.debug(`[Lag Monitor] Event loop latency: ${this.#lastLatency}ms`); - }, 0).unref(); - }, this.options.lagMonitor).unref(); - } - - if (this.options.ipconfig) { - this.routePlanner = new IPRotator(this.options.ipconfig); - } - - _internals.addPlayer(this); - - if (!(kSingleton in Player)) { - Object.defineProperty(Player, kSingleton, { - value: this, - writable: true, - configurable: true, - enumerable: false - }); - } - } - - /** - * The hooks context for this player instance. - */ - public get context() { - if (!this.#hooksCtx) { - this.#hooksCtx = createContext(); - } - - return this.#hooksCtx; - } - - /** - * Override default voice state update handler - * @param handler The handler callback - */ - public onVoiceStateUpdate(handler: VoiceStateHandler) { - this.#onVoiceStateUpdate = handler; - } - - public debug(m: string) { - return this.emit('debug', m); - } - - /** - * Creates discord-player singleton instance. - * @param client The client that instantiated player - * @param options Player initializer options - */ - public static singleton(client: Client, options: Omit = {}) { - return new Player(client, { - ...options, - ignoreInstance: false - }); - } - - /** - * Creates new discord-player instance. - * @param client The client that instantiated player - * @param options Player initializer options - */ - public static create(client: Client, options: Omit = {}) { - return new Player(client, { - ...options, - ignoreInstance: true - }); - } - - /** - * Get all active master player instances - */ - public static getAllPlayers() { - return _internals.getPlayers(); - } - - /** - * Clear all master player instances - */ - public static clearAllPlayers() { - return _internals.instances.clear(); - } - - /** - * The current query cache provider in use - */ - public get queryCache() { - return this.options.queryCache ?? null; - } - - /** - * Alias to `Player.nodes`. - */ - public get queues() { - return this.nodes; - } - - /** - * Event loop latency in ms. If your bot is laggy and this returns a number above 20ms for example, - * some expensive task is being executed on the current thread which is slowing down the event loop. - * @type {number} - */ - public get eventLoopLag() { - return this.#lastLatency; - } - - /** - * Generates statistics that could be useful. Statistics generator is still experimental. - * @example ```typescript - * const stats = player.generateStatistics(); - * - * console.log(stats); - * - * // outputs something like - * // { - * // instances: number, - * // queuesCount: number, - * // queryCacheEnabled: boolean, - * // queues: [ - * // GuildQueueStatisticsMetadata, - * // GuildQueueStatisticsMetadata, - * // GuildQueueStatisticsMetadata, - * // ... - * // ] - * // } - * ``` - */ - public generateStatistics() { - return { - instances: _internals.instances.size, - queuesCount: this.queues.cache.size, - queryCacheEnabled: this.queryCache != null, - queues: this.queues.cache.map((m) => m.stats.generate()) - }; - } - - /** - * Destroy every single queues managed by this master player instance - * @example ```typescript - * // use me when you want to immediately terminate every single queues in existence 🔪 - * await player.destroy(); - * ``` - */ - public async destroy() { - this.nodes.cache.forEach((node) => node.delete()); - this.client.off(Events.VoiceStateUpdate, this.#voiceStateUpdateListener); - this.client.setMaxListeners(this.client.getMaxListeners() - 1); - this.removeAllListeners(); - this.events.removeAllListeners(); - await this.extractors.unregisterAll(); - if (this.#lagMonitorInterval) clearInterval(this.#lagMonitorInterval); - if (this.#lagMonitorTimeout) clearInterval(this.#lagMonitorTimeout); - _internals.clearPlayer(this); - } - - private _handleVoiceState(oldState: VoiceState, newState: VoiceState) { - const queue = this.nodes.get(oldState.guild.id); - if (!queue || !queue.connection || !queue.channel) return; - - // dispatch voice state update - const wasHandled = this.events.emit(GuildQueueEvent.voiceStateUpdate, queue, oldState, newState); - // if the event was handled, return assuming the listener implemented all of the logic below - if (wasHandled && !this.options.lockVoiceStateHandler) return; - - return this.#onVoiceStateUpdate(this, queue, oldState, newState); - } - - /** - * Handles voice state update - * @param {VoiceState} oldState The old voice state - * @param {VoiceState} newState The new voice state - * @returns {void} - * @example ```typescript - * // passing voice state update data to this method will trigger voice state handler - * - * client.on('voiceStateUpdate', (oldState, newState) => { - * // this is definitely a rocket science, right here - * player.handleVoiceState(oldState, newState); - * }); - * ``` - */ - public handleVoiceState(oldState: VoiceState, newState: VoiceState): void { - this._handleVoiceState(oldState, newState); - } - - /** - * Lock voice state handler. When this method is called, discord-player will keep using the default voice state update handler, even if custom implementation exists. - */ - public lockVoiceStateHandler() { - this.options.lockVoiceStateHandler = true; - } - - /** - * Unlock voice state handler. When this method is called, discord-player will stop using the default voice state update handler if custom implementation exists. - */ - public unlockVoiceStateHandler() { - this.options.lockVoiceStateHandler = false; - } - - /** - * Checks if voice state handler is locked. - */ - public isVoiceStateHandlerLocked() { - return !!this.options.lockVoiceStateHandler; - } - - /** - * Initiate audio player - * @param channel The voice channel on which the music should be played - * @param query The track or source to play - * @param options Options for player - * @example ```typescript - * // no need to worry about queue management, just use this method 😄 - * const query = 'this is my super cool search query that I want to play'; - * - * try { - * const { track } = await player.play(voiceChannel, query); - * console.log(`🎉 I am playing ${track.title} 🎉`); - * } catch(e) { - * console.log(`😭 Failed to play error oh no:\n\n${e}`); - * } - * ``` - */ - public async play(channel: GuildVoiceChannelResolvable, query: TrackLike, options: PlayerNodeInitializerOptions = {}): Promise> { - const vc = this.client.channels.resolve(channel); - if (!vc?.isVoiceBased()) throw Exceptions.ERR_INVALID_ARG_TYPE('channel', 'VoiceBasedChannel', !vc ? 'undefined' : `channel type ${vc.type}`); - - const originalResult = query instanceof SearchResult ? query : await this.search(query, options); - const result = (await options.afterSearch?.(originalResult)) || originalResult; - if (result.isEmpty()) { - throw Exceptions.ERR_NO_RESULT(`No results found for "${query}" (Extractor: ${result.extractor?.identifier || 'N/A'})`); - } - - const queue = this.nodes.create(vc.guild, options.nodeOptions); - - if (this.hasDebugger) this.debug(`[AsyncQueue] Acquiring an entry...`); - const entry = queue.tasksQueue.acquire({ signal: options.signal }); - if (this.hasDebugger) this.debug(`[AsyncQueue] Entry ${entry.id} was acquired successfully!`); - - if (this.hasDebugger) this.debug(`[AsyncQueue] Waiting for the queue to resolve...`); - await entry.getTask(); - if (this.hasDebugger) this.debug(`[AsyncQueue] Entry ${entry.id} was resolved!`); - - try { - if (!queue.channel) await queue.connect(vc, options.connectionOptions); - - if (!result.playlist) { - queue.addTrack(result.tracks[0]); - } else { - queue.addTrack(result.playlist); - } - if (!queue.isPlaying()) await queue.node.play(null, options.audioPlayerOptions); - } finally { - if (this.hasDebugger) this.debug(`[AsyncQueue] Releasing an entry from the queue...`); - queue.tasksQueue.release(); - } - - return { - track: result.tracks[0], - extractor: result.extractor, - searchResult: result, - queue - }; - } - - /** - * Search tracks - * @param {string | Track | Track[] | Playlist | SearchResult} query The search query - * @param {SearchOptions} options The search options - * @returns {Promise} - * @example ```typescript - * const searchQuery = 'pass url or text or discord-player track constructable objects, we got you covered 😎'; - * const result = await player.search(searchQuery); - * - * console.log(result); // Logs `SearchResult` object - * ``` - */ - public async search(searchQuery: string | Track | Track[] | Playlist | SearchResult, options: SearchOptions = {}): Promise { - if (searchQuery instanceof SearchResult) return searchQuery; - - if (options.requestedBy != null) options.requestedBy = this.client.users.resolve(options.requestedBy)!; - options.blockExtractors ??= this.options.blockExtractors; - options.fallbackSearchEngine ??= QueryType.AUTO_SEARCH; - - if (searchQuery instanceof Track) { - return new SearchResult(this, { - playlist: searchQuery.playlist || null, - tracks: [searchQuery], - query: searchQuery.title, - extractor: searchQuery.extractor, - queryType: searchQuery.queryType, - requestedBy: options.requestedBy - }); - } - - if (searchQuery instanceof Playlist) { - return new SearchResult(this, { - playlist: searchQuery, - tracks: searchQuery.tracks, - query: searchQuery.title, - extractor: searchQuery.tracks[0]?.extractor, - queryType: QueryType.AUTO, - requestedBy: options.requestedBy - }); - } - - if (Array.isArray(searchQuery)) { - const tracks = searchQuery.filter((t) => t instanceof Track); - return new SearchResult(this, { - playlist: null, - tracks, - query: '@@#%{{UserLoadedContent}}%#@@', - extractor: null, - queryType: QueryType.AUTO, - requestedBy: options.requestedBy - }); - } - - if (this.hasDebugger) this.debug(`Searching ${searchQuery}`); - - let extractor: BaseExtractor | null = null, - protocol: string | null = null; - - options.searchEngine ??= QueryType.AUTO; - options.fallbackSearchEngine ??= QueryType.AUTO_SEARCH; - - if (this.hasDebugger) this.debug(`Search engine set to ${options.searchEngine}, fallback search engine set to ${options.fallbackSearchEngine}`); - - if (/^\w+:/.test(searchQuery)) { - const [protocolName, ...query] = searchQuery.split(':'); - if (this.hasDebugger) this.debug(`Protocol ${protocolName} detected in query`); - - const matchingExtractor = this.extractors.store.find((e) => !this.extractors.isDisabled(e.identifier) && e.protocols.includes(protocolName)); - - if (matchingExtractor) { - if (this.hasDebugger) this.debug(`Protocol ${protocolName} is supported by ${matchingExtractor.identifier} extractor!`); - extractor = matchingExtractor; - searchQuery = query.join(':'); - protocol = protocolName; - } else { - if (this.hasDebugger) this.debug(`Could not find an extractor that supports ${protocolName} protocol. Falling back to default behavior...`); - } - } - - const redirected = await QueryResolver.preResolve(searchQuery); - const { type: queryType, query } = - options.searchEngine === QueryType.AUTO ? QueryResolver.resolve(redirected, options.fallbackSearchEngine) : ({ type: options.searchEngine, query: redirected } as ResolvedQuery); - - if (this.hasDebugger) this.debug(`Query type identified as ${queryType}${extractor && protocol ? ' but might not be used due to the presence of protocol' : ''}`); - - // force particular extractor - if (options.searchEngine.startsWith('ext:')) { - if (this.hasDebugger) this.debug(`Forcing ${options.searchEngine.substring(4)} extractor...`); - extractor = this.extractors.get(options.searchEngine.substring(4))!; - if (!extractor) - return new SearchResult(this, { - query, - queryType, - extractor, - requestedBy: options.requestedBy - }); - } - - // query all extractors - if (!extractor) { - // cache validation - if (!options.ignoreCache) { - if (this.hasDebugger) this.debug(`Checking cache...`); - const res = await this.queryCache?.resolve({ - query, - queryType, - requestedBy: options.requestedBy - }); - // cache hit - if (res?.hasTracks()) { - if (this.hasDebugger) this.debug(`Cache hit for query ${query}`); - return res; - } - - if (this.hasDebugger) this.debug(`Cache miss for query ${query}`); - } - - if (this.hasDebugger) this.debug(`Executing extractors...`); - - // cache miss - extractor = - ( - await this.extractors.run(async (ext) => { - if (options.blockExtractors?.includes(ext.identifier)) return false; - return ext.validate(query, queryType as SearchQueryType); - }) - )?.extractor || null; - } - - // no extractors available - if (!extractor) { - if (this.hasDebugger) this.debug('Failed to find appropriate extractor'); - return new SearchResult(this, { - query, - queryType, - requestedBy: options.requestedBy - }); - } - - if (this.hasDebugger) this.debug(`Executing metadata query using ${extractor.identifier} extractor...`); - const res = await extractor - .handle(query, { - type: queryType as SearchQueryType, - requestedBy: options.requestedBy as User, - requestOptions: options.requestOptions, - protocol - }) - .catch(() => null); - - if (res) { - if (this.hasDebugger) this.debug('Metadata query was successful!'); - const result = new SearchResult(this, { - query, - queryType, - playlist: res.playlist, - tracks: res.tracks, - extractor, - requestedBy: options.requestedBy - }); - - if (!options.ignoreCache) { - if (this.hasDebugger) this.debug(`Adding data to cache...`); - await this.queryCache?.addData(result); - } - - return result; - } - - if (this.hasDebugger) this.debug('Failed to find result using appropriate extractor. Querying all extractors...'); - const result = await this.extractors.run( - async (ext) => - !options.blockExtractors?.includes(ext.identifier) && - (await ext.validate(query)) && - ext.handle(query, { - type: queryType as SearchQueryType, - requestedBy: options.requestedBy as User, - requestOptions: options.requestOptions, - protocol - }) - ); - if (!result?.result) { - if (this.hasDebugger) this.debug(`Failed to query metadata query using ${result?.extractor.identifier || 'N/A'} extractor.`); - return new SearchResult(this, { - query, - queryType, - requestedBy: options.requestedBy, - extractor: result?.extractor - }); - } - - if (this.hasDebugger) this.debug(`Metadata query was successful using ${result.extractor.identifier}!`); - - const data = new SearchResult(this, { - query, - queryType, - playlist: result.result.playlist, - tracks: result.result.tracks, - extractor: result.extractor, - requestedBy: options.requestedBy - }); - - if (!options.ignoreCache) { - if (this.hasDebugger) this.debug(`Adding data to cache...`); - await this.queryCache?.addData(data); - } - - return data; - } - - /** - * Generates a report of the dependencies used by the `discord-voip` module. Useful for debugging. - * @example ```typescript - * console.log(player.scanDeps()); - * // -> logs dependencies report - * ``` - * @returns {string} - */ - public scanDeps() { - const line = '-'.repeat(50); - const runtime = 'Bun' in globalThis ? 'Bun' : 'Deno' in globalThis ? 'Deno' : 'Node'; - const depsReport = [ - 'Discord Player', - line, - `- discord-player: ${Player.version}`, - `- discord-voip: ${dVoiceVersion}`, - `- discord.js: ${djsVersion}`, - `- Node version: ${process.version} (Detected Runtime: ${runtime}, Platform: ${process.platform} [${process.arch}])`, - (() => { - if (this.options.useLegacyFFmpeg) return '- ffmpeg: N/A (using legacy ffmpeg)'; - const info = FFmpeg.locateSafe(); - if (!info) return 'FFmpeg/Avconv not found'; - - return [`- ffmpeg: ${info.version}`, `- command: ${info.command}`, `- static: ${info.isStatic}`, `- libopus: ${info.metadata!.includes('--enable-libopus')}`].join('\n'); - })(), - '\n', - 'Loaded Extractors:', - line, - this.extractors.store - .map((m) => { - return m.identifier; - }) - .join('\n') || 'N/A', - '\n\ndiscord-voip', - generateDependencyReport() - ]; - - return depsReport.join('\n'); +export class Player { + public readonly adapter: IAdapter; + public constructor(adapter: PlayerAdapterInterface) { + this.adapter = setPlayerAdapterContext(this, adapter); } +} - public *[Symbol.iterator]() { - yield* this.nodes.cache.values(); - } +export type PlayerAdapterInterface = () => IAdapter; - /** - * Creates `Playlist` instance - * @param data The data to initialize a playlist - */ - public createPlaylist(data: PlaylistInitData) { - return new Playlist(this, data); - } +export function createPlayer(adapter: PlayerAdapterInterface>) { + return new Player(adapter); } diff --git a/packages/discord-player/src/VoiceInterface/StreamDispatcher.ts b/packages/discord-player/src/VoiceInterface/StreamDispatcher.ts deleted file mode 100644 index 91a569e095..0000000000 --- a/packages/discord-player/src/VoiceInterface/StreamDispatcher.ts +++ /dev/null @@ -1,435 +0,0 @@ -import { - AudioPlayer, - AudioPlayerError, - AudioPlayerStatus, - AudioResource, - createAudioPlayer, - createAudioResource, - entersState, - StreamType, - VoiceConnection, - VoiceConnectionStatus, - VoiceConnectionDisconnectReason -} from 'discord-voip'; -import { StageChannel, VoiceChannel } from 'discord.js'; -import type { Readable } from 'stream'; -import { EventEmitter } from '@discord-player/utils'; -import { Track } from '../fabric/Track'; -import { Util } from '../utils/Util'; -import { EqualizerBand, BiquadFilters, PCMFilters, FiltersChain } from '@discord-player/equalizer'; -import { GuildQueue, GuildQueueEvent, PostProcessedResult } from '../queue'; -import { VoiceReceiverNode } from '../queue/VoiceReceiverNode'; -import { Exceptions } from '../errors'; - -export interface CreateStreamOps { - type?: StreamType; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - data?: any; - disableVolume?: boolean; - disableEqualizer?: boolean; - disableBiquad?: boolean; - eq?: EqualizerBand[]; - biquadFilter?: BiquadFilters; - disableFilters?: boolean; - defaultFilters?: PCMFilters[]; - volume?: number; - disableResampler?: boolean; - sampleRate?: number; - skipFFmpeg?: boolean; -} - -export interface VoiceEvents { - /* eslint-disable @typescript-eslint/no-explicit-any */ - error: (error: AudioPlayerError) => any; - debug: (message: string) => any; - start: (resource: AudioResource) => any; - finish: (resource: AudioResource) => any; - dsp: (filters: PCMFilters[]) => any; - eqBands: (filters: EqualizerBand[]) => any; - sampleRate: (filters: number) => any; - biquad: (filters: BiquadFilters) => any; - volume: (volume: number) => any; - destroyed: () => any; - /* eslint-enable @typescript-eslint/no-explicit-any */ -} - -class StreamDispatcher extends EventEmitter { - public voiceConnection: VoiceConnection; - public audioPlayer: AudioPlayer; - public receiver = new VoiceReceiverNode(this); - public channel: VoiceChannel | StageChannel; - public audioResource?: AudioResource | null; - public dsp = new FiltersChain(); - - /** - * Creates new connection object - * @param {VoiceConnection} connection The connection - * @param {VoiceChannel|StageChannel} channel The connected channel - * @private - */ - constructor(connection: VoiceConnection, channel: VoiceChannel | StageChannel, public queue: GuildQueue, public readonly connectionTimeout: number = 20000, audioPlayer?: AudioPlayer) { - super(); - - /** - * The voice connection - * @type {VoiceConnection} - */ - this.voiceConnection = connection; - - /** - * The audio player - * @type {AudioPlayer} - */ - this.audioPlayer = - audioPlayer || - createAudioPlayer({ - debug: this.queue.hasDebugger - }); - - /** - * The voice channel - * @type {VoiceChannel|StageChannel} - */ - this.channel = channel; - - this.voiceConnection.on('debug', (m) => void this.emit('debug', m)); - this.voiceConnection.on('error', (error) => void this.emit('error', error as AudioPlayerError)); - this.audioPlayer.on('debug', (m) => void this.emit('debug', m)); - this.audioPlayer.on('error', (error) => void this.emit('error', error)); - - this.dsp.onUpdate = () => { - if (!this.dsp) return; - if (this.dsp.filters?.filters) this.emit('dsp', this.dsp.filters?.filters); - if (this.dsp.biquad?.filter) this.emit('biquad', this.dsp.biquad?.filter); - if (this.dsp.equalizer) this.emit('eqBands', this.dsp.equalizer.getEQ()); - if (this.dsp.volume) this.emit('volume', this.dsp.volume.volume); - if (this.dsp.resampler) this.emit('sampleRate', this.dsp.resampler.targetSampleRate); - }; - - this.dsp.onError = (e) => this.emit('error', e as AudioPlayerError); - - this.voiceConnection - .on(VoiceConnectionStatus.Disconnected, async (oldState, newState) => { - if (newState.reason === VoiceConnectionDisconnectReason.Manual) { - this.destroy(); - return; - } - - if (newState.reason === VoiceConnectionDisconnectReason.WebSocketClose && newState.closeCode === 4014) { - try { - await entersState(this.voiceConnection, VoiceConnectionStatus.Connecting, this.connectionTimeout); - } catch { - try { - if (this.voiceConnection.state.status !== VoiceConnectionStatus.Destroyed) this.destroy(); - } catch (err) { - this.emit('error', err as AudioPlayerError); - } - } - } else if (this.voiceConnection.rejoinAttempts < 5) { - await Util.wait((this.voiceConnection.rejoinAttempts + 1) * 5000); - this.voiceConnection.rejoin(); - } else { - try { - if (this.voiceConnection.state.status !== VoiceConnectionStatus.Destroyed) this.destroy(); - } catch (err) { - this.emit('error', err as AudioPlayerError); - } - } - }) - .on(VoiceConnectionStatus.Destroyed, () => { - this.end(); - this.queue.emit(GuildQueueEvent.connectionDestroyed, this.queue); - }); - - this.audioPlayer.on('stateChange', (oldState, newState) => { - if (oldState.status !== AudioPlayerStatus.Paused && newState.status === AudioPlayerStatus.Paused) { - this.queue.emit(GuildQueueEvent.playerPause, this.queue); - } - - if (oldState.status === AudioPlayerStatus.Paused && newState.status !== AudioPlayerStatus.Paused) { - this.queue.emit(GuildQueueEvent.playerResume, this.queue); - } - - if (newState.status === AudioPlayerStatus.Playing) { - if (oldState.status === AudioPlayerStatus.Idle || oldState.status === AudioPlayerStatus.Buffering) { - return this.emit('start', this.audioResource!); - } - } else if (newState.status === AudioPlayerStatus.Idle && oldState.status !== AudioPlayerStatus.Idle) { - this.emit('finish', this.audioResource!); - this.dsp.destroy(); - this.audioResource = null; - } - }); - - this.voiceConnection.subscribe(this.audioPlayer); - } - - /** - * Check if the player has been paused manually - */ - get paused() { - return this.audioPlayer.state.status === AudioPlayerStatus.Paused; - } - - set paused(val: boolean) { - val ? this.pause(true) : this.resume(); - } - - /** - * Whether or not the player is currently paused automatically or manually. - */ - isPaused() { - return this.paused || this.audioPlayer.state.status === AudioPlayerStatus.AutoPaused; - } - - /** - * Whether or not the player is currently buffering - */ - isBuffering() { - return this.audioPlayer.state.status === AudioPlayerStatus.Buffering; - } - - /** - * Whether or not the player is currently playing - */ - isPlaying() { - return this.audioPlayer.state.status === AudioPlayerStatus.Playing; - } - - /** - * Whether or not the player is currently idle - */ - isIdle() { - return this.audioPlayer.state.status === AudioPlayerStatus.Idle; - } - - /** - * Whether or not the voice connection has been destroyed - */ - isDestroyed() { - return this.voiceConnection.state.status === VoiceConnectionStatus.Destroyed; - } - - /** - * Whether or not the voice connection has been destroyed - */ - isDisconnected() { - return this.voiceConnection.state.status === VoiceConnectionStatus.Disconnected; - } - - /** - * Whether or not the voice connection is ready to play - */ - isReady() { - return this.voiceConnection.state.status === VoiceConnectionStatus.Ready; - } - - /** - * Whether or not the voice connection is signalling - */ - isSignalling() { - return this.voiceConnection.state.status === VoiceConnectionStatus.Signalling; - } - - /** - * Whether or not the voice connection is connecting - */ - isConnecting() { - return this.voiceConnection.state.status === VoiceConnectionStatus.Connecting; - } - - /** - * Creates stream - * @param {Readable} src The stream source - * @param {object} [ops] Options - * @returns {AudioResource} - */ - async createStream(src: Readable, ops?: CreateStreamOps) { - if (!ops?.disableFilters && this.queue.hasDebugger) this.queue.debug('Initiating DSP filters pipeline...'); - const stream = !ops?.disableFilters - ? this.dsp.create(src, { - dsp: { - filters: ops?.defaultFilters, - disabled: ops?.disableFilters - }, - biquad: ops?.biquadFilter - ? { - filter: ops.biquadFilter, - disabled: ops?.disableBiquad - } - : undefined, - resampler: { - targetSampleRate: ops?.sampleRate, - disabled: ops?.disableResampler - }, - equalizer: { - bandMultiplier: ops?.eq, - disabled: ops?.disableEqualizer - }, - volume: { - volume: ops?.volume, - disabled: ops?.disableVolume - } - }) - : src; - - if (this.queue.hasDebugger) this.queue.debug('Executing onAfterCreateStream hook...'); - const postStream = await this.queue.onAfterCreateStream?.(stream, this.queue).catch( - () => - ({ - stream: stream, - type: ops?.type ?? StreamType.Arbitrary - } as PostProcessedResult) - ); - - if (this.queue.hasDebugger) this.queue.debug('Preparing AudioResource...'); - this.audioResource = createAudioResource(postStream?.stream ?? stream, { - inputType: postStream?.type ?? ops?.type ?? StreamType.Arbitrary, - metadata: ops?.data, - // volume controls happen from AudioFilter DSP utility - inlineVolume: false - }); - - return this.audioResource; - } - - public get resampler() { - return this.dsp?.resampler; - } - - public get filters() { - return this.dsp?.filters; - } - - public get biquad() { - return this.dsp?.biquad || null; - } - - public get equalizer() { - return this.dsp?.equalizer || null; - } - - /** - * The player status - * @type {AudioPlayerStatus} - */ - get status() { - return this.audioPlayer.state.status; - } - - /** - * Disconnects from voice - * @returns {void} - */ - disconnect() { - try { - if (this.audioPlayer) this.audioPlayer.stop(true); - if (this.voiceConnection.state.status !== VoiceConnectionStatus.Destroyed) this.voiceConnection.destroy(); - } catch {} // eslint-disable-line no-empty - } - - /** - * Destroys this dispatcher - */ - public destroy() { - this.disconnect(); - this.audioPlayer.removeAllListeners(); - this.voiceConnection.removeAllListeners(); - this.dsp.destroy(); - this.audioResource = null; - this.emit('destroyed'); - } - - /** - * Stops the player - * @returns {void} - */ - end() { - try { - this.audioPlayer.stop(); - this.dsp.destroy(); - } catch { - // - } - } - - /** - * Pauses the stream playback - * @param {boolean} [interpolateSilence=false] If true, the player will play 5 packets of silence after pausing to prevent audio glitches. - * @returns {boolean} - */ - pause(interpolateSilence?: boolean) { - const success = this.audioPlayer.pause(interpolateSilence); - return success; - } - - /** - * Resumes the stream playback - * @returns {boolean} - */ - resume() { - const success = this.audioPlayer.unpause(); - return success; - } - - /** - * Play stream - * @param {AudioResource} [resource=this.audioResource] The audio resource to play - * @param {boolean} [opus=false] Whether or not to use opus - * @returns {Promise} - */ - async playStream(resource: AudioResource = this.audioResource!) { - if (!resource) { - throw Exceptions.ERR_NO_AUDIO_RESOURCE(); - } - if (resource.ended) { - return void this.emit('finish', resource); - } - if (!this.audioResource) this.audioResource = resource; - if (this.voiceConnection.state.status !== VoiceConnectionStatus.Ready) { - try { - await entersState(this.voiceConnection, VoiceConnectionStatus.Ready, this.connectionTimeout); - } catch (err) { - return void this.emit('error', err as AudioPlayerError); - } - } - - try { - this.audioPlayer.play(resource); - } catch (e) { - this.emit('error', e as AudioPlayerError); - } - - return this; - } - - /** - * Sets playback volume - * @param {number} value The volume amount - * @returns {boolean} - */ - setVolume(value: number) { - if (!this.dsp.volume) return false; - return this.dsp.volume.setVolume(value); - } - - /** - * The current volume - * @type {number} - */ - get volume() { - if (!this.dsp.volume) return 100; - return this.dsp.volume.volume; - } - - /** - * The playback time - * @type {number} - */ - get streamTime() { - if (!this.audioResource) return 0; - return this.audioResource.playbackDuration; - } -} - -export { StreamDispatcher as StreamDispatcher }; diff --git a/packages/discord-player/src/VoiceInterface/VoiceUtils.ts b/packages/discord-player/src/VoiceInterface/VoiceUtils.ts deleted file mode 100644 index d7c5658c5d..0000000000 --- a/packages/discord-player/src/VoiceInterface/VoiceUtils.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { VoiceChannel, StageChannel, Snowflake } from 'discord.js'; -import { DiscordGatewayAdapterCreator, joinVoiceChannel, VoiceConnection, getVoiceConnection, VoiceConnectionStatus, AudioPlayer } from 'discord-voip'; -import { StreamDispatcher } from './StreamDispatcher'; -import { Collection } from '@discord-player/utils'; -import { GuildQueue } from '../queue'; -import type { Player } from '../Player'; -import { Exceptions } from '../errors'; - -class VoiceUtils { - /** - * Voice connection cache to store voice connections of the Player components. - * This property is deprecated and will be removed in the future. - * It only exists for compatibility reasons. - * @deprecated - */ - public cache: Collection = new Collection(); - - /** - * The voice utils constructor - */ - constructor(public player: Player) {} - - /** - * Joins a voice channel, creating basic stream dispatch manager - * @param {StageChannel|VoiceChannel} channel The voice channel - * @param {object} [options] Join options - * @returns {Promise} - */ - public async connect( - channel: VoiceChannel | StageChannel, - options?: { - deaf?: boolean; - maxTime?: number; - queue: GuildQueue; - audioPlayer?: AudioPlayer; - group?: string; - } - ): Promise { - if (!options?.queue) throw Exceptions.ERR_NO_GUILD_QUEUE(); - const conn = await this.join(channel, options); - const sub = new StreamDispatcher(conn, channel, options.queue, options.maxTime, options.audioPlayer); - return sub; - } - - /** - * Joins a voice channel - * @param {StageChannel|VoiceChannel} [channel] The voice/stage channel to join - * @param {object} [options] Join options - * @returns {VoiceConnection} - */ - public async join( - channel: VoiceChannel | StageChannel, - options?: { - deaf?: boolean; - maxTime?: number; - group?: string; - } - ) { - const conn = joinVoiceChannel({ - guildId: channel.guild.id, - channelId: channel.id, - adapterCreator: channel.guild.voiceAdapterCreator as unknown as DiscordGatewayAdapterCreator, - selfDeaf: Boolean(options?.deaf), - debug: this.player.events.listenerCount('debug') > 0, - group: options?.group - }); - - return conn; - } - - /** - * Disconnects voice connection - * @param {VoiceConnection} connection The voice connection - * @returns {void} - */ - public disconnect(connection: VoiceConnection | StreamDispatcher) { - if (connection instanceof StreamDispatcher) connection = connection.voiceConnection; - - try { - if (connection.state.status !== VoiceConnectionStatus.Destroyed) return connection.destroy(); - } catch { - // - } - } - - /** - * Returns Discord Player voice connection - * @param {Snowflake} guild The guild id - * @returns {StreamDispatcher} - */ - public getConnection(guild: Snowflake, group?: string) { - return getVoiceConnection(guild, group); - } -} - -export { VoiceUtils }; diff --git a/packages/discord-player/src/adapter.ts b/packages/discord-player/src/adapter.ts new file mode 100644 index 0000000000..fcd2cda2e7 --- /dev/null +++ b/packages/discord-player/src/adapter.ts @@ -0,0 +1,59 @@ +import { unsafe } from './common/types'; +import { getPlayerAdapterContext } from './context'; +import { Player } from './player'; + +export type OnGatewayPacket = (packet: unsafe) => void; + +export interface IAdapter { + metadata: T; + sendPacket: (packet: unsafe) => void; + resolveGuild(guild: string): string; + resolveGuildByChannel(channel: string): string; + resolveChannel(channel: string): string; + resolveUser(user: string): string; + setRequestToSpeak(guild: string, channel: string, value: boolean): void; + isVoiceChannel(guild: string, channel: string): boolean; + isStageChannel(guild: string, channel: string): boolean; + getVoiceChannelMembersCount(guild: string, channel: string): number; +} + +export interface IVoiceStateUpdateData { + guild: string; + channel: string; + user: string; + me: string; + selfDeaf: boolean; + selfMute: boolean; + serverDeaf: boolean; + serverMute: boolean; + suppress: boolean; + memberCount: number; +} + +export type VoiceStateUpdateDispatch = (oldState: IVoiceStateUpdateData, newState: IVoiceStateUpdateData) => void; + +export interface AdapterImpl { + onPacket: OnGatewayPacket; + handleVoiceStateUpdate: VoiceStateUpdateDispatch; +} + +export class Adapter implements AdapterImpl { + public readonly player: Player; + public constructor(private readonly config: IAdapter) { + this.player = getPlayerAdapterContext(); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public onPacket(packet: unsafe): void { + throw new Error('Not implemented'); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public handleVoiceStateUpdate(oldState: IVoiceStateUpdateData, newState: IVoiceStateUpdateData): void { + throw new Error('Not implemented'); + } +} + +export function createAdapter(adapterConfig: IAdapter): Adapter { + return new Adapter(adapterConfig); +} diff --git a/packages/discord-player/src/common/EventEmitter.ts b/packages/discord-player/src/common/EventEmitter.ts new file mode 100644 index 0000000000..c7683e1d21 --- /dev/null +++ b/packages/discord-player/src/common/EventEmitter.ts @@ -0,0 +1,97 @@ +import { type DefaultListener, type ListenerSignature, EventEmitter as IEventEmitter } from '@discord-player/utils'; + +export type DebugCallback = (message: string) => void; + +export type WithDebugger> = L & { debug: DebugCallback }; + +export const DEBUG_EVENT = 'debug'; + +export class EventEmitter = DefaultListener> extends IEventEmitter> { + #hasDebugger = false; + #debug: DebugCallback | null = null; + + public constructor(public requiredEvents: Array = []) { + super(); + } + + public get debug() { + return this.#debug; + } + + public on>(name: K, listener: WithDebugger[K]) { + if (name === DEBUG_EVENT) { + this.#hasDebugger = true; + this.#setupDebugCallback(); + } + + return super.on(name, listener); + } + + public once>(name: K, listener: WithDebugger[K]) { + if (name === DEBUG_EVENT) { + this.#hasDebugger = true; + this.#setupDebugCallback(); + } + + return super.once(name, listener); + } + + public addListener>(name: K, listener: WithDebugger[K]) { + if (name === DEBUG_EVENT) { + this.#hasDebugger = true; + this.#setupDebugCallback(); + } + + return super.addListener(name, listener); + } + + public off>(name: K, listener: WithDebugger[K]) { + this.#hasDebugger = this.listenerCount(DEBUG_EVENT) > 0; + this.#setupDebugCallback(); + + return super.off(name, listener); + } + + public removeListener>(name: K, listener: WithDebugger[K]) { + this.#hasDebugger = this.listenerCount(DEBUG_EVENT) > 0; + this.#setupDebugCallback(); + + return super.removeListener(name, listener); + } + + public removeAllListeners>(name?: K) { + this.#hasDebugger = this.listenerCount(DEBUG_EVENT) > 0; + this.#setupDebugCallback(); + + return super.removeAllListeners(name); + } + + public emit>(name: K, ...args: Parameters[K]>) { + if (this.requiredEvents.includes(name as keyof L) && !this.eventNames().includes(name)) { + // eslint-disable-next-line no-console + console.error(...args); + process.emitWarning( + `No event listener found for event "${String(name)}". Events ${this.requiredEvents.map((m) => `"${String(m)}"`).join(', ')} must have event listeners.`, + 'UnhandledEventsWarning' + ); + return false; + } + + return super.emit(name, ...args); + } + + public get hasDebugger() { + return this.#hasDebugger; + } + + #setupDebugCallback() { + const hasDebugger = this.listenerCount(DEBUG_EVENT) > 0; + + if (hasDebugger && !this.#debug) { + // @ts-expect-error + this.#debug = (message: string) => this.emit(DEBUG_EVENT, message); + } else if (!hasDebugger && this.#debug) { + this.#debug = null; + } + } +} diff --git a/packages/discord-player/src/common/types.ts b/packages/discord-player/src/common/types.ts new file mode 100644 index 0000000000..d29b6e7ae7 --- /dev/null +++ b/packages/discord-player/src/common/types.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type unsafe = any; diff --git a/packages/discord-player/src/context.ts b/packages/discord-player/src/context.ts new file mode 100644 index 0000000000..897f1bc8eb --- /dev/null +++ b/packages/discord-player/src/context.ts @@ -0,0 +1,17 @@ +import { AsyncLocalStorage } from 'node:async_hooks'; +import type { Player, PlayerAdapterInterface } from './player'; +import { unsafe } from './common/types'; + +const PlayerAdapterContext = new AsyncLocalStorage>(); + +export function getPlayerAdapterContext(): Player { + const ctx = PlayerAdapterContext.getStore(); + + if (!ctx) throw new Error('No player adapter context found'); + + return ctx; +} + +export function setPlayerAdapterContext(player: Player, adapterInterface: PlayerAdapterInterface) { + return PlayerAdapterContext.run(player, adapterInterface); +} diff --git a/packages/discord-player/src/errors/index.ts b/packages/discord-player/src/errors/index.ts deleted file mode 100644 index f635dd7cb8..0000000000 --- a/packages/discord-player/src/errors/index.ts +++ /dev/null @@ -1,178 +0,0 @@ -const DiscordPlayerErrors = { - ERR_OUT_OF_SPACE: { - name: 'ERR_OUT_OF_SPACE', - type: RangeError, - createError(target: string, capacity: number, total: number) { - return `[${this.name}] Max capacity reached for ${target} (Capacity ${capacity}/Total ${total})`; - } - }, - ERR_INVALID_ARG_TYPE: { - name: 'ERR_INVALID_ARG_TYPE', - type: TypeError, - createError(target: string, expectation: string, found: string) { - return `[${this.name}] Expected ${target} to be "${expectation}", received "${found}"`; - } - }, - ERR_NO_RESULT: { - name: 'ERR_NO_RESULT', - type: Error, - createError(message: string) { - return `[${this.name}] ${message}`; - } - }, - ERR_NOT_IMPLEMENTED: { - name: 'ERR_NOT_IMPLEMENTED', - type: Error, - createError(target: string) { - return `[${this.name}] ${target} is not yet implemented`; - } - }, - ERR_NOT_EXISTING: { - name: 'ERR_NOT_EXISTING', - type: Error, - createError(target: string) { - return `[${this.name}] ${target} does not exist`; - } - }, - ERR_OUT_OF_RANGE: { - name: 'ERR_OUT_OF_RANGE', - type: RangeError, - createError(target: string, value: string, minimum: string, maximum: string) { - return `[${this.name}] ${target} is out of range (Expected minimum ${maximum} and maximum ${maximum}, got ${value})`; - } - }, - ERR_NO_VOICE_CONNECTION: { - name: 'ERR_NO_VOICE_CONNECTION', - type: Error, - createError(message?: string) { - return `[${this.name}] ` + (message || 'No voice connection available, maybe connect to a voice channel first?'); - } - }, - ERR_VOICE_CONNECTION_DESTROYED: { - name: 'ERR_VOICE_CONNECTION_DESTROYED', - type: Error, - createError() { - return `[${this.name}] ` + 'Cannot use destroyed voice connection'; - } - }, - ERR_NO_VOICE_CHANNEL: { - name: 'ERR_NO_VOICE_CHANNEL', - type: Error, - createError() { - return `[${this.name}] ` + 'Could not get the voice channel'; - } - }, - ERR_INVALID_VOICE_CHANNEL: { - name: 'ERR_INVALID_VOICE_CHANNEL', - type: Error, - createError() { - return `[${this.name}] ` + 'Expected a voice channel'; - } - }, - ERR_NO_RECEIVER: { - name: 'ERR_NO_RECEIVER', - type: Error, - createError(message?: string) { - return `[${this.name}] ` + (message || 'No voice receiver is available, maybe connect to a voice channel first?'); - } - }, - ERR_FFMPEG_LOCATOR: { - name: 'ERR_FFMPEG_LOCATOR', - type: Error, - createError(message: string) { - return `[${this.name}] ` + message; - } - }, - ERR_NO_AUDIO_RESOURCE: { - name: 'ERR_NO_AUDIO_RESOURCE', - type: Error, - createError(message?: string) { - return `[${this.name}] ` + (message || 'Expected an audio resource'); - } - }, - ERR_NO_GUILD_QUEUE: { - name: 'ERR_NO_GUILD_QUEUE', - type: Error, - createError(message?: string) { - return `[${this.name}] ` + (message || 'Expected a guild queue'); - } - }, - ERR_NO_GUILD: { - name: 'ERR_NO_GUILD', - type: Error, - createError(message?: string) { - return `[${this.name}] ` + (message || 'Expected a guild'); - } - }, - ERR_INFO_REQUIRED: { - name: 'ERR_INFO_REQUIRED', - type: Error, - createError(target: string, actual: string) { - return `[${this.name}] Expected ${target}, found "${actual}"`; - } - }, - ERR_SERIALIZATION_FAILED: { - name: 'ERR_SERIALIZATION_FAILED', - type: Error, - createError() { - return `[${this.name}]` + "Don't know how to serialize this data"; - } - }, - ERR_DESERIALIZATION_FAILED: { - name: 'ERR_DESERIALIZATION_FAILED', - type: Error, - createError() { - return `[${this.name}]` + "Don't know how to deserialize this data"; - } - }, - ERR_ILLEGAL_HOOK_INVOCATION: { - name: 'ERR_ILLEGAL_HOOK_INVOCATION', - type: Error, - createError(target: string, message?: string) { - return `[${this.name}] ` + `Illegal invocation of ${target} hook.${message ? ` ${message}` : ''}`; - } - } -} as const; - -type FinalException = { - name: O['name']; -} & InstanceType; - -type DiscordPlayerException = { - [K in keyof typeof DiscordPlayerErrors]: (...args: Parameters<(typeof DiscordPlayerErrors)[K]['createError']>) => FinalException<(typeof DiscordPlayerErrors)[K]>; -}; - -const target = {} as DiscordPlayerException; - -const handler: ProxyHandler = { - get(target, p: keyof typeof DiscordPlayerErrors, receiver) { - const err = DiscordPlayerErrors[p]; - - if (!err) return Reflect.get(target, p, receiver); - - return (...args: Parameters<(typeof err)['createError']>) => { - // @ts-expect-error - const exception = new err.type(err.createError(...args)); - const originalName = exception.name; - exception.name = `${err.name} [${originalName}]`; - - return exception; - }; - } -}; - -export const ErrorCodes = (() => { - type ErrCodes = { - -readonly [K in keyof typeof DiscordPlayerErrors]: (typeof DiscordPlayerErrors)[K]['name']; - }; - - const dict = {} as ErrCodes; - - for (const prop in DiscordPlayerErrors) { - // @ts-expect-error - dict[prop] = prop; - } - - return Object.freeze(dict); -})(); -export const Exceptions = new Proxy(target, handler); diff --git a/packages/discord-player/src/extractors/BaseExtractor.ts b/packages/discord-player/src/extractors/BaseExtractor.ts deleted file mode 100644 index 1edd8472bf..0000000000 --- a/packages/discord-player/src/extractors/BaseExtractor.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { User } from 'discord.js'; -import { Readable } from 'stream'; -import { Playlist } from '../fabric/Playlist'; -import { Track } from '../fabric/Track'; -import { PlayerEvents, SearchQueryType } from '../types/types'; -import { ExtractorExecutionContext } from './ExtractorExecutionContext'; -import type { RequestOptions } from 'http'; -import { Exceptions } from '../errors'; -import type { GuildQueueHistory } from '../queue'; - -export type ExtractorStreamable = - | Readable - | string - | { - $fmt: string; - stream: Readable; - }; - -export class BaseExtractor { - /** - * Identifier for this extractor - */ - public static identifier = 'com.discord-player.extractor'; - - /** - * Priority of this extractor. Higher value means higher priority (will be executed first). - */ - public priority = 1; - - /** - * A list of query protocols that this extractor supports. - */ - public protocols: string[] = []; - - /** - * Handle bridge query creation - * @param track The track to build query for - */ - public createBridgeQuery = (track: Track) => `${track.title} by ${track.author} official audio`; - - /** - * Extractor constructor - * @param context Context that instantiated this extractor - * @param options Initialization options for this extractor - */ - public constructor(public context: ExtractorExecutionContext, public options: T = {}) {} - - /** - * Identifier of this extractor - */ - public get identifier() { - return (this.constructor as typeof BaseExtractor).identifier; - } - - /** - * Reconfigures this extractor - * @param options The new options to apply - */ - public async reconfigure(options: T) { - this.options = options; - await this.deactivate(); - await this.activate(); - } - - /** - * This method will be executed when this extractor is activated - */ - public async activate() { - // executed when this extractor is activated - return; - } - - /** - * This method will be executed when this extractor is deactivated - */ - public async deactivate() { - // executed when this extractor is deactivated - return; - } - - /** - * Validate incoming query - * @param query The query to validate - */ - public async validate(query: string, type?: SearchQueryType | null): Promise { - void type; - return false; - } - - /** - * Stream the given track - * @param info The track to stream - */ - public async stream(info: Track): Promise { - void info; - throw Exceptions.ERR_NOT_IMPLEMENTED(`${this.constructor.name}.stream()`); - } - - /** - * Handle the given query - * @param query The query to handle - */ - public async handle(query: string, context: ExtractorSearchContext): Promise { - void context; - throw Exceptions.ERR_NOT_IMPLEMENTED(`${this.constructor.name}.handle()`); - } - - /** - * Get related tracks for the given track - * @param track The track source - */ - public async getRelatedTracks(track: Track, history: GuildQueueHistory): Promise { - void track; - void history; - throw Exceptions.ERR_NOT_IMPLEMENTED(`${this.constructor.name}.getRelatedTracks()`); - } - - /** - * A stream middleware to handle streams before passing it to the player - * @param stream The incoming stream - * @param next The next function - */ - public handlePostStream(stream: Readable, next: NextFunction) { - return next(null, stream); - } - - /** - * Dispatch an event to the player - * @param event The event to dispatch - * @param args The data to dispatch - */ - public emit(event: K, ...args: Parameters) { - return this.context.player.emit(event, ...args); - } - - /** - * Create extractor response - * @param playlist The playlist - * @param tracks The track array - */ - public createResponse(playlist?: Playlist | null, tracks: Track[] = playlist?.tracks || []): ExtractorInfo { - return { playlist: playlist || null, tracks }; - } - - /** - * Write debug message - * @param message The debug message - */ - public debug(message: string) { - return this.context.player.debug(message); - } - - /** - * IP rotator instance, if available - */ - public get routePlanner() { - return this.context.player.routePlanner; - } - - /** - * A flag to indicate `Demuxable` stream support for `opus`/`ogg/opus`/`webm/opus` formats. - */ - public get supportsDemux() { - return !!this.context.player.options.skipFFmpeg; - } -} - -export type NextFunction = (error?: Error | null, stream?: Readable) => void; - -export interface ExtractorInfo { - playlist: Playlist | null; - tracks: Track[]; -} - -export interface ExtractorSearchContext { - type?: SearchQueryType | null; - requestedBy?: User | null; - requestOptions?: RequestOptions; - protocol?: string | null; -} diff --git a/packages/discord-player/src/extractors/ExtractorExecutionContext.ts b/packages/discord-player/src/extractors/ExtractorExecutionContext.ts deleted file mode 100644 index 96974559e0..0000000000 --- a/packages/discord-player/src/extractors/ExtractorExecutionContext.ts +++ /dev/null @@ -1,261 +0,0 @@ -import { Player } from '../Player'; -import { Collection } from '@discord-player/utils'; -import { BaseExtractor } from './BaseExtractor'; -import { Util } from '../utils/Util'; -import { PlayerEventsEmitter } from '../utils/PlayerEventsEmitter'; -import { TypeUtil } from '../utils/TypeUtil'; - -// prettier-ignore -const knownExtractorKeys = [ - 'SpotifyExtractor', - 'AppleMusicExtractor', - 'SoundCloudExtractor', - 'YouTubeExtractor', - 'VimeoExtractor', - 'ReverbnationExtractor', - 'AttachmentExtractor' -] as const; -const knownExtractorLib = '@discord-player/extractor'; - -export type ExtractorLoaderOptionDict = { - // @ts-ignore types - [K in (typeof knownExtractorKeys)[number]]?: ConstructorParameters[1]; -}; - -export interface ExtractorExecutionEvents { - /** - * Emitted when a extractor is registered - * @param context The context where extractor was registered - * @param extractor The extractor that was registered - */ - registered: (context: ExtractorExecutionContext, extractor: BaseExtractor) => unknown; - /** - * Emitted when a extractor is unregistered - * @param context The context where extractor was unregistered - * @param extractor The extractor that was unregistered - */ - unregistered: (context: ExtractorExecutionContext, extractor: BaseExtractor) => unknown; - /** - * Emitted when a extractor is activated - * @param context The context where this event occurred - * @param extractor The extractor which was activated - */ - activate: (context: ExtractorExecutionContext, extractor: BaseExtractor) => unknown; - /** - * Emitted when a extractor is deactivated - * @param context The context where this event occurred - * @param extractor The extractor which was deactivated - */ - deactivate: (context: ExtractorExecutionContext, extractor: BaseExtractor) => unknown; - /** - * Emitted when a extractor fails to activate/deactivate - * @param context The context where this event occurred - * @param extractor The extractor which was deactivated - */ - error: (context: ExtractorExecutionContext, extractor: BaseExtractor, error: Error) => unknown; -} - -export class ExtractorExecutionContext extends PlayerEventsEmitter { - /** - * The extractors store - */ - public store = new Collection(); - - public constructor(public player: Player) { - super(['error']); - } - - /** - * Load default extractors from `@discord-player/extractor` - */ - public async loadDefault(filter?: (ext: (typeof knownExtractorKeys)[number]) => boolean | null, options?: ExtractorLoaderOptionDict) { - const mod = await Util.import(knownExtractorLib); - if (mod.error) return { success: false, error: mod.error as Error }; - - (filter ? knownExtractorKeys.filter(filter) : knownExtractorKeys).forEach((key) => { - if (!mod.module[key]) return; - // @ts-ignore types - this.register(mod.module[key], options?.[key] || {}); - }); - - return { success: true, error: null }; - } - - /** - * Validate if the given extractor is registered - * @param identifier The extractor identifier - */ - public isRegistered(identifier: string) { - return this.store.has(identifier); - } - - /** - * The size of registered extractors - */ - public get size() { - return this.store.size; - } - - /** - * Get single extractor - * @param identifier The extractor to get - */ - public get(identifier: string) { - return this.store.get(identifier); - } - - /** - * Register single extractor - * @param _extractor The extractor to register - * @param options Options supplied to the extractor - */ - public async register>(_extractor: T, options: ConstructorParameters['1']): Promise | null> { - if (typeof _extractor.identifier !== 'string' || this.store.has(_extractor.identifier)) return null; - const extractor = new _extractor(this, options); - - // @ts-ignore - if (this.player.options.bridgeProvider) options.bridgeProvider ??= this.player.options.bridgeProvider; - - try { - this.store.set(_extractor.identifier, extractor); - if (this.player.hasDebugger) this.player.debug(`${_extractor.identifier} extractor loaded!`); - this.emit('registered', this, extractor); - await extractor.activate(); - if (this.player.hasDebugger) this.player.debug(`${_extractor.identifier} extractor activated!`); - this.emit('activate', this, extractor); - return extractor as unknown as InstanceType; - } catch (e) { - this.store.delete(_extractor.identifier); - if (this.player.hasDebugger) this.player.debug(`${_extractor.identifier} extractor failed to activate! Error: ${e}`); - this.emit('error', this, extractor, e as Error); - return null; - } - } - - /** - * Unregister single extractor - * @param _extractor The extractor to unregister - */ - public async unregister(_extractor: K) { - const extractor = typeof _extractor === 'string' ? this.store.get(_extractor) : this.store.find((r) => r === _extractor); - if (!extractor) return; - - try { - const key = extractor.identifier || this.store.findKey((e) => e === extractor)!; - this.store.delete(key); - if (this.player.hasDebugger) this.player.debug(`${extractor.identifier} extractor disabled!`); - this.emit('unregistered', this, extractor); - await extractor.deactivate(); - if (this.player.hasDebugger) this.player.debug(`${extractor.identifier} extractor deactivated!`); - this.emit('deactivate', this, extractor); - } catch (e) { - if (this.player.hasDebugger) this.player.debug(`${extractor.identifier} extractor failed to deactivate!`); - this.emit('error', this, extractor, e as Error); - } - } - - /** - * Unregister all extractors - */ - public async unregisterAll() { - try { - await Promise.all(this.store.map((e) => this.unregister(e))); - } catch { - // do nothing - } - } - - /** - * Run all the extractors - * @param fn The runner function - * @param filterBlocked Filter blocked extractors - */ - public async run(fn: ExtractorExecutionFN, filterBlocked = true) { - const blocked = this.player.options.blockExtractors ?? []; - - if (!this.store.size) { - Util.warn('Skipping extractors execution since zero extractors were registered', 'NoExtractors'); - return; - } - - // sort by priority so that extractors with higher priority are executed first - const extractors = this.store.sort((a, b) => b.priority - a.priority); - - let err: Error | null = null, - lastExt: BaseExtractor | null = null; - - for (const ext of extractors.values()) { - if (filterBlocked && blocked.some((e) => e === ext.identifier)) continue; - if (this.player.hasDebugger) this.player.debug(`Executing extractor ${ext.identifier}...`); - const result = await fn(ext).then( - (res) => { - return res; - }, - (e) => { - if (this.player.hasDebugger) this.player.debug(`Extractor ${ext.identifier} failed with error: ${e}`); - - return TypeUtil.isError(e) ? e : new Error(`${e}`); - } - ); - - lastExt = ext; - - if (result && !TypeUtil.isError(result)) { - if (this.player.hasDebugger) this.player.debug(`Extractor ${ext.identifier} executed successfully!`); - - return { - extractor: ext, - error: null, - result - } as ExtractorExecutionResult; - } else if (TypeUtil.isError(result)) { - err = result; - } - } - - if (err) - return { - extractor: lastExt!, - error: err, - result: false - } as ExtractorExecutionResult; - } - - /** - * Check if extractor is disabled - */ - public isDisabled(identifier: string) { - return this.player.options.blockExtractors?.includes(identifier) ?? false; - } - - /** - * Check if extractor is enabled - */ - public isEnabled(identifier: string) { - return !this.isDisabled(identifier); - } - - /** - * Resolve extractor identifier - */ - public resolveId(resolvable: ExtractorResolvable) { - return typeof resolvable === 'string' ? resolvable : resolvable.identifier; - } - - /** - * Resolve extractor - */ - public resolve(resolvable: ExtractorResolvable) { - return typeof resolvable === 'string' ? this.get(resolvable) : resolvable; - } -} - -export interface ExtractorExecutionResult { - extractor: BaseExtractor; - error: Error | null; - result: T; -} - -export type ExtractorExecutionFN = (extractor: BaseExtractor) => Promise; - -export type ExtractorResolvable = string | BaseExtractor; diff --git a/packages/discord-player/src/fabric/Playlist.ts b/packages/discord-player/src/fabric/Playlist.ts deleted file mode 100644 index 056e1e5777..0000000000 --- a/packages/discord-player/src/fabric/Playlist.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { Player, PlayerNodeInitializationResult, PlayerNodeInitializerOptions } from '../Player'; -import { Track } from './Track'; -import { PlaylistInitData, PlaylistJSON, TrackJSON, TrackSource } from '../types/types'; -import { Util } from '../utils/Util'; -import { GuildVoiceChannelResolvable } from 'discord.js'; -import { SerializedType, tryIntoThumbnailString } from '../utils/serde'; -import { TypeUtil } from '../utils/TypeUtil'; -import { Exceptions } from '../errors'; - -export type SerializedPlaylist = ReturnType; - -export class Playlist { - public readonly player: Player; - public tracks: Track[]; - public title: string; - public description: string; - public thumbnail: string; - public type: 'album' | 'playlist'; - public source: TrackSource; - public author: { - name: string; - url: string; - }; - public id: string; - public url: string; - public readonly rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any - - /** - * Playlist constructor - * @param {Player} player The player - * @param {PlaylistInitData} data The data - */ - constructor(player: Player, data: PlaylistInitData) { - /** - * The player - * @name Playlist#player - * @type {Player} - * @readonly - */ - this.player = player; - - /** - * The tracks in this playlist - * @name Playlist#tracks - * @type {Track[]} - */ - this.tracks = data.tracks ?? []; - - /** - * The author of this playlist - * @name Playlist#author - * @type {object} - */ - this.author = data.author; - - /** - * The description - * @name Playlist#description - * @type {string} - */ - this.description = data.description; - - /** - * The thumbnail of this playlist - * @name Playlist#thumbnail - * @type {string} - */ - this.thumbnail = data.thumbnail; - - /** - * The playlist type: - * - `album` - * - `playlist` - * @name Playlist#type - * @type {string} - */ - this.type = data.type; - - /** - * The source of this playlist: - * - `youtube` - * - `soundcloud` - * - `spotify` - * - `arbitrary` - * @name Playlist#source - * @type {string} - */ - this.source = data.source; - - /** - * The playlist id - * @name Playlist#id - * @type {string} - */ - this.id = data.id; - - /** - * The playlist url - * @name Playlist#url - * @type {string} - */ - this.url = data.url; - - /** - * The playlist title - * @type {string} - */ - this.title = data.title; - - /** - * @name Playlist#rawPlaylist - * @type {any} - * @readonly - */ - } - - *[Symbol.iterator]() { - yield* this.tracks; - } - - /** - * Estimated duration of this playlist - */ - public get estimatedDuration() { - return this.tracks.reduce((p, c) => p + c.durationMS, 0); - } - - /** - * Formatted estimated duration of this playlist - */ - public get durationFormatted() { - return Util.buildTimeCode(Util.parseMS(this.estimatedDuration)); - } - - /** - * JSON representation of this playlist - * @param {boolean} [withTracks=true] If it should build json with tracks - * @returns {PlaylistJSON} - */ - toJSON(withTracks = true) { - const payload = { - id: this.id, - url: this.url, - title: this.title, - description: this.description, - thumbnail: this.thumbnail, - type: this.type, - source: this.source, - author: this.author, - tracks: [] as TrackJSON[] - }; - - if (withTracks) payload.tracks = this.tracks.map((m) => m.toJSON(true)); - - return payload as PlaylistJSON; - } - - /** - * Serialize this playlist into reconstructable data - */ - public serialize() { - return { - tracks: this.tracks.map((m) => m.serialize()), - title: this.title, - description: this.description, - thumbnail: TypeUtil.isString(this.thumbnail) ? this.thumbnail : tryIntoThumbnailString(this.thumbnail), - type: this.type, - source: this.source, - author: this.author, - id: this.id, - url: this.url, - $type: SerializedType.Playlist, - $encoder_version: '[VI]{{inject}}[/VI]' - }; - } - - /** - * Deserialize this playlist from serialized data - * @param player Player instance - * @param data Serialized data - */ - public static fromSerialized(player: Player, data: SerializedPlaylist) { - if (data.$type !== SerializedType.Playlist) throw Exceptions.ERR_INVALID_ARG_TYPE('data', 'SerializedPlaylist', 'malformed data'); - return new Playlist(player, { - ...data, - tracks: data.tracks.map((m) => Track.fromSerialized(player, m)) - }); - } - - /** - * Play this playlist to the given voice channel. If queue exists and another track is being played, this playlist will be added to the queue. - * @param channel Voice channel on which this playlist shall be played - * @param options Node initialization options - */ - public async play(channel: GuildVoiceChannelResolvable, options?: PlayerNodeInitializerOptions): Promise> { - const fn = this.player.play.bind(this.player); - - return await fn(channel, this, options); - } -} diff --git a/packages/discord-player/src/fabric/SearchResult.ts b/packages/discord-player/src/fabric/SearchResult.ts deleted file mode 100644 index 5cdde45d43..0000000000 --- a/packages/discord-player/src/fabric/SearchResult.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { User } from 'discord.js'; -import { BaseExtractor } from '../extractors/BaseExtractor'; -import { Player } from '../Player'; -import { QueryExtractorSearch, QueryType, SearchQueryType } from '../types/types'; -import { Playlist } from './Playlist'; -import { Track } from './Track'; - -export interface SearchResultData { - query: string; - queryType?: SearchQueryType | QueryExtractorSearch | null; - extractor?: BaseExtractor | null; - playlist?: Playlist | null; - tracks?: Track[]; - requestedBy?: User | null; -} - -export class SearchResult { - public constructor(public player: Player, private _data: SearchResultData) { - this._data.tracks?.forEach((track) => { - track.extractor ??= this._data.extractor || null; - track.requestedBy ??= _data.requestedBy || null; - }); - } - - public setQueryType(type: SearchQueryType | QueryExtractorSearch) { - this._data.queryType = type; - return this; - } - - public setRequestedBy(user: User) { - this._data.requestedBy = user; - this._data.tracks?.forEach((track) => { - track.requestedBy = user; - }); - return this; - } - - public setExtractor(extractor: BaseExtractor) { - this._data.extractor = extractor; - this._data.tracks?.forEach((track) => { - track.extractor = extractor; - }); - return this; - } - - public setTracks(tracks: Track[]) { - this._data.tracks = tracks; - return this; - } - - public setQuery(query: string) { - this._data.query = query; - return this; - } - - public setPlaylist(playlist: Playlist) { - this._data.playlist = playlist; - return this; - } - - /** - * The search query - */ - public get query() { - return this._data.query; - } - - /** - * The search query type - */ - public get queryType() { - return this._data.queryType || QueryType.AUTO; - } - - /** - * The extractor - */ - public get extractor() { - return this._data.extractor || null; - } - - /** - * Playlist result - */ - public get playlist() { - return this._data.playlist; - } - - /** - * Tracks result - */ - public get tracks() { - return this._data.tracks || []; - } - - /** - * Requested by - */ - public get requestedBy() { - return this._data.requestedBy || null; - } - - /** - * Re-execute this search - */ - public async execute() { - return this.player.search(this.query, { - searchEngine: this.queryType, - requestedBy: this.requestedBy! - }); - } - - /** - * If this search result is empty - */ - public isEmpty() { - return !this.tracks.length; - } - - /** - * If this search result has playlist - */ - public hasPlaylist() { - return this.playlist != null; - } - - /** - * If this search result has tracks - */ - public hasTracks() { - return this.tracks.length > 0; - } - - /** - * JSON representation of this search - */ - public toJSON() { - return { - query: this.query, - queryType: this.queryType, - playlist: this.playlist?.toJSON(false) || null, - tracks: this.tracks.map((m) => m.toJSON(true)), - extractor: this.extractor?.identifier || null, - requestedBy: this.requestedBy?.toJSON() || null - }; - } -} diff --git a/packages/discord-player/src/fabric/Track.ts b/packages/discord-player/src/fabric/Track.ts deleted file mode 100644 index cfd73450f9..0000000000 --- a/packages/discord-player/src/fabric/Track.ts +++ /dev/null @@ -1,234 +0,0 @@ -import { User, escapeMarkdown, SnowflakeUtil, GuildVoiceChannelResolvable, APIUser } from 'discord.js'; -import { Player, PlayerNodeInitializationResult, PlayerNodeInitializerOptions } from '../Player'; -import { RawTrackData, SearchQueryType, TrackJSON } from '../types/types'; -import { Playlist } from './Playlist'; -import { GuildQueue } from '../queue/GuildQueue'; -import { BaseExtractor } from '../extractors/BaseExtractor'; -import { Collection } from '@discord-player/utils'; -import { TypeUtil } from '../utils/TypeUtil'; -import { SerializedType, tryIntoThumbnailString } from '../utils/serde'; -import { Exceptions } from '../errors'; -import { Util } from '../utils/Util'; - -export type TrackResolvable = Track | string | number; - -export type WithMetadata = T & { - metadata: M; - requestMetadata(): Promise; -}; - -export type SerializedTrack = ReturnType; - -export class Track { - public title: string; - public description: string; - public author: string; - public url: string; - public thumbnail: string; - public duration: string; - public views: number; - public requestedBy: User | null = null; - public playlist?: Playlist; - public queryType: SearchQueryType | null | undefined = null; - public raw: RawTrackData = { - source: 'arbitrary' - } as RawTrackData; - public extractor: BaseExtractor | null = null; - public readonly id = SnowflakeUtil.generate().toString(); - private __metadata: T | null = null; - private __reqMetadataFn: () => Promise; - public cleanTitle: string; - - /** - * Track constructor - * @param player The player that instantiated this Track - * @param data Track data - */ - public constructor(public readonly player: Player, data: Partial>) { - this.title = escapeMarkdown(data.title ?? ''); - this.author = data.author ?? ''; - this.url = data.url ?? ''; - this.thumbnail = data.thumbnail ?? ''; - this.duration = data.duration ?? ''; - this.views = data.views ?? 0; - this.queryType = data.queryType; - this.requestedBy = data.requestedBy || null; - this.playlist = data.playlist; - this.description = `${this.title} by ${this.author}`; - this.raw = Object.assign({}, { source: data.raw?.source ?? data.source }, data.raw ?? data); - this.__metadata = data.metadata ?? null; - this.__reqMetadataFn = data.requestMetadata || (() => Promise.resolve(null)); - this.cleanTitle = data.cleanTitle ?? Util.cleanTitle(this.title, this.source); - } - - /** - * Request metadata for this track - */ - public async requestMetadata() { - const res = await this.__reqMetadataFn(); - - this.setMetadata(res); - - return res; - } - - /** - * Set metadata for this track - */ - public setMetadata(m: T | null) { - this.__metadata = m; - } - - /** - * Metadata of this track - */ - public get metadata() { - return this.__metadata; - } - - /** - * If this track has metadata - */ - public get hasMetadata() { - return this.metadata != null; - } - - /** - * The queue in which this track is located - */ - public get queue(): GuildQueue { - return this.player.nodes.cache.find((q) => q.tracks.some((ab) => ab.id === this.id))!; - } - - /** - * The track duration in millisecond - */ - public get durationMS(): number { - const times = (n: number, t: number) => { - let tn = 1; - for (let i = 0; i < t; i++) tn *= n; - return t <= 0 ? 1000 : tn * 1000; - }; - - return this.duration - .split(':') - .reverse() - .map((m, i) => parseInt(m) * times(60, i)) - .reduce((a, c) => a + c, 0); - } - - /** - * Discord hyperlink representation of this track - */ - public toHyperlink(): string /* not using `[${string}](${string})` yet */ { - return `[${this.title}](${this.url})`; - } - - /** - * Returns source of this track - */ - public get source() { - return this.raw?.source ?? 'arbitrary'; - } - - /** - * String representation of this track - */ - public toString(): string { - return `${this.title} by ${this.author}`; - } - - /** - * Raw JSON representation of this track - */ - public toJSON(hidePlaylist?: boolean) { - return { - id: this.id, - title: this.title, - description: this.description, - author: this.author, - url: this.url, - thumbnail: this.thumbnail, - duration: this.duration, - durationMS: this.durationMS, - views: this.views, - requestedBy: this.requestedBy?.id || null, - playlist: hidePlaylist ? null : this.playlist?.toJSON() ?? null - } as TrackJSON; - } - - /** - * Serialized track data that can be reconstructed - */ - public serialize() { - return { - title: this.title, - description: this.description, - author: this.author, - url: this.url, - thumbnail: TypeUtil.isString(this.thumbnail) ? this.thumbnail : tryIntoThumbnailString(this.thumbnail), - duration: this.duration, - views: this.views ?? 0, - requested_by: this.requestedBy?.toJSON() ?? null, - source: this.source, - live: false, - query_type: this.queryType, - extractor: this.extractor?.identifier ?? null, - metadata: this.metadata, - $type: SerializedType.Track, - $encoder_version: '[VI]{{inject}}[/VI]' - }; - } - - /** - * Construct a track from serialized data - * @param player Player instance - * @param data Serialized data - */ - public static fromSerialized(player: Player, data: ReturnType) { - if (data.$type !== SerializedType.Track) throw Exceptions.ERR_INVALID_ARG_TYPE('data', 'SerializedTrack', 'malformed data'); - const track = new Track(player, { - ...data, - requestedBy: data.requested_by - ? (() => { - const res = data.requested_by as APIUser; - try { - const resolved = player.client.users.resolve(res.id); - if (resolved) return resolved; - if (player.client.users.cache.has(res.id)) return player.client.users.cache.get(res.id)!; - // @ts-expect-error - const user = new User(player.client, res); - return user; - } catch { - return null; - } - })() - : null, - queryType: data.query_type ?? undefined - }); - - track.setMetadata(data.metadata); - - return track; - } - - /** - * Get belonging queues of this track - */ - public getBelongingQueues() { - const nodes = this.player.nodes.cache.filter((node) => node.tracks.some((t) => t.id === this.id)); - - return nodes as Collection>; - } - - /** - * Play this track to the given voice channel. If queue exists and another track is being played, this track will be added to the queue. - * @param channel Voice channel on which this track shall be played - * @param options Node initialization options - */ - public async play(channel: GuildVoiceChannelResolvable, options?: PlayerNodeInitializerOptions): Promise> { - const fn = this.player.play.bind(this.player); - - return await fn(channel, this, options); - } -} diff --git a/packages/discord-player/src/fabric/index.ts b/packages/discord-player/src/fabric/index.ts deleted file mode 100644 index ef20a30649..0000000000 --- a/packages/discord-player/src/fabric/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './Playlist'; -export * from './Track'; -export * from './SearchResult'; -export * from '../utils/AudioFilters'; diff --git a/packages/discord-player/src/hooks/common.ts b/packages/discord-player/src/hooks/common.ts deleted file mode 100644 index 00a76e8e47..0000000000 --- a/packages/discord-player/src/hooks/common.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Guild } from 'discord.js'; -import type { Player } from '../Player'; -import { GuildQueue, NodeResolvable } from '../queue'; -import { instances } from '../utils/__internal__'; -import { Exceptions } from '../errors'; -import { useContext } from './context/async-context'; - -const preferredInstanceKey = '__discord_player_hook_instance_cache__'; - -export const getPlayer = () => { - return instances.get(preferredInstanceKey) || instances.first() || null; -}; - -export interface HooksCtx { - guild: Guild; -} - -/** - * @private - */ -export function useHooksContext(hookName: string) { - const player = getPlayer(); - if (!player) throw Exceptions.ERR_ILLEGAL_HOOK_INVOCATION('discord-player', 'Player instance must be created before using hooks'); - - const context = useContext(player.context); - - if (!context) throw Exceptions.ERR_ILLEGAL_HOOK_INVOCATION(hookName, `${hookName} must be called inside a player context created by .context.provide()`); - - return context; -} - -/** - * Bind a player instance to the hook system, defaults to the first instance. - */ -export const bindHook = (player: Player) => { - instances.set(preferredInstanceKey, player); -}; - -export const getQueue = (node: NodeResolvable) => { - const player = getPlayer(); - if (!player) return null; - - return (player.nodes.resolve(node) as GuildQueue) || null; -}; - -export interface HookDeclarationContext { - getQueue: typeof getQueue; - getPlayer: typeof getPlayer; - instances: typeof instances; -} - -/* eslint-disable @typescript-eslint/no-explicit-any */ -export type HookDeclaration any> = (context: HookDeclarationContext) => T; - -export function createHook any>>(hook: T): ReturnType { - return hook({ - getQueue, - getPlayer, - instances - }) as ReturnType; -} - -/* eslint-enable @typescript-eslint/no-explicit-any */ diff --git a/packages/discord-player/src/hooks/context/async-context.ts b/packages/discord-player/src/hooks/context/async-context.ts deleted file mode 100644 index 5c99a5eed3..0000000000 --- a/packages/discord-player/src/hooks/context/async-context.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { AsyncLocalStorage } from 'node:async_hooks'; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type unsafe = any; - -/** - * The receiver function that will be called when the context is provided - */ -export type ContextReceiver = () => R; - -export class Context { - private storage = new AsyncLocalStorage(); - - public constructor(private defaultValue?: T) {} - - /** - * Exit out of this context - */ - public exit(scope: ContextReceiver) { - this.storage.exit(scope); - } - - /** - * Whether the context is lost - */ - public get isLost() { - return this.storage.getStore() === undefined; - } - - /** - * Get the current value of the context. If the context is lost and no default value is provided, undefined will be returned. - */ - public consume(): T | undefined { - const data = this.storage.getStore(); - - if (data === undefined && this.defaultValue !== undefined) return this.defaultValue; - - return data; - } - - /** - * Run a function within the context of this provider - */ - public provide(value: T, receiver: ContextReceiver): R { - if (value === undefined) { - throw new Error('Context value may not be undefined'); - } - - if (typeof receiver !== 'function') { - throw new Error('Context receiver must be a function'); - } - - return this.storage.run(value, receiver); - } -} - -/** - * Create a new context. The default value is optional. - * @param defaultValue The default value of the context - * @example const userContext = createContext(); - * - * // the value to provide - * const user = { - * id: 1, - * name: 'John Doe' - * }; - * - * // provide the context value to the receiver - * context.provide(user, handler); - * - * function handler() { - * // get the context value - * const { id, name } = useContext(context); - * - * console.log(id, name); // 1, John Doe - * } - */ -export function createContext(defaultValue?: T): Context { - return new Context(defaultValue); -} - -/** - * Get the current value of the context. If the context is lost and no default value is provided, undefined will be returned. - * @param context The context to get the value from - * @example const value = useContext(context); - */ -export function useContext(context: Context): T | undefined { - return context.consume(); -} diff --git a/packages/discord-player/src/hooks/index.ts b/packages/discord-player/src/hooks/index.ts deleted file mode 100644 index d7775601b7..0000000000 --- a/packages/discord-player/src/hooks/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -export * from './context/async-context'; -export * from './useHistory'; -export * from './usePlayer'; -export * from './useQueue'; -export * from './useMainPlayer'; -export * from './useMetadata'; -export * from './useTimeline'; -export * from './stream'; -export * from './useVolume'; -export { createHook, type HookDeclaration, type HookDeclarationContext } from './common'; diff --git a/packages/discord-player/src/hooks/stream/index.ts b/packages/discord-player/src/hooks/stream/index.ts deleted file mode 100644 index 3343cc7938..0000000000 --- a/packages/discord-player/src/hooks/stream/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './onAfterCreateStream'; -export * from './onBeforeCreateStream'; diff --git a/packages/discord-player/src/hooks/stream/onAfterCreateStream.ts b/packages/discord-player/src/hooks/stream/onAfterCreateStream.ts deleted file mode 100644 index dfd22f4b36..0000000000 --- a/packages/discord-player/src/hooks/stream/onAfterCreateStream.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { OnAfterCreateStreamHandler } from '../../queue'; -import { getGlobalRegistry } from '../../utils/__internal__'; - -/** - * Global onAfterCreateStream handler - * @param handler The handler callback - */ -export function onAfterCreateStream(handler: OnAfterCreateStreamHandler) { - getGlobalRegistry().set('@[onAfterCreateStream]', handler); -} diff --git a/packages/discord-player/src/hooks/stream/onBeforeCreateStream.ts b/packages/discord-player/src/hooks/stream/onBeforeCreateStream.ts deleted file mode 100644 index 3082d02c57..0000000000 --- a/packages/discord-player/src/hooks/stream/onBeforeCreateStream.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { OnBeforeCreateStreamHandler } from '../../queue'; -import { getGlobalRegistry } from '../../utils/__internal__'; - -/** - * Global onBeforeCreateStream handler - * @param handler The handler callback - */ -export function onBeforeCreateStream(handler: OnBeforeCreateStreamHandler) { - getGlobalRegistry().set('@[onBeforeCreateStream]', handler); -} diff --git a/packages/discord-player/src/hooks/useHistory.ts b/packages/discord-player/src/hooks/useHistory.ts deleted file mode 100644 index df2eb661b0..0000000000 --- a/packages/discord-player/src/hooks/useHistory.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { GuildQueueHistory, NodeResolvable } from '../queue'; -import { getQueue, useHooksContext } from './common'; - -/** - * Fetch guild queue history - * @param node guild queue node resolvable - */ -export function useHistory(): GuildQueueHistory | null; -export function useHistory(node: NodeResolvable): GuildQueueHistory | null; -export function useHistory(node?: NodeResolvable): GuildQueueHistory | null { - const _node = node ?? useHooksContext('useHistory').guild; - - const queue = getQueue(_node); - if (!queue) return null; - - return queue.history; -} diff --git a/packages/discord-player/src/hooks/useMainPlayer.ts b/packages/discord-player/src/hooks/useMainPlayer.ts deleted file mode 100644 index be01b5b850..0000000000 --- a/packages/discord-player/src/hooks/useMainPlayer.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Exceptions } from '../errors'; -import { Util } from '../utils/Util'; -import { getPlayer } from './common'; - -/** - * Fetch main player instance - * @deprecated - */ -export function useMasterPlayer() { - Util.warn('useMasterPlayer() hook is deprecated, use useMainPlayer() instead.', 'DeprecationWarning'); - return useMainPlayer(); -} - -/** - * Fetch main player instance - */ -export function useMainPlayer() { - const instance = getPlayer(); - if (!instance) { - throw Exceptions.ERR_ILLEGAL_HOOK_INVOCATION('useMainPlayer', 'This is likely caused by calling "useMainPlayer" hook before creating a player instance.'); - } - - return instance; -} diff --git a/packages/discord-player/src/hooks/useMetadata.ts b/packages/discord-player/src/hooks/useMetadata.ts deleted file mode 100644 index c854e4de1a..0000000000 --- a/packages/discord-player/src/hooks/useMetadata.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { TypeUtil } from '../utils/TypeUtil'; -import { NodeResolvable } from '../queue'; -import { getQueue, useHooksContext } from './common'; - -export type SetterFN = (previous: P) => T; -export type MetadataDispatch = readonly [() => T, (metadata: T | SetterFN) => void]; - -/** - * Fetch or manipulate guild queue metadata - * @param node Guild queue node resolvable - */ -export function useMetadata(): MetadataDispatch; -export function useMetadata(node: NodeResolvable): MetadataDispatch; -export function useMetadata(node?: NodeResolvable): MetadataDispatch { - const _node = node ?? useHooksContext('useMetadata').guild; - const queue = getQueue(_node); - const setter = (metadata: T | SetterFN) => { - if (queue) { - if (TypeUtil.isFunction(metadata)) return queue.setMetadata(metadata(queue.metadata)); - return queue.setMetadata(metadata); - } - }; - - const getter = () => { - return queue?.metadata as T; - }; - - return [getter, setter] as const; -} diff --git a/packages/discord-player/src/hooks/usePlayer.ts b/packages/discord-player/src/hooks/usePlayer.ts deleted file mode 100644 index c64e829f79..0000000000 --- a/packages/discord-player/src/hooks/usePlayer.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { GuildQueuePlayerNode, NodeResolvable } from '../queue'; -import { getQueue, useHooksContext } from './common'; - -/** - * Fetch guild queue player node - * @param node Guild queue node resolvable - */ -export function usePlayer(): GuildQueuePlayerNode | null; -export function usePlayer(node: NodeResolvable): GuildQueuePlayerNode | null; -export function usePlayer(node?: NodeResolvable): GuildQueuePlayerNode | null { - const _node = node ?? useHooksContext('usePlayer').guild; - const queue = getQueue(_node); - if (!queue) return null; - - return queue.node; -} diff --git a/packages/discord-player/src/hooks/useQueue.ts b/packages/discord-player/src/hooks/useQueue.ts deleted file mode 100644 index 1c42a20b0a..0000000000 --- a/packages/discord-player/src/hooks/useQueue.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { GuildQueue, NodeResolvable } from '../queue'; -import { getQueue, useHooksContext } from './common'; - -/** - * Fetch guild queue - * @param node Guild queue node resolvable - */ -export function useQueue(): GuildQueue | null; -export function useQueue(node: NodeResolvable): GuildQueue | null; -export function useQueue(node?: NodeResolvable): GuildQueue | null { - const _node = node ?? useHooksContext('useQueue').guild; - const queue = getQueue(_node); - if (!queue) return null; - - return queue; -} diff --git a/packages/discord-player/src/hooks/useTimeline.ts b/packages/discord-player/src/hooks/useTimeline.ts deleted file mode 100644 index b0641cd0c4..0000000000 --- a/packages/discord-player/src/hooks/useTimeline.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { NodeResolvable } from '../queue'; -import { getQueue, useHooksContext } from './common'; - -export interface TimelineDispatcherOptions { - ignoreFilters: boolean; -} - -/** - * Fetch or manipulate current track - * @param node Guild queue node resolvable - * @param options Options for timeline dispatcher - */ -export function useTimeline(node?: NodeResolvable, options?: Partial) { - const _node = node ?? useHooksContext('useTimeline').guild; - const queue = getQueue(_node); - if (!queue) return null; - - return Object.preventExtensions({ - get timestamp() { - return queue.node.getTimestamp(options?.ignoreFilters)!; - }, - get volume() { - return queue.node.volume; - }, - get paused() { - return queue.node.isPaused(); - }, - get track() { - return queue.currentTrack; - }, - pause() { - return queue.node.pause(); - }, - resume() { - return queue.node.resume(); - }, - setVolume(vol: number) { - return queue.node.setVolume(vol); - }, - async setPosition(time: number) { - return queue.node.seek(time); - } - }); -} diff --git a/packages/discord-player/src/hooks/useVolume.ts b/packages/discord-player/src/hooks/useVolume.ts deleted file mode 100644 index 5171ff085e..0000000000 --- a/packages/discord-player/src/hooks/useVolume.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { TypeUtil } from '../utils/TypeUtil'; -import { NodeResolvable } from '../queue'; -import { getQueue, useHooksContext } from './common'; - -type SetterFN = (previous: number) => number; -type VolumeDispatch = readonly [() => number, (volume: number | SetterFN) => boolean | undefined]; - -/** - * Fetch or manipulate player volume - * @param node Guild queue node resolvable - */ -export function useVolume(): VolumeDispatch; -export function useVolume(node: NodeResolvable): VolumeDispatch; -export function useVolume(node?: NodeResolvable): VolumeDispatch { - const _node = node ?? useHooksContext('useVolume').guild; - const queue = getQueue(_node); - const setter = (volume: number | SetterFN) => { - if (queue) { - if (TypeUtil.isFunction(volume)) return queue.node.setVolume(volume(queue.node.volume)); - return queue.node.setVolume(volume); - } - }; - - const getter = () => { - return queue?.node.volume as number; - }; - - return [getter, setter] as const; -} diff --git a/packages/discord-player/src/index.ts b/packages/discord-player/src/index.ts index 8b08d900e4..cb0ff5c3b5 100644 --- a/packages/discord-player/src/index.ts +++ b/packages/discord-player/src/index.ts @@ -1,44 +1 @@ -import { version as djsVersion } from 'discord.js'; - -export * from './utils/PlayerEventsEmitter'; -export * from './utils/AudioFilters'; -export * from './extractors/BaseExtractor'; -export * from './extractors/ExtractorExecutionContext'; -export * from './fabric'; -export * from './queue'; -export * from './lrclib/LrcLib'; -export * from './utils/SequentialBucket'; -export * from './VoiceInterface/VoiceUtils'; -export * from './VoiceInterface/StreamDispatcher'; -export * from './utils/Util'; -export * from './utils/TypeUtil'; -export * from './utils/AsyncQueue'; -export * from './types/types'; -export * from './utils/FFmpegStream'; -export * from './utils/QueryCache'; -export * from './utils/QueryResolver'; -export * from '@discord-player/ffmpeg'; -export * from './Player'; -export * from './hooks'; -export * from './utils/IPRotator'; -export * from './utils/serde'; -export { - AudioFilters as PCMAudioFilters, - type BiquadFilters, - FilterType as BiquadFilterType, - type PCMFilters, - Q_BUTTERWORTH, - VolumeTransformer, - BASS_EQ_BANDS, - AF_NIGHTCORE_RATE, - AF_VAPORWAVE_RATE, - FiltersChain -} from '@discord-player/equalizer'; -export { createAudioPlayer, AudioPlayer, type CreateAudioPlayerOptions } from 'discord-voip'; - -// eslint-disable-next-line @typescript-eslint/no-inferrable-types -export const version: string = '[VI]{{inject}}[/VI]'; - -if (!djsVersion.startsWith('14')) { - process.emitWarning(`Discord.js v${djsVersion} is incompatible with Discord Player v${version}! Please use >=v14.x of Discord.js`); -} +export {}; diff --git a/packages/discord-player/src/lrclib/LrcLib.ts b/packages/discord-player/src/lrclib/LrcLib.ts deleted file mode 100644 index 0781426a7f..0000000000 --- a/packages/discord-player/src/lrclib/LrcLib.ts +++ /dev/null @@ -1,208 +0,0 @@ -import { Exceptions } from '../errors'; -import type { Player } from '../Player'; -import { Util } from '../utils/Util'; -import { SequentialBucket } from '../utils/SequentialBucket'; - -export interface LrcSearchParams { - /** - * The query to search for. Either this or trackName is required. - */ - q?: string; - /** - * The track name to search for. Either this or query is required. - */ - trackName?: string; - /** - * The artist name - */ - artistName?: string; - /** - * The album name - */ - albumName?: string; -} - -export interface LrcGetParams extends Required> { - /** - * The duration of the track - */ - duration: number; -} - -const toSnakeCase = (obj: Record): Record => { - const snakeObj: Record = {}; - - for (const [key, value] of Object.entries(obj)) { - if (value == null) continue; - const newKey = key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`); - snakeObj[newKey] = value; - } - - return snakeObj; -}; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const createQuery = (params: any) => new URLSearchParams(toSnakeCase(params)).toString(); - -export interface LrcSearchResult { - /** - * The track id - */ - id: number; - /** - * The track name - */ - name: string; - /** - * The artist name - */ - trackName: string; - /** - * The album name - */ - artistName: string; - /** - * The album name - */ - albumName: string; - /** - * The duration of the track - */ - duration: number; - /** - * The release date of the track - */ - instrumental: boolean; - /** - * The release date of the track - */ - plainLyrics: string; - /** - * The release date of the track - */ - syncedLyrics?: string; -} - -export type LrcGetResult = Omit; - -export class LrcLib { - /** - * The API URL - */ - public api = 'https://lrclib.net/api'; - /** - * The request timeout. Default is 15 seconds. - */ - public timeout = 15_000; - /** - * The request bucket - */ - public bucket = new SequentialBucket(); - - /** - * Creates a new LrcLib instance - * @param {Player} player The player instance - */ - public constructor(public readonly player: Player) {} - - /** - * Sets the request timeout - * @param {number} timeout The timeout in milliseconds - */ - public setRequestTimeout(timeout: number) { - this.timeout = timeout; - } - - /** - * Sets the retry limit. Default is 5. - * @param {number} limit The retry limit - */ - public setRetryLimit(limit: number) { - this.bucket.MAX_RETRIES = limit; - } - - /** - * Gets lyrics - * @param params The get params - */ - public get(params: LrcGetParams) { - const path = `get?${createQuery(params)}`; - - return this.request(path); - } - - /** - * Gets lyrics by ID - * @param id The lyrics ID - */ - public getById(id: `${number}` | number) { - return this.request(`get/${id}`); - } - - /** - * Gets cached lyrics - * @param params The get params - */ - public getCached(params: LrcGetParams) { - const path = `get-cached?${createQuery(params)}`; - - return this.request(path); - } - - /** - * Searches for lyrics - * @param params The search params - */ - public search(params: LrcSearchParams) { - if (!params.q && !params.trackName) { - throw Exceptions.ERR_INVALID_ARG_TYPE('one of q or trackName', 'string', [String(params.q), String(params.trackName)].join(', ')); - } - - const path = `search?${createQuery(params)}`; - - return this.request(path); - } - - /** - * Requests the API - * @param path The path - * @param options The request options - */ - public async request(path: string, options?: RequestInit): Promise { - let timeout: NodeJS.Timeout | null = null; - - const dispatcher = () => { - const controller = new AbortController(); - - timeout = setTimeout(() => { - controller.abort(); - }, this.timeout).unref(); - - const { name, version } = Util.getRuntime(); - - const runtimeVersion = name === 'unknown' ? version : `${name}/${version}`; - - const init: RequestInit = { - method: 'GET', - redirect: 'follow', - signal: controller.signal, - ...options, - headers: { - 'User-Agent': `Discord-Player/${this.player.version} ${runtimeVersion ?? ''}`.trimEnd(), - 'Content-Type': 'application/json', - ...options?.headers - } - }; - - this.player.debug(`[LrcLib] Requesting ${path}`); - - return fetch(`${this.api}${path.startsWith('/') ? path : '/' + path}`, init); - }; - - const res = await this.bucket.enqueue(dispatcher); - - if (timeout) clearTimeout(timeout); - - return res.json(); - } -} diff --git a/packages/discord-player/src/queue/GuildNodeManager.ts b/packages/discord-player/src/queue/GuildNodeManager.ts deleted file mode 100644 index bcbb9246cd..0000000000 --- a/packages/discord-player/src/queue/GuildNodeManager.ts +++ /dev/null @@ -1,201 +0,0 @@ -import { EqualizerBand, PCMFilters, BiquadFilters } from '@discord-player/equalizer'; -import { Collection, QueueStrategy } from '@discord-player/utils'; -import { GuildResolvable } from 'discord.js'; -import { Player } from '../Player'; -import { GuildQueue, OnAfterCreateStreamHandler, OnBeforeCreateStreamHandler } from './GuildQueue'; -import { FiltersName, QueueRepeatMode } from '../types/types'; -import { getGlobalRegistry } from '../utils/__internal__'; -import { Exceptions } from '../errors'; - -export interface GuildNodeCreateOptions { - strategy?: QueueStrategy; - volume?: number; - equalizer?: EqualizerBand[]; - a_filter?: PCMFilters[]; - biquad?: BiquadFilters; - resampler?: number; - disableHistory?: boolean; - onBeforeCreateStream?: OnBeforeCreateStreamHandler; - onAfterCreateStream?: OnAfterCreateStreamHandler; - repeatMode?: QueueRepeatMode; - pauseOnEmpty?: boolean; - leaveOnEmpty?: boolean; - leaveOnEmptyCooldown?: number; - leaveOnEnd?: boolean; - leaveOnEndCooldown?: number; - leaveOnStop?: boolean; - leaveOnStopCooldown?: number; - metadata?: T | null; - selfDeaf?: boolean; - connectionTimeout?: number; - defaultFFmpegFilters?: FiltersName[]; - bufferingTimeout?: number; - noEmitInsert?: boolean; - maxSize?: number; - maxHistorySize?: number; - preferBridgedMetadata?: boolean; - disableVolume?: boolean; - disableEqualizer?: boolean; - disableFilterer?: boolean; - disableBiquad?: boolean; - disableResampler?: boolean; -} - -export type NodeResolvable = GuildQueue | GuildResolvable; - -export class GuildNodeManager { - public cache = new Collection(); - public constructor(public player: Player) {} - - /** - * Create guild queue if it does not exist - * @param guild The guild which will be the owner of the queue - * @param options Queue initializer options - */ - public create(guild: GuildResolvable, options: GuildNodeCreateOptions = {}): GuildQueue { - const server = this.player.client.guilds.resolve(guild); - if (!server) { - throw Exceptions.ERR_NO_GUILD('Invalid or unknown guild'); - } - - if (this.cache.has(server.id)) { - return this.cache.get(server.id) as GuildQueue; - } - - options.strategy ??= 'FIFO'; - options.volume ??= 100; - options.equalizer ??= []; - options.a_filter ??= []; - options.disableHistory ??= false; - options.leaveOnEmpty ??= true; - options.leaveOnEmptyCooldown ??= 0; - options.leaveOnEnd ??= true; - options.leaveOnEndCooldown ??= 0; - options.leaveOnStop ??= true; - options.leaveOnStopCooldown ??= 0; - options.resampler ??= 48000; - options.selfDeaf ??= true; - options.connectionTimeout ??= this.player.options.connectionTimeout; - options.bufferingTimeout ??= 1000; - options.maxSize ??= Infinity; - options.maxHistorySize ??= Infinity; - options.preferBridgedMetadata ??= true; - options.pauseOnEmpty ??= true; - // todo(twlite): maybe disable these by default? - options.disableBiquad ??= false; - options.disableEqualizer ??= false; - options.disableFilterer ??= false; - options.disableVolume ??= false; - options.disableResampler ??= true; - - if (getGlobalRegistry().has('@[onBeforeCreateStream]') && !options.onBeforeCreateStream) { - options.onBeforeCreateStream = getGlobalRegistry().get('@[onBeforeCreateStream]') as OnBeforeCreateStreamHandler; - } - - if (getGlobalRegistry().has('@[onAfterCreateStream]') && !options.onAfterCreateStream) { - options.onAfterCreateStream = getGlobalRegistry().get('@[onAfterCreateStream]') as OnAfterCreateStreamHandler; - } - - const queue = new GuildQueue(this.player, { - guild: server, - queueStrategy: options.strategy, - volume: options.volume, - equalizer: options.equalizer, - filterer: options.a_filter, - biquad: options.biquad, - resampler: options.resampler, - disableHistory: options.disableHistory, - onBeforeCreateStream: options.onBeforeCreateStream, - onAfterCreateStream: options.onAfterCreateStream, - repeatMode: options.repeatMode, - leaveOnEmpty: options.leaveOnEmpty, - leaveOnEmptyCooldown: options.leaveOnEmptyCooldown, - leaveOnEnd: options.leaveOnEnd, - leaveOnEndCooldown: options.leaveOnEndCooldown, - leaveOnStop: options.leaveOnStop, - leaveOnStopCooldown: options.leaveOnStopCooldown, - metadata: options.metadata, - connectionTimeout: options.connectionTimeout ?? 120_000, - selfDeaf: options.selfDeaf, - ffmpegFilters: options.defaultFFmpegFilters ?? [], - bufferingTimeout: options.bufferingTimeout, - noEmitInsert: options.noEmitInsert ?? false, - preferBridgedMetadata: options.preferBridgedMetadata, - maxHistorySize: options.maxHistorySize, - maxSize: options.maxSize, - pauseOnEmpty: options.pauseOnEmpty, - disableBiquad: options.disableBiquad, - disableEqualizer: options.disableEqualizer, - disableFilterer: options.disableFilterer, - disableResampler: options.disableResampler, - disableVolume: options.disableVolume - }); - - this.cache.set(server.id, queue); - - return queue; - } - - /** - * Get existing queue - * @param node Queue resolvable - */ - public get(node: NodeResolvable) { - const queue = this.resolve(node); - if (!queue) return null; - - return (this.cache.get(queue.id) as GuildQueue) || null; - } - - /** - * Check if a queue exists - * @param node Queue resolvable - */ - public has(node: NodeResolvable) { - const id = node instanceof GuildQueue ? node.id : this.player.client.guilds.resolveId(node)!; - return this.cache.has(id); - } - - /** - * Delete queue - * @param node Queue resolvable - */ - public delete(node: NodeResolvable) { - const queue = this.resolve(node); - if (!queue) { - throw Exceptions.ERR_NO_GUILD_QUEUE('Cannot delete non-existing queue'); - } - - queue.setTransitioning(true); - queue.node.stop(true); - queue.connection?.removeAllListeners(); - queue.dispatcher?.removeAllListeners(); - queue.dispatcher?.disconnect(); - queue.timeouts.forEach((tm) => clearTimeout(tm)); - queue.history.clear(); - queue.tracks.clear(); - - return this.cache.delete(queue.id); - } - - /** - * Resolve queue - * @param node Queue resolvable - */ - public resolve(node: NodeResolvable) { - if (node instanceof GuildQueue) { - return node; - } - - return this.cache.get(this.player.client.guilds.resolveId(node)!) as GuildQueue | undefined; - } - - /** - * Resolve queue id - * @param node Queue resolvable - */ - public resolveId(node: NodeResolvable) { - const q = this.resolve(node); - return q?.id || null; - } -} diff --git a/packages/discord-player/src/queue/GuildQueue.ts b/packages/discord-player/src/queue/GuildQueue.ts deleted file mode 100644 index 53f9e9013f..0000000000 --- a/packages/discord-player/src/queue/GuildQueue.ts +++ /dev/null @@ -1,1164 +0,0 @@ -import { Player, PlayerNodeInitializerOptions, TrackLike } from '../Player'; -import { ChannelType, Guild, GuildVoiceChannelResolvable, VoiceBasedChannel, VoiceState } from 'discord.js'; -import { Collection, Queue, QueueStrategy } from '@discord-player/utils'; -import { BiquadFilters, EqualizerBand, PCMFilters } from '@discord-player/equalizer'; -import { Track, TrackResolvable } from '../fabric/Track'; -import { StreamDispatcher } from '../VoiceInterface/StreamDispatcher'; -import { type AudioPlayer, AudioResource, StreamType, VoiceConnection, VoiceConnectionStatus } from 'discord-voip'; -import { Util, VALIDATE_QUEUE_CAP } from '../utils/Util'; -import { Playlist } from '../fabric/Playlist'; -import { GuildQueueHistory } from './GuildQueueHistory'; -import { GuildQueuePlayerNode, StreamConfig } from './GuildQueuePlayerNode'; -import { GuildQueueAudioFilters } from './GuildQueueAudioFilters'; -import { Readable } from 'stream'; -import { FiltersName, QueueRepeatMode, SearchQueryType } from '../types/types'; -import { setTimeout } from 'timers'; -import { GuildQueueStatistics } from './GuildQueueStatistics'; -import { TypeUtil } from '../utils/TypeUtil'; -import { AsyncQueue } from '../utils/AsyncQueue'; -import { Exceptions } from '../errors'; -import { SyncedLyricsProvider } from './SyncedLyricsProvider'; -import { LrcGetResult, LrcSearchResult } from '../lrclib/LrcLib'; - -export interface GuildNodeInit { - guild: Guild; - queueStrategy: QueueStrategy; - equalizer: EqualizerBand[] | boolean; - volume: number | boolean; - biquad: BiquadFilters | boolean | undefined; - resampler: number | boolean; - filterer: PCMFilters[] | boolean; - ffmpegFilters: FiltersName[]; - disableHistory: boolean; - onBeforeCreateStream?: OnBeforeCreateStreamHandler; - onAfterCreateStream?: OnAfterCreateStreamHandler; - repeatMode?: QueueRepeatMode; - leaveOnEmpty: boolean; - leaveOnEmptyCooldown: number; - leaveOnEnd: boolean; - leaveOnEndCooldown: number; - leaveOnStop: boolean; - leaveOnStopCooldown: number; - connectionTimeout: number; - selfDeaf?: boolean; - metadata?: Meta | null; - bufferingTimeout: number; - noEmitInsert: boolean; - maxSize?: number; - maxHistorySize?: number; - preferBridgedMetadata: boolean; - pauseOnEmpty?: boolean; - disableVolume: boolean; - disableEqualizer: boolean; - disableFilterer: boolean; - disableBiquad: boolean; - disableResampler: boolean; -} - -export interface VoiceConnectConfig { - deaf?: boolean; - timeout?: number; - group?: string; - audioPlayer?: AudioPlayer; -} - -export interface PostProcessedResult { - stream: Readable; - type: StreamType; -} - -export type OnBeforeCreateStreamHandler = (track: Track, queryType: SearchQueryType, queue: GuildQueue) => Promise; -export type OnAfterCreateStreamHandler = (stream: Readable, queue: GuildQueue) => Promise; - -export type PlayerTriggeredReason = 'filters' | 'normal'; - -export const GuildQueueEvent = { - /** - * Emitted when audio track is added to the queue - */ - audioTrackAdd: 'audioTrackAdd', - AudioTrackAdd: 'audioTrackAdd', - /** - * Emitted when audio tracks were added to the queue - */ - audioTracksAdd: 'audioTracksAdd', - AudioTracksAdd: 'audioTracksAdd', - /** - * Emitted when audio track is removed from the queue - */ - audioTrackRemove: 'audioTrackRemove', - AudioTrackRemove: 'audioTrackRemove', - /** - * Emitted when audio tracks are removed from the queue - */ - audioTracksRemove: 'audioTracksRemove', - AudioTracksRemove: 'audioTracksRemove', - /** - * Emitted when a connection is created - */ - connection: 'connection', - Connection: 'connection', - /** - * Emitted when a voice connection is destroyed - */ - connectionDestroyed: 'connectionDestroyed', - ConnectionDestroyed: 'connectionDestroyed', - /** - * Emitted when the bot is disconnected from the channel - */ - disconnect: 'disconnect', - Disconnect: 'disconnect', - /** - * Emitted when the queue sends a debug info - */ - debug: 'debug', - Debug: 'debug', - /** - * Emitted when the queue encounters error - */ - error: 'error', - Error: 'error', - /** - * Emitted when the voice channel is empty - */ - emptyChannel: 'emptyChannel', - EmptyChannel: 'emptyChannel', - /** - * Emitted when the queue is empty - */ - emptyQueue: 'emptyQueue', - EmptyQueue: 'emptyQueue', - /** - * Emitted when the audio player starts streaming audio track - */ - playerStart: 'playerStart', - PlayerStart: 'playerStart', - /** - * Emitted when the audio player errors while streaming audio track - */ - playerError: 'playerError', - PlayerError: 'playerError', - /** - * Emitted when the audio player finishes streaming audio track - */ - playerFinish: 'playerFinish', - PlayerFinish: 'playerFinish', - /** - * Emitted when the audio player skips current track - */ - playerSkip: 'playerSkip', - PlayerSkip: 'playerSkip', - /** - * Emitted when the audio player is triggered - */ - playerTrigger: 'playerTrigger', - PlayerTrigger: 'playerTrigger', - /** - * Emitted when the voice state is updated. Consuming this event may disable default voice state update handler if `Player.isVoiceStateHandlerLocked()` returns `false`. - */ - voiceStateUpdate: 'voiceStateUpdate', - VoiceStateUpdate: 'voiceStateUpdate', - /** - * Emitted when volume is updated - */ - volumeChange: 'volumeChange', - VolumeChange: 'volumeChange', - /** - * Emitted when player is paused - */ - playerPause: 'playerPause', - PlayerPause: 'playerPause', - /** - * Emitted when player is resumed - */ - playerResume: 'playerResume', - PlayerResume: 'playerResume', - /** - * Biquad Filters Update - */ - biquadFiltersUpdate: 'biquadFiltersUpdate', - BiquadFiltersUpdate: 'biquadFiltersUpdate', - /** - * Equalizer Update - */ - equalizerUpdate: 'equalizerUpdate', - EqualizerUpdate: 'equalizerUpdate', - /** - * DSP update - */ - dspUpdate: 'dspUpdate', - DSPUpdate: 'dspUpdate', - /** - * Audio Filters Update - */ - audioFiltersUpdate: 'audioFiltersUpdate', - AudioFiltersUpdate: 'audioFiltersUpdate', - /** - * Audio player will play next track - */ - willPlayTrack: 'willPlayTrack', - WillPlayTrack: 'willPlayTrack', - /** - * Emitted when a voice channel is repopulated - */ - channelPopulate: 'channelPopulate', - ChannelPopulate: 'channelPopulate', - /** - * Emitted when a queue is successfully created - */ - queueCreate: 'queueCreate', - QueueCreate: 'queueCreate', - /** - * Emitted when a queue is deleted - */ - queueDelete: 'queueDelete', - QueueDelete: 'queueDelete', - /** - * Emitted when a queue is trying to add similar track for autoplay - */ - willAutoPlay: 'willAutoPlay', - WillAutoPlay: 'willAutoPlay' -} as const; - -export type GuildQueueEvent = (typeof GuildQueueEvent)[keyof typeof GuildQueueEvent]; - -export enum TrackSkipReason { - NoStream = 'ERR_NO_STREAM', - Manual = 'MANUAL', - SEEK_OVER_THRESHOLD = 'SEEK_OVER_THRESHOLD', - Jump = 'JUMPED_TO_ANOTHER_TRACK', - SkipTo = 'SKIP_TO_ANOTHER_TRACK', - HistoryNext = 'HISTORY_NEXT_TRACK' -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export interface GuildQueueEvents { - /** - * Emitted when audio track is added to the queue - * @param queue The queue where this event occurred - * @param track The track - */ - [GuildQueueEvent.AudioTrackAdd]: (queue: GuildQueue, track: Track) => unknown; - /** - * Emitted when audio tracks were added to the queue - * @param queue The queue where this event occurred - * @param tracks The tracks array - */ - [GuildQueueEvent.AudioTracksAdd]: (queue: GuildQueue, track: Track[]) => unknown; - /** - * Emitted when audio track is removed from the queue - * @param queue The queue where this event occurred - * @param track The track - */ - [GuildQueueEvent.AudioTrackRemove]: (queue: GuildQueue, track: Track) => unknown; - /** - * Emitted when audio tracks are removed from the queue - * @param queue The queue where this event occurred - * @param track The track - */ - [GuildQueueEvent.AudioTracksRemove]: (queue: GuildQueue, track: Track[]) => unknown; - /** - * Emitted when a connection is created - * @param queue The queue where this event occurred - */ - [GuildQueueEvent.Connection]: (queue: GuildQueue) => unknown; - /** - * Emitted when a connection is destroyed - * @param queue The queue where this event occurred - */ - [GuildQueueEvent.ConnectionDestroyed]: (queue: GuildQueue) => unknown; - /** - * Emitted when the bot is disconnected from the channel - * @param queue The queue where this event occurred - */ - [GuildQueueEvent.Disconnect]: (queue: GuildQueue) => unknown; - /** - * Emitted when the queue sends a debug info - * @param queue The queue where this event occurred - * @param message The debug message - */ - [GuildQueueEvent.Debug]: (queue: GuildQueue, message: string) => unknown; - /** - * Emitted when the queue encounters error - * @param queue The queue where this event occurred - * @param error The error - */ - [GuildQueueEvent.Error]: (queue: GuildQueue, error: Error) => unknown; - /** - * Emitted when the voice channel is empty - * @param queue The queue where this event occurred - */ - [GuildQueueEvent.EmptyChannel]: (queue: GuildQueue) => unknown; - /** - * Emitted when the queue is empty - * @param queue The queue where this event occurred - */ - [GuildQueueEvent.EmptyQueue]: (queue: GuildQueue) => unknown; - /** - * Emitted when the audio player starts streaming audio track - * @param queue The queue where this event occurred - * @param track The track that is being streamed - */ - [GuildQueueEvent.PlayerStart]: (queue: GuildQueue, track: Track) => unknown; - /** - * Emitted when the audio player errors while streaming audio track - * @param queue The queue where this event occurred - * @param error The error - * @param track The track that is being streamed - */ - [GuildQueueEvent.PlayerError]: (queue: GuildQueue, error: Error, track: Track) => unknown; - /** - * Emitted when the audio player finishes streaming audio track - * @param queue The queue where this event occurred - * @param track The track that was being streamed - */ - [GuildQueueEvent.PlayerFinish]: (queue: GuildQueue, track: Track) => unknown; - /** - * Emitted when the audio player skips current track - * @param queue The queue where this event occurred - * @param track The track that was skipped - * @param reason The reason for skipping - * @param description The description for skipping - */ - [GuildQueueEvent.PlayerSkip]: (queue: GuildQueue, track: Track, reason: TrackSkipReason, description: string) => unknown; - /** - * Emitted when the audio player is triggered - * @param queue The queue where this event occurred - * @param track The track which was played in this event - */ - [GuildQueueEvent.PlayerTrigger]: (queue: GuildQueue, track: Track, reason: PlayerTriggeredReason) => unknown; - /** - * Emitted when the voice state is updated. Consuming this event may disable default voice state update handler if `Player.isVoiceStateHandlerLocked()` returns `false`. - * @param queue The queue where this event occurred - * @param oldState The old voice state - * @param newState The new voice state - */ - [GuildQueueEvent.VoiceStateUpdate]: (queue: GuildQueue, oldState: VoiceState, newState: VoiceState) => unknown; - /** - * Emitted when audio player is paused - * @param queue The queue where this event occurred - */ - [GuildQueueEvent.PlayerPause]: (queue: GuildQueue) => unknown; - /** - * Emitted when audio player is resumed - * @param queue The queue where this event occurred - */ - [GuildQueueEvent.PlayerResume]: (queue: GuildQueue) => unknown; - /** - * Emitted when audio player's volume is changed - * @param queue The queue where this event occurred - * @param oldVolume The old volume - * @param newVolume The updated volume - */ - [GuildQueueEvent.VolumeChange]: (queue: GuildQueue, oldVolume: number, newVolume: number) => unknown; - /** - * Emitted when equalizer config is updated - * @param queue The queue where this event occurred - * @param oldFilters Old filters - * @param newFilters New filters - */ - [GuildQueueEvent.EqualizerUpdate]: (queue: GuildQueue, oldFilters: EqualizerBand[], newFilters: EqualizerBand[]) => unknown; - /** - * Emitted when biquad filters is updated - * @param queue The queue where this event occurred - * @param oldFilters Old filters - * @param newFilters New filters - */ - [GuildQueueEvent.BiquadFiltersUpdate]: (queue: GuildQueue, oldFilters: BiquadFilters | null, newFilters: BiquadFilters | null) => unknown; - /** - * Emitted when dsp filters is updated - * @param queue The queue where this event occurred - * @param oldFilters Old filters - * @param newFilters New filters - */ - [GuildQueueEvent.DSPUpdate]: (queue: GuildQueue, oldFilters: PCMFilters[], newFilters: PCMFilters[]) => unknown; - /** - * Emitted when ffmpeg audio filters is updated - * @param queue The queue where this event occurred - * @param oldFilters Old filters - * @param newFilters New filters - */ - [GuildQueueEvent.AudioFiltersUpdate]: (queue: GuildQueue, oldFilters: FiltersName[], newFilters: FiltersName[]) => unknown; - - /** - * Emitted before streaming an audio track. This event can be used to modify stream config before playing a track. - * Listening to this event will pause the execution of audio player until `done()` is invoked. - * @param queue The queue where this event occurred - * @param track The track that will be streamed - * @param config Configurations for streaming - * @param done Done callback - */ - [GuildQueueEvent.WillPlayTrack]: (queue: GuildQueue, track: Track, config: StreamConfig, done: () => void) => unknown; - /** - * Emitted when a voice channel is populated - * @param queue The queue where this event occurred - */ - [GuildQueueEvent.ChannelPopulate]: (queue: GuildQueue) => unknown; - /** - * Emitted when a queue is successfully created - * @param queue The queue where this event occurred - */ - [GuildQueueEvent.QueueCreate]: (queue: GuildQueue) => unknown; - /** - * Emitted when a queue is successfully deleted - * @param queue The queue where this event occurred - */ - [GuildQueueEvent.QueueDelete]: (queue: GuildQueue) => unknown; - /** - * Emitted when a queue is trying to add similar track for autoplay - * @param queue The queue where this event occurred - * @param tracks The similar tracks that were found - * @param done Done callback - */ - [GuildQueueEvent.WillAutoPlay]: (queue: GuildQueue, tracks: Track[], done: (track: Track | null) => void) => unknown; -} - -export class GuildQueue { - #transitioning = false; - #deleted = false; - #shuffle = false; - private __current: Track | null = null; - public tracks: Queue; - public history = new GuildQueueHistory(this); - public dispatcher: StreamDispatcher | null = null; - public node = new GuildQueuePlayerNode(this); - public filters = new GuildQueueAudioFilters(this); - public onBeforeCreateStream: OnBeforeCreateStreamHandler = async () => null; - public onAfterCreateStream: OnAfterCreateStreamHandler = async (stream) => ({ - stream, - type: StreamType.Raw - }); - public repeatMode = QueueRepeatMode.OFF; - public timeouts = new Collection(); - public stats = new GuildQueueStatistics(this); - public tasksQueue = new AsyncQueue(); - public syncedLyricsProvider = new SyncedLyricsProvider(this); - - public constructor(public player: Player, public options: GuildNodeInit) { - this.tracks = new Queue(options.queueStrategy); - if (TypeUtil.isFunction(options.onBeforeCreateStream)) this.onBeforeCreateStream = options.onBeforeCreateStream; - if (TypeUtil.isFunction(options.onAfterCreateStream)) this.onAfterCreateStream = options.onAfterCreateStream; - if (!TypeUtil.isNullish(options.repeatMode)) this.repeatMode = options.repeatMode; - - options.selfDeaf ??= true; - options.maxSize ??= Infinity; - options.maxHistorySize ??= Infinity; - options.pauseOnEmpty ??= true; - - if (!TypeUtil.isNullish(this.options.biquad) && !TypeUtil.isBoolean(this.options.biquad)) { - this.filters._lastFiltersCache.biquad = this.options.biquad; - } - - if (Array.isArray(this.options.equalizer)) { - this.filters._lastFiltersCache.equalizer = this.options.equalizer; - } - - if (Array.isArray(this.options.filterer)) { - this.filters._lastFiltersCache.filters = this.options.filterer; - } - - if (TypeUtil.isNumber(this.options.resampler)) { - this.filters._lastFiltersCache.sampleRate = this.options.resampler; - } - - if (TypeUtil.isArray(this.options.ffmpegFilters)) { - this.filters.ffmpeg.setDefaults(this.options.ffmpegFilters); - } - - if (!TypeUtil.isNumber(options.maxSize)) { - throw Exceptions.ERR_INVALID_ARG_TYPE('[GuildNodeInit.maxSize]', 'number', typeof options.maxSize); - } - - if (!TypeUtil.isNumber(options.maxHistorySize)) { - throw Exceptions.ERR_INVALID_ARG_TYPE('[GuildNodeInit.maxHistorySize]', 'number', typeof options.maxHistorySize); - } - - if (options.maxSize < 1) options.maxSize = Infinity; - if (options.maxHistorySize < 1) options.maxHistorySize = Infinity; - - if (this.hasDebugger) this.debug(`GuildQueue initialized for guild ${this.options.guild.name} (ID: ${this.options.guild.id})`); - this.emit(GuildQueueEvent.queueCreate, this); - } - - /** - * Estimated duration of this queue in ms - */ - public get estimatedDuration() { - return this.tracks.store.reduce((a, c) => a + c.durationMS, 0); - } - - /** - * Formatted duration of this queue - */ - public get durationFormatted() { - return Util.buildTimeCode(Util.parseMS(this.estimatedDuration)); - } - - /** - * The voice receiver for this queue - */ - public get voiceReceiver() { - return this.dispatcher?.receiver ?? null; - } - - /** - * The sync lyrics provider for this queue. - * @example const lyrics = await player.lyrics.search({ q: 'Alan Walker Faded' }); - * const syncedLyrics = queue.syncedLyrics(lyrics[0]); - * console.log(syncedLyrics.at(10_000)); - * // subscribe to lyrics change - * const unsubscribe = syncedLyrics.onChange((lyrics, timestamp) => { - * console.log(lyrics, timestamp); - * }); - * // unsubscribe from lyrics change - * unsubscribe(); // or - * syncedLyrics.unsubscribe(); - */ - public syncedLyrics(lyrics: LrcGetResult | LrcSearchResult) { - this.syncedLyricsProvider.load(lyrics?.syncedLyrics ?? ''); - return this.syncedLyricsProvider; - } - - /** - * Write a debug message to this queue - * @param m The message to write - */ - public debug(m: string) { - this.emit(GuildQueueEvent.debug, this, m); - } - - /** - * The metadata of this queue - */ - public get metadata() { - return this.options.metadata!; - } - - public set metadata(m: Meta) { - this.options.metadata = m; - } - - /** - * Set metadata for this queue - * @param m Metadata to set - */ - public setMetadata(m: Meta) { - this.options.metadata = m; - } - - /** - * Indicates current track of this queue - */ - public get currentTrack() { - return this.dispatcher?.audioResource?.metadata || this.__current; - } - - /** - * Indicates if this queue was deleted previously - */ - public get deleted() { - return this.#deleted; - } - - /** - * The voice channel of this queue - */ - public get channel() { - return this.dispatcher?.channel || null; - } - - public set channel(c: VoiceBasedChannel | null) { - if (this.dispatcher) { - if (c) { - this.dispatcher.channel = c; - } else { - this.delete(); - } - } - } - - /** - * The voice connection of this queue - */ - public get connection() { - return this.dispatcher?.voiceConnection || null; - } - - /** - * The guild this queue belongs to - */ - public get guild() { - return this.options.guild; - } - - /** - * The id of this queue - */ - public get id() { - return this.guild.id; - } - - /** - * Set transition mode for this queue - * @param state The state to set - */ - public setTransitioning(state: boolean) { - this.#transitioning = state; - } - - /** - * if this queue is currently under transition mode - */ - public isTransitioning() { - return this.#transitioning; - } - - /** - * Set repeat mode for this queue - * @param mode The repeat mode to apply - */ - public setRepeatMode(mode: QueueRepeatMode) { - this.repeatMode = mode; - } - - /** - * Max size of this queue - */ - public get maxSize() { - return this.options.maxSize ?? Infinity; - } - - /** - * Max size of this queue - */ - public getMaxSize() { - return this.maxSize; - } - - /** - * Gets the size of the queue - */ - public get size() { - return this.tracks.size; - } - - /** - * The size of this queue - */ - public getSize() { - return this.size; - } - - /** - * Max history size of this queue - */ - public get maxHistorySize() { - return this.options.maxHistorySize ?? Infinity; - } - - /** - * Max history size of this queue - */ - public getMaxHistorySize() { - return this.maxHistorySize; - } - - /** - * Set max history size for this queue - * @param size The size to set - */ - public setMaxHistorySize(size: number) { - if (!TypeUtil.isNumber(size)) { - throw Exceptions.ERR_INVALID_ARG_TYPE('size', 'number', typeof size); - } - - if (size < 1) size = Infinity; - - this.options.maxHistorySize = size; - } - - /** - * Set max size for this queue - * @param size The size to set - */ - public setMaxSize(size: number) { - if (!TypeUtil.isNumber(size)) { - throw Exceptions.ERR_INVALID_ARG_TYPE('size', 'number', typeof size); - } - - if (size < 1) size = Infinity; - - this.options.maxSize = size; - } - - /** - * Clear this queue - */ - public clear() { - this.tracks.clear(); - this.history.clear(); - } - - /** - * Check if this queue has no tracks left in it - */ - public isEmpty() { - return this.tracks.size < 1; - } - - /** - * Check if this queue is full - */ - public isFull() { - return this.tracks.size >= this.maxSize; - } - - /** - * Get queue capacity - */ - public getCapacity() { - if (this.isFull()) return 0; - const cap = this.maxSize - this.size; - return cap; - } - - /** - * Check if this queue currently holds active audio resource - */ - public isPlaying() { - return this.dispatcher?.audioResource != null && !this.dispatcher.audioResource.ended; - } - - /** - * Add track to the queue. This will emit `audioTracksAdd` when multiple tracks are added, otherwise `audioTrackAdd`. - * @param track Track or playlist or array of tracks to add - */ - public addTrack(track: Track | Track[] | Playlist) { - const toAdd = track instanceof Playlist ? track.tracks : track; - const isMulti = Array.isArray(toAdd); - - VALIDATE_QUEUE_CAP(this, toAdd); - - this.tracks.add(toAdd); - - if (isMulti) { - this.emit(GuildQueueEvent.audioTracksAdd, this, toAdd); - } else { - this.emit(GuildQueueEvent.audioTrackAdd, this, toAdd); - } - } - - /** - * Remove a track from queue - * @param track The track to remove - */ - public removeTrack(track: TrackResolvable) { - return this.node.remove(track); - } - - /** - * Inserts the track to the given index - * @param track The track to insert - * @param index The index to insert the track at (defaults to 0) - */ - public insertTrack(track: Track, index = 0): void { - return this.node.insert(track, index); - } - - /** - * Moves a track in the queue - * @param from The track to move - * @param to The position to move to - */ - public moveTrack(track: TrackResolvable, index = 0): void { - return this.node.move(track, index); - } - - /** - * Copy a track in the queue - * @param from The track to clone - * @param to The position to clone at - */ - public copyTrack(track: TrackResolvable, index = 0): void { - return this.node.copy(track, index); - } - - /** - * Swap two tracks in the queue - * @param src The first track to swap - * @param dest The second track to swap - */ - public swapTracks(src: TrackResolvable, dest: TrackResolvable): void { - return this.node.swap(src, dest); - } - - /** - * Create stream dispatcher from the given connection - * @param connection The connection to use - */ - public createDispatcher(connection: VoiceConnection, options: Pick = {}) { - if (connection.state.status === VoiceConnectionStatus.Destroyed) { - throw Exceptions.ERR_VOICE_CONNECTION_DESTROYED(); - } - - const channel = this.player.client.channels.cache.get(connection.joinConfig.channelId!); - if (!channel) throw Exceptions.ERR_NO_VOICE_CHANNEL(); - if (!channel.isVoiceBased()) throw Exceptions.ERR_INVALID_ARG_TYPE('channel', `VoiceBasedChannel (type ${ChannelType.GuildVoice}/${ChannelType.GuildStageVoice})`, String(channel?.type)); - - if (this.dispatcher) { - this.#removeListeners(this.dispatcher); - this.dispatcher.destroy(); - this.dispatcher = null; - } - - this.dispatcher = new StreamDispatcher(connection, channel, this, options.timeout ?? this.options.connectionTimeout, options.audioPlayer); - } - - /** - * Connect to a voice channel - * @param channelResolvable The voice channel to connect to - * @param options Join config - */ - public async connect(channelResolvable: GuildVoiceChannelResolvable, options: VoiceConnectConfig = {}) { - const channel = this.player.client.channels.resolve(channelResolvable); - if (!channel || !channel.isVoiceBased()) { - throw Exceptions.ERR_INVALID_ARG_TYPE('channel', `VoiceBasedChannel (type ${ChannelType.GuildVoice}/${ChannelType.GuildStageVoice})`, String(channel?.type)); - } - - if (this.hasDebugger) this.debug(`Connecting to ${channel.type === ChannelType.GuildStageVoice ? 'stage' : 'voice'} channel ${channel.name} (ID: ${channel.id})`); - - if (this.dispatcher && channel.id !== this.dispatcher.channel.id) { - if (this.hasDebugger) this.debug('Destroying old connection'); - this.#removeListeners(this.dispatcher); - this.dispatcher.destroy(); - this.dispatcher = null; - } - - this.dispatcher = await this.player.voiceUtils.connect(channel, { - deaf: options.deaf ?? this.options.selfDeaf ?? true, - maxTime: options?.timeout ?? this.options.connectionTimeout ?? 120_000, - queue: this, - audioPlayer: options?.audioPlayer, - group: options.group ?? this.player.client.user?.id - }); - - this.emit(GuildQueueEvent.connection, this); - - if (this.channel!.type === ChannelType.GuildStageVoice) { - await this.channel!.guild.members.me!.voice.setSuppressed(false).catch(async () => { - return await this.channel!.guild.members.me!.voice.setRequestToSpeak(true).catch(Util.noop); - }); - } - - this.#attachListeners(this.dispatcher); - - return this; - } - - /** - * Enable shuffle mode for this queue - * @param dynamic Whether to shuffle the queue dynamically. Defaults to `true`. - * Dynamic shuffling will shuffle the queue when the current track ends, without mutating the queue. - * If set to `false`, the queue will be shuffled immediately in-place, which cannot be undone. - */ - public enableShuffle(dynamic = true) { - if (!dynamic) { - this.tracks.shuffle(); - return true; - } - - this.#shuffle = true; - return true; - } - - /** - * Disable shuffle mode for this queue. - */ - public disableShuffle() { - this.#shuffle = false; - return true; - } - - /** - * Toggle shuffle mode for this queue. - * @param dynamic Whether to shuffle the queue dynamically. Defaults to `true`. - * @returns Whether shuffle is enabled or disabled. - */ - public toggleShuffle(dynamic = true) { - if (dynamic) { - this.#shuffle = !this.#shuffle; - return this.#shuffle; - } else { - this.tracks.shuffle(); - return true; - } - } - - /** - * Whether shuffle mode is enabled for this queue. - */ - public get isShuffling() { - return this.#shuffle; - } - - /** - * The voice connection latency of this queue - */ - public get ping() { - return this.connection?.ping.udp ?? -1; - } - - /** - * Delete this queue - */ - public delete() { - if (this.player.nodes.delete(this.id)) { - this.#deleted = true; - this.player.events.emit(GuildQueueEvent.queueDelete, this); - this.node.tasksQueue.cancelAll(); - this.tasksQueue.cancelAll(); - } - } - - /** - * Revives this queue - * @returns - */ - public revive() { - if (!this.deleted || this.player.nodes.has(this.id)) return; - this.#deleted = false; - this.setTransitioning(false); - this.player.nodes.cache.set(this.id, this); - this.player.events.emit(GuildQueueEvent.queueCreate, this); - } - - /** - * Set self deaf - * @param mode On/Off state - * @param reason Reason - */ - public setSelfDeaf(mode?: boolean, reason?: string) { - return this.guild.members.me!.voice.setDeaf(mode, reason); - } - - /** - * Set self mute - * @param mode On/Off state - * @param reason Reason - */ - public setSelfMute(mode?: boolean, reason?: string) { - return this.guild.members.me!.voice.setMute(mode, reason); - } - - /** - * Play a track in this queue - * @param track The track to be played - * @param options Player node initialization options - */ - public async play(track: TrackLike, options?: PlayerNodeInitializerOptions) { - if (!this.channel) throw Exceptions.ERR_NO_VOICE_CONNECTION(); - - return this.player.play(this.channel, track, options); - } - - /** - * Emit an event on this queue - * @param event The event to emit - * @param args The args for the event - */ - public emit>(event: K, ...args: Parameters[K]>): boolean { - if (this.deleted) return false; - return this.player.events.emit(event, ...args); - } - - #attachListeners(dispatcher: StreamDispatcher) { - dispatcher.on('error', (e) => this.emit(GuildQueueEvent.error, this, e)); - dispatcher.on('debug', (m) => this.hasDebugger && this.emit(GuildQueueEvent.debug, this, m)); - dispatcher.on('finish', (r) => this.#performFinish(r)); - dispatcher.on('start', (r) => this.#performStart(r)); - dispatcher.on('destroyed', () => { - this.#removeListeners(dispatcher); - this.dispatcher = null; - }); - dispatcher.on('dsp', (f) => { - if (!Object.is(this.filters._lastFiltersCache.filters, f)) { - this.emit(GuildQueueEvent.dspUpdate, this, this.filters._lastFiltersCache.filters, f); - } - this.filters._lastFiltersCache.filters = f; - }); - dispatcher.on('biquad', (f) => { - if (this.filters._lastFiltersCache.biquad !== f) { - this.emit(GuildQueueEvent.biquadFiltersUpdate, this, this.filters._lastFiltersCache.biquad, f); - } - this.filters._lastFiltersCache.biquad = f; - }); - dispatcher.on('eqBands', (f) => { - if (!Object.is(f, this.filters._lastFiltersCache.equalizer)) { - this.emit(GuildQueueEvent.equalizerUpdate, this, this.filters._lastFiltersCache.equalizer, f); - } - this.filters._lastFiltersCache.equalizer = f; - }); - dispatcher.on('volume', (f) => { - if (this.filters._lastFiltersCache.volume !== f) this.emit(GuildQueueEvent.volumeChange, this, this.filters._lastFiltersCache.volume, f); - this.filters._lastFiltersCache.volume = f; - }); - } - - public get hasDebugger() { - return this.player.events.hasDebugger; - } - - #removeListeners unknown }>(target: T) { - target.removeAllListeners(); - } - - #performStart(resource?: AudioResource) { - const track = resource?.metadata || this.currentTrack; - const reason = this.isTransitioning() ? 'filters' : 'normal'; - - if (this.hasDebugger) - this.debug( - `Player triggered for Track ${JSON.stringify({ - title: track?.title, - reason - })}` - ); - - this.emit(GuildQueueEvent.playerTrigger, this, track!, reason); - if (track && !this.isTransitioning()) this.emit(GuildQueueEvent.playerStart, this, track); - this.setTransitioning(false); - } - - #getNextTrack() { - if (!this.isShuffling) { - return this.tracks.dispatch(); - } - - const store = this.tracks.store; - - if (!store.length) return; - - const track = Util.randomChoice(store); - - this.tracks.removeOne((t) => { - return t.id === track.id; - }); - - return track; - } - - #performFinish(resource?: AudioResource) { - const track = resource?.metadata || this.currentTrack; - - if (this.hasDebugger) - this.debug( - `Track ${JSON.stringify({ - title: track?.title, - isTransitionMode: this.isTransitioning() - })} was marked as finished` - ); - - if (track && !this.isTransitioning()) { - this.syncedLyricsProvider.unsubscribe(); - this.syncedLyricsProvider.lyrics.clear(); - if (this.hasDebugger) this.debug('Adding track to history and emitting finish event since transition mode is disabled...'); - this.history.push(track); - this.node.resetProgress(); - this.emit(GuildQueueEvent.playerFinish, this, track); - if (this.#deleted) return this.#emitEnd(); - if (this.tracks.size < 1 && this.repeatMode === QueueRepeatMode.OFF) { - if (this.hasDebugger) this.debug('No more tracks left in the queue to play and repeat mode is off, initiating #emitEnd()'); - this.#emitEnd(); - } else { - if (this.repeatMode === QueueRepeatMode.TRACK) { - if (this.hasDebugger) this.debug('Repeat mode is set to track, repeating last track from the history...'); - this.__current = this.history.tracks.dispatch() || track; - return this.node.play(this.__current!, { queue: false }); - } - if (this.repeatMode === QueueRepeatMode.QUEUE) { - if (this.hasDebugger) this.debug('Repeat mode is set to queue, moving last track from the history to current queue...'); - this.tracks.add(this.history.tracks.dispatch() || track); - } - if (!this.tracks.size) { - if (this.repeatMode === QueueRepeatMode.AUTOPLAY) { - if (this.hasDebugger) this.debug('Repeat mode is set to autoplay, initiating autoplay handler...'); - this.#handleAutoplay(track); - return; - } - } else { - if (this.hasDebugger) this.debug('Initializing next track of the queue...'); - this.__current = this.#getNextTrack()!; - this.node.play(this.__current, { - queue: false - }); - } - } - } - } - - #emitEnd() { - this.__current = null; - this.emit(GuildQueueEvent.emptyQueue, this); - if (this.options.leaveOnEnd) { - const tm: NodeJS.Timeout = setTimeout(() => { - if (this.isPlaying()) return clearTimeout(tm); - this.dispatcher?.disconnect(); - }, this.options.leaveOnEndCooldown).unref(); - } - } - - async #handleAutoplay(track: Track) { - try { - if (this.hasDebugger) this.debug(`Autoplay >> Finding related tracks for Track ${track.title} (${track.url}) [ext:${track.extractor?.identifier || 'N/A'}]`); - const tracks = - (await track.extractor?.getRelatedTracks(track, this.history))?.tracks || - ( - await this.player.extractors.run(async (ext) => { - if (this.hasDebugger) this.debug(`Autoplay >> Querying extractor ${ext.identifier}`); - const res = await ext.getRelatedTracks(track, this.history); - if (!res.tracks.length) { - if (this.hasDebugger) this.debug(`Autoplay >> Extractor ${ext.identifier} failed to provide results.`); - return false; - } - - if (this.hasDebugger) this.debug(`Autoplay >> Extractor ${ext.identifier} successfully returned results.`); - - return res.tracks; - }) - )?.result || - []; - - let resolver: (track: Track | null) => void = Util.noop; - const donePromise = new Promise((resolve) => (resolver = resolve)); - - const success = this.emit(GuildQueueEvent.willAutoPlay, this, tracks, resolver!); - - // prevent dangling promise - if (!success) { - resolver( - tracks.length - ? (() => { - const unique = tracks.filter((tr) => !this.history.tracks.find((t) => t.url === tr.url)); - return unique?.[0] ?? Util.randomChoice(tracks.slice(0, 5)); - })() - : null - ); - } - - const nextTrack = await donePromise; - - if (!nextTrack) { - if (this.hasDebugger) this.debug('Autoplay >> No track was found, initiating #emitEnd()'); - throw 'No track was found'; - } - - await this.node.play(nextTrack, { - queue: false, - seek: 0, - transitionMode: false - }); - } catch { - return this.#emitEnd(); - } - } -} diff --git a/packages/discord-player/src/queue/GuildQueueAudioFilters.ts b/packages/discord-player/src/queue/GuildQueueAudioFilters.ts deleted file mode 100644 index cd263cde44..0000000000 --- a/packages/discord-player/src/queue/GuildQueueAudioFilters.ts +++ /dev/null @@ -1,397 +0,0 @@ -import { Readable } from 'stream'; -import { FiltersName, QueueFilters } from '../types/types'; -import { AudioFilters } from '../utils/AudioFilters'; -import { GuildQueue, GuildQueueEvent } from './GuildQueue'; -import { BiquadFilters, Equalizer, EqualizerBand, PCMFilters } from '@discord-player/equalizer'; -import { FFmpegStreamOptions, createFFmpegStream } from '../utils/FFmpegStream'; -import { Exceptions } from '../errors'; - -type Filters = keyof typeof AudioFilters.filters; - -const makeBands = (arr: number[]) => { - return Array.from( - { - length: Equalizer.BAND_COUNT - }, - (_, i) => ({ - band: i, - gain: arr[i] ? arr[i] / 30 : 0 - }) - ) as EqualizerBand[]; -}; - -type EQPreset = { - Flat: EqualizerBand[]; - Classical: EqualizerBand[]; - Club: EqualizerBand[]; - Dance: EqualizerBand[]; - FullBass: EqualizerBand[]; - FullBassTreble: EqualizerBand[]; - FullTreble: EqualizerBand[]; - Headphones: EqualizerBand[]; - LargeHall: EqualizerBand[]; - Live: EqualizerBand[]; - Party: EqualizerBand[]; - Pop: EqualizerBand[]; - Reggae: EqualizerBand[]; - Rock: EqualizerBand[]; - Ska: EqualizerBand[]; - Soft: EqualizerBand[]; - SoftRock: EqualizerBand[]; - Techno: EqualizerBand[]; -}; - -export const EqualizerConfigurationPreset: Readonly = Object.freeze({ - Flat: makeBands([]), - Classical: makeBands([-1.11022e-15, -1.11022e-15, -1.11022e-15, -1.11022e-15, -1.11022e-15, -1.11022e-15, -7.2, -7.2, -7.2, -9.6]), - Club: makeBands([-1.11022e-15, -1.11022e-15, 8.0, 5.6, 5.6, 5.6, 3.2, -1.11022e-15, -1.11022e-15, -1.11022e-15]), - Dance: makeBands([9.6, 7.2, 2.4, -1.11022e-15, -1.11022e-15, -5.6, -7.2, -7.2, -1.11022e-15, -1.11022e-15]), - FullBass: makeBands([-8.0, 9.6, 9.6, 5.6, 1.6, -4.0, -8.0, -10.4, -11.2, -11.2]), - FullBassTreble: makeBands([7.2, 5.6, -1.11022e-15, -7.2, -4.8, 1.6, 8.0, 11.2, 12.0, 12.0]), - FullTreble: makeBands([-9.6, -9.6, -9.6, -4.0, 2.4, 11.2, 16.0, 16.0, 16.0, 16.8]), - Headphones: makeBands([4.8, 11.2, 5.6, -3.2, -2.4, 1.6, 4.8, 9.6, 12.8, 14.4]), - LargeHall: makeBands([10.4, 10.4, 5.6, 5.6, -1.11022e-15, -4.8, -4.8, -4.8, -1.11022e-15, -1.11022e-15]), - Live: makeBands([-4.8, -1.11022e-15, 4.0, 5.6, 5.6, 5.6, 4.0, 2.4, 2.4, 2.4]), - Party: makeBands([7.2, 7.2, -1.11022e-15, -1.11022e-15, -1.11022e-15, -1.11022e-15, -1.11022e-15, -1.11022e-15, 7.2, 7.2]), - Pop: makeBands([-1.6, 4.8, 7.2, 8.0, 5.6, -1.11022e-15, -2.4, -2.4, -1.6, -1.6]), - Reggae: makeBands([-1.11022e-15, -1.11022e-15, -1.11022e-15, -5.6, -1.11022e-15, 6.4, 6.4, -1.11022e-15, -1.11022e-15, -1.11022e-15]), - Rock: makeBands([8.0, 4.8, -5.6, -8.0, -3.2, 4.0, 8.8, 11.2, 11.2, 11.2]), - Ska: makeBands([-2.4, -4.8, -4.0, -1.11022e-15, 4.0, 5.6, 8.8, 9.6, 11.2, 9.6]), - Soft: makeBands([4.8, 1.6, -1.11022e-15, -2.4, -1.11022e-15, 4.0, 8.0, 9.6, 11.2, 12.0]), - SoftRock: makeBands([4.0, 4.0, 2.4, -1.11022e-15, -4.0, -5.6, -3.2, -1.11022e-15, 2.4, 8.8]), - Techno: makeBands([8.0, 5.6, -1.11022e-15, -5.6, -4.8, -1.11022e-15, 8.0, 9.6, 9.6, 8.8]) -}); - -export class FFmpegFilterer { - #ffmpegFilters: Filters[] = []; - #inputArgs: string[] = []; - public constructor(public af: GuildQueueAudioFilters) {} - - /** - * Indicates whether ffmpeg may be skipped - */ - public get skippable() { - return !!this.af.queue.player.options.skipFFmpeg; - } - - #setFilters(filters: Filters[]) { - const { queue } = this.af; - // skip if filters are the same - if (filters.every((f) => this.#ffmpegFilters.includes(f)) && this.#ffmpegFilters.every((f) => filters.includes(f))) return Promise.resolve(false); - const ignoreFilters = this.filters.some((ff) => ff === 'nightcore' || ff === 'vaporwave') && !filters.some((ff) => ff === 'nightcore' || ff === 'vaporwave'); - const seekTime = queue.node.getTimestamp(ignoreFilters)?.current.value || 0; - const prev = this.#ffmpegFilters.slice(); - this.#ffmpegFilters = [...new Set(filters)]; - - return this.af.triggerReplay(seekTime).then((t) => { - queue.emit(GuildQueueEvent.audioFiltersUpdate, queue, prev, this.#ffmpegFilters.slice()); - return t; - }); - } - - /** - * Set input args for FFmpeg - */ - public setInputArgs(args: string[]) { - if (!args.every((arg) => typeof arg === 'string')) throw Exceptions.ERR_INVALID_ARG_TYPE('args', 'Array', 'invalid item(s)'); - this.#inputArgs = args; - } - - /** - * Get input args - */ - public get inputArgs() { - return this.#inputArgs; - } - - /** - * Get encoder args - */ - public get encoderArgs() { - if (!this.filters.length) return []; - - return ['-af', this.toString()]; - } - - /** - * Get final ffmpeg args - */ - public get args() { - return this.inputArgs.concat(this.encoderArgs); - } - - /** - * Create ffmpeg stream - * @param source The stream source - * @param options The stream options - */ - public createStream(source: string | Readable, options: FFmpegStreamOptions) { - if (this.#inputArgs.length) options.encoderArgs = [...this.#inputArgs, ...(options.encoderArgs || [])]; - return createFFmpegStream(source, options); - } - - /** - * Set ffmpeg filters - * @param filters The filters - */ - public setFilters(filters: Filters[] | Record | string[] | boolean) { - let _filters: Filters[] = []; - if (typeof filters === 'boolean') { - _filters = !filters ? [] : (Object.keys(AudioFilters.filters) as Filters[]); - } else if (Array.isArray(filters)) { - _filters = filters as Filters[]; - } else { - _filters = Object.entries(filters) - .filter((res) => res[1] === true) - .map((m) => m[0]) as Filters[]; - } - - return this.#setFilters(_filters); - } - - /** - * Currently active ffmpeg filters - */ - public get filters() { - return this.#ffmpegFilters; - } - - public set filters(filters: Filters[]) { - this.setFilters(filters); - } - - /** - * Toggle given ffmpeg filter(s) - * @param filters The filter(s) - */ - public toggle(filters: Filters[] | Filters) { - if (!Array.isArray(filters)) filters = [filters]; - const fresh: Filters[] = []; - - filters.forEach((f) => { - if (this.filters.includes(f)) return; - fresh.push(f); - }); - - return this.#setFilters(this.#ffmpegFilters.filter((r) => !filters.includes(r)).concat(fresh)); - } - - /** - * Set default filters - * @param ff Filters list - */ - public setDefaults(ff: Filters[]) { - this.#ffmpegFilters = ff; - } - - /** - * Get list of enabled filters - */ - public getFiltersEnabled() { - return this.#ffmpegFilters; - } - - /** - * Get list of disabled filters - */ - public getFiltersDisabled() { - return AudioFilters.names.filter((f) => !this.#ffmpegFilters.includes(f)); - } - - /** - * Check if the given filter is enabled - * @param filter The filter - */ - public isEnabled(filter: T): boolean { - return this.#ffmpegFilters.includes(filter); - } - - /** - * Check if the given filter is disabled - * @param filter The filter - */ - public isDisabled(filter: T): boolean { - return !this.isEnabled(filter); - } - - /** - * Check if the given filter is a valid filter - * @param filter The filter to test - */ - public isValidFilter(filter: string): filter is FiltersName { - return AudioFilters.has(filter as Filters); - } - - /** - * Convert current filters to array - */ - public toArray() { - return this.filters.map((filter) => AudioFilters.get(filter)); - } - - /** - * Convert current filters to JSON object - */ - public toJSON() { - const obj = {} as Record; - - this.filters.forEach((filter) => (obj[filter] = AudioFilters.get(filter))); - - return obj; - } - - /** - * String representation of current filters - */ - public toString() { - return AudioFilters.create(this.filters); - } -} - -export interface GuildQueueAFiltersCache { - equalizer: EqualizerBand[]; - biquad: BiquadFilters | null; - filters: PCMFilters[]; - volume: number; - sampleRate: number; -} - -export class GuildQueueAudioFilters { - public graph = new AFilterGraph(this); - public ffmpeg = new FFmpegFilterer(this); - public equalizerPresets = EqualizerConfigurationPreset; - public _lastFiltersCache: GuildQueueAFiltersCache = { - biquad: null, - equalizer: [], - filters: [], - volume: 100, - sampleRate: -1 - }; - public constructor(public queue: GuildQueue) { - if (typeof this.queue.options.volume === 'number') { - this._lastFiltersCache.volume = this.queue.options.volume; - } - } - - // TODO: enable this in the future - // public get ffmpeg(): FFmpegFilterer | null { - // if (this.queue.player.options.skipFFmpeg) { - // if (this.#ffmpeg) this.#ffmpeg = null; - // return null; - // } - - // if (!this.#ffmpeg) { - // this.#ffmpeg = new FFmpegFilterer(this); - // } - - // return this.#ffmpeg; - // } - - /** - * Volume transformer - */ - public get volume() { - return this.queue.dispatcher?.dsp?.volume || null; - } - - /** - * 15 Band Equalizer - */ - public get equalizer() { - return this.queue.dispatcher?.equalizer || null; - } - - /** - * Digital biquad filters - */ - public get biquad() { - return this.queue.dispatcher?.biquad || null; - } - - /** - * DSP filters - */ - public get filters() { - return this.queue.dispatcher?.filters || null; - } - - /** - * Audio resampler - */ - public get resampler() { - return this.queue.dispatcher?.resampler || null; - } - - /** - * Replay current track in transition mode - * @param seek The duration to seek to - */ - public async triggerReplay(seek = 0) { - if (!this.queue.currentTrack) return false; - const entry = this.queue.node.tasksQueue.acquire(); - try { - await entry.getTask(); - await this.queue.node.play(this.queue.currentTrack, { - queue: false, - seek, - transitionMode: true - }); - this.queue.node.tasksQueue.release(); - return true; - } catch { - this.queue.node.tasksQueue.release(); - return false; - } - } -} - -export class AFilterGraph { - public constructor(public af: GuildQueueAudioFilters) {} - - public get ffmpeg() { - return this.af.ffmpeg?.filters ?? []; - } - - public get equalizer() { - return (this.af.equalizer?.bandMultipliers || []).map((m, i) => ({ - band: i, - gain: m - })) as EqualizerBand[]; - } - - public get biquad() { - return (this.af.biquad?.getFilterName() as Exclude | null) || null; - } - - public get filters() { - return this.af.filters?.filters || []; - } - - public get volume() { - return this.af.volume; - } - - public get resampler() { - return this.af.resampler; - } - - public dump(): FilterGraph { - return { - ffmpeg: this.ffmpeg, - equalizer: this.equalizer, - biquad: this.biquad, - filters: this.filters, - sampleRate: this.resampler?.targetSampleRate || this.resampler?.sampleRate || 48000, - volume: this.volume?.volume ?? 100 - }; - } -} - -export interface FilterGraph { - ffmpeg: Filters[]; - equalizer: EqualizerBand[]; - biquad: Exclude | null; - filters: PCMFilters[]; - volume: number; - sampleRate: number; -} diff --git a/packages/discord-player/src/queue/GuildQueueHistory.ts b/packages/discord-player/src/queue/GuildQueueHistory.ts deleted file mode 100644 index ffba57c1ec..0000000000 --- a/packages/discord-player/src/queue/GuildQueueHistory.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { Queue } from '@discord-player/utils'; -import { Exceptions } from '../errors'; -import { Track } from '../fabric/Track'; -import { GuildQueue, TrackSkipReason } from './GuildQueue'; - -export class GuildQueueHistory { - public tracks = new Queue('LIFO'); - public constructor(public queue: GuildQueue) {} - - /** - * Current track in the queue - */ - public get currentTrack() { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return this.queue.dispatcher?.audioResource?.metadata || ((this.queue as any).__current as Track | null); - } - - /** - * Next track in the queue - */ - public get nextTrack() { - return this.queue.tracks.at(0) || null; - } - - /** - * Previous track in the queue - */ - public get previousTrack() { - return this.tracks.at(0) || null; - } - - /** - * If history is disabled - */ - public get disabled() { - return this.queue.options.disableHistory; - } - - /** - * Gets the size of the queue - */ - public get size() { - return this.tracks.size; - } - - public getSize() { - return this.size; - } - - /** - * If history is empty - */ - public isEmpty() { - return this.tracks.size < 1; - } - - /** - * Add track to track history - * @param track The track to add - */ - public push(track: Track | Track[]) { - if (this.disabled) return false; - this.tracks.add(track); - - this.resize(); - - return true; - } - - /** - * Clear history - */ - public clear() { - this.tracks.clear(); - } - - /** - * Play the next track in the queue - */ - public async next() { - const track = this.nextTrack; - if (!track) { - throw Exceptions.ERR_NO_RESULT('No next track in the queue'); - } - - this.queue.node.skip({ - reason: TrackSkipReason.HistoryNext, - description: 'Skipped by GuildQueueHistory.next()' - }); - } - - /** - * Play the previous track in the queue - */ - public async previous(preserveCurrent = true) { - const track = this.tracks.dispatch(); - if (!track) { - throw Exceptions.ERR_NO_RESULT('No previous track in the queue'); - } - - const current = this.currentTrack; - - await this.queue.node.play(track, { queue: false }); - if (current && preserveCurrent) this.queue.node.insert(current, 0); - } - - /** - * Alias to [GuildQueueHistory].previous() - */ - public back(preserveCurrent = true) { - return this.previous(preserveCurrent); - } - - /** - * Resize history store - */ - public resize() { - if (!Number.isFinite(this.queue.maxHistorySize)) return; - if (this.tracks.store.length < this.queue.maxHistorySize) return; - this.tracks.store.splice(this.queue.maxHistorySize); - } -} diff --git a/packages/discord-player/src/queue/GuildQueuePlayerNode.ts b/packages/discord-player/src/queue/GuildQueuePlayerNode.ts deleted file mode 100644 index 7ae5906d1d..0000000000 --- a/packages/discord-player/src/queue/GuildQueuePlayerNode.ts +++ /dev/null @@ -1,733 +0,0 @@ -import { AudioResource, StreamType } from 'discord-voip'; -import { Readable } from 'stream'; -import { PlayerProgressbarOptions, SearchQueryType } from '../types/types'; -import { QueryResolver } from '../utils/QueryResolver'; -import { Util, VALIDATE_QUEUE_CAP } from '../utils/Util'; -import { Track, TrackResolvable } from '../fabric/Track'; -import { GuildQueue, GuildQueueEvent, TrackSkipReason } from './GuildQueue'; -import { setTimeout as waitFor } from 'timers/promises'; -import { AsyncQueue } from '../utils/AsyncQueue'; -import { Exceptions } from '../errors'; -import { TypeUtil } from '../utils/TypeUtil'; -import { CreateStreamOps } from '../VoiceInterface/StreamDispatcher'; -import { ExtractorStreamable } from '../extractors/BaseExtractor'; -import * as prism from 'prism-media'; -import { OpusDecoder } from '@discord-player/opus'; - -export const FFMPEG_SRATE_REGEX = /asetrate=\d+\*(\d(\.\d)?)/; - -export interface ResourcePlayOptions { - queue?: boolean; - seek?: number; - transitionMode?: boolean; -} - -export interface SkipOptions { - reason: TrackSkipReason; - description: string; -} - -export interface PlayerTimestamp { - current: { - label: string; - value: number; - }; - total: { - label: string; - value: number; - }; - progress: number; -} - -export interface StreamConfig { - dispatcherConfig: CreateStreamOps; - playerConfig: ResourcePlayOptions; -} - -export class GuildQueuePlayerNode { - #progress = 0; - #hasFFmpegOptimization = false; - public tasksQueue = new AsyncQueue(); - public constructor(public queue: GuildQueue) { - this.#hasFFmpegOptimization = /libopus: (yes|true)/.test(this.queue.player.scanDeps()); - } - - /** - * If the player is currently in idle mode - */ - public isIdle() { - return !!this.queue.dispatcher?.isIdle(); - } - - /** - * If the player is currently buffering the track - */ - public isBuffering() { - return !!this.queue.dispatcher?.isBuffering(); - } - - /** - * If the player is currently playing a track - */ - public isPlaying() { - return !!this.queue.dispatcher?.isPlaying(); - } - - /** - * If the player is currently paused - */ - public isPaused() { - return !!this.queue.dispatcher?.isPaused(); - } - - /** - * Reset progress history - */ - public resetProgress() { - this.#progress = 0; - } - - /** - * Set player progress - */ - public setProgress(progress: number) { - this.#progress = progress; - } - - /** - * The stream time for current session - */ - public get streamTime() { - return this.queue.dispatcher?.streamTime ?? 0; - } - - /** - * Current playback duration with history included - */ - public get playbackTime() { - const dur = this.#progress + this.streamTime; - - return dur; - } - - /** - * Get duration multiplier - */ - public getDurationMultiplier() { - const srateFilters = this.queue.filters.ffmpeg.toArray().filter((ff) => FFMPEG_SRATE_REGEX.test(ff)); - const multipliers = srateFilters - .map((m) => { - return parseFloat(FFMPEG_SRATE_REGEX.exec(m)?.[1] as string); - }) - .filter((f) => !isNaN(f)); - - return !multipliers.length ? 1 : multipliers.reduce((accumulator, current) => current + accumulator); - } - - /** - * Estimated progress of the player - */ - public get estimatedPlaybackTime() { - const dur = this.playbackTime; - return Math.round(this.getDurationMultiplier() * dur); - } - - /** - * Estimated total duration of the player - */ - public get estimatedDuration() { - const dur = this.totalDuration; - - return Math.round(dur / this.getDurationMultiplier()); - } - - /** - * Total duration of the current audio track - */ - public get totalDuration() { - const prefersBridgedMetadata = this.queue.options.preferBridgedMetadata; - const track = this.queue.currentTrack; - - if (prefersBridgedMetadata && track?.metadata != null && typeof track.metadata === 'object' && 'bridge' in track.metadata) { - const duration = ( - track as Track<{ - bridge: { - duration: number; - }; - }> - ).metadata?.bridge.duration; - - if (TypeUtil.isNumber(duration)) return duration; - } - - return track?.durationMS ?? 0; - } - - /** - * Get stream progress - * @param ignoreFilters Ignore filters - */ - public getTimestamp(ignoreFilters = false): PlayerTimestamp | null { - if (!this.queue.currentTrack) return null; - - const current = ignoreFilters ? this.playbackTime : this.estimatedPlaybackTime; - const total = ignoreFilters ? this.totalDuration : this.estimatedDuration; - - return { - current: { - label: Util.buildTimeCode(Util.parseMS(current)), - value: current - }, - total: { - label: Util.buildTimeCode(Util.parseMS(total)), - value: total - }, - progress: Math.round((current / total) * 100) - }; - } - - /** - * Create progress bar for current progress - * @param options Progress bar options - */ - public createProgressBar(options?: PlayerProgressbarOptions) { - const timestamp = this.getTimestamp(); - if (!timestamp) return null; - const { indicator = '\u{1F518}', leftChar = '\u25AC', rightChar = '\u25AC', length = 15, timecodes = true, separator = '\u2503' } = options || {}; - if (isNaN(length) || length < 0 || !Number.isFinite(length)) { - throw Exceptions.ERR_OUT_OF_RANGE('[PlayerProgressBarOptions.length]', String(length), '0', 'Finite Number'); - } - const index = Math.round((timestamp.current.value / timestamp.total.value) * length); - if (index >= 1 && index <= length) { - const bar = leftChar.repeat(index - 1).split(''); - bar.push(indicator); - bar.push(rightChar.repeat(length - index)); - if (timecodes) { - return `${timestamp.current.label} ${separator} ${bar.join('')} ${separator} ${timestamp.total.label}`; - } else { - return `${bar.join('')}`; - } - } else { - if (timecodes) { - return `${timestamp.current.label} ${separator} ${indicator}${rightChar.repeat(length - 1)} ${separator} ${timestamp.total.label}`; - } else { - return `${indicator}${rightChar.repeat(length - 1)}`; - } - } - } - - /** - * Seek the player - * @param duration The duration to seek to - */ - public async seek(duration: number) { - if (!this.queue.currentTrack) return false; - if (duration === this.estimatedPlaybackTime) return true; - if (duration > this.totalDuration) { - return this.skip({ - reason: TrackSkipReason.SEEK_OVER_THRESHOLD, - description: Exceptions.ERR_OUT_OF_RANGE('[duration]', String(duration), '0', String(this.totalDuration)).message - }); - } - if (duration < 0) duration = 0; - return await this.queue.filters.triggerReplay(duration); - } - - /** - * Current volume - */ - public get volume() { - return this.queue.dispatcher?.volume ?? 100; - } - - /** - * Set volume - * @param vol Volume amount to set - */ - public setVolume(vol: number) { - if (!this.queue.dispatcher) return false; - const res = this.queue.dispatcher.setVolume(vol); - if (res) this.queue.filters._lastFiltersCache.volume = vol; - return res; - } - - /** - * Set bit rate - * @param rate The bit rate to set - */ - public setBitrate(rate: number | 'auto') { - this.queue.dispatcher?.audioResource?.encoder?.setBitrate(rate === 'auto' ? this.queue.channel?.bitrate ?? 64000 : rate); - } - - /** - * Set paused state - * @param state The state - */ - public setPaused(state: boolean) { - if (state) return this.queue.dispatcher?.pause(true) || false; - return this.queue.dispatcher?.resume() || false; - } - - /** - * Pause the playback - */ - public pause() { - return this.setPaused(true); - } - - /** - * Resume the playback - */ - public resume() { - return this.setPaused(false); - } - - /** - * Skip current track - */ - public skip(options?: SkipOptions) { - if (!this.queue.dispatcher) return false; - const track = this.queue.currentTrack; - if (!track) return false; - this.queue.setTransitioning(false); - this.queue.dispatcher.end(); - const { reason, description } = options || { - reason: TrackSkipReason.Manual, - description: 'The track was skipped manually' - }; - this.queue.emit(GuildQueueEvent.playerSkip, this.queue, track, reason, description); - return true; - } - - /** - * Remove the given track from queue - * @param track The track to remove - * @param emitEvent Whether or not to emit the event @defaultValue true - */ - public remove(track: TrackResolvable, emitEvent = true) { - const foundTrack = this.queue.tracks.find((t, idx) => { - if (track instanceof Track || typeof track === 'string') { - return (typeof track === 'string' ? track : track.id) === t.id; - } - if (typeof track === 'string') return track === t.id; - return idx === track; - }); - if (!foundTrack) return null; - - this.queue.tracks.removeOne((t) => t.id === foundTrack.id); - - if (emitEvent) this.queue.emit(GuildQueueEvent.audioTrackRemove, this.queue, foundTrack); - - return foundTrack; - } - - /** - * Jump to specific track on the queue - * @param track The track to jump to without removing other tracks - */ - public jump(track: TrackResolvable) { - const removed = this.remove(track, false); - if (!removed) return false; - this.queue.tracks.store.unshift(removed); - return this.skip({ - reason: TrackSkipReason.Jump, - description: 'The track was jumped to manually' - }); - } - - /** - * Get track position - * @param track The track - */ - public getTrackPosition(track: TrackResolvable): number { - return this.queue.tracks.toArray().findIndex((t, idx) => { - if (track instanceof Track || typeof track === 'string') { - return (typeof track === 'string' ? track : track.id) === t.id; - } - if (typeof track === 'string') return track === t.id; - return idx === track; - }); - } - - /** - * Skip to the given track, removing others on the way - * @param track The track to skip to - */ - public skipTo(track: TrackResolvable) { - const idx = this.getTrackPosition(track); - if (idx < 0) return false; - const removed = this.remove(idx); - if (!removed) return false; - const toRemove = this.queue.tracks.store.filter((_, i) => i <= idx); - this.queue.tracks.store.splice(0, idx, removed); - this.queue.emit(GuildQueueEvent.audioTracksRemove, this.queue, toRemove); - return this.skip({ - reason: TrackSkipReason.SkipTo, - description: 'The player was skipped to another track manually' - }); - } - - /** - * Insert a track on the given position in queue - * @param track The track to insert - * @param index The position to insert to, defaults to 0. - */ - public insert(track: Track, index = 0) { - if (!(track instanceof Track)) throw Exceptions.ERR_INVALID_ARG_TYPE('track value', 'instance of Track', String(track)); - VALIDATE_QUEUE_CAP(this.queue, track); - this.queue.tracks.store.splice(index, 0, track); - if (!this.queue.options.noEmitInsert) this.queue.emit(GuildQueueEvent.audioTrackAdd, this.queue, track); - } - - /** - * Moves a track in the queue - * @param from The track to move - * @param to The position to move to - */ - public move(from: TrackResolvable, to: number) { - const removed = this.remove(from); - if (!removed) { - throw Exceptions.ERR_NO_RESULT('invalid track to move'); - } - this.insert(removed, to); - } - - /** - * Copy a track in the queue - * @param from The track to clone - * @param to The position to clone at - */ - public copy(from: TrackResolvable, to: number) { - const src = this.queue.tracks.at(this.getTrackPosition(from)); - if (!src) { - throw Exceptions.ERR_NO_RESULT('invalid track to copy'); - } - this.insert(src, to); - } - - /** - * Swap two tracks in the queue - * @param first The first track to swap - * @param second The second track to swap - */ - public swap(first: TrackResolvable, second: TrackResolvable) { - const src = this.getTrackPosition(first); - if (src < 0) throw Exceptions.ERR_NO_RESULT('invalid src track to swap'); - - const dest = this.getTrackPosition(second); - if (dest < 0) throw Exceptions.ERR_NO_RESULT('invalid dest track to swap'); - - const srcT = this.queue.tracks.store[src]; - const destT = this.queue.tracks.store[dest]; - - this.queue.tracks.store[src] = destT; - this.queue.tracks.store[dest] = srcT; - } - - /** - * Stop the playback - * @param force Whether or not to forcefully stop the playback - */ - public stop(force = false) { - this.queue.tracks.clear(); - this.queue.history.clear(); - if (!this.queue.dispatcher) return false; - this.queue.dispatcher.end(); - if (force) { - this.queue.dispatcher.destroy(); - return true; - } - if (this.queue.options.leaveOnStop) { - const tm: NodeJS.Timeout = setTimeout(() => { - if (this.isPlaying() || this.queue.tracks.size) return clearTimeout(tm); - this.queue.dispatcher?.destroy(); - }, this.queue.options.leaveOnStopCooldown).unref(); - } - return true; - } - - /** - * Play raw audio resource - * @param resource The audio resource to play - */ - public async playRaw(resource: AudioResource) { - await this.queue.dispatcher?.playStream(resource as AudioResource); - } - - /** - * Play the given track - * @param res The track to play - * @param options Options for playing the track - */ - public async play(res?: Track | null, options?: ResourcePlayOptions) { - if (!this.queue.dispatcher?.voiceConnection) { - throw Exceptions.ERR_NO_VOICE_CONNECTION(); - } - - if (this.queue.hasDebugger) this.queue.debug(`Received play request from guild ${this.queue.guild.name} (ID: ${this.queue.guild.id})`); - - options = Object.assign( - {}, - { - queue: this.queue.currentTrack != null, - transitionMode: false, - seek: 0 - } as ResourcePlayOptions, - options - )!; - - if (res && options.queue) { - if (this.queue.hasDebugger) this.queue.debug('Requested option requires to queue the track, adding the given track to queue instead...'); - return this.queue.addTrack(res); - } - - const track = res || this.queue.tracks.dispatch(); - if (!track) { - const error = Exceptions.ERR_NO_RESULT('Play request received but track was not provided'); - this.queue.emit(GuildQueueEvent.error, this.queue, error); - return; - } - - if (this.queue.hasDebugger) this.queue.debug('Requested option requires to play the track, initializing...'); - - try { - if (this.queue.hasDebugger) this.queue.debug(`Initiating stream extraction process...`); - const src = track.raw?.source || track.source; - const qt: SearchQueryType = track.queryType || (src === 'spotify' ? 'spotifySong' : src === 'apple_music' ? 'appleMusicSong' : src); - if (this.queue.hasDebugger) this.queue.debug(`Executing onBeforeCreateStream hook (QueryType: ${qt})...`); - - const streamSrc = { - error: null as Error | null, - stream: null as ExtractorStreamable | null - }; - - await this.queue.onBeforeCreateStream?.(track, qt || 'arbitrary', this.queue).then( - (s) => { - if (s) { - streamSrc.stream = s; - } - }, - (e: Error) => (streamSrc.error = e) - ); - - // throw if 'onBeforeCreateStream' panics - if (!streamSrc.stream && streamSrc.error) return this.#throw(track, streamSrc.error); - - // default behavior when 'onBeforeCreateStream' did not panic - if (!streamSrc.stream) { - if (this.queue.hasDebugger) this.queue.debug('Failed to get stream from onBeforeCreateStream!'); - await this.#createGenericStream(track).then( - (r) => { - if (r?.result) { - streamSrc.stream = r.result; - return; - } - - if (r?.error) { - streamSrc.error = r.error; - return; - } - - streamSrc.stream = streamSrc.error = null; - }, - (e: Error) => (streamSrc.error = e) - ); - } - - if (!streamSrc.stream) return this.#throw(track, streamSrc.error); - - if (typeof options.seek === 'number' && options.seek >= 0) { - this.#progress = options.seek; - } else { - this.#progress = 0; - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const cookies = track.raw?.source === 'youtube' ? (this.queue.player.options.ytdlOptions?.requestOptions)?.headers?.cookie : undefined; - - const trackStreamConfig: StreamConfig = { - dispatcherConfig: { - disableBiquad: this.queue.options.disableBiquad, - disableEqualizer: this.queue.options.disableEqualizer, - disableVolume: this.queue.options.disableVolume, - disableFilters: this.queue.options.disableFilterer, - disableResampler: this.queue.options.disableResampler, - sampleRate: typeof this.queue.options.resampler === 'number' && this.queue.options.resampler > 0 ? this.queue.options.resampler : undefined, - biquadFilter: this.queue.filters._lastFiltersCache.biquad || undefined, - eq: this.queue.filters._lastFiltersCache.equalizer, - defaultFilters: this.queue.filters._lastFiltersCache.filters, - volume: this.queue.filters._lastFiltersCache.volume, - data: track, - type: StreamType.Raw, - skipFFmpeg: this.queue.player.options.skipFFmpeg - }, - playerConfig: options - }; - - let resolver: () => void = Util.noop; - const donePromise = new Promise((resolve) => (resolver = resolve)); - - const success = this.queue.emit(GuildQueueEvent.willPlayTrack, this.queue, track, trackStreamConfig, resolver!); - - // prevent dangling promise - if (!success) resolver(); - - if (this.queue.hasDebugger) this.queue.debug('Waiting for willPlayTrack event to resolve...'); - - await donePromise; - - // prettier-ignore - const daspDisabled = [ - trackStreamConfig.dispatcherConfig.disableBiquad, - trackStreamConfig.dispatcherConfig.disableEqualizer, - trackStreamConfig.dispatcherConfig.disableFilters, - trackStreamConfig.dispatcherConfig.disableResampler, - trackStreamConfig.dispatcherConfig.disableVolume - ].every((e) => !!e === true); - - const needsFilters = !!trackStreamConfig.playerConfig.seek || !!this.queue.filters.ffmpeg.args.length; - const shouldSkipFFmpeg = !!trackStreamConfig.dispatcherConfig.skipFFmpeg && !needsFilters; - - let finalStream: Readable; - - const demuxable = (fmt: string) => [StreamType.Opus, StreamType.WebmOpus, StreamType.OggOpus].includes(fmt as StreamType); - - // skip ffmpeg when possible - if (shouldSkipFFmpeg && !(streamSrc.stream instanceof Readable) && typeof streamSrc.stream !== 'string' && demuxable(streamSrc.stream.$fmt)) { - const { $fmt, stream } = streamSrc.stream; - const shouldPCM = !daspDisabled; - - if (this.queue.hasDebugger) this.queue.debug(`skipFFmpeg is set to true and stream is demuxable, creating stream with type ${shouldPCM ? 'pcm' : 'opus'}`); - - // prettier-ignore - const opusStream = $fmt === StreamType.Opus ? - stream : - $fmt === StreamType.OggOpus ? - stream.pipe(new prism.opus.OggDemuxer()) : - stream.pipe(new prism.opus.WebmDemuxer()); - - if (shouldPCM) { - // if we have any filters enabled, we need to decode the opus stream to pcm - finalStream = opusStream.pipe( - new OpusDecoder({ - channels: 2, - frameSize: 960, - rate: 48000 - }) - ); - trackStreamConfig.dispatcherConfig.type = StreamType.Raw; - } else { - finalStream = opusStream; - trackStreamConfig.dispatcherConfig.type = StreamType.Opus; - } - } else { - // const opus = daspDisabled && this.#hasFFmpegOptimization; - // if (opus && this.queue.hasDebugger) this.queue.debug('Disabling PCM output since all filters are disabled and opus encoding is supported...'); - - finalStream = this.#createFFmpegStream( - streamSrc.stream instanceof Readable || typeof streamSrc.stream === 'string' ? streamSrc.stream : streamSrc.stream.stream, - track, - options.seek ?? 0, - cookies - // opus - ); - trackStreamConfig.dispatcherConfig.type = StreamType.Raw; - // FIXME: OggOpus results in static noise - // trackStreamConfig.dispatcherConfig.type = opus ? StreamType.OggOpus : StreamType.Raw; - } - - if (options.transitionMode) { - if (this.queue.hasDebugger) this.queue.debug(`Transition mode detected, player will wait for buffering timeout to expire (Timeout: ${this.queue.options.bufferingTimeout}ms)`); - await waitFor(this.queue.options.bufferingTimeout); - if (this.queue.hasDebugger) this.queue.debug('Buffering timeout has expired!'); - } - - if (this.queue.hasDebugger) this.queue.debug(`Preparing final stream config: ${JSON.stringify(trackStreamConfig, null, 2)}`); - - const dispatcher = this.queue.dispatcher; - - if (!dispatcher) { - if (this.queue.hasDebugger) { - this.queue.debug('Dispatcher is not available, this is most likely due to the queue being deleted in the middle of operation. Cancelling the stream...'); - } - - finalStream.destroy(); - } else { - const resource = await dispatcher.createStream(finalStream, trackStreamConfig.dispatcherConfig); - - this.queue.setTransitioning(!!options.transitionMode); - - await this.#performPlay(resource); - } - } catch (e) { - if (this.queue.hasDebugger) this.queue.debug(`Failed to initialize audio player: ${e}`); - throw e; - } - } - - #throw(track: Track, error?: Error | null) { - // prettier-ignore - const streamDefinitelyFailedMyDearT_TPleaseTrustMeItsNotMyFault = ( - Exceptions.ERR_NO_RESULT(`Could not extract stream for this track${error ? `\n\n${error.stack || error}` : ''}`) - ); - - this.queue.emit(GuildQueueEvent.playerSkip, this.queue, track, TrackSkipReason.NoStream, streamDefinitelyFailedMyDearT_TPleaseTrustMeItsNotMyFault.message); - this.queue.emit(GuildQueueEvent.playerError, this.queue, streamDefinitelyFailedMyDearT_TPleaseTrustMeItsNotMyFault, track); - const nextTrack = this.queue.tracks.dispatch(); - if (nextTrack) this.play(nextTrack, { queue: false }); - return; - } - - async #performPlay(resource: AudioResource) { - if (!this.queue.dispatcher) { - if (this.queue.hasDebugger) { - this.queue.debug('Dispatcher is not available, this is most likely due to the queue being deleted in the middle of operation. Cancelling the stream...'); - } - } else { - if (this.queue.hasDebugger) this.queue.debug('Initializing audio player...'); - await this.queue.dispatcher.playStream(resource); - if (this.queue.hasDebugger) this.queue.debug('Dispatching audio...'); - } - } - - async #createGenericStream(track: Track) { - if (this.queue.hasDebugger) this.queue.debug(`Attempting to extract stream for Track { title: ${track.title}, url: ${track.url} } using registered extractors`); - const streamInfo = await this.queue.player.extractors.run(async (extractor) => { - if (this.queue.player.options.blockStreamFrom?.some((ext) => ext === extractor.identifier)) return false; - const canStream = await extractor.validate(track.url, track.queryType || QueryResolver.resolve(track.url).type); - if (!canStream) return false; - return await extractor.stream(track); - }, false); - if (!streamInfo || !streamInfo.result) { - if (this.queue.hasDebugger) this.queue.debug(`Failed to extract stream for Track { title: ${track.title}, url: ${track.url} } using registered extractors`); - return streamInfo || null; - } - - if (this.queue.hasDebugger) - this.queue.debug(`Stream extraction was successful for Track { title: ${track.title}, url: ${track.url} } (Extractor: ${streamInfo.extractor?.identifier || 'N/A'})`); - - return streamInfo; - } - - #createFFmpegStream(stream: Readable | string, track: Track, seek = 0, cookies?: string, opus?: boolean) { - const ffmpegStream = this.queue.filters.ffmpeg - .createStream(stream, { - encoderArgs: this.queue.filters.ffmpeg.args, - seek: seek / 1000, - fmt: opus ? 'opus' : 's16le', - cookies, - useLegacyFFmpeg: !!this.queue.player.options.useLegacyFFmpeg - }) - .on('error', (err) => { - const m = `${err}`.toLowerCase(); - - if (this.queue.hasDebugger) this.queue.debug(`Stream closed due to an error from FFmpeg stream: ${err.stack || err.message || err}`); - - if (m.includes('premature close') || m.includes('epipe')) return; - - this.queue.emit(GuildQueueEvent.playerError, this.queue, err, track); - }); - - return ffmpegStream; - } -} diff --git a/packages/discord-player/src/queue/GuildQueueStatistics.ts b/packages/discord-player/src/queue/GuildQueueStatistics.ts deleted file mode 100644 index 3a4b37b5f3..0000000000 --- a/packages/discord-player/src/queue/GuildQueueStatistics.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { GuildQueue } from './GuildQueue'; - -export interface GuildQueueStatisticsMetadata { - latency: { - eventLoop: number; - voiceConnection: number; - }; - status: { - buffering: boolean; - playing: boolean; - paused: boolean; - idle: boolean; - }; - tracksCount: number; - historySize: number; - extractors: number; - listeners: number; - memoryUsage: NodeJS.MemoryUsage; - versions: { - node: string; - player: string; - }; -} - -export class GuildQueueStatistics { - public constructor(public queue: GuildQueue) {} - - /** - * Generate statistics of this queue - */ - public generate() { - return { - latency: { - eventLoop: this.queue.player.eventLoopLag, - voiceConnection: this.queue.ping - }, - status: { - buffering: this.queue.node.isBuffering(), - playing: this.queue.node.isPlaying(), - paused: this.queue.node.isPaused(), - idle: this.queue.node.isIdle() - }, - tracksCount: this.queue.tracks.size, - historySize: this.queue.history.tracks.size, - extractors: this.queue.player.extractors.size, - listeners: this.queue.guild.members.me?.voice.channel?.members.filter((m) => !m.user.bot).size || 0, - memoryUsage: process.memoryUsage(), - versions: { - node: process.version, - player: '[VI]{{inject}}[/VI]' - } - } as GuildQueueStatisticsMetadata; - } -} diff --git a/packages/discord-player/src/queue/SyncedLyricsProvider.ts b/packages/discord-player/src/queue/SyncedLyricsProvider.ts deleted file mode 100644 index 0214bd3caf..0000000000 --- a/packages/discord-player/src/queue/SyncedLyricsProvider.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { Exceptions } from '../errors'; -import { LrcGetResult, LrcSearchResult } from '../lrclib/LrcLib'; -import type { GuildQueue } from './GuildQueue'; - -export type LyricsData = Map; -export type Unsubscribe = () => void; -export type LyricsCallback = (lyrics: string, timestamp: number) => unknown; -export type LyricsAt = { timestamp: number; line: string }; - -const timestampPattern = /\[(\d{2}):(\d{2})\.(\d{2})\]/; - -export class SyncedLyricsProvider { - #loop: NodeJS.Timer | null = null; - #callback: LyricsCallback | null = null; - #onUnsubscribe: Unsubscribe | null = null; - - public interval = 100; - public readonly lyrics: LyricsData = new Map(); - - public constructor(public readonly queue: GuildQueue, public readonly raw?: LrcGetResult | LrcSearchResult) { - if (raw?.syncedLyrics) this.load(raw?.syncedLyrics); - } - - public isSubscribed() { - return this.#callback !== null; - } - - public load(lyrics: string) { - if (!lyrics) throw Exceptions.ERR_NOT_EXISTING('syncedLyrics'); - - this.lyrics.clear(); - this.unsubscribe(); - - const lines = lyrics.split('\n'); - - for (const line of lines) { - const match = line.match(timestampPattern); - - if (match) { - const [, minutes, seconds, milliseconds] = match; - const timestamp = parseInt(minutes) * 60 * 1000 + parseInt(seconds) * 1000 + parseInt(milliseconds); - - this.lyrics.set(timestamp, line.replace(timestampPattern, '').trim()); - } - } - } - - /** - * Returns the lyrics at a specific time or at the closest time (±2 seconds) - * @param time The time in milliseconds - */ - public at(time: number): LyricsAt | null { - const lowestTime = this.lyrics.keys().next().value; - if (lowestTime == null || time < lowestTime) return null; - if (this.lyrics.has(time)) return { line: this.lyrics.get(time) as string, timestamp: time }; - - const keys = Array.from(this.lyrics.keys()); - - const closest = keys.reduce((a, b) => (Math.abs(b - time) < Math.abs(a - time) ? b : a)); - - if (closest > time) return null; - - if (Math.abs(closest - time) > 2000) return null; - - const line = this.lyrics.get(closest); - - if (!line) return null; - - return { timestamp: closest, line }; - } - - /** - * Callback for the lyrics change. - * @param callback The callback function - */ - public onChange(callback: LyricsCallback) { - this.#callback = callback; - } - - /** - * Callback to detect when the provider is unsubscribed. - * @param callback The callback function - */ - public onUnsubscribe(callback: Unsubscribe) { - this.#onUnsubscribe = callback; - } - - /** - * Unsubscribes from the queue. - */ - public unsubscribe() { - if (this.#loop) clearInterval(this.#loop); - if (this.#onUnsubscribe) this.#onUnsubscribe(); - - this.#callback = null; - this.#onUnsubscribe = null; - this.#loop = null; - } - - /** - * Subscribes to the queue to monitor the current time. - * @returns The unsubscribe function - */ - public subscribe(): Unsubscribe { - if (this.#loop) return () => this.unsubscribe(); - - this.#createLoop(); - - return () => this.unsubscribe(); - } - - /** - * Pauses the lyrics provider. - */ - public pause() { - const hasLoop = this.#loop !== null; - - if (hasLoop) { - clearInterval(this.#loop as NodeJS.Timer); - this.#loop = null; - } - - return hasLoop; - } - - /** - * Resumes the lyrics provider. - */ - public resume() { - const hasLoop = this.#loop !== null; - - if (!hasLoop) this.#createLoop(); - - return !hasLoop; - } - - #createLoop() { - if (!this.#callback) return; - if (this.#loop) clearInterval(this.#loop); - - let lastValue: LyricsAt | null = null; - - this.#loop = setInterval(() => { - if (this.queue.deleted) return this.unsubscribe(); - - if (!this.#callback || !this.queue.isPlaying()) return; - - const time = this.queue.node.getTimestamp(); - if (!time) return; - - const lyrics = this.at(time.current.value); - - if (!lyrics) return; - - if (lastValue !== null && lyrics.line === lastValue.line && lyrics.timestamp === lastValue.timestamp) return; - - lastValue = lyrics; - - this.#callback(lyrics.line, lyrics.timestamp); - }, this.interval).unref(); - } -} diff --git a/packages/discord-player/src/queue/VoiceReceiverNode.ts b/packages/discord-player/src/queue/VoiceReceiverNode.ts deleted file mode 100644 index cf79d847a9..0000000000 --- a/packages/discord-player/src/queue/VoiceReceiverNode.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { UserResolvable } from 'discord.js'; -import { PassThrough, type Readable } from 'stream'; -import { EndBehaviorType } from 'discord-voip'; -import * as prism from 'prism-media'; -import { StreamDispatcher } from '../VoiceInterface/StreamDispatcher'; -import { Track } from '../fabric/Track'; -import { RawTrackData } from '../types/types'; -import { Exceptions } from '../errors'; - -export interface VoiceReceiverOptions { - mode?: 'opus' | 'pcm'; - end?: EndBehaviorType; - silenceDuration?: number; - crc?: boolean; -} - -export type RawTrackInit = Partial>; - -export class VoiceReceiverNode { - public constructor(public dispatcher: StreamDispatcher) {} - - public createRawTrack(stream: Readable, data: RawTrackInit = {}) { - data.title ??= `Recording ${Date.now()}`; - - return new Track(this.dispatcher.queue.player, { - author: 'Discord', - description: data.title, - title: data.title, - duration: data.duration || '0:00', - views: 0, - requestedBy: data.requestedBy, - thumbnail: data.thumbnail || 'https://cdn.discordapp.com/embed/avatars/0.png', - url: data.url || 'https://discord.com', - source: 'arbitrary', - raw: { - engine: stream, - source: 'arbitrary' - } - }); - } - - /** - * Merge multiple streams together - * @param streams The array of streams to merge - */ - public mergeRecordings(streams: Readable[]) { - // TODO - void streams; - throw Exceptions.ERR_NOT_IMPLEMENTED(`${this.constructor.name}.mergeRecordings()`); - } - - /** - * Record a user in voice channel - * @param user The user to record - * @param options Recording options - */ - public recordUser( - user: UserResolvable, - options: VoiceReceiverOptions = { - end: EndBehaviorType.AfterSilence, - mode: 'pcm', - silenceDuration: 1000 - } - ) { - const _user = this.dispatcher.queue.player.client.users.resolveId(user); - - const passThrough = new PassThrough(); - const receiver = this.dispatcher.voiceConnection.receiver; - - if (!receiver) throw Exceptions.ERR_NO_RECEIVER(); - - receiver.speaking.on('start', (userId) => { - if (userId === _user) { - const receiveStream = receiver.subscribe(_user, { - end: { - behavior: options.end || EndBehaviorType.AfterSilence, - duration: options.silenceDuration ?? 1000 - } - }); - - setImmediate(async () => { - if (options.mode === 'pcm') { - const pcm = receiveStream.pipe( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - new (prism.opus || (prism).default.opus).Decoder({ - channels: 2, - frameSize: 960, - rate: 48000 - }) - ); - return pcm.pipe(passThrough); - } else { - return receiveStream.pipe(passThrough); - } - }).unref(); - } - }); - - return passThrough as Readable; - } -} diff --git a/packages/discord-player/src/queue/index.ts b/packages/discord-player/src/queue/index.ts deleted file mode 100644 index 1dee13e03c..0000000000 --- a/packages/discord-player/src/queue/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from './GuildNodeManager'; -export * from './GuildQueue'; -export * from './GuildQueueAudioFilters'; -export * from './GuildQueueHistory'; -export * from './GuildQueuePlayerNode'; -export * from './VoiceReceiverNode'; -export * from './GuildQueueStatistics'; diff --git a/packages/discord-player/src/types/types.ts b/packages/discord-player/src/types/types.ts deleted file mode 100644 index f66e5df006..0000000000 --- a/packages/discord-player/src/types/types.ts +++ /dev/null @@ -1,534 +0,0 @@ -import type { User, UserResolvable, VoiceState } from 'discord.js'; -import type { GuildQueue } from '../queue'; -import type { Track } from '../fabric/Track'; -import type { Playlist } from '../fabric/Playlist'; -import type { downloadOptions } from 'ytdl-core'; -import type { QueryCacheProvider } from '../utils/QueryCache'; -import type { IPRotationConfig } from '../utils/IPRotator'; - -// @ts-ignore -import type { BridgeProvider } from '@discord-player/extractor'; - -export type FiltersName = keyof QueueFilters; - -export interface PlayerSearchResult { - playlist: Playlist | null; - tracks: Track[]; -} - -/** - * Represents FFmpeg filters - */ -export interface QueueFilters { - bassboost_low?: boolean; - bassboost?: boolean; - bassboost_high?: boolean; - '8D'?: boolean; - vaporwave?: boolean; - nightcore?: boolean; - phaser?: boolean; - tremolo?: boolean; - vibrato?: boolean; - reverse?: boolean; - treble?: boolean; - normalizer?: boolean; - normalizer2?: boolean; - surrounding?: boolean; - pulsator?: boolean; - subboost?: boolean; - karaoke?: boolean; - flanger?: boolean; - gate?: boolean; - haas?: boolean; - mcompand?: boolean; - mono?: boolean; - mstlr?: boolean; - mstrr?: boolean; - compressor?: boolean; - expander?: boolean; - softlimiter?: boolean; - chorus?: boolean; - chorus2d?: boolean; - chorus3d?: boolean; - fadein?: boolean; - dim?: boolean; - earrape?: boolean; - lofi?: boolean; - silenceremove?: boolean; -} - -/** - * The track source: - * - soundcloud - * - youtube - * - spotify - * - apple_music - * - arbitrary - */ -export type TrackSource = 'soundcloud' | 'youtube' | 'spotify' | 'apple_music' | 'arbitrary'; - -export interface RawTrackData { - /** - * The title - */ - title: string; - /** - * The description - */ - description: string; - /** - * The author - */ - author: string; - /** - * The url - */ - url: string; - /** - * The thumbnail - */ - thumbnail: string; - /** - * The duration - */ - duration: string; - /** - * The duration in ms - */ - views: number; - /** - * The user who requested this track - */ - requestedBy?: User | null; - /** - * The playlist - */ - playlist?: Playlist; - /** - * The source - */ - source?: TrackSource; - /** - * The engine - */ - engine?: any; // eslint-disable-line @typescript-eslint/no-explicit-any - /** - * If this track is live - */ - live?: boolean; - /** - * The raw data - */ - raw?: any; // eslint-disable-line @typescript-eslint/no-explicit-any - /** - * The query type - */ - queryType?: SearchQueryType; - /** - * The seralised title - */ - cleanTitle?: string; -} - -export interface TimeData { - /** - * Time in days - */ - days: number; - /** - * Time in hours - */ - hours: number; - /** - * Time in minutes - */ - minutes: number; - /** - * Time in seconds - */ - seconds: number; -} - -export interface PlayerProgressbarOptions { - /** - * If it should render time codes - */ - timecodes?: boolean; - /** - * If it should create progress bar for the whole queue - */ - length?: number; - /** - * The bar length - */ - leftChar?: string; - /** - * The elapsed time track - */ - rightChar?: string; - /** - * The remaining time track - */ - separator?: string; - /** - * The separation between timestamp and line - */ - indicator?: string; - /** - * The indicator - */ - queue?: boolean; -} - -/** - * The search query type - * This can be one of: - * - AUTO - * - YOUTUBE - * - YOUTUBE_PLAYLIST - * - SOUNDCLOUD_TRACK - * - SOUNDCLOUD_PLAYLIST - * - SOUNDCLOUD - * - SPOTIFY_SONG - * - SPOTIFY_ALBUM - * - SPOTIFY_PLAYLIST - * - SPOTIFY_SEARCH - * - FACEBOOK - * - VIMEO - * - ARBITRARY - * - REVERBNATION - * - YOUTUBE_SEARCH - * - YOUTUBE_VIDEO - * - SOUNDCLOUD_SEARCH - * - APPLE_MUSIC_SONG - * - APPLE_MUSIC_ALBUM - * - APPLE_MUSIC_PLAYLIST - * - APPLE_MUSIC_SEARCH - * - FILE - * - AUTO_SEARCH - * @typedef {string} QueryType - */ -export const QueryType = { - AUTO: 'auto', - YOUTUBE: 'youtube', - YOUTUBE_PLAYLIST: 'youtubePlaylist', - SOUNDCLOUD_TRACK: 'soundcloudTrack', - SOUNDCLOUD_PLAYLIST: 'soundcloudPlaylist', - SOUNDCLOUD: 'soundcloud', - SPOTIFY_SONG: 'spotifySong', - SPOTIFY_ALBUM: 'spotifyAlbum', - SPOTIFY_PLAYLIST: 'spotifyPlaylist', - SPOTIFY_SEARCH: 'spotifySearch', - FACEBOOK: 'facebook', - VIMEO: 'vimeo', - ARBITRARY: 'arbitrary', - REVERBNATION: 'reverbnation', - YOUTUBE_SEARCH: 'youtubeSearch', - YOUTUBE_VIDEO: 'youtubeVideo', - SOUNDCLOUD_SEARCH: 'soundcloudSearch', - APPLE_MUSIC_SONG: 'appleMusicSong', - APPLE_MUSIC_ALBUM: 'appleMusicAlbum', - APPLE_MUSIC_PLAYLIST: 'appleMusicPlaylist', - APPLE_MUSIC_SEARCH: 'appleMusicSearch', - FILE: 'file', - AUTO_SEARCH: 'autoSearch' -} as const; - -export type SearchQueryType = keyof typeof QueryType | (typeof QueryType)[keyof typeof QueryType]; - -/* eslint-disable @typescript-eslint/no-explicit-any */ -export interface PlayerEvents { - debug: (message: string) => any; - error: (error: Error) => any; - voiceStateUpdate: (queue: GuildQueue, oldState: VoiceState, newState: VoiceState) => any; -} - -export const PlayerEvent = { - debug: 'debug', - Debug: 'debug', - error: 'error', - Error: 'error', - voiceStateUpdate: 'voiceStateUpdate', - VoiceStateUpdate: 'voiceStateUpdate' -} as const; -export type PlayerEvent = (typeof PlayerEvent)[keyof typeof PlayerEvent]; - -/* eslint-enable @typescript-eslint/no-explicit-any */ - -export interface PlayOptions { - /** - * If this play was triggered for filters update - */ - filtersUpdate?: boolean; - /** - * FFmpeg args passed to encoder - */ - encoderArgs?: string[]; - /** - * Time to seek to before playing - */ - seek?: number; - /** - * If it should start playing the provided track immediately - */ - immediate?: boolean; -} - -export type QueryExtractorSearch = `ext:${string}`; - -export interface SearchOptions { - /** - * The user who requested this search - */ - requestedBy?: UserResolvable; - /** - * The query search engine, can be extractor name to target specific one (custom) - */ - searchEngine?: SearchQueryType | QueryExtractorSearch; - /** - * List of the extractors to block - */ - blockExtractors?: string[]; - /** - * If it should ignore query cache lookup - */ - ignoreCache?: boolean; - /** - * Fallback search engine to use - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - requestOptions?: any; - /** - * Fallback search engine to use - */ - fallbackSearchEngine?: (typeof QueryType)[keyof typeof QueryType]; -} - -/** - * The queue repeat mode. This can be one of: - * - OFF - * - TRACK - * - QUEUE - * - AUTOPLAY - */ -export enum QueueRepeatMode { - /** - * Disable repeat mode. - */ - OFF = 0, - /** - * Repeat the current track. - */ - TRACK = 1, - /** - * Repeat the entire queue. - */ - QUEUE = 2, - /** - * When last track ends, play similar tracks in the future if queue is empty. - */ - AUTOPLAY = 3 -} - -export interface PlaylistInitData { - /** - * The tracks of this playlist - */ - tracks: Track[]; - /** - * The playlist title - */ - title: string; - /** - * The description - */ - description: string; - /** - * The thumbnail - */ - thumbnail: string; - /** - * The playlist type: `album` | `playlist` - */ - type: 'album' | 'playlist'; - /** - * The playlist source - */ - source: TrackSource; - /** - * The playlist author - */ - author: { - /** - * The author name - */ - name: string; - /** - * The author url - */ - url: string; - }; - /** - * The playlist id - */ - id: string; - /** - * The playlist url - */ - url: string; - /** - * The raw playlist data - */ - rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any -} - -export interface TrackJSON { - /** - * The track id - */ - id: string; - /** - * The track title - */ - title: string; - /** - * The track description - */ - description: string; - /** - * The track author - */ - author: string; - /** - * The track url - */ - url: string; - /** - * The track thumbnail - */ - thumbnail: string; - /** - * The track duration - */ - duration: string; - /** - * The track duration in ms - */ - durationMS: number; - /** - * The track views - */ - views: number; - /** - * The user id who requested this track - */ - requestedBy: string; - /** - * The playlist info (if any) - */ - playlist?: PlaylistJSON; -} - -export interface PlaylistJSON { - /** - * The playlist id - */ - id: string; - /** - * The playlist url - */ - url: string; - /** - * The playlist title - */ - title: string; - /** - * The playlist description - */ - description: string; - /** - * The thumbnail - */ - thumbnail: string; - /** - * The playlist type: `album` | `playlist` - */ - type: 'album' | 'playlist'; - /** - * The track source - */ - source: TrackSource; - /** - * The playlist author - */ - author: { - /** - * The author name - */ - name: string; - /** - * The author url - */ - url: string; - }; - /** - * The tracks data (if any) - */ - tracks: TrackJSON[]; -} - -export interface PlayerInitOptions { - /** - * The options passed to `ytdl-core`. - */ - ytdlOptions?: downloadOptions; - /** - * The voice connection timeout - */ - connectionTimeout?: number; - /** - * Time in ms to re-monitor event loop lag - */ - lagMonitor?: number; - /** - * Prevent voice state handler from being overridden - */ - lockVoiceStateHandler?: boolean; - /** - * List of extractors to disable querying metadata from - */ - blockExtractors?: string[]; - /** - * List of extractors to disable streaming from - */ - blockStreamFrom?: string[]; - /** - * Query cache provider - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - queryCache?: QueryCacheProvider | null; - /** - * Ignore player instance - */ - ignoreInstance?: boolean; - /** - * Use legacy version of ffmpeg - */ - useLegacyFFmpeg?: boolean; - /** - * Set bridge provider - */ - bridgeProvider?: BridgeProvider; - /** - * IP rotator config - */ - ipconfig?: IPRotationConfig; - /** - * Skip ffmpeg process when possible - */ - skipFFmpeg?: boolean; - /** - * The probe timeout in milliseconds. Defaults to 5000. - */ - probeTimeout?: number; -} diff --git a/packages/discord-player/src/utils/AsyncQueue.ts b/packages/discord-player/src/utils/AsyncQueue.ts deleted file mode 100644 index eab1e5f52c..0000000000 --- a/packages/discord-player/src/utils/AsyncQueue.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { SnowflakeUtil } from 'discord.js'; - -export interface AsyncQueueAcquisitionOptions { - /** - * AbortSignal to cancel this entry - */ - signal?: AbortSignal; -} - -export type AsyncQueueExceptionHandler = (exception: Error) => void; - -export class AsyncQueue { - /** - * The queued entries - */ - public entries: Array = []; - - public exceptionHandler?: AsyncQueueExceptionHandler; - - /** - * Clear entries queue - * @param consume Whether or not to consume all entries before clearing - */ - public clear(consume = false) { - if (consume) { - this.entries.forEach((entry) => entry.consume()); - } - - this.entries = []; - } - - /** - * The total number of entries in this queue. Returns `0` if no entries are available. - */ - public get size() { - return this.entries.length; - } - - /** - * Acquire an entry. - * - * @example // lock the queue - * const entry = asyncQueue.acquire(); - * // wait until previous task is completed - * await entry.getTask(); - * // do something expensive - * await performSomethingExpensive(); - * // make sure to release the lock once done - * asyncQueue.release(); - * - */ - public acquire(options?: AsyncQueueAcquisitionOptions) { - const entry = new AsyncQueueEntry(this, options); - - if (this.exceptionHandler) entry.getTask().catch(this.exceptionHandler); - - if (this.entries.length === 0) { - this.entries.push(entry); - entry.consume(); - return entry; - } - - this.entries.push(entry); - return entry; - } - - /** - * Release the current acquisition and move to next entry. - */ - public release(): void { - if (!this.entries.length) return; - - this.entries.shift(); - this.entries[0]?.consume(); - } - - /** - * Cancel all entries - */ - public cancelAll() { - this.entries.forEach((entry) => entry.cancel()); - } - - /** - * Remove the given entry from the queue - * @param entry The entry to remove - */ - public removeEntry(entry: AsyncQueueEntry) { - const entryIdx = this.entries.indexOf(entry); - - if (entryIdx !== -1) { - this.entries.splice(entryIdx, 1); - return true; - } - - return false; - } -} - -export class AsyncQueueEntry { - public readonly id = SnowflakeUtil.generate().toString(); - private readonly promise: Promise; - public signal: AbortSignal | null = null; - public onAbort: (() => void) | null = null; - private resolve!: () => void; - private reject!: (err: Error) => void; - - public constructor(public queue: AsyncQueue, public options?: AsyncQueueAcquisitionOptions) { - this.promise = new Promise((resolve, reject) => { - this.resolve = resolve; - this.reject = reject; - }); - - if (this.options?.signal) { - this.setAbortSignal(this.options.signal); - } - } - - public setAbortSignal(signal: AbortSignal) { - if (signal.aborted) return; - this.signal = signal; - this.onAbort = () => { - this.queue.removeEntry(this); - this.cancel(); - }; - - this.signal.addEventListener('abort', this.onAbort); - } - - public consume() { - this.cleanup(); - this.resolve(); - } - - public release() { - this.consume(); - this.queue.release(); - } - - public cancel() { - this.cleanup(); - this.reject(new Error('Cancelled')); - } - - public cleanup() { - if (this.onAbort) this.signal?.removeEventListener('abort', this.onAbort); - this.signal = null; - this.onAbort = null; - } - - public getTask() { - return this.promise; - } -} diff --git a/packages/discord-player/src/utils/AudioFilters.ts b/packages/discord-player/src/utils/AudioFilters.ts deleted file mode 100644 index 489c127a58..0000000000 --- a/packages/discord-player/src/utils/AudioFilters.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { FiltersName } from '../types/types'; - -const bass = (g: number) => `bass=g=${g}:f=110:w=0.3`; - -export class AudioFilters { - public constructor() { - return AudioFilters; - } - - public static filters: Record = { - bassboost_low: bass(15), - bassboost: bass(20), - bassboost_high: bass(30), - '8D': 'apulsator=hz=0.09', - vaporwave: 'aresample=48000,asetrate=48000*0.8', - nightcore: 'aresample=48000,asetrate=48000*1.25', - lofi: 'aresample=48000,asetrate=48000*0.9,extrastereo=m=2.5:c=disabled', - phaser: 'aphaser=in_gain=0.4', - tremolo: 'tremolo', - vibrato: 'vibrato=f=6.5', - reverse: 'areverse', - treble: 'treble=g=5', - normalizer2: 'dynaudnorm=g=101', - normalizer: 'acompressor', - surrounding: 'surround', - pulsator: 'apulsator=hz=1', - subboost: 'asubboost', - karaoke: 'stereotools=mlev=0.03', - flanger: 'flanger', - gate: 'agate', - haas: 'haas', - mcompand: 'mcompand', - mono: 'pan=mono|c0=.5*c0+.5*c1', - mstlr: 'stereotools=mode=ms>lr', - mstrr: 'stereotools=mode=ms>rr', - compressor: 'compand=points=-80/-105|-62/-80|-15.4/-15.4|0/-12|20/-7.6', - expander: 'compand=attacks=0:points=-80/-169|-54/-80|-49.5/-64.6|-41.1/-41.1|-25.8/-15|-10.8/-4.5|0/0|20/8.3', - softlimiter: 'compand=attacks=0:points=-80/-80|-12.4/-12.4|-6/-8|0/-6.8|20/-2.8', - chorus: 'chorus=0.7:0.9:55:0.4:0.25:2', - chorus2d: 'chorus=0.6:0.9:50|60:0.4|0.32:0.25|0.4:2|1.3', - chorus3d: 'chorus=0.5:0.9:50|60|40:0.4|0.32|0.3:0.25|0.4|0.3:2|2.3|1.3', - fadein: 'afade=t=in:ss=0:d=10', - dim: `afftfilt="'real=re * (1-clip((b/nb)*b,0,1))':imag='im * (1-clip((b/nb)*b,0,1))'"`, - earrape: 'channelsplit,sidechaingate=level_in=64', - silenceremove: 'silenceremove=1:0:-50dB' - }; - - public static get(name: K) { - return this.filters[name] ?? name; - } - - public static has(name: K) { - return name in this.filters; - } - - public static *[Symbol.iterator](): IterableIterator<{ name: FiltersName; value: string }> { - for (const [k, v] of Object.entries(this.filters)) { - yield { name: k as FiltersName, value: v as string }; - } - } - - public static get names() { - return Object.keys(this.filters) as FiltersName[]; - } - - // @ts-ignore - public static get length() { - return this.names.length; - } - - public static toString() { - return this.names.map((m) => (this as any)[m]).join(','); // eslint-disable-line @typescript-eslint/no-explicit-any - } - - /** - * Create ffmpeg args from the specified filters name - * @param filter The filter name - * @returns - */ - public static create(filters?: (K | string)[]) { - if (!filters || !Array.isArray(filters)) return this.toString(); - return filters - .filter((predicate) => typeof predicate === 'string') - .map((m) => this.get(m as K)) - .join(','); - } - - /** - * Defines audio filter - * @param filterName The name of the filter - * @param value The ffmpeg args - */ - public static define(filterName: string, value: string) { - this.filters[filterName as FiltersName] = value; - } - - /** - * Defines multiple audio filters - * @param filtersArray Array of filters containing the filter name and ffmpeg args - */ - public static defineBulk(filtersArray: { name: string; value: string }[]) { - filtersArray.forEach((arr) => this.define(arr.name, arr.value)); - } -} diff --git a/packages/discord-player/src/utils/FFmpegStream.ts b/packages/discord-player/src/utils/FFmpegStream.ts deleted file mode 100644 index 846c463c40..0000000000 --- a/packages/discord-player/src/utils/FFmpegStream.ts +++ /dev/null @@ -1,80 +0,0 @@ -import type { Duplex, Readable } from 'stream'; -import * as prism from 'prism-media'; -import { FFmpeg } from '@discord-player/ffmpeg'; - -export interface FFmpegStreamOptions { - fmt?: string; - encoderArgs?: string[]; - seek?: number; - skip?: boolean; - cookies?: string; - useLegacyFFmpeg?: boolean; -} - -const getFFmpegProvider = (legacy = false) => (legacy ? (prism as typeof prism & { default: typeof prism }).default?.FFmpeg || prism.FFmpeg : FFmpeg); - -const resolveArgs = (config: Record): string[] => { - return Object.entries(config).reduce((acc, [key, value]) => { - if (value == null) return acc; - acc.push(`-${key}`, String(value)); - return acc; - }, [] as string[]); -}; - -export function FFMPEG_ARGS_STRING(stream: string, fmt?: string, cookies?: string) { - const args = resolveArgs({ - reconnect: 1, - reconnect_streamed: 1, - reconnect_delay_max: 5, - i: stream, - analyzeduration: 0, - loglevel: 0, - ar: 48000, - ac: 2, - f: `${typeof fmt === 'string' ? fmt : 's16le'}`, - acodec: fmt === 'opus' ? 'libopus' : null, - cookies: typeof cookies === 'string' ? (!cookies.includes(' ') ? cookies : `"${cookies}"`) : null - }); - - return args; -} - -export function FFMPEG_ARGS_PIPED(fmt?: string) { - const args = resolveArgs({ - analyzeduration: 0, - loglevel: 0, - ar: 48000, - ac: 2, - f: `${typeof fmt === 'string' ? fmt : 's16le'}`, - acodec: fmt === 'opus' ? 'libopus' : null - }); - - return args; -} - -/** - * Creates FFmpeg stream - * @param stream The source stream - * @param options FFmpeg stream options - */ -export function createFFmpegStream(stream: Readable | Duplex | string, options?: FFmpegStreamOptions) { - if (options?.skip && typeof stream !== 'string') return stream; - options ??= {}; - const args = typeof stream === 'string' ? FFMPEG_ARGS_STRING(stream, options.fmt, options.cookies) : FFMPEG_ARGS_PIPED(options.fmt); - - if (!Number.isNaN(options.seek)) args.unshift('-ss', String(options.seek)); - if (Array.isArray(options.encoderArgs)) args.push(...options.encoderArgs); - - const FFMPEG = getFFmpegProvider(!!options.useLegacyFFmpeg); - - const transcoder = new FFMPEG({ shell: false, args }); - - transcoder.on('close', () => transcoder.destroy()); - - if (typeof stream !== 'string') { - stream.on('error', () => transcoder.destroy()); - stream.pipe(transcoder); - } - - return transcoder; -} diff --git a/packages/discord-player/src/utils/IPRotator.ts b/packages/discord-player/src/utils/IPRotator.ts deleted file mode 100644 index 4f1808cf1b..0000000000 --- a/packages/discord-player/src/utils/IPRotator.ts +++ /dev/null @@ -1,133 +0,0 @@ -import ip from 'ip'; - -export class IPBlock { - public usage = 0; - public readonly cidr: string; - public readonly cidrSize: number; - - public constructor(public block: string) { - if (ip.isV4Format(block.split('/')[0]) && !block.includes('/')) { - block += '/32'; - } else if (ip.isV6Format(block.split('/')[0]) && !block.includes('/')) { - block += '/128'; - } - - this.cidr = ip.cidr(this.block); - this.cidrSize = ip.cidrSubnet(this.block).subnetMaskLength; - } - - public consume() { - this.usage++; - } -} - -export interface IPRotationConfig { - /** - * IP blocks to use - */ - blocks: string[]; - /** - * IPs to exclude - */ - exclude?: string[]; - /** - * Max retries to find an IP that is not excluded - */ - maxRetries?: number; -} - -export class IPRotator { - public blocks: IPBlock[] = []; - public failures = new Map(); - public MAX_NEXT_RETRIES = 30; - #retries = 0; - - public constructor(public config: IPRotationConfig) { - config.exclude ??= []; - this.blocks = config.blocks.map((block) => new IPBlock(block)); - this.MAX_NEXT_RETRIES = config.maxRetries ?? 10; - } - - public getIP(): { ip: string; family: 4 | 6 } { - const block = this.blocks.sort((a, b) => a.usage - b.usage)[0]; - if (!block) { - throw new Error('No IP blocks available'); - } - - const random = IPRotator.getRandomIP(block.cidr, block.cidrSize); - - if (this.isFailedOrExcluded(random)) { - if (this.#retries++ > this.MAX_NEXT_RETRIES) { - this.#retries = 0; - throw new Error('Unable to find an IP that is not excluded'); - } - - return this.getIP(); - } - - this.#retries = 0; - block.consume(); - return { ip: random, family: ip.isV4Format(random) ? 4 : 6 }; - } - - public isFailedOrExcluded(ip: string) { - return this.failures.has(ip) || !!this.config.exclude?.includes(ip); - } - - public addFailed(ip: string) { - const lastFailedCount = this.failures.get(ip) ?? 0; - - this.failures.set(ip, lastFailedCount + 1); - } - - public static getRandomIP(address: string, start?: number, end?: number) { - // Author: Jesse Tane - // NPMJS: https://npmjs.org/random-ip - - const bytes = ip.toBuffer(address); - const ipv6 = bytes.length === 16; - const bytesize = 8; - - start = start || 0; - end = typeof end !== 'undefined' ? end : bytes.length * bytesize; - - for (let i = 0; i < bytes.length; i++) { - let bit = i * bytesize; - - if (bit + bytesize < start || bit >= end) { - continue; - } - - let b = bytes[i]; - - for (let n = 0; n < bytesize; n++) { - if (bit >= start && bit < end) { - const bitpos = bytesize - n - 1; - const bitmask = 1 << bitpos; - if (Math.random() < 0.5) { - b |= bitmask; - } else { - b &= ~bitmask; - } - } - bit++; - } - - bytes[i] = b; - } - - const tets = []; - - for (let i = 0; i < bytes.length; i++) { - if (ipv6) { - if (i % 2 === 0) { - tets[i >> 1] = ((bytes[i] << bytesize) | bytes[i + 1]).toString(16); - } - } else { - tets[i] = bytes[i]; - } - } - - return tets.join(ipv6 ? ':' : '.'); - } -} diff --git a/packages/discord-player/src/utils/PlayerEventsEmitter.ts b/packages/discord-player/src/utils/PlayerEventsEmitter.ts deleted file mode 100644 index 6424c096bc..0000000000 --- a/packages/discord-player/src/utils/PlayerEventsEmitter.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { DefaultListener } from '@discord-player/utils'; -import { ListenerSignature } from '@discord-player/utils'; -import { EventEmitter } from '@discord-player/utils'; -import { Util } from './Util'; - -export class PlayerEventsEmitter = DefaultListener> extends EventEmitter { - #hasDebugger = false; - public constructor(public requiredEvents: Array = []) { - super(); - } - - public on(name: K, listener: L[K]) { - if (name === 'debug') { - this.#hasDebugger = true; - } - - return super.on(name, listener); - } - - public once(name: K, listener: L[K]) { - if (name === 'debug') { - this.#hasDebugger = true; - } - - return super.once(name, listener); - } - - public addListener(name: K, listener: L[K]) { - if (name === 'debug') { - this.#hasDebugger = true; - } - - return super.addListener(name, listener); - } - - public off(name: K, listener: L[K]) { - this.#hasDebugger = this.listenerCount('debug' as K) > 0; - - return super.off(name, listener); - } - - public removeListener(name: K, listener: L[K]) { - this.#hasDebugger = this.listenerCount('debug' as K) > 0; - - return super.removeListener(name, listener); - } - - public removeAllListeners(name?: K) { - this.#hasDebugger = this.listenerCount('debug' as K) > 0; - - return super.removeAllListeners(name); - } - - public emit(name: K, ...args: Parameters) { - if (this.requiredEvents.includes(name) && !this.eventNames().includes(name)) { - // eslint-disable-next-line no-console - console.error(...args); - Util.warn( - `No event listener found for event "${String(name)}". Events ${this.requiredEvents.map((m) => `"${String(m)}"`).join(', ')} must have event listeners.`, - 'UnhandledEventsWarning' - ); - return false; - } - - return super.emit(name, ...args); - } - - public get hasDebugger() { - return this.#hasDebugger; - } -} diff --git a/packages/discord-player/src/utils/QueryCache.ts b/packages/discord-player/src/utils/QueryCache.ts deleted file mode 100644 index 84d393a794..0000000000 --- a/packages/discord-player/src/utils/QueryCache.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { Player } from '../Player'; -import { SearchResult } from '../fabric/SearchResult'; -import { Track } from '../fabric/Track'; -import { User } from 'discord.js'; -import { SearchQueryType } from '../types/types'; - -export interface QueryCacheOptions { - checkInterval?: number; -} - -// 5h -const DEFAULT_EXPIRY_TIMEOUT = 18_000_000; - -export interface QueryCacheProvider { - getData(): Promise[]>; - addData(data: SearchResult): Promise; - resolve(context: QueryCacheResolverContext): Promise; -} - -export class QueryCache implements QueryCacheProvider { - #defaultCache = new Map>(); - public timer: NodeJS.Timer; - public constructor( - public player: Player, - public options: QueryCacheOptions = { - checkInterval: DEFAULT_EXPIRY_TIMEOUT - } - ) { - this.timer = setInterval(this.cleanup.bind(this), this.checkInterval).unref(); - } - - public get checkInterval() { - return this.options.checkInterval ?? DEFAULT_EXPIRY_TIMEOUT; - } - - public async cleanup() { - for (const [id, value] of this.#defaultCache) { - if (value.hasExpired()) { - this.#defaultCache.delete(id); - } - } - } - - public async clear() { - this.#defaultCache.clear(); - } - - public async getData() { - return [...this.#defaultCache.values()]; - } - - public async addData(data: SearchResult) { - data.tracks.forEach((d) => { - if (this.#defaultCache.has(d.url)) return; - this.#defaultCache.set(d.url, new DiscordPlayerQueryResultCache(d)); - }); - } - - public async resolve(context: QueryCacheResolverContext) { - const result = this.#defaultCache.get(context.query); - if (!result) - return new SearchResult(this.player, { - query: context.query, - requestedBy: context.requestedBy, - queryType: context.queryType - }); - - return new SearchResult(this.player, { - query: context.query, - tracks: [result.data], - playlist: null, - queryType: context.queryType, - requestedBy: context.requestedBy - }); - } -} - -export class DiscordPlayerQueryResultCache { - public expireAfter = DEFAULT_EXPIRY_TIMEOUT; - public constructor(public data: T, expireAfter: number = DEFAULT_EXPIRY_TIMEOUT) { - if (typeof expireAfter === 'number') { - this.expireAfter = Date.now() + expireAfter; - } - } - - public hasExpired() { - if (typeof this.expireAfter !== 'number' || isNaN(this.expireAfter) || this.expireAfter < 1) return false; - return Date.now() <= this.expireAfter; - } -} - -export interface QueryCacheResolverContext { - query: string; - requestedBy?: User; - queryType?: SearchQueryType | `ext:${string}`; -} diff --git a/packages/discord-player/src/utils/QueryResolver.ts b/packages/discord-player/src/utils/QueryResolver.ts deleted file mode 100644 index e081bbce6b..0000000000 --- a/packages/discord-player/src/utils/QueryResolver.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { QueryType } from '../types/types'; -import { TypeUtil } from './TypeUtil'; -import { Exceptions } from '../errors'; -import { fetch } from 'undici'; - -// #region scary things below *sigh* -const spotifySongRegex = /^https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(intl-([a-z]|[A-Z])+\/)?(?:track\/|\?uri=spotify:track:)((\w|-){22})(\?si=.+)?$/; -const spotifyPlaylistRegex = /^https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(intl-([a-z]|[A-Z])+\/)?(?:playlist\/|\?uri=spotify:playlist:)((\w|-){22})(\?si=.+)?$/; -const spotifyAlbumRegex = /^https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(intl-([a-z]|[A-Z])+\/)?(?:album\/|\?uri=spotify:album:)((\w|-){22})(\?si=.+)?$/; -const vimeoRegex = /^(http|https)?:\/\/(www\.|player\.)?vimeo\.com\/(?:channels\/(?:\w+\/)?|groups\/([^/]*)\/videos\/|video\/|)(\d+)(?:|\/\?)$/; -const reverbnationRegex = /^https:\/\/(www.)?reverbnation.com\/(.+)\/song\/(.+)$/; -const attachmentRegex = /^https?:\/\/.+$/; -const appleMusicSongRegex = /^https?:\/\/music\.apple\.com\/.+?\/(song|album)\/.+?(\/.+?\?i=|\/)([0-9]+)$/; -const appleMusicPlaylistRegex = /^https?:\/\/music\.apple\.com\/.+?\/playlist\/.+\/pl\.(u-|pm-)?[a-zA-Z0-9]+$/; -const appleMusicAlbumRegex = /^https?:\/\/music\.apple\.com\/.+?\/album\/.+\/([0-9]+)$/; -const soundcloudTrackRegex = /^https?:\/\/(m.|www.)?soundcloud.com\/(\w|-)+\/(\w|-)+(.+)?$/; -const soundcloudPlaylistRegex = /^https?:\/\/(m.|www.)?soundcloud.com\/(\w|-)+\/sets\/(\w|-)+(.+)?$/; -const youtubePlaylistRegex = /^https?:\/\/(www.)?youtube.com\/playlist\?list=((PL|FL|UU|LL|RD|OL)[a-zA-Z0-9-_]{16,41})$/; -const youtubeVideoURLRegex = /^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w-]+\?v=|embed\/|v\/)?)([\w-]+)(\S+)?$/; -const youtubeVideoIdRegex = /^[a-zA-Z0-9-_]{11}$/; -// #endregion scary things above *sigh* - -const DomainsMap = { - YouTube: ['youtube.com', 'youtu.be', 'music.youtube.com', 'gaming.youtube.com', 'www.youtube.com', 'm.youtube.com'], - Spotify: ['open.spotify.com', 'embed.spotify.com'], - Vimeo: ['vimeo.com', 'player.vimeo.com'], - ReverbNation: ['reverbnation.com'], - SoundCloud: ['soundcloud.com'], - AppleMusic: ['music.apple.com'] -}; - -// prettier-ignore -const redirectDomains = new Set([ - /^https?:\/\/spotify.link\/[A-Za-z0-9]+$/, -]); - -export interface ResolvedQuery { - type: (typeof QueryType)[keyof typeof QueryType]; - query: string; -} - -class QueryResolver { - /** - * Query resolver - */ - private constructor() {} // eslint-disable-line @typescript-eslint/no-empty-function - - static get regex() { - return { - spotifyAlbumRegex, - spotifyPlaylistRegex, - spotifySongRegex, - vimeoRegex, - reverbnationRegex, - attachmentRegex, - appleMusicAlbumRegex, - appleMusicPlaylistRegex, - appleMusicSongRegex, - soundcloudTrackRegex, - soundcloudPlaylistRegex, - youtubePlaylistRegex - }; - } - - /** - * Pre-resolve redirect urls - */ - static async preResolve(query: string, maxDepth = 5): Promise { - if (!TypeUtil.isString(query)) throw Exceptions.ERR_INVALID_ARG_TYPE(query, 'string', typeof query); - - for (const domain of redirectDomains) { - if (domain.test(query)) { - try { - const res = await fetch(query, { - method: 'GET', - redirect: 'follow' - }); - - if (!res.ok) break; - - // spotify does not "redirect", it returns a page with js that redirects - if (/^https?:\/\/spotify.app.link\/(.+)$/.test(res.url)) { - const body = await res.text(); - const target = body.split('https://open.spotify.com/track/')[1].split('?si=')[0]; - - if (!target) break; - - return `https://open.spotify.com/track/${target}`; - } - return maxDepth < 1 ? res.url : this.preResolve(res.url, maxDepth - 1); - } catch { - break; - } - } - } - - return query; - } - - /** - * Resolves the given search query - * @param {string} query The query - */ - static resolve(query: string, fallbackSearchEngine: (typeof QueryType)[keyof typeof QueryType] = QueryType.AUTO_SEARCH): ResolvedQuery { - if (!TypeUtil.isString(query)) throw Exceptions.ERR_INVALID_ARG_TYPE(query, 'string', typeof query); - if (!query.length) throw Exceptions.ERR_INFO_REQUIRED('query', String(query)); - - const resolver = (type: typeof fallbackSearchEngine, query: string) => ({ type, query }); - - try { - const url = new URL(query); - - if (DomainsMap.YouTube.includes(url.host)) { - query = query.replace(/(m(usic)?|gaming)\./, '').trim(); - const playlistId = url.searchParams.get('list'); - const videoId = url.searchParams.get('v'); - if (playlistId) { - if (videoId && playlistId.startsWith('RD')) return resolver(QueryType.YOUTUBE_PLAYLIST, `https://www.youtube.com/watch?v=${videoId}&list=${playlistId}`); - return resolver(QueryType.YOUTUBE_PLAYLIST, `https://www.youtube.com/playlist?list=${playlistId}`); - } - if (QueryResolver.validateId(query) || QueryResolver.validateURL(query)) return resolver(QueryType.YOUTUBE_VIDEO, query); - return resolver(fallbackSearchEngine, query); - } else if (DomainsMap.Spotify.includes(url.host)) { - query = query.replace(/intl-([a-zA-Z]+)\//, ''); - if (spotifyPlaylistRegex.test(query)) return resolver(QueryType.SPOTIFY_PLAYLIST, query); - if (spotifyAlbumRegex.test(query)) return resolver(QueryType.SPOTIFY_ALBUM, query); - if (spotifySongRegex.test(query)) return resolver(QueryType.SPOTIFY_SONG, query); - return resolver(fallbackSearchEngine, query); - } else if (DomainsMap.Vimeo.includes(url.host)) { - if (vimeoRegex.test(query)) return resolver(QueryType.VIMEO, query); - return resolver(fallbackSearchEngine, query); - } else if (DomainsMap.ReverbNation.includes(url.host)) { - if (reverbnationRegex.test(query)) return resolver(QueryType.REVERBNATION, query); - return resolver(fallbackSearchEngine, query); - } else if (DomainsMap.SoundCloud.includes(url.host)) { - if (soundcloudPlaylistRegex.test(query)) return resolver(QueryType.SOUNDCLOUD_PLAYLIST, query); - if (soundcloudTrackRegex.test(query)) return resolver(QueryType.SOUNDCLOUD_TRACK, query); - return resolver(fallbackSearchEngine, query); - } else if (DomainsMap.AppleMusic.includes(url.host)) { - if (appleMusicAlbumRegex.test(query)) return resolver(QueryType.APPLE_MUSIC_ALBUM, query); - if (appleMusicPlaylistRegex.test(query)) return resolver(QueryType.APPLE_MUSIC_PLAYLIST, query); - if (appleMusicSongRegex.test(query)) return resolver(QueryType.APPLE_MUSIC_SONG, query); - return resolver(fallbackSearchEngine, query); - } else { - return resolver(QueryType.ARBITRARY, query); - } - } catch { - return resolver(fallbackSearchEngine, query); - } - } - - /** - * Parses vimeo id from url - * @param {string} query The query - * @returns {string} - */ - static getVimeoID(query: string): string | null | undefined { - return QueryResolver.resolve(query).type === QueryType.VIMEO ? query.split('/').filter(Boolean).pop() : null; - } - - static validateId(q: string) { - return youtubeVideoIdRegex.test(q); - } - - static validateURL(q: string) { - return youtubeVideoURLRegex.test(q); - } -} - -export { QueryResolver }; diff --git a/packages/discord-player/src/utils/SequentialBucket.ts b/packages/discord-player/src/utils/SequentialBucket.ts deleted file mode 100644 index c717f36380..0000000000 --- a/packages/discord-player/src/utils/SequentialBucket.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { setTimeout } from 'timers/promises'; -import { AsyncQueue } from './AsyncQueue'; - -export type RequestEntity = () => Promise; - -export class SequentialBucket { - public limit = 1; - public remaining = 1; - public resetAfter = 0; - public queue = new AsyncQueue(); - public MAX_RETRIES = 5; - - /** - * Checks if the bucket is rate limited. - */ - public isRateLimited() { - return this.remaining <= 0 && Date.now() < this.resetAfter; - } - - /** - * Enqueues a request. - * @param req The request function to enqueue - */ - public async enqueue(req: RequestEntity) { - const entry = this.queue.acquire(); - await entry.getTask(); - - try { - return this._request(req); - } finally { - entry.release(); - } - } - - private async _request(req: RequestEntity, retries = 0): Promise { - while (this.isRateLimited()) { - const reset = this.resetAfter - Date.now(); - await setTimeout(reset); - } - - let pass = false; - - try { - const res = await req(); - - this._patchHeaders(res); - - if (res.status === 429) { - const reset = this.resetAfter - Date.now(); - await setTimeout(reset); - return this._request(req); - } - - if (!res.ok) { - let err: Error; - - try { - const body: { - code: number; - name: string; - message: string; - } = await res.json(); - - const error = new Error(body.message) as Error & { code: number }; - - error.name = body.name; - error.code = body.code; - - err = error; - } catch { - err = new Error(`HTTP Error: ${res.status} ${res.statusText}`); - } - - pass = true; - - throw err; - } - - return res; - } catch (e) { - if (pass) throw e; - - const badReq = e instanceof Error && /Error: 4[0-9]{2}/.test(e.message); - - if (!badReq && retries < this.MAX_RETRIES) { - return this._request(req, ++retries); - } - - throw e; - } - } - - private _patchHeaders(res: Response) { - const limit = Number(res.headers.get('X-RateLimit-Limit')); - const remaining = Number(res.headers.get('X-RateLimit-Remaining')); - const resetAfter = Number(res.headers.get('X-RateLimit-Reset')) * 1000 + Date.now(); - - if (!Number.isNaN(limit)) this.limit = limit; - if (!Number.isNaN(remaining)) this.remaining = remaining; - if (!Number.isNaN(resetAfter)) this.resetAfter = resetAfter; - } -} diff --git a/packages/discord-player/src/utils/TypeUtil.ts b/packages/discord-player/src/utils/TypeUtil.ts deleted file mode 100644 index b69e551f79..0000000000 --- a/packages/discord-player/src/utils/TypeUtil.ts +++ /dev/null @@ -1,34 +0,0 @@ -export class TypeUtil { - private constructor() { - return TypeUtil; - } - - // eslint-disable-next-line @typescript-eslint/ban-types - public static isFunction(t: unknown): t is Function { - return typeof t === 'function'; - } - - public static isNumber(t: unknown): t is number { - return typeof t === 'number' && !isNaN(t); - } - - public static isString(t: unknown): t is string { - return typeof t === 'string'; - } - - public static isBoolean(t: unknown): t is boolean { - return typeof t === 'boolean'; - } - - public static isNullish(t: unknown): t is null | undefined { - return t == null; - } - - public static isArray(t: unknown): t is unknown[] { - return Array.isArray(t); - } - - public static isError(t: unknown): t is Error { - return t instanceof Error; - } -} diff --git a/packages/discord-player/src/utils/Util.ts b/packages/discord-player/src/utils/Util.ts deleted file mode 100644 index fbaf17c2b8..0000000000 --- a/packages/discord-player/src/utils/Util.ts +++ /dev/null @@ -1,250 +0,0 @@ -import { StageChannel, VoiceChannel } from 'discord.js'; -import { TimeData } from '../types/types'; -import { setTimeout } from 'node:timers/promises'; -import { GuildQueue } from '../queue'; -import { Playlist, Track } from '../fabric'; -import { Exceptions } from '../errors'; -import { randomInt } from 'node:crypto'; -import { - createFilter, - createSpotifyFilter, - fixTrackSuffix, - removeLive, - removeRemastered, - youtube, - removeZeroWidth, - replaceNbsp, - replaceSmartQuotes, - removeCleanExplicit -} from '@web-scrobbler/metadata-filter'; -import { TrackSource } from '../types/types'; - -export type RuntimeType = 'node' | 'deno' | 'bun' | 'unknown'; - -export interface Runtime { - name: RuntimeType; - version: string; -} - -class Util { - /** - * Utils - */ - private constructor() {} // eslint-disable-line @typescript-eslint/no-empty-function - - /** - * Gets the runtime information - */ - static getRuntime(): Runtime { - const version = typeof navigator !== 'undefined' ? navigator.userAgent : null; - - // @ts-ignore - if (typeof Deno !== 'undefined' && Deno.version) return { name: 'deno', version: Deno.version.deno }; - - // @ts-ignore - if (typeof Bun !== 'undefined' && Bun.version) return { name: 'bun', version: Bun.version }; - - if (typeof process !== 'undefined' && process.version) return { name: 'node', version: process.version }; - - return { name: 'unknown', version: version ?? 'unknown' }; - } - - /** - * Creates duration string - * @param {object} durObj The duration object - * @returns {string} - */ - static durationString(durObj: Record) { - return Object.values(durObj) - .map((m) => (isNaN(m) ? 0 : m)) - .join(':'); - } - - /** - * Parses milliseconds to consumable time object - * @param {number} milliseconds The time in ms - * @returns {TimeData} - */ - static parseMS(milliseconds: number) { - if (isNaN(milliseconds)) milliseconds = 0; - const round = milliseconds > 0 ? Math.floor : Math.ceil; - - return { - days: round(milliseconds / 86400000), - hours: round(milliseconds / 3600000) % 24, - minutes: round(milliseconds / 60000) % 60, - seconds: round(milliseconds / 1000) % 60 - } as TimeData; - } - - /** - * Builds time code - * @param {TimeData} duration The duration object - * @returns {string} - */ - static buildTimeCode(duration: TimeData) { - const items = Object.keys(duration); - const required = ['days', 'hours', 'minutes', 'seconds']; - - const parsed = items.filter((x) => required.includes(x)).map((m) => duration[m as keyof TimeData]); - const final = parsed - .slice(parsed.findIndex((x) => x !== 0)) - .map((x) => x.toString().padStart(2, '0')) - .join(':'); - - return final.length <= 3 ? `0:${final.padStart(2, '0') || 0}` : final; - } - - /** - * Formats duration - * @param {number} duration The duration in ms - */ - static formatDuration(duration: number) { - return this.buildTimeCode(this.parseMS(duration)); - } - - /** - * Picks last item of the given array - * @param {any[]} arr The array - * @returns {any} - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - static last(arr: T[]): T { - if (!Array.isArray(arr)) return arr; - return arr[arr.length - 1]; - } - - /** - * Checks if the voice channel is empty - * @param {VoiceChannel|StageChannel} channel The voice channel - * @returns {boolean} - */ - static isVoiceEmpty(channel: VoiceChannel | StageChannel) { - return channel && channel.members.filter((member) => !member.user.bot).size === 0; - } - - /** - * Cleans the track title - * @param title The title - * @param source The source - * @returns Cleaned title - */ - static cleanTitle(title: string, source: TrackSource) { - try { - const filterOpts = { - // prettier-ignore - track: [ - removeRemastered, - removeLive, - fixTrackSuffix, - removeZeroWidth, - replaceNbsp, - replaceSmartQuotes, - removeCleanExplicit - ] - }; - const spotifyFilter = createFilter(filterOpts); - spotifyFilter.extend(createSpotifyFilter()); - const defaultFilter = createFilter(filterOpts); - - switch (source) { - case 'youtube': - return youtube(title); - case 'spotify': - return spotifyFilter.filterField('track', title); - default: - return defaultFilter.filterField('track', title); - } - } catch { - return title; - } - } - - /** - * Safer require - * @param {string} id Node require id - * @returns {any} - */ - static require(id: string) { - try { - return { module: require(id), error: null }; - } catch (error) { - return { module: null, error }; - } - } - - static async import(id: string) { - try { - const mod = await import(id); - return { module: mod, error: null }; - } catch (error) { - return { module: null, error }; - } - } - - /** - * Asynchronous timeout - * @param {number} time The time in ms to wait - * @returns {Promise} - */ - static wait(time: number) { - return setTimeout(time, undefined, { ref: false }); - } - - static noop() {} // eslint-disable-line @typescript-eslint/no-empty-function - - static async getFetch() { - if ('fetch' in globalThis) return globalThis.fetch; - for (const lib of ['node-fetch', 'undici']) { - try { - return await import(lib).then((res) => res.fetch || res.default?.fetch || res.default); - } catch { - try { - // eslint-disable-next-line - const res = require(lib); - if (res) return res.fetch || res.default?.fetch || res.default; - } catch { - // no? - } - } - } - } - - static warn(message: string, code = 'DeprecationWarning', detail?: string) { - process.emitWarning(message, { - code, - detail - }); - } - - static randomChoice(src: T[]): T { - return src[randomInt(src.length)]; - } - - static arrayCloneShuffle(src: T[]): T[] { - const arr = src.slice(); - - let m = arr.length; - - while (m) { - const i = Math.floor(Math.random() * m--); - [arr[m], arr[i]] = [arr[i], arr[m]]; - } - - return arr; - } -} - -export const VALIDATE_QUEUE_CAP = (queue: GuildQueue, items: Playlist | Track | Track[]) => { - const tracks = items instanceof Playlist ? items.tracks : Array.isArray(items) ? items : [items]; - - if (queue.maxSize < 1 || queue.maxSize === Infinity) return; - - const maxCap = queue.getCapacity(); - - if (maxCap < tracks.length) { - throw Exceptions.ERR_OUT_OF_SPACE('tracks queue', maxCap, tracks.length); - } -}; - -export { Util }; diff --git a/packages/discord-player/src/utils/__internal__/_container.ts b/packages/discord-player/src/utils/__internal__/_container.ts deleted file mode 100644 index 037577bd36..0000000000 --- a/packages/discord-player/src/utils/__internal__/_container.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { Player } from '../../Player'; -import { Collection } from '@discord-player/utils'; - -export const instances = new Collection(); -export const globalRegistry = new Collection(); diff --git a/packages/discord-player/src/utils/__internal__/addPlayer.ts b/packages/discord-player/src/utils/__internal__/addPlayer.ts deleted file mode 100644 index 4d05a6bdde..0000000000 --- a/packages/discord-player/src/utils/__internal__/addPlayer.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Player } from '../../Player'; -import { instances } from './_container'; - -export function addPlayer(player: Player) { - if (instances.has(player.id)) return true; - - instances.set(player.id, player); - - return instances.has(player.id); -} diff --git a/packages/discord-player/src/utils/__internal__/clearPlayer.ts b/packages/discord-player/src/utils/__internal__/clearPlayer.ts deleted file mode 100644 index 1451fcf4fc..0000000000 --- a/packages/discord-player/src/utils/__internal__/clearPlayer.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Player } from '../../Player'; -import { instances } from './_container'; - -export function clearPlayer(player: Player) { - return instances.delete(player.id); -} diff --git a/packages/discord-player/src/utils/__internal__/getGlobalRegistry.ts b/packages/discord-player/src/utils/__internal__/getGlobalRegistry.ts deleted file mode 100644 index 8059691d41..0000000000 --- a/packages/discord-player/src/utils/__internal__/getGlobalRegistry.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { globalRegistry } from './_container'; - -export function getGlobalRegistry() { - return globalRegistry; -} diff --git a/packages/discord-player/src/utils/__internal__/getPlayers.ts b/packages/discord-player/src/utils/__internal__/getPlayers.ts deleted file mode 100644 index 8997bd611f..0000000000 --- a/packages/discord-player/src/utils/__internal__/getPlayers.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { instances } from './_container'; - -export function getPlayers() { - return instances.array(); -} diff --git a/packages/discord-player/src/utils/__internal__/index.ts b/packages/discord-player/src/utils/__internal__/index.ts deleted file mode 100644 index 87fc9836fa..0000000000 --- a/packages/discord-player/src/utils/__internal__/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './_container'; -export * from './addPlayer'; -export * from './clearPlayer'; -export * from './getPlayers'; -export * from './getGlobalRegistry'; diff --git a/packages/discord-player/src/utils/serde.ts b/packages/discord-player/src/utils/serde.ts deleted file mode 100644 index 702330b0a2..0000000000 --- a/packages/discord-player/src/utils/serde.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ - -import { Exceptions } from '../errors'; -import { Playlist, type SerializedTrack, Track, SerializedPlaylist } from '../fabric'; -import { TypeUtil } from './TypeUtil'; -import { Buffer } from 'buffer'; -import { Player } from '../Player'; - -export enum SerializedType { - Track = 'track', - Playlist = 'playlist' -} - -export type Encodable = SerializedTrack | SerializedPlaylist; - -const isTrack = (data: any): data is SerializedTrack => data.$type === SerializedType.Track; -const isPlaylist = (data: any): data is SerializedPlaylist => data.$type === SerializedType.Playlist; - -export function serialize(data: Track | Playlist | any) { - if (data instanceof Track) return data.serialize(); - if (data instanceof Playlist) return data.serialize(); - - try { - return data.toJSON(); - } catch { - throw Exceptions.ERR_SERIALIZATION_FAILED(); - } -} - -export function deserialize(player: Player, data: Encodable) { - if (isTrack(data)) return Track.fromSerialized(player, data); - if (isPlaylist(data)) return Playlist.fromSerialized(player, data); - - throw Exceptions.ERR_DESERIALIZATION_FAILED(); -} - -export function encode(data: Encodable) { - const str = JSON.stringify(data); - - return Buffer.from(str).toString('base64'); -} - -export function decode(data: string) { - const str = Buffer.from(data, 'base64').toString(); - - return JSON.parse(str); -} - -export function tryIntoThumbnailString(data: any) { - if (!data) return null; - try { - if (TypeUtil.isString(data)) return data; - return data?.url ?? data?.thumbnail?.url ?? null; - } catch { - return null; - } -} diff --git a/apps/music-bot/src/recordings/.gitkeep b/packages/discord-player/src/voip/connection.ts similarity index 100% rename from apps/music-bot/src/recordings/.gitkeep rename to packages/discord-player/src/voip/connection.ts diff --git a/packages/discord-player/src/voip/constants.ts b/packages/discord-player/src/voip/constants.ts new file mode 100644 index 0000000000..eae6ee1630 --- /dev/null +++ b/packages/discord-player/src/voip/constants.ts @@ -0,0 +1,29 @@ +export const NONCE = Buffer.alloc(24); + +export const OPUS_SAMPLE_RATE = 48000; + +export const OPUS_CHANNELS = 2; + +export const OPUS_FRAME_SIZE = 960; + +export const OPUS_FRAME_DURATION = Math.floor(OPUS_FRAME_SIZE / OPUS_SAMPLE_RATE); + +export const OPUS_SILENCE_FRAME = Buffer.from([0xf8, 0xff, 0xfe]); + +export const TIMESTAMP_INC = (OPUS_SAMPLE_RATE / 100) * OPUS_CHANNELS; + +export const MAX_COUNTER_VALUE = 2 ** 32 - 1; + +export const UDP_KEEPALIVE_INTERVAL = 5e3; + +export const MAX_NONCE = 2 ** 32 - 1; + +export const EncryptionMode = { + XSALSA20_POLY1305_LITE: 'xsalsa20_poly1305_lite', + XSALSA20_POLY1305_SUFFIX: 'xsalsa20_poly1305_suffix', + XSALSA20_POLY1305: 'xsalsa20_poly1305' +} as const; + +export type EncryptionMode = (typeof EncryptionMode)[keyof typeof EncryptionMode]; + +export const ENCRYPTION_MODES = new Set(['xsalsa20_poly1305_lite', 'xsalsa20_poly1305_suffix', 'xsalsa20_poly1305'] as const); diff --git a/packages/discord-player/src/voip/libsodium.ts b/packages/discord-player/src/voip/libsodium.ts new file mode 100644 index 0000000000..c34401d67b --- /dev/null +++ b/packages/discord-player/src/voip/libsodium.ts @@ -0,0 +1,80 @@ +import { unsafe } from '../common/types'; + +export interface ISodium { + open(buffer: Buffer, nonce: Buffer, key: Uint8Array): Buffer; + close(buffer: Buffer, nonce: Buffer, key: Uint8Array): Buffer; + random(length: number, target?: Buffer): Buffer; +} + +const libs: Record ISodium> = { + 'sodium-native': (lib) => ({ + open(buffer, nonce, key) { + const result = Buffer.allocUnsafe(buffer.length - lib.crypto_secretbox_MACBYTES); + lib.crypto_secretbox_open_easy(result, buffer, nonce, key); + return result; + }, + close(buffer, nonce, key) { + const result = Buffer.allocUnsafe(buffer.length + lib.crypto_secretbox_MACBYTES); + lib.crypto_secretbox_easy(result, buffer, nonce, key); + return result; + }, + random(length, target) { + const buffer = target ?? Buffer.allocUnsafe(length); + lib.randombytes_buf(buffer); + return buffer; + } + }), + sodium: (lib) => ({ + open: lib.api.crypto_secretbox_open_easy, + close: lib.api.crypto_secretbox_easy, + random: (length, target) => { + const buffer = target ?? Buffer.allocUnsafe(length); + lib.api.randombytes_buf(buffer); + return buffer; + } + }), + 'libsodium-wrappers': (lib) => ({ + open: lib.crypto_secretbox_open_easy, + close: lib.crypto_secretbox_easy, + random: lib.randombytes_buf + }), + tweetnacl: (lib) => ({ + open: lib.secretbox.open, + close: lib.secretbox, + random: lib.randombytes_buf + }) +}; + +libs['sodium-javascript'] = libs['sodium-native']; + +const err = () => { + const supported = Object.keys(libs).join(', '); + + throw new Error(`No supported libsodium found. Make sure you have installed one of ${supported} in your project.`); +}; + +const libsodium: ISodium = { + open: err, + close: err, + random: err +}; + +(async () => { + const libsEntries = Object.entries(libs); + + for (const [name, lib] of libsEntries) { + try { + const _sod = await import(name); + const sod = 'default' in _sod ? _sod.default : _sod; + + if (name === 'libsodium-wrappers' && 'ready' in sod) await sod.ready; + + Object.assign(libsodium, lib(sod)); + break; + } catch { + // + } + } +})(); + +export { libsodium }; diff --git a/packages/discord-player/src/voip/networking.ts b/packages/discord-player/src/voip/networking.ts new file mode 100644 index 0000000000..e7841e24f2 --- /dev/null +++ b/packages/discord-player/src/voip/networking.ts @@ -0,0 +1,65 @@ +import { EventEmitter } from '../common/EventEmitter'; +import { EncryptionMode, MAX_NONCE, NONCE } from './constants'; +import { libsodium } from './libsodium'; + +export const VoipEvents = {} as const; + +export type VoipEvents = (typeof VoipEvents)[keyof typeof VoipEvents]; + +export interface VoipEventsMap {} + +export interface NetworkingOptions {} + +export interface ConnectionData { + encryptionMode: EncryptionMode; + nonce: number; + nonceBuffer: Buffer; + packetsPlayed: number; + secretKey: Uint8Array; + sequence: number; + speaking: boolean; + ssrc: number; + timestamp: number; +} + +export class Networking extends EventEmitter { + public constructor(public readonly options: NetworkingOptions) { + super(); + } + + #createAudioPacket(opus: Buffer, data: ConnectionData) { + const packetBuffer = Buffer.alloc(12); + packetBuffer[0] = 0x80; + packetBuffer[1] = 0x78; + + const { sequence, timestamp, ssrc } = data; + + packetBuffer.writeUIntBE(sequence, 2, 2); + packetBuffer.writeUIntBE(timestamp, 4, 4); + packetBuffer.writeUIntBE(ssrc, 8, 4); + + packetBuffer.copy(NONCE, 0, 0, 12); + + return Buffer.concat([packetBuffer, ...this.#encrypt(opus, data)]); + } + + #encrypt(opus: Buffer, data: ConnectionData) { + switch (data.encryptionMode) { + case EncryptionMode.XSALSA20_POLY1305_LITE: { + data.nonce++; + if (data.nonce > MAX_NONCE) data.nonce = 0; + data.nonceBuffer.writeUInt32BE(data.nonce, 0); + + return [libsodium.close(opus, data.nonceBuffer, data.secretKey)]; + } + case EncryptionMode.XSALSA20_POLY1305_SUFFIX: { + const random = libsodium.random(24, data.nonceBuffer); + + return [libsodium.close(opus, random, data.secretKey), random]; + } + default: { + return [libsodium.close(opus, data.nonceBuffer, data.secretKey)]; + } + } + } +} diff --git a/packages/discord-player/src/voip/udp.ts b/packages/discord-player/src/voip/udp.ts new file mode 100644 index 0000000000..eb0e38560c --- /dev/null +++ b/packages/discord-player/src/voip/udp.ts @@ -0,0 +1,134 @@ +import { isIPv4 } from 'node:net'; +import { createSocket, type Socket } from 'node:dgram'; +import { EventEmitter } from '../common/EventEmitter'; +import { MAX_COUNTER_VALUE, UDP_KEEPALIVE_INTERVAL } from './constants'; + +export interface RemoteAddress { + address: string; + port: number; +} + +export interface VoipUdpOptions { + address: string; +} + +export const VoipUdpEvents = { + Error: 'error', + Close: 'close', + Message: 'message' +} as const; + +export type VoipUdpEvents = (typeof VoipUdpEvents)[keyof typeof VoipUdpEvents]; + +export interface VoipUdpEventsMap { + [VoipUdpEvents.Error]: (error: Error) => void; + [VoipUdpEvents.Close]: () => void; + [VoipUdpEvents.Message]: (data: Buffer) => void; +} + +export class VoipUdpSocket extends EventEmitter { + private readonly socket: Socket; + private keepAliveBuffer: Buffer; + private keepAliveInterval: NodeJS.Timeout; + private keepAliveCounter = 0; + + public constructor(private readonly remoteAddress: RemoteAddress) { + super(); + + this.socket = createSocket('udp4'); + this.socket.on('error', this.#onError.bind(this)); + this.socket.on('message', this.#onMessage.bind(this)); + this.socket.on('close', this.#onClose.bind(this)); + + this.keepAliveBuffer = Buffer.alloc(0); + this.keepAliveInterval = setInterval(this.#keepAlive.bind(this), UDP_KEEPALIVE_INTERVAL); + + setImmediate(() => this.#keepAlive()); + } + + public destroy() { + try { + this.debug?.('Destroying the UDP socket'); + this.socket.close(); + } catch { + // + } + + clearInterval(this.keepAliveInterval); + } + + public send(buffer: Buffer) { + const { address, port } = this.remoteAddress; + + this.socket.send(buffer, port, address); + } + + public performIPDiscovery(ssrc: number): Promise { + return new Promise((resolve, reject) => { + const listener = (message: Buffer) => { + try { + if (message.readUInt16BE(0) !== 2) return; + + const data = this.#parseLocalPacket(message); + this.socket.off('message', listener); + this.socket.off('close', rejectListener); + resolve(data); + } catch { + // + } + }; + + const rejectListener = () => reject(new Error('Socket closed before performing IP discovery')); + + this.socket.on('message', listener); + this.socket.once('close', rejectListener); + + const buffer = Buffer.alloc(74); + + buffer.writeUInt16BE(1, 0); + buffer.writeUInt16BE(70, 2); + buffer.writeUInt32BE(ssrc, 4); + + this.send(buffer); + }); + } + + #parseLocalPacket(buffer: Buffer): RemoteAddress { + const msg = Buffer.from(buffer); + + const address = msg.subarray(8, msg.indexOf(0, 8)).toString('utf-8'); + + if (!isIPv4(address)) { + throw new Error('Invalid IP address received'); + } + + const port = msg.readUInt16BE(msg.length - 2); + + return { + address, + port + } satisfies RemoteAddress; + } + + #onError(error: Error) { + this.emit(VoipUdpEvents.Error, error); + } + + #onClose() { + this.emit(VoipUdpEvents.Close); + } + + #onMessage(data: Buffer) { + this.emit(VoipUdpEvents.Message, data); + } + + #keepAlive() { + this.keepAliveBuffer.writeUInt32LE(this.keepAliveCounter, 0); + this.send(this.keepAliveBuffer); + this.keepAliveCounter++; + + if (this.keepAliveCounter > MAX_COUNTER_VALUE) { + this.keepAliveCounter = 0; + } + } +} diff --git a/packages/discord-player/src/voip/websocket.ts b/packages/discord-player/src/voip/websocket.ts new file mode 100644 index 0000000000..b1ae283a83 --- /dev/null +++ b/packages/discord-player/src/voip/websocket.ts @@ -0,0 +1,117 @@ +import WebSocket from 'ws'; +import { EventEmitter } from '../common/EventEmitter'; +import type { unsafe } from '../common/types'; +import { VoiceOpcodes } from 'discord-api-types/voice/v4'; + +export interface VoipWebSocketOptions { + address: string; +} + +export const VoipWebSocketEvents = { + Error: 'error', + Open: 'open', + Close: 'close', + Packet: 'packet' +} as const; + +export type VoipWebSocketEvents = (typeof VoipWebSocketEvents)[keyof typeof VoipWebSocketEvents]; + +export interface VoipWebSocketEventsMap { + [VoipWebSocketEvents.Error]: (error: Error) => void; + [VoipWebSocketEvents.Open]: (event: WebSocket.Event) => void; + [VoipWebSocketEvents.Close]: (event: WebSocket.CloseEvent) => void; + [VoipWebSocketEvents.Packet]: (data: unsafe) => void; +} + +export class VoipWebSocket extends EventEmitter { + private readonly ws: WebSocket; + private lastHeartbeatSent = 0; + private missedHeartbeats = 0; + private heartbeatInterval: NodeJS.Timeout | undefined; + + public constructor(private readonly options: VoipWebSocketOptions) { + super(); + + this.ws = new WebSocket(options.address); + this.ws.onmessage = this.#onMessage.bind(this); + this.ws.onerror = this.#onError.bind(this); + this.ws.onopen = this.#onOpen.bind(this); + this.ws.onclose = this.#onClose.bind(this); + } + + public destroy() { + try { + this.debug?.('Destroying the WebSocket'); + this.setHeartbeat(0); + this.ws.close(1000); + } catch (error) { + this.emit(VoipWebSocketEvents.Error, error as Error); + } + } + + public send(data: unsafe) { + try { + const payload = JSON.stringify(data); + this.debug?.('Sending payload ${payload}'); + this.ws.send(payload); + } catch (error) { + this.emit(VoipWebSocketEvents.Error, error as Error); + } + } + + public setHeartbeat(interval: number) { + if (this.heartbeatInterval) clearInterval(this.heartbeatInterval); + if (interval <= 0) return; + + this.heartbeatInterval = setInterval(() => { + if (this.lastHeartbeatSent !== 0 && this.missedHeartbeats >= 3) { + this.debug?.('Missed too many heartbeats'); + this.ws.close(); + this.setHeartbeat(0); + return; + } + + this.#heartbeat(); + }, interval); + } + + #heartbeat() { + this.lastHeartbeatSent = Date.now(); + this.missedHeartbeats++; + + this.send({ + op: VoiceOpcodes.Heartbeat, + d: this.lastHeartbeatSent + }); + } + + #onMessage(event: WebSocket.MessageEvent) { + if (typeof event.data !== 'string') return; + + try { + const data = JSON.parse(event.data); + + this.debug?.('Received payload ${event.data}'); + + if (data.op === VoiceOpcodes.HeartbeatAck) { + this.missedHeartbeats = 0; + } + + this.emit(VoipWebSocketEvents.Packet, data); + } catch (error) { + this.emit(VoipWebSocketEvents.Error, error as Error); + } + } + + #onError(event: WebSocket.ErrorEvent | Error) { + this.emit(VoipWebSocketEvents.Error, event instanceof Error ? event : event.error); + } + + #onOpen(event: WebSocket.Event) { + this.emit(VoipWebSocketEvents.Open, event); + } + + #onClose(event: WebSocket.CloseEvent) { + this.emit(VoipWebSocketEvents.Close, event); + } +} diff --git a/packages/downloader/LICENSE b/packages/downloader/LICENSE deleted file mode 100644 index fe07fc7364..0000000000 --- a/packages/downloader/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 Androz2091 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/packages/downloader/README.md b/packages/downloader/README.md deleted file mode 100644 index e8f7ff5209..0000000000 --- a/packages/downloader/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Downloader -Extractor for **[discord-player](https://npmjs.com/package/discord-player)** using **[youtube-dl](https://npmjs.com/package/youtube-dl)**. - -# Installing - -```sh -npm i @discord-player/downloader -``` - -# Example -## General -```js -const downloader = require("@discord-player/downloader").Downloader; -const fs = require("fs"); -const url = "https://soundcloud.com/dogesounds/alan-walker-feat-k-391-ignite"; - -const stream = downloader.download(url); -stream.pipe(fs.createWriteStream("./song.mp3")); -``` - -## With Discord Player -```js -const downloader = require("@discord-player/downloader").Downloader; - -player.use("YOUTUBE_DL", downloader); // enables youtube-dl extractor for discord-player -``` \ No newline at end of file diff --git a/packages/downloader/package.json b/packages/downloader/package.json deleted file mode 100644 index 28c0611b1b..0000000000 --- a/packages/downloader/package.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "@discord-player/downloader", - "version": "3.0.2", - "description": "Stream extractor for discord-player via youtube-dl", - "keywords": [ - "discord-player", - "music", - "bot", - "discord.js", - "javascript", - "voip", - "lavalink", - "lavaplayer" - ], - "author": "Androz2091 ", - "homepage": "https://discord-player.js.org", - "license": "MIT", - "main": "dist/index.js", - "module": "dist/index.mjs", - "types": "dist/index.d.ts", - "files": [ - "dist" - ], - "repository": { - "type": "git", - "url": "git+https://github.com/Androz2091/discord-player.git" - }, - "scripts": { - "build": "tsup", - "build:check": "tsc --noEmit", - "lint": "eslint src --ext .ts --fix" - }, - "bugs": { - "url": "https://github.com/Androz2091/discord-player/issues" - }, - "devDependencies": { - "@discord-player/tsconfig": "workspace:^", - "tsup": "^7.2.0", - "typescript": "^5.2.2", - "vitest": "^0.34.6" - }, - "dependencies": { - "youtube-dl-exec": "^2.1.11" - }, - "typedoc": { - "entryPoint": "./src/index.ts", - "readmeFile": "./README.md", - "tsconfig": "./tsconfig.json" - } -} diff --git a/packages/downloader/src/Downloader.ts b/packages/downloader/src/Downloader.ts deleted file mode 100644 index 621cb69bd8..0000000000 --- a/packages/downloader/src/Downloader.ts +++ /dev/null @@ -1,92 +0,0 @@ -import ytdl from 'youtube-dl-exec'; - -export interface Info { - title: string; - duration: number; - thumbnail: string; - views: number; - author: string; - description: string; - url: string; - source: string; - engine: import('stream').Readable; -} - -export class Downloader { - constructor() { - return Downloader; - } - - /** - * Downloads stream through youtube-dl - * @param {string} url URL to download stream from - */ - static download(url: string) { - if (!url || typeof url !== 'string') throw new Error('Invalid url'); - - const ytdlProcess = ytdl.exec(url, { - output: '-', - quiet: true, - preferFreeFormats: true, - limitRate: '100K' - }); - - if (!ytdlProcess.stdout) throw new Error('No stdout'); - const stream = ytdlProcess.stdout; - - stream.on('error', () => { - if (!ytdlProcess.killed) ytdlProcess.kill(); - stream.resume(); - }); - - return stream; - } - - /** - * Returns stream info - * @param {string} url stream url - */ - static getInfo(url: string) { - // eslint-disable-next-line - return new Promise<{ playlist: any; info: Info[] }>(async (resolve, reject) => { - if (!url || typeof url !== 'string') reject(new Error('Invalid url')); - - const info = await ytdl(url, { - dumpSingleJson: true, - skipDownload: true, - simulate: true - }).catch(() => undefined); - if (!info) return resolve({ playlist: null, info: [] }); - - try { - const data = { - title: info.fulltitle || info.title || 'Attachment', - duration: (info.duration || 0) * 1000, - thumbnail: info.thumbnails ? info.thumbnails[0].url : info.thumbnail || 'https://upload.wikimedia.org/wikipedia/commons/2/2a/ITunes_12.2_logo.png', - views: info.view_count || 0, - author: info.uploader || info.channel || 'YouTubeDL Media', - description: info.description || '', - url: url, - source: info.extractor, - get engine() { - return Downloader.download(url); - } - } as Info; - - resolve({ playlist: null, info: [data] }); - } catch { - resolve({ playlist: null, info: [] }); - } - }); - } - - static validate(url: string) { - const REGEX = - /^(?:(?:https?|ftp):\/\/)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/; - return REGEX.test(url || ''); - } - - static get important() { - return true; - } -} diff --git a/packages/downloader/src/index.ts b/packages/downloader/src/index.ts deleted file mode 100644 index 4f98195ea1..0000000000 --- a/packages/downloader/src/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './Downloader'; -export * as ytdl from 'youtube-dl-exec'; - -// eslint-disable-next-line @typescript-eslint/no-inferrable-types -export const version: string = '[VI]{{inject}}[/VI]'; diff --git a/packages/downloader/tsconfig.json b/packages/downloader/tsconfig.json deleted file mode 100644 index 3c774c914c..0000000000 --- a/packages/downloader/tsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "@discord-player/tsconfig/base.json", - "include": [ - "src/**/*" - ] -} \ No newline at end of file diff --git a/packages/downloader/tsup.config.ts b/packages/downloader/tsup.config.ts deleted file mode 100644 index e161ba4d4d..0000000000 --- a/packages/downloader/tsup.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { defineConfig } from '../../tsup.config'; -import { esbuildPluginVersionInjector } from 'esbuild-plugin-version-injector'; - -export default defineConfig({ - esbuildPlugins: [esbuildPluginVersionInjector()] -}); diff --git a/packages/extractor/LICENSE b/packages/extractor/LICENSE deleted file mode 100644 index fe07fc7364..0000000000 --- a/packages/extractor/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 Androz2091 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/packages/extractor/README.md b/packages/extractor/README.md deleted file mode 100644 index b67a9d6c39..0000000000 --- a/packages/extractor/README.md +++ /dev/null @@ -1,99 +0,0 @@ -# Extractors - -Extractors for `discord-player`. - -# Example - -```js -const { YouTubeExtractor } = require('@discord-player/extractor'); -const player = useMainPlayer(); - -// enables youtube extractor -player.extractors.register(YouTubeExtractor); -``` - -# Available Extractors - -- Attachment (Remote, Local) -- Reverbnation -- SoundCloud -- Vimeo -- YouTube -- Spotify -- Apple Music - -# Lyrics - -```js -const { lyricsExtractor } = require('@discord-player/extractor'); -const lyricsClient = lyricsExtractor('api_key_or_leave_it_blank'); - -lyricsClient - .search('alan walker faded') - .then((x) => console.log(x)) - .catch(console.error); -``` - -## Response - -```js -{ - title: 'Faded', - id: 2396871, - thumbnail: 'https://images.genius.com/10db94c5c11e1bb1ac9cc917a6c59250.300x300x1.jpg', - image: 'https://images.genius.com/10db94c5c11e1bb1ac9cc917a6c59250.1000x1000x1.jpg', - url: 'https://genius.com/Alan-walker-faded-lyrics', - artist: { - name: 'Alan Walker', - id: 456537, - url: 'https://genius.com/artists/Alan-walker', - image: 'https://images.genius.com/5dc7f5c57981ba34e464414f7fc08ebf.1000x333x1.jpg' - }, - lyrics: '[Verse 1]\n' + - 'You were the shadow to my light\n' + - 'Did you feel us?\n' + - 'Another star, you fade away\n' + - 'Afraid our aim is out of sight\n' + - 'Wanna see us alight\n' + - '\n' + - '[Pre-Chorus 1]\n' + - 'Where are you now?\n' + - 'Where are you now?\n' + - 'Where are you now?\n' + - 'Was it all in my fantasy?\n' + - 'Where are you now?\n' + - 'Were you only imaginary?\n' + - '\n' + - '[Chorus]\n' + - 'Where are you now?\n' + - 'Atlantis, under the sea, under the sea\n' + - 'Where are you now? Another dream\n' + - "The monster's running wild inside of me\n" + - "I'm faded, I'm faded\n" + - "So lost, I'm faded, I'm faded\n" + - "So lost, I'm faded\n" + - '\n' + - '[Verse 2]\n' + - 'These shallow waters never met what I needed\n' + - "I'm letting go, a deeper dive\n" + - 'Eternal silence of the sea\n' + - "I'm breathing, alive\n" + - '\n' + - '[Pre-Chorus 2]\n' + - 'Where are you now?\n' + - 'Where are you now?\n' + - 'Under the bright but faded lights\n' + - 'You set my heart on fire\n' + - 'Where are you now?\n' + - 'Where are you now?\n' + - '\n' + - '[Chorus]\n' + - 'Where are you now?\n' + - 'Atlantis, under the sea, under the sea\n' + - 'Where are you now? Another dream\n' + - "The monster's running wild inside of me\n" + - "I'm faded, I'm faded\n" + - "So lost, I'm faded, I'm faded\n" + - "So lost, I'm faded" -} -``` diff --git a/packages/extractor/package.json b/packages/extractor/package.json deleted file mode 100644 index 3f0ad03e43..0000000000 --- a/packages/extractor/package.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "name": "@discord-player/extractor", - "version": "4.4.7", - "description": "Extractors for discord-player", - "keywords": [ - "discord-player", - "music", - "bot", - "discord.js", - "javascript", - "voip", - "lavalink", - "lavaplayer" - ], - "author": "Androz2091 ", - "homepage": "https://discord-player.js.org", - "license": "MIT", - "main": "dist/index.js", - "module": "dist/index.mjs", - "types": "dist/index.d.ts", - "files": [ - "dist" - ], - "repository": { - "type": "git", - "url": "git+https://github.com/Androz2091/discord-player.git" - }, - "scripts": { - "build": "tsup", - "build:check": "tsc --noEmit", - "lint": "eslint src --ext .ts --fix" - }, - "bugs": { - "url": "https://github.com/Androz2091/discord-player/issues" - }, - "devDependencies": { - "@discord-player/tsconfig": "workspace:^", - "@distube/ytdl-core": "^4.13.0", - "@types/node": "^18.11.18", - "discord-player": "workspace:^", - "mediaplex": "^0.0.7", - "play-dl": "^1.9.6", - "tsup": "^7.2.0", - "typescript": "^5.2.2", - "vitest": "^0.34.6", - "youtube-ext": "^1.1.14", - "yt-stream": "^1.4.8", - "ytdl-core": "^4.11.4" - }, - "dependencies": { - "file-type": "^16.5.4", - "genius-lyrics": "^4.4.6", - "isomorphic-unfetch": "^4.0.2", - "node-html-parser": "^6.1.4", - "reverbnation-scraper": "^2.0.0", - "soundcloud.ts": "^0.5.2", - "spotify-url-info": "^3.2.6", - "youtube-sr": "^4.3.9" - }, - "typedoc": { - "entryPoint": "./src/index.ts", - "readmeFile": "./README.md", - "tsconfig": "./tsconfig.json" - } -} diff --git a/packages/extractor/src/extractors/AppleMusicExtractor.ts b/packages/extractor/src/extractors/AppleMusicExtractor.ts deleted file mode 100644 index e3732ef9fa..0000000000 --- a/packages/extractor/src/extractors/AppleMusicExtractor.ts +++ /dev/null @@ -1,267 +0,0 @@ -import { ExtractorInfo, ExtractorSearchContext, ExtractorStreamable, GuildQueueHistory, Playlist, QueryType, SearchQueryType, Track, Util } from 'discord-player'; -import { AppleMusic } from '../internal'; -import { Readable } from 'stream'; -import { StreamFN, pullYTMetadata } from './common/helper'; -import { BridgeProvider } from './common/BridgeProvider'; -import { BridgedExtractor } from './BridgedExtractor'; - -export interface AppleMusicExtractorInit { - createStream?: (ext: AppleMusicExtractor, url: string) => Promise; - bridgeProvider?: BridgeProvider; -} - -export class AppleMusicExtractor extends BridgedExtractor { - public static identifier = 'com.discord-player.applemusicextractor' as const; - private _stream!: StreamFN; - - public async activate(): Promise { - this.protocols = ['amsearch', 'applemusic']; - const fn = this.options.createStream; - - if (typeof fn === 'function') { - this._stream = (q: string) => { - return fn(this, q); - }; - } - } - - public async deactivate() { - this.protocols = []; - } - - public async validate(query: string, type?: SearchQueryType | null | undefined): Promise { - // prettier-ignore - return ([ - QueryType.APPLE_MUSIC_ALBUM, - QueryType.APPLE_MUSIC_PLAYLIST, - QueryType.APPLE_MUSIC_SONG, - QueryType.APPLE_MUSIC_SEARCH, - QueryType.AUTO, - QueryType.AUTO_SEARCH - ]).some((t) => t === type); - } - - public async getRelatedTracks(track: Track, history: GuildQueueHistory) { - if (track.queryType === QueryType.APPLE_MUSIC_SONG) { - const data = await this.handle(track.author || track.title, { - type: QueryType.APPLE_MUSIC_SEARCH, - requestedBy: track.requestedBy - }); - - const unique = data.tracks.filter((t) => !history.tracks.some((h) => h.url === t.url)); - return unique.length > 0 ? this.createResponse(null, unique) : this.createResponse(); - } - - return this.createResponse(); - } - - public async handle(query: string, context: ExtractorSearchContext): Promise { - if (context.protocol === 'amsearch') context.type = QueryType.APPLE_MUSIC_SEARCH; - - switch (context.type) { - case QueryType.AUTO: - case QueryType.AUTO_SEARCH: - case QueryType.APPLE_MUSIC_SEARCH: { - const data = await AppleMusic.search(query); - if (!data || !data.length) return this.createResponse(); - const tracks = data.map( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (m: any) => { - const track: Track = new Track(this.context.player, { - author: m.artist.name, - description: m.title, - duration: typeof m.duration === 'number' ? Util.buildTimeCode(Util.parseMS(m.duration)) : m.duration, - thumbnail: m.thumbnail, - title: m.title, - url: m.url, - views: 0, - source: 'apple_music', - requestedBy: context.requestedBy, - queryType: 'appleMusicSong', - metadata: { - source: m, - bridge: null - }, - requestMetadata: async () => { - return { - source: m, - bridge: this.options.bridgeProvider ? (await this.options.bridgeProvider.resolve(this, track)).data : await pullYTMetadata(this, track) - }; - } - }); - - track.extractor = this; - - return track; - } - ); - - return this.createResponse(null, tracks); - } - case QueryType.APPLE_MUSIC_ALBUM: { - const info = await AppleMusic.getAlbumInfo(query); - if (!info) return this.createResponse(); - - const playlist = new Playlist(this.context.player, { - author: { - name: info.artist.name, - url: '' - }, - description: info.title, - id: info.id, - source: 'apple_music', - thumbnail: info.thumbnail, - title: info.title, - tracks: [], - type: 'album', - url: info.url, - rawPlaylist: info - }); - - playlist.tracks = info.tracks.map( - ( - m: any // eslint-disable-line - ) => { - const track: Track = new Track(this.context.player, { - author: m.artist.name, - description: m.title, - duration: typeof m.duration === 'number' ? Util.buildTimeCode(Util.parseMS(m.duration)) : m.duration, - thumbnail: m.thumbnail, - title: m.title, - url: m.url, - views: 0, - source: 'apple_music', - requestedBy: context.requestedBy, - queryType: 'appleMusicSong', - metadata: { - source: info, - bridge: null - }, - requestMetadata: async () => { - return { - source: info, - bridge: (await this.options.bridgeProvider?.resolve(this, track))?.data - }; - } - }); - track.playlist = playlist; - track.extractor = this; - return track; - } - ); - - return { playlist, tracks: playlist.tracks }; - } - case QueryType.APPLE_MUSIC_PLAYLIST: { - const info = await AppleMusic.getPlaylistInfo(query); - if (!info) return this.createResponse(); - - const playlist = new Playlist(this.context.player, { - author: { - name: info.artist.name, - url: '' - }, - description: info.title, - id: info.id, - source: 'apple_music', - thumbnail: info.thumbnail, - title: info.title, - tracks: [], - type: 'playlist', - url: info.url, - rawPlaylist: info - }); - - playlist.tracks = info.tracks.map( - ( - m: any // eslint-disable-line - ) => { - const track: Track = new Track(this.context.player, { - author: m.artist.name, - description: m.title, - duration: typeof m.duration === 'number' ? Util.buildTimeCode(Util.parseMS(m.duration)) : m.duration, - thumbnail: m.thumbnail, - title: m.title, - url: m.url, - views: 0, - source: 'apple_music', - requestedBy: context.requestedBy, - queryType: 'appleMusicSong', - metadata: { - source: m, - bridge: null - }, - requestMetadata: async () => { - return { - source: m, - bridge: this.options.bridgeProvider ? (await this.options.bridgeProvider.resolve(this, track)).data : await pullYTMetadata(this, track) - }; - } - }); - - track.playlist = playlist; - track.extractor = this; - - return track; - } - ); - - return { playlist, tracks: playlist.tracks }; - } - case QueryType.APPLE_MUSIC_SONG: { - const info = await AppleMusic.getSongInfo(query); - if (!info) return this.createResponse(); - - const track: Track = new Track(this.context.player, { - author: info.artist.name, - description: info.title, - duration: typeof info.duration === 'number' ? Util.buildTimeCode(Util.parseMS(info.duration)) : info.duration, - thumbnail: info.thumbnail, - title: info.title, - url: info.url, - views: 0, - source: 'apple_music', - requestedBy: context.requestedBy, - queryType: context.type, - metadata: { - source: info, - bridge: null - }, - requestMetadata: async () => { - return { - source: info, - bridge: this.options.bridgeProvider ? (await this.options.bridgeProvider.resolve(this, track)).data : await pullYTMetadata(this, track) - }; - }, - }); - - track.extractor = this; - - return { playlist: null, tracks: [track] }; - } - default: - return { playlist: null, tracks: [] }; - } - } - - public async stream(info: Track): Promise { - if (this._stream) { - const stream = await this._stream(info.url, this); - if (typeof stream === 'string') return stream; - return stream; - } - - const provider = this.bridgeProvider; - if (!provider) throw new Error(`Could not find bridge provider for '${this.constructor.name}'`); - - const data = await provider.resolve(this, info); - if (!data) throw new Error('Failed to bridge this track'); - - info.setMetadata({ - ...(info.metadata || {}), - bridge: data.data - }); - - return await provider.stream(data); - } -} diff --git a/packages/extractor/src/extractors/AttachmentExtractor.ts b/packages/extractor/src/extractors/AttachmentExtractor.ts deleted file mode 100644 index e8c0bd6b6b..0000000000 --- a/packages/extractor/src/extractors/AttachmentExtractor.ts +++ /dev/null @@ -1,211 +0,0 @@ -// prettier-ignore -import { - BaseExtractor, - ExtractorInfo, - ExtractorSearchContext, - QueryType, - SearchQueryType, - Track, - Util -} from 'discord-player'; -import type { IncomingMessage } from 'http'; -import { createReadStream, existsSync } from 'fs'; -import { downloadStream } from '../internal/downloader'; -import * as fileType from 'file-type'; -import path from 'path'; -import { stat } from 'fs/promises'; - -const ATTACHMENT_HEADER = ['audio/', 'video/', 'application/ogg'] as const; - -export class AttachmentExtractor extends BaseExtractor { - public static identifier = 'com.discord-player.attachmentextractor' as const; - - // use lowest priority to avoid conflict with other extractors - public priority = 0; - - public async validate(query: string, type?: SearchQueryType | null | undefined): Promise { - if (typeof query !== 'string') return false; - return ([QueryType.ARBITRARY, QueryType.FILE] as SearchQueryType[]).some((r) => r === type); - } - - public async getRelatedTracks(track: Track) { - void track; - return this.createResponse(); - } - - public async handle(query: string, context: ExtractorSearchContext): Promise { - switch (context.type) { - case QueryType.ARBITRARY: { - const data = (await downloadStream(query, context.requestOptions)) as IncomingMessage; - if (!ATTACHMENT_HEADER.some((r) => !!data.headers['content-type']?.startsWith(r))) return this.emptyResponse(); - - const trackInfo = { - title: ( - query - .split('/') - .filter((x) => x.length) - .pop() ?? 'Attachment' - ) - .split('?')[0] - .trim(), - duration: 0, - thumbnail: 'https://upload.wikimedia.org/wikipedia/commons/2/2a/ITunes_12.2_logo.png', - engine: query, - // eslint-disable-next-line - author: ((data as any).client?.servername as string) || 'Attachment', - // eslint-disable-next-line - description: ((data as any).client?.servername as string) || 'Attachment', - url: data.url || query - }; - - try { - // eslint-disable-next-line - const mediaplex = require('mediaplex') as typeof import('mediaplex'); - const timeout = this.context.player.options.probeTimeout ?? 5000; - - const { result, stream } = (await Promise.race([ - mediaplex.probeStream(data), - new Promise((_, r) => { - setTimeout(() => r(new Error('Timeout')), timeout); - }) - ])) as Awaited>; - - if (result) { - trackInfo.duration = result.duration * 1000; - - const metadata = mediaplex.readMetadata(result); - if (metadata.author) trackInfo.author = metadata.author; - if (metadata.title) trackInfo.title = metadata.title; - - trackInfo.description = `${trackInfo.title} by ${trackInfo.author}`; - } - - stream.destroy(); - } catch { - // - } - - const track = new Track(this.context.player, { - title: trackInfo.title, - url: trackInfo.url, - duration: Util.buildTimeCode(Util.parseMS(trackInfo.duration)), - description: trackInfo.description, - thumbnail: trackInfo.thumbnail, - views: 0, - author: trackInfo.author, - requestedBy: context.requestedBy, - source: 'arbitrary', - engine: trackInfo.url, - queryType: context.type, - metadata: trackInfo, - async requestMetadata() { - return trackInfo; - } - }); - - track.extractor = this; - - // @ts-expect-error - track.raw.isFile = false; - - return { playlist: null, tracks: [track] }; - } - case QueryType.FILE: { - if (!existsSync(query)) return this.emptyResponse(); - const fstat = await stat(query); - if (!fstat.isFile()) return this.emptyResponse(); - const mime = await fileType.fromFile(query).catch(() => null); - if (!mime || !ATTACHMENT_HEADER.some((r) => !!mime.mime.startsWith(r))) return this.emptyResponse(); - - const trackInfo = { - title: path.basename(query) || 'Attachment', - duration: 0, - thumbnail: 'https://upload.wikimedia.org/wikipedia/commons/2/2a/ITunes_12.2_logo.png', - engine: query, - author: 'Attachment', - description: 'Attachment', - url: query - }; - - try { - // eslint-disable-next-line - const mediaplex = require('mediaplex') as typeof import('mediaplex'); - - const timeout = this.context.player.options.probeTimeout ?? 5000; - - const { result, stream } = (await Promise.race([ - mediaplex.probeStream( - createReadStream(query, { - start: 0, - end: 1024 * 1024 * 10 - }) - ), - new Promise((_, r) => { - setTimeout(() => r(new Error('Timeout')), timeout); - }) - ])) as Awaited>; - - if (result) { - trackInfo.duration = result.duration * 1000; - - const metadata = mediaplex.readMetadata(result); - if (metadata.author) trackInfo.author = metadata.author; - if (metadata.title) trackInfo.title = metadata.title; - - trackInfo.description = `${trackInfo.title} by ${trackInfo.author}`; - } - - stream.destroy(); - } catch { - // - } - - const track = new Track(this.context.player, { - title: trackInfo.title, - url: trackInfo.url, - duration: Util.buildTimeCode(Util.parseMS(trackInfo.duration)), - description: trackInfo.description, - thumbnail: trackInfo.thumbnail, - views: 0, - author: trackInfo.author, - requestedBy: context.requestedBy, - source: 'arbitrary', - engine: trackInfo.url, - queryType: context.type, - metadata: trackInfo, - async requestMetadata() { - return trackInfo; - } - }); - - track.extractor = this; - - // @ts-expect-error - track.raw.isFile = true; - - return { playlist: null, tracks: [track] }; - } - default: - return this.emptyResponse(); - } - } - - public emptyResponse(): ExtractorInfo { - return { playlist: null, tracks: [] }; - } - - public async stream(info: Track) { - const engine = info.raw.engine as string; - // @ts-expect-error - const isFile = info.raw.isFile as boolean; - - if (!engine) throw new Error('Could not find stream source'); - - if (!isFile) { - return engine; - // return await downloadStream(engine); - } - - return createReadStream(engine); - } -} diff --git a/packages/extractor/src/extractors/BridgedExtractor.ts b/packages/extractor/src/extractors/BridgedExtractor.ts deleted file mode 100644 index c0650f26e2..0000000000 --- a/packages/extractor/src/extractors/BridgedExtractor.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { BaseExtractor, type ExtractorExecutionContext } from 'discord-player'; -import { BridgeProvider, BridgeSource, defaultBridgeProvider, IBridgeSource } from './common/BridgeProvider'; - -export interface BridgedOption { - bridgeProvider?: BridgeProvider; -} - -export class BridgedExtractor extends BaseExtractor { - public constructor(context: ExtractorExecutionContext, options?: T | undefined) { - super(context, options); - } - - public setBridgeProvider(provider: BridgeProvider) { - this.options.bridgeProvider = provider; - } - - public setBridgeProviderSource(source: BridgeSource | IBridgeSource) { - this.bridgeProvider.setBridgeSource(source); - } - - public get bridgeProvider() { - return this.options.bridgeProvider ?? defaultBridgeProvider; - } -} diff --git a/packages/extractor/src/extractors/LyricsExtractor.ts b/packages/extractor/src/extractors/LyricsExtractor.ts deleted file mode 100644 index 9e0740054a..0000000000 --- a/packages/extractor/src/extractors/LyricsExtractor.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Client as GeniusClient } from 'genius-lyrics'; - -// lazy load client -let client: GeniusClient; - -export function lyricsExtractor(apiKey?: string, force?: boolean) { - if (!client && !force) client = new GeniusClient(apiKey); - return { search, client }; -} - -function search(query: string) { - return new Promise((resolve, reject) => { - if (typeof query !== 'string') return reject(new TypeError(`Expected search query to be a string, received "${typeof query}"!`)); - - client.songs - .search(query) - .then(async (songs) => { - const data = { - title: songs[0].title, - fullTitle: songs[0].fullTitle, - id: songs[0].id, - thumbnail: songs[0].thumbnail, - image: songs[0].image, - url: songs[0].url, - artist: { - name: songs[0].artist.name, - id: songs[0].artist.id, - url: songs[0].artist.url, - image: songs[0].artist.image - }, - lyrics: await songs[0].lyrics(false) - }; - - resolve(data); - }) - .catch(() => { - reject(new Error('Could not parse lyrics')); - }); - }); -} - -export interface LyricsData { - title: string; - fullTitle: string; - id: number; - thumbnail: string; - image: string; - url: string; - artist: { - name: string; - id: number; - url: string; - image: string; - }; - lyrics: string; -} diff --git a/packages/extractor/src/extractors/ReverbnationExtractor.ts b/packages/extractor/src/extractors/ReverbnationExtractor.ts deleted file mode 100644 index 093c9a4681..0000000000 --- a/packages/extractor/src/extractors/ReverbnationExtractor.ts +++ /dev/null @@ -1,79 +0,0 @@ -// prettier-ignore -import { - BaseExtractor, - ExtractorInfo, - ExtractorSearchContext, - QueryType, - SearchQueryType, - Track, - Util -} from 'discord-player'; -import reverbnation from 'reverbnation-scraper'; - -export class ReverbnationExtractor extends BaseExtractor { - public static identifier = 'com.discord-player.reverbnationextractor' as const; - - public async validate(query: string, type?: SearchQueryType | null | undefined): Promise { - if (typeof query !== 'string') return false; - return ([QueryType.REVERBNATION] as SearchQueryType[]).some((r) => r === type); - } - - public async getRelatedTracks(track: Track) { - void track; - return this.createResponse(); - } - - public async handle(query: string, context: ExtractorSearchContext): Promise { - switch (context.type) { - case QueryType.REVERBNATION: { - const trackInfo = await reverbnation.getInfo(query).catch(Util.noop); - - if (!trackInfo) return this.emptyResponse(); - - const track = new Track(this.context.player, { - title: trackInfo.title, - url: trackInfo.url, - duration: Util.buildTimeCode(Util.parseMS(trackInfo.duration)), - description: trackInfo.lyrics || `${trackInfo.title} by ${trackInfo.artist.name}`, - thumbnail: trackInfo.thumbnail, - views: 0, - author: trackInfo.artist.name, - requestedBy: context.requestedBy, - source: 'arbitrary', - engine: trackInfo.streamURL, - queryType: context.type, - metadata: trackInfo, - async requestMetadata() { - return trackInfo; - } - }); - - track.extractor = this; - - return { playlist: null, tracks: [track] }; - } - default: - return this.emptyResponse(); - } - } - - public emptyResponse(): ExtractorInfo { - return { playlist: null, tracks: [] }; - } - - public async stream(info: Track) { - const engine = info.raw.engine as string; - if (engine) { - return engine; - } - - const track = await reverbnation.getInfo(info.url).catch(Util.noop); - if (!track || !track.streamURL) throw new Error('Could not extract stream from this source'); - - info.raw.engine = { - streamURL: track.streamURL - }; - - return track.streamURL; - } -} diff --git a/packages/extractor/src/extractors/SoundCloudExtractor.ts b/packages/extractor/src/extractors/SoundCloudExtractor.ts deleted file mode 100644 index 98b76b550f..0000000000 --- a/packages/extractor/src/extractors/SoundCloudExtractor.ts +++ /dev/null @@ -1,225 +0,0 @@ -// prettier-ignore -import { - BaseExtractor, - ExtractorInfo, - ExtractorSearchContext, - type GuildQueueHistory, - Playlist, - QueryType, - SearchQueryType, - Track, - Util -} from 'discord-player'; -import * as SoundCloud from 'soundcloud.ts'; -import { filterSoundCloudPreviews } from './common/helper'; - -export interface SoundCloudExtractorInit { - clientId?: string; - oauthToken?: string; - proxy?: string; -} - -export class SoundCloudExtractor extends BaseExtractor { - public static identifier = 'com.discord-player.soundcloudextractor' as const; - public static instance: SoundCloudExtractor | null = null; - - public internal = new SoundCloud.default({ - clientId: this.options.clientId, - oauthToken: this.options.oauthToken, - proxy: this.options.proxy - }); - - public async activate(): Promise { - this.protocols = ['scsearch', 'soundcloud']; - SoundCloudExtractor.instance = this; - } - - public async deactivate(): Promise { - this.protocols = []; - SoundCloudExtractor.instance = null; - } - - public async validate(query: string, type?: SearchQueryType | null | undefined): Promise { - if (typeof query !== 'string') return false; - // prettier-ignore - return ([ - QueryType.SOUNDCLOUD, - QueryType.SOUNDCLOUD_PLAYLIST, - QueryType.SOUNDCLOUD_SEARCH, - QueryType.SOUNDCLOUD_TRACK, - QueryType.AUTO, - QueryType.AUTO_SEARCH - ] as SearchQueryType[]).some((r) => r === type); - } - - public async getRelatedTracks(track: Track, history: GuildQueueHistory) { - if (track.queryType === QueryType.SOUNDCLOUD_TRACK) { - const data = await this.internal.tracks.relatedV2(track.url, 5); - - const unique = filterSoundCloudPreviews(data).filter((t) => !history.tracks.some((h) => h.url === t.permalink_url)); - - return this.createResponse( - null, - (unique.length > 0 ? unique : data).map((trackInfo) => { - const newTrack = new Track(this.context.player, { - title: trackInfo.title, - url: trackInfo.permalink_url, - duration: Util.buildTimeCode(Util.parseMS(trackInfo.duration)), - description: trackInfo.description ?? '', - thumbnail: trackInfo.artwork_url, - views: trackInfo.playback_count, - author: trackInfo.user.username, - requestedBy: track.requestedBy, - source: 'soundcloud', - engine: trackInfo, - queryType: QueryType.SOUNDCLOUD_TRACK, - metadata: trackInfo, - requestMetadata: async () => { - return trackInfo; - }, - cleanTitle: trackInfo.title - }); - - newTrack.extractor = this; - - return newTrack; - }) - ); - } - - return this.createResponse(); - } - - public async handle(query: string, context: ExtractorSearchContext): Promise { - if (context.protocol === 'scsearch') context.type = QueryType.SOUNDCLOUD_SEARCH; - switch (context.type) { - case QueryType.SOUNDCLOUD_TRACK: { - const trackInfo = await this.internal.tracks.getV2(query).catch(Util.noop); - - if (!trackInfo) return this.emptyResponse(); - - const track = new Track(this.context.player, { - title: trackInfo.title, - url: trackInfo.permalink_url, - duration: Util.buildTimeCode(Util.parseMS(trackInfo.duration)), - description: trackInfo.description ?? '', - thumbnail: trackInfo.artwork_url, - views: trackInfo.playback_count, - author: trackInfo.user.username, - requestedBy: context.requestedBy, - source: 'soundcloud', - engine: trackInfo, - queryType: context.type, - metadata: trackInfo, - requestMetadata: async () => { - return trackInfo; - }, - cleanTitle: trackInfo.title - }); - - track.extractor = this; - - return { playlist: null, tracks: [track] }; - } - case QueryType.SOUNDCLOUD_PLAYLIST: { - const data = await this.internal.playlists.getV2(query).catch(Util.noop); - if (!data) return { playlist: null, tracks: [] }; - - const res = new Playlist(this.context.player, { - title: data.title, - description: data.description ?? '', - thumbnail: data.artwork_url ?? data.tracks[0].artwork_url, - type: 'playlist', - source: 'soundcloud', - author: { - name: data.user.username, - url: data.user.permalink_url - }, - tracks: [], - id: `${data.id}`, - url: data.permalink_url, - rawPlaylist: data - }); - - for (const song of data.tracks) { - const track = new Track(this.context.player, { - title: song.title, - description: song.description ?? '', - author: song.user.username, - url: song.permalink_url, - thumbnail: song.artwork_url, - duration: Util.buildTimeCode(Util.parseMS(song.duration)), - views: song.playback_count, - requestedBy: context.requestedBy, - playlist: res, - source: 'soundcloud', - engine: song, - queryType: context.type, - metadata: song, - requestMetadata: async () => { - return song; - }, - cleanTitle: song.title - }); - track.extractor = this; - track.playlist = res; - res.tracks.push(track); - } - - return { playlist: res, tracks: res.tracks }; - } - default: { - let tracks = await this.internal.tracks - .searchV2({ q: query }) - .then((t) => t.collection) - .catch(Util.noop); - - if (!tracks) tracks = await this.internal.tracks.searchAlt(query).catch(Util.noop); - - if (!tracks || !tracks.length) return this.emptyResponse(); - - tracks = filterSoundCloudPreviews(tracks); - - const resolvedTracks: Track[] = []; - - for (const trackInfo of tracks) { - if (!trackInfo.streamable) continue; - const track = new Track(this.context.player, { - title: trackInfo.title, - url: trackInfo.permalink_url, - duration: Util.buildTimeCode(Util.parseMS(trackInfo.duration)), - description: trackInfo.description ?? '', - thumbnail: trackInfo.artwork_url, - views: trackInfo.playback_count, - author: trackInfo.user.username, - requestedBy: context.requestedBy, - source: 'soundcloud', - engine: trackInfo, - queryType: 'soundcloudTrack', - metadata: trackInfo, - requestMetadata: async () => { - return trackInfo; - } - }); - - track.extractor = this; - - resolvedTracks.push(track); - } - - return { playlist: null, tracks: resolvedTracks }; - } - } - } - - public emptyResponse(): ExtractorInfo { - return { playlist: null, tracks: [] }; - } - - public async stream(info: Track) { - const url = await this.internal.util.streamLink(info.url).catch(Util.noop); - if (!url) throw new Error('Could not extract stream from this track source'); - - return url; - } -} diff --git a/packages/extractor/src/extractors/SpotifyExtractor.ts b/packages/extractor/src/extractors/SpotifyExtractor.ts deleted file mode 100644 index 5042137886..0000000000 --- a/packages/extractor/src/extractors/SpotifyExtractor.ts +++ /dev/null @@ -1,378 +0,0 @@ -import { ExtractorInfo, ExtractorSearchContext, ExtractorStreamable, Playlist, QueryType, SearchQueryType, Track, Util } from 'discord-player'; -import type { Readable } from 'stream'; -import { StreamFN, fetch, pullYTMetadata } from './common/helper'; -import spotify, { Spotify, SpotifyAlbum, SpotifyPlaylist, SpotifySong } from 'spotify-url-info'; -import { SpotifyAPI } from '../internal'; -import { BridgeProvider } from './common/BridgeProvider'; -import { BridgedExtractor } from './BridgedExtractor'; - -const re = /^(?:https:\/\/open\.spotify\.com\/(intl-([a-z]|[A-Z]){0,3}\/)?(?:user\/[A-Za-z0-9]+\/)?|spotify:)(album|playlist|track)(?:[/:])([A-Za-z0-9]+).*$/; - -export interface SpotifyExtractorInit { - clientId?: string | null; - clientSecret?: string | null; - createStream?: (ext: SpotifyExtractor, url: string) => Promise; - bridgeProvider?: BridgeProvider; -} - -export class SpotifyExtractor extends BridgedExtractor { - public static identifier = 'com.discord-player.spotifyextractor' as const; - private _stream!: StreamFN; - private _lib!: Spotify; - private _credentials = { - clientId: this.options.clientId || process.env.DP_SPOTIFY_CLIENT_ID || null, - clientSecret: this.options.clientSecret || process.env.DP_SPOTIFY_CLIENT_SECRET || null - }; - public internal = new SpotifyAPI(this._credentials); - - public async activate(): Promise { - this.protocols = ['spsearch', 'spotify']; - this._lib = spotify(fetch); - if (this.internal.isTokenExpired()) await this.internal.requestToken(); - - const fn = this.options.createStream; - if (typeof fn === 'function') { - this._stream = (q: string) => { - return fn(this, q); - }; - } - } - - public async deactivate() { - this._stream = undefined as unknown as StreamFN; - this._lib = undefined as unknown as Spotify; - this.protocols = []; - } - - public async validate(query: string, type?: SearchQueryType | null | undefined): Promise { - // prettier-ignore - return ([ - QueryType.SPOTIFY_ALBUM, - QueryType.SPOTIFY_PLAYLIST, - QueryType.SPOTIFY_SONG, - QueryType.SPOTIFY_SEARCH, - QueryType.AUTO, - QueryType.AUTO_SEARCH - ]).some((t) => t === type); - } - - public async getRelatedTracks(track: Track) { - return await this.handle(track.author || track.title, { - type: QueryType.SPOTIFY_SEARCH, - requestedBy: track.requestedBy - }); - } - - public async handle(query: string, context: ExtractorSearchContext): Promise { - if (context.protocol === 'spsearch') context.type = QueryType.SPOTIFY_SEARCH; - switch (context.type) { - case QueryType.AUTO: - case QueryType.AUTO_SEARCH: - case QueryType.SPOTIFY_SEARCH: { - const data = await this.internal.search(query); - if (!data) return this.createResponse(); - - return this.createResponse( - null, - data.map((spotifyData) => { - const track: Track = new Track(this.context.player, { - title: spotifyData.title, - description: `${spotifyData.title} by ${spotifyData.artist}`, - author: spotifyData.artist ?? 'Unknown Artist', - url: spotifyData.url, - thumbnail: spotifyData.thumbnail || 'https://www.scdn.co/i/_global/twitter_card-default.jpg', - duration: Util.buildTimeCode(Util.parseMS(spotifyData.duration ?? 0)), - views: 0, - requestedBy: context.requestedBy, - source: 'spotify', - queryType: QueryType.SPOTIFY_SONG, - metadata: { - source: spotifyData, - bridge: null - }, - requestMetadata: async () => { - return { - source: spotifyData, - bridge: (await this.options.bridgeProvider?.resolve(this, track))?.data - }; - } - }); - - track.extractor = this; - - return track; - }) - ); - } - case QueryType.SPOTIFY_SONG: { - const spotifyData: SpotifySong | void = await this._lib.getData(query, context.requestOptions as unknown as RequestInit).catch(Util.noop); - if (!spotifyData) return { playlist: null, tracks: [] }; - const spotifyTrack: Track = new Track(this.context.player, { - title: spotifyData.title, - description: `${spotifyData.name} by ${spotifyData.artists.map((m) => m.name).join(', ')}`, - author: spotifyData.artists[0]?.name ?? 'Unknown Artist', - url: spotifyData.id ? `https://open.spotify.com/track/${spotifyData.id}` : query, - thumbnail: spotifyData.coverArt?.sources?.[0]?.url || 'https://www.scdn.co/i/_global/twitter_card-default.jpg', - duration: Util.buildTimeCode(Util.parseMS(spotifyData.duration ?? spotifyData.maxDuration ?? 0)), - views: 0, - requestedBy: context.requestedBy, - source: 'spotify', - queryType: context.type, - metadata: { - source: spotifyData, - bridge: null - }, - requestMetadata: async () => { - return { - source: spotifyData, - bridge: this.options.bridgeProvider ? (await this.options.bridgeProvider.resolve(this, spotifyTrack)).data : await pullYTMetadata(this, spotifyTrack) - }; - } - }); - - spotifyTrack.extractor = this; - - return { playlist: null, tracks: [spotifyTrack] }; - } - case QueryType.SPOTIFY_PLAYLIST: { - try { - const { queryType, id } = this.parse(query); - if (queryType !== 'playlist') throw 'err'; - - const spotifyPlaylist = await this.internal.getPlaylist(id); - if (!spotifyPlaylist) throw 'err'; - - const playlist = new Playlist(this.context.player, { - title: spotifyPlaylist.name, - description: spotifyPlaylist.name ?? '', - thumbnail: spotifyPlaylist.thumbnail ?? 'https://www.scdn.co/i/_global/twitter_card-default.jpg', - type: 'playlist', - source: 'spotify', - author: { - name: spotifyPlaylist.author ?? 'Unknown Artist', - url: null as unknown as string - }, - tracks: [], - id: spotifyPlaylist.id, - url: spotifyPlaylist.url || query, - rawPlaylist: spotifyPlaylist - }); - - playlist.tracks = spotifyPlaylist.tracks.map((spotifyData) => { - const data: Track = new Track(this.context.player, { - title: spotifyData.title, - description: `${spotifyData.title} by ${spotifyData.artist}`, - author: spotifyData.artist ?? 'Unknown Artist', - url: spotifyData.url, - thumbnail: spotifyData.thumbnail || 'https://www.scdn.co/i/_global/twitter_card-default.jpg', - duration: Util.buildTimeCode(Util.parseMS(spotifyData.duration ?? 0)), - views: 0, - requestedBy: context.requestedBy, - source: 'spotify', - queryType: QueryType.SPOTIFY_SONG, - metadata: { - source: spotifyData, - bridge: null - }, - requestMetadata: async () => { - return { - source: spotifyData, - bridge: this.options.bridgeProvider ? (await this.options.bridgeProvider.resolve(this, data)).data : await pullYTMetadata(this, data) - }; - } - }); - data.extractor = this; - data.playlist = playlist; - return data; - }) as Track[]; - - return { playlist, tracks: playlist.tracks }; - } catch { - const spotifyPlaylist: SpotifyPlaylist | void = await this._lib.getData(query, context.requestOptions as unknown as RequestInit).catch(Util.noop); - if (!spotifyPlaylist) return { playlist: null, tracks: [] }; - - const playlist = new Playlist(this.context.player, { - title: spotifyPlaylist.name ?? spotifyPlaylist.title, - description: spotifyPlaylist.title ?? '', - thumbnail: spotifyPlaylist.coverArt?.sources?.[0]?.url ?? 'https://www.scdn.co/i/_global/twitter_card-default.jpg', - type: spotifyPlaylist.type, - source: 'spotify', - author: { - name: spotifyPlaylist.subtitle ?? 'Unknown Artist', - url: null as unknown as string - }, - tracks: [], - id: spotifyPlaylist.id, - url: spotifyPlaylist.id ? `https://open.spotify.com/playlist/${spotifyPlaylist.id}` : query, - rawPlaylist: spotifyPlaylist - }); - - playlist.tracks = spotifyPlaylist.trackList.map((m) => { - const data: Track = new Track(this.context.player, { - title: m.title ?? '', - description: m.title ?? '', - author: m.subtitle ?? 'Unknown Artist', - url: m.uid ? `https://open.spotify.com/tracks/${m.uid}` : query, - thumbnail: 'https://www.scdn.co/i/_global/twitter_card-default.jpg', - duration: Util.buildTimeCode(Util.parseMS(m.duration)), - views: 0, - requestedBy: context.requestedBy, - playlist, - source: 'spotify', - queryType: 'spotifySong', - metadata: { - source: m, - bridge: null - }, - requestMetadata: async () => { - return { - source: m, - bridge: this.options.bridgeProvider ? (await this.options.bridgeProvider.resolve(this, data)).data : await pullYTMetadata(this, data) - }; - } - }); - data.extractor = this; - data.playlist = playlist; - return data; - }) as Track[]; - - return { playlist, tracks: playlist.tracks }; - } - } - case QueryType.SPOTIFY_ALBUM: { - try { - const { queryType, id } = this.parse(query); - if (queryType !== 'album') throw 'err'; - - const spotifyAlbum = await this.internal.getAlbum(id); - if (!spotifyAlbum) throw 'err'; - - const playlist = new Playlist(this.context.player, { - title: spotifyAlbum.name, - description: spotifyAlbum.name ?? '', - thumbnail: spotifyAlbum.thumbnail ?? 'https://www.scdn.co/i/_global/twitter_card-default.jpg', - type: 'album', - source: 'spotify', - author: { - name: spotifyAlbum.author ?? 'Unknown Artist', - url: null as unknown as string - }, - tracks: [], - id: spotifyAlbum.id, - url: spotifyAlbum.url || query, - rawPlaylist: spotifyAlbum - }); - - playlist.tracks = spotifyAlbum.tracks.map((spotifyData) => { - const data: Track = new Track(this.context.player, { - title: spotifyData.title, - description: `${spotifyData.title} by ${spotifyData.artist}`, - author: spotifyData.artist ?? 'Unknown Artist', - url: spotifyData.url, - thumbnail: spotifyData.thumbnail || 'https://www.scdn.co/i/_global/twitter_card-default.jpg', - duration: Util.buildTimeCode(Util.parseMS(spotifyData.duration ?? 0)), - views: 0, - requestedBy: context.requestedBy, - source: 'spotify', - queryType: QueryType.SPOTIFY_SONG, - metadata: { - source: spotifyData, - bridge: null - }, - requestMetadata: async () => { - return { - source: spotifyData, - bridge: this.options.bridgeProvider ? (await this.options.bridgeProvider.resolve(this, data)).data : await pullYTMetadata(this, data) - }; - } - }); - data.extractor = this; - data.playlist = playlist; - return data; - }) as Track[]; - - return { playlist, tracks: playlist.tracks }; - } catch { - const album: SpotifyAlbum | void = await this._lib.getData(query, context.requestOptions as unknown as RequestInit).catch(Util.noop); - if (!album) return { playlist: null, tracks: [] }; - - const playlist = new Playlist(this.context.player, { - title: album.name ?? album.title, - description: album.title ?? '', - thumbnail: album.coverArt?.sources?.[0]?.url ?? 'https://www.scdn.co/i/_global/twitter_card-default.jpg', - type: album.type, - source: 'spotify', - author: { - name: album.subtitle ?? 'Unknown Artist', - url: null as unknown as string - }, - tracks: [], - id: album.id, - url: album.id ? `https://open.spotify.com/playlist/${album.id}` : query, - rawPlaylist: album - }); - - playlist.tracks = album.trackList.map((m) => { - const data: Track = new Track(this.context.player, { - title: m.title ?? '', - description: m.title ?? '', - author: m.subtitle ?? 'Unknown Artist', - url: m.uid ? `https://open.spotify.com/tracks/${m.uid}` : query, - thumbnail: 'https://www.scdn.co/i/_global/twitter_card-default.jpg', - duration: Util.buildTimeCode(Util.parseMS(m.duration)), - views: 0, - requestedBy: context.requestedBy, - playlist, - source: 'spotify', - queryType: 'spotifySong', - metadata: { - source: m, - bridge: null - }, - requestMetadata: async () => { - return { - source: m, - bridge: this.options.bridgeProvider ? (await this.options.bridgeProvider.resolve(this, data)).data : await pullYTMetadata(this, data) - }; - } - }); - data.extractor = this; - data.playlist = playlist; - return data; - }) as Track[]; - - return { playlist, tracks: playlist.tracks }; - } - } - default: - return { playlist: null, tracks: [] }; - } - } - - public async stream(info: Track): Promise { - if (this._stream) { - const stream = await this._stream(info.url, this); - if (typeof stream === 'string') return stream; - return stream; - } - - const provider = this.bridgeProvider; - if (!provider) throw new Error(`Could not find bridge provider for '${this.constructor.name}'`); - - const data = await provider.resolve(this, info); - if (!data) throw new Error('Failed to bridge this track'); - - info.setMetadata({ - ...(info.metadata || {}), - bridge: data.data - }); - - return await provider.stream(data); - } - - public parse(q: string) { - const [, , , queryType, id] = re.exec(q) || []; - - return { queryType, id }; - } -} diff --git a/packages/extractor/src/extractors/VimeoExtractor.ts b/packages/extractor/src/extractors/VimeoExtractor.ts deleted file mode 100644 index 9cffde0ffc..0000000000 --- a/packages/extractor/src/extractors/VimeoExtractor.ts +++ /dev/null @@ -1,84 +0,0 @@ -// prettier-ignore -import { - BaseExtractor, - ExtractorInfo, - ExtractorSearchContext, - QueryType, - SearchQueryType, - Track, - Util -} from 'discord-player'; -import { Vimeo } from '../internal/Vimeo'; - -export class VimeoExtractor extends BaseExtractor { - public static identifier = 'com.discord-player.vimeoextractor' as const; - - public async validate(query: string, type?: SearchQueryType | null | undefined): Promise { - if (typeof query !== 'string') return false; - return ([QueryType.VIMEO] as SearchQueryType[]).some((r) => r === type); - } - - public async getRelatedTracks(track: Track) { - void track; - return this.createResponse(); - } - - public async handle(query: string, context: ExtractorSearchContext): Promise { - switch (context.type) { - case QueryType.VIMEO: { - const trackInfo = await Vimeo.getInfo( - query - .split('/') - .filter((x) => !!x) - .pop()! - ).catch(Util.noop); - - if (!trackInfo) return this.emptyResponse(); - - const track = new Track(this.context.player, { - title: trackInfo.title, - url: trackInfo.url, - duration: Util.buildTimeCode(Util.parseMS(trackInfo.duration || 0)), - description: `${trackInfo.title} by ${trackInfo.author.name}`, - thumbnail: trackInfo.thumbnail, - views: 0, - author: trackInfo.author.name, - requestedBy: context.requestedBy, - source: 'arbitrary', - engine: trackInfo.stream, - queryType: context.type, - metadata: trackInfo, - async requestMetadata() { - return trackInfo; - } - }); - - track.extractor = this; - - return { playlist: null, tracks: [track] }; - } - default: - return this.emptyResponse(); - } - } - - public emptyResponse(): ExtractorInfo { - return { playlist: null, tracks: [] }; - } - - public async stream(info: Track) { - const engine = info.raw.engine as string; - if (engine) { - return engine; - } - - const track = await Vimeo.getInfo(info.url).catch(Util.noop); - if (!track || !track.stream) throw new Error('Could not extract stream from this source'); - - info.raw.engine = { - streamURL: track.stream - }; - - return track.stream; - } -} diff --git a/packages/extractor/src/extractors/YoutubeExtractor.ts b/packages/extractor/src/extractors/YoutubeExtractor.ts deleted file mode 100644 index 17d8a853a4..0000000000 --- a/packages/extractor/src/extractors/YoutubeExtractor.ts +++ /dev/null @@ -1,295 +0,0 @@ -import { Video, YouTube } from 'youtube-sr'; - -// prettier-ignore -import { - BaseExtractor, - ExtractorInfo, - ExtractorSearchContext, - type GuildQueueHistory, - Playlist, - QueryType, - SearchQueryType, - Track, - Util, - ExtractorStreamable -} from 'discord-player'; - -import { StreamFN, YouTubeLibs, loadYtdl, makeYTSearch } from './common/helper'; -import type { Readable } from 'stream'; - -// taken from ytdl-core -const validQueryDomains = new Set(['youtube.com', 'www.youtube.com', 'm.youtube.com', 'music.youtube.com', 'gaming.youtube.com']); -const validPathDomains = /^https?:\/\/(youtu\.be\/|(www\.)?youtube\.com\/(embed|v|shorts)\/)/; -const idRegex = /^[a-zA-Z0-9-_]{11}$/; - -export interface YoutubeExtractorInit { - createStream?: (ext: YoutubeExtractor, url: string) => Promise; -} - -export class YoutubeExtractor extends BaseExtractor { - public static identifier = 'com.discord-player.youtubeextractor' as const; - public _stream!: StreamFN; - public _ytLibName!: string; - public static instance: YoutubeExtractor | null; - - public async activate() { - this.protocols = ['ytsearch', 'youtube']; - const fn = this.options.createStream; - - if (typeof fn === 'function') { - this._stream = (q: string) => { - return fn(this, q); - }; - } else { - const { stream, name } = await loadYtdl(this.context.player.options.ytdlOptions); - this._stream = stream; - this._ytLibName = name; - } - - YoutubeExtractor.instance = this; - } - - public async deactivate(): Promise { - this.protocols = []; - YoutubeExtractor.instance = null; - } - - public async validate(query: string, type?: SearchQueryType | null | undefined): Promise { - if (typeof query !== 'string') return false; - // prettier-ignore - return ([ - QueryType.YOUTUBE, - QueryType.YOUTUBE_PLAYLIST, - QueryType.YOUTUBE_SEARCH, - QueryType.YOUTUBE_VIDEO, - QueryType.AUTO, - QueryType.AUTO_SEARCH - ] as SearchQueryType[]).some((r) => r === type); - } - - public async handle(query: string, context: ExtractorSearchContext): Promise { - if (context.protocol === 'ytsearch') context.type = QueryType.YOUTUBE_SEARCH; - query = query.includes('youtube.com') ? query.replace(/(m(usic)?|gaming)\./, '') : query; - if (!query.includes('list=RD') && YoutubeExtractor.validateURL(query)) context.type = QueryType.YOUTUBE_VIDEO; - - switch (context.type) { - case QueryType.YOUTUBE_PLAYLIST: { - const ytpl = await YouTube.getPlaylist(query, { - fetchAll: true, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - limit: (context.requestOptions as any)?.limit, - requestOptions: context.requestOptions as unknown as RequestInit - }).catch(Util.noop); - if (!ytpl) return this.emptyResponse(); - - const playlist = new Playlist(this.context.player, { - title: ytpl.title!, - thumbnail: ytpl.thumbnail?.displayThumbnailURL('maxresdefault') as string, - description: ytpl.title || '', - type: 'playlist', - source: 'youtube', - author: { - name: ytpl.channel!.name as string, - url: ytpl.channel!.url as string - }, - tracks: [], - id: ytpl.id as string, - url: ytpl.url as string, - rawPlaylist: ytpl - }); - - playlist.tracks = ytpl.videos.map((video) => { - const track = new Track(this.context.player, { - title: video.title as string, - description: video.description as string, - author: video.channel?.name as string, - url: video.url, - requestedBy: context.requestedBy, - thumbnail: video.thumbnail!.url as string, - views: video.views, - duration: video.durationFormatted, - raw: video, - playlist: playlist, - source: 'youtube', - queryType: 'youtubeVideo', - metadata: video, - async requestMetadata() { - return video; - } - }); - - track.extractor = this; - track.playlist = playlist; - return track; - }); - - return { playlist, tracks: playlist.tracks }; - } - case QueryType.YOUTUBE_VIDEO: { - const id = /[a-zA-Z0-9-_]{11}/.exec(query); - if (!id?.[0]) return this.emptyResponse(); - const video = await YouTube.getVideo(`https://www.youtube.com/watch?v=${id}`, context.requestOptions as unknown as RequestInit).catch(Util.noop); - if (!video) return this.emptyResponse(); - - // @ts-expect-error - video.source = 'youtube'; - - const track = new Track(this.context.player, { - title: video.title!, - description: video.description!, - author: video.channel?.name as string, - url: video.url, - requestedBy: context.requestedBy, - thumbnail: video.thumbnail?.displayThumbnailURL('maxresdefault') as string, - views: video.views, - duration: video.durationFormatted, - source: 'youtube', - raw: video, - queryType: context.type, - metadata: video, - async requestMetadata() { - return video; - } - }); - - track.extractor = this; - - return { - playlist: null, - tracks: [track] - }; - } - default: { - const tracks = await this._makeYTSearch(query, context); - return { playlist: null, tracks }; - } - } - } - - private async _makeYTSearch(query: string, context: ExtractorSearchContext) { - const res = await makeYTSearch(query, context.requestOptions).catch(Util.noop); - if (!res || !res.length) return []; - - return res.map((video) => { - // @ts-expect-error - video.source = 'youtube'; - - const track = new Track(this.context.player, { - title: video.title!, - description: video.description!, - author: video.channel?.name as string, - url: video.url, - requestedBy: context.requestedBy, - thumbnail: video.thumbnail?.displayThumbnailURL('maxresdefault') as string, - views: video.views, - duration: video.durationFormatted, - source: 'youtube', - raw: video, - queryType: context.type!, - metadata: video, - async requestMetadata() { - return video; - } - }); - - track.extractor = this; - - return track; - }); - } - - public async getRelatedTracks(track: Track, history: GuildQueueHistory) { - let info: Video[] | void = undefined; - - if (YoutubeExtractor.validateURL(track.url)) - info = await YouTube.getVideo(track.url) - .then((x) => x.videos) - .catch(Util.noop); - - // fallback - if (!info) - info = await YouTube.search(track.author || track.title, { limit: 5, type: 'video' }) - .then((x) => x) - .catch(Util.noop); - - if (!info?.length) { - return this.createResponse(); - } - - const unique = info.filter((t) => !history.tracks.some((x) => x.url === t.url)); - - const similar = (unique.length > 0 ? unique : info).map((video) => { - const t = new Track(this.context.player, { - title: video.title!, - url: `https://www.youtube.com/watch?v=${video.id}`, - duration: video.durationFormatted || Util.buildTimeCode(Util.parseMS(video.duration * 1000)), - description: video.title!, - thumbnail: typeof video.thumbnail === 'string' ? video.thumbnail! : video.thumbnail!.url!, - views: video.views, - author: video.channel!.name!, - requestedBy: track.requestedBy, - source: 'youtube', - queryType: 'youtubeVideo', - metadata: video, - async requestMetadata() { - return video; - } - }); - - t.extractor = this; - - return t; - }); - - return this.createResponse(null, similar); - } - - public emptyResponse(): ExtractorInfo { - return { playlist: null, tracks: [] }; - } - - public async stream(info: Track): Promise { - if (!this._stream) { - throw new Error(`Could not find youtube streaming library. Install one of ${YouTubeLibs.join(', ')}`); - } - - let url = info.url; - url = url.includes('youtube.com') ? url.replace(/(m(usic)?|gaming)\./, '') : url; - - return this._stream(url, this, this.supportsDemux); - } - - public static validateURL(link: string) { - try { - YoutubeExtractor.parseURL(link); - return true; - } catch { - return false; - } - } - - public static validateId(id: string) { - return idRegex.test(id.trim()); - } - - public static parseURL(link: string) { - const parsed = new URL(link.trim()); - let id = parsed.searchParams.get('v'); - if (validPathDomains.test(link.trim()) && !id) { - const paths = parsed.pathname.split('/'); - id = parsed.host === 'youtu.be' ? paths[1] : paths[2]; - } else if (parsed.hostname && !validQueryDomains.has(parsed.hostname)) { - throw Error('Not a YouTube domain'); - } - if (!id) { - throw Error(`No video id found: "${link}"`); - } - id = id.substring(0, 11); - if (!this.validateId(id)) { - throw TypeError(`Video id (${id}) does not match expected ` + `format (${idRegex.toString()})`); - } - return id; - } -} - -export { YoutubeExtractor as YouTubeExtractor }; diff --git a/packages/extractor/src/extractors/common/BridgeProvider.ts b/packages/extractor/src/extractors/common/BridgeProvider.ts deleted file mode 100644 index fc1a94180a..0000000000 --- a/packages/extractor/src/extractors/common/BridgeProvider.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { BaseExtractor, Track } from 'discord-player'; -import { SoundcloudTrackV2 } from 'soundcloud.ts'; -import { Video } from 'youtube-sr'; -import { SoundCloudExtractor } from '../SoundCloudExtractor'; -import { YouTubeExtractor } from '../YoutubeExtractor'; -import { pullSCMetadata, pullYTMetadata } from './helper'; - -export enum BridgeSource { - /** - * Automatically resolve the bridge source - */ - Auto = 'auto', - /** - * Use SoundCloud as the bridge source - */ - SoundCloud = 'soundcloud', - /** - * Use YouTube as the bridge source - */ - YouTube = 'youtube' -} - -export type IBridgeSource = 'soundcloud' | 'youtube' | 'auto'; - -export class BridgeProvider { - public bridgeSource: BridgeSource = BridgeSource.SoundCloud; - - public constructor(source: IBridgeSource) { - this.setBridgeSource(source); - } - - public setBridgeSource(source: BridgeSource | IBridgeSource) { - switch (source) { - case 'soundcloud': - case BridgeSource.SoundCloud: - this.bridgeSource = BridgeSource.SoundCloud; - break; - case 'youtube': - case BridgeSource.YouTube: - this.bridgeSource = BridgeSource.YouTube; - break; - case 'auto': - case BridgeSource.Auto: - this.bridgeSource = BridgeSource.Auto; - break; - default: - throw new TypeError('invalid bridge source'); - } - } - - public isSoundCloud() { - return this.bridgeSource === BridgeSource.SoundCloud; - } - - public isYouTube() { - return this.bridgeSource === BridgeSource.YouTube; - } - - public isAuto() { - return this.bridgeSource === BridgeSource.Auto; - } - - public resolveProvider() { - if (this.isAuto()) { - if (YouTubeExtractor.instance && !isExtDisabled(YouTubeExtractor.instance)) { - return BridgeSource.YouTube; - } - - if (SoundCloudExtractor.instance && !isExtDisabled(SoundCloudExtractor.instance)) { - return BridgeSource.SoundCloud; - } - - throw new Error('Could not find any available extractors for automatic bridging.'); - } - - return this.bridgeSource; - } - - public async resolve(ext: BaseExtractor, track: Track) { - const isSoundcloud = this.resolveProvider() === BridgeSource.SoundCloud; - const bridgefn = isSoundcloud ? pullSCMetadata : pullYTMetadata; - - // patch query - const oldQc = ext.createBridgeQuery; - if (isSoundcloud) ext.createBridgeQuery = (track) => `${track.author} ${track.title}`; - const res = await bridgefn(ext, track); - - ext.debug(`Extracted bridge metadata using ${isSoundcloud ? 'soundcloud' : 'youtube'} extractor: ${JSON.stringify(res)}`); - - ext.createBridgeQuery = oldQc; - - return { source: isSoundcloud ? 'soundcloud' : 'youtube', data: res } as BridgedMetadata; - } - - public async stream(meta: BridgedMetadata) { - if (!meta.data) throw new Error('Could not find bridge metadata info.'); - - if (meta.source === 'soundcloud') { - if (!SoundCloudExtractor.instance) { - throw new Error('Could not find soundcloud extractor, make sure SoundCloudExtractor is instantiated properly.'); - } - - if (isExtDisabled(SoundCloudExtractor.instance)) { - throw new Error('Cannot stream, SoundCloudExtractor is disabled.'); - } - - return await SoundCloudExtractor.instance.internal.util.streamLink(meta.data as SoundcloudTrackV2, 'progressive'); - } else if (meta.source === 'youtube') { - if (!YouTubeExtractor.instance) { - throw new Error('Could not find youtube extractor, make sure YouTubeExtractor is instantiated properly.'); - } - - if (isExtDisabled(YouTubeExtractor.instance)) { - throw new Error('Cannot stream, YouTubeExtractor is disabled.'); - } - - return YouTubeExtractor.instance._stream((meta.data as Video).url, YouTubeExtractor.instance, YouTubeExtractor.instance.supportsDemux); - } else { - throw new TypeError('invalid bridge source'); - } - } -} - -function isExtDisabled(ext: BaseExtractor) { - const streamBlocked = !!ext.context.player.options.blockStreamFrom?.some((x) => x === ext.identifier); - // const extBlocked = !!ext.context.player.options.blockExtractors?.some((x) => x === ext.identifier); - - return streamBlocked; -} - -interface BridgedMetadata { - source: IBridgeSource; - data: SoundcloudTrackV2 | Video | null; -} - -export const defaultBridgeProvider = new BridgeProvider(BridgeSource.Auto); -export const createBridgeProvider = (source: BridgeSource) => new BridgeProvider(source); diff --git a/packages/extractor/src/extractors/common/helper.ts b/packages/extractor/src/extractors/common/helper.ts deleted file mode 100644 index b51078b108..0000000000 --- a/packages/extractor/src/extractors/common/helper.ts +++ /dev/null @@ -1,372 +0,0 @@ -import { BaseExtractor, Track } from 'discord-player'; -import { YouTube } from 'youtube-sr'; -import { SoundCloudExtractor } from '../SoundCloudExtractor'; -import unfetch from 'isomorphic-unfetch'; -import http from 'http'; -import https from 'https'; -import type * as SoundCloud from 'soundcloud.ts'; - -let factory: { - name: string; - stream: StreamFN; - lib: string; -}; - -export const createImport = (lib: string) => import(lib).catch(() => null); -export const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.49'; -export const fetch = unfetch; - -export const YouTubeLibs = [ - 'youtube-ext', - 'ytdl-core', - '@distube/ytdl-core', - 'play-dl', - 'yt-stream' - // add more to the list if you have any -]; - -const ERR_NO_YT_LIB = new Error(`Could not load youtube library. Install one of ${YouTubeLibs.map((lib) => `"${lib}"`).join(', ')}`); - -// forced lib -const forcedLib = process.env.DP_FORCE_YTDL_MOD; -if (forcedLib) YouTubeLibs.unshift(...forcedLib.split(',')); - -export type StreamFN = ( - q: string, - ext: BaseExtractor, - demuxable?: boolean -) => Promise< - | import('stream').Readable - | string - | { - stream: import('stream').Readable; - $fmt: string; - } ->; - -let httpAgent: http.Agent, httpsAgent: https.Agent; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export async function loadYtdl(options?: any, force = false) { - if (factory && !force) return factory; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let lib: any, _ytLibName: string, _stream: StreamFN; - - for (const ytlib of YouTubeLibs) { - lib = await import(ytlib).then( - (m) => m, - () => null - ); - if (!lib) continue; - lib = lib.default || lib; - _ytLibName = ytlib; - break; - } - - if (lib) { - const isYtdl = ['ytdl-core'].some((lib) => lib === _ytLibName); - - const hlsRegex = /\/manifest\/hls_(variant|playlist)\//; - _stream = async (query, extractor, demuxable = false) => { - const planner = extractor.context.player.routePlanner; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const applyPlannerConfig = (opt: any, applyAgents = false) => { - if (planner) { - try { - const { ip, family } = planner.getIP(); - - if (!applyAgents) { - opt.requestOptions.localAddress = ip; - opt.requestOptions.family = family; - } else { - const options = opt?.requestOptions || {}; - - options.localAddress = ip; - options.family = family; - - if (!httpAgent) httpAgent = new http.Agent(options); - if (!httpsAgent) httpsAgent = new https.Agent(options); - - return Object.assign(opt, { - requestOptions: options, - httpAgent, - httpsAgent - }); - } - } catch { - // - } - } - - return opt; - }; - - if (_ytLibName === 'youtube-ext') { - const dl = lib as typeof import('youtube-ext'); - const opt = applyPlannerConfig( - { - ...options, - requestOptions: options?.requestOptions || {} - }, - true - ); - - const info = await dl.videoInfo(query, opt); - const videoFormats = await dl.getFormats(info.stream, opt); - - if (demuxable) { - const demuxableFormat = - info.duration.lengthSec != '0' - ? videoFormats.find((fmt) => { - return /audio\/webm; codecs="opus"/.test(fmt.mimeType || '') && fmt.audioSampleRate == '48000'; - }) - : null; - - if (demuxableFormat) { - return { - stream: await dl.getReadableStream(demuxableFormat, opt), - $fmt: 'webm/opus' - }; - } - } - - const formats = videoFormats - .filter((format) => { - if (!format.url) return false; - if (info.isLive) return dl.utils.isHlsContentURL(format.url) && format.url.endsWith('.m3u8'); - return typeof format.bitrate === 'number'; - }) - .sort((a, b) => Number(b.bitrate) - Number(a.bitrate)); - - const fmt = formats.find((format) => !format.qualityLabel) || formats.sort((a, b) => Number(a.bitrate) - Number(b.bitrate))[0]; - const url = fmt?.url; - if (!url) throw new Error(`Failed to parse stream url for ${query}`); - return url; - } else if (isYtdl) { - const dl = lib as typeof import('ytdl-core'); - const info = await dl.getInfo(query, applyPlannerConfig(options)); - - if (demuxable) { - const filter = (format: import('ytdl-core').videoFormat) => { - return format.codecs === 'opus' && format.container === 'webm' && format.audioSampleRate == '48000'; - }; - - const format = info.formats.find(filter); - - if (format && info.videoDetails.lengthSeconds != '0') { - return { - stream: dl.downloadFromInfo(info, { - ...applyPlannerConfig(options), - filter - }), - $fmt: 'webm/opus' - }; - } - } - - const formats = info.formats - .filter((format) => { - return info.videoDetails.isLiveContent ? format.isHLS && format.hasAudio : format.hasAudio; - }) - .sort((a, b) => Number(b.audioBitrate) - Number(a.audioBitrate) || Number(a.bitrate) - Number(b.bitrate)); - - const fmt = formats.find((format) => !format.hasVideo) || formats.sort((a, b) => Number(a.bitrate) - Number(b.bitrate))[0]; - const url = fmt?.url; - if (!url) throw new Error(`Failed to parse stream url for ${query}`); - return url; - // return dl(query, this.context.player.options.ytdlOptions); - } else if (_ytLibName === '@distube/ytdl-core') { - const dl = lib as typeof import('@distube/ytdl-core'); - let opt: any; // eslint-disable-line @typescript-eslint/no-explicit-any - - if (planner) { - opt = { - localAddress: planner.getIP().ip, - autoSelectFamily: true - }; - } - - const cookie = options?.requestOptions?.headers?.cookie; - - const agent = dl.createAgent(Array.isArray(cookie) ? cookie : undefined, opt); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const reqOpt: any = { - agent - }; - - if (cookie && !Array.isArray(cookie)) { - reqOpt.requestOptions = { - headers: { - cookie - } - }; - } - - const info = await dl.getInfo(query, reqOpt); - - if (demuxable) { - const filter = (format: import('@distube/ytdl-core').videoFormat) => { - return format.codecs === 'opus' && format.container === 'webm' && format.audioSampleRate == '48000'; - }; - - const format = info.formats.find(filter); - - if (format && info.videoDetails.lengthSeconds != '0') { - return { - stream: dl.downloadFromInfo(info, { - ...applyPlannerConfig(options), - filter - }), - $fmt: 'webm/opus' - }; - } - } - - const formats = info.formats - .filter((format) => { - return info.videoDetails.isLiveContent ? format.isHLS && format.hasAudio : format.hasAudio; - }) - .sort((a, b) => Number(b.audioBitrate) - Number(a.audioBitrate) || Number(a.bitrate) - Number(b.bitrate)); - - const fmt = formats.find((format) => !format.hasVideo) || formats.sort((a, b) => Number(a.bitrate) - Number(b.bitrate))[0]; - const url = fmt?.url; - if (!url) throw new Error(`Failed to parse stream url for ${query}`); - return url; - } else if (_ytLibName === 'play-dl') { - const dl = lib as typeof import('play-dl'); - - if (typeof options?.requestOptions?.headers?.cookie === 'string') { - dl.setToken({ - youtube: { - cookie: options.requestOptions.headers.cookie - } - }); - } - const info = await dl.video_info(query); - - if (demuxable) { - try { - const stream = await dl.stream(query, { - discordPlayerCompatibility: false - }); - - return { - stream: stream.stream, - $fmt: stream.type as string - }; - } catch { - // - } - } - - const formats = info.format - .filter((format) => { - if (!format.url) return false; - if (info.video_details.live) return (hlsRegex.test(format.url) && typeof format.bitrate === 'number') || (hlsRegex.test(format.url) && format.url.endsWith('.m3u8')); - return typeof format.bitrate === 'number'; - }) - .sort((a, b) => Number(b.bitrate) - Number(a.bitrate)); - - const fmt = formats.find((format) => !format.qualityLabel) || formats.sort((a, b) => Number(a.bitrate) - Number(b.bitrate))[0]; - const url = fmt?.url; - if (!url) throw new Error(`Failed to parse stream url for ${query}`); - return url; - } else if (_ytLibName === 'yt-stream') { - const dl = lib as typeof import('yt-stream'); - - const cookie = options?.requestOptions?.headers?.cookie; - - if (cookie && typeof cookie === 'string') dl.cookie = cookie; - - // @ts-ignore Default lib did not provide types for this function - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const decipher: any = await import('yt-stream/src/stream/decipher.js'); - - const info = await dl.getInfo(query); - - info.formats = await decipher?.format_decipher(info.formats, info.html5player); - - // @ts-ignore The lib did not provide ts support - const url = info.formats.filter((val) => val.mimeType.startsWith('audio') && val.audioQuality !== 'AUDIO_QUALITY_LOW').map((val) => val.url) as Array; - - if (url.length !== 0) return url[0]; - - // @ts-ignore The lib did not provide ts support - return info.formats.filter((val) => val.mimeType.startsWith('audio')).map((val) => val.url)[0] as string; - } else { - throw ERR_NO_YT_LIB; - } - }; - } else { - throw ERR_NO_YT_LIB; - } - - factory = { name: _ytLibName!, stream: _stream, lib }; - return factory; -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export async function makeYTSearch(query: string, opt: any) { - const res = await YouTube.search(query, { - type: 'video', - safeSearch: opt?.safeSearch, - requestOptions: opt - }).catch(() => { - // - }); - - return res || []; -} - -export async function makeSCSearch(query: string) { - const { instance } = SoundCloudExtractor; - if (!instance?.internal) return []; - - let data: SoundCloud.SoundcloudTrackV2[]; - - try { - const info = await instance.internal.tracks.searchV2({ - q: query, - limit: 5 - }); - - data = info.collection; - } catch { - // fallback - const info = await instance.internal.tracks.searchAlt(query); - - data = info; - } - - return filterSoundCloudPreviews(data); -} - -export async function pullYTMetadata(ext: BaseExtractor, info: Track) { - const meta = await makeYTSearch(ext.createBridgeQuery(info), 'video') - .then((r) => r[0]) - .catch(() => null); - - return meta; -} - -export async function pullSCMetadata(ext: BaseExtractor, info: Track) { - const meta = await makeSCSearch(ext.createBridgeQuery(info)) - .then((r) => r[0]) - .catch(() => null); - - return meta; -} - -export function filterSoundCloudPreviews(tracks: SoundCloud.SoundcloudTrackV2[]): SoundCloud.SoundcloudTrackV2[] { - const filtered = tracks.filter((t) => { - if (typeof t.policy === 'string') return t.policy.toUpperCase() === 'ALLOW'; - return !(t.duration === 30_000 && t.full_duration > 30_000); - }); - - const result = filtered.length > 0 ? filtered : tracks; - - return result; -} diff --git a/packages/extractor/src/extractors/index.ts b/packages/extractor/src/extractors/index.ts deleted file mode 100644 index b27bcc4ed7..0000000000 --- a/packages/extractor/src/extractors/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export * from './SoundCloudExtractor'; -export * from './YoutubeExtractor'; -export * from './LyricsExtractor'; -export * from './VimeoExtractor'; -export * from './ReverbnationExtractor'; -export * from './AttachmentExtractor'; -export * from './AppleMusicExtractor'; -export * from './SpotifyExtractor'; -export * from './BridgedExtractor'; -export * from './common/helper'; -export * from './common/BridgeProvider'; diff --git a/packages/extractor/src/index.ts b/packages/extractor/src/index.ts deleted file mode 100644 index c2ea6aeeaa..0000000000 --- a/packages/extractor/src/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './extractors'; -export * as Internal from './internal'; - -// eslint-disable-next-line @typescript-eslint/no-inferrable-types -export const version: string = '[VI]{{inject}}[/VI]'; diff --git a/packages/extractor/src/internal/AppleMusic.ts b/packages/extractor/src/internal/AppleMusic.ts deleted file mode 100644 index 7139c4be3c..0000000000 --- a/packages/extractor/src/internal/AppleMusic.ts +++ /dev/null @@ -1,287 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { QueryResolver } from 'discord-player'; -import { parse, HTMLElement } from 'node-html-parser'; -import { UA, fetch } from '../extractors/common/helper'; - -function getHTML(link: string): Promise { - return fetch(link, { - headers: { - 'User-Agent': UA - } - }) - .then((r) => r.text()) - .then( - (txt) => parse(txt), - () => null - ); -} - -function makeImage({ height, url, width, ext = 'jpg' }: { url: string; width: number; height: number; ext?: string }) { - return url.replace('{w}', `${width}`).replace('{h}', `${height}`).replace('{f}', ext); -} - -function parseDuration(d: string) { - const r = (name: string, unit: string) => `((?<${name}>-?\\d*[\\.,]?\\d+)${unit})?`; - const regex = new RegExp( - [ - '(?-)?P', - r('years', 'Y'), - r('months', 'M'), - r('weeks', 'W'), - r('days', 'D'), - '(T', - r('hours', 'H'), - r('minutes', 'M'), - r('seconds', 'S'), - ')?' // end optional time - ].join('') - ); - const test = regex.exec(d); - if (!test || !test.groups) return '0:00'; - - const dur = [test.groups.years, test.groups.months, test.groups.weeks, test.groups.days, test.groups.hours, test.groups.minutes, test.groups.seconds]; - - return ( - dur - .filter((r, i, a) => !!r || i > a.length - 2) - .map((m, i) => { - if (!m) m = '0'; - return i < 1 ? m : m.padStart(2, '0'); - }) - .join(':') || '0:00' - ); -} - -export class AppleMusic { - public constructor() { - return AppleMusic; - } - - public static async search(query: string) { - try { - const url = `https://music.apple.com/us/search?term=${encodeURIComponent(query)}`; - const node = await getHTML(url); - if (!node) return []; - - const rawData = node.getElementById('serialized-server-data'); - if (!rawData) return []; - - const data = JSON.parse(rawData.innerText)[0].data.sections; - const tracks = data.find((s: any) => s.itemKind === 'trackLockup')?.items; - if (!tracks) return []; - - return tracks.map((track: any) => ({ - id: track.contentDescriptor.identifiers.storeAdamID, - duration: track.duration || '0:00', - title: track.title, - url: track.contentDescriptor.url, - thumbnail: track?.artwork?.dictionary - ? makeImage({ - url: track.artwork.dictionary.url, - height: track.artwork.dictionary.height, - width: track.artwork.dictionary.width - }) - : 'https://music.apple.com/assets/favicon/favicon-180.png', - artist: { - name: track.subtitleLinks?.[0]?.title ?? 'Unknown Artist' - } - })); - } catch { - return []; - } - } - - public static async getSongInfoFallback(res: HTMLElement, name: string, id: string, link: string) { - try { - const metaTags = res.getElementsByTagName('meta'); - if (!metaTags.length) return null; - - const title = metaTags.find((r) => r.getAttribute('name') === 'apple:title')?.getAttribute('content') || res.querySelector('title')?.innerText || name; - const contentId = metaTags.find((r) => r.getAttribute('name') === 'apple:content_id')?.getAttribute('content') || id; - const durationRaw = metaTags.find((r) => r.getAttribute('property') === 'music:song:duration')?.getAttribute('content'); - - const song = { - id: contentId, - duration: durationRaw - ? parseDuration(durationRaw) - : metaTags - .find((m) => m.getAttribute('name') === 'apple:description') - ?.textContent.split('Duration: ')?.[1] - .split('"')?.[0] || '0:00', - title, - url: link, - thumbnail: - metaTags.find((r) => ['og:image:secure_url', 'og:image'].includes(r.getAttribute('property')!))?.getAttribute('content') || - 'https://music.apple.com/assets/favicon/favicon-180.png', - artist: { - name: res.querySelector('.song-subtitles__artists>a')?.textContent?.trim() || 'Apple Music' - } - }; - - return song; - } catch { - return null; - } - } - - public static async getSongInfo(link: string) { - if (!QueryResolver.regex.appleMusicSongRegex.test(link)) { - return null; - } - - const url = new URL(link); - const id = url.searchParams.get('i'); - const name = url.pathname.split('album/')[1]?.split('/')[0]; - - if (!id || !name) return null; - - const res = await getHTML(`https://music.apple.com/us/song/${name}/${id}`); - if (!res) return null; - - try { - const datasrc = - res.getElementById('serialized-server-data')?.innerText || res.innerText.split('')?.[0]; - if (!datasrc) throw 'not found'; - const data = JSON.parse(datasrc)[0].data.seoData; - const song = data.ogSongs[0]?.attributes; - - return { - id: data.ogSongs[0]?.id || data.appleContentId || id, - duration: song?.durationInMillis || '0:00', - title: song?.name || data.appleTitle, - url: song?.url || data.url || link, - thumbnail: song?.artwork - ? makeImage({ - url: song.artwork.url, - height: song.artwork.height, - width: song.artwork.width - }) - : data.artworkUrl - ? makeImage({ - height: data.height, - width: data.width, - url: data.artworkUrl, - ext: data.fileType || 'jpg' - }) - : 'https://music.apple.com/assets/favicon/favicon-180.png', - artist: { - name: song?.artistName || data.socialTitle || 'Apple Music' - } - }; - } catch { - return this.getSongInfoFallback(res, name, id, link); - } - } - - public static async getPlaylistInfo(link: string) { - if (!QueryResolver.regex.appleMusicPlaylistRegex.test(link)) { - return null; - } - - const res = await getHTML(link); - if (!res) return null; - - try { - const datasrc = - res.getElementById('serialized-server-data')?.innerText || res.innerText.split('')?.[0]; - if (!datasrc) throw 'not found'; - const pl = JSON.parse(datasrc)[0].data.seoData; - const thumbnail = pl.artworkUrl - ? makeImage({ - height: pl.height, - width: pl.width, - url: pl.artworkUrl, - ext: pl.fileType || 'jpg' - }) - : 'https://music.apple.com/assets/favicon/favicon-180.png'; - return { - id: pl.appleContentId, - title: pl.appleTitle, - thumbnail, - artist: { - name: pl.ogSongs?.[0]?.attributes?.artistName || 'Apple Music' - }, - url: pl.url, - tracks: - // eslint-disable-next-line - pl.ogSongs?.map((m: any) => { - const song = m.attributes; - return { - id: m.id, - duration: song.durationInMillis || '0:00', - title: song.name, - url: song.url, - thumbnail: song.artwork - ? makeImage({ - url: song.artwork.url, - height: song.artwork.height, - width: song.artwork.width - }) - : thumbnail, - artist: { - name: song.artistName || 'Apple Music' - } - }; - }) || [] - }; - } catch { - return null; - } - } - - public static async getAlbumInfo(link: string) { - if (!QueryResolver.regex.appleMusicAlbumRegex.test(link)) { - return null; - } - - const res = await getHTML(link); - if (!res) return null; - - try { - const datasrc = - res.getElementById('serialized-server-data')?.innerText || res.innerText.split('')?.[0]; - if (!datasrc) throw 'not found'; - const pl = JSON.parse(datasrc)[0].data.seoData; - const thumbnail = pl.artworkUrl - ? makeImage({ - height: pl.height, - width: pl.width, - url: pl.artworkUrl, - ext: pl.fileType || 'jpg' - }) - : 'https://music.apple.com/assets/favicon/favicon-180.png'; - return { - id: pl.appleContentId, - title: pl.appleTitle, - thumbnail, - artist: { - name: pl.ogSongs?.[0]?.attributes?.artistName || 'Apple Music' - }, - url: pl.url, - tracks: - // eslint-disable-next-line - pl.ogSongs?.map((m: any) => { - const song = m.attributes; - return { - id: m.id, - duration: song.durationInMillis || '0:00', - title: song.name, - url: song.url, - thumbnail: song.artwork - ? makeImage({ - url: song.artwork.url, - height: song.artwork.height, - width: song.artwork.width - }) - : thumbnail, - artist: { - name: song.artistName || 'Apple Music' - } - }; - }) || [] - }; - } catch { - return null; - } - } -} diff --git a/packages/extractor/src/internal/Spotify.ts b/packages/extractor/src/internal/Spotify.ts deleted file mode 100644 index a950a240af..0000000000 --- a/packages/extractor/src/internal/Spotify.ts +++ /dev/null @@ -1,294 +0,0 @@ -import { fetch, UA } from '../extractors'; - -const SP_ANON_TOKEN_URL = 'https://open.spotify.com/get_access_token?reason=transport&productType=embed'; -const SP_ACCESS_TOKEN_URL = 'https://accounts.spotify.com/api/token?grant_type=client_credentials'; -const SP_BASE = 'https://api.spotify.com/v1'; - -interface SP_ACCESS_TOKEN { - token: string; - expiresAfter: number; - type: 'Bearer'; -} - -export class SpotifyAPI { - public accessToken: SP_ACCESS_TOKEN | null = null; - - public constructor( - public credentials: { clientId: string | null; clientSecret: string | null } = { - clientId: null, - clientSecret: null - } - ) {} - - public get authorizationKey() { - if (!this.credentials.clientId || !this.credentials.clientSecret) return null; - return Buffer.from(`${this.credentials.clientId}:${this.credentials.clientSecret}`).toString('base64'); - } - - public async requestToken() { - const key = this.authorizationKey; - - if (!key) return await this.requestAnonymousToken(); - - try { - const res = await fetch(SP_ACCESS_TOKEN_URL, { - method: 'POST', - headers: { - 'User-Agent': UA, - Authorization: `Basic ${key}`, - 'Content-Type': 'application/json' - } - }); - - const body = await res.json(); - - if (!body.access_token) throw 'no token'; - - const data = { - token: body.access_token as string, - expiresAfter: body.expires_in as number, - type: 'Bearer' as const - }; - - return (this.accessToken = data); - } catch { - return await this.requestAnonymousToken(); - } - } - - public async requestAnonymousToken() { - try { - const res = await fetch(SP_ANON_TOKEN_URL, { - headers: { - 'User-Agent': UA, - 'Content-Type': 'application/json' - } - }); - - if (!res.ok) throw 'not_ok'; - - const body = await res.json(); - - if (!body.accessToken) throw 'no_access_token'; - - const data = { - token: body.accessToken as string, - expiresAfter: body.accessTokenExpirationTimestampMs as number, - type: 'Bearer' as const - }; - - return (this.accessToken = data); - } catch { - return null; - } - } - - public isTokenExpired() { - if (!this.accessToken) return true; - return Date.now() > this.accessToken.expiresAfter; - } - - public async search(query: string) { - try { - // req - if (this.isTokenExpired()) await this.requestToken(); - // failed - if (!this.accessToken) return null; - - const res = await fetch(`${SP_BASE}/search/?q=${encodeURIComponent(query)}&type=track&market=US`, { - headers: { - 'User-Agent': UA, - Authorization: `${this.accessToken.type} ${this.accessToken.token}`, - 'Content-Type': 'application/json' - } - }); - - if (!res.ok) return null; - - const data: { tracks: { items: SpotifyTrack[] } } = await res.json(); - - return data.tracks.items.map((m) => ({ - title: m.name, - duration: m.duration_ms, - artist: m.artists.map((m) => m.name).join(', '), - url: m.external_urls?.spotify || `https://open.spotify.com/track/${m.id}`, - thumbnail: m.album.images?.[0]?.url || null - })); - } catch { - return null; - } - } - - public async getPlaylist(id: string) { - try { - // req - if (this.isTokenExpired()) await this.requestToken(); - // failed - if (!this.accessToken) return null; - - const res = await fetch(`${SP_BASE}/playlists/${id}?market=US`, { - headers: { - 'User-Agent': UA, - Authorization: `${this.accessToken.type} ${this.accessToken.token}`, - 'Content-Type': 'application/json' - } - }); - if (!res.ok) return null; - - const data: { - external_urls: { spotify: string }; - owner: { display_name: string }; - id: string; - name: string; - images: { url: string }[]; - tracks: { - items: { track: SpotifyTrack }[]; - next?: string; - }; - } = await res.json(); - - if (!data.tracks.items.length) return null; - - const t: { track: SpotifyTrack }[] = data.tracks.items; - - let next: string | undefined = data.tracks.next; - - while (typeof next === 'string') { - try { - const res = await fetch(next, { - headers: { - 'User-Agent': UA, - Authorization: `${this.accessToken.type} ${this.accessToken.token}`, - 'Content-Type': 'application/json' - } - }); - if (!res.ok) break; - const nextPage: { items: { track: SpotifyTrack }[]; next?: string } = await res.json(); - - t.push(...nextPage.items); - next = nextPage.next; - - if (!next) break; - } catch { - break; - } - } - - const tracks = t.map(({ track: m }) => ({ - title: m.name, - duration: m.duration_ms, - artist: m.artists.map((m) => m.name).join(', '), - url: m.external_urls?.spotify || `https://open.spotify.com/track/${m.id}`, - thumbnail: m.album.images?.[0]?.url || null - })); - - if (!tracks.length) return null; - return { - name: data.name, - author: data.owner.display_name, - thumbnail: data.images?.[0]?.url || null, - id: data.id, - url: data.external_urls.spotify || `https://open.spotify.com/playlist/${id}`, - tracks - }; - } catch { - return null; - } - } - - public async getAlbum(id: string) { - try { - // req - if (this.isTokenExpired()) await this.requestToken(); - // failed - if (!this.accessToken) return null; - - const res = await fetch(`${SP_BASE}/albums/${id}?market=US`, { - headers: { - 'User-Agent': UA, - Authorization: `${this.accessToken.type} ${this.accessToken.token}`, - 'Content-Type': 'application/json' - } - }); - if (!res.ok) return null; - - const data: { - external_urls: { spotify: string }; - artists: { name: string }[]; - id: string; - name: string; - images: { url: string }[]; - tracks: { - items: SpotifyTrack[]; - next?: string; - }; - } = await res.json(); - - if (!data.tracks.items.length) return null; - - const t: SpotifyTrack[] = data.tracks.items; - - let next: string | undefined = data.tracks.next; - - while (typeof next === 'string') { - try { - const res = await fetch(next, { - headers: { - 'User-Agent': UA, - Authorization: `${this.accessToken.type} ${this.accessToken.token}`, - 'Content-Type': 'application/json' - } - }); - if (!res.ok) break; - const nextPage: { items: SpotifyTrack[]; next?: string } = await res.json(); - - t.push(...nextPage.items); - next = nextPage.next; - - if (!next) break; - } catch { - break; - } - } - - const tracks = t.map((m) => ({ - title: m.name, - duration: m.duration_ms, - artist: m.artists.map((m) => m.name).join(', '), - url: m.external_urls?.spotify || `https://open.spotify.com/track/${m.id}`, - thumbnail: data.images?.[0]?.url || null - })); - - if (!tracks.length) return null; - return { - name: data.name, - author: data.artists.map((m) => m.name).join(', '), - thumbnail: data.images?.[0]?.url || null, - id: data.id, - url: data.external_urls.spotify || `https://open.spotify.com/album/${id}`, - tracks - }; - } catch { - return null; - } - } -} - -export interface SpotifyTrack { - album: { - images: { - height: number; - url: string; - width: number; - }[]; - }; - artists: { - id: string; - name: string; - }[]; - duration_ms: number; - explicit: boolean; - external_urls: { spotify: string }; - id: string; - name: string; -} diff --git a/packages/extractor/src/internal/Vimeo.ts b/packages/extractor/src/internal/Vimeo.ts deleted file mode 100644 index c4bf70ca2b..0000000000 --- a/packages/extractor/src/internal/Vimeo.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { Readable } from 'stream'; -import http from 'http'; -import https from 'https'; -import { fetch } from '../extractors/common/helper'; - -class Vimeo { - constructor() { - throw new Error(`The ${this.constructor.name} class may not be instantiated!`); - } - - /** - * @typedef {Readable} Readable - */ - - /** - * Downloads from vimeo - * @param {number} id Vimeo video id - * @returns {Promise} - */ - static download(id: number | string): Promise { - return new Promise(async (resolve) => { - const info = await Vimeo.getInfo(id); - if (!info) return null; - - const downloader = info.stream.startsWith('https://') ? https : http; - - downloader.get(info.stream, (res) => { - resolve(res); - }); - }); - } - - /** - * Returns video info - * @param {number} id Video id - */ - static async getInfo(id: number | string): Promise { - if (!id) throw new Error('Invalid id'); - const url = `https://player.vimeo.com/video/${id}`; - - try { - const res = await fetch(url); - const data = await res.text(); - const json = JSON.parse(data.split('window.playerConfig =')[1].split(';')[0].trim()); - - const obj = { - id: json.video.id, - duration: json.video.duration * 1000, - title: json.video.title, - url: json.video.url, - thumbnail: json.video.thumbs['1280'] || json.video.thumbs.base, - stream: json.request.files.progressive[0].url, - author: { - id: json.video.owner.id, - name: json.video.owner.name, - url: json.video.owner.url, - avatar: json.video.owner.img_2x || json.video.owner.img - } - }; - - return obj; - } catch { - return null; - } - } -} - -export interface VimeoInfo { - id: number; - duration: number; - title: string; - url: string; - thumbnail: string; - stream: string; - author: { - id: number; - name: string; - url: string; - avatar: string; - }; -} - -export { Vimeo }; diff --git a/packages/extractor/src/internal/downloader.ts b/packages/extractor/src/internal/downloader.ts deleted file mode 100644 index 6204c63369..0000000000 --- a/packages/extractor/src/internal/downloader.ts +++ /dev/null @@ -1,11 +0,0 @@ -import http, { RequestOptions } from 'http'; -import https from 'https'; -import { Readable } from 'stream'; - -export function downloadStream(url: string, opts: RequestOptions = {}) { - return new Promise((resolve, reject) => { - const lib = url.startsWith('http://') ? http : https; - - lib.get(url, opts, (res) => resolve(res)).once('error', reject); - }); -} diff --git a/packages/extractor/src/internal/index.ts b/packages/extractor/src/internal/index.ts deleted file mode 100644 index a46b811c43..0000000000 --- a/packages/extractor/src/internal/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './AppleMusic'; -export * from './Vimeo'; -export * from './downloader'; -export * from './Spotify'; diff --git a/packages/extractor/src/types/reverbnation.d.ts b/packages/extractor/src/types/reverbnation.d.ts deleted file mode 100644 index 822d35a863..0000000000 --- a/packages/extractor/src/types/reverbnation.d.ts +++ /dev/null @@ -1,58 +0,0 @@ -declare module 'reverbnation-scraper' { - import internal from 'stream'; - - class Song { - public title: string; - public id: number; - public image: string; - public thumbnail: string; - public duration: number; - public bitrate: number; - public lyrics: string; - public streamURL: string; - public public: boolean; - public url: string; - public contextImage: { - original: string; - blur: string; - colors: { - average_lightness: number; - greyscale: boolean; - vibrant: string; - light_vibrant: string; - dark_vibrant: string; - muted: string; - light_muted: string; - dark_muted: string; - }; - source: string; - } | null; - } - - class Artist { - public id: number; - public name: string; - public profile: string; - public type: string; - public avatar: string; - public thumbnail: string; - public bio: string; - public genres: string[]; - public location: { - city: string; - state: string | null; - country: string; - }; - } - - export type ReverbnationInfo = Song & { - artist: Artist; - songs: Song[]; - }; - - export function getInfo(url: string): Promise; - - function rvdl(url: string): Promise; - - export = rvdl; -} diff --git a/packages/extractor/src/types/spotify.d.ts b/packages/extractor/src/types/spotify.d.ts deleted file mode 100644 index 21b6e8f912..0000000000 --- a/packages/extractor/src/types/spotify.d.ts +++ /dev/null @@ -1,137 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ - -declare module 'spotify-url-info' { - export interface Spotify { - getPreview(url: string, opts?: RequestInit): Promise; - getTracks(url: string, opts?: RequestInit): Promise; - getDetails(url: string, opts?: RequestInit): Promise; - getData(url: string, opts?: RequestInit): Promise; - getLink(data: any): string; - } - - function spotifyUrlInfo(fetch: any): Spotify; - - export interface SpotifySong { - type: 'track'; - name: string; - uri: string; - id: string; - title: string; - artists: { - name: string; - uri: string; - }[]; - coverArt: { - extractedColors: { - colorDark: { - hex: string; - }; - colorLight: { - hex: string; - }; - }; - sources: { - url: string; - width: number; - height: number; - }[]; - }; - releaseDate: { - isoString: string; - }; - duration: number; - maxDuration: number; - isPlayable: boolean; - isExplicit: boolean; - audioPreview: { - url: string; - format: string; - }; - hasVideo: boolean; - relatedEntityUri: string; - } - - export interface SpotifyAlbum { - type: 'album'; - name: string; - uri: string; - id: string; - title: string; - subtitle: string; - coverArt: { - extractedColors: { - colorDark: { - hex: string; - }; - }; - sources: { - height: number; - width: number; - url: string; - }[]; - }; - releaseDate: string; - duration: number; - maxDuration: number; - isPlayable: boolean; - isExplicit: boolean; - hasVideo: boolean; - relatedEntityUri: string; - trackList: { - uri: string; - uid: string; - title: string; - subtitle: string; - isExplicit: boolean; - duration: number; - isPlayable: boolean; - audioPreview: { - format: string; - url: string; - }; - }[]; - } - - export interface SpotifyPlaylist { - type: 'playlist'; - name: string; - uri: string; - id: string; - title: string; - subtitle: string; - coverArt: { - extractedColors: { - colorDark: { - hex: string; - }; - }; - sources: { - height: number; - width: number; - url: string; - }[]; - }; - releaseDate: string; - duration: number; - maxDuration: number; - isPlayable: boolean; - isExplicit: boolean; - hasVideo: boolean; - relatedEntityUri: string; - trackList: { - uri: string; - uid: string; - title: string; - subtitle: string; - isExplicit: boolean; - duration: number; - isPlayable: boolean; - audioPreview: { - format: string; - url: string; - }; - }[]; - } - - export = spotifyUrlInfo; -} diff --git a/packages/extractor/tsconfig.json b/packages/extractor/tsconfig.json deleted file mode 100644 index 7107d22e99..0000000000 --- a/packages/extractor/tsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "@discord-player/tsconfig/base.json", - "include": [ - "src/**/*" -, "../discord-player/src/utils/IPRotator.ts" ] -} \ No newline at end of file diff --git a/packages/extractor/tsup.config.ts b/packages/extractor/tsup.config.ts deleted file mode 100644 index e161ba4d4d..0000000000 --- a/packages/extractor/tsup.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { defineConfig } from '../../tsup.config'; -import { esbuildPluginVersionInjector } from 'esbuild-plugin-version-injector'; - -export default defineConfig({ - esbuildPlugins: [esbuildPluginVersionInjector()] -}); diff --git a/packages/extractor/typedoc.json b/packages/extractor/typedoc.json deleted file mode 100644 index b3eddf3024..0000000000 --- a/packages/extractor/typedoc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "entryPoints": ["src/index.ts"], - "excludePrivate": true, - "excludeExternals": true -} diff --git a/packages/tsconfig/package.json b/packages/tsconfig/package.json index 2b802aa76f..6fa7319139 100644 --- a/packages/tsconfig/package.json +++ b/packages/tsconfig/package.json @@ -20,4 +20,4 @@ "devDependencies": { "typescript": "^5.2.2" } -} \ No newline at end of file +} diff --git a/packages/voice/LICENSE b/packages/voice/LICENSE deleted file mode 100644 index fe07fc7364..0000000000 --- a/packages/voice/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 Androz2091 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/packages/voice/README.md b/packages/voice/README.md deleted file mode 100644 index 99c3658124..0000000000 --- a/packages/voice/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# `@discord-player/voice` - -A high level framework for Discord VoIP client. - -> WIP - -## Installation - -```sh -$ yarn add @discord-player/voice -``` - -## Example - -```js -import pkg from '@discord-player/voice'; - -// other code -``` diff --git a/packages/voice/__test__/sum.spec.ts b/packages/voice/__test__/sum.spec.ts deleted file mode 100644 index 6d3cd1af62..0000000000 --- a/packages/voice/__test__/sum.spec.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { add } from '../src'; -import { describe, it, expect } from 'vitest'; - -describe('Sum', () => { - it('should add two numbers', () => { - expect(add(2, 2)).toBe(4); - }); -}); diff --git a/packages/voice/package.json b/packages/voice/package.json deleted file mode 100644 index e5e8c8c13c..0000000000 --- a/packages/voice/package.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "name": "@discord-player/voice", - "version": "0.1.0", - "description": "A high level framework for Discord VoIP client", - "keywords": [ - "discord-player", - "music", - "bot", - "discord.js", - "javascript", - "voip", - "lavalink", - "lavaplayer" - ], - "author": "Androz2091 ", - "homepage": "https://discord-player.js.org", - "license": "MIT", - "main": "dist/index.js", - "module": "dist/index.mjs", - "types": "dist/index.d.ts", - "files": [ - "dist" - ], - "repository": { - "type": "git", - "url": "git+https://github.com/Androz2091/discord-player.git" - }, - "scripts": { - "build": "tsup", - "build:check": "tsc --noEmit", - "lint": "eslint src --ext .ts --fix", - "test": "vitest", - "coverage": "vitest run --coverage" - }, - "bugs": { - "url": "https://github.com/Androz2091/discord-player/issues" - }, - "devDependencies": { - "@discord-player/tsconfig": "workspace:^", - "tsup": "^7.2.0", - "typescript": "^5.2.2", - "vitest": "^0.34.6" - }, - "dependencies": { - "@discord-player/utils": "workspace:^", - "discord-voip": "^0.1.2" - } -} diff --git a/packages/voice/src/DiscordVoiceAdapter.ts b/packages/voice/src/DiscordVoiceAdapter.ts deleted file mode 100644 index c4609012d9..0000000000 --- a/packages/voice/src/DiscordVoiceAdapter.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { DiscordGatewayAdapterLibraryMethods } from 'discord-voip'; -import { GatewayVoiceServerUpdateDispatch, GatewayVoiceStateUpdateDispatch } from 'discord-api-types/v10'; -import { VoiceManager } from './VoiceManager'; - -export type VoiceAdapterData = DiscordGatewayAdapterLibraryMethods & { - id: string; -}; - -export type VoiceAdapterIncomingPayload = GatewayVoiceServerUpdateDispatch | GatewayVoiceStateUpdateDispatch; - -export class DiscordVoiceAdapter { - public constructor(public readonly manager: VoiceManager, public methods: VoiceAdapterData) {} - - public onPayload(data: VoiceAdapterIncomingPayload) { - void data; - } - - public sendPayload(data: unknown) { - // return this.manager.emit('payload', data); - void data; - } - - public destroy() { - this.manager.internalAdaptersCache.delete(this.methods.id); - } -} diff --git a/packages/voice/src/VoiceConnection.ts b/packages/voice/src/VoiceConnection.ts deleted file mode 100644 index ba20a6f884..0000000000 --- a/packages/voice/src/VoiceConnection.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { DiscordVoiceConnection } from './common'; -import type { VoiceJoinConfig, VoiceManager } from './VoiceManager'; - -export class VoiceConnection { - public constructor(public readonly manager: VoiceManager, public readonly connection: DiscordVoiceConnection) {} - - public get channel() { - return this.connection.joinConfig.channelId; - } - - public get guild() { - return this.connection.joinConfig.guildId; - } - - public static async create(manager: VoiceManager, config: VoiceJoinConfig) { - void config; - // const connection = await DiscordJoinVoiceChannel({ - // ...config, - // adapterCreator: (methods) => { - // manager.adapter; - // } - // }); - - // return new VoiceConnection(manager, connection); - } -} diff --git a/packages/voice/src/VoiceManager.ts b/packages/voice/src/VoiceManager.ts deleted file mode 100644 index fd125ad9a8..0000000000 --- a/packages/voice/src/VoiceManager.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Collection } from '@discord-player/utils'; -import { DiscordGatewayAdapterImplementerMethods } from 'discord-voip'; -// import { DiscordVoiceAdapter } from './DiscordVoiceAdapter'; -import { VoiceConnection } from './VoiceConnection'; - -export interface VoiceJoinConfig { - channelId: string; - guildId: string; - group?: string; - selfDeaf?: boolean; - selfMute?: boolean; -} - -export class VoiceManager { - public connections = new Collection(); - // public adapter = new DiscordVoiceAdapter(this); - public internalAdaptersCache = new Collection(); - - public async join(config: VoiceJoinConfig) { - if (this.connections.has(config.channelId)) return this.connections.get(config.channelId)!; - const connection = await VoiceConnection.create(this, config); - // this.connections.set(connection.channel!, connection); - - return connection; - } -} diff --git a/packages/voice/src/common.ts b/packages/voice/src/common.ts deleted file mode 100644 index 05cdf561a3..0000000000 --- a/packages/voice/src/common.ts +++ /dev/null @@ -1 +0,0 @@ -export { AudioPlayer as DiscordAudioPlayer, VoiceConnection as DiscordVoiceConnection, joinVoiceChannel as DiscordJoinVoiceChannel } from 'discord-voip'; diff --git a/packages/voice/src/index.ts b/packages/voice/src/index.ts deleted file mode 100644 index ac4ac60b7c..0000000000 --- a/packages/voice/src/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const add = (a: number, b: number) => { - return a + b; -}; - -// eslint-disable-next-line @typescript-eslint/no-inferrable-types -export const version: string = '[VI]{{inject}}[/VI]'; diff --git a/packages/voice/tsconfig.json b/packages/voice/tsconfig.json deleted file mode 100644 index 3c774c914c..0000000000 --- a/packages/voice/tsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "@discord-player/tsconfig/base.json", - "include": [ - "src/**/*" - ] -} \ No newline at end of file diff --git a/packages/voice/tsup.config.ts b/packages/voice/tsup.config.ts deleted file mode 100644 index e161ba4d4d..0000000000 --- a/packages/voice/tsup.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { defineConfig } from '../../tsup.config'; -import { esbuildPluginVersionInjector } from 'esbuild-plugin-version-injector'; - -export default defineConfig({ - esbuildPlugins: [esbuildPluginVersionInjector()] -}); diff --git a/packages/voice/vitest.config.ts b/packages/voice/vitest.config.ts deleted file mode 100644 index e055dbaa74..0000000000 --- a/packages/voice/vitest.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - test: { - dir: `${__dirname}/__test__`, - passWithNoTests: true, - watch: false - } -}); diff --git a/turbo.json b/turbo.json index 64612e3659..b2d049cf7e 100644 --- a/turbo.json +++ b/turbo.json @@ -10,11 +10,6 @@ ".next/**" ] }, - "@discord-player/extractor#build": { - "dependsOn": [ - "discord-player#build" - ] - }, "docs": {}, "build:check": {}, "lint": {}, diff --git a/typedoc.json b/typedoc.json index 435c0ecdfe..b62629bd1d 100644 --- a/typedoc.json +++ b/typedoc.json @@ -5,7 +5,6 @@ "entryPoints": [ "packages/discord-player", "packages/equalizer", - "packages/extractor", "packages/utils", "packages/ffmpeg", "packages/opus" diff --git a/yarn.lock b/yarn.lock index db08e7c715..ac743124d2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -82,42 +82,6 @@ __metadata: languageName: node linkType: hard -"@discord-player/adapter-local@workspace:packages/adapter-local": - version: 0.0.0-use.local - resolution: "@discord-player/adapter-local@workspace:packages/adapter-local" - dependencies: - "@discord-player/tsconfig": "workspace:^" - tsup: "npm:^7.2.0" - typescript: "npm:^5.2.2" - vitest: "npm:^0.34.6" - languageName: unknown - linkType: soft - -"@discord-player/adapter-remote@workspace:packages/adapter-remote": - version: 0.0.0-use.local - resolution: "@discord-player/adapter-remote@workspace:packages/adapter-remote" - dependencies: - "@discord-player/tsconfig": "workspace:^" - tsup: "npm:^7.2.0" - typescript: "npm:^5.2.2" - vitest: "npm:^0.34.6" - languageName: unknown - linkType: soft - -"@discord-player/core@workspace:packages/core": - version: 0.0.0-use.local - resolution: "@discord-player/core@workspace:packages/core" - dependencies: - "@discord-player/tsconfig": "workspace:^" - "@discord-player/utils": "workspace:^" - discord-api-types: "npm:^0.37.2" - discord-voip: "npm:^0.1.2" - tsup: "npm:^7.2.0" - typescript: "npm:^5.2.2" - vitest: "npm:^0.34.6" - languageName: unknown - linkType: soft - "@discord-player/discord-player@workspace:.": version: 0.0.0-use.local resolution: "@discord-player/discord-player@workspace:." @@ -138,18 +102,6 @@ __metadata: languageName: unknown linkType: soft -"@discord-player/downloader@workspace:packages/downloader": - version: 0.0.0-use.local - resolution: "@discord-player/downloader@workspace:packages/downloader" - dependencies: - "@discord-player/tsconfig": "workspace:^" - tsup: "npm:^7.2.0" - typescript: "npm:^5.2.2" - vitest: "npm:^0.34.6" - youtube-dl-exec: "npm:^2.1.11" - languageName: unknown - linkType: soft - "@discord-player/equalizer@workspace:^, @discord-player/equalizer@workspace:packages/equalizer": version: 0.0.0-use.local resolution: "@discord-player/equalizer@workspace:packages/equalizer" @@ -161,33 +113,6 @@ __metadata: languageName: unknown linkType: soft -"@discord-player/extractor@workspace:^, @discord-player/extractor@workspace:packages/extractor": - version: 0.0.0-use.local - resolution: "@discord-player/extractor@workspace:packages/extractor" - dependencies: - "@discord-player/tsconfig": "workspace:^" - "@distube/ytdl-core": "npm:^4.13.0" - "@types/node": "npm:^18.11.18" - discord-player: "workspace:^" - file-type: "npm:^16.5.4" - genius-lyrics: "npm:^4.4.6" - isomorphic-unfetch: "npm:^4.0.2" - mediaplex: "npm:^0.0.7" - node-html-parser: "npm:^6.1.4" - play-dl: "npm:^1.9.6" - reverbnation-scraper: "npm:^2.0.0" - soundcloud.ts: "npm:^0.5.2" - spotify-url-info: "npm:^3.2.6" - tsup: "npm:^7.2.0" - typescript: "npm:^5.2.2" - vitest: "npm:^0.34.6" - youtube-ext: "npm:^1.1.14" - youtube-sr: "npm:^4.3.9" - yt-stream: "npm:^1.4.8" - ytdl-core: "npm:^4.11.4" - languageName: unknown - linkType: soft - "@discord-player/ffmpeg@npm:^0.1.0, @discord-player/ffmpeg@workspace:^, @discord-player/ffmpeg@workspace:packages/ffmpeg": version: 0.0.0-use.local resolution: "@discord-player/ffmpeg@workspace:packages/ffmpeg" @@ -200,7 +125,7 @@ __metadata: languageName: unknown linkType: soft -"@discord-player/opus@npm:^0.1.0, @discord-player/opus@npm:^0.1.2, @discord-player/opus@workspace:packages/opus": +"@discord-player/opus@npm:^0.1.2, @discord-player/opus@workspace:packages/opus": version: 0.0.0-use.local resolution: "@discord-player/opus@workspace:packages/opus" dependencies: @@ -232,19 +157,6 @@ __metadata: languageName: unknown linkType: soft -"@discord-player/voice@workspace:packages/voice": - version: 0.0.0-use.local - resolution: "@discord-player/voice@workspace:packages/voice" - dependencies: - "@discord-player/tsconfig": "workspace:^" - "@discord-player/utils": "workspace:^" - discord-voip: "npm:^0.1.2" - tsup: "npm:^7.2.0" - typescript: "npm:^5.2.2" - vitest: "npm:^0.34.6" - languageName: unknown - linkType: soft - "@discordjs/builders@npm:^1.1.0": version: 1.1.0 resolution: "@discordjs/builders@npm:1.1.0" @@ -258,21 +170,6 @@ __metadata: languageName: node linkType: hard -"@discordjs/builders@npm:^1.6.3": - version: 1.6.3 - resolution: "@discordjs/builders@npm:1.6.3" - dependencies: - "@discordjs/formatters": "npm:^0.3.1" - "@discordjs/util": "npm:^0.3.1" - "@sapphire/shapeshift": "npm:^3.8.2" - discord-api-types: "npm:^0.37.41" - fast-deep-equal: "npm:^3.1.3" - ts-mixer: "npm:^6.0.3" - tslib: "npm:^2.5.0" - checksum: 10/1f9fa688c4e8a466eb53f2817c54aecb09e1d0c2a26ca6f309170d15c62ec382ba207d5838c75a58b497a03bbd743a4c2b07798d1658716f38d63336efb2f5fc - languageName: node - linkType: hard - "@discordjs/collection@npm:^1.0.1": version: 1.0.1 resolution: "@discordjs/collection@npm:1.0.1" @@ -287,51 +184,6 @@ __metadata: languageName: node linkType: hard -"@discordjs/collection@npm:^1.5.1": - version: 1.5.1 - resolution: "@discordjs/collection@npm:1.5.1" - checksum: 10/e7e7ee7eba078710785173f01ef1f530b66cde8521d14bec65f06bbfd11841741cc7e80e382a2c5e91ada5d1dcb1dc1f4db31adaa0790fb2ba9c1d7e9120cfef - languageName: node - linkType: hard - -"@discordjs/formatters@npm:^0.3.1": - version: 0.3.1 - resolution: "@discordjs/formatters@npm:0.3.1" - dependencies: - discord-api-types: "npm:^0.37.41" - checksum: 10/69e97aa42b0f9b0d7716763a0680cdb96674eeea5e82eed3d7efae5aa43b00be99498089a97021dd074a53aa8c2a87c6a6c2a74af9f03a550fb08e7c06140932 - languageName: node - linkType: hard - -"@discordjs/node-pre-gyp@npm:^0.4.5": - version: 0.4.5 - resolution: "@discordjs/node-pre-gyp@npm:0.4.5" - dependencies: - detect-libc: "npm:^2.0.0" - https-proxy-agent: "npm:^5.0.0" - make-dir: "npm:^3.1.0" - node-fetch: "npm:^2.6.7" - nopt: "npm:^5.0.0" - npmlog: "npm:^5.0.1" - rimraf: "npm:^3.0.2" - semver: "npm:^7.3.5" - tar: "npm:^6.1.11" - bin: - node-pre-gyp: bin/node-pre-gyp - checksum: 10/c612cbd50d2b7bbf3d31abc1d68f38361dc492010cdf706dd6a2c1e959456b632106840d254c7ffca0da014fb747e37c6983d4b6d8bb9914e5a921f22a7921df - languageName: node - linkType: hard - -"@discordjs/opus@npm:^0.9.0": - version: 0.9.0 - resolution: "@discordjs/opus@npm:0.9.0" - dependencies: - "@discordjs/node-pre-gyp": "npm:^0.4.5" - node-addon-api: "npm:^5.0.0" - checksum: 10/5b2d989c8ad950743a88c8e2141ec29bd9f075151df0d0479c47db3d33a4e9fae2620d34016e298323f0ea27a37a316fd1a6e071b7fba91e92b3186abc56c9c9 - languageName: node - linkType: hard - "@discordjs/rest@npm:^1.0.1": version: 1.0.1 resolution: "@discordjs/rest@npm:1.0.1" @@ -347,40 +199,6 @@ __metadata: languageName: node linkType: hard -"@discordjs/util@npm:^0.3.1": - version: 0.3.1 - resolution: "@discordjs/util@npm:0.3.1" - checksum: 10/3589b71f924eb174676d7c8fd9870bd9208163922e7dd3fd3f038f7f01f70b84c0dd12abb73262cb8064b1ff2ada1b9e5398a4ee6806283155e356bbf6e9cc6d - languageName: node - linkType: hard - -"@distube/ytdl-core@npm:^4.13.0": - version: 4.13.0 - resolution: "@distube/ytdl-core@npm:4.13.0" - dependencies: - http-cookie-agent: "npm:^5.0.4" - m3u8stream: "npm:^0.8.6" - sax: "npm:^1.2.4" - tough-cookie: "npm:^4.1.3" - undici: "npm:^5.23.0" - checksum: 10/3d2d0f57979714429eff546259ce7c556a34c035d5a236aea776dff2afe96b306bea715812b5684ce78bd135d8f9e35c6f25e2c88ebd019581708426bd3f901e - languageName: node - linkType: hard - -"@distube/ytdl-core@npm:^4.13.3": - version: 4.13.3 - resolution: "@distube/ytdl-core@npm:4.13.3" - dependencies: - http-cookie-agent: "npm:^5.0.4" - m3u8stream: "npm:^0.8.6" - miniget: "npm:^4.2.3" - sax: "npm:^1.2.4" - tough-cookie: "npm:^4.1.3" - undici: "npm:^5.25.2" - checksum: 10/b7c8080274346aeafa44d61204324d450c29dd8f000340e637467396a8ba0191984721ca83c564b3c2b221ce9b781bad09e7bd82e017b45bacdae718c3780877 - languageName: node - linkType: hard - "@edge-ui/react@npm:0.0.11": version: 0.0.11 resolution: "@edge-ui/react@npm:0.0.11" @@ -436,43 +254,6 @@ __metadata: languageName: node linkType: hard -"@esbuild-kit/cjs-loader@npm:^2.4.2": - version: 2.4.2 - resolution: "@esbuild-kit/cjs-loader@npm:2.4.2" - dependencies: - "@esbuild-kit/core-utils": "npm:^3.0.0" - get-tsconfig: "npm:^4.4.0" - checksum: 10/e346e339bfc7eff5c52c270fd0ec06a7f2341b624adfb69f84b7d83f119c35070420906f2761a0b4604e0a0ec90e35eaf12544585476c428ed6d6ee3b250c0fe - languageName: node - linkType: hard - -"@esbuild-kit/core-utils@npm:^3.0.0": - version: 3.1.0 - resolution: "@esbuild-kit/core-utils@npm:3.1.0" - dependencies: - esbuild: "npm:~0.17.6" - source-map-support: "npm:^0.5.21" - checksum: 10/a9b1702bcfe1232f2d89dee0ddd81430df95852abe3c18bf305b2bfdb380f689f8147b327c82728a92dfc3af43a59045bb37191665c383045293376c07665353 - languageName: node - linkType: hard - -"@esbuild-kit/esm-loader@npm:^2.5.5": - version: 2.5.5 - resolution: "@esbuild-kit/esm-loader@npm:2.5.5" - dependencies: - "@esbuild-kit/core-utils": "npm:^3.0.0" - get-tsconfig: "npm:^4.4.0" - checksum: 10/9d4a03ffc937fbec75a8456c3d45d7cdb1a65768416791a5720081753502bc9f485ba27942a46f564b12483b140a8a46c12433a4496430d93e4513e430484ec7 - languageName: node - linkType: hard - -"@esbuild/android-arm64@npm:0.17.19": - version: 0.17.19 - resolution: "@esbuild/android-arm64@npm:0.17.19" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/android-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/android-arm64@npm:0.18.20" @@ -487,13 +268,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm@npm:0.17.19": - version: 0.17.19 - resolution: "@esbuild/android-arm@npm:0.17.19" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - "@esbuild/android-arm@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/android-arm@npm:0.18.20" @@ -508,13 +282,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-x64@npm:0.17.19": - version: 0.17.19 - resolution: "@esbuild/android-x64@npm:0.17.19" - conditions: os=android & cpu=x64 - languageName: node - linkType: hard - "@esbuild/android-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/android-x64@npm:0.18.20" @@ -529,13 +296,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.17.19": - version: 0.17.19 - resolution: "@esbuild/darwin-arm64@npm:0.17.19" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/darwin-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/darwin-arm64@npm:0.18.20" @@ -550,13 +310,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.17.19": - version: 0.17.19 - resolution: "@esbuild/darwin-x64@npm:0.17.19" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - "@esbuild/darwin-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/darwin-x64@npm:0.18.20" @@ -571,13 +324,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.17.19": - version: 0.17.19 - resolution: "@esbuild/freebsd-arm64@npm:0.17.19" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/freebsd-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/freebsd-arm64@npm:0.18.20" @@ -592,13 +338,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.17.19": - version: 0.17.19 - resolution: "@esbuild/freebsd-x64@npm:0.17.19" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - "@esbuild/freebsd-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/freebsd-x64@npm:0.18.20" @@ -613,13 +352,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.17.19": - version: 0.17.19 - resolution: "@esbuild/linux-arm64@npm:0.17.19" - conditions: os=linux & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/linux-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-arm64@npm:0.18.20" @@ -634,13 +366,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.17.19": - version: 0.17.19 - resolution: "@esbuild/linux-arm@npm:0.17.19" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - "@esbuild/linux-arm@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-arm@npm:0.18.20" @@ -655,13 +380,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.17.19": - version: 0.17.19 - resolution: "@esbuild/linux-ia32@npm:0.17.19" - conditions: os=linux & cpu=ia32 - languageName: node - linkType: hard - "@esbuild/linux-ia32@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-ia32@npm:0.18.20" @@ -676,13 +394,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.17.19": - version: 0.17.19 - resolution: "@esbuild/linux-loong64@npm:0.17.19" - conditions: os=linux & cpu=loong64 - languageName: node - linkType: hard - "@esbuild/linux-loong64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-loong64@npm:0.18.20" @@ -697,13 +408,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.17.19": - version: 0.17.19 - resolution: "@esbuild/linux-mips64el@npm:0.17.19" - conditions: os=linux & cpu=mips64el - languageName: node - linkType: hard - "@esbuild/linux-mips64el@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-mips64el@npm:0.18.20" @@ -718,13 +422,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.17.19": - version: 0.17.19 - resolution: "@esbuild/linux-ppc64@npm:0.17.19" - conditions: os=linux & cpu=ppc64 - languageName: node - linkType: hard - "@esbuild/linux-ppc64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-ppc64@npm:0.18.20" @@ -739,13 +436,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.17.19": - version: 0.17.19 - resolution: "@esbuild/linux-riscv64@npm:0.17.19" - conditions: os=linux & cpu=riscv64 - languageName: node - linkType: hard - "@esbuild/linux-riscv64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-riscv64@npm:0.18.20" @@ -760,13 +450,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.17.19": - version: 0.17.19 - resolution: "@esbuild/linux-s390x@npm:0.17.19" - conditions: os=linux & cpu=s390x - languageName: node - linkType: hard - "@esbuild/linux-s390x@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-s390x@npm:0.18.20" @@ -781,13 +464,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.17.19": - version: 0.17.19 - resolution: "@esbuild/linux-x64@npm:0.17.19" - conditions: os=linux & cpu=x64 - languageName: node - linkType: hard - "@esbuild/linux-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/linux-x64@npm:0.18.20" @@ -802,13 +478,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.17.19": - version: 0.17.19 - resolution: "@esbuild/netbsd-x64@npm:0.17.19" - conditions: os=netbsd & cpu=x64 - languageName: node - linkType: hard - "@esbuild/netbsd-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/netbsd-x64@npm:0.18.20" @@ -823,13 +492,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.17.19": - version: 0.17.19 - resolution: "@esbuild/openbsd-x64@npm:0.17.19" - conditions: os=openbsd & cpu=x64 - languageName: node - linkType: hard - "@esbuild/openbsd-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/openbsd-x64@npm:0.18.20" @@ -844,13 +506,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.17.19": - version: 0.17.19 - resolution: "@esbuild/sunos-x64@npm:0.17.19" - conditions: os=sunos & cpu=x64 - languageName: node - linkType: hard - "@esbuild/sunos-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/sunos-x64@npm:0.18.20" @@ -865,13 +520,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.17.19": - version: 0.17.19 - resolution: "@esbuild/win32-arm64@npm:0.17.19" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/win32-arm64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/win32-arm64@npm:0.18.20" @@ -886,13 +534,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.17.19": - version: 0.17.19 - resolution: "@esbuild/win32-ia32@npm:0.17.19" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - "@esbuild/win32-ia32@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/win32-ia32@npm:0.18.20" @@ -907,13 +548,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.17.19": - version: 0.17.19 - resolution: "@esbuild/win32-x64@npm:0.17.19" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "@esbuild/win32-x64@npm:0.18.20": version: 0.18.20 resolution: "@esbuild/win32-x64@npm:0.18.20" @@ -1001,13 +635,6 @@ __metadata: languageName: node linkType: hard -"@fastify/busboy@npm:^2.0.0": - version: 2.0.0 - resolution: "@fastify/busboy@npm:2.0.0" - checksum: 10/6a2366d06b82aac1069b8323792f76f7a8fca02533cb3745fcd218d8f0f953dc4dbef057287237414658cd43f8dede0846ef33398999e3dbe54ddaeefec71c0a - languageName: node - linkType: hard - "@floating-ui/core@npm:^1.3.1": version: 1.3.1 resolution: "@floating-ui/core@npm:1.3.1" @@ -1564,22 +1191,6 @@ __metadata: languageName: node linkType: hard -"@mole-inc/bin-wrapper@npm:^8.0.1": - version: 8.0.1 - resolution: "@mole-inc/bin-wrapper@npm:8.0.1" - dependencies: - bin-check: "npm:^4.1.0" - bin-version-check: "npm:^5.0.0" - content-disposition: "npm:^0.5.4" - ext-name: "npm:^5.0.0" - file-type: "npm:^17.1.6" - filenamify: "npm:^5.0.2" - got: "npm:^11.8.5" - os-filter-obj: "npm:^2.0.0" - checksum: 10/565df38f6f91fefe2e2540bf4357024fd912990aecb1873fd9bf21e6a667d9930c73e5cbc87a9aac0cf476b8dffc30a4e00ec97b62e713ef5c00d46823ea599d - languageName: node - linkType: hard - "@next/env@npm:13.4.7": version: 13.4.7 resolution: "@next/env@npm:13.4.7" @@ -3294,241 +2905,60 @@ __metadata: languageName: node linkType: hard -"@sapphire/discord-utilities@npm:^3.0.3": - version: 3.0.3 - resolution: "@sapphire/discord-utilities@npm:3.0.3" - dependencies: - discord-api-types: "npm:^0.37.41" - checksum: 10/2e7e3b8189ea7d3a376080bfebcd93f74569e201840f3c113006760c852a68e94adc7575e00e4ce82481fdd071cace8e0dbcfc1b8dd4184e6b002c1896cdbb15 +"@sapphire/result@npm:^2.6.0": + version: 2.6.0 + resolution: "@sapphire/result@npm:2.6.0" + checksum: 10/548cb7cf63f6d17a0937e2575d85104bb607e529d5b97bc71dabb5896094432e712a1f3e28e374565f069ccb123bd6799a42810af65daa53f02452000e184c65 languageName: node linkType: hard -"@sapphire/discord.js-utilities@npm:^6.0.3, @sapphire/discord.js-utilities@npm:^6.1.0": - version: 6.1.0 - resolution: "@sapphire/discord.js-utilities@npm:6.1.0" +"@sapphire/shapeshift@npm:^3.5.1": + version: 3.5.1 + resolution: "@sapphire/shapeshift@npm:3.5.1" dependencies: - "@sapphire/discord-utilities": "npm:^3.0.3" - "@sapphire/duration": "npm:^1.1.0" - "@sapphire/utilities": "npm:^3.11.1" - tslib: "npm:^2.5.0" - checksum: 10/745c65cadb3110809931b15da30d91c81c78846f296e269558967fd9c5d7fa1fdd36e67864f64ca935a918a9a593d8e039b6d9a5a872e1527ad65fc0243ebc63 + fast-deep-equal: "npm:^3.1.3" + lodash.uniqwith: "npm:^4.5.0" + checksum: 10/3791c9b17507eb864656520fcaba17dc660e39f3b2e08d2560438b60ba7752dbdc502ca6db378498f2961d5fe12ca6fae0b4c6e41cff06896d940525981d775b languageName: node linkType: hard -"@sapphire/duration@npm:^1.0.0, @sapphire/duration@npm:^1.1.0": - version: 1.1.0 - resolution: "@sapphire/duration@npm:1.1.0" - checksum: 10/78e734594f91f6cbf30888d86d4666de3279971246ef8b261523b465e50331cd05fe09f4e2b46e6bf8397d270349b8b68fd316a7aed5108cfba02a01299e6ff7 +"@sapphire/snowflake@npm:^3.2.2": + version: 3.2.2 + resolution: "@sapphire/snowflake@npm:3.2.2" + checksum: 10/3b88706136e6a1ddcbbdd6b64d161515ef017d0f892bc580d4f9955e09c5edca14eda7594a0dfe8ba78d50273d9a6a02668d586c6b53ceb125f58f91e008a7bb languageName: node linkType: hard -"@sapphire/framework@npm:^4.2.1": - version: 4.4.4 - resolution: "@sapphire/framework@npm:4.4.4" - dependencies: - "@discordjs/builders": "npm:^1.6.3" - "@sapphire/discord-utilities": "npm:^3.0.3" - "@sapphire/discord.js-utilities": "npm:^6.1.0" - "@sapphire/lexure": "npm:^1.1.5" - "@sapphire/pieces": "npm:^3.6.3" - "@sapphire/ratelimits": "npm:^2.4.6" - "@sapphire/result": "npm:^2.6.4" - "@sapphire/stopwatch": "npm:^1.5.0" - "@sapphire/utilities": "npm:^3.11.2" - checksum: 10/b725fa1091b17d5674f29b16ee5a08a5e25849a870d73a94c7b9b395c680ba17a889f78f81e77fd40bca5e66750d319fd31cbaf713527ac9e9e88c4a575ba453 +"@sinclair/typebox@npm:^0.27.8": + version: 0.27.8 + resolution: "@sinclair/typebox@npm:0.27.8" + checksum: 10/297f95ff77c82c54de8c9907f186076e715ff2621c5222ba50b8d40a170661c0c5242c763cba2a4791f0f91cb1d8ffa53ea1d7294570cf8cd4694c0e383e484d languageName: node linkType: hard -"@sapphire/lexure@npm:^1.1.5": - version: 1.1.5 - resolution: "@sapphire/lexure@npm:1.1.5" - dependencies: - "@sapphire/result": "npm:^2.6.4" - checksum: 10/898d31fb54fa8106e8ee97f59f0866f0b421a6a055fb09622cd569312303dffaca689ec862fbacfd6848a4b65798a8cdbd9b4332b4107e44b39f8800bb4e87b6 +"@sindresorhus/is@npm:^5.2.0": + version: 5.6.0 + resolution: "@sindresorhus/is@npm:5.6.0" + checksum: 10/b077c325acec98e30f7d86df158aaba2e7af2acb9bb6a00fda4b91578539fbff4ecebe9b934e24fec0e6950de3089d89d79ec02d9062476b20ce185be0e01bd6 languageName: node linkType: hard -"@sapphire/pieces@npm:^3.6.3": - version: 3.6.3 - resolution: "@sapphire/pieces@npm:3.6.3" +"@sindresorhus/slugify@npm:^2.1.1": + version: 2.2.1 + resolution: "@sindresorhus/slugify@npm:2.2.1" dependencies: - "@discordjs/collection": "npm:^1.5.1" - "@sapphire/utilities": "npm:^3.11.1" - tslib: "npm:^2.5.0" - checksum: 10/17e3932ef1a5c8860700633819a6c80dfe0035e07f35c36f45aae0d536bd19a7f82f7764a683ab3889e4a5c2441f155c19ac8e9e79a065b030933a9e74ac415d + "@sindresorhus/transliterate": "npm:^1.0.0" + escape-string-regexp: "npm:^5.0.0" + checksum: 10/717f04cf71261ebac1284b982bd090cd4a29c277c146a42f861622dfd092504c65b20375ee41c6be3db30c483731bdf0377f3cdad9e8e9dc3f69560049aa203c languageName: node linkType: hard -"@sapphire/plugin-api@npm:^5.0.1": - version: 5.1.0 - resolution: "@sapphire/plugin-api@npm:5.1.0" +"@sindresorhus/transliterate@npm:^1.0.0": + version: 1.6.0 + resolution: "@sindresorhus/transliterate@npm:1.6.0" dependencies: - "@types/ws": "npm:^8.5.5" - tldts: "npm:^6.0.5" - tslib: "npm:^2.5.3" - undici: "npm:^5.22.1" - checksum: 10/4d7cb1a9a5dae5e0aa691009150cc3fabf9df6eb627d12c7ac087d915cac29d7f3d19d6e2d5a52b31b442eedd3ae499d864703b78f7747ca94c6fc9ee4b41892 - languageName: node - linkType: hard - -"@sapphire/plugin-hmr@npm:^2.0.0": - version: 2.0.1 - resolution: "@sapphire/plugin-hmr@npm:2.0.1" - dependencies: - chokidar: "npm:^3.5.3" - tslib: "npm:2.x" - checksum: 10/c8eb4062878387b680487a5fc7b3d99fc8213220f19877c69abfcbea5ef45377edfe7c0fa46b132d50bbb97f71c35048a3c0c172b99ea52de05e280c0f3443ec - languageName: node - linkType: hard - -"@sapphire/plugin-logger@npm:^3.0.1": - version: 3.0.4 - resolution: "@sapphire/plugin-logger@npm:3.0.4" - dependencies: - "@sapphire/timestamp": "npm:^1.0.1" - colorette: "npm:^2.0.20" - tslib: "npm:^2.5.0" - checksum: 10/574378879c82a2bc96dc28394ba7861e5097974ba19207907cb48830d06904f996d9af4f5dbc813a747d76ef1e920dbef625ce457e2c8dab5d859480eda107ce - languageName: node - linkType: hard - -"@sapphire/prettier-config@npm:^1.4.5": - version: 1.4.5 - resolution: "@sapphire/prettier-config@npm:1.4.5" - dependencies: - prettier: "npm:^2.8.2" - checksum: 10/378ce2099543a537c4f44b0a75f75ffc37e5802d0dd088bee8f3be5a2cb9616ac91ee952594b541624cf32648966d4b023d3e17a4b2cac66ee1a17aba92d8b86 - languageName: node - linkType: hard - -"@sapphire/ratelimits@npm:^2.4.6": - version: 2.4.6 - resolution: "@sapphire/ratelimits@npm:2.4.6" - checksum: 10/a83223893e4462f3e209679a206d7e0dd7f46771fc57522c1c5683c053977c314fa859eadd63317a832bdd909af583ce21007d4b2aae76b33aa78224f606679c - languageName: node - linkType: hard - -"@sapphire/result@npm:^2.6.0": - version: 2.6.0 - resolution: "@sapphire/result@npm:2.6.0" - checksum: 10/548cb7cf63f6d17a0937e2575d85104bb607e529d5b97bc71dabb5896094432e712a1f3e28e374565f069ccb123bd6799a42810af65daa53f02452000e184c65 - languageName: node - linkType: hard - -"@sapphire/result@npm:^2.6.4": - version: 2.6.4 - resolution: "@sapphire/result@npm:2.6.4" - checksum: 10/d47807b9f3ccf572b208d1052aafbc9f5f37f2d47bc661225d2dff0eb873c684bc37bcb5f0885a19309da37263be92743f611a140ba8601261ae2ae668c21a22 - languageName: node - linkType: hard - -"@sapphire/shapeshift@npm:^3.5.1": - version: 3.5.1 - resolution: "@sapphire/shapeshift@npm:3.5.1" - dependencies: - fast-deep-equal: "npm:^3.1.3" - lodash.uniqwith: "npm:^4.5.0" - checksum: 10/3791c9b17507eb864656520fcaba17dc660e39f3b2e08d2560438b60ba7752dbdc502ca6db378498f2961d5fe12ca6fae0b4c6e41cff06896d940525981d775b - languageName: node - linkType: hard - -"@sapphire/shapeshift@npm:^3.8.2": - version: 3.9.2 - resolution: "@sapphire/shapeshift@npm:3.9.2" - dependencies: - fast-deep-equal: "npm:^3.1.3" - lodash: "npm:^4.17.21" - checksum: 10/1f268ada96f1716ade47dac5fd5449e29fb2a04a2080f7bba0ea8d94f8fc83b5608e7f9fdbbfd28c0b3921997a8d60c875712f1cf4e14ea96e95b5f72af4d9ea - languageName: node - linkType: hard - -"@sapphire/snowflake@npm:^3.2.2": - version: 3.2.2 - resolution: "@sapphire/snowflake@npm:3.2.2" - checksum: 10/3b88706136e6a1ddcbbdd6b64d161515ef017d0f892bc580d4f9955e09c5edca14eda7594a0dfe8ba78d50273d9a6a02668d586c6b53ceb125f58f91e008a7bb - languageName: node - linkType: hard - -"@sapphire/stopwatch@npm:^1.5.0": - version: 1.5.0 - resolution: "@sapphire/stopwatch@npm:1.5.0" - dependencies: - tslib: "npm:^2.4.0" - checksum: 10/86e50cb851ea744922cf95341e312261f9c5b1e866a09bc2ef1186dbb5db50bc3bc15baa86bab1773777035c4caa22c2c3c176611039864d3e9ff290c6f2d513 - languageName: node - linkType: hard - -"@sapphire/timestamp@npm:^1.0.1": - version: 1.0.1 - resolution: "@sapphire/timestamp@npm:1.0.1" - checksum: 10/6bed5ac6efb82df3ae14e4215395b43b2c7ea1afb7b0f0da08e43c779ad851e2557f6b02dbd05a567e3134a5f6aef5bd71fce5cde3153299e4cf21dd0a4074a0 - languageName: node - linkType: hard - -"@sapphire/ts-config@npm:^3.3.4": - version: 3.3.4 - resolution: "@sapphire/ts-config@npm:3.3.4" - dependencies: - tslib: "npm:^2.3.1" - typescript: "npm:^4.6.3" - checksum: 10/8a163806edeac8f501ebeec3cdb025a1095af46ba75ce25f4938ec0f1b6e07cd56e91e359cff7c56a2a52fb1a25e4c672946d95bb5e729ae65ddd7e6da895c3e - languageName: node - linkType: hard - -"@sapphire/utilities@npm:^3.11.1, @sapphire/utilities@npm:^3.11.2": - version: 3.12.0 - resolution: "@sapphire/utilities@npm:3.12.0" - checksum: 10/403cc1afd7d17f28b8857284a34176dff4acef46aa3b0e0b0939a650fd5fe593986e96e2d49b978b3f02b1b07dadc8d28ad1b2833387f33689952fdf7076ffce - languageName: node - linkType: hard - -"@sinclair/typebox@npm:^0.27.8": - version: 0.27.8 - resolution: "@sinclair/typebox@npm:0.27.8" - checksum: 10/297f95ff77c82c54de8c9907f186076e715ff2621c5222ba50b8d40a170661c0c5242c763cba2a4791f0f91cb1d8ffa53ea1d7294570cf8cd4694c0e383e484d - languageName: node - linkType: hard - -"@sindresorhus/is@npm:^4.0.0": - version: 4.6.0 - resolution: "@sindresorhus/is@npm:4.6.0" - checksum: 10/e7f36ed72abfcd5e0355f7423a72918b9748bb1ef370a59f3e5ad8d40b728b85d63b272f65f63eec1faf417cda89dcb0aeebe94015647b6054659c1442fe5ce0 - languageName: node - linkType: hard - -"@sindresorhus/is@npm:^5.2.0": - version: 5.6.0 - resolution: "@sindresorhus/is@npm:5.6.0" - checksum: 10/b077c325acec98e30f7d86df158aaba2e7af2acb9bb6a00fda4b91578539fbff4ecebe9b934e24fec0e6950de3089d89d79ec02d9062476b20ce185be0e01bd6 - languageName: node - linkType: hard - -"@sindresorhus/slugify@npm:^2.1.1": - version: 2.2.1 - resolution: "@sindresorhus/slugify@npm:2.2.1" - dependencies: - "@sindresorhus/transliterate": "npm:^1.0.0" - escape-string-regexp: "npm:^5.0.0" - checksum: 10/717f04cf71261ebac1284b982bd090cd4a29c277c146a42f861622dfd092504c65b20375ee41c6be3db30c483731bdf0377f3cdad9e8e9dc3f69560049aa203c - languageName: node - linkType: hard - -"@sindresorhus/transliterate@npm:^1.0.0": - version: 1.6.0 - resolution: "@sindresorhus/transliterate@npm:1.6.0" - dependencies: - escape-string-regexp: "npm:^5.0.0" - checksum: 10/fbb5bbcaf986068dc5aec87ef18380f46a8beaf0c5a7a5adf6cee26ceacde564b21381b1068d0beae86e489c2ef368ca15042a86a196762f59feca25db66abb3 - languageName: node - linkType: hard - -"@skyra/env-utilities@npm:^1.1.0": - version: 1.2.1 - resolution: "@skyra/env-utilities@npm:1.2.1" - dependencies: - dotenv: "npm:^16.3.0" - dotenv-expand: "npm:^10.0.0" - checksum: 10/e9ba9f38197ae1b5b339c5f126113ff0e41475f34faa52ed2e0472031b6d3f540e04e56a54858eafdc7bf8cc7b84f0a925ebaa55fe668884d65823ea72abcac5 + escape-string-regexp: "npm:^5.0.0" + checksum: 10/fbb5bbcaf986068dc5aec87ef18380f46a8beaf0c5a7a5adf6cee26ceacde564b21381b1068d0beae86e489c2ef368ca15042a86a196762f59feca25db66abb3 languageName: node linkType: hard @@ -3539,144 +2969,6 @@ __metadata: languageName: node linkType: hard -"@swc/cli@npm:^0.1.62": - version: 0.1.62 - resolution: "@swc/cli@npm:0.1.62" - dependencies: - "@mole-inc/bin-wrapper": "npm:^8.0.1" - commander: "npm:^7.1.0" - fast-glob: "npm:^3.2.5" - semver: "npm:^7.3.8" - slash: "npm:3.0.0" - source-map: "npm:^0.7.3" - peerDependencies: - "@swc/core": ^1.2.66 - chokidar: ^3.5.1 - peerDependenciesMeta: - chokidar: - optional: true - bin: - spack: bin/spack.js - swc: bin/swc.js - swcx: bin/swcx.js - checksum: 10/be1a44f8e9831b52ff92cfe7b2d8034fde64d800751e3698c5ae815eb7dfe27a58770fc79ac677195250ecf14dd58d18ac02fb51752533d666539d389e4ff864 - languageName: node - linkType: hard - -"@swc/core-darwin-arm64@npm:1.3.65": - version: 1.3.65 - resolution: "@swc/core-darwin-arm64@npm:1.3.65" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@swc/core-darwin-x64@npm:1.3.65": - version: 1.3.65 - resolution: "@swc/core-darwin-x64@npm:1.3.65" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@swc/core-linux-arm-gnueabihf@npm:1.3.65": - version: 1.3.65 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.65" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"@swc/core-linux-arm64-gnu@npm:1.3.65": - version: 1.3.65 - resolution: "@swc/core-linux-arm64-gnu@npm:1.3.65" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - -"@swc/core-linux-arm64-musl@npm:1.3.65": - version: 1.3.65 - resolution: "@swc/core-linux-arm64-musl@npm:1.3.65" - conditions: os=linux & cpu=arm64 & libc=musl - languageName: node - linkType: hard - -"@swc/core-linux-x64-gnu@npm:1.3.65": - version: 1.3.65 - resolution: "@swc/core-linux-x64-gnu@npm:1.3.65" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - -"@swc/core-linux-x64-musl@npm:1.3.65": - version: 1.3.65 - resolution: "@swc/core-linux-x64-musl@npm:1.3.65" - conditions: os=linux & cpu=x64 & libc=musl - languageName: node - linkType: hard - -"@swc/core-win32-arm64-msvc@npm:1.3.65": - version: 1.3.65 - resolution: "@swc/core-win32-arm64-msvc@npm:1.3.65" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@swc/core-win32-ia32-msvc@npm:1.3.65": - version: 1.3.65 - resolution: "@swc/core-win32-ia32-msvc@npm:1.3.65" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@swc/core-win32-x64-msvc@npm:1.3.65": - version: 1.3.65 - resolution: "@swc/core-win32-x64-msvc@npm:1.3.65" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"@swc/core@npm:^1.3.37": - version: 1.3.65 - resolution: "@swc/core@npm:1.3.65" - dependencies: - "@swc/core-darwin-arm64": "npm:1.3.65" - "@swc/core-darwin-x64": "npm:1.3.65" - "@swc/core-linux-arm-gnueabihf": "npm:1.3.65" - "@swc/core-linux-arm64-gnu": "npm:1.3.65" - "@swc/core-linux-arm64-musl": "npm:1.3.65" - "@swc/core-linux-x64-gnu": "npm:1.3.65" - "@swc/core-linux-x64-musl": "npm:1.3.65" - "@swc/core-win32-arm64-msvc": "npm:1.3.65" - "@swc/core-win32-ia32-msvc": "npm:1.3.65" - "@swc/core-win32-x64-msvc": "npm:1.3.65" - peerDependencies: - "@swc/helpers": ^0.5.0 - dependenciesMeta: - "@swc/core-darwin-arm64": - optional: true - "@swc/core-darwin-x64": - optional: true - "@swc/core-linux-arm-gnueabihf": - optional: true - "@swc/core-linux-arm64-gnu": - optional: true - "@swc/core-linux-arm64-musl": - optional: true - "@swc/core-linux-x64-gnu": - optional: true - "@swc/core-linux-x64-musl": - optional: true - "@swc/core-win32-arm64-msvc": - optional: true - "@swc/core-win32-ia32-msvc": - optional: true - "@swc/core-win32-x64-msvc": - optional: true - peerDependenciesMeta: - "@swc/helpers": - optional: true - checksum: 10/6e7aff1c8716804abace0ff7fc5637085b5918eefac7c78a4cebf77d1a2b05467068221f2bc5a1d5f541dcd2f59ef2b3442fc54cbc061d6f63257c947eb1c2cd - languageName: node - linkType: hard - "@swc/helpers@npm:0.5.1": version: 0.5.1 resolution: "@swc/helpers@npm:0.5.1" @@ -3686,15 +2978,6 @@ __metadata: languageName: node linkType: hard -"@szmarczak/http-timer@npm:^4.0.5": - version: 4.0.6 - resolution: "@szmarczak/http-timer@npm:4.0.6" - dependencies: - defer-to-connect: "npm:^2.0.0" - checksum: 10/c29df3bcec6fc3bdec2b17981d89d9c9fc9bd7d0c9bcfe92821dc533f4440bc890ccde79971838b4ceed1921d456973c4180d7175ee1d0023ad0562240a58d95 - languageName: node - linkType: hard - "@szmarczak/http-timer@npm:^5.0.1": version: 5.0.1 resolution: "@szmarczak/http-timer@npm:5.0.1" @@ -3746,18 +3029,6 @@ __metadata: languageName: node linkType: hard -"@types/cacheable-request@npm:^6.0.1": - version: 6.0.3 - resolution: "@types/cacheable-request@npm:6.0.3" - dependencies: - "@types/http-cache-semantics": "npm:*" - "@types/keyv": "npm:^3.1.4" - "@types/node": "npm:*" - "@types/responselike": "npm:^1.0.0" - checksum: 10/159f9fdb2a1b7175eef453ae2ced5ea04c0d2b9610cc9ccd9f9abb066d36dacb1f37acd879ace10ad7cbb649490723feb396fb7307004c9670be29636304b988 - languageName: node - linkType: hard - "@types/chai-subset@npm:^1.3.3": version: 1.3.3 resolution: "@types/chai-subset@npm:1.3.3" @@ -3840,13 +3111,6 @@ __metadata: languageName: node linkType: hard -"@types/http-cache-semantics@npm:*": - version: 4.0.1 - resolution: "@types/http-cache-semantics@npm:4.0.1" - checksum: 10/d059bf8a15d5163cc60da51ba00d17620507f968d0b792cd55f62043016344a5f0e1aa94fa411089d41114035fcd0ea656f968bda7eabb6663a97787e3445a1c - languageName: node - linkType: hard - "@types/http-cache-semantics@npm:^4.0.2": version: 4.0.4 resolution: "@types/http-cache-semantics@npm:4.0.4" @@ -3898,15 +3162,6 @@ __metadata: languageName: node linkType: hard -"@types/keyv@npm:^3.1.4": - version: 3.1.4 - resolution: "@types/keyv@npm:3.1.4" - dependencies: - "@types/node": "npm:*" - checksum: 10/e009a2bfb50e90ca9b7c6e8f648f8464067271fd99116f881073fa6fa76dc8d0133181dd65e6614d5fb1220d671d67b0124aef7d97dc02d7e342ab143a47779d - languageName: node - linkType: hard - "@types/mdast@npm:^3.0.0": version: 3.0.11 resolution: "@types/mdast@npm:3.0.11" @@ -3962,20 +3217,6 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^18.11.18": - version: 18.11.18 - resolution: "@types/node@npm:18.11.18" - checksum: 10/da05cf3a0036ef05cd695ac4cb265948593acbe723ba818f0ca0ce466b13ba99e1aac3a363086d6b8c7ea8f30c9233478e0293ac878a6f4b1d5515b10c392257 - languageName: node - linkType: hard - -"@types/node@npm:^18.14.6": - version: 18.16.18 - resolution: "@types/node@npm:18.16.18" - checksum: 10/4692b4c927bf63efbc3aed6e18c1a264eb8b5673fa7cc0649a38f394ab34d4d4d0e7a2b98f4235bc8dc7615e88080a1b443a06eac9324f67e426398ed857b4a1 - languageName: node - linkType: hard - "@types/node@npm:^18.6.3": version: 18.6.3 resolution: "@types/node@npm:18.6.3" @@ -4042,15 +3283,6 @@ __metadata: languageName: node linkType: hard -"@types/responselike@npm:^1.0.0": - version: 1.0.0 - resolution: "@types/responselike@npm:1.0.0" - dependencies: - "@types/node": "npm:*" - checksum: 10/e4972389457e4edce3cbba5e8474fb33684d73879433a9eec989d0afb7e550fd6fa3ffb8fe68dbb429288d10707796a193bc0007c4e8429fd267bdc4d8404632 - languageName: node - linkType: hard - "@types/scheduler@npm:*": version: 0.16.2 resolution: "@types/scheduler@npm:0.16.2" @@ -4095,15 +3327,6 @@ __metadata: languageName: node linkType: hard -"@types/ws@npm:^8.5.4": - version: 8.5.4 - resolution: "@types/ws@npm:8.5.4" - dependencies: - "@types/node": "npm:*" - checksum: 10/8ad37f8ec1f7a1e2b8c0d53353ac30d182277c0bce4d877a497a0b7bcfbeee1838270eb6247a6978da66cc2891106d3c77511ebc827c58967ede8e756446422f - languageName: node - linkType: hard - "@types/ws@npm:^8.5.5": version: 8.5.5 resolution: "@types/ws@npm:8.5.5" @@ -4357,7 +3580,7 @@ __metadata: languageName: node linkType: hard -"abbrev@npm:1, abbrev@npm:^1.0.0": +"abbrev@npm:^1.0.0": version: 1.1.1 resolution: "abbrev@npm:1.1.1" checksum: 10/2d882941183c66aa665118bafdab82b7a177e9add5eb2776c33e960a4f3c89cff88a1b38aba13a456de01d0dd9d66a8bea7c903268b21ea91dd1097e1e2e8243 @@ -4451,15 +3674,6 @@ __metadata: languageName: node linkType: hard -"agent-base@npm:^7.1.0": - version: 7.1.0 - resolution: "agent-base@npm:7.1.0" - dependencies: - debug: "npm:^4.3.4" - checksum: 10/f7828f991470a0cc22cb579c86a18cbae83d8a3cbed39992ab34fc7217c4d126017f1c74d0ab66be87f71455318a8ea3e757d6a37881b8d0f2a2c6aa55e5418f - languageName: node - linkType: hard - "agentkeepalive@npm:^4.2.1": version: 4.2.1 resolution: "agentkeepalive@npm:4.2.1" @@ -4555,15 +3769,6 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^3.2.1": - version: 3.2.1 - resolution: "ansi-styles@npm:3.2.1" - dependencies: - color-convert: "npm:^1.9.0" - checksum: 10/d85ade01c10e5dd77b6c89f34ed7531da5830d2cb5882c645f330079975b716438cd7ebb81d0d6e6b4f9c577f19ae41ab55f07f19786b02f9dfd9e0377395665 - languageName: node - linkType: hard - "ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": version: 4.3.0 resolution: "ansi-styles@npm:4.3.0" @@ -4604,23 +3809,6 @@ __metadata: languageName: node linkType: hard -"arch@npm:^2.1.0": - version: 2.2.0 - resolution: "arch@npm:2.2.0" - checksum: 10/e35dbc6d362297000ab90930069576ba165fe63cd52383efcce14bd66c1b16a91ce849e1fd239964ed029d5e0bdfc32f68e9c7331b7df6c84ddebebfdbf242f7 - languageName: node - linkType: hard - -"are-we-there-yet@npm:^2.0.0": - version: 2.0.0 - resolution: "are-we-there-yet@npm:2.0.0" - dependencies: - delegates: "npm:^1.0.0" - readable-stream: "npm:^3.6.0" - checksum: 10/ea6f47d14fc33ae9cbea3e686eeca021d9d7b9db83a306010dd04ad5f2c8b7675291b127d3fcbfcbd8fec26e47b3324ad5b469a6cc3733a582f2fe4e12fc6756 - languageName: node - linkType: hard - "are-we-there-yet@npm:^3.0.0": version: 3.0.1 resolution: "are-we-there-yet@npm:3.0.1" @@ -4826,17 +4014,6 @@ __metadata: languageName: node linkType: hard -"axios@npm:^1.5.0": - version: 1.5.0 - resolution: "axios@npm:1.5.0" - dependencies: - follow-redirects: "npm:^1.15.0" - form-data: "npm:^4.0.0" - proxy-from-env: "npm:^1.1.0" - checksum: 10/128433020b1fe9a460121735016f377adc6109a1f62b61795b1a80704de0a70affb0d580c8abd057e28af5f343cb4fb9a17a0b2512ea7f314578bbe492851a23 - languageName: node - linkType: hard - "axobject-query@npm:^3.1.1": version: 3.2.1 resolution: "axobject-query@npm:3.2.1" @@ -4888,37 +4065,6 @@ __metadata: languageName: node linkType: hard -"bin-check@npm:^4.1.0": - version: 4.1.0 - resolution: "bin-check@npm:4.1.0" - dependencies: - execa: "npm:^0.7.0" - executable: "npm:^4.1.0" - checksum: 10/16f6d5d86df9365dab682c7dd238f93678b773a908b3bccea4b1acb82b9b4e49fcfa24c99b99180a8e4cdd89a8f15f03700b09908ed5ae651f52fd82488a3507 - languageName: node - linkType: hard - -"bin-version-check@npm:^5.0.0": - version: 5.0.0 - resolution: "bin-version-check@npm:5.0.0" - dependencies: - bin-version: "npm:^6.0.0" - semver: "npm:^7.3.5" - semver-truncate: "npm:^2.0.0" - checksum: 10/1d3dc92847f8ecd5e07109f5f44727f0cb3b17c00be5ae2a2e105b86bf161bc4e5c10ee2e2c21d5d28e6382994d8416b5e06048191a485be909a1e49a959c3c3 - languageName: node - linkType: hard - -"bin-version@npm:^6.0.0": - version: 6.0.0 - resolution: "bin-version@npm:6.0.0" - dependencies: - execa: "npm:^5.0.0" - find-versions: "npm:^5.0.0" - checksum: 10/78c29422ea9597eb4c8d4f0eff96df60d09aa82b53a87925bc403efbe5c55251b1a07baac538381d9096377f92d27e3c03963efa86db5bc0d6431b9563946229 - languageName: node - linkType: hard - "binary-extensions@npm:^2.0.0": version: 2.2.0 resolution: "binary-extensions@npm:2.2.0" @@ -4957,13 +4103,6 @@ __metadata: languageName: node linkType: hard -"boolbase@npm:^1.0.0": - version: 1.0.0 - resolution: "boolbase@npm:1.0.0" - checksum: 10/3e25c80ef626c3a3487c73dbfc70ac322ec830666c9ad915d11b701142fab25ec1e63eff2c450c74347acfd2de854ccde865cd79ef4db1683f7c7b046ea43bb0 - languageName: node - linkType: hard - "bplist-parser@npm:^0.2.0": version: 0.2.0 resolution: "bplist-parser@npm:0.2.0" @@ -5015,13 +4154,6 @@ __metadata: languageName: node linkType: hard -"buffer-from@npm:^1.0.0": - version: 1.1.2 - resolution: "buffer-from@npm:1.1.2" - checksum: 10/0448524a562b37d4d7ed9efd91685a5b77a50672c556ea254ac9a6d30e3403a517d8981f10e565db24e8339413b43c97ca2951f10e399c6125a0d8911f5679bb - languageName: node - linkType: hard - "buffer@npm:^6.0.3": version: 6.0.3 resolution: "buffer@npm:6.0.3" @@ -5052,7 +4184,7 @@ __metadata: languageName: node linkType: hard -"busboy@npm:1.6.0, busboy@npm:^1.6.0": +"busboy@npm:1.6.0": version: 1.6.0 resolution: "busboy@npm:1.6.0" dependencies: @@ -5101,13 +4233,6 @@ __metadata: languageName: node linkType: hard -"cacheable-lookup@npm:^5.0.3": - version: 5.0.4 - resolution: "cacheable-lookup@npm:5.0.4" - checksum: 10/618a8b3eea314060e74cb3285a6154e8343c244a34235acf91cfe626ee0705c24e3cd11e4b1a7b3900bd749ee203ae65afe13adf610c8ab173e99d4a208faf75 - languageName: node - linkType: hard - "cacheable-lookup@npm:^7.0.0": version: 7.0.0 resolution: "cacheable-lookup@npm:7.0.0" @@ -5130,21 +4255,6 @@ __metadata: languageName: node linkType: hard -"cacheable-request@npm:^7.0.2": - version: 7.0.4 - resolution: "cacheable-request@npm:7.0.4" - dependencies: - clone-response: "npm:^1.0.2" - get-stream: "npm:^5.1.0" - http-cache-semantics: "npm:^4.0.0" - keyv: "npm:^4.0.0" - lowercase-keys: "npm:^2.0.0" - normalize-url: "npm:^6.0.1" - responselike: "npm:^2.0.0" - checksum: 10/0f4f2001260ecca78b9f64fc8245e6b5a5dcde24ea53006daab71f5e0e1338095aa1512ec099c4f9895a9e5acfac9da423cb7c079e131485891e9214aca46c41 - languageName: node - linkType: hard - "call-bind@npm:^1.0.0, call-bind@npm:^1.0.2": version: 1.0.2 resolution: "call-bind@npm:1.0.2" @@ -5212,17 +4322,6 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^2.4.1": - version: 2.4.2 - resolution: "chalk@npm:2.4.2" - dependencies: - ansi-styles: "npm:^3.2.1" - escape-string-regexp: "npm:^1.0.5" - supports-color: "npm:^5.3.0" - checksum: 10/3d1d103433166f6bfe82ac75724951b33769675252d8417317363ef9d54699b7c3b2d46671b772b893a8e50c3ece70c4b933c73c01e81bc60ea4df9b55afa303 - languageName: node - linkType: hard - "chalk@npm:^4.0.0": version: 4.1.2 resolution: "chalk@npm:4.1.2" @@ -5365,15 +4464,6 @@ __metadata: languageName: node linkType: hard -"clone-response@npm:^1.0.2": - version: 1.0.3 - resolution: "clone-response@npm:1.0.3" - dependencies: - mimic-response: "npm:^1.0.0" - checksum: 10/4e671cac39b11c60aa8ba0a450657194a5d6504df51bca3fac5b3bd0145c4f8e8464898f87c8406b83232e3bc5cca555f51c1f9c8ac023969ebfbf7f6bdabb2e - languageName: node - linkType: hard - "clone@npm:^1.0.2": version: 1.0.4 resolution: "clone@npm:1.0.4" @@ -5401,15 +4491,6 @@ __metadata: languageName: node linkType: hard -"color-convert@npm:^1.9.0": - version: 1.9.3 - resolution: "color-convert@npm:1.9.3" - dependencies: - color-name: "npm:1.1.3" - checksum: 10/ffa319025045f2973919d155f25e7c00d08836b6b33ea2d205418c59bd63a665d713c52d9737a9e0fe467fb194b40fbef1d849bae80d674568ee220a31ef3d10 - languageName: node - linkType: hard - "color-convert@npm:^2.0.1": version: 2.0.1 resolution: "color-convert@npm:2.0.1" @@ -5419,13 +4500,6 @@ __metadata: languageName: node linkType: hard -"color-name@npm:1.1.3": - version: 1.1.3 - resolution: "color-name@npm:1.1.3" - checksum: 10/09c5d3e33d2105850153b14466501f2bfb30324a2f76568a408763a3b7433b0e50e5b4ab1947868e65cb101bb7cb75029553f2c333b6d4b8138a73fcc133d69d - languageName: node - linkType: hard - "color-name@npm:^1.0.0, color-name@npm:~1.1.4": version: 1.1.4 resolution: "color-name@npm:1.1.4" @@ -5443,7 +4517,7 @@ __metadata: languageName: node linkType: hard -"color-support@npm:^1.1.2, color-support@npm:^1.1.3": +"color-support@npm:^1.1.3": version: 1.1.3 resolution: "color-support@npm:1.1.3" bin: @@ -5462,13 +4536,6 @@ __metadata: languageName: node linkType: hard -"colorette@npm:^2.0.19, colorette@npm:^2.0.20": - version: 2.0.20 - resolution: "colorette@npm:2.0.20" - checksum: 10/0b8de48bfa5d10afc160b8eaa2b9938f34a892530b2f7d7897e0458d9535a066e3998b49da9d21161c78225b272df19ae3a64d6df28b4c9734c0e55bbd02406f - languageName: node - linkType: hard - "combined-stream@npm:^1.0.8": version: 1.0.8 resolution: "combined-stream@npm:1.0.8" @@ -5499,13 +4566,6 @@ __metadata: languageName: node linkType: hard -"commander@npm:^7.1.0": - version: 7.2.0 - resolution: "commander@npm:7.2.0" - checksum: 10/9973af10727ad4b44f26703bf3e9fdc323528660a7590efe3aa9ad5042b4584c0deed84ba443f61c9d6f02dade54a5a5d3c95e306a1e1630f8374ae6db16c06d - languageName: node - linkType: hard - "commander@npm:^8.3.0": version: 8.3.0 resolution: "commander@npm:8.3.0" @@ -5527,14 +4587,14 @@ __metadata: languageName: node linkType: hard -"console-control-strings@npm:^1.0.0, console-control-strings@npm:^1.1.0": +"console-control-strings@npm:^1.1.0": version: 1.1.0 resolution: "console-control-strings@npm:1.1.0" checksum: 10/27b5fa302bc8e9ae9e98c03c66d76ca289ad0c61ce2fe20ab288d288bee875d217512d2edb2363fc83165e88f1c405180cf3f5413a46e51b4fe1a004840c6cdb languageName: node linkType: hard -"content-disposition@npm:0.5.4, content-disposition@npm:^0.5.4": +"content-disposition@npm:0.5.4": version: 0.5.4 resolution: "content-disposition@npm:0.5.4" dependencies: @@ -5581,30 +4641,6 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:^5.0.1": - version: 5.1.0 - resolution: "cross-spawn@npm:5.1.0" - dependencies: - lru-cache: "npm:^4.0.1" - shebang-command: "npm:^1.2.0" - which: "npm:^1.2.9" - checksum: 10/726939c9954fc70c20e538923feaaa33bebc253247d13021737c3c7f68cdc3e0a57f720c0fe75057c0387995349f3f12e20e9bfdbf12274db28019c7ea4ec166 - languageName: node - linkType: hard - -"cross-spawn@npm:^6.0.5": - version: 6.0.5 - resolution: "cross-spawn@npm:6.0.5" - dependencies: - nice-try: "npm:^1.0.4" - path-key: "npm:^2.0.1" - semver: "npm:^5.5.0" - shebang-command: "npm:^1.2.0" - which: "npm:^1.2.9" - checksum: 10/f07e643b4875f26adffcd7f13bc68d9dff20cf395f8ed6f43a23f3ee24fc3a80a870a32b246fd074e514c8fd7da5f978ac6a7668346eec57aa87bac89c1ed3a1 - languageName: node - linkType: hard - "cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": version: 7.0.3 resolution: "cross-spawn@npm:7.0.3" @@ -5616,26 +4652,6 @@ __metadata: languageName: node linkType: hard -"css-select@npm:^5.1.0": - version: 5.1.0 - resolution: "css-select@npm:5.1.0" - dependencies: - boolbase: "npm:^1.0.0" - css-what: "npm:^6.1.0" - domhandler: "npm:^5.0.2" - domutils: "npm:^3.0.1" - nth-check: "npm:^2.0.1" - checksum: 10/d486b1e7eb140468218a5ab5af53257e01f937d2173ac46981f6b7de9c5283d55427a36715dc8decfc0c079cf89259ac5b41ef58f6e1a422eee44ab8bfdc78da - languageName: node - linkType: hard - -"css-what@npm:^6.1.0": - version: 6.1.0 - resolution: "css-what@npm:6.1.0" - checksum: 10/c67a3a2d0d81843af87f8bf0a4d0845b0f952377714abbb2884e48942409d57a2110eabee003609d02ee487b054614bdfcfc59ee265728ff105bd5aa221c1d0e - languageName: node - linkType: hard - "cssesc@npm:^3.0.0": version: 3.0.0 resolution: "cssesc@npm:3.0.0" @@ -5659,20 +4675,6 @@ __metadata: languageName: node linkType: hard -"dargs@npm:~7.0.0": - version: 7.0.0 - resolution: "dargs@npm:7.0.0" - checksum: 10/b8f1e3cba59c42e1f13a114ad4848c3fc1cf7470f633ee9e9f1043762429bc97d91ae31b826fb135eefde203a3fdb20deb0c0a0222ac29d937b8046085d668d1 - languageName: node - linkType: hard - -"data-uri-to-buffer@npm:^4.0.0": - version: 4.0.1 - resolution: "data-uri-to-buffer@npm:4.0.1" - checksum: 10/0d0790b67ffec5302f204c2ccca4494f70b4e2d940fea3d36b09f0bb2b8539c2e86690429eb1f1dc4bcc9e4df0644193073e63d9ee48ac9fce79ec1506e4aa4c - languageName: node - linkType: hard - "date-fns@npm:^2.29.3": version: 2.30.0 resolution: "date-fns@npm:2.30.0" @@ -5789,7 +4791,7 @@ __metadata: languageName: node linkType: hard -"defer-to-connect@npm:^2.0.0, defer-to-connect@npm:^2.0.1": +"defer-to-connect@npm:^2.0.1": version: 2.0.1 resolution: "defer-to-connect@npm:2.0.1" checksum: 10/8a9b50d2f25446c0bfefb55a48e90afd58f85b21bcf78e9207cd7b804354f6409032a1705c2491686e202e64fc05f147aa5aa45f9aa82627563f045937f5791b @@ -5869,13 +4871,6 @@ __metadata: languageName: node linkType: hard -"detect-libc@npm:^2.0.0": - version: 2.0.2 - resolution: "detect-libc@npm:2.0.2" - checksum: 10/6118f30c0c425b1e56b9d2609f29bec50d35a6af0b762b6ad127271478f3bbfda7319ce869230cf1a351f2b219f39332cde290858553336d652c77b970f15de8 - languageName: node - linkType: hard - "detect-libc@npm:^2.0.3": version: 2.0.3 resolution: "detect-libc@npm:2.0.3" @@ -5956,20 +4951,6 @@ __metadata: languageName: node linkType: hard -"discord-api-types@npm:^0.37.2": - version: 0.37.28 - resolution: "discord-api-types@npm:0.37.28" - checksum: 10/b478bc29b19234701e5c74edd8212a2673bf7aa0f1c37315e6149269099fd378fb61204cba67ce5289d6fc868929d21d3e34f98b9d3d9f3f563b363601b204aa - languageName: node - linkType: hard - -"discord-api-types@npm:^0.37.35, discord-api-types@npm:^0.37.41": - version: 0.37.46 - resolution: "discord-api-types@npm:0.37.46" - checksum: 10/3d3194a76ef59a18e82ce8dddf1eabe5285972dc3f5aceacdb80dbf1314c6f644c800b503a80bc49d98e2d3f4a51292af93f5f3aa7fc4ee5176eb1f25ffd7a4e - languageName: node - linkType: hard - "discord-api-types@npm:^0.37.50": version: 0.37.52 resolution: "discord-api-types@npm:0.37.52" @@ -5977,7 +4958,7 @@ __metadata: languageName: node linkType: hard -"discord-player@workspace:^, discord-player@workspace:packages/discord-player": +"discord-player@workspace:packages/discord-player": version: 0.0.0-use.local resolution: "discord-player@workspace:packages/discord-player" dependencies: @@ -5998,26 +4979,10 @@ __metadata: tsup: "npm:^7.2.0" typescript: "npm:^5.2.2" vitest: "npm:^0.34.6" - peerDependencies: - "@discord-player/extractor": "workspace:^" + ws: "npm:^8.17.0" languageName: unknown linkType: soft -"discord-voip@npm:^0.1.2": - version: 0.1.2 - resolution: "discord-voip@npm:0.1.2" - dependencies: - "@discord-player/ffmpeg": "npm:^0.1.0" - "@discord-player/opus": "npm:^0.1.0" - "@types/ws": "npm:^8.5.5" - discord-api-types: "npm:^0.37.50" - prism-media: "npm:^1.3.5" - tslib: "npm:^2.6.1" - ws: "npm:^8.13.0" - checksum: 10/5ffba1b2d9e914e58d68831606e1b319067a6d0c4195314fdd49575a87d2a2849757597ebb020965b54bb4be43acdd8c1ae7a50cc2f5ded8a8b38696ac1d5bb7 - languageName: node - linkType: hard - "discord-voip@npm:^0.1.3": version: 0.1.3 resolution: "discord-voip@npm:0.1.3" @@ -6103,65 +5068,6 @@ __metadata: languageName: node linkType: hard -"dom-serializer@npm:^2.0.0": - version: 2.0.0 - resolution: "dom-serializer@npm:2.0.0" - dependencies: - domelementtype: "npm:^2.3.0" - domhandler: "npm:^5.0.2" - entities: "npm:^4.2.0" - checksum: 10/e3bf9027a64450bca0a72297ecdc1e3abb7a2912268a9f3f5d33a2e29c1e2c3502c6e9f860fc6625940bfe0cfb57a44953262b9e94df76872fdfb8151097eeb3 - languageName: node - linkType: hard - -"domelementtype@npm:^2.3.0": - version: 2.3.0 - resolution: "domelementtype@npm:2.3.0" - checksum: 10/ee837a318ff702622f383409d1f5b25dd1024b692ef64d3096ff702e26339f8e345820f29a68bcdcea8cfee3531776b3382651232fbeae95612d6f0a75efb4f6 - languageName: node - linkType: hard - -"domhandler@npm:^5.0.1, domhandler@npm:^5.0.2": - version: 5.0.3 - resolution: "domhandler@npm:5.0.3" - dependencies: - domelementtype: "npm:^2.3.0" - checksum: 10/809b805a50a9c6884a29f38aec0a4e1b4537f40e1c861950ed47d10b049febe6b79ab72adaeeebb3cc8fc1cd33f34e97048a72a9265103426d93efafa78d3e96 - languageName: node - linkType: hard - -"domutils@npm:^3.0.1": - version: 3.0.1 - resolution: "domutils@npm:3.0.1" - dependencies: - dom-serializer: "npm:^2.0.0" - domelementtype: "npm:^2.3.0" - domhandler: "npm:^5.0.1" - checksum: 10/c0031e4bf89bf701c552c6aa7937262351ae863d5bb0395ebae9cdb23eb3de0077343ca0ddfa63861d98c31c02bbabe4c6e0e11be87b04a090a4d5dbb75197dc - languageName: node - linkType: hard - -"dotenv-expand@npm:^10.0.0": - version: 10.0.0 - resolution: "dotenv-expand@npm:10.0.0" - checksum: 10/b41eb278bc96b92cbf3037ca5f3d21e8845bf165dc06b6f9a0a03d278c2bd5a01c0cfbb3528ae3a60301ba1a8a9cace30e748c54b460753bc00d4c014b675597 - languageName: node - linkType: hard - -"dotenv@npm:^16.3.0": - version: 16.3.1 - resolution: "dotenv@npm:16.3.1" - checksum: 10/dbb778237ef8750e9e3cd1473d3c8eaa9cc3600e33a75c0e36415d0fa0848197f56c3800f77924c70e7828f0b03896818cd52f785b07b9ad4d88dba73fbba83f - languageName: node - linkType: hard - -"duplexer@npm:~0.1.1": - version: 0.1.2 - resolution: "duplexer@npm:0.1.2" - checksum: 10/62ba61a830c56801db28ff6305c7d289b6dc9f859054e8c982abd8ee0b0a14d2e9a8e7d086ffee12e868d43e2bbe8a964be55ddbd8c8957714c87373c7a4f9b0 - languageName: node - linkType: hard - "ee-first@npm:1.1.1": version: 1.1.1 resolution: "ee-first@npm:1.1.1" @@ -6206,15 +5112,6 @@ __metadata: languageName: node linkType: hard -"end-of-stream@npm:^1.1.0": - version: 1.4.4 - resolution: "end-of-stream@npm:1.4.4" - dependencies: - once: "npm:^1.4.0" - checksum: 10/530a5a5a1e517e962854a31693dbb5c0b2fc40b46dad2a56a2deec656ca040631124f4795823acc68238147805f8b021abbe221f4afed5ef3c8e8efc2024908b - languageName: node - linkType: hard - "engine.io-parser@npm:~5.2.1": version: 5.2.2 resolution: "engine.io-parser@npm:5.2.2" @@ -6250,13 +5147,6 @@ __metadata: languageName: node linkType: hard -"entities@npm:^4.2.0": - version: 4.4.0 - resolution: "entities@npm:4.4.0" - checksum: 10/b627cb900e901cc7817037b83bf993a1cbf6a64850540f7526af7bcf9c7d09ebc671198e6182cfae4680f733799e2852e6a1c46aa62ff36eb99680057a038df5 - languageName: node - linkType: hard - "entities@npm:^4.4.0": version: 4.5.0 resolution: "entities@npm:4.5.0" @@ -6278,15 +5168,6 @@ __metadata: languageName: node linkType: hard -"error-ex@npm:^1.3.1": - version: 1.3.2 - resolution: "error-ex@npm:1.3.2" - dependencies: - is-arrayish: "npm:^0.2.1" - checksum: 10/d547740aa29c34e753fb6fed2c5de81802438529c12b3673bd37b6bb1fe49b9b7abdc3c11e6062fe625d8a296b3cf769a80f878865e25e685f787763eede3ffb - languageName: node - linkType: hard - "es-abstract@npm:^1.19.0, es-abstract@npm:^1.20.4": version: 1.21.2 resolution: "es-abstract@npm:1.21.2" @@ -6547,83 +5428,6 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:~0.17.6": - version: 0.17.19 - resolution: "esbuild@npm:0.17.19" - dependencies: - "@esbuild/android-arm": "npm:0.17.19" - "@esbuild/android-arm64": "npm:0.17.19" - "@esbuild/android-x64": "npm:0.17.19" - "@esbuild/darwin-arm64": "npm:0.17.19" - "@esbuild/darwin-x64": "npm:0.17.19" - "@esbuild/freebsd-arm64": "npm:0.17.19" - "@esbuild/freebsd-x64": "npm:0.17.19" - "@esbuild/linux-arm": "npm:0.17.19" - "@esbuild/linux-arm64": "npm:0.17.19" - "@esbuild/linux-ia32": "npm:0.17.19" - "@esbuild/linux-loong64": "npm:0.17.19" - "@esbuild/linux-mips64el": "npm:0.17.19" - "@esbuild/linux-ppc64": "npm:0.17.19" - "@esbuild/linux-riscv64": "npm:0.17.19" - "@esbuild/linux-s390x": "npm:0.17.19" - "@esbuild/linux-x64": "npm:0.17.19" - "@esbuild/netbsd-x64": "npm:0.17.19" - "@esbuild/openbsd-x64": "npm:0.17.19" - "@esbuild/sunos-x64": "npm:0.17.19" - "@esbuild/win32-arm64": "npm:0.17.19" - "@esbuild/win32-ia32": "npm:0.17.19" - "@esbuild/win32-x64": "npm:0.17.19" - dependenciesMeta: - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-x64": - optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: 10/86ada7cad6d37a3445858fee31ca39fc6c0436c7c00b2e07b9ce308235be67f36aefe0dda25da9ab08653fde496d1e759d6ad891ce9479f9e1fb4964c8f2a0fa - languageName: node - linkType: hard - "escalade@npm:^3.1.1": version: 3.1.1 resolution: "escalade@npm:3.1.1" @@ -6645,13 +5449,6 @@ __metadata: languageName: node linkType: hard -"escape-string-regexp@npm:^1.0.5": - version: 1.0.5 - resolution: "escape-string-regexp@npm:1.0.5" - checksum: 10/6092fda75c63b110c706b6a9bfde8a612ad595b628f0bd2147eea1d3406723020810e591effc7db1da91d80a71a737a313567c5abb3813e8d9c71f4aa595b410 - languageName: node - linkType: hard - "escape-string-regexp@npm:^4.0.0": version: 4.0.0 resolution: "escape-string-regexp@npm:4.0.0" @@ -7128,37 +5925,7 @@ __metadata: languageName: node linkType: hard -"event-stream@npm:=3.3.4": - version: 3.3.4 - resolution: "event-stream@npm:3.3.4" - dependencies: - duplexer: "npm:~0.1.1" - from: "npm:~0" - map-stream: "npm:~0.1.0" - pause-stream: "npm:0.0.11" - split: "npm:0.3" - stream-combiner: "npm:~0.0.4" - through: "npm:~2.3.1" - checksum: 10/48ea0e17df89ff45778c25e7111a6691401c902162823ddd7656d83fc972e75380f789f7a48f272f50fe7015420cc04f835d458560bf95e34b2c7a479570c8fb - languageName: node - linkType: hard - -"execa@npm:^0.7.0": - version: 0.7.0 - resolution: "execa@npm:0.7.0" - dependencies: - cross-spawn: "npm:^5.0.1" - get-stream: "npm:^3.0.0" - is-stream: "npm:^1.1.0" - npm-run-path: "npm:^2.0.0" - p-finally: "npm:^1.0.0" - signal-exit: "npm:^3.0.0" - strip-eof: "npm:^1.0.0" - checksum: 10/7c1721de38e51d67cef2367b1f6037c4a89bc64993d93683f9f740fc74d6930dfc92faf2749b917b7337a8d632d7b86a4673400f762bc1fe4a977ea6b0f9055f - languageName: node - linkType: hard - -"execa@npm:^5.0.0, execa@npm:~5.1.0": +"execa@npm:^5.0.0": version: 5.1.1 resolution: "execa@npm:5.1.1" dependencies: @@ -7192,15 +5959,6 @@ __metadata: languageName: node linkType: hard -"executable@npm:^4.1.0": - version: 4.1.1 - resolution: "executable@npm:4.1.1" - dependencies: - pify: "npm:^2.2.0" - checksum: 10/f01927ce59bccec804e171bf859a26e362c1f50aa9ebc69f7cafdcce3859d29d4b6267fd47237c18b0a1830614bd3f0ee14b7380d9bad18a4e7af9b5f0b6984f - languageName: node - linkType: hard - "express@npm:^4.18.2": version: 4.19.2 resolution: "express@npm:4.19.2" @@ -7240,25 +5998,6 @@ __metadata: languageName: node linkType: hard -"ext-list@npm:^2.0.0": - version: 2.2.2 - resolution: "ext-list@npm:2.2.2" - dependencies: - mime-db: "npm:^1.28.0" - checksum: 10/fe69fedbef044e14d4ce9e84c6afceb696ba71500c15b8d0ce0a1e280237e17c95031b3d62d5e597652fea0065b9bf957346b3900d989dff59128222231ac859 - languageName: node - linkType: hard - -"ext-name@npm:^5.0.0": - version: 5.0.0 - resolution: "ext-name@npm:5.0.0" - dependencies: - ext-list: "npm:^2.0.0" - sort-keys-length: "npm:^1.0.0" - checksum: 10/f598269bd5de4295540ea7d6f8f6a01d82a7508f148b7700a05628ef6121648d26e6e5e942049e953b3051863df6b54bd8fe951e7877f185e34ace5d44370b33 - languageName: node - linkType: hard - "extend-shallow@npm:^2.0.1": version: 2.0.1 resolution: "extend-shallow@npm:2.0.1" @@ -7282,7 +6021,7 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.12, fast-glob@npm:^3.2.5": +"fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.12": version: 3.2.12 resolution: "fast-glob@npm:3.2.12" dependencies: @@ -7351,16 +6090,6 @@ __metadata: languageName: node linkType: hard -"fetch-blob@npm:^3.1.2, fetch-blob@npm:^3.1.4": - version: 3.2.0 - resolution: "fetch-blob@npm:3.2.0" - dependencies: - node-domexception: "npm:^1.0.0" - web-streams-polyfill: "npm:^3.0.3" - checksum: 10/5264ecceb5fdc19eb51d1d0359921f12730941e333019e673e71eb73921146dceabcb0b8f534582be4497312d656508a439ad0f5edeec2b29ab2e10c72a1f86b - languageName: node - linkType: hard - "file-entry-cache@npm:^6.0.1": version: 6.0.1 resolution: "file-entry-cache@npm:6.0.1" @@ -7370,18 +6099,7 @@ __metadata: languageName: node linkType: hard -"file-type@npm:^16.5.4": - version: 16.5.4 - resolution: "file-type@npm:16.5.4" - dependencies: - readable-web-to-node-stream: "npm:^3.0.0" - strtok3: "npm:^6.2.4" - token-types: "npm:^4.1.1" - checksum: 10/46ced46bb925ab547e0a6d43108a26d043619d234cb0588d7abce7b578dafac142bcfd2e23a6adb0a4faa4b951bd1b14b355134a193362e07cd352f9bf0dc349 - languageName: node - linkType: hard - -"file-type@npm:^17.1.4, file-type@npm:^17.1.6": +"file-type@npm:^17.1.4": version: 17.1.6 resolution: "file-type@npm:17.1.6" dependencies: @@ -7392,24 +6110,6 @@ __metadata: languageName: node linkType: hard -"filename-reserved-regex@npm:^3.0.0": - version: 3.0.0 - resolution: "filename-reserved-regex@npm:3.0.0" - checksum: 10/1803e19ce64d7cb88ee5a1bd3ce282470a5c263987269222426d889049fc857e302284fa71937de9582eba7a9f39539557d45e0562f2fa51cade8efc68c65dd9 - languageName: node - linkType: hard - -"filenamify@npm:^5.0.2": - version: 5.1.1 - resolution: "filenamify@npm:5.1.1" - dependencies: - filename-reserved-regex: "npm:^3.0.0" - strip-outer: "npm:^2.0.0" - trim-repeated: "npm:^2.0.0" - checksum: 10/55a7ed0858eb2655bb1bb1e945a59e3fb30ba4767f6924fa064ccd731bff07678aac3cb4f3899ae0e1621fe81d6472b5688232bb6afd4eeb989ade785fc1c6f1 - languageName: node - linkType: hard - "fill-range@npm:^7.0.1": version: 7.0.1 resolution: "fill-range@npm:7.0.1" @@ -7444,15 +6144,6 @@ __metadata: languageName: node linkType: hard -"find-versions@npm:^5.0.0": - version: 5.1.0 - resolution: "find-versions@npm:5.1.0" - dependencies: - semver-regex: "npm:^4.0.5" - checksum: 10/680bdb0081f631f7bfb6f0f8edcfa0b74ab8cabc82097a4527a37b0d042aabc56685bf459ff27991eab0baddc04eb8e3bba8a2869f5004ecf7cdd2779b6e51de - languageName: node - linkType: hard - "flat-cache@npm:^3.0.4": version: 3.0.4 resolution: "flat-cache@npm:3.0.4" @@ -7470,16 +6161,6 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.15.0": - version: 1.15.2 - resolution: "follow-redirects@npm:1.15.2" - peerDependenciesMeta: - debug: - optional: true - checksum: 10/8be0d39919770054812537d376850ccde0b4762b0501c440bd08724971a078123b55f57704f2984e0664fecc0c86adea85add63295804d9dce401cd9604c91d3 - languageName: node - linkType: hard - "follow-redirects@npm:^1.15.6": version: 1.15.6 resolution: "follow-redirects@npm:1.15.6" @@ -7524,15 +6205,6 @@ __metadata: languageName: node linkType: hard -"formdata-polyfill@npm:^4.0.10": - version: 4.0.10 - resolution: "formdata-polyfill@npm:4.0.10" - dependencies: - fetch-blob: "npm:^3.1.2" - checksum: 10/9b5001d2edef3c9449ac3f48bd4f8cc92e7d0f2e7c1a5c8ba555ad4e77535cc5cf621fabe49e97f304067037282dd9093b9160a3cb533e46420b446c4e6bc06f - languageName: node - linkType: hard - "forwarded@npm:0.2.0": version: 0.2.0 resolution: "forwarded@npm:0.2.0" @@ -7554,13 +6226,6 @@ __metadata: languageName: node linkType: hard -"from@npm:~0": - version: 0.1.7 - resolution: "from@npm:0.1.7" - checksum: 10/b85125b7890489656eb2e4f208f7654a93ec26e3aefaf3bbbcc0d496fc1941e4405834fcc9fe7333192aa2187905510ace70417bbf9ac6f6f4784a731d986939 - languageName: node - linkType: hard - "fs-extra@npm:^11.1.0": version: 11.1.0 resolution: "fs-extra@npm:11.1.0" @@ -7677,23 +6342,6 @@ __metadata: languageName: node linkType: hard -"gauge@npm:^3.0.0": - version: 3.0.2 - resolution: "gauge@npm:3.0.2" - dependencies: - aproba: "npm:^1.0.3 || ^2.0.0" - color-support: "npm:^1.1.2" - console-control-strings: "npm:^1.0.0" - has-unicode: "npm:^2.0.1" - object-assign: "npm:^4.1.1" - signal-exit: "npm:^3.0.0" - string-width: "npm:^4.2.3" - strip-ansi: "npm:^6.0.1" - wide-align: "npm:^1.1.2" - checksum: 10/46df086451672a5fecd58f7ec86da74542c795f8e00153fbef2884286ce0e86653c3eb23be2d0abb0c4a82b9b2a9dec3b09b6a1cf31c28085fa0376599a26589 - languageName: node - linkType: hard - "gauge@npm:^4.0.3": version: 4.0.4 resolution: "gauge@npm:4.0.4" @@ -7717,16 +6365,6 @@ __metadata: languageName: node linkType: hard -"genius-lyrics@npm:^4.4.6": - version: 4.4.6 - resolution: "genius-lyrics@npm:4.4.6" - dependencies: - node-html-parser: "npm:^6.1.9" - undici: "npm:^5.24.0" - checksum: 10/468425609e21fb3268f319ee7ba18173c045e3a2d19fd5769bf42598c9532afc3e60568b0e1715a6d75f39430462bd000c0fa4cafcbe71face1187eb7f08edda - languageName: node - linkType: hard - "get-caller-file@npm:^2.0.5": version: 2.0.5 resolution: "get-caller-file@npm:2.0.5" @@ -7767,22 +6405,6 @@ __metadata: languageName: node linkType: hard -"get-stream@npm:^3.0.0": - version: 3.0.0 - resolution: "get-stream@npm:3.0.0" - checksum: 10/de14fbb3b4548ace9ab6376be852eef9898c491282e29595bc908a1814a126d3961b11cd4b7be5220019fe3b2abb84568da7793ad308fc139925a217063fa159 - languageName: node - linkType: hard - -"get-stream@npm:^5.1.0": - version: 5.2.0 - resolution: "get-stream@npm:5.2.0" - dependencies: - pump: "npm:^3.0.0" - checksum: 10/13a73148dca795e41421013da6e3ebff8ccb7fba4d2f023fd0c6da2c166ec4e789bec9774a73a7b49c08daf2cae552f8a3e914042ac23b5f59dd278cc8f9cbfb - languageName: node - linkType: hard - "get-stream@npm:^6.0.0, get-stream@npm:^6.0.1": version: 6.0.1 resolution: "get-stream@npm:6.0.1" @@ -7800,7 +6422,7 @@ __metadata: languageName: node linkType: hard -"get-tsconfig@npm:^4.4.0, get-tsconfig@npm:^4.5.0": +"get-tsconfig@npm:^4.5.0": version: 4.6.0 resolution: "get-tsconfig@npm:4.6.0" dependencies: @@ -7966,25 +6588,6 @@ __metadata: languageName: node linkType: hard -"got@npm:^11.8.5": - version: 11.8.6 - resolution: "got@npm:11.8.6" - dependencies: - "@sindresorhus/is": "npm:^4.0.0" - "@szmarczak/http-timer": "npm:^4.0.5" - "@types/cacheable-request": "npm:^6.0.1" - "@types/responselike": "npm:^1.0.0" - cacheable-lookup: "npm:^5.0.3" - cacheable-request: "npm:^7.0.2" - decompress-response: "npm:^6.0.0" - http2-wrapper: "npm:^1.0.0-beta.5.2" - lowercase-keys: "npm:^2.0.0" - p-cancelable: "npm:^2.0.0" - responselike: "npm:^2.0.0" - checksum: 10/a30c74029d81bd5fe50dea1a0c970595d792c568e188ff8be254b5bc11e6158d1b014570772d4a30d0a97723e7dd34e7c8cc1a2f23018f60aece3070a7a5c2a5 - languageName: node - linkType: hard - "got@npm:^12.0.0, got@npm:^12.1.0": version: 12.6.1 resolution: "got@npm:12.6.1" @@ -8070,13 +6673,6 @@ __metadata: languageName: node linkType: hard -"has-flag@npm:^3.0.0": - version: 3.0.0 - resolution: "has-flag@npm:3.0.0" - checksum: 10/4a15638b454bf086c8148979aae044dd6e39d63904cd452d970374fa6a87623423da485dfb814e7be882e05c096a7ccf1ebd48e7e7501d0208d8384ff4dea73b - languageName: node - linkType: hard - "has-flag@npm:^4.0.0": version: 4.0.0 resolution: "has-flag@npm:4.0.0" @@ -8413,29 +7009,6 @@ __metadata: languageName: node linkType: hard -"he@npm:1.2.0": - version: 1.2.0 - resolution: "he@npm:1.2.0" - bin: - he: bin/he - checksum: 10/d09b2243da4e23f53336e8de3093e5c43d2c39f8d0d18817abfa32ce3e9355391b2edb4bb5edc376aea5d4b0b59d6a0482aab4c52bc02ef95751e4b818e847f1 - languageName: node - linkType: hard - -"himalaya@npm:~1.1.0": - version: 1.1.0 - resolution: "himalaya@npm:1.1.0" - checksum: 10/cae7b3612951220a9cfe33e2df1a3eb0d12f9280be04637f4f60820fb46571248557923501802a803184c07de5ae425015a3406c3db6fdb50e406828fbbd0d4c - languageName: node - linkType: hard - -"hosted-git-info@npm:^2.1.4": - version: 2.8.9 - resolution: "hosted-git-info@npm:2.8.9" - checksum: 10/96da7d412303704af41c3819207a09ea2cab2de97951db4cf336bb8bce8d8e36b9a6821036ad2e55e67d3be0af8f967a7b57981203fbfb88bc05cd803407b8c3 - languageName: node - linkType: hard - "html-void-elements@npm:^2.0.0": version: 2.0.1 resolution: "html-void-elements@npm:2.0.1" @@ -8443,13 +7016,6 @@ __metadata: languageName: node linkType: hard -"http-cache-semantics@npm:^4.0.0, http-cache-semantics@npm:^4.1.1": - version: 4.1.1 - resolution: "http-cache-semantics@npm:4.1.1" - checksum: 10/362d5ed66b12ceb9c0a328fb31200b590ab1b02f4a254a697dc796850cc4385603e75f53ec59f768b2dad3bfa1464bd229f7de278d2899a0e3beffc634b6683f - languageName: node - linkType: hard - "http-cache-semantics@npm:^4.1.0": version: 4.1.0 resolution: "http-cache-semantics@npm:4.1.0" @@ -8457,21 +7023,10 @@ __metadata: languageName: node linkType: hard -"http-cookie-agent@npm:^5.0.4": - version: 5.0.4 - resolution: "http-cookie-agent@npm:5.0.4" - dependencies: - agent-base: "npm:^7.1.0" - peerDependencies: - deasync: ^0.1.26 - tough-cookie: ^4.0.0 - undici: ^5.11.0 - peerDependenciesMeta: - deasync: - optional: true - undici: - optional: true - checksum: 10/c1cae8efb1353999c127b2590d2d012a4b09ddf00a87f06ca3f39c821b3d5afa409baaf1598ee34cb6b83e93013e1a84fa2c66e664296f83ede06a485cb0525e +"http-cache-semantics@npm:^4.1.1": + version: 4.1.1 + resolution: "http-cache-semantics@npm:4.1.1" + checksum: 10/362d5ed66b12ceb9c0a328fb31200b590ab1b02f4a254a697dc796850cc4385603e75f53ec59f768b2dad3bfa1464bd229f7de278d2899a0e3beffc634b6683f languageName: node linkType: hard @@ -8499,16 +7054,6 @@ __metadata: languageName: node linkType: hard -"http2-wrapper@npm:^1.0.0-beta.5.2": - version: 1.0.3 - resolution: "http2-wrapper@npm:1.0.3" - dependencies: - quick-lru: "npm:^5.1.1" - resolve-alpn: "npm:^1.0.0" - checksum: 10/8097ee2699440c2e64bda52124990cc5b0fb347401c7797b1a0c1efd5a0f79a4ebaa68e8a6ac3e2dde5f09460c1602764da6da2412bad628ed0a3b0ae35e72d4 - languageName: node - linkType: hard - "http2-wrapper@npm:^2.1.10": version: 2.2.1 resolution: "http2-wrapper@npm:2.2.1" @@ -8736,13 +7281,6 @@ __metadata: languageName: node linkType: hard -"is-arrayish@npm:^0.2.1": - version: 0.2.1 - resolution: "is-arrayish@npm:0.2.1" - checksum: 10/73ced84fa35e59e2c57da2d01e12cd01479f381d7f122ce41dcbb713f09dbfc651315832cd2bf8accba7681a69e4d6f1e03941d94dd10040d415086360e7005e - languageName: node - linkType: hard - "is-arrayish@npm:^0.3.1": version: 0.3.2 resolution: "is-arrayish@npm:0.3.2" @@ -8957,13 +7495,6 @@ __metadata: languageName: node linkType: hard -"is-plain-obj@npm:^1.0.0": - version: 1.1.0 - resolution: "is-plain-obj@npm:1.1.0" - checksum: 10/0ee04807797aad50859652a7467481816cbb57e5cc97d813a7dcd8915da8195dc68c436010bf39d195226cde6a2d352f4b815f16f26b7bf486a5754290629931 - languageName: node - linkType: hard - "is-plain-obj@npm:^4.0.0": version: 4.1.0 resolution: "is-plain-obj@npm:4.1.0" @@ -9006,13 +7537,6 @@ __metadata: languageName: node linkType: hard -"is-stream@npm:^1.1.0": - version: 1.1.0 - resolution: "is-stream@npm:1.1.0" - checksum: 10/351aa77c543323c4e111204482808cfad68d2e940515949e31ccd0b010fc13d5fba4b9c230e4887fd24284713040f43e542332fbf172f6b9944b7d62e389c0ec - languageName: node - linkType: hard - "is-stream@npm:^2.0.0": version: 2.0.1 resolution: "is-stream@npm:2.0.1" @@ -9065,13 +7589,6 @@ __metadata: languageName: node linkType: hard -"is-unix@npm:~2.0.1": - version: 2.0.7 - resolution: "is-unix@npm:2.0.7" - checksum: 10/64a6f0bb1e4fe2a4d152484073547c5d55e66b4b7bc9857e5074d251c8d6efbf524249e3663c79f5b98034348b8cc07a36770ebe86fece2635c9c283ab60fc59 - languageName: node - linkType: hard - "is-weakref@npm:^1.0.2": version: 1.0.2 resolution: "is-weakref@npm:1.0.2" @@ -9097,16 +7614,6 @@ __metadata: languageName: node linkType: hard -"isomorphic-unfetch@npm:^4.0.2": - version: 4.0.2 - resolution: "isomorphic-unfetch@npm:4.0.2" - dependencies: - node-fetch: "npm:^3.2.0" - unfetch: "npm:^5.0.0" - checksum: 10/53561c3e42de8b1d6719563906d0e04367b3cc55b5eb2e5fc1dbd6445ae4a79f914d481716ab5f2ff188e2df45c730bfcc610364df24844514862f52760c14fd - languageName: node - linkType: hard - "jiti@npm:^1.18.2": version: 1.18.2 resolution: "jiti@npm:1.18.2" @@ -9160,13 +7667,6 @@ __metadata: languageName: node linkType: hard -"json-parse-better-errors@npm:^1.0.1": - version: 1.0.2 - resolution: "json-parse-better-errors@npm:1.0.2" - checksum: 10/5553232045359b767b0f2039a6777fede1a8d7dca1a0ffb1f9ef73a7519489ae7f566b2e040f2b4c38edb8e35e37ae07af7f0a52420902f869ee0dbf5dc6c784 - languageName: node - linkType: hard - "json-schema-traverse@npm:^0.4.1": version: 0.4.1 resolution: "json-schema-traverse@npm:0.4.1" @@ -9240,15 +7740,6 @@ __metadata: languageName: node linkType: hard -"keyv@npm:^4.0.0": - version: 4.5.2 - resolution: "keyv@npm:4.5.2" - dependencies: - json-buffer: "npm:3.0.1" - checksum: 10/fbe6068cb46cfbf37b46f4a80e484a5e9c48c9a1eb09d9cb89382db6e12b801b60f07268ec8d7fa8d49f1f1e77badc5820c3135d478022df42691890a4c37038 - languageName: node - linkType: hard - "keyv@npm:^4.5.3": version: 4.5.4 resolution: "keyv@npm:4.5.4" @@ -9344,18 +7835,6 @@ __metadata: languageName: node linkType: hard -"load-json-file@npm:^4.0.0": - version: 4.0.0 - resolution: "load-json-file@npm:4.0.0" - dependencies: - graceful-fs: "npm:^4.1.2" - parse-json: "npm:^4.0.0" - pify: "npm:^3.0.0" - strip-bom: "npm:^3.0.0" - checksum: 10/8f5d6d93ba64a9620445ee9bde4d98b1eac32cf6c8c2d20d44abfa41a6945e7969456ab5f1ca2fb06ee32e206c9769a20eec7002fe290de462e8c884b6b8b356 - languageName: node - linkType: hard - "load-tsconfig@npm:^0.2.3": version: 0.2.5 resolution: "load-tsconfig@npm:0.2.5" @@ -9451,13 +7930,6 @@ __metadata: languageName: node linkType: hard -"lowercase-keys@npm:^2.0.0": - version: 2.0.0 - resolution: "lowercase-keys@npm:2.0.0" - checksum: 10/1c233d2da35056e8c49fae8097ee061b8c799b2f02e33c2bf32f9913c7de8fb481ab04dab7df35e94156c800f5f34e99acbf32b21781d87c3aa43ef7b748b79e - languageName: node - linkType: hard - "lowercase-keys@npm:^3.0.0": version: 3.0.0 resolution: "lowercase-keys@npm:3.0.0" @@ -9465,16 +7937,6 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^4.0.1": - version: 4.1.5 - resolution: "lru-cache@npm:4.1.5" - dependencies: - pseudomap: "npm:^1.0.2" - yallist: "npm:^2.1.2" - checksum: 10/9ec7d73f11a32cba0e80b7a58fdf29970814c0c795acaee1a6451ddfd609bae6ef9df0837f5bbeabb571ecd49c1e2d79e10e9b4ed422cfba17a0cb6145b018a9 - languageName: node - linkType: hard - "lru-cache@npm:^6.0.0": version: 6.0.0 resolution: "lru-cache@npm:6.0.0" @@ -9516,16 +7978,6 @@ __metadata: languageName: node linkType: hard -"m3u8stream@npm:^0.8.6": - version: 0.8.6 - resolution: "m3u8stream@npm:0.8.6" - dependencies: - miniget: "npm:^4.2.2" - sax: "npm:^1.2.4" - checksum: 10/6efb90aa392caf24142eafb88e6f9204655c859fbb3a61f2e6d81d0298d6357a962f392423187f686416bd630733ce461d0d45198a9c0694195dda59d79ec492 - languageName: node - linkType: hard - "magic-string@npm:^0.30.1": version: 0.30.5 resolution: "magic-string@npm:0.30.5" @@ -9535,15 +7987,6 @@ __metadata: languageName: node linkType: hard -"make-dir@npm:^3.1.0": - version: 3.1.0 - resolution: "make-dir@npm:3.1.0" - dependencies: - semver: "npm:^6.0.0" - checksum: 10/484200020ab5a1fdf12f393fe5f385fc8e4378824c940fba1729dcd198ae4ff24867bc7a5646331e50cead8abff5d9270c456314386e629acec6dff4b8016b78 - languageName: node - linkType: hard - "make-fetch-happen@npm:^10.0.3": version: 10.2.1 resolution: "make-fetch-happen@npm:10.2.1" @@ -9568,13 +8011,6 @@ __metadata: languageName: node linkType: hard -"map-stream@npm:~0.1.0": - version: 0.1.0 - resolution: "map-stream@npm:0.1.0" - checksum: 10/f04a07041dccdf8140a4a6613e4731e917153ee031d3c837cb32ea7d609e8fbea538c44053718772f59dd1dca0ce68a5689ad006688612ee720d78bacf5bf24d - languageName: node - linkType: hard - "markdown-extensions@npm:^1.0.0": version: 1.1.1 resolution: "markdown-extensions@npm:1.1.1" @@ -9843,242 +8279,17 @@ __metadata: languageName: node linkType: hard -"mdast@npm:^3.0.0": - version: 3.0.0 - resolution: "mdast@npm:3.0.0" - checksum: 10/92acc29825657d45677c26a358a7ec1c91e16c037545c1619baee9d22457eef967d9ae3e8551d968bf0af7fe443f24dcceda24a3b9473b094fed29fe9d285fda - languageName: node - linkType: hard - -"media-typer@npm:0.3.0": - version: 0.3.0 - resolution: "media-typer@npm:0.3.0" - checksum: 10/38e0984db39139604756903a01397e29e17dcb04207bb3e081412ce725ab17338ecc47220c1b186b6bbe79a658aad1b0d41142884f5a481f36290cdefbe6aa46 - languageName: node - linkType: hard - -"mediaplex-android-arm64@npm:0.0.7": - version: 0.0.7 - resolution: "mediaplex-android-arm64@npm:0.0.7" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - -"mediaplex-android-arm64@npm:0.0.9": - version: 0.0.9 - resolution: "mediaplex-android-arm64@npm:0.0.9" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - -"mediaplex-darwin-arm64@npm:0.0.7": - version: 0.0.7 - resolution: "mediaplex-darwin-arm64@npm:0.0.7" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"mediaplex-darwin-arm64@npm:0.0.9": - version: 0.0.9 - resolution: "mediaplex-darwin-arm64@npm:0.0.9" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"mediaplex-darwin-universal@npm:0.0.7": - version: 0.0.7 - resolution: "mediaplex-darwin-universal@npm:0.0.7" - conditions: os=darwin - languageName: node - linkType: hard - -"mediaplex-darwin-universal@npm:0.0.9": - version: 0.0.9 - resolution: "mediaplex-darwin-universal@npm:0.0.9" - conditions: os=darwin - languageName: node - linkType: hard - -"mediaplex-darwin-x64@npm:0.0.7": - version: 0.0.7 - resolution: "mediaplex-darwin-x64@npm:0.0.7" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"mediaplex-darwin-x64@npm:0.0.9": - version: 0.0.9 - resolution: "mediaplex-darwin-x64@npm:0.0.9" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"mediaplex-freebsd-x64@npm:0.0.7": - version: 0.0.7 - resolution: "mediaplex-freebsd-x64@npm:0.0.7" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"mediaplex-freebsd-x64@npm:0.0.9": - version: 0.0.9 - resolution: "mediaplex-freebsd-x64@npm:0.0.9" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"mediaplex-linux-arm-gnueabihf@npm:0.0.7": - version: 0.0.7 - resolution: "mediaplex-linux-arm-gnueabihf@npm:0.0.7" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"mediaplex-linux-arm-gnueabihf@npm:0.0.9": - version: 0.0.9 - resolution: "mediaplex-linux-arm-gnueabihf@npm:0.0.9" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"mediaplex-linux-x64-gnu@npm:0.0.7": - version: 0.0.7 - resolution: "mediaplex-linux-x64-gnu@npm:0.0.7" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - -"mediaplex-linux-x64-gnu@npm:0.0.9": - version: 0.0.9 - resolution: "mediaplex-linux-x64-gnu@npm:0.0.9" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - -"mediaplex-win32-arm64-msvc@npm:0.0.7": - version: 0.0.7 - resolution: "mediaplex-win32-arm64-msvc@npm:0.0.7" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"mediaplex-win32-arm64-msvc@npm:0.0.9": - version: 0.0.9 - resolution: "mediaplex-win32-arm64-msvc@npm:0.0.9" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"mediaplex-win32-ia32-msvc@npm:0.0.7": - version: 0.0.7 - resolution: "mediaplex-win32-ia32-msvc@npm:0.0.7" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"mediaplex-win32-ia32-msvc@npm:0.0.9": - version: 0.0.9 - resolution: "mediaplex-win32-ia32-msvc@npm:0.0.9" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"mediaplex-win32-x64-msvc@npm:0.0.7": - version: 0.0.7 - resolution: "mediaplex-win32-x64-msvc@npm:0.0.7" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"mediaplex-win32-x64-msvc@npm:0.0.9": - version: 0.0.9 - resolution: "mediaplex-win32-x64-msvc@npm:0.0.9" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"mediaplex@npm:^0.0.7": - version: 0.0.7 - resolution: "mediaplex@npm:0.0.7" - dependencies: - mediaplex-android-arm64: "npm:0.0.7" - mediaplex-darwin-arm64: "npm:0.0.7" - mediaplex-darwin-universal: "npm:0.0.7" - mediaplex-darwin-x64: "npm:0.0.7" - mediaplex-freebsd-x64: "npm:0.0.7" - mediaplex-linux-arm-gnueabihf: "npm:0.0.7" - mediaplex-linux-x64-gnu: "npm:0.0.7" - mediaplex-win32-arm64-msvc: "npm:0.0.7" - mediaplex-win32-ia32-msvc: "npm:0.0.7" - mediaplex-win32-x64-msvc: "npm:0.0.7" - dependenciesMeta: - mediaplex-android-arm64: - optional: true - mediaplex-darwin-arm64: - optional: true - mediaplex-darwin-universal: - optional: true - mediaplex-darwin-x64: - optional: true - mediaplex-freebsd-x64: - optional: true - mediaplex-linux-arm-gnueabihf: - optional: true - mediaplex-linux-x64-gnu: - optional: true - mediaplex-win32-arm64-msvc: - optional: true - mediaplex-win32-ia32-msvc: - optional: true - mediaplex-win32-x64-msvc: - optional: true - checksum: 10/54bdd39f199682007a8a0f641387c3e6b684cd51ba00a6ae073cc9f4882d59b217e6f66cc8b6bccb189585836fbcd2b7a5579a36aa7010dd21774c27cdeb666f - languageName: node - linkType: hard - -"mediaplex@npm:^0.0.9": - version: 0.0.9 - resolution: "mediaplex@npm:0.0.9" - dependencies: - mediaplex-android-arm64: "npm:0.0.9" - mediaplex-darwin-arm64: "npm:0.0.9" - mediaplex-darwin-universal: "npm:0.0.9" - mediaplex-darwin-x64: "npm:0.0.9" - mediaplex-freebsd-x64: "npm:0.0.9" - mediaplex-linux-arm-gnueabihf: "npm:0.0.9" - mediaplex-linux-x64-gnu: "npm:0.0.9" - mediaplex-win32-arm64-msvc: "npm:0.0.9" - mediaplex-win32-ia32-msvc: "npm:0.0.9" - mediaplex-win32-x64-msvc: "npm:0.0.9" - dependenciesMeta: - mediaplex-android-arm64: - optional: true - mediaplex-darwin-arm64: - optional: true - mediaplex-darwin-universal: - optional: true - mediaplex-darwin-x64: - optional: true - mediaplex-freebsd-x64: - optional: true - mediaplex-linux-arm-gnueabihf: - optional: true - mediaplex-linux-x64-gnu: - optional: true - mediaplex-win32-arm64-msvc: - optional: true - mediaplex-win32-ia32-msvc: - optional: true - mediaplex-win32-x64-msvc: - optional: true - checksum: 10/1e922f7a0526369bf8c4755a7f225ae6135cd33f1d5712648c8773cb117c21ccc3a6d8e21a27058201acf56bdf3067596dee858ec8f9324f213daddf9d4fcb42 - languageName: node - linkType: hard - -"memorystream@npm:^0.3.1": - version: 0.3.1 - resolution: "memorystream@npm:0.3.1" - checksum: 10/2e34a1e35e6eb2e342f788f75f96c16f115b81ff6dd39e6c2f48c78b464dbf5b1a4c6ebfae4c573bd0f8dbe8c57d72bb357c60523be184655260d25855c03902 +"mdast@npm:^3.0.0": + version: 3.0.0 + resolution: "mdast@npm:3.0.0" + checksum: 10/92acc29825657d45677c26a358a7ec1c91e16c037545c1619baee9d22457eef967d9ae3e8551d968bf0af7fe443f24dcceda24a3b9473b094fed29fe9d285fda + languageName: node + linkType: hard + +"media-typer@npm:0.3.0": + version: 0.3.0 + resolution: "media-typer@npm:0.3.0" + checksum: 10/38e0984db39139604756903a01397e29e17dcb04207bb3e081412ce725ab17338ecc47220c1b186b6bbe79a658aad1b0d41142884f5a481f36290cdefbe6aa46 languageName: node linkType: hard @@ -10606,7 +8817,7 @@ __metadata: languageName: node linkType: hard -"mime-db@npm:1.52.0, mime-db@npm:^1.28.0": +"mime-db@npm:1.52.0": version: 1.52.0 resolution: "mime-db@npm:1.52.0" checksum: 10/54bb60bf39e6f8689f6622784e668a3d7f8bed6b0d886f5c3c446cb3284be28b30bf707ed05d0fe44a036f8469976b2629bbea182684977b084de9da274694d7 @@ -10645,13 +8856,6 @@ __metadata: languageName: node linkType: hard -"mimic-response@npm:^1.0.0": - version: 1.0.1 - resolution: "mimic-response@npm:1.0.1" - checksum: 10/034c78753b0e622bc03c983663b1cdf66d03861050e0c8606563d149bc2b02d63f62ce4d32be4ab50d0553ae0ffe647fc34d1f5281184c6e1e8cf4d85e8d9823 - languageName: node - linkType: hard - "mimic-response@npm:^3.1.0": version: 3.1.0 resolution: "mimic-response@npm:3.1.0" @@ -10666,20 +8870,6 @@ __metadata: languageName: node linkType: hard -"miniget@npm:^4.2.2": - version: 4.2.2 - resolution: "miniget@npm:4.2.2" - checksum: 10/6a420fa99774ee7cbf8febdb6b9e72246f7e235c13245778864e11b119f501aa5f1d79acc0565fbc46c082b5e02b2092aaddec4245c644e6a9ba2b9b99e05f6c - languageName: node - linkType: hard - -"miniget@npm:^4.2.3": - version: 4.2.3 - resolution: "miniget@npm:4.2.3" - checksum: 10/fac25ed0b64e72d6383698f7416b6e1f23f91a48cd9a2abea8b4d284784329cc1442346ae0fddd3b69d2a846122997eb22af4b0b9176f25efed6d6dce9c1d6e0 - languageName: node - linkType: hard - "minimatch@npm:^3.0.4": version: 3.0.4 resolution: "minimatch@npm:3.0.4" @@ -10869,45 +9059,6 @@ __metadata: languageName: node linkType: hard -"music-bot@workspace:apps/music-bot": - version: 0.0.0-use.local - resolution: "music-bot@workspace:apps/music-bot" - dependencies: - "@discord-player/equalizer": "workspace:^" - "@discord-player/extractor": "workspace:^" - "@discord-player/utils": "workspace:^" - "@discordjs/opus": "npm:^0.9.0" - "@distube/ytdl-core": "npm:^4.13.3" - "@sapphire/discord.js-utilities": "npm:^6.0.3" - "@sapphire/duration": "npm:^1.0.0" - "@sapphire/framework": "npm:^4.2.1" - "@sapphire/plugin-api": "npm:^5.0.1" - "@sapphire/plugin-hmr": "npm:^2.0.0" - "@sapphire/plugin-logger": "npm:^3.0.1" - "@sapphire/prettier-config": "npm:^1.4.5" - "@sapphire/ts-config": "npm:^3.3.4" - "@skyra/env-utilities": "npm:^1.1.0" - "@swc/cli": "npm:^0.1.62" - "@swc/core": "npm:^1.3.37" - "@types/node": "npm:^18.14.6" - "@types/ws": "npm:^8.5.4" - colorette: "npm:^2.0.19" - discord-api-types: "npm:^0.37.35" - discord-player: "workspace:^" - mediaplex: "npm:^0.0.9" - npm-run-all: "npm:^4.1.5" - opusscript: "npm:^0.0.8" - play-dl: "npm:^1.9.7" - prettier: "npm:^2.8.4" - tsc-watch: "npm:^6.0.0" - tsup: "npm:^7.2.0" - tsx: "npm:^3.12.7" - typescript: "npm:^5.2.2" - vitest: "npm:^0.34.6" - youtube-ext: "npm:^1.1.23" - languageName: unknown - linkType: soft - "mz@npm:^2.7.0": version: 2.7.0 resolution: "mz@npm:2.7.0" @@ -11025,13 +9176,6 @@ __metadata: languageName: node linkType: hard -"nice-try@npm:^1.0.4": - version: 1.0.5 - resolution: "nice-try@npm:1.0.5" - checksum: 10/0b4af3b5bb5d86c289f7a026303d192a7eb4417231fe47245c460baeabae7277bcd8fd9c728fb6bd62c30b3e15cd6620373e2cf33353b095d8b403d3e8a15aff - languageName: node - linkType: hard - "nlcst-to-string@npm:^3.0.0": version: 3.1.1 resolution: "nlcst-to-string@npm:3.1.1" @@ -11041,29 +9185,6 @@ __metadata: languageName: node linkType: hard -"node-addon-api@npm:^5.0.0": - version: 5.1.0 - resolution: "node-addon-api@npm:5.1.0" - dependencies: - node-gyp: "npm:latest" - checksum: 10/595f59ffb4630564f587c502119cbd980d302e482781021f3b479f5fc7e41cf8f2f7280fdc2795f32d148e4f3259bd15043c52d4a3442796aa6f1ae97b959636 - languageName: node - linkType: hard - -"node-cleanup@npm:^2.1.2": - version: 2.1.2 - resolution: "node-cleanup@npm:2.1.2" - checksum: 10/eeb831d27d734179ca6aa7504a65fa0debd7c77a883c5dbea2849fb7ed8fa0a3fe3a346926c5b1aaaf5537fd801d03da0efcf20b28385d7150276a9e8a2127a5 - languageName: node - linkType: hard - -"node-domexception@npm:^1.0.0": - version: 1.0.0 - resolution: "node-domexception@npm:1.0.0" - checksum: 10/e332522f242348c511640c25a6fc7da4f30e09e580c70c6b13cb0be83c78c3e71c8d4665af2527e869fc96848924a4316ae7ec9014c091e2156f41739d4fa233 - languageName: node - linkType: hard - "node-fetch@npm:^2.0.0": version: 2.6.11 resolution: "node-fetch@npm:2.6.11" @@ -11078,20 +9199,6 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.6.0": - version: 2.6.8 - resolution: "node-fetch@npm:2.6.8" - dependencies: - whatwg-url: "npm:^5.0.0" - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - checksum: 10/497d0e24985ba59eee37c25d4515b263cd9c92febcdc8606288f1a516d127c70123ce524318b66abe55ccefabda2a1c2bee8d3538f1a234a71ceee599b5cdb55 - languageName: node - linkType: hard - "node-fetch@npm:^2.6.7": version: 2.6.12 resolution: "node-fetch@npm:2.6.12" @@ -11106,17 +9213,6 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^3.2.0": - version: 3.3.2 - resolution: "node-fetch@npm:3.3.2" - dependencies: - data-uri-to-buffer: "npm:^4.0.0" - fetch-blob: "npm:^3.1.4" - formdata-polyfill: "npm:^4.0.10" - checksum: 10/24207ca8c81231c7c59151840e3fded461d67a31cf3e3b3968e12201a42f89ce4a0b5fb7079b1fa0a4655957b1ca9257553200f03a9f668b45ebad265ca5593d - languageName: node - linkType: hard - "node-gyp@npm:latest": version: 9.3.1 resolution: "node-gyp@npm:9.3.1" @@ -11137,26 +9233,6 @@ __metadata: languageName: node linkType: hard -"node-html-parser@npm:^6.1.4": - version: 6.1.4 - resolution: "node-html-parser@npm:6.1.4" - dependencies: - css-select: "npm:^5.1.0" - he: "npm:1.2.0" - checksum: 10/c97fa6593ca8f1eac4e2de4897568b7a4ef7328b642247ecc3037df24846f383d1db68eef264568d64b0ce71d0e89ebe3025769061e7af6f63fe4eae19f05c94 - languageName: node - linkType: hard - -"node-html-parser@npm:^6.1.9": - version: 6.1.10 - resolution: "node-html-parser@npm:6.1.10" - dependencies: - css-select: "npm:^5.1.0" - he: "npm:1.2.0" - checksum: 10/3b618a7616eeba4c4ecdcd023232baf7f22e22726db0592fe4d85817401be51e62646e6f75eacb361936209afc42318b0bc0978cf7f8b9cdf3a4642a19ad5883 - languageName: node - linkType: hard - "node-releases@npm:^2.0.12": version: 2.0.12 resolution: "node-releases@npm:2.0.12" @@ -11164,17 +9240,6 @@ __metadata: languageName: node linkType: hard -"nopt@npm:^5.0.0": - version: 5.0.0 - resolution: "nopt@npm:5.0.0" - dependencies: - abbrev: "npm:1" - bin: - nopt: bin/nopt.js - checksum: 10/00f9bb2d16449469ba8ffcf9b8f0eae6bae285ec74b135fec533e5883563d2400c0cd70902d0a7759e47ac031ccf206ace4e86556da08ed3f1c66dda206e9ccd - languageName: node - linkType: hard - "nopt@npm:^6.0.0": version: 6.0.0 resolution: "nopt@npm:6.0.0" @@ -11186,18 +9251,6 @@ __metadata: languageName: node linkType: hard -"normalize-package-data@npm:^2.3.2": - version: 2.5.0 - resolution: "normalize-package-data@npm:2.5.0" - dependencies: - hosted-git-info: "npm:^2.1.4" - resolve: "npm:^1.10.0" - semver: "npm:2 || 3 || 4 || 5" - validate-npm-package-license: "npm:^3.0.1" - checksum: 10/644f830a8bb9b7cc9bf2f6150618727659ee27cdd0840d1c1f97e8e6cab0803a098a2c19f31c6247ad9d3a0792e61521a13a6e8cd87cc6bb676e3150612c03d4 - languageName: node - linkType: hard - "normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0": version: 3.0.0 resolution: "normalize-path@npm:3.0.0" @@ -11212,13 +9265,6 @@ __metadata: languageName: node linkType: hard -"normalize-url@npm:^6.0.1": - version: 6.1.0 - resolution: "normalize-url@npm:6.1.0" - checksum: 10/5ae699402c9d5ffa330adc348fcd6fc6e6a155ab7c811b96e30b7ecab60ceef821d8f86443869671dda71bbc47f4b9625739c82ad247e883e9aefe875bfb8659 - languageName: node - linkType: hard - "normalize-url@npm:^8.0.0": version: 8.0.1 resolution: "normalize-url@npm:8.0.1" @@ -11226,36 +9272,6 @@ __metadata: languageName: node linkType: hard -"npm-run-all@npm:^4.1.5": - version: 4.1.5 - resolution: "npm-run-all@npm:4.1.5" - dependencies: - ansi-styles: "npm:^3.2.1" - chalk: "npm:^2.4.1" - cross-spawn: "npm:^6.0.5" - memorystream: "npm:^0.3.1" - minimatch: "npm:^3.0.4" - pidtree: "npm:^0.3.0" - read-pkg: "npm:^3.0.0" - shell-quote: "npm:^1.6.1" - string.prototype.padend: "npm:^3.0.0" - bin: - npm-run-all: bin/npm-run-all/index.js - run-p: bin/run-p/index.js - run-s: bin/run-s/index.js - checksum: 10/46020e92813223d015f4178cce5a2338164be5f25b0c391e256c0e84ac082544986c220013f1be7f002dcac07b81c7ee0cb5c5c30b84fd6ebb6de96a8d713745 - languageName: node - linkType: hard - -"npm-run-path@npm:^2.0.0": - version: 2.0.2 - resolution: "npm-run-path@npm:2.0.2" - dependencies: - path-key: "npm:^2.0.0" - checksum: 10/acd5ad81648ba4588ba5a8effb1d98d2b339d31be16826a118d50f182a134ac523172101b82eab1d01cb4c2ba358e857d54cfafd8163a1ffe7bd52100b741125 - languageName: node - linkType: hard - "npm-run-path@npm:^4.0.1": version: 4.0.1 resolution: "npm-run-path@npm:4.0.1" @@ -11274,18 +9290,6 @@ __metadata: languageName: node linkType: hard -"npmlog@npm:^5.0.1": - version: 5.0.1 - resolution: "npmlog@npm:5.0.1" - dependencies: - are-we-there-yet: "npm:^2.0.0" - console-control-strings: "npm:^1.1.0" - gauge: "npm:^3.0.0" - set-blocking: "npm:^2.0.0" - checksum: 10/f42c7b9584cdd26a13c41a21930b6f5912896b6419ab15be88cc5721fc792f1c3dd30eb602b26ae08575694628ba70afdcf3675d86e4f450fc544757e52726ec - languageName: node - linkType: hard - "npmlog@npm:^6.0.0": version: 6.0.2 resolution: "npmlog@npm:6.0.2" @@ -11298,15 +9302,6 @@ __metadata: languageName: node linkType: hard -"nth-check@npm:^2.0.1": - version: 2.1.1 - resolution: "nth-check@npm:2.1.1" - dependencies: - boolbase: "npm:^1.0.0" - checksum: 10/5afc3dafcd1573b08877ca8e6148c52abd565f1d06b1eb08caf982e3fa289a82f2cae697ffb55b5021e146d60443f1590a5d6b944844e944714a5b549675bcd3 - languageName: node - linkType: hard - "object-assign@npm:^4, object-assign@npm:^4.0.1, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" @@ -11399,7 +9394,7 @@ __metadata: languageName: node linkType: hard -"once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": +"once@npm:^1.3.0, once@npm:^1.4.0": version: 1.4.0 resolution: "once@npm:1.4.0" dependencies: @@ -11494,15 +9489,6 @@ __metadata: languageName: node linkType: hard -"os-filter-obj@npm:^2.0.0": - version: 2.0.0 - resolution: "os-filter-obj@npm:2.0.0" - dependencies: - arch: "npm:^2.1.0" - checksum: 10/08808a109b2dba9be8686cc006e082a0f6595e6d87e2a30e4147cb1d22b62a30a6e5f4fd78226aee76d9158c84db3cea292adec02e6591452e93cb33bf5da877 - languageName: node - linkType: hard - "p-any@npm:^4.0.0": version: 4.0.0 resolution: "p-any@npm:4.0.0" @@ -11513,13 +9499,6 @@ __metadata: languageName: node linkType: hard -"p-cancelable@npm:^2.0.0": - version: 2.1.1 - resolution: "p-cancelable@npm:2.1.1" - checksum: 10/7f1b64db17fc54acf359167d62898115dcf2a64bf6b3b038e4faf36fc059e5ed762fb9624df8ed04b25bee8de3ab8d72dea9879a2a960cd12e23c420a4aca6ed - languageName: node - linkType: hard - "p-cancelable@npm:^3.0.0": version: 3.0.0 resolution: "p-cancelable@npm:3.0.0" @@ -11527,13 +9506,6 @@ __metadata: languageName: node linkType: hard -"p-finally@npm:^1.0.0": - version: 1.0.0 - resolution: "p-finally@npm:1.0.0" - checksum: 10/93a654c53dc805dd5b5891bab16eb0ea46db8f66c4bfd99336ae929323b1af2b70a8b0654f8f1eae924b2b73d037031366d645f1fd18b3d30cbd15950cc4b1d4 - languageName: node - linkType: hard - "p-limit@npm:^3.0.2": version: 3.1.0 resolution: "p-limit@npm:3.1.0" @@ -11612,16 +9584,6 @@ __metadata: languageName: node linkType: hard -"parse-json@npm:^4.0.0": - version: 4.0.0 - resolution: "parse-json@npm:4.0.0" - dependencies: - error-ex: "npm:^1.3.1" - json-parse-better-errors: "npm:^1.0.1" - checksum: 10/0fe227d410a61090c247e34fa210552b834613c006c2c64d9a05cfe9e89cf8b4246d1246b1a99524b53b313e9ac024438d0680f67e33eaed7e6f38db64cfe7b5 - languageName: node - linkType: hard - "parse-latin@npm:^5.0.0": version: 5.0.1 resolution: "parse-latin@npm:5.0.1" @@ -11670,13 +9632,6 @@ __metadata: languageName: node linkType: hard -"path-key@npm:^2.0.0, path-key@npm:^2.0.1": - version: 2.0.1 - resolution: "path-key@npm:2.0.1" - checksum: 10/6e654864e34386a2a8e6bf72cf664dcabb76574dd54013add770b374384d438aca95f4357bb26935b514a4e4c2c9b19e191f2200b282422a76ee038b9258c5e7 - languageName: node - linkType: hard - "path-key@npm:^3.0.0, path-key@npm:^3.1.0": version: 3.1.1 resolution: "path-key@npm:3.1.1" @@ -11705,15 +9660,6 @@ __metadata: languageName: node linkType: hard -"path-type@npm:^3.0.0": - version: 3.0.0 - resolution: "path-type@npm:3.0.0" - dependencies: - pify: "npm:^3.0.0" - checksum: 10/735b35e256bad181f38fa021033b1c33cfbe62ead42bb2222b56c210e42938eecb272ae1949f3b6db4ac39597a61b44edd8384623ec4d79bfdc9a9c0f12537a6 - languageName: node - linkType: hard - "path-type@npm:^4.0.0": version: 4.0.0 resolution: "path-type@npm:4.0.0" @@ -11742,22 +9688,6 @@ __metadata: languageName: node linkType: hard -"pause-stream@npm:0.0.11": - version: 0.0.11 - resolution: "pause-stream@npm:0.0.11" - dependencies: - through: "npm:~2.3" - checksum: 10/1407efadfe814b5c487e4b28d6139cb7e03ee5d25fbb5f89a68f2053e81f05ce6b2bec196eeb3d46ef2c856f785016d14816b0d0e3c3abd1b64311c5c20660dc - languageName: node - linkType: hard - -"peek-readable@npm:^4.1.0": - version: 4.1.0 - resolution: "peek-readable@npm:4.1.0" - checksum: 10/97373215dcf382748645c3d22ac5e8dbd31759f7bd0c539d9fdbaaa7d22021838be3e55110ad0ed8f241c489342304b14a50dfee7ef3bcee2987d003b24ecc41 - languageName: node - linkType: hard - "peek-readable@npm:^5.0.0": version: 5.0.0 resolution: "peek-readable@npm:5.0.0" @@ -11790,29 +9720,13 @@ __metadata: languageName: node linkType: hard -"pidtree@npm:^0.3.0": - version: 0.3.1 - resolution: "pidtree@npm:0.3.1" - bin: - pidtree: bin/pidtree.js - checksum: 10/eb85b841cd168151bfadb984f9514d67a884d6962d4a2d250d4e8acf85cf031d7dab080f7272fb2735f9033364e5058c73eeebbee3cf6fd829169a75d19f189a - languageName: node - linkType: hard - -"pify@npm:^2.2.0, pify@npm:^2.3.0": +"pify@npm:^2.3.0": version: 2.3.0 resolution: "pify@npm:2.3.0" checksum: 10/9503aaeaf4577acc58642ad1d25c45c6d90288596238fb68f82811c08104c800e5a7870398e9f015d82b44ecbcbef3dc3d4251a1cbb582f6e5959fe09884b2ba languageName: node linkType: hard -"pify@npm:^3.0.0": - version: 3.0.0 - resolution: "pify@npm:3.0.0" - checksum: 10/668c1dc8d9fc1b34b9ce3b16ba59deb39d4dc743527bf2ed908d2b914cb8ba40aa5ba6960b27c417c241531c5aafd0598feeac2d50cb15278cf9863fa6b02a77 - languageName: node - linkType: hard - "pirates@npm:^4.0.1": version: 4.0.5 resolution: "pirates@npm:4.0.5" @@ -11831,31 +9745,6 @@ __metadata: languageName: node linkType: hard -"play-audio@npm:^0.5.2": - version: 0.5.2 - resolution: "play-audio@npm:0.5.2" - checksum: 10/c636d419c92f48ce642918360258ffba43ceef2fa3463eb4f1b98b3fdd41dbfb079750895e44cd5b5b957ee7a80b5301f06e527266667fe8f174f44e28a3c3ce - languageName: node - linkType: hard - -"play-dl@npm:^1.9.6": - version: 1.9.6 - resolution: "play-dl@npm:1.9.6" - dependencies: - play-audio: "npm:^0.5.2" - checksum: 10/4c3d9e9c51edca71d5effdf1d1ef8610558cb60d8c9835d4c4a35669c1729cbced6e550a4e3e48ba798da6bb2198035601669818306d296bbf5e18f5cd497968 - languageName: node - linkType: hard - -"play-dl@npm:^1.9.7": - version: 1.9.7 - resolution: "play-dl@npm:1.9.7" - dependencies: - play-audio: "npm:^0.5.2" - checksum: 10/6187e04dbd1f5f41beb6ff995bb746c78152f038f9a84963f1fbdd2ecb48ff8a5a713ad2985108a1d3cd7d01c9b689eee6ff2bb4a61176e4a988729a2438bca2 - languageName: node - linkType: hard - "postcss-import@npm:^15.1.0": version: 15.1.0 resolution: "postcss-import@npm:15.1.0" @@ -11975,15 +9864,6 @@ __metadata: languageName: node linkType: hard -"prettier@npm:^2.8.4": - version: 2.8.8 - resolution: "prettier@npm:2.8.8" - bin: - prettier: bin-prettier.js - checksum: 10/00cdb6ab0281f98306cd1847425c24cbaaa48a5ff03633945ab4c701901b8e96ad558eb0777364ffc312f437af9b5a07d0f45346266e8245beaf6247b9c62b24 - languageName: node - linkType: hard - "pretty-format@npm:^29.5.0": version: 29.7.0 resolution: "pretty-format@npm:29.7.0" @@ -12080,31 +9960,6 @@ __metadata: languageName: node linkType: hard -"ps-tree@npm:^1.2.0": - version: 1.2.0 - resolution: "ps-tree@npm:1.2.0" - dependencies: - event-stream: "npm:=3.3.4" - bin: - ps-tree: ./bin/ps-tree.js - checksum: 10/0587defdc20c0768fad884623c0204c77e5228878a5cb043676b00529220ec12d9cb6a328a0580767a9909a317bff466fe4530a4676e3d145a9deb3b7fbbeef3 - languageName: node - linkType: hard - -"pseudomap@npm:^1.0.2": - version: 1.0.2 - resolution: "pseudomap@npm:1.0.2" - checksum: 10/856c0aae0ff2ad60881168334448e898ad7a0e45fe7386d114b150084254c01e200c957cf378378025df4e052c7890c5bd933939b0e0d2ecfcc1dc2f0b2991f5 - languageName: node - linkType: hard - -"psl@npm:^1.1.33": - version: 1.9.0 - resolution: "psl@npm:1.9.0" - checksum: 10/d07879d4bfd0ac74796306a8e5a36a93cfb9c4f4e8ee8e63fbb909066c192fe1008cd8f12abd8ba2f62ca28247949a20c8fb32e1d18831d9e71285a1569720f9 - languageName: node - linkType: hard - "public-ip@npm:^5.0.0": version: 5.0.0 resolution: "public-ip@npm:5.0.0" @@ -12116,16 +9971,6 @@ __metadata: languageName: node linkType: hard -"pump@npm:^3.0.0": - version: 3.0.0 - resolution: "pump@npm:3.0.0" - dependencies: - end-of-stream: "npm:^1.1.0" - once: "npm:^1.3.1" - checksum: 10/e42e9229fba14732593a718b04cb5e1cfef8254544870997e0ecd9732b189a48e1256e4e5478148ecb47c8511dca2b09eae56b4d0aad8009e6fac8072923cfc9 - languageName: node - linkType: hard - "punycode@npm:^2.1.0": version: 2.1.1 resolution: "punycode@npm:2.1.1" @@ -12133,13 +9978,6 @@ __metadata: languageName: node linkType: hard -"punycode@npm:^2.1.1": - version: 2.3.0 - resolution: "punycode@npm:2.3.0" - checksum: 10/d4e7fbb96f570c57d64b09a35a1182c879ac32833de7c6926a2c10619632c1377865af3dab5479f59d51da18bcd5035a20a5ef6ceb74020082a3e78025d9a9ca - languageName: node - linkType: hard - "qs@npm:6.11.0": version: 6.11.0 resolution: "qs@npm:6.11.0" @@ -12149,13 +9987,6 @@ __metadata: languageName: node linkType: hard -"querystringify@npm:^2.1.1": - version: 2.2.0 - resolution: "querystringify@npm:2.2.0" - checksum: 10/46ab16f252fd892fc29d6af60966d338cdfeea68a231e9457631ffd22d67cec1e00141e0a5236a2eb16c0d7d74175d9ec1d6f963660c6f2b1c2fc85b194c5680 - languageName: node - linkType: hard - "queue-microtask@npm:^1.2.2": version: 1.2.3 resolution: "queue-microtask@npm:1.2.3" @@ -12332,17 +10163,6 @@ __metadata: languageName: node linkType: hard -"read-pkg@npm:^3.0.0": - version: 3.0.0 - resolution: "read-pkg@npm:3.0.0" - dependencies: - load-json-file: "npm:^4.0.0" - normalize-package-data: "npm:^2.3.2" - path-type: "npm:^3.0.0" - checksum: 10/398903ebae6c7e9965419a1062924436cc0b6f516c42c4679a90290d2f87448ed8f977e7aa2dbba4aa1ac09248628c43e493ac25b2bc76640e946035200e34c6 - languageName: node - linkType: hard - "readable-stream@npm:^3.4.0": version: 3.6.2 resolution: "readable-stream@npm:3.6.2" @@ -12365,7 +10185,7 @@ __metadata: languageName: node linkType: hard -"readable-web-to-node-stream@npm:^3.0.0, readable-web-to-node-stream@npm:^3.0.2": +"readable-web-to-node-stream@npm:^3.0.2": version: 3.0.2 resolution: "readable-web-to-node-stream@npm:3.0.2" dependencies: @@ -12551,14 +10371,7 @@ __metadata: languageName: node linkType: hard -"requires-port@npm:^1.0.0": - version: 1.0.0 - resolution: "requires-port@npm:1.0.0" - checksum: 10/878880ee78ccdce372784f62f52a272048e2d0827c29ae31e7f99da18b62a2b9463ea03a75f277352f4697c100183debb0532371ad515a2d49d4bfe596dd4c20 - languageName: node - linkType: hard - -"resolve-alpn@npm:^1.0.0, resolve-alpn@npm:^1.2.0": +"resolve-alpn@npm:^1.2.0": version: 1.2.1 resolution: "resolve-alpn@npm:1.2.1" checksum: 10/744e87888f0b6fa0b256ab454ca0b9c0b80808715e2ef1f3672773665c92a941f6181194e30ccae4a8cd0adbe0d955d3f133102636d2ee0cca0119fec0bc9aec @@ -12586,7 +10399,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.1.7, resolve@npm:^1.10.0, resolve@npm:^1.22.2": +"resolve@npm:^1.1.7, resolve@npm:^1.22.2": version: 1.22.3 resolution: "resolve@npm:1.22.3" dependencies: @@ -12625,7 +10438,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^1.1.7#optional!builtin, resolve@patch:resolve@npm%3A^1.10.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.2#optional!builtin": +"resolve@patch:resolve@npm%3A^1.1.7#optional!builtin, resolve@patch:resolve@npm%3A^1.22.2#optional!builtin": version: 1.22.3 resolution: "resolve@patch:resolve@npm%3A1.22.3#optional!builtin::version=1.22.3&hash=c3c19d" dependencies: @@ -12664,15 +10477,6 @@ __metadata: languageName: node linkType: hard -"responselike@npm:^2.0.0": - version: 2.0.1 - resolution: "responselike@npm:2.0.1" - dependencies: - lowercase-keys: "npm:^2.0.0" - checksum: 10/b122535466e9c97b55e69c7f18e2be0ce3823c5d47ee8de0d9c0b114aa55741c6db8bfbfce3766a94d1272e61bfb1ebf0a15e9310ac5629fbb7446a861b4fd3a - languageName: node - linkType: hard - "responselike@npm:^3.0.0": version: 3.0.0 resolution: "responselike@npm:3.0.0" @@ -12753,15 +10557,6 @@ __metadata: languageName: node linkType: hard -"reverbnation-scraper@npm:^2.0.0": - version: 2.0.0 - resolution: "reverbnation-scraper@npm:2.0.0" - dependencies: - node-fetch: "npm:^2.6.0" - checksum: 10/e1b9894f5cb1628d1d32732ef4d6fe76e112f6dfb87fbf7dd36d1ab8117d65555ddcd9a94cf1de4259c82d6439c6ee4029857206c9831b766e411108b4401cdd - languageName: node - linkType: hard - "rimraf@npm:^3.0.2": version: 3.0.2 resolution: "rimraf@npm:3.0.2" @@ -12905,13 +10700,6 @@ __metadata: languageName: node linkType: hard -"sax@npm:^1.1.3, sax@npm:^1.2.4": - version: 1.2.4 - resolution: "sax@npm:1.2.4" - checksum: 10/09b79ff6dc09689a24323352117c94593c69db348997b2af0edbd82fa08aba47d778055bf9616b57285bb73d25d790900c044bf631a8f10c8252412e3f3fe5dd - languageName: node - linkType: hard - "scheduler@npm:^0.23.0": version: 0.23.0 resolution: "scheduler@npm:0.23.0" @@ -12931,32 +10719,7 @@ __metadata: languageName: node linkType: hard -"semver-regex@npm:^4.0.5": - version: 4.0.5 - resolution: "semver-regex@npm:4.0.5" - checksum: 10/b9e5c0573c4a997fb7e6e76321385d254797e86c8dba5e23f3cd8cf8f40b40414097a51514e5fead61dcb88ff10d3676355c01e2040f3c68f6c24bfd2073da2e - languageName: node - linkType: hard - -"semver-truncate@npm:^2.0.0": - version: 2.0.0 - resolution: "semver-truncate@npm:2.0.0" - dependencies: - semver: "npm:^6.0.0" - checksum: 10/713c2bd49add098c3fd6271091e7c8c13908ab3f052d58a19b68920da9f101d34eb6a0c60ef4bd5fa3c345f001e0df37bb831602082441bb35ba857cac42e0f4 - languageName: node - linkType: hard - -"semver@npm:2 || 3 || 4 || 5, semver@npm:^5.5.0": - version: 5.7.1 - resolution: "semver@npm:5.7.1" - bin: - semver: ./bin/semver - checksum: 10/fbc71cf00736480ca0dd67f2527cda6e0fde5447af00bd2ce06cb522d510216603a63ed0c6c87d8904507c1a4e8113e628a71424ebd9e0fd7d345ee8ed249690 - languageName: node - linkType: hard - -"semver@npm:^6.0.0, semver@npm:^6.3.0": +"semver@npm:^6.3.0": version: 6.3.0 resolution: "semver@npm:6.3.0" bin: @@ -12987,17 +10750,6 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.8": - version: 7.5.2 - resolution: "semver@npm:7.5.2" - dependencies: - lru-cache: "npm:^6.0.0" - bin: - semver: bin/semver.js - checksum: 10/f77b3a1842e19b78e5864a175d62699a17c0c25f6223366684041e8c7dd6a55f0091887f405c534895dfe69e1d770528d072b32d9ed866ab24392fe34344d3b5 - languageName: node - linkType: hard - "semver@npm:^7.5.3": version: 7.5.3 resolution: "semver@npm:7.5.3" @@ -13145,28 +10897,12 @@ __metadata: languageName: node linkType: hard -"shebang-command@npm:^1.2.0": - version: 1.2.0 - resolution: "shebang-command@npm:1.2.0" - dependencies: - shebang-regex: "npm:^1.0.0" - checksum: 10/9eed1750301e622961ba5d588af2212505e96770ec376a37ab678f965795e995ade7ed44910f5d3d3cb5e10165a1847f52d3348c64e146b8be922f7707958908 - languageName: node - linkType: hard - "shebang-command@npm:^2.0.0": version: 2.0.0 resolution: "shebang-command@npm:2.0.0" dependencies: - shebang-regex: "npm:^3.0.0" - checksum: 10/6b52fe87271c12968f6a054e60f6bde5f0f3d2db483a1e5c3e12d657c488a15474121a1d55cd958f6df026a54374ec38a4a963988c213b7570e1d51575cea7fa - languageName: node - linkType: hard - -"shebang-regex@npm:^1.0.0": - version: 1.0.0 - resolution: "shebang-regex@npm:1.0.0" - checksum: 10/404c5a752cd40f94591dfd9346da40a735a05139dac890ffc229afba610854d8799aaa52f87f7e0c94c5007f2c6af55bdcaeb584b56691926c5eaf41dc8f1372 + shebang-regex: "npm:^3.0.0" + checksum: 10/6b52fe87271c12968f6a054e60f6bde5f0f3d2db483a1e5c3e12d657c488a15474121a1d55cd958f6df026a54374ec38a4a963988c213b7570e1d51575cea7fa languageName: node linkType: hard @@ -13177,13 +10913,6 @@ __metadata: languageName: node linkType: hard -"shell-quote@npm:^1.6.1": - version: 1.8.1 - resolution: "shell-quote@npm:1.8.1" - checksum: 10/af19ab5a1ec30cb4b2f91fd6df49a7442d5c4825a2e269b3712eded10eedd7f9efeaab96d57829880733fc55bcdd8e9b1d8589b4befb06667c731d08145e274d - languageName: node - linkType: hard - "shiki@npm:^0.14.7": version: 0.14.7 resolution: "shiki@npm:0.14.7" @@ -13214,31 +10943,13 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": +"signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" checksum: 10/a2f098f247adc367dffc27845853e9959b9e88b01cb301658cfe4194352d8d2bb32e18467c786a7fe15f1d44b233ea35633d076d5e737870b7139949d1ab6318 languageName: node linkType: hard -"simple-concat@npm:^1.0.0": - version: 1.0.1 - resolution: "simple-concat@npm:1.0.1" - checksum: 10/4d211042cc3d73a718c21ac6c4e7d7a0363e184be6a5ad25c8a1502e49df6d0a0253979e3d50dbdd3f60ef6c6c58d756b5d66ac1e05cda9cacd2e9fc59e3876a - languageName: node - linkType: hard - -"simple-get@npm:~4.0.1": - version: 4.0.1 - resolution: "simple-get@npm:4.0.1" - dependencies: - decompress-response: "npm:^6.0.0" - once: "npm:^1.3.1" - simple-concat: "npm:^1.0.0" - checksum: 10/93f1b32319782f78f2f2234e9ce34891b7ab6b990d19d8afefaa44423f5235ce2676aae42d6743fecac6c8dfff4b808d4c24fe5265be813d04769917a9a44f36 - languageName: node - linkType: hard - "simple-swizzle@npm:^0.2.2": version: 0.2.2 resolution: "simple-swizzle@npm:0.2.2" @@ -13248,7 +10959,7 @@ __metadata: languageName: node linkType: hard -"slash@npm:3.0.0, slash@npm:^3.0.0": +"slash@npm:^3.0.0": version: 3.0.0 resolution: "slash@npm:3.0.0" checksum: 10/94a93fff615f25a999ad4b83c9d5e257a7280c90a32a7cb8b4a87996e4babf322e469c42b7f649fd5796edd8687652f3fb452a86dc97a816f01113183393f11c @@ -13325,33 +11036,6 @@ __metadata: languageName: node linkType: hard -"sort-keys-length@npm:^1.0.0": - version: 1.0.1 - resolution: "sort-keys-length@npm:1.0.1" - dependencies: - sort-keys: "npm:^1.0.0" - checksum: 10/f9acac5fb31580a9e3d43b419dc86a1b75e85b79036a084d95dd4d1062b621c9589906588ac31e370a0dd381be46d8dbe900efa306d087ca9c912d7a59b5a590 - languageName: node - linkType: hard - -"sort-keys@npm:^1.0.0": - version: 1.1.2 - resolution: "sort-keys@npm:1.1.2" - dependencies: - is-plain-obj: "npm:^1.0.0" - checksum: 10/0ac2ea2327d92252f07aa7b2f8c7023a1f6ce3306439a3e81638cce9905893c069521d168f530fb316d1a929bdb052b742969a378190afaef1bc64fa69e29576 - languageName: node - linkType: hard - -"soundcloud.ts@npm:^0.5.2": - version: 0.5.2 - resolution: "soundcloud.ts@npm:0.5.2" - dependencies: - undici: "npm:^5.22.1" - checksum: 10/db4df8921415087d99d8522552d137ebc176a95ef476c19d327a93960f745a4e4ceaa6fe3b4f1e035f094608128fdab23cad96a9561380812cab7cb238ce310e - languageName: node - linkType: hard - "source-map-js@npm:^1.0.2": version: 1.0.2 resolution: "source-map-js@npm:1.0.2" @@ -13359,16 +11043,6 @@ __metadata: languageName: node linkType: hard -"source-map-support@npm:^0.5.21": - version: 0.5.21 - resolution: "source-map-support@npm:0.5.21" - dependencies: - buffer-from: "npm:^1.0.0" - source-map: "npm:^0.6.0" - checksum: 10/8317e12d84019b31e34b86d483dd41d6f832f389f7417faf8fc5c75a66a12d9686e47f589a0554a868b8482f037e23df9d040d29387eb16fa14cb85f091ba207 - languageName: node - linkType: hard - "source-map@npm:0.8.0-beta.0": version: 0.8.0-beta.0 resolution: "source-map@npm:0.8.0-beta.0" @@ -13378,14 +11052,7 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.6.0": - version: 0.6.1 - resolution: "source-map@npm:0.6.1" - checksum: 10/59ef7462f1c29d502b3057e822cdbdae0b0e565302c4dd1a95e11e793d8d9d62006cdc10e0fd99163ca33ff2071360cf50ee13f90440806e7ed57d81cba2f7ff - languageName: node - linkType: hard - -"source-map@npm:^0.7.0, source-map@npm:^0.7.3": +"source-map@npm:^0.7.0": version: 0.7.4 resolution: "source-map@npm:0.7.4" checksum: 10/a0f7c9b797eda93139842fd28648e868a9a03ea0ad0d9fa6602a0c1f17b7fb6a7dcca00c144476cccaeaae5042e99a285723b1a201e844ad67221bf5d428f1dc @@ -13399,66 +11066,6 @@ __metadata: languageName: node linkType: hard -"spdx-correct@npm:^3.0.0": - version: 3.2.0 - resolution: "spdx-correct@npm:3.2.0" - dependencies: - spdx-expression-parse: "npm:^3.0.0" - spdx-license-ids: "npm:^3.0.0" - checksum: 10/cc2e4dbef822f6d12142116557d63f5facf3300e92a6bd24e907e4865e17b7e1abd0ee6b67f305cae6790fc2194175a24dc394bfcc01eea84e2bdad728e9ae9a - languageName: node - linkType: hard - -"spdx-exceptions@npm:^2.1.0": - version: 2.3.0 - resolution: "spdx-exceptions@npm:2.3.0" - checksum: 10/cb69a26fa3b46305637123cd37c85f75610e8c477b6476fa7354eb67c08128d159f1d36715f19be6f9daf4b680337deb8c65acdcae7f2608ba51931540687ac0 - languageName: node - linkType: hard - -"spdx-expression-parse@npm:^3.0.0": - version: 3.0.1 - resolution: "spdx-expression-parse@npm:3.0.1" - dependencies: - spdx-exceptions: "npm:^2.1.0" - spdx-license-ids: "npm:^3.0.0" - checksum: 10/a1c6e104a2cbada7a593eaa9f430bd5e148ef5290d4c0409899855ce8b1c39652bcc88a725259491a82601159d6dc790bedefc9016c7472f7de8de7361f8ccde - languageName: node - linkType: hard - -"spdx-license-ids@npm:^3.0.0": - version: 3.0.13 - resolution: "spdx-license-ids@npm:3.0.13" - checksum: 10/6328c516e958ceee80362dc657a58cab01c7fdb4667a1a4c1a3e91d069983977f87971340ee857eb66f65079b5d8561e56dc91510802cd7bebaae7632a6aa7fa - languageName: node - linkType: hard - -"split@npm:0.3": - version: 0.3.3 - resolution: "split@npm:0.3.3" - dependencies: - through: "npm:2" - checksum: 10/41b397e9fedc984ee1b061780bf173ef72a4f99265ca9cbccd9765b8cc0729eeee6cdeaf70664eb3eb0823e8430db033e50a33050498d75569fc743c6964c84e - languageName: node - linkType: hard - -"spotify-uri@npm:~4.0.0": - version: 4.0.0 - resolution: "spotify-uri@npm:4.0.0" - checksum: 10/f3cb0c5f1c4d0ae02746bcab837704df75d7d86d6011bd5424e5a3829b26fa04a6aea7e7382e8b7e3c3f221cf0d87afbeffaacdd9892cdd7ae506a06285970ec - languageName: node - linkType: hard - -"spotify-url-info@npm:^3.2.6": - version: 3.2.6 - resolution: "spotify-url-info@npm:3.2.6" - dependencies: - himalaya: "npm:~1.1.0" - spotify-uri: "npm:~4.0.0" - checksum: 10/4101df615b63307478a41375696ea188402116ce6e49193e1b930ca4f8046fd1246c6ef3db9b9138580c31850636e290cbf9bb7dc6099148702820bb23215d2c - languageName: node - linkType: hard - "sprintf-js@npm:~1.0.2": version: 1.0.3 resolution: "sprintf-js@npm:1.0.3" @@ -13505,15 +11112,6 @@ __metadata: languageName: node linkType: hard -"stream-combiner@npm:~0.0.4": - version: 0.0.4 - resolution: "stream-combiner@npm:0.0.4" - dependencies: - duplexer: "npm:~0.1.1" - checksum: 10/844b622cfe8b9de45a6007404f613b60aaf85200ab9862299066204242f89a7c8033b1c356c998aa6cfc630f6cd9eba119ec1c6dc1f93e245982be4a847aee7d - languageName: node - linkType: hard - "streamsearch@npm:^1.1.0": version: 1.1.0 resolution: "streamsearch@npm:1.1.0" @@ -13521,13 +11119,6 @@ __metadata: languageName: node linkType: hard -"string-argv@npm:^0.3.1": - version: 0.3.2 - resolution: "string-argv@npm:0.3.2" - checksum: 10/f9d3addf887026b4b5f997a271149e93bf71efc8692e7dc0816e8807f960b18bcb9787b45beedf0f97ff459575ee389af3f189d8b649834cac602f2e857e75af - languageName: node - linkType: hard - "string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" @@ -13555,17 +11146,6 @@ __metadata: languageName: node linkType: hard -"string.prototype.padend@npm:^3.0.0": - version: 3.1.4 - resolution: "string.prototype.padend@npm:3.1.4" - dependencies: - call-bind: "npm:^1.0.2" - define-properties: "npm:^1.1.4" - es-abstract: "npm:^1.20.4" - checksum: 10/0625316ab60227a95d996205888bc906012c028adba052ff5044caf1ce1b127c8df512a13b17d1059c7c0139e319e251b1cfc91a4c5ebaab9432f90079dd2ea9 - languageName: node - linkType: hard - "string.prototype.trim@npm:^1.2.7": version: 1.2.7 resolution: "string.prototype.trim@npm:1.2.7" @@ -13650,13 +11230,6 @@ __metadata: languageName: node linkType: hard -"strip-eof@npm:^1.0.0": - version: 1.0.0 - resolution: "strip-eof@npm:1.0.0" - checksum: 10/40bc8ddd7e072f8ba0c2d6d05267b4e0a4800898c3435b5fb5f5a21e6e47dfaff18467e7aa0d1844bb5d6274c3097246595841fbfeb317e541974ee992cac506 - languageName: node - linkType: hard - "strip-final-newline@npm:^2.0.0": version: 2.0.0 resolution: "strip-final-newline@npm:2.0.0" @@ -13687,23 +11260,6 @@ __metadata: languageName: node linkType: hard -"strip-outer@npm:^2.0.0": - version: 2.0.0 - resolution: "strip-outer@npm:2.0.0" - checksum: 10/14ef9fe861e59a5f1555f1860982ae4edce2edb4ed34ab1b37cb62a8ba2f7c3540cbca6c884eabe4006e6cd729ab5d708a631169dd5b66fda570836e7e3b6589 - languageName: node - linkType: hard - -"strtok3@npm:^6.2.4": - version: 6.3.0 - resolution: "strtok3@npm:6.3.0" - dependencies: - "@tokenizer/token": "npm:^0.3.0" - peek-readable: "npm:^4.1.0" - checksum: 10/98fba564d3830202aa3a6bcd5ccaf2cbd849bd87ae79ece91d337e1913916705a8e633c9577138d030a984f8ec987dea51807e01252f995cf5e183fdea35eb2b - languageName: node - linkType: hard - "strtok3@npm:^7.0.0-alpha.9": version: 7.0.0 resolution: "strtok3@npm:7.0.0" @@ -13774,15 +11330,6 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^5.3.0": - version: 5.5.0 - resolution: "supports-color@npm:5.5.0" - dependencies: - has-flag: "npm:^3.0.0" - checksum: 10/5f505c6fa3c6e05873b43af096ddeb22159831597649881aeb8572d6fe3b81e798cc10840d0c9735e0026b250368851b7f77b65e84f4e4daa820a4f69947f55b - languageName: node - linkType: hard - "supports-color@npm:^7.1.0": version: 7.2.0 resolution: "supports-color@npm:7.2.0" @@ -13919,13 +11466,6 @@ __metadata: languageName: node linkType: hard -"through@npm:2, through@npm:~2.3, through@npm:~2.3.1": - version: 2.3.8 - resolution: "through@npm:2.3.8" - checksum: 10/5da78346f70139a7d213b65a0106f3c398d6bc5301f9248b5275f420abc2c4b1e77c2abc72d218dedc28c41efb2e7c312cb76a7730d04f9c2d37d247da3f4198 - languageName: node - linkType: hard - "tinybench@npm:^2.5.0": version: 2.5.1 resolution: "tinybench@npm:2.5.1" @@ -13954,24 +11494,6 @@ __metadata: languageName: node linkType: hard -"tldts-core@npm:^6.0.8": - version: 6.0.8 - resolution: "tldts-core@npm:6.0.8" - checksum: 10/86924f36a4db822cb9b97e4c23aae066600020e0aadb71ebeff1bfab2b72fd900ba5f811765d35e0fa8026d8feecb8b549ca0d911b2e9f4106f34d38eb30b7ab - languageName: node - linkType: hard - -"tldts@npm:^6.0.5": - version: 6.0.8 - resolution: "tldts@npm:6.0.8" - dependencies: - tldts-core: "npm:^6.0.8" - bin: - tldts: bin/cli.js - checksum: 10/a225836445da3134db89c78fe0a831b84bfbff11e65a70e36b9956dbcf9d0b06ec9aee856359d5fb5e0f8cb5bd647f04789fda6a919b36ea4892c5e934cd1782 - languageName: node - linkType: hard - "tmp@npm:^0.2.1": version: 0.2.3 resolution: "tmp@npm:0.2.3" @@ -13995,16 +11517,6 @@ __metadata: languageName: node linkType: hard -"token-types@npm:^4.1.1": - version: 4.2.1 - resolution: "token-types@npm:4.2.1" - dependencies: - "@tokenizer/token": "npm:^0.3.0" - ieee754: "npm:^1.2.1" - checksum: 10/2995257d246387e773758c3c92a3cc99d0c0bf13cbafe0de5d712e4c35ed298da6704e21545cb123fa1f1b42ad62936c35bbd0611018b735e78c30b8b22b42d9 - languageName: node - linkType: hard - "token-types@npm:^5.0.0-alpha.2": version: 5.0.0-alpha.2 resolution: "token-types@npm:5.0.0-alpha.2" @@ -14015,18 +11527,6 @@ __metadata: languageName: node linkType: hard -"tough-cookie@npm:^4.1.3": - version: 4.1.3 - resolution: "tough-cookie@npm:4.1.3" - dependencies: - psl: "npm:^1.1.33" - punycode: "npm:^2.1.1" - universalify: "npm:^0.2.0" - url-parse: "npm:^1.5.3" - checksum: 10/cf148c359b638a7069fc3ba9a5257bdc9616a6948a98736b92c3570b3f8401cf9237a42bf716878b656f372a1fb65b74dd13a46ccff8eceba14ffd053d33f72a - languageName: node - linkType: hard - "tr46@npm:^1.0.1": version: 1.0.1 resolution: "tr46@npm:1.0.1" @@ -14059,15 +11559,6 @@ __metadata: languageName: node linkType: hard -"trim-repeated@npm:^2.0.0": - version: 2.0.0 - resolution: "trim-repeated@npm:2.0.0" - dependencies: - escape-string-regexp: "npm:^5.0.0" - checksum: 10/4086eb0bc560f3da0370f427f423db4e3fc0a8e1560ecffc3b68512071319fe82dc9dd86d76b981d36ada76d7d49c3f8897ac054c87bc177e7a25abfd29e2bcd - languageName: node - linkType: hard - "trough@npm:^2.0.0": version: 2.1.0 resolution: "trough@npm:2.1.0" @@ -14098,29 +11589,6 @@ __metadata: languageName: node linkType: hard -"ts-mixer@npm:^6.0.3": - version: 6.0.3 - resolution: "ts-mixer@npm:6.0.3" - checksum: 10/ac9178bdac5e5f760472269ad4c461587a0f6793532ddbef1326bb01482425a6247be98f9bd11bf35a9fdd36b63b8c8dde393942b9b9ee52d154eef082fca39a - languageName: node - linkType: hard - -"tsc-watch@npm:^6.0.0": - version: 6.0.4 - resolution: "tsc-watch@npm:6.0.4" - dependencies: - cross-spawn: "npm:^7.0.3" - node-cleanup: "npm:^2.1.2" - ps-tree: "npm:^1.2.0" - string-argv: "npm:^0.3.1" - peerDependencies: - typescript: "*" - bin: - tsc-watch: dist/lib/tsc-watch.js - checksum: 10/b0e793f63fbb260d4bb94f7fac6cb4b31c7bd710f16cac5018859b87a4550663d404a2cf8478105a48d77f7c72a9cf5568d605381eb6e44b77055e4f9031886a - languageName: node - linkType: hard - "tsconfig-paths@npm:^3.14.1": version: 3.14.2 resolution: "tsconfig-paths@npm:3.14.2" @@ -14133,13 +11601,6 @@ __metadata: languageName: node linkType: hard -"tslib@npm:2.x, tslib@npm:^2.0.0, tslib@npm:^2.1.0, tslib@npm:^2.3.1, tslib@npm:^2.5.3": - version: 2.5.3 - resolution: "tslib@npm:2.5.3" - checksum: 10/d507e60ebe2480af4efc1655dfdb2762bb6ca57d76c4ba680375af801493648c2e97808bbd7e54691eb40e33a7e2e793cdef9c24ce6a8539b03cac8b26e09a61 - languageName: node - linkType: hard - "tslib@npm:^1.8.1": version: 1.14.1 resolution: "tslib@npm:1.14.1" @@ -14147,6 +11608,13 @@ __metadata: languageName: node linkType: hard +"tslib@npm:^2.0.0, tslib@npm:^2.1.0, tslib@npm:^2.5.3": + version: 2.5.3 + resolution: "tslib@npm:2.5.3" + checksum: 10/d507e60ebe2480af4efc1655dfdb2762bb6ca57d76c4ba680375af801493648c2e97808bbd7e54691eb40e33a7e2e793cdef9c24ce6a8539b03cac8b26e09a61 + languageName: node + linkType: hard + "tslib@npm:^2.4.0": version: 2.4.0 resolution: "tslib@npm:2.4.0" @@ -14215,23 +11683,6 @@ __metadata: languageName: node linkType: hard -"tsx@npm:^3.12.7": - version: 3.12.7 - resolution: "tsx@npm:3.12.7" - dependencies: - "@esbuild-kit/cjs-loader": "npm:^2.4.2" - "@esbuild-kit/core-utils": "npm:^3.0.0" - "@esbuild-kit/esm-loader": "npm:^2.5.5" - fsevents: "npm:~2.3.2" - dependenciesMeta: - fsevents: - optional: true - bin: - tsx: dist/cli.js - checksum: 10/fc126d08a50eb85e09c11acb440cdbc549b1ed61e3d3603c83e45825ca5a9aeeb4bf12e5f6ba0996aa663218cd623bc703cc68b9399b91d7a8e8961bea017bb4 - languageName: node - linkType: hard - "turbo-darwin-64@npm:1.10.16": version: 1.10.16 resolution: "turbo-darwin-64@npm:1.10.16" @@ -14373,16 +11824,6 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^4.6.3": - version: 4.9.5 - resolution: "typescript@npm:4.9.5" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10/458f7220ab11e0fc191514cc41be1707645ec9a8c2d609448a448e18c522cef9646f58728f6811185a4c35613dacdf6c98cf8965c88b3541d0288c47291e4300 - languageName: node - linkType: hard - "typescript@npm:^5.2.2": version: 5.2.2 resolution: "typescript@npm:5.2.2" @@ -14413,16 +11854,6 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^4.6.3#optional!builtin": - version: 4.9.5 - resolution: "typescript@patch:typescript@npm%3A4.9.5#optional!builtin::version=4.9.5&hash=289587" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10/5659316360b5cc2d6f5931b346401fa534107b68b60179cf14970e27978f0936c1d5c46f4b5b8175f8cba0430f522b3ce355b4b724c0ea36ce6c0347fab25afd - languageName: node - linkType: hard - "typescript@patch:typescript@npm%3A^5.2.2#optional!builtin": version: 5.2.2 resolution: "typescript@patch:typescript@npm%3A5.2.2#optional!builtin::version=5.2.2&hash=f3b441" @@ -14469,33 +11900,6 @@ __metadata: languageName: node linkType: hard -"undici@npm:^5.22.1": - version: 5.22.1 - resolution: "undici@npm:5.22.1" - dependencies: - busboy: "npm:^1.6.0" - checksum: 10/4e4ae061372508bad6c017e0188cdbf1bb73e427d881aefe6277f88cb0bdd45b57bb88d7ab6fc136ff08e7d022bd83ca550a28272aebfb36b28c06fe8f07ac5e - languageName: node - linkType: hard - -"undici@npm:^5.23.0, undici@npm:^5.24.0": - version: 5.24.0 - resolution: "undici@npm:5.24.0" - dependencies: - busboy: "npm:^1.6.0" - checksum: 10/ae43417f36e7adf14bfae9a632352a9a53ce8ca6a3c6c5ff9e3561a17c5f999a1dfdd85072f270dfab75224a63b143780b2fc3e9a6000eb7878d6c67c894a4a1 - languageName: node - linkType: hard - -"undici@npm:^5.25.2": - version: 5.26.3 - resolution: "undici@npm:5.26.3" - dependencies: - "@fastify/busboy": "npm:^2.0.0" - checksum: 10/7280135e89c6f96f17f02fa99b8dcf5c64511d36de31b5cea0e1a858c8a16f07ea22aba524d5b8f574e9341a543d295aebb20cb715545829f2e959329149a638 - languageName: node - linkType: hard - "undici@npm:^5.8.0": version: 5.8.0 resolution: "undici@npm:5.8.0" @@ -14503,20 +11907,6 @@ __metadata: languageName: node linkType: hard -"undici@npm:^6.11.1": - version: 6.13.0 - resolution: "undici@npm:6.13.0" - checksum: 10/4ec2038e95779d4f1114a5dcf5bc74ec59c7fc76f6287f8a6bea6d69113f0190e6d41cc6e14409b5d912b0a92ce910b33bfa05808f40b6bf2b802b58b427f2cf - languageName: node - linkType: hard - -"unfetch@npm:^5.0.0": - version: 5.0.0 - resolution: "unfetch@npm:5.0.0" - checksum: 10/8a59f9d910f179ef588aa30885849de7b4c895a85b3679ab4da7305be3751b85a4811d9164d87960fef1a388b9a7afdc23ab2154f517db040b27171578fa9e8b - languageName: node - linkType: hard - "unherit@npm:^3.0.0": version: 3.0.1 resolution: "unherit@npm:3.0.1" @@ -14764,13 +12154,6 @@ __metadata: languageName: node linkType: hard -"universalify@npm:^0.2.0": - version: 0.2.0 - resolution: "universalify@npm:0.2.0" - checksum: 10/e86134cb12919d177c2353196a4cc09981524ee87abf621f7bc8d249dbbbebaec5e7d1314b96061497981350df786e4c5128dbf442eba104d6e765bc260678b5 - languageName: node - linkType: hard - "universalify@npm:^2.0.0": version: 2.0.0 resolution: "universalify@npm:2.0.0" @@ -14815,16 +12198,6 @@ __metadata: languageName: node linkType: hard -"url-parse@npm:^1.5.3": - version: 1.5.10 - resolution: "url-parse@npm:1.5.10" - dependencies: - querystringify: "npm:^2.1.1" - requires-port: "npm:^1.0.0" - checksum: 10/c9e96bc8c5b34e9f05ddfeffc12f6aadecbb0d971b3cc26015b58d5b44676a99f50d5aeb1e5c9e61fa4d49961ae3ab1ae997369ed44da51b2f5ac010d188e6ad - languageName: node - linkType: hard - "use-callback-ref@npm:^1.3.0": version: 1.3.0 resolution: "use-callback-ref@npm:1.3.0" @@ -14891,16 +12264,6 @@ __metadata: languageName: node linkType: hard -"validate-npm-package-license@npm:^3.0.1": - version: 3.0.4 - resolution: "validate-npm-package-license@npm:3.0.4" - dependencies: - spdx-correct: "npm:^3.0.0" - spdx-expression-parse: "npm:^3.0.0" - checksum: 10/86242519b2538bb8aeb12330edebb61b4eb37fd35ef65220ab0b03a26c0592c1c8a7300d32da3cde5abd08d18d95e8dabfad684b5116336f6de9e6f207eec224 - languageName: node - linkType: hard - "vary@npm:^1, vary@npm:~1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" @@ -15138,13 +12501,6 @@ __metadata: languageName: node linkType: hard -"web-streams-polyfill@npm:^3.0.3": - version: 3.2.1 - resolution: "web-streams-polyfill@npm:3.2.1" - checksum: 10/08fcf97b7883c1511dd3da794f50e9bde75a660884783baaddb2163643c21a94086f394dc4bd20dff0f55c98d98d60c4bea05a5809ef5005bdf835b63ada8900 - languageName: node - linkType: hard - "webidl-conversions@npm:^3.0.0": version: 3.0.1 resolution: "webidl-conversions@npm:3.0.1" @@ -15239,17 +12595,6 @@ __metadata: languageName: node linkType: hard -"which@npm:^1.2.9": - version: 1.3.1 - resolution: "which@npm:1.3.1" - dependencies: - isexe: "npm:^2.0.0" - bin: - which: ./bin/which - checksum: 10/549dcf1752f3ee7fbb64f5af2eead4b9a2f482108b7de3e85c781d6c26d8cf6a52d37cfbe0642a155fa6470483fe892661a859c03157f24c669cf115f3bbab5e - languageName: node - linkType: hard - "which@npm:^2.0.1, which@npm:^2.0.2": version: 2.0.2 resolution: "which@npm:2.0.2" @@ -15273,7 +12618,7 @@ __metadata: languageName: node linkType: hard -"wide-align@npm:^1.1.2, wide-align@npm:^1.1.5": +"wide-align@npm:^1.1.5": version: 1.1.5 resolution: "wide-align@npm:1.1.5" dependencies: @@ -15322,6 +12667,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:^8.17.0": + version: 8.17.0 + resolution: "ws@npm:8.17.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10/5e1dcb0ae70c6e2f158f5b446e0a72a2cd335b07aba73ee1872e9bae1285382286a10e53ed479db21bdd690a5dfd05641a768611ebb236253c62fefa43ef58b4 + languageName: node + linkType: hard + "ws@npm:^8.8.1": version: 8.8.1 resolution: "ws@npm:8.8.1" @@ -15376,13 +12736,6 @@ __metadata: languageName: node linkType: hard -"yallist@npm:^2.1.2": - version: 2.1.2 - resolution: "yallist@npm:2.1.2" - checksum: 10/75fc7bee4821f52d1c6e6021b91b3e079276f1a9ce0ad58da3c76b79a7e47d6f276d35e206a96ac16c1cf48daee38a8bb3af0b1522a3d11c8ffe18f898828832 - languageName: node - linkType: hard - "yallist@npm:^4.0.0": version: 4.0.0 resolution: "yallist@npm:4.0.0" @@ -15433,61 +12786,6 @@ __metadata: languageName: node linkType: hard -"youtube-dl-exec@npm:^2.1.11": - version: 2.1.11 - resolution: "youtube-dl-exec@npm:2.1.11" - dependencies: - dargs: "npm:~7.0.0" - execa: "npm:~5.1.0" - is-unix: "npm:~2.0.1" - simple-get: "npm:~4.0.1" - checksum: 10/96584b7980679bf578a209b6d240c2c4adda9909c53e2f66a146267c71c8c51a68828ba0066087defbb4d294303a62a996bbeefd94d01c3f1a4e0fb837870a88 - languageName: node - linkType: hard - -"youtube-ext@npm:^1.1.14": - version: 1.1.14 - resolution: "youtube-ext@npm:1.1.14" - dependencies: - axios: "npm:^1.5.0" - checksum: 10/6825baea5a222d444a7b7214ba121b80b244435816ca068dd551a595cb47a13e4ed92d4ee959e480847c1c6123fbfd4272ba9c6ce156808cb2499b9f2563ff22 - languageName: node - linkType: hard - -"youtube-ext@npm:^1.1.23": - version: 1.1.23 - resolution: "youtube-ext@npm:1.1.23" - dependencies: - undici: "npm:^6.11.1" - checksum: 10/3a8bb7612327d869914f895b22d6b0fcd00af857d38b0cc78a966cdf4d2ff386e7ff9c10b333baf43edb7ba486d657e2cb1cd6c07c5e623e9fc088778781e356 - languageName: node - linkType: hard - -"youtube-sr@npm:^4.3.9": - version: 4.3.9 - resolution: "youtube-sr@npm:4.3.9" - checksum: 10/ad0ba149c4013f917c54cabebb0244ffbcb1f777cc23f7a7a1bc71c6b937dc2434200c936274394b9d7a18ec18393d341ca2eaac24984a6b72a090957be130ea - languageName: node - linkType: hard - -"yt-stream@npm:^1.4.8": - version: 1.4.8 - resolution: "yt-stream@npm:1.4.8" - checksum: 10/18fe099e9037287cdab899ee3c9448f79ff2b86992ff1bb7fff6dc0525c00addba2ae02f3c8998bd53b86b087b637bd8421c84a23c6e6a9922805ddb1a9b5cac - languageName: node - linkType: hard - -"ytdl-core@npm:^4.11.4": - version: 4.11.4 - resolution: "ytdl-core@npm:4.11.4" - dependencies: - m3u8stream: "npm:^0.8.6" - miniget: "npm:^4.2.2" - sax: "npm:^1.1.3" - checksum: 10/ee455a446f7c5d1c5a64d888189c58a490178fa734af57b5de455ac18761a54127a7b8eaa44849293c2186ed92d2080f6c56dcc407a11ae25192486aaeceda83 - languageName: node - linkType: hard - "zod-to-json-schema@npm:^3.20.3": version: 3.23.0 resolution: "zod-to-json-schema@npm:3.23.0"