play-dl-fix/play-dl/index.ts
Killer069 4c8162ecdd
1.2.6
Added option to pass html body data directly to video_basic_info | video_info | stream functions.
 Fixed Playlist ID regexp to allow multiple playlist ID support.
 Added Dezer advanced search
 Improved Deezer Share Link handling.
 Added cookiesHeaders options to update cookies when using external https request module.
 Added Artists badge in YouTubeChannel Class.
2021-11-01 15:43:52 +05:30

276 lines
12 KiB
TypeScript

export {
playlist_info,
video_basic_info,
video_info,
decipher_info,
yt_validate,
extractID,
YouTube,
YouTubeStream,
cookieHeaders
} from './YouTube';
export { spotify, sp_validate, refreshToken, is_expired, Spotify } from './Spotify';
export { soundcloud, so_validate, SoundCloud, SoundCloudStream, getFreeClientID } from './SoundCloud';
export { deezer, dz_validate, dz_search, dz_advanced_track_search, Deezer } from './Deezer';
export { setToken } from './token';
enum AudioPlayerStatus {
Idle = 'idle',
Buffering = 'buffering',
Paused = 'paused',
Playing = 'playing',
AutoPaused = 'autopaused'
}
interface SearchOptions {
limit?: number;
source?: {
youtube?: 'video' | 'playlist' | 'channel';
spotify?: 'album' | 'playlist' | 'track';
soundcloud?: 'tracks' | 'playlists' | 'albums';
deezer?: 'track' | 'playlist' | 'album';
};
}
import readline from 'node:readline';
import fs from 'node:fs';
import {
sp_validate,
yt_validate,
so_validate,
YouTubeStream,
SoundCloudStream,
YouTube,
SoundCloud,
Spotify
} from '.';
import { SpotifyAuthorize, sp_search } from './Spotify';
import { check_id, so_search, stream as so_stream, stream_from_info as so_stream_info } from './SoundCloud';
import { InfoData, stream as yt_stream, StreamOptions, stream_from_info as yt_stream_info } from './YouTube/stream';
import { SoundCloudTrack } from './SoundCloud/classes';
import { yt_search } from './YouTube/search';
import { EventEmitter } from 'stream';
import { Deezer, dz_search, dz_validate } from './Deezer';
/**
* Main stream Command for streaming through various sources
* @param url The video / track url to make stream of
* @param options contains quality, cookie and proxy to set for stream
* @returns YouTube / SoundCloud Stream to play
*/
export async function stream(url: string, options: StreamOptions = {}): Promise<YouTubeStream | SoundCloudStream> {
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.'
);
}
if (url.indexOf('soundcloud') !== -1) return await so_stream(url, options.quality);
else return await yt_stream(url, options);
}
/**
* Main Search Command for searching through various sources
* @param query string to search.
* @param options contains limit and source to choose.
* @returns Array of YouTube or Spotify or SoundCloud or Deezer
*/
export async function search(
query: string,
options: SearchOptions = {}
): Promise<YouTube[] | Spotify[] | SoundCloud[] | Deezer[]> {
if (!options.source) options.source = { youtube: 'video' };
query = encodeURIComponent(query);
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)
return await dz_search(query, { limit: options.limit, type: options.source.deezer });
else throw new Error('Not possible to reach Here LOL. Easter Egg of play-dl if someone get this.');
}
/**
* stream Command for streaming through various sources using data from video_info or soundcloud
* SoundCloud Track is only supported
* @param info video_info data or SoundCloud Track data.
* @param options contains quality, cookie and proxy to set for stream
* @returns YouTube / SoundCloud Stream to play
*/
export async function stream_from_info(
info: InfoData | SoundCloudTrack,
options: StreamOptions = {}
): Promise<YouTubeStream | SoundCloudStream> {
if (info instanceof SoundCloudTrack) return await so_stream_info(info, options.quality);
else return await yt_stream_info(info, options);
}
/**
* Command to validate the provided url. It checks whether it supports play-dl or not.
* @param url url to validate
* @returns On failure, returns false else type of url.
*/
export async function validate(
url: string
): Promise<
| 'so_playlist'
| 'so_track'
| 'sp_track'
| 'sp_album'
| 'sp_playlist'
| 'dz_track'
| 'dz_playlist'
| 'dz_album'
| 'yt_video'
| 'yt_playlist'
| 'search'
| false
> {
let check;
if (!url.startsWith('https')) return 'search';
if (url.indexOf('spotify') !== -1) {
check = sp_validate(url);
return check !== false ? (('sp_' + check) as 'sp_track' | 'sp_album' | 'sp_playlist') : false;
} else if (url.indexOf('soundcloud') !== -1) {
check = await so_validate(url);
return check !== false ? (('so_' + check) as 'so_playlist' | 'so_track') : false;
} else if (url.indexOf('deezer') !== -1) {
check = await dz_validate(url);
return check !== false ? (('dz_' + check) as 'dz_track' | 'dz_playlist' | 'dz_album') : false;
} else {
check = yt_validate(url);
return check !== false ? (('yt_' + check) as 'yt_video' | 'yt_playlist') : false;
}
}
/**
* Authorization interface for Spotify and SoundCloud.
*/
export function authorization(): void {
const ask = readline.createInterface({
input: process.stdin,
output: process.stdout
});
ask.question('Do you want to save data in a file ? (Yes / No): ', (msg) => {
let file: boolean;
if (msg.toLowerCase() === 'yes') file = true;
else if (msg.toLowerCase() === 'no') file = false;
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;
console.log(
'\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'
);
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 (!fs.existsSync('.data')) fs.mkdirSync('.data');
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();
});
});
});
});
});
} else if (msg.toLowerCase().startsWith('sc')) {
if (!file) {
console.log('You already had a client ID, just paste that in setToken function.');
ask.close();
return;
}
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 (!fs.existsSync('.data')) fs.mkdirSync('.data');
console.log('Validating your client ID, hold on...');
if (await check_id(client_id)) {
console.log('Client ID has been validated successfully.');
fs.writeFileSync('.data/soundcloud.data', JSON.stringify({ client_id }, undefined, 4));
} 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')) {
if (!file) {
console.log('You already had cookie, just paste that in setToken function.');
ask.close();
return;
}
ask.question('Cookies : ', (cook: string) => {
if (!cook || cook.length === 0) {
console.log("You didn't provide a cookie. Try again...");
ask.close();
return;
}
if (!fs.existsSync('.data')) fs.mkdirSync('.data');
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 });
});
fs.writeFileSync('.data/youtube.data', JSON.stringify({ cookie }, undefined, 4));
ask.close();
});
} else {
console.log("That option doesn't exist. Try again...");
ask.close();
}
});
});
}
export function attachListeners(player: EventEmitter, resource: YouTubeStream | SoundCloudStream) {
const pauseListener = () => resource.pause();
const resumeListener = () => resource.resume();
player.on(AudioPlayerStatus.Paused, pauseListener);
player.on(AudioPlayerStatus.AutoPaused, pauseListener);
player.on(AudioPlayerStatus.Playing, resumeListener);
player.once(AudioPlayerStatus.Idle, () => {
player.removeListener(AudioPlayerStatus.Paused, pauseListener);
player.removeListener(AudioPlayerStatus.AutoPaused, pauseListener);
player.removeListener(AudioPlayerStatus.Playing, resumeListener);
});
}