@danhachuel/thunderbolt 0.7.59 → 0.7.60
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/integrations/platforms.py +56 -0
- package/package.json +1 -1
|
@@ -248,6 +248,45 @@ def _public_feed_data(channel_id: str, headers: dict[str, str]) -> dict[str, Any
|
|
|
248
248
|
return {}
|
|
249
249
|
|
|
250
250
|
|
|
251
|
+
def _public_page_videos(document: str, channel_id: str, limit: int) -> list[dict[str, Any]]:
|
|
252
|
+
"""Extract public video cards from ytInitialData as an RSS fallback."""
|
|
253
|
+
initial_data = _extract_json_assignment(document, "ytInitialData") or {}
|
|
254
|
+
videos: list[dict[str, Any]] = []
|
|
255
|
+
|
|
256
|
+
def visit(value: Any) -> None:
|
|
257
|
+
if len(videos) >= limit:
|
|
258
|
+
return
|
|
259
|
+
if isinstance(value, dict):
|
|
260
|
+
renderer = value.get("videoRenderer")
|
|
261
|
+
if isinstance(renderer, dict):
|
|
262
|
+
video_id = str(renderer.get("videoId") or "").strip()
|
|
263
|
+
if video_id:
|
|
264
|
+
title = _text_from_node(renderer.get("title")) or "Vídeo sem título"
|
|
265
|
+
thumbnails = ((renderer.get("thumbnail") or {}).get("thumbnails") or [])
|
|
266
|
+
thumbnail = str(thumbnails[-1].get("url", "")) if thumbnails and isinstance(thumbnails[-1], dict) else ""
|
|
267
|
+
videos.append({
|
|
268
|
+
"id": f"youtube_{video_id}",
|
|
269
|
+
"youtube_video_id": video_id,
|
|
270
|
+
"channel_id": channel_id,
|
|
271
|
+
"title": title,
|
|
272
|
+
"published_at": _text_from_node(renderer.get("publishedTimeText")),
|
|
273
|
+
"updated_at": "",
|
|
274
|
+
"url": f"https://www.youtube.com/watch?v={video_id}",
|
|
275
|
+
"thumbnail_url": thumbnail,
|
|
276
|
+
"source": "youtube_public_page",
|
|
277
|
+
"status": "publicado",
|
|
278
|
+
})
|
|
279
|
+
return
|
|
280
|
+
for child in value.values():
|
|
281
|
+
visit(child)
|
|
282
|
+
elif isinstance(value, list):
|
|
283
|
+
for child in value:
|
|
284
|
+
visit(child)
|
|
285
|
+
|
|
286
|
+
visit(initial_data)
|
|
287
|
+
return videos[:limit]
|
|
288
|
+
|
|
289
|
+
|
|
251
290
|
def fetch_channel_videos_public(channel_ref: str | dict[str, Any], limit: int = 10) -> "IntegrationResult":
|
|
252
291
|
"""Fetch the latest public channel videos from YouTube's Atom feed without an API key."""
|
|
253
292
|
if isinstance(channel_ref, dict):
|
|
@@ -281,6 +320,23 @@ def fetch_channel_videos_public(channel_ref: str | dict[str, Any], limit: int =
|
|
|
281
320
|
response.raise_for_status()
|
|
282
321
|
root = ET.fromstring(response.text)
|
|
283
322
|
except (requests.RequestException, ET.ParseError, ValueError) as exc:
|
|
323
|
+
for page_url in _public_page_candidates(f"https://www.youtube.com/channel/{channel_id}"):
|
|
324
|
+
try:
|
|
325
|
+
page_response = requests.get(
|
|
326
|
+
page_url,
|
|
327
|
+
headers={**headers, "Accept": "text/html,application/xhtml+xml"},
|
|
328
|
+
timeout=12,
|
|
329
|
+
)
|
|
330
|
+
page_response.raise_for_status()
|
|
331
|
+
except requests.RequestException:
|
|
332
|
+
continue
|
|
333
|
+
fallback_videos = _public_page_videos(page_response.text or "", channel_id, max(1, min(10, int(limit or 10))))
|
|
334
|
+
if fallback_videos:
|
|
335
|
+
return IntegrationResult(
|
|
336
|
+
True,
|
|
337
|
+
f"{len(fallback_videos)} vídeo(s) público(s) carregado(s) pela página do canal sem API Key.",
|
|
338
|
+
{"channel_id": channel_id, "videos": fallback_videos},
|
|
339
|
+
)
|
|
284
340
|
return IntegrationResult(False, f"Não foi possível carregar os vídeos públicos do canal: {exc}", {"channel_id": channel_id, "videos": []})
|
|
285
341
|
namespace = {
|
|
286
342
|
"yt": "http://www.youtube.com/xml/schemas/2015",
|
package/package.json
CHANGED