{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "ae4935b5-9d82-45c6-849e-e4b5d99d9f98",
      "name": "Daily Morning Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "notes": "Triggers every day at 7:50 AM to send weather forecast before work hours",
      "position": [
        -200,
        -300
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 7,
              "triggerAtMinute": 50
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "fd99b9db-969a-4ce2-8823-343c0b847c84",
      "name": "OpenWeather API Request",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Fetches 5-day forecast data with 3-hour intervals for Strassen, Luxembourg",
      "maxTries": 3,
      "position": [
        220,
        -300
      ],
      "parameters": {
        "url": "https://api.openweathermap.org/data/2.5/forecast?q=Strassen&appid=API_KEY&units=metric",
        "options": {
          "timeout": 30000
        }
      },
      "notesInFlow": true,
      "retryOnFail": true,
      "typeVersion": 4.2,
      "waitBetweenTries": 5000
    },
    {
      "id": "0703447c-83b0-4261-a2a6-4063a48e64d0",
      "name": "Weather Data Processor",
      "type": "n8n-nodes-base.code",
      "notes": "Enhanced processor with comprehensive weather analysis, recommendations, and formatting",
      "position": [
        620,
        -300
      ],
      "parameters": {
        "jsCode": "// Enhanced Weather Forecast Processor\n// Processes OpenWeatherMap API data and formats for Telegram\n\nfunction getWeatherEmoji(weatherMain, cloudiness = 0, isNight = false) {\n const emojiMap = {\n   'Clear': isNight ? '\ud83c\udf19' : '\u2600\ufe0f',\n   'Clouds': cloudiness > 75 ? '\u2601\ufe0f' : cloudiness > 25 ? '\u26c5' : '\ud83c\udf24\ufe0f',\n   'Rain': '\ud83c\udf27\ufe0f',\n   'Drizzle': '\ud83c\udf26\ufe0f',\n   'Thunderstorm': '\u26c8\ufe0f',\n   'Snow': '\u2744\ufe0f',\n   'Mist': '\ud83c\udf2b\ufe0f',\n   'Fog': '\ud83c\udf2b\ufe0f',\n   'Haze': '\ud83c\udf2b\ufe0f',\n   'Smoke': '\ud83c\udf2b\ufe0f'\n };\n return emojiMap[weatherMain] || '\ud83c\udf24\ufe0f';\n}\n\nfunction getTemperatureEmoji(temp) {\n if (temp >= 30) return '\ud83d\udd25';\n if (temp >= 25) return '\ud83d\ude0e';\n if (temp >= 20) return '\ud83d\ude0a';\n if (temp >= 15) return '\ud83d\ude10';\n if (temp >= 10) return '\ud83e\udde5';\n if (temp >= 5) return '\ud83e\udd76';\n return '\ud83e\uddca';\n}\n\nfunction getWindInfo(windSpeed, windDir = null) {\n let windEmoji = '\ud83d\udca8';\n let windText = '';\n \n if (windSpeed < 2) windText = 'Calm';\n else if (windSpeed < 6) windText = 'Light breeze';\n else if (windSpeed < 12) windText = 'Moderate breeze';\n else if (windSpeed < 20) windText = 'Strong breeze';\n else windText = 'Very windy';\n \n const windKmh = (windSpeed * 3.6).toFixed(1);\n return `${windEmoji} ${windText} (${windKmh} km/h)`;\n}\n\nfunction getHumidityLevel(humidity) {\n if (humidity >= 80) return '\ud83d\udca7 Very humid';\n if (humidity >= 60) return '\ud83d\udca7 Humid';\n if (humidity >= 40) return '\ud83d\udca7 Comfortable';\n return '\ud83c\udfdc\ufe0f Dry';\n}\n\nfunction getUVAdvice(hour) {\n // Estimate UV based on time (simplified)\n if (hour >= 10 && hour <= 16) return '\u2600\ufe0f Consider sun protection';\n return '';\n}\n\nfunction formatDateTime(dtText) {\n const date = new Date(dtText);\n const timeStr = date.toLocaleTimeString('en-GB', { \n   hour: '2-digit', \n   minute: '2-digit',\n   timeZone: 'Europe/Luxembourg'\n });\n return timeStr;\n}\n\n// Main processing\nconst forecast = items[0].json.list;\nconst cityName = items[0].json.city?.name || 'Strassen';\nconst country = items[0].json.city?.country || 'LU';\nconst timezone = items[0].json.city?.timezone || 0;\n\nconst now = new Date();\nconst today = now.toISOString().split('T')[0];\nconst desiredHours = [\"09:00\", \"12:00\", \"15:00\", \"18:00\", \"21:00\"];\n\nlet summary = '';\nlet headerIcon = '\ud83c\udf24\ufe0f';\nlet dayHighTemp = -999;\nlet dayLowTemp = 999;\nlet totalRain = 0;\nlet avgHumidity = 0;\nlet avgWindSpeed = 0;\nlet weatherCount = 0;\n\n// Process today's forecast\nlet firstMatch = true;\nforecast.forEach(item => {\n const dt = item.dt_txt;\n const [date, time] = dt.split(' ');\n const hour = time.slice(0, 5);\n const hourNum = parseInt(time.slice(0, 2));\n\n if (date === today && desiredHours.includes(hour)) {\n   const temp = Math.round(item.main.temp);\n   const feelsLike = Math.round(item.main.feels_like);\n   const humidity = item.main.humidity;\n   const windSpeed = item.wind?.speed || 0;\n   const rain = item.rain?.['3h'] || 0;\n   const clouds = item.clouds?.all || 0;\n   const weatherMain = item.weather?.[0]?.main || '';\n   const weatherDesc = item.weather?.[0]?.description || '';\n   const isNight = hourNum < 6 || hourNum > 20;\n\n   // Track daily stats\n   dayHighTemp = Math.max(dayHighTemp, temp);\n   dayLowTemp = Math.min(dayLowTemp, temp);\n   totalRain += rain;\n   avgHumidity += humidity;\n   avgWindSpeed += windSpeed;\n   weatherCount++;\n\n   // Set header icon based on first matching hour\n   if (firstMatch && weatherMain) {\n     headerIcon = getWeatherEmoji(weatherMain, clouds, isNight);\n     firstMatch = false;\n   }\n\n   const tempEmoji = getTemperatureEmoji(temp);\n   const weatherEmoji = getWeatherEmoji(weatherMain, clouds, isNight);\n   const feelsLikeText = Math.abs(temp - feelsLike) > 2 ? ` (feels ${feelsLike}\u00b0C)` : '';\n   const rainText = rain > 0 ? `, \ud83c\udf27\ufe0f ${rain}mm` : '';\n   const windText = windSpeed > 3 ? `, ${getWindInfo(windSpeed).split(' ')[0]} ${(windSpeed * 3.6).toFixed(0)} km/h` : '';\n   const humidityText = humidity > 80 || humidity < 30 ? `, \ud83d\udca7 ${humidity}%` : '';\n   const uvText = getUVAdvice(hourNum);\n\n   summary += `\ud83d\udd52 <b>${hour}</b> ${weatherEmoji} ${temp}\u00b0C${feelsLikeText}${rainText}${windText}${humidityText}\\n`;\n   if (uvText) summary += `    ${uvText}\\n`;\n }\n});\n\n// Calculate averages\nif (weatherCount > 0) {\n avgHumidity = Math.round(avgHumidity / weatherCount);\n avgWindSpeed = avgWindSpeed / weatherCount;\n}\n\n// Build comprehensive message\nlet message = `${headerIcon} <b>Weather Forecast for ${cityName}, ${country}</b>\\n`;\nmessage += `\ud83d\udcc5 ${now.toLocaleDateString('en-GB', { \n weekday: 'long', \n year: 'numeric', \n month: 'long', \n day: 'numeric',\n timeZone: 'Europe/Luxembourg'\n})}\\n\\n`;\n\n// Daily overview\nmessage += `<b>\ud83d\udcca Daily Overview</b>\\n`;\nif (dayHighTemp > -999 && dayLowTemp < 999) {\n message += `\ud83c\udf21\ufe0f Range: ${dayLowTemp}\u00b0C - ${dayHighTemp}\u00b0C\\n`;\n}\nif (totalRain > 0) {\n message += `\ud83c\udf27\ufe0f Expected rain: ${totalRain.toFixed(1)}mm\\n`;\n}\nif (avgWindSpeed > 2) {\n message += `${getWindInfo(avgWindSpeed)}\\n`;\n}\nmessage += `${getHumidityLevel(avgHumidity)} (${avgHumidity}%)\\n\\n`;\n\n// Hourly forecast\nmessage += `<b>\u23f0 Hourly Forecast</b>\\n`;\nmessage += summary;\n\n// Smart recommendations - only show section if there are recommendations\nlet recommendations = '';\nif (totalRain > 0) {\n recommendations += `\u2602\ufe0f Bring an umbrella or raincoat\\n`;\n}\nif (dayLowTemp < 10) {\n recommendations += `\ud83e\udde5 Dress warmly (below 10\u00b0C expected)\\n`;\n}\nif (dayHighTemp > 25) {\n recommendations += `\ud83d\udca7 Stay hydrated and consider sun protection\\n`;\n}\nif (avgWindSpeed > 15) {\n recommendations += `\ud83d\udca8 Windy conditions - secure loose items\\n`;\n}\nif (avgHumidity > 80) {\n recommendations += `\ud83d\udca6 High humidity - may feel warmer than actual temperature\\n`;\n}\n\n// Only add recommendations section if there are any recommendations\nif (recommendations) {\n message += `\\n<b>\ud83d\udca1 Recommendations</b>\\n`;\n message += recommendations;\n}\n\n// Footer with data source and time\nconst updateTime = now.toLocaleTimeString('en-GB', {\n hour: '2-digit',\n minute: '2-digit',\n timeZone: 'Europe/Luxembourg'\n});\nmessage += `\\n<i>\ud83d\udce1 Data from OpenWeatherMap | Updated: ${updateTime} CET</i>`;\n\nreturn [{ json: { message: message } }];"
      },
      "notesInFlow": true,
      "typeVersion": 2
    },
    {
      "id": "c620d227-2228-4473-a83f-7de21b02ba8c",
      "name": "Send Weather Update",
      "type": "n8n-nodes-base.telegram",
      "notes": "Sends formatted weather forecast to Telegram with HTML formatting",
      "position": [
        1000,
        -300
      ],
      "parameters": {
        "text": "={{$json[\"message\"]}}",
        "chatId": "123456789",
        "additionalFields": {
          "parse_mode": "HTML",
          "disable_web_page_preview": true
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "fad24450-beef-4da4-9f56-e6659e06d06d",
      "name": "Schedule Setup",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -240,
        -120
      ],
      "parameters": {
        "width": 300,
        "height": 520,
        "content": "### \u23f0 Schedule Configuration\n\n**Current Settings:**\n- **Time**: 7:50 AM daily\n- **Timezone**: System timezone (usually UTC in n8n cloud)\n\n**\ud83d\udca1 Tips:**\n- For Luxembourg time, consider UTC offset\n- Winter: UTC+1 (CET)\n- Summer: UTC+2 (CEST)\n- Adjust trigger time accordingly\n\n**\ud83d\udd27 To Change:**\n1. Click on Schedule Trigger node\n2. Modify `triggerAtHour` and `triggerAtMinute`\n3. Add multiple schedules if needed"
      },
      "typeVersion": 1
    },
    {
      "id": "153eed2e-091b-4045-80c9-4c12fe11beaf",
      "name": "API Configuration",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        120,
        -120
      ],
      "parameters": {
        "color": 3,
        "width": 340,
        "height": 520,
        "content": "### \ud83c\udf10 OpenWeatherMap API\n\n**\ud83d\udd11 API Key Setup:**\n1. Sign up at [OpenWeatherMap](https://openweathermap.org/api)\n2. Get your free API key\n3. Replace the key in the URL parameter\n\n**\ud83d\udccd Location Settings:**\n- Current: `q=Strassen` (Luxembourg)\n- Format: `q=CityName,CountryCode`\n- Examples: `q=Paris,FR` or `q=London,GB`\n\n**\ud83d\udcca API Features:**\n- 5-day forecast with 3-hour intervals\n- Metric units (Celsius, km/h)\n- Includes: temp, humidity, wind, rain, clouds\n\n**\u26a0\ufe0f Rate Limits:**\n- Free tier: 60 calls/minute, 1000 calls/day\n- This workflow uses 1 call per day"
      },
      "typeVersion": 1
    },
    {
      "id": "764cbb6e-db67-4128-8f55-936403f4c07f",
      "name": "Processing Features",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        500,
        -120
      ],
      "parameters": {
        "color": 5,
        "width": 340,
        "height": 520,
        "content": "### \ud83d\udcbb Code Node Features\n\n**\ud83d\udd27 Enhanced Processing:**\n- **Smart Emojis**: Weather + temperature-based icons\n- **Comprehensive Data**: Feels-like temp, wind, humidity\n- **Daily Stats**: High/low temps, total rainfall\n- **Time-Aware**: Night/day emoji variations\n- **Recommendations**: Context-aware advice\n\n**\ud83d\udcca Calculated Metrics:**\n- Temperature ranges and averages\n- Total expected rainfall\n- Wind speed in km/h\n- Humidity comfort levels\n- UV protection recommendations\n\n**\ud83c\udfa8 Formatting:**\n- HTML bold/italic formatting\n- Structured sections with headers\n- Emoji-based visual indicators\n- Timezone-aware timestamps"
      },
      "typeVersion": 1
    },
    {
      "id": "56f717ee-2231-4ca7-b596-a32e9defd1f6",
      "name": "Telegram Setup",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        900,
        -120
      ],
      "parameters": {
        "width": 340,
        "height": 520,
        "content": "### \ud83d\udcf1 Telegram Setup\n\n**\ud83e\udd16 Bot Creation:**\n1. Message [@BotFather](https://t.me/BotFather) on Telegram\n2. Send `/newbot` command\n3. Choose bot name and username\n4. Copy the API token\n\n**\ud83c\udd94 Chat ID Setup:**\n1. Add bot to your group/channel OR\n2. Start private chat with bot\n3. Send a message to the bot\n4. Visit: `https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getUpdates`\n5. Find your chat ID in the response\n6. Update the `chatId` parameter\n\n**\u2699\ufe0f Message Settings:**\n- **HTML parsing** enabled for formatting\n- **Web preview** disabled to keep messages clean\n- Supports emojis and rich text"
      },
      "typeVersion": 1
    },
    {
      "id": "ae5a111b-8be6-4260-abb4-66093c757e35",
      "name": "Setup Checklist",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1280,
        -560
      ],
      "parameters": {
        "color": 7,
        "width": 400,
        "height": 640,
        "content": "### \ud83d\ude80 Quick Setup Checklist\n\n**\u2705 Essential Steps:**\n1. **OpenWeatherMap API Key**\n   - Sign up and get free API key\n   - Replace in HTTP Request URL\n\n2. **Telegram Bot**\n   - Create bot via @BotFather\n   - Add bot token to credentials\n   - Update chatId in Telegram node\n\n3. **Test the Workflow**\n   - Execute manually first\n   - Check Telegram for message\n   - Activate for daily runs\n\n**\ud83d\udd27 Optional Customizations:**\n- Change city in HTTP URL\n- Adjust schedule timing\n- Modify forecast hours in code\n- Add more weather parameters\n\n**\ud83d\udccb Monitoring:**\n- Check execution history\n- Monitor API usage\n- Verify daily messages"
      },
      "typeVersion": 1
    },
    {
      "id": "28c828bc-5b66-4e59-81f7-d941088cbbc6",
      "name": "Weather Bot Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -260,
        -900
      ],
      "parameters": {
        "color": 6,
        "width": 900,
        "height": 500,
        "content": "## \ud83c\udf24\ufe0f Daily Weather Forecast Bot\n\n### \ud83d\udccb What It Does\nFetches detailed weather forecast from OpenWeatherMap API for **Strassen, Luxembourg** and sends a comprehensive daily weather summary to Telegram every morning at **7:50 AM**.\n\n---\n### \ud83d\udd0d Features\n- **\ud83d\udcca Daily Overview**: Temperature range, total rainfall, wind conditions\n- **\u23f0 Hourly Forecast**: Weather at key times (9AM, 12PM, 3PM, 6PM, 9PM)\n- **\ud83c\udf21\ufe0f Smart Emojis**: Weather-based icons and temperature indicators\n- **\ud83d\udca1 Recommendations**: Contextual advice (umbrella, warm clothes, sun protection)\n- **\ud83c\udf2a\ufe0f Enhanced Details**: Feels-like temperature, humidity, wind speed, UV warnings\n- **\ud83d\udcf1 Rich Formatting**: HTML-formatted messages for better readability\n\n---\n### \ud83d\udee0\ufe0f Technical Details\n- **API**: OpenWeatherMap 5-day forecast (3-hour intervals)\n- **Location**: Strassen, Luxembourg (configurable in HTTP node)\n- **Timezone**: Europe/Luxembourg (CET/CEST)\n- **Format**: HTML with emojis and formatting\n- **Error Handling**: 3 retry attempts with 5-second delays\n\n---\n### \u2699\ufe0f Configuration\n1. **API Key**: Replace `dsqdsqdsqdsqsdq` with your OpenWeatherMap API key\n2. **Location**: Change `q=Strassen` in the HTTP request URL for different cities\n3. **Schedule**: Modify trigger time in Schedule node\n4. **Telegram**: Update `chatId` with your chat/group ID\n\n---\n\u2705 **Ready to use!** Just update the API key and chat ID."
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Daily Morning Trigger": {
      "main": [
        [
          {
            "node": "OpenWeather API Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Weather Data Processor": {
      "main": [
        [
          {
            "node": "Send Weather Update",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenWeather API Request": {
      "main": [
        [
          {
            "node": "Weather Data Processor",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}