StreamingCommunity/server.py
2024-12-10 12:07:01 +01:00

594 lines
20 KiB
Python

import os
import logging
import shutil
import datetime
from urllib.parse import urlparse
from pymongo import MongoClient
from urllib.parse import unquote
from flask_cors import CORS
from flask import Flask, jsonify, request
from flask import send_from_directory
# Util
from StreamingCommunity.Util._jsonConfig import config_manager
# Internal
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
from StreamingCommunity.Api.Site.streamingcommunity.api import get_version_and_domain, search_titles, get_infoSelectTitle, get_infoSelectSeason
from StreamingCommunity.Api.Site.streamingcommunity.film import download_film
from StreamingCommunity.Api.Site.streamingcommunity.series import download_video
from StreamingCommunity.Api.Site.streamingcommunity.util.ScrapeSerie import ScrapeSerie
# Player
from StreamingCommunity.Api.Player.vixcloud import VideoSource
# Variable
app = Flask(__name__)
CORS(app)
version, domain = get_version_and_domain()
season_name = None
scrape_serie = ScrapeSerie("streamingcommunity")
video_source = VideoSource("streamingcommunity", True)
DOWNLOAD_DIRECTORY = os.getcwd()
client = MongoClient(config_manager.get("EXTRA", "mongodb"))
db = client[config_manager.get("EXTRA", "database")]
watchlist_collection = db['watchlist']
downloads_collection = db['downloads']
# ---------- SITE API ------------
@app.route('/')
def index():
"""
Health check endpoint to confirm server is operational.
Returns:
str: Operational status message
"""
logging.info("Health check endpoint accessed")
return 'Server is operational'
@app.route('/api/search', methods=['GET'])
def get_list_search():
"""
Search for titles based on query parameter.
Returns:
JSON response with search results or error message
"""
try:
query = request.args.get('q')
if not query:
logging.warning("Search request without query parameter")
return jsonify({'error': 'Missing query parameter'}), 400
result = search_titles(query, domain)
logging.info(f"Search performed for query: {query}")
return jsonify(result), 200
except Exception as e:
logging.error(f"Error in search: {str(e)}", exc_info=True)
return jsonify({'error': 'Internal server error'}), 500
@app.route('/api/getInfo', methods=['GET'])
def get_info_title():
"""
Retrieve information for a specific title.
Returns:
JSON response with title information or error message
"""
try:
title_url = request.args.get('url')
if not title_url:
logging.warning("GetInfo request without URL parameter")
return jsonify({'error': 'Missing URL parameter'}), 400
result = get_infoSelectTitle(title_url, domain, version)
if result.get('type') == "tv":
global season_name, scrape_serie, video_source
season_name = result.get('slug')
scrape_serie.setup(
version=version,
media_id=int(result.get('id')),
series_name=result.get('slug')
)
video_source.setup(result.get('id'))
logging.info(f"TV series info retrieved: {season_name}")
return jsonify(result), 200
except Exception as e:
logging.error(f"Error retrieving title info: {str(e)}", exc_info=True)
return jsonify({'error': 'Failed to retrieve title information'}), 500
@app.route('/api/getInfoSeason', methods=['GET'])
def get_info_season():
"""
Retrieve season information for a specific title.
Returns:
JSON response with season information or error message
"""
try:
title_url = request.args.get('url')
number_season = request.args.get('n')
if not title_url or not number_season:
logging.warning("GetInfoSeason request with missing parameters")
return jsonify({'error': 'Missing URL or season number'}), 400
result = get_infoSelectSeason(title_url, number_season, domain, version)
logging.info(f"Season info retrieved for season {number_season}")
return jsonify(result), 200
except Exception as e:
logging.error(f"Error retrieving season info: {str(e)}", exc_info=True)
return jsonify({'error': 'Failed to retrieve season information'}), 500
@app.route('/api/getdomain', methods=['GET'])
def get_domain():
"""
Retrieve current domain and version.
Returns:
JSON response with domain and version
"""
try:
global version, domain
version, domain = get_version_and_domain()
logging.info(f"Domain retrieved: {domain}, Version: {version}")
return jsonify({'domain': domain, 'version': version}), 200
except Exception as e:
logging.error(f"Error retrieving domain: {str(e)}", exc_info=True)
return jsonify({'error': 'Failed to retrieve domain information'}), 500
# ---------- DOWNLOAD API ------------
@app.route('/downloadFilm', methods=['GET'])
def call_download_film():
"""
Download a film by its ID and slug.
Returns:
JSON response with download path or error message
"""
try:
film_id = request.args.get('id')
slug = request.args.get('slug')
if not film_id or not slug:
logging.warning("Download film request with missing parameters")
return jsonify({'error': 'Missing film ID or slug'}), 400
item_media = MediaItem(**{'id': film_id, 'slug': slug})
path_download = download_film(item_media)
download_data = {
'type': 'movie',
'id': film_id,
'slug': slug,
'path': path_download,
'timestamp': datetime.datetime.now(datetime.timezone.utc)
}
downloads_collection.insert_one(download_data)
logging.info(f"Film downloaded: {slug}")
return jsonify({'path': path_download}), 200
except Exception as e:
logging.error(f"Error downloading film: {str(e)}", exc_info=True)
return jsonify({'error': 'Failed to download film'}), 500
@app.route('/downloadEpisode', methods=['GET'])
def call_download_episode():
"""
Download a specific TV series episode.
Returns:
JSON response with download path or error message
"""
try:
season_number = request.args.get('n_s')
episode_number = request.args.get('n_ep')
if not season_number or not episode_number:
logging.warning("Download episode request with missing parameters")
return jsonify({'error': 'Missing season or episode number'}), 400
season_number = int(season_number)
episode_number = int(episode_number)
scrape_serie.collect_title_season(season_number)
path_download = download_video(
season_name,
season_number,
episode_number,
scrape_serie,
video_source
)
download_data = {
'type': 'tv',
'id': scrape_serie.media_id,
'slug': scrape_serie.series_name,
'n_s': season_number,
'n_ep': episode_number,
'path': path_download,
'timestamp': datetime.datetime.now(datetime.timezone.utc)
}
downloads_collection.insert_one(download_data)
logging.info(f"Episode downloaded: S{season_number}E{episode_number}")
return jsonify({'path': path_download}), 200
except ValueError:
logging.error("Invalid season or episode number format")
return jsonify({'error': 'Invalid season or episode number'}), 400
except Exception as e:
logging.error(f"Error downloading episode: {str(e)}", exc_info=True)
return jsonify({'error': 'Failed to download episode'}), 500
@app.route('/downloaded/<path:filename>', methods=['GET'])
def serve_downloaded_file(filename):
"""
Serve downloaded files with proper URL decoding and error handling.
Returns:
Downloaded file or error message
"""
try:
# URL decode the filename
decoded_filename = unquote(filename)
logging.debug(f"Requested file: {decoded_filename}")
# Construct full file path
file_path = os.path.join(DOWNLOAD_DIRECTORY, decoded_filename)
logging.debug(f"Full file path: {file_path}")
# Verify file exists
if not os.path.isfile(file_path):
logging.warning(f"File not found: {decoded_filename}")
return jsonify({'error': 'File not found'}), 404
# Serve the file
return send_from_directory(DOWNLOAD_DIRECTORY, decoded_filename, as_attachment=False)
except Exception as e:
logging.error(f"Error serving file: {str(e)}", exc_info=True)
return jsonify({'error': 'Internal server error'}), 500
# ---------- WATCHLIST MONGO ------------
@app.route('/api/addWatchlist', methods=['POST'])
def add_to_watchlist():
title_name = request.json.get('name')
title_url = request.json.get('url')
season = request.json.get('season')
if title_url and season:
existing_item = watchlist_collection.find_one({'name': title_name, 'url': title_url, 'season': season})
if existing_item:
return jsonify({'message': 'Il titolo è già nella watchlist'}), 400
watchlist_collection.insert_one({
'name': title_name,
'title_url': title_url,
'season': season,
'added_on': datetime.datetime.utcnow()
})
return jsonify({'message': 'Titolo aggiunto alla watchlist'}), 200
else:
return jsonify({'message': 'Missing title_url or season'}), 400
@app.route('/api/updateTitleWatchlist', methods=['POST'])
def update_title_watchlist():
print(request.json)
title_url = request.json.get('url')
new_season = request.json.get('season')
if title_url is not None and new_season is not None:
result = watchlist_collection.update_one(
{'title_url': title_url},
{'$set': {'season': new_season}}
)
if result.matched_count == 0:
return jsonify({'message': 'Titolo non trovato nella watchlist'}), 404
if result.modified_count == 0:
return jsonify({'message': 'La stagione non è cambiata'}), 200
return jsonify({'message': 'Stagione aggiornata con successo'}), 200
else:
return jsonify({'message': 'Missing title_url or season'}), 400
@app.route('/api/removeWatchlist', methods=['POST'])
def remove_from_watchlist():
title_name = request.json.get('name')
if title_name:
result = watchlist_collection.delete_one({'name': title_name})
if result.deleted_count == 1:
return jsonify({'message': 'Titolo rimosso dalla watchlist'}), 200
else:
return jsonify({'message': 'Titolo non trovato nella watchlist'}), 404
else:
return jsonify({'message': 'Missing title_url or season'}), 400
@app.route('/api/getWatchlist', methods=['GET'])
def get_watchlist():
watchlist_items = list(watchlist_collection.find({}, {'_id': 0}))
if watchlist_items:
return jsonify(watchlist_items), 200
else:
return jsonify({'message': 'La watchlist è vuota'}), 200
@app.route('/api/checkWatchlist', methods=['GET'])
def get_newSeason():
title_newSeasons = []
watchlist_items = list(watchlist_collection.find({}, {'_id': 0}))
if not watchlist_items:
return jsonify({'message': 'La watchlist è vuota'}), 200
for item in watchlist_items:
title_url = item.get('title_url')
if not title_url:
continue
try:
parsed_url = urlparse(title_url)
hostname = parsed_url.hostname
domain_part = hostname.split('.')[1]
new_url = title_url.replace(domain_part, domain)
result = get_infoSelectTitle(new_url, domain, version)
if not result or 'season_count' not in result:
continue
number_season = result.get("season_count")
if number_season > item.get("season"):
title_newSeasons.append({
'title_url': item.get('title_url'),
'name': item.get('name'),
'season': int(number_season),
'nNewSeason': int(number_season) - int(item.get("season"))
})
except Exception as e:
print(f"Errore nel recuperare informazioni per {item.get('title_url')}: {e}")
if title_newSeasons:
return jsonify(title_newSeasons), 200
else:
return jsonify({'message': 'Nessuna nuova stagione disponibile'}), 200
# ---------- DOWNLOAD MONGO ------------
def ensure_collections_exist(db):
"""
Ensures that the required collections exist in the database.
If they do not exist, they are created.
Args:
db: The MongoDB database object.
"""
required_collections = ['watchlist', 'downloads']
existing_collections = db.list_collection_names()
for collection_name in required_collections:
if collection_name not in existing_collections:
# Creazione della collezione
db.create_collection(collection_name)
logging.info(f"Created missing collection: {collection_name}")
else:
logging.info(f"Collection already exists: {collection_name}")
@app.route('/downloads', methods=['GET'])
def fetch_all_downloads():
"""
Endpoint to fetch all downloads.
"""
try:
downloads = list(downloads_collection.find({}, {'_id': 0}))
return jsonify(downloads), 200
except Exception as e:
logging.error(f"Error fetching all downloads: {str(e)}")
return []
@app.route('/deleteEpisode', methods=['DELETE'])
def remove_episode():
"""
Endpoint to delete a specific episode and its file.
"""
try:
series_id = request.args.get('id')
season_number = request.args.get('season')
episode_number = request.args.get('episode')
if not series_id or not season_number or not episode_number:
return jsonify({'error': 'Missing parameters (id, season, episode)'}), 400
try:
series_id = int(series_id)
season_number = int(season_number)
episode_number = int(episode_number)
except ValueError:
return jsonify({'error': 'Invalid season or episode number'}), 400
# Trova il percorso del file
episode = downloads_collection.find_one({
'type': 'tv',
'id': series_id,
'n_s': season_number,
'n_ep': episode_number
}, {'_id': 0, 'path': 1})
if not episode or 'path' not in episode:
return jsonify({'error': 'Episode not found'}), 404
file_path = episode['path']
# Elimina il file fisico
try:
if os.path.exists(file_path):
os.remove(file_path)
logging.info(f"Deleted episode file: {file_path}")
else:
logging.warning(f"Episode file not found: {file_path}")
except Exception as e:
logging.error(f"Error deleting episode file: {str(e)}")
# Rimuovi l'episodio dal database
result = downloads_collection.delete_one({
'type': 'tv',
'id': series_id,
'n_s': season_number,
'n_ep': episode_number
})
if result.deleted_count > 0:
return jsonify({'success': True}), 200
else:
return jsonify({'error': 'Failed to delete episode from database'}), 500
except Exception as e:
logging.error(f"Error deleting episode: {str(e)}")
return jsonify({'error': 'Failed to delete episode'}), 500
@app.route('/deleteMovie', methods=['DELETE'])
def remove_movie():
"""
Endpoint to delete a specific movie, its file, and its parent folder if empty.
"""
try:
movie_id = request.args.get('id')
if not movie_id:
return jsonify({'error': 'Missing movie ID'}), 400
# Trova il percorso del file
movie = downloads_collection.find_one({'type': 'movie', 'id': movie_id}, {'_id': 0, 'path': 1})
if not movie or 'path' not in movie:
return jsonify({'error': 'Movie not found'}), 404
file_path = movie['path']
parent_folder = os.path.dirname(file_path)
# Elimina il file fisico
try:
if os.path.exists(file_path):
os.remove(file_path)
logging.info(f"Deleted movie file: {file_path}")
else:
logging.warning(f"Movie file not found: {file_path}")
except Exception as e:
logging.error(f"Error deleting movie file: {str(e)}")
# Elimina la cartella superiore se vuota
try:
if os.path.exists(parent_folder) and not os.listdir(parent_folder):
os.rmdir(parent_folder)
logging.info(f"Deleted empty parent folder: {parent_folder}")
except Exception as e:
logging.error(f"Error deleting parent folder: {str(e)}")
# Rimuovi il film dal database
result = downloads_collection.delete_one({'type': 'movie', 'id': movie_id})
if result.deleted_count > 0:
return jsonify({'success': True}), 200
else:
return jsonify({'error': 'Failed to delete movie from database'}), 500
except Exception as e:
logging.error(f"Error deleting movie: {str(e)}")
return jsonify({'error': 'Failed to delete movie'}), 500
@app.route('/moviePath', methods=['GET'])
def fetch_movie_path():
"""
Endpoint to fetch the path of a specific movie.
"""
try:
movie_id = int(request.args.get('id'))
if not movie_id:
return jsonify({'error': 'Missing movie ID'}), 400
movie = downloads_collection.find_one({'type': 'movie', 'id': movie_id}, {'_id': 0, 'path': 1})
if movie and 'path' in movie:
return jsonify({'path': movie['path']}), 200
else:
return jsonify({'error': 'Movie not found'}), 404
except Exception as e:
logging.error(f"Error fetching movie path: {str(e)}")
return jsonify({'error': 'Failed to fetch movie path'}), 500
@app.route('/episodePath', methods=['GET'])
def fetch_episode_path():
"""
Endpoint to fetch the path of a specific episode.
"""
try:
series_id = request.args.get('id')
season_number = request.args.get('season')
episode_number = request.args.get('episode')
if not series_id or not season_number or not episode_number:
return jsonify({'error': 'Missing parameters (id, season, episode)'}), 400
try:
series_id = int(series_id)
season_number = int(season_number)
episode_number = int(episode_number)
except ValueError:
return jsonify({'error': 'Invalid season or episode number'}), 400
episode = downloads_collection.find_one({
'type': 'tv',
'id': series_id,
'n_s': season_number,
'n_ep': episode_number
}, {'_id': 0, 'path': 1})
if episode and 'path' in episode:
return jsonify({'path': episode['path']}), 200
else:
return jsonify({'error': 'Episode not found'}), 404
except Exception as e:
logging.error(f"Error fetching episode path: {str(e)}")
return jsonify({'error': 'Failed to fetch episode path'}), 500
if __name__ == '__main__':
ensure_collections_exist(db)
app.run(debug=True, port=1234, threaded=True)