File size: 4,409 Bytes
eb156c5
ba24ffc
eb156c5
ba24ffc
eb156c5
ba24ffc
 
 
eb156c5
ba24ffc
 
eb156c5
 
 
 
 
 
ba24ffc
eb156c5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ba24ffc
 
 
eb156c5
ba24ffc
eb156c5
ba24ffc
 
 
 
 
 
 
eb156c5
 
 
 
 
 
 
 
 
 
 
 
 
 
ba24ffc
 
eb156c5
ba24ffc
eb156c5
 
 
ba24ffc
 
 
 
 
 
 
 
 
 
 
 
eb156c5
 
 
 
 
 
 
 
 
 
ba24ffc
 
 
 
 
 
 
eb156c5
ba24ffc
eb156c5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
"""VNEWS v2 Patch - auto scheduler + status endpoints + keep-alive.
This is imported by app_v2_entry.py to add auto posting functionality.
FIX v2: Catch-up scheduler + keep-alive to prevent Space sleep
"""
import sys, os, threading, json, time, logging
from datetime import datetime, timezone, timedelta
from fastapi import Request
from fastapi.responses import JSONResponse
import requests as _req

VN_TZ = timezone(timedelta(hours=7))
LOG = logging.getLogger("app_v2_patch")
LOG.setLevel(logging.INFO)
if not LOG.handlers:
    ch = logging.StreamHandler()
    ch.setFormatter(logging.Formatter('%(asctime)s [app_v2_patch] %(levelname)s: %(message)s'))
    LOG.addHandler(ch)

# ===== Keep-alive: prevent Space from sleeping =====
# HF Spaces sleep after ~30 min of inactivity on free tier
# This thread pings the Space every 10 minutes to keep it alive
SPACE_URL = "https://bep40-vnews.hf.space"

def _keep_alive_loop():
    """Ping the Space every 10 minutes to prevent sleep."""
    LOG.info(f"🔄 Keep-alive thread started - ping {SPACE_URL} every 10 min")
    while True:
        try:
            time.sleep(600)  # 10 minutes
            _req.get(f"{SPACE_URL}/api/scheduler/status", 
                     headers={"User-Agent": "VNEWS-KeepAlive/1.0"},
                     timeout=15)
            LOG.debug("Keep-alive ping OK")
        except Exception as e:
            LOG.warning(f"Keep-alive ping failed (Space may be sleeping): {e}")

# Start keep-alive in background
try:
    _ka_thread = threading.Thread(target=_keep_alive_loop, daemon=True, name="keep-alive")
    _ka_thread.start()
    LOG.info("🔄 Keep-alive started - Space will stay awake")
except Exception as e:
    LOG.warning(f"Keep-alive start failed: {e}")

# ===== Start auto scheduler =====
try:
    import auto_scheduler as _as
    _as.start_auto_scheduler()
    LOG.info("[auto_scheduler] Started successfully - will post at 7:00, 13:00, 19:00 VN time (with catch-up)")
except Exception as e:
    LOG.error(f"[auto_scheduler] Start failed: {e}")

def register_scheduler_endpoints(app):
    """Register scheduler status/trigger endpoints on the FastAPI app."""
    
    @app.get('/api/scheduler/status')
    def scheduler_status():
        running = any(t.name == 'auto-scheduler' and t.is_alive() for t in threading.enumerate())
        keep_alive = any(t.name == 'keep-alive' and t.is_alive() for t in threading.enumerate())
        
        # Load state to show which slots ran today
        today_str = datetime.now(VN_TZ).strftime('%Y-%m-%d')
        state = {}
        try:
            state_file = '/data/scheduler_state.json' if os.path.isdir('/data') else None
            if state_file and os.path.exists(state_file):
                state = json.load(open(state_file, 'r'))
        except:
            pass
        
        ran_today = state.get(today_str, {}) if state else {}
        
        return JSONResponse({
            "running": running,
            "keep_alive": keep_alive,
            "schedule": "7:00, 13:00, 19:00 VN time",
            "today": today_str,
            "slots_ran_today": ran_today,
            "catch_up_enabled": True,
            "next_run": "7:00, 13:00, or 19:00 VN time (whichever is next)"
        })

    @app.post('/api/scheduler/trigger')
    async def scheduler_trigger():
        try:
            import auto_scheduler as _as2
            _as2._run_scheduled_posting()
            return JSONResponse({"ok": True, "message": "Scheduled posting triggered manually"})
        except Exception as e:
            return JSONResponse({"ok": False, "error": str(e)}, status_code=500)

    @app.get('/api/scheduler/force')
    def scheduler_force():
        """Force-run all missed slots immediately. Useful after deploy."""
        try:
            import auto_scheduler as _as2
            _as2._check_missed_slots()
            return JSONResponse({"ok": True, "message": "Missed slots check triggered"})
        except Exception as e:
            return JSONResponse({"ok": False, "error": str(e)}, status_code=500)

    return app


# Auto-register on the main app from app_v2_entry
try:
    from main import app
    register_scheduler_endpoints(app)
    LOG.info("[app_v2_patch] Scheduler endpoints registered: /api/scheduler/status, /api/scheduler/trigger, /api/scheduler/force")
except Exception as e:
    LOG.error(f"[app_v2_patch] Could not register endpoints: {e}")