{
  "nodes": [
    {
      "id": "77e9fdfd-ee83-4394-a938-ceb6be4a58a8",
      "name": "When clicking \u2018Execute workflow\u2019",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -1840,
        -32
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "04569eb9-91d9-4eda-a3b7-f1044b3aacf2",
      "name": "make hashtag links",
      "type": "n8n-nodes-base.code",
      "position": [
        -1216,
        -32
      ],
      "parameters": {
        "jsCode": "// Mode: Run once for all items\nconst tags = $input.first().json.Hashtag || [];           // [\"vegan\", \"travel\", \u2026]\n\nconst startUrls = tags.map(\n  t => `\"https://www.instagram.com/explore/tags/${encodeURIComponent(t)}/\"`\n);\n\n// Build the final body Apify expects\nreturn [{   // or delete if you don\u2019t need the date filter\n  startUrls                 // \u2190 trailing slash now added\n  // customMapFunction left out \u2014 see note below\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "45305b08-fd9f-4872-b4e3-e37d38b866d0",
      "name": "Scrape instagram hashtag posts",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -976,
        -32
      ],
      "parameters": {
        "url": "https://api.apify.com/v2/acts/culc72xb7MP3EbaeX/run-sync-get-dataset-items",
        "method": "POST",
        "options": {
          "redirect": {
            "redirect": {}
          }
        },
        "jsonBody": "={\n    \"maxItems\": 300,\n    \"startUrls\": [\n{{ $json.startUrls }}\n    ],\n    \"until\": \"2025-06-20\"\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/json"
            },
            {
              "name": "Authorization",
              "value": "Bearer YOUR_TOKEN_HERE"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "067ed683-3a63-4d59-8c23-2bb87d8d3287",
      "name": "IF \u2014 Hashtags Only",
      "type": "n8n-nodes-base.if",
      "position": [
        -560,
        -32
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-only-hashtags",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.hasHashtags && ($json.textNoTags || '').trim() === '' }}"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "43e985f4-579d-49ed-839c-7b5809d8ddd8",
      "name": "IF \u2014 English Only",
      "type": "n8n-nodes-base.if",
      "position": [
        -352,
        64
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-english-only",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.enHits > 0 && $json.frHits === 0 && $json.esHits === 0 }}"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "2b17b052-1474-40a3-8c86-6eb450969b2b",
      "name": "Aggregate Hashtags",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        -1424,
        -32
      ],
      "parameters": {
        "options": {},
        "fieldsToAggregate": {
          "fieldToAggregate": [
            {
              "fieldToAggregate": "Hashtag"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "9640b845-8f56-4dea-bcdc-f315e30b1224",
      "name": "Format captions, usernames and data",
      "type": "n8n-nodes-base.code",
      "position": [
        -768,
        -32
      ],
      "parameters": {
        "jsCode": "return items.map(item => {\n  const json = item.json;\n  // 1) Original caption or title\n  const caption = json.caption || json.title || '';\n  json.caption = caption;\n  // 2) Count hashtags & strip them + strip URLs\n  const hashtags = caption.match(/#\\S+/g) || [];\n  json.hashtagCount = hashtags.length;\n  json.hasHashtags = json.hashtagCount > 0;\n  const noUrls = caption.replace(/https?:\\/\\/\\S+/gi, '');\n  const textNoTags = noUrls.replace(/#[^\\s#]+/g, '').replace(/\\.{2,}/g, ' ').replace(/\\s+/g, ' ').trim();\n  json.textNoTags = textNoTags;\n  json.hasOnlyHashtags = json.hasHashtags && textNoTags === '';\n  // 3) Word array (no numbers-only, min length 2)\n  let words = textNoTags.toLowerCase().split(/\\W+/).filter(w => w.length > 1 && !/^\\d+$/.test(w));\n  const totalWords = words.length;\n  json.totalWords = totalWords;\n  // 4) Keyword hit lists\n  const enKw = ['the','and','to','of','in','for','on','with','is','are','this','that','it','be','at','by','from','as','was','were','have','has','you','we','i'];\n  const frKw = ['le','la','les','des','un','une','du','de','et','en','dans','pour','avec','est','sur','au','aux','ce','cet','cette','\u00e7a','pas','plus','que','qui','o\u00f9','je','tu','il','elle','nous','vous','ils','elles'];\n  const esKw = ['el','la','los','las','de','del','y','en','por','para','con','es','un','una','que','como','muy','pero','si','porque'];\n  json.enHits = words.filter(w => enKw.includes(w)).length;\n  json.frHits = words.filter(w => frKw.includes(w)).length;\n  json.esHits = words.filter(w => esKw.includes(w)).length;\n  // 5) Ratios\n  json.enRatio = totalWords ? json.enHits / totalWords : 0;\n  json.frRatio = totalWords ? json.frHits / totalWords : 0;\n  json.esRatio = totalWords ? json.esHits / totalWords : 0;\n  // 6) ASCII vs non-ASCII\n  const asciiCount = textNoTags.replace(/[^\\x00-\\x7F]/g, '').length;\n  json.asciiRatio = textNoTags.length ? asciiCount / textNoTags.length : 0;\n  // 7) Accent & script flags\n  json.hasFrenchAccent = /[\u00e9\u00e8\u00ea\u00eb\u00e0\u00e2\u00ee\u00ef\u00f4\u00f6\u00f9\u00fb\u00e7\u0153\u00e6]/i.test(textNoTags);\n  json.hasSpanishAccentStrict = /[\u00f1\u00a1\u00bf]/.test(textNoTags);\n  json.hasArabicOrCJK = /[\\u0600-\\u06FF\\u0750-\\u077F\\u4E00-\\u9FFF\\u3040-\\u30FF\\uAC00-\\uD7AF]/u.test(textNoTags);\n  return { json };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "6626209f-fc93-4688-a41c-51606d03c887",
      "name": "Combine all usernames",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        112,
        -64
      ],
      "parameters": {
        "options": {},
        "fieldsToAggregate": {
          "fieldToAggregate": [
            {
              "fieldToAggregate": "owner.username"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "1d876779-5409-4214-b6b0-48276e44ab41",
      "name": "Remove Duplicate Usernames",
      "type": "n8n-nodes-base.removeDuplicates",
      "position": [
        -112,
        -64
      ],
      "parameters": {
        "compare": "selectedFields",
        "options": {},
        "fieldsToCompare": "owner.username"
      },
      "typeVersion": 2
    },
    {
      "id": "12f7a1bd-4ce4-47c0-ae1d-2444ecd7ac6d",
      "name": "Scrape instagram Profiles",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        384,
        -64
      ],
      "parameters": {
        "url": "https://api.apify.com/v2/acts/dSCLg0C3YEZ83HzYX/run-sync-get-dataset-items",
        "method": "POST",
        "options": {
          "redirect": {
            "redirect": {}
          }
        },
        "jsonBody": "={\n    \"usernames\": [\n        \t{{ $json.username.map(u => `\"${u}\"`).join(\", \") }}\n    ]\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/json"
            },
            {
              "name": "Authorization",
              "value": "Bearer YOUR_TOKEN_HERE"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "3288d091-4f14-4c7b-8813-7a4a2b64db66",
      "name": "If followers are in a certain range continue",
      "type": "n8n-nodes-base.if",
      "position": [
        592,
        -64
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "7b585f6a-c548-4b62-a6e8-0bcf6c7ba771",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.followersCount }}",
              "rightValue": 10000
            },
            {
              "id": "4753a598-fe31-4670-99f2-a0d5ad164074",
              "operator": {
                "type": "number",
                "operation": "lte"
              },
              "leftValue": "={{ $json.followersCount }}",
              "rightValue": 100000
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "79d0609c-f68b-41fa-96ec-cc0676fdd706",
      "name": "Get list of Hashtags",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -1632,
        -32
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "id",
          "value": "SHEET_ID"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "SPREADSHEET_ID"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.6
    },
    {
      "id": "08bc2863-b41f-4e1d-99db-30e03b1b1c01",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -704,
        -736
      ],
      "parameters": {
        "color": 5,
        "width": 512,
        "height": 240,
        "content": "# Workflow Overview\n- Start: Get a list of hashtags from Google Sheets\n- Scrape Instagram posts for each hashtag (via Apify)\n- Analyze captions: extract keywords, check for language & hashtags\n- Gather/post-process usernames, scrape their profiles\n- Filter users based on followers count\n- Result: List of users meeting your criteria"
      },
      "typeVersion": 1
    },
    {
      "id": "1066082c-43db-4e5b-b9be-4beb44d9061a",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1680,
        -272
      ],
      "parameters": {
        "color": 4,
        "width": 528,
        "height": 144,
        "content": "## Read Hashtags & Prepare Instagram Links\n- Retrieve all hashtags from a Google Sheet\n- Build direct Instagram tag-explore URLs for each hashtag\n- Output is a list of start URLs for scraping Instagram hashtag feeds"
      },
      "typeVersion": 1
    },
    {
      "id": "f5527a08-5916-4234-9225-835262206952",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -752,
        -288
      ],
      "parameters": {
        "color": 2,
        "width": 560,
        "height": 176,
        "content": "## Analyze Post Captions\n- Extract and process each Instagram caption\n- Count & strip hashtags, links, and analyze language (English/French/Spanish) using keyword lists\n- Mark posts as 'hashtags only', and apply logic to filter for language or other text patterns"
      },
      "typeVersion": 1
    },
    {
      "id": "4e0a1454-b1c2-4af2-b3f0-d3328d3956c4",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        112,
        -288
      ],
      "parameters": {
        "color": 3,
        "width": 592,
        "content": "# Gather & Filter User Profiles\n- Combine all usernames from found posts, remove duplicates\n- Scrape profile details (like follower count) for each username\n- Only allow users whose follower counts are within a target range for your use case"
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Aggregate Hashtags": {
      "main": [
        [
          {
            "node": "make hashtag links",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "make hashtag links": {
      "main": [
        [
          {
            "node": "Scrape instagram hashtag posts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF \u2014 English Only": {
      "main": [
        [
          {
            "node": "Remove Duplicate Usernames",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get list of Hashtags": {
      "main": [
        [
          {
            "node": "Aggregate Hashtags",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF \u2014 Hashtags Only": {
      "main": [
        [
          {
            "node": "Remove Duplicate Usernames",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "IF \u2014 English Only",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine all usernames": {
      "main": [
        [
          {
            "node": "Scrape instagram Profiles",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape instagram Profiles": {
      "main": [
        [
          {
            "node": "If followers are in a certain range continue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Remove Duplicate Usernames": {
      "main": [
        [
          {
            "node": "Combine all usernames",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape instagram hashtag posts": {
      "main": [
        [
          {
            "node": "Format captions, usernames and data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format captions, usernames and data": {
      "main": [
        [
          {
            "node": "IF \u2014 Hashtags Only",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking \u2018Execute workflow\u2019": {
      "main": [
        [
          {
            "node": "Get list of Hashtags",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}