New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

iyoutube

Package Overview
Dependencies
Maintainers
1
Versions
31
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

iyoutube - npm Package Compare versions

Comparing version 0.0.29 to 0.0.30

8

output/fetchers/helpers.d.ts

@@ -9,4 +9,5 @@ import { Channel } from "../interfaces/Channel";

declare function getNumberFromText(str: string): number;
declare function replaceAll(search: string, value: string, source: string): string;
declare function processRendererItems(arr: Array<any>, httpclient: WrappedHTTPClient): (Video | Channel | Playlist | Comment | CommentThread | undefined)[];
export declare function getVideoDefaultThumbnail(videoId: string): {
declare function getVideoDefaultThumbnail(videoId: string): {
url: string;

@@ -16,2 +17,4 @@ height: number;

};
declare function getIndexBefore(str: string, index: number, source: string): number;
declare function getIndexAfter(str: string, index: number, source: string): number;
declare const _default: {

@@ -23,3 +26,6 @@ recursiveSearchForPair: typeof recursiveSearchForPair;

getVideoDefaultThumbnail: typeof getVideoDefaultThumbnail;
replaceAll: typeof replaceAll;
getIndexAfter: typeof getIndexAfter;
getIndexBefore: typeof getIndexBefore;
};
export default _default;

30

output/fetchers/helpers.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getVideoDefaultThumbnail = void 0;
const Channel_1 = require("../interfaces/Channel");

@@ -51,7 +50,4 @@ const Playlist_1 = require("../interfaces/Playlist");

}
function replaceAll(regex, value, source) {
while (source.includes(regex)) {
source = source.replace(regex, value);
}
return source;
function replaceAll(search, value, source) {
return source.replace(new RegExp(search, 'g'), value);
}

@@ -123,9 +119,19 @@ function processRendererItems(arr, httpclient) {

}
exports.getVideoDefaultThumbnail = getVideoDefaultThumbnail;
function getIndexBefore(str, index, source) {
var before = source.substring(0, index);
return before.lastIndexOf(str);
}
function getIndexAfter(str, index, source) {
var after = source.substring(index, source.length);
return index + after.indexOf(str);
}
exports.default = {
recursiveSearchForPair: recursiveSearchForPair,
recursiveSearchForKey: recursiveSearchForKey,
getNumberFromText: getNumberFromText,
processRendererItems: processRendererItems,
getVideoDefaultThumbnail: getVideoDefaultThumbnail,
recursiveSearchForPair,
recursiveSearchForKey,
getNumberFromText,
processRendererItems,
getVideoDefaultThumbnail,
replaceAll,
getIndexAfter,
getIndexBefore
};

@@ -1,11 +0,2 @@

import { HTTPClient } from './main';
export declare class CiphService {
httpClient: HTTPClient;
constructor(httpClient: HTTPClient);
cache: Map<any, any>;
getTokens(html5playerfile: any, options: any): Promise<string[]>;
extractActions(body: any): string[] | null;
decipherFormats(formats: any, html5player: any, options: any): Promise<any>;
decipher: (tokens: any, sig: any) => any;
setDownloadURL(format: any, sig: any): void;
}
import { WrappedHTTPClient } from "./WrappedHTTPClient";
export declare function decipher(formats: Array<any>, playerURL: string, httpClient: WrappedHTTPClient): Promise<any[]>;

@@ -12,180 +12,65 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.CiphService = void 0;
const url = require("url");
const querystring = require("querystring");
exports.decipher = void 0;
const helpers_1 = require("./fetchers/helpers");
const HTTPClient_1 = require("./interfaces/HTTPClient");
const jsVarStr = '[a-zA-Z_\\$][a-zA-Z_0-9]*';
const jsSingleQuoteStr = `'[^'\\\\]*(:?\\\\[\\s\\S][^'\\\\]*)*'`;
const jsDoubleQuoteStr = `"[^"\\\\]*(:?\\\\[\\s\\S][^"\\\\]*)*"`;
const jsQuoteStr = `(?:${jsSingleQuoteStr}|${jsDoubleQuoteStr})`;
const jsKeyStr = `(?:${jsVarStr}|${jsQuoteStr})`;
const jsPropStr = `(?:\\.${jsVarStr}|\\[${jsQuoteStr}\\])`;
const jsEmptyStr = `(?:''|"")`;
const reverseStr = ':function\\(a\\)\\{' +
'(?:return )?a\\.reverse\\(\\)' +
'\\}';
const sliceStr = ':function\\(a,b\\)\\{' +
'return a\\.slice\\(b\\)' +
'\\}';
const spliceStr = ':function\\(a,b\\)\\{' +
'a\\.splice\\(0,b\\)' +
'\\}';
const swapStr = ':function\\(a,b\\)\\{' +
'var c=a\\[0\\];a\\[0\\]=a\\[b(?:%a\\.length)?\\];a\\[b(?:%a\\.length)?\\]=c(?:;return a)?' +
'\\}';
const actionsObjRegexp = new RegExp(`var (${jsVarStr})=\\{((?:(?:` +
jsKeyStr + reverseStr + '|' +
jsKeyStr + sliceStr + '|' +
jsKeyStr + spliceStr + '|' +
jsKeyStr + swapStr +
'),?\\r?\\n?)+)\\};');
const actionsFuncRegexp = new RegExp(`function(?: ${jsVarStr})?\\(a\\)\\{` +
`a=a\\.split\\(${jsEmptyStr}\\);\\s*` +
`((?:(?:a=)?${jsVarStr}` +
jsPropStr +
'\\(a,\\d+\\);)+)' +
`return a\\.join\\(${jsEmptyStr}\\)` +
'\\}');
const reverseRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${reverseStr}`, 'm');
const sliceRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${sliceStr}`, 'm');
const spliceRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${spliceStr}`, 'm');
const swapRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${swapStr}`, 'm');
class CiphService {
constructor(httpClient) {
this.httpClient = httpClient;
this.cache = new Map();
this.decipher = (tokens, sig) => {
sig = sig.split('');
for (let i = 0, len = tokens.length; i < len; i++) {
let token = tokens[i], pos;
switch (token[0]) {
case 'r':
sig = sig.reverse();
break;
case 'w':
pos = ~~token.slice(1);
const first = sig[0];
sig[0] = sig[pos % sig.length];
sig[pos] = first;
break;
case 's':
pos = ~~token.slice(1);
sig = sig.slice(pos);
break;
case 'p':
pos = ~~token.slice(1);
sig.splice(0, pos);
break;
}
const REGEXES = [
new RegExp("(?:\\b|[^a-zA-Z0-9$])([a-zA-Z0-9$]{2,})\\s*=\\s*function\\(\\s*a\\s*\\)" + "\\s*\\{\\s*a\\s*=\\s*a\\.split\\(\\s*\"\"\\s*\\)"),
new RegExp("\\bm=([a-zA-Z0-9$]{2,})\\(decodeURIComponent\\(h\\.s\\)\\)"),
new RegExp("\\bc&&\\(c=([a-zA-Z0-9$]{2,})\\(decodeURIComponent\\(c\\)\\)"),
new RegExp("([\\w$]+)\\s*=\\s*function\\((\\w+)\\)\\{\\s*\\2=\\s*\\2\\.split\\(\"\"\\)\\s*;"),
new RegExp("\\b([\\w$]{2,})\\s*=\\s*function\\((\\w+)\\)\\{\\s*\\2=\\s*\\2\\.split\\(\"\"\\)\\s*;"),
new RegExp("\\bc\\s*&&\\s*d\\.set\\([^,]+\\s*,\\s*(:encodeURIComponent\\s*\\()([a-zA-Z0-9$]+)\\(")
];
function decipher(formats, playerURL, httpClient) {
return __awaiter(this, void 0, void 0, function* () {
const playerResults = yield httpClient.request({
method: HTTPClient_1.HTTPRequestMethod.GET,
url: playerURL
});
const playerJS = playerResults.data;
let deobfuscateFunctionName = "";
for (const reg of REGEXES) {
deobfuscateFunctionName = matchGroup1(reg, playerJS);
if (deobfuscateFunctionName) {
break;
}
return sig.join('');
};
}
getTokens(html5playerfile, options) {
return __awaiter(this, void 0, void 0, function* () {
const cachedTokens = this.cache.get(html5playerfile);
const response = yield this.httpClient.request({
method: HTTPClient_1.HTTPRequestMethod.GET,
url: html5playerfile
});
const tokens = this.extractActions(response.data);
if (!tokens || !tokens.length) {
throw new Error('Could not extract signature deciphering actions');
}
this.cache.set(html5playerfile, tokens);
return tokens;
});
}
extractActions(body) {
const objResult = actionsObjRegexp.exec(body);
const funcResult = actionsFuncRegexp.exec(body);
if (!objResult || !funcResult) {
return null;
}
const obj = objResult[1].replace(/\$/g, '\\$');
const objBody = objResult[2].replace(/\$/g, '\\$');
const funcBody = funcResult[1].replace(/\$/g, '\\$');
let result = reverseRegexp.exec(objBody);
const reverseKey = result && result[1]
.replace(/\$/g, '\\$')
.replace(/\$|^'|^"|'$|"$/g, '');
result = sliceRegexp.exec(objBody);
const sliceKey = result && result[1]
.replace(/\$/g, '\\$')
.replace(/\$|^'|^"|'$|"$/g, '');
result = spliceRegexp.exec(objBody);
const spliceKey = result && result[1]
.replace(/\$/g, '\\$')
.replace(/\$|^'|^"|'$|"$/g, '');
result = swapRegexp.exec(objBody);
const swapKey = result && result[1]
.replace(/\$/g, '\\$')
.replace(/\$|^'|^"|'$|"$/g, '');
const keys = `(${[reverseKey, sliceKey, spliceKey, swapKey].join('|')})`;
const myreg = '(?:a=)?' + obj +
`(?:\\.${keys}|\\['${keys}'\\]|\\["${keys}"\\])` +
'\\(a,(\\d+)\\)';
const tokenizeRegexp = new RegExp(myreg, 'g');
const tokens = [];
while ((result = tokenizeRegexp.exec(funcBody)) !== null) {
const key = result[1] || result[2] || result[3];
switch (key) {
case swapKey:
tokens.push('w' + result[4]);
break;
case reverseKey:
tokens.push('r');
break;
case sliceKey:
tokens.push('s' + result[4]);
break;
case spliceKey:
tokens.push('p' + result[4]);
break;
const functionPattern = new RegExp("(" + deobfuscateFunctionName.replace("$", "\\$") + "=function\\([a-zA-Z0-9_]+\\)\\{.+?\\})");
const deobfuscateFunction = "var " + matchGroup1(functionPattern, playerJS) + ";";
const helperObjectName = matchGroup1(new RegExp(";([A-Za-z0-9_\\$]{2})\\...\\("), deobfuscateFunction);
const helperPattern = new RegExp("(var " + helperObjectName + "=\\{.+?\\}\\};)");
const helperObject = matchGroup1(helperPattern, helpers_1.default.replaceAll("\n", "", playerJS));
const finalFunc = eval(`(function getDecipherFunction() {
` + helperObject + `
` + deobfuscateFunction + `
return (val) => ` + deobfuscateFunctionName + `;
})()`)();
for (const format of formats) {
if (format.signatureCipher) {
const signatureParams = parseQuery(format.signatureCipher);
const resolvedSignature = finalFunc(signatureParams.s);
const finalURL = new URL(signatureParams.url);
finalURL.searchParams.set(signatureParams.sp, resolvedSignature);
format.url = finalURL.toString();
}
}
return tokens;
return formats;
});
}
exports.decipher = decipher;
function matchGroup1(regex, str) {
const res = regex.exec(str);
if (!res)
return "";
return res[1];
}
function parseQuery(queryString) {
var query = {};
var pairs = (queryString[0] === '?' ? queryString.substr(1) : queryString).split('&');
for (var i = 0; i < pairs.length; i++) {
var pair = pairs[i].split('=');
query[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || '');
}
decipherFormats(formats, html5player, options) {
return __awaiter(this, void 0, void 0, function* () {
const decipheredFormats = [];
const tokens = yield this.getTokens(html5player, options);
formats.forEach((format) => {
const cipher = format.signatureCipher || format.cipher;
if (cipher) {
Object.assign(format, querystring.parse(cipher));
delete format.signatureCipher;
delete format.cipher;
}
const sig = tokens && format.s ? this.decipher(tokens, format.s) : null;
this.setDownloadURL(format, sig);
decipheredFormats.push(format);
});
return decipheredFormats;
});
}
setDownloadURL(format, sig) {
let decodedUrl;
if (format.url) {
decodedUrl = format.url;
}
else {
return;
}
try {
decodedUrl = decodeURIComponent(decodedUrl);
}
catch (err) {
return;
}
const parsedUrl = url.parse(decodedUrl, true);
delete parsedUrl.search;
const query = parsedUrl.query;
query.ratebypass = 'yes';
if (sig) {
query[format.sp || 'signature'] = sig;
}
format.url = url.format(parsedUrl);
}
return query;
}
exports.CiphService = CiphService;

@@ -17,3 +17,3 @@ "use strict";

const HTTPClient_1 = require("./HTTPClient");
const formatsChipher_1 = require("../formatsChipher");
const formatsChipher = require("../formatsChipher");
class Video {

@@ -183,2 +183,3 @@ constructor(httpclient) {

}
yield this.loadFormats();
});

@@ -188,38 +189,23 @@ }

return __awaiter(this, void 0, void 0, function* () {
const playerResponse = yield this.httpclient.request({
method: HTTPClient_1.HTTPRequestMethod.POST,
url: constants_1.ENDPOINT_PLAYER,
data: {
videoId: this.videoId,
racyCheckOk: false,
contentCheckOk: false,
playbackContext: {
contentPlaybackContent: {
currentUrl: "/watch?v=6Dh-RL__uN4",
autonavState: "STATE_OFF",
autoCaptionsDefaultOn: false,
html5Preference: "HTML5_PREF_WANTS",
lactMilliseconds: "-1",
referer: "https://www.youtube.com/",
signatureTimestamp: 19095,
splay: false,
vis: 0
}
}
}
});
const playerJSON = yield JSON.parse(playerResponse.data);
const formats = helpers_1.default.recursiveSearchForKey("adaptiveFormats", playerJSON)[0];
const watchURL = new URL(constants_1.ENDPOINT_WATCHPAGE);
watchURL.searchParams.set("v", this.videoId);
const playPage = yield this.httpclient.request({
const playPage = yield this.httpclient.client.request({
method: HTTPClient_1.HTTPRequestMethod.GET,
url: watchURL.toString()
url: watchURL.toString(),
headers: {
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
}
});
const html = playPage.data;
const varFind = html.indexOf("var ytInitialPlayerResponse =");
const scriptStart = helpers_1.default.getIndexAfter(">", helpers_1.default.getIndexBefore("<script", varFind, html), html) + 1;
const scriptEnd = helpers_1.default.getIndexAfter("</script>", varFind, html);
const script = html.substring(scriptStart, scriptEnd);
const scriptFunc = new Function(script + " return ytInitialPlayerResponse;");
const playerJSON = scriptFunc();
const formats = helpers_1.default.recursiveSearchForKey("adaptiveFormats", playerJSON)[0];
let playerScript = /<script\s+src="([^"]+)"(?:\s+type="text\/javascript")?\s+name="player_ias\/base"\s*>|"jsUrl":"([^"]+)"/.exec(html);
playerScript = playerScript[2] || playerScript[1];
const playerURLString = new URL(playerScript, watchURL).href;
const cipher = new formatsChipher_1.CiphService(this.httpclient);
const resolvedFormats = yield cipher.decipherFormats(formats, playerURLString, {});
const resolvedFormats = yield formatsChipher.decipher(formats, playerURLString, this.httpclient);
this.formats = [];

@@ -226,0 +212,0 @@ resolvedFormats.forEach((a) => {

@@ -28,3 +28,4 @@ import { Authenticator } from "./Authenticator";

getUser(): User;
getCookies(): string;
throwErrorIfNotReady(): void;
}

@@ -112,2 +112,5 @@ "use strict";

}
getCookies() {
return this.wrappedHttpClient.cookieString;
}
throwErrorIfNotReady() {

@@ -114,0 +117,0 @@ if (!this.storageAdapter)

@@ -22,2 +22,3 @@ import { Explorer } from "./fetchers/Explorer";

import { default as IYoutube } from "./IYoutube";
import { HTTPRequestOptions, HTTPRequestMethod, HTTPResponse } from "./interfaces/HTTPClient";
export { IYoutube as IYoutube };

@@ -44,2 +45,5 @@ export { HTTPClient as HTTPClient };

export { Format as Format };
export { HTTPRequestMethod as HTTPRequestMethod };
export { HTTPRequestOptions as HTTPRequestOptions };
export { HTTPResponse as HTTPResponse };
export declare const nodeDefault: () => Promise<IYoutube>;

@@ -12,3 +12,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.nodeDefault = exports.List = exports.CommentThreadRepliesContinuatedList = exports.CommentThread = exports.Comment = exports.CommentSectionContinuatedList = exports.Playlist = exports.Video = exports.Channel = exports.Authenticator = exports.ContinuatedList = exports.User = exports.Explorer = exports.WrappedHTTPClient = exports.IYoutube = void 0;
exports.nodeDefault = exports.HTTPRequestMethod = exports.List = exports.CommentThreadRepliesContinuatedList = exports.CommentThread = exports.Comment = exports.CommentSectionContinuatedList = exports.Playlist = exports.Video = exports.Channel = exports.Authenticator = exports.ContinuatedList = exports.User = exports.Explorer = exports.WrappedHTTPClient = exports.IYoutube = void 0;
const Explorer_1 = require("./fetchers/Explorer");

@@ -42,2 +42,4 @@ Object.defineProperty(exports, "Explorer", { enumerable: true, get: function () { return Explorer_1.Explorer; } });

Object.defineProperty(exports, "IYoutube", { enumerable: true, get: function () { return IYoutube_1.default; } });
const HTTPClient_1 = require("./interfaces/HTTPClient");
Object.defineProperty(exports, "HTTPRequestMethod", { enumerable: true, get: function () { return HTTPClient_1.HTTPRequestMethod; } });
const nodeDefault = () => __awaiter(void 0, void 0, void 0, function* () {

@@ -44,0 +46,0 @@ const path = "./nodeDefault";

{
"name": "iyoutube",
"version": "0.0.29",
"version": "0.0.30",
"description": "The ultimate unofficial YouTube API Client for Javascript",

@@ -5,0 +5,0 @@ "main": "output/main.js",

@@ -53,7 +53,4 @@ import { Channel } from "../interfaces/Channel";

function replaceAll(regex:string, value: string, source:string) {
while(source.includes(regex)) {
source = source.replace(regex, value);
}
return source;
function replaceAll(search:string, value: string, source:string) {
return source.replace(new RegExp(search, 'g'), value);
}

@@ -128,3 +125,3 @@

export function getVideoDefaultThumbnail(videoId:string) {
function getVideoDefaultThumbnail(videoId:string) {
return {

@@ -136,8 +133,21 @@ url: "https://i.ytimg.com/vi/" + videoId + "/maxresdefault.jpg",

}
function getIndexBefore(str: string, index: number, source: string) {
var before = source.substring(0, index);
return before.lastIndexOf(str);
}
function getIndexAfter(str: string, index: number, source: string) {
var after = source.substring(index, source.length);
return index + after.indexOf(str);
}
export default {
recursiveSearchForPair: recursiveSearchForPair,
recursiveSearchForKey: recursiveSearchForKey,
getNumberFromText: getNumberFromText,
processRendererItems: processRendererItems,
getVideoDefaultThumbnail: getVideoDefaultThumbnail,
recursiveSearchForPair,
recursiveSearchForKey,
getNumberFromText,
processRendererItems,
getVideoDefaultThumbnail,
replaceAll,
getIndexAfter,
getIndexBefore
}

@@ -1,210 +0,73 @@

//From https://github.com/appit-online/ionic-youtube-streams/blob/eeee1741857f06c81380c317bd6e71732c895ee1/src/lib/cip.service.ts#L53
import * as url from 'url';
import * as querystring from 'querystring';
import { HTTPClient } from './main';
import { HTTPRequestMethod } from './interfaces/HTTPClient';
//Worked out from: https://github.com/TeamNewPipe/NewPipeExtractor
import helpers from "./fetchers/helpers";
import { HTTPRequestMethod } from "./interfaces/HTTPClient";
import { WrappedHTTPClient } from "./WrappedHTTPClient";
const jsVarStr = '[a-zA-Z_\\$][a-zA-Z_0-9]*';
const jsSingleQuoteStr = `'[^'\\\\]*(:?\\\\[\\s\\S][^'\\\\]*)*'`;
const jsDoubleQuoteStr = `"[^"\\\\]*(:?\\\\[\\s\\S][^"\\\\]*)*"`;
const jsQuoteStr = `(?:${jsSingleQuoteStr}|${jsDoubleQuoteStr})`;
const jsKeyStr = `(?:${jsVarStr}|${jsQuoteStr})`;
const jsPropStr = `(?:\\.${jsVarStr}|\\[${jsQuoteStr}\\])`;
const jsEmptyStr = `(?:''|"")`;
const reverseStr = ':function\\(a\\)\\{' +
'(?:return )?a\\.reverse\\(\\)' +
'\\}';
const sliceStr = ':function\\(a,b\\)\\{' +
'return a\\.slice\\(b\\)' +
'\\}';
const spliceStr = ':function\\(a,b\\)\\{' +
'a\\.splice\\(0,b\\)' +
'\\}';
const swapStr = ':function\\(a,b\\)\\{' +
'var c=a\\[0\\];a\\[0\\]=a\\[b(?:%a\\.length)?\\];a\\[b(?:%a\\.length)?\\]=c(?:;return a)?' +
'\\}';
const actionsObjRegexp = new RegExp(
`var (${jsVarStr})=\\{((?:(?:` +
jsKeyStr + reverseStr + '|' +
jsKeyStr + sliceStr + '|' +
jsKeyStr + spliceStr + '|' +
jsKeyStr + swapStr +
'),?\\r?\\n?)+)\\};'
);
const actionsFuncRegexp = new RegExp(`function(?: ${jsVarStr})?\\(a\\)\\{` +
`a=a\\.split\\(${jsEmptyStr}\\);\\s*` +
`((?:(?:a=)?${jsVarStr}` +
jsPropStr +
'\\(a,\\d+\\);)+)' +
`return a\\.join\\(${jsEmptyStr}\\)` +
'\\}'
);
const reverseRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${reverseStr}`, 'm');
const sliceRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${sliceStr}`, 'm');
const spliceRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${spliceStr}`, 'm');
const swapRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${swapStr}`, 'm');
const REGEXES = [
new RegExp("(?:\\b|[^a-zA-Z0-9$])([a-zA-Z0-9$]{2,})\\s*=\\s*function\\(\\s*a\\s*\\)" + "\\s*\\{\\s*a\\s*=\\s*a\\.split\\(\\s*\"\"\\s*\\)"),
new RegExp("\\bm=([a-zA-Z0-9$]{2,})\\(decodeURIComponent\\(h\\.s\\)\\)"),
new RegExp("\\bc&&\\(c=([a-zA-Z0-9$]{2,})\\(decodeURIComponent\\(c\\)\\)"),
new RegExp("([\\w$]+)\\s*=\\s*function\\((\\w+)\\)\\{\\s*\\2=\\s*\\2\\.split\\(\"\"\\)\\s*;"),
new RegExp("\\b([\\w$]{2,})\\s*=\\s*function\\((\\w+)\\)\\{\\s*\\2=\\s*\\2\\.split\\(\"\"\\)\\s*;"),
new RegExp("\\bc\\s*&&\\s*d\\.set\\([^,]+\\s*,\\s*(:encodeURIComponent\\s*\\()([a-zA-Z0-9$]+)\\(")
];
export async function decipher(formats: Array<any>, playerURL: string, httpClient: WrappedHTTPClient) {
const playerResults = await httpClient.request({
method: HTTPRequestMethod.GET,
url: playerURL
});
export class CiphService {
const playerJS = playerResults.data;
constructor(public httpClient: HTTPClient) {}
cache = new Map();
async getTokens(html5playerfile: any, options: any) {
const cachedTokens = this.cache.get(html5playerfile);
const response = await this.httpClient.request({
method: HTTPRequestMethod.GET,
url: html5playerfile
});
const tokens = this.extractActions(response.data);
if (!tokens || !tokens.length) {
throw new Error('Could not extract signature deciphering actions');
let deobfuscateFunctionName:any = "";
for(const reg of REGEXES) {
deobfuscateFunctionName = matchGroup1(reg, playerJS);
if(deobfuscateFunctionName) {
break;
}
this.cache.set(html5playerfile, tokens);
return tokens;
}
extractActions(body: any) {
const objResult = actionsObjRegexp.exec(body);
const funcResult = actionsFuncRegexp.exec(body);
if (!objResult || !funcResult) { return null; }
const functionPattern = new RegExp("(" + deobfuscateFunctionName.replace("$", "\\$") + "=function\\([a-zA-Z0-9_]+\\)\\{.+?\\})");
const deobfuscateFunction = "var " + matchGroup1(functionPattern, playerJS) + ";";
const obj = objResult[1].replace(/\$/g, '\\$');
const objBody = objResult[2].replace(/\$/g, '\\$');
const funcBody = funcResult[1].replace(/\$/g, '\\$');
const helperObjectName = matchGroup1(new RegExp(";([A-Za-z0-9_\\$]{2})\\...\\("), deobfuscateFunction);
const helperPattern = new RegExp("(var " + helperObjectName + "=\\{.+?\\}\\};)");
const helperObject = matchGroup1(helperPattern, helpers.replaceAll("\n", "", playerJS));
const finalFunc = eval(`(function getDecipherFunction() {
` + helperObject + `
` + deobfuscateFunction + `
let result = reverseRegexp.exec(objBody);
const reverseKey = result && result[1]
.replace(/\$/g, '\\$')
.replace(/\$|^'|^"|'$|"$/g, '');
result = sliceRegexp.exec(objBody);
const sliceKey = result && result[1]
.replace(/\$/g, '\\$')
.replace(/\$|^'|^"|'$|"$/g, '');
result = spliceRegexp.exec(objBody);
const spliceKey = result && result[1]
.replace(/\$/g, '\\$')
.replace(/\$|^'|^"|'$|"$/g, '');
result = swapRegexp.exec(objBody);
const swapKey = result && result[1]
.replace(/\$/g, '\\$')
.replace(/\$|^'|^"|'$|"$/g, '');
return (val) => ` + deobfuscateFunctionName + `;
})()`)();
const keys = `(${[reverseKey, sliceKey, spliceKey, swapKey].join('|')})`;
const myreg = '(?:a=)?' + obj +
`(?:\\.${keys}|\\['${keys}'\\]|\\["${keys}"\\])` +
'\\(a,(\\d+)\\)';
const tokenizeRegexp = new RegExp(myreg, 'g');
const tokens = [];
// tslint:disable-next-line:no-conditional-assignment
while ((result = tokenizeRegexp.exec(funcBody)) !== null) {
const key = result[1] || result[2] || result[3];
switch (key) {
case swapKey:
tokens.push('w' + result[4]);
break;
case reverseKey:
tokens.push('r');
break;
case sliceKey:
tokens.push('s' + result[4]);
break;
case spliceKey:
tokens.push('p' + result[4]);
break;
}
for(const format of formats) {
if(format.signatureCipher) {
const signatureParams = parseQuery(format.signatureCipher);
const resolvedSignature = finalFunc(signatureParams.s);
const finalURL = new URL(signatureParams.url);
finalURL.searchParams.set(signatureParams.sp, resolvedSignature)
format.url = finalURL.toString();
}
return tokens;
}
return formats;
}
async decipherFormats(formats: any, html5player: any, options: any) {
const decipheredFormats: any = [];
const tokens = await this.getTokens(html5player, options);
function matchGroup1(regex: RegExp, str: string) {
const res = regex.exec(str);
if(!res) return "";
return res[1];
}
formats.forEach((format: any) => {
const cipher = format.signatureCipher || format.cipher;
if (cipher) {
Object.assign(format, querystring.parse(cipher));
delete format.signatureCipher;
delete format.cipher;
}
const sig = tokens && format.s ? this.decipher(tokens, format.s) : null;
this.setDownloadURL(format, sig);
decipheredFormats.push(format);
});
return decipheredFormats;
function parseQuery(queryString:string) {
var query:any = {};
var pairs = (queryString[0] === '?' ? queryString.substr(1) : queryString).split('&');
for (var i = 0; i < pairs.length; i++) {
var pair = pairs[i].split('=');
query[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || '');
}
decipher = (tokens: any, sig: any) => {
sig = sig.split('');
for (let i = 0, len = tokens.length; i < len; i++) {
// tslint:disable-next-line:prefer-const one-variable-per-declaration
let token = tokens[i], pos;
switch (token[0]) {
case 'r':
sig = sig.reverse();
break;
case 'w':
// tslint:disable-next-line:no-bitwise
pos = ~~token.slice(1);
const first = sig[0];
sig[0] = sig[pos % sig.length];
sig[pos] = first;
break;
case 's':
// tslint:disable-next-line:no-bitwise
pos = ~~token.slice(1);
sig = sig.slice(pos);
break;
case 'p':
// tslint:disable-next-line:no-bitwise
pos = ~~token.slice(1);
sig.splice(0, pos);
break;
}
}
return sig.join('');
}
setDownloadURL(format: any, sig: any) {
let decodedUrl;
if (format.url) {
decodedUrl = format.url;
} else {
return;
}
try {
decodedUrl = decodeURIComponent(decodedUrl);
} catch (err) {
return;
}
// Make some adjustments to the final url.
const parsedUrl = url.parse(decodedUrl, true);
// Deleting the `search` part is necessary otherwise changes to
// `query` won't reflect when running `url.format()`
// @ts-ignore
delete parsedUrl.search;
const query = parsedUrl.query;
// This is needed for a speedier download.
// See https://github.com/fent/node-ytdl-core/issues/127
query.ratebypass = 'yes';
if (sig) {
// When YouTube provides a `sp` parameter the signature `sig` must go
// into the parameter it specifies.
// See https://github.com/fent/node-ytdl-core/issues/417
query[format.sp || 'signature'] = sig;
}
format.url = url.format(parsedUrl);
}
return query;
}

@@ -1,6 +0,6 @@

import { ENDPOINT_COMMENT_CREATE, ENDPOINT_DISLIKE, ENDPOINT_LIKE, ENDPOINT_NEXT, ENDPOINT_PLAYER, ENDPOINT_REMOVELIKE, ENDPOINT_WATCHPAGE } from "../constants";
import { DEFAULT_CLIENT_VERSION, DEFAULT_USER_AGENT, ENDPOINT_COMMENT_CREATE, ENDPOINT_DISLIKE, ENDPOINT_LIKE, ENDPOINT_NEXT, ENDPOINT_PLAYER, ENDPOINT_REMOVELIKE, ENDPOINT_WATCHPAGE } from "../constants";
import helpers from "../fetchers/helpers";
import { CommentSectionContinuatedList, ContinuatedList, WrappedHTTPClient, Channel, Thumbnail, CaptionTrack, CommentThread, Format } from "../main";
import { HTTPRequestMethod } from "./HTTPClient";
import { CiphService } from "../formatsChipher";
import * as formatsChipher from "../formatsChipher";

@@ -236,39 +236,29 @@ export class Video {

await this.loadFormats();
}
async loadFormats() {
const playerResponse = await this.httpclient.request({
method: HTTPRequestMethod.POST,
url: ENDPOINT_PLAYER,
data: {
videoId: this.videoId,
racyCheckOk: false,
contentCheckOk: false,
playbackContext: {
contentPlaybackContent: {
currentUrl: "/watch?v=6Dh-RL__uN4",
autonavState: "STATE_OFF",
autoCaptionsDefaultOn: false,
html5Preference: "HTML5_PREF_WANTS",
lactMilliseconds: "-1",
referer: "https://www.youtube.com/",
signatureTimestamp: 19095,
splay: false,
vis: 0
}
}
}
});
const playerJSON = await JSON.parse(playerResponse.data);
const formats = helpers.recursiveSearchForKey("adaptiveFormats", playerJSON)[0];
const watchURL = new URL(ENDPOINT_WATCHPAGE);
watchURL.searchParams.set("v", this.videoId);
const playPage = await this.httpclient.request({
const playPage = await this.httpclient.client.request({
method: HTTPRequestMethod.GET,
url: watchURL.toString()
url: watchURL.toString(),
headers: {
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
}
});
const html = playPage.data;
const varFind = html.indexOf("var ytInitialPlayerResponse ="); //Locate the Var Definition
const scriptStart = helpers.getIndexAfter(">", helpers.getIndexBefore("<script", varFind, html), html) + 1; //Get Script Tag Before
const scriptEnd = helpers.getIndexAfter("</script>", varFind, html); //Get Script Tag After
const script = html.substring(scriptStart, scriptEnd); //Get Script (between both Tags)
const scriptFunc = new Function(script + " return ytInitialPlayerResponse;"); //Parse the Functions Inside
const playerJSON = scriptFunc(); //Get the Information
const formats = helpers.recursiveSearchForKey("adaptiveFormats", playerJSON)[0];
let playerScript:any = /<script\s+src="([^"]+)"(?:\s+type="text\/javascript")?\s+name="player_ias\/base"\s*>|"jsUrl":"([^"]+)"/.exec(html);

@@ -278,4 +268,3 @@ playerScript = playerScript[2] || playerScript[1];

const cipher = new CiphService(this.httpclient);
const resolvedFormats = await cipher.decipherFormats(formats, playerURLString, {});
const resolvedFormats = await formatsChipher.decipher(formats, playerURLString, this.httpclient);

@@ -282,0 +271,0 @@ this.formats = [];

@@ -115,2 +115,6 @@ import { Authenticator } from "./Authenticator";

getCookies() {
return this.wrappedHttpClient.cookieString;
}
throwErrorIfNotReady(){

@@ -117,0 +121,0 @@ if(!this.storageAdapter) throw new Error("The provided Storage Adapter was invalid");

@@ -22,2 +22,3 @@ import { Explorer } from "./fetchers/Explorer";

import { default as IYoutube } from "./IYoutube";
import { HTTPRequestOptions, HTTPRequestMethod, HTTPResponse } from "./interfaces/HTTPClient";

@@ -48,2 +49,5 @@

export { Format as Format }
export { HTTPRequestMethod as HTTPRequestMethod }
export { HTTPRequestOptions as HTTPRequestOptions }
export { HTTPResponse as HTTPResponse }

@@ -50,0 +54,0 @@ //Skips Webpack checks

@@ -106,2 +106,2 @@ import { CONSOLE_COLORS, DEBUG, DEFAULT_API_KEY, DEFAULT_CLIENT_NAME, DEFAULT_CLIENT_VERSION, DEFAULT_CONTEXT, DEFAULT_USER_AGENT } from "./constants";

return resString;
}
}

@@ -10,3 +10,3 @@ ## Todo

- [X] Fetch all Information (Youtube Player Response)
- [ ] Stream URL Decryption & Video Formats
- [X] Stream URL Decryption & Video/Audio Formats
- [X] Get Comment Section

@@ -13,0 +13,0 @@ - [X] Like and Dislike Comments

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc