{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "ba38a1f6-c843-466d-b531-bdd9e99cc3e5",
      "name": "Google Gemini Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        11504,
        112
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "9945101a-46f5-4bf5-a139-1474d939866e",
      "name": "Loop Over Items",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        11280,
        -96
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "9d264dc4-fb1a-4fc4-97ad-0868fe835053",
      "name": "Google Gemini Chat Model1",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        11792,
        -352
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "915e1239-4e78-413a-8901-7cf1cc86f032",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        9872,
        -208
      ],
      "parameters": {
        "color": 7,
        "width": 528,
        "height": 272,
        "content": "## Input & Search\nReceives the company name from Telegram and performs a Google search for relevant review pages using Decodo.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "8eabfbdc-4925-4243-864e-b1b527f47d40",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        10432,
        -208
      ],
      "parameters": {
        "color": 7,
        "width": 544,
        "height": 272,
        "content": "## URL Extraction & Scraping\nExtracts all review URLs from search results, fetches each page, and cleans the content for analysis.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "e633c1a7-614e-469b-a623-0ef4f004c4be",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        11024,
        -192
      ],
      "parameters": {
        "color": 7,
        "width": 784,
        "height": 400,
        "content": "## Sentiment Analysis\nProcesses each review page individually to classify sentiment, assign a score, and extract key topics.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "52b39c26-9802-4993-988c-d1897aecfe31",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        11424,
        -608
      ],
      "parameters": {
        "color": 7,
        "width": 848,
        "height": 384,
        "content": "## Final Report\nCombines all sentiment results into a single summary and generates an actionable verdict sent via Telegram.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "a19c275a-6fb5-47d0-9762-fd266e7c0efa",
      "name": "Get Message",
      "type": "n8n-nodes-base.telegramTrigger",
      "position": [
        9904,
        -96
      ],
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "7f3c7e04-ce05-4ea3-9cb8-8d0b38aeb48c",
      "name": "Set Search Query",
      "type": "n8n-nodes-base.set",
      "position": [
        10064,
        -96
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "ba5eae0e-6e9e-4f2b-84cb-8e792aaa33ff",
              "name": "query",
              "type": "string",
              "value": "={{ $json.message.text }} reviews"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "dc96a499-b8f7-4e02-a685-aeb0049c4b3e",
      "name": "Extract URLs",
      "type": "n8n-nodes-base.code",
      "position": [
        10544,
        -96
      ],
      "parameters": {
        "jsCode": "// Recursively collect all URLs in ANY depth\n\nfunction extractUrls(obj, urls) {\n  if (!obj || typeof obj !== 'object') return;\n\n  // If object has a URL, collect it\n  if (obj.url && typeof obj.url === 'string' && obj.url.startsWith('http')) {\n    urls.push(obj.url);\n  }\n\n  // Recursively scan inner values\n  if (Array.isArray(obj)) {\n    for (const item of obj) extractUrls(item, urls);\n  } else {\n    for (const key of Object.keys(obj)) {\n      extractUrls(obj[key], urls);\n    }\n  }\n}\n\nlet urls = [];\n\n// extract from all decodo items\nfor (const item of items) {\n  extractUrls(item.json, urls);\n}\n\n// remove duplicates\nurls = [...new Set(urls)];\n\n// SKIP the first URL (Decodo's own search URL)\nurls = urls.slice(1);\n\n// return one item per URL\nreturn urls.map(url => ({\n  json: { url }\n}));\n"
      },
      "typeVersion": 2
    },
    {
      "id": "96148a7d-6376-4b98-ae06-cb66f547acee",
      "name": "Clean HTML",
      "type": "n8n-nodes-base.code",
      "position": [
        11072,
        -96
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const item = $input.item.json;\n\nlet html = \"\";\n\ntry {\n  html = item.results[0].content || \"\";\n} catch (e) {\n  html = \"\";\n}\n\nlet cleaned = html\n  .replace(/<script[^>]*>[\\s\\S]*?<\\/script>/gi, \"\")\n  .replace(/<style[^>]*>[\\s\\S]*?<\\/style>/gi, \"\")\n  .replace(/<noscript[^>]*>[\\s\\S]*?<\\/noscript>/gi, \"\")\n  .replace(/<\\/?[^>]+>/g, \" \")\n  .replace(/&nbsp;/gi, \" \")\n  .replace(/&amp;/gi, \"&\")\n  .replace(/&quot;/gi, '\"')\n  .replace(/&#39;/gi, \"'\")\n  .replace(/&lt;/gi, \"<\")\n  .replace(/&gt;/gi, \">\")\n  .replace(/\\s+/g, \" \")\n  .trim();\n\nreturn {\n  json: {\n    text_clean: cleaned\n  }\n};\n"
      },
      "typeVersion": 2
    },
    {
      "id": "0f022eee-85c0-4c1f-8fd8-122c2ee04e64",
      "name": "Analyze Sentiment",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        11504,
        -80
      ],
      "parameters": {
        "text": "=You are a sentiment engine. Analyze the review below and return JSON.\n\nReview:\n{{ $json.text_clean }}\n\nReturn:\n{\n  \"sentiment\": \"positive | neutral | negative\",\n  \"score\": 0-1,\n  \"topics\": [\"...\"]\n}\n",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 3
    },
    {
      "id": "0626aefd-a66c-4864-b76a-c583ef326115",
      "name": "Merge Results",
      "type": "n8n-nodes-base.code",
      "position": [
        11520,
        -528
      ],
      "parameters": {
        "jsCode": "// 1. Get all results from the previous AI Agent\nconst items = $input.all();\n\n// 2. Start a generic variable\nlet combinedData = \"Here is the combined analysis data gathered from the search results:\\n\\n\";\n\n// 3. Loop and stack the text\nitems.forEach((item, index) => {\n  // We grab the 'output' field from your previous AI node\n  combinedData += `--- Search Result ${index + 1} ---\\n`;\n  combinedData += (item.json.output || \"No data\") + \"\\n\\n\";\n});\n\n// 4. Return one single item to the next AI\nreturn [{\n  json: {\n    aggregated_report: combinedData\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "7bb60ae6-815e-49d7-b505-f8987653cff9",
      "name": "Draft Final Report",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        11792,
        -528
      ],
      "parameters": {
        "text": "=Role: You are a strict data analyst. You do not use flowery language. You only extract facts.\n\nData Source: {{ $json.aggregated_report }}\n\nConstraints:\n1. STRICTLY use the provided \"Data Source\" only. Do not invent features or sentiment.\n2. If the data mentions specific numbers or scores, quote them.\n3. Max length: 200 words.\n4. Format: Clean bullet points optimized for a Telegram message.\n\nOutput Format:\n**Sentiment Score:** [Extract the average score or dominant sentiment, e.g., \"Positive (0.95)\"]\n\n**Customer Voice:**\n\u2022 [One specific, distinct quote or observation from the data]\n\u2022 [Another specific observation]\n\n**Actionable Verdict:**\n[One sentence recommendation based on the data]",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 3
    },
    {
      "id": "48ff4042-3c0b-46c6-99ef-aeb1cd2d3daa",
      "name": "Reply to User",
      "type": "n8n-nodes-base.telegram",
      "position": [
        12144,
        -528
      ],
      "parameters": {
        "text": "={{ $json.output }}",
        "chatId": "={{ $('Get Message').first().json.message.chat.id }}",
        "additionalFields": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "e459df98-8bb9-4b7d-b836-8c34321d04ff",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        9184,
        -464
      ],
      "parameters": {
        "width": 656,
        "height": 576,
        "content": "## How it works\nWhen a user sends a company name to the Telegram bot, the workflow automatically performs a Google search for review-related pages using Decodo. All discovered URLs are extracted dynamically, regardless of their depth in the search response. Each page is then fetched and cleaned by removing HTML, scripts, and unnecessary markup to produce readable text.\n\nThe workflow processes each cleaned review page individually using an AI sentiment agent. For every source, it determines the sentiment (positive, neutral, or negative), assigns a confidence score, and extracts key discussion topics. All individual analyses are then merged into a single aggregated dataset.\n\nA final AI analysis step reviews the combined data and generates a structured executive-style report. The report includes an overall sentiment score, direct customer voice observations, and a clear actionable verdict. The final result is sent back to the user via Telegram.\n\n## Setup steps\n1. Connect your Telegram Bot credentials.\n2. Add your Decodo API key for Google search and page fetching.\n3. Configure your Google Gemini API credentials.\n4. Deploy the workflow and send a company name via Telegram to start analysis.\n\n## Customization\nYou can adjust prompt strictness, sentiment thresholds, or limit the number of analyzed pages to control cost and depth.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "ea960533-c286-47ac-9afe-07692f4e2652",
      "name": "Google Search1",
      "type": "@decodo/n8n-nodes-decodo.decodo",
      "position": [
        10272,
        -96
      ],
      "parameters": {
        "query": "={{ $json.query }}",
        "operation": "google_search"
      },
      "credentials": {
        "decodoApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "44d6cfe0-b68e-4f26-830b-98b28a10a8ce",
      "name": "Scrape Page Content1",
      "type": "@decodo/n8n-nodes-decodo.decodo",
      "position": [
        10752,
        -96
      ],
      "parameters": {
        "url": "={{ $json.url }}"
      },
      "credentials": {
        "decodoApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Clean HTML": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Message": {
      "main": [
        [
          {
            "node": "Set Search Query",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract URLs": {
      "main": [
        [
          {
            "node": "Scrape Page Content1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Results": {
      "main": [
        [
          {
            "node": "Draft Final Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Search1": {
      "main": [
        [
          {
            "node": "Extract URLs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [
          {
            "node": "Merge Results",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Analyze Sentiment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Search Query": {
      "main": [
        [
          {
            "node": "Google Search1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze Sentiment": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Draft Final Report": {
      "main": [
        [
          {
            "node": "Reply to User",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape Page Content1": {
      "main": [
        [
          {
            "node": "Clean HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Analyze Sentiment",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "Draft Final Report",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  }
}