Files
weval-l99/claude-web-api.py
2026-04-13 12:43:21 +02:00

150 lines
5.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""
WEVIA Claude Web — Navigateur headless pour claude.ai
Utilise le forfait Claude Max via Playwright
Port: 8903
PRÉREQUIS: Se connecter à claude.ai via Blade pour sauvegarder les cookies
"""
import json, os, time, threading, traceback
from http.server import HTTPServer, BaseHTTPRequestHandler
PORT = 8903
SESSION_DIR = '/opt/weval-l99/claude-session'
LOCK = threading.Lock()
browser = None
context = None
page = None
ready = False
last_error = ''
def init_browser():
global browser, context, page, ready, last_error
try:
from playwright.sync_api import sync_playwright
pw = sync_playwright().start()
browser = pw.chromium.launch(
headless=True,
args=['--no-sandbox', '--disable-gpu', '--disable-blink-features=AutomationControlled']
)
os.makedirs(SESSION_DIR, exist_ok=True)
state_file = os.path.join(SESSION_DIR, 'state.json')
ctx_args = {
'viewport': {'width': 1280, 'height': 900},
'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/131.0.0.0 Safari/537.36',
}
if os.path.exists(state_file):
ctx_args['storage_state'] = state_file
context = browser.new_context(**ctx_args)
page = context.new_page()
page.goto('https://claude.ai/new', timeout=20000, wait_until='domcontentloaded')
page.wait_for_timeout(3000)
# Check if we're on chat page
textarea = page.query_selector('div[contenteditable], textarea')
if textarea:
ready = True
last_error = ''
else:
ready = False
last_error = f"No textarea found. URL={page.url} Title={page.title()}"
except Exception as e:
last_error = str(e)[:200]
ready = False
def send_message(msg):
global page, ready, last_error
if not ready:
return None, "Not ready: " + last_error
with LOCK:
try:
# Find input
textarea = page.query_selector('div[contenteditable="true"]') or page.query_selector('textarea')
if not textarea:
return None, "No textarea found"
textarea.click()
textarea.fill(msg)
page.wait_for_timeout(500)
# Click send button
send_btn = page.query_selector('button[aria-label="Send Message"]') or page.query_selector('button[type="submit"]')
if send_btn:
send_btn.click()
else:
page.keyboard.press('Enter')
# Wait for response
time.sleep(2)
for _ in range(30): # 30 seconds max
page.wait_for_timeout(1000)
# Check if response is complete (no loading indicator)
loading = page.query_selector('[class*="loading"]') or page.query_selector('[class*="streaming"]')
if not loading:
break
# Get last assistant message
messages = page.query_selector_all('[data-testid*="message"], .prose, [class*="response"]')
if messages:
last_msg = messages[-1].inner_text()
return last_msg, None
return None, "No response found"
except Exception as e:
return None, str(e)[:200]
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == '/health':
data = {"status": "ready" if ready else "not_ready", "port": PORT}
if last_error: data["error"] = last_error
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(data).encode())
else:
self.send_response(404)
self.end_headers()
def do_POST(self):
if self.path == '/chat':
length = int(self.headers.get('Content-Length', 0))
body = json.loads(self.rfile.read(length)) if length else {}
msg = body.get('message', '')
if not msg:
self.send_response(400)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps({"error": "No message"}).encode())
return
content, error = send_message(msg)
self.send_response(200 if content else 503)
self.send_header('Content-Type', 'application/json')
self.end_headers()
result = {"content": content, "provider": "Claude.ai Max", "model": "claude-opus-4"} if content else {"error": error}
self.wfile.write(json.dumps(result).encode())
elif self.path == '/save_cookies':
try:
context.storage_state(path=os.path.join(SESSION_DIR, 'state.json'))
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps({"ok": True}).encode())
except Exception as e:
self.send_response(500)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps({"error": str(e)}).encode())
def log_message(self, format, *args): pass
if __name__ == '__main__':
threading.Thread(target=init_browser, daemon=True).start()
print(f"Claude Web API on port {PORT}")
HTTPServer(('127.0.0.1', PORT), Handler).serve_forever()