mirror of
https://github.com/Arrowar/StreamingCommunity.git
synced 2025-06-06 19:45:24 +00:00
Migrate to fastapi, add button download serie
This commit is contained in:
parent
cff39796fc
commit
5969fb2f00
4
.gitignore
vendored
4
.gitignore
vendored
@ -23,6 +23,7 @@ share/python-wheels/
|
|||||||
.installed.cfg
|
.installed.cfg
|
||||||
*.egg
|
*.egg
|
||||||
MANIFEST
|
MANIFEST
|
||||||
|
*.pyc
|
||||||
|
|
||||||
# PyInstaller
|
# PyInstaller
|
||||||
*.manifest
|
*.manifest
|
||||||
@ -34,6 +35,7 @@ note.txt
|
|||||||
list_proxy.txt
|
list_proxy.txt
|
||||||
cmd.txt
|
cmd.txt
|
||||||
downloaded_files
|
downloaded_files
|
||||||
|
key.t
|
||||||
|
|
||||||
# Cache
|
# Cache
|
||||||
__pycache__/
|
__pycache__/
|
||||||
@ -42,4 +44,4 @@ __pycache__/
|
|||||||
/client/dashboard/node_modules
|
/client/dashboard/node_modules
|
||||||
|
|
||||||
# Ignore build directory in the client dashboard to avoid committing build artifacts
|
# Ignore build directory in the client dashboard to avoid committing build artifacts
|
||||||
/client/dashboard/build
|
/client/dashboard/build
|
||||||
|
14
README.md
14
README.md
@ -1,16 +1,16 @@
|
|||||||
Per testare
|
Per testare
|
||||||
|
|
||||||
1. Installare requirements
|
1. Installare requirements
|
||||||
2. Inserire url per mongodb e creare database
|
1. Installare node js: https://nodejs.org/en/download/package-manager
|
||||||
3. Runnare server.py
|
2. Inserire url per mongodb. Pagina login: https://account.mongodb.com/account/login
|
||||||
|
3. Eseguire server.py: python server.py
|
||||||
|
|
||||||
4. Spostarsi su client\dashboard
|
4. Spostarsi su client\dashboard
|
||||||
5. Eseguire npm install, npm run build, npm install -g serve
|
5. Eseguire npm install, npm run build, npm install -g serve, serve -s build
|
||||||
|
|
||||||
Cosa da fare
|
Cosa da fare:
|
||||||
- Aggiungere documentazione
|
- Aggiungere documentazione
|
||||||
- Bottone download intera stagione
|
- Migliore player quando si clicca sul button watch.
|
||||||
- Messaggio con richiesta se scaricare le nuove stagione quando si fa il check in watchlist
|
|
||||||
- Migliore player in case watch con bottone
|
|
||||||
- Coda di download con bottone aggiungere alla coda (complessa)
|
- Coda di download con bottone aggiungere alla coda (complessa)
|
||||||
|
- Progresso di download (complesso)
|
||||||
...
|
...
|
@ -87,14 +87,6 @@ def get_infoSelectTitle(url_title: str, domain: str, version: str):
|
|||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
json_response = response.json()['props']
|
json_response = response.json()['props']
|
||||||
|
|
||||||
generes = []
|
|
||||||
for g in json_response["genres"]:
|
|
||||||
generes.append(g["name"])
|
|
||||||
|
|
||||||
trailer = None
|
|
||||||
if len(json_response['title']['trailers']) > 0:
|
|
||||||
trailer = f"https://www.youtube.com/watch?v={json_response['title']['trailers'][0]['youtube_id']}"
|
|
||||||
|
|
||||||
images = {}
|
images = {}
|
||||||
for dict_image in json_response['title'].get('images', []):
|
for dict_image in json_response['title'].get('images', []):
|
||||||
images[dict_image.get('type')] = f"https://cdn.{SITE_NAME}.{domain}/images/{dict_image.get('filename')}"
|
images[dict_image.get('type')] = f"https://cdn.{SITE_NAME}.{domain}/images/{dict_image.get('filename')}"
|
||||||
@ -106,8 +98,6 @@ def get_infoSelectTitle(url_title: str, domain: str, version: str):
|
|||||||
'plot': json_response['title']['plot'],
|
'plot': json_response['title']['plot'],
|
||||||
'type': json_response['title']['type'],
|
'type': json_response['title']['type'],
|
||||||
'season_count': json_response['title']['seasons_count'],
|
'season_count': json_response['title']['seasons_count'],
|
||||||
'generes': generes,
|
|
||||||
'trailer': trailer,
|
|
||||||
'image': images
|
'image': images
|
||||||
}
|
}
|
||||||
|
|
||||||
|
6
client/dashboard/src/components/ApiUrl.js
Normal file
6
client/dashboard/src/components/ApiUrl.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export const API_BASE_URL = "http://127.0.0.1:1234";
|
||||||
|
|
||||||
|
export const API_URL = `${API_BASE_URL}/api`;
|
||||||
|
export const SERVER_WATCHLIST_URL = `${API_BASE_URL}/server/watchlist`;
|
||||||
|
export const SERVER_PATH_URL = `${API_BASE_URL}/server/path`;
|
||||||
|
export const SERVER_DELETE_URL = `${API_BASE_URL}/server/delete`;
|
@ -3,8 +3,7 @@ import axios from 'axios';
|
|||||||
import { Container, Button, Form, InputGroup } from 'react-bootstrap';
|
import { Container, Button, Form, InputGroup } from 'react-bootstrap';
|
||||||
|
|
||||||
import SearchBar from './SearchBar.js';
|
import SearchBar from './SearchBar.js';
|
||||||
|
import { API_BASE_URL } from './ApiUrl.js';
|
||||||
const API_BASE_URL = "http://127.0.0.1:1234";
|
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
const [items, setItems] = useState([]);
|
const [items, setItems] = useState([]);
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { Container, Row, Col, Card, Button, Badge, Modal } from 'react-bootstrap';
|
import { Container, Row, Col, Card, Button, Modal } from 'react-bootstrap'; // Aggiungi Modal qui
|
||||||
import { FaTrash, FaPlay } from 'react-icons/fa';
|
import { FaTrash, FaPlay } from 'react-icons/fa';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
const API_BASE_URL = "http://127.0.0.1:1234";
|
import { SERVER_PATH_URL, SERVER_DELETE_URL, API_URL } from './ApiUrl';
|
||||||
|
|
||||||
const Downloads = () => {
|
const Downloads = () => {
|
||||||
const [downloads, setDownloads] = useState([]);
|
const [downloads, setDownloads] = useState([]);
|
||||||
@ -15,7 +14,7 @@ const Downloads = () => {
|
|||||||
// Fetch all downloads
|
// Fetch all downloads
|
||||||
const fetchDownloads = async () => {
|
const fetchDownloads = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`${API_BASE_URL}/downloads`);
|
const response = await axios.get(`${SERVER_PATH_URL}/getAll`);
|
||||||
setDownloads(response.data);
|
setDownloads(response.data);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -27,8 +26,8 @@ const Downloads = () => {
|
|||||||
// Delete a TV episode
|
// Delete a TV episode
|
||||||
const handleDeleteEpisode = async (id, season, episode) => {
|
const handleDeleteEpisode = async (id, season, episode) => {
|
||||||
try {
|
try {
|
||||||
await axios.delete(`${API_BASE_URL}/deleteEpisode`, {
|
await axios.delete(`${SERVER_DELETE_URL}/episode`, {
|
||||||
params: { id, season, episode }
|
params: { series_id: id, season_number: season, episode_number: episode }
|
||||||
});
|
});
|
||||||
fetchDownloads(); // Refresh the list
|
fetchDownloads(); // Refresh the list
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -39,8 +38,8 @@ const Downloads = () => {
|
|||||||
// Delete a movie
|
// Delete a movie
|
||||||
const handleDeleteMovie = async (id) => {
|
const handleDeleteMovie = async (id) => {
|
||||||
try {
|
try {
|
||||||
await axios.delete(`${API_BASE_URL}/deleteMovie`, {
|
await axios.delete(`${SERVER_DELETE_URL}/movie`, {
|
||||||
params: { id }
|
params: { movie_id: id }
|
||||||
});
|
});
|
||||||
fetchDownloads(); // Refresh the list
|
fetchDownloads(); // Refresh the list
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -111,13 +110,6 @@ const Downloads = () => {
|
|||||||
>
|
>
|
||||||
<FaPlay className="me-2" /> Watch
|
<FaPlay className="me-2" /> Watch
|
||||||
</Button>
|
</Button>
|
||||||
<Link
|
|
||||||
to={`/title/${movie.slug}`}
|
|
||||||
state={{ url: movie.slug }}
|
|
||||||
className="btn btn-secondary btn-sm ms-2"
|
|
||||||
>
|
|
||||||
View Details
|
|
||||||
</Link>
|
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
@ -160,13 +152,6 @@ const Downloads = () => {
|
|||||||
>
|
>
|
||||||
<FaPlay className="me-2" /> Watch
|
<FaPlay className="me-2" /> Watch
|
||||||
</Button>
|
</Button>
|
||||||
<Link
|
|
||||||
to={`/title/${slug}`}
|
|
||||||
state={{ url: slug }}
|
|
||||||
className="btn btn-secondary btn-sm ms-2"
|
|
||||||
>
|
|
||||||
View Details
|
|
||||||
</Link>
|
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
@ -181,7 +166,7 @@ const Downloads = () => {
|
|||||||
<Modal show={showPlayer} onHide={() => setShowPlayer(false)} size="lg" centered>
|
<Modal show={showPlayer} onHide={() => setShowPlayer(false)} size="lg" centered>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<video
|
<video
|
||||||
src={`http://127.0.0.1:1234/downloaded/${currentVideo}`}
|
src={`${API_URL}/downloaded/${currentVideo}`}
|
||||||
controls
|
controls
|
||||||
autoPlay
|
autoPlay
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
|
@ -4,8 +4,7 @@ import axios from 'axios';
|
|||||||
import { Container, Row, Col, Card, Spinner } from 'react-bootstrap';
|
import { Container, Row, Col, Card, Spinner } from 'react-bootstrap';
|
||||||
|
|
||||||
import SearchBar from './SearchBar.js';
|
import SearchBar from './SearchBar.js';
|
||||||
|
import { API_URL } from './ApiUrl.js';
|
||||||
const API_BASE_URL = "http://127.0.0.1:1234";
|
|
||||||
|
|
||||||
const SearchResults = () => {
|
const SearchResults = () => {
|
||||||
const [results, setResults] = useState([]);
|
const [results, setResults] = useState([]);
|
||||||
@ -20,7 +19,7 @@ const SearchResults = () => {
|
|||||||
const fetchSearchResults = async () => {
|
const fetchSearchResults = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await axios.get(`${API_BASE_URL}/api/search`, {
|
const response = await axios.get(`${API_URL}/search`, {
|
||||||
params: { q: query }
|
params: { q: query }
|
||||||
});
|
});
|
||||||
setResults(response.data);
|
setResults(response.data);
|
||||||
|
@ -5,8 +5,7 @@ import { Container, Row, Col, Image, Button, Dropdown, Modal, Alert } from 'reac
|
|||||||
import { FaDownload, FaPlay, FaPlus, FaTrash } from 'react-icons/fa';
|
import { FaDownload, FaPlay, FaPlus, FaTrash } from 'react-icons/fa';
|
||||||
|
|
||||||
import SearchBar from './SearchBar.js';
|
import SearchBar from './SearchBar.js';
|
||||||
|
import { API_BASE_URL, API_URL, SERVER_DELETE_URL, SERVER_PATH_URL, SERVER_WATCHLIST_URL} from './ApiUrl.js';
|
||||||
const API_BASE_URL = "http://127.0.0.1:1234";
|
|
||||||
|
|
||||||
const TitleDetail = () => {
|
const TitleDetail = () => {
|
||||||
const [titleDetails, setTitleDetails] = useState(null);
|
const [titleDetails, setTitleDetails] = useState(null);
|
||||||
@ -16,6 +15,7 @@ const TitleDetail = () => {
|
|||||||
const [hoveredEpisode, setHoveredEpisode] = useState(null);
|
const [hoveredEpisode, setHoveredEpisode] = useState(null);
|
||||||
const [isInWatchlist, setIsInWatchlist] = useState(false);
|
const [isInWatchlist, setIsInWatchlist] = useState(false);
|
||||||
const [downloadStatus, setDownloadStatus] = useState({});
|
const [downloadStatus, setDownloadStatus] = useState({});
|
||||||
|
const [downloadingAllSeason, setDownloadingAllSeason] = useState(false);
|
||||||
const [showPlayer, setShowPlayer] = useState(false);
|
const [showPlayer, setShowPlayer] = useState(false);
|
||||||
const [currentVideo, setCurrentVideo] = useState("");
|
const [currentVideo, setCurrentVideo] = useState("");
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@ -27,7 +27,7 @@ const TitleDetail = () => {
|
|||||||
const titleUrl = location.state?.url || location.pathname.split('/title/')[1];
|
const titleUrl = location.state?.url || location.pathname.split('/title/')[1];
|
||||||
|
|
||||||
// Fetch title information
|
// Fetch title information
|
||||||
const response = await axios.get(`${API_BASE_URL}/api/getInfo`, {
|
const response = await axios.get(`${API_URL}/getInfo`, {
|
||||||
params: { url: titleUrl }
|
params: { url: titleUrl }
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ const TitleDetail = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (titleData.type === 'tv') {
|
} else if (titleData.type === 'tv') {
|
||||||
const response = await axios.get(`${API_BASE_URL}/downloads`);
|
const response = await axios.get(`${SERVER_PATH_URL}/getAll`);
|
||||||
const downloadedEpisodes = response.data.filter(
|
const downloadedEpisodes = response.data.filter(
|
||||||
download => download.type === 'tv' && download.slug === titleData.slug
|
download => download.type === 'tv' && download.slug === titleData.slug
|
||||||
);
|
);
|
||||||
@ -92,7 +92,7 @@ const TitleDetail = () => {
|
|||||||
// Check watchlist status
|
// Check watchlist status
|
||||||
const checkWatchlistStatus = async (slug) => {
|
const checkWatchlistStatus = async (slug) => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`${API_BASE_URL}/api/getWatchlist`);
|
const response = await axios.get(`${SERVER_WATCHLIST_URL}/get`);
|
||||||
const inWatchlist = response.data.some(item => item.name === slug);
|
const inWatchlist = response.data.some(item => item.name === slug);
|
||||||
setIsInWatchlist(inWatchlist);
|
setIsInWatchlist(inWatchlist);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -104,7 +104,7 @@ const TitleDetail = () => {
|
|||||||
if (titleDetails.type === 'tv') {
|
if (titleDetails.type === 'tv') {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const seasonResponse = await axios.get(`${API_BASE_URL}/api/getInfoSeason`, {
|
const seasonResponse = await axios.get(`${API_URL}/getInfoSeason`, {
|
||||||
params: {
|
params: {
|
||||||
url: location.state?.url,
|
url: location.state?.url,
|
||||||
n: seasonNumber
|
n: seasonNumber
|
||||||
@ -123,7 +123,7 @@ const TitleDetail = () => {
|
|||||||
|
|
||||||
const handleDownloadFilm = async () => {
|
const handleDownloadFilm = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`${API_BASE_URL}/downloadFilm`, {
|
const response = await axios.get(`${API_URL}/download/film`, {
|
||||||
params: {
|
params: {
|
||||||
id: titleDetails.id,
|
id: titleDetails.id,
|
||||||
slug: titleDetails.slug
|
slug: titleDetails.slug
|
||||||
@ -146,7 +146,7 @@ const TitleDetail = () => {
|
|||||||
|
|
||||||
const handleDownloadEpisode = async (seasonNumber, episodeNumber) => {
|
const handleDownloadEpisode = async (seasonNumber, episodeNumber) => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`${API_BASE_URL}/downloadEpisode`, {
|
const response = await axios.get(`${API_URL}/download/episode`, {
|
||||||
params: {
|
params: {
|
||||||
n_s: seasonNumber,
|
n_s: seasonNumber,
|
||||||
n_ep: episodeNumber
|
n_ep: episodeNumber
|
||||||
@ -170,13 +170,52 @@ const TitleDetail = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDownloadAllSeason = async () => {
|
||||||
|
if (!titleDetails || titleDetails.type !== 'tv') return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setDownloadingAllSeason(true);
|
||||||
|
|
||||||
|
// Download all episodes in the current season
|
||||||
|
for (const episode of episodes) {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${API_URL}/download/episode`, {
|
||||||
|
params: {
|
||||||
|
n_s: selectedSeason,
|
||||||
|
n_ep: episode.number
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const videoPath = response.data.path;
|
||||||
|
|
||||||
|
// Update download status for this specific episode
|
||||||
|
setDownloadStatus(prev => ({
|
||||||
|
tv: {
|
||||||
|
...prev.tv,
|
||||||
|
[`S${selectedSeason}E${episode.number}`]: {
|
||||||
|
downloaded: true,
|
||||||
|
path: videoPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error downloading episode ${episode.number}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error downloading all season episodes:", error);
|
||||||
|
alert("Error downloading season. Some episodes might not have downloaded.");
|
||||||
|
} finally {
|
||||||
|
setDownloadingAllSeason(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleWatchVideo = async (videoPath) => {
|
const handleWatchVideo = async (videoPath) => {
|
||||||
if (!videoPath) {
|
if (!videoPath) {
|
||||||
// If no path provided, attempt to get path from downloads
|
// If no path provided, attempt to get path from downloads
|
||||||
try {
|
try {
|
||||||
let path;
|
let path;
|
||||||
if (titleDetails.type === 'movie') {
|
if (titleDetails.type === 'movie') {
|
||||||
const response = await axios.get(`${API_BASE_URL}/moviePath`, {
|
const response = await axios.get(`${SERVER_PATH_URL}/movie`, {
|
||||||
params: { id: titleDetails.id }
|
params: { id: titleDetails.id }
|
||||||
});
|
});
|
||||||
path = response.data.path;
|
path = response.data.path;
|
||||||
@ -198,7 +237,7 @@ const TitleDetail = () => {
|
|||||||
|
|
||||||
const handleAddToWatchlist = async () => {
|
const handleAddToWatchlist = async () => {
|
||||||
try {
|
try {
|
||||||
await axios.post(`${API_BASE_URL}/api/addWatchlist`, {
|
await axios.post(`${SERVER_WATCHLIST_URL}/add`, {
|
||||||
name: titleDetails.slug,
|
name: titleDetails.slug,
|
||||||
url: location.state?.url || location.pathname.split('/title/')[1],
|
url: location.state?.url || location.pathname.split('/title/')[1],
|
||||||
season: titleDetails.season_count
|
season: titleDetails.season_count
|
||||||
@ -212,7 +251,7 @@ const TitleDetail = () => {
|
|||||||
|
|
||||||
const handleRemoveFromWatchlist = async () => {
|
const handleRemoveFromWatchlist = async () => {
|
||||||
try {
|
try {
|
||||||
await axios.post(`${API_BASE_URL}/api/removeWatchlist`, {
|
await axios.post(`${SERVER_WATCHLIST_URL}/remove`, {
|
||||||
name: titleDetails.slug
|
name: titleDetails.slug
|
||||||
});
|
});
|
||||||
setIsInWatchlist(false);
|
setIsInWatchlist(false);
|
||||||
@ -318,22 +357,34 @@ const TitleDetail = () => {
|
|||||||
<>
|
<>
|
||||||
<Row className="mb-3">
|
<Row className="mb-3">
|
||||||
<Col>
|
<Col>
|
||||||
<Dropdown>
|
<div className="d-flex align-items-center">
|
||||||
<Dropdown.Toggle variant="secondary">
|
{/* Dropdown per la selezione della stagione */}
|
||||||
Season {selectedSeason}
|
<Dropdown>
|
||||||
</Dropdown.Toggle>
|
<Dropdown.Toggle variant="secondary">
|
||||||
|
Season {selectedSeason}
|
||||||
|
</Dropdown.Toggle>
|
||||||
|
<Dropdown.Menu>
|
||||||
|
{[...Array(titleDetails.season_count)].map((_, index) => (
|
||||||
|
<Dropdown.Item
|
||||||
|
key={index + 1}
|
||||||
|
onClick={() => handleSeasonSelect(index + 1)}
|
||||||
|
>
|
||||||
|
Season {index + 1}
|
||||||
|
</Dropdown.Item>
|
||||||
|
))}
|
||||||
|
</Dropdown.Menu>
|
||||||
|
</Dropdown>
|
||||||
|
|
||||||
<Dropdown.Menu>
|
{/* Bottone per il download con margine a sinistra */}
|
||||||
{[...Array(titleDetails.season_count)].map((_, index) => (
|
<Button
|
||||||
<Dropdown.Item
|
variant="secondary"
|
||||||
key={index + 1}
|
className="ms-3"
|
||||||
onClick={() => handleSeasonSelect(index + 1)}
|
onClick={handleDownloadAllSeason}
|
||||||
>
|
disabled={downloadingAllSeason}
|
||||||
Season {index + 1}
|
>
|
||||||
</Dropdown.Item>
|
{downloadingAllSeason ? 'Downloading...' : `Download Season ${selectedSeason}`}
|
||||||
))}
|
</Button>
|
||||||
</Dropdown.Menu>
|
</div>
|
||||||
</Dropdown>
|
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
@ -393,7 +444,7 @@ const TitleDetail = () => {
|
|||||||
<Modal show={showPlayer} onHide={() => setShowPlayer(false)} size="lg" centered>
|
<Modal show={showPlayer} onHide={() => setShowPlayer(false)} size="lg" centered>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<video
|
<video
|
||||||
src={`http://127.0.0.1:1234/downloaded/${currentVideo}`}
|
src={`${API_URL}/downloaded/${currentVideo}`}
|
||||||
controls
|
controls
|
||||||
autoPlay
|
autoPlay
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { Container, Row, Col, Card, Button, Badge, Alert } from 'react-bootstrap';
|
import { Container, Row, Col, Card, Button, Badge, Alert, Modal } from 'react-bootstrap';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { FaTrash } from 'react-icons/fa';
|
import { FaTrash, FaDownload } from 'react-icons/fa';
|
||||||
|
|
||||||
const API_BASE_URL = "http://127.0.0.1:1234";
|
import { SERVER_WATCHLIST_URL, API_URL } from './ApiUrl';
|
||||||
|
|
||||||
const Watchlist = () => {
|
const Watchlist = () => {
|
||||||
const [watchlistItems, setWatchlistItems] = useState([]);
|
const [watchlistItems, setWatchlistItems] = useState([]);
|
||||||
@ -12,10 +12,19 @@ const Watchlist = () => {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [newSeasonsMessage, setNewSeasonsMessage] = useState(""); // Stato per il messaggio delle nuove stagioni
|
const [newSeasonsMessage, setNewSeasonsMessage] = useState(""); // Stato per il messaggio delle nuove stagioni
|
||||||
|
|
||||||
|
// Nuovo stato per la gestione del modal di download
|
||||||
|
const [showDownloadModal, setShowDownloadModal] = useState(false);
|
||||||
|
const [seasonsToDownload, setSeasonsToDownload] = useState([]);
|
||||||
|
const [downloadProgress, setDownloadProgress] = useState({
|
||||||
|
status: 'idle', // 'idle', 'downloading', 'completed', 'error'
|
||||||
|
current: 0,
|
||||||
|
total: 0
|
||||||
|
});
|
||||||
|
|
||||||
// Funzione per recuperare i dati della watchlist
|
// Funzione per recuperare i dati della watchlist
|
||||||
const fetchWatchlistData = async () => {
|
const fetchWatchlistData = async () => {
|
||||||
try {
|
try {
|
||||||
const watchlistResponse = await axios.get(`${API_BASE_URL}/api/getWatchlist`);
|
const watchlistResponse = await axios.get(`${SERVER_WATCHLIST_URL}/get`);
|
||||||
setWatchlistItems(watchlistResponse.data);
|
setWatchlistItems(watchlistResponse.data);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -27,27 +36,22 @@ const Watchlist = () => {
|
|||||||
// Funzione per controllare se ci sono nuove stagioni (attivata dal bottone)
|
// Funzione per controllare se ci sono nuove stagioni (attivata dal bottone)
|
||||||
const checkNewSeasons = async () => {
|
const checkNewSeasons = async () => {
|
||||||
try {
|
try {
|
||||||
const newSeasonsResponse = await axios.get(`${API_BASE_URL}/api/checkWatchlist`);
|
const newSeasonsResponse = await axios.get(`${SERVER_WATCHLIST_URL}/checkNewSeason`);
|
||||||
|
|
||||||
if (Array.isArray(newSeasonsResponse.data)) {
|
if (Array.isArray(newSeasonsResponse.data) && newSeasonsResponse.data.length > 0) {
|
||||||
setNewSeasons(newSeasonsResponse.data);
|
setNewSeasons(newSeasonsResponse.data);
|
||||||
|
setSeasonsToDownload(newSeasonsResponse.data);
|
||||||
|
setShowDownloadModal(true);
|
||||||
|
|
||||||
// Crea un messaggio per i titoli con nuove stagioni
|
|
||||||
const titlesWithNewSeasons = newSeasonsResponse.data.map(season => season.name);
|
const titlesWithNewSeasons = newSeasonsResponse.data.map(season => season.name);
|
||||||
if (titlesWithNewSeasons.length > 0) {
|
setNewSeasonsMessage(`Nuove stagioni disponibili per: ${titlesWithNewSeasons.join(", ")}`);
|
||||||
setNewSeasonsMessage(`Nuove stagioni disponibili per: ${titlesWithNewSeasons.join(", ")}`);
|
|
||||||
|
|
||||||
// Dopo aver mostrato il messaggio, aggiorniamo i titoli con le nuove stagioni
|
|
||||||
updateTitlesWithNewSeasons(newSeasonsResponse.data);
|
|
||||||
} else {
|
|
||||||
setNewSeasonsMessage("Nessuna nuova stagione disponibile.");
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
setNewSeasons([]); // In caso contrario, non ci sono nuove stagioni
|
setNewSeasons([]);
|
||||||
setNewSeasonsMessage("Nessuna nuova stagione disponibile.");
|
setNewSeasonsMessage("Nessuna nuova stagione disponibile.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching new seasons:", error);
|
console.error("Error fetching new seasons:", error);
|
||||||
|
setNewSeasonsMessage("Errore nel recuperare le nuove stagioni.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -58,7 +62,7 @@ const Watchlist = () => {
|
|||||||
// Manda una richiesta POST per ogni titolo con nuove stagioni
|
// Manda una richiesta POST per ogni titolo con nuove stagioni
|
||||||
console.log(`Updated watchlist for ${season.name} with new season ${season.nNewSeason}, url: ${season.title_url}`);
|
console.log(`Updated watchlist for ${season.name} with new season ${season.nNewSeason}, url: ${season.title_url}`);
|
||||||
|
|
||||||
await axios.post(`${API_BASE_URL}/api/updateTitleWatchlist`, {
|
await axios.post(`${SERVER_WATCHLIST_URL}/update`, {
|
||||||
url: season.title_url,
|
url: season.title_url,
|
||||||
season: season.season
|
season: season.season
|
||||||
});
|
});
|
||||||
@ -69,10 +73,76 @@ const Watchlist = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const downloadNewSeasons = async () => {
|
||||||
|
try {
|
||||||
|
setDownloadProgress({
|
||||||
|
status: 'downloading',
|
||||||
|
current: 0,
|
||||||
|
total: seasonsToDownload.length
|
||||||
|
});
|
||||||
|
for (const [index, season] of seasonsToDownload.entries()) {
|
||||||
|
try {
|
||||||
|
// Request complete series information
|
||||||
|
const seriesInfoResponse = await axios.get(`${API_URL}/getInfo`, {
|
||||||
|
params: { url: season.title_url }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Download entire season
|
||||||
|
const seasonResponse = await axios.get(`${API_URL}/getInfoSeason`, {
|
||||||
|
params: {
|
||||||
|
url: season.title_url,
|
||||||
|
n: season.nNewSeason
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Download each episode of the season
|
||||||
|
for (const episode of seasonResponse.data) {
|
||||||
|
await axios.get(`${API_URL}/download/episode`, {
|
||||||
|
params: {
|
||||||
|
n_s: season.nNewSeason,
|
||||||
|
n_ep: episode.number,
|
||||||
|
media_id: seriesInfoResponse.data.id,
|
||||||
|
series_name: seriesInfoResponse.data.slug
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update watchlist with new season
|
||||||
|
await axios.post(`${SERVER_WATCHLIST_URL}/update`, {
|
||||||
|
url: season.title_url,
|
||||||
|
season: season.season
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update progress
|
||||||
|
setDownloadProgress(prev => ({
|
||||||
|
...prev,
|
||||||
|
current: index + 1,
|
||||||
|
status: index + 1 === seasonsToDownload.length ? 'completed' : 'downloading'
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Errore durante lo scaricamento della stagione ${season.name}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close modal after completion
|
||||||
|
setShowDownloadModal(false);
|
||||||
|
|
||||||
|
// Reload watchlist to show updates
|
||||||
|
fetchWatchlistData();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Errore durante lo scaricamento delle nuove stagioni:", error);
|
||||||
|
setDownloadProgress({
|
||||||
|
status: 'error',
|
||||||
|
current: 0,
|
||||||
|
total: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Funzione per rimuovere un elemento dalla watchlist
|
// Funzione per rimuovere un elemento dalla watchlist
|
||||||
const handleRemoveFromWatchlist = async (serieName) => {
|
const handleRemoveFromWatchlist = async (serieName) => {
|
||||||
try {
|
try {
|
||||||
await axios.post(`${API_BASE_URL}/api/removeWatchlist`, { name: serieName });
|
await axios.post(`${SERVER_WATCHLIST_URL}/remove`, { name: serieName });
|
||||||
|
|
||||||
// Aggiorna lo stato locale per rimuovere l'elemento dalla watchlist
|
// Aggiorna lo stato locale per rimuovere l'elemento dalla watchlist
|
||||||
setWatchlistItems((prev) => prev.filter((item) => item.name !== serieName));
|
setWatchlistItems((prev) => prev.filter((item) => item.name !== serieName));
|
||||||
@ -156,6 +226,57 @@ const Watchlist = () => {
|
|||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
|
|
||||||
|
<Modal show={showDownloadModal} onHide={() => setShowDownloadModal(false)}>
|
||||||
|
<Modal.Header closeButton>
|
||||||
|
<Modal.Title>Nuove Stagioni Disponibili</Modal.Title>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body>
|
||||||
|
<p>Sono disponibili nuove stagioni per i seguenti titoli:</p>
|
||||||
|
<ul>
|
||||||
|
{seasonsToDownload.map((season) => (
|
||||||
|
<li key={season.name}>
|
||||||
|
{season.name.replace(/-/g, ' ')} - Stagione {season.nNewSeason}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{downloadProgress.status !== 'idle' && (
|
||||||
|
<div className="mt-3">
|
||||||
|
<p>Progresso download:</p>
|
||||||
|
<div className="progress">
|
||||||
|
<div
|
||||||
|
className={`progress-bar ${
|
||||||
|
downloadProgress.status === 'completed' ? 'bg-success' :
|
||||||
|
downloadProgress.status === 'error' ? 'bg-danger' : 'bg-primary'
|
||||||
|
}`}
|
||||||
|
role="progressbar"
|
||||||
|
style={{width: `${(downloadProgress.current / downloadProgress.total) * 100}%`}}
|
||||||
|
aria-valuenow={(downloadProgress.current / downloadProgress.total) * 100}
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100"
|
||||||
|
>
|
||||||
|
{downloadProgress.current}/{downloadProgress.total} Stagioni
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Modal.Body>
|
||||||
|
<Modal.Footer>
|
||||||
|
<Button variant="secondary" onClick={() => setShowDownloadModal(false)}>
|
||||||
|
Annulla
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
onClick={downloadNewSeasons}
|
||||||
|
disabled={downloadProgress.status === 'downloading'}
|
||||||
|
>
|
||||||
|
<FaDownload className="me-2" />
|
||||||
|
Scarica Nuove Stagioni
|
||||||
|
</Button>
|
||||||
|
</Modal.Footer>
|
||||||
|
</Modal>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
15
config.json
15
config.json
@ -1,22 +1,12 @@
|
|||||||
{
|
{
|
||||||
"DEFAULT": {
|
"DEFAULT": {
|
||||||
"debug": false,
|
"debug": false,
|
||||||
"log_file": "app.log",
|
|
||||||
"log_to_file": true,
|
|
||||||
"show_message": true,
|
"show_message": true,
|
||||||
"clean_console": true,
|
|
||||||
"root_path": "Video",
|
"root_path": "Video",
|
||||||
"movie_folder_name": "Movie",
|
"movie_folder_name": "Movie",
|
||||||
"serie_folder_name": "TV",
|
"serie_folder_name": "TV",
|
||||||
"map_episode_name": "%(tv_name)_S%(season)E%(episode)_%(episode_name)",
|
"map_episode_name": "%(tv_name)_S%(season)E%(episode)_%(episode_name)",
|
||||||
"config_qbit_tor": {
|
"clean_console": true
|
||||||
"host": "192.168.1.59",
|
|
||||||
"port": "8080",
|
|
||||||
"user": "admin",
|
|
||||||
"pass": "adminadmin"
|
|
||||||
},
|
|
||||||
"not_close": false,
|
|
||||||
"show_trending": false
|
|
||||||
},
|
},
|
||||||
"REQUESTS": {
|
"REQUESTS": {
|
||||||
"timeout": 20,
|
"timeout": 20,
|
||||||
@ -26,9 +16,6 @@
|
|||||||
"proxy_start_max": 0.5,
|
"proxy_start_max": 0.5,
|
||||||
"user-agent": ""
|
"user-agent": ""
|
||||||
},
|
},
|
||||||
"BROWSER": {
|
|
||||||
"headless": false
|
|
||||||
},
|
|
||||||
"M3U8_DOWNLOAD": {
|
"M3U8_DOWNLOAD": {
|
||||||
"tqdm_delay": 0.01,
|
"tqdm_delay": 0.01,
|
||||||
"tqdm_use_large_bar": true,
|
"tqdm_use_large_bar": true,
|
||||||
|
@ -13,4 +13,4 @@ qbittorrent-api
|
|||||||
python-qbittorrent
|
python-qbittorrent
|
||||||
googlesearch-python
|
googlesearch-python
|
||||||
pymongo
|
pymongo
|
||||||
flask
|
fastapi
|
618
server.py
618
server.py
@ -1,17 +1,23 @@
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import shutil
|
|
||||||
import datetime
|
import datetime
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from pymongo import MongoClient
|
from pymongo import MongoClient
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
from flask_cors import CORS
|
from pydantic import BaseModel
|
||||||
from flask import Flask, jsonify, request
|
from typing import List, Optional
|
||||||
from flask import send_from_directory
|
|
||||||
|
# Fast api
|
||||||
|
from fastapi import FastAPI, HTTPException
|
||||||
|
from fastapi.responses import JSONResponse, FileResponse
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
|
||||||
# Util
|
# Util
|
||||||
from StreamingCommunity.Util._jsonConfig import config_manager
|
from StreamingCommunity.Util._jsonConfig import config_manager
|
||||||
|
|
||||||
|
|
||||||
# Internal
|
# Internal
|
||||||
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
|
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.api import get_version_and_domain, search_titles, get_infoSelectTitle, get_infoSelectSeason
|
||||||
@ -19,13 +25,23 @@ from StreamingCommunity.Api.Site.streamingcommunity.film import download_film
|
|||||||
from StreamingCommunity.Api.Site.streamingcommunity.series import download_video
|
from StreamingCommunity.Api.Site.streamingcommunity.series import download_video
|
||||||
from StreamingCommunity.Api.Site.streamingcommunity.util.ScrapeSerie import ScrapeSerie
|
from StreamingCommunity.Api.Site.streamingcommunity.util.ScrapeSerie import ScrapeSerie
|
||||||
|
|
||||||
|
|
||||||
# Player
|
# Player
|
||||||
from StreamingCommunity.Api.Player.vixcloud import VideoSource
|
from StreamingCommunity.Api.Player.vixcloud import VideoSource
|
||||||
|
|
||||||
# Variable
|
|
||||||
app = Flask(__name__)
|
|
||||||
CORS(app)
|
|
||||||
|
|
||||||
|
# Variable
|
||||||
|
app = FastAPI()
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Site variable
|
||||||
version, domain = get_version_and_domain()
|
version, domain = get_version_and_domain()
|
||||||
season_name = None
|
season_name = None
|
||||||
scrape_serie = ScrapeSerie("streamingcommunity")
|
scrape_serie = ScrapeSerie("streamingcommunity")
|
||||||
@ -33,6 +49,7 @@ video_source = VideoSource("streamingcommunity", True)
|
|||||||
DOWNLOAD_DIRECTORY = os.getcwd()
|
DOWNLOAD_DIRECTORY = os.getcwd()
|
||||||
|
|
||||||
|
|
||||||
|
# Mongo
|
||||||
client = MongoClient(config_manager.get("EXTRA", "mongodb"))
|
client = MongoClient(config_manager.get("EXTRA", "mongodb"))
|
||||||
db = client[config_manager.get("EXTRA", "database")]
|
db = client[config_manager.get("EXTRA", "database")]
|
||||||
watchlist_collection = db['watchlist']
|
watchlist_collection = db['watchlist']
|
||||||
@ -40,9 +57,20 @@ downloads_collection = db['downloads']
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def update_domain(url: str):
|
||||||
|
parsed_url = urlparse(url)
|
||||||
|
hostname = parsed_url.hostname
|
||||||
|
domain_part = hostname.split('.')[1]
|
||||||
|
new_url = url.replace(domain_part, domain)
|
||||||
|
|
||||||
|
return new_url
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ---------- SITE API ------------
|
# ---------- SITE API ------------
|
||||||
@app.route('/')
|
@app.get("/", summary="Health Check")
|
||||||
def index():
|
async def index():
|
||||||
"""
|
"""
|
||||||
Health check endpoint to confirm server is operational.
|
Health check endpoint to confirm server is operational.
|
||||||
|
|
||||||
@ -50,51 +78,56 @@ def index():
|
|||||||
str: Operational status message
|
str: Operational status message
|
||||||
"""
|
"""
|
||||||
logging.info("Health check endpoint accessed")
|
logging.info("Health check endpoint accessed")
|
||||||
return 'Server is operational'
|
return {"status": "Server is operational"}
|
||||||
|
|
||||||
@app.route('/api/search', methods=['GET'])
|
@app.get("/api/search", summary="Search Titles")
|
||||||
def get_list_search():
|
async def get_list_search(q):
|
||||||
"""
|
"""
|
||||||
Search for titles based on query parameter.
|
Search for titles based on query parameter.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
q (str, optional): Search query parameter
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
JSON response with search results or error message
|
JSON response with search results
|
||||||
"""
|
"""
|
||||||
|
if not q:
|
||||||
|
logging.warning("Search request without query parameter")
|
||||||
|
raise HTTPException(status_code=400, detail="Missing query parameter")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
query = request.args.get('q')
|
result = search_titles(q, domain)
|
||||||
|
logging.info(f"Search performed for query: {q}")
|
||||||
if not query:
|
return result
|
||||||
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:
|
except Exception as e:
|
||||||
logging.error(f"Error in search: {str(e)}", exc_info=True)
|
logging.error(f"Error in search: {str(e)}", exc_info=True)
|
||||||
return jsonify({'error': 'Internal server error'}), 500
|
raise HTTPException(status_code=500, detail="Internal server error")
|
||||||
|
|
||||||
@app.route('/api/getInfo', methods=['GET'])
|
@app.get("/api/getInfo", summary="Get Title Information")
|
||||||
def get_info_title():
|
async def get_info_title(url):
|
||||||
"""
|
"""
|
||||||
Retrieve information for a specific title.
|
Retrieve information for a specific title.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url (str, optional): Title URL parameter
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
JSON response with title information or error message
|
JSON response with title information
|
||||||
"""
|
"""
|
||||||
|
if not url:
|
||||||
|
logging.warning("GetInfo request without URL parameter")
|
||||||
|
raise HTTPException(status_code=400, detail="Missing URL parameter")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
title_url = request.args.get('url')
|
result = get_infoSelectTitle(update_domain(url), domain, version)
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
# Global state management for TV series
|
||||||
if result.get('type') == "tv":
|
if result.get('type') == "tv":
|
||||||
global season_name, scrape_serie, video_source
|
global season_name, scrape_serie, video_source
|
||||||
season_name = result.get('slug')
|
season_name = result.get('slug')
|
||||||
|
|
||||||
|
# Setup for TV series (adjust based on your actual implementation)
|
||||||
scrape_serie.setup(
|
scrape_serie.setup(
|
||||||
version=version,
|
version=version,
|
||||||
media_id=int(result.get('id')),
|
media_id=int(result.get('id')),
|
||||||
@ -104,38 +137,39 @@ def get_info_title():
|
|||||||
|
|
||||||
logging.info(f"TV series info retrieved: {season_name}")
|
logging.info(f"TV series info retrieved: {season_name}")
|
||||||
|
|
||||||
return jsonify(result), 200
|
return result
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error retrieving title info: {str(e)}", exc_info=True)
|
logging.error(f"Error retrieving title info: {str(e)}", exc_info=True)
|
||||||
return jsonify({'error': 'Failed to retrieve title information'}), 500
|
raise HTTPException(status_code=500, detail="Failed to retrieve title information")
|
||||||
|
|
||||||
@app.route('/api/getInfoSeason', methods=['GET'])
|
@app.get("/api/getInfoSeason", summary="Get Season Information")
|
||||||
def get_info_season():
|
async def get_info_season(url, n):
|
||||||
"""
|
"""
|
||||||
Retrieve season information for a specific title.
|
Retrieve season information for a specific title.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url (str, optional): Title URL parameter
|
||||||
|
n (str, optional): Season number
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
JSON response with season information or error message
|
JSON response with season information
|
||||||
"""
|
"""
|
||||||
|
if not url or not n:
|
||||||
|
logging.warning("GetInfoSeason request with missing parameters")
|
||||||
|
raise HTTPException(status_code=400, detail="Missing URL or season number")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
title_url = request.args.get('url')
|
result = get_infoSelectSeason(update_domain(url), n, domain, version)
|
||||||
number_season = request.args.get('n')
|
logging.info(f"Season info retrieved for season {n}")
|
||||||
|
return result
|
||||||
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:
|
except Exception as e:
|
||||||
logging.error(f"Error retrieving season info: {str(e)}", exc_info=True)
|
logging.error(f"Error retrieving season info: {str(e)}", exc_info=True)
|
||||||
return jsonify({'error': 'Failed to retrieve season information'}), 500
|
raise HTTPException(status_code=500, detail="Failed to retrieve season information")
|
||||||
|
|
||||||
@app.route('/api/getdomain', methods=['GET'])
|
@app.get("/api/getDomain", summary="Get Current Domain")
|
||||||
def get_domain():
|
async def get_domain():
|
||||||
"""
|
"""
|
||||||
Retrieve current domain and version.
|
Retrieve current domain and version.
|
||||||
|
|
||||||
@ -146,37 +180,38 @@ def get_domain():
|
|||||||
global version, domain
|
global version, domain
|
||||||
version, domain = get_version_and_domain()
|
version, domain = get_version_and_domain()
|
||||||
logging.info(f"Domain retrieved: {domain}, Version: {version}")
|
logging.info(f"Domain retrieved: {domain}, Version: {version}")
|
||||||
return jsonify({'domain': domain, 'version': version}), 200
|
return {"domain": domain, "version": version}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error retrieving domain: {str(e)}", exc_info=True)
|
logging.error(f"Error retrieving domain: {str(e)}", exc_info=True)
|
||||||
return jsonify({'error': 'Failed to retrieve domain information'}), 500
|
raise HTTPException(status_code=500, detail="Failed to retrieve domain information")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ---------- DOWNLOAD API ------------
|
# ---------- CALL DOWNLOAD API ------------
|
||||||
@app.route('/downloadFilm', methods=['GET'])
|
@app.get("/api/download/film", summary="Download Film")
|
||||||
def call_download_film():
|
async def call_download_film(id, slug):
|
||||||
"""
|
"""
|
||||||
Download a film by its ID and slug.
|
Download a film by its ID and slug.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
id (str, optional): Film ID
|
||||||
|
slug (str, optional): Film slug
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
JSON response with download path or error message
|
JSON response with download path or error message
|
||||||
"""
|
"""
|
||||||
|
if not id or not slug:
|
||||||
|
logging.warning("Download film request with missing parameters")
|
||||||
|
raise HTTPException(status_code=400, detail="Missing film ID or slug")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
film_id = request.args.get('id')
|
item_media = MediaItem(**{'id': id, 'slug': slug})
|
||||||
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)
|
path_download = download_film(item_media)
|
||||||
|
|
||||||
download_data = {
|
download_data = {
|
||||||
'type': 'movie',
|
'type': 'movie',
|
||||||
'id': film_id,
|
'id': id,
|
||||||
'slug': slug,
|
'slug': slug,
|
||||||
'path': path_download,
|
'path': path_download,
|
||||||
'timestamp': datetime.datetime.now(datetime.timezone.utc)
|
'timestamp': datetime.datetime.now(datetime.timezone.utc)
|
||||||
@ -184,44 +219,58 @@ def call_download_film():
|
|||||||
downloads_collection.insert_one(download_data)
|
downloads_collection.insert_one(download_data)
|
||||||
|
|
||||||
logging.info(f"Film downloaded: {slug}")
|
logging.info(f"Film downloaded: {slug}")
|
||||||
return jsonify({'path': path_download}), 200
|
return {"path": path_download}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error downloading film: {str(e)}", exc_info=True)
|
logging.error(f"Error downloading film: {str(e)}", exc_info=True)
|
||||||
return jsonify({'error': 'Failed to download film'}), 500
|
raise HTTPException(status_code=500, detail="Failed to download film")
|
||||||
|
|
||||||
@app.route('/downloadEpisode', methods=['GET'])
|
@app.get("/api/download/episode", summary="Download TV Episode")
|
||||||
def call_download_episode():
|
async def call_download_episode(n_s: str, n_ep: str, media_id: Optional[int] = None, series_name: Optional[str] = None):
|
||||||
"""
|
"""
|
||||||
Download a specific TV series episode.
|
Download a specific TV series episode.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
n_s (str, optional): Season number
|
||||||
|
n_ep (str, optional): Episode number
|
||||||
|
media_id (int, optional): Media ID of the series
|
||||||
|
series_name (str, optional): Series slug
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
JSON response with download path or error message
|
JSON response with download path or error message
|
||||||
"""
|
"""
|
||||||
|
if not n_s or not n_ep:
|
||||||
|
logging.warning("Download episode request with missing parameters")
|
||||||
|
raise HTTPException(status_code=400, detail="Missing season or episode number")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
season_number = request.args.get('n_s')
|
season_number = int(n_s)
|
||||||
episode_number = request.args.get('n_ep')
|
episode_number = int(n_ep)
|
||||||
|
|
||||||
if not season_number or not episode_number:
|
# Se i parametri opzionali sono presenti, impostare la serie con setup
|
||||||
logging.warning("Download episode request with missing parameters")
|
if media_id is not None and series_name is not None:
|
||||||
return jsonify({'error': 'Missing season or episode number'}), 400
|
scrape_serie.setup(
|
||||||
|
version=version,
|
||||||
|
media_id=media_id,
|
||||||
|
series_name=series_name
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
scrape_serie.collect_title_season(season_number)
|
||||||
|
|
||||||
season_number = int(season_number)
|
# Scaricare il video
|
||||||
episode_number = int(episode_number)
|
|
||||||
|
|
||||||
scrape_serie.collect_title_season(season_number)
|
|
||||||
path_download = download_video(
|
path_download = download_video(
|
||||||
season_name,
|
scrape_serie.series_name,
|
||||||
season_number,
|
season_number,
|
||||||
episode_number,
|
episode_number,
|
||||||
scrape_serie,
|
scrape_serie,
|
||||||
video_source
|
video_source
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Salvare i dati del download
|
||||||
download_data = {
|
download_data = {
|
||||||
'type': 'tv',
|
'type': 'tv',
|
||||||
'id': scrape_serie.media_id,
|
'id': media_id if media_id else scrape_serie.media_id,
|
||||||
'slug': scrape_serie.series_name,
|
'slug': series_name if series_name else scrape_serie.series_name,
|
||||||
'n_s': season_number,
|
'n_s': season_number,
|
||||||
'n_ep': episode_number,
|
'n_ep': episode_number,
|
||||||
'path': path_download,
|
'path': path_download,
|
||||||
@ -230,21 +279,24 @@ def call_download_episode():
|
|||||||
downloads_collection.insert_one(download_data)
|
downloads_collection.insert_one(download_data)
|
||||||
|
|
||||||
logging.info(f"Episode downloaded: S{season_number}E{episode_number}")
|
logging.info(f"Episode downloaded: S{season_number}E{episode_number}")
|
||||||
return jsonify({'path': path_download}), 200
|
return {"path": path_download}
|
||||||
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
logging.error("Invalid season or episode number format")
|
logging.error("Invalid season or episode number format")
|
||||||
return jsonify({'error': 'Invalid season or episode number'}), 400
|
raise HTTPException(status_code=400, detail="Invalid season or episode number")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error downloading episode: {str(e)}", exc_info=True)
|
logging.error(f"Error downloading episode: {str(e)}", exc_info=True)
|
||||||
return jsonify({'error': 'Failed to download episode'}), 500
|
raise HTTPException(status_code=500, detail="Failed to download episode")
|
||||||
|
|
||||||
@app.route('/downloaded/<path:filename>', methods=['GET'])
|
@app.get("/api/downloaded/{filename:path}", summary="Serve Downloaded Files")
|
||||||
def serve_downloaded_file(filename):
|
async def serve_downloaded_file(filename: str):
|
||||||
"""
|
"""
|
||||||
Serve downloaded files with proper URL decoding and error handling.
|
Serve downloaded files with proper URL decoding and error handling.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename (str): Encoded filename path
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Downloaded file or error message
|
Downloaded file or error message
|
||||||
"""
|
"""
|
||||||
@ -260,132 +312,204 @@ def serve_downloaded_file(filename):
|
|||||||
# Verify file exists
|
# Verify file exists
|
||||||
if not os.path.isfile(file_path):
|
if not os.path.isfile(file_path):
|
||||||
logging.warning(f"File not found: {decoded_filename}")
|
logging.warning(f"File not found: {decoded_filename}")
|
||||||
return jsonify({'error': 'File not found'}), 404
|
HTTPException(status_code=404, detail="File not found")
|
||||||
|
|
||||||
# Serve the file
|
# Serve the file
|
||||||
return send_from_directory(DOWNLOAD_DIRECTORY, decoded_filename, as_attachment=False)
|
return FileResponse(
|
||||||
|
path=file_path,
|
||||||
|
filename=decoded_filename,
|
||||||
|
media_type='application/octet-stream'
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error serving file: {str(e)}", exc_info=True)
|
logging.error(f"Error serving file: {str(e)}", exc_info=True)
|
||||||
return jsonify({'error': 'Internal server error'}), 500
|
raise HTTPException(status_code=500, detail="Internal server error")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ---------- WATCHLIST MONGO ------------
|
# ---------- WATCHLIST MONGO ------------
|
||||||
@app.route('/api/addWatchlist', methods=['POST'])
|
class WatchlistItem(BaseModel):
|
||||||
def add_to_watchlist():
|
name: str
|
||||||
title_name = request.json.get('name')
|
url: str
|
||||||
title_url = request.json.get('url')
|
season: int
|
||||||
season = request.json.get('season')
|
|
||||||
|
|
||||||
if title_url and season:
|
class UpdateWatchlistItem(BaseModel):
|
||||||
|
url: str
|
||||||
|
season: int
|
||||||
|
|
||||||
existing_item = watchlist_collection.find_one({'name': title_name, 'url': title_url, 'season': season})
|
class RemoveWatchlistItem(BaseModel):
|
||||||
|
name: str
|
||||||
|
|
||||||
|
@app.post("/server/watchlist/add", summary="Add Item to Watchlist")
|
||||||
|
async def add_to_watchlist(item: WatchlistItem):
|
||||||
|
"""
|
||||||
|
Add a new item to the watchlist.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
item (WatchlistItem): Details of the item to add
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON response with success or error message
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Check if item already exists
|
||||||
|
existing_item = watchlist_collection.find_one({
|
||||||
|
'name': item.name,
|
||||||
|
'title_url': item.url,
|
||||||
|
'season': item.season
|
||||||
|
})
|
||||||
|
|
||||||
if existing_item:
|
if existing_item:
|
||||||
return jsonify({'message': 'Il titolo è già nella watchlist'}), 400
|
raise HTTPException(status_code=400, detail="Il titolo è già nella watchlist")
|
||||||
|
|
||||||
|
# Insert new item
|
||||||
watchlist_collection.insert_one({
|
watchlist_collection.insert_one({
|
||||||
'name': title_name,
|
'name': item.name,
|
||||||
'title_url': title_url,
|
'title_url': item.url,
|
||||||
'season': season,
|
'season': item.season,
|
||||||
'added_on': datetime.datetime.utcnow()
|
'added_on': datetime.datetime.utcnow()
|
||||||
})
|
})
|
||||||
return jsonify({'message': 'Titolo aggiunto alla watchlist'}), 200
|
|
||||||
else:
|
return {"message": "Titolo aggiunto alla watchlist"}
|
||||||
return jsonify({'message': 'Missing title_url or season'}), 400
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error adding to watchlist: {str(e)}")
|
||||||
|
raise HTTPException(status_code=500, detail="Errore interno del server")
|
||||||
|
|
||||||
@app.route('/api/updateTitleWatchlist', methods=['POST'])
|
@app.post("/server/watchlist/update", summary="Update Watchlist Item")
|
||||||
def update_title_watchlist():
|
async def update_title_watchlist(item: UpdateWatchlistItem):
|
||||||
print(request.json)
|
"""
|
||||||
|
Update the season for an existing watchlist item.
|
||||||
title_url = request.json.get('url')
|
|
||||||
new_season = request.json.get('season')
|
Args:
|
||||||
|
item (UpdateWatchlistItem): Details of the item to update
|
||||||
if title_url is not None and new_season is not None:
|
|
||||||
|
Returns:
|
||||||
|
JSON response with update status
|
||||||
|
"""
|
||||||
|
try:
|
||||||
result = watchlist_collection.update_one(
|
result = watchlist_collection.update_one(
|
||||||
{'title_url': title_url},
|
{'title_url': item.url},
|
||||||
{'$set': {'season': new_season}}
|
{'$set': {'season': item.season}}
|
||||||
)
|
)
|
||||||
|
|
||||||
if result.matched_count == 0:
|
if result.matched_count == 0:
|
||||||
return jsonify({'message': 'Titolo non trovato nella watchlist'}), 404
|
raise HTTPException(status_code=404, detail="Titolo non trovato nella watchlist")
|
||||||
|
|
||||||
if result.modified_count == 0:
|
if result.modified_count == 0:
|
||||||
return jsonify({'message': 'La stagione non è cambiata'}), 200
|
return {"message": "La stagione non è cambiata"}
|
||||||
|
|
||||||
return jsonify({'message': 'Stagione aggiornata con successo'}), 200
|
return {"message": "Stagione aggiornata con successo"}
|
||||||
|
|
||||||
else:
|
except HTTPException:
|
||||||
return jsonify({'message': 'Missing title_url or season'}), 400
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error updating watchlist: {str(e)}")
|
||||||
|
raise HTTPException(status_code=500, detail="Errore interno del server")
|
||||||
|
|
||||||
|
@app.post("/server/watchlist/remove", summary="Remove Item from Watchlist")
|
||||||
|
async def remove_from_watchlist(item: RemoveWatchlistItem):
|
||||||
|
"""
|
||||||
|
Remove an item from the watchlist.
|
||||||
|
|
||||||
@app.route('/api/removeWatchlist', methods=['POST'])
|
Args:
|
||||||
def remove_from_watchlist():
|
item (RemoveWatchlistItem): Details of the item to remove
|
||||||
title_name = request.json.get('name')
|
|
||||||
|
Returns:
|
||||||
if title_name:
|
JSON response with removal status
|
||||||
result = watchlist_collection.delete_one({'name': title_name})
|
"""
|
||||||
|
try:
|
||||||
|
result = watchlist_collection.delete_one({'name': item.name})
|
||||||
|
|
||||||
if result.deleted_count == 1:
|
if result.deleted_count == 1:
|
||||||
return jsonify({'message': 'Titolo rimosso dalla watchlist'}), 200
|
return {"message": "Titolo rimosso dalla watchlist"}
|
||||||
else:
|
|
||||||
return jsonify({'message': 'Titolo non trovato nella watchlist'}), 404
|
raise HTTPException(status_code=404, detail="Titolo non trovato nella watchlist")
|
||||||
else:
|
|
||||||
return jsonify({'message': 'Missing title_url or season'}), 400
|
|
||||||
|
|
||||||
@app.route('/api/getWatchlist', methods=['GET'])
|
except HTTPException:
|
||||||
def get_watchlist():
|
raise
|
||||||
watchlist_items = list(watchlist_collection.find({}, {'_id': 0}))
|
except Exception as e:
|
||||||
|
logging.error(f"Error removing from watchlist: {str(e)}")
|
||||||
|
raise HTTPException(status_code=500, detail="Errore interno del server")
|
||||||
|
|
||||||
if watchlist_items:
|
@app.get("/server/watchlist/get", summary="Get Watchlist Items")
|
||||||
return jsonify(watchlist_items), 200
|
async def get_watchlist():
|
||||||
else:
|
"""
|
||||||
return jsonify({'message': 'La watchlist è vuota'}), 200
|
Retrieve all items in the watchlist.
|
||||||
|
|
||||||
@app.route('/api/checkWatchlist', methods=['GET'])
|
Returns:
|
||||||
def get_newSeason():
|
List of watchlist items or empty list message
|
||||||
title_newSeasons = []
|
"""
|
||||||
watchlist_items = list(watchlist_collection.find({}, {'_id': 0}))
|
try:
|
||||||
|
watchlist_items = list(watchlist_collection.find({}, {'_id': 0}))
|
||||||
|
|
||||||
|
if not watchlist_items:
|
||||||
|
return {"message": "La watchlist è vuota"}
|
||||||
|
|
||||||
|
return watchlist_items
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error retrieving watchlist: {str(e)}")
|
||||||
|
raise HTTPException(status_code=500, detail="Errore interno del server")
|
||||||
|
|
||||||
if not watchlist_items:
|
@app.get("/server/watchlist/checkNewSeason", summary="Check for New Seasons")
|
||||||
return jsonify({'message': 'La watchlist è vuota'}), 200
|
async def get_new_seasons():
|
||||||
|
"""
|
||||||
for item in watchlist_items:
|
Check for new seasons of watchlist items.
|
||||||
title_url = item.get('title_url')
|
|
||||||
if not title_url:
|
Returns:
|
||||||
continue
|
List of items with new seasons or message
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
parsed_url = urlparse(title_url)
|
watchlist_items = list(watchlist_collection.find({}, {'_id': 0}))
|
||||||
hostname = parsed_url.hostname
|
|
||||||
domain_part = hostname.split('.')[1]
|
if not watchlist_items:
|
||||||
new_url = title_url.replace(domain_part, domain)
|
return {"message": "La watchlist è vuota"}
|
||||||
|
|
||||||
result = get_infoSelectTitle(new_url, domain, version)
|
title_new_seasons = []
|
||||||
|
|
||||||
if not result or 'season_count' not in result:
|
for item in watchlist_items:
|
||||||
continue
|
title_url = item.get('title_url')
|
||||||
|
if not title_url:
|
||||||
number_season = result.get("season_count")
|
continue
|
||||||
|
|
||||||
if number_season > item.get("season"):
|
try:
|
||||||
title_newSeasons.append({
|
new_url = update_domain(title_url)
|
||||||
'title_url': item.get('title_url'),
|
|
||||||
'name': item.get('name'),
|
# Fetch title info
|
||||||
'season': int(number_season),
|
result = get_infoSelectTitle(new_url, domain, version)
|
||||||
'nNewSeason': int(number_season) - int(item.get("season"))
|
|
||||||
})
|
if not result or 'season_count' not in result:
|
||||||
|
continue
|
||||||
except Exception as e:
|
|
||||||
print(f"Errore nel recuperare informazioni per {item.get('title_url')}: {e}")
|
number_season = result.get("season_count")
|
||||||
|
|
||||||
if title_newSeasons:
|
# Check for new seasons
|
||||||
return jsonify(title_newSeasons), 200
|
if number_season > item.get("season"):
|
||||||
else:
|
title_new_seasons.append({
|
||||||
return jsonify({'message': 'Nessuna nuova stagione disponibile'}), 200
|
'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:
|
||||||
|
logging.error(f"Error checking seasons for {item.get('title_url')}: {str(e)}")
|
||||||
|
|
||||||
|
if title_new_seasons:
|
||||||
|
return title_new_seasons
|
||||||
|
|
||||||
|
return {"message": "Nessuna nuova stagione disponibile"}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error in check watchlist: {str(e)}")
|
||||||
|
raise HTTPException(status_code=500, detail="Errore interno del server")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ---------- DOWNLOAD MONGO ------------
|
# ---------- REMOVE DOWNLOAD FILE WITH MONGO ------------
|
||||||
def ensure_collections_exist(db):
|
def ensure_collections_exist(db):
|
||||||
"""
|
"""
|
||||||
Ensures that the required collections exist in the database.
|
Ensures that the required collections exist in the database.
|
||||||
@ -405,40 +529,36 @@ def ensure_collections_exist(db):
|
|||||||
else:
|
else:
|
||||||
logging.info(f"Collection already exists: {collection_name}")
|
logging.info(f"Collection already exists: {collection_name}")
|
||||||
|
|
||||||
@app.route('/downloads', methods=['GET'])
|
class Episode(BaseModel):
|
||||||
def fetch_all_downloads():
|
id: int
|
||||||
|
season: int
|
||||||
|
episode: int
|
||||||
|
|
||||||
|
class Movie(BaseModel):
|
||||||
|
id: int
|
||||||
|
|
||||||
|
# Fetch all downloads
|
||||||
|
@app.get("/server/path/getAll", response_model=List[dict], summary="Get all download from disk")
|
||||||
|
async def fetch_all_downloads():
|
||||||
"""
|
"""
|
||||||
Endpoint to fetch all downloads.
|
Endpoint to fetch all downloads.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
downloads = list(downloads_collection.find({}, {'_id': 0}))
|
downloads = list(downloads_collection.find({}, {'_id': 0}))
|
||||||
return jsonify(downloads), 200
|
return downloads
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error fetching all downloads: {str(e)}")
|
logging.error(f"Error fetching all downloads: {str(e)}")
|
||||||
return []
|
raise HTTPException(status_code=500, detail="Error fetching all downloads")
|
||||||
|
|
||||||
@app.route('/deleteEpisode', methods=['DELETE'])
|
# Remove a specific episode and its file
|
||||||
def remove_episode():
|
@app.delete("/server/delete/episode", summary="Remove episode from disk")
|
||||||
|
async def remove_episode(series_id: int, season_number: int, episode_number: int):
|
||||||
"""
|
"""
|
||||||
Endpoint to delete a specific episode and its file.
|
Endpoint to delete a specific episode and its file.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
series_id = request.args.get('id')
|
# Find the episode in the database
|
||||||
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({
|
episode = downloads_collection.find_one({
|
||||||
'type': 'tv',
|
'type': 'tv',
|
||||||
'id': series_id,
|
'id': series_id,
|
||||||
@ -446,12 +566,15 @@ def remove_episode():
|
|||||||
'n_ep': episode_number
|
'n_ep': episode_number
|
||||||
}, {'_id': 0, 'path': 1})
|
}, {'_id': 0, 'path': 1})
|
||||||
|
|
||||||
|
logging.info("FIND => ", episode)
|
||||||
|
|
||||||
if not episode or 'path' not in episode:
|
if not episode or 'path' not in episode:
|
||||||
return jsonify({'error': 'Episode not found'}), 404
|
raise HTTPException(status_code=404, detail="Episode not found")
|
||||||
|
|
||||||
file_path = episode['path']
|
file_path = episode['path']
|
||||||
|
logging.info("PATH => ", file_path)
|
||||||
|
|
||||||
# Elimina il file fisico
|
# Delete the file
|
||||||
try:
|
try:
|
||||||
if os.path.exists(file_path):
|
if os.path.exists(file_path):
|
||||||
os.remove(file_path)
|
os.remove(file_path)
|
||||||
@ -461,7 +584,7 @@ def remove_episode():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error deleting episode file: {str(e)}")
|
logging.error(f"Error deleting episode file: {str(e)}")
|
||||||
|
|
||||||
# Rimuovi l'episodio dal database
|
# Remove the episode from the database
|
||||||
result = downloads_collection.delete_one({
|
result = downloads_collection.delete_one({
|
||||||
'type': 'tv',
|
'type': 'tv',
|
||||||
'id': series_id,
|
'id': series_id,
|
||||||
@ -470,35 +593,31 @@ def remove_episode():
|
|||||||
})
|
})
|
||||||
|
|
||||||
if result.deleted_count > 0:
|
if result.deleted_count > 0:
|
||||||
return jsonify({'success': True}), 200
|
return JSONResponse(status_code=200, content={'success': True})
|
||||||
else:
|
else:
|
||||||
return jsonify({'error': 'Failed to delete episode from database'}), 500
|
raise HTTPException(status_code=500, detail="Failed to delete episode from database")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error deleting episode: {str(e)}")
|
logging.error(f"Error deleting episode: {str(e)}")
|
||||||
return jsonify({'error': 'Failed to delete episode'}), 500
|
raise HTTPException(status_code=500, detail="Failed to delete episode")
|
||||||
|
|
||||||
@app.route('/deleteMovie', methods=['DELETE'])
|
# Remove a specific movie, its file, and its parent folder if empty
|
||||||
def remove_movie():
|
@app.delete("/server/delete/movie", summary="Remove a movie from disk")
|
||||||
|
async def remove_movie(movie_id: int):
|
||||||
"""
|
"""
|
||||||
Endpoint to delete a specific movie, its file, and its parent folder if empty.
|
Endpoint to delete a specific movie, its file, and its parent folder if empty.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
movie_id = request.args.get('id')
|
# Find the movie in the database
|
||||||
|
movie = downloads_collection.find_one({'type': 'movie', 'id': str(movie_id)}, {'_id': 0, 'path': 1})
|
||||||
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:
|
if not movie or 'path' not in movie:
|
||||||
return jsonify({'error': 'Movie not found'}), 404
|
raise HTTPException(status_code=404, detail="Movie not found")
|
||||||
|
|
||||||
file_path = movie['path']
|
file_path = movie['path']
|
||||||
parent_folder = os.path.dirname(file_path)
|
parent_folder = os.path.dirname(file_path)
|
||||||
|
|
||||||
# Elimina il file fisico
|
# Delete the movie file
|
||||||
try:
|
try:
|
||||||
if os.path.exists(file_path):
|
if os.path.exists(file_path):
|
||||||
os.remove(file_path)
|
os.remove(file_path)
|
||||||
@ -508,7 +627,7 @@ def remove_movie():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error deleting movie file: {str(e)}")
|
logging.error(f"Error deleting movie file: {str(e)}")
|
||||||
|
|
||||||
# Elimina la cartella superiore se vuota
|
# Delete the parent folder if empty
|
||||||
try:
|
try:
|
||||||
if os.path.exists(parent_folder) and not os.listdir(parent_folder):
|
if os.path.exists(parent_folder) and not os.listdir(parent_folder):
|
||||||
os.rmdir(parent_folder)
|
os.rmdir(parent_folder)
|
||||||
@ -516,60 +635,43 @@ def remove_movie():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error deleting parent folder: {str(e)}")
|
logging.error(f"Error deleting parent folder: {str(e)}")
|
||||||
|
|
||||||
# Rimuovi il film dal database
|
# Remove the movie from the database
|
||||||
result = downloads_collection.delete_one({'type': 'movie', 'id': movie_id})
|
result = downloads_collection.delete_one({'type': 'movie', 'id': str(movie_id)})
|
||||||
|
|
||||||
if result.deleted_count > 0:
|
if result.deleted_count > 0:
|
||||||
return jsonify({'success': True}), 200
|
return JSONResponse(status_code=200, content={'success': True})
|
||||||
else:
|
else:
|
||||||
return jsonify({'error': 'Failed to delete movie from database'}), 500
|
raise HTTPException(status_code=500, detail="Failed to delete movie from database")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error deleting movie: {str(e)}")
|
logging.error(f"Error deleting movie: {str(e)}")
|
||||||
return jsonify({'error': 'Failed to delete movie'}), 500
|
raise HTTPException(status_code=500, detail="Failed to delete movie")
|
||||||
|
|
||||||
@app.route('/moviePath', methods=['GET'])
|
# Fetch the path of a specific movie
|
||||||
def fetch_movie_path():
|
@app.get("/server/path/movie", response_model=dict, summary="Get movie download path on disk")
|
||||||
|
async def fetch_movie_path(movie_id: int):
|
||||||
"""
|
"""
|
||||||
Endpoint to fetch the path of a specific movie.
|
Endpoint to fetch the path of a specific movie.
|
||||||
"""
|
"""
|
||||||
try:
|
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})
|
movie = downloads_collection.find_one({'type': 'movie', 'id': movie_id}, {'_id': 0, 'path': 1})
|
||||||
|
|
||||||
if movie and 'path' in movie:
|
if movie and 'path' in movie:
|
||||||
return jsonify({'path': movie['path']}), 200
|
return {"path": movie['path']}
|
||||||
else:
|
else:
|
||||||
return jsonify({'error': 'Movie not found'}), 404
|
raise HTTPException(status_code=404, detail="Movie not found")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error fetching movie path: {str(e)}")
|
logging.error(f"Error fetching movie path: {str(e)}")
|
||||||
return jsonify({'error': 'Failed to fetch movie path'}), 500
|
raise HTTPException(status_code=500, detail="Failed to fetch movie path")
|
||||||
|
|
||||||
@app.route('/episodePath', methods=['GET'])
|
# Fetch the path of a specific episode
|
||||||
def fetch_episode_path():
|
@app.get("/server/path/episode", response_model=dict, summary="Get episode download path on disk")
|
||||||
|
async def fetch_episode_path(series_id: int, season_number: int, episode_number: int):
|
||||||
"""
|
"""
|
||||||
Endpoint to fetch the path of a specific episode.
|
Endpoint to fetch the path of a specific episode.
|
||||||
"""
|
"""
|
||||||
try:
|
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({
|
episode = downloads_collection.find_one({
|
||||||
'type': 'tv',
|
'type': 'tv',
|
||||||
'id': series_id,
|
'id': series_id,
|
||||||
@ -578,17 +680,17 @@ def fetch_episode_path():
|
|||||||
}, {'_id': 0, 'path': 1})
|
}, {'_id': 0, 'path': 1})
|
||||||
|
|
||||||
if episode and 'path' in episode:
|
if episode and 'path' in episode:
|
||||||
return jsonify({'path': episode['path']}), 200
|
return {"path": episode['path']}
|
||||||
else:
|
else:
|
||||||
return jsonify({'error': 'Episode not found'}), 404
|
raise HTTPException(status_code=404, detail="Episode not found")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error fetching episode path: {str(e)}")
|
logging.error(f"Error fetching episode path: {str(e)}")
|
||||||
return jsonify({'error': 'Failed to fetch episode path'}), 500
|
raise HTTPException(status_code=500, detail="Failed to fetch episode path")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ---------- MAIN ------------
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
ensure_collections_exist(db)
|
uvicorn.run(app, host="127.0.0.1", port=1234, loop="asyncio")
|
||||||
app.run(debug=True, port=1234, threaded=True)
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user