bep40 commited on
Commit
1acc897
·
verified ·
1 Parent(s): b44cf8f

Upload app_v2_entry.py

Browse files
Files changed (1) hide show
  1. app_v2_entry.py +158 -152
app_v2_entry.py CHANGED
@@ -1,4 +1,4 @@
1
- """VNEWS v2 Entry Point - with AI endpoints + fast bongda proxy"""
2
  import sys, os
3
  from main import app, HEADERS, BONGDA_HEADERS, fetch_bongda_api, HL_LEAGUES
4
 
@@ -12,10 +12,10 @@ from fastapi.staticfiles import StaticFiles
12
  from starlette.routing import Mount
13
  from fastapi import Query, Request
14
  import requests as req
15
- from urllib.parse import quote, urlparse
16
  from bs4 import BeautifulSoup
17
  import re, html as html_lib, json, threading, time
18
  from concurrent.futures import ThreadPoolExecutor, as_completed
 
19
 
20
  HL_LEAGUES['friendly'] = {"path": "giai-khac/friendly", "name": "Giao hữu", "emoji": "🤝"}
21
 
@@ -25,7 +25,162 @@ app.routes[:]=[r for r in app.routes if not isinstance(r, Mount)]
25
  app.router.routes=[r for r in app.router.routes if not isinstance(r, Mount)]
26
 
27
  def _clean(s): return re.sub(r"\s+"," ",html_lib.unescape(str(s or""))).strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  _STOP=set('và của các những một được trong với cho tại sau trước khi không người việt nam hôm nay mới nhất nóng tin tức cập nhật theo từ đến là có thì này đã để'.split())
 
29
  def _has_kw(topic,title):
30
  tl=topic.lower();tt=(title or'').lower()
31
  if tl in tt:return True
@@ -33,7 +188,6 @@ def _has_kw(topic,title):
33
  if not words:return True
34
  return any(w in tt for w in words)
35
 
36
- # Search functions...
37
  def _s_vnexpress(topic,limit=8):
38
  items=[]
39
  try:
@@ -236,149 +390,6 @@ def _st():return JSONResponse({'persistent':os.path.isdir('/data') and os.access
236
  @app.get('/s')
237
  async def _sh(url:str='',title:str='',img:str=''):return HTMLResponse(f'<!DOCTYPE html><html><head><meta property="og:title" content="{_clean(title)}"><meta property="og:image" content="{_clean(img)}"><meta http-equiv="refresh" content="0;url={_clean(url) or "/"}"></head><body></body></html>')
238
 
239
- # === BONGDA PROXY (v5-fast) ===
240
- @app.get('/api/proxy/bongda')
241
- def proxy_bongda(event_id: int = Query(default=None)):
242
- """Fast server-side proxy for bongda.com.vn match detail."""
243
- if event_id is None:
244
- return JSONResponse({'error': 'event_id required'}, status_code=400)
245
-
246
- result = {"event_id": event_id, "found": False, "sections": []}
247
-
248
- try:
249
- url = f"https://bongda.com.vn/tran-dau/{event_id}/centre/"
250
- headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Accept": "text/html", "Referer": "https://bongda.com.vn/"}
251
- resp = req.get(url, headers=headers, timeout=15, allow_redirects=True)
252
-
253
- if resp.status_code != 200:
254
- return JSONResponse(result)
255
-
256
- soup = BeautifulSoup(resp.text, 'html.parser')
257
- info = {}
258
-
259
- tel = soup.select_one('.teams')
260
- if tel:
261
- he = tel.select_one('.team.home')
262
- if he:
263
- ne = he.select_one('p:not(.logo)') or he.find('p')
264
- if ne: info['home_team'] = _clean(ne.get_text())
265
- lo = he.select_one('img')
266
- if lo: info['home_logo'] = lo.get('src', '')
267
- ae = tel.select_one('.team.away')
268
- if ae:
269
- ne = ae.select_one('p:not(.logo)') or ae.find('p')
270
- if ne: info['away_team'] = _clean(ne.get_text())
271
- lo = ae.select_one('img')
272
- if lo: info['away_logo'] = lo.get('src', '')
273
- sc = tel.select_one('.score')
274
- if sc:
275
- parts = [_clean(p.get_text()) for p in sc.select('p')]
276
- if len(parts) >= 2: info['score'] = f"{parts[0]} - {parts[1]}"
277
- lb = sc.select_one('.label')
278
- if lb: info['status_label'] = _clean(lb.get_text())
279
-
280
- if info.get('home_team') and info.get('away_team'):
281
- result['info'] = info
282
- result['found'] = True
283
- result['sections'].append('info')
284
-
285
- events = []
286
- ev_div = soup.select_one('.events')
287
- if ev_div:
288
- period = ''
289
- for child in ev_div.children:
290
- if not hasattr(child, 'name') or not child.name: continue
291
- cls = ' '.join(child.get('class', []))
292
- if 'period' in cls:
293
- h2 = child.find('h2')
294
- if h2: period = _clean(h2.get_text())
295
- for ev in child.children:
296
- if not hasattr(ev, 'name') or not ev.name: continue
297
- ev_cls = ' '.join(ev.get('class', []))
298
- if 'event' not in ev_cls: continue
299
-
300
- ev_data = {
301
- 'team': 'home' if 'home' in ev_cls else 'away',
302
- 'period': period,
303
- 'type': 'unknown',
304
- 'time': '',
305
- 'players': '',
306
- }
307
-
308
- type_el = ev.select_one('.event-type')
309
- if type_el:
310
- if type_el.select_one('[class*="redcard"]'): ev_data['type'] = 'redcard'
311
- elif type_el.select_one('[class*="yellowcard"]'): ev_data['type'] = 'yellowcard'
312
- elif type_el.select_one('[class*="goal"]'): ev_data['type'] = 'goal'
313
- elif type_el.select_one('[class*="substitution"]'): ev_data['type'] = 'substitution'
314
-
315
- players_el = ev.select_one('.players')
316
- if players_el:
317
- time_el = players_el.select_one('.event-time')
318
- if time_el: ev_data['time'] = _clean(time_el.get_text())
319
- ev_data['players'] = _clean(players_el.get_text(' ', strip=True))
320
-
321
- events.append(ev_data)
322
-
323
- if events:
324
- result['events'] = events
325
- result['sections'].append('events')
326
-
327
- pred = soup.select_one('.prediction-card')
328
- if pred:
329
- team_info = pred.select_one('.team-info')
330
- if team_info:
331
- teams = team_info.select('.team')
332
- if len(teams) >= 2:
333
- pred_data = {}
334
- pred_data['home_name'] = _clean(teams[0].select_one('.team-name').get_text()) if teams[0].select_one('.team-name') else ''
335
- pred_data['away_name'] = _clean(teams[1].select_one('.team-name').get_text()) if teams[1].select_one('.team-name') else ''
336
- divider = team_info.select_one('.divider')
337
- if divider: pred_data['result'] = _clean(divider.get_text())
338
- vc = pred.select_one('.vote-count')
339
- if vc: pred_data['vote_count'] = _clean(vc.get_text())
340
- result['prediction'] = pred_data
341
-
342
- recent = []
343
- ml = soup.select_one('.matches-list')
344
- if ml:
345
- for item in ml.select('.match-detail, .match-item, li'):
346
- de = item.select_one('.date, .time')
347
- le = item.select_one('.league')
348
- he_item = item.select_one('.home, .team-home')
349
- ae_item = item.select_one('.away, .team-away')
350
- se = item.select_one('.score, .result')
351
- if he_item or ae_item:
352
- recent.append({
353
- 'date': _clean(de.get_text()) if de else '', 'league': _clean(le.get_text()) if le else '',
354
- 'home': _clean(he_item.get_text()) if he_item else '', 'away': _clean(ae_item.get_text()) if ae_item else '',
355
- 'score': _clean(se.get_text()) if se else 'vs',
356
- })
357
- if recent:
358
- result['recent_matches'] = recent
359
- result['sections'].append('recent')
360
-
361
- try:
362
- api_h = {"User-Agent": "Mozilla/5.0", "Accept": "application/json", "X-Requested-With": "XMLHttpRequest", "Referer": "https://bongda.com.vn/"}
363
- ar = req.get(f"https://bongda.com.vn/api/fixtures/h2h-stats?event_id={event_id}", headers=api_h, timeout=10)
364
- if ar.status_code == 200:
365
- ad = ar.json()
366
- if ad.get('status') == 'success' and ad.get('html'):
367
- asp = BeautifulSoup(ad['html'], 'html.parser')
368
- ast = {}
369
- for row in asp.select('li, tr'):
370
- cells = row.select('td, span, p')
371
- if len(cells) >= 3:
372
- lb = _clean(cells[0].get_text())
373
- if lb: ast[lb] = {'home': _clean(cells[1].get_text()), 'away': _clean(cells[2].get_text())}
374
- if ast: result['h2h_stats_parsed'] = ast; result['sections'].append('h2h_stats')
375
- except: pass
376
-
377
- return JSONResponse(result)
378
- except Exception as e:
379
- return JSONResponse({"event_id": event_id, "found": False, "error": str(e)})
380
-
381
- # === WC2026 endpoints ===
382
  from wc2026_scraper import(scrape_summary,scrape_fixtures,scrape_standings,scrape_stats,scrape_wc_news,scrape_road_to_wc,get_wc2026_all,scrape_history,scrape_h2h,scrape_lineups,scrape_match_detail)
383
  @app.get('/api/wc2026')
384
  def _w():return JSONResponse(get_wc2026_all())
@@ -444,9 +455,4 @@ def _bg():
444
  time.sleep(90)
445
  threading.Thread(target=_bg,daemon=True).start()
446
 
447
- app.mount('/static',StaticFiles(directory=STATIC_DIR),name='vnews_static')
448
-
449
- # _run.py helper
450
- RUN_PY_CONTENT = 'from app_v2_entry import app'
451
- with open('/app/_run.py', 'w') as f:
452
- f.write(RUN_PY_CONTENT)
 
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
 
 
12
  from starlette.routing import Mount
13
  from fastapi import Query, Request
14
  import requests as req
 
15
  from bs4 import BeautifulSoup
16
  import re, html as html_lib, json, threading, time
17
  from concurrent.futures import ThreadPoolExecutor, as_completed
18
+ from urllib.parse import quote
19
 
20
  HL_LEAGUES['friendly'] = {"path": "giai-khac/friendly", "name": "Giao hữu", "emoji": "🤝"}
21
 
 
25
  app.router.routes=[r for r in app.router.routes if not isinstance(r, Mount)]
26
 
27
  def _clean(s): return re.sub(r"\s+"," ",html_lib.unescape(str(s or""))).strip()
28
+
29
+ # Cache for match details (5 min TTL)
30
+ _match_cache = {}
31
+
32
+ # === FAST BONGDA PROXY ENDPOINT ===
33
+ def _get_match_detail(event_id, slug=None):
34
+ """Internal function to scrape match detail from bongda.com.vn"""
35
+ headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Accept": "text/html", "Referer": "https://bongda.com.vn/"}
36
+
37
+ if slug:
38
+ url = f"https://bongda.com.vn/tran-dau/{event_id}/centre/{slug}"
39
+ else:
40
+ url = f"https://bongda.com.vn/tran-dau/{event_id}"
41
+
42
+ resp = req.get(url, headers=headers, timeout=15, allow_redirects=True)
43
+ if resp.status_code != 200:
44
+ return None
45
+
46
+ soup = BeautifulSoup(resp.text, 'html.parser')
47
+ result = {"event_id": event_id, "found": False, "sections": []}
48
+ info = {}
49
+
50
+ tel = soup.select_one('.teams')
51
+ if tel:
52
+ he = tel.select_one('.team.home')
53
+ if he:
54
+ p_tags = [p for p in he.select('p') if not p.get('class') or 'logo' not in p.get('class', [])]
55
+ if p_tags: info['home_team'] = _clean(p_tags[0].get_text())
56
+ lo = he.select_one('img')
57
+ if lo: info['home_logo'] = lo.get('src', '')
58
+ ae = tel.select_one('.team.away')
59
+ if ae:
60
+ p_tags = ae.select('p')
61
+ team_ps = [p for p in p_tags if not p.get('class') or 'logo' not in p.get('class', [])]
62
+ if team_ps: info['away_team'] = _clean(team_ps[-1].get_text())
63
+ lo = ae.select_one('img')
64
+ if lo: info['away_logo'] = lo.get('src', '')
65
+ sc = tel.select_one('.score')
66
+ if sc:
67
+ parts = [_clean(p.get_text()) for p in sc.select('p')]
68
+ if len(parts) >= 2: info['score'] = f"{parts[0]} - {parts[1]}"
69
+ lb = sc.select_one('.label')
70
+ if lb: info['status_label'] = _clean(lb.get_text())
71
+
72
+ if info.get('home_team') and info.get('away_team'):
73
+ result['info'] = info
74
+ result['found'] = True
75
+ result['sections'].append('info')
76
+
77
+ events = []
78
+ for ev in soup.select('.events .period .event'):
79
+ ev_cls = ' '.join(ev.get('class', []))
80
+ ev_data = {'team': 'home' if 'home' in ev_cls else 'away', 'period': '', 'type': 'unknown', 'time': '', 'players': ''}
81
+
82
+ parent = ev.parent
83
+ if parent:
84
+ h2 = parent.find('h2')
85
+ if h2: ev_data['period'] = _clean(h2.get_text())
86
+
87
+ if ev.select_one('[class*="goal"]'): ev_data['type'] = 'goal'
88
+ elif ev.select_one('[class*="redcard"]'): ev_data['type'] = 'redcard'
89
+ elif ev.select_one('[class*="yellowcard"]'): ev_data['type'] = 'yellowcard'
90
+ elif ev.select_one('[class*="substitution"]'): ev_data['type'] = 'substitution'
91
+
92
+ players_el = ev.select_one('.players')
93
+ if players_el:
94
+ pl_text = _clean(players_el.get_text(' ', strip=True))
95
+ m = re.match(r"(\d+)'(.*)", pl_text)
96
+ if m:
97
+ ev_data['time'] = f"{m.group(1)}'"
98
+ ev_data['players'] = m.group(2)
99
+ else:
100
+ ev_data['players'] = pl_text
101
+ events.append(ev_data)
102
+
103
+ if events:
104
+ result['events'] = events
105
+ result['sections'].append('events')
106
+
107
+ pred = soup.select_one('.prediction-card')
108
+ if pred:
109
+ team_info = pred.select_one('.team-info')
110
+ if team_info:
111
+ teams = team_info.select('.team')
112
+ pred_data = {}
113
+ if len(teams) >= 2:
114
+ pred_data['home_name'] = _clean(teams[0].select_one('.team-name').get_text()) if teams[0].select_one('.team-name') else ''
115
+ pred_data['away_name'] = _clean(teams[1].select_one('.team-name').get_text()) if teams[1].select_one('.team-name') else ''
116
+ divider = team_info.select_one('.divider')
117
+ if divider: pred_data['result'] = _clean(divider.get_text())
118
+ vc = pred.select_one('.vote-count')
119
+ if vc: pred_data['vote_count'] = _clean(vc.get_text())
120
+ result['prediction'] = pred_data
121
+
122
+ recent = []
123
+ ml = soup.select_one('.matches-list')
124
+ if ml:
125
+ for item in ml.select('.match-detail, .match-item, li'):
126
+ de = item.select_one('.date, .time')
127
+ le = item.select_one('.league')
128
+ he_item = item.select_one('.home, .team-home')
129
+ ae_item = item.select_one('.away, .team-away')
130
+ se = item.select_one('.score, .result')
131
+ if he_item or ae_item:
132
+ recent.append({'date': _clean(de.get_text()) if de else '', 'league': _clean(le.get_text()) if le else '', 'home': _clean(he_item.get_text()) if he_item else '', 'away': _clean(ae_item.get_text()) if ae_item else '', 'score': _clean(se.get_text()) if se else 'vs'})
133
+ if recent:
134
+ result['recent_matches'] = recent
135
+ result['sections'].append('recent')
136
+
137
+ try:
138
+ api_h = {"User-Agent": "Mozilla/5.0", "Accept": "application/json", "X-Requested-With": "XMLHttpRequest", "Referer": "https://bongda.com.vn/"}
139
+ ar = req.get(f"https://bongda.com.vn/api/fixtures/h2h-stats?event_id={event_id}", headers=api_h, timeout=10)
140
+ if ar.status_code == 200:
141
+ ad = ar.json()
142
+ if ad.get('status') == 'success' and ad.get('html'):
143
+ asp = BeautifulSoup(ad['html'], 'html.parser')
144
+ ast = {}
145
+ for row in asp.select('li, tr'):
146
+ cells = row.select('td, span, p')
147
+ if len(cells) >= 3:
148
+ lb = _clean(cells[0].get_text())
149
+ if lb: ast[lb] = {'home': _clean(cells[1].get_text()), 'away': _clean(cells[2].get_text())}
150
+ if ast:
151
+ result['h2h_stats_parsed'] = ast
152
+ result['sections'].append('h2h_stats')
153
+ except: pass
154
+
155
+ return result
156
+
157
+ @app.get('/api/proxy/bongda')
158
+ def proxy_bongda(event_id: int = Query(default=None), slug: str = Query(default=None)):
159
+ if event_id is None:
160
+ return JSONResponse({'error': 'event_id required'}, status_code=400)
161
+
162
+ cache_key = f"{event_id}_{slug}"
163
+ now = time.time()
164
+ cached = _match_cache.get(cache_key)
165
+ if cached and now - cached.get('_ts', 0) < 300:
166
+ return JSONResponse(cached)
167
+
168
+ try:
169
+ result = _get_match_detail(event_id, slug)
170
+ if result:
171
+ result['_ts'] = now
172
+ _match_cache[cache_key] = result
173
+ return JSONResponse(result)
174
+ except Exception as e:
175
+ err = {"event_id": event_id, "found": False, "error": str(e), "_ts": now}
176
+ _match_cache[cache_key] = err
177
+ return JSONResponse(err)
178
+
179
+ return JSONResponse({"event_id": event_id, "found": False})
180
+
181
+ # === Rest of endpoints (existing) ===
182
  _STOP=set('và của các những một được trong với cho tại sau trước khi không người việt nam hôm nay mới nhất nóng tin tức cập nhật theo từ đến là có thì này đã để'.split())
183
+
184
  def _has_kw(topic,title):
185
  tl=topic.lower();tt=(title or'').lower()
186
  if tl in tt:return True
 
188
  if not words:return True
189
  return any(w in tt for w in words)
190
 
 
191
  def _s_vnexpress(topic,limit=8):
192
  items=[]
193
  try:
 
390
  @app.get('/s')
391
  async def _sh(url:str='',title:str='',img:str=''):return HTMLResponse(f'<!DOCTYPE html><html><head><meta property="og:title" content="{_clean(title)}"><meta property="og:image" content="{_clean(img)}"><meta http-equiv="refresh" content="0;url={_clean(url) or "/"}"></head><body></body></html>')
392
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
393
  from wc2026_scraper import(scrape_summary,scrape_fixtures,scrape_standings,scrape_stats,scrape_wc_news,scrape_road_to_wc,get_wc2026_all,scrape_history,scrape_h2h,scrape_lineups,scrape_match_detail)
394
  @app.get('/api/wc2026')
395
  def _w():return JSONResponse(get_wc2026_all())
 
455
  time.sleep(90)
456
  threading.Thread(target=_bg,daemon=True).start()
457
 
458
+ app.mount('/static',StaticFiles(directory=STATIC_DIR),name='vnews_static')