{"workflow":{"id":13992,"name":"Build a Reddit no-API weekly digest with ScrapeOps and Google Sheets","views":15,"recentViews":0,"totalViews":15,"createdAt":"2026-03-11T06:59:56.442Z","description":"## Overview\nThis n8n template automates a weekly Reddit industry digest without the Reddit API. It scrapes top posts from selected subreddits via ScrapeOps Proxy, enriches them with full post text, deduplicates against Google Sheets, and generates a weekly summary - optionally emailed to your inbox.\n\n## Who is this for?\n- Developers and product teams monitoring industry trends on Reddit\n- Marketers and founders tracking niche community conversations\n- Analysts building automated weekly briefings from Reddit\n\n## What problem does it solve?\nManually checking multiple subreddits weekly is time-consuming. This workflow runs automatically, scrapes top posts, removes duplicates, and delivers a clean weekly digest to Google Sheets and optionally your email.\n\n## How it works\n1. A weekly schedule triggers the workflow automatically.\n2. ScrapeOps Proxy scrapes \"Top of Week\" from each subreddit on `old.reddit.com`.\n3. Post metadata is parsed from HTML: title, URL, score, comments, author, timestamps.\n4. Each post is fetched as JSON to extract the full `selftext` body.\n5. Data is merged, normalized, and deduplicated against existing Sheet rows.\n6. New posts are appended to the `posts` tab.\n7. A weekly digest is written to `weekly_digest` and optionally emailed.\n\n## Set up steps (~10–15 minutes)\n1. Register for a free ScrapeOps API key: [https://scrapeops.io/app/register/n8n](https://scrapeops.io/app/register/n8n)\n2. Add ScrapeOps credentials in n8n. Docs: [https://scrapeops.io/docs/n8n/overview/](https://scrapeops.io/docs/n8n/overview/)\n3. Duplicate [this sheet](https://docs.google.com/spreadsheets/d/1rKuVREV4pedie7uAbuEcLvghNrAEeAjIPUBE6cQPleI/edit?usp=sharing) to copy Columns and Spreadsheet ID.\n4. Connect Google Sheets and set your Spreadsheet ID in the Read, Append, and Digest nodes.\n5. Update your subreddit list and week range in **Configure Subreddits & Week Range**.\n6. Optional: configure the Send Email node with sender and recipient credentials.\n7. Run once manually to confirm, then activate.\n\n## Pre-conditions\n- Active ScrapeOps account ([free tier](https://scrapeops.io/app/register/n8n)).\n- ScrapeOps [community node installed](https://scrapeops.io/docs/n8n/overview/) in n8n.\n- Google Sheets credentials configured in n8n\n- A [Google Sheet](https://docs.google.com/spreadsheets/d/1rKuVREV4pedie7uAbuEcLvghNrAEeAjIPUBE6cQPleI/edit?gid=0#gid=0) with `posts` and `weekly_digest` tabs and correct column headers\n- Optional: email credentials for the Send Email node\n\n## Disclaimer\nThis template uses [ScrapeOps](https://n8n.io/integrations/scrapeops/) as a community node. You are responsible for complying with Reddit's Terms of Use, robots.txt directives, and applicable laws in your jurisdiction. Scraping targets may change at any time; adjust render, scroll, and wait settings and parsers as needed. Use responsibly and only for legitimate business purposes.\n","workflow":{"id":"E7W5wh1CRWDsRWWN","meta":{"instanceId":"c2ff056313a72210aa803da7c5191a260dbed0dab6ae2b8e39a8dd21701bf0ab","templateCredsSetupCompleted":true},"name":"Reddit Industry Digest with ScrapeOps and Google Sheets","tags":[{"id":"EYCu0h4UjINqJjNC","name":"Industry Digest","createdAt":"2026-03-10T07:51:48.024Z","updatedAt":"2026-03-10T07:51:48.024Z"},{"id":"jiBunnRJASi9V3kB","name":"Reddit Scraper","createdAt":"2026-03-10T07:51:32.325Z","updatedAt":"2026-03-10T07:51:32.325Z"},{"id":"lZKSh2IoxHklnOUw","name":"ScrapeOps","createdAt":"2025-10-20T20:27:13.410Z","updatedAt":"2025-10-20T20:27:13.410Z"},{"id":"oSx6yEPAYlnqqBNC","name":"Weekly Newsletter Automa","createdAt":"2026-03-10T07:51:54.577Z","updatedAt":"2026-03-10T07:51:54.577Z"},{"id":"yzylwxvLF3YwGBRm","name":"Google Sheets Automation","createdAt":"2026-03-10T07:03:25.329Z","updatedAt":"2026-03-10T07:03:25.329Z"}],"nodes":[{"id":"db080f2b-cb69-4222-84a1-691b012d6bde","name":"Overview (Sticky)","type":"n8n-nodes-base.stickyNote","position":[0,-176],"parameters":{"width":600,"height":904,"content":"# 📰 Reddit Industry Digest (Weekly) → Google Sheets\n\nThis workflow builds a weekly industry digest by collecting top posts from selected subreddits — no Reddit API needed. It scrapes public Reddit pages via **ScrapeOps Proxy**, enriches each post with full text using Reddit's JSON endpoint, deduplicates against your Google Sheet, and generates a weekly summary that can optionally be emailed.\n\n### How it works\n1. ⏰ **Weekly Schedule Trigger** fires automatically once a week.\n2. ⚙️ **Configure Subreddits & Week Range** sets the subreddit list, week range, and Sheet IDs.\n3. 📦 **Split Subreddits Into Batches** processes each subreddit one at a time.\n4. 🌐 **ScrapeOps: Fetch Subreddit Listing** scrapes the top-of-week page from `old.reddit.com`.\n5. ⏳ **Polite Delay** adds a 1–3s pause between requests.\n6. 🔍 **Parse Listing HTML** extracts title, URL, score, comments, author, and timestamps.\n7. 📡 **ScrapeOps: Fetch Post Details** retrieves each post as JSON to extract `selftext`.\n8. 🔀 **Merge & Normalize** combines listing data with post body text into a final record.\n9. 🧹 **Deduplicate New Posts** filters posts already in the Sheet by hash and URL.\n10. 💾 **Append New Posts** saves only new posts to the `posts` tab.\n11. 📊 **Build Weekly Digest** generates topic clusters and top post summaries.\n12. 📧 **Send Digest Email** optionally emails the weekly summary.\n\n### Setup steps\n- Register for a free ScrapeOps API key: https://scrapeops.io/app/register/n8n\n- Add ScrapeOps credentials in n8n. Docs: https://scrapeops.io/docs/n8n/overview/\n- Duplicate [this sheet](https://docs.google.com/spreadsheets/d/1rKuVREV4pedie7uAbuEcLvghNrAEeAjIPUBE6cQPleI/edit?usp=sharing) to copy Columns and Spreadsheet ID.\n- Connect Google Sheets and set your Spreadsheet ID in the Sheet nodes.\n- Update your subreddit list in **Configure Subreddits & Week Range**.\n- Optional: enable **Send Digest Email** and configure credentials.\n\n### Customization\n- Add or remove subreddits in the configure node.\n- Change timeframe from `week` to `month` in the fetch URL.\n- Add a Slack node to post the digest to a channel."},"typeVersion":1},{"id":"6b2932d3-1c23-42d1-8d12-f6bb66f2442e","name":"Section: Trigger & Inputs","type":"n8n-nodes-base.stickyNote","position":[656,48],"parameters":{"color":7,"width":436,"height":344,"content":"## 1. Trigger & Configuration\nFires weekly and sets runtime config — subreddit list, week range, batch size, and Google Sheet IDs."},"typeVersion":1},{"id":"2f094883-7d84-4677-bc49-ea5b64b8552c","name":"Section: Scrape Listings","type":"n8n-nodes-base.stickyNote","position":[1104,48],"parameters":{"color":7,"width":664,"height":344,"content":"## 2. Scrape Subreddit Listings\nBatch through each subreddit and scrape the \"Top of Week\" page via [ScrapeOps Proxy](https://scrapeops.io/docs/n8n/proxy-api/) with a polite delay between requests."},"typeVersion":1},{"id":"639b418a-dd27-48c6-9946-525c86f2c3c6","name":"Section: Post Enrichment","type":"n8n-nodes-base.stickyNote","position":[1792,48],"parameters":{"color":7,"width":328,"height":344,"content":"## 3. Parse Post Metadata\nExtract title, URL, score, comment count, author, and timestamps from listing HTML into structured JSON."},"typeVersion":1},{"id":"b5e1e99e-c8b6-4542-b9b6-0228cce219e0","name":"Parse Listing HTML → Post Metadata","type":"n8n-nodes-base.code","position":[1904,224],"parameters":{"jsCode":"const html = $json.data || $json.body || $json || '';\nconst maxPosts = $json.limit || 20;\n\n// Fallback metadata\nconst now = new Date();\nconst iso = now.toISOString();\nconst run_id = $json.run_id || iso;\nconst run_date = $json.run_date || iso.slice(0,10);\n// If week_range missing, compute current week (Monâ€“Sun)\nlet week_range = $json.week_range;\nif (!week_range) {\n  const ws = new Date(now);\n  const day = (ws.getUTCDay() + 6) % 7; // Monday=0\n  ws.setUTCDate(ws.getUTCDate() - day);\n  const we = new Date(ws);\n  we.setUTCDate(we.getUTCDate() + 6);\n  const fmt = d => d.toISOString().slice(0,10);\n  week_range = `${fmt(ws)} to ${fmt(we)}`;\n}\n\nconst upstreamSub = $json.subreddit || '';\nconst sort = $json.sort || 'top';\nconst time_range = $json.time_range || 'week';\nconst extracted_at = new Date().toISOString();\nconst crypto = require('crypto');\n\n// Detect subreddit from permalink/URL if upstream missing\nconst detectSubreddit = (block, url) => {\n  const m1 = block.match(/\\/r\\/([A-Za-z0-9_]+)\\/comments\\//i);\n  if (m1) return m1[1];\n  const m2 = url && url.match(/\\/r\\/([A-Za-z0-9_]+)\\//i);\n  if (m2) return m2[1];\n  const m3 = html.match(/<meta property=\"og:url\" content=\"https:\\/\\/old\\.reddit\\.com\\/r\\/([^/]+)/i);\n  if (m3) return m3[1];\n  const m4 = html.match(/<meta property=\"og:url\" content=\"https:\\/\\/www\\.reddit\\.com\\/r\\/([^/]+)/i);\n  if (m4) return m4[1];\n  return upstreamSub;\n};\n\nconst posts = [];\nconst blocks = html.split('<div class=\"thing');\nfor (const blk of blocks) {\n  if (!blk.includes('data-fullname')) continue;\n\n  const idMatch = blk.match(/data-fullname=\"([^\"]+)\"/);\n  const authorMatch = blk.match(/data-author=\"([^\"]*)\"/);\n  const titleMatch = blk.match(/<a[^>]*class=\"title[^>]*\"[^>]*href=\"([^\"]+)\"[^>]*>([\\s\\S]*?)<\\/a>/);\n  if (!titleMatch) continue;\n\n  // Prefer Reddit permalink; emit www.reddit.com\n  const permalinkMatch = blk.match(/data-permalink=\"([^\"]+)\"/);\n  const href = titleMatch[1];\n  const urlPath = permalinkMatch ? permalinkMatch[1] : href;\n  const url = urlPath.startsWith('http')\n    ? urlPath.replace('://old.reddit.com','://www.reddit.com')\n    : `https://www.reddit.com${urlPath.startsWith('/') ? urlPath : `/${urlPath}`}`;\n\n  const subreddit = detectSubreddit(blk, url);\n  const title = titleMatch[2].replace(/<[^>]+>/g,'').trim();\n\n  const scoreMatch = blk.match(/<div class=\"score[^>]*?(?:title=\"([\\d,]+)\")?[^>]*>([\\d,]+|\\u2022)/);\n  const scoreVal = scoreMatch ? (scoreMatch[1] || scoreMatch[2]) : '';\n  const commentsMatch =\n    blk.match(/<a[^>]*class=\"comments[^\"]*\"[^>]*>(\\d+)[^<]*comment/) ||\n    blk.match(/<a[^>]*href=\"[^\"]*comments[^\"]*\"[^>]*>(\\d+)\\s+comment/);\n\n  const flairMatch =\n    blk.match(/class=\"linkflairlabel[^\"]*\"[^>]*>([^<]+)<\\/span>/) ||\n    blk.match(/class=\"linkflair[^\"]*\"[^>]*>([^<]+)<\\/span>/);\n\n  // Selftext on listing is usually absent for link/image posts\n  const selftextMatch = blk.match(/data-selftext-html=\"([^\"]*)\"/);\n  const post_text = selftextMatch\n    ? selftextMatch[1]\n        .replace(/&lt;/g,'<')\n        .replace(/&gt;/g,'>')\n        .replace(/&amp;/g,'&')\n        .replace(/<[^>]+>/g,'')\n        .trim()\n    : '';\n\n  let created_utc = '';\n  const tsMatch = blk.match(/data-timestamp=\"(\\d+)\"/);\n  if (tsMatch) created_utc = new Date(Number(tsMatch[1])).toISOString();\n\n  const post_id = idMatch ? idMatch[1] : (url.split('/').filter(Boolean).pop() || '');\n  const score = scoreVal && scoreVal !== '\\u2022' ? Number(scoreVal.replace(/,/g,'')) : '';\n  const num_comments = commentsMatch ? Number(commentsMatch[1]) : '';\n  const flair = flairMatch ? flairMatch[1] : '';\n  const author = authorMatch ? authorMatch[1] : '';\n  const hash = crypto.createHash('sha1').update(`${subreddit}${title}${url}`).digest('hex');\n\n  posts.push({\n    run_id,\n    run_date,\n    week_range,\n    subreddit,\n    sort,\n    time_range,\n    post_id,\n    post_url: url,\n    post_title: title,\n    post_text,\n    author,\n    created_utc,\n    score,\n    num_comments,\n    flair,\n    extracted_at,\n    content_hash: hash,\n    is_new: true\n  });\n  if (posts.length >= maxPosts) break;\n}\n\nreturn posts.map(p => ({ json: p }));\n"},"typeVersion":2,"alwaysOutputData":true},{"id":"b5ae003d-5943-4e1d-ad00-636b7e5287ae","name":"Section: Post Enrichment1","type":"n8n-nodes-base.stickyNote","position":[2144,48],"parameters":{"color":7,"width":856,"height":344,"content":"## 4. Enrich & Finalize Posts\nFetch each post as JSON to extract `selftext`, merge with listing metadata, and normalize all fields into the final record."},"typeVersion":1},{"id":"692706d5-37e1-43e0-9d53-20165176691a","name":"Section: Post Enrichment2","type":"n8n-nodes-base.stickyNote","position":[1264,464],"parameters":{"color":7,"width":856,"height":280,"content":"## 5. Deduplicate & Save\nCompare against existing Sheet rows by hash and URL, then append only new posts to the `posts` tab."},"typeVersion":1},{"id":"ca7a04f5-3e93-40c7-aafe-db3adf4237b8","name":"Section: Post Enrichment3","type":"n8n-nodes-base.stickyNote","position":[2144,464],"parameters":{"color":7,"width":856,"height":280,"content":"## 6. Weekly Digest & Email\nGenerate topic clusters and top post summaries, write to `weekly_digest` tab, and optionally send by email."},"typeVersion":1},{"id":"fd788a62-92a1-46f8-8d8f-300c67b9028e","name":"Weekly Schedule Trigger","type":"n8n-nodes-base.scheduleTrigger","position":[720,224],"parameters":{"rule":{"interval":[{}]}},"typeVersion":1},{"id":"7514a857-699a-40a5-a111-2d32af2cf38a","name":"Configure Subreddits & Week Range","type":"n8n-nodes-base.code","position":[944,224],"parameters":{"jsCode":"// Robust static store helper (works in Code node)\nconst getStatic = () => {\n  if (typeof $getWorkflowStaticData === 'function') return $getWorkflowStaticData('global');\n  if (typeof this !== 'undefined' && this.getWorkflowStaticData) return this.getWorkflowStaticData('global');\n  // fallback (per-execution)\n  if (!globalThis.__staticData) globalThis.__staticData = { global: {} };\n  return globalThis.__staticData.global;\n};\n\nconst global = getStatic();\nglobal.seen = [];\nglobal.newPosts = [];\n\nconst now = new Date();\nconst iso = now.toISOString();\nconst run_date = iso.slice(0,10);\nconst weekStart = new Date(now);\nconst day = (weekStart.getUTCDay() + 6) % 7; // Monday=0\nweekStart.setUTCDate(weekStart.getUTCDate() - day);\nconst weekEnd = new Date(weekStart);\nweekEnd.setUTCDate(weekEnd.getUTCDate() + 6);\nconst fmt = d => d.toISOString().slice(0,10);\n\nconst subs = [\"selfhosted\",\"devops\",\"programming\",\"webdev\"];\nreturn subs.map(sub => ({ \n  json: {\n    run_id: iso,\n    run_date,\n    week_range: `${fmt(weekStart)} to ${fmt(weekEnd)}`,\n    subreddit: sub,\n    sort: \"top\",\n    time_range: \"week\",\n    limit: 20,\n    sheet_id: \"1rKuVREV4pedie7uAbuEcLvghNrAEeAjIPUBE6cQPleI\"\n  }\n}));\n"},"typeVersion":2},{"id":"a2cba4ba-859a-4af4-85de-ea86979d3393","name":" Split Subreddits Into Batches","type":"n8n-nodes-base.splitInBatches","position":[1168,224],"parameters":{"options":{},"batchSize":1},"typeVersion":2},{"id":"c501694f-dcf7-4781-a1fc-f5eebd38e5a0","name":"ScrapeOps: Fetch Subreddit Listing","type":"@scrapeops/n8n-nodes-scrapeops.ScrapeOps","position":[1408,224],"parameters":{"url":"={{`https://old.reddit.com/r/${$json.subreddit}/top/?t=week`}}","advancedOptions":{}},"credentials":{"scrapeOpsApi":{"id":"credential-id","name":"ScrapeOps account"}},"typeVersion":1},{"id":"68756bd8-82da-4204-b4f7-403d2c1f9c03","name":" Polite Delay (1–3s)","type":"n8n-nodes-base.wait","position":[1616,224],"webhookId":"ecba03ef-b95d-4d95-bd19-9c8561e54250","parameters":{"unit":"seconds","amount":"={{ Math.floor(Math.random()*3)+1 }}"},"typeVersion":1},{"id":"4aac1636-f65b-4173-91ea-2479bae04379","name":" ScrapeOps: Fetch Post Details (JSON)","type":"@scrapeops/n8n-nodes-scrapeops.ScrapeOps","position":[2208,224],"parameters":{"url":"={{ ($json.post_url || '').replace(/\\?.*$/, '').replace(/\\/$/, '') + '.json?raw_json=1' }}\n","returnType":"json","advancedOptions":{}},"credentials":{"scrapeOpsApi":{"id":"credential-id","name":"ScrapeOps account"}},"typeVersion":1},{"id":"8a539ed8-a2aa-4bfe-a493-c424ec9b7d39","name":"Extract Selftext & Post Type","type":"n8n-nodes-base.code","position":[2432,224],"parameters":{"jsCode":"// n8n Code node: Extract post data from Reddit .json via ScrapeOps\n// Robust for: normal JSON, HTML-encoded JSON, and text/link/image posts.\n\nconst payload = $json ?? {};\n\n// 1) Get response string\nlet raw = '';\nif (typeof payload.body === 'string') raw = payload.body;\nelse if (typeof payload.data === 'string') raw = payload.data;\nelse if (typeof payload.response === 'string') raw = payload.response;\nelse {\n  for (const v of Object.values(payload)) {\n    if (typeof v === 'string' && v.length > raw.length) raw = v;\n  }\n}\n\nconst trimmed = (raw || '').trim();\n\nif (!trimmed) {\n  return [{\n    json: {\n      extracted_ok: false,\n      error: 'No response found in body/data/response.',\n      available_keys: Object.keys(payload),\n      post_text_extracted: ''\n    }\n  }];\n}\n\n// 2) Decode entities (handles &#34; etc)\nconst decodeHtmlEntities = (s = '') =>\n  s\n    .replace(/&#34;/g, '\"')\n    .replace(/&quot;/g, '\"')\n    .replace(/&#39;/g, \"'\")\n    .replace(/&apos;/g, \"'\")\n    .replace(/&#38;/g, '&')\n    .replace(/&amp;/g, '&')\n    .replace(/&lt;/g, '<')\n    .replace(/&gt;/g, '>')\n    .replace(/&nbsp;/g, ' ');\n\nconst decoded = decodeHtmlEntities(trimmed);\n\n// 3) Only treat as HTML if it truly starts like HTML\nif (decoded.startsWith('<')) {\n  return [{\n    json: {\n      extracted_ok: false,\n      error: 'Got HTML (likely blocked/redirect).',\n      debug_first_200_chars: decoded.slice(0, 200),\n      status_code: payload.status_code ?? null,\n      url: payload.url ?? null,\n      post_text_extracted: ''\n    }\n  }];\n}\n\n// 4) If it looks like JSON, parse it\nconst firstChar = decoded[0];\nif (firstChar !== '[' && firstChar !== '{') {\n  return [{\n    json: {\n      extracted_ok: false,\n      error: 'Response is not JSON or HTML (unexpected format).',\n      debug_first_200_chars: decoded.slice(0, 200),\n      status_code: payload.status_code ?? null,\n      url: payload.url ?? null,\n      post_text_extracted: ''\n    }\n  }];\n}\n\nlet parsed;\ntry {\n  parsed = JSON.parse(decoded);\n} catch (e) {\n  return [{\n    json: {\n      extracted_ok: false,\n      error: `JSON parse failed: ${e.message}`,\n      debug_first_200_chars: decoded.slice(0, 200),\n      status_code: payload.status_code ?? null,\n      url: payload.url ?? null,\n      post_text_extracted: ''\n    }\n  }];\n}\n\n// 5) Extract post fields\nconst postListing = Array.isArray(parsed) ? parsed[0] : parsed;\nconst post = postListing?.data?.children?.[0]?.data;\n\nif (!post) {\n  return [{\n    json: {\n      extracted_ok: false,\n      error: 'Parsed JSON but could not find post at data.children[0].data',\n      url: payload.url ?? null,\n      post_text_extracted: ''\n    }\n  }];\n}\n\nconst selftext = (post.selftext || '').trim();\nconst post_type =\n  selftext ? 'text' :\n  (post.is_video ? 'video' :\n   (post.post_hint || (post.url ? 'link_or_image' : 'unknown')));\n\nreturn [{\n  json: {\n    extracted_ok: true,\n    post_text_extracted: selftext,  // can be \"\" for image/link posts\n    post_type,\n\n    post_title: post.title || '',\n    post_id: post.name || (post.id ? `t3_${post.id}` : ''),\n    post_url: post.permalink ? `https://www.reddit.com${post.permalink}` : (post.url || ''),\n    subreddit: post.subreddit || '',\n    score: post.score ?? null,\n    num_comments: post.num_comments ?? null,\n    author: post.author || '',\n    created_utc: post.created_utc ?? null\n  }\n}];\n"},"typeVersion":2},{"id":"4b2b0080-c6b9-4265-883a-821450fbdd04","name":"Merge Post Metadata + Text","type":"n8n-nodes-base.merge","position":[2640,224],"parameters":{"mode":"combine","options":{},"combinationMode":"mergeByPosition"},"typeVersion":2},{"id":"afd98102-216a-46da-b40f-8f22f8132063","name":"Finalize & Normalize Post Fields","type":"n8n-nodes-base.code","position":[2848,224],"parameters":{"jsCode":"return items.map(item => {\n  const j = { ...item.json };\n  const extracted = (j.post_text_extracted || '').trim();\n  if (extracted) {\n    j.post_text = extracted;\n  } else {\n    j.post_text = (j.post_text || '').trim();\n  }\n  delete j.post_text_extracted;\n  return { json: j };\n});"},"typeVersion":2},{"id":"d57d918f-c7ef-4ee1-928f-586ec9ad8509","name":"Read Existing Posts from Sheet","type":"n8n-nodes-base.googleSheets","position":[1360,560],"parameters":{"options":{},"sheetName":{"__rl":true,"mode":"list","value":"gid=0","cachedResultUrl":"https://docs.google.com/spreadsheets/d/1rKuVREV4pedie7uAbuEcLvghNrAEeAjIPUBE6cQPleI/edit#gid=0","cachedResultName":"posts"},"documentId":{"__rl":true,"mode":"id","value":"1rKuVREV4pedie7uAbuEcLvghNrAEeAjIPUBE6cQPleI"}},"credentials":{"googleSheetsOAuth2Api":{"id":"credential-id","name":"Google Sheets account 4"}},"typeVersion":3,"alwaysOutputData":true},{"id":"ee1034af-bede-4a50-8b60-76eeb894ad39","name":" Merge Scraped + Existing Posts","type":"n8n-nodes-base.merge","position":[1584,560],"parameters":{"mode":"combine","options":{},"combinationMode":"mergeByPosition"},"typeVersion":2},{"id":"678a0f94-b0e1-464b-8a60-2f1fbc51e771","name":"Deduplicate New Posts","type":"n8n-nodes-base.code","position":[1776,560],"parameters":{"jsCode":"// Shared store\nconst global =\n  (typeof $getWorkflowStaticData === 'function')\n    ? $getWorkflowStaticData('global')\n    : (globalThis.__static ??= { global: {} }).global;\n\nif (!global.seen) global.seen = [];\nif (!global.newPosts) global.newPosts = [];\n\n// Normalize URL\nconst normUrl = (u = '') => u.replace('://old.reddit.com', '://www.reddit.com').trim();\n\n// Read existing rows\nlet existingRows = [];\ntry { existingRows = $items('Read Existing Posts from Sheet') || []; } catch (e) { existingRows = []; }\n\nconst existingSet = new Set(global.seen || []);\nfor (const row of existingRows) {\n  const r = row?.json || {};\n  const h = r.content_hash || r[15] || r.Q || '';\n  const u = normUrl(r.post_url || r[6] || r.G || '');\n  if (h) existingSet.add(h);\n  if (u) existingSet.add(u);\n}\n\n// Process incoming items; keep all, flag duplicates\nconst out = [];\nfor (const item of items) {\n  const j = { ...item.json };\n  j.post_url = normUrl(j.post_url || '');\n  const seen = existingSet.has(j.content_hash) || existingSet.has(j.post_url);\n  if (!seen) {\n    existingSet.add(j.content_hash);\n    existingSet.add(j.post_url);\n    j.is_new = true;\n    global.newPosts.push(j);\n  } else {\n    j.is_new = false;\n  }\n  out.push({ json: j });\n}\n\n// Update cache\nglobal.seen = Array.from(existingSet);\n\n// Always return items\nreturn out;\n"},"typeVersion":2,"alwaysOutputData":true},{"id":"76be0f72-2be0-4b00-9549-18d739d62167","name":"Append New Posts to Sheet","type":"n8n-nodes-base.googleSheets","position":[1968,560],"parameters":{"columns":{"value":{"sort":"={{ $json.sort }}","flair":"={{ $json.flair }}","score":"={{ $json.score }}","author":"={{ $json.author }}","is_new":"={{ $json.is_new }}","run_id":"={{ $json.run_id }}","post_id":"={{ $json.post_id }}","post_url":"={{ $json.post_url }}","run_date":"={{ $json.run_date }}","post_text":"={{ $json.post_text }}","subreddit":"={{ $json.subreddit }}","post_title":"={{ $json.post_title }}","time_range":"={{ $json.time_range }}","created_utc":"={{ $json.created_utc }}","content_hash":"={{ $json.content_hash }}","extracted_at":"={{ $json.extracted_at }}","num_comments":"={{ $json.num_comments }}"},"schema":[{"id":"run_id","type":"string","display":true,"required":false,"displayName":"run_id","defaultMatch":false,"canBeUsedToMatch":true},{"id":"run_date","type":"string","display":true,"required":false,"displayName":"run_date","defaultMatch":false,"canBeUsedToMatch":true},{"id":"subreddit","type":"string","display":true,"required":false,"displayName":"subreddit","defaultMatch":false,"canBeUsedToMatch":true},{"id":"sort","type":"string","display":true,"required":false,"displayName":"sort","defaultMatch":false,"canBeUsedToMatch":true},{"id":"time_range","type":"string","display":true,"required":false,"displayName":"time_range","defaultMatch":false,"canBeUsedToMatch":true},{"id":"post_id","type":"string","display":true,"required":false,"displayName":"post_id","defaultMatch":false,"canBeUsedToMatch":true},{"id":"post_url","type":"string","display":true,"required":false,"displayName":"post_url","defaultMatch":false,"canBeUsedToMatch":true},{"id":"post_title","type":"string","display":true,"required":false,"displayName":"post_title","defaultMatch":false,"canBeUsedToMatch":true},{"id":"post_text","type":"string","display":true,"required":false,"displayName":"post_text","defaultMatch":false,"canBeUsedToMatch":true},{"id":"author","type":"string","display":true,"required":false,"displayName":"author","defaultMatch":false,"canBeUsedToMatch":true},{"id":"created_utc","type":"string","display":true,"required":false,"displayName":"created_utc","defaultMatch":false,"canBeUsedToMatch":true},{"id":"score","type":"string","display":true,"required":false,"displayName":"score","defaultMatch":false,"canBeUsedToMatch":true},{"id":"num_comments","type":"string","display":true,"required":false,"displayName":"num_comments","defaultMatch":false,"canBeUsedToMatch":true},{"id":"flair","type":"string","display":true,"required":false,"displayName":"flair","defaultMatch":false,"canBeUsedToMatch":true},{"id":"extracted_at","type":"string","display":true,"required":false,"displayName":"extracted_at","defaultMatch":false,"canBeUsedToMatch":true},{"id":"content_hash","type":"string","display":true,"required":false,"displayName":"content_hash","defaultMatch":false,"canBeUsedToMatch":true},{"id":"is_new","type":"string","display":true,"required":false,"displayName":"is_new","defaultMatch":false,"canBeUsedToMatch":true}],"mappingMode":"defineBelow","matchingColumns":[],"attemptToConvertTypes":false,"convertFieldsToString":false},"options":{},"operation":"append","sheetName":{"__rl":true,"mode":"name","value":"posts"},"documentId":{"__rl":true,"mode":"id","value":"1rKuVREV4pedie7uAbuEcLvghNrAEeAjIPUBE6cQPleI"}},"credentials":{"googleSheetsOAuth2Api":{"id":"credential-id","name":"Google Sheets account 4"}},"typeVersion":4.5},{"id":"08ab7fd2-6dd0-4fd1-8df4-9440f0830178","name":"Build Weekly Digest","type":"n8n-nodes-base.code","position":[2240,544],"parameters":{"jsCode":"// Grab shared store (works in Code node); fallback to per-run object\nconst global =\n  (typeof $getWorkflowStaticData === 'function')\n    ? $getWorkflowStaticData('global')\n    : (globalThis.__static ??= { global: {} }).global;\n\nconst posts = global.newPosts || [];\nconst total_posts = posts.length;\nconst subreddits = Array.from(new Set(posts.map(p => p.subreddit))).join(', ');\nconst stop = new Set(['the','a','an','to','for','and','or','of','in','on','with','by','from','is','are','was','were','be','this','that','it','as','at','we','you','i']);\nconst wordCounts = new Map();\nfor (const p of posts) {\n  const text = `${p.post_title || ''} ${p.post_text || ''}`.toLowerCase();\n  for (const w of text.split(/[^a-z0-9+#]+/).filter(Boolean)) {\n    if (stop.has(w) || w.length < 3) continue;\n    wordCounts.set(w, (wordCounts.get(w) || 0) + 1);\n  }\n}\nconst topWords = Array.from(wordCounts.entries()).sort((a,b)=>b[1]-a[1]).slice(0,40).map(([w])=>w);\nconst topics = [];\nfor (const w of topWords) {\n  if (topics.length >= 8) break;\n  const clusterPosts = posts.filter(p => (`${p.post_title} ${p.post_text}`).toLowerCase().includes(w)).slice(0,3);\n  if (!clusterPosts.length) continue;\n  topics.push({ label: w, summary: `Highlights related to ${w}.`, links: clusterPosts.map(p => p.post_url) });\n}\nwhile (topics.length < 5 && topWords[topics.length]) {\n  topics.push({ label: topWords[topics.length], summary: `Topic around ${topWords[topics.length]}.`, links: [] });\n}\nconst sorted = [...posts].sort((a,b)=>{\n  const as = Number(a.score || 0), bs = Number(b.score || 0);\n  if (bs !== as) return bs - as;\n  const ac = Number(a.num_comments || 0), bc = Number(b.num_comments || 0);\n  return bc - ac;\n}).slice(0,10);\nconst topPostsJson = sorted.map(p => ({ title: p.post_title, url: p.post_url, score: p.score ?? '', comments: p.num_comments ?? '', subreddit: p.subreddit }));\nconst brief = [];\nbrief.push(`Weekly Developer Tools Digest (Reddit) - ${posts[0]?.week_range || ''}`);\nbrief.push(`Subreddits: ${subreddits || 'webdev, programming, devops, selfhosted'}`);\nbrief.push(`Total new posts: ${total_posts}`);\nbrief.push('\\nTopics:');\nfor (const t of topics) {\n  brief.push(`- ${t.label}: ${t.summary}`);\n  if (t.links && t.links.length) brief.push(`  Links: ${t.links.join(', ')}`);\n}\nbrief.push('\\nTop posts overall:');\nfor (const p of topPostsJson) {\n  brief.push(`- [${p.subreddit}] ${p.title} (${p.score || 0} pts / ${p.comments || 0} comments) -> ${p.url}`);\n}\nconst created_at = new Date().toISOString();\nreturn [{\n  json: {\n    run_id: posts[0]?.run_id || created_at,\n    week_range: posts[0]?.week_range || '',\n    subreddits: subreddits || 'webdev, programming, devops, selfhosted',\n    total_posts,\n    top_topics_json: JSON.stringify(topics),\n    weekly_brief_text: brief.join('\\n'),\n    top_posts_json: JSON.stringify(topPostsJson),\n    created_at\n  }\n}];\n"},"typeVersion":2},{"id":"1532c19d-20d6-4d51-8852-bb8aead8dac2","name":"Append Weekly Digest to Sheet","type":"n8n-nodes-base.googleSheets","position":[2512,544],"parameters":{"columns":{"value":{"run_id":"={{ $json.run_id }}","created_at":"={{ $json.created_at }}","subreddits":"={{ $json.subreddits }}","week_range":"={{ $json.week_range }}","total_posts":"={{ $json.total_posts }}","top_posts_json":"={{ $json.top_posts_json }}","top_topics_json":"={{ $json.top_topics_json }}","weekly_brief_text":"={{ $json.weekly_brief_text }}"},"schema":[{"id":"run_id","type":"string","display":true,"required":false,"displayName":"run_id","defaultMatch":false,"canBeUsedToMatch":true},{"id":"week_range","type":"string","display":true,"required":false,"displayName":"week_range","defaultMatch":false,"canBeUsedToMatch":true},{"id":"subreddits","type":"string","display":true,"required":false,"displayName":"subreddits","defaultMatch":false,"canBeUsedToMatch":true},{"id":"total_posts","type":"string","display":true,"required":false,"displayName":"total_posts","defaultMatch":false,"canBeUsedToMatch":true},{"id":"top_topics_json","type":"string","display":true,"required":false,"displayName":"top_topics_json","defaultMatch":false,"canBeUsedToMatch":true},{"id":"weekly_brief_text","type":"string","display":true,"required":false,"displayName":"weekly_brief_text","defaultMatch":false,"canBeUsedToMatch":true},{"id":"top_posts_json","type":"string","display":true,"required":false,"displayName":"top_posts_json","defaultMatch":false,"canBeUsedToMatch":true},{"id":"created_at","type":"string","display":true,"required":false,"displayName":"created_at","defaultMatch":false,"canBeUsedToMatch":true}],"mappingMode":"defineBelow","matchingColumns":[],"attemptToConvertTypes":false,"convertFieldsToString":false},"options":{},"operation":"append","sheetName":{"__rl":true,"mode":"name","value":"weekly_digest"},"documentId":{"__rl":true,"mode":"id","value":"1rKuVREV4pedie7uAbuEcLvghNrAEeAjIPUBE6cQPleI"}},"credentials":{"googleSheetsOAuth2Api":{"id":"credential-id","name":"Google Sheets account 4"}},"typeVersion":4.5},{"id":"bca10483-5886-4c01-9440-f075c658efcc","name":"Send Weekly Digest Email","type":"n8n-nodes-base.emailSend","position":[2768,544],"webhookId":"40675526-a833-4559-ba60-4facc21d1a23","parameters":{"text":"={{$json.weekly_brief_text}}","options":{},"subject":"={{`Weekly Developer Tools Digest (Reddit) Ã¢â‚¬â€œ ${$json.week_range}`}}","toEmail":"user@example.com","fromEmail":"you@example.com"},"executeOnce":true,"typeVersion":2}],"active":false,"pinData":{},"settings":{"executionOrder":"v1"},"versionId":"b99cef81-f55c-49f8-940a-02db28ebb6d3","connections":{"Build Weekly Digest":{"main":[[{"node":"Append Weekly Digest to Sheet","type":"main","index":0}]]},"Deduplicate New Posts":{"main":[[{"node":"Append New Posts to Sheet","type":"main","index":0}]]}," Polite Delay (1–3s)":{"main":[[{"node":"Parse Listing HTML → Post Metadata","type":"main","index":0}]]},"Weekly Schedule Trigger":{"main":[[{"node":"Configure Subreddits & Week Range","type":"main","index":0}]]},"Append New Posts to Sheet":{"main":[[{"node":" Split Subreddits Into Batches","type":"main","index":0}]]},"Merge Post Metadata + Text":{"main":[[{"node":"Finalize & Normalize Post Fields","type":"main","index":0}]]},"Extract Selftext & Post Type":{"main":[[{"node":"Merge Post Metadata + Text","type":"main","index":1}]]},"Append Weekly Digest to Sheet":{"main":[[{"node":"Send Weekly Digest Email","type":"main","index":0}]]}," Split Subreddits Into Batches":{"main":[[{"node":"ScrapeOps: Fetch Subreddit Listing","type":"main","index":0}],[{"node":"Build Weekly Digest","type":"main","index":0}]]},"Read Existing Posts from Sheet":{"main":[[{"node":" Merge Scraped + Existing Posts","type":"main","index":1}]]}," Merge Scraped + Existing Posts":{"main":[[{"node":"Deduplicate New Posts","type":"main","index":0}]]},"Finalize & Normalize Post Fields":{"main":[[{"node":" Merge Scraped + Existing Posts","type":"main","index":0}]]},"Configure Subreddits & Week Range":{"main":[[{"node":" Split Subreddits Into Batches","type":"main","index":0},{"node":"Read Existing Posts from Sheet","type":"main","index":0}]]},"ScrapeOps: Fetch Subreddit Listing":{"main":[[{"node":" Polite Delay (1–3s)","type":"main","index":0}]]},"Parse Listing HTML → Post Metadata":{"main":[[{"node":" ScrapeOps: Fetch Post Details (JSON)","type":"main","index":0},{"node":"Merge Post Metadata + Text","type":"main","index":0}]]}," ScrapeOps: Fetch Post Details (JSON)":{"main":[[{"node":"Extract Selftext & Post Type","type":"main","index":0}]]}}},"lastUpdatedBy":1,"workflowInfo":{"nodeCount":24,"nodeTypes":{"n8n-nodes-base.code":{"count":6},"n8n-nodes-base.wait":{"count":1},"n8n-nodes-base.merge":{"count":2},"n8n-nodes-base.emailSend":{"count":1},"n8n-nodes-base.stickyNote":{"count":7},"n8n-nodes-base.googleSheets":{"count":3},"n8n-nodes-base.splitInBatches":{"count":1},"n8n-nodes-base.scheduleTrigger":{"count":1},"@scrapeops/n8n-nodes-scrapeops.ScrapeOps":{"count":2}}},"status":"published","readyToDemo":null,"user":{"name":"Ian Kerins","username":"iankerins","bio":"","verified":true,"links":["x.com/ianjkerins"],"avatar":"https://gravatar.com/avatar/890cb31fc440a02555fafa9eb072fb2282af0d51f9239c04e356c5eeda68be76?r=pg&d=retro&size=200"},"nodes":[{"id":11,"icon":"fa:envelope","name":"n8n-nodes-base.emailSend","codex":{"data":{"alias":["SMTP","email","human","form","wait","hitl","approval"],"resources":{"generic":[{"url":"https://n8n.io/blog/2021-the-year-to-automate-the-new-you-with-n8n/","icon":"☀️","label":"2021: The Year to Automate the New You with n8n"},{"url":"https://n8n.io/blog/build-your-own-virtual-assistant-with-n8n-a-step-by-step-guide/","icon":"👦","label":"Build your own virtual assistant with n8n: A step by step guide"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.sendemail/"}],"credentialDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/credentials/sendemail/"}]},"categories":["Communication","HITL","Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"HITL":["Human in the Loop"]}}},"group":"[\"output\"]","defaults":{"name":"Send Email","color":"#00bb88"},"iconData":{"icon":"envelope","type":"icon"},"displayName":"Send Email","typeVersion":2,"nodeCategories":[{"id":6,"name":"Communication"},{"id":9,"name":"Core Nodes"},{"id":28,"name":"HITL"}]},{"id":18,"icon":"file:googleSheets.svg","name":"n8n-nodes-base.googleSheets","codex":{"data":{"alias":["CSV","Sheet","Spreadsheet","GS"],"resources":{"generic":[{"url":"https://n8n.io/blog/love-at-first-sight-ricardos-n8n-journey/","icon":"❤️","label":"Love at first sight: Ricardo’s n8n journey"},{"url":"https://n8n.io/blog/why-business-process-automation-with-n8n-can-change-your-daily-life/","icon":"🧬","label":"Why business process automation with n8n can change your daily life"},{"url":"https://n8n.io/blog/automatically-adding-expense-receipts-to-google-sheets-with-telegram-mindee-twilio-and-n8n/","icon":"🧾","label":"Automatically Adding Expense Receipts to Google Sheets with Telegram, Mindee, Twilio, and n8n"},{"url":"https://n8n.io/blog/supercharging-your-conference-registration-process-with-n8n/","icon":"🎫","label":"Supercharging your conference registration process with n8n"},{"url":"https://n8n.io/blog/creating-triggers-for-n8n-workflows-using-polling/","icon":"⏲","label":"Creating triggers for n8n workflows using polling"},{"url":"https://n8n.io/blog/no-code-ecommerce-workflow-automations/","icon":"store","label":"6 e-commerce workflows to power up your Shopify s"},{"url":"https://n8n.io/blog/migrating-community-metrics-to-orbit-using-n8n/","icon":"📈","label":"Migrating Community Metrics to Orbit using n8n"},{"url":"https://n8n.io/blog/automate-google-apps-for-productivity/","icon":"💡","label":"15 Google apps you can combine and automate to increase productivity"},{"url":"https://n8n.io/blog/your-business-doesnt-need-you-to-operate/","icon":" 🖥️","label":"Hey founders! Your business doesn't need you to operate"},{"url":"https://n8n.io/blog/how-honest-burgers-use-automation-to-save-100k-per-year/","icon":"🍔","label":"How Honest Burgers Use Automation to Save $100k per year"},{"url":"https://n8n.io/blog/how-a-digital-strategist-uses-n8n-for-online-marketing/","icon":"💻","label":"How a digital strategist uses n8n for online marketing"},{"url":"https://n8n.io/blog/why-this-product-manager-loves-workflow-automation-with-n8n/","icon":"🧠","label":"Why this Product Manager loves workflow automation with n8n"},{"url":"https://n8n.io/blog/sending-automated-congratulations-with-google-sheets-twilio-and-n8n/","icon":"🙌","label":"Sending Automated Congratulations with Google Sheets, Twilio, and n8n "},{"url":"https://n8n.io/blog/how-a-membership-development-manager-automates-his-work-and-investments/","icon":"📈","label":"How a Membership Development Manager automates his work and investments"},{"url":"https://n8n.io/blog/aws-workflow-automation/","label":"7 no-code workflow automations for Amazon Web Services"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.googlesheets/"}],"credentialDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/credentials/google/oauth-single-service/"}]},"categories":["Data & Storage","Productivity"],"nodeVersion":"1.0","codexVersion":"1.0"}},"group":"[\"input\",\"output\"]","defaults":{"name":"Google Sheets"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MCIgaGVpZ2h0PSI2MCI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGZpbGw9IiMyOEI0NDYiIGQ9Ik0zNS42OSAxIDUyIDE3LjIyNXYzOS4wODdhMy42NyAzLjY3IDAgMCAxLTEuMDg0IDIuNjFBMy43IDMuNyAwIDAgMSA0OC4yOTMgNjBIMTIuNzA3YTMuNyAzLjcgMCAwIDEtMi42MjMtMS4wNzhBMy42NyAzLjY3IDAgMCAxIDkgNTYuMzEyVjQuNjg4YTMuNjcgMy42NyAwIDAgMSAxLjA4NC0yLjYxQTMuNyAzLjcgMCAwIDEgMTIuNzA3IDF6Ii8+PHBhdGggZmlsbD0iIzZBQ0U3QyIgZD0iTTM1LjY5IDEgNTIgMTcuMjI1SDM5LjM5N2MtMi4wNTQgMC0zLjcwNy0xLjgyOS0zLjcwNy0zLjg3MnoiLz48cGF0aCBmaWxsPSIjMjE5QjM4IiBkPSJNMzkuMjExIDE3LjIyNSA1MiAyMi40OHYtNS4yNTV6Ii8+PHBhdGggZmlsbD0iI0ZGRiIgZD0iTTIwLjEyIDMxLjk3NWMwLS44MTcuNjYyLTEuNDc1IDEuNDgzLTEuNDc1aDE3Ljc5NGMuODIxIDAgMS40ODIuNjU4IDEuNDgyIDEuNDc1djE1LjQ4N2MwIC44MTgtLjY2MSAxLjQ3NS0xLjQ4MiAxLjQ3NUgyMS42MDNhMS40NzYgMS40NzYgMCAwIDEtMS40ODItMS40NzRWMzEuOTc0em0yLjIyNSAxLjQ3NWg2LjY3MnYyLjIxMmgtNi42NzJ6bTAgNS4xNjJoNi42NzJ2Mi4yMTNoLTYuNjcyem0wIDUuMTYzaDYuNjcydjIuMjEyaC02LjY3MnptOS42MzgtMTAuMzI1aDYuNjcydjIuMjEyaC02LjY3MnptMCA1LjE2Mmg2LjY3MnYyLjIxM2gtNi42NzJ6bTAgNS4xNjNoNi42NzJ2Mi4yMTJoLTYuNjcyeiIvPjxwYXRoIGZpbGw9IiMyOEI0NDYiIGQ9Ik0zNC42OSAwIDUxIDE2LjIyNXYzOS4wODdhMy42NyAzLjY3IDAgMCAxLTEuMDg0IDIuNjFBMy43IDMuNyAwIDAgMSA0Ny4yOTMgNTlIMTEuNzA3YTMuNyAzLjcgMCAwIDEtMi42MjMtMS4wNzhBMy42NyAzLjY3IDAgMCAxIDggNTUuMzEyVjMuNjg4YTMuNjcgMy42NyAwIDAgMSAxLjA4NC0yLjYxQTMuNyAzLjcgMCAwIDEgMTEuNzA3IDB6Ii8+PHBhdGggZmlsbD0iIzZBQ0U3QyIgZD0iTTM0LjY5IDAgNTEgMTYuMjI1SDM4LjM5N2MtMi4wNTQgMC0zLjcwNy0xLjgyOS0zLjcwNy0zLjg3MnoiLz48cGF0aCBmaWxsPSIjMjE5QjM4IiBkPSJNMzguMjExIDE2LjIyNSA1MSAyMS40OHYtNS4yNTV6Ii8+PHBhdGggZmlsbD0iI0ZGRiIgZD0iTTE5LjEyIDMwLjk3NWMwLS44MTcuNjYyLTEuNDc1IDEuNDgzLTEuNDc1aDE3Ljc5NGMuODIxIDAgMS40ODIuNjU4IDEuNDgyIDEuNDc1djE1LjQ4N2MwIC44MTgtLjY2MSAxLjQ3NS0xLjQ4MiAxLjQ3NUgyMC42MDNhMS40NzYgMS40NzYgMCAwIDEtMS40ODItMS40NzRWMzAuOTc0em0yLjIyNSAxLjQ3NWg2LjY3MnYyLjIxMmgtNi42NzJ6bTAgNS4xNjJoNi42NzJ2Mi4yMTNoLTYuNjcyem0wIDUuMTYzaDYuNjcydjIuMjEyaC02LjY3MnptOS42MzgtMTAuMzI1aDYuNjcydjIuMjEyaC02LjY3MnptMCA1LjE2Mmg2LjY3MnYyLjIxM2gtNi42NzJ6bTAgNS4xNjNoNi42NzJ2Mi4yMTJoLTYuNjcyeiIvPjwvZz48L3N2Zz4="},"displayName":"Google Sheets","typeVersion":5,"nodeCategories":[{"id":3,"name":"Data & Storage"},{"id":4,"name":"Productivity"}]},{"id":24,"icon":"file:merge.svg","name":"n8n-nodes-base.merge","codex":{"data":{"alias":["Join","Concatenate","Wait"],"resources":{"generic":[{"url":"https://n8n.io/blog/how-to-sync-data-between-two-systems/","icon":"🏬","label":"How to synchronize data between two systems (one-way vs. two-way sync"},{"url":"https://n8n.io/blog/supercharging-your-conference-registration-process-with-n8n/","icon":"🎫","label":"Supercharging your conference registration process with n8n"},{"url":"https://n8n.io/blog/migrating-community-metrics-to-orbit-using-n8n/","icon":"📈","label":"Migrating Community Metrics to Orbit using n8n"},{"url":"https://n8n.io/blog/build-your-own-virtual-assistant-with-n8n-a-step-by-step-guide/","icon":"👦","label":"Build your own virtual assistant with n8n: A step by step guide"},{"url":"https://n8n.io/blog/sending-automated-congratulations-with-google-sheets-twilio-and-n8n/","icon":"🙌","label":"Sending Automated Congratulations with Google Sheets, Twilio, and n8n "},{"url":"https://n8n.io/blog/aws-workflow-automation/","label":"7 no-code workflow automations for Amazon Web Services"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.merge/"}]},"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Flow","Data Transformation"]}}},"group":"[\"transform\"]","defaults":{"name":"Merge"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDUxMiA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF8xMTc3XzUxOCkiPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTAgNDhDMCAyMS40OTAzIDIxLjQ5MDMgMCA0OCAwSDExMkMxMzguNTEgMCAxNjAgMjEuNDkwMyAxNjAgNDhWNTZIMTk2LjI1MkMyNDAuNDM1IDU2IDI3Ni4yNTIgOTEuODE3MiAyNzYuMjUyIDEzNlYxOTJDMjc2LjI1MiAyMTQuMDkxIDI5NC4xNjEgMjMyIDMxNi4yNTIgMjMySDM1MlYyMjRDMzUyIDE5Ny40OSAzNzMuNDkgMTc2IDQwMCAxNzZINDY0QzQ5MC41MSAxNzYgNTEyIDE5Ny40OSA1MTIgMjI0VjI4OEM1MTIgMzE0LjUxIDQ5MC41MSAzMzYgNDY0IDMzNkg0MDBDMzczLjQ5IDMzNiAzNTIgMzE0LjUxIDM1MiAyODhWMjgwSDMxNi4yNTJDMjk0LjE2MSAyODAgMjc2LjI1MiAyOTcuOTA5IDI3Ni4yNTIgMzIwVjM3NkMyNzYuMjUyIDQyMC4xODMgMjQwLjQzNSA0NTYgMTk2LjI1MiA0NTZIMTYwVjQ2NEMxNjAgNDkwLjUxIDEzOC41MSA1MTIgMTEyIDUxMkg0OEMyMS40OTAzIDUxMiAwIDQ5MC41MSAwIDQ2NFY0MDBDMCAzNzMuNDkgMjEuNDkwMyAzNTIgNDggMzUySDExMkMxMzguNTEgMzUyIDE2MCAzNzMuNDkgMTYwIDQwMFY0MDhIMTk2LjI1MkMyMTMuOTI1IDQwOCAyMjguMjUyIDM5My42NzMgMjI4LjI1MiAzNzZWMzIwQzIyOC4yNTIgMjk0Ljc4NCAyMzguODU5IDI3Mi4wNDQgMjU1Ljg1MyAyNTZDMjM4Ljg1OSAyMzkuOTU2IDIyOC4yNTIgMjE3LjIxNiAyMjguMjUyIDE5MlYxMzZDMjI4LjI1MiAxMTguMzI3IDIxMy45MjUgMTA0IDE5Ni4yNTIgMTA0SDE2MFYxMTJDMTYwIDEzOC41MSAxMzguNTEgMTYwIDExMiAxNjBINDhDMjEuNDkwMyAxNjAgMCAxMzguNTEgMCAxMTJWNDhaTTEwNCA0OEMxMDguNDE4IDQ4IDExMiA1MS41ODE3IDExMiA1NlYxMDRDMTEyIDEwOC40MTggMTA4LjQxOCAxMTIgMTA0IDExMkg1NkM1MS41ODE3IDExMiA0OCAxMDguNDE4IDQ4IDEwNFY1NkM0OCA1MS41ODE3IDUxLjU4MTcgNDggNTYgNDhIMTA0Wk00NTYgMjI0QzQ2MC40MTggMjI0IDQ2NCAyMjcuNTgyIDQ2NCAyMzJWMjgwQzQ2NCAyODQuNDE4IDQ2MC40MTggMjg4IDQ1NiAyODhINDA4QzQwMy41ODIgMjg4IDQwMCAyODQuNDE4IDQwMCAyODBWMjMyQzQwMCAyMjcuNTgyIDQwMy41ODIgMjI0IDQwOCAyMjRINDU2Wk0xMTIgNDA4QzExMiA0MDMuNTgyIDEwOC40MTggNDAwIDEwNCA0MDBINTZDNTEuNTgxNyA0MDAgNDggNDAzLjU4MiA0OCA0MDhWNDU2QzQ4IDQ2MC40MTggNTEuNTgxNyA0NjQgNTYgNDY0SDEwNEMxMDguNDE4IDQ2NCAxMTIgNDYwLjQxOCAxMTIgNDU2VjQwOFoiIGZpbGw9IiM1NEI4QzkiLz4KPC9nPgo8ZGVmcz4KPGNsaXBQYXRoIGlkPSJjbGlwMF8xMTc3XzUxOCI+CjxyZWN0IHdpZHRoPSI1MTIiIGhlaWdodD0iNTEyIiBmaWxsPSJ3aGl0ZSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo="},"displayName":"Merge","typeVersion":3,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":39,"icon":"fa:sync","name":"n8n-nodes-base.splitInBatches","codex":{"data":{"alias":["Loop","Concatenate","Batch","Split","Split In Batches"],"resources":{"generic":[{"url":"https://n8n.io/blog/how-uproc-scraped-a-multi-page-website-with-a-low-code-workflow/","icon":" 🕸️","label":"How uProc scraped a multi-page website with a low-code workflow"},{"url":"https://n8n.io/blog/benefits-of-automation-and-n8n-an-interview-with-hubspots-hugh-durkin/","icon":"🎖","label":"Benefits of automation and n8n: An interview with HubSpot's Hugh Durkin"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.splitinbatches/"}]},"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Flow"]}}},"group":"[\"organization\"]","defaults":{"name":"Loop Over Items","color":"#007755"},"iconData":{"icon":"sync","type":"icon"},"displayName":"Loop Over Items (Split in Batches)","typeVersion":3,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":514,"icon":"fa:pause-circle","name":"n8n-nodes-base.wait","codex":{"data":{"alias":["pause","sleep","delay","timeout"],"resources":{"generic":[{"url":"https://n8n.io/blog/how-to-get-started-with-crm-automation-and-no-code-workflow-ideas/","icon":"👥","label":"How to get started with CRM automation (with 3 no-code workflow ideas"},{"url":"https://n8n.io/blog/aws-workflow-automation/","label":"7 no-code workflow automations for Amazon Web Services"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.wait/"}]},"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers","Flow"]}}},"group":"[\"organization\"]","defaults":{"name":"Wait","color":"#804050"},"iconData":{"icon":"pause-circle","type":"icon"},"displayName":"Wait","typeVersion":1,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":565,"icon":"fa:sticky-note","name":"n8n-nodes-base.stickyNote","codex":{"data":{"alias":["Comments","Notes","Sticky"],"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers"]}}},"group":"[\"input\"]","defaults":{"name":"Sticky Note","color":"#FFD233"},"iconData":{"icon":"sticky-note","type":"icon"},"displayName":"Sticky Note","typeVersion":1,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":834,"icon":"file:code.svg","name":"n8n-nodes-base.code","codex":{"data":{"alias":["cpde","Javascript","JS","Python","Script","Custom Code","Function"],"details":"The Code node allows you to execute JavaScript in your workflow.","resources":{"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.code/"}]},"categories":["Development","Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers","Data Transformation"]}}},"group":"[\"transform\"]","defaults":{"name":"Code"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDUxMiA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF8xMTcxXzQ0MSkiPgo8cGF0aCBkPSJNMTcwLjI4MyA0OEgxOTYuNUMyMDMuMTI3IDQ4IDIwOC41IDQyLjYyNzQgMjA4LjUgMzZWMTJDMjA4LjUgNS4zNzI1OCAyMDMuMTI3IDAgMTk2LjUgMEgxNzAuMjgzQzEyNi4xIDAgOTAuMjgzIDM1LjgxNzIgOTAuMjgzIDgwVjE3NkM5MC4yODMgMjA2LjkyOCA2NS4yMTA5IDIzMiAzNC4yODMgMjMySDIzQzE2LjM3MjYgMjMyIDExIDIzNy4zNzIgMTEgMjQ0VjI2OEMxMSAyNzQuNjI3IDE2LjM3MjQgMjgwIDIyLjk5OTYgMjgwTDM0LjI4MyAyODBDNjUuMjEwOSAyODAgOTAuMjgzIDMwNS4wNzIgOTAuMjgzIDMzNlY0NDBDOTAuMjgzIDQ3OS43NjQgMTIyLjUxOCA1MTIgMTYyLjI4MyA1MTJIMTk2LjVDMjAzLjEyNyA1MTIgMjA4LjUgNTA2LjYyNyAyMDguNSA1MDBWNDc2QzIwOC41IDQ2OS4zNzMgMjAzLjEyNyA0NjQgMTk2LjUgNDY0SDE2Mi4yODNDMTQ5LjAyOCA0NjQgMTM4LjI4MyA0NTMuMjU1IDEzOC4yODMgNDQwVjMzNkMxMzguMjgzIDMwOS4wMjIgMTI4LjAxMSAyODQuNDQzIDExMS4xNjQgMjY1Ljk2MUMxMDYuMTA5IDI2MC40MTYgMTA2LjEwOSAyNTEuNTg0IDExMS4xNjQgMjQ2LjAzOUMxMjguMDExIDIyNy41NTcgMTM4LjI4MyAyMDIuOTc4IDEzOC4yODMgMTc2VjgwQzEzOC4yODMgNjIuMzI2OSAxNTIuNjEgNDggMTcwLjI4MyA0OFoiIGZpbGw9IiNGRjk5MjIiLz4KPHBhdGggZD0iTTMwNSAzNkMzMDUgNDIuNjI3NCAzMTAuMzczIDQ4IDMxNyA0OEgzNDIuOTc5QzM2MC42NTIgNDggMzc0Ljk3OCA2Mi4zMjY5IDM3NC45NzggODBWMTc2QzM3NC45NzggMjAyLjk3OCAzODUuMjUxIDIyNy41NTcgNDAyLjA5OCAyNDYuMDM5QzQwNy4xNTMgMjUxLjU4NCA0MDcuMTUzIDI2MC40MTYgNDAyLjA5OCAyNjUuOTYxQzM4NS4yNTEgMjg0LjQ0MyAzNzQuOTc4IDMwOS4wMjIgMzc0Ljk3OCAzMzZWNDMyQzM3NC45NzggNDQ5LjY3MyAzNjAuNjUyIDQ2NCAzNDIuOTc5IDQ2NEgzMTdDMzEwLjM3MyA0NjQgMzA1IDQ2OS4zNzMgMzA1IDQ3NlY1MDBDMzA1IDUwNi42MjcgMzEwLjM3MyA1MTIgMzE3IDUxMkgzNDIuOTc5QzM4Ny4xNjEgNTEyIDQyMi45NzggNDc2LjE4MyA0MjIuOTc4IDQzMlYzMzZDNDIyLjk3OCAzMDUuMDcyIDQ0OC4wNTEgMjgwIDQ3OC45NzkgMjgwSDQ5MEM0OTYuNjI3IDI4MCA1MDIgMjc0LjYyOCA1MDIgMjY4VjI0NEM1MDIgMjM3LjM3MyA0OTYuNjI4IDIzMiA0OTAgMjMyTDQ3OC45NzkgMjMyQzQ0OC4wNTEgMjMyIDQyMi45NzggMjA2LjkyOCA0MjIuOTc4IDE3NlY4MEM0MjIuOTc4IDM1LjgxNzIgMzg3LjE2MSAwIDM0Mi45NzkgMEgzMTdDMzEwLjM3MyAwIDMwNSA1LjM3MjU4IDMwNSAxMlYzNloiIGZpbGw9IiNGRjk5MjIiLz4KPC9nPgo8ZGVmcz4KPGNsaXBQYXRoIGlkPSJjbGlwMF8xMTcxXzQ0MSI+CjxyZWN0IHdpZHRoPSI1MTIiIGhlaWdodD0iNTEyIiBmaWxsPSJ3aGl0ZSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo="},"displayName":"Code","typeVersion":2,"nodeCategories":[{"id":5,"name":"Development"},{"id":9,"name":"Core Nodes"}]},{"id":839,"icon":"fa:clock","name":"n8n-nodes-base.scheduleTrigger","codex":{"data":{"alias":["Time","Scheduler","Polling","Cron","Interval"],"resources":{"generic":[],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.scheduletrigger/"}]},"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0"}},"group":"[\"trigger\",\"schedule\"]","defaults":{"name":"Schedule Trigger","color":"#31C49F"},"iconData":{"icon":"clock","type":"icon"},"displayName":"Schedule Trigger","typeVersion":1,"nodeCategories":[{"id":9,"name":"Core Nodes"}]}],"categories":[{"id":32,"name":"Market Research"}],"image":[]}}