bep40 commited on
Commit
6ba9db9
·
verified ·
1 Parent(s): f35afce

Auto scheduler: rewrite AI + short at 7/13/19 VN time from hot topics + latest articles

Browse files
Files changed (1) hide show
  1. app_v2_entry.py +202 -1
app_v2_entry.py CHANGED
@@ -1282,4 +1282,205 @@ def _bg():
1282
  time.sleep(90)
1283
  threading.Thread(target=_bg,daemon=True).start()
1284
 
1285
- app.mount('/static',StaticFiles(directory=STATIC_DIR),name='vnews_static')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1282
  time.sleep(90)
1283
  threading.Thread(target=_bg,daemon=True).start()
1284
 
1285
+ # ===== AUTO SCHEDULER: rewrite AI + short at 7/13/19 VN time =====
1286
+ _AUTO_SCHEDULE_TIMES = [(7, '07:00'), (13, '13:00'), (19, '19:00')]
1287
+ _AUTO_LOG = os.path.join(DATA_DIR, 'auto_rewrite_log.json')
1288
+
1289
+ def _load_auto_log():
1290
+ try:
1291
+ if os.path.exists(_AUTO_LOG):
1292
+ with open(_AUTO_LOG, 'r') as f:
1293
+ return json.load(f)
1294
+ except: pass
1295
+ return {}
1296
+
1297
+ def _save_auto_log(log):
1298
+ try:
1299
+ tmp = _AUTO_LOG + '.tmp'
1300
+ with open(tmp, 'w') as f:
1301
+ json.dump(log, f)
1302
+ os.replace(tmp, _AUTO_LOG)
1303
+ except: pass
1304
+
1305
+ async def _auto_fetch_short(post_id):
1306
+ """Try to auto-generate a short for a post."""
1307
+ try:
1308
+ import httpx
1309
+ async with httpx.AsyncClient(timeout=180) as cl:
1310
+ r = await cl.post(
1311
+ f"http://localhost:7860/api/ai/short/{post_id}",
1312
+ json={"voice":"vi-VN-HoaiMyNeural","emotion":"neutral","speed":1.2},
1313
+ headers={"Content-Type":"application/json"}
1314
+ )
1315
+ if r.status_code < 300:
1316
+ sj = r.json()
1317
+ if sj.get('video'):
1318
+ posts = _load_wall_posts()
1319
+ for p in posts:
1320
+ if p.get('id') == post_id:
1321
+ p['video'] = sj['video']
1322
+ break
1323
+ _save_wall_posts(posts)
1324
+ return True
1325
+ except: pass
1326
+ return False
1327
+
1328
+ async def _auto_rewrite_one(topic, slot_label):
1329
+ """Rewrite one topic: find articles, summarize, post to wall, trigger short."""
1330
+ from urllib.parse import quote as _q
1331
+ items = _search_all(topic, limit=6)
1332
+ if not items:
1333
+ # fallback: Google RSS
1334
+ try:
1335
+ r = req.get(f"https://news.google.com/rss/search?q={_q(topic)}&hl=vi&gl=VN&ceid=VN:vi",
1336
+ headers={'User-Agent':'Mozilla/5.0'}, timeout=8)
1337
+ r.encoding = 'utf-8'
1338
+ soup = BeautifulSoup(r.text, 'xml')
1339
+ for it in soup.find_all('item')[:5]:
1340
+ t = _clean(it.find('title').get_text(' ',strip=True) if it.find('title') else '')
1341
+ lk = it.find('link').get_text(strip=True) if it.find('link') else ''
1342
+ if t and lk: items.append({'title':t,'url':lk,'via':'Google News'})
1343
+ except: pass
1344
+ if not items:
1345
+ return False
1346
+
1347
+ item = items[0] # best match
1348
+ url = item.get('url', '')
1349
+ title = item.get('title', topic)
1350
+ if not url.startswith('http'):
1351
+ return False
1352
+
1353
+ data = _scrape_article_for_rewrite(url)
1354
+ if not data or not data.get('paragraphs'):
1355
+ return False
1356
+
1357
+ raw_text = '\n'.join(data['paragraphs'])
1358
+ ai_text = None
1359
+
1360
+ # Try AI generation
1361
+ try:
1362
+ import ai_ext
1363
+ prompt = f"Tóm tắt tin tức (tự động {slot_label}):\nTiêu đề: {data['title']}\n{raw_text[:10000]}\n\n4-6 ý chính dạng bullet. Cuối ghi nguồn."
1364
+ ai_text = await ai_ext.qwen_generate(prompt, max_tokens=1000)
1365
+ except: pass
1366
+
1367
+ if not ai_text or len(ai_text) < 80:
1368
+ pts = data['paragraphs'][:6]
1369
+ ai_text = '\n\n'.join([f"• {p[:300]}" for p in pts])
1370
+ via = item.get('via', '') or urlparse(url).netloc.replace('www.', '')
1371
+ ai_text += f"\n\nNguồn tham khảo: {via}"
1372
+
1373
+ # Build slides
1374
+ images = data.get('images', [])
1375
+ pts = data['paragraphs'][:10]
1376
+ slides = []
1377
+ for i, p in enumerate(pts[:8]):
1378
+ img = images[i] if i < len(images) else (images[-1] if images else data.get('og_img', ''))
1379
+ slides.append({'text': p[:300], 'image': img, 'index': i + 1})
1380
+
1381
+ post_id = str(int(time.time() * 1000)) + str(random.randint(100, 999))
1382
+ post = {
1383
+ "id": post_id, "title": data.get('title', title)[:200],
1384
+ "text": ai_text, "img": images[0] if images else data.get('og_img', ''),
1385
+ "url": url, "kind": "auto_rewrite", "slides": slides,
1386
+ "images": images[:10], "video": "",
1387
+ "voice": "vi-VN-HoaiMyNeural", "emotion": "neutral",
1388
+ "language": "vietnamese", "ts": int(time.time()),
1389
+ "auto_scheduled": True, "slot": slot_label,
1390
+ }
1391
+
1392
+ posts = _load_wall_posts()
1393
+ posts.insert(0, post)
1394
+ _save_wall_posts(posts)
1395
+
1396
+ # Trigger short generation async
1397
+ threading.Thread(target=lambda: asyncio.run(_auto_fetch_short(post_id)), daemon=True).start()
1398
+ return True
1399
+
1400
+ async def _do_scheduled_run(slot_label):
1401
+ """Main scheduled run: top 3 hot topics + latest articles."""
1402
+ print(f"[auto] Starting scheduled rewrite for {slot_label}")
1403
+
1404
+ # 1) Get top 3 hot topics
1405
+ topics = _get_hot_topics()[:3]
1406
+ job_topics = [t['topic'] for t in topics if t.get('topic')]
1407
+
1408
+ # 2) Add latest articles from RSS
1409
+ for feed_url in ['https://vnexpress.net/rss/tin-moi-nhat.rss',
1410
+ 'https://dantri.com.vn/rss/home.rss',
1411
+ 'https://vietnamnet.vn/rss/tin-moi-nhat.rss']:
1412
+ try:
1413
+ r = req.get(feed_url, headers={'User-Agent':'Mozilla/5.0'}, timeout=5)
1414
+ r.encoding = 'utf-8'
1415
+ soup = BeautifulSoup(r.text, 'xml')
1416
+ for it in soup.find_all('item')[:3]:
1417
+ t = _clean(it.find('title').get_text(' ',strip=True) if it.find('title') else '')
1418
+ if t and len(t) > 10 and t[:50] not in ' '.join(job_topics):
1419
+ job_topics.append(t[:60])
1420
+ except: pass
1421
+
1422
+ # 3) Execute jobs
1423
+ results = []
1424
+ for jt in job_topics[:6]:
1425
+ try:
1426
+ ok = await asyncio.wait_for(_auto_rewrite_one(jt, slot_label), timeout=90)
1427
+ results.append((jt, ok))
1428
+ except: results.append((jt, False))
1429
+ await asyncio.sleep(2)
1430
+
1431
+ # Log
1432
+ from datetime import datetime, timezone, timedelta
1433
+ VN_TZ_SCHED = timezone(timedelta(hours=7))
1434
+ today_str = datetime.now(VN_TZ_SCHED).strftime('%Y-%m-%d')
1435
+ log = _load_auto_log()
1436
+ if today_str not in log: log[today_str] = {}
1437
+ log[today_str][slot_label] = {
1438
+ 'time': datetime.now(VN_TZ_SCHED).strftime('%H:%M:%S'),
1439
+ 'count': sum(1 for _, ok in results if ok),
1440
+ 'total': len(results),
1441
+ }
1442
+ _save_auto_log(log)
1443
+ print(f"[auto] Done {slot_label}: {sum(1 for _, ok in results if ok)}/{len(results)} posts")
1444
+
1445
+ def _scheduler_loop():
1446
+ """Check every 60s; trigger at 7:00, 13:00, 19:00 VN time."""
1447
+ time.sleep(35)
1448
+ from datetime import datetime, timezone, timedelta
1449
+ VN_TZ_SCHED = timezone(timedelta(hours=7))
1450
+
1451
+ _last_run_date = ""
1452
+ _last_run_slots = set()
1453
+
1454
+ while True:
1455
+ try:
1456
+ now = datetime.now(VN_TZ_SCHED)
1457
+ today = now.strftime('%Y-%m-%d')
1458
+ hour = now.hour
1459
+ minute = now.minute
1460
+
1461
+ if today != _last_run_date:
1462
+ _last_run_date = today
1463
+ _last_run_slots = set()
1464
+
1465
+ slot = None
1466
+ for h, label in _AUTO_SCHEDULE_TIMES:
1467
+ if hour == h and 0 <= minute < 5:
1468
+ slot = label
1469
+ break
1470
+
1471
+ if slot and slot not in _last_run_slots:
1472
+ _last_run_slots.add(slot)
1473
+ loop = asyncio.new_event_loop()
1474
+ asyncio.set_event_loop(loop)
1475
+ try:
1476
+ loop.run_until_complete(_do_scheduled_run(slot))
1477
+ finally:
1478
+ loop.close()
1479
+ except Exception as e:
1480
+ print(f"[auto] Loop error: {e}")
1481
+
1482
+ time.sleep(60)
1483
+
1484
+ threading.Thread(target=_scheduler_loop, daemon=True, name='auto-rewrite-scheduler').start()
1485
+
1486
+ app.mount('/static',StaticFiles(directory=STATIC_DIR),name='vnews_static')