{
  "id": "6vj6GA2bdFbUXGlc",
  "meta": {
    "instanceId": "6fcb279fd6b00187153abdc6d8b627acfd9fe31f84b387f64b56a0996e8ea182"
  },
  "name": "Find cheap flight deals using AI, Telegram and BrowserAct",
  "tags": [],
  "nodes": [
    {
      "id": "ae57867e-ffbe-4188-9955-00725cc17f51",
      "name": "Structured Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        -720,
        496
      ],
      "parameters": {
        "autoFix": true,
        "jsonSchemaExample": "{\n  \"type\": \"travel\",\n  \"location\": \"San Francisco\"\n}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "f295c258-781c-46c1-8338-639dcc26b08c",
      "name": "Validate user Input",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -848,
        272
      ],
      "parameters": {
        "text": "={{ $json.message.text }}",
        "options": {
          "systemMessage": "You are an intelligent Intent Classifier and Entity Extractor for a travel bot. Your sole purpose is to analyze the user's input text and categorize it into one of three specific types based on their intent and the availability of data.\n\nTask: Analyze the user's input string and return a single JSON object based on the following logic:\n\n1. Travel Request with Location:\n\nTrigger: The user is explicitly asking for flight prices, travel offers, deals, or tickets AND you can identify a specific Departure City (e.g., \"flights from London\", \"deals leaving NYC\").\n\nAction: Extract the departure city.\n\nOutput: Set \"type\": \"travel\" and \"location\": \"City Name\".\n\n2. Travel Request without Location:\n\nTrigger: The user is asking for travel deals or prices (e.g., \"I want cheap flights\", \"Show me offers\") but DID NOT specify a departure city.\n\nAction: Flag as missing data.\n\nOutput: Set \"type\": \"nodata\" and \"location\": null.\n\n3. General Chat:\n\nTrigger: The user is engaging in casual conversation (e.g., \"Hello\", \"How are you?\", \"Who are you?\") that is unrelated to a specific travel search.\n\nAction: Classify as chat.\n\nOutput: Set \"type\": \"chat\" and \"location\": null.\n\nOutput Format: You must respond only with a raw JSON object. Do not add markdown formatting or conversational text.\n\n{\n  \"type\": \"travel\" | \"nodata\" | \"chat\",\n  \"location\": \"City Name\" | null\n}\nExamples of Behavior:\nInput: \"Find me cheap flights from Berlin to Paris\" Output:\n\n{\n  \"type\": \"travel\",\n  \"location\": \"Berlin\"\n}\nInput: \"I want to go on a vacation show me prices\" Output:\n\n{\n  \"type\": \"nodata\",\n  \"location\": null\n}\nInput: \"Good morning, how does this bot work?\" Output:\n\n{\n  \"type\": \"chat\",\n  \"location\": null\n}\nInput: \"Deals departing form San Francisco\" Output:\n\n{\n  \"type\": \"travel\",\n  \"location\": \"San Francisco\"\n}"
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 3
    },
    {
      "id": "8c74bb74-24e8-450a-9875-a4682abcc710",
      "name": "Validate user Inputs",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        -848,
        496
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "googlePalmApi": {
          "id": "9pXYiybR74j5zn4d",
          "name": "Google Gemini(PaLM) Api account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "c184ff93-0aed-445e-8ec4-1a66a91d542b",
      "name": "Google Gemini",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        -128,
        736
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "googlePalmApi": {
          "id": "9pXYiybR74j5zn4d",
          "name": "Google Gemini(PaLM) Api account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "0dd6095d-a693-46ba-8e77-a83476b97b34",
      "name": "Structured Output",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        624,
        512
      ],
      "parameters": {
        "autoFix": true,
        "jsonSchemaExample": "\n{\n  \"Telegram\": [\n    \"String 1 (HTML formatted, max 3500 chars)\",\n    \"String 2 (If needed due to length)\"\n  ]\n}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "527af11f-88b2-4a44-abe0-4bad44ced719",
      "name": "OpenRouter Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        512,
        512
      ],
      "parameters": {
        "model": "openai/gpt-4.1",
        "options": {}
      },
      "credentials": {
        "openRouterApi": {
          "id": "x6ohV3n8b2Kjebtm",
          "name": "OpenRouter account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "995ebd6f-dc22-4b8b-a585-1b4c803b3224",
      "name": "Loop Over Items",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        976,
        304
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "33b7eab4-658a-4e92-a22f-e49dc66fe069",
      "name": "Chatting With User",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -128,
        576
      ],
      "parameters": {
        "text": "=Input type : {{ $json.output.Type }} | User Input : {{ $('User Sends Message to Bot').item.json.message.text }}",
        "options": {
          "systemMessage": "if the input type is nodata ask the user to provide the Departure location.\n\nif the input type is chat, analyze the user input and generate single response\n\nsend the result out as a raw text.\navoid using any tags or notations like ```text ```"
        },
        "promptType": "define"
      },
      "typeVersion": 3
    },
    {
      "id": "71544b37-f48b-4c7a-babc-d3ce7ad62e5a",
      "name": "Answer the User",
      "type": "n8n-nodes-base.telegram",
      "position": [
        176,
        576
      ],
      "webhookId": "23e1fb3a-33d8-40fa-aa49-08b320348b0a",
      "parameters": {
        "text": "={{ $json.output }}",
        "chatId": "={{ $('User Sends Message to Bot').item.json.message.chat.id }}",
        "additionalFields": {
          "parse_mode": "HTML",
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "id": "gJnGTVzNOeiM3LHZ",
          "name": "Telegram account"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "f04821b9-be4a-4de0-bc7c-c1b23a339ed1",
      "name": "Check For Input Type",
      "type": "n8n-nodes-base.switch",
      "position": [
        -496,
        256
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "9e003f66-d011-459a-a313-b603c0e14551",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.output.type }}",
                    "rightValue": "travel"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "2ebb24ec-bd32-4e96-a022-93d066fda17f",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.output.type }}",
                    "rightValue": "chat"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "version": 3,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "24d355dd-efec-4946-96fe-3d82f3ee4d71",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.output.type }}",
                    "rightValue": "nodata"
                  }
                ]
              }
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.4
    },
    {
      "id": "b4d78d2d-bc32-4dd0-8465-67cef4ae2c45",
      "name": "User Sends Message to Bot",
      "type": "n8n-nodes-base.telegramTrigger",
      "position": [
        -1024,
        272
      ],
      "webhookId": "a98e8455-ba5a-447e-b928-733483d34db4",
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "id": "gJnGTVzNOeiM3LHZ",
          "name": "Telegram account"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "9f86f90d-26a1-4602-befe-417292fe1fb7",
      "name": "Process Initialization Alert",
      "type": "n8n-nodes-base.telegram",
      "position": [
        -48,
        144
      ],
      "webhookId": "9fd73bb8-75f1-499c-8c1f-0cd659833fb9",
      "parameters": {
        "text": "=Ok give me few minutes.",
        "chatId": "={{ $('User Sends Message to Bot').first().json.message.chat.id }}",
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "id": "gJnGTVzNOeiM3LHZ",
          "name": "Telegram account"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "ea9f17c3-584d-4b26-ab22-799d95a65133",
      "name": "Run \"Low-Const Travel Finder\" workflow",
      "type": "n8n-nodes-browseract.browserAct",
      "position": [
        -48,
        304
      ],
      "parameters": {
        "type": "WORKFLOW",
        "timeout": 7200,
        "workflowId": "72181632540107285",
        "workflowConfig": {
          "value": {
            "input-Location": "={{ $json.output.location }}"
          },
          "schema": [
            {
              "id": "input-Momondo",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "description": "If left blank, the default value defined in BrowserAct will be used.",
              "displayName": "Momondo",
              "defaultMatch": true
            },
            {
              "id": "input-Location",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "description": "If left blank, the default value defined in BrowserAct will be used.",
              "displayName": "Location",
              "defaultMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "input-Location"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "open_incognito_mode": false
      },
      "credentials": {
        "browserActApi": {
          "id": "G1U5ih38TKU5wcI5",
          "name": "BrowserAct account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "32fd8b4f-fded-4992-9e7d-560018c4524d",
      "name": "Analyze Data & Generate Response",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        512,
        304
      ],
      "parameters": {
        "text": "=Travels Data :{{ $json.output.string }},\nDeparture Location : {{ $('Validate user Input').first().json.output.location }}",
        "options": {
          "systemMessage": "Role: You are an expert Travel Deal Curator for a Telegram channel. Your job is to take raw flight data and a departure location, then transform it into a highly attractive, scannable, and HTML-formatted Telegram post.\n\nInput Data:\n\ndeparture_city: The city where flights originate.\n\nflight_data: A JSON array containing objects with location, date_range, stops, and price.\n\nOutput Specification: You must return a valid JSON object with the following structure: (do not add any markdow like \"```json ```\")\n\n\n{\n  \"Telegram\": [\n    \"String 1 (HTML formatted, max 3500 chars)\",\n    \"String 2 (If needed due to length)\"\n  ]\n}\n\nFormatting Guidelines:\n\nHeader: Start with an eye-catching title using the Departure City. (e.g., ✈️ **Cheap Flights from [City]** 🌍)\n\nStructure: Group flights logically. If the list is long (over 10 items), group them by Price Range (e.g., \"🔥 Steal Deals under 45\" vs \"💎 Great Value\") or by Month to make it scannable.\n\nLine Items: Make each flight easy to read. Use specific HTML tags: <b>, <i>, <code>, or <a href> (if links provided).\n\nStyle: Flag Destination | Date | Price\n\nExample: 🇮🇹 Milan — <i>Jan 23-25</i> — <b>40€</b>\n\nFlags: Infer the country flag based on the city name (e.g., Oslo = 🇳🇴, Milan = 🇮🇹).\n\nStops: If stops is \"Nonstop\", use a symbol like ⚡️ or omit it to save space. If there are stops, explicitly state it (e.g., \"1 Stop\").\n\nCharacter Limit: Telegram has a message limit. Monitor the character count.\n\nIf the text exceeds 3500 characters, split the message at a logical section break (e.g., between groups) into the next string in the JSON array.\n\nFooter: Add a brief \"Book Now\" or \"Prices subject to change\" footer.\n\nTask: Processing Logic\nSort the list by price (low to high).\n\nGroup the data if necessary to improve readability.\n\nFormat using emojis and permitted HTML (<b>, <i>, <u>, <s>, <code>, <pre>).\n\nValidate length. Split into multiple array elements if > 3500 chars.\n\nOutput the final JSON."
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 3
    },
    {
      "id": "1121479f-7036-42e7-9d25-c09a4d654888",
      "name": "Split Out Generated Data",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        784,
        304
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "output.Telegram"
      },
      "typeVersion": 1
    },
    {
      "id": "9b67af05-9df4-492b-bce2-ff78db51285c",
      "name": "Avoid Rate Limits",
      "type": "n8n-nodes-base.wait",
      "position": [
        1184,
        272
      ],
      "webhookId": "3e1785fe-7de4-4d1f-9370-ddcac3f27520",
      "parameters": {},
      "typeVersion": 1.1
    },
    {
      "id": "e930941e-30b9-44c8-8f72-b7537eeb39c7",
      "name": "Send Travel List to User",
      "type": "n8n-nodes-base.telegram",
      "position": [
        1408,
        304
      ],
      "webhookId": "5f5e7a33-b100-4727-be6c-f1cd4530622b",
      "parameters": {
        "text": "={{ $('Loop Over Items').item.json[\"output.Telegram\"] }}",
        "chatId": "={{ $('User Sends Message to Bot').first().json.message.chat.id }}",
        "additionalFields": {
          "parse_mode": "HTML"
        }
      },
      "credentials": {
        "telegramApi": {
          "id": "gJnGTVzNOeiM3LHZ",
          "name": "Telegram account"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "4e6d66e5-1774-4c8d-a540-4de1f46a722c",
      "name": "Step 1 Explanation",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1024,
        96
      ],
      "parameters": {
        "color": 7,
        "width": 636,
        "height": 124,
        "content": "### 🕵️ Step 1: Intent Analysis\n\nThe workflow begins by validating user input via Telegram. It identifies if the user is asking for travel deals, checks for a specified departure location, or is simply engaging in general conversation."
      },
      "typeVersion": 1
    },
    {
      "id": "1df5d09d-e4df-4b26-bcc8-68396cb9ba60",
      "name": "Step 2 Explanation",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -224,
        0
      ],
      "parameters": {
        "color": 7,
        "width": 572,
        "height": 124,
        "content": "### ✈️ Step 2: Automated Deal Scrape\n\nOnce a location is validated, BrowserAct automates a browser session on travel sites like Momondo to find the lowest prices. The system immediately notifies the user that the search is underway."
      },
      "typeVersion": 1
    },
    {
      "id": "ac2ca358-88c2-4cf4-8b0a-8fc2bf044565",
      "name": "Step 3 Explanation",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        512,
        144
      ],
      "parameters": {
        "color": 7,
        "width": 1036,
        "height": 108,
        "content": "### 📊 Step 3: AI Curation & Delivery\n\nAn AI agent processes the raw flight data, sorts it by price, and generates a scannable HTML report. Large lists are automatically split to respect Telegram limits and delivered sequentially to the user."
      },
      "typeVersion": 1
    },
    {
      "id": "36e9af59-8ec3-44b1-b60b-dc453e0de9c5",
      "name": "Step 4 Explanation",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -192,
        864
      ],
      "parameters": {
        "color": 7,
        "width": 580,
        "height": 120,
        "content": "### 💬 Step 2-2: Conversational Fallback\n\nIf no link is present in the user's message, this branch engages the user in natural conversation or prompts them to provide a URL for processing."
      },
      "typeVersion": 1
    },
    {
      "id": "b0451bb6-bd86-4b93-b558-ddf256df1297",
      "name": "Documentation",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1440,
        -304
      ],
      "parameters": {
        "width": 380,
        "height": 520,
        "content": "## ⚡ Workflow Overview & Setup\n\n**Summary:** This automation extracts the departure city from Telegram messages and uses BrowserAct to scrape travel sites for the cheapest flight deals, delivering a formatted list back to the user.\n\n### Requirements\n* **Credentials:** Telegram, BrowserAct, OpenRouter (GPT-4), Google Gemini (PaLM).\n* **Mandatory:** BrowserAct API (Template: **Low-Cost Travel Finder**)\n\n### How to Use\n1.  **Credentials:** Set up your Telegram Bot, BrowserAct, and AI model credentials in n8n.\n2.  **BrowserAct Template:** Ensure you have the **Low-Cost Travel Finder** template saved in your BrowserAct account.\n3.  **Search:** Send a message like \"Low-Cost Travel Finder For London\" to your Telegram bot to receive real-time deal alerts.\n\n### Need Help?\n[How to Find Your BrowserAct API Key & Workflow ID](https://docs.browseract.com)\n[How to Connect n8n to BrowserAct](https://docs.browseract.com)\n[How to Use & Customize BrowserAct Templates](https://docs.browseract.com)"
      },
      "typeVersion": 1
    },
    {
      "id": "48ec5d58-e69e-4e36-8d4e-42404e61381a",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1024,
        -304
      ],
      "parameters": {
        "color": 6,
        "width": 624,
        "height": 368,
        "content": "@[youtube](W4EYza-Te5w)"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "1897423c-cd71-4021-89ba-1a42589f4e08",
  "connections": {
    "Google Gemini": {
      "ai_languageModel": [
        [
          {
            "node": "Chatting With User",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [],
        [
          {
            "node": "Avoid Rate Limits",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Model": {
      "ai_languageModel": [
        [
          {
            "node": "Analyze Data & Generate Response",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Structured Output",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Avoid Rate Limits": {
      "main": [
        [
          {
            "node": "Send Travel List to User",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output": {
      "ai_outputParser": [
        [
          {
            "node": "Analyze Data & Generate Response",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Chatting With User": {
      "main": [
        [
          {
            "node": "Answer the User",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate user Input": {
      "main": [
        [
          {
            "node": "Check For Input Type",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check For Input Type": {
      "main": [
        [
          {
            "node": "Process Initialization Alert",
            "type": "main",
            "index": 0
          },
          {
            "node": "Run \"Low-Const Travel Finder\" workflow",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Chatting With User",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Chatting With User",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate user Inputs": {
      "ai_languageModel": [
        [
          {
            "node": "Validate user Input",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Structured Output Parser",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Send Travel List to User": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out Generated Data": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Validate user Input",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "User Sends Message to Bot": {
      "main": [
        [
          {
            "node": "Validate user Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze Data & Generate Response": {
      "main": [
        [
          {
            "node": "Split Out Generated Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run \"Low-Const Travel Finder\" workflow": {
      "main": [
        [
          {
            "node": "Analyze Data & Generate Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}