bep40 commited on
Commit
2e8a70b
·
verified ·
1 Parent(s): de76159

Upload main.py

Browse files
Files changed (1) hide show
  1. main.py +18 -44
main.py CHANGED
@@ -1,5 +1,5 @@
1
  """VNEWS - FastAPI backend with livescore + xemlaibongda highlights + YouTube VTV shorts"""
2
- import hashlib, re, time, subprocess, json, os, threading
3
  import html as html_lib
4
  from datetime import datetime, timezone, timedelta
5
 
@@ -7,7 +7,7 @@ VN_TZ = timezone(timedelta(hours=7))
7
  from concurrent.futures import ThreadPoolExecutor, as_completed
8
  from fastapi import FastAPI, Query, Request
9
  from fastapi.responses import HTMLResponse, JSONResponse, StreamingResponse, Response
10
- from urllib.parse import unquote, quote, urlencode
11
  import requests
12
  from bs4 import BeautifulSoup
13
 
@@ -20,46 +20,45 @@ app.include_router(vtv_router)
20
  HEADERS = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36","Accept-Language":"vi-VN,vi;q=0.9,en;q=0.8"}
21
  BONGDA_HEADERS = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36","Accept-Language":"vi-VN,vi;q=0.9","Referer":"https://bongda.com.vn/lich-thi-dau","X-Requested-With":"XMLHttpRequest"}
22
  BASE_BDP = "https://bongdaplus.vn"
23
- SPACE_URL = "https://bep40-vnews.hf.space"
24
  _cache = {}
25
  _cache_ttl = 300
26
  _cache_ttl_live = 60
27
  _cache_ttl_yt = 1800
28
 
29
- # ===== VTV NAM BỘ SHORTS FALLBACK =====
30
  SHORTS_FALLBACK = [
31
  {"id":"nqlLH6chLRo","title":"Tin nóng VTV Nam Bộ | #shorts","channel":"vtvnambo"},
32
  {"id":"E7Kq0v3hG6w","title":"VTV Nam Bộ - Tin tức miền Nam | #shorts","channel":"vtvnambo"},
33
- {"id":"Lu_iCQ5YwNM","title":"Công an lập hồ sơ xử lý người phụ nữ chửi bới, tát nam tài xế ô tô ở Hà Nội","channel":"baodantri7941"},
34
  {"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","channel":"baodantri7941"},
35
  {"id":"tvPewsc2ph4","title":"Tính năng ẩn trên iPhone giúp giảm mỏi mắt","channel":"baodantri7941"},
36
  {"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","channel":"baodantri7941"},
37
  {"id":"Xp5eTwAZAis","title":"Người đánh hàng xóm tại chung cư ở Hà Nội bị tuyên hơn 4 tháng tù","channel":"baodantri7941"},
38
  {"id":"Htzvwg6iOBM","title":"Xe điện Audi S6 Sportback e-tron có gì đặc biệt?","channel":"baodantri7941"},
39
  {"id":"iMdFmWvYdlo","title":"Cô gái người Nga yêu thời trang và đất nước Việt Nam","channel":"baodantri7941"},
40
- {"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","channel":"baodantri7941"},
41
- {"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ô ở Hà Nội","channel":"baodantri7941"},
42
  {"id":"VAfgNNgZDRs","title":"Khởi tố 4 đối tượng ném bom xăng vào nhà dân ở Đồng Nai","channel":"baodantri7941"},
43
  {"id":"sBH_-zGh0Xw","title":"Vì sao Times New Roman vẫn nổi tiếng sau hàng chục năm?","channel":"baodantri7941"},
44
  {"id":"woKn5f2bLHM","title":"Quảng Ninh ngập sâu diện rộng sau đợt mưa lớn","channel":"baodantri7941"},
45
  {"id":"bcpgRoxbLPw","title":"Giông lốc quật bay mái tôn ở TP.HCM","channel":"baodantri7941"},
46
  {"id":"ZIIC5osy544","title":"Bé trai Trung Quốc rơi từ tầng 11 vẫn sống sót kỳ diệu","channel":"baodantri7941"},
47
- {"id":"uTMJ49NQpyc","title":"Sau lớp mascot 40kg: Câu chuyện mưu sinh của người trẻ ở TPHCM","channel":"baodantri7941"},
48
  {"id":"7Pd6vZ2Lz1M","title":"Hành động ấm lòng của người đàn ông tìm kiếm 5 học sinh tử vong ở sông Lô","channel":"baosuckhoedoisongboyte"},
49
- {"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","channel":"baosuckhoedoisongboyte"},
50
  {"id":"IUOprcJyYr4","title":"Phụ nữ táo bón có phải do lười ăn rau?","channel":"baosuckhoedoisongboyte"},
51
- {"id":"YY8ojFNE-AU","title":"Quái xế tự quay clip nẹt pô, đánh võng đăng TikTok bị xử lý","channel":"baosuckhoedoisongboyte"},
52
  {"id":"OV7_oGdQGII","title":"Bố cô dâu khóc sụt sùi rồi quẩy cực sung gây bão mạng","channel":"baosuckhoedoisongboyte"},
53
- {"id":"FoxhFyz2skY","title":"Người đàn ông nước ngoài đập phá ô tô, bẻ cần gạt nước ở Đà Nẵng","channel":"baosuckhoedoisongboyte"},
54
- {"id":"R1oC_I8dFPU","title":"Thanh niên buông tay lái, đứng trên xe máy khi đổ đèo ở Đắk Lắk","channel":"baosuckhoedoisongboyte"},
55
  {"id":"U0Ft6ChWAIo","title":"Cô 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","channel":"baosuckhoedoisongboyte"},
56
  {"id":"hH0ANeze_4E","title":"Liên tiếp hàng chục con bò bị sét đánh chết trong ngày mưa dông","channel":"baosuckhoedoisongboyte"},
57
- {"id":"pXWt0QbAzRQ","title":"Va chạm giao thông, người phụ nữ lăng mạ tài xế ô tô","channel":"baosuckhoedoisongboyte"},
58
  {"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","channel":"baosuckhoedoisongboyte"},
59
  {"id":"AxhVTQutsuo","title":"Xuất tinh sớm và những hiểu lầm thường gặp","channel":"baosuckhoedoisongboyte"},
60
  {"id":"cNy6FgaNxYM","title":"Cô dâu khóc sưng mắt vì 6 chỉ vàng không cánh mà bay trong ngày cưới","channel":"baosuckhoedoisongboyte"},
61
- {"id":"IDt_S6q59Ro","title":"Chở bạn gái không đội mũ bảo hiểm, thanh niên đấm CSGT","channel":"baosuckhoedoisongboyte"},
62
- {"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","channel":"baosuckhoedoisongboyte"},
63
  ]
64
  for _v in SHORTS_FALLBACK:
65
  _v.setdefault("link", "https://www.youtube.com/watch?v="+_v["id"])
@@ -82,20 +81,7 @@ def _save_short_db(db):
82
  os.replace(tmp, SHORT_STATS_FILE)
83
  except: pass
84
  def _short_default(): return {"views":0,"likes":0,"shares":0,"comments":[]}
85
- WALL_FILE = "/data/wall_posts.json" if os.path.isdir("/data") else "/app/wall_posts.json"
86
- def _load_wall():
87
- try:
88
- if os.path.exists(WALL_FILE):
89
- with open(WALL_FILE,"r",encoding="utf-8") as f: return json.load(f)
90
- except: pass
91
- return []
92
- def _save_wall(posts):
93
- try:
94
- os.makedirs(os.path.dirname(WALL_FILE), exist_ok=True)
95
- tmp = WALL_FILE + ".tmp"
96
- with open(tmp,"w",encoding="utf-8") as f: json.dump(posts[:100], f, ensure_ascii=False)
97
- os.replace(tmp, WALL_FILE)
98
- except: pass
99
  PRIORITY_LEAGUES = ["Ngoại Hạng Anh","FA Cup","Champions League","LaLiga","Copa del Rey","Serie A","Bundesliga","Ligue 1","V-League"]
100
  LEAGUE_IDS = {"nha":27110,"laliga":27233,"seriea":27044,"bundesliga":26891,"ligue1":27212}
101
  HL_LEAGUES = {
@@ -236,15 +222,6 @@ def _scrape_xemlaibongda_page(page_path, limit=20):
236
  if not title or len(title) < 3: continue
237
  videos.append({"title":title,"link":href,"img":img_src,"source":"xemlaibongda"})
238
  if len(videos) >= limit: break
239
- if not videos:
240
- for vid_el in soup.find_all("video"):
241
- src = vid_el.get("src",""); poster = vid_el.get("poster","")
242
- source_el = vid_el.find("source")
243
- if not src and source_el: src = source_el.get("src","")
244
- if src:
245
- title = poster.rsplit("/",1)[-1].rsplit(".",1)[0].replace("-"," ").title() if poster else "Video"
246
- videos.append({"title":title,"link":src,"img":poster,"source":"xemlaibongda"})
247
- if len(videos) >= limit: break
248
  return videos
249
  except: return []
250
 
@@ -282,7 +259,7 @@ def extract_xemlaibongda_video(url):
282
  return None
283
  except: return None
284
 
285
- # ===== YOUTUBE SHORTS SCRAPING (legacy requests fallback) =====
286
  def _yt_channel_shorts_requests(channel, count=15):
287
  try:
288
  url=f"https://www.youtube.com/@{channel}/shorts"
@@ -324,16 +301,14 @@ def scrape_shorts():
324
  seen.add(vid); merged.append(v)
325
  return merged[:60]
326
 
327
- # ===== VTV NAM BỘ & WC SHORTS - using yt-dlp =====
328
  from yt_scraper import get_vtvnambo_shorts, get_wc_related_shorts
329
 
330
  @app.get("/api/shorts/vtvnamo")
331
  def api_shorts_vtvnamo(count: int = Query(default=50, le=100)):
332
- """Get all shorts from VTV Nam Bộ channel (@vtvnambo) using yt-dlp."""
333
  items = get_vtvnambo_shorts(count)
334
  if not items:
335
  items = [v for v in SHORTS_FALLBACK if v.get("channel") == "vtvnambo"]
336
- # Ensure nqlLH6chLRo is always first
337
  nql = [v for v in items if v.get("id") == "nqlLH6chLRo"]
338
  rest = [v for v in items if v.get("id") != "nqlLH6chLRo"]
339
  items = nql + rest
@@ -341,7 +316,6 @@ def api_shorts_vtvnamo(count: int = Query(default=50, le=100)):
341
 
342
  @app.get("/api/shorts/wc")
343
  def api_shorts_wc(count: int = Query(default=50, le=100)):
344
- """Get World Cup / football related shorts using yt-dlp."""
345
  items = get_wc_related_shorts(count)
346
  if not items:
347
  items = [v for v in SHORTS_FALLBACK if v.get("channel") == "vtvnambo"]
@@ -530,7 +504,7 @@ def scrape_genk_ai():
530
 
531
  @app.get("/api/homepage")
532
  def api_homepage():
533
- def _f()
534
  articles=[]
535
  with ThreadPoolExecutor(12) as ex:
536
  futs={ex.submit(scrape_vne,VNE_CATS[k][0]):VNE_CATS[k][1] for k in["thoi-su","the-gioi","kinh-doanh","the-thao","giai-tri","phap-luat","giao-duc","du-lich","doi-song"]}
 
1
  """VNEWS - FastAPI backend with livescore + xemlaibongda highlights + YouTube VTV shorts"""
2
+ import re, time, subprocess, json, os, threading
3
  import html as html_lib
4
  from datetime import datetime, timezone, timedelta
5
 
 
7
  from concurrent.futures import ThreadPoolExecutor, as_completed
8
  from fastapi import FastAPI, Query, Request
9
  from fastapi.responses import HTMLResponse, JSONResponse, StreamingResponse, Response
10
+ from urllib.parse import quote
11
  import requests
12
  from bs4 import BeautifulSoup
13
 
 
20
  HEADERS = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36","Accept-Language":"vi-VN,vi;q=0.9,en;q=0.8"}
21
  BONGDA_HEADERS = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36","Accept-Language":"vi-VN,vi;q=0.9","Referer":"https://bongda.com.vn/lich-thi-dau","X-Requested-With":"XMLHttpRequest"}
22
  BASE_BDP = "https://bongdaplus.vn"
 
23
  _cache = {}
24
  _cache_ttl = 300
25
  _cache_ttl_live = 60
26
  _cache_ttl_yt = 1800
27
 
28
+ # ===== VTV NAM BO SHORTS FALLBACK =====
29
  SHORTS_FALLBACK = [
30
  {"id":"nqlLH6chLRo","title":"Tin nóng VTV Nam Bộ | #shorts","channel":"vtvnambo"},
31
  {"id":"E7Kq0v3hG6w","title":"VTV Nam Bộ - Tin tức miền Nam | #shorts","channel":"vtvnambo"},
32
+ {"id":"Lu_iCQ5YwNM","title":"Công an lập hồ sơ xử lý người phụ nữ chửi bới tát nam tài xế ô tô ở Hà Nội","channel":"baodantri7941"},
33
  {"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","channel":"baodantri7941"},
34
  {"id":"tvPewsc2ph4","title":"Tính năng ẩn trên iPhone giúp giảm mỏi mắt","channel":"baodantri7941"},
35
  {"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","channel":"baodantri7941"},
36
  {"id":"Xp5eTwAZAis","title":"Người đánh hàng xóm tại chung cư ở Hà Nội bị tuyên hơn 4 tháng tù","channel":"baodantri7941"},
37
  {"id":"Htzvwg6iOBM","title":"Xe điện Audi S6 Sportback e-tron có gì đặc biệt?","channel":"baodantri7941"},
38
  {"id":"iMdFmWvYdlo","title":"Cô gái người Nga yêu thời trang và đất nước Việt Nam","channel":"baodantri7941"},
39
+ {"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","channel":"baodantri7941"},
40
+ {"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ô ở Hà Nội","channel":"baodantri7941"},
41
  {"id":"VAfgNNgZDRs","title":"Khởi tố 4 đối tượng ném bom xăng vào nhà dân ở Đồng Nai","channel":"baodantri7941"},
42
  {"id":"sBH_-zGh0Xw","title":"Vì sao Times New Roman vẫn nổi tiếng sau hàng chục năm?","channel":"baodantri7941"},
43
  {"id":"woKn5f2bLHM","title":"Quảng Ninh ngập sâu diện rộng sau đợt mưa lớn","channel":"baodantri7941"},
44
  {"id":"bcpgRoxbLPw","title":"Giông lốc quật bay mái tôn ở TP.HCM","channel":"baodantri7941"},
45
  {"id":"ZIIC5osy544","title":"Bé trai Trung Quốc rơi từ tầng 11 vẫn sống sót kỳ diệu","channel":"baodantri7941"},
46
+ {"id":"uTMJ49NQpyc","title":"Sau lớp mascot 40kg Câu chuyện mưu sinh của người trẻ ở TPHCM","channel":"baodantri7941"},
47
  {"id":"7Pd6vZ2Lz1M","title":"Hành động ấm lòng của người đàn ông tìm kiếm 5 học sinh tử vong ở sông Lô","channel":"baosuckhoedoisongboyte"},
48
+ {"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","channel":"baosuckhoedoisongboyte"},
49
  {"id":"IUOprcJyYr4","title":"Phụ nữ táo bón có phải do lười ăn rau?","channel":"baosuckhoedoisongboyte"},
50
+ {"id":"YY8ojFNE-AU","title":"Quái xế tự quay clip nẹt pô đánh võng đăng TikTok bị xử lý","channel":"baosuckhoedoisongboyte"},
51
  {"id":"OV7_oGdQGII","title":"Bố cô dâu khóc sụt sùi rồi quẩy cực sung gây bão mạng","channel":"baosuckhoedoisongboyte"},
52
+ {"id":"FoxhFyz2skY","title":"Người đàn ông nước ngoài đập phá ô tô bẻ cần gạt nước ở Đà Nẵng","channel":"baosuckhoedoisongboyte"},
53
+ {"id":"R1oC_I8dFPU","title":"Thanh niên buông tay lái đứng trên xe máy khi đổ đèo ở Đắk Lắk","channel":"baosuckhoedoisongboyte"},
54
  {"id":"U0Ft6ChWAIo","title":"Cô 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","channel":"baosuckhoedoisongboyte"},
55
  {"id":"hH0ANeze_4E","title":"Liên tiếp hàng chục con bò bị sét đánh chết trong ngày mưa dông","channel":"baosuckhoedoisongboyte"},
56
+ {"id":"pXWt0QbAzRQ","title":"Va chạm giao thông người phụ nữ lăng mạ tài xế ô tô","channel":"baosuckhoedoisongboyte"},
57
  {"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","channel":"baosuckhoedoisongboyte"},
58
  {"id":"AxhVTQutsuo","title":"Xuất tinh sớm và những hiểu lầm thường gặp","channel":"baosuckhoedoisongboyte"},
59
  {"id":"cNy6FgaNxYM","title":"Cô dâu khóc sưng mắt vì 6 chỉ vàng không cánh mà bay trong ngày cưới","channel":"baosuckhoedoisongboyte"},
60
+ {"id":"IDt_S6q59Ro","title":"Chở bạn gái không đội mũ bảo hiểm thanh niên đấm CSGT","channel":"baosuckhoedoisongboyte"},
61
+ {"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","channel":"baosuckhoedoisongboyte"},
62
  ]
63
  for _v in SHORTS_FALLBACK:
64
  _v.setdefault("link", "https://www.youtube.com/watch?v="+_v["id"])
 
81
  os.replace(tmp, SHORT_STATS_FILE)
82
  except: pass
83
  def _short_default(): return {"views":0,"likes":0,"shares":0,"comments":[]}
84
+
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  PRIORITY_LEAGUES = ["Ngoại Hạng Anh","FA Cup","Champions League","LaLiga","Copa del Rey","Serie A","Bundesliga","Ligue 1","V-League"]
86
  LEAGUE_IDS = {"nha":27110,"laliga":27233,"seriea":27044,"bundesliga":26891,"ligue1":27212}
87
  HL_LEAGUES = {
 
222
  if not title or len(title) < 3: continue
223
  videos.append({"title":title,"link":href,"img":img_src,"source":"xemlaibongda"})
224
  if len(videos) >= limit: break
 
 
 
 
 
 
 
 
 
225
  return videos
226
  except: return []
227
 
 
259
  return None
260
  except: return None
261
 
262
+ # ===== YOUTUBE SHORTS SCRAPING =====
263
  def _yt_channel_shorts_requests(channel, count=15):
264
  try:
265
  url=f"https://www.youtube.com/@{channel}/shorts"
 
301
  seen.add(vid); merged.append(v)
302
  return merged[:60]
303
 
304
+ # ===== VTV NAM BO & WC SHORTS - using yt-dlp =====
305
  from yt_scraper import get_vtvnambo_shorts, get_wc_related_shorts
306
 
307
  @app.get("/api/shorts/vtvnamo")
308
  def api_shorts_vtvnamo(count: int = Query(default=50, le=100)):
 
309
  items = get_vtvnambo_shorts(count)
310
  if not items:
311
  items = [v for v in SHORTS_FALLBACK if v.get("channel") == "vtvnambo"]
 
312
  nql = [v for v in items if v.get("id") == "nqlLH6chLRo"]
313
  rest = [v for v in items if v.get("id") != "nqlLH6chLRo"]
314
  items = nql + rest
 
316
 
317
  @app.get("/api/shorts/wc")
318
  def api_shorts_wc(count: int = Query(default=50, le=100)):
 
319
  items = get_wc_related_shorts(count)
320
  if not items:
321
  items = [v for v in SHORTS_FALLBACK if v.get("channel") == "vtvnambo"]
 
504
 
505
  @app.get("/api/homepage")
506
  def api_homepage():
507
+ def _f():
508
  articles=[]
509
  with ThreadPoolExecutor(12) as ex:
510
  futs={ex.submit(scrape_vne,VNE_CATS[k][0]):VNE_CATS[k][1] for k in["thoi-su","the-gioi","kinh-doanh","the-thao","giai-tri","phap-luat","giao-duc","du-lich","doi-song"]}