🐳 11/07 - 16:02 - Navigateur Virtuel Isolé. REN,FORCÉ import randomimport requestsfrom bs4 import BeautifulSoupfrom typing import Dict, Anyclass VirtualBrowserEngine: """ Moteur de Naviga
Browse files- mcp_server.py +46 -0
- navigateur.html +114 -0
mcp_server.py
CHANGED
|
@@ -251,6 +251,52 @@ def call_claude_api(messages, model=None, max_tokens=4096, temperature=0.4):
|
|
| 251 |
return None, f"Claude API request failed: {str(e)}"
|
| 252 |
|
| 253 |
# ── Flask App ──
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
app = Flask(__name__)
|
| 255 |
CORS(app)
|
| 256 |
|
|
|
|
| 251 |
return None, f"Claude API request failed: {str(e)}"
|
| 252 |
|
| 253 |
# ── Flask App ──
|
| 254 |
+
# ── Browser proxy with identity mutation ─────────────────────────────────
|
| 255 |
+
import random
|
| 256 |
+
|
| 257 |
+
USER_AGENTS = [
|
| 258 |
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
|
| 259 |
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
|
| 260 |
+
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
|
| 261 |
+
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Mobile/15E148 Safari/604.1",
|
| 262 |
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0"
|
| 263 |
+
]
|
| 264 |
+
ACCEPT_LANGUAGES = [
|
| 265 |
+
"en-US,en;q=0.9",
|
| 266 |
+
"fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7",
|
| 267 |
+
"de-DE,de;q=0.9,en-US;q=0.8",
|
| 268 |
+
"es-ES,es;q=0.9,en-US;q=0.8"
|
| 269 |
+
]
|
| 270 |
+
RESOLUTIONS = ["1920x1080", "1366x768", "1440x900", "375x812", "1024x768"]
|
| 271 |
+
|
| 272 |
+
@app.route("/browser", methods=["POST"])
|
| 273 |
+
def browser_proxy():
|
| 274 |
+
data = request.get_json(force=True)
|
| 275 |
+
url = data.get("url", "")
|
| 276 |
+
if not url:
|
| 277 |
+
return jsonify({"error": "No URL provided"}), 400
|
| 278 |
+
|
| 279 |
+
headers = {
|
| 280 |
+
"User-Agent": random.choice(USER_AGENTS),
|
| 281 |
+
"Accept-Language": random.choice(ACCEPT_LANGUAGES),
|
| 282 |
+
"Accept-Encoding": "gzip, deflate, br",
|
| 283 |
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
| 284 |
+
"DNT": "1",
|
| 285 |
+
"Upgrade-Insecure-Requests": "1",
|
| 286 |
+
"X-Virtual-Res": random.choice(RESOLUTIONS)
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
+
try:
|
| 290 |
+
resp = http_requests.get(url, headers=headers, timeout=25, allow_redirects=True)
|
| 291 |
+
return jsonify({
|
| 292 |
+
"status": resp.status_code,
|
| 293 |
+
"content": resp.text,
|
| 294 |
+
"identity_used": headers["User-Agent"],
|
| 295 |
+
"fingerprint_shield": "Active"
|
| 296 |
+
})
|
| 297 |
+
except Exception as e:
|
| 298 |
+
return jsonify({"status": "error", "message": str(e)}), 502
|
| 299 |
+
|
| 300 |
app = Flask(__name__)
|
| 301 |
CORS(app)
|
| 302 |
|
navigateur.html
CHANGED
|
@@ -112,6 +112,17 @@
|
|
| 112 |
<button onclick="loadPage()"
|
| 113 |
class="px-4 py-2 rounded-lg bg-gradient-to-r from-neon-cyan to-neon-blue text-dark-950 font-semibold text-sm hover:opacity-90 active:scale-95 transition shrink-0">Charger</button>
|
| 114 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
<div class="flex gap-2">
|
| 116 |
<button onclick="clearFrame()"
|
| 117 |
class="flex-1 py-2 rounded-lg border border-glass-border text-dark-300 text-xs hover:bg-white/5 transition">Effacer</button>
|
|
@@ -126,6 +137,7 @@
|
|
| 126 |
</div>
|
| 127 |
<div class="space-y-2 text-xs">
|
| 128 |
<div class="flex items-center gap-2"><span class="w-2 h-2 rounded-full bg-neon-green animate-pulse"></span><span class="text-dark-300">Proxy CORS: actif</span></div>
|
|
|
|
| 129 |
<div class="flex items-center gap-2"><span class="w-2 h-2 rounded-full bg-neon-green animate-pulse"></span><span class="text-dark-300">Historique & cookies: non sauvegardés</span></div>
|
| 130 |
<div class="flex items-center gap-2"><span class="w-2 h-2 rounded-full bg-neon-green animate-pulse"></span><span class="text-dark-300">Votre IP masquée du site cible</span></div>
|
| 131 |
</div>
|
|
@@ -180,6 +192,73 @@
|
|
| 180 |
|
| 181 |
<script>
|
| 182 |
let currentUrl = '';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
function updateClock() {
|
| 184 |
const now = new Date();
|
| 185 |
document.getElementById("navClock").textContent = now.toLocaleTimeString('fr-FR');
|
|
@@ -196,6 +275,10 @@
|
|
| 196 |
let url = input.value.trim();
|
| 197 |
if (!url) return;
|
| 198 |
if (!/^https?:\/\//i.test(url)) url = 'https://' + url;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
const proxyUrl = 'https://corsproxy.io/?' + encodeURIComponent(url);
|
| 200 |
document.getElementById('browserFrame').src = proxyUrl;
|
| 201 |
document.getElementById('placeholder').classList.add('hidden');
|
|
@@ -204,6 +287,37 @@
|
|
| 204 |
showToast('Page chargée via proxy');
|
| 205 |
}
|
| 206 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 207 |
function clearFrame() {
|
| 208 |
document.getElementById('browserFrame').src = 'about:blank';
|
| 209 |
document.getElementById('placeholder').classList.remove('hidden');
|
|
|
|
| 112 |
<button onclick="loadPage()"
|
| 113 |
class="px-4 py-2 rounded-lg bg-gradient-to-r from-neon-cyan to-neon-blue text-dark-950 font-semibold text-sm hover:opacity-90 active:scale-95 transition shrink-0">Charger</button>
|
| 114 |
</div>
|
| 115 |
+
<div class="flex gap-2 mb-3">
|
| 116 |
+
<label class="flex items-center gap-2 cursor-pointer">
|
| 117 |
+
<input type="checkbox" id="mutationToggle" onchange="toggleMutation()" class="w-4 h-4 rounded accent-neon-cyan">
|
| 118 |
+
<span class="text-xs text-dark-300">Mutation d'identité (via MCP)</span>
|
| 119 |
+
</label>
|
| 120 |
+
</div>
|
| 121 |
+
<div id="mutationPanel" class="hidden space-y-2 mb-3 pt-3 border-t border-glass-border">
|
| 122 |
+
<div class="text-[10px] text-dark-400">Identité actuelle :</div>
|
| 123 |
+
<div class="text-[11px] font-mono text-neon-cyan truncate" id="currentIdentity">—</div>
|
| 124 |
+
<button onclick="mutateIdentity()" class="text-xs text-neon-purple hover:text-neon-purple/70 transition">🔄 Nouvelle identité</button>
|
| 125 |
+
</div>
|
| 126 |
<div class="flex gap-2">
|
| 127 |
<button onclick="clearFrame()"
|
| 128 |
class="flex-1 py-2 rounded-lg border border-glass-border text-dark-300 text-xs hover:bg-white/5 transition">Effacer</button>
|
|
|
|
| 137 |
</div>
|
| 138 |
<div class="space-y-2 text-xs">
|
| 139 |
<div class="flex items-center gap-2"><span class="w-2 h-2 rounded-full bg-neon-green animate-pulse"></span><span class="text-dark-300">Proxy CORS: actif</span></div>
|
| 140 |
+
<div class="flex items-center gap-2"><span class="w-2 h-2 rounded-full bg-neon-green animate-pulse"></span><span class="text-dark-300">Mutation d'identité: <span id="mutationStatus">désactivée</span></span></div>
|
| 141 |
<div class="flex items-center gap-2"><span class="w-2 h-2 rounded-full bg-neon-green animate-pulse"></span><span class="text-dark-300">Historique & cookies: non sauvegardés</span></div>
|
| 142 |
<div class="flex items-center gap-2"><span class="w-2 h-2 rounded-full bg-neon-green animate-pulse"></span><span class="text-dark-300">Votre IP masquée du site cible</span></div>
|
| 143 |
</div>
|
|
|
|
| 192 |
|
| 193 |
<script>
|
| 194 |
let currentUrl = '';
|
| 195 |
+
let mutationEnabled = false;
|
| 196 |
+
let currentIdentity = null;
|
| 197 |
+
|
| 198 |
+
const USER_AGENTS = [
|
| 199 |
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
|
| 200 |
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
|
| 201 |
+
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
|
| 202 |
+
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Mobile/15E148 Safari/604.1",
|
| 203 |
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0"
|
| 204 |
+
];
|
| 205 |
+
const ACCEPT_LANGUAGES = [
|
| 206 |
+
"en-US,en;q=0.9",
|
| 207 |
+
"fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7",
|
| 208 |
+
"de-DE,de;q=0.9,en-US;q=0.8",
|
| 209 |
+
"es-ES,es;q=0.9,en-US;q=0.8"
|
| 210 |
+
];
|
| 211 |
+
const RESOLUTIONS = ["1920x1080", "1366x768", "1440x900", "375x812", "1024x768"];
|
| 212 |
+
|
| 213 |
+
function generateIdentity() {
|
| 214 |
+
currentIdentity = {
|
| 215 |
+
ua: USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)],
|
| 216 |
+
lang: ACCEPT_LANGUAGES[Math.floor(Math.random() * ACCEPT_LANGUAGES.length)],
|
| 217 |
+
res: RESOLUTIONS[Math.floor(Math.random() * RESOLUTIONS.length)]
|
| 218 |
+
};
|
| 219 |
+
document.getElementById('currentIdentity').textContent = currentIdentity.ua.substring(0, 50) + "…";
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
function toggleMutation() {
|
| 223 |
+
mutationEnabled = document.getElementById('mutationToggle').checked;
|
| 224 |
+
const panel = document.getElementById('mutationPanel');
|
| 225 |
+
const status = document.getElementById('mutationStatus');
|
| 226 |
+
if (mutationEnabled) {
|
| 227 |
+
panel.classList.remove('hidden');
|
| 228 |
+
generateIdentity();
|
| 229 |
+
status.textContent = 'active';
|
| 230 |
+
status.className = 'text-neon-cyan';
|
| 231 |
+
// Check MCP availability
|
| 232 |
+
checkMCPAvailability();
|
| 233 |
+
} else {
|
| 234 |
+
panel.classList.add('hidden');
|
| 235 |
+
status.textContent = 'désactivée';
|
| 236 |
+
status.className = 'text-dark-300';
|
| 237 |
+
}
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
function mutateIdentity() {
|
| 241 |
+
if (!mutationEnabled) return;
|
| 242 |
+
generateIdentity();
|
| 243 |
+
showToast('Identité mutée');
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
async function checkMCPAvailability() {
|
| 247 |
+
// Try to fetch /health on MCP endpoint
|
| 248 |
+
const endpoint = getMCPEndpoint();
|
| 249 |
+
try {
|
| 250 |
+
const resp = await fetch(endpoint + '/health');
|
| 251 |
+
if (resp.ok) {
|
| 252 |
+
showToast('MCP détecté — mutation complète disponible');
|
| 253 |
+
}
|
| 254 |
+
} catch(e) { /* MCP inaccessible, mutation partielle (side-client seule) */ }
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
function getMCPEndpoint() {
|
| 258 |
+
// Use the same endpoint as agent.html (par défaut http://localhost:5000)
|
| 259 |
+
return 'http://localhost:5000'; // Peut être rendu configurable plus tard
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
function updateClock() {
|
| 263 |
const now = new Date();
|
| 264 |
document.getElementById("navClock").textContent = now.toLocaleTimeString('fr-FR');
|
|
|
|
| 275 |
let url = input.value.trim();
|
| 276 |
if (!url) return;
|
| 277 |
if (!/^https?:\/\//i.test(url)) url = 'https://' + url;
|
| 278 |
+
if (mutationEnabled) {
|
| 279 |
+
loadPageWithMutation(url);
|
| 280 |
+
return;
|
| 281 |
+
}
|
| 282 |
const proxyUrl = 'https://corsproxy.io/?' + encodeURIComponent(url);
|
| 283 |
document.getElementById('browserFrame').src = proxyUrl;
|
| 284 |
document.getElementById('placeholder').classList.add('hidden');
|
|
|
|
| 287 |
showToast('Page chargée via proxy');
|
| 288 |
}
|
| 289 |
|
| 290 |
+
async function loadPageWithMutation(url) {
|
| 291 |
+
const frame = document.getElementById('browserFrame');
|
| 292 |
+
document.getElementById('placeholder').classList.add('hidden');
|
| 293 |
+
document.getElementById('currentUrlDisplay').textContent = url;
|
| 294 |
+
document.getElementById('proxyLabel').classList.remove('hidden');
|
| 295 |
+
// Tentative via MCP
|
| 296 |
+
try {
|
| 297 |
+
const resp = await fetch(getMCPEndpoint() + '/browser', {
|
| 298 |
+
method: 'POST',
|
| 299 |
+
headers: { 'Content-Type': 'application/json' },
|
| 300 |
+
body: JSON.stringify({ url })
|
| 301 |
+
});
|
| 302 |
+
if (resp.ok) {
|
| 303 |
+
const data = await resp.json();
|
| 304 |
+
if (data.status === 200 && data.content) {
|
| 305 |
+
// Injecter le HTML muté dans l'iframe via srcdoc
|
| 306 |
+
frame.srcdoc = data.content;
|
| 307 |
+
frame.src = 'about:blank'; // Nettoyer l'URL de l'iframe
|
| 308 |
+
document.getElementById('currentIdentity').textContent = data.identity_used.substring(0, 50) + "…";
|
| 309 |
+
showToast('Page chargée avec mutation d\'identité ✓');
|
| 310 |
+
return;
|
| 311 |
+
}
|
| 312 |
+
}
|
| 313 |
+
} catch(e) {}
|
| 314 |
+
// Fallback : proxy classique mais avertir
|
| 315 |
+
showToast('Mutation impossible sans serveur MCP, passage en mode proxy simple');
|
| 316 |
+
const proxyUrl = 'https://corsproxy.io/?' + encodeURIComponent(url);
|
| 317 |
+
frame.src = proxyUrl;
|
| 318 |
+
frame.srcdoc = ''; // Effacer srcdoc si présent
|
| 319 |
+
}
|
| 320 |
+
|
| 321 |
function clearFrame() {
|
| 322 |
document.getElementById('browserFrame').src = 'about:blank';
|
| 323 |
document.getElementById('placeholder').classList.remove('hidden');
|