scrapebadger-cli 0.2.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.
- scrapebadger_cli/__init__.py +1 -0
- scrapebadger_cli/client.py +57 -0
- scrapebadger_cli/commands/__init__.py +1 -0
- scrapebadger_cli/commands/amazon.py +227 -0
- scrapebadger_cli/commands/auth.py +52 -0
- scrapebadger_cli/commands/ebay.py +217 -0
- scrapebadger_cli/commands/google.py +1117 -0
- scrapebadger_cli/commands/idealista.py +220 -0
- scrapebadger_cli/commands/immobiliare.py +151 -0
- scrapebadger_cli/commands/leboncoin.py +154 -0
- scrapebadger_cli/commands/loopnet.py +89 -0
- scrapebadger_cli/commands/realtor.py +91 -0
- scrapebadger_cli/commands/reddit.py +173 -0
- scrapebadger_cli/commands/stream.py +270 -0
- scrapebadger_cli/commands/tiktok.py +283 -0
- scrapebadger_cli/commands/twitter.py +235 -0
- scrapebadger_cli/commands/vinted.py +105 -0
- scrapebadger_cli/commands/web.py +117 -0
- scrapebadger_cli/commands/youtube.py +625 -0
- scrapebadger_cli/commands/zillow.py +105 -0
- scrapebadger_cli/config.py +69 -0
- scrapebadger_cli/main.py +52 -0
- scrapebadger_cli/output.py +116 -0
- scrapebadger_cli-0.2.1.dist-info/METADATA +41 -0
- scrapebadger_cli-0.2.1.dist-info/RECORD +27 -0
- scrapebadger_cli-0.2.1.dist-info/WHEEL +4 -0
- scrapebadger_cli-0.2.1.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
"""YouTube scraper commands."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from scrapebadger_cli.client import api_get, api_post
|
|
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
|
+
GL = typer.Option(None, "--gl", help="Content region (US, GB, DE, BR, JP, ...)")
|
|
13
|
+
HL = typer.Option(None, "--hl", help="UI language (en, es, fr, ...)")
|
|
14
|
+
CONT = typer.Option(None, "--continuation", "-c", help="Pagination continuation token")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
# Search & autocomplete
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
@app.command()
|
|
21
|
+
def search(
|
|
22
|
+
query: str = typer.Argument(..., help="Search keywords"),
|
|
23
|
+
type: str | None = typer.Option(None, "--type", "-t", help="video|channel|playlist|movie|all"),
|
|
24
|
+
sort_by: str | None = typer.Option(None, "--sort-by", help="relevance|date|views|rating"),
|
|
25
|
+
upload_date: str | None = typer.Option(
|
|
26
|
+
None, "--upload-date", help="hour|today|week|month|year"
|
|
27
|
+
),
|
|
28
|
+
duration: str | None = typer.Option(None, "--duration", help="short|medium|long"),
|
|
29
|
+
features: str | None = typer.Option(None, "--features", help="Comma-separated (hd,4k,cc,...)"),
|
|
30
|
+
gl: str | None = GL,
|
|
31
|
+
hl: str | None = HL,
|
|
32
|
+
continuation: str | None = CONT,
|
|
33
|
+
fmt: str = FMT,
|
|
34
|
+
fields: str | None = FIELDS,
|
|
35
|
+
) -> None:
|
|
36
|
+
"""Search YouTube videos, channels and playlists."""
|
|
37
|
+
data = api_get(
|
|
38
|
+
"/v1/youtube/search",
|
|
39
|
+
{
|
|
40
|
+
"query": query,
|
|
41
|
+
"type": type,
|
|
42
|
+
"sort_by": sort_by,
|
|
43
|
+
"upload_date": upload_date,
|
|
44
|
+
"duration": duration,
|
|
45
|
+
"features": features,
|
|
46
|
+
"gl": gl,
|
|
47
|
+
"hl": hl,
|
|
48
|
+
"continuation": continuation,
|
|
49
|
+
},
|
|
50
|
+
)
|
|
51
|
+
render(data, fmt, fields)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@app.command()
|
|
55
|
+
def autocomplete(
|
|
56
|
+
query: str = typer.Argument(..., help="Partial query prefix"),
|
|
57
|
+
gl: str | None = GL,
|
|
58
|
+
hl: str | None = HL,
|
|
59
|
+
fmt: str = FMT,
|
|
60
|
+
fields: str | None = FIELDS,
|
|
61
|
+
) -> None:
|
|
62
|
+
"""Get YouTube search autocomplete suggestions."""
|
|
63
|
+
data = api_get("/v1/youtube/autocomplete", {"query": query, "gl": gl, "hl": hl})
|
|
64
|
+
render(data, fmt, fields)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
# Videos
|
|
69
|
+
# ---------------------------------------------------------------------------
|
|
70
|
+
@app.command()
|
|
71
|
+
def video(
|
|
72
|
+
video_id: str = typer.Argument(..., help="YouTube video ID"),
|
|
73
|
+
gl: str | None = GL,
|
|
74
|
+
hl: str | None = HL,
|
|
75
|
+
fmt: str = FMT,
|
|
76
|
+
fields: str | None = FIELDS,
|
|
77
|
+
) -> None:
|
|
78
|
+
"""Get full video detail (player + next merged)."""
|
|
79
|
+
data = api_get(f"/v1/youtube/videos/{video_id}", {"gl": gl, "hl": hl})
|
|
80
|
+
render(data, fmt, fields)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@app.command()
|
|
84
|
+
def batch(
|
|
85
|
+
video_ids: str = typer.Argument(..., help="Comma-separated video IDs (max 50)"),
|
|
86
|
+
gl: str | None = GL,
|
|
87
|
+
hl: str | None = HL,
|
|
88
|
+
fmt: str = FMT,
|
|
89
|
+
fields: str | None = FIELDS,
|
|
90
|
+
) -> None:
|
|
91
|
+
"""Get details for many videos in one call (billed per video)."""
|
|
92
|
+
ids = [v.strip() for v in video_ids.split(",") if v.strip()]
|
|
93
|
+
data = api_post("/v1/youtube/videos/batch", {"video_ids": ids, "gl": gl, "hl": hl})
|
|
94
|
+
render(data, fmt, fields)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@app.command()
|
|
98
|
+
def related(
|
|
99
|
+
video_id: str = typer.Argument(..., help="YouTube video ID"),
|
|
100
|
+
gl: str | None = GL,
|
|
101
|
+
hl: str | None = HL,
|
|
102
|
+
continuation: str | None = CONT,
|
|
103
|
+
fmt: str = FMT,
|
|
104
|
+
fields: str | None = FIELDS,
|
|
105
|
+
) -> None:
|
|
106
|
+
"""Get related videos for a video."""
|
|
107
|
+
data = api_get(
|
|
108
|
+
f"/v1/youtube/videos/{video_id}/related",
|
|
109
|
+
{"gl": gl, "hl": hl, "continuation": continuation},
|
|
110
|
+
)
|
|
111
|
+
render(data, fmt, fields)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@app.command()
|
|
115
|
+
def comments(
|
|
116
|
+
video_id: str = typer.Argument(..., help="YouTube video ID"),
|
|
117
|
+
sort_by: str = typer.Option("top", "--sort-by", help="top|newest"),
|
|
118
|
+
gl: str | None = GL,
|
|
119
|
+
hl: str | None = HL,
|
|
120
|
+
continuation: str | None = CONT,
|
|
121
|
+
fmt: str = FMT,
|
|
122
|
+
fields: str | None = FIELDS,
|
|
123
|
+
) -> None:
|
|
124
|
+
"""Get top-level comments on a video."""
|
|
125
|
+
data = api_get(
|
|
126
|
+
f"/v1/youtube/videos/{video_id}/comments",
|
|
127
|
+
{"sort_by": sort_by, "gl": gl, "hl": hl, "continuation": continuation},
|
|
128
|
+
)
|
|
129
|
+
render(data, fmt, fields)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@app.command()
|
|
133
|
+
def replies(
|
|
134
|
+
video_id: str = typer.Argument(..., help="YouTube video ID"),
|
|
135
|
+
comment_id: str = typer.Argument(..., help="Parent comment ID"),
|
|
136
|
+
continuation: str = typer.Option(
|
|
137
|
+
..., "--continuation", "-c", help="Replies continuation token (from a comment)"
|
|
138
|
+
),
|
|
139
|
+
gl: str | None = GL,
|
|
140
|
+
hl: str | None = HL,
|
|
141
|
+
fmt: str = FMT,
|
|
142
|
+
fields: str | None = FIELDS,
|
|
143
|
+
) -> None:
|
|
144
|
+
"""Get replies to a comment."""
|
|
145
|
+
data = api_get(
|
|
146
|
+
f"/v1/youtube/videos/{video_id}/comments/{comment_id}/replies",
|
|
147
|
+
{"continuation": continuation, "gl": gl, "hl": hl},
|
|
148
|
+
)
|
|
149
|
+
render(data, fmt, fields)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@app.command()
|
|
153
|
+
def transcript(
|
|
154
|
+
video_id: str = typer.Argument(..., help="YouTube video ID"),
|
|
155
|
+
language: str | None = typer.Option(None, "--language", "-l", help="BCP-47 language code"),
|
|
156
|
+
gl: str | None = GL,
|
|
157
|
+
hl: str | None = HL,
|
|
158
|
+
fmt: str = FMT,
|
|
159
|
+
fields: str | None = FIELDS,
|
|
160
|
+
) -> None:
|
|
161
|
+
"""Get the transcript / timed captions for a video."""
|
|
162
|
+
data = api_get(
|
|
163
|
+
f"/v1/youtube/videos/{video_id}/transcript",
|
|
164
|
+
{"language": language, "gl": gl, "hl": hl},
|
|
165
|
+
)
|
|
166
|
+
render(data, fmt, fields)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@app.command()
|
|
170
|
+
def captions(
|
|
171
|
+
video_id: str = typer.Argument(..., help="YouTube video ID"),
|
|
172
|
+
gl: str | None = GL,
|
|
173
|
+
hl: str | None = HL,
|
|
174
|
+
fmt: str = FMT,
|
|
175
|
+
fields: str | None = FIELDS,
|
|
176
|
+
) -> None:
|
|
177
|
+
"""List the available caption tracks for a video."""
|
|
178
|
+
data = api_get(f"/v1/youtube/videos/{video_id}/captions", {"gl": gl, "hl": hl})
|
|
179
|
+
render(data, fmt, fields)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@app.command()
|
|
183
|
+
def streams(
|
|
184
|
+
video_id: str = typer.Argument(..., help="YouTube video ID"),
|
|
185
|
+
client: str = typer.Option("IOS", "--client", help="IOS|ANDROID|WEB"),
|
|
186
|
+
gl: str | None = GL,
|
|
187
|
+
fmt: str = FMT,
|
|
188
|
+
fields: str | None = FIELDS,
|
|
189
|
+
) -> None:
|
|
190
|
+
"""Get downloadable stream formats for a video (best-effort)."""
|
|
191
|
+
data = api_get(f"/v1/youtube/videos/{video_id}/streams", {"client": client, "gl": gl})
|
|
192
|
+
render(data, fmt, fields)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@app.command(name="live-chat")
|
|
196
|
+
def live_chat(
|
|
197
|
+
video_id: str = typer.Argument(..., help="YouTube video ID"),
|
|
198
|
+
replay: bool = typer.Option(False, "--replay", help="Fetch chat replay for a finished stream"),
|
|
199
|
+
gl: str | None = GL,
|
|
200
|
+
hl: str | None = HL,
|
|
201
|
+
continuation: str | None = CONT,
|
|
202
|
+
fmt: str = FMT,
|
|
203
|
+
fields: str | None = FIELDS,
|
|
204
|
+
) -> None:
|
|
205
|
+
"""Get live chat messages for a stream."""
|
|
206
|
+
data = api_get(
|
|
207
|
+
f"/v1/youtube/videos/{video_id}/live_chat",
|
|
208
|
+
{"replay": replay, "gl": gl, "hl": hl, "continuation": continuation},
|
|
209
|
+
)
|
|
210
|
+
render(data, fmt, fields)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
# ---------------------------------------------------------------------------
|
|
214
|
+
# Channels
|
|
215
|
+
# ---------------------------------------------------------------------------
|
|
216
|
+
@app.command()
|
|
217
|
+
def channel(
|
|
218
|
+
channel_id: str = typer.Argument(..., help="Channel ID, @handle or custom URL"),
|
|
219
|
+
gl: str | None = GL,
|
|
220
|
+
hl: str | None = HL,
|
|
221
|
+
fmt: str = FMT,
|
|
222
|
+
fields: str | None = FIELDS,
|
|
223
|
+
) -> None:
|
|
224
|
+
"""Get a channel's profile (about + header)."""
|
|
225
|
+
data = api_get(f"/v1/youtube/channels/{channel_id}", {"gl": gl, "hl": hl})
|
|
226
|
+
render(data, fmt, fields)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@app.command(name="channel-videos")
|
|
230
|
+
def channel_videos(
|
|
231
|
+
channel_id: str = typer.Argument(..., help="Channel ID"),
|
|
232
|
+
gl: str | None = GL,
|
|
233
|
+
hl: str | None = HL,
|
|
234
|
+
continuation: str | None = CONT,
|
|
235
|
+
fmt: str = FMT,
|
|
236
|
+
fields: str | None = FIELDS,
|
|
237
|
+
) -> None:
|
|
238
|
+
"""Get a channel's uploaded videos."""
|
|
239
|
+
data = api_get(
|
|
240
|
+
f"/v1/youtube/channels/{channel_id}/videos",
|
|
241
|
+
{"gl": gl, "hl": hl, "continuation": continuation},
|
|
242
|
+
)
|
|
243
|
+
render(data, fmt, fields)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@app.command(name="channel-shorts")
|
|
247
|
+
def channel_shorts(
|
|
248
|
+
channel_id: str = typer.Argument(..., help="Channel ID"),
|
|
249
|
+
gl: str | None = GL,
|
|
250
|
+
hl: str | None = HL,
|
|
251
|
+
continuation: str | None = CONT,
|
|
252
|
+
fmt: str = FMT,
|
|
253
|
+
fields: str | None = FIELDS,
|
|
254
|
+
) -> None:
|
|
255
|
+
"""Get a channel's Shorts."""
|
|
256
|
+
data = api_get(
|
|
257
|
+
f"/v1/youtube/channels/{channel_id}/shorts",
|
|
258
|
+
{"gl": gl, "hl": hl, "continuation": continuation},
|
|
259
|
+
)
|
|
260
|
+
render(data, fmt, fields)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@app.command(name="channel-streams")
|
|
264
|
+
def channel_streams(
|
|
265
|
+
channel_id: str = typer.Argument(..., help="Channel ID"),
|
|
266
|
+
gl: str | None = GL,
|
|
267
|
+
hl: str | None = HL,
|
|
268
|
+
continuation: str | None = CONT,
|
|
269
|
+
fmt: str = FMT,
|
|
270
|
+
fields: str | None = FIELDS,
|
|
271
|
+
) -> None:
|
|
272
|
+
"""Get a channel's live streams / past broadcasts."""
|
|
273
|
+
data = api_get(
|
|
274
|
+
f"/v1/youtube/channels/{channel_id}/streams",
|
|
275
|
+
{"gl": gl, "hl": hl, "continuation": continuation},
|
|
276
|
+
)
|
|
277
|
+
render(data, fmt, fields)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@app.command(name="channel-playlists")
|
|
281
|
+
def channel_playlists(
|
|
282
|
+
channel_id: str = typer.Argument(..., help="Channel ID"),
|
|
283
|
+
gl: str | None = GL,
|
|
284
|
+
hl: str | None = HL,
|
|
285
|
+
continuation: str | None = CONT,
|
|
286
|
+
fmt: str = FMT,
|
|
287
|
+
fields: str | None = FIELDS,
|
|
288
|
+
) -> None:
|
|
289
|
+
"""Get a channel's playlists."""
|
|
290
|
+
data = api_get(
|
|
291
|
+
f"/v1/youtube/channels/{channel_id}/playlists",
|
|
292
|
+
{"gl": gl, "hl": hl, "continuation": continuation},
|
|
293
|
+
)
|
|
294
|
+
render(data, fmt, fields)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
@app.command(name="channel-community")
|
|
298
|
+
def channel_community(
|
|
299
|
+
channel_id: str = typer.Argument(..., help="Channel ID"),
|
|
300
|
+
gl: str | None = GL,
|
|
301
|
+
hl: str | None = HL,
|
|
302
|
+
continuation: str | None = CONT,
|
|
303
|
+
fmt: str = FMT,
|
|
304
|
+
fields: str | None = FIELDS,
|
|
305
|
+
) -> None:
|
|
306
|
+
"""Get a channel's community / posts feed."""
|
|
307
|
+
data = api_get(
|
|
308
|
+
f"/v1/youtube/channels/{channel_id}/community",
|
|
309
|
+
{"gl": gl, "hl": hl, "continuation": continuation},
|
|
310
|
+
)
|
|
311
|
+
render(data, fmt, fields)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
@app.command(name="channel-about")
|
|
315
|
+
def channel_about(
|
|
316
|
+
channel_id: str = typer.Argument(..., help="Channel ID"),
|
|
317
|
+
gl: str | None = GL,
|
|
318
|
+
hl: str | None = HL,
|
|
319
|
+
fmt: str = FMT,
|
|
320
|
+
fields: str | None = FIELDS,
|
|
321
|
+
) -> None:
|
|
322
|
+
"""Get a channel's About tab (links, stats, joined date, country)."""
|
|
323
|
+
data = api_get(f"/v1/youtube/channels/{channel_id}/about", {"gl": gl, "hl": hl})
|
|
324
|
+
render(data, fmt, fields)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
@app.command(name="subscriber-count")
|
|
328
|
+
def subscriber_count(
|
|
329
|
+
channel_id: str = typer.Argument(..., help="Channel ID"),
|
|
330
|
+
gl: str | None = GL,
|
|
331
|
+
hl: str | None = HL,
|
|
332
|
+
fmt: str = FMT,
|
|
333
|
+
fields: str | None = FIELDS,
|
|
334
|
+
) -> None:
|
|
335
|
+
"""Get a channel's subscriber count (fast path)."""
|
|
336
|
+
data = api_get(f"/v1/youtube/channels/{channel_id}/subscriber_count", {"gl": gl, "hl": hl})
|
|
337
|
+
render(data, fmt, fields)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
@app.command(name="channel-search")
|
|
341
|
+
def channel_search(
|
|
342
|
+
channel_id: str = typer.Argument(..., help="Channel ID"),
|
|
343
|
+
query: str = typer.Argument(..., help="Search keywords"),
|
|
344
|
+
gl: str | None = GL,
|
|
345
|
+
hl: str | None = HL,
|
|
346
|
+
continuation: str | None = CONT,
|
|
347
|
+
fmt: str = FMT,
|
|
348
|
+
fields: str | None = FIELDS,
|
|
349
|
+
) -> None:
|
|
350
|
+
"""Search within a channel's content."""
|
|
351
|
+
data = api_get(
|
|
352
|
+
f"/v1/youtube/channels/{channel_id}/search",
|
|
353
|
+
{"query": query, "gl": gl, "hl": hl, "continuation": continuation},
|
|
354
|
+
)
|
|
355
|
+
render(data, fmt, fields)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
@app.command()
|
|
359
|
+
def resolve(
|
|
360
|
+
handle: str | None = typer.Option(None, "--handle", help="@handle or custom name"),
|
|
361
|
+
url: str | None = typer.Option(None, "--url", help="Full channel/video/playlist URL"),
|
|
362
|
+
gl: str | None = GL,
|
|
363
|
+
hl: str | None = HL,
|
|
364
|
+
fmt: str = FMT,
|
|
365
|
+
fields: str | None = FIELDS,
|
|
366
|
+
) -> None:
|
|
367
|
+
"""Resolve a @handle or URL to a channel ID."""
|
|
368
|
+
data = api_get(
|
|
369
|
+
"/v1/youtube/channels/resolve",
|
|
370
|
+
{"handle": handle, "url": url, "gl": gl, "hl": hl},
|
|
371
|
+
)
|
|
372
|
+
render(data, fmt, fields)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
# ---------------------------------------------------------------------------
|
|
376
|
+
# Playlists & mixes
|
|
377
|
+
# ---------------------------------------------------------------------------
|
|
378
|
+
@app.command()
|
|
379
|
+
def playlist(
|
|
380
|
+
playlist_id: str = typer.Argument(..., help="Playlist ID"),
|
|
381
|
+
gl: str | None = GL,
|
|
382
|
+
hl: str | None = HL,
|
|
383
|
+
fmt: str = FMT,
|
|
384
|
+
fields: str | None = FIELDS,
|
|
385
|
+
) -> None:
|
|
386
|
+
"""Get playlist metadata (+ first item page)."""
|
|
387
|
+
data = api_get(f"/v1/youtube/playlists/{playlist_id}", {"gl": gl, "hl": hl})
|
|
388
|
+
render(data, fmt, fields)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
@app.command(name="playlist-items")
|
|
392
|
+
def playlist_items(
|
|
393
|
+
playlist_id: str = typer.Argument(..., help="Playlist ID"),
|
|
394
|
+
gl: str | None = GL,
|
|
395
|
+
hl: str | None = HL,
|
|
396
|
+
continuation: str | None = CONT,
|
|
397
|
+
fmt: str = FMT,
|
|
398
|
+
fields: str | None = FIELDS,
|
|
399
|
+
) -> None:
|
|
400
|
+
"""Get the items in a playlist (paginated)."""
|
|
401
|
+
data = api_get(
|
|
402
|
+
f"/v1/youtube/playlists/{playlist_id}/items",
|
|
403
|
+
{"gl": gl, "hl": hl, "continuation": continuation},
|
|
404
|
+
)
|
|
405
|
+
render(data, fmt, fields)
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
@app.command()
|
|
409
|
+
def mix(
|
|
410
|
+
playlist_id: str = typer.Argument(..., help="Mix / radio playlist ID (RD...)"),
|
|
411
|
+
gl: str | None = GL,
|
|
412
|
+
hl: str | None = HL,
|
|
413
|
+
fmt: str = FMT,
|
|
414
|
+
fields: str | None = FIELDS,
|
|
415
|
+
) -> None:
|
|
416
|
+
"""Get a YouTube mix / radio playlist."""
|
|
417
|
+
data = api_get(f"/v1/youtube/mixes/{playlist_id}", {"gl": gl, "hl": hl})
|
|
418
|
+
render(data, fmt, fields)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
# ---------------------------------------------------------------------------
|
|
422
|
+
# Trending, hashtags & home
|
|
423
|
+
# ---------------------------------------------------------------------------
|
|
424
|
+
@app.command()
|
|
425
|
+
def trending(
|
|
426
|
+
type: str = typer.Option("now", "--type", "-t", help="now|music|gaming|movies"),
|
|
427
|
+
gl: str | None = GL,
|
|
428
|
+
hl: str | None = HL,
|
|
429
|
+
continuation: str | None = CONT,
|
|
430
|
+
fmt: str = FMT,
|
|
431
|
+
fields: str | None = FIELDS,
|
|
432
|
+
) -> None:
|
|
433
|
+
"""Get the trending feed for a region."""
|
|
434
|
+
data = api_get(
|
|
435
|
+
"/v1/youtube/trending",
|
|
436
|
+
{"type": type, "gl": gl, "hl": hl, "continuation": continuation},
|
|
437
|
+
)
|
|
438
|
+
render(data, fmt, fields)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
@app.command(name="trending-shorts")
|
|
442
|
+
def trending_shorts(
|
|
443
|
+
gl: str | None = GL,
|
|
444
|
+
hl: str | None = HL,
|
|
445
|
+
continuation: str | None = CONT,
|
|
446
|
+
fmt: str = FMT,
|
|
447
|
+
fields: str | None = FIELDS,
|
|
448
|
+
) -> None:
|
|
449
|
+
"""Get the trending Shorts shelf for a region."""
|
|
450
|
+
data = api_get(
|
|
451
|
+
"/v1/youtube/trending/shorts",
|
|
452
|
+
{"gl": gl, "hl": hl, "continuation": continuation},
|
|
453
|
+
)
|
|
454
|
+
render(data, fmt, fields)
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
@app.command()
|
|
458
|
+
def hashtag(
|
|
459
|
+
tag: str = typer.Argument(..., help="Hashtag (without #)"),
|
|
460
|
+
gl: str | None = GL,
|
|
461
|
+
hl: str | None = HL,
|
|
462
|
+
continuation: str | None = CONT,
|
|
463
|
+
fmt: str = FMT,
|
|
464
|
+
fields: str | None = FIELDS,
|
|
465
|
+
) -> None:
|
|
466
|
+
"""Get the feed for a hashtag."""
|
|
467
|
+
data = api_get(
|
|
468
|
+
f"/v1/youtube/hashtags/{tag}",
|
|
469
|
+
{"gl": gl, "hl": hl, "continuation": continuation},
|
|
470
|
+
)
|
|
471
|
+
render(data, fmt, fields)
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
@app.command()
|
|
475
|
+
def home(
|
|
476
|
+
gl: str | None = GL,
|
|
477
|
+
hl: str | None = HL,
|
|
478
|
+
continuation: str | None = CONT,
|
|
479
|
+
fmt: str = FMT,
|
|
480
|
+
fields: str | None = FIELDS,
|
|
481
|
+
) -> None:
|
|
482
|
+
"""Get the guest home feed."""
|
|
483
|
+
data = api_get("/v1/youtube/home", {"gl": gl, "hl": hl, "continuation": continuation})
|
|
484
|
+
render(data, fmt, fields)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
# ---------------------------------------------------------------------------
|
|
488
|
+
# Shorts
|
|
489
|
+
# ---------------------------------------------------------------------------
|
|
490
|
+
@app.command()
|
|
491
|
+
def short(
|
|
492
|
+
video_id: str = typer.Argument(..., help="Short video ID"),
|
|
493
|
+
gl: str | None = GL,
|
|
494
|
+
hl: str | None = HL,
|
|
495
|
+
fmt: str = FMT,
|
|
496
|
+
fields: str | None = FIELDS,
|
|
497
|
+
) -> None:
|
|
498
|
+
"""Get full metadata for a Short."""
|
|
499
|
+
data = api_get(f"/v1/youtube/shorts/{video_id}", {"gl": gl, "hl": hl})
|
|
500
|
+
render(data, fmt, fields)
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
@app.command(name="shorts-by-sound")
|
|
504
|
+
def shorts_by_sound(
|
|
505
|
+
sound_id: str = typer.Argument(..., help="Sound / music ID"),
|
|
506
|
+
gl: str | None = GL,
|
|
507
|
+
hl: str | None = HL,
|
|
508
|
+
continuation: str | None = CONT,
|
|
509
|
+
fmt: str = FMT,
|
|
510
|
+
fields: str | None = FIELDS,
|
|
511
|
+
) -> None:
|
|
512
|
+
"""Get Shorts that use a given sound."""
|
|
513
|
+
data = api_get(
|
|
514
|
+
f"/v1/youtube/shorts/by_sound/{sound_id}",
|
|
515
|
+
{"gl": gl, "hl": hl, "continuation": continuation},
|
|
516
|
+
)
|
|
517
|
+
render(data, fmt, fields)
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
# ---------------------------------------------------------------------------
|
|
521
|
+
# Community posts
|
|
522
|
+
# ---------------------------------------------------------------------------
|
|
523
|
+
@app.command()
|
|
524
|
+
def post(
|
|
525
|
+
post_id: str = typer.Argument(..., help="Community post ID"),
|
|
526
|
+
gl: str | None = GL,
|
|
527
|
+
hl: str | None = HL,
|
|
528
|
+
fmt: str = FMT,
|
|
529
|
+
fields: str | None = FIELDS,
|
|
530
|
+
) -> None:
|
|
531
|
+
"""Get a community post detail."""
|
|
532
|
+
data = api_get(f"/v1/youtube/posts/{post_id}", {"gl": gl, "hl": hl})
|
|
533
|
+
render(data, fmt, fields)
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
@app.command(name="post-comments")
|
|
537
|
+
def post_comments(
|
|
538
|
+
post_id: str = typer.Argument(..., help="Community post ID"),
|
|
539
|
+
gl: str | None = GL,
|
|
540
|
+
hl: str | None = HL,
|
|
541
|
+
continuation: str | None = CONT,
|
|
542
|
+
fmt: str = FMT,
|
|
543
|
+
fields: str | None = FIELDS,
|
|
544
|
+
) -> None:
|
|
545
|
+
"""Get comments on a community post."""
|
|
546
|
+
data = api_get(
|
|
547
|
+
f"/v1/youtube/posts/{post_id}/comments",
|
|
548
|
+
{"gl": gl, "hl": hl, "continuation": continuation},
|
|
549
|
+
)
|
|
550
|
+
render(data, fmt, fields)
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
# ---------------------------------------------------------------------------
|
|
554
|
+
# Music & oEmbed
|
|
555
|
+
# ---------------------------------------------------------------------------
|
|
556
|
+
@app.command(name="music-search")
|
|
557
|
+
def music_search(
|
|
558
|
+
query: str = typer.Argument(..., help="Search keywords"),
|
|
559
|
+
gl: str | None = GL,
|
|
560
|
+
hl: str | None = HL,
|
|
561
|
+
continuation: str | None = CONT,
|
|
562
|
+
fmt: str = FMT,
|
|
563
|
+
fields: str | None = FIELDS,
|
|
564
|
+
) -> None:
|
|
565
|
+
"""Search YouTube Music."""
|
|
566
|
+
data = api_get(
|
|
567
|
+
"/v1/youtube/music/search",
|
|
568
|
+
{"query": query, "gl": gl, "hl": hl, "continuation": continuation},
|
|
569
|
+
)
|
|
570
|
+
render(data, fmt, fields)
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
@app.command()
|
|
574
|
+
def oembed(
|
|
575
|
+
url: str = typer.Argument(..., help="A YouTube video/playlist/channel URL"),
|
|
576
|
+
fmt: str = FMT,
|
|
577
|
+
fields: str | None = FIELDS,
|
|
578
|
+
) -> None:
|
|
579
|
+
"""Get cheap unauthenticated oEmbed metadata for a URL."""
|
|
580
|
+
data = api_get("/v1/youtube/oembed", {"url": url})
|
|
581
|
+
render(data, fmt, fields)
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
# ---------------------------------------------------------------------------
|
|
585
|
+
# Reference (zero-credit)
|
|
586
|
+
# ---------------------------------------------------------------------------
|
|
587
|
+
@app.command()
|
|
588
|
+
def categories(
|
|
589
|
+
gl: str | None = GL,
|
|
590
|
+
fmt: str = FMT,
|
|
591
|
+
fields: str | None = FIELDS,
|
|
592
|
+
) -> None:
|
|
593
|
+
"""List YouTube video categories."""
|
|
594
|
+
data = api_get("/v1/youtube/categories", {"gl": gl})
|
|
595
|
+
render(data, fmt, fields)
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
@app.command()
|
|
599
|
+
def languages(
|
|
600
|
+
fmt: str = FMT,
|
|
601
|
+
fields: str | None = FIELDS,
|
|
602
|
+
) -> None:
|
|
603
|
+
"""List supported UI languages (hl)."""
|
|
604
|
+
data = api_get("/v1/youtube/languages")
|
|
605
|
+
render(data, fmt, fields)
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
@app.command()
|
|
609
|
+
def regions(
|
|
610
|
+
fmt: str = FMT,
|
|
611
|
+
fields: str | None = FIELDS,
|
|
612
|
+
) -> None:
|
|
613
|
+
"""List supported content regions (gl)."""
|
|
614
|
+
data = api_get("/v1/youtube/regions")
|
|
615
|
+
render(data, fmt, fields)
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
@app.command()
|
|
619
|
+
def markets(
|
|
620
|
+
fmt: str = FMT,
|
|
621
|
+
fields: str | None = FIELDS,
|
|
622
|
+
) -> None:
|
|
623
|
+
"""List all supported YouTube markets."""
|
|
624
|
+
data = api_get("/v1/youtube/markets")
|
|
625
|
+
render(data, fmt, fields)
|