Docs/Skills and code/Top-level code

test-channel-response.js

Code478 lines10.8 KBTop-level code

import { Innertube } from "youtubei.js";
import { inspect } from "node:util";

// ============================================================
// HARDCODED TEST TARGET
// ============================================================

const CHANNEL_URL = "https://www.youtube.com/@DoctorWho";

// ============================================================
// LOGGING HELPERS
// ============================================================

const inspected = new WeakSet();

function section(title) {
  console.log("\n" + "=".repeat(80));
  console.log(title);
  console.log("=".repeat(80));
}

function log(label, value, options = {}) {
  console.log(`\n--- ${label} ---`);
  console.log(
    inspect(value, {
      depth: options.depth ?? 5,
      colors: false,
      compact: false,
      maxArrayLength: options.maxArrayLength ?? 100,
      maxStringLength: options.maxStringLength ?? 5000,
      breakLength: 120,
    })
  );
}

function safeKeys(value) {
  if (value == null) return [];

  try {
    return Reflect.ownKeys(value).map(String);
  } catch (error) {
    return [`<keys failed: ${error.message}>`];
  }
}

function prototypeChain(value) {
  const result = [];
  let current = value;
  let level = 0;

  while (current && level < 8) {
    result.push({
      level,
      constructor: current?.constructor?.name ?? "unknown",
      keys: safeKeys(current),
    });

    current = Object.getPrototypeOf(current);
    level++;
  }

  return result;
}

function describeObject(value, label) {
  if (value == null) {
    console.log(`\n[describe] ${label}: null/undefined`);
    return;
  }

  console.log(`\n[describe] ${label}`);
  console.log("constructor:", value?.constructor?.name);
  console.log("type:", value?.type);
  console.log("own keys:", safeKeys(value));
  console.log("prototype chain:", prototypeChain(value));
}

// ============================================================
// RECURSIVE SEARCH
// ============================================================

const interestingKeyPattern =
  /subscriber|subscribers|video.?count|keyword|title|description|metadata|header|content|author|channel/i;

function findInterestingPaths(
  value,
  path = "$",
  depth = 0,
  maxDepth = 8,
  results = []
) {
  if (value == null || depth > maxDepth) {
    return results;
  }

  const valueType = typeof value;

  if (valueType !== "object" && valueType !== "function") {
    return results;
  }

  if (inspected.has(value)) {
    return results;
  }

  inspected.add(value);

  let keys;

  try {
    keys = Reflect.ownKeys(value);
  } catch {
    return results;
  }

  for (const rawKey of keys) {
    const key = String(rawKey);
    const childPath = `${path}.${key}`;

    if (interestingKeyPattern.test(key)) {
      let child;

      try {
        child = value[rawKey];
      } catch (error) {
        child = `<getter threw: ${error.message}>`;
      }

      results.push({
        path: childPath,
        type: typeof child,
        constructor: child?.constructor?.name ?? null,
        value: child,
      });
    }

    let child;

    try {
      child = value[rawKey];
    } catch {
      continue;
    }

    findInterestingPaths(
      child,
      childPath,
      depth + 1,
      maxDepth,
      results
    );
  }

  return results;
}

function printInterestingPaths(value) {
  section("RECURSIVE SEARCH: INTERESTING FIELDS");

  const results = findInterestingPaths(value);

  console.log(`Found ${results.length} interesting field paths.`);

  for (const [index, result] of results.entries()) {
    console.log(`\n[${index + 1}] ${result.path}`);
    console.log("type:", result.type);
    console.log("constructor:", result.constructor);

    console.log(
      "value:",
      inspect(result.value, {
        depth: 3,
        colors: false,
        compact: false,
        maxArrayLength: 20,
        maxStringLength: 1000,
        breakLength: 120,
      })
    );
  }
}

// ============================================================
// MAIN TEST
// ============================================================

async function main() {
  section("CHANNEL RESPONSE MICROSCOPE");

  console.log("Hardcoded channel:", CHANNEL_URL);
  console.log("youtubei.js package will be queried directly.");
  console.log("NO application extractor is being used.");

  // ----------------------------------------------------------
  // 1. CREATE CLIENT
  // ----------------------------------------------------------

  section("1. CREATE YOUTUBE CLIENT");

  const youtube = await Innertube.create();

  console.log("Client created.");
  console.log("Client constructor:", youtube?.constructor?.name);
  console.log("Client own keys:", safeKeys(youtube));

  // ----------------------------------------------------------
  // 2. RESOLVE CHANNEL
  // ----------------------------------------------------------

  section("2. RESOLVE CHANNEL URL");

  console.log("Calling youtube.resolveURL()...");

  const endpoint = await youtube.resolveURL(CHANNEL_URL);

  console.log("resolveURL() succeeded.");

  describeObject(endpoint, "Resolved endpoint");

  log("Resolved endpoint", endpoint, {
    depth: 8,
  });

  const channelId =
    endpoint?.payload?.browseId ??
    endpoint?.payload?.browse_id ??
    null;

  console.log("\nExtracted channel ID:", channelId);

  if (!channelId) {
    throw new Error(
      "Could not extract channel ID from resolveURL response."
    );
  }

  // ----------------------------------------------------------
  // 3. GET CHANNEL
  // ----------------------------------------------------------

  section("3. GET CHANNEL");

  console.log(`Calling youtube.getChannel(${channelId})...`);

  const channel = await youtube.getChannel(channelId);

  console.log("getChannel() succeeded.");

  // ----------------------------------------------------------
  // 4. CHANNEL OBJECT
  // ----------------------------------------------------------

  section("4. CHANNEL OBJECT SHAPE");

  describeObject(channel, "Channel");

  log("Channel object — depth 4", channel, {
    depth: 4,
    maxArrayLength: 50,
    maxStringLength: 3000,
  });

  // ----------------------------------------------------------
  // 5. PROTOTYPE
  // ----------------------------------------------------------

  section("5. PROTOTYPE CHAIN");

  log(
    "Channel prototype chain",
    prototypeChain(channel),
    {
      depth: 6,
      maxArrayLength: 50,
    }
  );

  // ----------------------------------------------------------
  // 6. HEADER
  // ----------------------------------------------------------

  section("6. HEADER");

  console.log(
    "channel.header exists:",
    Boolean(channel?.header)
  );

  if (channel?.header) {
    describeObject(channel.header, "channel.header");

    log("channel.header", channel.header, {
      depth: 8,
      maxArrayLength: 50,
      maxStringLength: 5000,
    });
  }

  // ----------------------------------------------------------
  // 7. HEADER CONTENT
  // ----------------------------------------------------------

  section("7. HEADER.CONTENT");

  const headerContent = channel?.header?.content;

  console.log(
    "channel.header.content exists:",
    Boolean(headerContent)
  );

  if (headerContent) {
    describeObject(
      headerContent,
      "channel.header.content"
    );

    log(
      "channel.header.content",
      headerContent,
      {
        depth: 8,
        maxArrayLength: 50,
        maxStringLength: 5000,
      }
    );
  }

  // ----------------------------------------------------------
  // 8. KNOWN CANDIDATE PATHS
  // ----------------------------------------------------------

  section("8. KNOWN CANDIDATE PATHS");

  const candidates = {
    "channel.metadata": channel?.metadata,
    "channel.metadata.title": channel?.metadata?.title,
    "channel.metadata.external_id":
      channel?.metadata?.external_id,

    "channel.author": channel?.author,

    "channel.subscriber_count":
      channel?.subscriber_count,

    "channel.video_count":
      channel?.video_count,

    "channel.keywords":
      channel?.keywords,

    "channel.header.content.title":
      channel?.header?.content?.title,

    "channel.header.content.description":
      channel?.header?.content?.description,

    "channel.header.content.metadata":
      channel?.header?.content?.metadata,

    "channel.header.content.actions":
      channel?.header?.content?.actions,

    "channel.header.content.attributation":
      channel?.header?.content?.attributation,
  };

  for (const [path, value] of Object.entries(candidates)) {
    console.log(`\n${path}`);

    console.log(
      inspect(value, {
        depth: 8,
        colors: false,
        compact: false,
        maxArrayLength: 50,
        maxStringLength: 5000,
        breakLength: 120,
      })
    );
  }

  // ----------------------------------------------------------
  // 9. RECURSIVE SEARCH
  // ----------------------------------------------------------

  printInterestingPaths(channel);

  // ----------------------------------------------------------
  // 10. PROPERTY ACCESS TEST
  // ----------------------------------------------------------

  section("10. GETTERS / PROPERTY ACCESS TEST");

  const propertiesToTest = [
    "subscriber_count",
    "video_count",
    "keywords",
    "author",
    "metadata",
    "header",
    "thumbnails",
    "is_verified",
    "verified",
    "is_artist",
  ];

  for (const property of propertiesToTest) {
    try {
      const value = channel?.[property];

      console.log(`\nchannel.${property}`);

      console.log(
        "value:",
        inspect(value, {
          depth: 6,
          colors: false,
          compact: false,
          maxArrayLength: 30,
          maxStringLength: 3000,
        })
      );

      console.log("typeof:", typeof value);
      console.log(
        "constructor:",
        value?.constructor?.name ?? null
      );
    } catch (error) {
      console.log(`\nchannel.${property}`);
      console.log("ACCESS THREW:", error.message);
    }
  }

  // ----------------------------------------------------------
  // 11. FINAL SUMMARY
  // ----------------------------------------------------------

  section("11. FINAL RAW SUMMARY");

  console.log({
    channel_url: CHANNEL_URL,
    channel_id: channelId,
    constructor: channel?.constructor?.name,
    own_keys: safeKeys(channel),
    header_keys: safeKeys(channel?.header),
    header_content_keys: safeKeys(
      channel?.header?.content
    ),
    metadata_keys: safeKeys(channel?.metadata),
  });

  console.log("\nDONE.");
  console.log(
    "Do not modify the production extractor yet."
  );
  console.log(
    "Use this output to determine the actual response shape first."
  );
}

main().catch((error) => {
  console.error(
    "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
  );

  console.error("TEST FAILED");

  console.error(
    "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
  );

  console.error(error);

  process.exitCode = 1;
});

Shown as it is in the repository at build time (2026-11-03); the home directory appears as <home>. If this page and the file differ, the file is right. Back to the catalogue.