diff --git a/scanner/scanner/client.py b/scanner/scanner/client.py index 43e39319..65fffa93 100644 --- a/scanner/scanner/client.py +++ b/scanner/scanner/client.py @@ -3,6 +3,8 @@ from logging import getLogger from aiohttp import ClientSession +from .models.movie import Movie +from .models.serie import Serie from .models.videos import Video, VideoCreated, VideoInfo logger = getLogger(__name__) @@ -10,33 +12,32 @@ logger = getLogger(__name__) class KyooClient: def __init__(self) -> None: - self._api_key: str = os.environ.get("KYOO_APIKEY") # type: ignore - if not self._api_key: + api_key = os.environ.get("KYOO_APIKEY") + if not api_key: print("Missing environment variable 'KYOO_APIKEY'.") exit(2) - self._url = os.environ.get("KYOO_URL", "http://api:3567/api") - - async def __aenter__(self): self._client = ClientSession( + base_url=os.environ.get("KYOO_URL", "http://api:3567/api"), headers={ - "User-Agent": "kyoo", + "User-Agent": "kyoo scanner v5", + "X-API-KEY": api_key, }, ) + + async def __aenter__(self): return self async def __aexit__(self): await self._client.close() async def get_videos_info(self) -> VideoInfo: - async with self._client.get( - f"{self._url}/videos", - ) as r: + async with self._client.get("/videos") as r: r.raise_for_status() return VideoInfo(**await r.json()) async def create_videos(self, videos: list[Video]) -> list[VideoCreated]: async with self._client.post( - f"{self._url}/videos", + "videos", json=[x.model_dump_json() for x in videos], ) as r: r.raise_for_status() @@ -44,85 +45,21 @@ class KyooClient: async def delete_videos(self, videos: list[str] | set[str]): async with self._client.delete( - f"{self._url}/videos", + "videos", json=videos, ) as r: r.raise_for_status() - # async def link_collection( - # self, collection: str, type: Literal["movie"] | Literal["show"], id: str - # ): - # async with self.client.put( - # f"{self._url}/collections/{collection}/{type}/{id}", - # headers={"X-API-Key": self._api_key}, - # ) as r: - # # Allow 409 and continue as if it worked. - # if not r.ok and r.status != 409: - # logger.error(f"Request error: {await r.text()}") - # r.raise_for_status() - # - # async def post(self, path: str, *, data: dict[str, Any]) -> str: - # logger.debug( - # "Sending %s: %s", - # path, - # jsons.dumps( - # data, - # key_transformer=jsons.KEY_TRANSFORMER_CAMELCASE, - # jdkwargs={"indent": 4}, - # ), - # ) - # async with self.client.post( - # f"{self._url}/{path}", - # json=data, - # headers={"X-API-Key": self._api_key}, - # ) as r: - # # Allow 409 and continue as if it worked. - # if not r.ok and r.status != 409: - # logger.error(f"Request error: {await r.text()}") - # r.raise_for_status() - # ret = await r.json() - # return ret["id"] - # - # async def delete( - # self, - # path: str, - # ): - # logger.info("Deleting %s", path) - # - # async with self.client.delete( - # f"{self._url}/paths?recursive=true&path={quote(path)}", - # headers={"X-API-Key": self._api_key}, - # ) as r: - # if not r.ok: - # logger.error(f"Request error: {await r.text()}") - # r.raise_for_status() - # - # async def get(self, path: str): - # async with self.client.get( - # f"{self._url}/{path}", - # headers={"X-API-Key": self._api_key}, - # ) as r: - # if not r.ok: - # logger.error(f"Request error: {await r.text()}") - # r.raise_for_status() - # return await r.json() - # - # async def put(self, path: str, *, data: dict[str, Any]): - # logger.debug( - # "Sending %s: %s", - # path, - # jsons.dumps( - # data, - # key_transformer=jsons.KEY_TRANSFORMER_CAMELCASE, - # jdkwargs={"indent": 4}, - # ), - # ) - # async with self.client.put( - # f"{self._url}/{path}", - # json=data, - # headers={"X-API-Key": self._api_key}, - # ) as r: - # # Allow 409 and continue as if it worked. - # if not r.ok and r.status != 409: - # logger.error(f"Request error: {await r.text()}") - # r.raise_for_status() + async def create_movie(self, movie: Movie): + async with self._client.post( + "movies", + json=movie.model_dump_json(), + ) as r: + r.raise_for_status() + + async def create_serie(self, serie: Serie): + async with self._client.post( + "series", + json=serie.model_dump_json(), + ) as r: + r.raise_for_status() diff --git a/scanner/scanner/providers/provider.py b/scanner/scanner/providers/provider.py index 691c135d..4f732fad 100644 --- a/scanner/scanner/providers/provider.py +++ b/scanner/scanner/providers/provider.py @@ -36,7 +36,10 @@ class Provider(ABC): raise NotImplementedError async def find_movie( - self, title: str, year: int | None, external_id: dict[str, str] + self, + title: str, + year: int | None, + external_id: dict[str, str], ) -> Movie: ret = await self.get_movie(external_id) if ret is not None: @@ -54,7 +57,10 @@ class Provider(ABC): return ret async def find_serie( - self, title: str, year: int | None, external_id: dict[str, str] + self, + title: str, + year: int | None, + external_id: dict[str, str], ) -> Serie: ret = await self.get_serie(external_id) if ret is not None: diff --git a/scanner/scanner/requests.py b/scanner/scanner/requests.py index 76545fdf..bfa43881 100644 --- a/scanner/scanner/requests.py +++ b/scanner/scanner/requests.py @@ -1,5 +1,6 @@ from __future__ import annotations +from logging import getLogger from typing import Literal from .client import KyooClient @@ -7,6 +8,8 @@ from .models.videos import Guess from .providers.composite import CompositeProvider from .utils import Model +logger = getLogger(__name__) + class Request(Model): kind: Literal["episode", "movie"] @@ -33,21 +36,45 @@ async def enqueue(requests: list[Request]): class RequestProcessor: def __init__(self, client: KyooClient, providers: CompositeProvider): self._client = client + self._providers = providers async def process_scan_requests(self): # select for update skip_locked limit 1 request: Request = ... if request.kind == "movie": - movie = await providers.get_movie( - request.title, request.year, request.external_id + movie = await self._providers.find_movie( + request.title, + request.year, + request.external_id, ) - movie.videos = request.videos + movie.videos = [x.id for x in request.videos] await self._client.create_movie(movie) else: - serie = await providers.get_serie(request.title, request.year) - # for vid in request.videos: - # for ep in vid.episodes: - # entry = next(x for x in series.entries if (ep.season is None or x.season == ep.season), None) + serie = await self._providers.find_serie( + request.title, + request.year, + request.external_id, + ) + for vid in request.videos: + for ep in vid.episodes: + entry = next( + ( + x + for x in serie.entries + if (ep.season is None and x.order == ep.episode) + or ( + x.season_number == ep.season + and x.episode_number == ep.episode + ) + ), + None, + ) + if entry is None: + logger.warning( + f"Couldn't match entry for {serie.slug} {ep.season or 'abs'}-e{ep.episode}." + ) + continue + entry.videos.append(vid.id) await self._client.create_serie(serie) # delete request