mirror of
https://github.com/YuzuZensai/play-dl-test.git
synced 2026-01-31 14:58:05 +00:00
SoundCloud work + prettier
This commit is contained in:
@@ -34,19 +34,19 @@ export class Channel {
|
||||
* @param {object} options Icon options
|
||||
* @param {number} [options.size=0] Icon size. **Default is 0**
|
||||
*/
|
||||
iconURL(options = { size: 0 }): string | undefined{
|
||||
if (typeof options.size !== "number" || options.size < 0) throw new Error("invalid icon size");
|
||||
iconURL(options = { size: 0 }): string | undefined {
|
||||
if (typeof options.size !== 'number' || options.size < 0) throw new Error('invalid icon size');
|
||||
if (!this.icon?.url) return undefined;
|
||||
const def = this.icon.url.split("=s")[1].split("-c")[0];
|
||||
const def = this.icon.url.split('=s')[1].split('-c')[0];
|
||||
return this.icon.url.replace(`=s${def}-c`, `=s${options.size}-c`);
|
||||
}
|
||||
|
||||
get type(): "channel" {
|
||||
return "channel";
|
||||
get type(): 'channel' {
|
||||
return 'channel';
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this.name || "";
|
||||
return this.name || '';
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
@@ -60,4 +60,4 @@ export class Channel {
|
||||
subscribers: this.subscribers
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,222 +1,235 @@
|
||||
import { PassThrough } from 'stream'
|
||||
import { PassThrough } from 'stream';
|
||||
import { IncomingMessage } from 'http';
|
||||
import { StreamType } from '../stream';
|
||||
import { request, request_stream } from '../utils/request';
|
||||
import { video_info } from '..';
|
||||
|
||||
export interface FormatInterface{
|
||||
url : string;
|
||||
targetDurationSec : number;
|
||||
maxDvrDurationSec : number
|
||||
export interface FormatInterface {
|
||||
url: string;
|
||||
targetDurationSec: number;
|
||||
maxDvrDurationSec: number;
|
||||
}
|
||||
|
||||
export class LiveStreaming{
|
||||
type : StreamType
|
||||
stream : PassThrough
|
||||
private base_url : string
|
||||
private url : string
|
||||
private interval : number
|
||||
private packet_count : number
|
||||
private timer : NodeJS.Timer | null
|
||||
private video_url : string
|
||||
private dash_timer : NodeJS.Timer | null
|
||||
private segments_urls : string[]
|
||||
private request : IncomingMessage | null
|
||||
constructor(dash_url : string, target_interval : number, video_url : string){
|
||||
this.type = StreamType.Arbitrary
|
||||
this.url = dash_url
|
||||
this.base_url = ''
|
||||
this.stream = new PassThrough({ highWaterMark : 10 * 1000 * 1000 })
|
||||
this.segments_urls = []
|
||||
this.packet_count = 0
|
||||
this.request = null
|
||||
this.timer = null
|
||||
this.video_url = video_url
|
||||
this.interval = target_interval * 1000 || 0
|
||||
export class LiveStreaming {
|
||||
type: StreamType;
|
||||
stream: PassThrough;
|
||||
private base_url: string;
|
||||
private url: string;
|
||||
private interval: number;
|
||||
private packet_count: number;
|
||||
private timer: NodeJS.Timer | null;
|
||||
private video_url: string;
|
||||
private dash_timer: NodeJS.Timer | null;
|
||||
private segments_urls: string[];
|
||||
private request: IncomingMessage | null;
|
||||
constructor(dash_url: string, target_interval: number, video_url: string) {
|
||||
this.type = StreamType.Arbitrary;
|
||||
this.url = dash_url;
|
||||
this.base_url = '';
|
||||
this.stream = new PassThrough({ highWaterMark: 10 * 1000 * 1000 });
|
||||
this.segments_urls = [];
|
||||
this.packet_count = 0;
|
||||
this.request = null;
|
||||
this.timer = null;
|
||||
this.video_url = video_url;
|
||||
this.interval = target_interval * 1000 || 0;
|
||||
this.dash_timer = setTimeout(() => {
|
||||
this.dash_updater()
|
||||
}, 1800000)
|
||||
this.dash_updater();
|
||||
}, 1800000);
|
||||
this.stream.on('close', () => {
|
||||
this.cleanup()
|
||||
this.cleanup();
|
||||
});
|
||||
this.start()
|
||||
this.start();
|
||||
}
|
||||
|
||||
private async dash_updater(){
|
||||
let info = await video_info(this.video_url)
|
||||
if(info.LiveStreamData.isLive === true && info.LiveStreamData.hlsManifestUrl !== null && info.video_details.durationInSec === '0'){
|
||||
this.url = info.LiveStreamData.dashManifestUrl
|
||||
|
||||
private async dash_updater() {
|
||||
const info = await video_info(this.video_url);
|
||||
if (
|
||||
info.LiveStreamData.isLive === true &&
|
||||
info.LiveStreamData.hlsManifestUrl !== null &&
|
||||
info.video_details.durationInSec === '0'
|
||||
) {
|
||||
this.url = info.LiveStreamData.dashManifestUrl;
|
||||
}
|
||||
this.dash_timer = setTimeout(() => {
|
||||
this.dash_updater()
|
||||
}, 1800000)
|
||||
this.dash_updater();
|
||||
}, 1800000);
|
||||
}
|
||||
|
||||
private async dash_getter(){
|
||||
let response = await request(this.url)
|
||||
let audioFormat = response.split('<AdaptationSet id="0"')[1].split('</AdaptationSet>')[0].split('</Representation>')
|
||||
if(audioFormat[audioFormat.length - 1] === '') audioFormat.pop()
|
||||
this.base_url = audioFormat[audioFormat.length - 1].split('<BaseURL>')[1].split('</BaseURL>')[0]
|
||||
let list = audioFormat[audioFormat.length - 1].split('<SegmentList>')[1].split('</SegmentList>')[0]
|
||||
this.segments_urls = list.replace(new RegExp('<SegmentURL media="', 'g'), '').split('"/>')
|
||||
if(this.segments_urls[this.segments_urls.length - 1] === '') this.segments_urls.pop()
|
||||
private async dash_getter() {
|
||||
const response = await request(this.url);
|
||||
const audioFormat = response
|
||||
.split('<AdaptationSet id="0"')[1]
|
||||
.split('</AdaptationSet>')[0]
|
||||
.split('</Representation>');
|
||||
if (audioFormat[audioFormat.length - 1] === '') audioFormat.pop();
|
||||
this.base_url = audioFormat[audioFormat.length - 1].split('<BaseURL>')[1].split('</BaseURL>')[0];
|
||||
const list = audioFormat[audioFormat.length - 1].split('<SegmentList>')[1].split('</SegmentList>')[0];
|
||||
this.segments_urls = list.replace(new RegExp('<SegmentURL media="', 'g'), '').split('"/>');
|
||||
if (this.segments_urls[this.segments_urls.length - 1] === '') this.segments_urls.pop();
|
||||
}
|
||||
|
||||
private cleanup(){
|
||||
clearTimeout(this.timer as NodeJS.Timer)
|
||||
clearTimeout(this.dash_timer as NodeJS.Timer)
|
||||
this.request?.unpipe(this.stream)
|
||||
this.request?.destroy()
|
||||
this.dash_timer = null
|
||||
this.video_url = ''
|
||||
this.request = null
|
||||
this.timer = null
|
||||
this.url = ''
|
||||
this.base_url = ''
|
||||
this.segments_urls = []
|
||||
this.packet_count = 0
|
||||
this.interval = 0
|
||||
private cleanup() {
|
||||
clearTimeout(this.timer as NodeJS.Timer);
|
||||
clearTimeout(this.dash_timer as NodeJS.Timer);
|
||||
this.request?.unpipe(this.stream);
|
||||
this.request?.destroy();
|
||||
this.dash_timer = null;
|
||||
this.video_url = '';
|
||||
this.request = null;
|
||||
this.timer = null;
|
||||
this.url = '';
|
||||
this.base_url = '';
|
||||
this.segments_urls = [];
|
||||
this.packet_count = 0;
|
||||
this.interval = 0;
|
||||
}
|
||||
|
||||
private async start(){
|
||||
if(this.stream.destroyed){
|
||||
this.cleanup()
|
||||
return
|
||||
private async start() {
|
||||
if (this.stream.destroyed) {
|
||||
this.cleanup();
|
||||
return;
|
||||
}
|
||||
await this.dash_getter()
|
||||
if(this.segments_urls.length > 3) this.segments_urls.splice(0, this.segments_urls.length - 3)
|
||||
if(this.packet_count === 0) this.packet_count = Number(this.segments_urls[0].split('sq/')[1].split('/')[0])
|
||||
for await (let segment of this.segments_urls){
|
||||
if(Number(segment.split('sq/')[1].split('/')[0]) !== this.packet_count){
|
||||
continue
|
||||
await this.dash_getter();
|
||||
if (this.segments_urls.length > 3) this.segments_urls.splice(0, this.segments_urls.length - 3);
|
||||
if (this.packet_count === 0) this.packet_count = Number(this.segments_urls[0].split('sq/')[1].split('/')[0]);
|
||||
for await (const segment of this.segments_urls) {
|
||||
if (Number(segment.split('sq/')[1].split('/')[0]) !== this.packet_count) {
|
||||
continue;
|
||||
}
|
||||
await new Promise(async(resolve, reject) => {
|
||||
let stream = await request_stream(this.base_url + segment).catch((err: Error) => err)
|
||||
if(stream instanceof Error){
|
||||
this.stream.emit('error', stream)
|
||||
return
|
||||
await new Promise(async (resolve, reject) => {
|
||||
const stream = await request_stream(this.base_url + segment).catch((err: Error) => err);
|
||||
if (stream instanceof Error) {
|
||||
this.stream.emit('error', stream);
|
||||
return;
|
||||
}
|
||||
this.request = stream
|
||||
stream.pipe(this.stream, { end : false })
|
||||
this.request = stream;
|
||||
stream.pipe(this.stream, { end: false });
|
||||
stream.on('end', () => {
|
||||
this.packet_count++
|
||||
resolve('')
|
||||
})
|
||||
this.packet_count++;
|
||||
resolve('');
|
||||
});
|
||||
stream.once('error', (err) => {
|
||||
this.stream.emit('error', err)
|
||||
})
|
||||
})
|
||||
this.stream.emit('error', err);
|
||||
});
|
||||
});
|
||||
}
|
||||
this.timer = setTimeout(() => {
|
||||
this.start()
|
||||
}, this.interval)
|
||||
this.start();
|
||||
}, this.interval);
|
||||
}
|
||||
}
|
||||
|
||||
export class Stream {
|
||||
type : StreamType
|
||||
stream : PassThrough
|
||||
private url : string
|
||||
private bytes_count : number;
|
||||
private per_sec_bytes : number;
|
||||
private content_length : number;
|
||||
private video_url : string;
|
||||
private timer : NodeJS.Timer | null;
|
||||
private cookie : string;
|
||||
private data_ended : boolean;
|
||||
private playing_count : number;
|
||||
private request : IncomingMessage | null
|
||||
constructor(url : string, type : StreamType, duration : number, contentLength : number, video_url : string, cookie : string){
|
||||
this.url = url
|
||||
this.type = type
|
||||
this.stream = new PassThrough({ highWaterMark : 10 * 1000 * 1000 })
|
||||
this.bytes_count = 0
|
||||
this.video_url = video_url
|
||||
this.cookie = cookie
|
||||
type: StreamType;
|
||||
stream: PassThrough;
|
||||
private url: string;
|
||||
private bytes_count: number;
|
||||
private per_sec_bytes: number;
|
||||
private content_length: number;
|
||||
private video_url: string;
|
||||
private timer: NodeJS.Timer | null;
|
||||
private cookie: string;
|
||||
private data_ended: boolean;
|
||||
private playing_count: number;
|
||||
private request: IncomingMessage | null;
|
||||
constructor(
|
||||
url: string,
|
||||
type: StreamType,
|
||||
duration: number,
|
||||
contentLength: number,
|
||||
video_url: string,
|
||||
cookie: string
|
||||
) {
|
||||
this.url = url;
|
||||
this.type = type;
|
||||
this.stream = new PassThrough({ highWaterMark: 10 * 1000 * 1000 });
|
||||
this.bytes_count = 0;
|
||||
this.video_url = video_url;
|
||||
this.cookie = cookie;
|
||||
this.timer = setInterval(() => {
|
||||
this.retry()
|
||||
}, 7200 * 1000)
|
||||
this.per_sec_bytes = Math.ceil(contentLength / duration)
|
||||
this.content_length = contentLength
|
||||
this.request = null
|
||||
this.data_ended = false
|
||||
this.playing_count = 0
|
||||
this.retry();
|
||||
}, 7200 * 1000);
|
||||
this.per_sec_bytes = Math.ceil(contentLength / duration);
|
||||
this.content_length = contentLength;
|
||||
this.request = null;
|
||||
this.data_ended = false;
|
||||
this.playing_count = 0;
|
||||
this.stream.on('close', () => {
|
||||
this.cleanup()
|
||||
})
|
||||
this.cleanup();
|
||||
});
|
||||
this.stream.on('pause', () => {
|
||||
this.playing_count++;
|
||||
if(this.data_ended){
|
||||
this.bytes_count = 0
|
||||
this.per_sec_bytes = 0
|
||||
this.cleanup()
|
||||
this.stream.removeAllListeners('pause')
|
||||
if (this.data_ended) {
|
||||
this.bytes_count = 0;
|
||||
this.per_sec_bytes = 0;
|
||||
this.cleanup();
|
||||
this.stream.removeAllListeners('pause');
|
||||
} else if (this.playing_count === 280) {
|
||||
this.playing_count = 0;
|
||||
this.loop();
|
||||
}
|
||||
else if(this.playing_count === 280){
|
||||
this.playing_count = 0
|
||||
this.loop()
|
||||
}
|
||||
})
|
||||
this.loop()
|
||||
});
|
||||
this.loop();
|
||||
}
|
||||
|
||||
private async retry(){
|
||||
let info = await video_info(this.video_url, this.cookie)
|
||||
this.url = info.format[info.format.length - 1].url
|
||||
private async retry() {
|
||||
const info = await video_info(this.video_url, this.cookie);
|
||||
this.url = info.format[info.format.length - 1].url;
|
||||
}
|
||||
|
||||
private cleanup(){
|
||||
clearInterval(this.timer as NodeJS.Timer)
|
||||
this.request?.unpipe(this.stream)
|
||||
this.request?.destroy()
|
||||
this.timer = null
|
||||
this.request = null
|
||||
this.url = ''
|
||||
private cleanup() {
|
||||
clearInterval(this.timer as NodeJS.Timer);
|
||||
this.request?.unpipe(this.stream);
|
||||
this.request?.destroy();
|
||||
this.timer = null;
|
||||
this.request = null;
|
||||
this.url = '';
|
||||
}
|
||||
|
||||
private async loop(){
|
||||
if(this.stream.destroyed){
|
||||
this.cleanup()
|
||||
return
|
||||
private async loop() {
|
||||
if (this.stream.destroyed) {
|
||||
this.cleanup();
|
||||
return;
|
||||
}
|
||||
let end : number = this.bytes_count + this.per_sec_bytes * 300;
|
||||
let stream = await request_stream(this.url, {
|
||||
headers : {
|
||||
"range" : `bytes=${this.bytes_count}-${end >= this.content_length ? '' : end}`
|
||||
const end: number = this.bytes_count + this.per_sec_bytes * 300;
|
||||
const stream = await request_stream(this.url, {
|
||||
headers: {
|
||||
range: `bytes=${this.bytes_count}-${end >= this.content_length ? '' : end}`
|
||||
}
|
||||
}).catch((err: Error) => err)
|
||||
if(stream instanceof Error){
|
||||
this.stream.emit('error', stream)
|
||||
this.data_ended = true
|
||||
this.bytes_count = 0
|
||||
this.per_sec_bytes = 0
|
||||
this.cleanup()
|
||||
return
|
||||
}).catch((err: Error) => err);
|
||||
if (stream instanceof Error) {
|
||||
this.stream.emit('error', stream);
|
||||
this.data_ended = true;
|
||||
this.bytes_count = 0;
|
||||
this.per_sec_bytes = 0;
|
||||
this.cleanup();
|
||||
return;
|
||||
}
|
||||
if(Number(stream.statusCode) >= 400){
|
||||
this.cleanup()
|
||||
await this.retry()
|
||||
this.loop()
|
||||
if(!this.timer){
|
||||
if (Number(stream.statusCode) >= 400) {
|
||||
this.cleanup();
|
||||
await this.retry();
|
||||
this.loop();
|
||||
if (!this.timer) {
|
||||
this.timer = setInterval(() => {
|
||||
this.retry()
|
||||
}, 7200 * 1000)
|
||||
this.retry();
|
||||
}, 7200 * 1000);
|
||||
}
|
||||
return
|
||||
return;
|
||||
}
|
||||
this.request = stream
|
||||
stream.pipe(this.stream, { end : false })
|
||||
this.request = stream;
|
||||
stream.pipe(this.stream, { end: false });
|
||||
|
||||
stream.once('error', (err) => {
|
||||
this.stream.emit('error', err)
|
||||
})
|
||||
this.stream.emit('error', err);
|
||||
});
|
||||
|
||||
stream.on('data', (chunk: any) => {
|
||||
this.bytes_count += chunk.length
|
||||
})
|
||||
this.bytes_count += chunk.length;
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
if(end >= this.content_length) this.data_ended = true
|
||||
})
|
||||
if (end >= this.content_length) this.data_ended = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { getPlaylistVideos, getContinuationToken } from "../utils/extractor";
|
||||
import { request } from "../utils/request";
|
||||
import { Thumbnail } from "./Thumbnail";
|
||||
import { Channel } from "./Channel";
|
||||
import { Video } from "./Video";
|
||||
const BASE_API = "https://www.youtube.com/youtubei/v1/browse?key=";
|
||||
import { getPlaylistVideos, getContinuationToken } from '../utils/extractor';
|
||||
import { request } from '../utils/request';
|
||||
import { Thumbnail } from './Thumbnail';
|
||||
import { Channel } from './Channel';
|
||||
import { Video } from './Video';
|
||||
const BASE_API = 'https://www.youtube.com/youtubei/v1/browse?key=';
|
||||
|
||||
export class PlayList{
|
||||
export class PlayList {
|
||||
id?: string;
|
||||
title?: string;
|
||||
videoCount?: number;
|
||||
@@ -16,19 +16,23 @@ export class PlayList{
|
||||
channel?: Channel;
|
||||
thumbnail?: Thumbnail;
|
||||
private videos?: [];
|
||||
private fetched_videos : Map<string, Video[]>
|
||||
private _continuation: { api?: string; token?: string; clientVersion?: string } = {};
|
||||
private __count : number
|
||||
private fetched_videos: Map<string, Video[]>;
|
||||
private _continuation: {
|
||||
api?: string;
|
||||
token?: string;
|
||||
clientVersion?: string;
|
||||
} = {};
|
||||
private __count: number;
|
||||
|
||||
constructor(data : any, searchResult : Boolean = false){
|
||||
constructor(data: any, searchResult = false) {
|
||||
if (!data) throw new Error(`Cannot instantiate the ${this.constructor.name} class without data!`);
|
||||
this.__count = 0
|
||||
this.fetched_videos = new Map()
|
||||
if(searchResult) this.__patchSearch(data)
|
||||
else this.__patch(data)
|
||||
this.__count = 0;
|
||||
this.fetched_videos = new Map();
|
||||
if (searchResult) this.__patchSearch(data);
|
||||
else this.__patch(data);
|
||||
}
|
||||
|
||||
private __patch(data:any){
|
||||
private __patch(data: any) {
|
||||
this.id = data.id || undefined;
|
||||
this.url = data.url || undefined;
|
||||
this.title = data.title || undefined;
|
||||
@@ -39,14 +43,14 @@ export class PlayList{
|
||||
this.channel = data.author || undefined;
|
||||
this.thumbnail = data.thumbnail || undefined;
|
||||
this.videos = data.videos || [];
|
||||
this.__count ++
|
||||
this.fetched_videos.set(`page${this.__count}`, this.videos as Video[])
|
||||
this.__count++;
|
||||
this.fetched_videos.set(`page${this.__count}`, this.videos as Video[]);
|
||||
this._continuation.api = data.continuation?.api ?? undefined;
|
||||
this._continuation.token = data.continuation?.token ?? undefined;
|
||||
this._continuation.clientVersion = data.continuation?.clientVersion ?? "<important data>";
|
||||
this._continuation.clientVersion = data.continuation?.clientVersion ?? '<important data>';
|
||||
}
|
||||
|
||||
private __patchSearch(data: any){
|
||||
private __patchSearch(data: any) {
|
||||
this.id = data.id || undefined;
|
||||
this.url = this.id ? `https://www.youtube.com/playlist?list=${this.id}` : undefined;
|
||||
this.title = data.title || undefined;
|
||||
@@ -59,19 +63,19 @@ export class PlayList{
|
||||
this.views = 0;
|
||||
}
|
||||
|
||||
async next(limit: number = Infinity): Promise<Video[]> {
|
||||
async next(limit = Infinity): Promise<Video[]> {
|
||||
if (!this._continuation || !this._continuation.token) return [];
|
||||
|
||||
let nextPage = await request(`${BASE_API}${this._continuation.api}`, {
|
||||
method: "POST",
|
||||
const nextPage = await request(`${BASE_API}${this._continuation.api}`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
continuation: this._continuation.token,
|
||||
context: {
|
||||
client: {
|
||||
utcOffsetMinutes: 0,
|
||||
gl: "US",
|
||||
hl: "en",
|
||||
clientName: "WEB",
|
||||
gl: 'US',
|
||||
hl: 'en',
|
||||
clientName: 'WEB',
|
||||
clientVersion: this._continuation.clientVersion
|
||||
},
|
||||
user: {},
|
||||
@@ -79,24 +83,25 @@ export class PlayList{
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
let contents = JSON.parse(nextPage)?.onResponseReceivedActions[0]?.appendContinuationItemsAction?.continuationItems
|
||||
if(!contents) return []
|
||||
|
||||
let playlist_videos = getPlaylistVideos(contents, limit)
|
||||
this.fetched_videos.set(`page${this.__count}`, playlist_videos)
|
||||
this._continuation.token = getContinuationToken(contents)
|
||||
return playlist_videos
|
||||
const contents =
|
||||
JSON.parse(nextPage)?.onResponseReceivedActions[0]?.appendContinuationItemsAction?.continuationItems;
|
||||
if (!contents) return [];
|
||||
|
||||
const playlist_videos = getPlaylistVideos(contents, limit);
|
||||
this.fetched_videos.set(`page${this.__count}`, playlist_videos);
|
||||
this._continuation.token = getContinuationToken(contents);
|
||||
return playlist_videos;
|
||||
}
|
||||
|
||||
async fetch(max: number = Infinity) {
|
||||
let continuation = this._continuation.token;
|
||||
async fetch(max = Infinity) {
|
||||
const continuation = this._continuation.token;
|
||||
if (!continuation) return this;
|
||||
if (max < 1) max = Infinity;
|
||||
|
||||
while (typeof this._continuation.token === "string" && this._continuation.token.length) {
|
||||
if (this.videos?.length as number >= max) break;
|
||||
this.__count++
|
||||
while (typeof this._continuation.token === 'string' && this._continuation.token.length) {
|
||||
if ((this.videos?.length as number) >= max) break;
|
||||
this.__count++;
|
||||
const res = await this.next();
|
||||
if (!res.length) break;
|
||||
}
|
||||
@@ -104,23 +109,23 @@ export class PlayList{
|
||||
return this;
|
||||
}
|
||||
|
||||
get type(): "playlist" {
|
||||
return "playlist";
|
||||
get type(): 'playlist' {
|
||||
return 'playlist';
|
||||
}
|
||||
|
||||
page(number : number): Video[]{
|
||||
if(!number) throw new Error('Page number is not provided')
|
||||
if(!this.fetched_videos.has(`page${number}`)) throw new Error('Given Page number is invalid')
|
||||
return this.fetched_videos.get(`page${number}`) as Video[]
|
||||
page(number: number): Video[] {
|
||||
if (!number) throw new Error('Page number is not provided');
|
||||
if (!this.fetched_videos.has(`page${number}`)) throw new Error('Given Page number is invalid');
|
||||
return this.fetched_videos.get(`page${number}`) as Video[];
|
||||
}
|
||||
|
||||
get total_pages(){
|
||||
return this.fetched_videos.size
|
||||
get total_pages() {
|
||||
return this.fetched_videos.size;
|
||||
}
|
||||
|
||||
get total_videos(){
|
||||
let page_number: number = this.total_pages
|
||||
return (page_number - 1) * 100 + (this.fetched_videos.get(`page${page_number}`) as Video[]).length
|
||||
get total_videos() {
|
||||
const page_number: number = this.total_pages;
|
||||
return (page_number - 1) * 100 + (this.fetched_videos.get(`page${page_number}`) as Video[]).length;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
@@ -129,12 +134,12 @@ export class PlayList{
|
||||
title: this.title,
|
||||
thumbnail: this.thumbnail,
|
||||
channel: {
|
||||
name : this.channel?.name,
|
||||
id : this.channel?.id,
|
||||
icon : this.channel?.iconURL()
|
||||
name: this.channel?.name,
|
||||
id: this.channel?.id,
|
||||
icon: this.channel?.iconURL()
|
||||
},
|
||||
url: this.url,
|
||||
videos: this.videos
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
type ThumbnailType = "default" | "hqdefault" | "mqdefault" | "sddefault" | "maxresdefault" | "ultrares";
|
||||
type ThumbnailType = 'default' | 'hqdefault' | 'mqdefault' | 'sddefault' | 'maxresdefault' | 'ultrares';
|
||||
|
||||
export class Thumbnail {
|
||||
id?: string;
|
||||
@@ -21,20 +21,21 @@ export class Thumbnail {
|
||||
this.url = data.url || undefined;
|
||||
}
|
||||
|
||||
displayThumbnailURL(thumbnailType: ThumbnailType = "maxresdefault"): string {
|
||||
if (!["default", "hqdefault", "mqdefault", "sddefault", "maxresdefault", "ultrares"].includes(thumbnailType)) throw new Error(`Invalid thumbnail type "${thumbnailType}"!`);
|
||||
if (thumbnailType === "ultrares") return this.url as string;
|
||||
displayThumbnailURL(thumbnailType: ThumbnailType = 'maxresdefault'): string {
|
||||
if (!['default', 'hqdefault', 'mqdefault', 'sddefault', 'maxresdefault', 'ultrares'].includes(thumbnailType))
|
||||
throw new Error(`Invalid thumbnail type "${thumbnailType}"!`);
|
||||
if (thumbnailType === 'ultrares') return this.url as string;
|
||||
return `https://i3.ytimg.com/vi/${this.id}/${thumbnailType}.jpg`;
|
||||
}
|
||||
|
||||
defaultThumbnailURL(id: "0" | "1" | "2" | "3" | "4"): string {
|
||||
if (!id) id = "0";
|
||||
if (!["0", "1", "2", "3", "4"].includes(id)) throw new Error(`Invalid thumbnail id "${id}"!`);
|
||||
defaultThumbnailURL(id: '0' | '1' | '2' | '3' | '4'): string {
|
||||
if (!id) id = '0';
|
||||
if (!['0', '1', '2', '3', '4'].includes(id)) throw new Error(`Invalid thumbnail id "${id}"!`);
|
||||
return `https://i3.ytimg.com/vi/${this.id}/${id}.jpg`;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this.url ? `${this.url}` : "";
|
||||
return this.url ? `${this.url}` : '';
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Channel } from "./Channel";
|
||||
import { Thumbnail } from "./Thumbnail";
|
||||
import { Channel } from './Channel';
|
||||
import { Thumbnail } from './Thumbnail';
|
||||
|
||||
interface VideoOptions {
|
||||
id?: string;
|
||||
url? : string;
|
||||
url?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
durationRaw: string;
|
||||
@@ -12,21 +12,21 @@ interface VideoOptions {
|
||||
views: number;
|
||||
thumbnail?: {
|
||||
id: string | undefined;
|
||||
width: number | undefined ;
|
||||
width: number | undefined;
|
||||
height: number | undefined;
|
||||
url: string | undefined;
|
||||
};
|
||||
channel?: {
|
||||
name : string,
|
||||
id : string,
|
||||
icon : string
|
||||
name: string;
|
||||
id: string;
|
||||
icon: string;
|
||||
};
|
||||
videos?: Video[];
|
||||
type : string;
|
||||
ratings : {
|
||||
type: string;
|
||||
ratings: {
|
||||
likes: number;
|
||||
dislikes: number;
|
||||
}
|
||||
};
|
||||
live: boolean;
|
||||
private: boolean;
|
||||
tags: string[];
|
||||
@@ -34,7 +34,7 @@ interface VideoOptions {
|
||||
|
||||
export class Video {
|
||||
id?: string;
|
||||
url? : string;
|
||||
url?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
durationRaw: string;
|
||||
@@ -50,35 +50,35 @@ export class Video {
|
||||
private: boolean;
|
||||
tags: string[];
|
||||
|
||||
constructor(data : any){
|
||||
if(!data) throw new Error(`Can not initiate ${this.constructor.name} without data`)
|
||||
|
||||
constructor(data: any) {
|
||||
if (!data) throw new Error(`Can not initiate ${this.constructor.name} without data`);
|
||||
|
||||
this.id = data.id || undefined;
|
||||
this.url = `https://www.youtube.com/watch?v=${this.id}`
|
||||
this.url = `https://www.youtube.com/watch?v=${this.id}`;
|
||||
this.title = data.title || undefined;
|
||||
this.description = data.description || undefined;
|
||||
this.durationRaw = data.duration_raw || "0:00";
|
||||
this.durationRaw = data.duration_raw || '0:00';
|
||||
this.durationInSec = (data.duration < 0 ? 0 : data.duration) || 0;
|
||||
this.uploadedAt = data.uploadedAt || undefined;
|
||||
this.views = parseInt(data.views) || 0;
|
||||
this.thumbnail = data.thumbnail || {};
|
||||
this.channel = data.channel || {};
|
||||
this.likes = data.ratings?.likes as number || 0;
|
||||
this.likes = (data.ratings?.likes as number) || 0;
|
||||
this.dislikes = data.ratings?.dislikes || 0;
|
||||
this.live = !!data.live;
|
||||
this.private = !!data.private;
|
||||
this.tags = data.tags || [];
|
||||
}
|
||||
|
||||
get type(): "video" {
|
||||
return "video";
|
||||
get type(): 'video' {
|
||||
return 'video';
|
||||
}
|
||||
|
||||
get toString(): string {
|
||||
return this.url || "";
|
||||
return this.url || '';
|
||||
}
|
||||
|
||||
get toJSON(): VideoOptions{
|
||||
get toJSON(): VideoOptions {
|
||||
return {
|
||||
id: this.id,
|
||||
url: this.url,
|
||||
@@ -104,4 +104,4 @@ export class Video {
|
||||
private: this.private
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user