AlexanderStaniel commited on
Commit
72bbcfc
·
verified ·
1 Parent(s): 7eb6da0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1029 -327
app.py CHANGED
@@ -1,364 +1,1066 @@
1
- import gradio as gr
2
- import os
3
- import asyncio
4
  import json
5
- import datetime
6
- import re
7
- import subprocess
8
- import sys
9
- from dotenv import load_dotenv
10
 
11
- # --- Playwright Imports for Web Scraping ---
12
- from playwright.async_api import async_playwright
13
 
14
- # --- Langchain Imports ---
15
- from langchain_core.prompts import ChatPromptTemplate
16
- from langchain_core.output_parsers import JsonOutputParser
17
- from pydantic import BaseModel, Field
18
- from langchain_openai import ChatOpenAI
19
- from langchain_google_genai import ChatGoogleGenerativeAI
20
- from langchain_core.messages import SystemMessage, HumanMessage
21
 
22
- # Load environment variables from .env file (for local testing).
23
- # On Hugging Face Spaces, secrets are automatically available as environment variables.
24
- load_dotenv()
 
 
 
 
25
 
26
- # --- Improved Playwright Browser Installation for Hugging Face Spaces ---
27
- async def ensure_playwright_browsers():
28
- """Ensures Playwright browsers are installed and available."""
 
 
 
 
 
29
  try:
30
- # First, install Playwright browsers
31
- print("Installing Playwright browsers...")
32
- result = subprocess.run([
33
- sys.executable, "-m", "playwright", "install", "chromium"
34
- ], check=True, capture_output=True, text=True)
35
 
36
- if result.stdout:
37
- print("Playwright Install STDOUT:\n", result.stdout)
38
- if result.stderr:
39
- print("Playwright Install STDERR:\n", result.stderr)
40
-
41
- print("Playwright browsers installed successfully!")
42
- return True
43
 
44
- except subprocess.CalledProcessError as e:
45
- print(f"Error during Playwright browser installation: {e}")
46
- if e.stdout:
47
- print("STDOUT:", e.stdout)
48
- if e.stderr:
49
- print("STDERR:", e.stderr)
50
- return False
 
 
 
 
 
 
 
 
 
 
51
  except Exception as e:
52
- print(f"Unexpected error during Playwright browser installation: {e}")
53
- return False
54
-
55
- # Initialize browsers on startup
56
- browsers_ready = False
57
-
58
- # --- 1. API KEY CHECK ---
59
- # This block ensures that the necessary API keys are set before the application starts.
60
- try:
61
- openai_api_key = os.environ["OPENAI_API_KEY"]
62
- except KeyError:
63
- raise EnvironmentError("Missing OPENAI_API_KEY. Please set it in a .env file locally, or in Hugging Face Space secrets.")
64
 
65
- # --- 2. PYDANTIC DATA STRUCTURE DEFINITION ---
66
- class TravelRequest(BaseModel):
67
- departure_city: str = Field(description="The city or airport of departure. Infer if not specified.")
68
- destination_city: str = Field(description="The city or airport of the travel destination.")
69
- departure_date: str = Field(description="The departure date or general time frame (e.g., 'August 2025').")
70
- return_date: str = Field(description="The return date or general time frame.")
71
- duration_days_approx: str = Field(description="Approximate duration of the trip in days.")
72
- budget_preference: str = Field(description="User's budget preference (e.g., 'cheap', 'luxury').")
73
- flight_preferences: str = Field(description="Specific flight preferences (e.g., 'direct flights', 'few stops').")
74
- accommodation_type: str = Field(description="Preferred accommodation (e.g., 'hotel', 'hostel', 'resort').")
75
- number_of_travelers: int = Field(description="Number of adults traveling. Default to 1 if not specified.")
76
- activity_interests: str = Field(description="Specific interests for activities (e.g., 'museums', 'hiking').")
77
-
78
- # --- 3. ASYNC SEARCH FUNCTIONS (Some Simulated, Some Live) ---
79
- async def search_skyscanner(details):
80
- """Simulates searching for flights on Skyscanner."""
81
- print(f"Searching Skyscanner for: {details['destination_city']}")
82
- await asyncio.sleep(2) # Simulate network delay
83
- return [{"source": "Skyscanner", "type": "flight", "details": "Flight to Paris", "price": 850.00, "link": "https://www.skyscanner.com"}]
84
-
85
- async def search_expedia(details):
86
- """Simulates searching for hotels on Expedia."""
87
- print(f"Searching Expedia for: {details['destination_city']}")
88
- await asyncio.sleep(1.5) # Simulate network delay
89
- return [{"source": "Expedia", "type": "hotel", "details": "Hotel in Paris", "price": 150.00, "link": "https://www.expedia.com"}]
90
-
91
- async def scrape_Google_Flights(details):
92
- """Simulates scraping flight data from Google Flights."""
93
- print(f"Scraping Google Flights for: {details['destination_city']}")
94
- await asyncio.sleep(3) # Simulate network delay
95
- return [{"source": "Google Flights (Scraped)", "type": "flight", "details": "Cheaper Flight to Paris", "price": 814.00, "link": "https://www.google.com/flights"}]
96
-
97
- async def search_get_your_guide(details):
98
- """Simulates searching for activities on GetYourGuide."""
99
- print(f"Searching GetYourGuide for: {details['activity_interests']}")
100
- await asyncio.sleep(1) # Simulate network delay
101
- return [{"source": "GetYourGuide", "type": "activity", "details": "Eiffel Tower Tour", "price": 50.00, "link": "https://www.getyourguide.com"}]
102
-
103
- async def scrape_flight_vouchers(details):
104
  """
105
- Performs web scraping for flight vouchers with better error handling for Hugging Face Spaces.
 
106
  """
107
- global browsers_ready
108
-
109
- print(f"Starting web scraping for flight vouchers related to: {details.get('destination_city', 'general travel')}")
110
-
111
- # Check if browsers are ready, if not try to install them
112
- if not browsers_ready:
113
- browsers_ready = await ensure_playwright_browsers()
114
-
115
- if not browsers_ready:
116
- return [{
117
- "source": "Web Scraping (Disabled)",
118
- "type": "voucher",
119
- "details": "Web scraping is temporarily disabled due to browser installation issues. This is common on Hugging Face Spaces. The app will still work with simulated data.",
120
- "price": "N/A",
121
- "link": "#"
122
- }]
123
-
124
- vouchers_found = []
125
- target_urls = [
126
- "https://www.retailmenot.com/coupons/flights",
127
- ]
128
-
129
  try:
130
- async with async_playwright() as p:
131
- browser = await p.chromium.launch(
132
- headless=True,
133
- args=[
134
- '--no-sandbox',
135
- '--disable-setuid-sandbox',
136
- '--disable-dev-shm-usage',
137
- '--disable-accelerated-2d-canvas',
138
- '--no-first-run',
139
- '--no-zygote',
140
- '--disable-gpu',
141
- '--disable-web-security',
142
- '--disable-features=VizDisplayCompositor'
143
- ]
144
- )
145
 
146
- context = await browser.new_context(
147
- user_agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
148
- )
149
- page = await context.new_page()
150
-
151
- for url in target_urls:
152
- try:
153
- print(f"Navigating to: {url}")
154
- await page.goto(url, wait_until='domcontentloaded', timeout=30000)
155
-
156
- # Simple scraping strategy
157
- deals_locator = page.locator('div:has-text("coupon"), div:has-text("promo"), div:has-text("discount")')
158
- deals = await deals_locator.all()
159
-
160
- for i, deal_elem in enumerate(deals[:3]): # Limit to 3 deals
161
- try:
162
- text_content = await deal_elem.inner_text()
163
- if len(text_content.strip()) > 20:
164
- code_match = re.search(r'\b[A-Z0-9]{4,10}\b', text_content)
165
- code = code_match.group(0) if code_match else "N/A"
166
-
167
- vouchers_found.append({
168
- "source": f"Scraped from {url.split('/')[2]}",
169
- "type": "voucher",
170
- "details": f"{text_content[:100]}..." if len(text_content) > 100 else text_content,
171
- "price": f"Code: {code}",
172
- "link": url
173
- })
174
- except Exception as e_deal:
175
- print(f"Error processing deal element: {e_deal}")
176
- continue
177
-
178
- except Exception as e_url:
179
- print(f"Navigation error for {url}: {e_url}")
180
- continue
181
-
182
- await browser.close()
183
 
184
  except Exception as e:
185
- print(f"Playwright error: {e}")
186
- return [{
187
- "source": "Web Scraping (Error)",
188
- "type": "voucher",
189
- "details": f"Web scraping encountered an error: {str(e)[:100]}... This is common on cloud platforms. Using simulated data instead.",
190
- "price": "N/A",
191
- "link": "#"
192
- }]
193
-
194
- if not vouchers_found:
195
- # Return some simulated voucher data as fallback
196
- return [{
197
- "source": "Travel Deals (Simulated)",
198
- "type": "voucher",
199
- "details": "Get 10% off your next flight booking with major airlines - check airline websites directly for current promotions",
200
- "price": "Code: SAVE10",
201
- "link": "https://www.google.com/flights"
202
- }]
203
-
204
- return vouchers_found
205
 
206
- async def search_llm_redundancy(details, llm, llm_name):
 
 
 
207
  """
208
- Queries another LLM for additional, general travel ideas or insights.
 
209
  """
210
- print(f"Querying {llm_name} for additional ideas...")
211
- prompt = f"""As a helpful travel assistant, provide some brief travel suggestions for a trip based on these details: {details}.
212
- Focus on general tips, hidden gems, or activity ideas. Do not suggest specific prices or flights."""
213
- messages = [SystemMessage(content=prompt), HumanMessage(content="What are your suggestions?")]
 
 
214
 
215
  try:
216
- response = await llm.ainvoke(messages)
217
- return [{"source": llm_name, "type": "insight", "details": response.content}]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  except Exception as e:
219
- print(f"Error querying {llm_name}: {e}")
220
- return [{"source": f"{llm_name} (Error)", "type": "insight", "details": f"Unable to get insights from {llm_name} due to: {str(e)[:100]}"}]
221
 
222
- # --- 4. MAIN BOT LOGIC (ask_bot function) ---
223
- async def ask_bot(question):
 
 
224
  """
225
- This is the main ASYNCHRONOUS function for the bot.
 
226
  """
227
- # Part 1: Extract structured data from the user's natural language request using an LLM.
228
  try:
229
- llm_extractor = ChatOpenAI(model="gpt-4o", temperature=0)
230
- parser = JsonOutputParser(pydantic_object=TravelRequest)
231
- extraction_prompt = ChatPromptTemplate.from_messages([
232
- ("system", "You are a helpful travel assistant. Your goal is to accurately extract travel details from a user's natural language request and output them as a JSON object, following the specified schema. Be precise and infer intelligently. The current date is {current_date}."),
233
- ("human", "Extract travel details from this request:\n\n{format_instructions}\n\nUser request: {request}"),
234
- ]).partial(format_instructions=parser.get_format_instructions())
235
- extraction_chain = extraction_prompt | llm_extractor | parser
236
- current_date_str = datetime.date.today().strftime("%Y-%m-%d")
237
- structured_request = await extraction_chain.ainvoke({"request": question, "current_date": current_date_str})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  except Exception as e:
239
- return f"Error extracting request details: {e}. Please try rephrasing your request."
240
-
241
- # Part 2: Gather all search tasks to run concurrently.
242
- llm_openai_creative = ChatOpenAI(model="gpt-4o", temperature=0.7)
243
-
244
- # Compile all the asynchronous search functions into a list of tasks.
245
- tasks = [
246
- search_skyscanner(structured_request),
247
- search_expedia(structured_request),
248
- scrape_Google_Flights(structured_request),
249
- search_get_your_guide(structured_request),
250
- scrape_flight_vouchers(structured_request),
251
- search_llm_redundancy(structured_request, llm_openai_creative, "ChatGPT (Creative)"),
252
- ]
253
-
254
- # Part 3: Run all tasks concurrently and collect their results.
255
- results = await asyncio.gather(*tasks, return_exceptions=True)
256
-
257
- # Part 4: Normalize, filter, and rank the collected results.
258
- all_results = []
259
- for res in results:
260
- if isinstance(res, Exception):
261
- print(f"A search task failed: {res}")
262
- elif res:
263
- all_results.extend(res)
264
-
265
- flights = sorted([r for r in all_results if r['type'] == 'flight'], key=lambda x: x['price'])
266
- hotels = sorted([r for r in all_results if r['type'] == 'hotel'], key=lambda x: x['price'])
267
- activities = [r for r in all_results if r['type'] == 'activity']
268
- insights = [r for r in all_results if r['type'] == 'insight']
269
- vouchers = [r for r in all_results if r['type'] == 'voucher']
270
 
271
- # Part 5: Generate the final formatted output in Markdown.
272
- dest_city = structured_request['destination_city']
273
- output_text = f"## ✨ Your Personalized Travel Plan to {dest_city}! ✨\n\n"
274
-
275
- # Format Flights section
276
- output_text += f"### ✈️ Top Flights\n"
277
- if flights:
278
- for flight in flights[:3]:
279
- output_text += f"* **{flight['details']}** from **{flight['source']}**\n"
280
- output_text += f" * Price: **C${flight['price']:,.2f}**\n"
281
- output_text += f" * 🔗 [**View Deal**]({flight['link']})\n"
282
- else:
283
- output_text += "_No flights found matching your criteria. Try adjusting the dates._\n"
284
- output_text += "\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
 
286
- # Format Hotels section
287
- output_text += f"### 🏨 Top Accommodations\n"
288
- if hotels:
289
- for hotel in hotels[:2]:
290
- output_text += f"* **{hotel['details']}** from **{hotel['source']}**\n"
291
- output_text += f" * Price: **C${hotel['price']:,.2f}** / night\n"
292
- output_text += f" * 🔗 [**View Deal**]({hotel['link']})\n"
293
- else:
294
- output_text += "_No accommodations found matching your criteria._\n"
295
- output_text += "\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296
 
297
- # Format Activities section
298
- output_text += f"### 🎉 Fun Activities\n"
299
- if activities:
300
- for activity in activities[:2]:
301
- output_text += f"* **{activity['details']}** from **{activity['source']}**\n"
302
- output_text += f" * Price: **C${activity['price']:,.2f}**\n"
303
- output_text += f" * 🔗 [**View Activity**]({activity['link']})\n"
304
- else:
305
- output_text += "_No specific activities found, but there's always something to explore!_\n"
306
- output_text += "\n"
307
 
308
- # Format Flight Vouchers & Deals section
309
- output_text += f"### 💰 Flight Vouchers & Deals\n"
310
- if vouchers:
311
- for voucher in vouchers[:2]:
312
- output_text += f"* **{voucher['details']}** from **{voucher['source']}**\n"
313
- output_text += f" * 🔗 [**Claim Deal!**]({voucher['link']})\n"
314
- else:
315
- output_text += "_No specific flight vouchers or codes found at this time._\n"
316
- output_text += "\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
 
318
- # Format AI Insights section
319
- output_text += "### 💡 Additional AI Insights\n"
320
- if insights:
321
- for insight in insights:
322
- output_text += f"* **{insight['source']} says:** {insight['details']}\n"
323
- else:
324
- output_text += "_No additional AI insights available._\n"
325
- output_text += "\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
 
327
- output_text += "> **Disclaimer:** Prices and availability change rapidly. Click the links for the most up-to-date information. Happy travels! 🚀"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
 
329
- return output_text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
330
 
331
- # Wrapper function to handle the async call
332
- def bot_wrapper(question):
333
- """Synchronous wrapper for the async ask_bot function."""
334
- return asyncio.run(ask_bot(question))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
 
336
- # --- 5. DEFINE THE GRADIO INTERFACE ---
337
- with gr.Blocks(theme=gr.themes.Soft()) as iface:
338
- gr.Markdown(
339
- """
340
- # ✨ The Ultimate Global Travel Planner Bot - Bazinga Edition ✨
341
- Your smart AI assistant for finding the best flights, hotels, and activities worldwide!
342
- """
343
- )
344
- with gr.Row():
345
- user_input = gr.Textbox(lines=5, label="✈️ Tell me about your dream trip!", placeholder="e.g., I wanna go to Paris from Halifax, maybe around August for a week or so. Cheap as possible, no crazy layovers!")
346
- with gr.Row():
347
- submit_button = gr.Button("Find My Trip!", variant="primary")
348
- with gr.Row():
349
- output_display = gr.Markdown(label="🌟 Your Travel Plan:")
350
-
351
- gr.Examples(
352
- examples=[
353
- ["I need a cheap flight from Toronto to London around July for 5 days."],
354
- ["Looking for a beach resort in Mexico for 2 people in December, not too expensive."],
355
- ["Find me some cool things to do in Tokyo in spring."]
356
- ],
357
- inputs=user_input
358
- )
359
-
360
- submit_button.click(fn=bot_wrapper, inputs=user_input, outputs=output_display)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
361
 
362
- # --- 6. LAUNCH THE APP ---
363
- if __name__ == "__main__":
364
- iface.launch()
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify, redirect, url_for
2
+ import requests
 
3
  import json
4
+ from datetime import datetime, timedelta
5
+ import time
6
+ import random
 
 
7
 
8
+ app = Flask(__name__)
 
9
 
10
+ # =============================================================================
11
+ # 🚀 ULTIMATE FLIGHT API INTEGRATION - MULTIPLE DATA SOURCES
12
+ # =============================================================================
 
 
 
 
13
 
14
+ # API Configuration - Add your API keys here
15
+ API_KEYS = {
16
+ 'aviationstack': 'YOUR_AVIATIONSTACK_KEY_HERE', # Get from: https://aviationstack.com/
17
+ 'aerodatabox': 'YOUR_AERODATABOX_KEY_HERE', # Get from: https://rapidapi.com/aedbx-aedbx/api/aerodatabox
18
+ 'opensky': 'YOUR_OPENSKY_USERNAME:YOUR_OPENSKY_PASSWORD', # Get from: https://opensky-network.org/
19
+ 'airlabs': 'YOUR_AIRLABS_KEY_HERE' # Get from: https://airlabs.co/
20
+ }
21
 
22
+ # =============================================================================
23
+ # 🛩️ API #1: ADSBDB.COM - Aircraft Information (NO SIGNUP REQUIRED!)
24
+ # =============================================================================
25
+ def get_aircraft_info_adsbdb(tail_number):
26
+ """
27
+ Look up aircraft information by tail number using adsbdb.com
28
+ This API requires NO signup and NO API key!
29
+ """
30
  try:
31
+ tail_number = tail_number.strip().upper()
32
+ url = f"https://api.adsbdb.com/v0/aircraft/{tail_number}"
 
 
 
33
 
34
+ print(f"🔍 ADSBDB: Looking up aircraft {tail_number}")
35
+ response = requests.get(url, timeout=10)
 
 
 
 
 
36
 
37
+ if response.status_code == 200:
38
+ data = response.json()
39
+ return {
40
+ 'source': 'ADSBDB',
41
+ 'status': 'success',
42
+ 'data': {
43
+ 'tail_number': tail_number,
44
+ 'aircraft_type': data.get('type', 'Unknown'),
45
+ 'manufacturer': data.get('manufacturer', 'Unknown'),
46
+ 'model': data.get('model', 'Unknown'),
47
+ 'operator': data.get('operator', 'Unknown'),
48
+ 'registration_date': data.get('registered', 'Unknown')
49
+ }
50
+ }
51
+ else:
52
+ return {'source': 'ADSBDB', 'status': 'not_found', 'error': f'Status {response.status_code}'}
53
+
54
  except Exception as e:
55
+ return {'source': 'ADSBDB', 'status': 'error', 'error': str(e)}
 
 
 
 
 
 
 
 
 
 
 
56
 
57
+ # =============================================================================
58
+ # 📡 API #2: ADS-B EXCHANGE - Live Flight Tracking (NO SIGNUP REQUIRED!)
59
+ # =============================================================================
60
+ def get_live_flights_adsbexchange(lat=40.7128, lon=-74.0060, distance=100):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  """
62
+ Get live flights from ADS-B Exchange - World's largest unfiltered flight data
63
+ Default: New York area, 100nm radius
64
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  try:
66
+ url = f"https://adsbexchange.com/api/aircraft/lat/{lat}/lon/{lon}/dist/{distance}/"
67
+
68
+ print(f"📡 ADS-B Exchange: Getting live flights near {lat}, {lon}")
69
+ response = requests.get(url, timeout=15)
70
+
71
+ if response.status_code == 200:
72
+ data = response.json()
73
+ flights = []
 
 
 
 
 
 
 
74
 
75
+ # Process up to 10 flights for display
76
+ for flight in data.get('aircraft', [])[:10]:
77
+ flights.append({
78
+ 'flight_number': flight.get('flight', 'Unknown'),
79
+ 'aircraft_type': flight.get('t', 'Unknown'),
80
+ 'altitude': flight.get('alt_baro', 'Unknown'),
81
+ 'ground_speed': flight.get('gs', 'Unknown'),
82
+ 'latitude': flight.get('lat', 'Unknown'),
83
+ 'longitude': flight.get('lon', 'Unknown'),
84
+ 'squawk': flight.get('squawk', 'Unknown'),
85
+ 'last_seen': flight.get('seen', 'Unknown')
86
+ })
87
+
88
+ return {
89
+ 'source': 'ADS-B Exchange',
90
+ 'status': 'success',
91
+ 'data': {
92
+ 'total_flights': len(data.get('aircraft', [])),
93
+ 'flights_shown': len(flights),
94
+ 'flights': flights,
95
+ 'search_area': f"{lat}, {lon} ({distance}nm radius)"
96
+ }
97
+ }
98
+ else:
99
+ return {'source': 'ADS-B Exchange', 'status': 'error', 'error': f'Status {response.status_code}'}
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
  except Exception as e:
102
+ return {'source': 'ADS-B Exchange', 'status': 'error', 'error': str(e)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
 
104
+ # =============================================================================
105
+ # ✈️ API #3: AVIATIONSTACK - Flight Search & Status (FREE TIER: 100/month)
106
+ # =============================================================================
107
+ def search_flights_aviationstack(dep_iata, arr_iata, flight_date=None):
108
  """
109
+ Search flights using Aviationstack API
110
+ Get your free API key from: https://aviationstack.com/
111
  """
112
+ if API_KEYS['aviationstack'] == 'YOUR_AVIATIONSTACK_KEY_HERE':
113
+ return {
114
+ 'source': 'Aviationstack',
115
+ 'status': 'api_key_needed',
116
+ 'error': 'Please add your Aviationstack API key to use this feature'
117
+ }
118
 
119
  try:
120
+ url = "http://api.aviationstack.com/v1/flights"
121
+ params = {
122
+ 'access_key': API_KEYS['aviationstack'],
123
+ 'dep_iata': dep_iata,
124
+ 'arr_iata': arr_iata,
125
+ 'limit': 10
126
+ }
127
+
128
+ if flight_date:
129
+ params['flight_date'] = flight_date
130
+
131
+ print(f"✈️ Aviationstack: Searching flights {dep_iata} → {arr_iata}")
132
+ response = requests.get(url, params=params, timeout=15)
133
+
134
+ if response.status_code == 200:
135
+ data = response.json()
136
+ flights = []
137
+
138
+ for flight in data.get('data', []):
139
+ flights.append({
140
+ 'flight_number': flight.get('flight', {}).get('iata', 'Unknown'),
141
+ 'airline': flight.get('airline', {}).get('name', 'Unknown'),
142
+ 'departure_airport': flight.get('departure', {}).get('airport', 'Unknown'),
143
+ 'arrival_airport': flight.get('arrival', {}).get('airport', 'Unknown'),
144
+ 'departure_time': flight.get('departure', {}).get('scheduled', 'Unknown'),
145
+ 'arrival_time': flight.get('arrival', {}).get('scheduled', 'Unknown'),
146
+ 'aircraft_type': flight.get('aircraft', {}).get('registration', 'Unknown'),
147
+ 'flight_status': flight.get('flight_status', 'Unknown')
148
+ })
149
+
150
+ return {
151
+ 'source': 'Aviationstack',
152
+ 'status': 'success',
153
+ 'data': {
154
+ 'total_results': len(flights),
155
+ 'flights': flights,
156
+ 'route': f"{dep_iata} → {arr_iata}"
157
+ }
158
+ }
159
+ else:
160
+ return {'source': 'Aviationstack', 'status': 'error', 'error': f'Status {response.status_code}'}
161
+
162
  except Exception as e:
163
+ return {'source': 'Aviationstack', 'status': 'error', 'error': str(e)}
 
164
 
165
+ # =============================================================================
166
+ # 🌍 API #4: OPENSKY NETWORK - Live Flight Data (FREE: 4000 credits/day)
167
+ # =============================================================================
168
+ def get_flights_opensky(bbox=None):
169
  """
170
+ Get live flight data from OpenSky Network
171
+ bbox format: [min_lat, max_lat, min_lon, max_lon]
172
  """
 
173
  try:
174
+ url = "https://opensky-network.org/api/states/all"
175
+ params = {}
176
+
177
+ if bbox:
178
+ params['lamin'], params['lamax'], params['lomin'], params['lomax'] = bbox
179
+
180
+ print("🌍 OpenSky: Getting live flight states")
181
+ response = requests.get(url, params=params, timeout=15)
182
+
183
+ if response.status_code == 200:
184
+ data = response.json()
185
+ flights = []
186
+
187
+ # Process up to 15 flights
188
+ for state in (data.get('states', []) or [])[:15]:
189
+ if len(state) >= 17: # Ensure we have enough data
190
+ flights.append({
191
+ 'icao24': state[0],
192
+ 'callsign': (state[1] or '').strip(),
193
+ 'origin_country': state[2],
194
+ 'longitude': state[5],
195
+ 'latitude': state[6],
196
+ 'altitude': state[7],
197
+ 'on_ground': state[8],
198
+ 'velocity': state[9],
199
+ 'heading': state[10],
200
+ 'last_contact': datetime.fromtimestamp(state[4]).strftime('%H:%M:%S') if state[4] else 'Unknown'
201
+ })
202
+
203
+ return {
204
+ 'source': 'OpenSky Network',
205
+ 'status': 'success',
206
+ 'data': {
207
+ 'total_flights': len(flights),
208
+ 'flights': flights,
209
+ 'timestamp': datetime.now().strftime('%H:%M:%S')
210
+ }
211
+ }
212
+ else:
213
+ return {'source': 'OpenSky Network', 'status': 'error', 'error': f'Status {response.status_code}'}
214
+
215
  except Exception as e:
216
+ return {'source': 'OpenSky Network', 'status': 'error', 'error': str(e)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
 
218
+ # =============================================================================
219
+ # 🔄 API #5: AERODATABOX - Aircraft Details (FREE TIER: 300-600/month)
220
+ # =============================================================================
221
+ def get_aircraft_details_aerodatabox(registration):
222
+ """
223
+ Get detailed aircraft information from AeroDataBox
224
+ Get your free API key from: https://rapidapi.com/aedbx-aedbx/api/aerodatabox
225
+ """
226
+ if API_KEYS['aerodatabox'] == 'YOUR_AERODATABOX_KEY_HERE':
227
+ return {
228
+ 'source': 'AeroDataBox',
229
+ 'status': 'api_key_needed',
230
+ 'error': 'Please add your AeroDataBox API key to use this feature'
231
+ }
232
+
233
+ try:
234
+ url = f"https://aerodatabox.p.rapidapi.com/aircraft/reg/{registration}"
235
+ headers = {
236
+ 'X-RapidAPI-Key': API_KEYS['aerodatabox'],
237
+ 'X-RapidAPI-Host': 'aerodatabox.p.rapidapi.com'
238
+ }
239
+
240
+ print(f"🔄 AeroDataBox: Getting aircraft details for {registration}")
241
+ response = requests.get(url, headers=headers, timeout=10)
242
+
243
+ if response.status_code == 200:
244
+ data = response.json()
245
+ return {
246
+ 'source': 'AeroDataBox',
247
+ 'status': 'success',
248
+ 'data': {
249
+ 'registration': registration,
250
+ 'aircraft_type': data.get('model', 'Unknown'),
251
+ 'manufacturer': data.get('manufacturer', 'Unknown'),
252
+ 'production_line': data.get('productionLine', 'Unknown'),
253
+ 'first_flight': data.get('firstFlight', 'Unknown'),
254
+ 'delivery_date': data.get('delivery', 'Unknown'),
255
+ 'age_years': data.get('ageYears', 'Unknown')
256
+ }
257
+ }
258
+ else:
259
+ return {'source': 'AeroDataBox', 'status': 'error', 'error': f'Status {response.status_code}'}
260
+
261
+ except Exception as e:
262
+ return {'source': 'AeroDataBox', 'status': 'error', 'error': str(e)}
263
 
264
+ # =============================================================================
265
+ # 🎯 COMBINED FLIGHT SEARCH - USES MULTIPLE APIs AT ONCE!
266
+ # =============================================================================
267
+ def ultimate_flight_search(origin=None, destination=None, date=None, aircraft_reg=None):
268
+ """
269
+ The ULTIMATE flight search using multiple APIs simultaneously!
270
+ """
271
+ results = {
272
+ 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
273
+ 'search_params': {
274
+ 'origin': origin,
275
+ 'destination': destination,
276
+ 'date': date,
277
+ 'aircraft_registration': aircraft_reg
278
+ },
279
+ 'api_results': [],
280
+ 'summary': {
281
+ 'apis_called': 0,
282
+ 'successful_apis': 0,
283
+ 'total_flights_found': 0
284
+ }
285
+ }
286
+
287
+ print("🚀 ULTIMATE FLIGHT SEARCH INITIATED!")
288
+
289
+ # API 1: Live flights from ADS-B Exchange (always call this)
290
+ print("📡 Calling ADS-B Exchange...")
291
+ adsbx_result = get_live_flights_adsbexchange()
292
+ results['api_results'].append(adsbx_result)
293
+ results['summary']['apis_called'] += 1
294
+ if adsbx_result['status'] == 'success':
295
+ results['summary']['successful_apis'] += 1
296
+ results['summary']['total_flights_found'] += adsbx_result['data'].get('flights_shown', 0)
297
+
298
+ # API 2: Live flights from OpenSky Network
299
+ print("🌍 Calling OpenSky Network...")
300
+ opensky_result = get_flights_opensky()
301
+ results['api_results'].append(opensky_result)
302
+ results['summary']['apis_called'] += 1
303
+ if opensky_result['status'] == 'success':
304
+ results['summary']['successful_apis'] += 1
305
+ results['summary']['total_flights_found'] += opensky_result['data'].get('total_flights', 0)
306
+
307
+ # API 3: Flight search from Aviationstack (if origin/destination provided)
308
+ if origin and destination:
309
+ print("✈️ Calling Aviationstack...")
310
+ aviationstack_result = search_flights_aviationstack(origin, destination, date)
311
+ results['api_results'].append(aviationstack_result)
312
+ results['summary']['apis_called'] += 1
313
+ if aviationstack_result['status'] == 'success':
314
+ results['summary']['successful_apis'] += 1
315
+ results['summary']['total_flights_found'] += aviationstack_result['data'].get('total_results', 0)
316
+
317
+ # API 4: Aircraft details from ADSBDB (if aircraft registration provided)
318
+ if aircraft_reg:
319
+ print("🛩️ Calling ADSBDB...")
320
+ adsbdb_result = get_aircraft_info_adsbdb(aircraft_reg)
321
+ results['api_results'].append(adsbdb_result)
322
+ results['summary']['apis_called'] += 1
323
+ if adsbdb_result['status'] == 'success':
324
+ results['summary']['successful_apis'] += 1
325
+
326
+ # API 5: Aircraft details from AeroDataBox (if aircraft registration provided)
327
+ if aircraft_reg:
328
+ print("🔄 Calling AeroDataBox...")
329
+ aerodatabox_result = get_aircraft_details_aerodatabox(aircraft_reg)
330
+ results['api_results'].append(aerodatabox_result)
331
+ results['summary']['apis_called'] += 1
332
+ if aerodatabox_result['status'] == 'success':
333
+ results['summary']['successful_apis'] += 1
334
+
335
+ print(f"🎯 SEARCH COMPLETE! Called {results['summary']['apis_called']} APIs, {results['summary']['successful_apis']} successful")
336
+ return results
337
 
338
+ # =============================================================================
339
+ # 🌐 FLASK ROUTES
340
+ # =============================================================================
 
 
 
 
 
 
 
341
 
342
+ @app.route('/')
343
+ def home():
344
+ """Main page with the ultimate flight search interface"""
345
+ return '''
346
+ <!DOCTYPE html>
347
+ <html>
348
+ <head>
349
+ <title>🚀 ULTIMATE Flight Search Bot</title>
350
+ <style>
351
+ body {
352
+ font-family: Arial, sans-serif;
353
+ margin: 0;
354
+ padding: 20px;
355
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
356
+ color: white;
357
+ min-height: 100vh;
358
+ }
359
+ .container {
360
+ max-width: 1200px;
361
+ margin: 0 auto;
362
+ background: rgba(255,255,255,0.1);
363
+ padding: 40px;
364
+ border-radius: 20px;
365
+ backdrop-filter: blur(10px);
366
+ box-shadow: 0 8px 32px rgba(0,0,0,0.2);
367
+ }
368
+ .header {
369
+ text-align: center;
370
+ margin-bottom: 40px;
371
+ }
372
+ .header h1 {
373
+ font-size: 3em;
374
+ margin: 0;
375
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
376
+ }
377
+ .subtitle {
378
+ font-size: 1.2em;
379
+ margin: 10px 0;
380
+ opacity: 0.9;
381
+ }
382
+ .search-form {
383
+ display: grid;
384
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
385
+ gap: 20px;
386
+ margin-bottom: 30px;
387
+ background: rgba(255,255,255,0.1);
388
+ padding: 30px;
389
+ border-radius: 15px;
390
+ }
391
+ .form-group {
392
+ display: flex;
393
+ flex-direction: column;
394
+ }
395
+ .form-group label {
396
+ margin-bottom: 8px;
397
+ font-weight: bold;
398
+ font-size: 1.1em;
399
+ }
400
+ .form-group input {
401
+ padding: 12px;
402
+ border: none;
403
+ border-radius: 8px;
404
+ font-size: 1em;
405
+ background: rgba(255,255,255,0.9);
406
+ color: #333;
407
+ }
408
+ .search-btn {
409
+ grid-column: 1 / -1;
410
+ padding: 15px 30px;
411
+ background: #ff6b6b;
412
+ color: white;
413
+ border: none;
414
+ border-radius: 8px;
415
+ font-size: 1.2em;
416
+ cursor: pointer;
417
+ font-weight: bold;
418
+ transition: all 0.3s ease;
419
+ }
420
+ .search-btn:hover {
421
+ background: #ff5252;
422
+ transform: translateY(-2px);
423
+ box-shadow: 0 5px 15px rgba(0,0,0,0.2);
424
+ }
425
+ .api-status {
426
+ display: grid;
427
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
428
+ gap: 15px;
429
+ margin-top: 30px;
430
+ }
431
+ .api-card {
432
+ background: rgba(255,255,255,0.1);
433
+ padding: 20px;
434
+ border-radius: 10px;
435
+ text-align: center;
436
+ transition: transform 0.3s ease;
437
+ }
438
+ .api-card:hover {
439
+ transform: translateY(-5px);
440
+ }
441
+ .api-name {
442
+ font-weight: bold;
443
+ font-size: 1.1em;
444
+ margin-bottom: 10px;
445
+ }
446
+ .api-description {
447
+ font-size: 0.9em;
448
+ opacity: 0.8;
449
+ }
450
+ .quick-actions {
451
+ display: flex;
452
+ gap: 15px;
453
+ justify-content: center;
454
+ margin: 30px 0;
455
+ flex-wrap: wrap;
456
+ }
457
+ .quick-btn {
458
+ padding: 10px 20px;
459
+ background: rgba(255,255,255,0.2);
460
+ color: white;
461
+ text-decoration: none;
462
+ border-radius: 25px;
463
+ transition: all 0.3s ease;
464
+ border: 1px solid rgba(255,255,255,0.3);
465
+ }
466
+ .quick-btn:hover {
467
+ background: rgba(255,255,255,0.3);
468
+ transform: scale(1.05);
469
+ }
470
+ </style>
471
+ </head>
472
+ <body>
473
+ <div class="container">
474
+ <div class="header">
475
+ <h1>🚀 ULTIMATE Flight Search Bot</h1>
476
+ <div class="subtitle">⚡ Powered by 5+ APIs • Real-time data • Aircraft tracking • Global coverage</div>
477
+ <div class="subtitle">🌍 The most powerful flight search tool on Earth!</div>
478
+ </div>
479
+
480
+ <form class="search-form" action="/search" method="POST">
481
+ <div class="form-group">
482
+ <label for="origin">✈️ Origin Airport (IATA):</label>
483
+ <input type="text" id="origin" name="origin" placeholder="e.g., JFK, LAX, LHR" maxlength="3">
484
+ </div>
485
+
486
+ <div class="form-group">
487
+ <label for="destination">🛬 Destination Airport (IATA):</label>
488
+ <input type="text" id="destination" name="destination" placeholder="e.g., JFK, LAX, LHR" maxlength="3">
489
+ </div>
490
+
491
+ <div class="form-group">
492
+ <label for="date">📅 Flight Date (Optional):</label>
493
+ <input type="date" id="date" name="date">
494
+ </div>
495
+
496
+ <div class="form-group">
497
+ <label for="aircraft">🛩️ Aircraft Registration (Optional):</label>
498
+ <input type="text" id="aircraft" name="aircraft" placeholder="e.g., N12345, G-ABCD">
499
+ </div>
500
+
501
+ <button type="submit" class="search-btn">🚀 LAUNCH ULTIMATE SEARCH</button>
502
+ </form>
503
+
504
+ <div class="quick-actions">
505
+ <a href="/live" class="quick-btn">📡 Live Flight Map</a>
506
+ <a href="/aircraft/N12345" class="quick-btn">🛩️ Test Aircraft Lookup</a>
507
+ <a href="/test" class="quick-btn">🧪 Test All APIs</a>
508
+ <a href="/stats" class="quick-btn">📊 API Statistics</a>
509
+ </div>
510
+
511
+ <div class="api-status">
512
+ <div class="api-card">
513
+ <div class="api-name">🛩️ ADSBDB</div>
514
+ <div class="api-description">Aircraft database<br>No signup required</div>
515
+ </div>
516
+ <div class="api-card">
517
+ <div class="api-name">📡 ADS-B Exchange</div>
518
+ <div class="api-description">Live flight tracking<br>Unfiltered data</div>
519
+ </div>
520
+ <div class="api-card">
521
+ <div class="api-name">✈️ Aviationstack</div>
522
+ <div class="api-description">Flight schedules<br>100 calls/month free</div>
523
+ </div>
524
+ <div class="api-card">
525
+ <div class="api-name">🌍 OpenSky Network</div>
526
+ <div class="api-description">Live flight states<br>4000 credits/day</div>
527
+ </div>
528
+ <div class="api-card">
529
+ <div class="api-name">🔄 AeroDataBox</div>
530
+ <div class="api-description">Aircraft details<br>300-600 calls/month</div>
531
+ </div>
532
+ </div>
533
+ </div>
534
+ </body>
535
+ </html>
536
+ '''
537
 
538
+ @app.route('/search', methods=['POST'])
539
+ def search():
540
+ """Process the ultimate flight search"""
541
+ origin = request.form.get('origin', '').upper().strip()
542
+ destination = request.form.get('destination', '').upper().strip()
543
+ date = request.form.get('date', '').strip()
544
+ aircraft = request.form.get('aircraft', '').upper().strip()
545
+
546
+ # Perform the ultimate search
547
+ results = ultimate_flight_search(
548
+ origin=origin if origin else None,
549
+ destination=destination if destination else None,
550
+ date=date if date else None,
551
+ aircraft_reg=aircraft if aircraft else None
552
+ )
553
+
554
+ # Generate HTML response
555
+ html = f'''
556
+ <!DOCTYPE html>
557
+ <html>
558
+ <head>
559
+ <title>🚀 Ultimate Search Results</title>
560
+ <style>
561
+ body {{
562
+ font-family: Arial, sans-serif;
563
+ margin: 0;
564
+ padding: 20px;
565
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
566
+ color: white;
567
+ min-height: 100vh;
568
+ }}
569
+ .container {{
570
+ max-width: 1400px;
571
+ margin: 0 auto;
572
+ }}
573
+ .header {{
574
+ text-align: center;
575
+ margin-bottom: 30px;
576
+ background: rgba(255,255,255,0.1);
577
+ padding: 20px;
578
+ border-radius: 15px;
579
+ backdrop-filter: blur(10px);
580
+ }}
581
+ .summary {{
582
+ display: grid;
583
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
584
+ gap: 15px;
585
+ margin-bottom: 30px;
586
+ }}
587
+ .summary-card {{
588
+ background: rgba(255,255,255,0.1);
589
+ padding: 20px;
590
+ border-radius: 10px;
591
+ text-align: center;
592
+ }}
593
+ .api-result {{
594
+ background: rgba(255,255,255,0.1);
595
+ margin: 20px 0;
596
+ padding: 25px;
597
+ border-radius: 15px;
598
+ backdrop-filter: blur(10px);
599
+ }}
600
+ .api-header {{
601
+ display: flex;
602
+ justify-content: space-between;
603
+ align-items: center;
604
+ margin-bottom: 15px;
605
+ padding-bottom: 10px;
606
+ border-bottom: 1px solid rgba(255,255,255,0.2);
607
+ }}
608
+ .api-name {{
609
+ font-size: 1.3em;
610
+ font-weight: bold;
611
+ }}
612
+ .status-success {{
613
+ color: #4ade80;
614
+ font-weight: bold;
615
+ }}
616
+ .status-error {{
617
+ color: #f87171;
618
+ font-weight: bold;
619
+ }}
620
+ .data-grid {{
621
+ display: grid;
622
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
623
+ gap: 15px;
624
+ margin-top: 15px;
625
+ }}
626
+ .data-item {{
627
+ background: rgba(255,255,255,0.1);
628
+ padding: 15px;
629
+ border-radius: 8px;
630
+ }}
631
+ .data-label {{
632
+ font-weight: bold;
633
+ margin-bottom: 5px;
634
+ }}
635
+ .data-value {{
636
+ opacity: 0.9;
637
+ }}
638
+ .back-link {{
639
+ text-align: center;
640
+ margin-top: 30px;
641
+ }}
642
+ .back-link a {{
643
+ background: #ff6b6b;
644
+ color: white;
645
+ padding: 15px 30px;
646
+ text-decoration: none;
647
+ border-radius: 8px;
648
+ font-weight: bold;
649
+ transition: all 0.3s ease;
650
+ }}
651
+ .back-link a:hover {{
652
+ background: #ff5252;
653
+ transform: translateY(-2px);
654
+ }}
655
+ </style>
656
+ </head>
657
+ <body>
658
+ <div class="container">
659
+ <div class="header">
660
+ <h1>🚀 Ultimate Flight Search Results</h1>
661
+ <p>Search completed at: {results['timestamp']}</p>
662
+ </div>
663
+
664
+ <div class="summary">
665
+ <div class="summary-card">
666
+ <h3>📊 APIs Called</h3>
667
+ <div style="font-size: 2em; font-weight: bold;">{results['summary']['apis_called']}</div>
668
+ </div>
669
+ <div class="summary-card">
670
+ <h3>✅ Successful</h3>
671
+ <div style="font-size: 2em; font-weight: bold; color: #4ade80;">{results['summary']['successful_apis']}</div>
672
+ </div>
673
+ <div class="summary-card">
674
+ <h3>✈️ Flights Found</h3>
675
+ <div style="font-size: 2em; font-weight: bold; color: #60a5fa;">{results['summary']['total_flights_found']}</div>
676
+ </div>
677
+ <div class="summary-card">
678
+ <h3>🎯 Success Rate</h3>
679
+ <div style="font-size: 2em; font-weight: bold; color: #fbbf24;">
680
+ {int((results['summary']['successful_apis'] / results['summary']['apis_called']) * 100) if results['summary']['apis_called'] > 0 else 0}%
681
+ </div>
682
+ </div>
683
+ </div>
684
+ '''
685
+
686
+ # Add results from each API
687
+ for api_result in results['api_results']:
688
+ status_class = 'status-success' if api_result['status'] == 'success' else 'status-error'
689
+ status_text = '✅ SUCCESS' if api_result['status'] == 'success' else '❌ ERROR'
690
+
691
+ html += f'''
692
+ <div class="api-result">
693
+ <div class="api-header">
694
+ <div class="api-name">{api_result['source']}</div>
695
+ <div class="{status_class}">{status_text}</div>
696
+ </div>
697
+ '''
698
+
699
+ if api_result['status'] == 'success':
700
+ # Display successful data
701
+ data = api_result['data']
702
+ if 'flights' in data:
703
+ html += f"<p><strong>Found {len(data['flights'])} flights:</strong></p>"
704
+ html += '<div class="data-grid">'
705
+ for i, flight in enumerate(data['flights'][:6]): # Show max 6 flights
706
+ html += '<div class="data-item">'
707
+ html += f'<div class="data-label">Flight #{i+1}</div>'
708
+ for key, value in flight.items():
709
+ if value not in ['Unknown', None, '']:
710
+ html += f'<div><strong>{key.replace("_", " ").title()}:</strong> {value}</div>'
711
+ html += '</div>'
712
+ html += '</div>'
713
+ else:
714
+ # Display non-flight data
715
+ html += '<div class="data-grid">'
716
+ for key, value in data.items():
717
+ if isinstance(value, (str, int, float)) and value not in ['Unknown', None, '']:
718
+ html += f'''
719
+ <div class="data-item">
720
+ <div class="data-label">{key.replace("_", " ").title()}</div>
721
+ <div class="data-value">{value}</div>
722
+ </div>
723
+ '''
724
+ html += '</div>'
725
+ else:
726
+ # Display error
727
+ error_msg = api_result.get('error', 'Unknown error')
728
+ html += f'<p style="color: #f87171;"><strong>Error:</strong> {error_msg}</p>'
729
+
730
+ html += '</div>'
731
+
732
+ html += '''
733
+ <div class="back-link">
734
+ <a href="/">🔙 Back to Search</a>
735
+ </div>
736
+ </div>
737
+ </body>
738
+ </html>
739
+ '''
740
+
741
+ return html
742
 
743
+ @app.route('/live')
744
+ def live_flights():
745
+ """Live flight tracking page"""
746
+ live_data = get_live_flights_adsbexchange()
747
+ opensky_data = get_flights_opensky()
748
+
749
+ html = f'''
750
+ <!DOCTYPE html>
751
+ <html>
752
+ <head>
753
+ <title>📡 Live Flight Map</title>
754
+ <style>
755
+ body {{
756
+ font-family: Arial, sans-serif;
757
+ margin: 0;
758
+ padding: 20px;
759
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
760
+ color: white;
761
+ min-height: 100vh;
762
+ }}
763
+ .container {{ max-width: 1200px; margin: 0 auto; }}
764
+ .header {{ text-align: center; margin-bottom: 30px; }}
765
+ .flights-grid {{
766
+ display: grid;
767
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
768
+ gap: 20px;
769
+ }}
770
+ .flight-card {{
771
+ background: rgba(255,255,255,0.1);
772
+ padding: 20px;
773
+ border-radius: 10px;
774
+ backdrop-filter: blur(10px);
775
+ }}
776
+ .flight-header {{
777
+ font-size: 1.2em;
778
+ font-weight: bold;
779
+ margin-bottom: 10px;
780
+ color: #60a5fa;
781
+ }}
782
+ </style>
783
+ </head>
784
+ <body>
785
+ <div class="container">
786
+ <div class="header">
787
+ <h1>📡 Live Flight Tracking</h1>
788
+ <p>Real-time flight data from multiple sources</p>
789
+ <a href="/" style="color: #60a5fa;">← Back to Home</a>
790
+ </div>
791
+
792
+ <div style="margin-bottom: 30px;">
793
+ <h2>🛩️ ADS-B Exchange Data</h2>
794
+ <p>Status: {live_data['status']} | Source: {live_data['source']}</p>
795
+ </div>
796
+
797
+ <div class="flights-grid">
798
+ '''
799
+
800
+ if live_data['status'] == 'success':
801
+ for flight in live_data['data']['flights'][:12]:
802
+ html += f'''
803
+ <div class="flight-card">
804
+ <div class="flight-header">{flight['flight_number']}</div>
805
+ <div><strong>Aircraft:</strong> {flight['aircraft_type']}</div>
806
+ <div><strong>Altitude:</strong> {flight['altitude']} ft</div>
807
+ <div><strong>Speed:</strong> {flight['ground_speed']} kts</div>
808
+ <div><strong>Position:</strong> {flight['latitude']}, {flight['longitude']}</div>
809
+ <div><strong>Last Seen:</strong> {flight['last_seen']}s ago</div>
810
+ </div>
811
+ '''
812
+
813
+ html += '''
814
+ </div>
815
+ </div>
816
+ </body>
817
+ </html>
818
+ '''
819
+
820
+ return html
821
 
822
+ @app.route('/aircraft/<registration>')
823
+ def aircraft_lookup(registration):
824
+ """Aircraft lookup by registration"""
825
+ adsbdb_result = get_aircraft_info_adsbdb(registration)
826
+ aerodatabox_result = get_aircraft_details_aerodatabox(registration)
827
+
828
+ return f'''
829
+ <!DOCTYPE html>
830
+ <html>
831
+ <head>
832
+ <title>🛩️ Aircraft: {registration}</title>
833
+ <style>
834
+ body {{
835
+ font-family: Arial, sans-serif;
836
+ margin: 0;
837
+ padding: 20px;
838
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
839
+ color: white;
840
+ min-height: 100vh;
841
+ }}
842
+ .container {{ max-width: 800px; margin: 0 auto; }}
843
+ .header {{ text-align: center; margin-bottom: 30px; }}
844
+ .result-card {{
845
+ background: rgba(255,255,255,0.1);
846
+ padding: 25px;
847
+ border-radius: 15px;
848
+ margin: 20px 0;
849
+ backdrop-filter: blur(10px);
850
+ }}
851
+ .source-name {{
852
+ font-size: 1.3em;
853
+ font-weight: bold;
854
+ margin-bottom: 15px;
855
+ color: #60a5fa;
856
+ }}
857
+ .info-grid {{
858
+ display: grid;
859
+ grid-template-columns: 1fr 1fr;
860
+ gap: 15px;
861
+ }}
862
+ .info-item {{
863
+ background: rgba(255,255,255,0.1);
864
+ padding: 15px;
865
+ border-radius: 8px;
866
+ }}
867
+ </style>
868
+ </head>
869
+ <body>
870
+ <div class="container">
871
+ <div class="header">
872
+ <h1>🛩️ Aircraft Information</h1>
873
+ <h2>Registration: {registration}</h2>
874
+ <a href="/" style="color: #60a5fa;">← Back to Home</a>
875
+ </div>
876
+
877
+ <div class="result-card">
878
+ <div class="source-name">🛩️ ADSBDB Database</div>
879
+ <div>Status: {adsbdb_result['status']}</div>
880
+ {f'<div class="info-grid">' + ''.join([f'<div class="info-item"><strong>{k.replace("_", " ").title()}:</strong><br>{v}</div>' for k, v in adsbdb_result.get('data', {}).items()]) + '</div>' if adsbdb_result['status'] == 'success' else f'<div style="color: #f87171;">Error: {adsbdb_result.get("error", "Unknown error")}</div>'}
881
+ </div>
882
+
883
+ <div class="result-card">
884
+ <div class="source-name">🔄 AeroDataBox Database</div>
885
+ <div>Status: {aerodatabox_result['status']}</div>
886
+ {f'<div class="info-grid">' + ''.join([f'<div class="info-item"><strong>{k.replace("_", " ").title()}:</strong><br>{v}</div>' for k, v in aerodatabox_result.get('data', {}).items()]) + '</div>' if aerodatabox_result['status'] == 'success' else f'<div style="color: #f87171;">Error: {aerodatabox_result.get("error", "Unknown error")}</div>'}
887
+ </div>
888
+ </div>
889
+ </body>
890
+ </html>
891
+ '''
892
 
893
+ @app.route('/test')
894
+ def test_apis():
895
+ """Test all APIs"""
896
+ return '''
897
+ <!DOCTYPE html>
898
+ <html>
899
+ <head>
900
+ <title>🧪 API Test Center</title>
901
+ <style>
902
+ body {
903
+ font-family: Arial, sans-serif;
904
+ margin: 0;
905
+ padding: 20px;
906
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
907
+ color: white;
908
+ min-height: 100vh;
909
+ }
910
+ .container { max-width: 800px; margin: 0 auto; }
911
+ .test-card {
912
+ background: rgba(255,255,255,0.1);
913
+ padding: 20px;
914
+ border-radius: 10px;
915
+ margin: 15px 0;
916
+ backdrop-filter: blur(10px);
917
+ }
918
+ .test-btn {
919
+ background: #ff6b6b;
920
+ color: white;
921
+ padding: 10px 20px;
922
+ text-decoration: none;
923
+ border-radius: 5px;
924
+ display: inline-block;
925
+ margin: 5px;
926
+ }
927
+ </style>
928
+ </head>
929
+ <body>
930
+ <div class="container">
931
+ <h1>🧪 API Test Center</h1>
932
+ <a href="/" style="color: #60a5fa;">← Back to Home</a>
933
+
934
+ <div class="test-card">
935
+ <h3>🛩️ Test ADSBDB (Aircraft Database)</h3>
936
+ <p>No API key required - Test with real aircraft registrations</p>
937
+ <a href="/aircraft/N12345" class="test-btn">Test N12345</a>
938
+ <a href="/aircraft/N737MAX" class="test-btn">Test N737MAX</a>
939
+ <a href="/aircraft/G-ABCD" class="test-btn">Test G-ABCD</a>
940
+ </div>
941
+
942
+ <div class="test-card">
943
+ <h3>📡 Test ADS-B Exchange (Live Flights)</h3>
944
+ <p>No API key required - Real-time flight tracking</p>
945
+ <a href="/live" class="test-btn">View Live Flights</a>
946
+ </div>
947
+
948
+ <div class="test-card">
949
+ <h3>🚀 Test Ultimate Search</h3>
950
+ <p>Test all APIs together</p>
951
+ <a href="/search" class="test-btn">Ultimate Search</a>
952
+ </div>
953
+
954
+ <div class="test-card">
955
+ <h3>⚙️ API Key Setup</h3>
956
+ <p>To unlock all features, add your API keys to the app.py file:</p>
957
+ <ul>
958
+ <li>Aviationstack: <a href="https://aviationstack.com/" target="_blank">Get Free Key</a></li>
959
+ <li>AeroDataBox: <a href="https://rapidapi.com/aedbx-aedbx/api/aerodatabox" target="_blank">Get Free Key</a></li>
960
+ <li>OpenSky: <a href="https://opensky-network.org/" target="_blank">Register Free</a></li>
961
+ <li>Airlabs: <a href="https://airlabs.co/" target="_blank">Get Free Key</a></li>
962
+ </ul>
963
+ </div>
964
+ </div>
965
+ </body>
966
+ </html>
967
+ '''
968
 
969
+ @app.route('/stats')
970
+ def api_stats():
971
+ """API statistics and status"""
972
+ return '''
973
+ <!DOCTYPE html>
974
+ <html>
975
+ <head>
976
+ <title>📊 API Statistics</title>
977
+ <style>
978
+ body {
979
+ font-family: Arial, sans-serif;
980
+ margin: 0;
981
+ padding: 20px;
982
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
983
+ color: white;
984
+ min-height: 100vh;
985
+ }
986
+ .container { max-width: 1000px; margin: 0 auto; }
987
+ .stats-grid {
988
+ display: grid;
989
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
990
+ gap: 20px;
991
+ margin: 20px 0;
992
+ }
993
+ .stat-card {
994
+ background: rgba(255,255,255,0.1);
995
+ padding: 20px;
996
+ border-radius: 10px;
997
+ text-align: center;
998
+ backdrop-filter: blur(10px);
999
+ }
1000
+ </style>
1001
+ </head>
1002
+ <body>
1003
+ <div class="container">
1004
+ <h1>📊 Ultimate Flight Bot Statistics</h1>
1005
+ <a href="/" style="color: #60a5fa;">← Back to Home</a>
1006
+
1007
+ <div class="stats-grid">
1008
+ <div class="stat-card">
1009
+ <h3>🛩️ ADSBDB</h3>
1010
+ <div style="font-size: 2em; color: #4ade80;">✅</div>
1011
+ <p>Aircraft Database<br>No limits</p>
1012
+ </div>
1013
+
1014
+ <div class="stat-card">
1015
+ <h3>📡 ADS-B Exchange</h3>
1016
+ <div style="font-size: 2em; color: #4ade80;">✅</div>
1017
+ <p>Live Flight Tracking<br>No limits</p>
1018
+ </div>
1019
+
1020
+ <div class="stat-card">
1021
+ <h3>✈️ Aviationstack</h3>
1022
+ <div style="font-size: 2em; color: #fbbf24;">⚠️</div>
1023
+ <p>Flight Schedules<br>Needs API Key</p>
1024
+ </div>
1025
+
1026
+ <div class="stat-card">
1027
+ <h3>🌍 OpenSky Network</h3>
1028
+ <div style="font-size: 2em; color: #4ade80;">✅</div>
1029
+ <p>Live Flight States<br>4000/day free</p>
1030
+ </div>
1031
+
1032
+ <div class="stat-card">
1033
+ <h3>🔄 AeroDataBox</h3>
1034
+ <div style="font-size: 2em; color: #fbbf24;">⚠️</div>
1035
+ <p>Aircraft Details<br>Needs API Key</p>
1036
+ </div>
1037
+ </div>
1038
+
1039
+ <div style="background: rgba(255,255,255,0.1); padding: 20px; border-radius: 10px; margin: 20px 0;">
1040
+ <h3>🎯 Integration Status</h3>
1041
+ <ul>
1042
+ <li>✅ 2 APIs working without setup (ADSBDB, ADS-B Exchange)</li>
1043
+ <li>✅ 3 APIs ready for API keys (Aviationstack, AeroDataBox, OpenSky)</li>
1044
+ <li>🚀 Total APIs integrated: 5</li>
1045
+ <li>📈 Ready for 10+ more APIs</li>
1046
+ </ul>
1047
+ </div>
1048
+ </div>
1049
+ </body>
1050
+ </html>
1051
+ '''
1052
 
1053
+ if __name__ == '__main__':
1054
+ print("🚀 ULTIMATE FLIGHT SEARCH BOT STARTING...")
1055
+ print("📡 APIs Integrated:")
1056
+ print(" ✅ ADSBDB (Aircraft Database)")
1057
+ print(" ✅ ADS-B Exchange (Live Tracking)")
1058
+ print(" ⚙️ Aviationstack (Flight Search)")
1059
+ print(" ⚙️ OpenSky Network (Live States)")
1060
+ print(" ⚙️ AeroDataBox (Aircraft Details)")
1061
+ print("")
1062
+ print("🌐 Available at: http://localhost:5000")
1063
+ print("🧪 Test page: http://localhost:5000/test")
1064
+ print("📡 Live flights: http://localhost:5000/live")
1065
+ print("")
1066
+ app.run(debug=True, host='0.0.0.0', port=5000)