Spaces:
Sleeping
Sleeping
| import os | |
| import traceback | |
| import logging | |
| from flask import Flask, request, jsonify | |
| from flask_cors import CORS | |
| from werkzeug.utils import secure_filename | |
| from dotenv import load_dotenv | |
| from utils.anticheat import AntiCheatEngine | |
| from utils.predictor import Predictor | |
| from utils.verdict import get_verdict | |
| load_dotenv() | |
| # Setup logging | |
| logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') | |
| logger = logging.getLogger(__name__) | |
| # Initialize components | |
| app = Flask(__name__) | |
| CORS(app, resources={r"/*": {"origins": "*"}}, supports_credentials=True) | |
| ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'webp'} | |
| app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # Limit upload to 100MB | |
| logger.info("π§ Loading the MISSION 17 AI Brain (Ollama Vision)...") | |
| anticheat = AntiCheatEngine() | |
| predictor = Predictor() | |
| def allowed_file(filename): | |
| return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS | |
| def after_request(response): | |
| response.headers.add('Access-Control-Allow-Origin', '*') | |
| response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization') | |
| response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS') | |
| return response | |
| def health(): | |
| return jsonify({ | |
| "status": "ok", | |
| "model": predictor.get_model_name(), | |
| "anticheat_hashes": anticheat.count() | |
| }), 200 | |
| def reset_anti_cheat(): | |
| count = anticheat.clear() | |
| logger.info("π‘οΈ Anti-cheat hash database cleared!") | |
| return jsonify({"message": "Anti-cheat hash database cleared!", "count": count}), 200 | |
| def predict(): | |
| try: | |
| # π CHECK 1: File Presence | |
| if 'file' not in request.files: | |
| return jsonify({'error': 'No file uploaded'}), 400 | |
| file = request.files['file'] | |
| # π CHECK 2: Empty File Detection (Bug Fix) | |
| file.seek(0, os.SEEK_END) | |
| if file.tell() == 0: | |
| return jsonify({"error": "Processing failed: Empty file"}), 400 | |
| file.seek(0) | |
| # π CHECK 3: Empty Filename | |
| if file.filename == '': | |
| return jsonify({'error': 'No selected file'}), 400 | |
| # π CHECK 4: File Type Validation | |
| if not allowed_file(file.filename): | |
| return jsonify({'error': 'Invalid file type. Only JPG/PNG allowed.'}), 400 | |
| # Read file bytes ONCE and reuse them | |
| file_bytes = file.read() | |
| # π― MODULE 11: Calculate Hash and Check for Cheaters | |
| # skip_anticheat=1 is sent by the admin re-scan endpoint so that | |
| # already-registered hashes don't falsely trigger duplicate detection. | |
| skip_anticheat = request.form.get('skip_anticheat', '0') == '1' | |
| if not skip_anticheat and anticheat.is_duplicate(file_bytes): | |
| logger.warning("π¨ ANTI-CHEAT: Duplicate image detected!") | |
| return jsonify({ | |
| "status": "REJECTED", | |
| "error": "Duplicate image detected. You cannot farm points!", | |
| "prediction": "Anti-Cheat: Duplicate" | |
| }), 400 | |
| # π€ AI Vision Prediction via Ollama | |
| logger.info("π€ Sending image to Ollama Vision...") | |
| ai_result = predictor.predict(file_bytes) | |
| category = ai_result.get('category', 'Non_SDG_Invalid') | |
| confidence = ai_result.get('confidence', 0) | |
| reason = ai_result.get('reason', '') | |
| # βοΈ Get formatted verdict based on AI output | |
| verdict_response = get_verdict(category, confidence, threshold=55) | |
| verdict_response['reason'] = reason | |
| verdict_response['model'] = predictor.get_model_name() | |
| # Only register hash if the image was VERIFIED (save memory/prevent false positives on bad images) | |
| if verdict_response['is_verified']: | |
| anticheat.register(file_bytes) | |
| logger.info(f"β Unique verified image logged to anticheat.") | |
| return jsonify(verdict_response) | |
| except Exception as e: | |
| logger.error(f"β Processing Error: {str(e)}") | |
| traceback.print_exc() | |
| return jsonify({'error': "Processing failed", 'detail': str(e)}), 500 | |
| if __name__ == '__main__': | |
| # Hugging Face requires the app to listen on 0.0.0.0:7860 | |
| app.run(host='0.0.0.0', port=7860, debug=False) |