| from flask import Flask, jsonify, request |
| from flask_cors import CORS |
| import json |
| import os |
| import random |
| from src.crawler import Crawler |
| from src.analyzer import ImageAnalyzer |
| from src.monitor import SystemMonitor |
|
|
| app = Flask(__name__) |
| CORS(app) |
|
|
| REPORT_FILE = 'seo_report.json' |
|
|
| |
| import threading |
| import time |
| import requests |
|
|
| class KeepAlive(threading.Thread): |
| def __init__(self, interval=60, target_url="http://127.0.0.1:7860/health"): |
| super().__init__() |
| self.interval = interval |
| self.target_url = target_url |
| self.daemon = True |
| self.running = True |
|
|
| def run(self): |
| print("KeepAlive System Started") |
| while self.running: |
| try: |
| |
| print(f"[Heartbeat] System Active - {time.ctime()}") |
| |
| |
| time.sleep(self.interval) |
| |
| |
| try: |
| requests.get(self.target_url, timeout=5) |
| except: |
| pass |
| |
| except Exception as e: |
| print(f"[KeepAlive] Error: {e}") |
| time.sleep(60) |
|
|
| class ScheduledCrawler(threading.Thread): |
| def __init__(self, interval=14400): |
| super().__init__() |
| self.interval = interval |
| self.daemon = True |
| self.running = True |
|
|
| def run(self): |
| print(f"[Scheduler] Auto-Crawler initialized. Schedule: Every {self.interval/3600} hours.") |
| |
| time.sleep(60) |
| |
| while self.running: |
| try: |
| |
| |
| target_domain = os.environ.get("AUTO_CRAWL_TARGET") |
| |
| if target_domain: |
| print(f"\n[Scheduler] 🕒 Triggering scheduled crawl for: {target_domain}") |
| |
| |
| |
| crawler = Crawler() |
| site_data, _, _ = crawler.crawl_domain(target_domain, max_pages=50) |
| |
| if site_data: |
| analyzer = ImageAnalyzer() |
| results = analyzer.analyze_site(site_data) |
| |
| |
| with open(REPORT_FILE, 'w', encoding='utf-8') as f: |
| json.dump(results, f, indent=4) |
| |
| print(f"[Scheduler] ✅ Crawl finished for {target_domain}. Report saved.") |
| else: |
| print(f"[Scheduler] ⚠️ Crawl returned no data.") |
| else: |
| print("[Scheduler] ℹ️ waiting... (Set 'AUTO_CRAWL_TARGET' Env Var to enable auto-crawling)") |
|
|
| |
| time.sleep(self.interval) |
|
|
| except Exception as e: |
| print(f"[Scheduler] Error: {e}") |
| time.sleep(60) |
|
|
| |
| pinger = KeepAlive(interval=300) |
| pinger.start() |
|
|
| scheduler = ScheduledCrawler(interval=14400) |
| scheduler.start() |
| |
|
|
| @app.route('/') |
| def home(): |
| return "Antigravity API is Running. Use /api/status for system info." |
|
|
| @app.route('/health') |
| def health_check(): |
| return jsonify({"status": "alive"}), 200 |
|
|
| @app.route('/api/status', methods=['GET']) |
| def get_system_status(): |
| """ |
| Returns real-time system metadata including dynamic region and latency. |
| Accepts optional 'domain' parameter to check latency to a specific target. |
| """ |
| domain = request.args.get('domain') |
| stats = SystemMonitor.get_system_stats(target_url=domain) |
| return jsonify(stats) |
|
|
| @app.route('/api/seo-report', methods=['GET', 'POST']) |
| def get_seo_report(): |
| |
| if not hasattr(get_seo_report, "cache"): |
| get_seo_report.cache = {"data": None, "mtime": 0} |
|
|
| def get_cached_report(): |
| if not os.path.exists(REPORT_FILE): |
| return None |
| |
| current_mtime = os.path.getmtime(REPORT_FILE) |
| if get_seo_report.cache["data"] is None or current_mtime > get_seo_report.cache["mtime"]: |
| try: |
| with open(REPORT_FILE, 'r', encoding='utf-8') as f: |
| get_seo_report.cache["data"] = json.load(f) |
| get_seo_report.cache["mtime"] = current_mtime |
| except Exception as e: |
| |
| return None |
| return get_seo_report.cache["data"] |
|
|
| def get_param(name, default): |
| val = request.args.get(name) or request.form.get(name) |
| if val is None and request.is_json: |
| val = request.json.get(name) |
| return val if val is not None else default |
|
|
| |
| if not hasattr(get_seo_report, "domain_cache"): |
| get_seo_report.domain_cache = {} |
|
|
| domain = get_param('domain', None) |
| limit = int(get_param('limit', 25)) |
|
|
| if domain: |
| |
| cache_key = f"{domain}_{limit}" |
| cached_item = get_seo_report.domain_cache.get(cache_key) |
| |
| import time |
| if cached_item: |
| timestamp, data = cached_item |
| |
| |
| |
| |
| |
|
|
| try: |
| print(f"Starting live scan for: {domain}") |
| |
| |
| crawler = Crawler() |
| |
| site_data, total_discovered, _ = crawler.crawl_domain(domain, max_pages=limit) |
| |
| if not site_data: |
| response = { |
| "summary": { |
| "total_pages_scanned": 0, |
| "total_images_found": 0, |
| "total_images_missing_alt": 0, |
| "total_pages_discovered": 0 |
| }, |
| "details": [] |
| } |
| return jsonify(response) |
|
|
| |
| analyzer = ImageAnalyzer() |
| results = analyzer.analyze_site(site_data) |
| |
| |
| results['summary']['total_pages_discovered'] = total_discovered |
| results['details_count'] = len(results['details']) |
| |
| |
| get_seo_report.domain_cache[cache_key] = (time.time(), results) |
| |
| return jsonify(results) |
|
|
| except Exception as e: |
| return jsonify({"error": f"Scraping failed: {str(e)}"}), 500 |
|
|
| if not os.path.exists(REPORT_FILE): |
| return jsonify({"error": "Report file not found. Please run result logic first."}), 404 |
|
|
| try: |
| data = get_cached_report() |
| if data is None: |
| |
| |
| |
| |
| |
| return jsonify({"error": "Report file not found or unreadable."}), 404 |
| |
| |
| |
| random_param = str(get_param('random', 'true')).lower() |
| is_random = random_param == 'true' |
|
|
| summary = data.get('summary', {}) |
| details = data.get('details', []) |
|
|
| |
| if limit is not None and limit > 0: |
| if is_random and limit < len(details): |
| details = random.sample(details, limit) |
| else: |
| details = details[:limit] |
| |
| response = { |
| "summary": summary, |
| "details_count": len(details), |
| "details": details |
| } |
| |
| return jsonify(response) |
|
|
| except Exception as e: |
| return jsonify({"error": f"Failed to read report: {str(e)}"}), 500 |
|
|
| if __name__ == '__main__': |
| |
| app.run(debug=True, host='0.0.0.0', port=5050) |
|
|