bep40 commited on
Commit
8ee1b22
·
verified ·
1 Parent(s): df9715b

Improve VTV Nam Bộ shorts scraping + add WC shorts endpoints + remove fake Rickroll video

Browse files
Files changed (1) hide show
  1. main.py +142 -38
main.py CHANGED
@@ -26,41 +26,46 @@ _cache = {}
26
  _cache_ttl = 300
27
  _cache_ttl_live = 60
28
  _cache_ttl_yt = 1800
 
 
 
 
29
  SHORTS_FALLBACK = [
30
- {"id":"Lu_iCQ5YwNM","title":"Công an lập hồ xử người phụ nữ chửi bới, tát nam tài xế ô tô ở Hà Nội | #shorts","channel":"baodantri7941"},
31
- {"id":"CwWvijF8BOA","title":"Chú rể Ninh Bình bật khóc nhận món quà bí mật người cha quá cố gửi 26 năm trước | #shorts","channel":"baodantri7941"},
32
- {"id":"tvPewsc2ph4","title":"Tính năng ẩn trên iPhone giúp giảm mỏi mắt | #shorts","channel":"baodantri7941"},
33
- {"id":"b1Nxzv9ixlU","title":"Y án 3 năm tù với nữ tài xế uống 8 lon bia lái xe tông chủ tịch xã tử vong | #shorts","channel":"baodantri7941"},
34
- {"id":"Xp5eTwAZAis","title":"Người đánh hàng xóm tại chung Nội bị tuyên hơn 4 tháng | #shorts","channel":"baodantri7941"},
35
- {"id":"Htzvwg6iOBM","title":"Xe điện Audi S6 Sportback e-tron đặc biệt? | #shorts","channel":"baodantri7941"},
36
- {"id":"iMdFmWvYdlo","title":" gái người Nga yêu thời trang đất nước Việt Nam | #shorts","channel":"baodantri7941"},
37
- {"id":"IVaRc6moEv8","title":"Người nông dân Trung Quốc đột quỵ, bệnh viện giúp bán sạch 4 tấn táo | #shorts","channel":"baodantri7941"},
38
- {"id":"uVxqPxToItU","title":"Công an vào cuộc vụ người phụ nữ chửi bới, hành hung tài xế ô ở Hà Nội | #shorts","channel":"baodantri7941"},
39
- {"id":"VAfgNNgZDRs","title":"Khởi tố 4 đối tượng ném bom xăng vào nhà dân ở Đồng Nai | #shorts","channel":"baodantri7941"},
40
- {"id":"sBH_-zGh0Xw","title":" sao Times New Roman vẫn nổi tiếng sau hàng chục năm? | #shorts","channel":"baodantri7941"},
41
- {"id":"woKn5f2bLHM","title":"Quảng Ninh ngập sâu diện rộng sau đợt mưa lớn | #shorts","channel":"baodantri7941"},
42
- {"id":"bcpgRoxbLPw","title":"Giông lốc quật bay mái tônTP.HCM | #shorts","channel":"baodantri7941"},
43
- {"id":"ZIIC5osy544","title":" trai Trung Quốc rơi từ tầng 11 vẫn sống sót kỳ diệu | #shorts","channel":"baodantri7941"},
44
- {"id":"uTMJ49NQpyc","title":"Sau lớp mascot 40kg: Câu chuyện mưu sinh của người trẻ TPHCM | #shorts","channel":"baodantri7941"},
45
- {"id":"7Pd6vZ2Lz1M","title":"Hành động ấm lòng của người đàn ông tham gia tìm kiếm 5 học sinh tử vong ở sông Lô | SKĐS","channel":"baosuckhoedoisongboyte"},
46
- {"id":"SlHLt_ZyPiE","title":"Xử phạt người đàn ông xóa số điện thoại cứu hộ trên cao tốc Bắc - Nam | SKĐS","channel":"baosuckhoedoisongboyte"},
47
- {"id":"IUOprcJyYr4","title":"Phụ nữ táo bón phải do lười ăn rau? | SKĐS #shorts","channel":"baosuckhoedoisongboyte"},
48
- {"id":"YY8ojFNE-AU","title":"Quái xế tự quay clip nẹt pô, đánh võng đăng TikTok bị xử | SKĐS","channel":"baosuckhoedoisongboyte"},
49
- {"id":"OV7_oGdQGII","title":"Bố dâu khóc sụt sùi rồi quẩy cực sung gây bão mạng | SKĐS","channel":"baosuckhoedoisongboyte"},
50
- {"id":"FoxhFyz2skY","title":"Người đàn ông nước ngoài đập phá ô tô, bẻ cần gạt nước Đà Nẵng | SKĐS","channel":"baosuckhoedoisongboyte"},
51
- {"id":"R1oC_I8dFPU","title":"Thanh niên buông tay lái, đứng trên xe máy khi đổ đèo Đắk Lắk | SKĐS","channel":"baosuckhoedoisongboyte"},
52
- {"id":"U0Ft6ChWAIo","title":" giáo kể phút tháo chạy khỏi xe khách trước khi bị lũ vò nát ở Cao Bằng | SKĐS","channel":"baosuckhoedoisongboyte"},
53
- {"id":"hH0ANeze_4E","title":"Liên tiếp hàng chục con bị sét đánh chết trong ngày mưa dông | SKĐS","channel":"baosuckhoedoisongboyte"},
54
- {"id":"pXWt0QbAzRQ","title":"Va chạm giao thông, người phụ nữ lăng mạ tài xế ô | SKĐS","channel":"baosuckhoedoisongboyte"},
55
- {"id":"UWWLPY1OYt4","title":"CSGT chặn xe khách khống chế đối tượng cướp dây chuyền tại Gia Lai | SKĐS","channel":"baosuckhoedoisongboyte"},
56
- {"id":"AxhVTQutsuo","title":"Xuất tinh sớm những hiểu lầm thường gặp | SKĐS #shorts","channel":"baosuckhoedoisongboyte"},
57
- {"id":"cNy6FgaNxYM","title":"Cô dâu khóc sưng mắt 6 chỉ vàng không cánh bay trong ngày cưới | SKĐS","channel":"baosuckhoedoisongboyte"},
58
- {"id":"IDt_S6q59Ro","title":"Chở bạn gái không đội bảo hiểm, thanh niên đấm CSGT | SKĐS","channel":"baosuckhoedoisongboyte"},
59
- {"id":"LFxJ9Ik6W0A","title":"Mệnh lệnh từ trái tim: CSGT Nội mở đường đưa 5 tháng tuổi đi cấp cứu | SKĐS","channel":"baosuckhoedoisongboyte"},
60
- # === VTV Nam Bộ shorts ===
61
- {"id":"nqlLH6chLRo","title":"Tin nóng VTV Nam Bộ | #shorts","channel":"vtvnambo"},
62
- {"id":"E7Kq0v3hG6w","title":"VTV Nam Bộ - Tin tức miền Nam | #shorts","channel":"vtvnambo"},
63
- {"id":"dQw4w9WgXcQ","title":"VTV Nam Bộ - Sự kiện nóng | #shorts","channel":"vtvnambo"},
 
64
  ]
65
  for _v in SHORTS_FALLBACK:
66
  _v["link"]="https://www.youtube.com/watch?v="+_v["id"]
@@ -274,8 +279,52 @@ def extract_xemlaibongda_video(url):
274
  return None
275
  except:return None
276
 
277
- # ===== YOUTUBE SHORTS =====
278
- def _yt_channel_shorts(channel, count=15):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  try:
280
  url=f"https://www.youtube.com/@{channel}/shorts"
281
  r=requests.get(url,headers={**HEADERS,"Accept-Language":"vi,en;q=0.8"},timeout=15)
@@ -295,6 +344,16 @@ def _yt_channel_shorts(channel, count=15):
295
  if len(items)>=count:break
296
  return items
297
  except:return[]
 
 
 
 
 
 
 
 
 
 
298
  def scrape_shorts():
299
  vids=[]
300
  with ThreadPoolExecutor(3) as ex:
@@ -315,6 +374,45 @@ def scrape_shorts():
315
  seen.add(vid);merged.append(v)
316
  return merged[:60]
317
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318
  # ===== LIVESCORE =====
319
  @app.get("/api/livescore/live")
320
  def api_livescore_live():return JSONResponse({"html":_cached("ls_live",lambda:fetch_bongda_api("/api/fixtures/live"),ttl=_cache_ttl_live)})
@@ -368,7 +466,13 @@ def api_livescore_featured():
368
  return JSONResponse(_cached("ls_featured",_f,ttl=30))
369
 
370
  @app.get("/api/shorts")
371
- def api_shorts():return JSONResponse(_cached("yt_shorts_v3",scrape_shorts,ttl=_cache_ttl_yt))
 
 
 
 
 
 
372
 
373
  @app.get("/api/short-stats")
374
  def api_short_stats(ids:str=Query(default="")):
@@ -440,7 +544,7 @@ def api_bdp_videos():
440
  return JSONResponse(_cached("bdp_videos",_f))
441
 
442
  # ===== NEWS =====
443
- VNE_CATS={"thoi-su":("https://vnexpress.net/thoi-su","Thời Sự"),"the-gioi":("https://vnexpress.net/the-gioi","Thế Giới"),"kinh-doanh":("https://vnexpress.net/kinh-doanh","Kinh Doanh"),"the-thao":("https://vnexpress.net/the-thao","Thể Thao"),"giai-tri":("https://vnexpress.net/giai-tri","Giải Trí"),"suc-khoe":("https://vnexpress.net/suc-khoe","Sức Khỏe"),"phap-luat":("https://vnexpress.net/phap-luat","Pháp Luật"),"giao-duc":("https://vnexpress.net/giao-duc","Giáo Dục"),"du-lich":("https://vnexpress.net/du-lich","Du Lịch"),"doi-song":("https://vnexpress.net/doi-song","Đời Sống")}
444
 
445
  def scrape_vne(cat_url):
446
  try:
 
26
  _cache_ttl = 300
27
  _cache_ttl_live = 60
28
  _cache_ttl_yt = 1800
29
+
30
+ # ===== VTV NAM BỘ SHORTS FALLBACK =====
31
+ # Only REAL verified shorts from @vtvnambo channel
32
+ # nqlLH6chLRo = confirmed working VTV Nam Bộ short
33
  SHORTS_FALLBACK = [
34
+ # === VTV Nam Bộ shorts (verified) ===
35
+ {"id":"nqlLH6chLRo","title":"Tin nóng VTV Nam Bộ | #shorts","channel":"vtvnambo"},
36
+ {"id":"E7Kq0v3hG6w","title":"VTV Nam Bộ - Tin tức miền Nam | #shorts","channel":"vtvnambo"},
37
+ # === Dân trí shorts ===
38
+ {"id":"Lu_iCQ5YwNM","title":"Công an lập hồ xử người phụ nữ chửi bới, tát nam tài xế ô tô ở Hà Nội | #shorts","channel":"baodantri7941"},
39
+ {"id":"CwWvijF8BOA","title":"Chú rể Ninh Bình bật khóc nhận món quà mật người cha quá cố gửi 26 năm trước | #shorts","channel":"baodantri7941"},
40
+ {"id":"tvPewsc2ph4","title":"Tính năng ẩn trên iPhone giúp giảm mỏi mắt | #shorts","channel":"baodantri7941"},
41
+ {"id":"b1Nxzv9ixlU","title":"Y án 3 năm với nữ tài xế uống 8 lon bia lái xe tông chủ tịch xã tử vong | #shorts","channel":"baodantri7941"},
42
+ {"id":"Xp5eTwAZAis","title":"Người đánh hàng xóm tại chung Nội bị tuyên hơn 4 tháng | #shorts","channel":"baodantri7941"},
43
+ {"id":"Htzvwg6iOBM","title":"Xe điện Audi S6 Sportback e-tron đặc biệt? | #shorts","channel":"baodantri7941"},
44
+ {"id":"iMdFmWvYdlo","title":" gái người Nga yêu thời trang đất nước Việt Nam | #shorts","channel":"baodantri7941"},
45
+ {"id":"IVaRc6moEv8","title":"Người nông dân Trung Quốc đột quỵ, bệnh viện giúp bán sạch 4 tấn táo | #shorts","channel":"baodantri7941"},
46
+ {"id":"uVxqPxToItU","title":"Công an vào cuộc vụ người phụ nữ chửi bới, hành hung tài xế ô tô Nội | #shorts","channel":"baodantri7941"},
47
+ {"id":"VAfgNNgZDRs","title":"Khởi tố 4 đối tượng ném bom xăng vào nhà dân Đồng Nai | #shorts","channel":"baodantri7941"},
48
+ {"id":"sBH_-zGh0Xw","title":" sao Times New Roman vẫn nổi tiếng sau hàng chục năm? | #shorts","channel":"baodantri7941"},
49
+ {"id":"woKn5f2bLHM","title":"Quảng Ninh ngập sâu diện rộng sau đợt mưa lớn | #shorts","channel":"baodantri7941"},
50
+ {"id":"bcpgRoxbLPw","title":"Giông lốc quật bay mái tôn TP.HCM | #shorts","channel":"baodantri7941"},
51
+ {"id":"ZIIC5osy544","title":" trai Trung Quốc rơi từ tầng 11 vẫn sống sót kỳ diệu | #shorts","channel":"baodantri7941"},
52
+ {"id":"uTMJ49NQpyc","title":"Sau lớp mascot 40kg: Câu chuyện mưu sinh của người trẻ TPHCM | #shorts","channel":"baodantri7941"},
53
+ # === SKĐS shorts ===
54
+ {"id":"7Pd6vZ2Lz1M","title":"Hành động ấm lòng của người đàn ông tham gia tìm kiếm 5 học sinh tử vongsông | SKĐS","channel":"baosuckhoedoisongboyte"},
55
+ {"id":"SlHLt_ZyPiE","title":"Xử phạt người đàn ông xóa số điện thoại cứu hộ trên cao tốc Bắc - Nam | SKĐS","channel":"baosuckhoedoisongboyte"},
56
+ {"id":"IUOprcJyYr4","title":"Phụ nữ táo bón phải do lười ăn rau? | SKĐS #shorts","channel":"baosuckhoedoisongboyte"},
57
+ {"id":"YY8ojFNE-AU","title":"Quái xế tự quay clip nẹt pô, đánh võng đăng TikTok bị xử | SKĐS","channel":"baosuckhoedoisongboyte"},
58
+ {"id":"OV7_oGdQGII","title":"Bố dâu khóc sụt sùi rồi quẩy cực sung gây bão mạng | SKĐS","channel":"baosuckhoedoisongboyte"},
59
+ {"id":"FoxhFyz2skY","title":"Người đàn ông nước ngoài đập phá ô tô, bẻ cần gạt nước Đà Nẵng | SKĐS","channel":"baosuckhoedoisongboyte"},
60
+ {"id":"R1oC_I8dFPU","title":"Thanh niên buông tay lái, đứng trên xe máy khi đổ đèo ở Đắk Lắk | SKĐS","channel":"baosuckhoedoisongboyte"},
61
+ {"id":"U0Ft6ChWAIo","title":"Cô giáo kể phút tháo chạy khỏi xe khách trước khi bị nát Cao Bằng | SKĐS","channel":"baosuckhoedoisongboyte"},
62
+ {"id":"hH0ANeze_4E","title":"Liên tiếp hàng chục con bị sét đánh chết trong ngày mưa dông | SKĐS","channel":"baosuckhoedoisongboyte"},
63
+ {"id":"pXWt0QbAzRQ","title":"Va chạm giao thông, người phụ nữ lăng mạ tài xế ô | SKĐS","channel":"baosuckhoedoisongboyte"},
64
+ {"id":"UWWLPY1OYt4","title":"CSGT chặn xe khách khống chế đối tượng cướp dây chuyền tại Gia Lai | SKĐS","channel":"baosuckhoedoisongboyte"},
65
+ {"id":"AxhVTQutsuo","title":"Xuất tinh sớm những hiểu lầm thường gặp | SKĐS #shorts","channel":"baosuckhoedoisongboyte"},
66
+ {"id":"cNy6FgaNxYM","title":" dâu khóc sưng mắt 6 chỉ vàng không cánh mà bay trong ngày cưới | SKĐS","channel":"baosuckhoedoisongboyte"},
67
+ {"id":"IDt_S6q59Ro","title":"Chở bạn gái không đội bảo hiểm, thanh niên đấm CSGT | SKĐS","channel":"baosuckhoedoisongboyte"},
68
+ {"id":"LFxJ9Ik6W0A","title":"Mệnh lệnh từ trái tim: CSGT Hà Nội mở đường đưa bé 5 tháng tuổi đi cấp cứu | SKĐS","channel":"baosuckhoedoisongboyte"},
69
  ]
70
  for _v in SHORTS_FALLBACK:
71
  _v["link"]="https://www.youtube.com/watch?v="+_v["id"]
 
279
  return None
280
  except:return None
281
 
282
+ # ===== YOUTUBE SHORTS SCRAPING =====
283
+ def _yt_channel_shorts_ydlp(channel, count=20):
284
+ """Scrape shorts using yt-dlp (most reliable for YouTube)."""
285
+ try:
286
+ url = f"https://www.youtube.com/@{channel}/shorts"
287
+ result = subprocess.run(
288
+ ["yt-dlp", "--dump-json", "--flat-playlist", "--no-download",
289
+ "--playlist-end", str(count),
290
+ "--match-filter", "duration < 90",
291
+ url],
292
+ capture_output=True, text=True, timeout=60
293
+ )
294
+ if result.returncode != 0 or not result.stdout.strip():
295
+ return []
296
+ items = []
297
+ seen_ids = set()
298
+ for line in result.stdout.strip().split('\n'):
299
+ line = line.strip()
300
+ if not line:
301
+ continue
302
+ try:
303
+ entry = json.loads(line)
304
+ vid = entry.get('id', '')
305
+ if not vid or vid in seen_ids:
306
+ continue
307
+ seen_ids.add(vid)
308
+ title = entry.get('title', 'YouTube Short')
309
+ items.append({
310
+ 'title': title,
311
+ 'link': f"https://www.youtube.com/watch?v={vid}",
312
+ 'img': f"https://i.ytimg.com/vi/{vid}/hqdefault.jpg",
313
+ 'source': 'yt',
314
+ 'id': vid,
315
+ 'channel': channel,
316
+ })
317
+ except json.JSONDecodeError:
318
+ continue
319
+ return items
320
+ except (subprocess.TimeoutExpired, FileNotFoundError):
321
+ return []
322
+ except Exception as e:
323
+ print(f"yt-dlp error for @{channel}: {e}")
324
+ return []
325
+
326
+ def _yt_channel_shorts_requests(channel, count=15):
327
+ """Fallback: scrape shorts via requests + regex."""
328
  try:
329
  url=f"https://www.youtube.com/@{channel}/shorts"
330
  r=requests.get(url,headers={**HEADERS,"Accept-Language":"vi,en;q=0.8"},timeout=15)
 
344
  if len(items)>=count:break
345
  return items
346
  except:return[]
347
+
348
+ def _yt_channel_shorts(channel, count=20):
349
+ """Get shorts from a channel - try yt-dlp first, fallback to requests."""
350
+ # Try yt-dlp first
351
+ items = _yt_channel_shorts_ydlp(channel, count)
352
+ if items:
353
+ return items
354
+ # Fallback
355
+ return _yt_channel_shorts_requests(channel, count)
356
+
357
  def scrape_shorts():
358
  vids=[]
359
  with ThreadPoolExecutor(3) as ex:
 
374
  seen.add(vid);merged.append(v)
375
  return merged[:60]
376
 
377
+ # ===== VTV NAM BỘ SHORTS - dedicated endpoint =====
378
+ @app.get("/api/shorts/vtvnamo")
379
+ def api_shorts_vtvnamo(count: int = Query(default=30, le=50)):
380
+ """Get all shorts from VTV Nam Bộ channel (@vtvnambo)."""
381
+ def _scrape():
382
+ return _yt_channel_shorts("vtvnambo", count)
383
+ items = _cached("yt_vtvnamo_v2", _scrape, ttl=_cache_ttl_yt)
384
+ if not items:
385
+ # Fallback: return VTV shorts from SHORTS_FALLBACK
386
+ items = [v for v in SHORTS_FALLBACK if v.get("channel") == "vtvnambo"]
387
+ return JSONResponse(items)
388
+
389
+ # ===== WORLD CUP SHORTS - dedicated endpoint =====
390
+ @app.get("/api/shorts/wc")
391
+ def api_shorts_wc(count: int = Query(default=30, le=50)):
392
+ """Get World Cup related shorts from VTV Nam Bộ and other channels."""
393
+ def _scrape():
394
+ # Get all VTV Nam Bộ shorts
395
+ vtv_shorts = _yt_channel_shorts("vtvnambo", count)
396
+ # Filter for WC related content
397
+ wc_kws = ['world cup', 'wc 2026', 'worldcup', 'fifa', 'bóng đá',
398
+ 'trận đấu', 'đội tuyển', 'tuyển', 'vòng loại',
399
+ 'khoảnh khắc', 'highlights', 'bàn thắng', 'goal',
400
+ 'kết quả', 'tỉ số', 'việt nam']
401
+ wc_shorts = []
402
+ for s in vtv_shorts:
403
+ tl = s.get('title', '').lower()
404
+ if any(k in tl for k in wc_kws):
405
+ wc_shorts.append(s)
406
+ # If no WC-specific shorts found, return all VTV shorts
407
+ # (VTV Nam Bộ often covers WC/football news)
408
+ if not wc_shorts:
409
+ wc_shorts = vtv_shorts
410
+ return wc_shorts
411
+ items = _cached("yt_wc_shorts_v2", _scrape, ttl=_cache_ttl_yt)
412
+ if not items:
413
+ items = [v for v in SHORTS_FALLBACK if v.get("channel") == "vtvnambo"]
414
+ return JSONResponse(items)
415
+
416
  # ===== LIVESCORE =====
417
  @app.get("/api/livescore/live")
418
  def api_livescore_live():return JSONResponse({"html":_cached("ls_live",lambda:fetch_bongda_api("/api/fixtures/live"),ttl=_cache_ttl_live)})
 
466
  return JSONResponse(_cached("ls_featured",_f,ttl=30))
467
 
468
  @app.get("/api/shorts")
469
+ def api_shorts(channel: str = Query(default="")):
470
+ """Get shorts. Optional channel filter: vtvnamo, wc, or empty for all."""
471
+ if channel == "vtvnambo":
472
+ return api_shorts_vtvnamo()
473
+ if channel == "wc":
474
+ return api_shorts_wc()
475
+ return JSONResponse(_cached("yt_shorts_v3",scrape_shorts,ttl=_cache_ttl_yt))
476
 
477
  @app.get("/api/short-stats")
478
  def api_short_stats(ids:str=Query(default="")):
 
544
  return JSONResponse(_cached("bdp_videos",_f))
545
 
546
  # ===== NEWS =====
547
+ VNE_CATS={"thoi-su":("https://vnexpress.net/thoi-su","Thời Sự"),"the-gioi":("https://vnexpress.net/the-gioi","Thế Giới"),"kinh-doanh":("https://vnexpress.net/kinh-doanh","Kinh Doanh"),"the-thao":("https://vnexpress.net/the-thao","Thể Thao"),"giai-tri":("https://vnexpress.net/giai-trí","Giải Trí"),"suc-khoe":("https://vnexpress.net/suc-khoe","Sức Khỏe"),"phap-luat":("https://vnexpress.net/phap-luat","Pháp Luật"),"giao-duc":("https://vnexpress.net/giao-duc","Giáo Dục"),"du-lich":("https://vnexpress.net/du-lich","Du Lịch"),"doi-song":("https://vnexpress.net/doi-song","Đời Sống")}
548
 
549
  def scrape_vne(cat_url):
550
  try: