{
  "version": "1.0.0",
  "exported_at": "2026-06-01T15:20:00.000Z",
  "project": {
    "name": "Gab Scraper",
    "description": "Best-effort UScraper equivalent of the Octoparse Gab Scraper. Extracts Gab post data fields shown in the Octoparse preview: keyword, username, account, post_time, post, replies, reposts, quotes, post_url, and user_page. Because Gab keyword search did not expose public post containers during testing, this version uses a multi-URL loop over the public Gab post URLs shown in the Octoparse sample for keyword 'Elon Musk'. Replace or extend navigate.urls with discovered Gab post URLs for other keywords. Uses fileMode=append and loop-continue to collect all configured post pages.",
    "color": "bg-[#4589ff]",
    "template_id": "ai-generated-gab-scraper"
  },
  "blocks": [
    {
      "block_id": "set-window-size-1",
      "block_type": "process",
      "title": "Set Window Size",
      "description": "Set browser viewport size",
      "position_x": 120,
      "position_y": 280,
      "config": {
        "width": 1920,
        "height": 1080,
        "color": "bg-[#4589ff]"
      }
    },
    {
      "block_id": "navigate-1",
      "block_type": "process",
      "title": "Navigate",
      "description": "Go to a URL",
      "position_x": 480,
      "position_y": 280,
      "config": {
        "urls": [
          "https://gab.com/JordanMcClung/posts/111735113108632160",
          "https://gab.com/hardmasada/posts/111735110041114723",
          "https://gab.com/butterfliesRfree/posts/111735106292147527",
          "https://gab.com/TheodoreKavinsky/posts/111735091149823444",
          "https://gab.com/cavedawg/posts/111735016035103833",
          "https://gab.com/Kurama_the_Kitsune/posts/111735008836951054",
          "https://gab.com/kfleming54/posts/111734990069976514",
          "https://gab.com/JordanMcClung/posts/111734990696736795"
        ],
        "color": "bg-[#08bdba]",
        "tags": [
          "gab",
          "post-detail",
          "multi-url",
          "keyword-elon-musk"
        ]
      }
    },
    {
      "block_id": "wait-for-page-load-1",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 840,
      "position_y": 280,
      "config": {
        "timeout": 30,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 1200,
      "position_y": 280,
      "config": {
        "duration": 4,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "inject-javascript-1",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Execute custom JavaScript",
      "position_x": 1560,
      "position_y": 280,
      "config": {
        "jsCode": "(async () => { const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); window.scrollTo(0, 600); await sleep(1000); window.scrollTo(0, 0); document.body.setAttribute('data-uscraper-gab-detail', '1'); document.body.setAttribute('data-keyword', 'Elon Musk'); return true; })()",
        "waitForCompletion": true,
        "timeout": 15,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "wait-for-element-1",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 1920,
      "position_y": 280,
      "config": {
        "selector": "body[data-uscraper-gab-detail=\"1\"]",
        "timeout": 10,
        "visible": true,
        "color": "bg-[#42be65]"
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 2280,
      "position_y": 280,
      "config": {
        "rowSelector": "body[data-uscraper-gab-detail=\"1\"]",
        "fileName": "gab-scraper.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "color": "bg-[#42be65]",
        "columns": [
          {
            "name": "keyword",
            "selector": "ROW.getAttribute('data-keyword') || 'Elon Musk'",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "username",
            "selector": "(function(){ const og = document.querySelector('meta[property=\"og:title\"], meta[name=\"twitter:title\"]'); const title = og ? og.getAttribute('content') || '' : document.title || ''; let m = title.match(/^(.+?)\\s+(?:on\\s+Gab|\\|\\s+Gab|\\/\\s+Gab)/i); if (m) return m[1].replace(/^@/, '').trim(); const pathUser = location.pathname.match(/^\\/([^/?#]+)\\/posts\\//); const profileHref = pathUser ? '/' + pathUser[1] : ''; const link = profileHref ? Array.from(document.querySelectorAll('a[href]')).find(a => a.getAttribute('href') === profileHref && (a.innerText || '').trim() && !(a.innerText || '').trim().startsWith('@')) : null; if (link) return link.innerText.trim(); return pathUser ? decodeURIComponent(pathUser[1]) : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "account",
            "selector": "(function(){ const m = location.pathname.match(/^\\/([^/?#]+)\\/posts\\//); if (m) return '@' + decodeURIComponent(m[1]); const acct = Array.from(document.querySelectorAll('a, span, div')).find(e => /^@[A-Za-z0-9_]+$/.test((e.innerText || '').trim())); return acct ? acct.innerText.trim() : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "post_time",
            "selector": "(function(){ const time = document.querySelector('time'); if (time) return time.getAttribute('datetime') || time.textContent.trim(); const candidates = Array.from(document.querySelectorAll('a, span, div')).map(e => (e.innerText || '').trim()).filter(Boolean); const hit = candidates.find(t => /\\b(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\\b|\\d{4}\\/\\d{2}\\/\\d{2}|\\d{1,2}:\\d{2}\\s*(?:AM|PM)?/i.test(t)); return hit || ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "post",
            "selector": "(function(){ const selectors = ['[data-testid=\"status-content\"]','[data-testid=\"post-content\"]','.status__content','.post__content','.gab__post-body','.post-body','.status-body','.post']; for (const s of selectors) { const el = document.querySelector(s); if (el && (el.innerText || '').trim().length > 10) return el.innerText.trim(); } const meta = document.querySelector('meta[property=\"og:description\"], meta[name=\"description\"], meta[name=\"twitter:description\"]'); if (meta && (meta.getAttribute('content') || '').trim()) return meta.getAttribute('content').trim(); const article = document.querySelector('article') || document.querySelector('main') || document.body; const clone = article.cloneNode(true); clone.querySelectorAll('nav, header, footer, aside, button, svg, img, video, time, script, style, noscript, [role=\"button\"], [aria-label*=\"reply\" i], [aria-label*=\"repost\" i], [aria-label*=\"quote\" i], [aria-label*=\"like\" i], [aria-label*=\"share\" i]').forEach(e => e.remove()); let text = (clone.innerText || '').trim(); text = text.replace(/\\n{3,}/g, '\\n\\n'); text = text.replace(/^(Gab\\s*)+/i, '').trim(); return text; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "replies",
            "selector": "(function(){ const text = ((document.body.innerText || '') + ' ' + Array.from(document.querySelectorAll('[aria-label], [title]')).map(e => (e.getAttribute('aria-label') || '') + ' ' + (e.getAttribute('title') || '')).join(' ')); const m = text.match(/([\\d,.]+\\s*[KkMm]?)\\s*(?:reply|replies|comment|comments)\\b/i); return m ? m[1].trim() : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "reposts",
            "selector": "(function(){ const text = ((document.body.innerText || '') + ' ' + Array.from(document.querySelectorAll('[aria-label], [title]')).map(e => (e.getAttribute('aria-label') || '') + ' ' + (e.getAttribute('title') || '')).join(' ')); const m = text.match(/([\\d,.]+\\s*[KkMm]?)\\s*(?:repost|reposts|reposted|reblog|reblogs)\\b/i); return m ? m[1].trim() : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "quotes",
            "selector": "(function(){ const text = ((document.body.innerText || '') + ' ' + Array.from(document.querySelectorAll('[aria-label], [title]')).map(e => (e.getAttribute('aria-label') || '') + ' ' + (e.getAttribute('title') || '')).join(' ')); const m = text.match(/([\\d,.]+\\s*[KkMm]?)\\s*(?:quote|quotes)\\b/i); return m ? m[1].trim() : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "post_url",
            "selector": "(function(){ const m = location.pathname.match(/^\\/[^/?#]+\\/posts\\/\\d+/); if (m) return location.origin + m[0]; const canonical = document.querySelector('link[rel=\"canonical\"]'); return canonical ? canonical.href : location.href; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "user_page",
            "selector": "(function(){ const m = location.pathname.match(/^\\/([^/?#]+)\\/posts\\//); if (m) return location.origin + '/' + decodeURIComponent(m[1]); const profile = Array.from(document.querySelectorAll('a[href]')).find(a => { try { const u = new URL(a.getAttribute('href'), location.origin); return /^\\/[^/?#]+\\/?$/.test(u.pathname) && !['/login','/signup','/search','/home'].includes(u.pathname); } catch(e) { return false; } }); return profile ? new URL(profile.getAttribute('href'), location.origin).href : ''; })()",
            "attribute": "text",
            "isJs": true
          }
        ]
      }
    },
    {
      "block_id": "loop-continue-1",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 2640,
      "position_y": 280,
      "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": "sleep-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-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": "wait-for-element-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-element-1",
      "from_connector_id": "right",
      "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": 176,
      "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": 408,
      "position_y": 176,
      "width": 1760,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "sleep-1",
          "wait-for-element-1"
        ]
      }
    },
    {
      "id": "group-interaction",
      "element_type": "group",
      "title": "Interaction",
      "color": "#a56eff",
      "position_x": 1488,
      "position_y": 176,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "inject-javascript-1"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 2208,
      "position_y": 176,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "structured-export-1"
        ]
      }
    },
    {
      "id": "group-pagination",
      "element_type": "group",
      "title": "Pagination Loop",
      "color": "#ff832b",
      "position_x": 2568,
      "position_y": 176,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "loop-continue-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Best-effort UScraper equivalent of the Octoparse Gab Scraper. Extracts Gab post data fields shown in the Octoparse preview: keyword, username, account, post_time, post, replies, reposts, quotes, post_url, and user_page. Because Gab keyword search did not expose public post containers during testing, this version uses a multi-URL loop over the public Gab post URLs shown in the Octoparse sample for keyword 'Elon Musk'. Replace or extend navigate.urls with discovered Gab post URLs for other keywords. Uses fileMode=append and loop-continue to collect all configured post pages.",
      "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 8 pages. Pair with loop-continue at the end of each iteration.",
      "color": "#ee5396",
      "position_x": 680,
      "position_y": 260,
      "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: `(async () => { const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); window.scrollTo(...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 1760,
      "position_y": 260,
      "width": 340,
      "height": 140,
      "z_index": 22,
      "data": {
        "block_id": "inject-javascript-1"
      }
    },
    {
      "id": "note-block-structured-export-1",
      "element_type": "note",
      "title": "Note: Structured Export",
      "content": "Structured export with JS columns (keyword, username, account, post_time, post). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 2480,
      "position_y": 260,
      "width": 340,
      "height": 128,
      "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": 2840,
      "position_y": 260,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}