stremio-sekai/video_proxy_server.py
2025-10-31 19:03:17 +01:00

332 lines
11 KiB
Python

"""
Serveur proxy pour contourner la protection Referer de sekai.one
Permet d'accéder aux vidéos via une URL proxy
Usage:
python video_proxy_server.py
Puis accéder à:
http://localhost:8080/proxy?url=https://17.mugiwara.xyz/op/saga-7/hd/527.mp4
"""
from flask import Flask, request, Response, stream_with_context, jsonify
from flask_cors import CORS
import requests
from urllib.parse import unquote
import re
from utils.logger import setup_logger
logger = setup_logger(__name__)
app = Flask(__name__)
CORS(app) # Permettre les requêtes cross-origin
# Headers pour contourner la protection Referer
PROXY_HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36',
'Accept': '*/*',
'Accept-Language': 'fr-FR,fr;q=0.9',
'Referer': 'https://sekai.one/', # ← CLÉ : Le Referer qui permet l'accès
'Origin': 'https://sekai.one',
'Sec-Fetch-Dest': 'video',
'Sec-Fetch-Mode': 'no-cors',
'Sec-Fetch-Site': 'cross-site',
}
@app.route('/')
def index():
"""Page d'accueil avec instructions"""
return """
<!DOCTYPE html>
<html>
<head>
<title>Sekai Video Proxy</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
h1 { color: #333; }
code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; }
.example { background: #e8f4f8; padding: 15px; border-left: 4px solid #0066cc; margin: 20px 0; }
.warning { background: #fff3cd; padding: 15px; border-left: 4px solid #ffc107; margin: 20px 0; }
</style>
</head>
<body>
<h1>🎬 Sekai Video Proxy Server</h1>
<p>Serveur proxy pour contourner la protection Referer de sekai.one</p>
<h2>📖 Utilisation</h2>
<div class="example">
<strong>Format de l'URL :</strong><br>
<code>http://localhost:8080/proxy?url=[VIDEO_URL]</code>
</div>
<h3>Exemple pour One Piece Episode 527 :</h3>
<div class="example">
<strong>URL complète :</strong><br>
<code>http://localhost:8080/proxy?url=https://17.mugiwara.xyz/op/saga-7/hd/527.mp4</code>
<br><br>
<a href="/proxy?url=https://17.mugiwara.xyz/op/saga-7/hd/527.mp4" target="_blank">
🎬 Tester cet exemple
</a>
</div>
<h3>Intégration dans un lecteur vidéo :</h3>
<div class="example">
<pre>&lt;video controls width="640" height="360"&gt;
&lt;source src="http://localhost:8080/proxy?url=https://17.mugiwara.xyz/op/saga-7/hd/527.mp4" type="video/mp4"&gt;
&lt;/video&gt;</pre>
</div>
<h3>Télécharger avec wget/curl :</h3>
<div class="example">
<code>wget "http://localhost:8080/proxy?url=https://17.mugiwara.xyz/op/saga-7/hd/527.mp4" -O episode_527.mp4</code>
<br><br>
<code>curl "http://localhost:8080/proxy?url=https://17.mugiwara.xyz/op/saga-7/hd/527.mp4" -o episode_527.mp4</code>
</div>
<div class="warning">
⚠️ <strong>Avertissement :</strong> Ce serveur est destiné à des fins de bug bounty et éducatives uniquement.
</div>
<h2>📊 Endpoints disponibles</h2>
<ul>
<li><code>/proxy?url=[URL]</code> - Proxy vidéo avec streaming</li>
<li><code>/download?url=[URL]</code> - Téléchargement direct</li>
<li><code>/info?url=[URL]</code> - Informations sur la vidéo</li>
<li><code>/health</code> - Status du serveur</li>
</ul>
</body>
</html>
"""
@app.route('/health')
def health():
"""Endpoint de santé pour vérifier que le serveur fonctionne"""
return jsonify({
"status": "ok",
"service": "sekai-video-proxy",
"version": "1.0.0"
})
@app.route('/info')
def video_info():
"""Récupère les informations sur une vidéo sans la télécharger"""
video_url = request.args.get('url')
if not video_url:
return jsonify({"error": "Paramètre 'url' manquant"}), 400
video_url = unquote(video_url)
try:
# Faire une requête HEAD pour obtenir les métadonnées
response = requests.head(video_url, headers=PROXY_HEADERS, timeout=10)
info = {
"url": video_url,
"status_code": response.status_code,
"accessible": response.status_code == 200,
"content_type": response.headers.get('Content-Type'),
"content_length": response.headers.get('Content-Length'),
"content_length_mb": round(int(response.headers.get('Content-Length', 0)) / (1024 * 1024), 2) if response.headers.get('Content-Length') else None,
"server": response.headers.get('Server'),
"accept_ranges": response.headers.get('Accept-Ranges'),
"proxy_url": f"{request.url_root}proxy?url={video_url}"
}
return jsonify(info)
except Exception as e:
logger.error(f"Erreur lors de la récupération des infos: {str(e)}")
return jsonify({
"error": str(e),
"url": video_url
}), 500
@app.route('/proxy')
def proxy_video():
"""
Endpoint principal de proxy vidéo avec support du streaming
Supporte les Range requests pour le seeking dans la vidéo
"""
video_url = request.args.get('url')
if not video_url:
return jsonify({"error": "Paramètre 'url' manquant. Utilisez: /proxy?url=[VIDEO_URL]"}), 400
# Décoder l'URL si elle est encodée
video_url = unquote(video_url)
# Valider l'URL (sécurité)
if not video_url.startswith(('http://', 'https://')):
return jsonify({"error": "URL invalide"}), 400
logger.info(f"Proxying video: {video_url}")
try:
# Copier les headers de la requête client (notamment Range pour le seeking)
proxy_headers = PROXY_HEADERS.copy()
# Si le client demande un range spécifique (pour le seeking vidéo)
if 'Range' in request.headers:
proxy_headers['Range'] = request.headers['Range']
logger.info(f"Range request: {request.headers['Range']}")
# Faire la requête vers le serveur vidéo
response = requests.get(
video_url,
headers=proxy_headers,
stream=True, # Important : streaming pour ne pas charger tout en mémoire
timeout=30
)
# Vérifier si la requête a réussi
if response.status_code not in [200, 206]: # 200 OK ou 206 Partial Content
logger.error(f"Erreur serveur vidéo: {response.status_code}")
return jsonify({
"error": f"Le serveur vidéo a renvoyé une erreur: {response.status_code}",
"url": video_url
}), response.status_code
# Préparer les headers de réponse
response_headers = {
'Content-Type': response.headers.get('Content-Type', 'video/mp4'),
'Accept-Ranges': 'bytes',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS',
'Access-Control-Allow-Headers': 'Range',
}
# Copier les headers importants du serveur source
if 'Content-Length' in response.headers:
response_headers['Content-Length'] = response.headers['Content-Length']
if 'Content-Range' in response.headers:
response_headers['Content-Range'] = response.headers['Content-Range']
# Streamer la réponse chunk par chunk
def generate():
try:
for chunk in response.iter_content(chunk_size=8192):
if chunk:
yield chunk
except Exception as e:
logger.error(f"Erreur durant le streaming: {str(e)}")
status_code = response.status_code
logger.info(f"Streaming vidéo: {video_url} (Status: {status_code})")
return Response(
stream_with_context(generate()),
status=status_code,
headers=response_headers
)
except requests.exceptions.Timeout:
logger.error(f"Timeout lors de la connexion à {video_url}")
return jsonify({
"error": "Timeout lors de la connexion au serveur vidéo",
"url": video_url
}), 504
except Exception as e:
logger.error(f"Erreur lors du proxy: {str(e)}")
return jsonify({
"error": str(e),
"url": video_url
}), 500
@app.route('/download')
def download_video():
"""
Endpoint pour télécharger une vidéo complète
(Alternative au streaming pour téléchargement direct)
"""
video_url = request.args.get('url')
if not video_url:
return jsonify({"error": "Paramètre 'url' manquant"}), 400
video_url = unquote(video_url)
# Extraire le nom de fichier de l'URL
filename = video_url.split('/')[-1]
if not filename.endswith('.mp4'):
filename = 'video.mp4'
logger.info(f"Téléchargement: {video_url}")
try:
response = requests.get(
video_url,
headers=PROXY_HEADERS,
stream=True,
timeout=30
)
if response.status_code != 200:
return jsonify({
"error": f"Erreur: {response.status_code}",
"url": video_url
}), response.status_code
def generate():
for chunk in response.iter_content(chunk_size=8192):
if chunk:
yield chunk
headers = {
'Content-Type': 'video/mp4',
'Content-Disposition': f'attachment; filename="{filename}"',
'Content-Length': response.headers.get('Content-Length', ''),
'Access-Control-Allow-Origin': '*',
}
return Response(
stream_with_context(generate()),
headers=headers
)
except Exception as e:
logger.error(f"Erreur téléchargement: {str(e)}")
return jsonify({"error": str(e)}), 500
def main():
"""Démarrer le serveur"""
import argparse
parser = argparse.ArgumentParser(description="Serveur proxy vidéo pour sekai.one")
parser.add_argument('--host', default='0.0.0.0', help='Host (défaut: 0.0.0.0)')
parser.add_argument('--port', type=int, default=8080, help='Port (défaut: 8080)')
parser.add_argument('--debug', action='store_true', help='Mode debug')
args = parser.parse_args()
print("\n" + "="*80)
print("🎬 SEKAI VIDEO PROXY SERVER")
print("="*80)
print(f"\n✓ Serveur démarré sur http://{args.host}:{args.port}")
print(f"\n📖 Documentation : http://localhost:{args.port}/")
print(f"\n🎬 Exemple d'utilisation :")
print(f" http://localhost:{args.port}/proxy?url=https://17.mugiwara.xyz/op/saga-7/hd/527.mp4")
print("\n" + "="*80 + "\n")
app.run(
host=args.host,
port=args.port,
debug=args.debug,
threaded=True # Support pour plusieurs connexions simultanées
)
if __name__ == '__main__':
main()