{
  "version": "1.0.0",
  "exported_at": "2026-05-31T00:00:00.000Z",
  "project": {
    "name": "YouTube Video Comments Scraper",
    "description": "Scrapes YouTube video comments from one or more YouTube watch URLs. Extracts comment text, username, user channel URL, likes, published time, comment permalink, avatar URL, reply count, video URL, video ID, and video title. Navigation strategy: uses navigate.urls[] for multiple video URLs, handles common consent dialogs, progressively scrolls to load the comments section, then performs a bounded infinite-scroll loop for up to 20 scroll cycles before exporting all loaded comments. Results are appended to youtube-video-comments-scraper.csv and loop-continue advances to the next video URL. Best-effort template: YouTube is dynamic and may require consent, login, age verification, or block automated scraping; comments may be disabled; very large comment threads may require increasing the scroll-cycle guard in inject-javascript-3.",
    "color": "bg-[#ff0000]",
    "template_id": "ai-generated"
  },
  "blocks": [
    {
      "block_id": "set-window-size-1",
      "block_type": "process",
      "title": "Set Window Size",
      "description": "Set browser window dimensions",
      "position_x": 120,
      "position_y": 220,
      "config": {
        "width": 1920,
        "height": 1080,
        "color": "bg-[#4589ff]"
      }
    },
    {
      "block_id": "navigate-1",
      "block_type": "process",
      "title": "Navigate",
      "description": "Go to a URL",
      "position_x": 456,
      "position_y": 220,
      "config": {
        "urls": [
          "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
        ],
        "color": "bg-[#4589ff]"
      }
    },
    {
      "block_id": "wait-for-page-load-1",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 792,
      "position_y": 220,
      "config": {
        "timeout": 45,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "inject-javascript-1",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Run custom JavaScript on the page",
      "position_x": 1128,
      "position_y": 220,
      "config": {
        "jsCode": "(() => {\n  window.__uscraperCommentScrollCount = 0;\n  const textPattern = /(accept all|i agree|agree|accept|allow all)/i;\n  const candidates = Array.from(document.querySelectorAll('button, tp-yt-paper-button, yt-button-renderer button, form button, input[type=\"submit\"]'));\n  const button = candidates.find(el => {\n    const txt = ((el.innerText || el.textContent || el.value || '') + ' ' + (el.getAttribute('aria-label') || '')).trim();\n    const rect = el.getBoundingClientRect();\n    return rect.width > 0 && rect.height > 0 && textPattern.test(txt);\n  });\n  if (button) button.click();\n  return true;\n})()",
        "waitForCompletion": true,
        "timeout": 10,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 1464,
      "position_y": 220,
      "config": {
        "duration": 5,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "inject-javascript-2",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Run custom JavaScript on the page",
      "position_x": 1800,
      "position_y": 220,
      "config": {
        "jsCode": "(async () => {\n  const delay = ms => new Promise(resolve => setTimeout(resolve, ms));\n  const clickConsent = () => {\n    const pattern = /(accept all|i agree|agree|accept|allow all)/i;\n    const buttons = Array.from(document.querySelectorAll('button, tp-yt-paper-button, yt-button-renderer button, form button, input[type=\"submit\"]'));\n    const btn = buttons.find(el => {\n      const txt = ((el.innerText || el.textContent || el.value || '') + ' ' + (el.getAttribute('aria-label') || '')).trim();\n      const rect = el.getBoundingClientRect();\n      return rect.width > 0 && rect.height > 0 && pattern.test(txt);\n    });\n    if (btn) btn.click();\n  };\n  clickConsent();\n  await delay(1000);\n  for (let i = 0; i < 40; i++) {\n    clickConsent();\n    const comments = document.querySelector('ytd-comments#comments, #comments');\n    const rows = document.querySelectorAll('ytd-comment-thread-renderer');\n    if (rows.length > 0 && i >= 2) {\n      return rows.length;\n    }\n    if (comments && i < 8) {\n      comments.scrollIntoView({ block: 'start', inline: 'nearest' });\n      window.scrollBy(0, Math.floor(window.innerHeight * 0.75));\n    } else {\n      window.scrollBy(0, Math.floor(window.innerHeight * 0.9));\n    }\n    await delay(800);\n  }\n  return document.querySelectorAll('ytd-comment-thread-renderer').length;\n})()",
        "waitForCompletion": true,
        "timeout": 45,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "wait-for-element-1",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 2136,
      "position_y": 220,
      "config": {
        "selector": "ytd-comment-thread-renderer",
        "timeout": 90,
        "visible": true,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "scroll-1",
      "block_type": "process",
      "title": "Scroll",
      "description": "Scroll the page or element",
      "position_x": 2472,
      "position_y": 220,
      "config": {
        "direction": "down",
        "amount": 1800,
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "sleep-2",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 2808,
      "position_y": 220,
      "config": {
        "duration": 2,
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "inject-javascript-3",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Run custom JavaScript on the page",
      "position_x": 3144,
      "position_y": 220,
      "config": {
        "jsCode": "(() => {\n  const maxScrollCycles = 20;\n  window.__uscraperCommentScrollCount = (window.__uscraperCommentScrollCount || 0) + 1;\n  const loaded = document.querySelectorAll('ytd-comment-thread-renderer').length;\n  const continuations = Array.from(document.querySelectorAll('ytd-continuation-item-renderer'));\n  if (window.__uscraperCommentScrollCount >= maxScrollCycles) {\n    continuations.forEach(el => {\n      el.setAttribute('hidden', '');\n      el.style.display = 'none';\n    });\n  }\n  return { scrollCycles: window.__uscraperCommentScrollCount, loadedComments: loaded, maxScrollCycles };\n})()",
        "waitForCompletion": true,
        "timeout": 10,
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "element-exists-1",
      "block_type": "process",
      "title": "Element Exists",
      "description": "Check if element exists",
      "position_x": 3480,
      "position_y": 520,
      "config": {
        "selector": "ytd-continuation-item-renderer:not([hidden])",
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 3480,
      "position_y": 520,
      "config": {
        "rowSelector": "ytd-comment-thread-renderer",
        "fileName": "youtube-video-comments-scraper.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "color": "bg-[#42be65]",
        "columns": [
          {
            "name": "video_url",
            "selector": "(() => { try { const u = new URL(window.location.href); const v = u.searchParams.get('v'); return v ? `https://www.youtube.com/watch?v=${v}` : window.location.href; } catch(e) { return window.location.href; } })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "video_id",
            "selector": "(() => { try { return new URL(window.location.href).searchParams.get('v') || ''; } catch(e) { return ''; } })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "video_title",
            "selector": "(() => { const meta = document.querySelector('meta[property=\"og:title\"], meta[name=\"title\"]')?.getAttribute('content'); const h1 = document.querySelector('h1.ytd-watch-metadata yt-formatted-string, h1 yt-formatted-string, h1')?.textContent; const title = meta || h1 || document.title || ''; return title.replace(/\\s*-\\s*YouTube\\s*$/i, '').trim(); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "username",
            "selector": "(() => (ROW.querySelector('#author-text span, #author-text')?.textContent || '').trim())()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "user_channel_url",
            "selector": "(() => { const href = ROW.querySelector('#author-text')?.getAttribute('href') || ''; try { return href ? new URL(href, location.origin).href : ''; } catch(e) { return href; } })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "comment_text",
            "selector": "(() => (ROW.querySelector('#content-text')?.innerText || ROW.querySelector('#content-text')?.textContent || '').trim())()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "likes",
            "selector": "(() => (ROW.querySelector('#vote-count-middle')?.textContent || '').trim())()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "published_time",
            "selector": "(() => (ROW.querySelector('#published-time-text a')?.textContent || '').trim())()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "comment_url",
            "selector": "(() => { const href = ROW.querySelector('#published-time-text a')?.getAttribute('href') || ''; try { return href ? new URL(href, location.origin).href : ''; } catch(e) { return href; } })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "comment_id",
            "selector": "(() => { const href = ROW.querySelector('#published-time-text a')?.href || ''; try { return new URL(href, location.origin).searchParams.get('lc') || ''; } catch(e) { return ''; } })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "author_avatar_url",
            "selector": "(() => ROW.querySelector('#author-thumbnail img')?.src || ROW.querySelector('#author-thumbnail img')?.getAttribute('src') || '')()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "reply_count",
            "selector": "(() => (ROW.querySelector('#more-replies #text, ytd-button-renderer#more-replies yt-formatted-string, #replies #text')?.innerText || '').trim())()",
            "attribute": "text",
            "isJs": true
          }
        ]
      }
    },
    {
      "block_id": "loop-continue-1",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 3984,
      "position_y": 800,
      "config": {
        "color": "bg-[#8d8d8d]"
      }
    }
  ],
  "connections": [
    {
      "from_block_id": "set-window-size-1",
      "from_connector_id": "right",
      "to_block_id": "navigate-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "navigate-1",
      "from_connector_id": "right",
      "to_block_id": "wait-for-page-load-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-page-load-1",
      "from_connector_id": "right",
      "to_block_id": "inject-javascript-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "inject-javascript-1",
      "from_connector_id": "right",
      "to_block_id": "sleep-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-1",
      "from_connector_id": "right",
      "to_block_id": "inject-javascript-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "inject-javascript-2",
      "from_connector_id": "right",
      "to_block_id": "wait-for-element-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-element-1",
      "from_connector_id": "right",
      "to_block_id": "scroll-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "scroll-1",
      "from_connector_id": "right",
      "to_block_id": "sleep-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-2",
      "from_connector_id": "right",
      "to_block_id": "inject-javascript-3",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "inject-javascript-3",
      "from_connector_id": "right",
      "to_block_id": "element-exists-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-exists-1",
      "from_connector_id": "true",
      "to_block_id": "scroll-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-exists-1",
      "from_connector_id": "false",
      "to_block_id": "structured-export-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "structured-export-1",
      "from_connector_id": "right",
      "to_block_id": "loop-continue-1",
      "to_connector_id": "left"
    }
  ],
  "canvas_elements": [
    {
      "id": "group-entry",
      "element_type": "group",
      "title": "Entry & Setup",
      "color": "#4589ff",
      "position_x": 48,
      "position_y": 116,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "set-window-size-1"
        ]
      }
    },
    {
      "id": "group-load",
      "element_type": "group",
      "title": "Page Load",
      "color": "#08bdba",
      "position_x": 384,
      "position_y": 116,
      "width": 2672,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "sleep-1",
          "wait-for-element-1",
          "sleep-2"
        ]
      }
    },
    {
      "id": "group-interaction",
      "element_type": "group",
      "title": "Interaction",
      "color": "#a56eff",
      "position_x": 1056,
      "position_y": 116,
      "width": 2336,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "inject-javascript-1",
          "inject-javascript-2",
          "scroll-1",
          "inject-javascript-3"
        ]
      }
    },
    {
      "id": "group-pagination",
      "element_type": "group",
      "title": "Pagination Loop",
      "color": "#ff832b",
      "position_x": 3408,
      "position_y": 416,
      "width": 824,
      "height": 576,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "element-exists-1",
          "loop-continue-1"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 3408,
      "position_y": 416,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "structured-export-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Scrapes YouTube video comments from one or more YouTube watch URLs. Extracts comment text, username, user channel URL, likes, published time, comment permalink, avatar URL, reply count, video URL, video ID, and video title. Navigation strategy: uses navigate.urls[] for multiple video URLs, handles common consent dialogs, progressively scrolls to load the comments section, then performs a bounded infinite-scroll loop for up to 20 scroll cycles before exporting all loaded comments. Results are appended to youtube-video-comments-scraper.csv and loop-continue advances to the next video URL. Best-effort template: YouTube is dynamic and may require consent, login, age verification, or block automated scraping; comments may be disabled; very large comment threads may require increasing the scroll-cycle guard in inject-javascript-3.",
      "color": "#f1c21b",
      "position_x": 80,
      "position_y": 20,
      "width": 480,
      "height": 160,
      "z_index": 22,
      "data": {}
    },
    {
      "id": "note-block-navigate-1",
      "element_type": "note",
      "title": "Note: Navigate",
      "content": "Multi-URL loop over 1 pages. Pair with loop-continue at the end of each iteration.",
      "color": "#ee5396",
      "position_x": 656,
      "position_y": 200,
      "width": 328,
      "height": 107,
      "z_index": 22,
      "data": {
        "block_id": "navigate-1"
      }
    },
    {
      "id": "note-block-inject-javascript-1",
      "element_type": "note",
      "title": "Note: Inject JavaScript",
      "content": "Runs custom JavaScript in the page: `(() => {\n  window.__uscraperCommentScrollCount = 0;\n  const textPattern = /(accept all|i agree|agree...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 1328,
      "position_y": 200,
      "width": 340,
      "height": 140,
      "z_index": 22,
      "data": {
        "block_id": "inject-javascript-1"
      }
    },
    {
      "id": "note-block-inject-javascript-2",
      "element_type": "note",
      "title": "Note: Inject JavaScript",
      "content": "Runs custom JavaScript in the page: `(async () => {\n  const delay = ms => new Promise(resolve => setTimeout(resolve, ms));\n  const clickC...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 2000,
      "position_y": 200,
      "width": 340,
      "height": 140,
      "z_index": 22,
      "data": {
        "block_id": "inject-javascript-2"
      }
    },
    {
      "id": "note-block-inject-javascript-3",
      "element_type": "note",
      "title": "Note: Inject JavaScript",
      "content": "Runs custom JavaScript in the page: `(() => {\n  const maxScrollCycles = 20;\n  window.__uscraperCommentScrollCount = (window.__uscraperCom...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 3344,
      "position_y": 200,
      "width": 340,
      "height": 140,
      "z_index": 22,
      "data": {
        "block_id": "inject-javascript-3"
      }
    },
    {
      "id": "note-block-element-exists-1",
      "element_type": "note",
      "title": "Note: Element Exists",
      "content": "Condition block: checks `ytd-continuation-item-renderer:not([hidden])`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 3680,
      "position_y": 500,
      "width": 340,
      "height": 145,
      "z_index": 22,
      "data": {
        "block_id": "element-exists-1"
      }
    },
    {
      "id": "note-block-structured-export-1",
      "element_type": "note",
      "title": "Note: Structured Export",
      "content": "Structured export with JS columns (video_url, video_id, video_title, username, user_channel_url). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 3680,
      "position_y": 500,
      "width": 340,
      "height": 134,
      "z_index": 22,
      "data": {
        "block_id": "structured-export-1"
      }
    },
    {
      "id": "note-block-loop-continue-1",
      "element_type": "note",
      "title": "Note: Loop Continue",
      "content": "Loop Continue advances a multi-URL or multi-text loop. Place at the end of the loop body with a clear back-edge to the loop start.",
      "color": "#ee5396",
      "position_x": 4184,
      "position_y": 780,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}