tap
Back

youtube/channel

youtubeRead-only

Get YouTube channel info and recent videos

www.youtube.com
Last 7 days
0
Last 30 days
0
All time
0
youtube/channel.js
/* @meta
{
  "name": "youtube/channel",
  "description": "Get YouTube channel info and recent videos",
  "domain": "www.youtube.com",
  "args": {
    "id": {"required": false, "description": "Channel ID (UCxxxx) or handle (@name). Defaults to current page channel."},
    "max": {"required": false, "description": "Max recent videos to return (default: 10)"}
  },
  "capabilities": ["network"],
  "readOnly": true,
  "example": "tap site youtube/channel '@programmingwithmosh'"
}
*/

async function(args) {
  const cfg = window.ytcfg?.data_ || {};
  const apiKey = cfg.INNERTUBE_API_KEY;
  const context = cfg.INNERTUBE_CONTEXT;
  if (!apiKey || !context) return {error: 'YouTube config not found', hint: 'Make sure you are on youtube.com'};

  const max = Math.min(parseInt(args.max) || 10, 30);
  let browseId = args.id || '';

  // Detect from current page
  if (!browseId) {
    const match = location.href.match(/youtube\.com\/(channel\/|c\/|@)([^/?]+)/);
    if (match) {
      browseId = match[1] === 'channel/' ? match[2] : '@' + match[2].replace(/^@/, '');
    }
  }
  if (!browseId) return {error: 'No channel ID or handle', hint: 'Provide a channel ID (UCxxxx) or handle (@name)'};

  // If it's a handle, need to resolve it
  let resolvedBrowseId = browseId;
  if (browseId.startsWith('@')) {
    const resolveResp = await fetch('/youtubei/v1/navigation/resolve_url?key=' + apiKey + '&prettyPrint=false', {
      method: 'POST',
      credentials: 'include',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({context, url: 'https://www.youtube.com/' + browseId})
    });
    if (resolveResp.ok) {
      const resolveData = await resolveResp.json();
      resolvedBrowseId = resolveData.endpoint?.browseEndpoint?.browseId || browseId;
    }
  }

  // Fetch channel data
  const resp = await fetch('/youtubei/v1/browse?key=' + apiKey + '&prettyPrint=false', {
    method: 'POST',
    credentials: 'include',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({context, browseId: resolvedBrowseId})
  });

  if (!resp.ok) return {error: 'Channel API returned HTTP ' + resp.status, hint: resp.status === 404 ? 'Channel not found' : 'API error'};
  const data = await resp.json();

  // Channel metadata
  const metadata = data.metadata?.channelMetadataRenderer || {};
  const header = data.header?.pageHeaderRenderer || data.header?.c4TabbedHeaderRenderer || {};

  // Try to get subscriber count from header
  let subscriberCount = '';
  if (header.content?.pageHeaderViewModel?.metadata?.contentMetadataViewModel?.metadataRows) {
    const rows = header.content.pageHeaderViewModel.metadata.contentMetadataViewModel.metadataRows;
    for (const row of rows) {
      for (const part of (row.metadataParts || [])) {
        const text = part.text?.content || '';
        if (text.includes('subscriber')) subscriberCount = text;
      }
    }
  }

  // Get tabs to find Videos tab
  const tabs = data.contents?.twoColumnBrowseResultsRenderer?.tabs || [];
  const tabNames = tabs.map(t => t.tabRenderer?.title || t.expandableTabRenderer?.title).filter(Boolean);

  // Extract recent videos from the Home or Videos tab
  let recentVideos = [];

  // Try Home tab first
  const homeTab = tabs.find(t => t.tabRenderer?.selected);
  if (homeTab) {
    const sections = homeTab.tabRenderer?.content?.sectionListRenderer?.contents || [];
    for (const section of sections) {
      const shelfItems = section.itemSectionRenderer?.contents || [];
      for (const shelf of shelfItems) {
        const items = shelf.shelfRenderer?.content?.horizontalListRenderer?.items || [];
        for (const item of items) {
          const lvm = item.lockupViewModel;
          if (lvm && lvm.contentType === 'LOCKUP_CONTENT_TYPE_VIDEO' && recentVideos.length < max) {
            const meta = lvm.metadata?.lockupMetadataViewModel;
            const rows = meta?.metadata?.contentMetadataViewModel?.metadataRows || [];
            let viewsAndTime = (rows[0]?.metadataParts || []).map(p => p.text?.content).filter(Boolean).join(' | ');
            const overlays = lvm.contentImage?.thumbnailViewModel?.overlays || [];
            let duration = '';
            for (const ov of overlays) {
              for (const b of (ov.thumbnailBottomOverlayViewModel?.badges || [])) {
                if (b.thumbnailBadgeViewModel?.text) duration = b.thumbnailBadgeViewModel.text;
              }
            }
            recentVideos.push({
              videoId: lvm.contentId,
              title: meta?.title?.content || '',
              duration,
              viewsAndTime,
              url: 'https://www.youtube.com/watch?v=' + lvm.contentId
            });
          }
          // Also handle gridVideoRenderer (older format)
          if (item.gridVideoRenderer && recentVideos.length < max) {
            const v = item.gridVideoRenderer;
            recentVideos.push({
              videoId: v.videoId,
              title: v.title?.runs?.[0]?.text || v.title?.simpleText || '',
              duration: v.thumbnailOverlays?.[0]?.thumbnailOverlayTimeStatusRenderer?.text?.simpleText || '',
              viewsAndTime: (v.shortViewCountText?.simpleText || '') + (v.publishedTimeText?.simpleText ? ' | ' + v.publishedTimeText.simpleText : ''),
              url: 'https://www.youtube.com/watch?v=' + v.videoId
            });
          }
        }
      }
    }
  }

  return {
    channelId: metadata.externalId || resolvedBrowseId,
    name: metadata.title || '',
    handle: metadata.vanityChannelUrl?.split('/').pop() || '',
    description: (metadata.description || '').substring(0, 500),
    subscriberCount,
    channelUrl: metadata.channelUrl || 'https://www.youtube.com/channel/' + resolvedBrowseId,
    keywords: metadata.keywords || '',
    isFamilySafe: metadata.isFamilySafe,
    tabs: tabNames,
    recentVideoCount: recentVideos.length,
    recentVideos
  };
}
Updated Mar 31, 2026Created Mar 31, 2026SHA-256: 4c65ed213d67