feat(channel): page details chaine multi-providers avec onglets et infinite scroll
CI / build-and-test (push) Successful in 13m34s
CI / build-and-test (push) Successful in 13m34s
- Strategy Pattern front: ChannelProviderInterface + 6 providers (yt/dm/tw/pt/od/ru) + factory (ids longs/courts) - ChannelContentService: cache par onglet/tri/recherche (TTL 5min), pagination nextPage - ChannelPage: header (banniere/avatar/subs/description ...plus), tabs dynamiques par capabilities, grilles videos/shorts 9:16/playlists/live, recherche debounced, tri recent/populaire, infinite scroll IntersectionObserver, skeletons/empty/error - Backend: GET /api/channels/:provider/:id/content (videos/shorts/playlists/live) natif par provider
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Rumble Cloudflare bypass fetcher.
|
||||
|
||||
Node's TLS fingerprint (OpenSSL) is blocked by Rumble's Cloudflare config, while
|
||||
curl_cffi's browser impersonation passes. This helper fetches a URL with
|
||||
curl_cffi impersonating a Chrome client and prints the raw HTML to stdout.
|
||||
|
||||
Usage: python3 rumble_fetch.py <url> [timeout_seconds]
|
||||
Exit codes: 0 = ok (stdout = html), 2 = HTTP error / challenge, 3 = curl_cffi missing.
|
||||
"""
|
||||
import sys
|
||||
|
||||
URL = sys.argv[1] if len(sys.argv) > 1 else ""
|
||||
TIMEOUT = int(sys.argv[2]) if len(sys.argv) > 2 else 15
|
||||
|
||||
try:
|
||||
from curl_cffi import requests as curl_requests
|
||||
except ImportError:
|
||||
sys.stderr.write("curl_cffi not installed\n")
|
||||
sys.exit(3)
|
||||
|
||||
if not URL:
|
||||
sys.stderr.write("url required\n")
|
||||
sys.exit(2)
|
||||
|
||||
IMPERSONATE_CANDIDATES = ["chrome131", "chrome124", "chrome120", "safari18_0"]
|
||||
|
||||
for target in IMPERSONATE_CANDIDATES:
|
||||
try:
|
||||
resp = curl_requests.get(
|
||||
URL,
|
||||
impersonate=target,
|
||||
timeout=TIMEOUT,
|
||||
allow_redirects=True,
|
||||
headers={
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
},
|
||||
)
|
||||
body = resp.text or ""
|
||||
if resp.status_code == 200 and "Just a moment" not in body[:4000]:
|
||||
sys.stdout.write(body)
|
||||
sys.exit(0)
|
||||
# challenge or error: try next impersonation target
|
||||
last_status = resp.status_code
|
||||
except Exception as exc: # noqa: BLE001
|
||||
sys.stderr.write(f"{target}: {exc}\n")
|
||||
last_status = 0
|
||||
|
||||
sys.stderr.write(f"all impersonation targets failed (last={last_status})\n")
|
||||
sys.exit(2)
|
||||
Reference in New Issue
Block a user