bep40 commited on
Commit
437fe01
·
verified ·
1 Parent(s): fa0e8d3

Upload app_v2_entry.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app_v2_entry.py +228 -15
app_v2_entry.py CHANGED
@@ -1,11 +1,6 @@
1
  """VNEWS v2 Entry Point - with fast bongda proxy"""
2
  import sys, os
3
- from main import app, HEADERS, fetch_bongda_api, HL_LEAGUES
4
- # Bongda headers — try both names for compatibility
5
- try:
6
- from main import BONGDA_HEADERS as _BDH
7
- except ImportError:
8
- from main import BONGDA_API_HEADERS as _BDH
9
 
10
  try:
11
  import ai_ext
@@ -423,10 +418,11 @@ def _get_hot_topics():
423
  @app.get('/api/hot_topics')
424
  def api_hot_topics():return JSONResponse({'topics':_get_hot_topics()})
425
  @app.get('/')
426
- # Serve v4
427
  async def serve_index():
428
- p=os.path.join(STATIC_DIR,'index_v4.html')
429
  if os.path.exists(p):return FileResponse(p,media_type='text/html')
 
 
430
  return HTMLResponse('<h1>VNEWS</h1>')
431
  @app.get('/api/hashtag/sources')
432
  def _ht(topic:str=Query(...),page:int=Query(default=0)):
@@ -496,19 +492,237 @@ async def _pc(request:Request):
496
  with _il:idb=_lj(IF);idb.setdefault(v,{'views':0,'likes':0,'comments':0});idb[v]['comments']=len(cms);_sj(IF,idb)
497
  return JSONResponse({'comments':cms})
498
 
499
- # === Redirect bongda.com.vn URLs ===
500
- from fastapi.responses import RedirectResponse
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
501
 
502
  @app.get("/doi-bong/{path:path}")
503
- async def redirect_doi_bong(path: str):
504
- return RedirectResponse(f"https://bongda.com.vn/doi-bong/{path}", status_code=302)
 
505
 
506
  @app.get("/giai-dau/{path:path}")
507
- async def redirect_giai_dau(path: str):
 
508
  return RedirectResponse(f"https://bongda.com.vn/giai-dau/{path}", status_code=302)
509
 
 
510
  @app.get("/cau-thu/{path:path}")
511
  async def redirect_cau_thu(path: str):
 
512
  return RedirectResponse(f"https://bongda.com.vn/cau-thu/{path}", status_code=302)
513
 
514
  def _bg():
@@ -519,5 +733,4 @@ def _bg():
519
  time.sleep(90)
520
  threading.Thread(target=_bg,daemon=True).start()
521
 
522
- app.mount('/static',StaticFiles(directory=STATIC_DIR),name='vnews_static')
523
- # Updated: 1781062126
 
1
  """VNEWS v2 Entry Point - with fast bongda proxy"""
2
  import sys, os
3
+ from main import app, HEADERS, BONGDA_HEADERS, fetch_bongda_api, HL_LEAGUES
 
 
 
 
 
4
 
5
  try:
6
  import ai_ext
 
418
  @app.get('/api/hot_topics')
419
  def api_hot_topics():return JSONResponse({'topics':_get_hot_topics()})
420
  @app.get('/')
 
421
  async def serve_index():
422
+ p=os.path.join(STATIC_DIR,'index_v3.html')
423
  if os.path.exists(p):return FileResponse(p,media_type='text/html')
424
+ p2=os.path.join(STATIC_DIR,'index_v2.html')
425
+ if os.path.exists(p2):return FileResponse(p2,media_type='text/html')
426
  return HTMLResponse('<h1>VNEWS</h1>')
427
  @app.get('/api/hashtag/sources')
428
  def _ht(topic:str=Query(...),page:int=Query(default=0)):
 
492
  with _il:idb=_lj(IF);idb.setdefault(v,{'views':0,'likes':0,'comments':0});idb[v]['comments']=len(cms);_sj(IF,idb)
493
  return JSONResponse({'comments':cms})
494
 
495
+ # ============================================================
496
+ # TEAM STATS — scrape bongda.com.vn /doi-bong/ page, render inline
497
+ # ============================================================
498
+ def _scrape_team_page(team_path):
499
+ """Scrape team stats from bongda.com.vn team page."""
500
+ url = f"https://bongda.com.vn/doi-bong/{team_path}"
501
+ headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Accept": "text/html", "Referer": "https://bongda.com.vn/"}
502
+ try:
503
+ r = req.get(url, headers=headers, timeout=15, allow_redirects=True)
504
+ if r.status_code != 200:
505
+ return None
506
+ soup = BeautifulSoup(r.text, 'html.parser')
507
+ except:
508
+ return None
509
+
510
+ team = {"name": "", "logo": "", "league": "", "country": "", "stats": {}, "recent": [], "squad": [], "found": False}
511
+
512
+ h1 = soup.select_one('h1')
513
+ if h1:
514
+ team['name'] = _clean(h1.get_text())
515
+ team['found'] = True
516
+
517
+ logo_el = soup.select_one('.team-logo img, .club-logo img, img.team-logo, .team-header img')
518
+ if logo_el:
519
+ team['logo'] = logo_el.get('src', '')
520
+
521
+ info_el = soup.select_one('.team-info, .club-info, .team-header')
522
+ if info_el:
523
+ txt = info_el.get_text()
524
+ if 'việt nam' in txt.lower() or 'vietnam' in txt.lower():
525
+ team['country'] = 'Việt Nam'
526
+
527
+ for li in soup.select('li.match-detail, .match-list .match-item, .recent-matches li'):
528
+ home_el = li.select_one('.home-team .name, .team-home')
529
+ away_el = li.select_one('.away-team .name, .team-away')
530
+ if not home_el or not away_el:
531
+ continue
532
+ home = _clean(home_el.get_text())
533
+ away = _clean(away_el.get_text())
534
+ status_el = li.select_one('.status a, .score')
535
+ score = ""
536
+ event_id = ""
537
+ match_url = ""
538
+ if status_el:
539
+ href = status_el.get('href', '')
540
+ if href:
541
+ match_url = 'https://bongda.com.vn' + href if href.startswith('/') else href
542
+ m = re.search(r'/tran-dau/(\d+)/', href)
543
+ if m:
544
+ event_id = m.group(1)
545
+ spans = status_el.find_all('span')
546
+ if len(spans) >= 3:
547
+ score = f"{spans[0].get_text(strip=True)} - {spans[2].get_text(strip=True)}"
548
+ elif status_el.select_one('.vs'):
549
+ score = 'VS'
550
+ time_el = li.select_one('.match-time, .time, .date')
551
+ mt = _clean(time_el.get_text()) if time_el else ''
552
+ team['recent'].append({
553
+ 'home': home, 'away': away, 'score': score, 'time': mt,
554
+ 'event_id': event_id, 'url': match_url
555
+ })
556
+ if len(team['recent']) >= 10:
557
+ break
558
+
559
+ standings_rows = []
560
+ for tr in soup.select('.standings-table tr, .league-table tr, table tbody tr'):
561
+ cells = tr.select('td')
562
+ if len(cells) >= 5:
563
+ pos = _clean(cells[0].get_text())
564
+ tname = _clean(cells[1].get_text()) if len(cells) > 1 else ''
565
+ played = _clean(cells[2].get_text()) if len(cells) > 2 else ''
566
+ gd = _clean(cells[3].get_text()) if len(cells) > 3 else ''
567
+ pts = _clean(cells[4].get_text()) if len(cells) > 4 else ''
568
+ if pos and tname and pos.isdigit():
569
+ standings_rows.append({'pos': pos, 'team': tname, 'played': played, 'gd': gd, 'pts': pts})
570
+ if standings_rows:
571
+ team['standings'] = standings_rows
572
+
573
+ for p in soup.select('.player-item, .squad-item, .player-card'):
574
+ pname_el = p.select_one('.player-name, .name, h3, h4')
575
+ pname = _clean(pname_el.get_text()) if pname_el else ''
576
+ if not pname:
577
+ continue
578
+ ppos_el = p.select_one('.position, .pos, .player-pos')
579
+ ppos = _clean(ppos_el.get_text()) if ppos_el else ''
580
+ team['squad'].append({'name': pname, 'position': ppos})
581
+
582
+ breadcrumb = soup.select_one('.breadcrumb, .breadcrumbs')
583
+ if breadcrumb:
584
+ for a in breadcrumb.select('a'):
585
+ lt = _clean(a.get_text())
586
+ if lt and lt.lower() != 'trang chủ':
587
+ team['league'] = lt
588
+ break
589
+
590
+ return team
591
+
592
+
593
+ @app.get("/api/team/{path:path}")
594
+ async def api_team_stats(path: str):
595
+ data = _scrape_team_page(path)
596
+ if data and data.get('found'):
597
+ return JSONResponse(data)
598
+ return JSONResponse({"found": False, "error": "Không tìm thấy đội bóng"})
599
+
600
+
601
+ def _render_team_page(path: str):
602
+ """Render full HTML team stats page."""
603
+ data = _scrape_team_page(path)
604
+ if not data or not data.get('found'):
605
+ from fastapi.responses import RedirectResponse
606
+ return RedirectResponse(f"https://bongda.com.vn/doi-bong/{path}", status_code=302)
607
+
608
+ team = data
609
+ name = _clean(team.get('name') or 'Đội bóng')
610
+ logo = _clean(team.get('logo') or '')
611
+ league = _clean(team.get('league') or '')
612
+ country = _clean(team.get('country') or '')
613
+
614
+ recent_html = ''
615
+ if team.get('recent'):
616
+ for m in team['recent'][:10]:
617
+ eid = _clean(m.get('event_id', ''))
618
+ murl = _clean(m.get('url', ''))
619
+ sc = _clean(m.get('score', 'VS'))
620
+ mt = _clean(m.get('time', ''))
621
+ home = _clean(m.get('home', ''))
622
+ away = _clean(m.get('away', ''))
623
+ onclick = f'onclick="openMatch(\'{eid}\',\'{murl}\')"' if eid else ''
624
+ recent_html += f'<div class="tm-match" {onclick} style="cursor:pointer">'
625
+ recent_html += f'<span class="tm-match-time">{mt}</span>'
626
+ recent_html += f'<span class="tm-match-teams">{home} <span class="tm-score">{sc}</span> {away}</span>'
627
+ recent_html += '</div>'
628
+
629
+ standings_html = ''
630
+ if team.get('standings'):
631
+ standings_html = '<table class="tm-standings"><thead><tr><th>#</th><th>Đội</th><th>Trận</th><th>HS</th><th>Điểm</th></tr></thead><tbody>'
632
+ for row in team['standings']:
633
+ pos = _clean(row.get('pos', ''))
634
+ tname = _clean(row.get('team', ''))
635
+ played = _clean(row.get('played', ''))
636
+ gd = _clean(row.get('gd', ''))
637
+ pts = _clean(row.get('pts', ''))
638
+ highlight = ' class="tm-highlight"' if name and tname and name.lower()[:6] in tname.lower()[:6] else ''
639
+ standings_html += f'<tr{highlight}><td>{pos}</td><td>{tname}</td><td>{played}</td><td>{gd}</td><td>{pts}</td></tr>'
640
+ standings_html += '</tbody></table>'
641
+
642
+ squad_html = ''
643
+ if team.get('squad'):
644
+ for p in team['squad'][:30]:
645
+ pnm = _clean(p.get('name', ''))
646
+ pps = _clean(p.get('position', ''))
647
+ if not pnm:
648
+ continue
649
+ squad_html += f'<div class="tm-player"><span class="tm-player-name">{pnm}</span><span class="tm-player-pos">{pps}</span></div>'
650
+
651
+ logo_html = f'<img src="{logo}" class="tm-logo" alt="{name}" onerror="this.style.display=\'none\'">' if logo else ''
652
+
653
+ sections_html = ''
654
+ sections_html += '<div class="tm-section"><div class="tm-section-title">📋 Trận đấu gần nhất</div>'
655
+ sections_html += recent_html or '<div class="tm-nodata">Không có dữ liệu trận đấu</div>'
656
+ sections_html += '</div>'
657
+
658
+ if standings_html:
659
+ sections_html += '<div class="tm-section"><div class="tm-section-title">🏆 Bảng xếp hạng</div>' + standings_html + '</div>'
660
+
661
+ if squad_html:
662
+ sections_html += '<div class="tm-section"><div class="tm-section-title">👥 Đội hình</div><div>' + squad_html + '</div></div>'
663
+
664
+ html = f'''<!DOCTYPE html>
665
+ <html lang="vi">
666
+ <head>
667
+ <meta charset="utf-8">
668
+ <meta name="viewport" content="width=device-width,initial-scale=1">
669
+ <title>⚽ {name} — Thống kê | VNEWS</title>
670
+ <meta property="og:title" content="⚽ {name} — Thống kê | VNEWS">
671
+ <style>
672
+ *{{margin:0;padding:0;box-sizing:border-box}}body{{background:#0d1117;color:#e0e0e0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}}
673
+ .tm-header{{background:linear-gradient(135deg,#1a2a1f,#0d1117);border-bottom:2px solid #2d8659;padding:16px;text-align:center}}
674
+ .tm-top{{display:flex;align-items:center;justify-content:center;gap:12px;flex-wrap:wrap}}
675
+ .tm-logo{{width:64px;height:64px;object-fit:contain;border-radius:50%;border:3px solid #2d8659;background:#111}}
676
+ .tm-name{{font-size:24px;font-weight:900;color:#fff}}
677
+ .tm-badge{{font-size:12px;color:#888;margin-top:4px}}
678
+ .tm-badge span{{background:#1a2a1f;color:#5cb87a;padding:2px 8px;border-radius:4px;margin:0 2px}}
679
+ .tm-nav{{background:#161b22;padding:8px 16px;display:flex;gap:8px;align-items:center;border-bottom:1px solid #21262d}}
680
+ .tm-nav a{{color:#5cb87a;text-decoration:none;font-size:13px}}
681
+ .tm-nav a:hover{{text-decoration:underline}}
682
+ .tm-container{{max-width:900px;margin:0 auto;padding:16px}}
683
+ .tm-section{{margin-bottom:20px}}
684
+ .tm-section-title{{font-size:14px;font-weight:800;color:#5cb87a;margin-bottom:8px;padding-bottom:6px;border-bottom:1px solid #21262d}}
685
+ .tm-match{{display:flex;align-items:center;gap:10px;padding:10px 12px;background:#161b22;border-radius:6px;margin-bottom:4px;font-size:13px;border:1px solid #21262d;transition:background .2s}}
686
+ .tm-match:hover{{background:#1c2128;border-color:#2d8659}}
687
+ .tm-match-time{{font-size:11px;color:#888;min-width:60px}}
688
+ .tm-match-teams{{flex:1;color:#e0e0e0}}
689
+ .tm-score{{color:#f0c040;font-weight:800}}
690
+ .tm-standings{{width:100%;border-collapse:collapse;font-size:13px}}
691
+ .tm-standings th{{background:#1a2a1f;color:#5cb87a;padding:8px;text-align:left;font-size:11px;border-bottom:1px solid #2d8659}}
692
+ .tm-standings td{{padding:8px;border-bottom:1px solid #21262d}}
693
+ .tm-standings tr:hover td{{background:#161b22}}
694
+ .tm-highlight td{{background:#1a2a1f;border-left:3px solid #5cb87a}}
695
+ .tm-player{{display:inline-flex;align-items:center;gap:6px;background:#161b22;padding:5px 10px;border-radius:5px;margin:3px;font-size:12px;border:1px solid #21262d}}
696
+ .tm-player-name{{color:#e0e0e0}}
697
+ .tm-player-pos{{color:#888;font-size:10px}}
698
+ .tm-back{{display:inline-block;margin-bottom:12px;color:#5cb87a;text-decoration:none;font-size:14px}}
699
+ .tm-back:hover{{text-decoration:underline}}
700
+ .tm-nodata{{text-align:center;color:#666;padding:20px;font-size:13px}}
701
+ </style>
702
+ </head>
703
+ <body>
704
+ <div class="tm-header"><div class="tm-top">{logo_html}<div><div class="tm-name">⚽ {name}</div><div class="tm-badge">{league and f"<span>🏆 {league}</span>"}{country and f"<span>📍 {country}</span>"}<span>📡 VNEWS</span></div></div></div></div>
705
+ <div class="tm-nav"><a href="/" class="tm-back">← Quay về VNEWS</a> <a href="https://bongda.com.vn/doi-bong/{path}" target="_blank">Xem trên Bongda.com.vn ↗</a></div>
706
+ <div class="tm-container">{sections_html}</div>
707
+ <script>function openMatch(id,url){{if(!id)return;window.location.href='/?match='+id+(url?'&url='+encodeURIComponent(url):'');}}</script>
708
+ </body></html>'''
709
+ return HTMLResponse(html)
710
+
711
 
712
  @app.get("/doi-bong/{path:path}")
713
+ async def page_team_stats(path: str):
714
+ return _render_team_page(path)
715
+
716
 
717
  @app.get("/giai-dau/{path:path}")
718
+ async def page_league_internal(path: str):
719
+ from fastapi.responses import RedirectResponse
720
  return RedirectResponse(f"https://bongda.com.vn/giai-dau/{path}", status_code=302)
721
 
722
+
723
  @app.get("/cau-thu/{path:path}")
724
  async def redirect_cau_thu(path: str):
725
+ from fastapi.responses import RedirectResponse
726
  return RedirectResponse(f"https://bongda.com.vn/cau-thu/{path}", status_code=302)
727
 
728
  def _bg():
 
733
  time.sleep(90)
734
  threading.Thread(target=_bg,daemon=True).start()
735
 
736
+ app.mount('/static',StaticFiles(directory=STATIC_DIR),name='vnews_static')