bep40 commited on
Commit
672def2
·
verified ·
1 Parent(s): 76e5e54

Upload main.py

Browse files
Files changed (1) hide show
  1. main.py +39 -16
main.py CHANGED
@@ -209,10 +209,19 @@ def proxy_img(url: str = Query(...)):
209
  except: return Response(status_code=502)
210
 
211
  # ===== XEMLAIBONGDA HIGHLIGHTS =====
 
 
 
 
 
 
 
 
212
  def _fetch_video_thumbnail(url):
213
  """Fetch og:image from a video detail page."""
214
  try:
215
- r = requests.get(url, headers=HEADERS, timeout=8)
 
216
  if r.status_code != 200:
217
  return ""
218
  r.encoding = "utf-8"
@@ -245,9 +254,16 @@ def _fetch_video_thumbnail(url):
245
  pass
246
  return ""
247
 
 
 
 
 
 
 
248
  def _scrape_xemlaibongda_page(page_path, limit=20):
249
  """
250
  Scrape video từ xemlaibongda.top - with proper image extraction
 
251
  """
252
  try:
253
  url = f"https://xemlaibongda.top/{page_path}" if page_path else "https://xemlaibongda.top/"
@@ -331,24 +347,36 @@ def _scrape_xemlaibongda_page(page_path, limit=20):
331
  if not title or len(title) < 3:
332
  continue
333
 
334
- # ===== FIX: Fetch thumbnail from page if not found =====
335
- if not img_src:
336
- img_src = _fetch_video_thumbnail(clean_href)
337
-
338
- # Proxy images through our server to avoid referer/CORS issues
339
- if img_src and "xemlaibongda.top" in img_src:
340
- img_src = '/api/proxy/img?url=' + quote(img_src, safe='')
341
 
342
  videos.append({
343
  "title": title[:100],
344
  "link": clean_href,
345
  "img": img_src,
346
- "source": "xemlaibongda"
 
347
  })
348
 
349
  if len(videos) >= limit:
350
  break
351
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
  return videos
353
  except Exception as e:
354
  print(f"[xemlaibongda] Error: {e}")
@@ -658,17 +686,14 @@ def api_video_url(url:str=Query(...)):
658
  return JSONResponse({"error":"not found"})
659
 
660
  # ===== WORLD CUP 2026 API =====
661
- # Rate limiting cho WC API
662
  _wc_request_times = []
663
  _wc_rate_limit_lock = threading.Lock()
664
- _WC_RATE_LIMIT = 10 # Max 10 requests per minute
665
 
666
  def _wc_rate_limit():
667
- """Kiểm tra rate limit cho WC API"""
668
  global _wc_request_times
669
  with _wc_rate_limit_lock:
670
  now = time.time()
671
- # Xóa các request cũ hơn 60 giây
672
  _wc_request_times = [t for t in _wc_request_times if now - t < 60]
673
  if len(_wc_request_times) >= _WC_RATE_LIMIT:
674
  return False
@@ -677,12 +702,10 @@ def _wc_rate_limit():
677
 
678
  @app.get("/api/wc2026")
679
  def api_wc2026():
680
- """Trả về tất cả dữ liệu World Cup 2026"""
681
  return JSONResponse(_cached("wc2026", get_wc2026_all, ttl=_cache_ttl))
682
 
683
  @app.get("/api/wc2026/{tab}")
684
  def api_wc2026_tab(tab: str):
685
- """Trả về từng tab của World Cup"""
686
  valid_tabs = ["news", "fixtures", "standings", "stats", "highlights"]
687
  if tab not in valid_tabs:
688
  return JSONResponse({"error": "invalid tab"}, status_code=400)
@@ -829,4 +852,4 @@ def api_hot_topics():
829
  @app.get("/", response_class=HTMLResponse)
830
  async def root():
831
  return HTMLResponse("<h1>VNEWS</h1><p>Running</p>")
832
- # v15 rebuild 2026-06-27 - fix highlight images + AI rewrite line-by-line
 
209
  except: return Response(status_code=502)
210
 
211
  # ===== XEMLAIBONGDA HIGHLIGHTS =====
212
+ # Thread-local session for connection pooling
213
+ _thread_local = threading.local()
214
+ def _get_session():
215
+ if not hasattr(_thread_local, 'session'):
216
+ _thread_local.session = requests.Session()
217
+ _thread_local.session.headers.update(HEADERS)
218
+ return _thread_local.session
219
+
220
  def _fetch_video_thumbnail(url):
221
  """Fetch og:image from a video detail page."""
222
  try:
223
+ s = _get_session()
224
+ r = s.get(url, timeout=6)
225
  if r.status_code != 200:
226
  return ""
227
  r.encoding = "utf-8"
 
254
  pass
255
  return ""
256
 
257
+ def _proxy_img_url(src):
258
+ """Proxy image URL through our server if needed."""
259
+ if src and ("xemlaibongda.top" in src or "cdnphoto.dantri" in src):
260
+ return '/api/proxy/img?url=' + quote(src, safe='')
261
+ return src
262
+
263
  def _scrape_xemlaibongda_page(page_path, limit=20):
264
  """
265
  Scrape video từ xemlaibongda.top - with proper image extraction
266
+ Uses parallel fetching for thumbnails to avoid timeout
267
  """
268
  try:
269
  url = f"https://xemlaibongda.top/{page_path}" if page_path else "https://xemlaibongda.top/"
 
347
  if not title or len(title) < 3:
348
  continue
349
 
350
+ # Proxy image if needed
351
+ if img_src:
352
+ img_src = _proxy_img_url(img_src)
 
 
 
 
353
 
354
  videos.append({
355
  "title": title[:100],
356
  "link": clean_href,
357
  "img": img_src,
358
+ "source": "xemlaibongda",
359
+ "_needs_thumb": not bool(img_src) # flag for later fetch
360
  })
361
 
362
  if len(videos) >= limit:
363
  break
364
 
365
+ # Fetch missing thumbnails in parallel (max 5 at a time to avoid timeout)
366
+ missing = [v for v in videos if v.get("_needs_thumb")]
367
+ if missing:
368
+ def _fetch_thumb(v):
369
+ thumb = _fetch_video_thumbnail(v["link"])
370
+ if thumb:
371
+ v["img"] = _proxy_img_url(thumb)
372
+ v.pop("_needs_thumb", None)
373
+
374
+ with ThreadPoolExecutor(5) as ex:
375
+ list(ex.map(_fetch_thumb, missing))
376
+ else:
377
+ for v in videos:
378
+ v.pop("_needs_thumb", None)
379
+
380
  return videos
381
  except Exception as e:
382
  print(f"[xemlaibongda] Error: {e}")
 
686
  return JSONResponse({"error":"not found"})
687
 
688
  # ===== WORLD CUP 2026 API =====
 
689
  _wc_request_times = []
690
  _wc_rate_limit_lock = threading.Lock()
691
+ _WC_RATE_LIMIT = 10
692
 
693
  def _wc_rate_limit():
 
694
  global _wc_request_times
695
  with _wc_rate_limit_lock:
696
  now = time.time()
 
697
  _wc_request_times = [t for t in _wc_request_times if now - t < 60]
698
  if len(_wc_request_times) >= _WC_RATE_LIMIT:
699
  return False
 
702
 
703
  @app.get("/api/wc2026")
704
  def api_wc2026():
 
705
  return JSONResponse(_cached("wc2026", get_wc2026_all, ttl=_cache_ttl))
706
 
707
  @app.get("/api/wc2026/{tab}")
708
  def api_wc2026_tab(tab: str):
 
709
  valid_tabs = ["news", "fixtures", "standings", "stats", "highlights"]
710
  if tab not in valid_tabs:
711
  return JSONResponse({"error": "invalid tab"}, status_code=400)
 
852
  @app.get("/", response_class=HTMLResponse)
853
  async def root():
854
  return HTMLResponse("<h1>VNEWS</h1><p>Running</p>")
855
+ # v16 rebuild 2026-06-27 - parallel thumbnail fetch + AI rewrite per line