bep40 commited on
Commit
f25dbb0
·
verified ·
1 Parent(s): 33c00f0

Add highlights by league (Premier League, FA Cup, Bundesliga, Serie A, La Liga, Champions League, Europa League, World Cup 2026 Qualifiers)

Browse files
Files changed (1) hide show
  1. main.py +56 -13
main.py CHANGED
@@ -20,6 +20,19 @@ _cache_ttl_live = 60
20
  _cache_ttl_yt = 1800
21
  PRIORITY_LEAGUES = ["Ngoại Hạng Anh","FA Cup","Champions League","LaLiga","Copa del Rey","Serie A","Bundesliga","Ligue 1","V-League"]
22
  LEAGUE_IDS = {"nha":27110,"laliga":27233,"seriea":27044,"bundesliga":26891,"ligue1":27212}
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  def _cached(key, fn, ttl=None):
24
  now=time.time();t=ttl or _cache_ttl
25
  if key in _cache and now-_cache[key]["t"]<t:return _cache[key]["d"]
@@ -81,7 +94,6 @@ def proxy_m3u8(url: str = Query(...)):
81
  def proxy_video(url: str = Query(...), request: Request = None):
82
  """Proxy MP4/video for sources that block cross-origin (BDP). Supports range requests."""
83
  try:
84
- # Pass through range header for seeking
85
  req_headers = dict(HEADERS)
86
  if request and request.headers.get("range"):
87
  req_headers["Range"] = request.headers["range"]
@@ -104,10 +116,11 @@ def proxy_video(url: str = Query(...), request: Request = None):
104
  return Response(status_code=502, content="proxy error")
105
 
106
  # ===== XEMLAIBONGDA HIGHLIGHTS =====
107
- def scrape_xemlaibongda():
108
- """Get highlight videos from xemlaibongda.top - returns HLS m3u8 streams."""
109
  try:
110
- r=requests.get("https://xemlaibongda.top/",headers=HEADERS,timeout=15)
 
111
  if r.status_code!=200:return[]
112
  r.encoding="utf-8"
113
  soup=BeautifulSoup(r.text,"lxml")
@@ -118,23 +131,45 @@ def scrape_xemlaibongda():
118
  if not href.startswith("http"):href="https://xemlaibongda.top"+href
119
  if href in seen:continue
120
  seen.add(href)
121
- # Title from URL slug
122
  slug=href.split("/video/")[-1].rstrip("/")
123
  title=slug.replace("-"," ").title()
124
  title=re.sub(r'\d{4}\s*\d{2}\s*\d{2}$','',title).strip()
125
  title=re.sub(r'\s+V\s+',' vs ',title)
126
  title=re.sub(r'\s+Vs\s+',' vs ',title)
127
- # Image
128
  img=a.find("img") or (a.parent.find("img") if a.parent else None)
129
  img_src=""
130
  if img:img_src=img.get("data-src","") or img.get("src","") or img.get("data-lazy","")
131
  if not img_src:
132
  img_src=f"https://img.refooty.com/thumbnail/{slug}.webp"
133
  videos.append({"title":title,"link":href,"img":img_src,"source":"xemlaibongda"})
134
- if len(videos)>=20:break
135
  return videos
136
  except:return[]
137
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  def extract_xemlaibongda_video(url):
139
  """Extract HLS m3u8 URL from xemlaibongda article page."""
140
  try:
@@ -151,7 +186,6 @@ def extract_xemlaibongda_video(url):
151
  if source:src=source.get("src","")
152
  if src:
153
  return{"src":src,"poster":poster,"type":"hls" if".m3u8" in src else"video"}
154
- # Fallback: search for m3u8 in page
155
  m3u8s=re.findall(r'(https?://[^\s"\'<>]+\.m3u8)',r.text)
156
  if m3u8s:
157
  og=soup.find("meta",property="og:image")
@@ -246,23 +280,33 @@ def api_shorts():
246
  @app.get("/api/highlights")
247
  def api_highlights():
248
  return JSONResponse(_cached("xemlaibongda_hl",scrape_xemlaibongda,ttl=_cache_ttl))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  @app.get("/api/video_url")
250
  def api_video_url(url:str=Query(...)):
251
- # YouTube
252
  if "youtube.com" in url or "youtu.be" in url:
253
  m=re.search(r'(?:v=|shorts/|youtu\.be/)([a-zA-Z0-9_-]{11})',url)
254
  if m:
255
  vid=m.group(1)
256
  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"})
257
- # xemlaibongda - return proxied m3u8 URL
258
  if "xemlaibongda.top" in url:
259
  v=extract_xemlaibongda_video(url)
260
  if v:
261
  if v["type"]=="hls":
262
- # Proxy the m3u8 through our server to fix CORS
263
  v["src"] = "/api/proxy/m3u8?url=" + quote(v["src"], safe="")
264
  return JSONResponse(v)
265
- # BDP - return proxied video URL
266
  if "bongdaplus.vn" in url:
267
  try:
268
  m=re.search(r'-(\d{6,})\.html',url)
@@ -274,7 +318,6 @@ def api_video_url(url:str=Query(...)):
274
  src=source.get("src","") if source else ""
275
  poster=video.get("poster","")
276
  if src:
277
- # Proxy through our server to fix CORS
278
  proxied_src = "/api/proxy/video?url=" + quote(src, safe="")
279
  return JSONResponse({"src":proxied_src,"poster":poster,"type":"video"})
280
  except:pass
 
20
  _cache_ttl_yt = 1800
21
  PRIORITY_LEAGUES = ["Ngoại Hạng Anh","FA Cup","Champions League","LaLiga","Copa del Rey","Serie A","Bundesliga","Ligue 1","V-League"]
22
  LEAGUE_IDS = {"nha":27110,"laliga":27233,"seriea":27044,"bundesliga":26891,"ligue1":27212}
23
+
24
+ # ===== HIGHLIGHT LEAGUE CONFIG =====
25
+ HL_LEAGUES = {
26
+ "premier-league": {"path": "anh/premier-league", "name": "Premier League", "emoji": "🏴󠁧󠁢󠁥󠁮󠁧󠁿"},
27
+ "fa-cup": {"path": "anh/fa-cup", "name": "FA Cup", "emoji": "🏆"},
28
+ "bundesliga": {"path": "duc/bundesliga", "name": "Bundesliga", "emoji": "🇩🇪"},
29
+ "serie-a": {"path": "italy/serie-a", "name": "Serie A", "emoji": "🇮🇹"},
30
+ "la-liga": {"path": "tay-ban-nha/la-liga", "name": "La Liga", "emoji": "🇪🇸"},
31
+ "champions-league": {"path": "cup-chau-au/uefa-champions-league", "name": "Champions League", "emoji": "⭐"},
32
+ "europa-league": {"path": "cup-chau-au/uefa-europa-league", "name": "Europa League", "emoji": "🟠"},
33
+ "world-cup": {"path": "the-gioi/world-cup-qualifiers", "name": "World Cup 2026", "emoji": "🌍"},
34
+ }
35
+
36
  def _cached(key, fn, ttl=None):
37
  now=time.time();t=ttl or _cache_ttl
38
  if key in _cache and now-_cache[key]["t"]<t:return _cache[key]["d"]
 
94
  def proxy_video(url: str = Query(...), request: Request = None):
95
  """Proxy MP4/video for sources that block cross-origin (BDP). Supports range requests."""
96
  try:
 
97
  req_headers = dict(HEADERS)
98
  if request and request.headers.get("range"):
99
  req_headers["Range"] = request.headers["range"]
 
116
  return Response(status_code=502, content="proxy error")
117
 
118
  # ===== XEMLAIBONGDA HIGHLIGHTS =====
119
+ def _scrape_xemlaibongda_page(page_path):
120
+ """Scrape highlights from a specific xemlaibongda page (league or homepage)."""
121
  try:
122
+ url = f"https://xemlaibongda.top/{page_path}" if page_path else "https://xemlaibongda.top/"
123
+ r=requests.get(url,headers=HEADERS,timeout=15)
124
  if r.status_code!=200:return[]
125
  r.encoding="utf-8"
126
  soup=BeautifulSoup(r.text,"lxml")
 
131
  if not href.startswith("http"):href="https://xemlaibongda.top"+href
132
  if href in seen:continue
133
  seen.add(href)
 
134
  slug=href.split("/video/")[-1].rstrip("/")
135
  title=slug.replace("-"," ").title()
136
  title=re.sub(r'\d{4}\s*\d{2}\s*\d{2}$','',title).strip()
137
  title=re.sub(r'\s+V\s+',' vs ',title)
138
  title=re.sub(r'\s+Vs\s+',' vs ',title)
 
139
  img=a.find("img") or (a.parent.find("img") if a.parent else None)
140
  img_src=""
141
  if img:img_src=img.get("data-src","") or img.get("src","") or img.get("data-lazy","")
142
  if not img_src:
143
  img_src=f"https://img.refooty.com/thumbnail/{slug}.webp"
144
  videos.append({"title":title,"link":href,"img":img_src,"source":"xemlaibongda"})
145
+ if len(videos)>=12:break
146
  return videos
147
  except:return[]
148
 
149
+ def scrape_xemlaibongda():
150
+ """Get all highlight videos from homepage."""
151
+ return _scrape_xemlaibongda_page("")
152
+
153
+ def scrape_highlights_by_league(league_key):
154
+ """Get highlights for a specific league."""
155
+ if league_key not in HL_LEAGUES:return[]
156
+ return _scrape_xemlaibongda_page(HL_LEAGUES[league_key]["path"])
157
+
158
+ def scrape_all_league_highlights():
159
+ """Fetch highlights for all leagues in parallel."""
160
+ results = {}
161
+ def _fetch(key):
162
+ return key, scrape_highlights_by_league(key)
163
+ with ThreadPoolExecutor(8) as ex:
164
+ futs = [ex.submit(_fetch, k) for k in HL_LEAGUES]
165
+ for f in as_completed(futs):
166
+ try:
167
+ key, vids = f.result()
168
+ if vids:
169
+ results[key] = vids[:6]
170
+ except:pass
171
+ return results
172
+
173
  def extract_xemlaibongda_video(url):
174
  """Extract HLS m3u8 URL from xemlaibongda article page."""
175
  try:
 
186
  if source:src=source.get("src","")
187
  if src:
188
  return{"src":src,"poster":poster,"type":"hls" if".m3u8" in src else"video"}
 
189
  m3u8s=re.findall(r'(https?://[^\s"\'<>]+\.m3u8)',r.text)
190
  if m3u8s:
191
  og=soup.find("meta",property="og:image")
 
280
  @app.get("/api/highlights")
281
  def api_highlights():
282
  return JSONResponse(_cached("xemlaibongda_hl",scrape_xemlaibongda,ttl=_cache_ttl))
283
+ @app.get("/api/highlights/leagues")
284
+ def api_highlights_leagues():
285
+ """Get highlights grouped by league for homepage display."""
286
+ return JSONResponse(_cached("hl_leagues",scrape_all_league_highlights,ttl=_cache_ttl))
287
+ @app.get("/api/highlights/{league}")
288
+ def api_highlights_league(league:str):
289
+ """Get highlights for a specific league."""
290
+ if league not in HL_LEAGUES:
291
+ return JSONResponse({"error":"league not found"})
292
+ return JSONResponse(_cached(f"hl_{league}",lambda:scrape_highlights_by_league(league),ttl=_cache_ttl))
293
+ @app.get("/api/highlights_config")
294
+ def api_highlights_config():
295
+ """Return league config for frontend."""
296
+ return JSONResponse(HL_LEAGUES)
297
  @app.get("/api/video_url")
298
  def api_video_url(url:str=Query(...)):
 
299
  if "youtube.com" in url or "youtu.be" in url:
300
  m=re.search(r'(?:v=|shorts/|youtu\.be/)([a-zA-Z0-9_-]{11})',url)
301
  if m:
302
  vid=m.group(1)
303
  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"})
 
304
  if "xemlaibongda.top" in url:
305
  v=extract_xemlaibongda_video(url)
306
  if v:
307
  if v["type"]=="hls":
 
308
  v["src"] = "/api/proxy/m3u8?url=" + quote(v["src"], safe="")
309
  return JSONResponse(v)
 
310
  if "bongdaplus.vn" in url:
311
  try:
312
  m=re.search(r'-(\d{6,})\.html',url)
 
318
  src=source.get("src","") if source else ""
319
  poster=video.get("poster","")
320
  if src:
 
321
  proxied_src = "/api/proxy/video?url=" + quote(src, safe="")
322
  return JSONResponse({"src":proxied_src,"poster":poster,"type":"video"})
323
  except:pass