imprint-mcp 0.5.1 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/examples/google-hotels/README.md +40 -15
- package/examples/google-hotels/get_hotel_details/backends.json +16 -0
- package/examples/google-hotels/get_hotel_details/index.ts +122 -0
- package/examples/google-hotels/get_hotel_details/parser.ts +275 -0
- package/examples/google-hotels/get_hotel_details/playbook.yaml +40 -0
- package/examples/google-hotels/get_hotel_details/request-transform.ts +67 -0
- package/examples/google-hotels/get_hotel_details/workflow.json +67 -0
- package/examples/google-hotels/get_hotel_photos/backends.json +16 -0
- package/examples/google-hotels/get_hotel_photos/index.ts +90 -0
- package/examples/google-hotels/get_hotel_photos/parser.ts +75 -0
- package/examples/google-hotels/get_hotel_photos/playbook.yaml +23 -0
- package/examples/google-hotels/get_hotel_photos/request-transform.ts +27 -0
- package/examples/google-hotels/get_hotel_photos/workflow.json +44 -0
- package/examples/google-hotels/get_hotel_reviews/backends.json +16 -0
- package/examples/google-hotels/get_hotel_reviews/index.ts +14 -17
- package/examples/google-hotels/get_hotel_reviews/parser.ts +177 -105
- package/examples/google-hotels/get_hotel_reviews/playbook.yaml +9 -54
- package/examples/google-hotels/get_hotel_reviews/workflow.json +10 -13
- package/examples/google-hotels/get_travel_area_boundaries/backends.json +16 -0
- package/examples/google-hotels/get_travel_area_boundaries/index.ts +104 -0
- package/examples/google-hotels/get_travel_area_boundaries/package.json +9 -0
- package/examples/google-hotels/get_travel_area_boundaries/parser.ts +127 -0
- package/examples/google-hotels/get_travel_area_boundaries/playbook.yaml +38 -0
- package/examples/google-hotels/get_travel_area_boundaries/request-transform.ts +52 -0
- package/examples/google-hotels/get_travel_area_boundaries/workflow.json +54 -0
- package/examples/google-hotels/list_hotel_booking_options/backends.json +16 -0
- package/examples/google-hotels/list_hotel_booking_options/index.ts +123 -0
- package/examples/google-hotels/list_hotel_booking_options/package.json +9 -0
- package/examples/google-hotels/list_hotel_booking_options/parser.ts +308 -0
- package/examples/google-hotels/list_hotel_booking_options/playbook.yaml +95 -0
- package/examples/google-hotels/list_hotel_booking_options/request-transform.ts +77 -0
- package/examples/google-hotels/list_hotel_booking_options/workflow.json +67 -0
- package/examples/google-hotels/search_hotel_web_links/backends.json +16 -0
- package/examples/google-hotels/search_hotel_web_links/index.ts +90 -0
- package/examples/google-hotels/search_hotel_web_links/package.json +9 -0
- package/examples/google-hotels/search_hotel_web_links/parser.ts +96 -0
- package/examples/google-hotels/search_hotel_web_links/playbook.yaml +53 -0
- package/examples/google-hotels/search_hotel_web_links/request-transform.ts +17 -0
- package/examples/google-hotels/search_hotel_web_links/workflow.json +43 -0
- package/examples/google-hotels/search_hotels/backends.json +16 -0
- package/examples/google-hotels/search_hotels/index.ts +32 -102
- package/examples/google-hotels/search_hotels/parser.ts +237 -216
- package/examples/google-hotels/search_hotels/playbook.yaml +177 -42
- package/examples/google-hotels/search_hotels/request-transform.ts +101 -170
- package/examples/google-hotels/search_hotels/workflow.json +24 -73
- package/examples/google-hotels/suggest_hotel_destinations/backends.json +16 -0
- package/examples/google-hotels/{autocomplete_hotel_location → suggest_hotel_destinations}/index.ts +30 -19
- package/examples/google-hotels/suggest_hotel_destinations/package.json +9 -0
- package/examples/google-hotels/suggest_hotel_destinations/parser.ts +138 -0
- package/examples/google-hotels/suggest_hotel_destinations/playbook.yaml +47 -0
- package/examples/google-hotels/suggest_hotel_destinations/request-transform.ts +33 -0
- package/examples/google-hotels/suggest_hotel_destinations/workflow.json +44 -0
- package/examples/southwest/README.md +31 -70
- package/examples/southwest/get_account_details/backends.json +22 -0
- package/examples/southwest/get_account_details/credentials.manifest.json +17 -0
- package/examples/southwest/get_account_details/index.ts +138 -0
- package/examples/{google-hotels/_shared → southwest/get_account_details}/package.json +2 -2
- package/examples/southwest/get_account_details/parser.ts +136 -0
- package/examples/southwest/get_account_details/playbook.yaml +19 -0
- package/examples/southwest/get_account_details/workflow.json +93 -0
- package/examples/southwest/get_flight_status/backends.json +21 -0
- package/examples/southwest/get_flight_status/index.ts +130 -0
- package/examples/southwest/get_flight_status/package.json +9 -0
- package/examples/southwest/get_flight_status/parser.ts +138 -0
- package/examples/southwest/get_flight_status/playbook.yaml +58 -0
- package/examples/southwest/get_flight_status/workflow.json +77 -0
- package/examples/southwest/get_low_fare_calendar/backends.json +21 -0
- package/examples/southwest/get_low_fare_calendar/index.ts +134 -0
- package/examples/southwest/get_low_fare_calendar/package.json +9 -0
- package/examples/southwest/get_low_fare_calendar/parser.ts +107 -0
- package/examples/southwest/get_low_fare_calendar/playbook.yaml +41 -0
- package/examples/southwest/get_low_fare_calendar/workflow.json +81 -0
- package/examples/southwest/list_upcoming_trips/backends.json +22 -0
- package/examples/southwest/list_upcoming_trips/credentials.manifest.json +17 -0
- package/examples/southwest/list_upcoming_trips/index.ts +123 -0
- package/examples/southwest/list_upcoming_trips/package.json +9 -0
- package/examples/southwest/list_upcoming_trips/parser.ts +160 -0
- package/examples/southwest/list_upcoming_trips/playbook.yaml +24 -0
- package/examples/southwest/list_upcoming_trips/workflow.json +78 -0
- package/examples/southwest/search_flights/backends.json +21 -0
- package/examples/southwest/search_flights/index.ts +150 -0
- package/examples/southwest/search_flights/package.json +9 -0
- package/examples/southwest/search_flights/parser.ts +214 -0
- package/examples/southwest/search_flights/playbook.yaml +52 -0
- package/examples/southwest/search_flights/workflow.json +88 -0
- package/package.json +2 -1
- package/prompts/audit-agent.md +1 -0
- package/prompts/auth-compile-agent.md +149 -158
- package/prompts/build-planning.md +33 -9
- package/prompts/compile-agent.md +6 -2
- package/prompts/tool-candidate-detection.md +9 -29
- package/prompts/tool-planning.md +2 -0
- package/src/imprint/audit.ts +34 -13
- package/src/imprint/auth-compile-agent.ts +215 -155
- package/src/imprint/auth-compile-tools.ts +300 -118
- package/src/imprint/auth-verifier.ts +167 -180
- package/src/imprint/backend-cache.ts +114 -0
- package/src/imprint/backend-ladder.ts +253 -169
- package/src/imprint/build-plan.ts +276 -66
- package/src/imprint/cdp-browser-fetch.ts +893 -94
- package/src/imprint/claude-cli-compile.ts +8 -4
- package/src/imprint/codex-cli-compile.ts +335 -126
- package/src/imprint/compile-agent-types.ts +15 -10
- package/src/imprint/compile-agent.ts +18 -2
- package/src/imprint/compile-tools.ts +607 -137
- package/src/imprint/compile.ts +28 -6
- package/src/imprint/concurrency.ts +26 -3
- package/src/imprint/credential-store.ts +57 -44
- package/src/imprint/cron.ts +0 -3
- package/src/imprint/emit.ts +3 -1
- package/src/imprint/llm-url.ts +69 -0
- package/src/imprint/llm.ts +371 -49
- package/src/imprint/login.ts +107 -25
- package/src/imprint/mcp-compile-server.ts +82 -47
- package/src/imprint/mcp-server.ts +152 -46
- package/src/imprint/probe-backends.ts +21 -115
- package/src/imprint/recording-request.ts +36 -0
- package/src/imprint/request-capture.ts +42 -18
- package/src/imprint/runtime.ts +437 -334
- package/src/imprint/session-diff.ts +14 -1
- package/src/imprint/stealth-fetch.ts +27 -1
- package/src/imprint/stealth-token-cache.ts +2 -2
- package/src/imprint/teach-plan.ts +2 -0
- package/src/imprint/teach-state.ts +32 -10
- package/src/imprint/teach.ts +264 -129
- package/src/imprint/telemetry.ts +3 -0
- package/src/imprint/tool-candidates.ts +14 -13
- package/src/imprint/tool-loader.ts +12 -4
- package/src/imprint/tool-plan.ts +29 -3
- package/src/imprint/tracing.ts +18 -2
- package/src/imprint/types.ts +100 -68
- package/examples/google-hotels/_shared/batchexecute.ts +0 -73
- package/examples/google-hotels/_shared/freq.ts +0 -158
- package/examples/google-hotels/autocomplete_hotel_location/parser.ts +0 -71
- package/examples/google-hotels/autocomplete_hotel_location/playbook.yaml +0 -36
- package/examples/google-hotels/autocomplete_hotel_location/request-transform.ts +0 -37
- package/examples/google-hotels/autocomplete_hotel_location/workflow.json +0 -36
- package/examples/google-hotels/get_hotel_booking_options/index.ts +0 -143
- package/examples/google-hotels/get_hotel_booking_options/parser.ts +0 -271
- package/examples/google-hotels/get_hotel_booking_options/playbook.yaml +0 -154
- package/examples/google-hotels/get_hotel_booking_options/request-transform.ts +0 -154
- package/examples/google-hotels/get_hotel_booking_options/workflow.json +0 -84
- package/examples/google-hotels/get_hotel_reviews/request-transform.ts +0 -42
- package/examples/southwest/search_southwest_flights/backends.json +0 -23
- package/examples/southwest/search_southwest_flights/cron.json +0 -19
- package/examples/southwest/search_southwest_flights/index.ts +0 -128
- package/examples/southwest/search_southwest_flights/playbook.yaml +0 -46
- package/examples/southwest/search_southwest_flights/workflow.json +0 -72
- /package/examples/google-hotels/{autocomplete_hotel_location → get_hotel_details}/package.json +0 -0
- /package/examples/google-hotels/{get_hotel_booking_options → get_hotel_photos}/package.json +0 -0
|
@@ -1,24 +1,47 @@
|
|
|
1
|
-
# Google Hotels
|
|
1
|
+
# Google Hotels - `imprint-google-hotels`
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Generated Google Hotels MCP example. Every tool directory here was produced
|
|
4
|
+
> from a recorded Google Hotels session and refreshed from the verified
|
|
5
|
+
> generated artifacts under `~/.imprint/google-hotels`.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
An 8-tool MCP server for Google Hotels search, selected-hotel details,
|
|
8
|
+
booking options, reviews, photos, area boundaries, and related web links.
|
|
9
|
+
|
|
10
|
+
Fresh live verification on July 9, 2026:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
bun test ~/.imprint/google-hotels/*/integration.test.ts --timeout 180000
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Result: `14 pass`, `0 fail`, `66 expect() calls` across `8` generated
|
|
17
|
+
workflow integration suites. All generated `backends.json` files prefer
|
|
18
|
+
`fetch`, with browser-backed rungs available as fallbacks.
|
|
6
19
|
|
|
7
20
|
## Tools
|
|
8
21
|
|
|
9
22
|
| Tool | What it does | Notes |
|
|
10
23
|
|---|---|---|
|
|
11
|
-
| `
|
|
12
|
-
| `search_hotels` | Search
|
|
13
|
-
| `
|
|
14
|
-
| `
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
| `suggest_hotel_destinations` | Return destination and lodging autocomplete suggestions for a query. | Replacement for the older autocomplete example. |
|
|
25
|
+
| `search_hotels` | Search lodging results by destination, dates, minimum price, hotel class, and property type. | Emits reusable `hotel_token` / `result_token` values. |
|
|
26
|
+
| `list_hotel_booking_options` | List booking providers and prices for a selected hotel stay. | Consumes a `hotel_token`, typically from `search_hotels`. |
|
|
27
|
+
| `get_hotel_reviews` | Fetch review data for a selected hotel. | Consumes a `hotel_token`, typically from `search_hotels`. |
|
|
28
|
+
| `get_hotel_photos` | Fetch photo thumbnail URLs for a selected hotel. | Consumes a `hotel_token`, typically from `search_hotels`. |
|
|
29
|
+
| `get_hotel_details` | Fetch detailed information for a selected hotel or vacation rental. | Consumes a `hotel_token`. |
|
|
30
|
+
| `get_travel_area_boundaries` | Fetch map boundary or area geometry data for a destination. | Uses place id and coordinates. |
|
|
31
|
+
| `search_hotel_web_links` | Search web links for a selected hotel name. | Useful for official site and related links. |
|
|
32
|
+
|
|
33
|
+
## How It Was Compiled
|
|
34
|
+
|
|
35
|
+
- **Protocol**: Google's `batchexecute` endpoint returns nested-array payloads.
|
|
36
|
+
Each tool includes its generated request transform and parser.
|
|
37
|
+
- **Producer to consumer flow**: `search_hotels` emits selected-result tokens
|
|
38
|
+
that booking, reviews, photos, and detail tools can consume.
|
|
39
|
+
- **Backend reliability**: checked-in `backends.json` files record `fetch` as
|
|
40
|
+
the working preferred backend for every tool. Runtime can still fall back to
|
|
41
|
+
browser-backed rungs when needed.
|
|
42
|
+
- **Artifacts per tool**: `workflow.json` (API replay), `index.ts` (MCP tool),
|
|
43
|
+
`parser.ts`, optional `request-transform.ts`, `playbook.yaml` (DOM fallback),
|
|
44
|
+
`package.json`, and `backends.json`.
|
|
22
45
|
|
|
23
46
|
## Install
|
|
24
47
|
|
|
@@ -26,4 +49,6 @@ A 4-tool MCP server for Google Hotels, compiled from a recording of a normal hot
|
|
|
26
49
|
imprint install google-hotels --source examples --platform claude-desktop
|
|
27
50
|
```
|
|
28
51
|
|
|
29
|
-
|
|
52
|
+
Recording-derived defaults such as dates and locations age out. Pass explicit
|
|
53
|
+
dates, destinations, and selected-result tokens when using the tools. See the
|
|
54
|
+
repo [README](../../README.md) and [docs](../../docs/architecture.md).
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"probedAt": "2026-07-09T01:36:48.358Z",
|
|
3
|
+
"imprintVersion": "0.5.1",
|
|
4
|
+
"schemaVersion": 2,
|
|
5
|
+
"workflowHash": "2a7f5ddb6919ec3366885acf4ab4d3d7833b1e5c40888a8c6e34e7617cde0381",
|
|
6
|
+
"capabilityHash": "d0891f676244406c38af43944a1d24d32c068a9d1a75986d260a63bffd29e1b2",
|
|
7
|
+
"preferredOrder": [
|
|
8
|
+
"fetch"
|
|
9
|
+
],
|
|
10
|
+
"results": {
|
|
11
|
+
"fetch": {
|
|
12
|
+
"outcome": "ok",
|
|
13
|
+
"durationMs": 685
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED by `imprint emit` — DO NOT EDIT BY HAND.
|
|
3
|
+
*
|
|
4
|
+
* Tool: get_hotel_details
|
|
5
|
+
* Site: google-hotels
|
|
6
|
+
* Intent: Fetch detailed Google Hotels information for a selected hotel or vacation-rental result.
|
|
7
|
+
*
|
|
8
|
+
* To regenerate: imprint emit ~/.imprint/google-hotels/get_hotel_details/workflow.json --force
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import { dirname, join } from 'node:path';
|
|
13
|
+
import {
|
|
14
|
+
executeWorkflow,
|
|
15
|
+
type CredentialStore,
|
|
16
|
+
} from 'imprint/runtime';
|
|
17
|
+
import type { ToolResult, Workflow } from 'imprint/types';
|
|
18
|
+
|
|
19
|
+
const WORKFLOW: Workflow = {
|
|
20
|
+
"toolName": "get_hotel_details",
|
|
21
|
+
"intent": {
|
|
22
|
+
"description": "Fetch detailed Google Hotels information for a selected hotel or vacation-rental result.",
|
|
23
|
+
"userSaid": "clicked one of hte offerings, saw booking options; changed property type to vacation rentals instead of hotels; changed location to tahoe city"
|
|
24
|
+
},
|
|
25
|
+
"parameters": [
|
|
26
|
+
{
|
|
27
|
+
"name": "hotel_token",
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "Google Hotels selected result token for the hotel, usually emitted by a hotel search result.",
|
|
30
|
+
"verified": false,
|
|
31
|
+
"verifyNote": "annotated"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "destination",
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "Destination context used when opening the hotel result.",
|
|
37
|
+
"default": "tahoe city",
|
|
38
|
+
"verified": false,
|
|
39
|
+
"verifyNote": "annotated"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "check_in_date",
|
|
43
|
+
"type": "string",
|
|
44
|
+
"description": "Check-in date in YYYY-MM-DD format.",
|
|
45
|
+
"default": "2026-08-09",
|
|
46
|
+
"verified": false,
|
|
47
|
+
"verifyNote": "annotated"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "check_out_date",
|
|
51
|
+
"type": "string",
|
|
52
|
+
"description": "Check-out date in YYYY-MM-DD format.",
|
|
53
|
+
"default": "2026-08-16",
|
|
54
|
+
"verified": false,
|
|
55
|
+
"verifyNote": "annotated"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "property_type",
|
|
59
|
+
"type": "string",
|
|
60
|
+
"description": "Property type context, either hotels or vacation_rentals.",
|
|
61
|
+
"default": "vacation_rentals",
|
|
62
|
+
"verified": true
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"requests": [
|
|
66
|
+
{
|
|
67
|
+
"method": "POST",
|
|
68
|
+
"url": "https://www.google.com/_/TravelFrontendUi/data/batchexecute?rpcids=AtySUc&source-path=%2Ftravel%2Fsearch&f.sid=7513562915459271421&bl=boq_travel-frontend-ui_20260527.01_p0&hl=en-US&soc-app=162&soc-platform=1&soc-device=1&_reqid=${generated.epoch_ms}&rt=c",
|
|
69
|
+
"headers": {
|
|
70
|
+
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
|
71
|
+
"X-Same-Domain": "1",
|
|
72
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
73
|
+
"Referer": "https://www.google.com/travel/search?q=${param.destination}",
|
|
74
|
+
"x-goog-ext-190139975-jspb": "[\"US\",\"ZZ\",\"x6c25Q==\"]",
|
|
75
|
+
"x-goog-ext-259736195-jspb": "[\"en-US\",\"US\",\"USD\",2,null,[420],null,null,7,[]]"
|
|
76
|
+
},
|
|
77
|
+
"body": "f.req=${param.destination}|${param.check_in_date}|${param.check_out_date}|${param.property_type}|${param.hotel_token}",
|
|
78
|
+
"effect": "safe"
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
"site": "google-hotels",
|
|
82
|
+
"parserModule": "./parser.ts",
|
|
83
|
+
"requestTransformModule": "./request-transform.ts",
|
|
84
|
+
"liveVerified": true
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export interface GetHotelDetailsInput {
|
|
88
|
+
/** Google Hotels selected result token for the hotel, usually emitted by a hotel search result. */
|
|
89
|
+
hotel_token: string;
|
|
90
|
+
/** Destination context used when opening the hotel result. */
|
|
91
|
+
destination?: string;
|
|
92
|
+
/** Check-in date in YYYY-MM-DD format. */
|
|
93
|
+
check_in_date?: string;
|
|
94
|
+
/** Check-out date in YYYY-MM-DD format. */
|
|
95
|
+
check_out_date?: string;
|
|
96
|
+
/** Property type context, either hotels or vacation_rentals. */
|
|
97
|
+
property_type?: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function getHotelDetails(
|
|
101
|
+
input: GetHotelDetailsInput,
|
|
102
|
+
opts: { credentials?: CredentialStore; fetchImpl?: typeof fetch; initialState?: Record<string, unknown> } = {},
|
|
103
|
+
): Promise<ToolResult> {
|
|
104
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
105
|
+
const params: Record<string, string | number | boolean> = {
|
|
106
|
+
destination: input.destination ?? "tahoe city",
|
|
107
|
+
check_in_date: input.check_in_date ?? "2026-08-09",
|
|
108
|
+
check_out_date: input.check_out_date ?? "2026-08-16",
|
|
109
|
+
property_type: input.property_type ?? "vacation_rentals",
|
|
110
|
+
hotel_token: input.hotel_token,
|
|
111
|
+
};
|
|
112
|
+
return executeWorkflow({
|
|
113
|
+
workflow: WORKFLOW,
|
|
114
|
+
params,
|
|
115
|
+
credentials: opts.credentials,
|
|
116
|
+
fetchImpl: opts.fetchImpl,
|
|
117
|
+
initialState: opts.initialState,
|
|
118
|
+
workflowPath: join(__dirname, 'workflow.json'),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export { WORKFLOW };
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
type Json = null | boolean | number | string | Json[] | { [key: string]: Json };
|
|
2
|
+
|
|
3
|
+
type HotelOutput = {
|
|
4
|
+
hotel: {
|
|
5
|
+
name: string;
|
|
6
|
+
coordinates?: { latitude: number; longitude: number };
|
|
7
|
+
address?: string;
|
|
8
|
+
phone?: string;
|
|
9
|
+
checkInTime?: string;
|
|
10
|
+
checkOutTime?: string;
|
|
11
|
+
ratingBreakdown?: Record<string, number>;
|
|
12
|
+
} | null;
|
|
13
|
+
selectedHotelToken?: string;
|
|
14
|
+
photos: Array<{ url: string; sourceUrl?: string }>;
|
|
15
|
+
amenities: Array<{ name: string; available: boolean; section: string }>;
|
|
16
|
+
nearby: Array<{ name: string; category?: string; rating?: number; reviewCount?: number; distance?: string }>;
|
|
17
|
+
bookingOptions: Array<{ provider: string; url?: string; displayedPrice?: string }>;
|
|
18
|
+
rawSections: string[];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function extract(
|
|
22
|
+
rawResponse: unknown,
|
|
23
|
+
context?: { params: Record<string, string | number | boolean>; responses: unknown[] },
|
|
24
|
+
): unknown {
|
|
25
|
+
const decoded = decodeGoogleBatch(rawResponse);
|
|
26
|
+
if (!decoded) return emptyOutput(context);
|
|
27
|
+
|
|
28
|
+
const hotelRecord = findSection(decoded, '441552390');
|
|
29
|
+
if (!Array.isArray(hotelRecord) || typeof hotelRecord[1] !== 'string' || !hotelRecord[1].trim()) {
|
|
30
|
+
return emptyOutput(context);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const hotelName = hotelRecord[1].trim();
|
|
34
|
+
const hotelDetail = hotelRecord[2];
|
|
35
|
+
const allStrings = collectStrings(hotelRecord);
|
|
36
|
+
const times = findTimePair(hotelRecord);
|
|
37
|
+
|
|
38
|
+
const output: HotelOutput = {
|
|
39
|
+
hotel: {
|
|
40
|
+
name: hotelName,
|
|
41
|
+
coordinates: findCoordinatePair(hotelDetail ?? hotelRecord),
|
|
42
|
+
address: allStrings.find((value) => /\d+\s+.+,\s*[A-Z]{2}\s+\d{5}/.test(value)),
|
|
43
|
+
phone: allStrings.find((value) => /^(?:\+?1\s*)?\(?\d{3}\)?[-\s.]\d{3}[-\s.]\d{4}$/.test(value)),
|
|
44
|
+
checkInTime: times?.[0],
|
|
45
|
+
checkOutTime: times?.[1],
|
|
46
|
+
ratingBreakdown: findRatingBreakdown(hotelRecord),
|
|
47
|
+
},
|
|
48
|
+
selectedHotelToken: findSelectedToken(hotelRecord, context),
|
|
49
|
+
photos: collectPhotos(hotelRecord),
|
|
50
|
+
amenities: collectAmenitySections(hotelRecord),
|
|
51
|
+
nearby: collectNearbyPlaces(hotelRecord, hotelName),
|
|
52
|
+
bookingOptions: collectBookingOptions(hotelRecord),
|
|
53
|
+
rawSections: collectSectionKeys(decoded),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return output;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function emptyOutput(context?: { params: Record<string, string | number | boolean> }): HotelOutput {
|
|
60
|
+
const token = typeof context?.params?.hotel_token === 'string' ? context.params.hotel_token : undefined;
|
|
61
|
+
return { hotel: null, selectedHotelToken: token, photos: [], amenities: [], nearby: [], bookingOptions: [], rawSections: [] };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function decodeGoogleBatch(raw: unknown): Json | undefined {
|
|
65
|
+
if (raw == null) return undefined;
|
|
66
|
+
if (Array.isArray(raw) || typeof raw === 'object') return raw as Json;
|
|
67
|
+
if (typeof raw !== 'string' || raw.trim() === '') return undefined;
|
|
68
|
+
|
|
69
|
+
const lines = raw.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
70
|
+
for (const line of lines) {
|
|
71
|
+
if (!line.startsWith('[[')) continue;
|
|
72
|
+
try {
|
|
73
|
+
const outer = JSON.parse(line) as Json;
|
|
74
|
+
const payload = findWrbPayload(outer);
|
|
75
|
+
if (typeof payload === 'string') return JSON.parse(payload) as Json;
|
|
76
|
+
} catch {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
return JSON.parse(raw) as Json;
|
|
83
|
+
} catch {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function findWrbPayload(value: Json): string | undefined {
|
|
89
|
+
if (!Array.isArray(value)) return undefined;
|
|
90
|
+
if (value[0] === 'wrb.fr' && value[1] === 'AtySUc' && typeof value[2] === 'string') return value[2];
|
|
91
|
+
for (const item of value) {
|
|
92
|
+
const found = findWrbPayload(item);
|
|
93
|
+
if (found) return found;
|
|
94
|
+
}
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function findSection(value: Json, key: string): Json | undefined {
|
|
99
|
+
if (Array.isArray(value)) {
|
|
100
|
+
for (const item of value) {
|
|
101
|
+
const found = findSection(item, key);
|
|
102
|
+
if (found !== undefined) return found;
|
|
103
|
+
}
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
if (value && typeof value === 'object') {
|
|
107
|
+
const objectValue = value as Record<string, Json>;
|
|
108
|
+
if (objectValue[key] !== undefined) return objectValue[key];
|
|
109
|
+
for (const item of Object.values(objectValue)) {
|
|
110
|
+
const found = findSection(item, key);
|
|
111
|
+
if (found !== undefined) return found;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function collectSectionKeys(value: Json, keys = new Set<string>()): string[] {
|
|
118
|
+
if (Array.isArray(value)) {
|
|
119
|
+
for (const item of value) collectSectionKeys(item, keys);
|
|
120
|
+
} else if (value && typeof value === 'object') {
|
|
121
|
+
for (const [key, item] of Object.entries(value)) {
|
|
122
|
+
if (/^\d{6,}$/.test(key)) keys.add(key);
|
|
123
|
+
collectSectionKeys(item, keys);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return [...keys].sort();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function collectStrings(value: Json | undefined, out: string[] = []): string[] {
|
|
130
|
+
if (typeof value === 'string') out.push(value.replace(/\\u0026/g, '&').replace(/\\u003cbr\\u003e/g, '\n'));
|
|
131
|
+
else if (Array.isArray(value)) for (const item of value) collectStrings(item, out);
|
|
132
|
+
else if (value && typeof value === 'object') for (const item of Object.values(value)) collectStrings(item, out);
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function findCoordinatePair(value: Json | undefined): { latitude: number; longitude: number } | undefined {
|
|
137
|
+
if (Array.isArray(value) && value.length >= 2 && typeof value[0] === 'number' && typeof value[1] === 'number') {
|
|
138
|
+
if (Math.abs(value[0]) <= 90 && Math.abs(value[1]) <= 180) return { latitude: value[0], longitude: value[1] };
|
|
139
|
+
}
|
|
140
|
+
if (Array.isArray(value)) {
|
|
141
|
+
for (const item of value) {
|
|
142
|
+
const found = findCoordinatePair(item);
|
|
143
|
+
if (found) return found;
|
|
144
|
+
}
|
|
145
|
+
} else if (value && typeof value === 'object') {
|
|
146
|
+
for (const item of Object.values(value)) {
|
|
147
|
+
const found = findCoordinatePair(item);
|
|
148
|
+
if (found) return found;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function findTimePair(value: Json | undefined): [string, string] | undefined {
|
|
155
|
+
if (Array.isArray(value) && value.length >= 2 && typeof value[0] === 'string' && typeof value[1] === 'string') {
|
|
156
|
+
if (/[AP]M/.test(value[0]) && /[AP]M/.test(value[1])) return [value[0], value[1]];
|
|
157
|
+
}
|
|
158
|
+
if (Array.isArray(value)) {
|
|
159
|
+
for (const item of value) {
|
|
160
|
+
const found = findTimePair(item);
|
|
161
|
+
if (found) return found;
|
|
162
|
+
}
|
|
163
|
+
} else if (value && typeof value === 'object') {
|
|
164
|
+
for (const item of Object.values(value)) {
|
|
165
|
+
const found = findTimePair(item);
|
|
166
|
+
if (found) return found;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function findRatingBreakdown(value: Json | undefined): Record<string, number> | undefined {
|
|
173
|
+
if (Array.isArray(value) && value.length >= 5 && value.every((entry) => Array.isArray(entry) && typeof entry[0] === 'number' && typeof entry[1] === 'string')) {
|
|
174
|
+
const entries = value as Array<[number, string]>;
|
|
175
|
+
if (entries.some(([key]) => key >= 1 && key <= 5)) {
|
|
176
|
+
return Object.fromEntries(entries.map(([key, rating]) => [`category_${key}`, Number(rating)]));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (Array.isArray(value)) {
|
|
180
|
+
for (const item of value) {
|
|
181
|
+
const found = findRatingBreakdown(item);
|
|
182
|
+
if (found) return found;
|
|
183
|
+
}
|
|
184
|
+
} else if (value && typeof value === 'object') {
|
|
185
|
+
for (const item of Object.values(value)) {
|
|
186
|
+
const found = findRatingBreakdown(item);
|
|
187
|
+
if (found) return found;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function findSelectedToken(value: Json, context?: { params: Record<string, string | number | boolean> }): string | undefined {
|
|
194
|
+
if (typeof context?.params?.hotel_token === 'string' && context.params.hotel_token) return context.params.hotel_token;
|
|
195
|
+
return collectStrings(value).find((text) => /^[A-Za-z0-9_-]{20,}$/.test(text) && /[A-Z]/.test(text) && /[a-z]/.test(text));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function collectPhotos(value: Json): Array<{ url: string; sourceUrl?: string }> {
|
|
199
|
+
const urls = unique(collectStrings(value).filter((text) => /^https?:\/\//.test(text) || text.startsWith('//')))
|
|
200
|
+
.filter((url) => /(?:googleusercontent|gstatic|trvl-media|media\.vrbo|hostaway-platform)/.test(url))
|
|
201
|
+
.filter((url) => !/branding|mapfiles|annotations/.test(url));
|
|
202
|
+
return urls.slice(0, 30).map((url) => ({ url: normalizeUrl(url) }));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function collectAmenitySections(value: Json): Array<{ name: string; available: boolean; section: string }> {
|
|
206
|
+
const amenities: Array<{ name: string; available: boolean; section: string }> = [];
|
|
207
|
+
walk(value, (node) => {
|
|
208
|
+
if (!Array.isArray(node) || typeof node[0] !== 'string' || !Array.isArray(node[1])) return;
|
|
209
|
+
const section = node[0];
|
|
210
|
+
if (!/^(Amenities|Essential info)$/i.test(section)) return;
|
|
211
|
+
for (const entry of node[1]) {
|
|
212
|
+
if (Array.isArray(entry) && typeof entry[0] === 'string' && typeof entry[1] === 'boolean') {
|
|
213
|
+
amenities.push({ name: entry[0], available: entry[1], section });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
return uniqueBy(amenities, (item) => `${item.section}:${item.name}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function collectNearbyPlaces(value: Json, hotelName: string): Array<{ name: string; category?: string; rating?: number; reviewCount?: number; distance?: string }> {
|
|
221
|
+
const nearby: Array<{ name: string; category?: string; rating?: number; reviewCount?: number; distance?: string }> = [];
|
|
222
|
+
walk(value, (node) => {
|
|
223
|
+
if (!Array.isArray(node) || typeof node[0] !== 'string' || node[0] === hotelName) return;
|
|
224
|
+
const category = typeof node[13] === 'string' ? node[13] : undefined;
|
|
225
|
+
const rating = typeof node[5] === 'number' && node[5] <= 5 ? node[5] : undefined;
|
|
226
|
+
const reviewCount = typeof node[6] === 'number' ? node[6] : undefined;
|
|
227
|
+
const distance = node.find((item) => typeof item === 'string' && /(?:ft|mi)$/.test(item)) as string | undefined;
|
|
228
|
+
if (category && (rating || reviewCount || distance || findCoordinatePair(node))) {
|
|
229
|
+
nearby.push({ name: node[0], category, rating, reviewCount, distance });
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
return uniqueBy(nearby, (item) => `${item.name}:${item.category}`).slice(0, 30);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function collectBookingOptions(value: Json): Array<{ provider: string; url?: string; displayedPrice?: string }> {
|
|
236
|
+
const options: Array<{ provider: string; url?: string; displayedPrice?: string }> = [];
|
|
237
|
+
walk(value, (node) => {
|
|
238
|
+
if (!Array.isArray(node) || typeof node[0] !== 'string') return;
|
|
239
|
+
const strings = node.filter((item): item is string => typeof item === 'string');
|
|
240
|
+
const url = strings.find((item) => item.includes('/travel/clk') || /^https?:\/\//.test(item));
|
|
241
|
+
const displayedPrice = strings.find((item) => /^\$[\d,]+/.test(item));
|
|
242
|
+
if ((url || displayedPrice) && /(?:\.com|Stays|Booking|Expedia|Vrbo|Hotels|Hyatt|Hilton)/i.test(node[0])) {
|
|
243
|
+
options.push({ provider: node[0], url: url ? normalizeUrl(url) : undefined, displayedPrice });
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
return uniqueBy(options, (item) => `${item.provider}:${item.url ?? item.displayedPrice ?? ''}`).slice(0, 20);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function walk(value: Json | undefined, visitor: (value: Json) => void): void {
|
|
250
|
+
if (value === undefined) return;
|
|
251
|
+
visitor(value);
|
|
252
|
+
if (Array.isArray(value)) for (const item of value) walk(item, visitor);
|
|
253
|
+
else if (value && typeof value === 'object') for (const item of Object.values(value)) walk(item, visitor);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function normalizeUrl(url: string): string {
|
|
257
|
+
const cleaned = url.replace(/\\u0026/g, '&').replace(/\\u003d/g, '=').replace(/\\\//g, '/');
|
|
258
|
+
return cleaned.startsWith('//') ? `https:${cleaned}` : cleaned;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function unique(values: string[]): string[] {
|
|
262
|
+
return [...new Set(values)];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function uniqueBy<T>(values: T[], key: (value: T) => string): T[] {
|
|
266
|
+
const seen = new Set<string>();
|
|
267
|
+
const out: T[] = [];
|
|
268
|
+
for (const value of values) {
|
|
269
|
+
const id = key(value);
|
|
270
|
+
if (seen.has(id)) continue;
|
|
271
|
+
seen.add(id);
|
|
272
|
+
out.push(value);
|
|
273
|
+
}
|
|
274
|
+
return out;
|
|
275
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
toolName: get_hotel_details
|
|
2
|
+
summary: Fetch detailed Google Hotels information for a selected hotel result.
|
|
3
|
+
parameters:
|
|
4
|
+
- name: hotel_token
|
|
5
|
+
type: string
|
|
6
|
+
description: Google Hotels selected result token for the hotel.
|
|
7
|
+
- name: destination
|
|
8
|
+
type: string
|
|
9
|
+
description: Destination context used when opening the hotel result.
|
|
10
|
+
- name: check_in_date
|
|
11
|
+
type: string
|
|
12
|
+
description: Check-in date in YYYY-MM-DD format when supplied.
|
|
13
|
+
- name: check_out_date
|
|
14
|
+
type: string
|
|
15
|
+
description: Check-out date in YYYY-MM-DD format when supplied.
|
|
16
|
+
- name: property_type
|
|
17
|
+
type: string
|
|
18
|
+
description: Property type context such as hotels or vacation_rentals.
|
|
19
|
+
steps:
|
|
20
|
+
- action: navigate
|
|
21
|
+
url: "https://www.google.com/travel/search?q=${destination}&qs=${hotel_token}"
|
|
22
|
+
wait_for: networkidle
|
|
23
|
+
- action: click
|
|
24
|
+
locators:
|
|
25
|
+
- by: aria_label
|
|
26
|
+
value_pattern: "${destination}"
|
|
27
|
+
- by: text
|
|
28
|
+
value_pattern: "${destination}"
|
|
29
|
+
- by: css
|
|
30
|
+
value: "a[href*='${hotel_token}']"
|
|
31
|
+
wait_for:
|
|
32
|
+
xhr: "rpcids=AtySUc"
|
|
33
|
+
- action: wait
|
|
34
|
+
wait_for: networkidle
|
|
35
|
+
result:
|
|
36
|
+
source: xhr
|
|
37
|
+
url_pattern: "rpcids=AtySUc"
|
|
38
|
+
extract: "0.2"
|
|
39
|
+
return_as: hotel_details
|
|
40
|
+
notes: "Google Travel detail payloads are returned through batchexecute AtySUc responses as encoded nested arrays; session-bound query fields such as f.sid, bl, and _reqid should not be treated as user parameters."
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
type Params = Record<string, string | number | boolean>;
|
|
2
|
+
|
|
3
|
+
export function transform(
|
|
4
|
+
_method: string,
|
|
5
|
+
url: string,
|
|
6
|
+
_responses: unknown[],
|
|
7
|
+
params?: Params,
|
|
8
|
+
): { url: string; body?: string } {
|
|
9
|
+
const p = params ?? {};
|
|
10
|
+
const destination = stringParam(p.destination, 'tahoe city');
|
|
11
|
+
const propertyType = stringParam(p.property_type, 'vacation_rentals');
|
|
12
|
+
const checkIn = parseDate(stringParam(p.check_in_date, '2026-08-09'));
|
|
13
|
+
const checkOut = parseDate(stringParam(p.check_out_date, '2026-08-16'));
|
|
14
|
+
const hotelToken = stringParam(p.hotel_token, '');
|
|
15
|
+
const isVacationRental = propertyType === 'vacation_rentals' || propertyType === 'vacation rental';
|
|
16
|
+
|
|
17
|
+
const placeId = isVacationRental ? '0x809bd62ecf1fa721:0x2a98b230816c9ed1' : null;
|
|
18
|
+
const placeMid = isVacationRental ? '/m/0gyvmkl' : '/m/0gz469';
|
|
19
|
+
const placeLabel = isVacationRental ? 'Tahoe City' : 'Chicago Loop';
|
|
20
|
+
const propertyCode = isVacationRental ? 2 : 1;
|
|
21
|
+
const occupancy = isVacationRental ? [0] : [1];
|
|
22
|
+
const stayLength = diffDays(checkIn, checkOut);
|
|
23
|
+
|
|
24
|
+
const payload = [
|
|
25
|
+
destination,
|
|
26
|
+
[
|
|
27
|
+
propertyCode,
|
|
28
|
+
isVacationRental ? [[[3], [3], [3], [3]], 1] : [[[3], [3]], 0],
|
|
29
|
+
[
|
|
30
|
+
[null, [[placeMid, null, null, null, null, placeId, placeLabel]].map(trimTrailingNulls), isVacationRental ? undefined : []].filter((v) => v !== undefined),
|
|
31
|
+
[null, [checkIn, checkOut, stayLength], null, null, null, occupancy],
|
|
32
|
+
],
|
|
33
|
+
null,
|
|
34
|
+
[[null, null, null, null, null, null, 'USD'], null, []],
|
|
35
|
+
],
|
|
36
|
+
[propertyCode === 1 ? 1 : 0, null, null, 0, 0, hotelToken || null, 13, null, 0],
|
|
37
|
+
null,
|
|
38
|
+
1,
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const fReq = [[["AtySUc", JSON.stringify(payload), null, "1"]]];
|
|
42
|
+
return { url, body: `f.req=${encodeURIComponent(JSON.stringify(fReq))}&` };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function stringParam(value: string | number | boolean | undefined, fallback: string): string {
|
|
46
|
+
if (typeof value === 'string' && value.length > 0) return value;
|
|
47
|
+
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
|
|
48
|
+
return fallback;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function parseDate(value: string): [number, number, number] {
|
|
52
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
|
|
53
|
+
if (!match) throw new Error(`Expected date in YYYY-MM-DD format, got ${value}`);
|
|
54
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function diffDays(start: [number, number, number], end: [number, number, number]): number {
|
|
58
|
+
const startDate = Date.UTC(start[0], start[1] - 1, start[2]);
|
|
59
|
+
const endDate = Date.UTC(end[0], end[1] - 1, end[2]);
|
|
60
|
+
return Math.max(1, Math.round((endDate - startDate) / 86_400_000));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function trimTrailingNulls(values: unknown[]): unknown[] {
|
|
64
|
+
const out = [...values];
|
|
65
|
+
while (out.length > 0 && out[out.length - 1] == null) out.pop();
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"toolName": "get_hotel_details",
|
|
3
|
+
"intent": {
|
|
4
|
+
"description": "Fetch detailed Google Hotels information for a selected hotel or vacation-rental result.",
|
|
5
|
+
"userSaid": "clicked one of hte offerings, saw booking options; changed property type to vacation rentals instead of hotels; changed location to tahoe city"
|
|
6
|
+
},
|
|
7
|
+
"site": "google-hotels",
|
|
8
|
+
"parameters": [
|
|
9
|
+
{
|
|
10
|
+
"name": "hotel_token",
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "Google Hotels selected result token for the hotel, usually emitted by a hotel search result.",
|
|
13
|
+
"verified": false,
|
|
14
|
+
"verifyNote": "annotated"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"name": "destination",
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "Destination context used when opening the hotel result.",
|
|
20
|
+
"default": "tahoe city",
|
|
21
|
+
"verified": false,
|
|
22
|
+
"verifyNote": "annotated"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "check_in_date",
|
|
26
|
+
"type": "string",
|
|
27
|
+
"description": "Check-in date in YYYY-MM-DD format.",
|
|
28
|
+
"default": "2026-08-09",
|
|
29
|
+
"verified": false,
|
|
30
|
+
"verifyNote": "annotated"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "check_out_date",
|
|
34
|
+
"type": "string",
|
|
35
|
+
"description": "Check-out date in YYYY-MM-DD format.",
|
|
36
|
+
"default": "2026-08-16",
|
|
37
|
+
"verified": false,
|
|
38
|
+
"verifyNote": "annotated"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "property_type",
|
|
42
|
+
"type": "string",
|
|
43
|
+
"description": "Property type context, either hotels or vacation_rentals.",
|
|
44
|
+
"default": "vacation_rentals",
|
|
45
|
+
"verified": true
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"requests": [
|
|
49
|
+
{
|
|
50
|
+
"method": "POST",
|
|
51
|
+
"url": "https://www.google.com/_/TravelFrontendUi/data/batchexecute?rpcids=AtySUc&source-path=%2Ftravel%2Fsearch&f.sid=7513562915459271421&bl=boq_travel-frontend-ui_20260527.01_p0&hl=en-US&soc-app=162&soc-platform=1&soc-device=1&_reqid=${generated.epoch_ms}&rt=c",
|
|
52
|
+
"headers": {
|
|
53
|
+
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
|
54
|
+
"X-Same-Domain": "1",
|
|
55
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
56
|
+
"Referer": "https://www.google.com/travel/search?q=${param.destination}",
|
|
57
|
+
"x-goog-ext-190139975-jspb": "[\"US\",\"ZZ\",\"x6c25Q==\"]",
|
|
58
|
+
"x-goog-ext-259736195-jspb": "[\"en-US\",\"US\",\"USD\",2,null,[420],null,null,7,[]]"
|
|
59
|
+
},
|
|
60
|
+
"body": "f.req=${param.destination}|${param.check_in_date}|${param.check_out_date}|${param.property_type}|${param.hotel_token}",
|
|
61
|
+
"effect": "safe"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"parserModule": "./parser.ts",
|
|
65
|
+
"requestTransformModule": "./request-transform.ts",
|
|
66
|
+
"liveVerified": true
|
|
67
|
+
}
|