bep40 commited on
Commit
6e354ed
·
verified ·
1 Parent(s): bf2ca14

Upload app_v2_entry.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app_v2_entry.py +69 -31
app_v2_entry.py CHANGED
@@ -26,6 +26,7 @@ import asyncio
26
  HL_LEAGUES['friendly'] = {"path": "giai-khac/friendly", "name": "Giao hữu", "emoji": "🤝"}
27
 
28
  STATIC_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static')
 
29
  app.router.routes=[r for r in app.router.routes if not(getattr(r,'path',None)=='/' and hasattr(r,'methods') and 'GET' in getattr(r,'methods',set()))]
30
  app.routes[:]=[r for r in app.routes if not isinstance(r, Mount)]
31
  app.router.routes=[r for r in app.router.routes if not isinstance(r, Mount)]
@@ -328,7 +329,7 @@ def _search_all(topic,limit=36):
328
  if i<len(s) and s[i].get('url') and s[i]['url'] not in seen:seen.add(s[i]['url']);out.append(s[i])
329
  return out[:limit]
330
 
331
- for _path in ['/api/article', '/api/hot_topics', '/api/categories', '/api/storage_status', '/s']:
332
  app.router.routes=[r for r in app.router.routes if not(getattr(r,'path',None)==_path and 'GET' in getattr(r,'methods',set()))]
333
 
334
  _article_cache = {}
@@ -504,8 +505,44 @@ def _ht(topic:str=Query(...),page:int=Query(default=0)):
504
  def _cat():return JSONResponse([])
505
  @app.get('/api/storage_status')
506
  def _st():return JSONResponse({'persistent':os.path.isdir('/data') and os.access('/data',os.W_OK)})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
507
  @app.get('/s')
508
- 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>')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
509
 
510
  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
511
 
@@ -1075,7 +1112,7 @@ def _scrape_article_for_rewrite(url):
1075
 
1076
 
1077
  def _extract_key_points_rw(paragraphs, max_points=5):
1078
- """Extract key points from paragraphs - extracts ALL sentences, not just first one.
1079
 
1080
  Fixes: Original regex `^(.+?[.!?])\s` only captured first sentence per paragraph.
1081
  Now splits on all sentence boundaries and takes valid sentences until max_points.
@@ -1326,32 +1363,23 @@ async def _auto_fetch_short(post_id):
1326
  except: pass
1327
  return False
1328
 
1329
- async def _auto_rewrite_one(topic, slot_label, used_urls=None):
1330
  """Rewrite one topic: find articles, summarize, post to wall, trigger short.
1331
- used_urls: shared set to avoid duplicate articles across topics."""
 
1332
  from urllib.parse import quote as _q
1333
- items = _search_all(topic, limit=6)
 
1334
  # Skip URLs already used by another topic
1335
  if used_urls is not None:
1336
  filtered = [it for it in items if it.get('url') not in used_urls]
1337
  if filtered:
1338
  items = filtered
1339
- if not items:
1340
- # fallback: Google RSS
1341
- try:
1342
- r = req.get(f"https://news.google.com/rss/search?q={_q(topic)}&hl=vi&gl=VN&ceid=VN:vi",
1343
- headers={'User-Agent':'Mozilla/5.0'}, timeout=8)
1344
- r.encoding = 'utf-8'
1345
- soup = BeautifulSoup(r.text, 'xml')
1346
- for it in soup.find_all('item')[:5]:
1347
- t = _clean(it.find('title').get_text(' ',strip=True) if it.find('title') else '')
1348
- lk = it.find('link').get_text(strip=True) if it.find('link') else ''
1349
- if t and lk: items.append({'title':t,'url':lk,'via':'Google News'})
1350
- except: pass
1351
- if not items:
1352
  return False
1353
 
1354
- item = items[0] # best match
 
1355
  url = item.get('url', '')
1356
  title = item.get('title', topic)
1357
  if url and used_urls is not None:
@@ -1407,7 +1435,7 @@ async def _auto_rewrite_one(topic, slot_label, used_urls=None):
1407
  return True
1408
 
1409
  async def _do_scheduled_run(slot_label):
1410
- """Main scheduled run: exactly 3 posts from 3 different HOT topics, no duplicates."""
1411
  print(f"[auto] Starting scheduled rewrite for {slot_label}")
1412
 
1413
  # Get top hot topics, skip duplicates
@@ -1440,14 +1468,25 @@ async def _do_scheduled_run(slot_label):
1440
  _used_urls = set()
1441
  results = []
1442
 
 
1443
  for jt in job_topics:
1444
- try:
1445
- ok = await asyncio.wait_for(_auto_rewrite_one(jt, slot_label, _used_urls), timeout=120)
1446
- results.append((jt, ok))
1447
- except Exception as e:
1448
- print(f"[auto] Error on '{jt}': {e}")
1449
- results.append((jt, False))
1450
- await asyncio.sleep(2)
 
 
 
 
 
 
 
 
 
 
1451
 
1452
  # Log
1453
  from datetime import datetime, timezone, timedelta
@@ -1457,11 +1496,10 @@ async def _do_scheduled_run(slot_label):
1457
  if today_str not in log: log[today_str] = {}
1458
  log[today_str][slot_label] = {
1459
  'time': datetime.now(VN_TZ_SCHED).strftime('%H:%M:%S'),
1460
- 'count': sum(1 for _, ok in results if ok),
1461
- 'total': len(results),
1462
  }
1463
  _save_auto_log(log)
1464
- print(f"[auto] Done {slot_label}: {sum(1 for _, ok in results if ok)}/{len(results)} posts")
1465
 
1466
  def _scheduler_loop():
1467
  """Check every 60s; trigger at 7:00, 13:00, 19:00 VN time.
 
26
  HL_LEAGUES['friendly'] = {"path": "giai-khac/friendly", "name": "Giao hữu", "emoji": "🤝"}
27
 
28
  STATIC_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static')
29
+ SPACE = "https://bep40-vnews.hf.space" # SEO URL base for share links
30
  app.router.routes=[r for r in app.router.routes if not(getattr(r,'path',None)=='/' and hasattr(r,'methods') and 'GET' in getattr(r,'methods',set()))]
31
  app.routes[:]=[r for r in app.routes if not isinstance(r, Mount)]
32
  app.router.routes=[r for r in app.router.routes if not isinstance(r, Mount)]
 
329
  if i<len(s) and s[i].get('url') and s[i]['url'] not in seen:seen.add(s[i]['url']);out.append(s[i])
330
  return out[:limit]
331
 
332
+ for _path in ['/api/article', '/api/hot_topics', '/api/categories', '/api/storage_status']:
333
  app.router.routes=[r for r in app.router.routes if not(getattr(r,'path',None)==_path and 'GET' in getattr(r,'methods',set()))]
334
 
335
  _article_cache = {}
 
505
  def _cat():return JSONResponse([])
506
  @app.get('/api/storage_status')
507
  def _st():return JSONResponse({'persistent':os.path.isdir('/data') and os.access('/data',os.W_OK)})
508
+ @app.get('/s/{slug}')
509
+ async def _sh_slug(slug: str, url: str = '', title: str = '', img: str = ''):
510
+ """SEO-friendly share endpoint with slug in URL path."""
511
+ safe_title = _clean(title) if title else 'VNEWS - Tin tức'
512
+ safe_img = _clean(img) if img else ''
513
+ safe_url = _clean(url) if url else '/'
514
+ return HTMLResponse(f'''<!DOCTYPE html>
515
+ <html lang="vi">
516
+ <head>
517
+ <meta charset="utf-8">
518
+ <meta name="viewport" content="width=device-width,initial-scale=1">
519
+ <title>{safe_title}</title>
520
+ <meta property="og:title" content="{safe_title}">
521
+ <meta property="og:image" content="{safe_img}">
522
+ <meta property="og:description" content="Tin tức tóm tắt, AI rewrite, World Cup 2026">
523
+ <meta property="og:url" content="{SPACE}/s/{slug}">
524
+ <link rel="canonical" href="{SPACE}/s/{slug}">
525
+ <meta http-equiv="refresh" content="0;url={safe_url}">
526
+ </head><body></body></html>''')
527
+
528
  @app.get('/s')
529
+ async def _sh(url:str='',title:str='',img:str=''):
530
+ safe_title = _clean(title) if title else 'VNEWS - Tin tức'
531
+ safe_img = _clean(img) if img else ''
532
+ safe_url = _clean(url) if url else '/'
533
+ return HTMLResponse(f'''<!DOCTYPE html>
534
+ <html lang="vi">
535
+ <head>
536
+ <meta charset="utf-8">
537
+ <meta name="viewport" content="width=device-width,initial-scale=1">
538
+ <title>{safe_title}</title>
539
+ <meta property="og:title" content="{safe_title}">
540
+ <meta property="og:image" content="{safe_img}">
541
+ <meta property="og:description" content="Tin tức tóm tắt, AI rewrite, World Cup 2026">
542
+ <meta property="og:url" content="{SPACE}/s">
543
+ <link rel="canonical" href="{SPACE}/s">
544
+ <meta http-equiv="refresh" content="0;url={safe_url}">
545
+ </head><body></body></html>''')
546
 
547
  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
548
 
 
1112
 
1113
 
1114
  def _extract_key_points_rw(paragraphs, max_points=5):
1115
+ r"""Extract key points from paragraphs - extracts ALL sentences, not just first one.
1116
 
1117
  Fixes: Original regex `^(.+?[.!?])\s` only captured first sentence per paragraph.
1118
  Now splits on all sentence boundaries and takes valid sentences until max_points.
 
1363
  except: pass
1364
  return False
1365
 
1366
+ async def _auto_rewrite_one(topic, slot_label, used_urls=None, post_index=0):
1367
  """Rewrite one topic: find articles, summarize, post to wall, trigger short.
1368
+ used_urls: shared set to avoid duplicate articles across topics.
1369
+ post_index: 0-based index to create multiple posts per topic (0,1,2 = up to 3 posts)."""
1370
  from urllib.parse import quote as _q
1371
+ # Get MORE items to support 1-3 posts per topic
1372
+ items = _search_all(topic, limit=12)
1373
  # Skip URLs already used by another topic
1374
  if used_urls is not None:
1375
  filtered = [it for it in items if it.get('url') not in used_urls]
1376
  if filtered:
1377
  items = filtered
1378
+ if not items or post_index >= len(items):
 
 
 
 
 
 
 
 
 
 
 
 
1379
  return False
1380
 
1381
+ # Get article at post_index (0,1,2 for multiple posts)
1382
+ item = items[post_index] # post_index allows multiple articles per topic
1383
  url = item.get('url', '')
1384
  title = item.get('title', topic)
1385
  if url and used_urls is not None:
 
1435
  return True
1436
 
1437
  async def _do_scheduled_run(slot_label):
1438
+ """Main scheduled run: 1-3 posts from 3 different HOT topics (3-9 total), no duplicates."""
1439
  print(f"[auto] Starting scheduled rewrite for {slot_label}")
1440
 
1441
  # Get top hot topics, skip duplicates
 
1468
  _used_urls = set()
1469
  results = []
1470
 
1471
+ # Process each topic, create 1-3 posts per topic
1472
  for jt in job_topics:
1473
+ for post_idx in range(3): # Try up to 3 posts per topic
1474
+ try:
1475
+ ok = await asyncio.wait_for(_auto_rewrite_one(jt, slot_label, _used_urls, post_idx), timeout=120)
1476
+ if ok:
1477
+ results.append((jt, post_idx, True))
1478
+ print(f"[auto] Created post {post_idx+1} for '{jt}'")
1479
+ else:
1480
+ # No more articles for this topic
1481
+ break
1482
+ except Exception as e:
1483
+ print(f"[auto] Error on '{jt}' post {post_idx}: {e}")
1484
+ results.append((jt, post_idx, False))
1485
+ await asyncio.sleep(1) # Small delay between posts
1486
+
1487
+ # Ensure at least 3 posts total (fallback if needed)
1488
+ successful_posts = sum(1 for _, _, ok in results if ok)
1489
+ print(f"[auto] Done {slot_label}: {successful_posts} posts created")
1490
 
1491
  # Log
1492
  from datetime import datetime, timezone, timedelta
 
1496
  if today_str not in log: log[today_str] = {}
1497
  log[today_str][slot_label] = {
1498
  'time': datetime.now(VN_TZ_SCHED).strftime('%H:%M:%S'),
1499
+ 'count': successful_posts,
1500
+ 'total': len(job_topics),
1501
  }
1502
  _save_auto_log(log)
 
1503
 
1504
  def _scheduler_loop():
1505
  """Check every 60s; trigger at 7:00, 13:00, 19:00 VN time.