8
n8n 中文网amn8n.com

实时航班票价追踪器 – 通过短信和电子邮件发送即时降价提醒

中级

这是一个Miscellaneous领域的自动化工作流,包含 14 个节点。主要使用 If, Code, Cron, Gmail, Function 等节点。 实时航班票价追踪器(Aviation Stack API)– 通过Gmail和Telegram发送提醒

前置要求
  • Google 账号和 Gmail API 凭证
  • Telegram Bot Token
  • 可能需要目标 API 的认证凭证
  • Google Sheets API 凭证

分类

工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "4vQkJTdJPHnD0XUa",
  "meta": {
    "instanceId": "dd69efaf8212c74ad206700d104739d3329588a6f3f8381a46a481f34c9cc281",
    "templateCredsSetupCompleted": true
  },
  "name": "实时航班票价追踪器 – 通过短信和电子邮件发送即时价格下降提醒",
  "tags": [],
  "nodes": [
    {
      "id": "aa5f054a-6239-46c1-8ab3-492796e1f1c1",
      "name": "计划触发器",
      "type": "n8n-nodes-base.cron",
      "position": [
        -1440,
        380
      ],
      "parameters": {
        "triggerTimes": {
          "item": [
            {
              "mode": "everyX",
              "unit": "minutes",
              "value": 15
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "8d308fdf-5c40-46ab-b695-a7c95710ada2",
      "name": "获取航班数据",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -1220,
        380
      ],
      "parameters": {
        "url": "https://api.aviationstack.com/v1/flights",
        "options": {},
        "sendQuery": true,
        "authentication": "genericCredentialType",
        "genericAuthType": "httpQueryAuth",
        "queryParameters": {
          "parameters": [
            {
              "name": "access_key",
              "value": "0987c6845c09876yt"
            },
            {
              "name": "dep_iata",
              "value": "JFK"
            },
            {
              "name": "arr_iata",
              "value": "LAX"
            },
            {
              "name": "limit",
              "value": "10"
            }
          ]
        }
      },
      "credentials": {
        "httpQueryAuth": {
          "id": "xA2e6hA40RZ8bzrI",
          "name": "Query Auth account - test"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "f4675a74-c27d-4b8b-bb8e-624687125b4a",
      "name": "处理航班数据",
      "type": "n8n-nodes-base.function",
      "position": [
        -1000,
        380
      ],
      "parameters": {
        "functionCode": "// Process flight data and check for fare changes\nconst currentFlights = items[0].json.data || [];\nconst processedFlights = [];\n\nfor (const flight of currentFlights) {\n  if (flight.flight_status === 'scheduled' && flight.departure) {\n    // Extract fare information (you may need to adapt based on API response)\n    const flightInfo = {\n      flight_number: flight.flight.iata,\n      airline: flight.airline.name,\n      departure: flight.departure.airport,\n      arrival: flight.arrival.airport,\n      departure_time: flight.departure.scheduled,\n      arrival_time: flight.arrival.scheduled,\n      // Mock fare data - replace with actual fare from your chosen API\n      current_fare: Math.floor(Math.random() * 500) + 200,\n      route: `${flight.departure.iata}-${flight.arrival.iata}`,\n      timestamp: new Date().toISOString()\n    };\n    \n    processedFlights.push(flightInfo);\n  }\n}\n\nreturn processedFlights.map(flight => ({ json: flight }));"
      },
      "executeOnce": true,
      "typeVersion": 1
    },
    {
      "id": "87b6bde3-fe0a-47a2-9410-ddb8d78cbf77",
      "name": "检查是否需要提醒",
      "type": "n8n-nodes-base.if",
      "position": [
        -340,
        380
      ],
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{ $json.fare_change }}",
              "operation": "notEqual"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "11309f53-cdc0-4cf9-b060-d55ca3e9ca74",
      "name": "格式化提醒消息",
      "type": "n8n-nodes-base.function",
      "position": [
        -80,
        340
      ],
      "parameters": {
        "functionCode": "// Format alert message\nconst flight = items[0].json;\nconst alertType = flight.alert_type;\nconst emoji = alertType === 'PRICE_DROP' ? '📉' : '📈';\nconst alertColor = alertType === 'PRICE_DROP' ? 'good' : 'warning';\n\nconst message = {\n  email: {\n    subject: `${emoji} Flight Fare Alert: ${flight.flight_number}`,\n    html: `\n      <h2>${emoji} Fare ${alertType.replace('_', ' ')} Alert</h2>\n      <p><strong>Flight:</strong> ${flight.flight_number} (${flight.airline})</p>\n      <p><strong>Route:</strong> ${flight.departure} → ${flight.arrival}</p>\n      <p><strong>Departure:</strong> ${new Date(flight.departure_time).toLocaleString()}</p>\n      <p><strong>Previous Fare:</strong> $${flight.previous_fare}</p>\n      <p><strong>Current Fare:</strong> $${flight.current_fare}</p>\n      <p><strong>Change:</strong> $${flight.fare_change} (${flight.percentage_change}%)</p>\n      <p style=\"color: ${alertType === 'PRICE_DROP' ? 'green' : 'red'};\"><strong>Recommendation:</strong> ${alertType === 'PRICE_DROP' ? 'Consider booking now!' : 'Price increased - monitor for drops'}</p>\n    `\n  },\n  slack: {\n    text: `${emoji} Flight Fare Alert`,\n    attachments: [\n      {\n        color: alertColor,\n        fields: [\n          {\n            title: \"Flight\",\n            value: `${flight.flight_number} (${flight.airline})`,\n            short: true\n          },\n          {\n            title: \"Route\",\n            value: `${flight.departure} → ${flight.arrival}`,\n            short: true\n          },\n          {\n            title: \"Previous Fare\",\n            value: `$${flight.previous_fare}`,\n            short: true\n          },\n          {\n            title: \"Current Fare\",\n            value: `$${flight.current_fare}`,\n            short: true\n          },\n          {\n            title: \"Change\",\n            value: `$${flight.fare_change} (${flight.percentage_change}%)`,\n            short: false\n          }\n        ]\n      }\n    ]\n  },\n  sms: `${emoji} FARE ALERT: ${flight.flight_number} ${flight.departure}-${flight.arrival} fare changed from $${flight.previous_fare} to $${flight.current_fare} (${flight.percentage_change}%)`\n};\n\nreturn [{ json: { ...flight, formatted_messages: message } }];"
      },
      "typeVersion": 1
    },
    {
      "id": "026afcab-023a-4ef4-9573-28ba32edc01c",
      "name": "记录提醒活动",
      "type": "n8n-nodes-base.function",
      "position": [
        460,
        400
      ],
      "parameters": {
        "functionCode": "// Log alert activity\nconst alert = items[0].json;\n\nconst logEntry = {\n  timestamp: new Date().toISOString(),\n  flight_number: alert.flight_number,\n  route: alert.route,\n  alert_type: alert.alert_type,\n  fare_change: alert.fare_change,\n  percentage_change: alert.percentage_change,\n  notification_sent: true\n};\n\nconsole.log('Fare Alert Sent:', logEntry);\n\nreturn [{ json: logEntry }];"
      },
      "typeVersion": 1
    },
    {
      "id": "374b1e8e-15b3-4109-9c46-01406c0e4ca5",
      "name": "代码",
      "type": "n8n-nodes-base.code",
      "position": [
        -580,
        380
      ],
      "parameters": {
        "jsCode": "// Get current flight data from previous node (Process Flight Data)\nconst currentFlightsItems = $('Process Flight Data').all();\n\n// Get stored fare data from Google Sheets node\nconst sheetsData = $('Previous flight data').all();\n\nconsole.log('Current flights items:', currentFlightsItems.length);\nconsole.log('Sheets data items:', sheetsData.length);\n\n// Build lookup object from Google Sheets data (remove duplicates)\nconst storedFares = {};\nconst uniqueSheetRows = new Map();\n\n// Remove duplicates from sheets data first\nfor (const row of sheetsData) {\n  const rowData = row.json;\n  if (rowData.flight_number && rowData.route) {\n    const routeKey = `${rowData.flight_number}_${rowData.route}`;\n    \n    // Keep only the first occurrence of each flight\n    if (!uniqueSheetRows.has(routeKey)) {\n      uniqueSheetRows.set(routeKey, rowData);\n      storedFares[routeKey] = parseFloat(rowData.current_fare || 0);\n    }\n  }\n}\n\nconsole.log('Stored fares lookup:', Object.keys(storedFares));\n\nconst alertFlights = [];\nconst fareUpdates = [];\n\n// Process each current flight item\nfor (const flightItem of currentFlightsItems) {\n  const flightData = flightItem.json;\n  \n  if (!flightData.flight_number || !flightData.route) {\n    console.log('Skipping invalid flight data:', flightData);\n    continue;\n  }\n  \n  const routeKey = `${flightData.flight_number}_${flightData.route}`;\n  const currentFare = parseFloat(flightData.current_fare || 0);\n  const previousFare = storedFares[routeKey];\n  \n  console.log(`Processing ${routeKey}: Current=${currentFare}, Previous=${previousFare}`);\n  \n  if (previousFare && previousFare !== currentFare && currentFare > 0) {\n    const fareChange = currentFare - previousFare;\n    const percentageChange = (fareChange / previousFare) * 100;\n    \n    console.log(`${routeKey}: Change=${fareChange}, Percentage=${percentageChange.toFixed(2)}%`);\n    \n    // Alert if fare decreased by 10% or more, or increased by 15% or more\n    if (percentageChange <= -10 || percentageChange >= 15) {\n      alertFlights.push({\n        ...flightData,\n        previous_fare: previousFare,\n        fare_change: fareChange,\n        percentage_change: Math.round(percentageChange * 100) / 100,\n        alert_type: percentageChange < 0 ? 'PRICE_DROP' : 'PRICE_INCREASE',\n        alert_message: percentageChange < 0 \n          ? `🔥 PRICE DROP: ${Math.abs(percentageChange).toFixed(1)}% decrease!` \n          : `⚠️ PRICE INCREASE: ${percentageChange.toFixed(1)}% increase!`\n      });\n      \n      console.log(`ALERT TRIGGERED for ${routeKey}: ${percentageChange < 0 ? 'DROP' : 'INCREASE'} of ${Math.abs(percentageChange).toFixed(2)}%`);\n    }\n  } else if (!previousFare) {\n    console.log(`New flight detected: ${routeKey}`);\n  } else if (currentFare <= 0) {\n    console.log(`Invalid current fare for ${routeKey}: ${currentFare}`);\n  }\n  \n  // Prepare fare updates for Google Sheets (to update stored fares)\n  if (currentFare > 0) {\n    fareUpdates.push({\n      row_number: uniqueSheetRows.get(routeKey)?.row_number || null,\n      flight_number: flightData.flight_number,\n      airline: flightData.airline || 'Unknown',\n      departure: flightData.departure || 'Unknown',\n      arrival: flightData.arrival || 'Unknown',\n      departure_time: flightData.departure_time || '',\n      arrival_time: flightData.arrival_time || '',\n      current_fare: currentFare,\n      route: flightData.route,\n      timestamp: flightData.timestamp || new Date().toISOString(),\n      last_updated: new Date().toISOString()\n    });\n  }\n}\n\nconsole.log(`Total alerts generated: ${alertFlights.length}`);\nif (alertFlights.length > 0) {\n  console.log(`Alerts for flights:`, alertFlights.map(f => f.flight_number));\n}\n\n// Store fare updates in node context for the Google Sheets update node\n$node[\"Code\"].context = { fareUpdates };\n\n// If no alerts, return empty array but still process\nif (alertFlights.length === 0) {\n  console.log('No fare alerts triggered');\n  return [];\n}\n\n// Return alert flights for notification processing\nreturn alertFlights.map(flight => ({ json: flight }));\n"
      },
      "typeVersion": 2,
      "alwaysOutputData": true
    },
    {
      "id": "43084297-fd73-45ae-83b8-3e31db490777",
      "name": "Telegram",
      "type": "n8n-nodes-base.telegram",
      "onError": "continueRegularOutput",
      "position": [
        180,
        480
      ],
      "webhookId": "30b417c4-a6ea-42ec-8218-c40248df36b8",
      "parameters": {
        "text": "={{ $json.formatted_messages.sms }}",
        "chatId": "123SSHSJNASB",
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "id": "3ubbGgZx2YzylQZu",
          "name": "Telegram account - test"
        }
      },
      "executeOnce": true,
      "retryOnFail": false,
      "typeVersion": 1.2
    },
    {
      "id": "b2048186-6d7a-4b76-9849-bc694592ce39",
      "name": "工作流概述",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1440,
        60
      ],
      "parameters": {
        "width": 700,
        "height": 200,
        "content": "## ✈️ 航班票价追踪与提醒系统"
      },
      "typeVersion": 1
    },
    {
      "id": "06f7bb02-8151-43f9-b5c2-9a75555d0199",
      "name": "数据摄取与处理",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1400,
        560
      ],
      "parameters": {
        "color": 4,
        "width": 600,
        "height": 300,
        "content": "### 📊 数据摄取与处理"
      },
      "typeVersion": 1
    },
    {
      "id": "5b431dd1-1832-4a03-9df0-880dc17d1924",
      "name": "票价比较与提醒逻辑",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -700,
        20
      ],
      "parameters": {
        "color": 3,
        "width": 600,
        "height": 280,
        "content": "### 🔍 票价比较与提醒逻辑"
      },
      "typeVersion": 1
    },
    {
      "id": "a29a641a-f204-463f-8764-91bfaf753f2d",
      "name": "Notification & Logging",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        0
      ],
      "parameters": {
        "color": 5,
        "width": 600,
        "height": 280,
        "content": "### 🔔 Notification & Logging\n\n1.  **Format Alert Message**: Prepares the alert message in different formats (email HTML, SMS) using a Function node, based on the `alert_type` (price drop/increase).\n2.  **Gmail**: Sends an email notification with the formatted alert.\n3.  **Telegram**: Sends a Telegram message with the formatted alert.\n4.  **Log Alert Activity**: A final Function node that logs details about the sent alert, useful for auditing or debugging."
      },
      "typeVersion": 1
    },
    {
      "id": "cf96cc07-5679-4ba4-86f7-5a2cfec7dae3",
      "name": "Previous flight data",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -780,
        380
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1WAwV5oNEaedbi9saba87LTkxTFbBWwWaGxsgCmda1_Q/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1WAwV5oNEaedbi9saba87LTkxTFbBWwWaGxsgCmda1_Q",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1WAwV5oNEaedbi9saba87LTkxTFbBWwWaGxsgCmda1_Q/edit?usp=drivesdk",
          "cachedResultName": "fare details change logs"
        },
        "authentication": "serviceAccount"
      },
      "credentials": {
        "googleApi": {
          "id": "ScSS2KxGQULuPtdy",
          "name": "Google Sheets- test"
        }
      },
      "executeOnce": false,
      "typeVersion": 4.6
    },
    {
      "id": "ba34df18-2c7c-46ee-9fc4-acfacb7b8fb1",
      "name": "发送消息",
      "type": "n8n-nodes-base.gmail",
      "position": [
        180,
        300
      ],
      "webhookId": "2402da10-b757-4220-8399-d9dee79a3a51",
      "parameters": {
        "sendTo": "abc@gmail.com",
        "message": "={{ $json.formatted_messages.email.html }}",
        "options": {},
        "subject": "={{ $json.formatted_messages.email.subject }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "PcTqvGU9uCunfltE",
          "name": "Gmail account - test"
        }
      },
      "typeVersion": 2.1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "93eb556a-c580-4639-9d7d-0b0baaa6cda4",
  "connections": {
    "Code": {
      "main": [
        [
          {
            "node": "Check if Alert Needed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram": {
      "main": [
        [
          {
            "node": "Log Alert Activity",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send a message": {
      "main": [
        [
          {
            "node": "Log Alert Activity",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Fetch Flight Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Flight Data": {
      "main": [
        [
          {
            "node": "Process Flight Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Flight Data": {
      "main": [
        [
          {
            "node": "Previous flight data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Alert Message": {
      "main": [
        [
          {
            "node": "Send a message",
            "type": "main",
            "index": 0
          },
          {
            "node": "Telegram",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Previous flight data": {
      "main": [
        [
          {
            "node": "Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check if Alert Needed": {
      "main": [
        [
          {
            "node": "Format Alert Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。

这个工作流适合什么场景?

中级 - 杂项

需要付费吗?

本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。

工作流信息
难度等级
中级
节点数量14
分类1
节点类型9
难度说明

适合有一定经验的用户,包含 6-15 个节点的中等复杂度工作流

作者
Oneclick AI Squad

Oneclick AI Squad

@oneclick-ai

The AI Squad Initiative is a pioneering effort to build, automate and scale AI-powered workflows using n8n.io. Our mission is to help individuals and businesses integrate AI agents seamlessly into their daily operations from automating tasks and enhancing productivity to creating innovative, intelligent solutions. We design modular, reusable AI workflow templates that empower creators, developers and teams to supercharge their automation with minimal effort and maximum impact.

外部链接
在 n8n.io 查看

分享此工作流