scrapebadger-cli 0.6.0__tar.gz → 0.7.0__tar.gz

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.
Files changed (35) hide show
  1. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/.gitignore +3 -0
  2. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/PKG-INFO +1 -1
  3. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/pyproject.toml +1 -1
  4. scrapebadger_cli-0.7.0/src/scrapebadger_cli/commands/apartments.py +63 -0
  5. scrapebadger_cli-0.7.0/src/scrapebadger_cli/commands/facebook.py +271 -0
  6. scrapebadger_cli-0.7.0/src/scrapebadger_cli/commands/instagram.py +532 -0
  7. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/src/scrapebadger_cli/commands/vinted.py +1 -1
  8. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/src/scrapebadger_cli/main.py +10 -0
  9. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/uv.lock +1 -1
  10. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/README.md +0 -0
  11. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/src/scrapebadger_cli/__init__.py +0 -0
  12. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/src/scrapebadger_cli/client.py +0 -0
  13. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/src/scrapebadger_cli/commands/__init__.py +0 -0
  14. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/src/scrapebadger_cli/commands/amazon.py +0 -0
  15. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/src/scrapebadger_cli/commands/auth.py +0 -0
  16. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/src/scrapebadger_cli/commands/chatgpt.py +0 -0
  17. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/src/scrapebadger_cli/commands/depop.py +0 -0
  18. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/src/scrapebadger_cli/commands/ebay.py +0 -0
  19. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/src/scrapebadger_cli/commands/google.py +0 -0
  20. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/src/scrapebadger_cli/commands/idealista.py +0 -0
  21. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/src/scrapebadger_cli/commands/immobiliare.py +0 -0
  22. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/src/scrapebadger_cli/commands/leboncoin.py +0 -0
  23. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/src/scrapebadger_cli/commands/linkedin.py +0 -0
  24. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/src/scrapebadger_cli/commands/loopnet.py +0 -0
  25. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/src/scrapebadger_cli/commands/realtor.py +0 -0
  26. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/src/scrapebadger_cli/commands/reddit.py +0 -0
  27. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/src/scrapebadger_cli/commands/redfin.py +0 -0
  28. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/src/scrapebadger_cli/commands/stream.py +0 -0
  29. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/src/scrapebadger_cli/commands/tiktok.py +0 -0
  30. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/src/scrapebadger_cli/commands/twitter.py +0 -0
  31. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/src/scrapebadger_cli/commands/web.py +0 -0
  32. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/src/scrapebadger_cli/commands/youtube.py +0 -0
  33. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/src/scrapebadger_cli/commands/zillow.py +0 -0
  34. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/src/scrapebadger_cli/config.py +0 -0
  35. {scrapebadger_cli-0.6.0 → scrapebadger_cli-0.7.0}/src/scrapebadger_cli/output.py +0 -0
@@ -191,3 +191,6 @@ research/
191
191
  # The blog upload dir predates this and is deliberately left as-is.
192
192
  apps/portal/public/uploads/support/*
193
193
  !apps/portal/public/uploads/support/.gitkeep
194
+
195
+ # Instagram account pool seed (purchased credentials — never commit)
196
+ scrapers/instagram/.secrets/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scrapebadger-cli
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: ScrapeBadger CLI — Twitter, Vinted, Google, and Web Scraping from the command line
5
5
  Author: ScrapeBadger Team
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "scrapebadger-cli"
3
- version = "0.6.0"
3
+ version = "0.7.0"
4
4
  description = "ScrapeBadger CLI — Twitter, Vinted, Google, and Web Scraping from the command line"
5
5
  requires-python = ">=3.10"
6
6
  license = {text = "MIT"}
@@ -0,0 +1,63 @@
1
+ """Apartments.com rental-listing commands."""
2
+
3
+ import typer
4
+
5
+ from scrapebadger_cli.client import api_get
6
+ from scrapebadger_cli.output import render
7
+
8
+ app = typer.Typer(no_args_is_help=True)
9
+
10
+ FMT = typer.Option("json", "--output", "-o", help="Output format: json, csv, table, markdown")
11
+ FIELDS = typer.Option(None, "--fields", "-f", help="Comma-separated fields to include")
12
+
13
+
14
+ @app.command()
15
+ def search(
16
+ location: str = typer.Argument(..., help="Location slug, e.g. kansas-city-mo, or a ZIP"),
17
+ page: int = typer.Option(1, "--page", "-p", help="1-28"),
18
+ beds: int | None = typer.Option(None, "--beds", help="0=studio, 1-4 bedrooms"),
19
+ min_price: int | None = typer.Option(None, "--min-price"),
20
+ max_price: int | None = typer.Option(None, "--max-price"),
21
+ output: str = FMT,
22
+ fields: str | None = FIELDS,
23
+ ) -> None:
24
+ """Search rental listings in a location."""
25
+ params: dict = {"location": location, "page": page}
26
+ for k, v in (("beds", beds), ("min_price", min_price), ("max_price", max_price)):
27
+ if v is not None:
28
+ params[k] = v
29
+ data = api_get("/v1/apartments/search", params=params)
30
+ render(data.get("results", data) if isinstance(data, dict) else data, output, fields)
31
+
32
+
33
+ @app.command()
34
+ def property(
35
+ url: str = typer.Argument(..., help="Full apartments.com property URL"),
36
+ output: str = FMT,
37
+ fields: str | None = FIELDS,
38
+ ) -> None:
39
+ """Get a property with full per-unit pricing and availability."""
40
+ data = api_get("/v1/apartments/property", params={"url": url})
41
+ render(data, output, fields)
42
+
43
+
44
+ @app.command()
45
+ def units(
46
+ url: str = typer.Argument(..., help="Full apartments.com property URL"),
47
+ output: str = FMT,
48
+ fields: str | None = FIELDS,
49
+ ) -> None:
50
+ """List just the rentable units for a property — the flattened view."""
51
+ data = api_get("/v1/apartments/property", params={"url": url})
52
+ render(data.get("units", []) if isinstance(data, dict) else data, output, fields)
53
+
54
+
55
+ @app.command("floor-plans")
56
+ def floor_plans(
57
+ url: str = typer.Argument(..., help="Full apartments.com property URL"),
58
+ output: str = FMT,
59
+ fields: str | None = FIELDS,
60
+ ) -> None:
61
+ """List a property's floor plans."""
62
+ data = api_get("/v1/apartments/property", params={"url": url})
63
+ render(data.get("floor_plans", []) if isinstance(data, dict) else data, output, fields)
@@ -0,0 +1,271 @@
1
+ """Facebook pages, groups, posts, search & Marketplace commands."""
2
+
3
+ import typer
4
+
5
+ from scrapebadger_cli.client import api_get
6
+ from scrapebadger_cli.output import render
7
+
8
+ app = typer.Typer(no_args_is_help=True)
9
+
10
+ FMT = typer.Option("json", "--output", "-o", help="Output format: json, csv, table, markdown")
11
+ FIELDS = typer.Option(None, "--fields", "-f", help="Comma-separated fields to include")
12
+ LOCATION = typer.Option("nyc", "--location", "-l", help="Marketplace location slug (e.g. nyc, la)")
13
+ AFTER = typer.Option(None, "--after", help="Pagination cursor (end_cursor from a prior response)")
14
+
15
+
16
+ @app.command(name="marketplace-search")
17
+ def marketplace_search(
18
+ query: str = typer.Argument(..., help="Search keywords"),
19
+ location: str = LOCATION,
20
+ min_price: int | None = typer.Option(None, "--min-price"),
21
+ max_price: int | None = typer.Option(None, "--max-price"),
22
+ days_since_listed: int | None = typer.Option(
23
+ None, "--days-since-listed", help="Only listings posted in the last N days"
24
+ ),
25
+ sort_by: str | None = typer.Option(
26
+ None, "--sort-by", help="best_match, price_ascend, price_descend, creation_time_descend"
27
+ ),
28
+ item_condition: str | None = typer.Option(
29
+ None, "--condition", help="new, used_like_new, used_good, used_fair"
30
+ ),
31
+ delivery_method: str | None = typer.Option(
32
+ None, "--delivery", help="local_pick_up or shipping"
33
+ ),
34
+ after: str | None = AFTER,
35
+ fmt: str = FMT,
36
+ fields: str | None = FIELDS,
37
+ ) -> None:
38
+ """Search Facebook Marketplace listings."""
39
+ data = api_get(
40
+ "/v1/facebook/marketplace/search",
41
+ {
42
+ "query": query,
43
+ "location": location,
44
+ "min_price": min_price,
45
+ "max_price": max_price,
46
+ "days_since_listed": days_since_listed,
47
+ "sort_by": sort_by,
48
+ "item_condition": item_condition,
49
+ "delivery_method": delivery_method,
50
+ "after": after,
51
+ },
52
+ )
53
+ render(data, fmt, fields)
54
+
55
+
56
+ @app.command(name="marketplace-category")
57
+ def marketplace_category(
58
+ category: str = typer.Argument(..., help="Category slug (vehicles, electronics, ...)"),
59
+ location: str = LOCATION,
60
+ min_price: int | None = typer.Option(None, "--min-price"),
61
+ max_price: int | None = typer.Option(None, "--max-price"),
62
+ sort_by: str | None = typer.Option(None, "--sort-by"),
63
+ after: str | None = AFTER,
64
+ fmt: str = FMT,
65
+ fields: str | None = FIELDS,
66
+ ) -> None:
67
+ """Browse Marketplace listings in a category."""
68
+ data = api_get(
69
+ f"/v1/facebook/marketplace/category/{category}",
70
+ {
71
+ "location": location,
72
+ "min_price": min_price,
73
+ "max_price": max_price,
74
+ "sort_by": sort_by,
75
+ "after": after,
76
+ },
77
+ )
78
+ render(data, fmt, fields)
79
+
80
+
81
+ @app.command(name="marketplace-item")
82
+ def marketplace_item(
83
+ item_id: str = typer.Argument(..., help="Marketplace item ID"),
84
+ fmt: str = FMT,
85
+ fields: str | None = FIELDS,
86
+ ) -> None:
87
+ """Get full detail for a Marketplace listing."""
88
+ data = api_get(f"/v1/facebook/marketplace/item/{item_id}")
89
+ render(data, fmt, fields)
90
+
91
+
92
+ @app.command()
93
+ def locations(
94
+ fmt: str = FMT,
95
+ fields: str | None = FIELDS,
96
+ ) -> None:
97
+ """List common Marketplace location slugs."""
98
+ data = api_get("/v1/facebook/marketplace/locations")
99
+ render(data, fmt, fields)
100
+
101
+
102
+ @app.command()
103
+ def categories(
104
+ fmt: str = FMT,
105
+ fields: str | None = FIELDS,
106
+ ) -> None:
107
+ """List Marketplace category slugs."""
108
+ data = api_get("/v1/facebook/marketplace/categories")
109
+ render(data, fmt, fields)
110
+
111
+
112
+ @app.command()
113
+ def search(
114
+ q: str = typer.Argument(..., help="Search query"),
115
+ after: str | None = AFTER,
116
+ fmt: str = FMT,
117
+ fields: str | None = FIELDS,
118
+ ) -> None:
119
+ """Search Facebook globally (pages, people, groups, posts)."""
120
+ data = api_get("/v1/facebook/search", {"q": q, "after": after})
121
+ render(data, fmt, fields)
122
+
123
+
124
+ @app.command(name="search-pages")
125
+ def search_pages(
126
+ q: str = typer.Argument(..., help="Search query"),
127
+ after: str | None = AFTER,
128
+ fmt: str = FMT,
129
+ fields: str | None = FIELDS,
130
+ ) -> None:
131
+ """Search Facebook Pages."""
132
+ data = api_get("/v1/facebook/search/pages", {"q": q, "after": after})
133
+ render(data, fmt, fields)
134
+
135
+
136
+ @app.command(name="search-people")
137
+ def search_people(
138
+ q: str = typer.Argument(..., help="Search query"),
139
+ after: str | None = AFTER,
140
+ fmt: str = FMT,
141
+ fields: str | None = FIELDS,
142
+ ) -> None:
143
+ """Search Facebook profiles."""
144
+ data = api_get("/v1/facebook/search/people", {"q": q, "after": after})
145
+ render(data, fmt, fields)
146
+
147
+
148
+ @app.command(name="search-groups")
149
+ def search_groups(
150
+ q: str = typer.Argument(..., help="Search query"),
151
+ after: str | None = AFTER,
152
+ fmt: str = FMT,
153
+ fields: str | None = FIELDS,
154
+ ) -> None:
155
+ """Search Facebook groups."""
156
+ data = api_get("/v1/facebook/search/groups", {"q": q, "after": after})
157
+ render(data, fmt, fields)
158
+
159
+
160
+ @app.command(name="search-posts")
161
+ def search_posts(
162
+ q: str = typer.Argument(..., help="Search query"),
163
+ after: str | None = AFTER,
164
+ fmt: str = FMT,
165
+ fields: str | None = FIELDS,
166
+ ) -> None:
167
+ """Search public Facebook posts."""
168
+ data = api_get("/v1/facebook/search/posts", {"q": q, "after": after})
169
+ render(data, fmt, fields)
170
+
171
+
172
+ @app.command()
173
+ def page(
174
+ identifier: str = typer.Argument(..., help="Page username or numeric page ID"),
175
+ fmt: str = FMT,
176
+ fields: str | None = FIELDS,
177
+ ) -> None:
178
+ """Get a Facebook Page's profile."""
179
+ data = api_get(f"/v1/facebook/pages/{identifier}")
180
+ render(data, fmt, fields)
181
+
182
+
183
+ @app.command(name="page-posts")
184
+ def page_posts(
185
+ identifier: str = typer.Argument(..., help="Page username or numeric page ID"),
186
+ after: str | None = AFTER,
187
+ fmt: str = FMT,
188
+ fields: str | None = FIELDS,
189
+ ) -> None:
190
+ """Get a Facebook Page's timeline posts."""
191
+ data = api_get(f"/v1/facebook/pages/{identifier}/posts", {"after": after})
192
+ render(data, fmt, fields)
193
+
194
+
195
+ @app.command()
196
+ def profile(
197
+ identifier: str = typer.Argument(..., help="Profile username or numeric profile ID"),
198
+ fmt: str = FMT,
199
+ fields: str | None = FIELDS,
200
+ ) -> None:
201
+ """Get a Facebook profile's details."""
202
+ data = api_get(f"/v1/facebook/profiles/{identifier}")
203
+ render(data, fmt, fields)
204
+
205
+
206
+ @app.command()
207
+ def group(
208
+ group_id: str = typer.Argument(..., help="Group ID or vanity slug"),
209
+ fmt: str = FMT,
210
+ fields: str | None = FIELDS,
211
+ ) -> None:
212
+ """Get a Facebook group's details."""
213
+ data = api_get(f"/v1/facebook/groups/{group_id}")
214
+ render(data, fmt, fields)
215
+
216
+
217
+ @app.command()
218
+ def post(
219
+ post_id: str = typer.Argument(..., help="Post ID"),
220
+ url: str | None = typer.Option(
221
+ None, "--url", help="Full permalink/reel URL (overrides post_id)"
222
+ ),
223
+ fmt: str = FMT,
224
+ fields: str | None = FIELDS,
225
+ ) -> None:
226
+ """Get a Facebook post's detail plus its top comments."""
227
+ data = api_get(f"/v1/facebook/posts/{post_id}", {"url": url})
228
+ render(data, fmt, fields)
229
+
230
+
231
+ @app.command(name="post-comments")
232
+ def post_comments(
233
+ post_id: str = typer.Argument(..., help="Post ID"),
234
+ url: str | None = typer.Option(
235
+ None, "--url", help="Full permalink/reel URL (overrides post_id)"
236
+ ),
237
+ after: str | None = AFTER,
238
+ sort: str = typer.Option("relevance", "--sort", help="relevance, newest, most_relevant"),
239
+ fmt: str = FMT,
240
+ fields: str | None = FIELDS,
241
+ ) -> None:
242
+ """Get a Facebook post's comment thread."""
243
+ data = api_get(
244
+ f"/v1/facebook/posts/{post_id}/comments",
245
+ {"url": url, "after": after, "sort": sort},
246
+ )
247
+ render(data, fmt, fields)
248
+
249
+
250
+ @app.command(name="ads-search")
251
+ def ads_search(
252
+ query: str = typer.Argument(..., help="Advertiser name or keyword"),
253
+ country: str = typer.Option("US", "--country", "-c", help="ISO country code"),
254
+ ad_type: str = typer.Option("all", "--ad-type"),
255
+ active_status: str = typer.Option("active", "--active-status", help="active, inactive, or all"),
256
+ after: str | None = AFTER,
257
+ fmt: str = FMT,
258
+ fields: str | None = FIELDS,
259
+ ) -> None:
260
+ """Search the Facebook Ad Library."""
261
+ data = api_get(
262
+ "/v1/facebook/ads/search",
263
+ {
264
+ "query": query,
265
+ "country": country,
266
+ "ad_type": ad_type,
267
+ "active_status": active_status,
268
+ "after": after,
269
+ },
270
+ )
271
+ render(data, fmt, fields)
@@ -0,0 +1,532 @@
1
+ """Instagram commands."""
2
+
3
+ import typer
4
+
5
+ from scrapebadger_cli.client import api_get
6
+ from scrapebadger_cli.output import render
7
+
8
+ app = typer.Typer(no_args_is_help=True)
9
+
10
+ FMT = typer.Option("json", "--output", "-o", help="Output format: json, csv, table, markdown")
11
+ FIELDS = typer.Option(None, "--fields", "-f", help="Comma-separated fields to include")
12
+ AMOUNT = typer.Option(20, "--amount", "-n", help="Number of items to return")
13
+ CURSOR = typer.Option(None, "--cursor", "-c", help="Pagination cursor")
14
+
15
+
16
+ # ── Profile ────────────────────────────────────────────────────────────────
17
+
18
+
19
+ @app.command()
20
+ def user(
21
+ username: str = typer.Argument(..., help="Instagram username"),
22
+ fmt: str = FMT,
23
+ fields: str | None = FIELDS,
24
+ ) -> None:
25
+ """Get user profile."""
26
+ data = api_get(f"/v1/instagram/users/{username}")
27
+ render(data, fmt, fields)
28
+
29
+
30
+ @app.command()
31
+ def about(
32
+ username: str = typer.Argument(..., help="Instagram username"),
33
+ fmt: str = FMT,
34
+ fields: str | None = FIELDS,
35
+ ) -> None:
36
+ """Get 'About this account' info."""
37
+ data = api_get(f"/v1/instagram/users/{username}/about")
38
+ render(data, fmt, fields)
39
+
40
+
41
+ @app.command()
42
+ def related(
43
+ username: str = typer.Argument(..., help="Instagram username"),
44
+ fmt: str = FMT,
45
+ fields: str | None = FIELDS,
46
+ ) -> None:
47
+ """Get related/suggested accounts."""
48
+ data = api_get(f"/v1/instagram/users/{username}/related")
49
+ render(data, fmt, fields)
50
+
51
+
52
+ # ── Media feeds ────────────────────────────────────────────────────────────
53
+
54
+
55
+ @app.command()
56
+ def posts(
57
+ username: str = typer.Argument(..., help="Instagram username"),
58
+ amount: int = AMOUNT,
59
+ cursor: str | None = CURSOR,
60
+ fmt: str = FMT,
61
+ fields: str | None = FIELDS,
62
+ ) -> None:
63
+ """Get a user's posts."""
64
+ data = api_get(f"/v1/instagram/users/{username}/posts", {"amount": amount, "cursor": cursor})
65
+ render(data, fmt, fields)
66
+
67
+
68
+ @app.command()
69
+ def videos(
70
+ username: str = typer.Argument(..., help="Instagram username"),
71
+ amount: int = AMOUNT,
72
+ cursor: str | None = CURSOR,
73
+ fmt: str = FMT,
74
+ fields: str | None = FIELDS,
75
+ ) -> None:
76
+ """Get a user's videos."""
77
+ data = api_get(f"/v1/instagram/users/{username}/videos", {"amount": amount, "cursor": cursor})
78
+ render(data, fmt, fields)
79
+
80
+
81
+ @app.command()
82
+ def reels(
83
+ username: str = typer.Argument(..., help="Instagram username"),
84
+ amount: int = AMOUNT,
85
+ cursor: str | None = CURSOR,
86
+ fmt: str = FMT,
87
+ fields: str | None = FIELDS,
88
+ ) -> None:
89
+ """Get a user's reels."""
90
+ data = api_get(f"/v1/instagram/users/{username}/reels", {"amount": amount, "cursor": cursor})
91
+ render(data, fmt, fields)
92
+
93
+
94
+ @app.command()
95
+ def tagged(
96
+ username: str = typer.Argument(..., help="Instagram username"),
97
+ amount: int = AMOUNT,
98
+ cursor: str | None = CURSOR,
99
+ fmt: str = FMT,
100
+ fields: str | None = FIELDS,
101
+ ) -> None:
102
+ """Get media a user is tagged in."""
103
+ data = api_get(f"/v1/instagram/users/{username}/tagged", {"amount": amount, "cursor": cursor})
104
+ render(data, fmt, fields)
105
+
106
+
107
+ @app.command()
108
+ def pinned(
109
+ username: str = typer.Argument(..., help="Instagram username"),
110
+ fmt: str = FMT,
111
+ fields: str | None = FIELDS,
112
+ ) -> None:
113
+ """Get a user's pinned posts."""
114
+ data = api_get(f"/v1/instagram/users/{username}/pinned")
115
+ render(data, fmt, fields)
116
+
117
+
118
+ # ── Media detail ───────────────────────────────────────────────────────────
119
+
120
+
121
+ @app.command()
122
+ def media(
123
+ code: str = typer.Argument(..., help="Media shortcode"),
124
+ fmt: str = FMT,
125
+ fields: str | None = FIELDS,
126
+ ) -> None:
127
+ """Get a media item by shortcode."""
128
+ data = api_get(f"/v1/instagram/media/{code}")
129
+ render(data, fmt, fields)
130
+
131
+
132
+ @app.command()
133
+ def oembed(
134
+ code: str = typer.Argument(..., help="Media shortcode"),
135
+ fmt: str = FMT,
136
+ fields: str | None = FIELDS,
137
+ ) -> None:
138
+ """Get oEmbed metadata for a media item."""
139
+ data = api_get(f"/v1/instagram/media/{code}/oembed")
140
+ render(data, fmt, fields)
141
+
142
+
143
+ # ── Graph (followers / following) ──────────────────────────────────────────
144
+
145
+
146
+ @app.command()
147
+ def followers(
148
+ username: str = typer.Argument(..., help="Instagram username"),
149
+ amount: int = AMOUNT,
150
+ cursor: str | None = CURSOR,
151
+ order: str | None = typer.Option(None, "--order", help="Sort order"),
152
+ fmt: str = FMT,
153
+ fields: str | None = FIELDS,
154
+ ) -> None:
155
+ """Get a user's followers."""
156
+ data = api_get(
157
+ f"/v1/instagram/users/{username}/followers",
158
+ {"amount": amount, "cursor": cursor, "order": order},
159
+ )
160
+ render(data, fmt, fields)
161
+
162
+
163
+ @app.command()
164
+ def following(
165
+ username: str = typer.Argument(..., help="Instagram username"),
166
+ amount: int = AMOUNT,
167
+ cursor: str | None = CURSOR,
168
+ fmt: str = FMT,
169
+ fields: str | None = FIELDS,
170
+ ) -> None:
171
+ """Get accounts a user follows."""
172
+ data = api_get(
173
+ f"/v1/instagram/users/{username}/following", {"amount": amount, "cursor": cursor}
174
+ )
175
+ render(data, fmt, fields)
176
+
177
+
178
+ @app.command(name="search-followers")
179
+ def search_followers(
180
+ username: str = typer.Argument(..., help="Instagram username"),
181
+ query: str = typer.Argument(..., help="Search query"),
182
+ amount: int = AMOUNT,
183
+ cursor: str | None = CURSOR,
184
+ fmt: str = FMT,
185
+ fields: str | None = FIELDS,
186
+ ) -> None:
187
+ """Search within a user's followers."""
188
+ data = api_get(
189
+ f"/v1/instagram/users/{username}/followers/search",
190
+ {"query": query, "amount": amount, "cursor": cursor},
191
+ )
192
+ render(data, fmt, fields)
193
+
194
+
195
+ # ── Stories & highlights ───────────────────────────────────────────────────
196
+
197
+
198
+ @app.command()
199
+ def stories(
200
+ username: str = typer.Argument(..., help="Instagram username"),
201
+ fmt: str = FMT,
202
+ fields: str | None = FIELDS,
203
+ ) -> None:
204
+ """Get a user's active stories."""
205
+ data = api_get(f"/v1/instagram/users/{username}/stories")
206
+ render(data, fmt, fields)
207
+
208
+
209
+ @app.command()
210
+ def highlights(
211
+ username: str = typer.Argument(..., help="Instagram username"),
212
+ fmt: str = FMT,
213
+ fields: str | None = FIELDS,
214
+ ) -> None:
215
+ """Get a user's highlight reels."""
216
+ data = api_get(f"/v1/instagram/users/{username}/highlights")
217
+ render(data, fmt, fields)
218
+
219
+
220
+ @app.command()
221
+ def story(
222
+ story_pk: str = typer.Argument(..., help="Story primary key"),
223
+ fmt: str = FMT,
224
+ fields: str | None = FIELDS,
225
+ ) -> None:
226
+ """Get a single story."""
227
+ data = api_get(f"/v1/instagram/stories/{story_pk}")
228
+ render(data, fmt, fields)
229
+
230
+
231
+ @app.command()
232
+ def highlight(
233
+ highlight_pk: str = typer.Argument(..., help="Highlight primary key"),
234
+ fmt: str = FMT,
235
+ fields: str | None = FIELDS,
236
+ ) -> None:
237
+ """Get a highlight reel and its items."""
238
+ data = api_get(f"/v1/instagram/highlights/{highlight_pk}")
239
+ render(data, fmt, fields)
240
+
241
+
242
+ # ── Comments & likers ──────────────────────────────────────────────────────
243
+
244
+
245
+ @app.command()
246
+ def comments(
247
+ code: str = typer.Argument(..., help="Media shortcode"),
248
+ amount: int = AMOUNT,
249
+ cursor: str | None = CURSOR,
250
+ fmt: str = FMT,
251
+ fields: str | None = FIELDS,
252
+ ) -> None:
253
+ """Get comments on a media item."""
254
+ data = api_get(f"/v1/instagram/media/{code}/comments", {"amount": amount, "cursor": cursor})
255
+ render(data, fmt, fields)
256
+
257
+
258
+ @app.command(name="comment-replies")
259
+ def comment_replies(
260
+ code: str = typer.Argument(..., help="Media shortcode"),
261
+ comment_id: str = typer.Argument(..., help="Parent comment ID"),
262
+ amount: int = AMOUNT,
263
+ cursor: str | None = CURSOR,
264
+ fmt: str = FMT,
265
+ fields: str | None = FIELDS,
266
+ ) -> None:
267
+ """Get replies to a comment."""
268
+ data = api_get(
269
+ f"/v1/instagram/media/{code}/comments/{comment_id}/replies",
270
+ {"amount": amount, "cursor": cursor},
271
+ )
272
+ render(data, fmt, fields)
273
+
274
+
275
+ @app.command(name="comment-likers")
276
+ def comment_likers(
277
+ code: str = typer.Argument(..., help="Media shortcode"),
278
+ comment_id: str = typer.Argument(..., help="Comment ID"),
279
+ amount: int = AMOUNT,
280
+ cursor: str | None = CURSOR,
281
+ fmt: str = FMT,
282
+ fields: str | None = FIELDS,
283
+ ) -> None:
284
+ """Get users who liked a comment."""
285
+ data = api_get(
286
+ f"/v1/instagram/media/{code}/comments/{comment_id}/likers",
287
+ {"amount": amount, "cursor": cursor},
288
+ )
289
+ render(data, fmt, fields)
290
+
291
+
292
+ @app.command()
293
+ def likers(
294
+ code: str = typer.Argument(..., help="Media shortcode"),
295
+ amount: int = AMOUNT,
296
+ cursor: str | None = CURSOR,
297
+ fmt: str = FMT,
298
+ fields: str | None = FIELDS,
299
+ ) -> None:
300
+ """Get users who liked a media item."""
301
+ data = api_get(f"/v1/instagram/media/{code}/likers", {"amount": amount, "cursor": cursor})
302
+ render(data, fmt, fields)
303
+
304
+
305
+ # ── Hashtags ───────────────────────────────────────────────────────────────
306
+
307
+
308
+ @app.command()
309
+ def hashtag(
310
+ tag: str = typer.Argument(..., help="Hashtag (without #)"),
311
+ fmt: str = FMT,
312
+ fields: str | None = FIELDS,
313
+ ) -> None:
314
+ """Get hashtag metadata."""
315
+ data = api_get(f"/v1/instagram/hashtags/{tag}")
316
+ render(data, fmt, fields)
317
+
318
+
319
+ @app.command(name="hashtag-top")
320
+ def hashtag_top(
321
+ tag: str = typer.Argument(..., help="Hashtag (without #)"),
322
+ amount: int = AMOUNT,
323
+ cursor: str | None = CURSOR,
324
+ fmt: str = FMT,
325
+ fields: str | None = FIELDS,
326
+ ) -> None:
327
+ """Get top media for a hashtag."""
328
+ data = api_get(f"/v1/instagram/hashtags/{tag}/top", {"amount": amount, "cursor": cursor})
329
+ render(data, fmt, fields)
330
+
331
+
332
+ @app.command(name="hashtag-recent")
333
+ def hashtag_recent(
334
+ tag: str = typer.Argument(..., help="Hashtag (without #)"),
335
+ amount: int = AMOUNT,
336
+ cursor: str | None = CURSOR,
337
+ fmt: str = FMT,
338
+ fields: str | None = FIELDS,
339
+ ) -> None:
340
+ """Get recent media for a hashtag."""
341
+ data = api_get(f"/v1/instagram/hashtags/{tag}/recent", {"amount": amount, "cursor": cursor})
342
+ render(data, fmt, fields)
343
+
344
+
345
+ @app.command(name="hashtag-reels")
346
+ def hashtag_reels(
347
+ tag: str = typer.Argument(..., help="Hashtag (without #)"),
348
+ amount: int = AMOUNT,
349
+ cursor: str | None = CURSOR,
350
+ fmt: str = FMT,
351
+ fields: str | None = FIELDS,
352
+ ) -> None:
353
+ """Get reels for a hashtag."""
354
+ data = api_get(f"/v1/instagram/hashtags/{tag}/reels", {"amount": amount, "cursor": cursor})
355
+ render(data, fmt, fields)
356
+
357
+
358
+ # ── Locations ──────────────────────────────────────────────────────────────
359
+
360
+
361
+ @app.command()
362
+ def location(
363
+ location_pk: str = typer.Argument(..., help="Location primary key"),
364
+ fmt: str = FMT,
365
+ fields: str | None = FIELDS,
366
+ ) -> None:
367
+ """Get location metadata."""
368
+ data = api_get(f"/v1/instagram/locations/{location_pk}")
369
+ render(data, fmt, fields)
370
+
371
+
372
+ @app.command(name="location-top")
373
+ def location_top(
374
+ location_pk: str = typer.Argument(..., help="Location primary key"),
375
+ amount: int = AMOUNT,
376
+ cursor: str | None = CURSOR,
377
+ fmt: str = FMT,
378
+ fields: str | None = FIELDS,
379
+ ) -> None:
380
+ """Get top media for a location."""
381
+ data = api_get(
382
+ f"/v1/instagram/locations/{location_pk}/top", {"amount": amount, "cursor": cursor}
383
+ )
384
+ render(data, fmt, fields)
385
+
386
+
387
+ @app.command(name="location-recent")
388
+ def location_recent(
389
+ location_pk: str = typer.Argument(..., help="Location primary key"),
390
+ amount: int = AMOUNT,
391
+ cursor: str | None = CURSOR,
392
+ fmt: str = FMT,
393
+ fields: str | None = FIELDS,
394
+ ) -> None:
395
+ """Get recent media for a location."""
396
+ data = api_get(
397
+ f"/v1/instagram/locations/{location_pk}/recent", {"amount": amount, "cursor": cursor}
398
+ )
399
+ render(data, fmt, fields)
400
+
401
+
402
+ @app.command(name="location-search")
403
+ def location_search(
404
+ query: str | None = typer.Option(None, "--query", "-q", help="Location name"),
405
+ lat: float | None = typer.Option(None, "--lat", help="Latitude"),
406
+ lng: float | None = typer.Option(None, "--lng", help="Longitude"),
407
+ fmt: str = FMT,
408
+ fields: str | None = FIELDS,
409
+ ) -> None:
410
+ """Search locations by name or coordinates."""
411
+ data = api_get("/v1/instagram/locations/search", {"query": query, "lat": lat, "lng": lng})
412
+ render(data, fmt, fields)
413
+
414
+
415
+ # ── Search ─────────────────────────────────────────────────────────────────
416
+
417
+
418
+ @app.command(name="search-users")
419
+ def search_users(
420
+ query: str = typer.Argument(..., help="Search query"),
421
+ fmt: str = FMT,
422
+ fields: str | None = FIELDS,
423
+ ) -> None:
424
+ """Search users."""
425
+ data = api_get("/v1/instagram/search/users", {"query": query})
426
+ render(data, fmt, fields)
427
+
428
+
429
+ @app.command(name="search-hashtags")
430
+ def search_hashtags(
431
+ query: str = typer.Argument(..., help="Search query"),
432
+ fmt: str = FMT,
433
+ fields: str | None = FIELDS,
434
+ ) -> None:
435
+ """Search hashtags."""
436
+ data = api_get("/v1/instagram/search/hashtags", {"query": query})
437
+ render(data, fmt, fields)
438
+
439
+
440
+ @app.command(name="search-places")
441
+ def search_places(
442
+ query: str | None = typer.Option(None, "--query", "-q", help="Search query"),
443
+ lat: float | None = typer.Option(None, "--lat", help="Latitude"),
444
+ lng: float | None = typer.Option(None, "--lng", help="Longitude"),
445
+ fmt: str = FMT,
446
+ fields: str | None = FIELDS,
447
+ ) -> None:
448
+ """Search places by name or coordinates."""
449
+ data = api_get("/v1/instagram/search/places", {"query": query, "lat": lat, "lng": lng})
450
+ render(data, fmt, fields)
451
+
452
+
453
+ @app.command(name="search-top")
454
+ def search_top(
455
+ query: str = typer.Argument(..., help="Search query"),
456
+ fmt: str = FMT,
457
+ fields: str | None = FIELDS,
458
+ ) -> None:
459
+ """Top (blended) search across users, hashtags, and places."""
460
+ data = api_get("/v1/instagram/search/top", {"query": query})
461
+ render(data, fmt, fields)
462
+
463
+
464
+ @app.command(name="search-reels")
465
+ def search_reels(
466
+ query: str = typer.Argument(..., help="Search query"),
467
+ fmt: str = FMT,
468
+ fields: str | None = FIELDS,
469
+ ) -> None:
470
+ """Search reels."""
471
+ data = api_get("/v1/instagram/search/reels", {"query": query})
472
+ render(data, fmt, fields)
473
+
474
+
475
+ @app.command(name="search-music")
476
+ def search_music(
477
+ query: str = typer.Argument(..., help="Search query"),
478
+ fmt: str = FMT,
479
+ fields: str | None = FIELDS,
480
+ ) -> None:
481
+ """Search music/audio."""
482
+ data = api_get("/v1/instagram/search/music", {"query": query})
483
+ render(data, fmt, fields)
484
+
485
+
486
+ @app.command()
487
+ def autocomplete(
488
+ query: str = typer.Argument(..., help="Partial search query"),
489
+ fmt: str = FMT,
490
+ fields: str | None = FIELDS,
491
+ ) -> None:
492
+ """Get search autocomplete suggestions."""
493
+ data = api_get("/v1/instagram/search/autocomplete", {"query": query})
494
+ render(data, fmt, fields)
495
+
496
+
497
+ # ── Audio ──────────────────────────────────────────────────────────────────
498
+
499
+
500
+ @app.command()
501
+ def audio(
502
+ audio_id: str = typer.Argument(..., help="Audio/music ID"),
503
+ fmt: str = FMT,
504
+ fields: str | None = FIELDS,
505
+ ) -> None:
506
+ """Get audio track metadata."""
507
+ data = api_get(f"/v1/instagram/audio/{audio_id}")
508
+ render(data, fmt, fields)
509
+
510
+
511
+ @app.command(name="audio-media")
512
+ def audio_media(
513
+ audio_id: str = typer.Argument(..., help="Audio/music ID"),
514
+ amount: int = AMOUNT,
515
+ cursor: str | None = CURSOR,
516
+ fmt: str = FMT,
517
+ fields: str | None = FIELDS,
518
+ ) -> None:
519
+ """Get media using an audio track."""
520
+ data = api_get(f"/v1/instagram/audio/{audio_id}/media", {"amount": amount, "cursor": cursor})
521
+ render(data, fmt, fields)
522
+
523
+
524
+ @app.command(name="trending-audio")
525
+ def trending_audio(
526
+ product: str | None = typer.Option(None, "--product", "-p", help="Product/surface filter"),
527
+ fmt: str = FMT,
528
+ fields: str | None = FIELDS,
529
+ ) -> None:
530
+ """Get trending audio."""
531
+ data = api_get("/v1/instagram/audio/trending", {"product": product})
532
+ render(data, fmt, fields)
@@ -9,7 +9,7 @@ app = typer.Typer(no_args_is_help=True)
9
9
 
10
10
  FMT = typer.Option("json", "--output", "-o", help="Output format: json, csv, table, markdown")
11
11
  FIELDS = typer.Option(None, "--fields", "-f", help="Comma-separated fields to include")
12
- MARKET = typer.Option("fr", "--market", "-m", help="Vinted market code (fr, de, uk, us, etc.)")
12
+ MARKET = typer.Option("fr", "--market", "-m", help="Vinted market code (fr, de, gb, us, au, etc.)")
13
13
 
14
14
 
15
15
  @app.command()
@@ -4,13 +4,16 @@ import typer
4
4
 
5
5
  from scrapebadger_cli.commands import (
6
6
  amazon,
7
+ apartments,
7
8
  auth,
8
9
  chatgpt,
9
10
  depop,
10
11
  ebay,
12
+ facebook,
11
13
  google,
12
14
  idealista,
13
15
  immobiliare,
16
+ instagram,
14
17
  leboncoin,
15
18
  linkedin,
16
19
  loopnet,
@@ -34,6 +37,7 @@ app = typer.Typer(
34
37
 
35
38
  app.add_typer(twitter.app, name="twitter", help="Twitter/X data commands")
36
39
  app.add_typer(reddit.app, name="reddit", help="Reddit data commands")
40
+ app.add_typer(instagram.app, name="instagram", help="Instagram data commands")
37
41
  app.add_typer(stream.app, name="stream", help="Real-time streaming commands")
38
42
  app.add_typer(vinted.app, name="vinted", help="Vinted marketplace commands")
39
43
  app.add_typer(amazon.app, name="amazon", help="Amazon marketplace commands")
@@ -50,8 +54,14 @@ app.add_typer(realtor.app, name="realtor", help="Realtor real-estate commands")
50
54
  app.add_typer(loopnet.app, name="loopnet", help="LoopNet commercial real estate commands")
51
55
  app.add_typer(depop.app, name="depop", help="Depop marketplace commands")
52
56
  app.add_typer(redfin.app, name="redfin", help="Redfin real-estate commands")
57
+ app.add_typer(apartments.app, name="apartments", help="Apartments.com rental commands")
53
58
  app.add_typer(linkedin.app, name="linkedin", help="LinkedIn public data commands")
54
59
  app.add_typer(chatgpt.app, name="chatgpt", help="ChatGPT LLM scraping commands")
60
+ app.add_typer(
61
+ facebook.app,
62
+ name="facebook",
63
+ help="Facebook pages, groups, posts, search & Marketplace commands",
64
+ )
55
65
  app.command()(auth.auth)
56
66
  app.command(name="credits")(auth.credits)
57
67
  app.command(name="logout")(auth.logout)
@@ -228,7 +228,7 @@ wheels = [
228
228
 
229
229
  [[package]]
230
230
  name = "scrapebadger-cli"
231
- version = "0.4.1"
231
+ version = "0.6.0"
232
232
  source = { editable = "." }
233
233
  dependencies = [
234
234
  { name = "httpx" },