From f91508d652b52f412d7b1b9c24767bba2d18688d Mon Sep 17 00:00:00 2001 From: killer069 <65385476+killer069@users.noreply.github.com> Date: Sat, 7 Aug 2021 15:53:18 +0530 Subject: [PATCH 01/15] YouTube --- node-youtube-dl/YouTube/utils/extractor.ts | 45 ++++++ node-youtube-dl/YouTube/utils/request.ts | 10 ++ node-youtube-dl/index.ts | 3 + package-lock.json | 175 +++++++++++++++++++++ package.json | 26 +++ tsconfig.json | 23 +++ 6 files changed, 282 insertions(+) create mode 100644 node-youtube-dl/YouTube/utils/extractor.ts create mode 100644 node-youtube-dl/YouTube/utils/request.ts create mode 100644 node-youtube-dl/index.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 tsconfig.json diff --git a/node-youtube-dl/YouTube/utils/extractor.ts b/node-youtube-dl/YouTube/utils/extractor.ts new file mode 100644 index 0000000..62c71b6 --- /dev/null +++ b/node-youtube-dl/YouTube/utils/extractor.ts @@ -0,0 +1,45 @@ +import { get } from "./request"; +import fs from 'fs' + +export function valid_url(url : string): boolean{ + let valid_url = /^https?:\/\/(youtu\.be\/|(www\.)?youtube\.com\/(embed|watch|v|shorts)(\/|\?))/ + if(url.search(valid_url) !== -1) return true + else return false +} + +export async function getBasicInfo(url : string){ + if(valid_url(url)){ + let body = await get(url) + var final ={ + initial_data : yt_initial_data(body) + } + } + else { + throw 'Not a Valid YouTube URL' + process.exit(1) + } +} + +function parse_json(str : string, start_index : number){ + let matches = 0 + let result : string = ''; + if(str[start_index] !== '{') { + throw 'Start Index is wrong.' + process.exit(1) + } + + matches++ + result += str[start_index] + for(let x = start_index + 1; x <= str.length; x++){ + if(matches === 0) break + if(str[x] === '(' || str[x] === '{' || str[x] === '[') matches++ + if(str[x] === ')' || str[x] === '}' || str[x] === ']') matches-- + result += str[x] + } + return JSON.parse(result) +} + +function yt_initial_data(data : string): JSON{ + let pattern = /ytInitialData\s=\s/ + return parse_json(data, data.search(pattern) + 16) +} \ No newline at end of file diff --git a/node-youtube-dl/YouTube/utils/request.ts b/node-youtube-dl/YouTube/utils/request.ts new file mode 100644 index 0000000..8739c5c --- /dev/null +++ b/node-youtube-dl/YouTube/utils/request.ts @@ -0,0 +1,10 @@ +import fetch from 'node-fetch' + +export async function get (url : string) : Promise{ + return new Promise(async(resolve, reject) => { + let response = await fetch(url) + + if(response.status === 200) resolve(await response.text()) + else reject(`Got ${response.status} from ${url}`) + }) +} diff --git a/node-youtube-dl/index.ts b/node-youtube-dl/index.ts new file mode 100644 index 0000000..c47a59d --- /dev/null +++ b/node-youtube-dl/index.ts @@ -0,0 +1,3 @@ +import { getBasicInfo } from "./YouTube/utils/extractor"; + +getBasicInfo('https://www.youtube.com/watch?v=V_jHc_n0p9c') \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..81a5c9b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,175 @@ +{ + "name": "killer", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "killer", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "node-fetch": "^2.6.1" + }, + "devDependencies": { + "@types/node-fetch": "^2.5.12" + } + }, + "node_modules/@types/node": { + "version": "16.4.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.4.13.tgz", + "integrity": "sha512-bLL69sKtd25w7p1nvg9pigE4gtKVpGTPojBFLMkGHXuUgap2sLqQt2qUnqmVCDfzGUL0DRNZP+1prIZJbMeAXg==", + "dev": true + }, + "node_modules/@types/node-fetch": { + "version": "2.5.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz", + "integrity": "sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/mime-db": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", + "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.32", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", + "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", + "dev": true, + "dependencies": { + "mime-db": "1.49.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "engines": { + "node": "4.x || >=6.0.0" + } + } + }, + "dependencies": { + "@types/node": { + "version": "16.4.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.4.13.tgz", + "integrity": "sha512-bLL69sKtd25w7p1nvg9pigE4gtKVpGTPojBFLMkGHXuUgap2sLqQt2qUnqmVCDfzGUL0DRNZP+1prIZJbMeAXg==", + "dev": true + }, + "@types/node-fetch": { + "version": "2.5.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz", + "integrity": "sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==", + "dev": true, + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "mime-db": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", + "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==", + "dev": true + }, + "mime-types": { + "version": "2.1.32", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", + "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", + "dev": true, + "requires": { + "mime-db": "1.49.0" + } + }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..049857d --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "killer", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/killer069/node-youtube-dl.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/killer069/node-youtube-dl/issues" + }, + "homepage": "https://github.com/killer069/node-youtube-dl#readme", + "dependencies": { + "node-fetch": "^2.6.1" + }, + "devDependencies": { + "@types/node-fetch": "^2.5.12" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..9ef05e0 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "strict": true, + "moduleResolution": "node", + "removeComments": false, + "alwaysStrict": true, + "pretty": true, + "target": "es2019", + "lib": ["ESNext"], + "sourceMap": true, + "inlineSources": true, + "module": "commonjs", + "declaration": true, + "declarationMap": true, + "outDir": "dist", + "incremental": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true + }, + "include": ["node-youtube-dl/**/**/*.ts"], + "exclude": ["node-youtube-dl/**/__tests__"] +} \ No newline at end of file From 1a16c9af206ee6610bc1d59b63fa6924f7d2870f Mon Sep 17 00:00:00 2001 From: killer069 <65385476+killer069@users.noreply.github.com> Date: Sun, 8 Aug 2021 19:06:00 +0530 Subject: [PATCH 02/15] Added New Method for getting youtube data --- node-youtube-dl/YouTube/utils/extractor.ts | 54 +-- node-youtube-dl/YouTube/utils/request.ts | 10 - node-youtube-dl/index.ts | 2 +- package-lock.json | 497 ++++++++++++++++++++- package.json | 3 +- 5 files changed, 524 insertions(+), 42 deletions(-) delete mode 100644 node-youtube-dl/YouTube/utils/request.ts diff --git a/node-youtube-dl/YouTube/utils/extractor.ts b/node-youtube-dl/YouTube/utils/extractor.ts index 62c71b6..c64588f 100644 --- a/node-youtube-dl/YouTube/utils/extractor.ts +++ b/node-youtube-dl/YouTube/utils/extractor.ts @@ -1,5 +1,6 @@ -import { get } from "./request"; +import fetch from 'node-fetch' import fs from 'fs' +import got from 'got' export function valid_url(url : string): boolean{ let valid_url = /^https?:\/\/(youtu\.be\/|(www\.)?youtube\.com\/(embed|watch|v|shorts)(\/|\?))/ @@ -9,37 +10,38 @@ export function valid_url(url : string): boolean{ export async function getBasicInfo(url : string){ if(valid_url(url)){ - let body = await get(url) - var final ={ - initial_data : yt_initial_data(body) + let body = await url_get(url) + let final = { + player_response : get_ytPlayerResponse(body), + response : get_ytInitialData(body), + js_url : js_url(body) } } else { throw 'Not a Valid YouTube URL' - process.exit(1) } } -function parse_json(str : string, start_index : number){ - let matches = 0 - let result : string = ''; - if(str[start_index] !== '{') { - throw 'Start Index is wrong.' - process.exit(1) - } - - matches++ - result += str[start_index] - for(let x = start_index + 1; x <= str.length; x++){ - if(matches === 0) break - if(str[x] === '(' || str[x] === '{' || str[x] === '[') matches++ - if(str[x] === ')' || str[x] === '}' || str[x] === ']') matches-- - result += str[x] - } - return JSON.parse(result) +function js_url(data:string): string { + return data.split('"jsUrl":"')[1].split('"')[0] } -function yt_initial_data(data : string): JSON{ - let pattern = /ytInitialData\s=\s/ - return parse_json(data, data.search(pattern) + 16) -} \ No newline at end of file +function get_ytPlayerResponse(data : string): JSON { + return JSON.parse(data.split("var ytInitialPlayerResponse = ")[1].split(";")[0]) +} + +function get_ytInitialData(data:string): JSON { + return JSON.parse(data.split("var ytInitialData = ")[1].split(";")[0]) +} + +export async function url_get (url : string) : Promise{ + return new Promise(async(resolve, reject) => { + let time_start = Date.now() + let response = await fetch(url) + + if(response.status === 200) { + resolve(await response.text()) + } + else reject(`Got ${response.status} from ${url}`) + }) +} diff --git a/node-youtube-dl/YouTube/utils/request.ts b/node-youtube-dl/YouTube/utils/request.ts deleted file mode 100644 index 8739c5c..0000000 --- a/node-youtube-dl/YouTube/utils/request.ts +++ /dev/null @@ -1,10 +0,0 @@ -import fetch from 'node-fetch' - -export async function get (url : string) : Promise{ - return new Promise(async(resolve, reject) => { - let response = await fetch(url) - - if(response.status === 200) resolve(await response.text()) - else reject(`Got ${response.status} from ${url}`) - }) -} diff --git a/node-youtube-dl/index.ts b/node-youtube-dl/index.ts index c47a59d..d593d68 100644 --- a/node-youtube-dl/index.ts +++ b/node-youtube-dl/index.ts @@ -1,3 +1,3 @@ import { getBasicInfo } from "./YouTube/utils/extractor"; -getBasicInfo('https://www.youtube.com/watch?v=V_jHc_n0p9c') \ No newline at end of file +getBasicInfo('https://www.youtube.com/watch?v=IFYJNLZT_B0') \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 81a5c9b..d2f5cc0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,17 +9,63 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "got": "^11.8.2", "node-fetch": "^2.6.1" }, "devDependencies": { "@types/node-fetch": "^2.5.12" } }, + "node_modules/@sindresorhus/is": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.1.tgz", + "integrity": "sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", + "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" + }, + "node_modules/@types/keyv": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.2.tgz", + "integrity": "sha512-/FvAK2p4jQOaJ6CGDHJTqZcUtbZe820qIeTg7o0Shg7drB4JHeL+V/dhSaly7NXx6u8eSee+r7coT+yuJEvDLg==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "16.4.13", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.4.13.tgz", - "integrity": "sha512-bLL69sKtd25w7p1nvg9pigE4gtKVpGTPojBFLMkGHXuUgap2sLqQt2qUnqmVCDfzGUL0DRNZP+1prIZJbMeAXg==", - "dev": true + "integrity": "sha512-bLL69sKtd25w7p1nvg9pigE4gtKVpGTPojBFLMkGHXuUgap2sLqQt2qUnqmVCDfzGUL0DRNZP+1prIZJbMeAXg==" }, "node_modules/@types/node-fetch": { "version": "2.5.12", @@ -31,12 +77,53 @@ "form-data": "^3.0.0" } }, + "node_modules/@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", + "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dependencies": { + "mimic-response": "^1.0.0" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -49,6 +136,39 @@ "node": ">= 0.8" } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "engines": { + "node": ">=10" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -58,6 +178,14 @@ "node": ">=0.4.0" } }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/form-data": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", @@ -72,6 +200,82 @@ "node": ">= 6" } }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/got": { + "version": "11.8.2", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz", + "integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.1", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "node_modules/keyv": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", + "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "engines": { + "node": ">=8" + } + }, "node_modules/mime-db": { "version": "1.49.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", @@ -93,6 +297,14 @@ "node": ">= 0.6" } }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "engines": { + "node": ">=4" + } + }, "node_modules/node-fetch": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", @@ -100,14 +312,115 @@ "engines": { "node": "4.x || >=6.0.0" } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.0.tgz", + "integrity": "sha512-e4FNQs+9cINYMO5NMFc6kOUCdohjqFPSgMuwuZAOUWqrfWsen+Yjy5qZFkV5K7VO7tFSLKcUL97olkED7sCBHA==" + }, + "node_modules/responselike": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "dependencies": { + "lowercase-keys": "^2.0.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" } }, "dependencies": { + "@sindresorhus/is": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.1.tgz", + "integrity": "sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g==" + }, + "@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "requires": { + "defer-to-connect": "^2.0.0" + } + }, + "@types/cacheable-request": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", + "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, + "@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" + }, + "@types/keyv": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.2.tgz", + "integrity": "sha512-/FvAK2p4jQOaJ6CGDHJTqZcUtbZe820qIeTg7o0Shg7drB4JHeL+V/dhSaly7NXx6u8eSee+r7coT+yuJEvDLg==", + "requires": { + "@types/node": "*" + } + }, "@types/node": { "version": "16.4.13", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.4.13.tgz", - "integrity": "sha512-bLL69sKtd25w7p1nvg9pigE4gtKVpGTPojBFLMkGHXuUgap2sLqQt2qUnqmVCDfzGUL0DRNZP+1prIZJbMeAXg==", - "dev": true + "integrity": "sha512-bLL69sKtd25w7p1nvg9pigE4gtKVpGTPojBFLMkGHXuUgap2sLqQt2qUnqmVCDfzGUL0DRNZP+1prIZJbMeAXg==" }, "@types/node-fetch": { "version": "2.5.12", @@ -119,12 +432,47 @@ "form-data": "^3.0.0" } }, + "@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "requires": { + "@types/node": "*" + } + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, + "cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==" + }, + "cacheable-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", + "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + } + }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "requires": { + "mimic-response": "^1.0.0" + } + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -134,12 +482,40 @@ "delayed-stream": "~1.0.0" } }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "requires": { + "mimic-response": "^3.1.0" + }, + "dependencies": { + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + } + } + }, + "defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, "form-data": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", @@ -151,6 +527,64 @@ "mime-types": "^2.1.12" } }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + }, + "got": { + "version": "11.8.2", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz", + "integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==", + "requires": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.1", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + } + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + }, + "http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + } + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "keyv": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", + "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", + "requires": { + "json-buffer": "3.0.1" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + }, "mime-db": { "version": "1.49.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", @@ -166,10 +600,65 @@ "mime-db": "1.49.0" } }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + }, "node-fetch": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, + "normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + }, + "resolve-alpn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.0.tgz", + "integrity": "sha512-e4FNQs+9cINYMO5NMFc6kOUCdohjqFPSgMuwuZAOUWqrfWsen+Yjy5qZFkV5K7VO7tFSLKcUL97olkED7sCBHA==" + }, + "responselike": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "requires": { + "lowercase-keys": "^2.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" } } } diff --git a/package.json b/package.json index 049857d..cd5f8af 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "killer", "version": "1.0.0", "description": "", - "main": "index.js", + "main": "dist/index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, @@ -18,6 +18,7 @@ }, "homepage": "https://github.com/killer069/node-youtube-dl#readme", "dependencies": { + "got": "^11.8.2", "node-fetch": "^2.6.1" }, "devDependencies": { From 381403bac9f4627969c20b4aa75c3ad97665f310 Mon Sep 17 00:00:00 2001 From: killer069 <65385476+killer069@users.noreply.github.com> Date: Sun, 8 Aug 2021 19:07:06 +0530 Subject: [PATCH 03/15] non sense code --- node-youtube-dl/YouTube/utils/extractor.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/node-youtube-dl/YouTube/utils/extractor.ts b/node-youtube-dl/YouTube/utils/extractor.ts index c64588f..461fcb5 100644 --- a/node-youtube-dl/YouTube/utils/extractor.ts +++ b/node-youtube-dl/YouTube/utils/extractor.ts @@ -36,7 +36,6 @@ function get_ytInitialData(data:string): JSON { export async function url_get (url : string) : Promise{ return new Promise(async(resolve, reject) => { - let time_start = Date.now() let response = await fetch(url) if(response.status === 200) { From b012e4d942b23eadd1c1e9a99f505e66426d100b Mon Sep 17 00:00:00 2001 From: killer069 <65385476+killer069@users.noreply.github.com> Date: Thu, 12 Aug 2021 11:10:18 +0530 Subject: [PATCH 04/15] signature added. Almost Completed YouTube --- node-youtube-dl/YouTube/utils/cipher.ts | 173 +++++++++++++++++++++ node-youtube-dl/YouTube/utils/extractor.ts | 62 ++++++-- node-youtube-dl/index.ts | 11 +- 3 files changed, 227 insertions(+), 19 deletions(-) create mode 100644 node-youtube-dl/YouTube/utils/cipher.ts diff --git a/node-youtube-dl/YouTube/utils/cipher.ts b/node-youtube-dl/YouTube/utils/cipher.ts new file mode 100644 index 0000000..df2c1c5 --- /dev/null +++ b/node-youtube-dl/YouTube/utils/cipher.ts @@ -0,0 +1,173 @@ +import { URL } from 'node:url' +import { url_get } from './extractor' +import querystring from 'node:querystring' + +interface formatOptions { + url? : string; + sp? : string; + signatureCipher? : string; + cipher?: string; + s? : string; +} +const var_js = '[a-zA-Z_\\$][a-zA-Z_0-9]*'; +const singlequote_js = `'[^'\\\\]*(:?\\\\[\\s\\S][^'\\\\]*)*'`; +const duoblequote_js = `"[^"\\\\]*(:?\\\\[\\s\\S][^"\\\\]*)*"`; +const quote_js = `(?:${singlequote_js}|${duoblequote_js})`; +const key_js = `(?:${var_js}|${quote_js})`; +const prop_js = `(?:\\.${var_js}|\\[${quote_js}\\])`; +const empty_js = `(?:''|"")`; +const reverse_function = ':function\\(a\\)\\{' + +'(?:return )?a\\.reverse\\(\\)' + +'\\}'; +const slice_function = ':function\\(a,b\\)\\{' + +'return a\\.slice\\(b\\)' + +'\\}'; +const splice_function = ':function\\(a,b\\)\\{' + +'a\\.splice\\(0,b\\)' + +'\\}'; +const swap_function = ':function\\(a,b\\)\\{' + +'var c=a\\[0\\];a\\[0\\]=a\\[b(?:%a\\.length)?\\];a\\[b(?:%a\\.length)?\\]=c(?:;return a)?' + +'\\}'; +const obj_regexp = new RegExp( + `var (${var_js})=\\{((?:(?:${ + key_js}${reverse_function}|${ + key_js}${slice_function}|${ + key_js}${splice_function}|${ + key_js}${swap_function + }),?\\r?\\n?)+)\\};`) +const function_regexp = new RegExp(`${`function(?: ${var_js})?\\(a\\)\\{` + +`a=a\\.split\\(${empty_js}\\);\\s*` + +`((?:(?:a=)?${var_js}`}${ +prop_js +}\\(a,\\d+\\);)+)` + +`return a\\.join\\(${empty_js}\\)` + +`\\}`); +const reverse_regexp = new RegExp(`(?:^|,)(${key_js})${reverse_function}`, 'm'); +const slice_regexp = new RegExp(`(?:^|,)(${key_js})${slice_function}`, 'm'); +const splice_regexp = new RegExp(`(?:^|,)(${key_js})${splice_function}`, 'm'); +const swap_regexp = new RegExp(`(?:^|,)(${key_js})${swap_function}`, 'm'); + +export function js_tokens( body:string ) { + let function_action = function_regexp.exec(body) + let object_action = obj_regexp.exec(body) + if(!function_action || !object_action) return null + + let object = object_action[1].replace(/\$/g, '\\$') + let object_body = object_action[2].replace(/\$/g, '\\$') + let function_body = function_action[1].replace(/\$/g, '\\$') + + let result = reverse_regexp.exec(object_body); + const reverseKey = result && result[1] + .replace(/\$/g, '\\$') + .replace(/\$|^'|^"|'$|"$/g, ''); + + result = slice_regexp.exec(object_body) + const sliceKey = result && result[1] + .replace(/\$/g, '\\$') + .replace(/\$|^'|^"|'$|"$/g, ''); + + result = splice_regexp.exec(object_body); + const spliceKey = result && result[1] + .replace(/\$/g, '\\$') + .replace(/\$|^'|^"|'$|"$/g, ''); + + result = swap_regexp.exec(object_body); + const swapKey = result && result[1] + .replace(/\$/g, '\\$') + .replace(/\$|^'|^"|'$|"$/g, ''); + + const keys = `(${[reverseKey, sliceKey, spliceKey, swapKey].join('|')})`; + const myreg = `(?:a=)?${object + }(?:\\.${keys}|\\['${keys}'\\]|\\["${keys}"\\])` + + `\\(a,(\\d+)\\)`; + const tokenizeRegexp = new RegExp(myreg, 'g'); + const tokens = []; + while((result = tokenizeRegexp.exec(function_body)) !== null){ + let key = result[1] || result[2] || result[3]; + switch (key) { + case swapKey: + tokens.push(`sw${result[4]}`); + break; + case reverseKey: + tokens.push('rv'); + break; + case sliceKey: + tokens.push(`sl${result[4]}`); + break; + case spliceKey: + tokens.push(`sp${result[4]}`); + break; + } + } + return tokens +} + +export function deciper_signature(tokens : string[], signature :string){ + let sig = signature.split('') + let len = tokens.length + for(let i = 0; i < len; i++ ){ + let token = tokens[i], pos; + switch(token.slice(0,2)){ + case 'sw': + pos = parseInt(token.slice(2)) + sig = swappositions(sig, pos) + break + case 'rv': + sig = sig.reverse() + break + case 'sl': + pos = parseInt(token.slice(2)) + sig = sig.slice(pos) + break + case 'sp': + pos = parseInt(token.slice(2)) + sig.splice(0, pos) + break + } + } + return sig.join('') +} + + +function swappositions(array : string[], position : number){ + let first = array[0] + let pos_args = array[position] + array[0] = array[position] + array[position] = first + return array +} + +export function download_url(format: formatOptions, sig : string){ + let decoded_url; + if(!format.url) return; + decoded_url = format.url + + decoded_url = decodeURIComponent(decoded_url) + + let parsed_url = new URL(decoded_url) + parsed_url.searchParams.set('ratebypass', 'yes'); + + if(sig){ + parsed_url.searchParams.set(format.sp || 'signature', sig) + } + format.url = parsed_url.toString(); +} + +export async function format_decipher(format: formatOptions[], html5player : string){ + let body = await url_get(html5player) + let tokens = js_tokens(body) + format.forEach((format) => { + let cipher = format.signatureCipher || format.cipher; + if(cipher){ + Object.assign(format, querystring.parse(cipher)) + delete format.signatureCipher; + delete format.cipher; + } + let sig; + if(tokens && format.s){ + sig = deciper_signature(tokens, format.s) + download_url(format, sig) + } + }); + return format +} \ No newline at end of file diff --git a/node-youtube-dl/YouTube/utils/extractor.ts b/node-youtube-dl/YouTube/utils/extractor.ts index 461fcb5..c240591 100644 --- a/node-youtube-dl/YouTube/utils/extractor.ts +++ b/node-youtube-dl/YouTube/utils/extractor.ts @@ -1,6 +1,5 @@ import fetch from 'node-fetch' -import fs from 'fs' -import got from 'got' +import { format_decipher, js_tokens } from './cipher' export function valid_url(url : string): boolean{ let valid_url = /^https?:\/\/(youtu\.be\/|(www\.)?youtube\.com\/(embed|watch|v|shorts)(\/|\?))/ @@ -8,30 +7,59 @@ export function valid_url(url : string): boolean{ else return false } -export async function getBasicInfo(url : string){ +export async function yt_initial_data(url : string){ if(valid_url(url)){ let body = await url_get(url) - let final = { - player_response : get_ytPlayerResponse(body), - response : get_ytInitialData(body), - js_url : js_url(body) + let player_response = JSON.parse(body.split("var ytInitialPlayerResponse = ")[1].split(";")[0]) + let response = JSON.parse(body.split("var ytInitialData = ")[1].split(";")[0]) + let html5player = 'https://www.youtube.com' + body.split('"jsUrl":"')[1].split('"')[0] + let format = [] + format.push(player_response.streamingData.formats[0]) + format.push(...player_response.streamingData.adaptiveFormats) + let vid = player_response.videoDetails + let microformat = player_response.microformat.playerMicroformatRenderer + let video_details = { + id : vid.videoId, + url : 'https://www.youtube.com/watch?v=' + vid.videoId, + title : vid.title, + description : vid.shortDescription, + duration : vid.lengthSeconds, + uploadedDate : microformat.publishDate, + thumbnail : `https://i.ytimg.com/vi/${vid.videoId}/maxresdefault.jpg`, + channel : { + name : vid.author, + id : vid.channelId, + url : `https://www.youtube.com/channel/${vid.channelId}` + }, + views : vid.viewCount, + tags : vid.keywords, + averageRating : vid.averageRating, + live : vid.isLiveContent, + private : vid.isPrivate } + let final = { + player_response, + response, + html5player, + format, + video_details + } + return final } else { throw 'Not a Valid YouTube URL' } } -function js_url(data:string): string { - return data.split('"jsUrl":"')[1].split('"')[0] -} - -function get_ytPlayerResponse(data : string): JSON { - return JSON.parse(data.split("var ytInitialPlayerResponse = ")[1].split(";")[0]) -} - -function get_ytInitialData(data:string): JSON { - return JSON.parse(data.split("var ytInitialData = ")[1].split(";")[0]) +export async function yt_deciphered_data(url : string) { + let data = await yt_initial_data(url) + if(data.format[0].signatureCipher || data.format[0].cipher){ + data.format = await format_decipher(data.format, data.html5player) + return data + } + else { + return data + } } export async function url_get (url : string) : Promise{ diff --git a/node-youtube-dl/index.ts b/node-youtube-dl/index.ts index d593d68..6ac1355 100644 --- a/node-youtube-dl/index.ts +++ b/node-youtube-dl/index.ts @@ -1,3 +1,10 @@ -import { getBasicInfo } from "./YouTube/utils/extractor"; +import { yt_deciphered_data } from "./YouTube/utils/extractor"; -getBasicInfo('https://www.youtube.com/watch?v=IFYJNLZT_B0') \ No newline at end of file +let main = async() => { + let time_start = Date.now() + await yt_deciphered_data('https://www.youtube.com/watch?v=jbMHA3P7RzU') + let time_end = Date.now() + console.log(`Time Taken : ${(time_end - time_start)/1000} seconds`) +} + +main() \ No newline at end of file From 5fbd98ca98001f9bf3df81e71a13b9d0a1a3f735 Mon Sep 17 00:00:00 2001 From: killer069 <65385476+killer069@users.noreply.github.com> Date: Thu, 12 Aug 2021 13:28:17 +0530 Subject: [PATCH 05/15] YouTube is closing soon..... --- node-youtube-dl/YouTube/classes/Playlist.ts | 0 node-youtube-dl/YouTube/classes/Video.ts | 93 +++++++++++++++++++++ node-youtube-dl/YouTube/index.ts | 1 + node-youtube-dl/YouTube/search.ts | 10 +++ node-youtube-dl/YouTube/utils/cipher.ts | 6 +- node-youtube-dl/YouTube/utils/extractor.ts | 23 +---- node-youtube-dl/YouTube/utils/index.ts | 1 + node-youtube-dl/YouTube/utils/parser.ts | 6 ++ node-youtube-dl/YouTube/utils/request.ts | 12 +++ node-youtube-dl/index.ts | 4 +- 10 files changed, 129 insertions(+), 27 deletions(-) create mode 100644 node-youtube-dl/YouTube/classes/Playlist.ts create mode 100644 node-youtube-dl/YouTube/classes/Video.ts create mode 100644 node-youtube-dl/YouTube/index.ts create mode 100644 node-youtube-dl/YouTube/search.ts create mode 100644 node-youtube-dl/YouTube/utils/index.ts create mode 100644 node-youtube-dl/YouTube/utils/parser.ts create mode 100644 node-youtube-dl/YouTube/utils/request.ts diff --git a/node-youtube-dl/YouTube/classes/Playlist.ts b/node-youtube-dl/YouTube/classes/Playlist.ts new file mode 100644 index 0000000..e69de29 diff --git a/node-youtube-dl/YouTube/classes/Video.ts b/node-youtube-dl/YouTube/classes/Video.ts new file mode 100644 index 0000000..8b6a944 --- /dev/null +++ b/node-youtube-dl/YouTube/classes/Video.ts @@ -0,0 +1,93 @@ +interface VideoOptions { + id?: string; + url? : string; + title?: string; + description?: string; + duration_formatted: string; + duration: number; + uploadedAt?: string; + views: number; + thumbnail?: JSON; + channel?: JSON; + videos?: Video[]; + type : string; + ratings : { + likes: number; + dislikes: number; + } + live: boolean; + private: boolean; + tags: string[]; +} + +export class Video { + id?: string; + title?: string; + description?: string; + durationFormatted: string; + duration: number; + uploadedAt?: string; + views: number; + thumbnail?: JSON; + channel?: JSON; + videos?: Video[]; + likes: number; + dislikes: number; + live: boolean; + private: boolean; + tags: string[]; + + constructor(data : any){ + if(!data) throw new Error(`Can not initiate ${this.constructor.name} without data`) + this.id = data.id || undefined; + this.title = data.title || undefined; + this.description = data.description || undefined; + this.durationFormatted = data.duration_raw || "0:00"; + this.duration = (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.dislikes = data.ratings?.dislikes || 0; + this.live = !!data.live; + this.private = !!data.private; + this.tags = data.tags || []; + } + + get url(){ + if(!this.id) return undefined + else return `https://www.youtube.com/watch?v=${this.id}`; + } + + get type(): "video" { + return "video"; + } + + get toString(): string { + return this.url || ""; + } + + get toJSON(): VideoOptions{ + return { + id: this.id, + url: this.url, + title: this.title, + description: this.description, + duration: this.duration, + duration_formatted: this.durationFormatted, + uploadedAt: this.uploadedAt, + thumbnail: this.thumbnail, + channel: this.channel, + views: this.views, + type: this.type, + tags: this.tags, + ratings: { + likes: this.likes, + dislikes: this.dislikes + }, + live: this.live, + private: this.private + }; + } +} \ No newline at end of file diff --git a/node-youtube-dl/YouTube/index.ts b/node-youtube-dl/YouTube/index.ts new file mode 100644 index 0000000..128af5e --- /dev/null +++ b/node-youtube-dl/YouTube/index.ts @@ -0,0 +1 @@ +export { search } from './search' \ No newline at end of file diff --git a/node-youtube-dl/YouTube/search.ts b/node-youtube-dl/YouTube/search.ts new file mode 100644 index 0000000..8aed895 --- /dev/null +++ b/node-youtube-dl/YouTube/search.ts @@ -0,0 +1,10 @@ +import { url_get } from "./utils/request"; +import fs from 'fs' + + +export async function search(url:string, options? : {limit : number}) { + let body = await url_get(url) + let json_convert = body.split("var ytInitialData = ")[1].split(";")[0] + let result = JSON.parse(json_convert).contents.twoColumnSearchResultsRenderer.primaryContents.sectionListRenderer.contents[0].itemSectionRenderer.contents + console.log(result.length) +} \ No newline at end of file diff --git a/node-youtube-dl/YouTube/utils/cipher.ts b/node-youtube-dl/YouTube/utils/cipher.ts index df2c1c5..e6ebada 100644 --- a/node-youtube-dl/YouTube/utils/cipher.ts +++ b/node-youtube-dl/YouTube/utils/cipher.ts @@ -1,5 +1,5 @@ import { URL } from 'node:url' -import { url_get } from './extractor' +import { url_get } from './request' import querystring from 'node:querystring' interface formatOptions { @@ -102,7 +102,7 @@ export function js_tokens( body:string ) { return tokens } -export function deciper_signature(tokens : string[], signature :string){ +function deciper_signature(tokens : string[], signature :string){ let sig = signature.split('') let len = tokens.length for(let i = 0; i < len; i++ ){ @@ -137,7 +137,7 @@ function swappositions(array : string[], position : number){ return array } -export function download_url(format: formatOptions, sig : string){ +function download_url(format: formatOptions, sig : string){ let decoded_url; if(!format.url) return; decoded_url = format.url diff --git a/node-youtube-dl/YouTube/utils/extractor.ts b/node-youtube-dl/YouTube/utils/extractor.ts index c240591..56e79e1 100644 --- a/node-youtube-dl/YouTube/utils/extractor.ts +++ b/node-youtube-dl/YouTube/utils/extractor.ts @@ -1,14 +1,8 @@ -import fetch from 'node-fetch' +import { url_get } from './request' import { format_decipher, js_tokens } from './cipher' -export function valid_url(url : string): boolean{ - let valid_url = /^https?:\/\/(youtu\.be\/|(www\.)?youtube\.com\/(embed|watch|v|shorts)(\/|\?))/ - if(url.search(valid_url) !== -1) return true - else return false -} export async function yt_initial_data(url : string){ - if(valid_url(url)){ let body = await url_get(url) let player_response = JSON.parse(body.split("var ytInitialPlayerResponse = ")[1].split(";")[0]) let response = JSON.parse(body.split("var ytInitialData = ")[1].split(";")[0]) @@ -45,10 +39,6 @@ export async function yt_initial_data(url : string){ video_details } return final - } - else { - throw 'Not a Valid YouTube URL' - } } export async function yt_deciphered_data(url : string) { @@ -61,14 +51,3 @@ export async function yt_deciphered_data(url : string) { return data } } - -export async function url_get (url : string) : Promise{ - return new Promise(async(resolve, reject) => { - let response = await fetch(url) - - if(response.status === 200) { - resolve(await response.text()) - } - else reject(`Got ${response.status} from ${url}`) - }) -} diff --git a/node-youtube-dl/YouTube/utils/index.ts b/node-youtube-dl/YouTube/utils/index.ts new file mode 100644 index 0000000..f09bebc --- /dev/null +++ b/node-youtube-dl/YouTube/utils/index.ts @@ -0,0 +1 @@ +export { yt_initial_data, yt_deciphered_data } from './extractor' \ No newline at end of file diff --git a/node-youtube-dl/YouTube/utils/parser.ts b/node-youtube-dl/YouTube/utils/parser.ts new file mode 100644 index 0000000..13ddda5 --- /dev/null +++ b/node-youtube-dl/YouTube/utils/parser.ts @@ -0,0 +1,6 @@ +import { Video } from "../classes/Video"; + + +export function ParseSearchResult(html:string) { + +} \ No newline at end of file diff --git a/node-youtube-dl/YouTube/utils/request.ts b/node-youtube-dl/YouTube/utils/request.ts new file mode 100644 index 0000000..89e22d1 --- /dev/null +++ b/node-youtube-dl/YouTube/utils/request.ts @@ -0,0 +1,12 @@ +import fetch, { RequestInit } from 'node-fetch' + +export async function url_get (url : string, options? : RequestInit) : Promise{ + return new Promise(async(resolve, reject) => { + let response = await fetch(url, options) + + if(response.status === 200) { + resolve(await response.text()) + } + else reject(`Got ${response.status} from ${url}`) + }) +} \ No newline at end of file diff --git a/node-youtube-dl/index.ts b/node-youtube-dl/index.ts index 6ac1355..18cc5d6 100644 --- a/node-youtube-dl/index.ts +++ b/node-youtube-dl/index.ts @@ -1,8 +1,8 @@ -import { yt_deciphered_data } from "./YouTube/utils/extractor"; +import { search } from "./YouTube/"; let main = async() => { let time_start = Date.now() - await yt_deciphered_data('https://www.youtube.com/watch?v=jbMHA3P7RzU') + await search('https://www.youtube.com/results?search_query=Hello+Neghibour') let time_end = Date.now() console.log(`Time Taken : ${(time_end - time_start)/1000} seconds`) } From 455c7dfb69a2ffb348d7b11003c8795ae5547b25 Mon Sep 17 00:00:00 2001 From: killer069 <65385476+killer069@users.noreply.github.com> Date: Thu, 12 Aug 2021 15:58:55 +0530 Subject: [PATCH 06/15] End soon --- node-youtube-dl/YouTube/classes/Channel.ts | 63 +++ node-youtube-dl/YouTube/classes/Playlist.ts | 119 +++++ node-youtube-dl/YouTube/classes/Thumbnail.ts | 48 ++ node-youtube-dl/YouTube/classes/Video.ts | 29 +- node-youtube-dl/YouTube/search.ts | 11 +- node-youtube-dl/YouTube/utils/parser.ts | 209 +++++++- package-lock.json | 497 +------------------ package.json | 1 - 8 files changed, 471 insertions(+), 506 deletions(-) create mode 100644 node-youtube-dl/YouTube/classes/Channel.ts create mode 100644 node-youtube-dl/YouTube/classes/Thumbnail.ts diff --git a/node-youtube-dl/YouTube/classes/Channel.ts b/node-youtube-dl/YouTube/classes/Channel.ts new file mode 100644 index 0000000..c0fa6bc --- /dev/null +++ b/node-youtube-dl/YouTube/classes/Channel.ts @@ -0,0 +1,63 @@ +export interface ChannelIconInterface { + url?: string; + width: number; + height: number; +} + +export class Channel { + name?: string; + verified!: boolean; + id?: string; + url?: string; + icon!: ChannelIconInterface; + subscribers?: string; + + constructor(data: any) { + if (!data) throw new Error(`Cannot instantiate the ${this.constructor.name} class without data!`); + + this._patch(data); + } + + private _patch(data: any): void { + if (!data) data = {}; + + this.name = data.name || null; + this.verified = !!data.verified || false; + this.id = data.id || null; + this.url = data.url || null; + this.icon = data.icon || { url: null, width: 0, height: 0 }; + this.subscribers = data.subscribers || null; + } + + /** + * Returns channel icon url + * @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"); + if (!this.icon.url) return undefined; + 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"; + } + + toString(): string { + return this.name || ""; + } + + toJSON() { + return { + name: this.name, + verified: this.verified, + id: this.id, + url: this.url, + iconURL: this.iconURL(), + type: this.type, + subscribers: this.subscribers + }; + } +} \ No newline at end of file diff --git a/node-youtube-dl/YouTube/classes/Playlist.ts b/node-youtube-dl/YouTube/classes/Playlist.ts index e69de29..f321981 100644 --- a/node-youtube-dl/YouTube/classes/Playlist.ts +++ b/node-youtube-dl/YouTube/classes/Playlist.ts @@ -0,0 +1,119 @@ +import { getContinuationToken, getPlaylistVideos } from "../utils/parser"; +import { url_get } 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{ + id?: string; + title?: string; + videoCount!: number; + lastUpdate?: string; + views?: number; + url?: string; + link?: string; + channel?: Channel; + thumbnail?: Thumbnail; + videos!: []; + private _continuation: { api?: string; token?: string; clientVersion?: string } = {}; + + constructor(data : any, searchResult : Boolean = false){ + if (!data) throw new Error(`Cannot instantiate the ${this.constructor.name} class without data!`); + + if(searchResult) this.__patchSearch(data) + else this.__patch(data) + } + + private __patch(data:any){ + this.id = data.id || undefined; + this.title = data.title || undefined; + this.videoCount = data.videoCount || 0; + this.lastUpdate = data.lastUpdate || undefined; + this.views = data.views || 0; + this.url = data.url || undefined; + this.link = data.link || undefined; + this.channel = data.author || undefined; + this.thumbnail = data.thumbnail || undefined; + this.videos = data.videos || []; + this._continuation.api = data.continuation?.api ?? undefined; + this._continuation.token = data.continuation?.token ?? undefined; + this._continuation.clientVersion = data.continuation?.clientVersion ?? ""; + } + + private __patchSearch(data: any){ + this.id = data.id || undefined; + this.title = data.title || undefined; + this.thumbnail = data.thumbnail || undefined; + this.channel = data.channel || undefined; + this.videos = []; + this.videoCount = data.videos || 0; + this.url = this.id ? `https://www.youtube.com/playlist?list=${this.id}` : undefined; + this.link = undefined; + this.lastUpdate = undefined; + this.views = 0; + } + + async next(limit: number = Infinity): Promise { + if (!this._continuation || !this._continuation.token) return []; + + let nextPage = await url_get(`${BASE_API}${this._continuation.api}`, { + method: "POST", + body: JSON.stringify({ + continuation: this._continuation.token, + context: { + client: { + utcOffsetMinutes: 0, + gl: "US", + hl: "en", + clientName: "WEB", + clientVersion: this._continuation.clientVersion + }, + user: {}, + request: {} + } + }) + }); + + let contents = JSON.parse(nextPage)?.onResponseReceivedActions[0]?.appendContinuationItemsAction?.continuationItems + if(!contents) return [] + + let playlist_videos = getPlaylistVideos(contents, limit) + this._continuation.token = getContinuationToken(contents) + + return playlist_videos + } + + async fetch(max: number = Infinity) { + let 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 >= max) break; + const res = await this.next(); + if (!res.length) break; + } + + return this; + } + + get type(): "playlist" { + return "playlist"; + } + + toJSON() { + return { + id: this.id, + title: this.title, + thumbnail: this.thumbnail, + channel: { + name : this.channel?.name, + id : this.channel?.id, + icon : this.channel?.iconURL() + }, + url: this.url, + videos: this.videos + }; + } +} \ No newline at end of file diff --git a/node-youtube-dl/YouTube/classes/Thumbnail.ts b/node-youtube-dl/YouTube/classes/Thumbnail.ts new file mode 100644 index 0000000..2bead19 --- /dev/null +++ b/node-youtube-dl/YouTube/classes/Thumbnail.ts @@ -0,0 +1,48 @@ +type ThumbnailType = "default" | "hqdefault" | "mqdefault" | "sddefault" | "maxresdefault" | "ultrares"; + +export class Thumbnail { + id?: string; + width!: number; + height!: number; + url?: string; + + constructor(data: any) { + if (!data) throw new Error(`Cannot instantiate the ${this.constructor.name} class without data!`); + + this._patch(data); + } + + private _patch(data: any) { + if (!data) data = {}; + + this.id = data.id || undefined; + this.width = data.width || 0; + this.height = data.height || 0; + 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; + 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}"!`); + return `https://i3.ytimg.com/vi/${this.id}/${id}.jpg`; + } + + toString(): string { + return this.url ? `${this.url}` : ""; + } + + toJSON() { + return { + id: this.id, + width: this.width, + height: this.height, + url: this.url + }; + } +} diff --git a/node-youtube-dl/YouTube/classes/Video.ts b/node-youtube-dl/YouTube/classes/Video.ts index 8b6a944..2295683 100644 --- a/node-youtube-dl/YouTube/classes/Video.ts +++ b/node-youtube-dl/YouTube/classes/Video.ts @@ -1,3 +1,6 @@ +import { Channel } from "./Channel"; +import { Thumbnail } from "./Thumbnail"; + interface VideoOptions { id?: string; url? : string; @@ -7,8 +10,17 @@ interface VideoOptions { duration: number; uploadedAt?: string; views: number; - thumbnail?: JSON; - channel?: JSON; + thumbnail?: { + id: string | undefined; + width: number; + height: number; + url: string | undefined; + }; + channel?: { + name : string, + id : string, + icon : string + }; videos?: Video[]; type : string; ratings : { @@ -28,8 +40,8 @@ export class Video { duration: number; uploadedAt?: string; views: number; - thumbnail?: JSON; - channel?: JSON; + thumbnail?: Thumbnail; + channel?: Channel; videos?: Video[]; likes: number; dislikes: number; @@ -39,6 +51,7 @@ export class Video { constructor(data : any){ if(!data) throw new Error(`Can not initiate ${this.constructor.name} without data`) + this.id = data.id || undefined; this.title = data.title || undefined; this.description = data.description || undefined; @@ -77,8 +90,12 @@ export class Video { duration: this.duration, duration_formatted: this.durationFormatted, uploadedAt: this.uploadedAt, - thumbnail: this.thumbnail, - channel: this.channel, + thumbnail: this.thumbnail?.toJSON(), + channel: { + name: this.channel?.name as string, + id: this.channel?.id as string, + icon: this.channel?.iconURL() as string + }, views: this.views, type: this.type, tags: this.tags, diff --git a/node-youtube-dl/YouTube/search.ts b/node-youtube-dl/YouTube/search.ts index 8aed895..76d7c7f 100644 --- a/node-youtube-dl/YouTube/search.ts +++ b/node-youtube-dl/YouTube/search.ts @@ -1,10 +1,13 @@ import { url_get } from "./utils/request"; import fs from 'fs' +import { ParseSearchInterface, ParseSearchResult } from "./utils/parser"; +import { Video } from "./classes/Video"; +import { Channel } from "./classes/Channel"; +import { PlayList } from "./classes/Playlist"; -export async function search(url:string, options? : {limit : number}) { +export async function search(url:string, options? : ParseSearchInterface): Promise<(Video | Channel | PlayList)[]> { let body = await url_get(url) - let json_convert = body.split("var ytInitialData = ")[1].split(";")[0] - let result = JSON.parse(json_convert).contents.twoColumnSearchResultsRenderer.primaryContents.sectionListRenderer.contents[0].itemSectionRenderer.contents - console.log(result.length) + let data = ParseSearchResult(body) + return data } \ No newline at end of file diff --git a/node-youtube-dl/YouTube/utils/parser.ts b/node-youtube-dl/YouTube/utils/parser.ts index 13ddda5..5fc1dbf 100644 --- a/node-youtube-dl/YouTube/utils/parser.ts +++ b/node-youtube-dl/YouTube/utils/parser.ts @@ -1,6 +1,211 @@ import { Video } from "../classes/Video"; +import { PlayList } from "../classes/Playlist"; +import { Channel } from "../classes/Channel"; +import { RequestInit } from "node-fetch"; +export interface ParseSearchInterface { + type?: "video" | "playlist" | "channel" | "all"; + limit?: number; + requestOptions?: RequestInit; +} -export function ParseSearchResult(html:string) { - +export function ParseSearchResult(html :string, options? : ParseSearchInterface): (Video | PlayList | Channel)[] { + if(!html) throw new Error('Can\'t parse Search result without data') + if (!options) options = { type: "video", limit: 0 }; + if (!options.type) options.type = "video"; + + let results = [] + let details = [] + let fetched = false; + + try { + let data = html.split("ytInitialData = JSON.parse('")[1].split("');")[0]; + html = data.replace(/\\x([0-9A-F]{2})/gi, (...items) => { + return String.fromCharCode(parseInt(items[1], 16)); + }); + } catch { + /* do nothing */ + } + + try { + details = JSON.parse(html.split('{"itemSectionRenderer":{"contents":')[html.split('{"itemSectionRenderer":{"contents":').length - 1].split(',"continuations":[{')[0]); + fetched = true; + } catch { + /* do nothing */ + } + + if (!fetched) { + try { + details = JSON.parse(html.split('{"itemSectionRenderer":')[html.split('{"itemSectionRenderer":').length - 1].split('},{"continuationItemRenderer":{')[0]).contents; + fetched = true; + } catch { + /* do nothing */ + } + } + + if (!fetched) throw new Error('Failed to Fetch the data') + + for (let i = 0; i < details.length; i++) { + if (typeof options.limit === "number" && options.limit > 0 && results.length >= options.limit) break; + let data = details[i]; + let res; + if (options.type === "all") { + if (!!data.videoRenderer) options.type = "video"; + else if (!!data.channelRenderer) options.type = "channel"; + else if (!!data.playlistRenderer) options.type = "playlist"; + else continue; + } + + if (options.type === "video") { + const parsed = parseVideo(data); + if (!parsed) continue; + res = parsed; + } else if (options.type === "channel") { + const parsed = parseChannel(data); + if (!parsed) continue; + res = parsed; + } else if (options.type === "playlist") { + const parsed = parsePlaylist(data); + if (!parsed) continue; + res = parsed; + } + + results.push(res); + } + +return results as (Video | Channel | PlayList)[]; +} + +export function getPlaylistVideos(data:any, limit : number = Infinity) : Video[] { + const videos = []; + + for (let i = 0; i < data.length; i++) { + if (limit === videos.length) break; + const info = data[i].playlistVideoRenderer; + if (!info || !info.shortBylineText) continue; + + videos.push( + new Video({ + id: info.videoId, + index: parseInt(info.index?.simpleText) || 0, + duration: parseDuration(info.lengthText?.simpleText) || 0, + duration_raw: info.lengthText?.simpleText ?? "0:00", + thumbnail: { + id: info.videoId, + url: info.thumbnail.thumbnails[info.thumbnail.thumbnails.length - 1].url, + height: info.thumbnail.thumbnails[info.thumbnail.thumbnails.length - 1].height, + width: info.thumbnail.thumbnails[info.thumbnail.thumbnails.length - 1].width + }, + title: info.title.runs[0].text, + channel: { + id: info.shortBylineText.runs[0].navigationEndpoint.browseEndpoint.browseId || undefined, + name: info.shortBylineText.runs[0].text || undefined, + url: `https://www.youtube.com${info.shortBylineText.runs[0].navigationEndpoint.browseEndpoint.canonicalBaseUrl || info.shortBylineText.runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url}`, + icon: undefined + } + }) + ); + } + return videos +} + +export function parseDuration(duration: string): number { + duration ??= "0:00"; + const args = duration.split(":"); + let dur = 0; + + switch (args.length) { + case 3: + dur = parseInt(args[0]) * 60 * 60 + parseInt(args[1]) * 60 + parseInt(args[2]); + break; + case 2: + dur = parseInt(args[0]) * 60 + parseInt(args[1]); + break; + default: + dur = parseInt(args[0]); + } + + return dur; +} + +export function getContinuationToken(data:any): string { + const continuationToken = data.find((x: any) => Object.keys(x)[0] === "continuationItemRenderer")?.continuationItemRenderer.continuationEndpoint?.continuationCommand?.token; + return continuationToken; +} + +export function parseChannel(data?: any): Channel | void { + if (!data || !data.channelRenderer) return; + const badge = data.channelRenderer.ownerBadges && data.channelRenderer.ownerBadges[0]; + let url = `https://www.youtube.com${data.channelRenderer.navigationEndpoint.browseEndpoint.canonicalBaseUrl || data.channelRenderer.navigationEndpoint.commandMetadata.webCommandMetadata.url}`; + let res = new Channel({ + id: data.channelRenderer.channelId, + name: data.channelRenderer.title.simpleText, + icon: data.channelRenderer.thumbnail.thumbnails[data.channelRenderer.thumbnail.thumbnails.length - 1], + url: url, + verified: Boolean(badge?.metadataBadgeRenderer?.style?.toLowerCase().includes("verified")), + subscribers: data.channelRenderer.subscriberCountText.simpleText + }); + + return res; +} + +export function parseVideo(data?: any): Video | void { + if (!data || !data.videoRenderer) return; + + const badge = data.videoRenderer.ownerBadges && data.videoRenderer.ownerBadges[0]; + let res = new Video({ + id: data.videoRenderer.videoId, + url: `https://www.youtube.com/watch?v=${data.videoRenderer.videoId}`, + title: data.videoRenderer.title.runs[0].text, + description: data.videoRenderer.descriptionSnippet && data.videoRenderer.descriptionSnippet.runs[0] ? data.videoRenderer.descriptionSnippet.runs[0].text : "", + duration: data.videoRenderer.lengthText ? parseDuration(data.videoRenderer.lengthText.simpleText) : 0, + duration_raw: data.videoRenderer.lengthText ? data.videoRenderer.lengthText.simpleText : null, + thumbnail: { + id: data.videoRenderer.videoId, + url: data.videoRenderer.thumbnail.thumbnails[data.videoRenderer.thumbnail.thumbnails.length - 1].url, + height: data.videoRenderer.thumbnail.thumbnails[data.videoRenderer.thumbnail.thumbnails.length - 1].height, + width: data.videoRenderer.thumbnail.thumbnails[data.videoRenderer.thumbnail.thumbnails.length - 1].width + }, + channel: { + id: data.videoRenderer.ownerText.runs[0].navigationEndpoint.browseEndpoint.browseId || null, + name: data.videoRenderer.ownerText.runs[0].text || null, + url: `https://www.youtube.com${data.videoRenderer.ownerText.runs[0].navigationEndpoint.browseEndpoint.canonicalBaseUrl || data.videoRenderer.ownerText.runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url}`, + icon: { + url: data.videoRenderer.channelThumbnailSupportedRenderers.channelThumbnailWithLinkRenderer.thumbnail.thumbnails[0].url, + width: data.videoRenderer.channelThumbnailSupportedRenderers.channelThumbnailWithLinkRenderer.thumbnail.thumbnails[0].width, + height: data.videoRenderer.channelThumbnailSupportedRenderers.channelThumbnailWithLinkRenderer.thumbnail.thumbnails[0].height + }, + verified: Boolean(badge?.metadataBadgeRenderer?.style?.toLowerCase().includes("verified")) + }, + uploadedAt: data.videoRenderer.publishedTimeText?.simpleText ?? null, + views: data.videoRenderer.viewCountText?.simpleText?.replace(/[^0-9]/g, "") ?? 0 + }); + + return res; +} + +export function parsePlaylist(data?: any): PlayList | void { + if (!data.playlistRenderer) return; + + const res = new PlayList( + { + id: data.playlistRenderer.playlistId, + title: data.playlistRenderer.title.simpleText, + thumbnail: { + id: data.playlistRenderer.playlistId, + url: data.playlistRenderer.thumbnails[0].thumbnails[data.playlistRenderer.thumbnails[0].thumbnails.length - 1].url, + height: data.playlistRenderer.thumbnails[0].thumbnails[data.playlistRenderer.thumbnails[0].thumbnails.length - 1].height, + width: data.playlistRenderer.thumbnails[0].thumbnails[data.playlistRenderer.thumbnails[0].thumbnails.length - 1].width + }, + channel: { + id: data.playlistRenderer.shortBylineText.runs[0].navigationEndpoint.browseEndpoint.browseId, + name: data.playlistRenderer.shortBylineText.runs[0].text, + url: `https://www.youtube.com${data.playlistRenderer.shortBylineText.runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url}` + }, + videos: parseInt(data.playlistRenderer.videoCount.replace(/[^0-9]/g, "")) + }, + true + ); + + return res; } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d2f5cc0..81a5c9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,63 +9,17 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "got": "^11.8.2", "node-fetch": "^2.6.1" }, "devDependencies": { "@types/node-fetch": "^2.5.12" } }, - "node_modules/@sindresorhus/is": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.1.tgz", - "integrity": "sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "dependencies": { - "defer-to-connect": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@types/cacheable-request": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", - "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", - "dependencies": { - "@types/http-cache-semantics": "*", - "@types/keyv": "*", - "@types/node": "*", - "@types/responselike": "*" - } - }, - "node_modules/@types/http-cache-semantics": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", - "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" - }, - "node_modules/@types/keyv": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.2.tgz", - "integrity": "sha512-/FvAK2p4jQOaJ6CGDHJTqZcUtbZe820qIeTg7o0Shg7drB4JHeL+V/dhSaly7NXx6u8eSee+r7coT+yuJEvDLg==", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/node": { "version": "16.4.13", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.4.13.tgz", - "integrity": "sha512-bLL69sKtd25w7p1nvg9pigE4gtKVpGTPojBFLMkGHXuUgap2sLqQt2qUnqmVCDfzGUL0DRNZP+1prIZJbMeAXg==" + "integrity": "sha512-bLL69sKtd25w7p1nvg9pigE4gtKVpGTPojBFLMkGHXuUgap2sLqQt2qUnqmVCDfzGUL0DRNZP+1prIZJbMeAXg==", + "dev": true }, "node_modules/@types/node-fetch": { "version": "2.5.12", @@ -77,53 +31,12 @@ "form-data": "^3.0.0" } }, - "node_modules/@types/responselike": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", - "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, - "node_modules/cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "engines": { - "node": ">=10.6.0" - } - }, - "node_modules/cacheable-request": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", - "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "dependencies": { - "mimic-response": "^1.0.0" - } - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -136,39 +49,6 @@ "node": ">= 0.8" } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "engines": { - "node": ">=10" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -178,14 +58,6 @@ "node": ">=0.4.0" } }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/form-data": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", @@ -200,82 +72,6 @@ "node": ">= 6" } }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/got": { - "version": "11.8.2", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz", - "integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==", - "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.1", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=10.19.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" - }, - "node_modules/http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" - }, - "node_modules/keyv": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", - "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "engines": { - "node": ">=8" - } - }, "node_modules/mime-db": { "version": "1.49.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", @@ -297,14 +93,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "engines": { - "node": ">=4" - } - }, "node_modules/node-fetch": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", @@ -312,115 +100,14 @@ "engines": { "node": "4.x || >=6.0.0" } - }, - "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/resolve-alpn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.0.tgz", - "integrity": "sha512-e4FNQs+9cINYMO5NMFc6kOUCdohjqFPSgMuwuZAOUWqrfWsen+Yjy5qZFkV5K7VO7tFSLKcUL97olkED7sCBHA==" - }, - "node_modules/responselike": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", - "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", - "dependencies": { - "lowercase-keys": "^2.0.0" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" } }, "dependencies": { - "@sindresorhus/is": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.1.tgz", - "integrity": "sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g==" - }, - "@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "requires": { - "defer-to-connect": "^2.0.0" - } - }, - "@types/cacheable-request": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", - "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", - "requires": { - "@types/http-cache-semantics": "*", - "@types/keyv": "*", - "@types/node": "*", - "@types/responselike": "*" - } - }, - "@types/http-cache-semantics": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", - "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" - }, - "@types/keyv": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.2.tgz", - "integrity": "sha512-/FvAK2p4jQOaJ6CGDHJTqZcUtbZe820qIeTg7o0Shg7drB4JHeL+V/dhSaly7NXx6u8eSee+r7coT+yuJEvDLg==", - "requires": { - "@types/node": "*" - } - }, "@types/node": { "version": "16.4.13", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.4.13.tgz", - "integrity": "sha512-bLL69sKtd25w7p1nvg9pigE4gtKVpGTPojBFLMkGHXuUgap2sLqQt2qUnqmVCDfzGUL0DRNZP+1prIZJbMeAXg==" + "integrity": "sha512-bLL69sKtd25w7p1nvg9pigE4gtKVpGTPojBFLMkGHXuUgap2sLqQt2qUnqmVCDfzGUL0DRNZP+1prIZJbMeAXg==", + "dev": true }, "@types/node-fetch": { "version": "2.5.12", @@ -432,47 +119,12 @@ "form-data": "^3.0.0" } }, - "@types/responselike": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", - "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", - "requires": { - "@types/node": "*" - } - }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, - "cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==" - }, - "cacheable-request": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", - "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - } - }, - "clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "requires": { - "mimic-response": "^1.0.0" - } - }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -482,40 +134,12 @@ "delayed-stream": "~1.0.0" } }, - "decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "requires": { - "mimic-response": "^3.1.0" - }, - "dependencies": { - "mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" - } - } - }, - "defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" - }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "requires": { - "once": "^1.4.0" - } - }, "form-data": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", @@ -527,64 +151,6 @@ "mime-types": "^2.1.12" } }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "requires": { - "pump": "^3.0.0" - } - }, - "got": { - "version": "11.8.2", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz", - "integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==", - "requires": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.1", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - } - }, - "http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" - }, - "http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "requires": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - } - }, - "json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" - }, - "keyv": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", - "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", - "requires": { - "json-buffer": "3.0.1" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" - }, "mime-db": { "version": "1.49.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", @@ -600,65 +166,10 @@ "mime-db": "1.49.0" } }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" - }, "node-fetch": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" - }, - "normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==" - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" - }, - "resolve-alpn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.0.tgz", - "integrity": "sha512-e4FNQs+9cINYMO5NMFc6kOUCdohjqFPSgMuwuZAOUWqrfWsen+Yjy5qZFkV5K7VO7tFSLKcUL97olkED7sCBHA==" - }, - "responselike": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", - "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", - "requires": { - "lowercase-keys": "^2.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" } } } diff --git a/package.json b/package.json index cd5f8af..27a6b00 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,6 @@ }, "homepage": "https://github.com/killer069/node-youtube-dl#readme", "dependencies": { - "got": "^11.8.2", "node-fetch": "^2.6.1" }, "devDependencies": { From 04770cc9291dd2a4018a89bf7eca4bcf50f5eba8 Mon Sep 17 00:00:00 2001 From: killer069 <65385476+killer069@users.noreply.github.com> Date: Fri, 13 Aug 2021 13:16:34 +0530 Subject: [PATCH 07/15] YouTube : Completed --- node-youtube-dl/YouTube/classes/Channel.ts | 6 +- node-youtube-dl/YouTube/classes/Playlist.ts | 38 ++++- node-youtube-dl/YouTube/classes/Thumbnail.ts | 4 +- node-youtube-dl/YouTube/classes/Video.ts | 11 +- node-youtube-dl/YouTube/index.ts | 4 +- node-youtube-dl/YouTube/search.ts | 25 +++- node-youtube-dl/YouTube/utils/extractor.ts | 146 ++++++++++++++++++- node-youtube-dl/YouTube/utils/index.ts | 2 +- node-youtube-dl/YouTube/utils/parser.ts | 51 ++----- node-youtube-dl/index.ts | 4 +- 10 files changed, 215 insertions(+), 76 deletions(-) diff --git a/node-youtube-dl/YouTube/classes/Channel.ts b/node-youtube-dl/YouTube/classes/Channel.ts index c0fa6bc..d1bafa3 100644 --- a/node-youtube-dl/YouTube/classes/Channel.ts +++ b/node-youtube-dl/YouTube/classes/Channel.ts @@ -6,10 +6,10 @@ export interface ChannelIconInterface { export class Channel { name?: string; - verified!: boolean; + verified?: boolean; id?: string; url?: string; - icon!: ChannelIconInterface; + icon?: ChannelIconInterface; subscribers?: string; constructor(data: any) { @@ -36,7 +36,7 @@ export class Channel { */ 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; + if (!this.icon?.url) return undefined; const def = this.icon.url.split("=s")[1].split("-c")[0]; return this.icon.url.replace(`=s${def}-c`, `=s${options.size}-c`); } diff --git a/node-youtube-dl/YouTube/classes/Playlist.ts b/node-youtube-dl/YouTube/classes/Playlist.ts index f321981..46a48e1 100644 --- a/node-youtube-dl/YouTube/classes/Playlist.ts +++ b/node-youtube-dl/YouTube/classes/Playlist.ts @@ -1,41 +1,47 @@ -import { getContinuationToken, getPlaylistVideos } from "../utils/parser"; +import { getPlaylistVideos, getContinuationToken } from "../utils/extractor"; import { url_get } from "../utils/request"; import { Thumbnail } from "./Thumbnail"; import { Channel } from "./Channel"; import { Video } from "./Video"; +import fs from 'fs' const BASE_API = "https://www.youtube.com/youtubei/v1/browse?key="; export class PlayList{ id?: string; title?: string; - videoCount!: number; + videoCount?: number; lastUpdate?: string; views?: number; url?: string; link?: string; channel?: Channel; thumbnail?: Thumbnail; - videos!: []; + videos?: []; + private fetched_videos : Map private _continuation: { api?: string; token?: string; clientVersion?: string } = {}; + private __count : number constructor(data : any, searchResult : Boolean = 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) } private __patch(data:any){ this.id = data.id || undefined; + this.url = data.url || undefined; this.title = data.title || undefined; this.videoCount = data.videoCount || 0; this.lastUpdate = data.lastUpdate || undefined; this.views = data.views || 0; - this.url = data.url || undefined; this.link = data.link || undefined; 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._continuation.api = data.continuation?.api ?? undefined; this._continuation.token = data.continuation?.token ?? undefined; this._continuation.clientVersion = data.continuation?.clientVersion ?? ""; @@ -43,12 +49,12 @@ export class PlayList{ 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; this.thumbnail = data.thumbnail || undefined; this.channel = data.channel || undefined; this.videos = []; this.videoCount = data.videos || 0; - this.url = this.id ? `https://www.youtube.com/playlist?list=${this.id}` : undefined; this.link = undefined; this.lastUpdate = undefined; this.views = 0; @@ -79,8 +85,8 @@ export class PlayList{ 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 } @@ -90,7 +96,8 @@ export class PlayList{ if (max < 1) max = Infinity; while (typeof this._continuation.token === "string" && this._continuation.token.length) { - if (this.videos.length >= max) break; + if (this.videos?.length as number >= max) break; + this.__count++ const res = await this.next(); if (!res.length) break; } @@ -102,6 +109,21 @@ export class PlayList{ return "playlist"; } + page(number : number): Video[]{ + if(!number) throw new Error('Given 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_videos(){ + let page_number: number = this.total_pages + return (page_number - 1) * 100 + (this.fetched_videos.get(`page${page_number}`) as Video[]).length + } + toJSON() { return { id: this.id, diff --git a/node-youtube-dl/YouTube/classes/Thumbnail.ts b/node-youtube-dl/YouTube/classes/Thumbnail.ts index 2bead19..4ea8db5 100644 --- a/node-youtube-dl/YouTube/classes/Thumbnail.ts +++ b/node-youtube-dl/YouTube/classes/Thumbnail.ts @@ -2,8 +2,8 @@ type ThumbnailType = "default" | "hqdefault" | "mqdefault" | "sddefault" | "maxr export class Thumbnail { id?: string; - width!: number; - height!: number; + width?: number; + height?: number; url?: string; constructor(data: any) { diff --git a/node-youtube-dl/YouTube/classes/Video.ts b/node-youtube-dl/YouTube/classes/Video.ts index 2295683..bb1969e 100644 --- a/node-youtube-dl/YouTube/classes/Video.ts +++ b/node-youtube-dl/YouTube/classes/Video.ts @@ -12,8 +12,8 @@ interface VideoOptions { views: number; thumbnail?: { id: string | undefined; - width: number; - height: number; + width: number | undefined ; + height: number | undefined; url: string | undefined; }; channel?: { @@ -34,6 +34,7 @@ interface VideoOptions { export class Video { id?: string; + url? : string; title?: string; description?: string; durationFormatted: string; @@ -53,6 +54,7 @@ export class Video { 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.title = data.title || undefined; this.description = data.description || undefined; this.durationFormatted = data.duration_raw || "0:00"; @@ -68,11 +70,6 @@ export class Video { this.tags = data.tags || []; } - get url(){ - if(!this.id) return undefined - else return `https://www.youtube.com/watch?v=${this.id}`; - } - get type(): "video" { return "video"; } diff --git a/node-youtube-dl/YouTube/index.ts b/node-youtube-dl/YouTube/index.ts index 128af5e..ea8738c 100644 --- a/node-youtube-dl/YouTube/index.ts +++ b/node-youtube-dl/YouTube/index.ts @@ -1 +1,3 @@ -export { search } from './search' \ No newline at end of file +export { search } from './search' + +export * from './utils' \ No newline at end of file diff --git a/node-youtube-dl/YouTube/search.ts b/node-youtube-dl/YouTube/search.ts index 76d7c7f..0d597d7 100644 --- a/node-youtube-dl/YouTube/search.ts +++ b/node-youtube-dl/YouTube/search.ts @@ -6,8 +6,29 @@ import { Channel } from "./classes/Channel"; import { PlayList } from "./classes/Playlist"; -export async function search(url:string, options? : ParseSearchInterface): Promise<(Video | Channel | PlayList)[]> { +enum SearchType { + Video = 'EgIQAQ%253D%253D', + PlayList = 'EgIQAw%253D%253D', + Channel = 'EgIQAg%253D%253D', +} + +export async function search(search :string, options? : ParseSearchInterface): Promise<(Video | Channel | PlayList)[]> { + let url = 'https://www.youtube.com/results?search_query=' + search.replaceAll(' ', '+') + if(!url.match('&sp=')){ + url += '&sp=' + switch(options?.type){ + case 'channel': + url += SearchType.Channel + break + case 'playlist': + url += SearchType.PlayList + break + case 'video': + url += SearchType.Video + break + } + } let body = await url_get(url) - let data = ParseSearchResult(body) + let data = ParseSearchResult(body, options) return data } \ No newline at end of file diff --git a/node-youtube-dl/YouTube/utils/extractor.ts b/node-youtube-dl/YouTube/utils/extractor.ts index 56e79e1..8ea2078 100644 --- a/node-youtube-dl/YouTube/utils/extractor.ts +++ b/node-youtube-dl/YouTube/utils/extractor.ts @@ -1,10 +1,24 @@ import { url_get } from './request' import { format_decipher, js_tokens } from './cipher' +import { Video } from '../classes/Video' +import { RequestInit } from 'node-fetch' +import { PlayList } from '../classes/Playlist' +import fs from 'fs' +const DEFAULT_API_KEY = "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8"; +const youtube_url = /https:\/\/www.youtube.com\//g +const video_pattern = /^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$/; -export async function yt_initial_data(url : string){ +export interface PlaylistOptions { + limit?: number; + requestOptions?: RequestInit; +} + +export async function video_basic_info(url : string){ + if(!url.match(youtube_url) || !url.match(video_pattern)) throw new Error('This is not a YouTube URL') let body = await url_get(url) let player_response = JSON.parse(body.split("var ytInitialPlayerResponse = ")[1].split(";")[0]) + if(player_response.playabilityStatus.status === 'ERROR') throw new Error(`While getting info from url \n ${player_response.playabilityStatus.reason}`) let response = JSON.parse(body.split("var ytInitialData = ")[1].split(";")[0]) let html5player = 'https://www.youtube.com' + body.split('"jsUrl":"')[1].split('"')[0] let format = [] @@ -12,14 +26,18 @@ export async function yt_initial_data(url : string){ format.push(...player_response.streamingData.adaptiveFormats) let vid = player_response.videoDetails let microformat = player_response.microformat.playerMicroformatRenderer - let video_details = { + let video_details = new Video ({ id : vid.videoId, url : 'https://www.youtube.com/watch?v=' + vid.videoId, title : vid.title, description : vid.shortDescription, duration : vid.lengthSeconds, uploadedDate : microformat.publishDate, - thumbnail : `https://i.ytimg.com/vi/${vid.videoId}/maxresdefault.jpg`, + thumbnail : { + width : vid.thumbnail.thumbnails[vid.thumbnail.thumbnails.length - 1].width, + height : vid.thumbnail.thumbnails[vid.thumbnail.thumbnails.length - 1].height, + url : `https://i.ytimg.com/vi/${vid.videoId}/maxresdefault.jpg` + }, channel : { name : vid.author, id : vid.channelId, @@ -30,19 +48,18 @@ export async function yt_initial_data(url : string){ averageRating : vid.averageRating, live : vid.isLiveContent, private : vid.isPrivate - } - let final = { + }) + return { player_response, response, html5player, format, video_details } - return final } -export async function yt_deciphered_data(url : string) { - let data = await yt_initial_data(url) +export async function video_info(url : string) { + let data = await video_basic_info(url) if(data.format[0].signatureCipher || data.format[0].cipher){ data.format = await format_decipher(data.format, data.html5player) return data @@ -51,3 +68,116 @@ export async function yt_deciphered_data(url : string) { return data } } + +export async function playlist_info(url : string , options? : PlaylistOptions) { + if (!options) options = { limit: 100, requestOptions: {} }; + if(!options.limit) options.limit = 100 + if (!url || typeof url !== "string") throw new Error(`Expected playlist url, received ${typeof url}!`); + if(url.search('(\\?|\\&)list\\=') === -1) throw new Error('This is not a PlayList URL') + + let Playlist_id = url.split('list=')[1].split('&')[0] + let new_url = `https://www.youtube.com/playlist?list=${Playlist_id}` + + let body = await url_get(new_url) + let response = JSON.parse(body.split("var ytInitialData = ")[1].split(";")[0]) + if(response.alerts && response.alerts[0].alertRenderer.type === 'ERROR') throw new Error(`While parsing playlist url\n ${response.alerts[0].alertRenderer.text.runs[0].text}`) + + let rawJSON = `${body.split('{"playlistVideoListRenderer":{"contents":')[1].split('}],"playlistId"')[0]}}]`; + let parsed = JSON.parse(rawJSON); + let playlistDetails = JSON.parse(body.split('{"playlistSidebarRenderer":')[1].split("}};")[0]).items; + + let API_KEY = body.split('INNERTUBE_API_KEY":"')[1]?.split('"')[0] ?? body.split('innertubeApiKey":"')[1]?.split('"')[0] ?? DEFAULT_API_KEY; + let videos = getPlaylistVideos(parsed, options.limit); + + let data = playlistDetails[0].playlistSidebarPrimaryInfoRenderer; + if (!data.title.runs || !data.title.runs.length) return undefined; + + let author = playlistDetails[1]?.playlistSidebarSecondaryInfoRenderer.videoOwner; + let views = data.stats.length === 3 ? data.stats[1].simpleText.replace(/[^0-9]/g, "") : 0; + let lastUpdate = data.stats.find((x: any) => "runs" in x && x["runs"].find((y: any) => y.text.toLowerCase().includes("last update")))?.runs.pop()?.text ?? null; + let videosCount = data.stats[0].runs[0].text.replace(/[^0-9]/g, "") || 0; + + let res = new PlayList({ + continuation: { + api: API_KEY, + token: getContinuationToken(parsed), + clientVersion: body.split('"INNERTUBE_CONTEXT_CLIENT_VERSION":"')[1]?.split('"')[0] ?? body.split('"innertube_context_client_version":"')[1]?.split('"')[0] ?? "" + }, + id: data.title.runs[0].navigationEndpoint.watchEndpoint.playlistId, + title: data.title.runs[0].text, + videoCount: parseInt(videosCount) || 0, + lastUpdate: lastUpdate, + views: parseInt(views) || 0, + videos: videos, + url: `https://www.youtube.com/playlist?list=${data.title.runs[0].navigationEndpoint.watchEndpoint.playlistId}`, + link: `https://www.youtube.com${data.title.runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url}`, + author: author + ? { + name: author.videoOwnerRenderer.title.runs[0].text, + id: author.videoOwnerRenderer.title.runs[0].navigationEndpoint.browseEndpoint.browseId, + url: `https://www.youtube.com${author.videoOwnerRenderer.navigationEndpoint.commandMetadata.webCommandMetadata.url || author.videoOwnerRenderer.navigationEndpoint.browseEndpoint.canonicalBaseUrl}`, + icon: author.videoOwnerRenderer.thumbnail.thumbnails.length ? author.videoOwnerRenderer.thumbnail.thumbnails[author.videoOwnerRenderer.thumbnail.thumbnails.length - 1].url : null + } + : {}, + thumbnail: data.thumbnailRenderer.playlistVideoThumbnailRenderer?.thumbnail.thumbnails.length ? data.thumbnailRenderer.playlistVideoThumbnailRenderer.thumbnail.thumbnails[data.thumbnailRenderer.playlistVideoThumbnailRenderer.thumbnail.thumbnails.length - 1].url : null + }); + return res; +} + +export function getPlaylistVideos(data:any, limit : number = Infinity) : Video[] { + const videos = []; + + for (let i = 0; i < data.length; i++) { + if (limit === videos.length) break; + const info = data[i].playlistVideoRenderer; + if (!info || !info.shortBylineText) continue; + + videos.push( + new Video({ + id: info.videoId, + index: parseInt(info.index?.simpleText) || 0, + duration: parseDuration(info.lengthText?.simpleText) || 0, + duration_raw: info.lengthText?.simpleText ?? "0:00", + thumbnail: { + id: info.videoId, + url: info.thumbnail.thumbnails[info.thumbnail.thumbnails.length - 1].url, + height: info.thumbnail.thumbnails[info.thumbnail.thumbnails.length - 1].height, + width: info.thumbnail.thumbnails[info.thumbnail.thumbnails.length - 1].width + }, + title: info.title.runs[0].text, + channel: { + id: info.shortBylineText.runs[0].navigationEndpoint.browseEndpoint.browseId || undefined, + name: info.shortBylineText.runs[0].text || undefined, + url: `https://www.youtube.com${info.shortBylineText.runs[0].navigationEndpoint.browseEndpoint.canonicalBaseUrl || info.shortBylineText.runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url}`, + icon: undefined + } + }) + ); + } + return videos +} + +function parseDuration(duration: string): number { + duration ??= "0:00"; + const args = duration.split(":"); + let dur = 0; + + switch (args.length) { + case 3: + dur = parseInt(args[0]) * 60 * 60 + parseInt(args[1]) * 60 + parseInt(args[2]); + break; + case 2: + dur = parseInt(args[0]) * 60 + parseInt(args[1]); + break; + default: + dur = parseInt(args[0]); + } + + return dur; +} + + +export function getContinuationToken(data:any): string { + const continuationToken = data.find((x: any) => Object.keys(x)[0] === "continuationItemRenderer")?.continuationItemRenderer.continuationEndpoint?.continuationCommand?.token; + return continuationToken; +} \ No newline at end of file diff --git a/node-youtube-dl/YouTube/utils/index.ts b/node-youtube-dl/YouTube/utils/index.ts index f09bebc..52ec51e 100644 --- a/node-youtube-dl/YouTube/utils/index.ts +++ b/node-youtube-dl/YouTube/utils/index.ts @@ -1 +1 @@ -export { yt_initial_data, yt_deciphered_data } from './extractor' \ No newline at end of file +export { video_basic_info, video_info, playlist_info } from './extractor' \ No newline at end of file diff --git a/node-youtube-dl/YouTube/utils/parser.ts b/node-youtube-dl/YouTube/utils/parser.ts index 5fc1dbf..ab8c702 100644 --- a/node-youtube-dl/YouTube/utils/parser.ts +++ b/node-youtube-dl/YouTube/utils/parser.ts @@ -2,6 +2,7 @@ import { Video } from "../classes/Video"; import { PlayList } from "../classes/Playlist"; import { Channel } from "../classes/Channel"; import { RequestInit } from "node-fetch"; +import fs from 'fs' export interface ParseSearchInterface { type?: "video" | "playlist" | "channel" | "all"; @@ -31,7 +32,7 @@ export function ParseSearchResult(html :string, options? : ParseSearchInterface) details = JSON.parse(html.split('{"itemSectionRenderer":{"contents":')[html.split('{"itemSectionRenderer":{"contents":').length - 1].split(',"continuations":[{')[0]); fetched = true; } catch { - /* do nothing */ + /* Do nothing*/ } if (!fetched) { @@ -76,40 +77,7 @@ export function ParseSearchResult(html :string, options? : ParseSearchInterface) return results as (Video | Channel | PlayList)[]; } -export function getPlaylistVideos(data:any, limit : number = Infinity) : Video[] { - const videos = []; - - for (let i = 0; i < data.length; i++) { - if (limit === videos.length) break; - const info = data[i].playlistVideoRenderer; - if (!info || !info.shortBylineText) continue; - - videos.push( - new Video({ - id: info.videoId, - index: parseInt(info.index?.simpleText) || 0, - duration: parseDuration(info.lengthText?.simpleText) || 0, - duration_raw: info.lengthText?.simpleText ?? "0:00", - thumbnail: { - id: info.videoId, - url: info.thumbnail.thumbnails[info.thumbnail.thumbnails.length - 1].url, - height: info.thumbnail.thumbnails[info.thumbnail.thumbnails.length - 1].height, - width: info.thumbnail.thumbnails[info.thumbnail.thumbnails.length - 1].width - }, - title: info.title.runs[0].text, - channel: { - id: info.shortBylineText.runs[0].navigationEndpoint.browseEndpoint.browseId || undefined, - name: info.shortBylineText.runs[0].text || undefined, - url: `https://www.youtube.com${info.shortBylineText.runs[0].navigationEndpoint.browseEndpoint.canonicalBaseUrl || info.shortBylineText.runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url}`, - icon: undefined - } - }) - ); - } - return videos -} - -export function parseDuration(duration: string): number { +function parseDuration(duration: string): number { duration ??= "0:00"; const args = duration.split(":"); let dur = 0; @@ -128,11 +96,6 @@ export function parseDuration(duration: string): number { return dur; } -export function getContinuationToken(data:any): string { - const continuationToken = data.find((x: any) => Object.keys(x)[0] === "continuationItemRenderer")?.continuationItemRenderer.continuationEndpoint?.continuationCommand?.token; - return continuationToken; -} - export function parseChannel(data?: any): Channel | void { if (!data || !data.channelRenderer) return; const badge = data.channelRenderer.ownerBadges && data.channelRenderer.ownerBadges[0]; @@ -140,10 +103,14 @@ export function parseChannel(data?: any): Channel | void { let res = new Channel({ id: data.channelRenderer.channelId, name: data.channelRenderer.title.simpleText, - icon: data.channelRenderer.thumbnail.thumbnails[data.channelRenderer.thumbnail.thumbnails.length - 1], + icon: { + url : data.channelRenderer.thumbnail.thumbnails[data.channelRenderer.thumbnail.thumbnails.length - 1].url.replace('//', 'https://'), + width : data.channelRenderer.thumbnail.thumbnails[data.channelRenderer.thumbnail.thumbnails.length - 1].width, + height: data.channelRenderer.thumbnail.thumbnails[data.channelRenderer.thumbnail.thumbnails.length - 1].height + }, url: url, verified: Boolean(badge?.metadataBadgeRenderer?.style?.toLowerCase().includes("verified")), - subscribers: data.channelRenderer.subscriberCountText.simpleText + subscribers: (data.channelRenderer.subscriberCountText?.simpleText) ? data.channelRenderer.subscriberCountText.simpleText : '0 subscribers' }); return res; diff --git a/node-youtube-dl/index.ts b/node-youtube-dl/index.ts index 18cc5d6..4b7e624 100644 --- a/node-youtube-dl/index.ts +++ b/node-youtube-dl/index.ts @@ -1,8 +1,8 @@ -import { search } from "./YouTube/"; +import { playlist_info } from "./YouTube"; let main = async() => { let time_start = Date.now() - await search('https://www.youtube.com/results?search_query=Hello+Neghibour') + let playlist = await playlist_info('https://www.youtube.com/watch?v=bM7SZ5SBzyY&list=PLzkuLC6Yvumv_Rd5apfPRWEcjf9b1JRnq') let time_end = Date.now() console.log(`Time Taken : ${(time_end - time_start)/1000} seconds`) } From 06cc44d4852a039aae0536fab3611004f65a755d Mon Sep 17 00:00:00 2001 From: killer069 <65385476+killer069@users.noreply.github.com> Date: Fri, 13 Aug 2021 14:58:57 +0530 Subject: [PATCH 08/15] YouTube examples --- node-youtube-dl/YouTube/README.md | 76 +++++++++++++++++++++ node-youtube-dl/YouTube/classes/Playlist.ts | 3 +- node-youtube-dl/YouTube/search.ts | 1 - node-youtube-dl/YouTube/utils/cipher.ts | 8 ++- node-youtube-dl/YouTube/utils/extractor.ts | 23 ++----- node-youtube-dl/YouTube/utils/parser.ts | 3 - node-youtube-dl/index.ts | 6 +- 7 files changed, 93 insertions(+), 27 deletions(-) create mode 100644 node-youtube-dl/YouTube/README.md diff --git a/node-youtube-dl/YouTube/README.md b/node-youtube-dl/YouTube/README.md new file mode 100644 index 0000000..65081cf --- /dev/null +++ b/node-youtube-dl/YouTube/README.md @@ -0,0 +1,76 @@ +# YouTube Downloader/Search +### Downloades youtube videos, playlist and also searches song + +This is a light-weight youtube downloader and searcher. + +- searches by video, playlist, channel +- obtains audio playback url. + +## Video commands usage :- +### 1. video_basic_info(url : `string`) +*This is what downloader gets first.* +```js +let video = await video_basic_info(url) +``` +### 2. video_info(url : `string`) +*This contains everything with deciphered formats along with video_details.* +```js +let video = await video_info(url) +``` +### 3. formats +*This shows all formats availiable of a video* +```js + let video = await video_info(url) + console.log(video.format) +``` + +## Playlist commands usage :- +### 1. playlist_info(url : `string`) +*This containes every thing about a playlist* +```js +let playlist = await playlist_info(url) //This only fetches first 100 songs from a playlist +``` + +#### 2. playlist.fetch() +*This fetches whole playlist.* +```js +let playlist = await playlist_info(url) //This only fetches first 100 songs from a playlist +await playlist.fetch() // This one fetches all songs from a playlist. +``` +#### 3. playlist.page(page_number : `number`) +*This gives you no. of videos from a page* +> Pages : every 100 songs have been divided into pages. +> So for example: There is 782 songs in a playlist, so there will be 8 pages. + +```js +let playlist = await playlist_info(url) //This only fetches first 100 songs from a playlist +await playlist.fetch() // This one fetches all songs from a playlist. +console.log(playlist.page(1)) // This displays first 100 songs of a playlist +``` +#### 4. playlist.total_videos +*This tells you total no of videos that have been fetched so far.* +```js +let playlist = await playlist_info(url) //This only fetches first 100 songs from a playlist +await playlist.fetch() // This one fetches all songs from a playlist. +console.log(playlist.total_videos) // This displays total no. of videos fetched so far. +``` +#### 5. playlist.videoCount +*This tells total no. of songs in a playlist.* +```js +let playlist = await playlist_info(url) //This only fetches first 100 songs from a playlist +await playlist.fetch() // This one fetches all songs from a playlist. +console.log(playlist.videoCount) // This displays total no. of videos in a playlist +``` + +## Search Command Usage :- +### 1. search(url : `string`, options? : `SearchOptions`) +*This enables all searching mechanism (video, channel, playlist)* +```js +let result = await search('Rick Roll') +console.log(result[0].url) +``` +### SearchOptions +``` +type?: "video" | "playlist" | "channel" | "all"; +limit?: number; +``` diff --git a/node-youtube-dl/YouTube/classes/Playlist.ts b/node-youtube-dl/YouTube/classes/Playlist.ts index 46a48e1..2eb5ec2 100644 --- a/node-youtube-dl/YouTube/classes/Playlist.ts +++ b/node-youtube-dl/YouTube/classes/Playlist.ts @@ -3,7 +3,6 @@ import { url_get } from "../utils/request"; import { Thumbnail } from "./Thumbnail"; import { Channel } from "./Channel"; import { Video } from "./Video"; -import fs from 'fs' const BASE_API = "https://www.youtube.com/youtubei/v1/browse?key="; export class PlayList{ @@ -16,7 +15,7 @@ export class PlayList{ link?: string; channel?: Channel; thumbnail?: Thumbnail; - videos?: []; + private videos?: []; private fetched_videos : Map private _continuation: { api?: string; token?: string; clientVersion?: string } = {}; private __count : number diff --git a/node-youtube-dl/YouTube/search.ts b/node-youtube-dl/YouTube/search.ts index 0d597d7..4df4f6d 100644 --- a/node-youtube-dl/YouTube/search.ts +++ b/node-youtube-dl/YouTube/search.ts @@ -1,5 +1,4 @@ import { url_get } from "./utils/request"; -import fs from 'fs' import { ParseSearchInterface, ParseSearchResult } from "./utils/parser"; import { Video } from "./classes/Video"; import { Channel } from "./classes/Channel"; diff --git a/node-youtube-dl/YouTube/utils/cipher.ts b/node-youtube-dl/YouTube/utils/cipher.ts index e6ebada..0c2cb98 100644 --- a/node-youtube-dl/YouTube/utils/cipher.ts +++ b/node-youtube-dl/YouTube/utils/cipher.ts @@ -153,10 +153,10 @@ function download_url(format: formatOptions, sig : string){ format.url = parsed_url.toString(); } -export async function format_decipher(format: formatOptions[], html5player : string){ +export async function format_decipher(formats: formatOptions[], html5player : string){ let body = await url_get(html5player) let tokens = js_tokens(body) - format.forEach((format) => { + formats.forEach((format) => { let cipher = format.signatureCipher || format.cipher; if(cipher){ Object.assign(format, querystring.parse(cipher)) @@ -167,7 +167,9 @@ export async function format_decipher(format: formatOptions[], html5player : str if(tokens && format.s){ sig = deciper_signature(tokens, format.s) download_url(format, sig) + delete format.s + delete format.sp } }); - return format + return formats } \ No newline at end of file diff --git a/node-youtube-dl/YouTube/utils/extractor.ts b/node-youtube-dl/YouTube/utils/extractor.ts index 8ea2078..25b5bb8 100644 --- a/node-youtube-dl/YouTube/utils/extractor.ts +++ b/node-youtube-dl/YouTube/utils/extractor.ts @@ -3,30 +3,25 @@ import { format_decipher, js_tokens } from './cipher' import { Video } from '../classes/Video' import { RequestInit } from 'node-fetch' import { PlayList } from '../classes/Playlist' -import fs from 'fs' const DEFAULT_API_KEY = "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8"; const youtube_url = /https:\/\/www.youtube.com\//g const video_pattern = /^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$/; -export interface PlaylistOptions { - limit?: number; - requestOptions?: RequestInit; -} - export async function video_basic_info(url : string){ if(!url.match(youtube_url) || !url.match(video_pattern)) throw new Error('This is not a YouTube URL') - let body = await url_get(url) + let video_id = url.split('watch?v=')[1].split('&')[0] + let new_url = 'https://www.youtube.com/watch?v=' + video_id + let body = await url_get(new_url) let player_response = JSON.parse(body.split("var ytInitialPlayerResponse = ")[1].split(";")[0]) if(player_response.playabilityStatus.status === 'ERROR') throw new Error(`While getting info from url \n ${player_response.playabilityStatus.reason}`) - let response = JSON.parse(body.split("var ytInitialData = ")[1].split(";")[0]) let html5player = 'https://www.youtube.com' + body.split('"jsUrl":"')[1].split('"')[0] let format = [] format.push(player_response.streamingData.formats[0]) format.push(...player_response.streamingData.adaptiveFormats) let vid = player_response.videoDetails let microformat = player_response.microformat.playerMicroformatRenderer - let video_details = new Video ({ + let video_details = { id : vid.videoId, url : 'https://www.youtube.com/watch?v=' + vid.videoId, title : vid.title, @@ -48,10 +43,8 @@ export async function video_basic_info(url : string){ averageRating : vid.averageRating, live : vid.isLiveContent, private : vid.isPrivate - }) + } return { - player_response, - response, html5player, format, video_details @@ -69,9 +62,7 @@ export async function video_info(url : string) { } } -export async function playlist_info(url : string , options? : PlaylistOptions) { - if (!options) options = { limit: 100, requestOptions: {} }; - if(!options.limit) options.limit = 100 +export async function playlist_info(url : string) { if (!url || typeof url !== "string") throw new Error(`Expected playlist url, received ${typeof url}!`); if(url.search('(\\?|\\&)list\\=') === -1) throw new Error('This is not a PlayList URL') @@ -87,7 +78,7 @@ export async function playlist_info(url : string , options? : PlaylistOptions) { let playlistDetails = JSON.parse(body.split('{"playlistSidebarRenderer":')[1].split("}};")[0]).items; let API_KEY = body.split('INNERTUBE_API_KEY":"')[1]?.split('"')[0] ?? body.split('innertubeApiKey":"')[1]?.split('"')[0] ?? DEFAULT_API_KEY; - let videos = getPlaylistVideos(parsed, options.limit); + let videos = getPlaylistVideos(parsed, 100); let data = playlistDetails[0].playlistSidebarPrimaryInfoRenderer; if (!data.title.runs || !data.title.runs.length) return undefined; diff --git a/node-youtube-dl/YouTube/utils/parser.ts b/node-youtube-dl/YouTube/utils/parser.ts index ab8c702..b0bdfc1 100644 --- a/node-youtube-dl/YouTube/utils/parser.ts +++ b/node-youtube-dl/YouTube/utils/parser.ts @@ -1,13 +1,10 @@ import { Video } from "../classes/Video"; import { PlayList } from "../classes/Playlist"; import { Channel } from "../classes/Channel"; -import { RequestInit } from "node-fetch"; -import fs from 'fs' export interface ParseSearchInterface { type?: "video" | "playlist" | "channel" | "all"; limit?: number; - requestOptions?: RequestInit; } export function ParseSearchResult(html :string, options? : ParseSearchInterface): (Video | PlayList | Channel)[] { diff --git a/node-youtube-dl/index.ts b/node-youtube-dl/index.ts index 4b7e624..c2176cf 100644 --- a/node-youtube-dl/index.ts +++ b/node-youtube-dl/index.ts @@ -1,8 +1,10 @@ -import { playlist_info } from "./YouTube"; +//This File is in testing stage, everything will change in this +import { playlist_info, video_basic_info, video_info, search } from "./YouTube"; let main = async() => { let time_start = Date.now() - let playlist = await playlist_info('https://www.youtube.com/watch?v=bM7SZ5SBzyY&list=PLzkuLC6Yvumv_Rd5apfPRWEcjf9b1JRnq') + let result = await search('Rick Roll') + console.log(result[0].url) let time_end = Date.now() console.log(`Time Taken : ${(time_end - time_start)/1000} seconds`) } From 92c50752023e4971d5505236ce5def3cffff185b Mon Sep 17 00:00:00 2001 From: killer069 <65385476+killer069@users.noreply.github.com> Date: Fri, 13 Aug 2021 15:00:22 +0530 Subject: [PATCH 09/15] ReadMe updated --- node-youtube-dl/YouTube/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node-youtube-dl/YouTube/README.md b/node-youtube-dl/YouTube/README.md index 65081cf..4314925 100644 --- a/node-youtube-dl/YouTube/README.md +++ b/node-youtube-dl/YouTube/README.md @@ -40,7 +40,7 @@ await playlist.fetch() // This one fetches all songs from a playlist. #### 3. playlist.page(page_number : `number`) *This gives you no. of videos from a page* > Pages : every 100 songs have been divided into pages. -> So for example: There is 782 songs in a playlist, so there will be 8 pages. +> So for example: There are 782 songs in a playlist, so there will be 8 pages. ```js let playlist = await playlist_info(url) //This only fetches first 100 songs from a playlist From 89d36ad0c6e06e1589f1d282681d6f031f12db74 Mon Sep 17 00:00:00 2001 From: killer069 <65385476+killer069@users.noreply.github.com> Date: Fri, 13 Aug 2021 15:01:22 +0530 Subject: [PATCH 10/15] ReadMe updated --- node-youtube-dl/YouTube/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/node-youtube-dl/YouTube/README.md b/node-youtube-dl/YouTube/README.md index 4314925..a785889 100644 --- a/node-youtube-dl/YouTube/README.md +++ b/node-youtube-dl/YouTube/README.md @@ -35,6 +35,7 @@ let playlist = await playlist_info(url) //This only fetches first 100 songs from *This fetches whole playlist.* ```js let playlist = await playlist_info(url) //This only fetches first 100 songs from a playlist + await playlist.fetch() // This one fetches all songs from a playlist. ``` #### 3. playlist.page(page_number : `number`) @@ -44,21 +45,27 @@ await playlist.fetch() // This one fetches all songs from a playlist. ```js let playlist = await playlist_info(url) //This only fetches first 100 songs from a playlist + await playlist.fetch() // This one fetches all songs from a playlist. + console.log(playlist.page(1)) // This displays first 100 songs of a playlist ``` #### 4. playlist.total_videos *This tells you total no of videos that have been fetched so far.* ```js let playlist = await playlist_info(url) //This only fetches first 100 songs from a playlist + await playlist.fetch() // This one fetches all songs from a playlist. + console.log(playlist.total_videos) // This displays total no. of videos fetched so far. ``` #### 5. playlist.videoCount *This tells total no. of songs in a playlist.* ```js let playlist = await playlist_info(url) //This only fetches first 100 songs from a playlist + await playlist.fetch() // This one fetches all songs from a playlist. + console.log(playlist.videoCount) // This displays total no. of videos in a playlist ``` From e6e2f0243631ed4f034065b1261d3d29f0363a78 Mon Sep 17 00:00:00 2001 From: "C++, JS, TS, Python Developer" <65385476+killer069@users.noreply.github.com> Date: Fri, 13 Aug 2021 15:02:25 +0530 Subject: [PATCH 11/15] Update README.md --- node-youtube-dl/YouTube/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node-youtube-dl/YouTube/README.md b/node-youtube-dl/YouTube/README.md index a785889..ad3c88d 100644 --- a/node-youtube-dl/YouTube/README.md +++ b/node-youtube-dl/YouTube/README.md @@ -70,7 +70,7 @@ console.log(playlist.videoCount) // This displays total no. of videos in a playl ``` ## Search Command Usage :- -### 1. search(url : `string`, options? : `SearchOptions`) +### 1. search(url : `string`, options? : [SearchOptions](https://github.com/node-youtube-dl/node-youtube-dl/tree/Killer/node-youtube-dl/YouTube#searchoptions)) *This enables all searching mechanism (video, channel, playlist)* ```js let result = await search('Rick Roll') From edb137fe4efd7c2a5944c51f94994ca8e1104b89 Mon Sep 17 00:00:00 2001 From: killer069 <65385476+killer069@users.noreply.github.com> Date: Fri, 13 Aug 2021 15:05:50 +0530 Subject: [PATCH 12/15] ReadMe updated --- node-youtube-dl/YouTube/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node-youtube-dl/YouTube/README.md b/node-youtube-dl/YouTube/README.md index a785889..2e90bce 100644 --- a/node-youtube-dl/YouTube/README.md +++ b/node-youtube-dl/YouTube/README.md @@ -20,8 +20,8 @@ let video = await video_info(url) ### 3. formats *This shows all formats availiable of a video* ```js - let video = await video_info(url) - console.log(video.format) +let video = await video_info(url) +console.log(video.format) ``` ## Playlist commands usage :- From 4990e9c0d0785bd5b0ff39ae4ee1ce8b0a1f22bb Mon Sep 17 00:00:00 2001 From: killer069 <65385476+killer069@users.noreply.github.com> Date: Fri, 13 Aug 2021 15:25:18 +0530 Subject: [PATCH 13/15] Spotify comming --- node-youtube-dl/Spotify/index.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 node-youtube-dl/Spotify/index.ts diff --git a/node-youtube-dl/Spotify/index.ts b/node-youtube-dl/Spotify/index.ts new file mode 100644 index 0000000..e69de29 From bb6bc6d124b00fe8598414214f4605968bb2c295 Mon Sep 17 00:00:00 2001 From: killer069 <65385476+killer069@users.noreply.github.com> Date: Fri, 13 Aug 2021 15:27:10 +0530 Subject: [PATCH 14/15] Error --- node-youtube-dl/Spotify/index.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 node-youtube-dl/Spotify/index.ts diff --git a/node-youtube-dl/Spotify/index.ts b/node-youtube-dl/Spotify/index.ts deleted file mode 100644 index e69de29..0000000 From b96d591180451589152e008c645a7583903f3875 Mon Sep 17 00:00:00 2001 From: killer069 <65385476+killer069@users.noreply.github.com> Date: Fri, 13 Aug 2021 15:31:12 +0530 Subject: [PATCH 15/15] Spotify comming --- node-youtube-dl/Spotify/index.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 node-youtube-dl/Spotify/index.ts diff --git a/node-youtube-dl/Spotify/index.ts b/node-youtube-dl/Spotify/index.ts new file mode 100644 index 0000000..e69de29