Initial commit - Current state of vip-coordinator

This commit is contained in:
2026-01-24 09:30:26 +01:00
commit aa900505b9
96 changed files with 31868 additions and 0 deletions

View File

@@ -0,0 +1,262 @@
// Real Flight tracking service with Google scraping
// No mock data - only real flight information
interface FlightData {
flightNumber: string;
flightDate: string;
status: string;
airline?: string;
aircraft?: string;
departure: {
airport: string;
airportName?: string;
scheduled: string;
estimated?: string;
actual?: string;
terminal?: string;
gate?: string;
};
arrival: {
airport: string;
airportName?: string;
scheduled: string;
estimated?: string;
actual?: string;
terminal?: string;
gate?: string;
};
delay?: number;
lastUpdated: string;
source: 'google' | 'aviationstack' | 'not_found';
}
interface FlightSearchParams {
flightNumber: string;
date: string; // YYYY-MM-DD format
departureAirport?: string;
arrivalAirport?: string;
}
class FlightService {
private flightCache: Map<string, { data: FlightData; expires: number }> = new Map();
private updateIntervals: Map<string, NodeJS.Timeout> = new Map();
constructor() {
// No API keys needed for Google scraping
}
// Real flight lookup - no mock data
async getFlightInfo(params: FlightSearchParams): Promise<FlightData | null> {
const cacheKey = `${params.flightNumber}_${params.date}`;
// Check cache first (shorter cache for real data)
const cached = this.flightCache.get(cacheKey);
if (cached && cached.expires > Date.now()) {
return cached.data;
}
try {
// Try Google scraping first
let flightData = await this.scrapeGoogleFlights(params);
// If Google fails, try AviationStack (if API key available)
if (!flightData) {
flightData = await this.getFromAviationStack(params);
}
// Cache the result for 2 minutes (shorter for real data)
if (flightData) {
this.flightCache.set(cacheKey, {
data: flightData,
expires: Date.now() + (2 * 60 * 1000)
});
}
return flightData;
} catch (error) {
console.error('Error fetching flight data:', error);
return null; // Return null instead of mock data
}
}
// Google Flights scraping implementation
private async scrapeGoogleFlights(params: FlightSearchParams): Promise<FlightData | null> {
try {
// Google Flights URL format
const googleUrl = `https://www.google.com/travel/flights/search?tfs=CBwQAhoeEgoyMDI1LTA3LTAxagcIARIDTEFYcgcIARIDSkZLQAFIAXABggELCP___________wFAAUgBmAEB&hl=en`;
// For now, return null to indicate no real scraping implementation
// In production, you would implement actual web scraping here
console.log(`Would scrape Google for flight ${params.flightNumber} on ${params.date}`);
return null;
} catch (error) {
console.error('Google scraping error:', error);
return null;
}
}
// AviationStack API integration (only if API key available)
private async getFromAviationStack(params: FlightSearchParams): Promise<FlightData | null> {
const apiKey = process.env.AVIATIONSTACK_API_KEY;
console.log('Checking AviationStack API key:', apiKey ? `Key present (${apiKey.length} chars)` : 'No key');
if (!apiKey || apiKey === 'demo_key' || apiKey === '') {
console.log('No valid AviationStack API key available');
return null; // No API key available
}
try {
// Format flight number: Remove spaces and convert to uppercase
const formattedFlightNumber = params.flightNumber.replace(/\s+/g, '').toUpperCase();
console.log(`Formatted flight number: ${params.flightNumber} -> ${formattedFlightNumber}`);
// Note: Free tier doesn't support date filtering, so we get recent flights
// For future dates, this won't work well - consider upgrading subscription
const url = `http://api.aviationstack.com/v1/flights?access_key=${apiKey}&flight_iata=${formattedFlightNumber}&limit=10`;
console.log('AviationStack API URL:', url.replace(apiKey, '***'));
console.log('Note: Free tier returns recent flights only, not future scheduled flights');
const response = await fetch(url);
const data: any = await response.json();
console.log('AviationStack response status:', response.status);
if (!response.ok) {
console.error('AviationStack API error - HTTP status:', response.status);
return null;
}
// Check for API errors in response
if (data?.error) {
console.error('AviationStack API error:', data.error);
return null;
}
if (Array.isArray(data?.data) && data.data.length > 0) {
// This is a valid flight number that exists!
console.log(`✅ Valid flight number: ${formattedFlightNumber} exists in the system`);
// Try to find a flight matching the requested date
let flight = data.data.find((f: any) => f.flight_date === params.date);
// If no exact date match, use most recent for validation
if (!flight) {
flight = data.data[0];
console.log(` Flight ${formattedFlightNumber} is valid`);
console.log(`Recent flight: ${flight.departure.airport}${flight.arrival.airport}`);
console.log(`Operated by: ${flight.airline?.name || 'Unknown'}`);
console.log(`Note: Showing recent data from ${flight.flight_date} for validation`);
} else {
console.log(`✅ Flight found for exact date: ${params.date}`);
}
console.log('Flight route:', `${flight.departure.iata}${flight.arrival.iata}`);
console.log('Status:', flight.flight_status);
return {
flightNumber: flight.flight.iata,
flightDate: flight.flight_date,
status: this.normalizeStatus(flight.flight_status),
airline: flight.airline?.name,
aircraft: flight.aircraft?.registration,
departure: {
airport: flight.departure.iata,
airportName: flight.departure.airport,
scheduled: flight.departure.scheduled,
estimated: flight.departure.estimated,
actual: flight.departure.actual,
terminal: flight.departure.terminal,
gate: flight.departure.gate
},
arrival: {
airport: flight.arrival.iata,
airportName: flight.arrival.airport,
scheduled: flight.arrival.scheduled,
estimated: flight.arrival.estimated,
actual: flight.arrival.actual,
terminal: flight.arrival.terminal,
gate: flight.arrival.gate
},
delay: flight.departure.delay || 0,
lastUpdated: new Date().toISOString(),
source: 'aviationstack'
};
}
console.log(`❌ Invalid flight number: ${formattedFlightNumber} not found`);
console.log('This flight number does not exist or has not operated recently');
return null;
} catch (error) {
console.error('AviationStack API error:', error);
return null;
}
}
// Start periodic updates for a flight
startPeriodicUpdates(params: FlightSearchParams, intervalMinutes: number = 5): void {
const key = `${params.flightNumber}_${params.date}`;
// Clear existing interval if any
this.stopPeriodicUpdates(key);
// Set up new interval
const interval = setInterval(async () => {
try {
await this.getFlightInfo(params); // This will update the cache
console.log(`Updated flight data for ${params.flightNumber} on ${params.date}`);
} catch (error) {
console.error(`Error updating flight ${params.flightNumber}:`, error);
}
}, intervalMinutes * 60 * 1000);
this.updateIntervals.set(key, interval);
}
// Stop periodic updates for a flight
stopPeriodicUpdates(key: string): void {
const interval = this.updateIntervals.get(key);
if (interval) {
clearInterval(interval);
this.updateIntervals.delete(key);
}
}
// Get multiple flights with date specificity
async getMultipleFlights(flightParams: FlightSearchParams[]): Promise<{ [key: string]: FlightData | null }> {
const results: { [key: string]: FlightData | null } = {};
for (const params of flightParams) {
const key = `${params.flightNumber}_${params.date}`;
results[key] = await this.getFlightInfo(params);
}
return results;
}
// Normalize flight status across different APIs
private normalizeStatus(status: string): string {
const statusMap: { [key: string]: string } = {
'scheduled': 'scheduled',
'active': 'active',
'landed': 'landed',
'cancelled': 'cancelled',
'incident': 'delayed',
'diverted': 'diverted'
};
return statusMap[status.toLowerCase()] || status;
}
// Clean up resources
cleanup(): void {
for (const [key, interval] of this.updateIntervals) {
clearInterval(interval);
}
this.updateIntervals.clear();
this.flightCache.clear();
}
}
export default new FlightService();
export { FlightData, FlightSearchParams };