import datetime import os import requests as py_requests from flask import Blueprint, request, jsonify, Response, session, current_app from backend.config import Config from backend.core.decorators import get_current_user from backend.core.rate_limit import get_client_ip from backend.core.security import verify_access_token from backend.services.translation import ( get_engine, translate_texts, translate_stream_generator, translation_limit_tracker, call_ai_chat_proxy ) translate_bp = Blueprint("translate", __name__) def is_vip_request(): """Check if current request has VIP privileges (session, JWT, or header code).""" user = get_current_user() if user and user.get("vip_status", 0) == 1: return True # Check headers vip_code = request.headers.get("X-VIP-Code", "") or request.headers.get("X-VIP-Key", "") if not vip_code: # Check request body try: data = request.json or {} vip_code = data.get("vip_key", "") or data.get("vip_code", "") except: pass if vip_code and vip_code in Config.VALID_VIP_CODES: return True return False def _check_translation_rate_limit(texts): """Returns (exceeded: bool, tracker_key, count) for standard users.""" client_ip = get_client_ip() today_str = datetime.date.today().isoformat() tracker_key = f"{client_ip}:{today_str}" current_count = translation_limit_tracker.get(tracker_key, 0) requested_count = len([t for t in texts if t.strip()]) exceeded = (current_count + requested_count) > 50 return exceeded, tracker_key, requested_count @translate_bp.route("/translate", methods=["POST"]) def translate(): data = request.json if not data or "texts" not in data: return jsonify({"error": "Missing 'texts' array"}), 400 texts = data["texts"] if not is_vip_request(): exceeded, tracker_key, count = _check_translation_rate_limit(texts) if exceeded: return jsonify({ "error": "Hạn mức dịch máy chủ Standard đã hết (50 đoạn/ngày). Vui lòng nâng cấp tài khoản VIP!" }), 403 translation_limit_tracker[tracker_key] = translation_limit_tracker.get(tracker_key, 0) + count mode = data.get("mode") # If standalone translation server is running, proxy request to it local_translate_url = os.environ.get("LOCAL_TRANSLATE_URL", "") if not local_translate_url: local_tts_url = os.environ.get("LOCAL_TTS_URL", "") if local_tts_url: local_translate_url = local_tts_url.split("/v1/")[0] if local_translate_url: try: translate_url = f"{local_translate_url.rstrip('/')}/v1/translate" r = py_requests.post(translate_url, json={"texts": texts, "mode": mode}, timeout=(2.0, 60.0)) if r.status_code == 200: return jsonify(r.json()) current_app.logger.error(f"[Translate Proxy Error]: {r.status_code} - {r.text}") except Exception as e: current_app.logger.error(f"[Translate Proxy Exception]: {e}") translations = translate_texts(texts, mode) return jsonify({"translations": translations}) @translate_bp.route("/translate_stream", methods=["POST"]) def translate_stream(): data = request.json if not data or "texts" not in data: return jsonify({"error": "Missing 'texts' array"}), 400 texts = data["texts"] if not is_vip_request(): exceeded, tracker_key, count = _check_translation_rate_limit(texts) if exceeded: return jsonify({ "error": "Hạn mức dịch máy chủ Standard đã hết (50 đoạn/ngày). Vui lòng nâng cấp tài khoản VIP!" }), 403 translation_limit_tracker[tracker_key] = translation_limit_tracker.get(tracker_key, 0) + count mode = data.get("mode") # If standalone translation server is running, proxy stream request to it local_translate_url = os.environ.get("LOCAL_TRANSLATE_URL", "") if not local_translate_url: local_tts_url = os.environ.get("LOCAL_TTS_URL", "") if local_tts_url: local_translate_url = local_tts_url.split("/v1/")[0] if local_translate_url: try: translate_url = f"{local_translate_url.rstrip('/')}/v1/translate_stream" r = py_requests.post(translate_url, json={"texts": texts, "mode": mode}, stream=True, timeout=(2.0, 60.0)) if r.status_code == 200: def stream_forwarder(): for chunk in r.iter_content(chunk_size=1024): if chunk: yield chunk response = Response(stream_forwarder(), mimetype="text/event-stream") response.headers["X-Accel-Buffering"] = "no" response.headers["Cache-Control"] = "no-cache, no-transform" response.headers["Connection"] = "keep-alive" response.headers["X-Content-Type-Options"] = "nosniff" return response current_app.logger.error(f"[Translate Stream Proxy Error]: {r.status_code} - {r.text}") except Exception as e: current_app.logger.error(f"[Translate Stream Proxy Exception]: {e}") response = Response(translate_stream_generator(texts, mode), mimetype="text/event-stream") response.headers["X-Accel-Buffering"] = "no" response.headers["Cache-Control"] = "no-cache, no-transform" response.headers["Connection"] = "keep-alive" response.headers["X-Content-Type-Options"] = "nosniff" return response @translate_bp.route("/api/ai/chat", methods=["POST"]) def ai_chat_proxy(): if not is_vip_request(): return jsonify({ "error": "Chức năng AI chỉ dành cho thành viên VIP. Vui lòng nâng cấp hoặc nhập API Key cá nhân trong cài đặt!" }), 403 data = request.json or {} messages = data.get("messages", []) model = data.get("model", "gemini-1.5-flash") prompt = data.get("prompt", "") try: text = call_ai_chat_proxy(messages, model, prompt) return jsonify({"text": text}) except ValueError as e: return jsonify({"error": str(e)}), 501 except RuntimeError as e: return jsonify({"error": str(e)}), 502 except Exception as e: return jsonify({"error": f"Lỗi xử lý AI Proxy: {str(e)}"}), 500 @translate_bp.route("/iframe_proxy", methods=["GET"]) def iframe_proxy(): url = request.args.get("url") if not url: return "Missing 'url' parameter", 400 try: headers = { "User-Agent": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.5" } # Follow redirects r = py_requests.get(url, headers=headers, timeout=15, allow_redirects=True) encoding = r.encoding if not encoding or encoding.lower() == 'iso-8859-1': encoding = r.apparent_encoding or 'utf-8' try: html = r.content.decode(encoding, errors='ignore') except Exception: html = r.content.decode('utf-8', errors='ignore') # Insert base URL tag so all links/resources load relative to original page base_tag = f'' if "" in html: html = html.replace("", f"{base_tag}", 1) elif "" in html: html = html.replace("", f"{base_tag}", 1) else: html = base_tag + html # Injected proxy handler script injected_script = """ """ if "" in html: html = html.replace("", f"{injected_script}", 1) elif "" in html: html = html.replace("", f"{injected_script}", 1) else: html = html + injected_script response = Response(html, mimetype="text/html") response.headers.pop("X-Frame-Options", None) response.headers.pop("Content-Security-Policy", None) response.headers["Access-Control-Allow-Origin"] = "*" return response except Exception as e: return f"Proxy error: {str(e)}", 500