{
  "nodes": [
    {
      "id": "7df92b27-05c7-4c4a-9b2f-76883f0d8974",
      "name": "If1",
      "type": "n8n-nodes-base.if",
      "position": [
        624,
        -64
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "8e217cc9-d842-4b92-a62b-b79e99a059df",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.sequence }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "40fe3671-d5ed-49fd-a342-f89df42f5657",
      "name": "Identify trips",
      "type": "n8n-nodes-base.code",
      "position": [
        400,
        -64
      ],
      "parameters": {
        "jsCode": "const travelKeywords = ['trip', 'travel', 'vacation', 'flight', 'holiday'];\n\nreturn items.filter(item => {\n  const title = (item.json.summary || '').toLowerCase();\n  const description = (item.json.description || '').toLowerCase();\n\n  return travelKeywords.some(keyword => title.includes(keyword) || description.includes(keyword));\n});\n"
      },
      "typeVersion": 2
    },
    {
      "id": "41df49be-15c4-4c83-8c69-6c8cc109ac34",
      "name": "Extract locations",
      "type": "n8n-nodes-base.code",
      "position": [
        848,
        -64
      ],
      "parameters": {
        "jsCode": "return items.map(item => {\n  const startDate = item.json.start.dateTime || item.json.start.date;\n  const endDate = item.json.end.dateTime || item.json.end.date;\n\n  // Initialize location variable\n  let location = null;\n\n  // Extract destination from summary first (e.g. \"Flight to Istanbul\")\n  const summary = (item.json.summary || '').toLowerCase();\n  const matchSummary = summary.match(/to ([a-zA-Z\\s,]+)/);\n  if (matchSummary && matchSummary[1]) {\n    location = matchSummary[1].trim();\n  }\n\n  // If no destination yet, try description (e.g. \"Flight from Mauritius (port Louis) to Istanbul\")\n  if (!location) {\n    const description = (item.json.description || '').toLowerCase();\n    // Match pattern after \"to \"\n    const matchDescription = description.match(/to ([a-zA-Z\\s,]+)/);\n    if (matchDescription && matchDescription[1]) {\n      location = matchDescription[1].trim();\n    }\n  }\n\n  // Only use the location field if above patterns fail or if location is not a \"from\" location\n  if (!location && item.json.location) {\n    const locLower = item.json.location.toLowerCase();\n    if (!locLower.startsWith('from ')) {  // Avoid departure locations starting with \"from\"\n      location = item.json.location;\n    }\n  }\n\n  // Final fallback if no location found\n  if (!location) {\n    location = null; // or 'YOUR_DEFAULT_TRIP_LOCATION'\n  }\n\n  return {\n    json: {\n      startDate,\n      endDate,\n      location\n    }\n  };\n});\n"
      },
      "typeVersion": 2
    },
    {
      "id": "1c3d2277-df1f-4178-bdba-bff40a182c2d",
      "name": "Wait",
      "type": "n8n-nodes-base.wait",
      "position": [
        1104,
        320
      ],
      "parameters": {
        "resume": "specificTime",
        "dateTime": "={{ new Date(new Date($('Extract locations').item.json.startDate).getTime() - 24 * 60 * 60 * 1000).toISOString() }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "0d3f1ec2-2377-46e3-84bc-c303adddb825",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -448,
        -400
      ],
      "parameters": {
        "width": 560,
        "height": 752,
        "content": "## How it works\nWhen a calendar event is created or updated, identified as trip, provides timely weather alerts and forecasts tailored to travel dates and locations.\n\n## Step-by-step\n\n\ud83d\udcc5 **Google Calendar Triggers** (Event Created/Updated): \nThe workflow starts immediately upon creation or update of any calendar event, enabling real-time detection of new or changed travel plans.\n\n\u2708 **Identify Trips & Extract Locations**: \nFilters these calendar events to detect travel-related trips by matching keywords such as \"trip,\" \"flight,\" or \"vacation\" in titles or descriptions and extract start and end dates & the trip destination.\n\n\ud83c\udf10 **Get Forecast & send it**: \nUsing Visual Crossing API (1000 free daily requests) fetches the detailed weather forecast and alert data for the trip location then formats the raw weather data into a readable summary \ud83c\udf24\ufe0f\ud83c\udf2a\ud83c\udf00, and eventual severe weather alerts.\n\n\ud83d\udcf2 \ud83d\udce7 **Send Forecast**: \nSends the forecast summary with alerts via Telegram to keep the user informed instantly.\n\n\u231b**One day before the trip**: \nPauses the workflow until exactly one day before the trip start date, ensuring a timely second fetch when more accurate or updated weather data is available and the updated forecast is sent.\n\n## Optional\nYou can replace the Telegram node with email, WhatsApp, Slack, SMS notifications, or add multiple notification nodes to receive them across all desired channels."
      },
      "typeVersion": 1
    },
    {
      "id": "79b81985-724d-4208-a225-25ecde0851e7",
      "name": "Event created",
      "type": "n8n-nodes-base.googleCalendarTrigger",
      "position": [
        176,
        -160
      ],
      "parameters": {
        "options": {},
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "triggerOn": "eventCreated",
        "calendarId": {
          "__rl": true,
          "mode": "list",
          "value": "user@example.com",
          "cachedResultName": "user@example.com"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "38413099-7d19-4c06-bec7-a2d542607474",
      "name": "Event updated",
      "type": "n8n-nodes-base.googleCalendarTrigger",
      "position": [
        176,
        32
      ],
      "parameters": {
        "options": {},
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "triggerOn": "eventUpdated",
        "calendarId": {
          "__rl": true,
          "mode": "list",
          "value": "user@example.com",
          "cachedResultName": "user@example.com"
        }
      },
      "credentials": {},
      "typeVersion": 1
    },
    {
      "id": "1cac0052-349b-4ae2-8c4e-b1a63bd834f4",
      "name": "Send Forecast",
      "type": "n8n-nodes-base.telegram",
      "position": [
        1808,
        -64
      ],
      "parameters": {
        "operation": "send"
      },
      "notesInFlow": [
        {
          "note": "Sends the trip weather forecast summary to user via Telegram."
        }
      ],
      "typeVersion": 1
    },
    {
      "id": "bb15bccd-f857-4de6-907f-7a48457a125f",
      "name": "Build interogation URL",
      "type": "n8n-nodes-base.code",
      "position": [
        1072,
        -64
      ],
      "parameters": {
        "jsCode": "const location = encodeURIComponent($json.location);\nconst startDate = $json.startDate.split('T')[0]; // date only\nconst endDate = $json.endDate.split('T')[0]; // date only\nconst apiKey = '[your API]';\n\nconst url = `https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/${location}/${startDate}/${endDate}?unitGroup=metric&key=YOUR_TOKEN_HERE&include=days,alerts`;\n\nreturn [{ json: { url } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "e29e2ff1-46fd-44c5-ab8f-02d345586e41",
      "name": "Get updated Destination Weather Forecast",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "One day before the trip",
      "position": [
        1328,
        320
      ],
      "parameters": {
        "url": "={{ $json.url }}",
        "options": {}
      },
      "notesInFlow": [
        {
          "note": "Fetches weather forecast and alerts for trip location and dates from Visual Crossing."
        }
      ],
      "typeVersion": 1
    },
    {
      "id": "c69a55de-bbf3-4921-bd89-24d4b8826111",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        368,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 608,
        "height": 240,
        "content": "## Identify \n**if** the event is a trip\n**Then** > extracting destination and period"
      },
      "typeVersion": 1
    },
    {
      "id": "50def3d9-67bc-4f5c-b2a4-18f4235fffec",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1024,
        208
      ],
      "parameters": {
        "color": 6,
        "width": 448,
        "height": 288,
        "content": "## Update the forecast one day before the trip\nWait one day before the trip and request again the forecast"
      },
      "typeVersion": 1
    },
    {
      "id": "8aec9d53-2af5-4e3f-87a2-f0f67a2497e3",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1008,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 672,
        "content": "## Get Forecast\n"
      },
      "typeVersion": 1
    },
    {
      "id": "57b375da-2354-4b04-a6ee-b510bed576d0",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1536,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 256,
        "content": "## Send notification\nMake sure Telegram credentials and chat ID are set in the node."
      },
      "typeVersion": 1
    },
    {
      "id": "b4b8e95d-3ea8-4ea2-ab88-a889a74d6818",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        128,
        -272
      ],
      "parameters": {
        "color": 7,
        "width": 192,
        "height": 464,
        "content": "## Trigger:\n**EVENT**\nCreated or updated"
      },
      "typeVersion": 1
    },
    {
      "id": "5c65c141-7527-43b9-8e85-bd8f1291b54e",
      "name": "Get Destination Weather forecast",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "At the event creation/update",
      "position": [
        1296,
        -64
      ],
      "parameters": {
        "url": "={{ $json.url }}",
        "options": {}
      },
      "notesInFlow": [
        {
          "note": "Fetches weather forecast and alerts for trip location and dates from Visual Crossing."
        }
      ],
      "typeVersion": 1
    },
    {
      "id": "b92af965-eecf-4019-b5cf-ca38e8ee9928",
      "name": "Format message",
      "type": "n8n-nodes-base.code",
      "position": [
        1584,
        -64
      ],
      "parameters": {
        "jsCode": "return items.map(item => {\n  let forecastSummary = `Weather forecast for your trip to ${item.json.address || 'destination'} from ${item.json.days[0].datetime} to ${item.json.days[item.json.days.length - 1].datetime}:\\n`;\n\n  item.json.days.forEach(day => {\n    const precipprob = day.precipprob !== undefined ? `, Precip Prob: ${day.precipprob}%` : '';\n    const preciptype = day.preciptype ? `, Precip Type: ${day.preciptype.join(\", \")}` : '';\n    const conditions = day.conditions ? `, Conditions: ${day.conditions}` : '';\n    const description = day.description ? `,  ${day.description}` : '';\n    forecastSummary += `${day.datetime}: Max ${day.tempmax}\u00b0C, Min ${day.tempmin}\u00b0C${precipprob}${preciptype}${conditions}${description}\\n`;\n  });\n\n  if (item.json.alerts && item.json.alerts.length > 0) {\n    forecastSummary += `\\nALERTS:\\n`;\n    item.json.alerts.forEach(alert => {\n      forecastSummary += `${alert.description}\\n`;\n    });\n  }\n\n  return {\n    json: { forecastSummary }\n  };\n});\n"
      },
      "typeVersion": 2
    }
  ],
  "connections": {
    "If1": {
      "main": [
        [
          {
            "node": "Extract locations",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait": {
      "main": [
        [
          {
            "node": "Get updated Destination Weather Forecast",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Event created": {
      "main": [
        [
          {
            "node": "Identify trips",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Event updated": {
      "main": [
        [
          {
            "node": "Identify trips",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format message": {
      "main": [
        [
          {
            "node": "Send Forecast",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Identify trips": {
      "main": [
        [
          {
            "node": "If1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract locations": {
      "main": [
        [
          {
            "node": "Build interogation URL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build interogation URL": {
      "main": [
        [
          {
            "node": "Wait",
            "type": "main",
            "index": 0
          },
          {
            "node": "Get Destination Weather forecast",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Destination Weather forecast": {
      "main": [
        [
          {
            "node": "Format message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get updated Destination Weather Forecast": {
      "main": [
        [
          {
            "node": "Format message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}