mirror of
https://github.com/Viren070/MediaFusion.git
synced 2025-12-01 23:21:11 +01:00
209 lines
7.5 KiB
Python
209 lines
7.5 KiB
Python
import asyncio
|
|
from datetime import timedelta
|
|
from typing import List, Dict, Any
|
|
|
|
import PTT
|
|
from httpx import Response
|
|
from tenacity import RetryError
|
|
|
|
from db.config import settings
|
|
from db.models import TorrentStreams, MediaFusionMetaData, EpisodeFile
|
|
from scrapers.base_scraper import BaseScraper, ScraperError
|
|
from utils.parser import (
|
|
is_contain_18_plus_keywords,
|
|
)
|
|
from utils.runtime_const import ZILEAN_SEARCH_TTL
|
|
|
|
|
|
class ZileanScraper(BaseScraper):
|
|
cache_key_prefix = "zilean"
|
|
|
|
def __init__(self):
|
|
super().__init__(cache_key_prefix=self.cache_key_prefix, logger_name=__name__)
|
|
self.semaphore = asyncio.Semaphore(10)
|
|
|
|
@BaseScraper.cache(ttl=ZILEAN_SEARCH_TTL)
|
|
@BaseScraper.rate_limit(calls=5, period=timedelta(seconds=1))
|
|
async def _scrape_and_parse(
|
|
self,
|
|
user_data,
|
|
metadata: MediaFusionMetaData,
|
|
catalog_type: str,
|
|
season: int = None,
|
|
episode: int = None,
|
|
) -> List[TorrentStreams]:
|
|
search_task = asyncio.create_task(
|
|
self.make_request(
|
|
f"{settings.zilean_url}/dmm/search",
|
|
method="POST",
|
|
json={"queryText": metadata.title},
|
|
timeout=10,
|
|
)
|
|
)
|
|
|
|
if metadata.type == "movie":
|
|
params = {
|
|
"Query": metadata.title,
|
|
"Year": metadata.year,
|
|
}
|
|
else:
|
|
params = {
|
|
"Query": metadata.title,
|
|
"Season": season,
|
|
"Episode": episode,
|
|
}
|
|
|
|
filtered_task = asyncio.create_task(
|
|
self.make_request(
|
|
f"{settings.zilean_url}/dmm/filtered",
|
|
method="GET",
|
|
params=params,
|
|
timeout=10,
|
|
)
|
|
)
|
|
|
|
search_response, filtered_response = await asyncio.gather(
|
|
search_task, filtered_task, return_exceptions=True
|
|
)
|
|
|
|
stream_data = []
|
|
if isinstance(search_response, Response):
|
|
stream_data.extend(search_response.json())
|
|
else:
|
|
self.metrics.record_error("search_request_failed")
|
|
self.logger.error(
|
|
f"Error occurred while search {metadata.title}: {search_response}"
|
|
)
|
|
if isinstance(filtered_response, Response):
|
|
stream_data.extend(filtered_response.json())
|
|
else:
|
|
self.metrics.record_error("filtered_request_failed")
|
|
self.logger.error(
|
|
f"Error occurred while filtering {metadata.title}: {filtered_response}"
|
|
)
|
|
|
|
self.metrics.record_found_items(len(stream_data))
|
|
|
|
if not self.validate_response(stream_data):
|
|
self.metrics.record_error("no_valid_streams")
|
|
self.logger.error(f"No valid streams found for {metadata.title}")
|
|
return []
|
|
|
|
try:
|
|
streams = await self.parse_response(
|
|
stream_data, user_data, metadata, catalog_type, season, episode
|
|
)
|
|
return streams
|
|
except (ScraperError, RetryError):
|
|
self.metrics.record_error("parsing_failed")
|
|
except Exception as e:
|
|
self.metrics.record_error("unexpected_error")
|
|
self.logger.exception(
|
|
f"Error occurred while fetching {metadata.title}: {e}"
|
|
)
|
|
return []
|
|
|
|
async def parse_response(
|
|
self,
|
|
response: List[Dict[str, Any]],
|
|
user_data,
|
|
metadata: MediaFusionMetaData,
|
|
catalog_type: str,
|
|
season: int = None,
|
|
episode: int = None,
|
|
) -> List[TorrentStreams]:
|
|
tasks = [
|
|
self.process_stream(stream, metadata, catalog_type, season, episode)
|
|
for stream in response
|
|
]
|
|
results = await asyncio.gather(*tasks)
|
|
return [stream for stream in results if stream is not None]
|
|
|
|
async def process_stream(
|
|
self,
|
|
stream: Dict[str, Any],
|
|
metadata: MediaFusionMetaData,
|
|
catalog_type: str,
|
|
season: int = None,
|
|
episode: int = None,
|
|
) -> TorrentStreams | None:
|
|
async with self.semaphore:
|
|
try:
|
|
if is_contain_18_plus_keywords(stream["raw_title"]):
|
|
self.metrics.record_skip("Adult content")
|
|
self.logger.warning(
|
|
f"Stream contains 18+ keywords: {stream['raw_title']}"
|
|
)
|
|
return None
|
|
|
|
torrent_data = PTT.parse_title(stream["raw_title"], True)
|
|
if not self.validate_title_and_year(
|
|
torrent_data,
|
|
metadata,
|
|
catalog_type,
|
|
stream["raw_title"],
|
|
):
|
|
return None
|
|
|
|
torrent_stream = TorrentStreams(
|
|
id=stream["info_hash"],
|
|
meta_id=metadata.id,
|
|
torrent_name=stream["raw_title"],
|
|
announce_list=[],
|
|
size=stream["size"],
|
|
languages=torrent_data["languages"],
|
|
resolution=torrent_data.get("resolution"),
|
|
codec=torrent_data.get("codec"),
|
|
quality=torrent_data.get("quality"),
|
|
audio=torrent_data.get("audio"),
|
|
hdr=torrent_data.get("hdr"),
|
|
source="Zilean DMM",
|
|
catalog=["zilean_dmm_streams"],
|
|
)
|
|
|
|
if catalog_type == "movie":
|
|
# For the Movies, should not have seasons and episodes
|
|
if torrent_data.get("seasons") or torrent_data.get("episodes"):
|
|
self.metrics.record_skip("Unexpected season/episode info")
|
|
return None
|
|
torrent_stream.catalog.append("zilean_dmm_movies")
|
|
else:
|
|
torrent_stream.catalog.append("zilean_dmm_series")
|
|
seasons = torrent_data.get("seasons")
|
|
if not seasons:
|
|
self.metrics.record_skip("Missing season info")
|
|
return None
|
|
|
|
if episodes := torrent_data.get("episodes"):
|
|
episode_data = [
|
|
EpisodeFile(
|
|
season_number=seasons[0], episode_number=episode_number
|
|
)
|
|
for episode_number in episodes
|
|
]
|
|
elif seasons:
|
|
episode_data = [
|
|
EpisodeFile(season_number=season_number, episode_number=1)
|
|
for season_number in seasons
|
|
]
|
|
else:
|
|
self.metrics.record_skip("Missing episode info")
|
|
return None
|
|
|
|
torrent_stream.episode_files = episode_data
|
|
|
|
# Record metrics for successful processing
|
|
self.metrics.record_processed_item()
|
|
self.metrics.record_quality(torrent_stream.quality)
|
|
self.metrics.record_source(torrent_stream.source)
|
|
|
|
return torrent_stream
|
|
|
|
except Exception as e:
|
|
self.metrics.record_error("stream_processing_error")
|
|
self.logger.exception(f"Error processing stream: {e}")
|
|
return None
|
|
|
|
def validate_response(self, response: List[Dict[str, Any]]) -> bool:
|
|
return isinstance(response, list) and len(response) > 0
|