Spaces:
Running
Running
World Cup RSS all articles + persistent AI rewrite wall APIs
Browse files
main.py
CHANGED
|
@@ -72,6 +72,20 @@ def _save_short_db(db):
|
|
| 72 |
except:pass
|
| 73 |
|
| 74 |
def _short_default():return {"views":0,"likes":0,"shares":0,"comments":[]}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
PRIORITY_LEAGUES = ["Ngoại Hạng Anh","FA Cup","Champions League","LaLiga","Copa del Rey","Serie A","Bundesliga","Ligue 1","V-League"]
|
| 76 |
LEAGUE_IDS = {"nha":27110,"laliga":27233,"seriea":27044,"bundesliga":26891,"ligue1":27212}
|
| 77 |
HL_LEAGUES = {"premier-league":{"path":"anh/premier-league","name":"Premier League","emoji":"🏴"},"fa-cup":{"path":"anh/fa-cup","name":"FA Cup","emoji":"🏆"},"bundesliga":{"path":"duc/bundesliga","name":"Bundesliga","emoji":"🇩🇪"},"serie-a":{"path":"italy/serie-a","name":"Serie A","emoji":"🇮🇹"},"la-liga":{"path":"tay-ban-nha/la-liga","name":"La Liga","emoji":"🇪🇸"},"champions-league":{"path":"cup-chau-au/uefa-champions-league","name":"Champions League","emoji":"⭐"},"europa-league":{"path":"cup-chau-au/uefa-europa-league","name":"Europa League","emoji":"🟠"},"world-cup":{"path":"the-gioi/world-cup-qualifiers","name":"World Cup 2026","emoji":"🌍"}}
|
|
@@ -541,7 +555,20 @@ def scrape_bbc_article(url):
|
|
| 541 |
except:return None
|
| 542 |
|
| 543 |
def scrape_ttvh_worldcup():
|
| 544 |
-
"""Scrape World Cup 2026 articles from The Thao Van Hoa."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 545 |
try:
|
| 546 |
soup=_get("https://thethaovanhoa.vn/world-cup-2026.htm");arts=[];seen=set()
|
| 547 |
for a in soup.find_all("a",href=True):
|
|
@@ -646,6 +673,43 @@ def api_article(url:str=Query(...)):
|
|
| 646 |
elif"thethaovanhoa.vn" in url:data=scrape_ttvh_article(url)
|
| 647 |
else:data=None
|
| 648 |
return JSONResponse(data if data else{"error":"not supported"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 649 |
@app.get("/v")
|
| 650 |
async def video_share(url:str=Query(default=""),title:str=Query(default="VNEWS Video"),img:str=Query(default=""),type:str=Query(default="highlights")):
|
| 651 |
decoded_url=unquote(url);decoded_title=unquote(title)
|
|
|
|
| 72 |
except:pass
|
| 73 |
|
| 74 |
def _short_default():return {"views":0,"likes":0,"shares":0,"comments":[]}
|
| 75 |
+
WALL_FILE = "/data/wall_posts.json" if os.path.isdir("/data") else "/app/wall_posts.json"
|
| 76 |
+
def _load_wall():
|
| 77 |
+
try:
|
| 78 |
+
if os.path.exists(WALL_FILE):
|
| 79 |
+
with open(WALL_FILE,"r",encoding="utf-8") as f:return json.load(f)
|
| 80 |
+
except:pass
|
| 81 |
+
return []
|
| 82 |
+
def _save_wall(posts):
|
| 83 |
+
try:
|
| 84 |
+
os.makedirs(os.path.dirname(WALL_FILE),exist_ok=True)
|
| 85 |
+
tmp=WALL_FILE+".tmp"
|
| 86 |
+
with open(tmp,"w",encoding="utf-8") as f:json.dump(posts[:100],f,ensure_ascii=False)
|
| 87 |
+
os.replace(tmp,WALL_FILE)
|
| 88 |
+
except:pass
|
| 89 |
PRIORITY_LEAGUES = ["Ngoại Hạng Anh","FA Cup","Champions League","LaLiga","Copa del Rey","Serie A","Bundesliga","Ligue 1","V-League"]
|
| 90 |
LEAGUE_IDS = {"nha":27110,"laliga":27233,"seriea":27044,"bundesliga":26891,"ligue1":27212}
|
| 91 |
HL_LEAGUES = {"premier-league":{"path":"anh/premier-league","name":"Premier League","emoji":"🏴"},"fa-cup":{"path":"anh/fa-cup","name":"FA Cup","emoji":"🏆"},"bundesliga":{"path":"duc/bundesliga","name":"Bundesliga","emoji":"🇩🇪"},"serie-a":{"path":"italy/serie-a","name":"Serie A","emoji":"🇮🇹"},"la-liga":{"path":"tay-ban-nha/la-liga","name":"La Liga","emoji":"🇪🇸"},"champions-league":{"path":"cup-chau-au/uefa-champions-league","name":"Champions League","emoji":"⭐"},"europa-league":{"path":"cup-chau-au/uefa-europa-league","name":"Europa League","emoji":"🟠"},"world-cup":{"path":"the-gioi/world-cup-qualifiers","name":"World Cup 2026","emoji":"🌍"}}
|
|
|
|
| 555 |
except:return None
|
| 556 |
|
| 557 |
def scrape_ttvh_worldcup():
|
| 558 |
+
"""Scrape all World Cup 2026 articles from The Thao Van Hoa RSS."""
|
| 559 |
+
try:
|
| 560 |
+
r=requests.get("https://thethaovanhoa.vn/rss/world-cup-2026.rss",headers=HEADERS,timeout=15);r.encoding="utf-8"
|
| 561 |
+
soup=BeautifulSoup(r.text,"xml");arts=[];seen=set()
|
| 562 |
+
for it in soup.find_all("item"):
|
| 563 |
+
title=(it.find("title").get_text(strip=True) if it.find("title") else "")
|
| 564 |
+
link=(it.find("link").get_text(strip=True) if it.find("link") else "")
|
| 565 |
+
desc=(it.find("description").get_text(" ",strip=True) if it.find("description") else "")
|
| 566 |
+
img="";ds=BeautifulSoup(desc,"lxml");im=ds.find("img")
|
| 567 |
+
if im:img=im.get("src","") or im.get("data-src","")
|
| 568 |
+
if title and link and link not in seen:
|
| 569 |
+
seen.add(link);arts.append({"title":title,"link":link,"img":img,"source":"ttvh"})
|
| 570 |
+
if arts:return arts
|
| 571 |
+
except:pass
|
| 572 |
try:
|
| 573 |
soup=_get("https://thethaovanhoa.vn/world-cup-2026.htm");arts=[];seen=set()
|
| 574 |
for a in soup.find_all("a",href=True):
|
|
|
|
| 673 |
elif"thethaovanhoa.vn" in url:data=scrape_ttvh_article(url)
|
| 674 |
else:data=None
|
| 675 |
return JSONResponse(data if data else{"error":"not supported"})
|
| 676 |
+
def _article_by_url(url):
|
| 677 |
+
if "vnexpress.net" in url:return scrape_vne_article(url)
|
| 678 |
+
if "bbc.com" in url:return scrape_bbc_article(url)
|
| 679 |
+
if "dantri.com.vn" in url:return scrape_dantri_article(url)
|
| 680 |
+
if "genk.vn" in url:return scrape_genk_article(url)
|
| 681 |
+
if "thethaovanhoa.vn" in url:return scrape_ttvh_article(url)
|
| 682 |
+
return None
|
| 683 |
+
|
| 684 |
+
def _ai_rewrite_article(data):
|
| 685 |
+
title=(data or {}).get("title","");summary=(data or {}).get("summary","")
|
| 686 |
+
ps=[b.get("text","") for b in (data or {}).get("body",[]) if b.get("type")=="p" and len(b.get("text",""))>30]
|
| 687 |
+
lead=summary or (ps[0] if ps else "")
|
| 688 |
+
points=[]
|
| 689 |
+
for p in ps[:5]:
|
| 690 |
+
t=p.strip()
|
| 691 |
+
if len(t)>180:t=t[:177]+"..."
|
| 692 |
+
points.append(t)
|
| 693 |
+
rewritten="Bản tin AI viết lại: "+title+"\n\n"+lead
|
| 694 |
+
if points:rewritten += "\n\nĐiểm chính:\n" + "\n".join(["• "+x for x in points])
|
| 695 |
+
return rewritten
|
| 696 |
+
|
| 697 |
+
@app.get("/api/wall")
|
| 698 |
+
def api_wall():return JSONResponse({"posts":_load_wall()[:50]})
|
| 699 |
+
|
| 700 |
+
@app.post("/api/rewrite_share")
|
| 701 |
+
async def api_rewrite_share(request:Request):
|
| 702 |
+
try:body=await request.json()
|
| 703 |
+
except:body={}
|
| 704 |
+
url=str(body.get("url","")).strip()
|
| 705 |
+
if not url:return JSONResponse({"error":"missing url"},status_code=400)
|
| 706 |
+
data=_article_by_url(url)
|
| 707 |
+
if not data or not data.get("title") or not data.get("body"):
|
| 708 |
+
return JSONResponse({"error":"Không đọc được bài viết"},status_code=422)
|
| 709 |
+
post={"id":hashlib.md5((url+str(time.time())).encode()).hexdigest()[:12],"url":url,"title":data.get("title",""),"img":data.get("og_image","") or "","text":_ai_rewrite_article(data),"ts":int(time.time()),"source":data.get("source","")}
|
| 710 |
+
posts=_load_wall();posts.insert(0,post);_save_wall(posts)
|
| 711 |
+
return JSONResponse({"post":post})
|
| 712 |
+
|
| 713 |
@app.get("/v")
|
| 714 |
async def video_share(url:str=Query(default=""),title:str=Query(default="VNEWS Video"),img:str=Query(default=""),type:str=Query(default="highlights")):
|
| 715 |
decoded_url=unquote(url);decoded_title=unquote(title)
|