Spaces:
Running
Running
Upload main.py
Browse files
main.py
CHANGED
|
@@ -202,17 +202,52 @@ def proxy_video(url: str = Query(...), request: Request = None):
|
|
| 202 |
@app.get("/api/proxy/img")
|
| 203 |
def proxy_img(url: str = Query(...)):
|
| 204 |
try:
|
| 205 |
-
r = requests.get(url, headers={**HEADERS, "Referer": "https://
|
| 206 |
if r.status_code != 200: return Response(status_code=502)
|
| 207 |
ct = r.headers.get("Content-Type", "image/jpeg")
|
| 208 |
return Response(content=r.content, media_type=ct, headers={"Cache-Control": "public, max-age=86400", "Access-Control-Allow-Origin": "*"})
|
| 209 |
except: return Response(status_code=502)
|
| 210 |
|
| 211 |
# ===== XEMLAIBONGDA HIGHLIGHTS =====
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
def _scrape_xemlaibongda_page(page_path, limit=20):
|
| 213 |
"""
|
| 214 |
-
Scrape video từ xemlaibongda.top -
|
| 215 |
-
Dùng logic cũ đã test, không fetch từng trang (tránh timeout)
|
| 216 |
"""
|
| 217 |
try:
|
| 218 |
url = f"https://xemlaibongda.top/{page_path}" if page_path else "https://xemlaibongda.top/"
|
|
@@ -296,10 +331,13 @@ def _scrape_xemlaibongda_page(page_path, limit=20):
|
|
| 296 |
if not title or len(title) < 3:
|
| 297 |
continue
|
| 298 |
|
| 299 |
-
#
|
| 300 |
if not img_src:
|
| 301 |
-
|
| 302 |
-
|
|
|
|
|
|
|
|
|
|
| 303 |
|
| 304 |
videos.append({
|
| 305 |
"title": title[:100],
|
|
@@ -664,9 +702,6 @@ def api_wc2026_tab(tab: str):
|
|
| 664 |
|
| 665 |
return JSONResponse(_cached(f"wc2026_{tab}", _fetch_tab, ttl=_cache_ttl))
|
| 666 |
|
| 667 |
-
# Note: WC functions (scrape_wc_news, scrape_fixtures, scrape_stats, scrape_standings)
|
| 668 |
-
# are imported from wc2026_scraper.py at the top of this file
|
| 669 |
-
|
| 670 |
@app.get("/api/video_url")
|
| 671 |
def api_video_url(url:str=Query(...)):
|
| 672 |
if "youtube.com" in url or "youtu.be" in url:
|
|
@@ -794,4 +829,4 @@ def api_hot_topics():
|
|
| 794 |
@app.get("/", response_class=HTMLResponse)
|
| 795 |
async def root():
|
| 796 |
return HTMLResponse("<h1>VNEWS</h1><p>Running</p>")
|
| 797 |
-
#
|
|
|
|
| 202 |
@app.get("/api/proxy/img")
|
| 203 |
def proxy_img(url: str = Query(...)):
|
| 204 |
try:
|
| 205 |
+
r = requests.get(url, headers={**HEADERS, "Referer": "https://xemlaibongda.top/"}, timeout=10)
|
| 206 |
if r.status_code != 200: return Response(status_code=502)
|
| 207 |
ct = r.headers.get("Content-Type", "image/jpeg")
|
| 208 |
return Response(content=r.content, media_type=ct, headers={"Cache-Control": "public, max-age=86400", "Access-Control-Allow-Origin": "*"})
|
| 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"
|
| 219 |
+
soup = BeautifulSoup(r.text, "lxml")
|
| 220 |
+
# Try og:image
|
| 221 |
+
og = soup.find("meta", property="og:image")
|
| 222 |
+
if og:
|
| 223 |
+
src = og.get("content", "")
|
| 224 |
+
if src:
|
| 225 |
+
if src.startswith("//"): src = "https:" + src
|
| 226 |
+
elif src.startswith("/"): src = "https://xemlaibongda.top" + src
|
| 227 |
+
return src
|
| 228 |
+
# Try twitter:image
|
| 229 |
+
tw = soup.find("meta", attrs={"name": "twitter:image"})
|
| 230 |
+
if tw:
|
| 231 |
+
src = tw.get("content", "")
|
| 232 |
+
if src:
|
| 233 |
+
if src.startswith("//"): src = "https:" + src
|
| 234 |
+
elif src.startswith("/"): src = "https://xemlaibongda.top" + src
|
| 235 |
+
return src
|
| 236 |
+
# Try first significant img
|
| 237 |
+
for img in soup.find_all("img"):
|
| 238 |
+
src = img.get("src", "") or img.get("data-src", "")
|
| 239 |
+
if src and "logo" not in src.lower() and "avatar" not in src.lower() and "icon" not in src.lower():
|
| 240 |
+
if src.startswith("//"): src = "https:" + src
|
| 241 |
+
elif src.startswith("/"): src = "https://xemlaibongda.top" + src
|
| 242 |
+
if src.startswith("http"):
|
| 243 |
+
return src
|
| 244 |
+
except:
|
| 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 |
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],
|
|
|
|
| 702 |
|
| 703 |
return JSONResponse(_cached(f"wc2026_{tab}", _fetch_tab, ttl=_cache_ttl))
|
| 704 |
|
|
|
|
|
|
|
|
|
|
| 705 |
@app.get("/api/video_url")
|
| 706 |
def api_video_url(url:str=Query(...)):
|
| 707 |
if "youtube.com" in url or "youtu.be" in url:
|
|
|
|
| 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
|