@mks2508/bundlp 0.1.12 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +67 -50
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -206,7 +206,7 @@ function isRecoverable(code) {
|
|
|
206
206
|
|
|
207
207
|
//#endregion
|
|
208
208
|
//#region src/http/client.ts
|
|
209
|
-
const log$
|
|
209
|
+
const log$8 = logger.scope("HttpClient");
|
|
210
210
|
function parseCookies$2(setCookieHeaders) {
|
|
211
211
|
return setCookieHeaders.map((header) => {
|
|
212
212
|
const [nameValue, ...attributes] = header.split(";").map((p) => p.trim());
|
|
@@ -248,15 +248,15 @@ var HttpClient = class {
|
|
|
248
248
|
const baseHeaders = new Headers(fetchOptions.headers);
|
|
249
249
|
if (cookieHeader) {
|
|
250
250
|
baseHeaders.set("Cookie", cookieHeader);
|
|
251
|
-
log$
|
|
251
|
+
log$8.info(`Attaching Cookie header (${cookieHeader.length} chars)`);
|
|
252
252
|
}
|
|
253
|
-
log$
|
|
254
|
-
log$
|
|
255
|
-
log$
|
|
256
|
-
log$
|
|
253
|
+
log$8.debug("=== HTTP REQUEST ===");
|
|
254
|
+
log$8.debug(`URL: ${url}`);
|
|
255
|
+
log$8.debug(`Method: ${fetchOptions.method || "GET"}`);
|
|
256
|
+
log$8.debug(`Headers: ${JSON.stringify(Object.fromEntries(baseHeaders.entries()), null, 2)}`);
|
|
257
257
|
if (fetchOptions.body) {
|
|
258
258
|
const bodyStr = typeof fetchOptions.body === "string" ? fetchOptions.body : "[binary]";
|
|
259
|
-
log$
|
|
259
|
+
log$8.debug(`Body (first 1000 chars): ${bodyStr.substring(0, 1e3)}`);
|
|
260
260
|
}
|
|
261
261
|
if (!manualRedirects) try {
|
|
262
262
|
const response = await fetch(url, {
|
|
@@ -264,9 +264,9 @@ var HttpClient = class {
|
|
|
264
264
|
headers: baseHeaders,
|
|
265
265
|
signal: timeout ? AbortSignal.timeout(timeout) : void 0
|
|
266
266
|
});
|
|
267
|
-
log$
|
|
268
|
-
log$
|
|
269
|
-
log$
|
|
267
|
+
log$8.debug("=== HTTP RESPONSE ===");
|
|
268
|
+
log$8.debug(`Status: ${response.status} ${response.statusText}`);
|
|
269
|
+
log$8.debug(`Headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()), null, 2)}`);
|
|
270
270
|
return ok(response);
|
|
271
271
|
} catch (error) {
|
|
272
272
|
return err(createError("NETWORK_ERROR", `Request to ${url} failed: ${error.message}`, error));
|
|
@@ -337,7 +337,7 @@ var HttpClient = class {
|
|
|
337
337
|
const domain = c.domain?.toLowerCase() || "";
|
|
338
338
|
return domain.includes("youtube") || domain.includes("google") || domain.includes("googlevideo") || !c.domain;
|
|
339
339
|
});
|
|
340
|
-
log$
|
|
340
|
+
log$8.info(`Parsed ${cookies.length} cookies (format: ${format}), ${youtubeCookies.length} YouTube/Google related`);
|
|
341
341
|
for (const c of youtubeCookies) this.setCookie({
|
|
342
342
|
name: c.name,
|
|
343
343
|
value: c.value,
|
|
@@ -347,10 +347,10 @@ var HttpClient = class {
|
|
|
347
347
|
httpOnly: c.httpOnly,
|
|
348
348
|
expires: c.expires
|
|
349
349
|
});
|
|
350
|
-
log$
|
|
350
|
+
log$8.success(`Loaded ${youtubeCookies.length} YouTube/Google cookies`);
|
|
351
351
|
return ok(youtubeCookies.length);
|
|
352
352
|
} catch (error) {
|
|
353
|
-
log$
|
|
353
|
+
log$8.error("Failed to parse cookies:", error);
|
|
354
354
|
return err(createError("COOKIE_PARSE_ERROR", `Failed to parse cookies: ${error.message}`, error));
|
|
355
355
|
}
|
|
356
356
|
}
|
|
@@ -529,7 +529,7 @@ function isDrmOnly(formats) {
|
|
|
529
529
|
|
|
530
530
|
//#endregion
|
|
531
531
|
//#region src/innertube/client.ts
|
|
532
|
-
const log$
|
|
532
|
+
const log$7 = logger.scope("InnerTube");
|
|
533
533
|
/**
|
|
534
534
|
* Client for interacting with YouTube's InnerTube API.
|
|
535
535
|
*/
|
|
@@ -593,7 +593,7 @@ var InnerTubeClient = class {
|
|
|
593
593
|
*/
|
|
594
594
|
async fetchPlayer(videoId, options = {}) {
|
|
595
595
|
const config = INNERTUBE_CLIENTS[this.currentClient];
|
|
596
|
-
log$
|
|
596
|
+
log$7.debug(`Fetching player for ${videoId} with client ${this.currentClient}`);
|
|
597
597
|
const payload = {
|
|
598
598
|
context: this.buildContext(),
|
|
599
599
|
videoId,
|
|
@@ -604,57 +604,57 @@ var InnerTubeClient = class {
|
|
|
604
604
|
if (options.poToken) {
|
|
605
605
|
if (!payload.serviceIntegrityDimensions) payload.serviceIntegrityDimensions = {};
|
|
606
606
|
payload.serviceIntegrityDimensions.poToken = options.poToken;
|
|
607
|
-
log$
|
|
607
|
+
log$7.debug(`Using po_token (length: ${options.poToken.length})`);
|
|
608
608
|
}
|
|
609
609
|
const url = `${INNER_TUBE_API_URL}/player?key=${config.API_KEY || ""}`;
|
|
610
|
-
log$
|
|
611
|
-
log$
|
|
612
|
-
log$
|
|
613
|
-
log$
|
|
614
|
-
log$
|
|
615
|
-
log$
|
|
610
|
+
log$7.debug("=== INNERTUBE REQUEST ===");
|
|
611
|
+
log$7.debug(`Client: ${this.currentClient}`);
|
|
612
|
+
log$7.debug(`API Key: ${config.API_KEY || "none"}`);
|
|
613
|
+
log$7.debug(`URL: ${url}`);
|
|
614
|
+
log$7.debug(`Payload: ${JSON.stringify(payload, null, 2)}`);
|
|
615
|
+
log$7.debug(`Headers: ${JSON.stringify(this.getHeaders(), null, 2)}`);
|
|
616
616
|
const responseResult = await httpClient.post(url, payload, { headers: this.getHeaders() });
|
|
617
617
|
if (isErr(responseResult)) {
|
|
618
|
-
log$
|
|
618
|
+
log$7.error(`HTTP request failed for client ${this.currentClient}:`, responseResult.error.message);
|
|
619
619
|
return responseResult;
|
|
620
620
|
}
|
|
621
621
|
const response = responseResult.value;
|
|
622
622
|
if (!response.ok) {
|
|
623
|
-
log$
|
|
623
|
+
log$7.error(`Player request failed for ${this.currentClient}: HTTP ${response.status}`);
|
|
624
624
|
return err(createError("INNERTUBE_ERROR", `Player request failed with status ${response.status}`));
|
|
625
625
|
}
|
|
626
626
|
try {
|
|
627
627
|
const data = await response.json();
|
|
628
|
-
log$
|
|
629
|
-
log$
|
|
630
|
-
log$
|
|
631
|
-
log$
|
|
632
|
-
if (data.streamingData?.adaptiveFormats?.[0]) log$
|
|
628
|
+
log$7.debug("=== INNERTUBE RESPONSE ===");
|
|
629
|
+
log$7.debug(`playabilityStatus: ${JSON.stringify(data.playabilityStatus, null, 2)}`);
|
|
630
|
+
log$7.debug(`streamingData formats: ${data.streamingData?.formats?.length || 0}`);
|
|
631
|
+
log$7.debug(`streamingData adaptiveFormats: ${data.streamingData?.adaptiveFormats?.length || 0}`);
|
|
632
|
+
if (data.streamingData?.adaptiveFormats?.[0]) log$7.debug(`Sample format: ${JSON.stringify(data.streamingData.adaptiveFormats[0], null, 2)}`);
|
|
633
633
|
const validated = PlayerResponseSchema(data);
|
|
634
634
|
if (validated instanceof type.errors) {
|
|
635
|
-
log$
|
|
635
|
+
log$7.error(`Parse error for ${this.currentClient}:`, validated.summary);
|
|
636
636
|
return err(createError("PARSE_ERROR", `Invalid player response: ${validated.summary}`));
|
|
637
637
|
}
|
|
638
638
|
const status = validated.playabilityStatus.status;
|
|
639
639
|
const reason = validated.playabilityStatus.reason || "No reason";
|
|
640
|
-
log$
|
|
640
|
+
log$7.debug(`Client ${this.currentClient} response: status=${status}, reason=${reason}`);
|
|
641
641
|
if (status === "ERROR") {
|
|
642
|
-
log$
|
|
642
|
+
log$7.warn(`Client ${this.currentClient} returned ERROR: ${reason}`);
|
|
643
643
|
return err(createError("VIDEO_UNAVAILABLE", reason));
|
|
644
644
|
}
|
|
645
645
|
if (status === "LOGIN_REQUIRED") {
|
|
646
|
-
log$
|
|
646
|
+
log$7.warn(`Client ${this.currentClient} requires login: ${reason}`);
|
|
647
647
|
return err(createError("VIDEO_UNAVAILABLE", `Login required: ${reason}`));
|
|
648
648
|
}
|
|
649
649
|
if (status === "UNPLAYABLE") {
|
|
650
|
-
log$
|
|
650
|
+
log$7.warn(`Client ${this.currentClient} unplayable: ${reason}`);
|
|
651
651
|
return err(createError("VIDEO_UNAVAILABLE", `Unplayable: ${reason}`));
|
|
652
652
|
}
|
|
653
653
|
const formatsCount = (validated.streamingData?.formats?.length || 0) + (validated.streamingData?.adaptiveFormats?.length || 0);
|
|
654
|
-
log$
|
|
654
|
+
log$7.info(`Client ${this.currentClient} success: ${formatsCount} formats`);
|
|
655
655
|
return ok(validated);
|
|
656
656
|
} catch (error) {
|
|
657
|
-
log$
|
|
657
|
+
log$7.error(`Parse exception for ${this.currentClient}:`, error);
|
|
658
658
|
return err(createError("PARSE_ERROR", "Failed to parse InnerTube response", error));
|
|
659
659
|
}
|
|
660
660
|
}
|
|
@@ -667,35 +667,35 @@ var InnerTubeClient = class {
|
|
|
667
667
|
*/
|
|
668
668
|
async fetchPlayerWithFallback(videoId, options = {}) {
|
|
669
669
|
const errors = [];
|
|
670
|
-
log$
|
|
671
|
-
log$
|
|
670
|
+
log$7.info(`Starting fallback extraction for ${videoId}`);
|
|
671
|
+
log$7.info(`Fallback order: ${this.FALLBACK_ORDER.join(" → ")}`);
|
|
672
672
|
for (const clientName of this.FALLBACK_ORDER) {
|
|
673
|
-
log$
|
|
673
|
+
log$7.info(`Trying client: ${clientName}`);
|
|
674
674
|
if (isErr(this.setClient(clientName))) {
|
|
675
|
-
log$
|
|
675
|
+
log$7.warn(`Failed to set client ${clientName}`);
|
|
676
676
|
continue;
|
|
677
677
|
}
|
|
678
678
|
const result = await this.fetchPlayer(videoId, options);
|
|
679
679
|
if (isOk(result)) {
|
|
680
680
|
const validationResult = this.isValidResponse(result.value);
|
|
681
681
|
if (validationResult.valid) {
|
|
682
|
-
log$
|
|
682
|
+
log$7.success(`Client ${clientName} returned valid response`);
|
|
683
683
|
return result;
|
|
684
684
|
}
|
|
685
|
-
log$
|
|
685
|
+
log$7.warn(`Client ${clientName} response invalid: ${validationResult.reason}`);
|
|
686
686
|
errors.push({
|
|
687
687
|
client: clientName,
|
|
688
688
|
error: createError("VIDEO_UNAVAILABLE", validationResult.reason)
|
|
689
689
|
});
|
|
690
690
|
} else {
|
|
691
|
-
log$
|
|
691
|
+
log$7.warn(`Client ${clientName} failed: ${result.error.message}`);
|
|
692
692
|
errors.push({
|
|
693
693
|
client: clientName,
|
|
694
694
|
error: result.error
|
|
695
695
|
});
|
|
696
696
|
}
|
|
697
697
|
}
|
|
698
|
-
log$
|
|
698
|
+
log$7.error(`All clients failed for ${videoId}. Errors:`, errors.map((e) => ({
|
|
699
699
|
client: e.client,
|
|
700
700
|
error: e.error.message
|
|
701
701
|
})));
|
|
@@ -1412,6 +1412,7 @@ var PlayerCache = class {
|
|
|
1412
1412
|
|
|
1413
1413
|
//#endregion
|
|
1414
1414
|
//#region src/player/player.ts
|
|
1415
|
+
const log$6 = logger.scope("Player");
|
|
1415
1416
|
/**
|
|
1416
1417
|
* Handles YouTube player.js downloading, parsing, and cipher function extraction.
|
|
1417
1418
|
* Uses AST-based extraction for robustness against YouTube's obfuscation changes.
|
|
@@ -1432,20 +1433,36 @@ var Player = class {
|
|
|
1432
1433
|
* Downloads and parses the YouTube player.js to extract cipher functions.
|
|
1433
1434
|
* Uses cache-first strategy to minimize network requests.
|
|
1434
1435
|
* @param playerUrl - Optional direct URL to player.js, auto-detected if not provided
|
|
1436
|
+
* @param videoId - Optional video ID to fetch player URL from watch page (more reliable)
|
|
1435
1437
|
* @returns Result indicating success or failure
|
|
1436
1438
|
*/
|
|
1437
|
-
async initialize(playerUrl) {
|
|
1439
|
+
async initialize(playerUrl, videoId) {
|
|
1438
1440
|
if (this.initialized) return ok(void 0);
|
|
1439
1441
|
if (this.cache && !playerUrl) {
|
|
1440
1442
|
const latestResult = this.cache.getLatest();
|
|
1441
1443
|
if (isOk(latestResult) && latestResult.value) return this.restoreFromCache(latestResult.value);
|
|
1442
1444
|
}
|
|
1443
1445
|
if (!playerUrl) {
|
|
1444
|
-
const
|
|
1445
|
-
|
|
1446
|
-
const
|
|
1447
|
-
if (!
|
|
1446
|
+
const fetchUrl = videoId ? `${YOUTUBE_BASE_URL}/watch?v=${videoId}` : YOUTUBE_BASE_URL;
|
|
1447
|
+
log$6.info(`Fetching player URL from: ${fetchUrl}`);
|
|
1448
|
+
const htmlResult = await httpClient.get(fetchUrl);
|
|
1449
|
+
if (!isOk(htmlResult)) {
|
|
1450
|
+
log$6.error("Failed to fetch page:", htmlResult.error.message);
|
|
1451
|
+
return err(htmlResult.error);
|
|
1452
|
+
}
|
|
1453
|
+
const html = await htmlResult.value.text();
|
|
1454
|
+
log$6.info(`Response: ${html.length} chars`);
|
|
1455
|
+
const title = html.match(/<title>([^<]*)<\/title>/)?.[1] || "";
|
|
1456
|
+
if (html.includes("Sign in to confirm") || title.toLowerCase().includes("sign in")) log$6.error(`BOT DETECTION: YouTube returned login page (title: ${title})`);
|
|
1457
|
+
if (html.includes("robot") || html.includes("captcha")) log$6.error("BOT DETECTION: YouTube returned robot/captcha page");
|
|
1458
|
+
const match$1 = html.match(/\/s\/player\/[a-zA-Z0-9_-]+\/[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+\/base\.js/);
|
|
1459
|
+
if (!match$1) {
|
|
1460
|
+
log$6.error("Player URL pattern not found in HTML");
|
|
1461
|
+
log$6.error(`HTML title: ${title}`);
|
|
1462
|
+
return err(createError("PLAYER_FETCH_FAILED", `Could not determine player URL from ${fetchUrl}`));
|
|
1463
|
+
}
|
|
1448
1464
|
playerUrl = YOUTUBE_BASE_URL + match$1[0];
|
|
1465
|
+
log$6.success(`Found player URL: ${playerUrl}`);
|
|
1449
1466
|
}
|
|
1450
1467
|
const playerId = this.extractPlayerId(playerUrl);
|
|
1451
1468
|
if (this.cache && playerId) {
|
|
@@ -2346,7 +2363,7 @@ var YouTubeExtractor = class {
|
|
|
2346
2363
|
return err(createError("INVALID_URL", `Could not extract video ID from: ${url}`));
|
|
2347
2364
|
}
|
|
2348
2365
|
async realExtract(videoId) {
|
|
2349
|
-
const playerResult = await this.player.initialize();
|
|
2366
|
+
const playerResult = await this.player.initialize(void 0, videoId);
|
|
2350
2367
|
if (isErr(playerResult)) return err(playerResult.error);
|
|
2351
2368
|
const responseResult = await this.client.fetchPlayerWithFallback(videoId, {
|
|
2352
2369
|
signatureTimestamp: this.player.getSignatureTimestamp(),
|