From cb257f84cd59c868e88de87f0d17f81ae44a7c8c Mon Sep 17 00:00:00 2001 From: salvacybersec Date: Thu, 13 Nov 2025 06:49:44 +0300 Subject: [PATCH] player error --- src/transcript_extractor.py | 166 +++++++++++++++++++++++++++--------- 1 file changed, 128 insertions(+), 38 deletions(-) diff --git a/src/transcript_extractor.py b/src/transcript_extractor.py index dd9d597..4703d30 100644 --- a/src/transcript_extractor.py +++ b/src/transcript_extractor.py @@ -69,7 +69,8 @@ class TranscriptExtractor: try: import requests - test_response = requests.get(f"{self.flaresolverr_url.replace('/v1', '')}/v1", timeout=5) + # Timeout'u kısalt (erişilemezse hemen normal isteğe geç) + test_response = requests.get(f"{self.flaresolverr_url.replace('/v1', '')}/v1", timeout=3) if test_response.status_code == 405: # Method Not Allowed normal (GET yerine POST bekliyor) logger.info("[FLARESOLVERR] ✅ FlareSolverr erişilebilir") self.flaresolverr_available = True @@ -78,11 +79,15 @@ class TranscriptExtractor: logger.warning(f"[FLARESOLVERR] ⚠️ FlareSolverr yanıtı beklenmedik: {test_response.status_code}") self.flaresolverr_available = False return False - except Exception as e: - logger.warning(f"[FLARESOLVERR] ⚠️ FlareSolverr test edilemedi: {e}") - logger.warning(f"[FLARESOLVERR] Test başarısız, ancak gerçek isteklerde tekrar denenilecek") + except requests.exceptions.Timeout: + logger.warning(f"[FLARESOLVERR] ⚠️ FlareSolverr timeout (erişilemiyor), normal istekler kullanılacak") self.flaresolverr_available = False - # use_flaresolverr'ı False yapma, gerçek isteklerde tekrar dene + return False + except Exception as e: + logger.warning(f"[FLARESOLVERR] ⚠️ FlareSolverr test edilemedi: {type(e).__name__} - {str(e)[:100]}") + logger.warning(f"[FLARESOLVERR] Test başarısız, ancak gerçek isteklerde tekrar denenilecek (timeout: 10s)") + self.flaresolverr_available = False + # use_flaresolverr'ı False yapma, gerçek isteklerde tekrar dene (ama timeout kısa olacak) return False def _make_flaresolverr_request(self, url: str, method: str = 'GET', **kwargs) -> Optional: @@ -94,12 +99,25 @@ class TranscriptExtractor: import requests import json - # FlareSolverr API isteği - flaresolverr_payload = { - "cmd": "request.get", - "url": url, - "maxTimeout": 60000, # 60 saniye timeout - } + # FlareSolverr API isteği - GET veya POST + if method.upper() == 'POST': + flaresolverr_payload = { + "cmd": "request.post", + "url": url, + "maxTimeout": 60000, # 60 saniye timeout + } + + # POST data'sını ekle + if 'data' in kwargs: + flaresolverr_payload["postData"] = kwargs['data'] + elif 'json' in kwargs: + flaresolverr_payload["postData"] = json.dumps(kwargs['json']) + else: + flaresolverr_payload = { + "cmd": "request.get", + "url": url, + "maxTimeout": 60000, # 60 saniye timeout + } # Header'ları ekle headers = kwargs.get('headers', {}) @@ -108,59 +126,81 @@ class TranscriptExtractor: logger.debug(f"[FLARESOLVERR] İstek gönderiliyor: {url[:50]}...") + # FlareSolverr timeout'unu kısalt (erişilemezse hemen normal isteğe geç) response = requests.post( self.flaresolverr_url, json=flaresolverr_payload, - timeout=65 # FlareSolverr timeout'undan biraz fazla + timeout=10 # 10 saniye timeout (erişilemezse hemen normal isteğe geç) ) if response.status_code == 200: result = response.json() if result.get('status') == 'ok': solution = result.get('solution', {}) - # FlareSolverr response formatı: solution.status HTTP status code, solution.response HTML içerik + # FlareSolverr response formatı: solution.status HTTP status code, solution.response içerik (HTML veya JSON) status_code = solution.get('status', 200) - html = solution.get('response', '') + response_content = solution.get('response', '') headers = solution.get('headers', {}) # Response objesi oluştur (requests.Response benzeri) class FlareSolverrResponse: - def __init__(self, status_code, text, headers, url): + def __init__(self, status_code, text, headers, url, is_post=False): self.status_code = status_code self.text = text self.content = text.encode('utf-8') if isinstance(text, str) else text self.headers = headers if headers else {} self.url = url self.ok = 200 <= status_code < 300 + self.is_post = is_post # POST isteği mi? def json(self): import json try: + # POST istekleri genellikle JSON döndürür + if self.is_post: + logger.debug(f"[FLARESOLVERR] POST response JSON parse ediliyor...") + return json.loads(self.text) - except: + except json.JSONDecodeError as e: + logger.error(f"[FLARESOLVERR] ❌ JSON parse hatası: {e}") + logger.error(f"[FLARESOLVERR] İçerik (ilk 900 karakter): {self.text[:900]}") + return {} + except Exception as e: + logger.error(f"[FLARESOLVERR] ❌ JSON parse beklenmeyen hata: {type(e).__name__} - {str(e)}") return {} - logger.info(f"[FLARESOLVERR] ✅ İstek başarılı: HTTP {status_code}, {len(html)} byte içerik") + is_post_request = method.upper() == 'POST' + content_size = len(response_content) + logger.info(f"[FLARESOLVERR] ✅ İstek başarılı: HTTP {status_code}, {content_size} byte içerik ({'POST' if is_post_request else 'GET'})") - # Debug: HTML içeriğinin ilk 500 karakterini logla - logger.debug(f"[FLARESOLVERR] HTML önizleme (ilk 500 karakter): {html[:500]}") + # Debug: İçeriğin ilk 500 karakterini logla + logger.debug(f"[FLARESOLVERR] İçerik önizleme (ilk 500 karakter): {response_content[:500]}") - # Debug: HTML'de transcript ile ilgili pattern'leri kontrol et - import re - if 'ytInitialPlayerResponse' in html or 'ytInitialData' in html: - logger.debug(f"[FLARESOLVERR] ✅ YouTube player response bulundu HTML'de") + # GET istekleri için HTML analizi (POST istekleri genellikle JSON) + if not is_post_request: + import re + if 'ytInitialPlayerResponse' in response_content or 'ytInitialData' in response_content: + logger.debug(f"[FLARESOLVERR] ✅ YouTube player response bulundu HTML'de") + else: + logger.warning(f"[FLARESOLVERR] ⚠️ YouTube player response bulunamadı HTML'de") + + # Debug: Transcript endpoint URL'lerini ara + transcript_urls = re.findall(r'https?://[^"\s]+timedtext[^"\s]*', response_content) + if transcript_urls: + logger.debug(f"[FLARESOLVERR] ✅ Transcript URL'leri bulundu: {len(transcript_urls)} adet") + logger.debug(f"[FLARESOLVERR] İlk transcript URL: {transcript_urls[0][:100]}...") + else: + logger.warning(f"[FLARESOLVERR] ⚠️ Transcript URL'leri bulunamadı HTML'de") else: - logger.warning(f"[FLARESOLVERR] ⚠️ YouTube player response bulunamadı HTML'de") + # POST istekleri için JSON kontrolü + try: + import json + json.loads(response_content) + logger.debug(f"[FLARESOLVERR] ✅ POST response geçerli JSON") + except: + logger.warning(f"[FLARESOLVERR] ⚠️ POST response JSON değil, HTML olabilir") - # Debug: Transcript endpoint URL'lerini ara - transcript_urls = re.findall(r'https?://[^"\s]+timedtext[^"\s]*', html) - if transcript_urls: - logger.debug(f"[FLARESOLVERR] ✅ Transcript URL'leri bulundu: {len(transcript_urls)} adet") - logger.debug(f"[FLARESOLVERR] İlk transcript URL: {transcript_urls[0][:100]}...") - else: - logger.warning(f"[FLARESOLVERR] ⚠️ Transcript URL'leri bulunamadı HTML'de") - - return FlareSolverrResponse(status_code, html, headers, url) + return FlareSolverrResponse(status_code, response_content, headers, url, is_post=is_post_request) else: error = result.get('message', 'Unknown error') logger.error(f"[FLARESOLVERR] ❌ FlareSolverr hatası: {error}") @@ -238,7 +278,10 @@ class TranscriptExtractor: return PatchedResponse(flaresolverr_response) else: logger.debug(f"[FLARESOLVERR] FlareSolverr yanıt vermedi, normal istek deneniyor") - elif extractor_instance.use_flaresolverr and ('youtube.com' in url or 'youtu.be' in url): + elif extractor_instance.use_flaresolverr and is_video_page and not extractor_instance.flaresolverr_available: + # FlareSolverr erişilemiyor, normal istek yap + logger.debug(f"[FLARESOLVERR] FlareSolverr erişilemiyor, normal istek yapılıyor: {url[:50]}...") + elif extractor_instance.use_flaresolverr and ('youtube.com' in url or 'youtu.be' in url) and '/api/' in url: # Transcript API endpoint'leri için FlareSolverr kullanma, sadece header'ları ekle logger.debug(f"[FLARESOLVERR] Transcript API endpoint'i tespit edildi, FlareSolverr atlanıyor: {url[:50]}...") @@ -251,10 +294,57 @@ class TranscriptExtractor: def patched_post(session_self, url, **kwargs): """requests.Session.post'i patch et - header'ları ekle ve FlareSolverr kullan""" - # POST istekleri için FlareSolverr kullanma (genellikle API endpoint'leri) - # Sadece header'ları ekle - if extractor_instance.use_flaresolverr and ('youtube.com' in url or 'youtu.be' in url): - logger.debug(f"[FLARESOLVERR] POST isteği tespit edildi, FlareSolverr atlanıyor (sadece header'lar): {url[:50]}...") + # YouTube API endpoint'leri için FlareSolverr kullan (youtubei/v1/player gibi) + is_youtube_api = ('youtube.com' in url or 'youtu.be' in url) and ('/youtubei/v1/' in url or '/api/' in url) + + # FlareSolverr kullanılıyorsa ve YouTube API endpoint'i ise + if extractor_instance.use_flaresolverr and is_youtube_api and extractor_instance.flaresolverr_available: + logger.debug(f"[FLARESOLVERR] YouTube API POST isteği FlareSolverr üzerinden deneniyor: {url[:50]}...") + # POST için FlareSolverr'da request.post kullan + flaresolverr_response = extractor_instance._make_flaresolverr_request(url, 'POST', **kwargs) + if flaresolverr_response: + logger.debug(f"[FLARESOLVERR] ✅ FlareSolverr POST başarılı, response döndürülüyor") + class PatchedResponse: + def __init__(self, flaresolverr_response): + self.status_code = flaresolverr_response.status_code + self.text = flaresolverr_response.text + self.content = flaresolverr_response.content + self.headers = flaresolverr_response.headers + self.url = flaresolverr_response.url + self.ok = 200 <= self.status_code < 300 + + def json(self): + import json + try: + # Debug: JSON parse edilmeye çalışılan içeriği logla + logger.debug(f"[FLARESOLVERR] JSON parse deneniyor (POST), içerik tipi: {type(self.text)}, uzunluk: {len(self.text)}") + logger.debug(f"[FLARESOLVERR] İçerik önizleme (ilk 200 karakter): {self.text[:200]}") + + # Eğer HTML ise JSON parse etme + if self.text.strip().startswith('<') or 'html' in self.headers.get('content-type', '').lower(): + logger.warning(f"[FLARESOLVERR] ⚠️ HTML içerik JSON olarak parse edilmeye çalışılıyor, boş dict döndürülüyor") + return {} + + return json.loads(self.text) + except json.JSONDecodeError as e: + logger.error(f"[FLARESOLVERR] ❌ JSON parse hatası (POST): {e}") + logger.error(f"[FLARESOLVERR] İçerik (ilk 500 karakter): {self.text[:500]}") + return {} + except Exception as e: + logger.error(f"[FLARESOLVERR] ❌ JSON parse beklenmeyen hata (POST): {type(e).__name__} - {str(e)}") + return {} + + def raise_for_status(self): + """requests.Response.raise_for_status() uyumluluğu""" + if not self.ok: + from requests.exceptions import HTTPError + raise HTTPError(f"{self.status_code} Client Error: {self.text[:100]}", response=self) + + return PatchedResponse(flaresolverr_response) + else: + logger.debug(f"[FLARESOLVERR] FlareSolverr POST yanıt vermedi, normal istek deneniyor") + elif extractor_instance.use_flaresolverr and ('youtube.com' in url or 'youtu.be' in url): + logger.debug(f"[FLARESOLVERR] POST isteği tespit edildi, FlareSolverr kullanılmıyor (sadece header'lar): {url[:50]}...") # Normal istek (header'ları ekle) headers = kwargs.get('headers', {})