add download service
This commit is contained in:
145
backend/app/openings_downloader/services/shikimori.py
Normal file
145
backend/app/openings_downloader/services/shikimori.py
Normal file
@@ -0,0 +1,145 @@
|
||||
import httpx
|
||||
from typing import List, Optional
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from ..db_models import Anime
|
||||
from ..schemas import AnimeSearchResult
|
||||
from ..config import downloader_settings
|
||||
|
||||
# Shared HTTP client for Shikimori API
|
||||
_shikimori_client: Optional[httpx.AsyncClient] = None
|
||||
|
||||
|
||||
def _get_shikimori_client() -> httpx.AsyncClient:
|
||||
"""Get or create shared HTTP client for Shikimori."""
|
||||
global _shikimori_client
|
||||
if _shikimori_client is None or _shikimori_client.is_closed:
|
||||
headers = {
|
||||
"User-Agent": downloader_settings.shikimori_user_agent,
|
||||
}
|
||||
if downloader_settings.shikimori_token:
|
||||
headers["Authorization"] = f"Bearer {downloader_settings.shikimori_token}"
|
||||
_shikimori_client = httpx.AsyncClient(
|
||||
headers=headers,
|
||||
timeout=30.0,
|
||||
)
|
||||
return _shikimori_client
|
||||
|
||||
|
||||
class ShikimoriService:
|
||||
"""Service for Shikimori GraphQL API."""
|
||||
|
||||
GRAPHQL_URL = "https://shikimori.one/api/graphql"
|
||||
|
||||
def __init__(self):
|
||||
self.client = _get_shikimori_client()
|
||||
|
||||
async def search(
|
||||
self,
|
||||
query: str,
|
||||
year: Optional[int] = None,
|
||||
status: Optional[str] = None,
|
||||
limit: int = 20,
|
||||
) -> List[AnimeSearchResult]:
|
||||
"""Search anime by query using Shikimori GraphQL API."""
|
||||
|
||||
graphql_query = """
|
||||
query($search: String, $limit: Int, $season: SeasonString, $status: AnimeStatusString) {
|
||||
animes(search: $search, limit: $limit, season: $season, status: $status) {
|
||||
id
|
||||
russian
|
||||
english
|
||||
japanese
|
||||
airedOn { year }
|
||||
poster { originalUrl }
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
variables = {
|
||||
"search": query,
|
||||
"limit": limit,
|
||||
}
|
||||
if year:
|
||||
variables["season"] = str(year)
|
||||
if status:
|
||||
variables["status"] = status
|
||||
|
||||
response = await self.client.post(
|
||||
self.GRAPHQL_URL,
|
||||
json={"query": graphql_query, "variables": variables},
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
results = []
|
||||
for anime in data.get("data", {}).get("animes", []):
|
||||
results.append(AnimeSearchResult(
|
||||
shikimori_id=int(anime["id"]),
|
||||
title_russian=anime.get("russian"),
|
||||
title_english=anime.get("english"),
|
||||
title_japanese=anime.get("japanese"),
|
||||
year=anime.get("airedOn", {}).get("year") if anime.get("airedOn") else None,
|
||||
poster_url=anime.get("poster", {}).get("originalUrl") if anime.get("poster") else None,
|
||||
))
|
||||
|
||||
return results
|
||||
|
||||
async def get_or_create_anime(self, db: AsyncSession, shikimori_id: int) -> Anime:
|
||||
"""Get anime from DB or fetch from Shikimori and create."""
|
||||
|
||||
# Check if exists (with themes eagerly loaded)
|
||||
query = (
|
||||
select(Anime)
|
||||
.where(Anime.shikimori_id == shikimori_id)
|
||||
.options(selectinload(Anime.themes))
|
||||
)
|
||||
result = await db.execute(query)
|
||||
anime = result.scalar_one_or_none()
|
||||
|
||||
if anime:
|
||||
return anime
|
||||
|
||||
# Fetch from Shikimori
|
||||
graphql_query = """
|
||||
query($ids: String!) {
|
||||
animes(ids: $ids, limit: 1) {
|
||||
id
|
||||
russian
|
||||
english
|
||||
japanese
|
||||
airedOn { year }
|
||||
poster { originalUrl }
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
response = await self.client.post(
|
||||
self.GRAPHQL_URL,
|
||||
json={"query": graphql_query, "variables": {"ids": str(shikimori_id)}},
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
animes = data.get("data", {}).get("animes", [])
|
||||
if not animes:
|
||||
raise ValueError(f"Anime with ID {shikimori_id} not found on Shikimori")
|
||||
|
||||
anime_data = animes[0]
|
||||
|
||||
anime = Anime(
|
||||
shikimori_id=shikimori_id,
|
||||
title_russian=anime_data.get("russian"),
|
||||
title_english=anime_data.get("english"),
|
||||
title_japanese=anime_data.get("japanese"),
|
||||
year=anime_data.get("airedOn", {}).get("year") if anime_data.get("airedOn") else None,
|
||||
poster_url=anime_data.get("poster", {}).get("originalUrl") if anime_data.get("poster") else None,
|
||||
)
|
||||
|
||||
db.add(anime)
|
||||
await db.commit()
|
||||
await db.refresh(anime)
|
||||
|
||||
return anime
|
||||
Reference in New Issue
Block a user