hotfix: use Android client when requesting initial video info

This commit is contained in:
LuanRT
2022-08-11 20:35:30 -03:00
parent dc79b19d56
commit 34022fddfb
5 changed files with 56 additions and 68 deletions

View File

@@ -70,12 +70,16 @@ class Innertube {
/**
* Retrieves video info.
*/
async getInfo(video_id: string) {
async getInfo(video_id: string | undefined) {
throwIfMissing({ video_id });
const cpn = generateRandomString(16);
const initial_info = this.actions.getVideoInfo(video_id, cpn);
const initial_info = await this.actions.execute('/player', {
client: 'ANDROID',
videoId: 'mFWf4Tb5m6Y'
});
const continuation = this.actions.next({ video_id });
const response = await Promise.all([ initial_info, continuation ]);
@@ -85,11 +89,15 @@ class Innertube {
/**
* Retrieves basic video info.
*/
async getBasicInfo(video_id: string) {
async getBasicInfo(video_id: string | undefined) {
throwIfMissing({ video_id });
const cpn = generateRandomString(16);
const response = await this.actions.getVideoInfo(video_id, cpn);
const response = await this.actions.execute('/player', {
client: 'ANDROID',
videoId: 'mFWf4Tb5m6Y'
});
return new VideoInfo([ response ], this.actions, this.session.player, cpn);
}
@@ -238,11 +246,11 @@ class Innertube {
*
* If you wish to retrieve the video info too, have a look at {@link getBasicInfo} or {@link getInfo}.
*/
async download(video_id: string, options?: DownloadOptions) {
async download(video_id: string | undefined, options?: DownloadOptions) {
throwIfMissing({ video_id });
const info = await this.getBasicInfo(video_id);
return info.download(options);
}
}
export default Innertube;
export default Innertube;

View File

@@ -4,19 +4,23 @@ import { getRandomUserAgent, getStringBetweenStrings, PlayerError } from '../uti
import Constants from '../utils/Constants';
import UniversalCache from '../utils/Cache';
import NToken from '../deciphers/NToken';
import Signature from '../deciphers/Signature';
// Import NToken from '../deciphers/NToken';
// Import Signature from '../deciphers/Signature';
export default class Player {
#ntoken;
#signature;
// #ntoken;
// #signature;
#signature_timestamp;
#player_id;
constructor(signature: Signature, ntoken: NToken, signature_timestamp: number, player_id: string) {
this.#ntoken = ntoken;
this.#signature = signature;
constructor(signature_timestamp: number, player_id: string) {
// This.#ntoken = ntoken;
// This.#signature = signature;
this.#signature_timestamp = signature_timestamp;
this.#player_id = player_id;
}
@@ -56,13 +60,15 @@ export default class Player {
const player_js = await player_res.text();
const sig_timestamp = this.extractSigTimestamp(player_js);
const sig_decipher_sc = this.extractSigDecipherSc(player_js);
const ntoken_sc = this.extractNTokenSc(player_js);
return await Player.fromSource(cache, sig_timestamp, sig_decipher_sc, ntoken_sc, player_id);
// Const sig_decipher_sc = this.extractSigDecipherSc(player_js);
// Const ntoken_sc = this.extractNTokenSc(player_js);
return await Player.fromSource(cache, sig_timestamp, player_id);
}
decipher(url?: string, signature_cipher?: string, cipher?: string) {
/*
Decipher(url?: string, signature_cipher?: string, cipher?: string) {
url = url || signature_cipher || cipher;
if (!url)
@@ -90,7 +96,7 @@ export default class Player {
}
return url_components.toString();
}
}*/
static async fromCache(cache: UniversalCache, player_id: string) {
const buffer = await cache.get(player_id);
@@ -105,15 +111,18 @@ export default class Player {
return null;
const sig_timestamp = view.getUint32(4, true);
const sig_decipher_len = view.getUint32(8, true);
const sig_decipher_buf = buffer.slice(12, 12 + sig_decipher_len);
const ntoken_transform_buf = buffer.slice(12 + sig_decipher_len);
return new Player(Signature.fromArrayBuffer(sig_decipher_buf), NToken.fromArrayBuffer(ntoken_transform_buf), sig_timestamp, player_id);
/*
* Const sig_decipher_len = view.getUint32(8, true);
* const sig_decipher_buf = buffer.slice(12, 12 + sig_decipher_len);
* const ntoken_transform_buf = buffer.slice(12 + sig_decipher_len);
*/
return new Player(sig_timestamp, player_id);
}
static async fromSource(cache: UniversalCache | undefined, sig_timestamp: number, sig_decipher_sc: string, ntoken_sc: string, player_id: string) {
const player = new Player(Signature.fromSourceCode(sig_decipher_sc), NToken.fromSourceCode(ntoken_sc), sig_timestamp, player_id);
static async fromSource(cache: UniversalCache | undefined, sig_timestamp: number, player_id: string) {
const player = new Player(sig_timestamp, player_id);
await player.cache(cache);
return player;
}
@@ -121,17 +130,21 @@ export default class Player {
async cache(cache?: UniversalCache) {
if (!cache) return;
const ntoken_buf = this.#ntoken.toArrayBuffer();
const sig_decipher_buf = this.#signature.toArrayBuffer();
const buffer = new ArrayBuffer(12 + sig_decipher_buf.byteLength + ntoken_buf.byteLength);
/**
* Const ntoken_buf = this.#ntoken.toArrayBuffer();
* const sig_decipher_buf = this.#signature.toArrayBuffer();
*/
const buffer = new ArrayBuffer(12 /* + sig_decipher_buf.byteLength + ntoken_buf.byteLength */);
const view = new DataView(buffer);
view.setUint32(0, Player.LIBRARY_VERSION, true);
view.setUint32(4, this.#signature_timestamp, true);
view.setUint32(8, sig_decipher_buf.byteLength, true);
new Uint8Array(buffer).set(new Uint8Array(sig_decipher_buf), 12);
new Uint8Array(buffer).set(new Uint8Array(ntoken_buf), 12 + sig_decipher_buf.byteLength);
// View.setUint32(8, sig_decipher_buf.byteLength, true);
// New Uint8Array(buffer).set(new Uint8Array(sig_decipher_buf), 12);
// New Uint8Array(buffer).set(new Uint8Array(ntoken_buf), 12 + sig_decipher_buf.byteLength);
await cache.set(this.#player_id, new Uint8Array(buffer));
}
@@ -162,6 +175,7 @@ export default class Player {
static extractNTokenSc(data: string) {
const sc = `var b=a.split("")${getStringBetweenStrings(data, 'b=a.split("")', '}return b.join("")}')}} return b.join("");`;
console.log(sc);
if (!sc)
throw new PlayerError('Failed to extract n-token decipher algorithm');

View File

@@ -36,12 +36,11 @@ class Format {
/**
* Decipher the streaming url of the format.
*
* @param {import('../../../core/Player').default} player
* @returns {string} Deciphered URL for downloading
*/
decipher(player) {
return player.decipher(this.url, this.signature_cipher, this.cipher);
decipher() {
return this.url;
// Return player.decipher(this.url, this.signature_cipher, this.cipher);
}
}

View File

@@ -6,7 +6,6 @@ import Player from '../../core/Player';
import TwoColumnWatchNextResults from '../classes/TwoColumnWatchNextResults';
import VideoPrimaryInfo from '../classes/VideoPrimaryInfo';
import VideoSecondaryInfo from '../classes/VideoSecondaryInfo';
import PlayerMicroformat from '../classes/PlayerMicroformat';
import Format from '../classes/misc/Format';
import MerchandiseShelf from '../classes/MerchandiseShelf';
@@ -96,22 +95,8 @@ class VideoInfo {
if (info.playability_status?.status === 'ERROR')
throw new InnertubeError('This video is unavailable', info.playability_status);
if (!info.microformat?.is(PlayerMicroformat))
throw new InnertubeError('Invalid microformat', info.microformat);
this.basic_info = { // This type is inferred so no need for an explicit type
...info.video_details,
...{
/**
* Microformat is a bit redundant, so only
* a few things there are interesting to us.
*/
embed: info.microformat?.embed,
channel: info.microformat?.channel,
is_unlisted: info.microformat?.is_unlisted,
is_family_safe: info.microformat?.is_family_safe,
has_ypc_metadata: info.microformat?.has_ypc_metadata
},
like_count: undefined as number | undefined,
is_liked: undefined as boolean | undefined,
is_disliked: undefined as boolean | undefined
@@ -438,7 +423,7 @@ class VideoInfo {
if (!format.index_range || !format.init_range)
throw new InnertubeError('Index and init ranges not available', { format });
const url = new URL(format.decipher(this.#player));
const url = new URL(format.decipher());
url.searchParams.set('cpn', this.#cpn);
set.appendChild(this.#el(document, 'Representation', {

View File

@@ -83,24 +83,6 @@ describe('YouTube.js Tests', () => {
expect(result).toBeTruthy();
}, 30000);
});
/*
// TODO: fix this, doesn't run on node 12
const { default: NToken } = require('../../src/deciphers/NToken');
const { default: Signature} = require('../../src/deciphers/Signature');
describe('Deciphers', () => {
it('Should decipher signature', () => {
const result = Signature.fromSourceCode(Constants.DECIPHERS.SIG.ALGORITHM).decipher(Constants.DECIPHERS.SIG.ORIGINAL_URL);
expect(result).toEqual(Constants.DECIPHERS.SIG.DECIPHERED_URL);
});
it('Should decipher ntoken', () => {
const result = NToken.fromSourceCode(Constants.DECIPHERS.N.ALGORITHM).transform(Constants.DECIPHERS.N.ORIGINAL_TOKEN);
expect(result).toEqual(Constants.DECIPHERS.N.DECIPHERED_TOKEN);
});
});
*/
});
async function download(id, session) {