Both competitor booking engines changed their API contracts, silently
breaking their direct scrapes ~16 days ago (200 responses with empty
bodies, so no exception was ever raised):
- Guestline: the /enhanced availability endpoint now returns a bare []
for every date. Switch to the base /api/availabilities/{coll}/{hotel}
endpoint, which returns {"rooms":[...]} with the same room shape.
- Mews: getAvailability now rejects the old body with "Invalid
EnterpriseId" — it requires enterpriseId + serviceId. getPricing
additionally requires currencyCode. Capture the enterprise's
defaultCurrencyCode in _ensure_config and send all three.
Also stop last_scraped_at advancing when a run saves 0 rows, so the
overview no longer reports a phantom-fresh scrape while the per-date
data stays stale — the symptom that surfaced this.
Verified live: Three Ways now yields 41 rows/night, Old Stocks 10.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
49 lines
2.1 KiB
Python
49 lines
2.1 KiB
Python
import re
|
|
from datetime import date, timedelta
|
|
from .base import BaseProfile
|
|
|
|
HEADERS = {
|
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
|
"Accept": "application/json",
|
|
}
|
|
|
|
|
|
class GuestlineProfile(BaseProfile):
|
|
name = "guestline"
|
|
label = "Guestline"
|
|
required_params = [
|
|
{"key": "hotel_id", "label": "Hotel ID", "help": "e.g. THREEWAYS — from the booking URL ?hotel= parameter"},
|
|
{"key": "collection_id", "label": "Collection ID", "help": "e.g. MT — the path segment before /availability in the booking URL"},
|
|
]
|
|
|
|
@classmethod
|
|
def detect(cls, url: str) -> dict | None:
|
|
# Matches: https://booking.eu.guestline.app/MT/availability?hotel=THREEWAYS
|
|
m = re.search(r'booking\.(?:eu\.)?guestline\.app/([^/?\s]+)/availability\?hotel=([^&\s]+)', url)
|
|
if m:
|
|
return {"collection_id": m.group(1), "hotel_id": m.group(2)}
|
|
return None
|
|
|
|
async def fetch_arrival_dates(self, client, params: dict) -> list[str]:
|
|
hotel_id = params["hotel_id"]
|
|
today = date.today()
|
|
url = f"https://booking.eu.guestline.app/api/availabilities/{hotel_id}/arrivals"
|
|
r = await client.get(url, params={
|
|
"month": today.month, "year": today.year,
|
|
"adults": 2, "children": 0, "count": 12,
|
|
}, headers=HEADERS, timeout=20)
|
|
r.raise_for_status()
|
|
return [a["date"] for a in r.json().get("arrivals", [])]
|
|
|
|
async def fetch_night_rates(self, client, params: dict, arrival: str, nights: int = 1) -> list[dict]:
|
|
hotel_id = params["hotel_id"]
|
|
collection_id = params.get("collection_id", "MT")
|
|
dep = (date.fromisoformat(arrival) + timedelta(days=nights)).isoformat()
|
|
url = f"https://booking.eu.guestline.app/api/availabilities/{collection_id}/{hotel_id}"
|
|
r = await client.get(url, params={
|
|
"arrival": arrival, "departure": dep, "adults": 2, "children": 0,
|
|
}, headers=HEADERS, timeout=20)
|
|
if r.status_code == 404:
|
|
return []
|
|
r.raise_for_status()
|
|
return r.json().get("rooms", [])
|