from flask import Flask, jsonify import threading import time import requests from datetime import datetime import logging # Setup logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) app = Flask(__name__) # ============ CONFIG ============ NUM_SERVERS = 55 # serverclass1 to serverclass55 PING_INTERVAL = 21600 # seconds (5 minutes) - like cron-job.org REQUEST_TIMEOUT = 30 # Extra fixed URLs EXTRA_URLS = [ "https://dooratre-backup.hf.space/health", "https://dooratre-reload-bot.hf.space/health", "https://corvo-ai-cron.hf.space/health", ] def build_urls(): urls = [] urls.extend(EXTRA_URLS) return urls ALL_URLS = build_urls() # ============ STATE ============ state = { "running": False, "thread": None, "lock": threading.Lock(), "stop_event": threading.Event(), "last_cycle_start": None, "last_cycle_end": None, "cycles_completed": 0, "total_pings": 0, "success_count": 0, "fail_count": 0, "last_results": {}, } def ping_url(url): try: r = requests.get(url, timeout=REQUEST_TIMEOUT) ok = r.status_code == 200 return ok, r.status_code, None except Exception as e: return False, None, str(e) def cron_worker(stop_event: threading.Event): logger.info(f"Cron worker started. Total URLs: {len(ALL_URLS)}") while not stop_event.is_set(): cycle_start = datetime.utcnow().isoformat() state["last_cycle_start"] = cycle_start logger.info(f"=== Cycle started at {cycle_start} ===") for url in ALL_URLS: if stop_event.is_set(): logger.info("Stop event received, breaking cycle.") break ok, status, err = ping_url(url) state["total_pings"] += 1 if ok: state["success_count"] += 1 logger.info(f"[OK {status}] {url}") else: state["fail_count"] += 1 logger.warning(f"[FAIL {status}] {url} | err={err}") state["last_results"][url] = { "ok": ok, "status": status, "error": err, "time": datetime.utcnow().isoformat(), } state["cycles_completed"] += 1 state["last_cycle_end"] = datetime.utcnow().isoformat() logger.info(f"=== Cycle done. Sleeping {PING_INTERVAL}s ===") # Sleep but be responsive to stop_event stop_event.wait(PING_INTERVAL) logger.info("Cron worker stopped.") @app.route("/") def index(): return jsonify({ "service": "Cron-like pinger", "running": state["running"], "total_urls": len(ALL_URLS), "endpoints": ["/start", "/end", "/status", "/urls"], }) @app.route("/start", methods=["GET", "POST"]) def start(): with state["lock"]: if state["running"]: return jsonify({"status": "already_running"}), 200 state["stop_event"] = threading.Event() t = threading.Thread(target=cron_worker, args=(state["stop_event"],), daemon=True) state["thread"] = t state["running"] = True t.start() logger.info("Cron started via /start") return jsonify({ "status": "started", "total_urls": len(ALL_URLS), "interval_seconds": PING_INTERVAL, }) @app.route("/end", methods=["GET", "POST"]) def end(): with state["lock"]: if not state["running"]: return jsonify({"status": "not_running"}), 200 state["stop_event"].set() state["running"] = False logger.info("Cron stop requested via /end") return jsonify({"status": "stopping"}) @app.route("/status") def status(): return jsonify({ "running": state["running"], "total_urls": len(ALL_URLS), "cycles_completed": state["cycles_completed"], "total_pings": state["total_pings"], "success_count": state["success_count"], "fail_count": state["fail_count"], "last_cycle_start": state["last_cycle_start"], "last_cycle_end": state["last_cycle_end"], "interval_seconds": PING_INTERVAL, }) @app.route("/urls") def urls(): return jsonify({"count": len(ALL_URLS), "urls": ALL_URLS}) @app.route("/results") def results(): return jsonify(state["last_results"]) @app.route("/health") def health(): return jsonify({"status": "ok"}) if __name__ == "__main__": # Auto-start cron on boot (optional - comment out if you only want manual /start) # Uncomment below if you want it to auto-run: # state["stop_event"] = threading.Event() # t = threading.Thread(target=cron_worker, args=(state["stop_event"],), daemon=True) # state["thread"] = t # state["running"] = True # t.start() app.run(host="0.0.0.0", port=7860, debug=False, threaded=True)