feat(trip): add Trip.com international adapter#2158
Merged
Conversation
Trip.com is the English-facing sibling of the ctrip adapter. trip flight searches worldwide one-way flights, browser-mode + cookie like ctrip flight. Results are read from .result-item cards by stable data-testid anchors rather than positional innerText; incomplete cards are dropped, not sentinel-filled. Closes jackwener#2157
Single-hotel profile from the detail-page SSR (same shape ctrip hotel uses); also documents the existing hotel-search command.
Reuses the shared .result-item flight extractor against a triptype=rt search URL.
The 1-50 limit parser is shared by hotel-search and both flight commands, so a neutral name reads truer than the flight-specific one.
Benjamin-eecs
marked this pull request as ready for review
July 19, 2026 23:04
Anchors on each things-to-do card's stable detail link (name + per-row url) and reads rating/reviews/booked/price by data-format pattern, since the cards use hashed CSS-module classes.
Reads the per-country SEO route timetable (departure/arrival times, stations, duration, changes) by stable class fields; per-journey fares sit behind the booking step.
Benjamin-eecs
marked this pull request as draft
July 20, 2026 02:26
Benjamin-eecs
marked this pull request as ready for review
July 20, 2026 16:55
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds a new
tripadapter for Trip.com, the English-facing international sibling of the existingctripadapter (same company, Trip.com Group). It covers Trip.com's travel verticals end to end in English / USD, reading rows from stabledata-testid/ class anchors or SSR, dropping incomplete rows rather than surfacing blanks, and raisingAuthRequiredErroron a verification gate. Most commands are browser-mode plusStrategy.COOKIElike thectrippeers;searchandpackageresolve through public unsigned endpoints and need no browser session.Commands in this PR (each live-verified, output below):
search(public destination / airport suggest; resolves the ids the other commands take)flight(one-way),flight-round(round-trip)hotel-search(city listing),hotel(single-hotel detail)attraction(tickets, tours, experiences)train(route timetable)car(car-rental listing by city)transfer(city + airport vehicle listing)tour(private / group tour packages)package(public; package flight options at the bundle rate)deals(live-promotions hub)On #2157's
esim: Trip.com sells eSIM / SIM data plans as things-to-do products, so they already surface throughattraction(e.g.opencli trip attraction eSIMreturns them, and the ids match the eSIM page's own buy buttons). A separateesimcommand would only hardcode that keyword against the same endpoint and extractor, so it is covered byattractionrather than duplicated.Related issue: Closes #2157.
Type of Change
Checklist
Documentation (if adding/modifying an adapter)
docs/adapters/(if new adapter)docs/adapters/index.mdtable (if new adapter)docs/.vitepress/config.mts(if new adapter)README.md/README.zh-CN.mdwhen command discoverability changedCliErrorsubclasses instead of rawErrorScreenshots / Output
All twelve commands are live-verified against a Trip.com session (English / USD), no verification gate.
Discovery:
searchsearchis a public unsigned POST to Trip.com's POI-suggest endpoint (the same lookup the flight / hotel boxes use, no browser session). Each city keeps its own row and its nearby airports follow, so one call surfaces both thecityIdthe id-based commands take and theairportCodethe flight / transfer commands take.Flights:
flight(one-way)Flights:
flight-round(round-trip)flight-roundreturns the outbound leg priced for the round trip, reusing the same.result-itemextractor against atriptype=rtsearch URL.Hotels:
hotel-search(city listing)Hotels:
hotel(single-hotel detail)hotelreads the SSR__NEXT_DATA__.props.pageProps.hotelDetailResponse, the same shape the mainlandctrip hoteldetail uses; room-level nightly prices load via a post-SSR XHR and are out of scope, sincehotel-searchalready reports a representative nightly price.Attractions & experiences:
attractionThe things-to-do products load into hashed CSS-module cards, so rows anchor on each card's stable
things-to-do/detail/<id>link (name is its text, and it gives a real per-rowurl) and read rating / reviews / booked / price from the card text by data-format pattern; the promo$N offtag is excluded. This covers Trip.com's combined "Attractions & Tours" (tickets, tours, experiences), and, since Trip.com files eSIM / SIM data plans as things-to-do products,#2157's eSIM vertical too (opencli trip attraction eSIM).Trains:
trainTrip.com files train routes as per-country SEO timetable pages, so
--countryis required; the page lists journeys (departure / arrival times, stations, duration, changes) by stable class fields. Per-journey fares sit behind the booking step and are out of scope here.Cars:
carTrip.com files car-rental listings under an SEO path whose text slugs are cosmetic, so only the numeric carhire city id routes the page (
313is San Francisco, discovered via the carhire search box, the same discovery flowhotel-searchuses). Rows come from.card-itemcards read by stable class fields; the daily price is the site's near-term representative rate, and a dated pickup / drop-off quote sits behind the booking step.Airport transfers:
transferTrip.com files airport transfers under an SEO path keyed on the city slug plus the airport IATA code (
airport-transfers/<city>/airport-<iata>/). A city that does not match the airport bounces to the transfer landing, so the command checks the landed path and raisesCommandExecutionErrorrather than returning the generic landing list (verified:trip transfer Paris DMKerrors instead of returning Bangkok's list). Rows come from.vehicle-cardcards read by stable class fields; the$N offpromo sits in a separate discount tag and is excluded.Tours:
tourTrip.com serves tour results through a signed POST that only fires on a search submit, so rather than replaying the signature the command opens the results page and re-submits the keyword in the page's own search box, letting the page issue its own signed request while a fetch hook captures the
productsresponse.--type private/groupselects the product line; per-departure pricing sits behind the booking step, sopriceis the starting per-person estimate.Flight+hotel packages:
packageTrip.com prices flight+hotel packages through the flight-selection step of the booking flow: a public unsigned POST keyed on the metro city codes plus the destination hotel city id returns the outbound flight options priced at the bundle rate. So
packageresolves both keywords to their city code + id through the same POI searchsearchuses, then lists those flights with no browser session. The specific hotel is chosen in a later booking step, so per-row hotel detail and the return leg (which rides on the hotel checkout date) are out of scope here.Deals:
dealsdealsreads Trip.com's "Today's Top Deals" hub, one row per running campaign (title, offer line, discount parsed from the text, and the canonical campaign URL with the per-session promo tracking stripped). Tiles without a title or offer are dropped, and rows are deduped by campaign URL.Notes for review:
ctrip, not a flag on it: different domain (trip.com), language (English), currency (USD), and DOM. This matches how the repo keeps region variants apart (e.g.rednotevsxiaohongshu).data-testidcards (flight/flight-round), stable class fields (hotel-search,car,transfer,deals), the SSR detail response (hotel), the per-product detail link (attraction), timetable class cells (train), the page's own captured signed request (tour), or a public unsigned JSON endpoint (search,package). Incomplete rows are dropped, never surfaced with sentinels;priceand numeric fields staynullwhen non-numeric.AuthRequiredError. Rendered-but-unparseable drift raisesCommandExecutionError(render timeout, malformed extraction, atransferlanding-page bounce, apackageresponse whose groups carry no parseable itinerary, adealshub that renders but parses no tiles, or atourcapture whose products carry no name), keeping it distinct from a genuinely empty result, which raisesEmptyResultError(e.g.tourreads the page's0 routes found).--limit(1-50) is validated up front with no silent clamp.clis/trip/trip.test.jspasses 110 / 110 locally (arg validation, the verification / timeout / empty / drift ladder per command, the public-endpoint fetch / invalid-JSON / drift paths, and JSDOM / SSR extraction).npm run check:typed-error-lintandnpm run check:silent-column-dropboth reportnew=0;scripts/check-doc-coverage.sh --strictpasses.