@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 CHANGED
@@ -206,7 +206,7 @@ function isRecoverable(code) {
206
206
 
207
207
  //#endregion
208
208
  //#region src/http/client.ts
209
- const log$7 = logger.scope("HttpClient");
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$7.info(`Attaching Cookie header (${cookieHeader.length} chars)`);
251
+ log$8.info(`Attaching Cookie header (${cookieHeader.length} chars)`);
252
252
  }
253
- log$7.debug("=== HTTP REQUEST ===");
254
- log$7.debug(`URL: ${url}`);
255
- log$7.debug(`Method: ${fetchOptions.method || "GET"}`);
256
- log$7.debug(`Headers: ${JSON.stringify(Object.fromEntries(baseHeaders.entries()), null, 2)}`);
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$7.debug(`Body (first 1000 chars): ${bodyStr.substring(0, 1e3)}`);
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$7.debug("=== HTTP RESPONSE ===");
268
- log$7.debug(`Status: ${response.status} ${response.statusText}`);
269
- log$7.debug(`Headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()), null, 2)}`);
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$7.info(`Parsed ${cookies.length} cookies (format: ${format}), ${youtubeCookies.length} YouTube/Google related`);
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$7.success(`Loaded ${youtubeCookies.length} YouTube/Google cookies`);
350
+ log$8.success(`Loaded ${youtubeCookies.length} YouTube/Google cookies`);
351
351
  return ok(youtubeCookies.length);
352
352
  } catch (error) {
353
- log$7.error("Failed to parse cookies:", error);
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$6 = logger.scope("InnerTube");
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$6.debug(`Fetching player for ${videoId} with client ${this.currentClient}`);
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$6.debug(`Using po_token (length: ${options.poToken.length})`);
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$6.debug("=== INNERTUBE REQUEST ===");
611
- log$6.debug(`Client: ${this.currentClient}`);
612
- log$6.debug(`API Key: ${config.API_KEY || "none"}`);
613
- log$6.debug(`URL: ${url}`);
614
- log$6.debug(`Payload: ${JSON.stringify(payload, null, 2)}`);
615
- log$6.debug(`Headers: ${JSON.stringify(this.getHeaders(), null, 2)}`);
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$6.error(`HTTP request failed for client ${this.currentClient}:`, responseResult.error.message);
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$6.error(`Player request failed for ${this.currentClient}: HTTP ${response.status}`);
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$6.debug("=== INNERTUBE RESPONSE ===");
629
- log$6.debug(`playabilityStatus: ${JSON.stringify(data.playabilityStatus, null, 2)}`);
630
- log$6.debug(`streamingData formats: ${data.streamingData?.formats?.length || 0}`);
631
- log$6.debug(`streamingData adaptiveFormats: ${data.streamingData?.adaptiveFormats?.length || 0}`);
632
- if (data.streamingData?.adaptiveFormats?.[0]) log$6.debug(`Sample format: ${JSON.stringify(data.streamingData.adaptiveFormats[0], null, 2)}`);
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$6.error(`Parse error for ${this.currentClient}:`, validated.summary);
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$6.debug(`Client ${this.currentClient} response: status=${status}, reason=${reason}`);
640
+ log$7.debug(`Client ${this.currentClient} response: status=${status}, reason=${reason}`);
641
641
  if (status === "ERROR") {
642
- log$6.warn(`Client ${this.currentClient} returned ERROR: ${reason}`);
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$6.warn(`Client ${this.currentClient} requires login: ${reason}`);
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$6.warn(`Client ${this.currentClient} unplayable: ${reason}`);
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$6.info(`Client ${this.currentClient} success: ${formatsCount} formats`);
654
+ log$7.info(`Client ${this.currentClient} success: ${formatsCount} formats`);
655
655
  return ok(validated);
656
656
  } catch (error) {
657
- log$6.error(`Parse exception for ${this.currentClient}:`, error);
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$6.info(`Starting fallback extraction for ${videoId}`);
671
- log$6.info(`Fallback order: ${this.FALLBACK_ORDER.join(" → ")}`);
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$6.info(`Trying client: ${clientName}`);
673
+ log$7.info(`Trying client: ${clientName}`);
674
674
  if (isErr(this.setClient(clientName))) {
675
- log$6.warn(`Failed to set client ${clientName}`);
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$6.success(`Client ${clientName} returned valid response`);
682
+ log$7.success(`Client ${clientName} returned valid response`);
683
683
  return result;
684
684
  }
685
- log$6.warn(`Client ${clientName} response invalid: ${validationResult.reason}`);
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$6.warn(`Client ${clientName} failed: ${result.error.message}`);
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$6.error(`All clients failed for ${videoId}. Errors:`, errors.map((e) => ({
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 htmlResult = await httpClient.get(YOUTUBE_BASE_URL);
1445
- if (!isOk(htmlResult)) return err(htmlResult.error);
1446
- const match$1 = (await htmlResult.value.text()).match(/\/s\/player\/[a-zA-Z0-9_-]+\/[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+\/base\.js/);
1447
- if (!match$1) return err(createError("PLAYER_FETCH_FAILED", "Could not determine player URL"));
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(),