Spaces:
Running
Running
Replace FPT highlights with ScoreBat API (50 highlight videos, no geo-restrict)"
Browse files
main.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
"""VNEWS - FastAPI backend with livescore + YouTube FPT
|
| 2 |
import hashlib, re, time, subprocess, json
|
| 3 |
from datetime import datetime
|
| 4 |
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
@@ -61,8 +61,34 @@ def _parse_match_from_li(li, status_type="live"):
|
|
| 61 |
league=league_el.get_text(strip=True) if league_el else ""
|
| 62 |
return{"home":home_el.get_text(strip=True),"away":away_el.get_text(strip=True),"score":score or"VS","minute":minute,"league":league,"time":time_el.get_text(strip=True) if time_el else "","event_id":event_id,"home_logo":home_logo.get("src","") if home_logo else "","away_logo":away_logo.get("src","") if away_logo else "","status":status_type}
|
| 63 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
def _yt_playlist(channel_path, count=15):
|
| 65 |
-
"""Get videos from YouTube channel using yt-dlp."""
|
| 66 |
try:
|
| 67 |
result=subprocess.run(["yt-dlp","--flat-playlist","--dump-json","--no-download","--playlist-end",str(count),f"https://www.youtube.com/@fptbongdaofficial/{channel_path}"],capture_output=True,text=True,timeout=60)
|
| 68 |
videos=[]
|
|
@@ -81,9 +107,8 @@ def _yt_playlist(channel_path, count=15):
|
|
| 81 |
|
| 82 |
def scrape_fpt_shorts():
|
| 83 |
return _yt_playlist("shorts", 15)
|
| 84 |
-
def scrape_fpt_highlights():
|
| 85 |
-
return _yt_playlist("videos", 15)
|
| 86 |
|
|
|
|
| 87 |
@app.get("/api/livescore/live")
|
| 88 |
def api_livescore_live():
|
| 89 |
html=_cached("ls_live",lambda:fetch_bongda_api("/api/fixtures/live"),ttl=_cache_ttl_live)
|
|
@@ -140,19 +165,29 @@ def api_livescore_featured():
|
|
| 140 |
return all_matches[0]
|
| 141 |
return None
|
| 142 |
return JSONResponse(_cached("ls_featured",_f,ttl=30))
|
|
|
|
|
|
|
| 143 |
@app.get("/api/shorts")
|
| 144 |
def api_shorts():
|
| 145 |
return JSONResponse(_cached("fpt_shorts",scrape_fpt_shorts,ttl=_cache_ttl_yt))
|
|
|
|
| 146 |
@app.get("/api/highlights")
|
| 147 |
def api_highlights():
|
| 148 |
-
|
|
|
|
|
|
|
| 149 |
@app.get("/api/video_url")
|
| 150 |
def api_video_url(url:str=Query(...)):
|
|
|
|
| 151 |
if "youtube.com" in url or "youtu.be" in url:
|
| 152 |
m=re.search(r'(?:v=|shorts/|youtu\.be/)([a-zA-Z0-9_-]{11})',url)
|
| 153 |
if m:
|
| 154 |
vid=m.group(1)
|
| 155 |
return JSONResponse({"src":f"https://www.youtube.com/embed/{vid}?autoplay=1&rel=0&enablejsapi=1","poster":f"https://i.ytimg.com/vi/{vid}/hqdefault.jpg","type":"youtube"})
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
if "bongdaplus.vn" in url:
|
| 157 |
try:
|
| 158 |
m=re.search(r'-(\d{6,})\.html',url)
|
|
@@ -164,6 +199,7 @@ def api_video_url(url:str=Query(...)):
|
|
| 164 |
return JSONResponse({"src":source.get("src","") if source else "","poster":video.get("poster",""),"type":"video"})
|
| 165 |
except:pass
|
| 166 |
return JSONResponse({"error":"not found"})
|
|
|
|
| 167 |
@app.get("/api/bdp_videos")
|
| 168 |
def api_bdp_videos():
|
| 169 |
def _f():
|
|
@@ -182,6 +218,8 @@ def api_bdp_videos():
|
|
| 182 |
return arts[:20]
|
| 183 |
except:return[]
|
| 184 |
return JSONResponse(_cached("bdp_videos",_f))
|
|
|
|
|
|
|
| 185 |
def scrape_vne(cat_url):
|
| 186 |
try:
|
| 187 |
soup=_get(cat_url);arts=[]
|
|
|
|
| 1 |
+
"""VNEWS - FastAPI backend with livescore + ScoreBat highlights + YouTube FPT shorts"""
|
| 2 |
import hashlib, re, time, subprocess, json
|
| 3 |
from datetime import datetime
|
| 4 |
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
|
|
| 61 |
league=league_el.get_text(strip=True) if league_el else ""
|
| 62 |
return{"home":home_el.get_text(strip=True),"away":away_el.get_text(strip=True),"score":score or"VS","minute":minute,"league":league,"time":time_el.get_text(strip=True) if time_el else "","event_id":event_id,"home_logo":home_logo.get("src","") if home_logo else "","away_logo":away_logo.get("src","") if away_logo else "","status":status_type}
|
| 63 |
|
| 64 |
+
# ===== SCOREBAT HIGHLIGHTS =====
|
| 65 |
+
def scrape_scorebat_highlights():
|
| 66 |
+
"""Get highlight videos from ScoreBat free API."""
|
| 67 |
+
try:
|
| 68 |
+
r=requests.get("https://www.scorebat.com/video-api/v3/",headers=HEADERS,timeout=15)
|
| 69 |
+
if r.status_code!=200:return[]
|
| 70 |
+
data=r.json()
|
| 71 |
+
response=data.get("response",[])
|
| 72 |
+
videos=[]
|
| 73 |
+
for item in response:
|
| 74 |
+
title=item.get("title","")
|
| 75 |
+
thumb=item.get("thumbnail","")
|
| 76 |
+
competition=item.get("competition","")
|
| 77 |
+
matchview=item.get("matchviewUrl","")
|
| 78 |
+
vids=item.get("videos",[])
|
| 79 |
+
# Get embed URL from first video
|
| 80 |
+
embed=""
|
| 81 |
+
if vids:
|
| 82 |
+
embed_html=vids[0].get("embed","")
|
| 83 |
+
m=re.search(r'src=["\']([^"\']+)["\']',embed_html)
|
| 84 |
+
if m:embed=m.group(1)
|
| 85 |
+
if title and (embed or matchview):
|
| 86 |
+
videos.append({"title":title,"img":thumb,"link":matchview or embed,"source":"scorebat","embed":embed,"competition":competition})
|
| 87 |
+
return videos[:30]
|
| 88 |
+
except:return[]
|
| 89 |
+
|
| 90 |
+
# ===== YOUTUBE FPT SHORTS =====
|
| 91 |
def _yt_playlist(channel_path, count=15):
|
|
|
|
| 92 |
try:
|
| 93 |
result=subprocess.run(["yt-dlp","--flat-playlist","--dump-json","--no-download","--playlist-end",str(count),f"https://www.youtube.com/@fptbongdaofficial/{channel_path}"],capture_output=True,text=True,timeout=60)
|
| 94 |
videos=[]
|
|
|
|
| 107 |
|
| 108 |
def scrape_fpt_shorts():
|
| 109 |
return _yt_playlist("shorts", 15)
|
|
|
|
|
|
|
| 110 |
|
| 111 |
+
# ===== LIVESCORE =====
|
| 112 |
@app.get("/api/livescore/live")
|
| 113 |
def api_livescore_live():
|
| 114 |
html=_cached("ls_live",lambda:fetch_bongda_api("/api/fixtures/live"),ttl=_cache_ttl_live)
|
|
|
|
| 165 |
return all_matches[0]
|
| 166 |
return None
|
| 167 |
return JSONResponse(_cached("ls_featured",_f,ttl=30))
|
| 168 |
+
|
| 169 |
+
# ===== VIDEO APIs =====
|
| 170 |
@app.get("/api/shorts")
|
| 171 |
def api_shorts():
|
| 172 |
return JSONResponse(_cached("fpt_shorts",scrape_fpt_shorts,ttl=_cache_ttl_yt))
|
| 173 |
+
|
| 174 |
@app.get("/api/highlights")
|
| 175 |
def api_highlights():
|
| 176 |
+
"""ScoreBat highlight videos."""
|
| 177 |
+
return JSONResponse(_cached("scorebat_highlights",scrape_scorebat_highlights,ttl=_cache_ttl))
|
| 178 |
+
|
| 179 |
@app.get("/api/video_url")
|
| 180 |
def api_video_url(url:str=Query(...)):
|
| 181 |
+
# YouTube
|
| 182 |
if "youtube.com" in url or "youtu.be" in url:
|
| 183 |
m=re.search(r'(?:v=|shorts/|youtu\.be/)([a-zA-Z0-9_-]{11})',url)
|
| 184 |
if m:
|
| 185 |
vid=m.group(1)
|
| 186 |
return JSONResponse({"src":f"https://www.youtube.com/embed/{vid}?autoplay=1&rel=0&enablejsapi=1","poster":f"https://i.ytimg.com/vi/{vid}/hqdefault.jpg","type":"youtube"})
|
| 187 |
+
# ScoreBat embed
|
| 188 |
+
if "scorebat.com" in url:
|
| 189 |
+
return JSONResponse({"src":url,"poster":"","type":"youtube"})
|
| 190 |
+
# BDP
|
| 191 |
if "bongdaplus.vn" in url:
|
| 192 |
try:
|
| 193 |
m=re.search(r'-(\d{6,})\.html',url)
|
|
|
|
| 199 |
return JSONResponse({"src":source.get("src","") if source else "","poster":video.get("poster",""),"type":"video"})
|
| 200 |
except:pass
|
| 201 |
return JSONResponse({"error":"not found"})
|
| 202 |
+
|
| 203 |
@app.get("/api/bdp_videos")
|
| 204 |
def api_bdp_videos():
|
| 205 |
def _f():
|
|
|
|
| 218 |
return arts[:20]
|
| 219 |
except:return[]
|
| 220 |
return JSONResponse(_cached("bdp_videos",_f))
|
| 221 |
+
|
| 222 |
+
# ===== NEWS =====
|
| 223 |
def scrape_vne(cat_url):
|
| 224 |
try:
|
| 225 |
soup=_get(cat_url);arts=[]
|