mirror of
https://github.com/Viren070/MediaFusion.git
synced 2025-12-01 23:21:11 +01:00
175 lines
5.8 KiB
Python
175 lines
5.8 KiB
Python
import json
|
|
import logging
|
|
from typing import Optional, Dict, List
|
|
|
|
import httpx
|
|
|
|
from db import schemas
|
|
from db.config import settings
|
|
from db.redis_database import REDIS_ASYNC_CLIENT
|
|
|
|
|
|
class MDBListScraper:
|
|
def __init__(self, api_key: str):
|
|
self.api_key = api_key
|
|
self.base_url = "https://api.mdblist.com"
|
|
self.client = httpx.AsyncClient(proxy=settings.requests_proxy_url, timeout=30.0)
|
|
self.batch_size = 200
|
|
|
|
async def close(self):
|
|
await self.client.aclose()
|
|
|
|
async def _fetch_list(
|
|
self,
|
|
list_config: schemas.MDBListItem,
|
|
offset: int = 0,
|
|
genre: Optional[str] = None,
|
|
) -> Optional[Dict]:
|
|
"""Fetch a list from MDBList API"""
|
|
params = {
|
|
"apikey": self.api_key,
|
|
"limit": self.batch_size,
|
|
"offset": offset,
|
|
"append_to_response": "genre",
|
|
"sort": list_config.sort,
|
|
"order": list_config.order,
|
|
}
|
|
if genre:
|
|
params["filter_genre"] = genre
|
|
|
|
# Update cache key to include sort and order
|
|
cache_key = f"mdblist:raw:{list_config.id}:offset_{offset}:{genre or 'all'}:sort_{list_config.sort}:order_{list_config.order}"
|
|
cached_data = await REDIS_ASYNC_CLIENT.get(cache_key)
|
|
|
|
if cached_data:
|
|
return json.loads(cached_data)
|
|
|
|
try:
|
|
response = await self.client.get(
|
|
f"{self.base_url}/lists/{list_config.id}/items", params=params
|
|
)
|
|
if response.status_code == 200:
|
|
# Cache raw API response for 60 minutes
|
|
await REDIS_ASYNC_CLIENT.set(cache_key, response.text, ex=3600)
|
|
return response.json()
|
|
logging.error(f"Failed to fetch MDBList data: {response.status_code}")
|
|
return None
|
|
except Exception as e:
|
|
logging.error(f"Error fetching MDBList data: {e}")
|
|
return None
|
|
|
|
@staticmethod
|
|
def _convert_to_meta(item: Dict, media_type: str) -> schemas.Meta:
|
|
"""Convert MDBList item to Meta object"""
|
|
genres = item.get("genre", [])
|
|
if genres and genres[0] is None:
|
|
genres = None
|
|
return schemas.Meta.model_validate(
|
|
{
|
|
"_id": item["imdb_id"],
|
|
"type": media_type,
|
|
"title": item["title"],
|
|
"year": item["release_year"],
|
|
"genres": genres,
|
|
"poster": f"{settings.poster_host_url}/poster/{media_type}/{item['imdb_id']}.jpg",
|
|
}
|
|
)
|
|
|
|
async def get_all_list_items(
|
|
self,
|
|
list_config: schemas.MDBListItem,
|
|
genre: Optional[str] = None,
|
|
) -> List[str]:
|
|
"""
|
|
Fetch all IMDb IDs from a list until no more results are available.
|
|
Used for filtered results to ensure complete dataset.
|
|
"""
|
|
cache_key = f"mdblist:all_ids:{list_config.id}:{list_config.catalog_type}:{genre or 'all'}:sort_{list_config.sort}:order_{list_config.order}"
|
|
cached_ids = await REDIS_ASYNC_CLIENT.lrange(cache_key, 0, -1)
|
|
|
|
if cached_ids:
|
|
return [cached_id.decode() for cached_id in cached_ids]
|
|
|
|
all_imdb_ids = []
|
|
offset = 0
|
|
|
|
while True:
|
|
batch = await self._fetch_list(list_config, offset, genre)
|
|
if not batch:
|
|
break
|
|
|
|
# Get the correct list based on media type
|
|
items = batch.get(
|
|
"movies" if list_config.catalog_type == "movie" else "shows", []
|
|
)
|
|
if not items:
|
|
break
|
|
|
|
# Filter valid IMDb IDs for the specific media type
|
|
new_ids = [
|
|
item["imdb_id"]
|
|
for item in items
|
|
if item.get("imdb_id", "").startswith("tt")
|
|
]
|
|
|
|
if not new_ids:
|
|
break
|
|
|
|
all_imdb_ids.extend(new_ids)
|
|
offset += self.batch_size
|
|
|
|
# If we got fewer items than batch_size, we've reached the end
|
|
if len(items) < self.batch_size:
|
|
break
|
|
|
|
# Cache the complete list if we got any results
|
|
if all_imdb_ids:
|
|
pipeline = await REDIS_ASYNC_CLIENT.pipeline()
|
|
pipeline.delete(cache_key)
|
|
pipeline.rpush(cache_key, *all_imdb_ids)
|
|
pipeline.expire(cache_key, 3600) # Cache for 1 hour
|
|
await pipeline.execute()
|
|
|
|
return all_imdb_ids
|
|
|
|
async def get_list_items(
|
|
self,
|
|
list_config: schemas.MDBListItem,
|
|
skip: int = 0,
|
|
limit: int = 25,
|
|
genre: Optional[str] = None,
|
|
use_filters: bool = True,
|
|
) -> List[str] | List[schemas.Meta]:
|
|
"""
|
|
Get items from a MDBList list.
|
|
For filtered results (use_filters=True), fetches all available items.
|
|
For unfiltered results, uses regular pagination.
|
|
"""
|
|
if use_filters:
|
|
return await self.get_all_list_items(list_config, genre)
|
|
|
|
# For unfiltered results, use regular pagination
|
|
offset = (skip // self.batch_size) * self.batch_size
|
|
batch = await self._fetch_list(list_config, offset, genre)
|
|
if not batch:
|
|
return []
|
|
|
|
items = batch.get(
|
|
"movies" if list_config.catalog_type == "movie" else "shows", []
|
|
)
|
|
meta_list = [
|
|
self._convert_to_meta(item, list_config.catalog_type)
|
|
for item in items
|
|
if item.get("imdb_id", "").startswith("tt")
|
|
]
|
|
|
|
# Calculate the slice we need from this batch
|
|
start_idx = skip % self.batch_size
|
|
end_idx = min(start_idx + limit, len(meta_list))
|
|
return meta_list[start_idx:end_idx]
|
|
|
|
|
|
async def initialize_mdblist_scraper(api_key: str) -> MDBListScraper:
|
|
"""Initialize MDBList scraper with API key"""
|
|
return MDBListScraper(api_key)
|