From 10d21bdb881bd03b030ce123959a1c1db9e71ebf Mon Sep 17 00:00:00 2001 From: killer069 <65385476+killer069@users.noreply.github.com> Date: Mon, 11 Oct 2021 10:35:29 +0530 Subject: [PATCH 1/6] Playlist ID and video ID extract method updated --- play-dl/YouTube/utils/extractor.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/play-dl/YouTube/utils/extractor.ts b/play-dl/YouTube/utils/extractor.ts index d615d45..12b8250 100644 --- a/play-dl/YouTube/utils/extractor.ts +++ b/play-dl/YouTube/utils/extractor.ts @@ -13,12 +13,12 @@ interface PlaylistOptions { } const video_id_pattern = /^[a-zA-Z\d_-]{11,12}$/; -const playlist_id_pattern = /^PL[a-zA-Z\d_-]{32}$/; +const playlist_id_pattern = /^PL[a-zA-Z\d_-]{16,41}$/; const DEFAULT_API_KEY = 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8'; const video_pattern = /^((?:https?:)?\/\/)?(?:(?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$/; const playlist_pattern = - /^((?:https?:)?\/\/)?(?:(?:www|m)\.)?(youtube\.com)\/(?:(playlist|watch))(.*)?((\?|\&)list=)PL[a-zA-Z\d_-]{32}(.*)?$/; + /^((?:https?:)?\/\/)?(?:(?:www|m)\.)?(youtube\.com)\/(?:(playlist|watch))(.*)?((\?|\&)list=)PL[a-zA-Z\d_-]{16,41}(.*)?$/; /** * Command to validate a YouTube url * @param url Url for validation @@ -28,7 +28,10 @@ export function yt_validate(url: string): 'playlist' | 'video' | 'search' | fals if (url.indexOf('list=') === -1) { if (url.startsWith('https')) { if (url.match(video_pattern)) { - const id = url.split('v=')[1].split('&')[0]; + let id: string; + if (url.includes('youtu.be/')) id = url.split('youtu.be/')[1].split(/(\?|\/|&)/)[0]; + else if (url.includes('youtube.com/embed/')) id = url.split('youtube.com/embed/')[1].split(/(\?|\/|&)/)[0]; + else id = url.split('watch?v=')[1].split(/(\?|\/|&)/)[0]; if (id.match(video_id_pattern)) return 'video'; else return false; } else return false; @@ -56,9 +59,9 @@ export function extractID(url: string): string { if (url.startsWith('https')) { if (url.indexOf('list=') === -1) { let video_id: string; - if (url.includes('youtu.be/')) video_id = url.split('youtu.be/')[1].split('/')[0]; - else if (url.includes('youtube.com/embed/')) video_id = url.split('youtube.com/embed/')[1].split('/')[0]; - else video_id = url.split('watch?v=')[1].split('&')[0]; + if (url.includes('youtu.be/')) video_id = url.split('youtu.be/')[1].split(/(\?|\/|&)/)[0]; + else if (url.includes('youtube.com/embed/')) video_id = url.split('youtube.com/embed/')[1].split(/(\?|\/|&)/)[0]; + else video_id = url.split('watch?v=')[1].split(/(\?|\/|&)/)[0]; return video_id; } else { return url.split('list=')[1].split('&')[0]; From 511c9193662be701b3f0b57ce346c76a82b6fc5c Mon Sep 17 00:00:00 2001 From: killer069 <65385476+killer069@users.noreply.github.com> Date: Mon, 11 Oct 2021 11:51:47 +0530 Subject: [PATCH 2/6] PlayList ID fixed --- play-dl/YouTube/utils/extractor.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/play-dl/YouTube/utils/extractor.ts b/play-dl/YouTube/utils/extractor.ts index 12b8250..6078620 100644 --- a/play-dl/YouTube/utils/extractor.ts +++ b/play-dl/YouTube/utils/extractor.ts @@ -42,10 +42,7 @@ export function yt_validate(url: string): 'playlist' | 'video' | 'search' | fals } } else { if (!url.match(playlist_pattern)) return false; - const Playlist_id = url.split('list=')[1].split('&')[0]; - if (Playlist_id.length !== 34 || !Playlist_id.startsWith('PL')) { - return false; - } else return 'playlist'; + else return 'playlist'; } } /** From 602927a5c725bcc17bae9fc3fa9751d63997d430 Mon Sep 17 00:00:00 2001 From: killer069 <65385476+killer069@users.noreply.github.com> Date: Mon, 11 Oct 2021 17:29:10 +0530 Subject: [PATCH 3/6] PassThrough Changed --- play-dl/SoundCloud/classes.ts | 11 ++++++----- play-dl/YouTube/classes/LiveStream.ts | 20 +++++++++++--------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/play-dl/SoundCloud/classes.ts b/play-dl/SoundCloud/classes.ts index f1f0398..81edce0 100644 --- a/play-dl/SoundCloud/classes.ts +++ b/play-dl/SoundCloud/classes.ts @@ -1,5 +1,5 @@ import { request, request_stream } from '../YouTube/utils/request'; -import { PassThrough } from 'stream'; +import { Readable } from 'stream'; import { IncomingMessage } from 'http'; import { StreamType } from '../YouTube/stream'; import { Timer } from '../YouTube/classes/LiveStream'; @@ -202,7 +202,7 @@ export class SoundCloudPlaylist { * SoundCloud Stream class */ export class Stream { - stream: PassThrough; + stream: Readable; type: StreamType; private url: string; private downloaded_time: number; @@ -212,7 +212,7 @@ export class Stream { private time: number[]; private segment_urls: string[]; constructor(url: string, type: StreamType = StreamType.Arbitrary) { - this.stream = new PassThrough({ highWaterMark: 10 * 1000 * 1000 }); + this.stream = new Readable({ highWaterMark: 10 * 1000 * 1000, read(){} }); this.type = type; this.url = url; this.downloaded_time = 0; @@ -274,7 +274,9 @@ export class Stream { } this.request = stream; - stream.pipe(this.stream, { end: false }); + stream.on('data', (c) => { + this.stream.push(c) + }) stream.on('end', () => { if (this.downloaded_time >= 300) return; else this.loop(); @@ -286,7 +288,6 @@ export class Stream { private cleanup() { this.timer.destroy(); - this.request?.unpipe(this.stream); this.request?.destroy(); this.url = ''; this.downloaded_time = 0; diff --git a/play-dl/YouTube/classes/LiveStream.ts b/play-dl/YouTube/classes/LiveStream.ts index 4dc8fdd..f5e8f85 100644 --- a/play-dl/YouTube/classes/LiveStream.ts +++ b/play-dl/YouTube/classes/LiveStream.ts @@ -1,4 +1,4 @@ -import { PassThrough } from 'stream'; +import { Readable } from 'stream'; import { IncomingMessage } from 'http'; import { parseAudioFormats, StreamOptions, StreamType } from '../stream'; import { Proxy, request, request_stream } from '../utils/request'; @@ -11,7 +11,7 @@ export interface FormatInterface { } export class LiveStreaming { - stream: PassThrough; + stream: Readable; type: StreamType; private base_url: string; private url: string; @@ -23,7 +23,7 @@ export class LiveStreaming { private segments_urls: string[]; private request: IncomingMessage | null; constructor(dash_url: string, target_interval: number, video_url: string) { - this.stream = new PassThrough({ highWaterMark: 10 * 1000 * 1000 }); + this.stream = new Readable({ highWaterMark: 10 * 1000 * 1000, read(){} }); this.type = StreamType.Arbitrary; this.url = dash_url; this.base_url = ''; @@ -72,7 +72,6 @@ export class LiveStreaming { private cleanup() { this.timer.destroy(); this.dash_timer.destroy(); - this.request?.unpipe(this.stream); this.request?.destroy(); this.video_url = ''; this.request = null; @@ -102,7 +101,9 @@ export class LiveStreaming { return; } this.request = stream; - stream.pipe(this.stream, { end: false }); + stream.on('data', (c) => { + this.stream.push(c) + }) stream.on('end', () => { this.packet_count++; resolve(''); @@ -123,7 +124,7 @@ export class LiveStreaming { * Class for YouTube Stream */ export class Stream { - stream: PassThrough; + stream: Readable; type: StreamType; private url: string; private bytes_count: number; @@ -142,7 +143,7 @@ export class Stream { video_url: string, options: StreamOptions ) { - this.stream = new PassThrough({ highWaterMark: 10 * 1000 * 1000 }); + this.stream = new Readable({ highWaterMark : 10 * 1000 * 1000, read(){} }); this.url = url; this.quality = options.quality as number; this.proxy = options.proxy || undefined; @@ -170,7 +171,6 @@ export class Stream { } private cleanup() { - this.request?.unpipe(this.stream); this.request?.destroy(); this.request = null; this.url = ''; @@ -203,7 +203,9 @@ export class Stream { return; } this.request = stream; - stream.pipe(this.stream, { end: false }); + stream.on('data', (c) => { + this.stream.push(c) + }) stream.once('error', async (err) => { this.cleanup(); From 80d6ecba969e67ce9125d0a1b0cd7900613a8d84 Mon Sep 17 00:00:00 2001 From: killer069 <65385476+killer069@users.noreply.github.com> Date: Mon, 11 Oct 2021 18:38:08 +0530 Subject: [PATCH 4/6] Proxy issues fixed --- play-dl/Request/classes.ts | 48 ++--- play-dl/Request/index.ts | 230 ++++++++++++++++++++---- play-dl/SoundCloud/classes.ts | 2 +- play-dl/SoundCloud/index.ts | 2 +- play-dl/Spotify/classes.ts | 2 +- play-dl/Spotify/index.ts | 2 +- play-dl/YouTube/classes/LiveStream.ts | 2 +- play-dl/YouTube/classes/Playlist.ts | 2 +- play-dl/YouTube/search.ts | 2 +- play-dl/YouTube/stream.ts | 2 +- play-dl/YouTube/utils/cipher.ts | 2 +- play-dl/YouTube/utils/extractor.ts | 4 +- play-dl/YouTube/utils/request.ts | 249 -------------------------- 13 files changed, 218 insertions(+), 331 deletions(-) delete mode 100644 play-dl/YouTube/utils/request.ts diff --git a/play-dl/Request/classes.ts b/play-dl/Request/classes.ts index a1f2f1b..b7b0921 100644 --- a/play-dl/Request/classes.ts +++ b/play-dl/Request/classes.ts @@ -1,16 +1,12 @@ import tls, { TLSSocket } from 'tls'; import { URL } from 'url'; -import { Timer } from '../YouTube/classes/LiveStream'; -interface ResponseOptions extends tls.ConnectionOptions { - body?: string; - method: 'GET' | 'POST'; - cookies?: boolean; +interface ProxyOptions extends tls.ConnectionOptions { + method: 'GET'; headers?: Object; - timeout?: number; } -export class Response { +export class Proxy { parsed_url: URL; statusCode: number; rawHeaders: string; @@ -18,18 +14,14 @@ export class Response { body: string; socket: TLSSocket; sentHeaders: string; - sentBody: string; - private options: ResponseOptions; - private timer: Timer | null; - constructor(req_url: string, options: ResponseOptions) { - this.parsed_url = new URL(req_url); + private options: ProxyOptions; + constructor(parsed_url: URL, options: ProxyOptions){ + this.parsed_url = parsed_url; this.sentHeaders = ''; this.statusCode = 0; - this.sentBody = ''; this.rawHeaders = ''; this.body = ''; this.headers = {}; - this.timer = null; this.options = options; this.socket = tls.connect( { @@ -45,17 +37,15 @@ export class Response { this.sentHeaders += `${key}: ${value}\r\n`; } } - if (options.body) this.sentBody = options.body; } private onConnect() { this.socket.write( `${this.options.method} ${this.parsed_url.pathname}${this.parsed_url.search} HTTP/1.1\r\n` + - `Host : ${this.parsed_url.hostname}\r\n` + - this.sentHeaders + - `Connection: close\r\n` + - `\r\n` + - this.sentBody + `Host : ${this.parsed_url.hostname}\r\n` + + this.sentHeaders + + `Connection: close\r\n` + + `\r\n` ); } @@ -75,21 +65,7 @@ export class Response { } } - stream(): Promise { - return new Promise((resolve, reject) => { - this.timer = new Timer(() => this.socket.end(), this.options.timeout || 1); - this.socket.once('error', (err) => reject(err)); - this.socket.once('data', (chunk) => { - this.rawHeaders = chunk.toString('utf-8'); - this.parseHeaders(); - resolve(this.socket); - }); - this.socket.on('data', () => this.timer?.reuse()); - this.socket.once('end', () => this.timer?.destroy()); - }); - } - - fetch(): Promise { + fetch(): Promise { return new Promise((resolve, reject) => { this.socket.setEncoding('utf-8'); this.socket.once('error', (err) => reject(err)); @@ -108,4 +84,4 @@ export class Response { }); }); } -} +} \ No newline at end of file diff --git a/play-dl/Request/index.ts b/play-dl/Request/index.ts index 0946551..b6d753b 100644 --- a/play-dl/Request/index.ts +++ b/play-dl/Request/index.ts @@ -1,6 +1,17 @@ -import { Response } from './classes'; +import http, { ClientRequest, IncomingMessage } from 'http'; +import https ,{ RequestOptions } from 'https'; +import { URL } from 'url'; +import { getCookies, setCookie, uploadCookie } from '../YouTube/utils/cookie'; +import { Proxy } from './classes'; -export type Proxy = ProxyOpts | string; +export type ProxyOptions = ProxyOpts | string; + +interface RequestOpts extends RequestOptions{ + body?: string; + method?: 'GET' | 'POST'; + proxies?: ProxyOptions[]; + cookies?: boolean; +} interface ProxyOpts { host: string; @@ -10,43 +21,192 @@ interface ProxyOpts { password: string; }; } - -interface RequestOptions { - body?: string; - method: 'GET' | 'POST'; - proxies?: Proxy[]; - cookies?: boolean; - headers?: Object; - timeout?: number; -} - -interface StreamGetterOptions { - method: 'GET' | 'POST'; - cookies?: boolean; - headers: Object; -} - -export function request_stream(req_url: string, options: RequestOptions = { method: 'GET' }): Promise { +/** + * Main module which play-dl uses to make a request to stream url. + * @param url URL to make https request to + * @param options Request options for https request + * @returns IncomingMessage from the request + */ +export function request_stream(req_url: string, options: RequestOpts = { method: 'GET' }): Promise { return new Promise(async (resolve, reject) => { - let res = new Response(req_url, options); - await res.stream(); - if (res.statusCode >= 300 && res.statusCode < 400) { - res = await request_stream((res.headers as any).location, options); - await res.stream(); + let res = await https_getter(req_url, options).catch((err: Error) => err); + if (res instanceof Error) { + reject(res); + return; } - resolve(res); - }); -} - -export function request(req_url: string, options: RequestOptions = { method: 'GET' }): Promise { - return new Promise(async (resolve, reject) => { - let res = new Response(req_url, options); - await res.fetch(); if (Number(res.statusCode) >= 300 && Number(res.statusCode) < 400) { - res = await request((res.headers as any).location, options); - } else if (Number(res.statusCode) > 400) { - reject(new Error(`Got ${res.statusCode} from the request`)); + res = await https_getter(res.headers.location as string, options); } resolve(res); }); } +/** + * Main module which play-dl uses to make a proxy or normal request + * @param url URL to make https request to + * @param options Request options for https request + * @returns body of that request + */ +export function request(req_url: string, options: RequestOpts = { method: 'GET' }): Promise { + return new Promise(async (resolve, reject) => { + if (!options?.proxies || options.proxies.length === 0) { + let data = ''; + let cookies_added = false; + if (options.cookies) { + let cook = getCookies(); + if (typeof cook === 'string' && options.headers) { + Object.assign(options.headers, { cookie: cook }); + cookies_added = true; + } + } + let res = await https_getter(req_url, options).catch((err: Error) => err); + if (res instanceof Error) { + reject(res); + return; + } + if (res.headers && res.headers['set-cookie'] && cookies_added) { + res.headers['set-cookie'].forEach((x) => { + x.split(';').forEach((x) => { + const arr = x.split('='); + if (arr.length <= 1) return; + const key = arr.shift()?.trim() as string; + const value = arr.join('=').trim(); + setCookie(key, value); + }); + }); + uploadCookie(); + } + if (Number(res.statusCode) >= 300 && Number(res.statusCode) < 400) { + res = await https_getter(res.headers.location as string, options); + } else if (Number(res.statusCode) > 400) { + reject(new Error(`Got ${res.statusCode} from the request`)); + } + res.setEncoding('utf-8'); + res.on('data', (c) => (data += c)); + res.on('end', () => resolve(data)); + } + else { + let cookies_added = false; + if (options.cookies) { + let cook = getCookies(); + if (typeof cook === 'string' && options.headers) { + Object.assign(options.headers, { cookie: cook }); + cookies_added = true; + } + } + let res = await proxy_getter(req_url, options.proxies, options.headers).catch((e: Error) => e); + if (res instanceof Error) { + reject(res); + return; + } + if (res.headers && (res.headers as any)['set-cookie'] && cookies_added) { + (res.headers as any)['set-cookie'].forEach((x: string) => { + x.split(';').forEach((x) => { + const arr = x.split('='); + if (arr.length <= 1) return; + const key = arr.shift()?.trim() as string; + const value = arr.join('=').trim(); + setCookie(key, value); + }); + }); + uploadCookie(); + } + if (res.statusCode >= 300 && res.statusCode < 400) { + res = await proxy_getter((res.headers as any)['location'], options.proxies, options.headers); + } else if (res.statusCode > 400) { + reject(new Error(`GOT ${res.statusCode} from proxy request`)); + } + resolve(res.body) + } + }); +} + +/** + * Chooses one random number between max and min number. + * @param min Minimum number + * @param max Maximum number + * @returns Random Number + */ + function randomIntFromInterval(min: number, max: number): number { + let x = Math.floor(Math.random() * (max - min + 1) + min); + if (x === 0) return 0; + else return x - 1; +} +/** + * Main module that play-dl uses for proxy. + * @param req_url URL to make https request to + * @param req_proxy Proxies array + * @returns Object with statusCode, head and body + */ +function proxy_getter(req_url : string, req_proxy : ProxyOptions[], headers? : Object): Promise{ + return new Promise((resolve, reject) => { + const proxy: string | ProxyOpts = req_proxy[randomIntFromInterval(0, req_proxy.length)]; + const parsed_url = new URL(req_url) + let opts: ProxyOpts; + if (typeof proxy === 'string') { + const parsed = new URL(proxy); + opts = { + host: parsed.hostname, + port: Number(parsed.port), + authentication: { + username: parsed.username, + password: parsed.password + } + }; + } else opts = proxy; + let req: ClientRequest; + if (opts.authentication?.username.length === 0) { + req = http.request({ + host: opts.host, + port: opts.port, + method: 'CONNECT', + path: `${parsed_url.host}:443` + }); + } else { + req = http.request({ + host: opts.host, + port: opts.port, + method: 'CONNECT', + path: `${parsed_url.host}:443`, + headers: { + 'Proxy-Authorization': `Basic ${Buffer.from( + `${opts.authentication?.username}:${opts.authentication?.password}` + ).toString('base64')}` + } + }); + } + req.on('connect', async function (res, socket) { + const conn_proxy = new Proxy(parsed_url, { method : "GET", socket : socket, headers : headers }) + await conn_proxy.fetch() + socket.end() + resolve(conn_proxy) + }) + req.on('error', (e: Error) => reject(e)); + req.end(); + }) +} + +/** + * Main module that play-dl uses for making a https request + * @param req_url URL to make https request to + * @param options Request options for https request + * @returns Incoming Message from the https request + */ + function https_getter(req_url: string, options: RequestOpts = {}): Promise { + return new Promise((resolve, reject) => { + const s = new URL(req_url); + options.method ??= 'GET'; + const req_options: RequestOptions = { + host: s.hostname, + path: s.pathname + s.search, + headers: options.headers ?? {}, + method: options.method + }; + + const req = https.request(req_options, resolve); + req.on('error', (err) => { + reject(err); + }); + if (options.method === 'POST') req.write(options.body); + req.end(); + }); +} \ No newline at end of file diff --git a/play-dl/SoundCloud/classes.ts b/play-dl/SoundCloud/classes.ts index 81edce0..faca32c 100644 --- a/play-dl/SoundCloud/classes.ts +++ b/play-dl/SoundCloud/classes.ts @@ -1,4 +1,4 @@ -import { request, request_stream } from '../YouTube/utils/request'; +import { request, request_stream } from '../Request'; import { Readable } from 'stream'; import { IncomingMessage } from 'http'; import { StreamType } from '../YouTube/stream'; diff --git a/play-dl/SoundCloud/index.ts b/play-dl/SoundCloud/index.ts index e949809..d21d9eb 100644 --- a/play-dl/SoundCloud/index.ts +++ b/play-dl/SoundCloud/index.ts @@ -1,6 +1,6 @@ import fs from 'fs'; import { StreamType } from '../YouTube/stream'; -import { request } from '../YouTube/utils/request'; +import { request } from '../Request'; import { SoundCloudPlaylist, SoundCloudTrack, SoundCloudTrackFormat, Stream } from './classes'; let soundData: SoundDataOptions; diff --git a/play-dl/Spotify/classes.ts b/play-dl/Spotify/classes.ts index 807497a..2e5148b 100644 --- a/play-dl/Spotify/classes.ts +++ b/play-dl/Spotify/classes.ts @@ -1,4 +1,4 @@ -import { request } from '../YouTube/utils/request'; +import { request } from '../Request'; import { SpotifyDataOptions } from '.'; interface SpotifyTrackAlbum { diff --git a/play-dl/Spotify/index.ts b/play-dl/Spotify/index.ts index f4ecc84..2297848 100644 --- a/play-dl/Spotify/index.ts +++ b/play-dl/Spotify/index.ts @@ -1,4 +1,4 @@ -import { request } from '../YouTube/utils/request'; +import { request } from '../Request'; import { SpotifyAlbum, SpotifyPlaylist, SpotifyTrack } from './classes'; import fs from 'fs'; diff --git a/play-dl/YouTube/classes/LiveStream.ts b/play-dl/YouTube/classes/LiveStream.ts index f5e8f85..d21f6cd 100644 --- a/play-dl/YouTube/classes/LiveStream.ts +++ b/play-dl/YouTube/classes/LiveStream.ts @@ -1,7 +1,7 @@ import { Readable } from 'stream'; import { IncomingMessage } from 'http'; import { parseAudioFormats, StreamOptions, StreamType } from '../stream'; -import { Proxy, request, request_stream } from '../utils/request'; +import { ProxyOptions as Proxy, request, request_stream } from '../../Request'; import { video_info } from '..'; export interface FormatInterface { diff --git a/play-dl/YouTube/classes/Playlist.ts b/play-dl/YouTube/classes/Playlist.ts index ee07455..92ac573 100644 --- a/play-dl/YouTube/classes/Playlist.ts +++ b/play-dl/YouTube/classes/Playlist.ts @@ -1,5 +1,5 @@ import { getPlaylistVideos, getContinuationToken } from '../utils/extractor'; -import { request } from '../utils/request'; +import { request } from '../../Request'; import { YouTubeChannel } from './Channel'; import { YouTubeVideo } from './Video'; const BASE_API = 'https://www.youtube.com/youtubei/v1/browse?key='; diff --git a/play-dl/YouTube/search.ts b/play-dl/YouTube/search.ts index 7446ba9..de952e3 100644 --- a/play-dl/YouTube/search.ts +++ b/play-dl/YouTube/search.ts @@ -1,4 +1,4 @@ -import { request } from './utils/request'; +import { request } from './../Request'; import { ParseSearchInterface, ParseSearchResult } from './utils/parser'; import { YouTubeVideo } from './classes/Video'; import { YouTubeChannel } from './classes/Channel'; diff --git a/play-dl/YouTube/stream.ts b/play-dl/YouTube/stream.ts index c2a8663..ebe9f7c 100644 --- a/play-dl/YouTube/stream.ts +++ b/play-dl/YouTube/stream.ts @@ -1,6 +1,6 @@ import { video_info } from '.'; import { LiveStreaming, Stream } from './classes/LiveStream'; -import { Proxy } from './utils/request'; +import { ProxyOptions as Proxy } from './../Request'; export enum StreamType { Arbitrary = 'arbitrary', diff --git a/play-dl/YouTube/utils/cipher.ts b/play-dl/YouTube/utils/cipher.ts index dcb6880..77296d6 100644 --- a/play-dl/YouTube/utils/cipher.ts +++ b/play-dl/YouTube/utils/cipher.ts @@ -1,5 +1,5 @@ import { URL } from 'url'; -import { request } from './request'; +import { request } from './../../Request'; import querystring from 'querystring'; interface formatOptions { diff --git a/play-dl/YouTube/utils/extractor.ts b/play-dl/YouTube/utils/extractor.ts index 6078620..2f3a8c4 100644 --- a/play-dl/YouTube/utils/extractor.ts +++ b/play-dl/YouTube/utils/extractor.ts @@ -1,4 +1,4 @@ -import { Proxy, request } from './request'; +import { ProxyOptions as Proxy, request } from './../../Request/index'; import { format_decipher } from './cipher'; import { YouTubeVideo } from '../classes/Video'; import { YouTubePlayList } from '../classes/Playlist'; @@ -76,7 +76,7 @@ export async function video_basic_info(url: string, options: InfoOptions = {}) { let video_id: string = extractID(url); const new_url = `https://www.youtube.com/watch?v=${video_id}&has_verified=1`; const body = await request(new_url, { - proxies: options.proxy ?? undefined, + proxies: options.proxy ?? [], headers: { 'accept-language': 'en-US,en-IN;q=0.9,en;q=0.8,hi;q=0.7' }, cookies: true }); diff --git a/play-dl/YouTube/utils/request.ts b/play-dl/YouTube/utils/request.ts deleted file mode 100644 index 1b930b2..0000000 --- a/play-dl/YouTube/utils/request.ts +++ /dev/null @@ -1,249 +0,0 @@ -import https, { RequestOptions } from 'https'; -import tls from 'tls'; -import http, { ClientRequest, IncomingMessage } from 'http'; -import { URL } from 'url'; -import { getCookies, setCookie, uploadCookie } from './cookie'; -/** - * Types for Proxy - */ -export type Proxy = ProxyOpts | string; - -interface ProxyOpts { - host: string; - port: number; - authentication?: { - username: string; - password: string; - }; -} - -interface ProxyOutput { - statusCode: number; - head: string[]; - body: string; -} - -interface RequestOpts extends RequestOptions { - body?: string; - method?: 'GET' | 'POST'; - proxies?: Proxy[]; - cookies?: boolean; -} -/** - * Main module that play-dl uses for making a https request - * @param req_url URL to make https request to - * @param options Request options for https request - * @returns Incoming Message from the https request - */ -function https_getter(req_url: string, options: RequestOpts = {}): Promise { - return new Promise((resolve, reject) => { - const s = new URL(req_url); - options.method ??= 'GET'; - const req_options: RequestOptions = { - host: s.hostname, - path: s.pathname + s.search, - headers: options.headers ?? {}, - method: options.method - }; - - const req = https.request(req_options, resolve); - req.on('error', (err) => { - reject(err); - }); - if (options.method === 'POST') req.write(options.body); - req.end(); - }); -} -/** - * Chooses one random number between max and min number. - * @param min Minimum number - * @param max Maximum number - * @returns Random Number - */ -function randomIntFromInterval(min: number, max: number): number { - let x = Math.floor(Math.random() * (max - min + 1) + min); - if (x === 0) return 0; - else return x - 1; -} -/** - * Main module that play-dl uses for proxy. - * @param req_url URL to make https request to - * @param req_proxy Proxies array - * @returns Object with statusCode, head and body - */ -async function proxy_getter(req_url: string, req_proxy: Proxy[]): Promise { - return new Promise((resolve, reject) => { - const proxy: string | ProxyOpts = req_proxy[randomIntFromInterval(0, req_proxy.length)]; - const parsed_url = new URL(req_url); - let opts: ProxyOpts; - if (typeof proxy === 'string') { - const parsed = new URL(proxy); - opts = { - host: parsed.hostname, - port: Number(parsed.port), - authentication: { - username: parsed.username, - password: parsed.password - } - }; - } else opts = proxy; - let req: ClientRequest; - if (opts.authentication?.username.length === 0) { - req = http.request({ - host: opts.host, - port: opts.port, - method: 'CONNECT', - path: `${parsed_url.host}:443` - }); - } else { - req = http.request({ - host: opts.host, - port: opts.port, - method: 'CONNECT', - path: `${parsed_url.host}:443`, - headers: { - 'Proxy-Authorization': `Basic ${Buffer.from( - `${opts.authentication?.username}:${opts.authentication?.password}` - ).toString('base64')}` - } - }); - } - - req.on('connect', function (res, socket, head) { - const tlsConnection = tls.connect( - { - host: parsed_url.hostname, - port: 443, - socket: socket, - rejectUnauthorized: false - }, - function () { - tlsConnection.write( - `GET ${parsed_url.pathname}${parsed_url.search} HTTP/1.1\r\n` + - `Host : ${parsed_url.hostname}\r\n` + - 'Connection: close\r\n' + - '\r\n' - ); - } - ); - - tlsConnection.setEncoding('utf-8'); - let data = ''; - tlsConnection.once('error', (e) => reject(e)); - tlsConnection.on('data', (c) => (data += c)); - tlsConnection.on('end', () => { - const y = data.split('\r\n\r\n'); - const head = y.shift() as string; - resolve({ - statusCode: Number(head.split('\n')[0].split(' ')[1]), - head: head.split('\r\n'), - body: y.join('\n') - }); - }); - }); - req.on('error', (e: Error) => reject(e)); - req.end(); - }); -} -/** - * Main module which play-dl uses to make a proxy or normal request - * @param url URL to make https request to - * @param options Request options for https request - * @returns body of that request - */ -export async function request(url: string, options: RequestOpts = {}): Promise { - return new Promise(async (resolve, reject) => { - if (!options?.proxies || options.proxies.length === 0) { - let data = ''; - let cookies_added = false; - if (options.cookies) { - let cook = getCookies(); - if (typeof cook === 'string' && options.headers) { - Object.assign(options.headers, { cookie: cook }); - cookies_added = true; - } - } - let res = await https_getter(url, options).catch((err: Error) => err); - if (res instanceof Error) { - reject(res); - return; - } - if (res.headers && res.headers['set-cookie'] && cookies_added) { - res.headers['set-cookie'].forEach((x) => { - x.split(';').forEach((x) => { - const arr = x.split('='); - if (arr.length <= 1) return; - const key = arr.shift()?.trim() as string; - const value = arr.join('=').trim(); - setCookie(key, value); - }); - }); - uploadCookie(); - } - if (Number(res.statusCode) >= 300 && Number(res.statusCode) < 400) { - res = await https_getter(res.headers.location as string, options); - } else if (Number(res.statusCode) > 400) { - reject(new Error(`Got ${res.statusCode} from the request`)); - } - res.setEncoding('utf-8'); - res.on('data', (c) => (data += c)); - res.on('end', () => resolve(data)); - } else { - let cookies_added = false; - if (options.cookies) { - let cook = getCookies(); - if (typeof cook === 'string' && options.headers) { - Object.assign(options.headers, { cookie: cook }); - cookies_added = true; - } - } - let res = await proxy_getter(url, options.proxies).catch((e: Error) => e); - if (res instanceof Error) { - reject(res); - return; - } - if (res.head && cookies_added) { - let cookies = res.head.filter((x) => x.toLocaleLowerCase().startsWith('set-cookie: ')); - cookies.forEach((x) => { - x.toLocaleLowerCase() - .split('set-cookie: ')[1] - .split(';') - .forEach((y) => { - const arr = y.split('='); - if (arr.length <= 1) return; - const key = arr.shift()?.trim() as string; - const value = arr.join('=').trim(); - setCookie(key, value); - }); - }); - uploadCookie(); - } - if (res.statusCode >= 300 && res.statusCode < 400) { - let url = res.head.filter((x) => x.startsWith('Location: ')); - res = await proxy_getter(url[0].split('\n')[0], options.proxies); - } else if (res.statusCode > 400) { - reject(new Error(`GOT ${res.statusCode} from proxy request`)); - } - resolve(res.body); - } - }); -} -/** - * Main module which play-dl uses to make a request to stream url. - * @param url URL to make https request to - * @param options Request options for https request - * @returns IncomingMessage from the request - */ -export async function request_stream(url: string, options?: RequestOpts): Promise { - return new Promise(async (resolve, reject) => { - let res = await https_getter(url, options).catch((err: Error) => err); - if (res instanceof Error) { - reject(res); - return; - } - if (Number(res.statusCode) >= 300 && Number(res.statusCode) < 400) { - res = await https_getter(res.headers.location as string, options); - } - resolve(res); - }); -} From 229bbc7a11d4a58840d37dbe0e5678f6fa37d37e Mon Sep 17 00:00:00 2001 From: killer069 <65385476+killer069@users.noreply.github.com> Date: Tue, 12 Oct 2021 13:56:33 +0530 Subject: [PATCH 5/6] SetToken function added --- play-dl/SoundCloud/index.ts | 4 + play-dl/Spotify/index.ts | 25 ++++- play-dl/YouTube/utils/cookie.ts | 18 +++- play-dl/index.ts | 169 ++++++++++++++++++-------------- play-dl/token.ts | 24 +++++ 5 files changed, 160 insertions(+), 80 deletions(-) create mode 100644 play-dl/token.ts diff --git a/play-dl/SoundCloud/index.ts b/play-dl/SoundCloud/index.ts index d21d9eb..13312fe 100644 --- a/play-dl/SoundCloud/index.ts +++ b/play-dl/SoundCloud/index.ts @@ -151,3 +151,7 @@ function parseHlsFormats(data: SoundCloudTrackFormat[]) { }); return result; } + +export function setSoundCloudToken(options : SoundDataOptions){ + soundData = options +} \ No newline at end of file diff --git a/play-dl/Spotify/index.ts b/play-dl/Spotify/index.ts index 2297848..297e249 100644 --- a/play-dl/Spotify/index.ts +++ b/play-dl/Spotify/index.ts @@ -5,6 +5,7 @@ import fs from 'fs'; let spotifyData: SpotifyDataOptions; if (fs.existsSync('.data/spotify.data')) { spotifyData = JSON.parse(fs.readFileSync('.data/spotify.data').toString()); + spotifyData.file = true } /** * Spotify Data options that are stored in spotify.data file. @@ -12,7 +13,7 @@ if (fs.existsSync('.data/spotify.data')) { export interface SpotifyDataOptions { client_id: string; client_secret: string; - redirect_url: string; + redirect_url?: string; authorization_code?: string; access_token?: string; refresh_token?: string; @@ -20,6 +21,7 @@ export interface SpotifyDataOptions { expires_in?: number; expiry?: number; market?: string; + file? : boolean } const pattern = /^((https:)?\/\/)?open.spotify.com\/(track|album|playlist)\//; @@ -89,14 +91,14 @@ export function sp_validate(url: string): 'track' | 'playlist' | 'album' | 'sear * @param data Sportify Data options to validate * @returns boolean. */ -export async function SpotifyAuthorize(data: SpotifyDataOptions): Promise { +export async function SpotifyAuthorize(data: SpotifyDataOptions, file : boolean): Promise { const response = await request(`https://accounts.spotify.com/api/token`, { headers: { 'Authorization': `Basic ${Buffer.from(`${data.client_id}:${data.client_secret}`).toString('base64')}`, 'Content-Type': 'application/x-www-form-urlencoded' }, body: `grant_type=authorization_code&code=${data.authorization_code}&redirect_uri=${encodeURI( - data.redirect_url + data.redirect_url as string )}`, method: 'POST' }).catch((err: Error) => { @@ -115,7 +117,14 @@ export async function SpotifyAuthorize(data: SpotifyDataOptions): Promise { spotifyData.expires_in = Number(resp_json.expires_in); spotifyData.expiry = Date.now() + (resp_json.expires_in - 1) * 1000; spotifyData.token_type = resp_json.token_type; - fs.writeFileSync('.data/spotify.data', JSON.stringify(spotifyData, undefined, 4)); + if(spotifyData.file) fs.writeFileSync('.data/spotify.data', JSON.stringify(spotifyData, undefined, 4)); return true; } + +export function setSpotifyToken(options: SpotifyDataOptions) { + spotifyData = options + spotifyData.file = false + refreshToken() +} \ No newline at end of file diff --git a/play-dl/YouTube/utils/cookie.ts b/play-dl/YouTube/utils/cookie.ts index e43293a..835f1bf 100644 --- a/play-dl/YouTube/utils/cookie.ts +++ b/play-dl/YouTube/utils/cookie.ts @@ -3,10 +3,12 @@ import fs from 'fs'; let youtubeData: youtubeDataOptions; if (fs.existsSync('.data/youtube.data')) { youtubeData = JSON.parse(fs.readFileSync('.data/youtube.data').toString()); + youtubeData.file = true } interface youtubeDataOptions { cookie?: Object; + file? : boolean; } export function getCookies(): undefined | string { @@ -27,5 +29,19 @@ export function setCookie(key: string, value: string): boolean { } export function uploadCookie() { - if (youtubeData) fs.writeFileSync('.data/youtube.data', JSON.stringify(youtubeData, undefined, 4)); + if (youtubeData.cookie && youtubeData.file) fs.writeFileSync('.data/youtube.data', JSON.stringify(youtubeData, undefined, 4)); } + +export function setCookieToken(options : { cookie : string }){ + let cook = options.cookie + 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 }); + }); + youtubeData = { cookie } + youtubeData.file = false +} \ No newline at end of file diff --git a/play-dl/index.ts b/play-dl/index.ts index ad9fa55..19a4a59 100644 --- a/play-dl/index.ts +++ b/play-dl/index.ts @@ -1,6 +1,7 @@ export { playlist_info, video_basic_info, video_info, yt_validate, extractID, YouTube, YouTubeStream } from './YouTube'; export { spotify, sp_validate, refreshToken, is_expired, Spotify } from './Spotify'; export { soundcloud, so_validate, SoundCloud, SoundCloudStream } from './SoundCloud'; +export { setToken } from './token' enum AudioPlayerStatus { Idle = 'idle', @@ -114,92 +115,112 @@ export function authorization(): void { input: process.stdin, output: process.stdout }); - 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 { + 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( - "That doesn't look like a valid region code, IN will be selected as default." + '\nNow open your browser and paste the below url, then authorize it and copy the redirected url. \n' ); - 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); - if (check === false) throw new Error('Failed to get access token.'); - ask.close(); + 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')) { - ask.question('Client ID : ', async (id) => { - let client_id = id; - if (!client_id) { - console.log("You didn't provide a client ID. Try again..."); + } 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; } - 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')) { - ask.question('Cookies : ', (cook: string) => { - if (!cook || cook.length === 0) { - console.log("You didn't provide a cookie. Try again..."); + 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(); - 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)); + } 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(); - }); - } else { - console.log("That option doesn't exist. Try again..."); - ask.close(); - } - }); + } + }); + }) } export function attachListeners(player: EventEmitter, resource: YouTubeStream | SoundCloudStream) { diff --git a/play-dl/token.ts b/play-dl/token.ts new file mode 100644 index 0000000..0d56169 --- /dev/null +++ b/play-dl/token.ts @@ -0,0 +1,24 @@ +import { setSoundCloudToken } from "./SoundCloud"; +import { setSpotifyToken } from "./Spotify"; +import { setCookieToken } from "./YouTube/utils/cookie"; + +interface tokenOptions { + spotify? : { + client_id : string + client_secret : string; + refresh_token : string + market : string + } + soundcloud? : { + client_id : string + } + youtube? : { + cookie : string + } +} + +export function setToken(options : tokenOptions){ + if(options.spotify) setSpotifyToken(options.spotify) + if(options.soundcloud) setSoundCloudToken(options.soundcloud) + if(options.youtube) setCookieToken(options.youtube) +} \ No newline at end of file From bd4afbe82a98ad70e190a7c4c213d3f55eb3f692 Mon Sep 17 00:00:00 2001 From: killer069 <65385476+killer069@users.noreply.github.com> Date: Tue, 12 Oct 2021 14:09:14 +0530 Subject: [PATCH 6/6] Docs + Pretty code --- docs/README.md | 27 ++++++++++++++++++ play-dl/Request/classes.ts | 12 ++++---- play-dl/Request/index.ts | 31 ++++++++++----------- play-dl/SoundCloud/classes.ts | 6 ++-- play-dl/SoundCloud/index.ts | 6 ++-- play-dl/Spotify/index.ts | 28 +++++++++---------- play-dl/YouTube/classes/LiveStream.ts | 12 ++++---- play-dl/YouTube/utils/cookie.ts | 17 ++++++------ play-dl/YouTube/utils/extractor.ts | 6 ++-- play-dl/index.ts | 22 +++++++-------- play-dl/token.ts | 40 +++++++++++++-------------- 11 files changed, 118 insertions(+), 89 deletions(-) diff --git a/docs/README.md b/docs/README.md index 75dd14b..cc6aa41 100644 --- a/docs/README.md +++ b/docs/README.md @@ -42,6 +42,33 @@ _This creates basic spotify / soundcloud / youtube data to be stored locally._ authorization() //After then you will be asked about type of data you want to create and then follow the steps properly. ``` +### setToken(options : `TokenOptions`) + +_This sets token without using file._ + +```js +setToken({ + spotify : { + client_id : "ID" + client_secret : "Secret" + refresh_token : "Token" + market : "Country Code" + } +}) // Setting Spotify Token [ To get refresh_token, just run through authorization, and set file save to No ] + +setToken({ + soundcloud : { + client_id : "ID" + } +}) // Setting SoundCloud Token + +setToken({ + youtube : { + cookie : "Cookies" + } +}) // Warning : Using setToken for youtube cookies will only update cookies present in memory only. +``` + ### Search #### SearchOptions : diff --git a/play-dl/Request/classes.ts b/play-dl/Request/classes.ts index b7b0921..5644319 100644 --- a/play-dl/Request/classes.ts +++ b/play-dl/Request/classes.ts @@ -15,7 +15,7 @@ export class Proxy { socket: TLSSocket; sentHeaders: string; private options: ProxyOptions; - constructor(parsed_url: URL, options: ProxyOptions){ + constructor(parsed_url: URL, options: ProxyOptions) { this.parsed_url = parsed_url; this.sentHeaders = ''; this.statusCode = 0; @@ -42,10 +42,10 @@ export class Proxy { private onConnect() { this.socket.write( `${this.options.method} ${this.parsed_url.pathname}${this.parsed_url.search} HTTP/1.1\r\n` + - `Host : ${this.parsed_url.hostname}\r\n` + - this.sentHeaders + - `Connection: close\r\n` + - `\r\n` + `Host : ${this.parsed_url.hostname}\r\n` + + this.sentHeaders + + `Connection: close\r\n` + + `\r\n` ); } @@ -84,4 +84,4 @@ export class Proxy { }); }); } -} \ No newline at end of file +} diff --git a/play-dl/Request/index.ts b/play-dl/Request/index.ts index b6d753b..0bb612d 100644 --- a/play-dl/Request/index.ts +++ b/play-dl/Request/index.ts @@ -1,12 +1,12 @@ import http, { ClientRequest, IncomingMessage } from 'http'; -import https ,{ RequestOptions } from 'https'; +import https, { RequestOptions } from 'https'; import { URL } from 'url'; import { getCookies, setCookie, uploadCookie } from '../YouTube/utils/cookie'; import { Proxy } from './classes'; export type ProxyOptions = ProxyOpts | string; -interface RequestOpts extends RequestOptions{ +interface RequestOpts extends RequestOptions { body?: string; method?: 'GET' | 'POST'; proxies?: ProxyOptions[]; @@ -83,8 +83,7 @@ export function request(req_url: string, options: RequestOpts = { method: 'GET' res.setEncoding('utf-8'); res.on('data', (c) => (data += c)); res.on('end', () => resolve(data)); - } - else { + } else { let cookies_added = false; if (options.cookies) { let cook = getCookies(); @@ -115,7 +114,7 @@ export function request(req_url: string, options: RequestOpts = { method: 'GET' } else if (res.statusCode > 400) { reject(new Error(`GOT ${res.statusCode} from proxy request`)); } - resolve(res.body) + resolve(res.body); } }); } @@ -126,7 +125,7 @@ export function request(req_url: string, options: RequestOpts = { method: 'GET' * @param max Maximum number * @returns Random Number */ - function randomIntFromInterval(min: number, max: number): number { +function randomIntFromInterval(min: number, max: number): number { let x = Math.floor(Math.random() * (max - min + 1) + min); if (x === 0) return 0; else return x - 1; @@ -137,10 +136,10 @@ export function request(req_url: string, options: RequestOpts = { method: 'GET' * @param req_proxy Proxies array * @returns Object with statusCode, head and body */ -function proxy_getter(req_url : string, req_proxy : ProxyOptions[], headers? : Object): Promise{ +function proxy_getter(req_url: string, req_proxy: ProxyOptions[], headers?: Object): Promise { return new Promise((resolve, reject) => { const proxy: string | ProxyOpts = req_proxy[randomIntFromInterval(0, req_proxy.length)]; - const parsed_url = new URL(req_url) + const parsed_url = new URL(req_url); let opts: ProxyOpts; if (typeof proxy === 'string') { const parsed = new URL(proxy); @@ -175,14 +174,14 @@ function proxy_getter(req_url : string, req_proxy : ProxyOptions[], headers? : O }); } req.on('connect', async function (res, socket) { - const conn_proxy = new Proxy(parsed_url, { method : "GET", socket : socket, headers : headers }) - await conn_proxy.fetch() - socket.end() - resolve(conn_proxy) - }) + const conn_proxy = new Proxy(parsed_url, { method: 'GET', socket: socket, headers: headers }); + await conn_proxy.fetch(); + socket.end(); + resolve(conn_proxy); + }); req.on('error', (e: Error) => reject(e)); req.end(); - }) + }); } /** @@ -191,7 +190,7 @@ function proxy_getter(req_url : string, req_proxy : ProxyOptions[], headers? : O * @param options Request options for https request * @returns Incoming Message from the https request */ - function https_getter(req_url: string, options: RequestOpts = {}): Promise { +function https_getter(req_url: string, options: RequestOpts = {}): Promise { return new Promise((resolve, reject) => { const s = new URL(req_url); options.method ??= 'GET'; @@ -209,4 +208,4 @@ function proxy_getter(req_url : string, req_proxy : ProxyOptions[], headers? : O if (options.method === 'POST') req.write(options.body); req.end(); }); -} \ No newline at end of file +} diff --git a/play-dl/SoundCloud/classes.ts b/play-dl/SoundCloud/classes.ts index faca32c..3356957 100644 --- a/play-dl/SoundCloud/classes.ts +++ b/play-dl/SoundCloud/classes.ts @@ -212,7 +212,7 @@ export class Stream { private time: number[]; private segment_urls: string[]; constructor(url: string, type: StreamType = StreamType.Arbitrary) { - this.stream = new Readable({ highWaterMark: 10 * 1000 * 1000, read(){} }); + this.stream = new Readable({ highWaterMark: 10 * 1000 * 1000, read() {} }); this.type = type; this.url = url; this.downloaded_time = 0; @@ -275,8 +275,8 @@ export class Stream { this.request = stream; stream.on('data', (c) => { - this.stream.push(c) - }) + this.stream.push(c); + }); stream.on('end', () => { if (this.downloaded_time >= 300) return; else this.loop(); diff --git a/play-dl/SoundCloud/index.ts b/play-dl/SoundCloud/index.ts index 13312fe..9c37e49 100644 --- a/play-dl/SoundCloud/index.ts +++ b/play-dl/SoundCloud/index.ts @@ -152,6 +152,6 @@ function parseHlsFormats(data: SoundCloudTrackFormat[]) { return result; } -export function setSoundCloudToken(options : SoundDataOptions){ - soundData = options -} \ No newline at end of file +export function setSoundCloudToken(options: SoundDataOptions) { + soundData = options; +} diff --git a/play-dl/Spotify/index.ts b/play-dl/Spotify/index.ts index 297e249..39d5e3d 100644 --- a/play-dl/Spotify/index.ts +++ b/play-dl/Spotify/index.ts @@ -5,7 +5,7 @@ import fs from 'fs'; let spotifyData: SpotifyDataOptions; if (fs.existsSync('.data/spotify.data')) { spotifyData = JSON.parse(fs.readFileSync('.data/spotify.data').toString()); - spotifyData.file = true + spotifyData.file = true; } /** * Spotify Data options that are stored in spotify.data file. @@ -21,7 +21,7 @@ export interface SpotifyDataOptions { expires_in?: number; expiry?: number; market?: string; - file? : boolean + file?: boolean; } const pattern = /^((https:)?\/\/)?open.spotify.com\/(track|album|playlist)\//; @@ -91,7 +91,7 @@ export function sp_validate(url: string): 'track' | 'playlist' | 'album' | 'sear * @param data Sportify Data options to validate * @returns boolean. */ -export async function SpotifyAuthorize(data: SpotifyDataOptions, file : boolean): Promise { +export async function SpotifyAuthorize(data: SpotifyDataOptions, file: boolean): Promise { const response = await request(`https://accounts.spotify.com/api/token`, { headers: { 'Authorization': `Basic ${Buffer.from(`${data.client_id}:${data.client_secret}`).toString('base64')}`, @@ -117,13 +117,13 @@ export async function SpotifyAuthorize(data: SpotifyDataOptions, file : boolean) token_type: resp_json.token_type, market: data.market }; - if(file) fs.writeFileSync('.data/spotify.data', JSON.stringify(spotifyData, undefined, 4)); + if (file) fs.writeFileSync('.data/spotify.data', JSON.stringify(spotifyData, undefined, 4)); else { - console.log(`Client ID : ${spotifyData.client_id}`) - console.log(`Client Secret : ${spotifyData.client_secret}`) - console.log(`Refresh Token : ${spotifyData.refresh_token}`) - console.log(`Market : ${spotifyData.market}`) - console.log(`\nPaste above info in setToken function.`) + console.log(`Client ID : ${spotifyData.client_id}`); + console.log(`Client Secret : ${spotifyData.client_secret}`); + console.log(`Refresh Token : ${spotifyData.refresh_token}`); + console.log(`Market : ${spotifyData.market}`); + console.log(`\nPaste above info in setToken function.`); } return true; } @@ -207,12 +207,12 @@ export async function refreshToken(): Promise { spotifyData.expires_in = Number(resp_json.expires_in); spotifyData.expiry = Date.now() + (resp_json.expires_in - 1) * 1000; spotifyData.token_type = resp_json.token_type; - if(spotifyData.file) fs.writeFileSync('.data/spotify.data', JSON.stringify(spotifyData, undefined, 4)); + if (spotifyData.file) fs.writeFileSync('.data/spotify.data', JSON.stringify(spotifyData, undefined, 4)); return true; } export function setSpotifyToken(options: SpotifyDataOptions) { - spotifyData = options - spotifyData.file = false - refreshToken() -} \ No newline at end of file + spotifyData = options; + spotifyData.file = false; + refreshToken(); +} diff --git a/play-dl/YouTube/classes/LiveStream.ts b/play-dl/YouTube/classes/LiveStream.ts index d21f6cd..ae63860 100644 --- a/play-dl/YouTube/classes/LiveStream.ts +++ b/play-dl/YouTube/classes/LiveStream.ts @@ -23,7 +23,7 @@ export class LiveStreaming { private segments_urls: string[]; private request: IncomingMessage | null; constructor(dash_url: string, target_interval: number, video_url: string) { - this.stream = new Readable({ highWaterMark: 10 * 1000 * 1000, read(){} }); + this.stream = new Readable({ highWaterMark: 10 * 1000 * 1000, read() {} }); this.type = StreamType.Arbitrary; this.url = dash_url; this.base_url = ''; @@ -102,8 +102,8 @@ export class LiveStreaming { } this.request = stream; stream.on('data', (c) => { - this.stream.push(c) - }) + this.stream.push(c); + }); stream.on('end', () => { this.packet_count++; resolve(''); @@ -143,7 +143,7 @@ export class Stream { video_url: string, options: StreamOptions ) { - this.stream = new Readable({ highWaterMark : 10 * 1000 * 1000, read(){} }); + this.stream = new Readable({ highWaterMark: 10 * 1000 * 1000, read() {} }); this.url = url; this.quality = options.quality as number; this.proxy = options.proxy || undefined; @@ -204,8 +204,8 @@ export class Stream { } this.request = stream; stream.on('data', (c) => { - this.stream.push(c) - }) + this.stream.push(c); + }); stream.once('error', async (err) => { this.cleanup(); diff --git a/play-dl/YouTube/utils/cookie.ts b/play-dl/YouTube/utils/cookie.ts index 835f1bf..82f5cf4 100644 --- a/play-dl/YouTube/utils/cookie.ts +++ b/play-dl/YouTube/utils/cookie.ts @@ -3,12 +3,12 @@ import fs from 'fs'; let youtubeData: youtubeDataOptions; if (fs.existsSync('.data/youtube.data')) { youtubeData = JSON.parse(fs.readFileSync('.data/youtube.data').toString()); - youtubeData.file = true + youtubeData.file = true; } interface youtubeDataOptions { cookie?: Object; - file? : boolean; + file?: boolean; } export function getCookies(): undefined | string { @@ -29,11 +29,12 @@ export function setCookie(key: string, value: string): boolean { } export function uploadCookie() { - if (youtubeData.cookie && youtubeData.file) fs.writeFileSync('.data/youtube.data', JSON.stringify(youtubeData, undefined, 4)); + if (youtubeData.cookie && youtubeData.file) + fs.writeFileSync('.data/youtube.data', JSON.stringify(youtubeData, undefined, 4)); } -export function setCookieToken(options : { cookie : string }){ - let cook = options.cookie +export function setCookieToken(options: { cookie: string }) { + let cook = options.cookie; let cookie: Object = {}; cook.split(';').forEach((x) => { const arr = x.split('='); @@ -42,6 +43,6 @@ export function setCookieToken(options : { cookie : string }){ const value = arr.join('=').trim(); Object.assign(cookie, { [key]: value }); }); - youtubeData = { cookie } - youtubeData.file = false -} \ No newline at end of file + youtubeData = { cookie }; + youtubeData.file = false; +} diff --git a/play-dl/YouTube/utils/extractor.ts b/play-dl/YouTube/utils/extractor.ts index 2f3a8c4..8a010af 100644 --- a/play-dl/YouTube/utils/extractor.ts +++ b/play-dl/YouTube/utils/extractor.ts @@ -30,7 +30,8 @@ export function yt_validate(url: string): 'playlist' | 'video' | 'search' | fals if (url.match(video_pattern)) { let id: string; if (url.includes('youtu.be/')) id = url.split('youtu.be/')[1].split(/(\?|\/|&)/)[0]; - else if (url.includes('youtube.com/embed/')) id = url.split('youtube.com/embed/')[1].split(/(\?|\/|&)/)[0]; + else if (url.includes('youtube.com/embed/')) + id = url.split('youtube.com/embed/')[1].split(/(\?|\/|&)/)[0]; else id = url.split('watch?v=')[1].split(/(\?|\/|&)/)[0]; if (id.match(video_id_pattern)) return 'video'; else return false; @@ -57,7 +58,8 @@ export function extractID(url: string): string { if (url.indexOf('list=') === -1) { let video_id: string; if (url.includes('youtu.be/')) video_id = url.split('youtu.be/')[1].split(/(\?|\/|&)/)[0]; - else if (url.includes('youtube.com/embed/')) video_id = url.split('youtube.com/embed/')[1].split(/(\?|\/|&)/)[0]; + else if (url.includes('youtube.com/embed/')) + video_id = url.split('youtube.com/embed/')[1].split(/(\?|\/|&)/)[0]; else video_id = url.split('watch?v=')[1].split(/(\?|\/|&)/)[0]; return video_id; } else { diff --git a/play-dl/index.ts b/play-dl/index.ts index 19a4a59..5ef77de 100644 --- a/play-dl/index.ts +++ b/play-dl/index.ts @@ -1,7 +1,7 @@ export { playlist_info, video_basic_info, video_info, yt_validate, extractID, YouTube, YouTubeStream } from './YouTube'; export { spotify, sp_validate, refreshToken, is_expired, Spotify } from './Spotify'; export { soundcloud, so_validate, SoundCloud, SoundCloudStream } from './SoundCloud'; -export { setToken } from './token' +export { setToken } from './token'; enum AudioPlayerStatus { Idle = 'idle', @@ -116,9 +116,9 @@ export function authorization(): void { 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 + 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(); @@ -170,8 +170,8 @@ export function authorization(): void { }); }); } else if (msg.toLowerCase().startsWith('sc')) { - if(!file){ - console.log("You already had a client ID, just paste that in setToken function."); + if (!file) { + console.log('You already had a client ID, just paste that in setToken function.'); ask.close(); return; } @@ -191,8 +191,8 @@ export function authorization(): void { ask.close(); }); } else if (msg.toLowerCase().startsWith('yo')) { - if(!file){ - console.log("You already had cookie, just paste that in setToken function."); + if (!file) { + console.log('You already had cookie, just paste that in setToken function.'); ask.close(); return; } @@ -220,12 +220,12 @@ export function authorization(): void { ask.close(); } }); - }) + }); } export function attachListeners(player: EventEmitter, resource: YouTubeStream | SoundCloudStream) { - const pauseListener = () => resource.pause() - const resumeListener = () => resource.resume() + const pauseListener = () => resource.pause(); + const resumeListener = () => resource.resume(); player.on(AudioPlayerStatus.Paused, pauseListener); player.on(AudioPlayerStatus.AutoPaused, pauseListener); player.on(AudioPlayerStatus.Playing, resumeListener); diff --git a/play-dl/token.ts b/play-dl/token.ts index 0d56169..17237b9 100644 --- a/play-dl/token.ts +++ b/play-dl/token.ts @@ -1,24 +1,24 @@ -import { setSoundCloudToken } from "./SoundCloud"; -import { setSpotifyToken } from "./Spotify"; -import { setCookieToken } from "./YouTube/utils/cookie"; +import { setSoundCloudToken } from './SoundCloud'; +import { setSpotifyToken } from './Spotify'; +import { setCookieToken } from './YouTube/utils/cookie'; interface tokenOptions { - spotify? : { - client_id : string - client_secret : string; - refresh_token : string - market : string - } - soundcloud? : { - client_id : string - } - youtube? : { - cookie : string - } + spotify?: { + client_id: string; + client_secret: string; + refresh_token: string; + market: string; + }; + soundcloud?: { + client_id: string; + }; + youtube?: { + cookie: string; + }; } -export function setToken(options : tokenOptions){ - if(options.spotify) setSpotifyToken(options.spotify) - if(options.soundcloud) setSoundCloudToken(options.soundcloud) - if(options.youtube) setCookieToken(options.youtube) -} \ No newline at end of file +export function setToken(options: tokenOptions) { + if (options.spotify) setSpotifyToken(options.spotify); + if (options.soundcloud) setSoundCloudToken(options.soundcloud); + if (options.youtube) setCookieToken(options.youtube); +}