{
  "version": "1.0.0",
  "exported_at": "2026-06-03T14:25:00.000Z",
  "project": {
    "name": "Reddit Subreddit Scraper",
    "description": "Scrapes Reddit subreddit posts by subreddit URL, extracting Subreddit, Subreddit_URL, Post_URL, Post_title, Author, Post_time, Upvote, and Comment_count. Reddit HTML and JSON endpoints were blocked in automated testing, so this best-effort template starts from the subreddit Atom/RSS feed, attempts JSON pagination for full score/comment data, and falls back to parsing Atom feed entries. Feed fallback may leave Upvote or Comment_count blank if Reddit does not expose them in feed content. Edit the Navigate urls array to add more subreddit feed URLs, e.g. https://www.reddit.com/r/news/.rss?limit=100.",
    "color": "bg-[#ff4500]",
    "template_id": "ai-generated"
  },
  "blocks": [
    {
      "block_id": "navigate-1",
      "block_type": "process",
      "title": "Navigate",
      "description": "Go to a URL",
      "position_x": 120,
      "position_y": 220,
      "config": {
        "urls": [
          "https://www.reddit.com/r/cats/.rss?limit=100"
        ],
        "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": 456,
      "position_y": 220,
      "config": {
        "timeout": 30
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 792,
      "position_y": 220,
      "config": {
        "duration": 1
      }
    },
    {
      "block_id": "inject-javascript-1",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Execute custom JavaScript",
      "position_x": 1128,
      "position_y": 220,
      "config": {
        "jsCode": "(function () {\n  var MAX_JSON_PAGES = 25;\n  var LIMIT = 100;\n\n  function getSubredditFromUrl() {\n    var m = location.pathname.match(/\\/r\\/([^\\/?#]+)/i);\n    return m ? decodeURIComponent(m[1]) : 'cats';\n  }\n\n  function txt(node) {\n    return node ? String(node.textContent || '').trim() : '';\n  }\n\n  function firstByTag(parent, tag) {\n    try {\n      var list = parent.getElementsByTagName(tag);\n      return list && list.length ? list[0] : null;\n    } catch (e) {\n      return null;\n    }\n  }\n\n  function allByTag(parent, tag) {\n    try {\n      return Array.prototype.slice.call(parent.getElementsByTagName(tag));\n    } catch (e) {\n      return [];\n    }\n  }\n\n  function syncGet(url) {\n    try {\n      var xhr = new XMLHttpRequest();\n      xhr.open('GET', url, false);\n      xhr.setRequestHeader('Accept', 'application/json, application/atom+xml, application/rss+xml, application/xml, text/xml, text/plain, */*');\n      xhr.send(null);\n      if (xhr.status >= 200 && xhr.status < 300) return xhr.responseText || '';\n    } catch (e) {}\n    return '';\n  }\n\n  function safeJson(text) {\n    if (!text) return null;\n    try { return JSON.parse(text); } catch (e) {}\n    var s = String(text).trim();\n    var a = s.indexOf('{');\n    var b = s.lastIndexOf('}');\n    if (a >= 0 && b > a) {\n      try { return JSON.parse(s.slice(a, b + 1)); } catch (e2) {}\n    }\n    return null;\n  }\n\n  function buildJsonUrl(subreddit, after) {\n    var u = location.origin + '/r/' + encodeURIComponent(subreddit) + '/hot.json?limit=' + LIMIT + '&raw_json=1';\n    if (after) u += '&after=' + encodeURIComponent(after);\n    return u;\n  }\n\n  function normalizeJsonPost(child, subredditFallback) {\n    var d = child && child.data ? child.data : {};\n    var sub = d.subreddit || subredditFallback || '';\n    var permalink = d.permalink ? ('https://www.reddit.com' + d.permalink) : '';\n    var postTime = '';\n    if (d.created_utc) {\n      try { postTime = new Date(d.created_utc * 1000).toISOString(); } catch (e) { postTime = String(d.created_utc); }\n    }\n    return {\n      subreddit: sub,\n      subredditUrl: sub ? ('https://old.reddit.com/r/' + sub + '/') : '',\n      postUrl: permalink || d.url || '',\n      title: d.title || '',\n      author: d.author || '',\n      postTime: postTime,\n      upvote: d.score != null ? String(d.score) : '',\n      commentCount: d.num_comments != null ? String(d.num_comments) : ''\n    };\n  }\n\n  function tryJsonPagination(subreddit) {\n    var out = [];\n    var after = '';\n    var pages = 0;\n    while (pages < MAX_JSON_PAGES) {\n      var text = syncGet(buildJsonUrl(subreddit, after));\n      var json = safeJson(text);\n      if (!json || !json.data || !Array.isArray(json.data.children)) break;\n      json.data.children.forEach(function (child) {\n        var p = normalizeJsonPost(child, subreddit);\n        if (p.title || p.postUrl) out.push(p);\n      });\n      after = json.data.after || '';\n      pages += 1;\n      if (!after) break;\n    }\n    return { posts: out, pages: pages, source: out.length ? 'json' : '' };\n  }\n\n  function parsePostUrlFromEntry(entry) {\n    var links = allByTag(entry, 'link');\n    var fallback = '';\n    for (var i = 0; i < links.length; i++) {\n      var href = links[i].getAttribute('href') || '';\n      var rel = links[i].getAttribute('rel') || '';\n      if (!fallback && href) fallback = href;\n      if (href && /\\/comments\\//i.test(href)) return href.replace('old.reddit.com', 'www.reddit.com');\n      if (href && rel === 'alternate') fallback = href;\n    }\n    return fallback ? fallback.replace('old.reddit.com', 'www.reddit.com') : '';\n  }\n\n  function parseAuthorFromAtomEntry(entry) {\n    var author = firstByTag(entry, 'author');\n    if (!author) return '';\n    var name = txt(firstByTag(author, 'name')) || txt(author);\n    return name.replace(/^\\/u\\//, '').replace(/^u\\//, '').replace(/^reddit:/i, '').trim();\n  }\n\n  function parseNumbersFromContent(contentText) {\n    var text = String(contentText || '').replace(/,/g, ' ');\n    var upvote = '';\n    var comments = '';\n    var mScore = text.match(/(?:score|points?|upvotes?)\\s*[:\\-]?\\s*(\\d+)/i) || text.match(/(\\d+)\\s*(?:points?|upvotes?)/i);\n    if (mScore) upvote = mScore[1];\n    var mComments = text.match(/(\\d+)\\s*(?:comments?)/i) || text.match(/comments?\\s*[:\\-]?\\s*(\\d+)/i);\n    if (mComments) comments = mComments[1];\n    return { upvote: upvote, comments: comments };\n  }\n\n  function parseAtomEntriesFromDocument(doc, subreddit) {\n    var posts = [];\n    if (!doc) return posts;\n    var entries = allByTag(doc, 'entry');\n    entries.forEach(function (entry) {\n      var title = txt(firstByTag(entry, 'title'));\n      var link = parsePostUrlFromEntry(entry);\n      var author = parseAuthorFromAtomEntry(entry);\n      var postTime = txt(firstByTag(entry, 'updated')) || txt(firstByTag(entry, 'published'));\n      var contentNode = firstByTag(entry, 'content') || firstByTag(entry, 'summary');\n      var contentText = txt(contentNode);\n      var nums = parseNumbersFromContent(contentText);\n      if (title || link) {\n        posts.push({\n          subreddit: subreddit,\n          subredditUrl: 'https://old.reddit.com/r/' + subreddit + '/',\n          postUrl: link,\n          title: title,\n          author: author,\n          postTime: postTime,\n          upvote: nums.upvote,\n          commentCount: nums.comments\n        });\n      }\n    });\n    return posts;\n  }\n\n  function parseClassicRssFromDocument(doc, subreddit) {\n    var posts = [];\n    if (!doc) return posts;\n    var items = allByTag(doc, 'item');\n    items.forEach(function (item) {\n      var title = txt(firstByTag(item, 'title'));\n      var link = txt(firstByTag(item, 'link'));\n      var author = txt(firstByTag(item, 'creator')) || txt(firstByTag(item, 'author'));\n      author = author.replace(/^\\/u\\//, '').replace(/^u\\//, '').trim();\n      var postTime = txt(firstByTag(item, 'pubDate')) || txt(firstByTag(item, 'updated')) || txt(firstByTag(item, 'published'));\n      var desc = txt(firstByTag(item, 'description')) || txt(firstByTag(item, 'content'));\n      var nums = parseNumbersFromContent(desc);\n      var comments = txt(firstByTag(item, 'comments')) || nums.comments;\n      if (title || link) {\n        posts.push({\n          subreddit: subreddit,\n          subredditUrl: 'https://old.reddit.com/r/' + subreddit + '/',\n          postUrl: link,\n          title: title,\n          author: author,\n          postTime: postTime,\n          upvote: nums.upvote,\n          commentCount: comments\n        });\n      }\n    });\n    return posts;\n  }\n\n  function parseXmlText(xmlText) {\n    try {\n      return new DOMParser().parseFromString(xmlText, 'application/xml');\n    } catch (e) {\n      return null;\n    }\n  }\n\n  function tryFeed(subreddit) {\n    var posts = parseAtomEntriesFromDocument(document, subreddit);\n    if (!posts.length) posts = parseClassicRssFromDocument(document, subreddit);\n    if (!posts.length) {\n      var rssUrl = location.origin + '/r/' + encodeURIComponent(subreddit) + '/.rss?limit=100';\n      var xmlText = syncGet(rssUrl);\n      var xml = parseXmlText(xmlText);\n      posts = parseAtomEntriesFromDocument(xml, subreddit);\n      if (!posts.length) posts = parseClassicRssFromDocument(xml, subreddit);\n    }\n    return { posts: posts, pages: posts.length ? 1 : 0, source: posts.length ? 'atom_rss' : '' };\n  }\n\n  function render(posts, subreddit, source, pages) {\n    var html = '<!doctype html><html><head><title>UScraper Reddit Results</title></head><body><div id=\"uscraper-reddit-results\"></div></body></html>';\n    try {\n      document.open('text/html', 'replace');\n      document.write(html);\n      document.close();\n    } catch (e) {\n      try { document.documentElement.innerHTML = '<body><div id=\"uscraper-reddit-results\"></div></body>'; } catch (e2) {}\n    }\n    var container = document.getElementById('uscraper-reddit-results');\n    if (!container) {\n      container = document.createElement('div');\n      container.id = 'uscraper-reddit-results';\n      if (document.body) document.body.appendChild(container);\n      else document.documentElement.appendChild(container);\n    }\n    container.setAttribute('data-source', source || 'none');\n    container.setAttribute('data-pages-fetched', String(pages || 0));\n    container.setAttribute('data-post-count', String(posts.length));\n    var h = document.createElement('h1');\n    h.textContent = 'UScraper Reddit Results: r/' + subreddit + ' — ' + posts.length + ' posts from ' + (source || 'no source');\n    container.appendChild(h);\n    posts.forEach(function (p) {\n      var row = document.createElement('div');\n      row.className = 'reddit-post-row';\n      row.setAttribute('data-subreddit', p.subreddit || '');\n      row.setAttribute('data-subreddit-url', p.subredditUrl || '');\n      row.setAttribute('data-post-url', p.postUrl || '');\n      row.setAttribute('data-author', p.author || '');\n      row.setAttribute('data-post-time', p.postTime || '');\n      row.setAttribute('data-upvote', p.upvote || '');\n      row.setAttribute('data-comment-count', p.commentCount || '');\n      var a = document.createElement('a');\n      a.className = 'post-title';\n      a.href = p.postUrl || '#';\n      a.textContent = p.title || '';\n      row.appendChild(a);\n      container.appendChild(row);\n    });\n    if (!posts.length) {\n      var warning = document.createElement('div');\n      warning.id = 'uscraper-reddit-warning';\n      warning.textContent = 'No posts were loaded. Reddit may have blocked JSON and Atom/RSS for this browser/session/network, or the subreddit URL is invalid/private.';\n      container.appendChild(warning);\n    }\n  }\n\n  var subreddit = getSubredditFromUrl();\n  var result = tryJsonPagination(subreddit);\n  if (!result.posts.length) result = tryFeed(subreddit);\n  render(result.posts, subreddit, result.source, result.pages);\n  return { subreddit: subreddit, source: result.source, pagesFetched: result.pages, postsRendered: result.posts.length };\n})();",
        "waitForCompletion": true,
        "timeout": 30
      }
    },
    {
      "block_id": "wait-for-element-1",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 1464,
      "position_y": 220,
      "config": {
        "selector": "#uscraper-reddit-results",
        "timeout": 15,
        "visible": true
      }
    },
    {
      "block_id": "element-exists-1",
      "block_type": "process",
      "title": "Element Exists",
      "description": "Check if element exists",
      "position_x": 1800,
      "position_y": 220,
      "config": {
        "selector": ".reddit-post-row"
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 2136,
      "position_y": 520,
      "config": {
        "rowSelector": ".reddit-post-row",
        "fileName": "reddit-subreddit-scraper-by-url.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "columns": [
          {
            "name": "Subreddit",
            "selector": "",
            "attribute": "data-subreddit"
          },
          {
            "name": "Subreddit_URL",
            "selector": "",
            "attribute": "data-subreddit-url"
          },
          {
            "name": "Post_URL",
            "selector": "",
            "attribute": "data-post-url"
          },
          {
            "name": "Post_title",
            "selector": ".post-title",
            "attribute": "text"
          },
          {
            "name": "Author",
            "selector": "",
            "attribute": "data-author"
          },
          {
            "name": "Post_time",
            "selector": "",
            "attribute": "data-post-time"
          },
          {
            "name": "Upvote",
            "selector": "",
            "attribute": "data-upvote"
          },
          {
            "name": "Comment_count",
            "selector": "",
            "attribute": "data-comment-count"
          }
        ]
      }
    },
    {
      "block_id": "loop-continue-1",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 2472,
      "position_y": 520,
      "config": {}
    },
    {
      "block_id": "loop-continue-2",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 1800,
      "position_y": 520,
      "config": {}
    }
  ],
  "connections": [
    {
      "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": "element-exists-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-exists-1",
      "from_connector_id": "true",
      "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"
    },
    {
      "from_block_id": "element-exists-1",
      "from_connector_id": "false",
      "to_block_id": "loop-continue-2",
      "to_connector_id": "left"
    }
  ],
  "canvas_elements": [
    {
      "id": "group-load",
      "element_type": "group",
      "title": "Page Load",
      "color": "#08bdba",
      "position_x": 48,
      "position_y": 116,
      "width": 1664,
      "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": 1056,
      "position_y": 116,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "inject-javascript-1"
        ]
      }
    },
    {
      "id": "group-pagination",
      "element_type": "group",
      "title": "Pagination Loop",
      "color": "#ff832b",
      "position_x": 1728,
      "position_y": 116,
      "width": 992,
      "height": 596,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "element-exists-1",
          "loop-continue-1",
          "loop-continue-2"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 2064,
      "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 Reddit subreddit posts by subreddit URL, extracting Subreddit, Subreddit_URL, Post_URL, Post_title, Author, Post_time, Upvote, and Comment_count. Reddit HTML and JSON endpoints were blocked in automated testing, so this best-effort template starts from the subreddit Atom/RSS feed, attempts JSON pagination for full score/comment data, and falls back to parsing Atom feed entries. Feed fallback may leave Upvote or Comment_count blank if Reddit does not expose them in feed content. Edit the Navigate urls array to add more subreddit feed URLs, e.g. https://www.reddit.com/r/news/.rss?limit=100.",
      "color": "#f1c21b",
      "position_x": 80,
      "position_y": 20,
      "width": 480,
      "height": 160,
      "z_index": 22,
      "data": {}
    },
    {
      "id": "note-block-inject-javascript-1",
      "element_type": "note",
      "title": "Note: Inject JavaScript",
      "content": "Runs custom JavaScript in the page: `(function () {\n  var MAX_JSON_PAGES = 25;\n  var LIMIT = 100;\n\n  function getSubredditFromUrl() {\n   ...` 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-element-exists-1",
      "element_type": "note",
      "title": "Note: Element Exists",
      "content": "Condition block: checks `.reddit-post-row`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 2000,
      "position_y": 200,
      "width": 340,
      "height": 135,
      "z_index": 22,
      "data": {
        "block_id": "element-exists-1"
      }
    },
    {
      "id": "note-block-structured-export-1",
      "element_type": "note",
      "title": "Note: Structured Export",
      "content": "Extracts rows matching `.reddit-post-row`. Confirm row count > 0 before running at scale.",
      "color": "#ee5396",
      "position_x": 2336,
      "position_y": 500,
      "width": 340,
      "height": 109,
      "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": 2672,
      "position_y": 500,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    },
    {
      "id": "note-block-loop-continue-2",
      "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": 2000,
      "position_y": 500,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-2"
      }
    }
  ]
}