AlexanderStaniel commited on
Commit
a01d4f9
·
verified ·
1 Parent(s): 481af1d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +150 -1058
app.py CHANGED
@@ -1,1066 +1,158 @@
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)
 
1
+ # 1. IMPORTS & SETUP
2
+ import asyncio
3
+ import datetime
4
+ import logging
5
+ import os
6
+ import gradio as gr
7
+ from cachetools import TTLCache
8
+ from langchain_core.output_parsers import JsonOutputParser
9
+ from langchain_core.prompts import ChatPromptTemplate
10
+ from langchain_core.pydantic_v1 import BaseModel, Field
11
+ from langchain_openai import ChatOpenAI
12
+
13
+ # Configure logging
14
+ logging.basicConfig(
15
+ level=logging.INFO,
16
+ format='%(asctime)s - %(levelname)s - %(message)s'
17
+ )
18
+
19
+ # Simple cache
20
+ ttl_cache = TTLCache(maxsize=100, ttl=3600)
21
+
22
+ # 2. DATA MODEL
23
+ class TravelRequest(BaseModel):
24
+ origin_city: str = Field(description="Starting city or airport")
25
+ destination_city: str = Field(description="Destination city or airport")
26
+ start_date: str = Field(description="Trip start date")
27
+ end_date: str = Field(description="Trip end date")
28
+ budget: int = Field(description="Budget in USD")
29
+ interests: list[str] = Field(description="List of interests")
30
+
31
+ # 3. EXTRACT REQUEST
32
+ async def _extract_user_request(question: str) -> dict:
33
+ llm = ChatOpenAI(model="gpt-4o", temperature=0)
34
+ parser = JsonOutputParser(pydantic_object=TravelRequest)
35
+ prompt = ChatPromptTemplate.from_messages([
36
+ ("system", "You are a travel assistant. Extract details."),
37
+ ("human", "{format_instructions}
38
+ {request}")
39
+ ]).partial(format_instructions=parser.get_format_instructions())
40
+ date_str = datetime.date.today().isoformat()
41
+ logging.info(f"Extracting: {question}")
42
+ return await (prompt | llm | parser).ainvoke({
43
+ "request": question,
44
+ "current_date": date_str
45
+ })
46
+
47
+ # 4. SEARCH HELPERS
48
+ async def _cached_search(func, details):
49
+ key = (func.__name__, str(details))
50
+ if key in ttl_cache:
51
+ logging.info(f"Cache hit for {func.__name__}")
52
+ return ttl_cache[key]
53
+ logging.info(f"Cache miss for {func.__name__}")
54
+ res = await func(details)
55
+ ttl_cache[key] = res
56
+ return res
57
+
58
+ async def _search_skyscanner(details):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  try:
60
+ logging.info(f"Skyscanner: {details['destination_city']}")
61
+ await asyncio.sleep(1)
62
+ return [{
63
+ "source": "Skyscanner",
64
+ "type": "flight",
65
+ "details": f"Flight to {details['destination_city']}",
66
+ "price": 800,
67
+ "link": "https://skyscanner.com"
68
+ }]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  except Exception as e:
70
+ logging.error(e)
71
+ return []
72
 
73
+ async def _search_expedia(details):
 
 
 
 
 
 
 
74
  try:
75
+ logging.info(f"Expedia: {details['destination_city']}")
76
+ await asyncio.sleep(1)
77
+ return [{
78
+ "source": "Expedia",
79
+ "type": "hotel",
80
+ "details": f"Hotel in {details['destination_city']}",
81
+ "price": 1200,
82
+ "link": "https://expedia.com"
83
+ }]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  except Exception as e:
85
+ logging.error(e)
86
+ return []
87
 
88
+ async def _search_getyourguide(details):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  try:
90
+ logging.info(f"GetYourGuide: {details['interests']}")
91
+ await asyncio.sleep(1)
92
+ return [{
93
+ "source": "GetYourGuide",
94
+ "type": "activity",
95
+ "details": f"Tour: {details['interests']}",
96
+ "price": 150,
97
+ "link": "https://getyourguide.com"
98
+ }]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  except Exception as e:
100
+ logging.error(e)
101
+ return []
102
+
103
+ # 5. GATHER RESULTS
104
+ async def _gather_travel_data(req):
105
+ tasks = [
106
+ _cached_search(_search_skyscanner, req),
107
+ _cached_search(_search_expedia, req),
108
+ _cached_search(_search_getyourguide, req)
109
+ ]
110
+ results = await asyncio.gather(*tasks, return_exceptions=True)
111
+ combined = []
112
+ for r in results:
113
+ if not isinstance(r, Exception): combined.extend(r)
114
+ return combined
115
+
116
+ # 6. FORMAT OUTPUT
117
+ def _format_response(req, data):
118
+ dest = req['destination_city']
119
+ text = f"## Travel Plan for {dest}
120
+ "
121
+ flights = [i for i in data if i['type']=='flight']
122
+ hotels = [i for i in data if i['type']=='hotel']
123
+ acts = [i for i in data if i['type']=='activity']
124
+ if flights:
125
+ text += "
126
+ ### Flights:
127
+ "
128
+ for f in flights: text += f"- {f['details']} (${f['price']}) [Book]({f['link']})
129
+ "
130
+ if hotels:
131
+ text += "
132
+ ### Hotels:
133
+ "
134
+ for h in hotels: text += f"- {h['details']} (${h['price']}) [Book]({h['link']})
135
+ "
136
+ if acts:
137
+ text += "
138
+ ### Activities:
139
+ "
140
+ for a in acts: text += f"- {a['details']} (${a['price']}) [Book]({a['link']})
141
+ "
142
+ return text
143
+
144
+ # 7. MAIN & UI
145
+ async def ask_bot(question):
146
+ if not question: return "Tell me your trip!"
147
+ req = await _extract_user_request(question)
148
+ data = await _gather_travel_data(req)
149
+ return _format_response(req, data)
150
+
151
+ iface = gr.Interface(
152
+ fn=ask_bot,
153
+ inputs=gr.Textbox(lines=4, label="Your dream trip?", placeholder="E.g., a week in Tokyo..."),
154
+ outputs=gr.Markdown(label="Your Travel Plan"),
155
+ title="AI Travel Bot"
156
+ )
157
+
158
+ if __name__ == "__main__": iface.launch(server_name="0.0.0.0", server_port=7860)