332 lines
11 KiB
Python
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><video controls width="640" height="360">
|
|
<source src="http://localhost:8080/proxy?url=https://17.mugiwara.xyz/op/saga-7/hd/527.mp4" type="video/mp4">
|
|
</video></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()
|
|
|