Documentation
¶
Overview ¶
Package booking is the library behind the booking command line: the HTTP client, the offline reference layer, and the typed records read from public Booking.com surfaces.
Booking.com has one web plane with two reliability tiers. The destination estate (country, region, city, district, landmark, and airport landing pages) is built to be crawled and reads from anywhere; the interactive client (the property page, search, reviews, and autocomplete) is fronted by a bot manager and is best-effort from a datacenter. The Client below GETs both, paces and retries politely, caches on disk, and turns a walled response into ErrBlocked before any parser sees it. There is no API key: every surface is anonymous.
Index ¶
- Constants
- Variables
- func Defaults(c *kit.Config)
- func Identity() kit.Identity
- func URLFor(kind, id string) string
- type Client
- func (c *Client) GetDestination(ctx context.Context, ref string) (*Destination, error)
- func (c *Client) GetProperty(ctx context.Context, ref string) (*Property, error)
- func (c *Client) ListChildren(ctx context.Context, ref string, limit int) ([]*Destination, error)
- func (c *Client) ListProperties(ctx context.Context, ref string, limit int) ([]*Property, error)
- func (c *Client) Reviews(ctx context.Context, ref string, limit int) ([]*Review, error)
- func (c *Client) Search(ctx context.Context, dest string, limit int) ([]*Property, error)
- func (c *Client) Sitemap(ctx context.Context, kind string, limit int) ([]*Seed, error)
- func (c *Client) Sitemaps(ctx context.Context, limit int) ([]*SitemapIndex, error)
- func (c *Client) Suggest(ctx context.Context, prefix string, limit int) ([]*Suggestion, error)
- type Config
- type Destination
- type Domain
- type Property
- type Ref
- type Review
- type Seed
- type SitemapIndex
- type Suggestion
Constants ¶
const BaseURL = "https://" + Host
BaseURL is the root every request is built from.
const DefaultCacheTTL = 24 * time.Hour
DefaultCacheTTL is how long a cached response stays fresh by default.
const DefaultUserAgent = "booking-cli/0.1 (+https://github.com/tamnd/booking-cli)"
DefaultUserAgent identifies the client to Booking.com. It is honest: it names the tool rather than impersonating a browser or a specific search crawler.
const Host = "www.booking.com"
Host is the site this client talks to, and the host the URI driver in domain.go claims.
Variables ¶
var ( // ErrBlocked is the bot wall: a 403, a connection reset, a challenge or // interstitial body, or a gated JSON envelope. It maps to need-auth (exit 4), // with a remedy of retrying from a residential or mobile connection or reading // the destination estate, which is not gated. ErrBlocked = errors.New("blocked by Booking.com's bot wall: retry from a residential or mobile connection, or read the destination estate (country/region/city pages), which is not gated") // ErrNotFound is a missing place or property (a 404). Maps to exit 6. ErrNotFound = errors.New("not found") // ErrRateLimited is a sustained 429 after retries. Maps to exit 5. ErrRateLimited = errors.New("rate limited by Booking.com: slow down with --rate or try again later") // ErrUsage is a bad argument that the library catches (a malformed date, an // unrecognized reference). Maps to exit 2. ErrUsage = errors.New("usage") )
Sentinel errors the library returns; domain.go's mapErr turns each into the kit error kind that carries the right exit code (see the spec section 4.4).
Functions ¶
func Defaults ¶
Defaults seeds the framework baseline with booking's own values, so an unset --rate or --timeout uses the booking default rather than the generic kit one.
Types ¶
type Client ¶
Client reads public Booking.com data over HTTP.
func ClientFromConfig ¶
ClientFromConfig maps the framework config onto a booking.Config and returns a client. There are no credential keys: Booking offers no free public API, so every surface is anonymous.
func (*Client) GetDestination ¶
GetDestination fetches one landing page and returns its node.
func (*Client) GetProperty ¶
GetProperty fetches a property by id ("<cc>/<slug>") or URL and returns it.
func (*Client) ListChildren ¶
ListChildren returns the destination nodes linked from a landing page. A country page links down to its regions and cities and also across to peer countries, so this returns both: the extra peer edges are what let a crawl reach every country from any one of them. Each node's ParentRef is set by its own place in the tree rather than to this page, so a peer country is not given a false parent.
func (*Client) ListProperties ¶
ListProperties returns the property cards linked from a landing page.
func (*Client) Reviews ¶
Reviews returns up to limit reviews for a property by id ("<cc>/<slug>") or URL.
func (*Client) Sitemap ¶
Sitemap returns up to limit seeds for a kind by reading its sitemap index and the locale's shards. It is the crawl root: every seed names a live landing page and, when the page maps to a record, the edge into it.
func (*Client) Sitemaps ¶
Sitemaps lists the sitemap indexes Booking advertises in robots.txt, the root-of-roots: each record names one index, the kind it enumerates, and the category of page it covers. SeedsRef links into the sitemap op for that kind, so a crawl walks from here into every backbone the site publishes.
type Config ¶
type Config struct {
UserAgent string
Delay time.Duration // minimum gap between requests
Retries int // retries on 429/5xx
Timeout time.Duration // per-request timeout
Locale string // language/locale for names, dates, prices, e.g. "en-us"
Currency string // ISO 4217 price currency, e.g. "USD"
// Search occupancy and dates. A price is filled only when CheckIn and
// CheckOut are both set; otherwise the price fields stay empty.
CheckIn string // YYYY-MM-DD
CheckOut string // YYYY-MM-DD
Adults int
Children int
Rooms int
CacheDir string
NoCache bool
CacheTTL time.Duration
Refresh bool // refetch and rewrite the cache, ignoring any hit
}
Config is the resolved settings a Client reads. domain.go's ClientFromConfig maps the framework's kit.Config onto this, so the standalone binary and a host pace, identify, and locate themselves the same way. There are no credential fields: Booking exposes no free public API key, so every surface is anonymous.
type Destination ¶
type Destination struct {
ID string `json:"id" kit:"id"` // "city/us/orlando", "country/us"
Name string `json:"name,omitempty"` //
Kind string `json:"kind,omitempty"` // country, region, city, district, landmark, airport
Country string `json:"country,omitempty"` // country name or code
Region string `json:"region,omitempty" table:"-"`
PropertyCount int `json:"property_count,omitempty"` // properties Booking lists here
Lat float64 `json:"lat,omitempty" table:"-"`
Lng float64 `json:"lng,omitempty" table:"-"`
URL string `json:"url"`
ParentRef string `json:"parent_ref,omitempty" table:"-" kit:"link,kind=booking/destination"` // up the tree
ChildrenRef string `json:"children_ref,omitempty" table:"-" kit:"link,kind=booking/destinations"` // = ID, the child nodes
PropertiesRef string `json:"properties_ref,omitempty" table:"-" kit:"link,kind=booking/properties"` // = ID, the properties here
SearchRef string `json:"search_ref,omitempty" table:"-" kit:"link,kind=booking/search"` // = Name, a free-text search
}
Destination is one node of Booking's geographic tree, emitted by destination (one node) and, as elements, by destinations (a node's children). The id is "<kind>/<cc>[/<slug>]", e.g. "country/us", "region/us/florida", "city/us/orlando". ChildrenRef and PropertiesRef carry the node's own id so a host follows them into the destinations and properties list authorities, and ParentRef climbs the tree, so a crawl walks the taxonomy in both directions.
type Domain ¶
type Domain struct{}
Domain is the booking driver. It carries no state; the per-run client is built by the factory Register hands kit.
func (Domain) Classify ¶
Classify turns any accepted input into the canonical (type, id), so `ant resolve` and `ant url` touch no network.
func (Domain) Info ¶
func (Domain) Info() kit.DomainInfo
Info describes the scheme, the hostnames a pasted link is matched against, and the identity reused for the binary's help and version.
func (Domain) Register ¶
Register installs the client factory and every operation onto app. A resolver op (Single) names its own record type and answers `ant get`; a List op enumerates a parent resource's members and answers `ant ls`. Each list op names its own collection authority, distinct from the property and destination resolvers, so booking://search/<term>, booking://reviews/<id>, booking://destinations/<ref>, booking://properties/<ref>, and booking://suggest/<prefix> each reach the right op rather than shadowing one another.
type Property ¶
type Property struct {
ID string `json:"id" kit:"id"` // "<cc>/<slug>", e.g. "gb/the-savoy"
Name string `json:"name,omitempty" table:",truncate"`
Type string `json:"type,omitempty"` // hotel, apartment, hostel, resort, villa, guesthouse, bnb
Stars int `json:"stars,omitempty"` // hotel class, 1 to 5
Rating float64 `json:"rating,omitempty"` // review score, 0 to 10
ReviewCount int `json:"review_count,omitempty"` //
ReviewWord string `json:"review_word,omitempty" table:"-"` // "Superb", "Fabulous", localized
Price float64 `json:"price,omitempty"` // nightly, only with dates
Total float64 `json:"total,omitempty" table:"-"` // stay total, only with dates
Currency string `json:"currency,omitempty"` // ISO 4217, when price is set
PriceRange string `json:"price_range,omitempty" table:"-"` // the schema.org "$$" band, when the island carries it
Phone string `json:"phone,omitempty" table:"-"` // telephone from the island
Map string `json:"map,omitempty" table:"-"` // hasMap link from the island
Street string `json:"street,omitempty" table:"-"`
City string `json:"city,omitempty"`
Region string `json:"region,omitempty" table:"-"`
Zip string `json:"zip,omitempty" table:"-"`
Country string `json:"country,omitempty" table:"-"`
DisplayAddress []string `json:"display_address,omitempty" table:"-"`
Lat float64 `json:"lat,omitempty" table:"-"`
Lng float64 `json:"lng,omitempty" table:"-"`
CheckIn string `json:"check_in,omitempty" table:"-"` // property check-in window
CheckOut string `json:"check_out,omitempty" table:"-"`
Description string `json:"description,omitempty" table:",truncate" kit:"body"`
Amenities []string `json:"amenities,omitempty" table:"-"`
Image string `json:"image,omitempty" table:",truncate"`
Photos []string `json:"photos,omitempty" table:"-"`
URL string `json:"url"`
ReviewsRef string `json:"reviews_ref,omitempty" table:"-" kit:"link,kind=booking/reviews"` // = ID
DestinationRef string `json:"destination_ref,omitempty" table:"-" kit:"link,kind=booking/destination"` // the city node, "city/<cc>/<slug>"
}
Property is a Booking.com property, emitted by search (as a card) and by property (full detail from the page's JSON-LD island). The id is "<cc>/<slug>", the country code and slug in /hotel/<cc>/<slug>.html, so a card and a direct read address the same record identically.
type Ref ¶
type Ref struct {
Input string `json:"input"`
Kind string `json:"kind"`
ID string `json:"id"`
URL string `json:"url"`
}
Ref is the result of `booking ref id`: the canonical (kind, id) a reference resolves to, plus the live URL, all without touching the network.
type Review ¶
type Review struct {
ID string `json:"id" kit:"id"`
Author string `json:"author,omitempty"`
Country string `json:"country,omitempty"` // reviewer's country
Score float64 `json:"score,omitempty"` // 0 to 10
Date string `json:"date,omitempty"`
Title string `json:"title,omitempty" table:",truncate"`
Positive string `json:"positive,omitempty" table:"-"` // what the guest liked
Negative string `json:"negative,omitempty" table:"-"` // what the guest disliked
Text string `json:"text,omitempty" table:"-" kit:"body"` // positive + negative, for cat/export
RoomType string `json:"room_type,omitempty" table:"-"`
Nights int `json:"nights,omitempty" table:"-"`
TravelerType string `json:"traveler_type,omitempty"` // couple, family, solo, group, business
Language string `json:"language,omitempty" table:"-"`
Property string `json:"property,omitempty" table:"-" kit:"link,kind=booking/property"` // = "<cc>/<slug>"
}
Review is one review of a property, emitted by reviews. Booking splits each review into what the guest liked and what they disliked; the property page's JSON-LD island carries the combined body, which fills Text, while Positive and Negative are filled only when a source actually splits them, so the body is never mislabeled as praise. Property is the edge back to the reviewed property.
type Seed ¶
type Seed struct {
URL string `json:"url" kit:"id"` // the live landing-page URL, the unique key
Kind string `json:"kind,omitempty"` // the sitemap kind the seed came from, e.g. country, hotel, hotel-review, themed-city-ski
ID string `json:"id,omitempty"` // the resolved id, "<cc>/<slug>" or "<kind>/<cc>[/<slug>]", empty for a page with no typed record
Lastmod string `json:"lastmod,omitempty"` // the sitemap's last-modified date, when present
Destination string `json:"destination,omitempty" table:"-" kit:"link,kind=booking/destination"` // a place seed
Property string `json:"property,omitempty" table:"-" kit:"link,kind=booking/property"` // a hotel or hotel-review seed
}
Seed is one entry from Booking's sitemaps, emitted by sitemap. The sitemaps are the reconstruction backbone: robots.txt advertises a per-kind index, each index lists per-language url shards, and each shard enumerates every landing page of that kind. A Seed names one such page (kind, id, url, lastmod) and carries the edge into the rest of the graph: a place seed fills Destination, a hotel or hotel-review seed fills Property. A page outside the accommodations graph (an attraction, beach, or themed list) still comes back as a Seed with the URL and lastmod and no edge, so no page is dropped. A crawl that walks the sitemaps and then follows those edges reconstructs the reachable public estate from nothing, no prior knowledge of any country or property required.
type SitemapIndex ¶
type SitemapIndex struct {
URL string `json:"url" kit:"id"` // the index URL, the unique key
Kind string `json:"kind,omitempty"` // the sitemap kind, e.g. "country", "hotel-review", "themed-city-ski"
Category string `json:"category,omitempty"` // place, property, reviews, attraction, beach, theme, car, flight, article, other
SeedsRef string `json:"seeds_ref,omitempty" table:"-" kit:"link,kind=booking/sitemap"` // = Kind, the seeds in this index
}
SitemapIndex is one published sitemap index, emitted by sitemaps. Booking lists every index in robots.txt, and this is the root of the whole backbone: each record names the index URL, the kind it enumerates, and the category of page it covers. SeedsRef links into the sitemap operation for that kind, so a crawl walks from here into every backbone the site publishes and on into the records. The few language master indexes that do not fit the per-kind template carry the URL only, with no Kind or SeedsRef.
type Suggestion ¶
type Suggestion struct {
Query string `json:"query"` // the prefix that was queried
Text string `json:"text" kit:"id"` // the suggested label
Kind string `json:"kind,omitempty"` // city, region, district, country, landmark, airport, hotel
Country string `json:"country,omitempty"`
PropertyCount int `json:"property_count,omitempty"` // Booking's hotel count for the match
DestID string `json:"dest_id,omitempty" table:"-"` // Booking's internal destination id
DestType string `json:"dest_type,omitempty" table:"-"`
Lat float64 `json:"lat,omitempty" table:"-"`
Lng float64 `json:"lng,omitempty" table:"-"`
SearchRef string `json:"search_ref,omitempty" table:"-" kit:"link,kind=booking/search"` // = Text
Destination string `json:"destination,omitempty" table:"-" kit:"link,kind=booking/destination"` // a place match, "<kind>/<cc>/<slug>"
Property string `json:"property,omitempty" table:"-" kit:"link,kind=booking/property"` // a hotel match, "<cc>/<slug>"
}
Suggestion is one autocomplete entry, emitted by suggest. Kind names what the suggestion points at; the matching edge is filled accordingly. A place suggestion fills Destination and SearchRef; a hotel suggestion fills Property. Every suggestion fills SearchRef so a prefix can always fan into a search.