fix merge issues

This commit is contained in:
chrisk325 2026-03-06 04:15:27 +05:30 committed by GitHub
parent 33fa8a0826
commit 7bd4779745
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -67,35 +67,14 @@ export interface YouTubeExtractionResult {
// Constants — matching the Kotlin extractor exactly // Constants — matching the Kotlin extractor exactly
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Innertube client configs — we use Android (no cipher, direct URLs) // Used for all GET requests (watch page, HLS manifest fetch)
// and web as fallback (may need cipher decode) const DEFAULT_USER_AGENT =
// Note: ?key= param was deprecated by YouTube in mid-2023 and is no longer sent. 'Mozilla/5.0 (Linux; Android 12; Android TV) AppleWebKit/537.36 ' +
const INNERTUBE_URL = 'https://www.youtube.com/youtubei/v1/player'; '(KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36';
// Android client gives direct URLs without cipher obfuscation const DEFAULT_HEADERS: Record<string, string> = {
const ANDROID_CLIENT_CONTEXT = { 'accept-language': 'en-US,en;q=0.9',
client: { 'user-agent': DEFAULT_USER_AGENT,
clientName: 'ANDROID',
clientVersion: '19.44.41',
androidSdkVersion: 30,
userAgent:
'com.google.android.youtube/19.44.41 (Linux; U; Android 11) gzip',
hl: 'en',
gl: 'US',
},
};
// iOS client as secondary fallback
const IOS_CLIENT_CONTEXT = {
client: {
clientName: 'IOS',
clientVersion: '19.45.4',
deviceModel: 'iPhone16,2',
userAgent:
'com.google.ios.youtube/19.45.4 (iPhone16,2; U; CPU iOS 17_5_1 like Mac OS X)',
hl: 'en',
gl: 'US',
},
}; };
const PREFERRED_ADAPTIVE_CLIENT = 'android_vr'; const PREFERRED_ADAPTIVE_CLIENT = 'android_vr';
@ -349,36 +328,37 @@ async function fetchWatchConfig(videoId: string): Promise<WatchConfig> {
async function fetchPlayerResponse( async function fetchPlayerResponse(
videoId: string, videoId: string,
context: object, client: ClientDef,
userAgent: string, apiKey: string | null,
clientNameId: string = '3' visitorData: string | null,
): Promise<InnertubePlayerResponse | null> { ): Promise<PlayerResponse | null> {
const controller = new AbortController(); const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS); const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
try { const endpoint = apiKey
const body = { ? `https://www.youtube.com/youtubei/v1/player?key=${encodeURIComponent(apiKey)}&prettyPrint=false`
videoId, : `https://www.youtube.com/youtubei/v1/player?prettyPrint=false`;
context,
contentCheckOk: true,
racyCheckOk: true,
};
const response = await fetch( const headers: Record<string, string> = {
`${INNERTUBE_URL}?prettyPrint=false`, ...DEFAULT_HEADERS,
{ 'content-type': 'application/json',
method: 'POST', 'origin': 'https://www.youtube.com',
headers: { 'referer': `https://www.youtube.com/watch?v=${videoId}`,
'Content-Type': 'application/json', 'x-youtube-client-name': client.id,
'User-Agent': userAgent, 'x-youtube-client-version': client.version,
'X-YouTube-Client-Name': clientNameId, 'user-agent': client.userAgent,
'Origin': 'https://www.youtube.com', };
'Referer': `https://www.youtube.com/watch?v=${videoId}`, if (visitorData) headers['x-goog-visitor-id'] = visitorData;
},
body: JSON.stringify(body), const body = JSON.stringify({
signal: controller.signal, videoId,
} contentCheckOk: true,
); racyCheckOk: true,
context: { client: client.context },
playbackContext: {
contentPlaybackContext: { html5Preference: 'HTML5_PREF_WANTS' },
},
});
try { try {
const res = await fetch(endpoint, { const res = await fetch(endpoint, {
@ -617,48 +597,13 @@ export class YouTubeExtractor {
return null; return null;
} }
logger.info('YouTubeExtractor', `Extracting for videoId=${videoId} platform=${platform ?? 'unknown'}`); const effectivePlatform = platform ?? (Platform.OS === 'android' ? 'android' : 'ios');
const clients: Array<{ context: object; userAgent: string; name: string; clientNameId: string }> = [ for (let attempt = 1; attempt <= MAX_RETRIES + 1; attempt++) {
{ if (attempt > 1) {
name: 'ANDROID', const delay = attempt * 300;
clientNameId: '3', logger.info('YouTubeExtractor', `Retry attempt ${attempt}/${MAX_RETRIES + 1} after ${delay}ms`);
context: ANDROID_CLIENT_CONTEXT, await new Promise(resolve => setTimeout(resolve, delay));
userAgent: 'com.google.android.youtube/19.44.41 (Linux; U; Android 11) gzip',
},
{
name: 'IOS',
clientNameId: '5',
context: IOS_CLIENT_CONTEXT,
userAgent: 'com.google.ios.youtube/19.45.4 (iPhone16,2; U; CPU iOS 17_5_1 like Mac OS X)',
},
{
name: 'TVHTML5_EMBEDDED',
clientNameId: '85',
context: TVHTML5_EMBEDDED_CONTEXT,
userAgent: 'Mozilla/5.0 (SMART-TV; Linux; Tizen 6.0)',
},
{
name: 'WEB_EMBEDDED',
clientNameId: '56',
context: WEB_EMBEDDED_CONTEXT,
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36',
},
];
let muxedFormats: InnertubeFormat[] = [];
let adaptiveFormats: InnertubeFormat[] = [];
let playerResponse: InnertubePlayerResponse | null = null;
for (const client of clients) {
logger.info('YouTubeExtractor', `Trying ${client.name} client...`);
const resp = await fetchPlayerResponse(videoId, client.context, client.userAgent, client.clientNameId);
if (!resp) continue;
const status = resp.playabilityStatus?.status;
if (status === 'UNPLAYABLE' || status === 'LOGIN_REQUIRED') {
logger.warn('YouTubeExtractor', `${client.name}: playabilityStatus=${status}`);
continue;
} }
const result = await this.extractOnce(videoId, effectivePlatform); const result = await this.extractOnce(videoId, effectivePlatform);
if (result) return result; if (result) return result;
@ -723,7 +668,7 @@ export class YouTubeExtractor {
// Priority: HLS > progressive muxed // Priority: HLS > progressive muxed
// HLS manifests don't need validation — they're not CDN segment URLs // HLS manifests don't need validation — they're not CDN segment URLs
if (bestHls) { if (bestHls) {
logger.info('YouTubeExtractor', `Using HLS: ${summarizeUrl(bestHls.manifestUrl)}`); logger.info('YouTubeExtractor', `Using HLS manifest: ${summarizeUrl(bestHls.manifestUrl)} ${bestHls.height}p`);
return { return {
videoUrl: bestHls.manifestUrl, videoUrl: bestHls.manifestUrl,
audioUrl: null, audioUrl: null,