mirror of
https://github.com/Viren070/MediaFusion.git
synced 2025-12-01 23:21:11 +01:00
166 lines
5.5 KiB
Python
166 lines
5.5 KiB
Python
from datetime import timedelta
|
|
from typing import List, Dict, Any, Optional
|
|
|
|
import PTT
|
|
from db.models import TorrentStreams, MediaFusionMetaData
|
|
from scrapers.base_scraper import BaseScraper
|
|
from utils.runtime_const import YTS_SEARCH_TTL
|
|
|
|
|
|
class YTSScraper(BaseScraper):
|
|
cache_key_prefix = "yts"
|
|
yts_url = "https://yts.mx"
|
|
|
|
def __init__(self):
|
|
super().__init__(cache_key_prefix=self.cache_key_prefix, logger_name=__name__)
|
|
|
|
@BaseScraper.cache(ttl=YTS_SEARCH_TTL)
|
|
@BaseScraper.rate_limit(calls=2, period=timedelta(seconds=1))
|
|
async def _scrape_and_parse(
|
|
self,
|
|
user_data,
|
|
metadata: MediaFusionMetaData,
|
|
catalog_type: str,
|
|
season: Optional[int] = None,
|
|
episode: Optional[int] = None,
|
|
) -> List[TorrentStreams]:
|
|
# YTS is only for movies
|
|
if catalog_type != "movie":
|
|
self.metrics.record_skip("Not a movie")
|
|
return []
|
|
|
|
try:
|
|
response = await self.make_request(
|
|
f"{self.yts_url}/api/v2/movie_details.json",
|
|
params={"imdb_id": metadata.id},
|
|
timeout=15,
|
|
)
|
|
|
|
data = response.json()
|
|
|
|
if not self.validate_response(data):
|
|
self.metrics.record_error("invalid_response")
|
|
return []
|
|
|
|
movie = data.get("data", {}).get("movie")
|
|
if not movie:
|
|
self.metrics.record_skip("Movie not found")
|
|
return []
|
|
|
|
torrents = movie.get("torrents", [])
|
|
if not torrents:
|
|
self.metrics.record_skip("No torrents found")
|
|
return []
|
|
|
|
self.metrics.record_found_items(len(torrents))
|
|
self.logger.info(
|
|
f"Found {len(torrents)} torrents for {metadata.title} "
|
|
f"({metadata.year}) with IMDB ID {metadata.id}"
|
|
)
|
|
|
|
return await self.parse_movie_torrents(
|
|
processed_info_hashes=set(),
|
|
metadata=metadata,
|
|
movie=movie,
|
|
)
|
|
|
|
except Exception as e:
|
|
self.metrics.record_error("movie_search_error")
|
|
self.logger.error(f"Error searching movie: {e}")
|
|
return []
|
|
|
|
def validate_response(self, response: Dict[str, Any]) -> bool:
|
|
return (
|
|
isinstance(response, dict)
|
|
and response.get("status") == "ok"
|
|
and "data" in response
|
|
and isinstance(response["data"], dict)
|
|
)
|
|
|
|
async def parse_movie_torrents(
|
|
self,
|
|
processed_info_hashes: set[str],
|
|
metadata: MediaFusionMetaData,
|
|
movie: Dict[str, Any],
|
|
) -> List[TorrentStreams]:
|
|
streams = []
|
|
|
|
for torrent in movie.get("torrents", []):
|
|
try:
|
|
stream = await self.process_torrent(
|
|
movie, torrent, metadata, processed_info_hashes
|
|
)
|
|
if stream:
|
|
streams.append(stream)
|
|
|
|
except Exception as e:
|
|
self.metrics.record_error("torrent_processing_error")
|
|
self.logger.exception(f"Error processing torrent: {e}")
|
|
continue
|
|
|
|
return streams
|
|
|
|
async def process_torrent(
|
|
self,
|
|
movie: Dict[str, Any],
|
|
torrent: Dict[str, Any],
|
|
metadata: MediaFusionMetaData,
|
|
processed_info_hashes: set[str],
|
|
) -> Optional[TorrentStreams]:
|
|
try:
|
|
# Skip if we've already processed this info hash
|
|
info_hash = torrent.get("hash", "").lower()
|
|
if not info_hash or info_hash in processed_info_hashes:
|
|
self.metrics.record_skip("Duplicate info_hash")
|
|
return None
|
|
|
|
# Construct a standardized torrent name
|
|
torrent_title = (
|
|
f"{movie['title']} ({movie['year']}) "
|
|
f"{torrent['quality']} {torrent.get('type', '')} {torrent['video_codec']} "
|
|
f"{torrent['audio_channels']} ({torrent['size']}) [{movie['language']}] YTS"
|
|
).strip()
|
|
|
|
# Construct a standardized torrent name
|
|
parsed_data = {
|
|
"title": movie.get("title"),
|
|
"year": movie.get("year"),
|
|
}
|
|
if not self.validate_title_and_year(
|
|
parsed_data,
|
|
metadata,
|
|
"movie",
|
|
torrent_title,
|
|
):
|
|
return None
|
|
|
|
stream = TorrentStreams(
|
|
id=info_hash,
|
|
meta_id=metadata.id,
|
|
torrent_name=torrent_title,
|
|
size=int(torrent.get("size_bytes", 0)),
|
|
languages=PTT.parse.translate_langs([movie.get("language")]),
|
|
resolution=torrent["quality"],
|
|
codec=torrent.get("codec", ""),
|
|
quality=torrent["quality"],
|
|
audio=torrent.get("audio"),
|
|
source="YTS",
|
|
catalog=["yts_streams", "yts_movies"],
|
|
seeders=int(torrent.get("seeds", 0)),
|
|
announce_list=[],
|
|
created_at=torrent.get("date_uploaded"),
|
|
)
|
|
|
|
# Record metrics
|
|
self.metrics.record_processed_item()
|
|
self.metrics.record_quality(stream.quality)
|
|
self.metrics.record_source(stream.source)
|
|
|
|
processed_info_hashes.add(info_hash)
|
|
return stream
|
|
|
|
except Exception as e:
|
|
self.metrics.record_error("stream_processing_error")
|
|
self.logger.exception(f"Error creating stream: {e}")
|
|
return None
|