Files
play-dl-test/play-dl/index.ts

470 lines
19 KiB
TypeScript
Raw Normal View History

export {
playlist_info,
video_basic_info,
video_info,
2021-10-17 21:41:16 +02:00
decipher_info,
yt_validate,
extractID,
YouTube,
2021-11-01 15:32:51 +05:30
YouTubeStream,
2021-11-19 13:49:21 +05:30
YouTubeChannel,
YouTubePlayList,
YouTubeVideo
} from './YouTube';
2021-11-23 09:56:08 +05:30
export {
spotify,
sp_validate,
refreshToken,
is_expired,
SpotifyAlbum,
SpotifyPlaylist,
SpotifyTrack,
Spotify
2021-11-23 09:56:08 +05:30
} from './Spotify';
export {
soundcloud,
so_validate,
SoundCloud,
SoundCloudStream,
getFreeClientID,
SoundCloudPlaylist,
SoundCloudTrack
} from './SoundCloud';
export {
deezer,
dz_validate,
dz_advanced_track_search,
Deezer,
DeezerTrack,
DeezerPlaylist,
DeezerAlbum
} from './Deezer';
2021-10-12 14:09:14 +05:30
export { setToken } from './token';
2021-09-05 18:50:57 +05:30
2021-10-05 18:47:09 +05:30
enum AudioPlayerStatus {
Idle = 'idle',
Buffering = 'buffering',
Paused = 'paused',
Playing = 'playing',
AutoPaused = 'autopaused'
}
2021-09-24 15:13:45 +05:30
interface SearchOptions {
limit?: number;
source?: {
youtube?: 'video' | 'playlist' | 'channel';
spotify?: 'album' | 'playlist' | 'track';
soundcloud?: 'tracks' | 'playlists' | 'albums';
deezer?: 'track' | 'playlist' | 'album';
2021-09-24 15:13:45 +05:30
};
2021-11-18 15:38:25 +05:30
fuzzy?: boolean;
2021-09-24 15:13:45 +05:30
}
import { createInterface } from 'node:readline';
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
import {
sp_validate,
yt_validate,
so_validate,
YouTubeStream,
SoundCloudStream,
YouTube,
SoundCloud,
Spotify
} from '.';
import { SpotifyAuthorize, sp_search } from './Spotify';
2021-09-24 15:13:45 +05:30
import { check_id, so_search, stream as so_stream, stream_from_info as so_stream_info } from './SoundCloud';
2021-11-18 15:38:25 +05:30
import { stream as yt_stream, StreamOptions, stream_from_info as yt_stream_info } from './YouTube/stream';
import { SoundCloudPlaylist, SoundCloudTrack } from './SoundCloud/classes';
2021-09-24 15:13:45 +05:30
import { yt_search } from './YouTube/search';
2021-10-05 18:47:09 +05:30
import { EventEmitter } from 'stream';
import { Deezer, dz_search, dz_validate } from './Deezer';
2021-11-18 15:38:25 +05:30
import { InfoData } from './YouTube/utils/constants';
import { YouTubeVideo } from './YouTube/classes/Video';
import { YouTubePlayList } from './YouTube/classes/Playlist';
import { YouTubeChannel } from './YouTube/classes/Channel';
import { SpotifyAlbum, SpotifyPlaylist, SpotifyTrack } from './Spotify/classes';
import { DeezerAlbum, DeezerPlaylist, DeezerTrack } from './Deezer/classes';
2021-12-14 15:33:18 +05:30
export async function stream(url: string, options: { seek? : number , seekMode?: 'precise' | 'granular'} & StreamOptions) : Promise<YouTubeStream>
export async function stream(url: string, options? : StreamOptions) : Promise<YouTubeStream | SoundCloudStream>
2021-09-27 22:20:50 +05:30
/**
2021-11-23 11:05:13 +05:30
* Creates a Stream [ YouTube or SoundCloud ] class from a url for playing.
2021-11-29 09:43:25 +05:30
*
* Example
2021-11-23 11:05:13 +05:30
* ```ts
* const source = await play.stream('youtube video URL') // YouTube Video Stream
2021-11-29 09:43:25 +05:30
*
2021-11-23 11:05:13 +05:30
* const source = await play.stream('soundcloud track URL') // SoundCloud Track Stream
2021-12-14 15:33:18 +05:30
*
* const source = await play.stream('youtube video URL', { seek : 45 }) // Seeks 45 seconds (approx.) in YouTube Video Stream [ seekMode = "granular" ]
* // Granular = 1 - 9 seconds before given time. [ Fast ]
* // Precise = Exact seek [ Little bit Slow ]
* // Above command seeks to 45 seconds approximately while below command seeks to 45 seconds precisely
*
* const source = await play.stream('youtube video URL', { seek : 45, seekMode: "precise" }) // Seeks precisely to 45 seconds in YouTube Video Stream
*
2021-11-23 11:05:13 +05:30
* const resource = createAudioResource(source.stream, {
* inputType : source.type
* }) // Use discordjs voice createAudioResource function.
* ```
* @param url Video / Track URL
2021-11-29 09:43:25 +05:30
* @param options
*
2021-11-23 11:05:13 +05:30
* - `number` quality : Quality number. [ 0 = Lowest, 1 = Medium, 2 = Highest ]
* - `boolean` htmldata : given data is html data or not
* @returns A {@link YouTubeStream} or {@link SoundCloudStream} Stream to play
2021-09-27 22:20:50 +05:30
*/
export async function stream(url: string, options: StreamOptions = {}): Promise<YouTubeStream | SoundCloudStream> {
2021-09-24 12:50:14 +05:30
if (url.length === 0) throw new Error('Stream URL has a length of 0. Check your url again.');
if (url.indexOf('spotify') !== -1) {
throw new Error(
'Streaming from Spotify is not supported. Please use search() to find a similar track on YouTube or SoundCloud instead.'
);
}
if (url.indexOf('deezer') !== -1) {
throw new Error(
'Streaming from Deezer is not supported. Please use search() to find a similar track on YouTube or SoundCloud instead.'
);
}
2021-09-24 12:49:39 +05:30
if (url.indexOf('soundcloud') !== -1) return await so_stream(url, options.quality);
2021-09-27 23:12:22 +05:30
else return await yt_stream(url, options);
2021-09-20 17:20:15 +05:30
}
2021-11-23 09:56:08 +05:30
export async function search(
query: string,
options: { source: { deezer: 'album' } } & SearchOptions
): Promise<DeezerAlbum[]>;
export async function search(
query: string,
options: { source: { deezer: 'playlist' } } & SearchOptions
): Promise<DeezerPlaylist[]>;
export async function search(
query: string,
options: { source: { deezer: 'track' } } & SearchOptions
): Promise<DeezerTrack[]>;
export async function search(
query: string,
options: { source: { soundcloud: 'albums' } } & SearchOptions
): Promise<SoundCloudPlaylist[]>;
export async function search(
query: string,
options: { source: { soundcloud: 'playlists' } } & SearchOptions
): Promise<SoundCloudPlaylist[]>;
export async function search(
query: string,
options: { source: { soundcloud: 'tracks' } } & SearchOptions
): Promise<SoundCloudTrack[]>;
export async function search(
query: string,
options: { source: { spotify: 'album' } } & SearchOptions
): Promise<SpotifyAlbum[]>;
export async function search(
query: string,
options: { source: { spotify: 'playlist' } } & SearchOptions
): Promise<SpotifyPlaylist[]>;
export async function search(
query: string,
options: { source: { spotify: 'track' } } & SearchOptions
): Promise<SpotifyTrack[]>;
export async function search(
query: string,
options: { source: { youtube: 'channel' } } & SearchOptions
): Promise<YouTubeChannel[]>;
export async function search(
query: string,
options: { source: { youtube: 'playlist' } } & SearchOptions
): Promise<YouTubePlayList[]>;
export async function search(
query: string,
options: { source: { youtube: 'video' } } & SearchOptions
): Promise<YouTubeVideo[]>;
export async function search(query: string, options: { limit: number } & SearchOptions): Promise<YouTubeVideo[]>;
2021-11-29 09:43:25 +05:30
export async function search(query: string, options?: SearchOptions): Promise<YouTubeVideo[]>;
2021-09-27 22:20:50 +05:30
/**
2021-11-23 11:05:13 +05:30
* Searches through a particular source and gives respective info.
*
* Example
* ```ts
* const searched = await play.search('Rick Roll', { source : { youtube : "video" } }) // YouTube Video Search
*
* const searched = await play.search('Rick Roll', { limit : 1 }) // YouTube Video Search but returns only 1 video.
*
* const searched = await play.search('Rick Roll', { source : { spotify : "track" } }) // Spotify Track Search
*
* const searched = await play.search('Rick Roll', { source : { soundcloud : "tracks" } }) // SoundCloud Track Search
*
* const searched = await play.search('Rick Roll', { source : { deezer : "track" } }) // Deezer Track Search
* ```
2021-09-27 22:20:50 +05:30
* @param query string to search.
2021-11-23 11:05:13 +05:30
* @param options
*
* - `number` limit : No of searches you want to have.
* - `boolean` fuzzy : Whether the search should be fuzzy or only return exact matches. Defaults to `true`. [ for `Deezer` Only ]
* - `Object` source : Contains type of source and type of result you want to have
* ```ts
* - youtube : 'video' | 'playlist' | 'channel';
- spotify : 'album' | 'playlist' | 'track';
- soundcloud : 'tracks' | 'playlists' | 'albums';
- deezer : 'track' | 'playlist' | 'album';
```
* @returns Array of {@link YouTube} or {@link Spotify} or {@link SoundCloud} or {@link Deezer} type
2021-09-27 22:20:50 +05:30
*/
export async function search(
query: string,
options: SearchOptions = {}
): Promise<YouTube[] | Spotify[] | SoundCloud[] | Deezer[]> {
2021-09-24 15:13:45 +05:30
if (!options.source) options.source = { youtube: 'video' };
2021-10-26 14:21:29 +05:30
query = encodeURIComponent(query);
2021-09-24 15:13:45 +05:30
if (options.source.youtube) return await yt_search(query, { limit: options.limit, type: options.source.youtube });
else if (options.source.spotify) return await sp_search(query, options.source.spotify, options.limit);
else if (options.source.soundcloud) return await so_search(query, options.source.soundcloud, options.limit);
else if (options.source.deezer)
2021-11-23 09:56:08 +05:30
return await dz_search(query, { limit: options.limit, type: options.source.deezer, fuzzy: options.fuzzy });
else throw new Error('Not possible to reach Here LOL. Easter Egg of play-dl if someone get this.');
2021-09-24 15:13:45 +05:30
}
2021-12-14 15:33:18 +05:30
export async function stream_from_info(info: SoundCloudTrack, options? : StreamOptions) : Promise<SoundCloudStream>
export async function stream_from_info(info: InfoData, options? : StreamOptions) : Promise<YouTubeStream>
2021-09-27 22:20:50 +05:30
/**
2021-11-23 11:05:13 +05:30
* Creates a Stream [ YouTube or SoundCloud ] class from video or track info for playing.
2021-11-29 09:43:25 +05:30
*
* Example
2021-11-23 11:05:13 +05:30
* ```ts
* const info = await video_info('youtube URL')
* const source = await play.stream_from_info(info) // YouTube Video Stream
2021-11-29 09:43:25 +05:30
*
2021-11-23 11:05:13 +05:30
* const soundInfo = await play.soundcloud('SoundCloud URL')
* const source = await play.stream_from_info(soundInfo) // SoundCloud Track Stream
2021-12-14 15:33:18 +05:30
*
* const source = await play.stream_from_info(info, { seek : 45 }) // Seeks 45 seconds (approx.) in YouTube Video Stream [ seekMode = "granular" ]
* // Granular = 1 - 9 seconds before given time. [ Fast ]
* // Precise = Exact seek [ Little bit Slow ]
* // Above command seeks to 45 seconds approximately while below command seeks to 45 seconds precisely
*
* const source = await play.stream_from_info(info, { seek : 45, seekMode: "precise" }) // Seeks precisely to 45 seconds in YouTube Video Stream
2021-11-29 09:43:25 +05:30
*
2021-11-23 11:05:13 +05:30
* const resource = createAudioResource(source.stream, {
* inputType : source.type
* }) // Use discordjs voice createAudioResource function.
* ```
* @param info YouTube video info OR SoundCloud track Class
2021-11-29 09:43:25 +05:30
* @param options
*
2021-11-23 11:05:13 +05:30
* - `number` quality : Quality number. [ 0 = Lowest, 1 = Medium, 2 = Highest ]
* - `Proxy[]` proxy : sends data through a proxy
* - `boolean` htmldata : given data is html data or not
* @returns A {@link YouTubeStream} or {@link SoundCloudStream} Stream to play
2021-09-27 22:20:50 +05:30
*/
2021-09-20 17:20:15 +05:30
export async function stream_from_info(
info: InfoData | SoundCloudTrack,
2021-09-24 12:49:39 +05:30
options: StreamOptions = {}
2021-09-27 22:20:50 +05:30
): Promise<YouTubeStream | SoundCloudStream> {
2021-09-27 23:12:22 +05:30
if (info instanceof SoundCloudTrack) return await so_stream_info(info, options.quality);
else return await yt_stream_info(info, options);
2021-09-20 17:20:15 +05:30
}
2021-09-29 20:23:16 +05:30
/**
2021-11-23 11:05:13 +05:30
* Validates url that play-dl supports.
2021-11-29 09:43:25 +05:30
*
2021-11-23 11:05:13 +05:30
* - `so` - SoundCloud
* - `sp` - Spotify
* - `dz` - Deezer
* - `yt` - YouTube
* @param url URL
2021-11-29 09:43:25 +05:30
* @returns
2021-11-23 11:05:13 +05:30
* ```ts
* 'so_playlist' / 'so_track' | 'sp_track' | 'sp_album' | 'sp_playlist' | 'dz_track' | 'dz_playlist' | 'dz_album' | 'yt_video' | 'yt_playlist' | 'search' | false
* ```
2021-09-29 20:23:16 +05:30
*/
2021-09-27 22:20:50 +05:30
export async function validate(
url: string
2021-10-09 16:18:05 +05:30
): Promise<
| 'so_playlist'
| 'so_track'
| 'sp_track'
| 'sp_album'
| 'sp_playlist'
| 'dz_track'
| 'dz_playlist'
| 'dz_album'
| 'yt_video'
| 'yt_playlist'
| 'search'
| false
2021-10-09 16:18:05 +05:30
> {
let check;
2021-10-09 18:59:16 +05:30
if (!url.startsWith('https')) return 'search';
2021-09-17 14:36:32 +05:30
if (url.indexOf('spotify') !== -1) {
check = sp_validate(url);
2021-10-09 18:59:16 +05:30
return check !== false ? (('sp_' + check) as 'sp_track' | 'sp_album' | 'sp_playlist') : false;
2021-09-20 17:20:15 +05:30
} else if (url.indexOf('soundcloud') !== -1) {
check = await so_validate(url);
2021-10-09 18:59:16 +05:30
return check !== false ? (('so_' + check) as 'so_playlist' | 'so_track') : false;
} else if (url.indexOf('deezer') !== -1) {
2021-10-31 16:26:36 +01:00
check = await dz_validate(url);
return check !== false ? (('dz_' + check) as 'dz_track' | 'dz_playlist' | 'dz_album') : false;
2021-09-17 14:36:32 +05:30
} else {
check = yt_validate(url);
2021-10-09 18:59:16 +05:30
return check !== false ? (('yt_' + check) as 'yt_video' | 'yt_playlist') : false;
2021-09-05 18:50:57 +05:30
}
2021-09-17 14:36:32 +05:30
}
2021-09-29 20:23:16 +05:30
/**
2021-11-23 11:05:13 +05:30
* Authorization interface for Spotify, SoundCloud and YouTube.
2021-11-29 09:43:25 +05:30
*
2021-11-23 11:05:13 +05:30
* Either stores info in `.data` folder or shows relevant data to be used in `setToken` function.
2021-11-29 09:43:25 +05:30
*
2021-11-23 11:05:13 +05:30
* ```ts
2021-11-23 11:21:43 +05:30
* const play = require('play-dl')
2021-11-29 09:43:25 +05:30
*
2021-11-23 11:05:13 +05:30
* play.authorization()
* ```
2021-11-29 09:43:25 +05:30
*
2021-11-23 11:05:13 +05:30
* Just run the above command and you will get a interface asking some questions.
2021-09-29 20:23:16 +05:30
*/
2021-09-20 17:20:15 +05:30
export function authorization(): void {
const ask = createInterface({
2021-09-17 14:36:32 +05:30
input: process.stdin,
output: process.stdout
});
2021-10-12 13:56:33 +05:30
ask.question('Do you want to save data in a file ? (Yes / No): ', (msg) => {
2021-10-12 14:09:14 +05:30
let file: boolean;
if (msg.toLowerCase() === 'yes') file = true;
else if (msg.toLowerCase() === 'no') file = false;
2021-10-12 13:56:33 +05:30
else {
console.log("That option doesn't exist. Try again...");
ask.close();
return;
}
ask.question('Choose your service - sc (for SoundCloud) / sp (for Spotify) / yo (for YouTube): ', (msg) => {
if (msg.toLowerCase().startsWith('sp')) {
let client_id: string, client_secret: string, redirect_url: string, market: string;
ask.question('Start by entering your Client ID : ', (id) => {
client_id = id;
ask.question('Now enter your Client Secret : ', (secret) => {
client_secret = secret;
ask.question('Enter your Redirect URL now : ', (url) => {
redirect_url = url;
2021-09-17 14:36:32 +05:30
console.log(
2021-10-12 13:56:33 +05:30
'\nIf you would like to know your region code visit : \nhttps://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements \n'
2021-09-17 14:36:32 +05:30
);
2021-10-12 13:56:33 +05:30
ask.question('Enter your region code (2-letter country code) : ', (mar) => {
if (mar.length === 2) market = mar;
else {
console.log(
"That doesn't look like a valid region code, IN will be selected as default."
);
market = 'IN';
}
console.log(
'\nNow open your browser and paste the below url, then authorize it and copy the redirected url. \n'
);
console.log(
`https://accounts.spotify.com/authorize?client_id=${client_id}&response_type=code&redirect_uri=${encodeURI(
redirect_url
)} \n`
);
ask.question('Paste the url which you just copied : ', async (url) => {
if (!existsSync('.data')) mkdirSync('.data');
2021-10-12 13:56:33 +05:30
const spotifyData = {
client_id,
client_secret,
redirect_url,
authorization_code: url.split('code=')[1],
market
};
const check = await SpotifyAuthorize(spotifyData, file);
if (check === false) throw new Error('Failed to get access token.');
ask.close();
});
2021-09-17 14:36:32 +05:30
});
});
});
});
2021-10-12 13:56:33 +05:30
} else if (msg.toLowerCase().startsWith('sc')) {
2021-10-12 14:09:14 +05:30
if (!file) {
console.log('You already had a client ID, just paste that in setToken function.');
2021-09-17 14:36:32 +05:30
ask.close();
return;
}
2021-10-12 13:56:33 +05:30
ask.question('Client ID : ', async (id) => {
let client_id = id;
if (!client_id) {
console.log("You didn't provide a client ID. Try again...");
ask.close();
return;
}
if (!existsSync('.data')) mkdirSync('.data');
2021-10-12 13:56:33 +05:30
console.log('Validating your client ID, hold on...');
if (await check_id(client_id)) {
console.log('Client ID has been validated successfully.');
writeFileSync('.data/soundcloud.data', JSON.stringify({ client_id }, undefined, 4));
2021-10-12 13:56:33 +05:30
} else console.log("That doesn't look like a valid client ID. Retry with a correct client ID.");
ask.close();
});
} else if (msg.toLowerCase().startsWith('yo')) {
2021-10-12 14:09:14 +05:30
if (!file) {
console.log('You already had cookie, just paste that in setToken function.');
2021-10-08 14:58:06 +05:30
ask.close();
return;
}
2021-10-12 13:56:33 +05:30
ask.question('Cookies : ', (cook: string) => {
if (!cook || cook.length === 0) {
console.log("You didn't provide a cookie. Try again...");
ask.close();
return;
}
if (!existsSync('.data')) mkdirSync('.data');
2021-10-12 13:56:33 +05:30
console.log('Cookies has been added successfully.');
let cookie: Object = {};
cook.split(';').forEach((x) => {
const arr = x.split('=');
if (arr.length <= 1) return;
const key = arr.shift()?.trim() as string;
const value = arr.join('=').trim();
Object.assign(cookie, { [key]: value });
});
writeFileSync('.data/youtube.data', JSON.stringify({ cookie }, undefined, 4));
2021-10-12 13:56:33 +05:30
ask.close();
2021-10-09 16:18:05 +05:30
});
2021-10-12 13:56:33 +05:30
} else {
console.log("That option doesn't exist. Try again...");
2021-10-08 14:58:06 +05:30
ask.close();
2021-10-12 13:56:33 +05:30
}
});
2021-10-12 14:09:14 +05:30
});
2021-09-17 14:36:32 +05:30
}
2021-11-23 11:05:13 +05:30
/**
* Attaches paused, playing, autoPaused Listeners to discordjs voice AudioPlayer.
2021-11-29 09:43:25 +05:30
*
2021-11-23 11:05:13 +05:30
* Useful if you don't want extra data to be downloaded by play-dl.
* @param player discordjs voice AudioPlayer
* @param resource A {@link YouTubeStream} or {@link SoundCloudStream}
*/
2021-10-05 18:47:09 +05:30
export function attachListeners(player: EventEmitter, resource: YouTubeStream | SoundCloudStream) {
// cleanup existing listeners if they are still registered
type listenerType = (...args: any[]) => void;
const listeners = player.listeners(AudioPlayerStatus.Idle);
for (const cleanup of listeners) {
if ((cleanup as any).__playDlAttachedListener) {
cleanup();
player.removeListener(AudioPlayerStatus.Idle, cleanup as listenerType);
}
}
2021-10-12 14:09:14 +05:30
const pauseListener = () => resource.pause();
const resumeListener = () => resource.resume();
const idleListener = () => {
2021-10-09 21:19:35 +05:30
player.removeListener(AudioPlayerStatus.Paused, pauseListener);
player.removeListener(AudioPlayerStatus.AutoPaused, pauseListener);
player.removeListener(AudioPlayerStatus.Playing, resumeListener);
};
pauseListener.__playDlAttachedListener = true;
resumeListener.__playDlAttachedListener = true;
idleListener.__playDlAttachedListener = true;
player.on(AudioPlayerStatus.Paused, pauseListener);
player.on(AudioPlayerStatus.AutoPaused, pauseListener);
player.on(AudioPlayerStatus.Playing, resumeListener);
player.once(AudioPlayerStatus.Idle, idleListener);
2021-10-09 16:18:05 +05:30
}