Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
/main.py
.ruff/
.idea
.venv
.ipynb_checkpoints/
venv
.ipynb_checkpoints/
25 changes: 15 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
> **Note:** This is a fork of the original [fast-flights](https://github.com/AWeirdDev/flights) library, with added features.

Try out the dev version: [**Pypi (`3.0rc0`)**](https://pypi.org/project/fast-flights/3.0rc0/)

<br /><br /><br />

<div align="center">

# ✈️ fast-flights
Expand All @@ -16,6 +19,7 @@ $ pip install fast-flights
</div>

## Basics

**TL;DR**: To use `fast-flights`, you'll first create a filter (for `?tfs=`) to perform a request.
Then, add `flight_data`, `trip`, `seat`, `passengers` to use the API directly.

Expand Down Expand Up @@ -69,16 +73,19 @@ Airport.TAIPEI
```

## What's new

- `v2.0` – New (much more succinct) API, fallback support for Playwright serverless functions, and [documentation](https://aweirddev.github.io/flights)!
- `v2.2` - Now supports **local playwright** for sending requests.

## Cookies & consent

The EU region is a bit tricky to solve for now, but the fallback support should be able to handle it.

## Contributing

Contributing is welcomed! I probably won't work on this project unless there's a need for a major update, but boy howdy do I love pull requests.

***
---

## How it's made

Expand All @@ -89,8 +96,7 @@ The other day, I was making a chat-interface-based trip recommendation app and w

The results? Bad. It seems like they discontinued this service and it now lives in the Graveyard of Google.

> <sup><a href="https://duffel.com/blog/google-flights-api" target="_blank">🧏‍♂️ <b>duffel.com</b></a></sup><br />
> <sup><i>Google Flights API: How did it work & what happened to it?</i></b>
> <sup><a href="https://duffel.com/blog/google-flights-api" target="_blank">🧏‍♂️ <b>duffel.com</b></a></sup><br /> > <sup><i>Google Flights API: How did it work & what happened to it?</i></b>
>
> The Google Flights API offered developers access to aggregated airline data, including flight times, availability, and prices. Over a decade ago, Google announced the acquisition of ITA Software Inc. which it used to develop its API. **However, in 2018, Google ended access to the public-facing API and now only offers access through the QPX enterprise product**.

Expand All @@ -115,9 +121,9 @@ Life tells me to never give up. Let's just take a look at their URL params...
https://www.google.com/travel/flights/search?tfs=CBwQAhoeEgoyMDI0LTA1LTI4agcIARIDVFBFcgcIARIDTVlKGh4SCjIwMjQtMDUtMzBqBwgBEgNNWUpyBwgBEgNUUEVAAUgBcAGCAQsI____________AZgBAQ&hl=en
```

| Param | Content | My past understanding |
|-------|---------|-----------------------|
| hl | en | Sets the language. |
| Param | Content | My past understanding |
| ----- | --------------------------------- | --------------------- |
| hl | en | Sets the language. |
| tfs | CBwQAhoeEgoyMDI0LTA1LTI4agcIARID… | What is this???? 🤮🤮 |

I removed the `?tfs=` parameter and found out that this is the control of our request! And it looks so base64-y.
Expand All @@ -131,16 +137,15 @@ Or maybe it's some kind of a **data-storing method** Google uses? What if it's s

> 🐣 **Result**<br />
> Solution: The Power of **Protocol Buffers**
>
>
> LinkedIn turned to Protocol Buffers, often referred to as **protobuf**, a binary serialization format developed by Google. The key advantage of Protocol Buffers is its efficiency, compactness, and speed, making it significantly faster than JSON for serialization and deserialization.

Gotcha, Protobuf! Let's feed it to an online decoder and see how it does:

> 🔎 **Search** <br />
> protobuf decoder

> 🐣 **Result**<br />
> [protobuf-decoder.netlify.app](https://protobuf-decoder.netlify.app)
> 🐣 **Result**<br /> > [protobuf-decoder.netlify.app](https://protobuf-decoder.netlify.app)

I then pasted the Base64-encoded string to the decoder and no way! It DID return valid data!

Expand Down Expand Up @@ -172,7 +177,7 @@ It works! Now, I won't consider myself an "experienced Protobuf developer" but r

I have no idea what I wrote but... it worked! And here it is, `fast-flights`.

***
---

<div align="center">

Expand Down
Binary file added dist/fast_flights-2.3.0-py3-none-any.whl
Binary file not shown.
Binary file added dist/fast_flights-2.3.0.tar.gz
Binary file not shown.
46 changes: 32 additions & 14 deletions example.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ def flight_to_dict(flight):
return {
"is_best": getattr(flight, 'is_best', None),
"name": getattr(flight, 'name', None),
"flight_code": getattr(flight, 'flight_code', None),
"departure": getattr(flight, 'departure', None),
"arrival": getattr(flight, 'arrival', None),
"arrival_time_ahead": getattr(flight, 'arrival_time_ahead', None),
Expand All @@ -27,30 +28,39 @@ def main():
parser.add_argument('--origin', required=True, help="Origin airport code")
parser.add_argument('--destination', required=True, help="Destination airport code")
parser.add_argument('--depart_date', required=True, help="Beginning trip date (YYYY-MM-DD)")
parser.add_argument('--return_date', required=True, help="Ending trip date (YYYY-MM-DD)")
parser.add_argument('--return_date', help="Ending trip date (YYYY-MM-DD), optional for one-way")
parser.add_argument('--adults', type=int, default=1, help="Number of adult passengers")
parser.add_argument('--type', type=str, default="economy", help="Fare class (economy, premium-economy, business or first)")
parser.add_argument('--max_stops', type=int, help="Maximum number of stops (optional, [0|1|2])")
parser.add_argument('--fetch_mode', type=str, default="common", help="Fetch mode: common, fallback, force-fallback, local, bright-data")
parser.add_argument('--output_file', type=str, help="Path to save the JSON output file (optional)")


args = parser.parse_args()

# Create a new filter
filter = create_filter(
flight_data=[
FlightData(
date=args.depart_date, # Date of departure for outbound flight
from_airport=args.origin,
to_airport=args.destination
),
flight_data = [
FlightData(
date=args.depart_date,
from_airport=args.origin,
to_airport=args.destination
)
]
trip_type = "one-way"

if args.return_date:
flight_data.append(
FlightData(
date=args.return_date, # Date of departure for return flight
date=args.return_date,
from_airport=args.destination,
to_airport=args.origin
),
],
trip="round-trip", # Trip (round-trip, one-way)
)
)
trip_type = "round-trip"

# Create a new filter
filter = create_filter(
flight_data=flight_data,
trip=trip_type,
seat=args.type, # Seat (economy, premium-economy, business or first)
passengers=Passengers(
adults=args.adults,
Expand All @@ -74,7 +84,15 @@ def main():
try:
# Manually convert the result to a dictionary before serialization
result_dict = result_to_dict(result)
print(json.dumps(result_dict, indent=4))
output_json = json.dumps(result_dict, indent=4)

if args.output_file:
with open(args.output_file, 'w') as f:
f.write(output_json)
print(f"Output saved to {args.output_file}")
else:
print(output_json)

except TypeError as e:
print("Serialization to JSON failed. Raw result output:")
print(result)
Expand Down
17 changes: 17 additions & 0 deletions fast_flights/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,22 @@ def safe(n: Optional[LexborNode]):
# Get flight stops
stops = safe(item.css_first(".BbR8Ec .ogfYpf")).text()

# Get flight code
flight_code = None
if impact_model_element := item.css_first('div[data-travelimpactmodelwebsiteurl]'):
if flight_code_url := impact_model_element.attrs.get('data-travelimpactmodelwebsiteurl'):
try:
itinerary = flight_code_url.split('itinerary=')[-1]
flight_codes = []
for segment in itinerary.split(','):
parts = segment.split('-')
if len(parts) >= 4:
flight_codes.append("-".join(parts[2:-1]))
if flight_codes:
flight_code = ", ".join(flight_codes)
except Exception:
pass

# Get delay
delay = safe(item.css_first(".GsCCve")).text() or None

Expand All @@ -182,6 +198,7 @@ def safe(n: Optional[LexborNode]):
{
"is_best": is_best_flight,
"name": name,
"flight_code": flight_code,
"departure": " ".join(departure_time.split()),
"arrival": " ".join(arrival_time.split()),
"arrival_time_ahead": time_ahead,
Expand Down
1 change: 1 addition & 0 deletions fast_flights/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class Result:
class Flight:
is_best: bool
name: str
flight_code: Optional[str]
departure: str
arrival: str
arrival_time_ahead: str
Expand Down
Loading