"""
app.py — CroxyProxy Rotating Proxy API
L'interface (UI) est dans ui.py.
UI
GET / - Interface navigateur
API
GET /api - Infos API
GET /health - Status + stats
GET /servers - Liste des serveurs
GET /view - Rend une page via proxy (utilisé par l'UI)
POST /proxy/fetch - Proxy rotatif
POST /proxy/random - Serveur aléatoire
POST /proxy/batch - Plusieurs URLs
"""
import json, base64, re, random, time, threading, socket, ipaddress
from datetime import datetime, timezone
from urllib.parse import urlparse
from flask import Flask, request, jsonify, Response
from bs4 import BeautifulSoup
import cloudscraper
from html import unescape, escape
import warnings
warnings.filterwarnings("ignore")
from ui import BROWSER_UI, error_page
BASE = "https://www.croxyproxy.com"
app = Flask(__name__)
KEEP_HEADERS = {
"content-type", "content-length", "content-encoding",
"server", "date", "connection",
"access-control-allow-origin", "access-control-allow-credentials",
"cache-control", "etag", "last-modified",
"x-ratelimit-limit", "x-ratelimit-remaining",
"x-request-id", "location", "retry-after",
}
DROP_HEADERS = {
"set-cookie", "__cph", "__cpc",
"content-security-policy", "strict-transport-security",
"referrer-policy", "access-control-allow-headers",
"x-frame-options", "x-content-type-options",
"permissions-policy", "cross-origin-opener-policy",
"cross-origin-embedder-policy",
}
class S:
servers = []
idx = 0
lock = threading.Lock()
last = None
stats = {"req": 0, "ok": 0, "fail": 0}
def dec(e):
try:
return json.loads(bytes.fromhex(base64.b64decode(e).decode()).decode())
except Exception:
return None
def filter_headers(raw_headers, include_all=False):
if include_all:
return dict(raw_headers)
cleaned = {}
for k, v in raw_headers.items():
kl = k.lower()
if kl in DROP_HEADERS:
continue
if kl in KEEP_HEADERS:
cleaned[k] = v
return cleaned
def parse_body(text, content_type=""):
if not text:
return None
if "json" in content_type.lower() or text.strip().startswith(("{", "[")):
try:
return json.loads(text)
except (json.JSONDecodeError, ValueError):
pass
if "html" in content_type.lower() or text.strip().startswith("<"):
return {
"_type": "html", "_length": len(text), "content": text,
"_preview": text[:300].strip() + ("..." if len(text) > 300 else ""),
}
if len(text) > 2000:
return {
"_type": "text", "_length": len(text), "content": text,
"_preview": text[:500].strip() + "...",
}
return text
def extract_ip(url_str):
return (url_str or "").replace("https://", "").replace("http://", "").split("/")[0]
def format_result(raw, include_raw_headers=False):
if not raw.get("success"):
return {"success": False, "error": raw.get("error"), "server": raw.get("server")}
ct = ""
if raw.get("headers"):
ct = raw["headers"].get("Content-Type", raw["headers"].get("content-type", ""))
result = {
"success": True,
"status": raw.get("status"),
"url": raw.get("url"),
"body": parse_body(raw.get("body", ""), ct),
"proxy": raw.get("proxy"),
"servers_available": raw.get("servers_available"),
}
if raw.get("headers"):
result["headers"] = filter_headers(raw["headers"], include_all=include_raw_headers)
return result
def fetch_raw(url, sid=None):
sc = cloudscraper.create_scraper(
browser={"browser": "chrome", "platform": "windows", "desktop": True}
)
S.stats["req"] += 1
try:
r1 = sc.get(BASE, timeout=30)
if r1.status_code != 200:
S.stats["fail"] += 1
return {"success": False, "error": f"Homepage {r1.status_code}"}
s1 = BeautifulSoup(r1.text, "lxml")
ci = s1.find("input", {"name": "csrf"})
if not ci:
S.stats["fail"] += 1
return {"success": False, "error": "No CSRF"}
r2 = sc.post(
f"{BASE}/servers",
data={"url": url, "proxyServerId": "274", "csrf": ci["value"],
"demo": "0", "frontOrigin": BASE},
headers={"Content-Type": "application/x-www-form-urlencoded",
"Origin": BASE, "Referer": BASE + "/"},
allow_redirects=True, timeout=30,
)
if r2.status_code != 200:
S.stats["fail"] += 1
return {"success": False, "error": f"Servers {r2.status_code}"}
s2 = BeautifulSoup(r2.text, "lxml")
sel = s2.find("script", {"id": "serverSelectorScript"})
if not sel:
S.stats["fail"] += 1
return {"success": False, "error": "No selector"}
ss = [x for x in (dec(i) for i in json.loads(unescape(sel.get("data-ss", ""))))
if x and x.get("id")]
csrf2 = unescape(sel.get("data-csrf", "")).strip('"')
fo = unescape(sel.get("data-fo", "")).strip('"')
if not ss:
S.stats["fail"] += 1
return {"success": False, "error": "No servers"}
S.servers = ss
S.last = datetime.now(timezone.utc).isoformat()
ch = None
if sid:
ch = next((x for x in ss if x["id"] == sid), None)
if not ch:
with S.lock:
ch = ss[S.idx % len(ss)]
S.idx += 1
r3 = sc.post(
f"{BASE}/requests?fso=",
data={"url": url, "proxyServerId": str(ch["id"]), "csrf": csrf2,
"demo": "0", "frontOrigin": fo},
headers={"Content-Type": "application/x-www-form-urlencoded",
"Origin": BASE, "Referer": f"{BASE}/servers"},
allow_redirects=False, timeout=30,
)
loc = r3.headers.get("Location") or r3.headers.get("location")
if not loc:
S.stats["fail"] += 1
return {"success": False, "error": f"No redirect ({r3.status_code})",
"server": ch.get("name")}
r4 = sc.get(loc, timeout=30, allow_redirects=True)
dr = re.search(r'data-r="([^"]+)"', r4.text)
if not dr:
S.stats["fail"] += 1
return {"success": False, "error": "No data-r", "server": ch.get("name")}
final = base64.b64decode(dr.group(1)).decode()
r5 = sc.get(final, timeout=30, allow_redirects=True)
S.stats["ok"] += 1
return {
"success": True,
"status": r5.status_code,
"headers": dict(r5.headers),
"body": r5.text,
"url": url,
"proxy": {
"server_id": ch["id"],
"server_name": ch.get("name"),
"ip": extract_ip(ch.get("url", "")),
},
"servers_available": len(ss),
}
except Exception as e:
S.stats["fail"] += 1
return {"success": False, "error": str(e)}
# ═══════════════════════════════════════════════
# RÉÉCRITURE HTML (mode navigation)
# ═══════════════════════════════════════════════
# JS injecté dans chaque page : reroute clics + formulaires GET par le proxy.
INJECT_JS = r"""
(function(){
var sidParam = __SID__ ? ("&server_id=" + __SID__) : "";
function proxify(u){ return "/view?url=" + encodeURIComponent(u) + sidParam; }
document.addEventListener("click", function(e){
var a = e.target && e.target.closest ? e.target.closest("a") : null;
if(!a || !a.href) return;
var raw = a.getAttribute("href") || "";
if(raw.startsWith("#")) return;
var h = a.href;
if(/^(javascript:|mailto:|tel:|blob:|data:)/i.test(h)) return;
e.preventDefault();
window.location.href = proxify(h);
}, true);
document.addEventListener("submit", function(e){
var f = e.target;
if(!f || f.tagName !== "FORM") return;
if((f.getAttribute("method") || "get").toLowerCase() !== "get") return;
e.preventDefault();
var action = f.action || __BASE__;
var params = new URLSearchParams(new FormData(f)).toString();
var sep = action.indexOf("?") === -1 ? "?" : "&";
window.location.href = proxify(action + (params ? sep + params : ""));
}, true);
})();
"""
def rewrite_html(html, base_url, server_id=None):
"""Injecte