bep40 commited on
Commit
efd02f9
·
verified ·
1 Parent(s): f90b78c

Fix: sort YouTube by date (newest first) + stop iframe audio on slide change

Browse files
Files changed (1) hide show
  1. main.py +9 -24
main.py CHANGED
@@ -17,7 +17,7 @@ SPACE_URL = "https://bep40-vnews.hf.space"
17
  _cache = {}
18
  _cache_ttl = 300
19
  _cache_ttl_live = 60
20
- _cache_ttl_yt = 1800 # 30min for YouTube lists
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):
@@ -63,9 +63,9 @@ def _parse_match_from_li(li, status_type="live"):
63
 
64
  # ===== YOUTUBE FPT BÓNG ĐÁ =====
65
  def _yt_playlist(channel_path, count=15):
66
- """Get video list from YouTube channel using yt-dlp."""
67
  try:
68
- 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)
69
  videos=[]
70
  for line in result.stdout.strip().split('\n'):
71
  if not line:continue
@@ -74,7 +74,6 @@ def _yt_playlist(channel_path, count=15):
74
  vid=info.get("id","")
75
  title=info.get("title","")
76
  if not vid or not title:continue
77
- # Thumbnail: use YouTube default
78
  thumb=f"https://i.ytimg.com/vi/{vid}/hqdefault.jpg"
79
  videos.append({"title":title,"link":f"https://www.youtube.com/watch?v={vid}","img":thumb,"source":"fpt","id":vid})
80
  except:pass
@@ -82,11 +81,9 @@ def _yt_playlist(channel_path, count=15):
82
  except:return[]
83
 
84
  def scrape_fpt_shorts():
85
- """Get YouTube Shorts from FPT Bóng Đá."""
86
  return _yt_playlist("shorts", 15)
87
 
88
  def scrape_fpt_highlights():
89
- """Get YouTube Videos from FPT Bóng Đá."""
90
  return _yt_playlist("videos", 15)
91
 
92
  # ===== LIVESCORE =====
@@ -146,28 +143,20 @@ def api_livescore_featured():
146
  return all_matches[0]
147
  return None
148
  return JSONResponse(_cached("ls_featured",_f,ttl=30))
149
-
150
  # ===== VIDEO APIs =====
151
  @app.get("/api/shorts")
152
  def api_shorts():
153
- """YouTube Shorts from FPT Bóng Đá."""
154
  return JSONResponse(_cached("fpt_shorts",scrape_fpt_shorts,ttl=_cache_ttl_yt))
155
-
156
  @app.get("/api/highlights")
157
  def api_highlights():
158
- """YouTube Videos from FPT Bóng Đá."""
159
  return JSONResponse(_cached("fpt_highlights",scrape_fpt_highlights,ttl=_cache_ttl_yt))
160
-
161
  @app.get("/api/video_url")
162
  def api_video_url(url:str=Query(...)):
163
- """Get playable video URL. YouTube → embed URL. BDP → MP4."""
164
- # YouTube
165
  if "youtube.com" in url or "youtu.be" in url:
166
  m=re.search(r'(?:v=|shorts/|youtu\.be/)([a-zA-Z0-9_-]{11})',url)
167
  if m:
168
  vid=m.group(1)
169
- return JSONResponse({"src":f"https://www.youtube.com/embed/{vid}?autoplay=1&rel=0","poster":f"https://i.ytimg.com/vi/{vid}/hqdefault.jpg","type":"youtube"})
170
- # BDP
171
  if "bongdaplus.vn" in url:
172
  try:
173
  m=re.search(r'-(\d{6,})\.html',url)
@@ -179,7 +168,6 @@ def api_video_url(url:str=Query(...)):
179
  return JSONResponse({"src":source.get("src","") if source else "","poster":video.get("poster",""),"type":"video"})
180
  except:pass
181
  return JSONResponse({"error":"not found"})
182
-
183
  @app.get("/api/bdp_videos")
184
  def api_bdp_videos():
185
  def _f():
@@ -198,8 +186,7 @@ def api_bdp_videos():
198
  return arts[:20]
199
  except:return[]
200
  return JSONResponse(_cached("bdp_videos",_f))
201
-
202
- # ===== NEWS SCRAPERS =====
203
  def scrape_vne(cat_url):
204
  try:
205
  soup=_get(cat_url);arts=[]
@@ -267,8 +254,7 @@ def scrape_dantri_article(url):
267
  except:return None
268
  def scrape_bbc_vietnamese():
269
  try:
270
- bbc_h={"User-Agent":"Mozilla/5.0","Accept-Language":"en-GB,en;q=0.9"}
271
- r=requests.get("https://www.bbc.com/vietnamese",headers=bbc_h,timeout=15);r.encoding="utf-8"
272
  soup=BeautifulSoup(r.text,"lxml");arts=[];seen=set()
273
  for a in soup.select("a[href*='/vietnamese/']"):
274
  href=a.get("href","")
@@ -276,8 +262,7 @@ def scrape_bbc_vietnamese():
276
  if not href.startswith("http"):href="https://www.bbc.com"+href
277
  if href in seen:continue
278
  title=a.get_text(strip=True)
279
- if not title or len(title)<15:continue
280
- if any(x in title.lower() for x in["đăng nhập","trang chủ","bbc news"]):continue
281
  img="";container=a.parent
282
  for _ in range(3):
283
  if container:
@@ -339,13 +324,13 @@ async def video_share(url:str=Query(default=""),title:str=Query(default="VNEWS V
339
  og_image=unquote(img) if img else "https://s1.vnecdn.net/vnexpress/restruct/i/v9505/logo_default.jpg"
340
  decoded_url=unquote(url);decoded_title=unquote(title)
341
  redirect_script=f'<script>localStorage.setItem("pending_video",JSON.stringify({{"url":"{decoded_url}","type":"{type}"}}));location.href="{SPACE_URL}";</script>' if decoded_url else f'<script>location.href="{SPACE_URL}";</script>'
342
- return HTMLResponse(f'<!DOCTYPE html><html><head><meta charset="utf-8"><title>{decoded_title}</title><meta property="og:title" content="{decoded_title}"><meta property="og:image" content="{og_image}"></head><body style="background:#111;color:#fff;text-align:center;padding:40px"><p>⏳</p>{redirect_script}</body></html>')
343
  @app.get("/s")
344
  async def share_redirect(url:str=Query(default=""),title:str=Query(default="VNEWS"),img:str=Query(default="")):
345
  og_image=unquote(img) if img else "https://s1.vnecdn.net/vnexpress/restruct/i/v9505/logo_default.jpg"
346
  decoded_url=unquote(url)
347
  redirect_script=f'<script>localStorage.setItem("pending_article","{decoded_url}");location.href="{SPACE_URL}";</script>' if decoded_url else f'<script>location.href="{SPACE_URL}";</script>'
348
- return HTMLResponse(f'<!DOCTYPE html><html><head><meta charset="utf-8"><title>{unquote(title)}</title><meta property="og:title" content="{unquote(title)}"><meta property="og:image" content="{og_image}"></head><body>{redirect_script}</body></html>')
349
  @app.get("/")
350
  async def index():
351
  with open("/app/static/index.html","r",encoding="utf-8") as f:return HTMLResponse(content=f.read())
 
17
  _cache = {}
18
  _cache_ttl = 300
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):
 
63
 
64
  # ===== YOUTUBE FPT BÓNG ĐÁ =====
65
  def _yt_playlist(channel_path, count=15):
66
+ """Get LATEST videos from YouTube channel sorted by date."""
67
  try:
68
+ result=subprocess.run(["yt-dlp","--flat-playlist","--dump-json","--no-download","--playlist-end",str(count),"--playlist-sort","date",f"https://www.youtube.com/@fptbongdaofficial/{channel_path}"],capture_output=True,text=True,timeout=60)
69
  videos=[]
70
  for line in result.stdout.strip().split('\n'):
71
  if not line:continue
 
74
  vid=info.get("id","")
75
  title=info.get("title","")
76
  if not vid or not title:continue
 
77
  thumb=f"https://i.ytimg.com/vi/{vid}/hqdefault.jpg"
78
  videos.append({"title":title,"link":f"https://www.youtube.com/watch?v={vid}","img":thumb,"source":"fpt","id":vid})
79
  except:pass
 
81
  except:return[]
82
 
83
  def scrape_fpt_shorts():
 
84
  return _yt_playlist("shorts", 15)
85
 
86
  def scrape_fpt_highlights():
 
87
  return _yt_playlist("videos", 15)
88
 
89
  # ===== LIVESCORE =====
 
143
  return all_matches[0]
144
  return None
145
  return JSONResponse(_cached("ls_featured",_f,ttl=30))
 
146
  # ===== VIDEO APIs =====
147
  @app.get("/api/shorts")
148
  def api_shorts():
 
149
  return JSONResponse(_cached("fpt_shorts",scrape_fpt_shorts,ttl=_cache_ttl_yt))
 
150
  @app.get("/api/highlights")
151
  def api_highlights():
 
152
  return JSONResponse(_cached("fpt_highlights",scrape_fpt_highlights,ttl=_cache_ttl_yt))
 
153
  @app.get("/api/video_url")
154
  def api_video_url(url:str=Query(...)):
 
 
155
  if "youtube.com" in url or "youtu.be" in url:
156
  m=re.search(r'(?:v=|shorts/|youtu\.be/)([a-zA-Z0-9_-]{11})',url)
157
  if m:
158
  vid=m.group(1)
159
+ 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"})
 
160
  if "bongdaplus.vn" in url:
161
  try:
162
  m=re.search(r'-(\d{6,})\.html',url)
 
168
  return JSONResponse({"src":source.get("src","") if source else "","poster":video.get("poster",""),"type":"video"})
169
  except:pass
170
  return JSONResponse({"error":"not found"})
 
171
  @app.get("/api/bdp_videos")
172
  def api_bdp_videos():
173
  def _f():
 
186
  return arts[:20]
187
  except:return[]
188
  return JSONResponse(_cached("bdp_videos",_f))
189
+ # ===== NEWS =====
 
190
  def scrape_vne(cat_url):
191
  try:
192
  soup=_get(cat_url);arts=[]
 
254
  except:return None
255
  def scrape_bbc_vietnamese():
256
  try:
257
+ r=requests.get("https://www.bbc.com/vietnamese",headers={"User-Agent":"Mozilla/5.0","Accept-Language":"en-GB"},timeout=15);r.encoding="utf-8"
 
258
  soup=BeautifulSoup(r.text,"lxml");arts=[];seen=set()
259
  for a in soup.select("a[href*='/vietnamese/']"):
260
  href=a.get("href","")
 
262
  if not href.startswith("http"):href="https://www.bbc.com"+href
263
  if href in seen:continue
264
  title=a.get_text(strip=True)
265
+ if not title or len(title)<15 or any(x in title.lower() for x in["đăng nhập","trang chủ","bbc news"]):continue
 
266
  img="";container=a.parent
267
  for _ in range(3):
268
  if container:
 
324
  og_image=unquote(img) if img else "https://s1.vnecdn.net/vnexpress/restruct/i/v9505/logo_default.jpg"
325
  decoded_url=unquote(url);decoded_title=unquote(title)
326
  redirect_script=f'<script>localStorage.setItem("pending_video",JSON.stringify({{"url":"{decoded_url}","type":"{type}"}}));location.href="{SPACE_URL}";</script>' if decoded_url else f'<script>location.href="{SPACE_URL}";</script>'
327
+ return HTMLResponse(f'<!DOCTYPE html><html><head><meta charset="utf-8"><title>{decoded_title}</title></head><body style="background:#111;color:#fff;text-align:center;padding:40px"><p>⏳</p>{redirect_script}</body></html>')
328
  @app.get("/s")
329
  async def share_redirect(url:str=Query(default=""),title:str=Query(default="VNEWS"),img:str=Query(default="")):
330
  og_image=unquote(img) if img else "https://s1.vnecdn.net/vnexpress/restruct/i/v9505/logo_default.jpg"
331
  decoded_url=unquote(url)
332
  redirect_script=f'<script>localStorage.setItem("pending_article","{decoded_url}");location.href="{SPACE_URL}";</script>' if decoded_url else f'<script>location.href="{SPACE_URL}";</script>'
333
+ return HTMLResponse(f'<!DOCTYPE html><html><head><meta charset="utf-8"><title>{unquote(title)}</title></head><body>{redirect_script}</body></html>')
334
  @app.get("/")
335
  async def index():
336
  with open("/app/static/index.html","r",encoding="utf-8") as f:return HTMLResponse(content=f.read())