cli-web-tripadvisor 0.1.1__py3-none-any.whl

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.
@@ -0,0 +1,110 @@
1
+ # cli-web-tripadvisor
2
+
3
+ > Generated by [CLI-Anything-Web](../../../../cli-anything-web-plugin/) from [tripadvisor.com](https://www.tripadvisor.com)
4
+
5
+ Agent-native CLI for TripAdvisor — search destinations, hotels, restaurants,
6
+ and attractions from the terminal. Data is extracted from SSR HTML pages
7
+ (JSON-LD structured data) and the TypeAheadJson REST endpoint; DataDome bot
8
+ protection is bypassed with curl_cffi Safari-iOS impersonation.
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ cd tripadvisor/agent-harness
14
+ pip install -e .
15
+ ```
16
+
17
+ Binary: `cli-web-tripadvisor`
18
+
19
+ ## Usage
20
+
21
+ ```bash
22
+ # 1. Resolve a destination to its geo_id
23
+ cli-web-tripadvisor locations search "Paris"
24
+
25
+ # 2. Search hotels (faster with a known geo_id)
26
+ cli-web-tripadvisor hotels search "Paris" --geo-id 187147
27
+ cli-web-tripadvisor hotels search "Paris" --page 2
28
+
29
+ # 3. Hotel detail by TripAdvisor URL
30
+ cli-web-tripadvisor hotels get "https://www.tripadvisor.com/Hotel_Review-g187147-d229968-Reviews-..."
31
+
32
+ # Restaurants
33
+ cli-web-tripadvisor restaurants search "New York City"
34
+ cli-web-tripadvisor restaurants get "https://www.tripadvisor.com/Restaurant_Review-..."
35
+
36
+ # Attractions / things to do
37
+ cli-web-tripadvisor attractions search "London" --page 2
38
+ cli-web-tripadvisor attractions get "https://www.tripadvisor.com/Attraction_Review-..."
39
+
40
+ # Interactive REPL (default when no subcommand is given)
41
+ cli-web-tripadvisor
42
+ ```
43
+
44
+ ### Commands
45
+
46
+ | Group | Command | Purpose |
47
+ |-------|---------|---------|
48
+ | `locations` | `search QUERY [--max N]` | Search destinations; returns `geo_id` for use with `--geo-id` (default max: 6) |
49
+ | `hotels` | `search LOCATION [--geo-id ID] [--page N]` | Search hotels in a location (30 per page) |
50
+ | `hotels` | `get URL` | Detailed hotel info from a TripAdvisor URL |
51
+ | `restaurants` | `search LOCATION [--geo-id ID] [--page N]` | Search restaurants in a location (30 per page) |
52
+ | `restaurants` | `get URL` | Detailed restaurant info from a TripAdvisor URL |
53
+ | `attractions` | `search LOCATION [--geo-id ID] [--page N]` | Search attractions/things to do (30 per page) |
54
+ | `attractions` | `get URL` | Detailed attraction info from a TripAdvisor URL |
55
+ | — | `doctor` | Environment / connectivity self-check |
56
+ | — | `mcp` | Run as an MCP server over stdio (every command exposed as a tool) |
57
+
58
+ Typical workflow: `locations search` → grab the `geo_id` → pass it to
59
+ `hotels`/`restaurants`/`attractions search` via `--geo-id` to skip the
60
+ lookup round-trip.
61
+
62
+ ## Auth
63
+
64
+ **No authentication required.** All search, listing, and detail operations
65
+ use public TripAdvisor pages. There is no `login` command and no `auth.json`.
66
+
67
+ If requests start failing with `AUTH_EXPIRED` (HTTP 401/403), DataDome bot
68
+ protection has triggered — the impersonation profile can be overridden via:
69
+
70
+ ```bash
71
+ export CLI_WEB_TRIPADVISOR_IMPERSONATE=safari17_2_ios # default
72
+ ```
73
+
74
+ ## JSON Output
75
+
76
+ Every command supports `--json` (global or per-command) for structured,
77
+ agent-friendly output:
78
+
79
+ ```bash
80
+ cli-web-tripadvisor --json locations search "Paris"
81
+ cli-web-tripadvisor hotels search "Paris" --geo-id 187147 --json
82
+ ```
83
+
84
+ Errors are also structured in JSON mode:
85
+
86
+ ```json
87
+ {"error": true, "code": "NOT_FOUND", "message": "Page not found: ..."}
88
+ ```
89
+
90
+ ## REPL
91
+
92
+ Running `cli-web-tripadvisor` with no subcommand opens an interactive REPL.
93
+ Type `help` for the command list, `quit`/`exit` to leave. Quoted arguments
94
+ work (`hotels search "New York City"`), and starting the REPL with `--json`
95
+ makes every REPL command emit JSON.
96
+
97
+ ## Testing
98
+
99
+ ```bash
100
+ cd tripadvisor/agent-harness
101
+ pip install -e .
102
+
103
+ # Unit tests (offline — HTTP layer is mocked)
104
+ python -m pytest cli_web/tripadvisor/tests/test_core.py -v -s
105
+
106
+ # E2E tests (live site + subprocess CLI tests)
107
+ python -m pytest cli_web/tripadvisor/tests/test_e2e.py -v -s
108
+ ```
109
+
110
+ See `TRIPADVISOR.md` (in `agent-harness/`) for the full API map.
File without changes
@@ -0,0 +1,6 @@
1
+ """Allow running as: python -m cli_web.tripadvisor"""
2
+
3
+ from .tripadvisor_cli import cli
4
+
5
+ if __name__ == "__main__":
6
+ cli()
File without changes
@@ -0,0 +1,153 @@
1
+ """Attraction commands for cli-web-tripadvisor."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import click
6
+ from rich.console import Console
7
+ from rich.table import Table
8
+
9
+ from ..core.client import TripAdvisorClient
10
+ from ..utils.helpers import (
11
+ format_rating,
12
+ handle_errors,
13
+ print_json,
14
+ resolve_json_mode,
15
+ truncate,
16
+ )
17
+
18
+ console = Console()
19
+
20
+
21
+ @click.group("attractions")
22
+ @click.pass_context
23
+ def attractions(ctx):
24
+ """Search and browse TripAdvisor attractions and things to do."""
25
+ ctx.ensure_object(dict)
26
+
27
+
28
+ @attractions.command("search")
29
+ @click.argument("location")
30
+ @click.option(
31
+ "--geo-id", default=None, metavar="ID", help="Use known geo_id to skip location lookup."
32
+ )
33
+ @click.option(
34
+ "--page", default=1, type=int, show_default=True, help="Page number (30 attractions per page)."
35
+ )
36
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
37
+ @click.pass_context
38
+ def search_attractions(ctx, location, geo_id, page, json_mode):
39
+ """Search attractions and things to do in LOCATION.
40
+
41
+ LOCATION is a destination name like "Paris" or "New York City".
42
+ Use --geo-id to skip the location-lookup step (faster).
43
+
44
+ Examples:
45
+
46
+ cli-web-tripadvisor attractions search "Paris"
47
+
48
+ cli-web-tripadvisor attractions search "Paris" --geo-id 187147
49
+
50
+ cli-web-tripadvisor attractions search "London" --page 2
51
+
52
+ cli-web-tripadvisor attractions search "Tokyo" --json
53
+ """
54
+ json_mode = resolve_json_mode(json_mode, ctx)
55
+
56
+ with handle_errors(json_mode=json_mode):
57
+ with TripAdvisorClient() as client:
58
+ result = client.search_attractions(location, geo_id=geo_id, page=page)
59
+
60
+ attrlist = result.get("attractions", [])
61
+
62
+ if json_mode:
63
+ print_json(
64
+ {
65
+ "success": True,
66
+ "data": {
67
+ "location": location,
68
+ "geo_id": result.get("geo_id"),
69
+ "page": page,
70
+ "count": len(attrlist),
71
+ "attractions": [a.to_dict() for a in attrlist],
72
+ },
73
+ }
74
+ )
75
+ return
76
+
77
+ if not attrlist:
78
+ click.echo(
79
+ f"No attractions found for '{location}' (page {page}). "
80
+ "Try a different location name or --geo-id."
81
+ )
82
+ return
83
+
84
+ table = Table(
85
+ title=f"Attractions in {location} — page {page}",
86
+ show_lines=False,
87
+ expand=False,
88
+ )
89
+ table.add_column("ID", style="dim", no_wrap=True, max_width=10)
90
+ table.add_column("Name", max_width=40)
91
+ table.add_column("Rating", justify="right", max_width=14)
92
+ table.add_column("City", max_width=16)
93
+ table.add_column("Phone", max_width=18)
94
+
95
+ for a in attrlist:
96
+ table.add_row(
97
+ a.id,
98
+ truncate(a.name, 40),
99
+ format_rating(a.rating, a.review_count),
100
+ a.city or "—",
101
+ a.telephone or "—",
102
+ )
103
+
104
+ console.print(table)
105
+ click.echo(f"\nShowing {len(attrlist)} attraction(s) on page {page}.")
106
+ if len(attrlist) >= 30:
107
+ click.echo(f"Next page: attractions search '{location}' --page {page + 1}")
108
+ click.echo("Tip: Use 'attractions get URL' with the attraction URL to see full details.")
109
+
110
+
111
+ @attractions.command("get")
112
+ @click.argument("url")
113
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
114
+ @click.pass_context
115
+ def get_attraction(ctx, url, json_mode):
116
+ """Get detailed information for an attraction by its TripAdvisor URL.
117
+
118
+ The URL is the full TripAdvisor attraction URL from a search result,
119
+ e.g. https://www.tripadvisor.com/Attraction_Review-g187147-d188151-Reviews-...
120
+
121
+ Examples:
122
+
123
+ cli-web-tripadvisor attractions get "https://www.tripadvisor.com/Attraction_Review-g187147-d188151-Reviews-Eiffel_Tower-Paris_Ile_de_France.html"
124
+
125
+ cli-web-tripadvisor attractions get "https://www.tripadvisor.com/Attraction_Review-..." --json
126
+ """
127
+ json_mode = resolve_json_mode(json_mode, ctx)
128
+
129
+ with handle_errors(json_mode=json_mode):
130
+ with TripAdvisorClient() as client:
131
+ attr = client.get_attraction(url)
132
+
133
+ if json_mode:
134
+ print_json({"success": True, "data": {"attraction": attr.to_dict()}})
135
+ return
136
+
137
+ click.echo(f"\n{'=' * 60}")
138
+ click.echo(f" {attr.name}")
139
+ click.echo(f"{'=' * 60}")
140
+ click.echo(f" ID: {attr.id or '—'}")
141
+ click.echo(f" Rating: {format_rating(attr.rating, attr.review_count)}")
142
+ click.echo(f" Address: {attr.address or '—'}")
143
+ click.echo(f" City: {attr.city or '—'}")
144
+ click.echo(f" Telephone: {attr.telephone or '—'}")
145
+ click.echo(f" Coordinates: {attr.latitude or '?'}, {attr.longitude or '?'}")
146
+ if attr.opening_hours:
147
+ click.echo(" Hours:")
148
+ for h in attr.opening_hours[:7]:
149
+ click.echo(f" {h}")
150
+ if attr.description:
151
+ click.echo(f" Description: {truncate(attr.description, 200)}")
152
+ click.echo(f" URL: {attr.url}")
153
+ click.echo()
@@ -0,0 +1,155 @@
1
+ """Hotel commands for cli-web-tripadvisor."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import click
6
+ from rich.console import Console
7
+ from rich.table import Table
8
+
9
+ from ..core.client import TripAdvisorClient
10
+ from ..utils.helpers import (
11
+ format_rating,
12
+ handle_errors,
13
+ print_json,
14
+ resolve_json_mode,
15
+ truncate,
16
+ )
17
+
18
+ console = Console()
19
+
20
+
21
+ @click.group("hotels")
22
+ @click.pass_context
23
+ def hotels(ctx):
24
+ """Search and browse TripAdvisor hotels."""
25
+ ctx.ensure_object(dict)
26
+
27
+
28
+ @hotels.command("search")
29
+ @click.argument("location")
30
+ @click.option(
31
+ "--geo-id", default=None, metavar="ID", help="Use known geo_id to skip location lookup."
32
+ )
33
+ @click.option(
34
+ "--page", default=1, type=int, show_default=True, help="Page number (30 hotels per page)."
35
+ )
36
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
37
+ @click.pass_context
38
+ def search_hotels(ctx, location, geo_id, page, json_mode):
39
+ """Search hotels in LOCATION.
40
+
41
+ LOCATION is a destination name like "Paris" or "New York City".
42
+ Use --geo-id to skip the location-lookup step (faster).
43
+
44
+ Examples:
45
+
46
+ cli-web-tripadvisor hotels search "Paris"
47
+
48
+ cli-web-tripadvisor hotels search "Paris" --geo-id 187147
49
+
50
+ cli-web-tripadvisor hotels search "Tokyo" --page 2
51
+
52
+ cli-web-tripadvisor hotels search "London" --json
53
+ """
54
+ json_mode = resolve_json_mode(json_mode, ctx)
55
+
56
+ with handle_errors(json_mode=json_mode):
57
+ with TripAdvisorClient() as client:
58
+ result = client.search_hotels(location, geo_id=geo_id, page=page)
59
+
60
+ hotellist = result.get("hotels", [])
61
+
62
+ if json_mode:
63
+ print_json(
64
+ {
65
+ "success": True,
66
+ "data": {
67
+ "location": location,
68
+ "geo_id": result.get("geo_id"),
69
+ "page": page,
70
+ "count": len(hotellist),
71
+ "hotels": [h.to_dict() for h in hotellist],
72
+ },
73
+ }
74
+ )
75
+ return
76
+
77
+ if not hotellist:
78
+ click.echo(
79
+ f"No hotels found for '{location}' (page {page}). "
80
+ "Try a different location name or --geo-id."
81
+ )
82
+ return
83
+
84
+ table = Table(
85
+ title=f"Hotels in {location} — page {page}",
86
+ show_lines=False,
87
+ expand=False,
88
+ )
89
+ table.add_column("ID", style="dim", no_wrap=True, max_width=10)
90
+ table.add_column("Name", max_width=38)
91
+ table.add_column("Rating", justify="right", max_width=14)
92
+ table.add_column("Price", justify="center", max_width=8)
93
+ table.add_column("City", max_width=16)
94
+ table.add_column("Phone", max_width=18)
95
+
96
+ for h in hotellist:
97
+ table.add_row(
98
+ h.id,
99
+ truncate(h.name, 38),
100
+ format_rating(h.rating, h.review_count),
101
+ h.price_range or "—",
102
+ h.city or "—",
103
+ h.telephone or "—",
104
+ )
105
+
106
+ console.print(table)
107
+ click.echo(f"\nShowing {len(hotellist)} hotel(s) on page {page}.")
108
+ if len(hotellist) >= 30:
109
+ click.echo(f"Next page: hotels search '{location}' --page {page + 1}")
110
+ click.echo("Tip: Use 'hotels get URL' with the hotel URL to see full details.")
111
+
112
+
113
+ @hotels.command("get")
114
+ @click.argument("url")
115
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
116
+ @click.pass_context
117
+ def get_hotel(ctx, url, json_mode):
118
+ """Get detailed information for a hotel by its TripAdvisor URL.
119
+
120
+ The URL is the full TripAdvisor hotel URL from a search result,
121
+ e.g. https://www.tripadvisor.com/Hotel_Review-g187147-d229968-Reviews-...
122
+
123
+ Examples:
124
+
125
+ cli-web-tripadvisor hotels get "https://www.tripadvisor.com/Hotel_Review-g187147-d229968-Reviews-Hotel_Astra_Opera_Astotel-Paris_Ile_de_France.html"
126
+
127
+ cli-web-tripadvisor hotels get "https://www.tripadvisor.com/Hotel_Review-..." --json
128
+ """
129
+ json_mode = resolve_json_mode(json_mode, ctx)
130
+
131
+ with handle_errors(json_mode=json_mode):
132
+ with TripAdvisorClient() as client:
133
+ hotel = client.get_hotel(url)
134
+
135
+ if json_mode:
136
+ print_json({"success": True, "data": {"hotel": hotel.to_dict()}})
137
+ return
138
+
139
+ click.echo(f"\n{'=' * 60}")
140
+ click.echo(f" {hotel.name}")
141
+ click.echo(f"{'=' * 60}")
142
+ click.echo(f" ID: {hotel.id or '—'}")
143
+ click.echo(f" Rating: {format_rating(hotel.rating, hotel.review_count)}")
144
+ click.echo(f" Price range: {hotel.price_range or '—'}")
145
+ click.echo(f" Address: {hotel.address or '—'}")
146
+ click.echo(f" City: {hotel.city or '—'}")
147
+ click.echo(f" Country: {hotel.country or '—'}")
148
+ click.echo(f" Telephone: {hotel.telephone or '—'}")
149
+ click.echo(f" Coordinates: {hotel.latitude or '?'}, {hotel.longitude or '?'}")
150
+ if hotel.amenities:
151
+ click.echo(f" Amenities: {', '.join(hotel.amenities[:10])}")
152
+ if len(hotel.amenities) > 10:
153
+ click.echo(f" ... and {len(hotel.amenities) - 10} more")
154
+ click.echo(f" URL: {hotel.url}")
155
+ click.echo()
@@ -0,0 +1,93 @@
1
+ """Location search commands for cli-web-tripadvisor."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import click
6
+ from rich.console import Console
7
+ from rich.table import Table
8
+
9
+ from ..core.client import TripAdvisorClient
10
+ from ..utils.helpers import handle_errors, print_json, resolve_json_mode
11
+
12
+ console = Console()
13
+
14
+
15
+ @click.group("locations")
16
+ @click.pass_context
17
+ def locations(ctx):
18
+ """Search TripAdvisor destinations and locations."""
19
+ ctx.ensure_object(dict)
20
+
21
+
22
+ @locations.command("search")
23
+ @click.argument("query")
24
+ @click.option(
25
+ "--max",
26
+ "max_results",
27
+ default=6,
28
+ type=int,
29
+ show_default=True,
30
+ help="Maximum number of results.",
31
+ )
32
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
33
+ @click.pass_context
34
+ def search_locations(ctx, query, max_results, json_mode):
35
+ """Search for destinations matching QUERY.
36
+
37
+ Returns locations with their geo_id, which can be passed to
38
+ hotels/restaurants/attractions search via --geo-id.
39
+
40
+ Examples:
41
+
42
+ cli-web-tripadvisor locations search "Paris"
43
+
44
+ cli-web-tripadvisor locations search "New York" --max 10
45
+
46
+ cli-web-tripadvisor locations search "Tokyo" --json
47
+ """
48
+ json_mode = resolve_json_mode(json_mode, ctx)
49
+
50
+ with handle_errors(json_mode=json_mode):
51
+ with TripAdvisorClient() as client:
52
+ results = client.search_locations(query, max_results=max_results)
53
+
54
+ if json_mode:
55
+ print_json(
56
+ {
57
+ "success": True,
58
+ "data": {
59
+ "query": query,
60
+ "count": len(results),
61
+ "locations": [loc.to_dict() for loc in results],
62
+ },
63
+ }
64
+ )
65
+ return
66
+
67
+ if not results:
68
+ click.echo(f"No locations found for '{query}'.")
69
+ return
70
+
71
+ table = Table(
72
+ title=f"TripAdvisor Locations — {query}",
73
+ show_lines=False,
74
+ expand=False,
75
+ )
76
+ table.add_column("Geo ID", style="dim", no_wrap=True, max_width=10)
77
+ table.add_column("Type", max_width=12)
78
+ table.add_column("Name", max_width=50)
79
+ table.add_column("Region", max_width=30)
80
+
81
+ for loc in results:
82
+ table.add_row(
83
+ loc.geo_id,
84
+ loc.type,
85
+ loc.name,
86
+ loc.geo_name or loc.parent_name or "",
87
+ )
88
+
89
+ console.print(table)
90
+ click.echo(f"\nFound {len(results)} location(s) for '{query}'.")
91
+ click.echo(
92
+ "Tip: Use --geo-id GEO_ID with hotels/restaurants/attractions search for faster results."
93
+ )
@@ -0,0 +1,157 @@
1
+ """Restaurant commands for cli-web-tripadvisor."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import click
6
+ from rich.console import Console
7
+ from rich.table import Table
8
+
9
+ from ..core.client import TripAdvisorClient
10
+ from ..utils.helpers import (
11
+ format_rating,
12
+ handle_errors,
13
+ print_json,
14
+ resolve_json_mode,
15
+ truncate,
16
+ )
17
+
18
+ console = Console()
19
+
20
+
21
+ @click.group("restaurants")
22
+ @click.pass_context
23
+ def restaurants(ctx):
24
+ """Search and browse TripAdvisor restaurants."""
25
+ ctx.ensure_object(dict)
26
+
27
+
28
+ @restaurants.command("search")
29
+ @click.argument("location")
30
+ @click.option(
31
+ "--geo-id", default=None, metavar="ID", help="Use known geo_id to skip location lookup."
32
+ )
33
+ @click.option(
34
+ "--page", default=1, type=int, show_default=True, help="Page number (30 restaurants per page)."
35
+ )
36
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
37
+ @click.pass_context
38
+ def search_restaurants(ctx, location, geo_id, page, json_mode):
39
+ """Search restaurants in LOCATION.
40
+
41
+ LOCATION is a destination name like "Paris" or "New York City".
42
+ Use --geo-id to skip the location-lookup step (faster).
43
+
44
+ Examples:
45
+
46
+ cli-web-tripadvisor restaurants search "Paris"
47
+
48
+ cli-web-tripadvisor restaurants search "Paris" --geo-id 187147
49
+
50
+ cli-web-tripadvisor restaurants search "Barcelona" --page 2
51
+
52
+ cli-web-tripadvisor restaurants search "Rome" --json
53
+ """
54
+ json_mode = resolve_json_mode(json_mode, ctx)
55
+
56
+ with handle_errors(json_mode=json_mode):
57
+ with TripAdvisorClient() as client:
58
+ result = client.search_restaurants(location, geo_id=geo_id, page=page)
59
+
60
+ restlist = result.get("restaurants", [])
61
+
62
+ if json_mode:
63
+ print_json(
64
+ {
65
+ "success": True,
66
+ "data": {
67
+ "location": location,
68
+ "geo_id": result.get("geo_id"),
69
+ "page": page,
70
+ "count": len(restlist),
71
+ "restaurants": [r.to_dict() for r in restlist],
72
+ },
73
+ }
74
+ )
75
+ return
76
+
77
+ if not restlist:
78
+ click.echo(
79
+ f"No restaurants found for '{location}' (page {page}). "
80
+ "Try a different location name or --geo-id."
81
+ )
82
+ return
83
+
84
+ table = Table(
85
+ title=f"Restaurants in {location} — page {page}",
86
+ show_lines=False,
87
+ expand=False,
88
+ )
89
+ table.add_column("ID", style="dim", no_wrap=True, max_width=10)
90
+ table.add_column("Name", max_width=36)
91
+ table.add_column("Rating", justify="right", max_width=14)
92
+ table.add_column("Price", justify="center", max_width=8)
93
+ table.add_column("Cuisines", max_width=22)
94
+ table.add_column("Phone", max_width=18)
95
+
96
+ for r in restlist:
97
+ cuisines = ", ".join(r.cuisines[:2]) if r.cuisines else "—"
98
+ table.add_row(
99
+ r.id,
100
+ truncate(r.name, 36),
101
+ format_rating(r.rating, r.review_count),
102
+ r.price_range or "—",
103
+ cuisines,
104
+ r.telephone or "—",
105
+ )
106
+
107
+ console.print(table)
108
+ click.echo(f"\nShowing {len(restlist)} restaurant(s) on page {page}.")
109
+ if len(restlist) >= 30:
110
+ click.echo(f"Next page: restaurants search '{location}' --page {page + 1}")
111
+ click.echo("Tip: Use 'restaurants get URL' with the restaurant URL to see full details.")
112
+
113
+
114
+ @restaurants.command("get")
115
+ @click.argument("url")
116
+ @click.option("--json", "json_mode", is_flag=True, help="Output as JSON.")
117
+ @click.pass_context
118
+ def get_restaurant(ctx, url, json_mode):
119
+ """Get detailed information for a restaurant by its TripAdvisor URL.
120
+
121
+ The URL is the full TripAdvisor restaurant URL from a search result,
122
+ e.g. https://www.tripadvisor.com/Restaurant_Review-g187147-d1035679-Reviews-...
123
+
124
+ Examples:
125
+
126
+ cli-web-tripadvisor restaurants get "https://www.tripadvisor.com/Restaurant_Review-g187147-d1035679-Reviews-Da_Franco-Paris_Ile_de_France.html"
127
+
128
+ cli-web-tripadvisor restaurants get "https://www.tripadvisor.com/Restaurant_Review-..." --json
129
+ """
130
+ json_mode = resolve_json_mode(json_mode, ctx)
131
+
132
+ with handle_errors(json_mode=json_mode):
133
+ with TripAdvisorClient() as client:
134
+ rest = client.get_restaurant(url)
135
+
136
+ if json_mode:
137
+ print_json({"success": True, "data": {"restaurant": rest.to_dict()}})
138
+ return
139
+
140
+ click.echo(f"\n{'=' * 60}")
141
+ click.echo(f" {rest.name}")
142
+ click.echo(f"{'=' * 60}")
143
+ click.echo(f" ID: {rest.id or '—'}")
144
+ click.echo(f" Rating: {format_rating(rest.rating, rest.review_count)}")
145
+ click.echo(f" Price range: {rest.price_range or '—'}")
146
+ if rest.cuisines:
147
+ click.echo(f" Cuisines: {', '.join(rest.cuisines)}")
148
+ click.echo(f" Address: {rest.address or '—'}")
149
+ click.echo(f" City: {rest.city or '—'}")
150
+ click.echo(f" Telephone: {rest.telephone or '—'}")
151
+ click.echo(f" Coordinates: {rest.latitude or '?'}, {rest.longitude or '?'}")
152
+ if rest.opening_hours:
153
+ click.echo(" Hours:")
154
+ for h in rest.opening_hours[:7]:
155
+ click.echo(f" {h}")
156
+ click.echo(f" URL: {rest.url}")
157
+ click.echo()
File without changes