| from flask import Flask, jsonify |
| import threading |
| import time |
| import requests |
| from datetime import datetime |
| import logging |
|
|
| |
| logging.basicConfig( |
| level=logging.INFO, |
| format='%(asctime)s - %(levelname)s - %(message)s' |
| ) |
| logger = logging.getLogger(__name__) |
|
|
| app = Flask(__name__) |
|
|
| |
| NUM_SERVERS = 55 |
| PING_INTERVAL = 21600 |
| REQUEST_TIMEOUT = 30 |
|
|
| |
| SERVERCLASS_TEMPLATES = [ |
| "https://serverclass{n}-claude-4-6-opus.hf.space/health", |
| "https://serverclass{n}-gpt-5-4.hf.space/health", |
| "https://serverclass{n}-tts.hf.space/health", |
| "https://serverclass{n}-transcript.hf.space/health", |
| ] |
|
|
| |
| EXTRA_URLS = [ |
| "https://dooratre-backup.hf.space/health", |
| "https://dooratre-reload-bot.hf.space/health", |
| "https://corvo-ai-cron-itself.hf.space/health", |
| ] |
|
|
|
|
| def build_urls(): |
| urls = [] |
| for n in range(1, NUM_SERVERS + 1): |
| for tpl in SERVERCLASS_TEMPLATES: |
| urls.append(tpl.format(n=n)) |
| urls.extend(EXTRA_URLS) |
| return urls |
|
|
|
|
| ALL_URLS = build_urls() |
|
|
| |
| 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 ===") |
|
|
| |
| 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__": |
| |
| |
| |
| |
| |
| |
| |
|
|
| app.run(host="0.0.0.0", port=7860, debug=False, threaded=True) |