リアルタイム航空券価格トラッカー – SMSとメールで即時の値下げアラートを送信
これはMiscellaneous分野の自動化ワークフローで、14個のノードを含みます。主にIf, Code, Cron, Gmail, Functionなどのノードを使用。 リアルタイム航空料金追跡ツール(Aviation Stack API)– GmailとTelegramで通知
- •Googleアカウント + Gmail API認証情報
- •Telegram Bot Token
- •ターゲットAPIの認証情報が必要な場合あり
- •Google Sheets API認証情報
カテゴリー
{
"id": "4vQkJTdJPHnD0XUa",
"meta": {
"instanceId": "dd69efaf8212c74ad206700d104739d3329588a6f3f8381a46a481f34c9cc281",
"templateCredsSetupCompleted": true
},
"name": "Live Flight Fare Tracker – Instant Price Drop Alerts via SMS & Email",
"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": "## ✈️ Flight Fare Tracker & Alert System\n\nThis workflow is designed to monitor flight prices and send alerts when significant fare changes occur (drops or increases). It automates the process of fetching flight data, comparing current fares against historical records, and notifying users via email or Telegram if an alert condition is met. 📉📈"
},
"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": "### 📊 Data Ingestion & Processing\n\n1. **Schedule Trigger**: Starts the workflow at regular intervals.\n2. **Fetch Flight Data**: Retrieves flight information from the AviationStack API, filtering by JFK to LAX route.\n3. **Process Flight Data**: A Function node that extracts and formats relevant flight details (flight number, airline, departure/arrival times, and a *mock* current fare). This prepares data for comparison.\n4. **Google Sheets**: Reads existing flight fare data from a Google Sheet, which acts as a historical record."
},
"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": "### 🔍 Fare Comparison & Alert Logic\n\n1. **Code**: This critical node compares the `current_fare` from the \"Process Flight Data\" node with the `previous_fare` fetched from \"Google Sheets\". It calculates fare changes (absolute and percentage) and determines if an alert is needed based on predefined thresholds (e.g., >=10% drop or >=15% increase).\n2. **Check if Alert Needed**: An If node that checks if the `fare_change` calculated in the Code node is non-zero. If there's a change, it proceeds to format and send alerts."
},
"typeVersion": 1
},
{
"id": "a29a641a-f204-463f-8764-91bfaf753f2d",
"name": "通知・ロギング",
"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": "過去フライトデータ",
"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": {
"374b1e8e-15b3-4109-9c46-01406c0e4ca5": {
"main": [
[
{
"node": "87b6bde3-fe0a-47a2-9410-ddb8d78cbf77",
"type": "main",
"index": 0
}
]
]
},
"43084297-fd73-45ae-83b8-3e31db490777": {
"main": [
[
{
"node": "026afcab-023a-4ef4-9573-28ba32edc01c",
"type": "main",
"index": 0
}
]
]
},
"ba34df18-2c7c-46ee-9fc4-acfacb7b8fb1": {
"main": [
[
{
"node": "026afcab-023a-4ef4-9573-28ba32edc01c",
"type": "main",
"index": 0
}
]
]
},
"aa5f054a-6239-46c1-8ab3-492796e1f1c1": {
"main": [
[
{
"node": "8d308fdf-5c40-46ab-b695-a7c95710ada2",
"type": "main",
"index": 0
}
]
]
},
"8d308fdf-5c40-46ab-b695-a7c95710ada2": {
"main": [
[
{
"node": "f4675a74-c27d-4b8b-bb8e-624687125b4a",
"type": "main",
"index": 0
}
]
]
},
"f4675a74-c27d-4b8b-bb8e-624687125b4a": {
"main": [
[
{
"node": "cf96cc07-5679-4ba4-86f7-5a2cfec7dae3",
"type": "main",
"index": 0
}
]
]
},
"11309f53-cdc0-4cf9-b060-d55ca3e9ca74": {
"main": [
[
{
"node": "ba34df18-2c7c-46ee-9fc4-acfacb7b8fb1",
"type": "main",
"index": 0
},
{
"node": "43084297-fd73-45ae-83b8-3e31db490777",
"type": "main",
"index": 0
}
]
]
},
"cf96cc07-5679-4ba4-86f7-5a2cfec7dae3": {
"main": [
[
{
"node": "374b1e8e-15b3-4109-9c46-01406c0e4ca5",
"type": "main",
"index": 0
}
]
]
},
"87b6bde3-fe0a-47a2-9410-ddb8d78cbf77": {
"main": [
[
{
"node": "11309f53-cdc0-4cf9-b060-d55ca3e9ca74",
"type": "main",
"index": 0
}
]
]
}
}
}このワークフローの使い方は?
上記のJSON設定コードをコピーし、n8nインスタンスで新しいワークフローを作成して「JSONからインポート」を選択、設定を貼り付けて認証情報を必要に応じて変更してください。
このワークフローはどんな場面に適していますか?
中級 - その他
有料ですか?
このワークフローは完全無料です。ただし、ワークフローで使用するサードパーティサービス(OpenAI APIなど)は別途料金が発生する場合があります。
関連ワークフロー
Oneclick AI Squad
@oneclick-aiThe 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.
このワークフローを共有