booking

package
v0.0.0-...-7bbcbd2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 17, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

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

View Source
const BaseURL = "https://" + Host

BaseURL is the root every request is built from.

View Source
const DefaultCacheTTL = 24 * time.Hour

DefaultCacheTTL is how long a cached response stays fresh by default.

View Source
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.

View Source
const Host = "www.booking.com"

Host is the site this client talks to, and the host the URI driver in domain.go claims.

Variables

View Source
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

func Defaults(c *kit.Config)

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.

func Identity

func Identity() kit.Identity

Identity is the fixed description of the booking CLI, shared by the domain and the standalone composition root so help and version read the same everywhere.

func URLFor

func URLFor(kind, id string) string

URLFor builds the canonical live URL for a (kind, id), or "" if it cannot.

Types

type Client

type Client struct {
	HTTP *http.Client
	// contains filtered or unexported fields
}

Client reads public Booking.com data over HTTP.

func ClientFromConfig

func ClientFromConfig(cfg kit.Config) *Client

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 NewClient

func NewClient(cfg Config) *Client

NewClient returns a Client configured from cfg.

func (*Client) GetDestination

func (c *Client) GetDestination(ctx context.Context, ref string) (*Destination, error)

GetDestination fetches one landing page and returns its node.

func (*Client) GetProperty

func (c *Client) GetProperty(ctx context.Context, ref string) (*Property, error)

GetProperty fetches a property by id ("<cc>/<slug>") or URL and returns it.

func (*Client) ListChildren

func (c *Client) ListChildren(ctx context.Context, ref string, limit int) ([]*Destination, error)

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

func (c *Client) ListProperties(ctx context.Context, ref string, limit int) ([]*Property, error)

ListProperties returns the property cards linked from a landing page.

func (*Client) Reviews

func (c *Client) Reviews(ctx context.Context, ref string, limit int) ([]*Review, error)

Reviews returns up to limit reviews for a property by id ("<cc>/<slug>") or URL.

func (*Client) Search

func (c *Client) Search(ctx context.Context, dest string, limit int) ([]*Property, error)

Search returns up to limit property cards for a free-text destination.

func (*Client) Sitemap

func (c *Client) Sitemap(ctx context.Context, kind string, limit int) ([]*Seed, error)

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

func (c *Client) Sitemaps(ctx context.Context, limit int) ([]*SitemapIndex, error)

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.

func (*Client) Suggest

func (c *Client) Suggest(ctx context.Context, prefix string, limit int) ([]*Suggestion, error)

Suggest returns up to limit autocomplete matches for a prefix.

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.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns the baseline settings.

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

func (Domain) Classify(input string) (uriType, id string, err error)

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) Locate

func (Domain) Locate(uriType, id string) (string, error)

Locate is the inverse: the live https URL for a (type, id).

func (Domain) Register

func (Domain) Register(app *kit.App)

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.

func Classify

func Classify(input string) Ref

Classify resolves a reference offline. It accepts a full Booking.com URL, a path, or a bare id ("gb/the-savoy", "city/us/orlando").

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL