Files
variet-agent/tools/anissia_client.py
CD c92433b0b1 feat(tools): 애니메이션 자동화 파이프라인 구현
- tools/anissia_client.py: Anissia API 클라이언트 (편성표/자막)
- tools/nyaa_client.py: Nyaa.si RSS 토렌트 검색
- tools/qbit_client.py: qBittorrent Web API 클라이언트
- tools/subtitle_downloader.py: Google Drive/Tistory/Naver 자막 파서
- tools/title_matcher.py: 제목 매칭 + NAS 폴더명 생성
- tools/anime_pipeline.py: 전체 파이프라인 오케스트레이터
- tools/nas_scanner.py: NAS 폴더/파일 스캔
- prompts/unified.md: anime 모드 추가 (AI 평문 의도 분류)
- api/discord_bot.py: AI 평문 anime 핸들러 + /anime 슬래시 커맨드
- config.py: qBittorrent/NAS 설정 추가
- .agents/: agent_guide 워크플로우 통합
- docs/devlog: 세션 기록
2026-03-08 16:07:16 +09:00

121 lines
3.8 KiB
Python

"""Anissia API 클라이언트 — 애니 편성표 + 자막 정보 조회.
API Base: https://api.anissia.net
"""
import httpx
import logging
from dataclasses import dataclass, field
from typing import Optional
logger = logging.getLogger("variet.tools.anissia")
BASE_URL = "https://api.anissia.net"
WEEK_NAMES = {
0: "", 1: "", 2: "", 3: "",
4: "", 5: "", 6: "", 7: "기타",
}
@dataclass
class CaptionInfo:
"""자막 제작 정보."""
episode: str
name: str # 제작자 이름
website: str # 제작자 사이트 URL
updated: str # 업데이트 시각
@dataclass
class AnimeInfo:
"""애니메이션 정보."""
anime_no: int
subject: str # 한글 제목
original_subject: str # 원어 제목 (일어)
genres: str
week: int
time: str
status: str # ON / OFF
caption_count: int
start_date: str
end_date: str
website: str
twitter: str
class AnissiaClient:
"""Anissia REST API 클라이언트."""
def __init__(self, timeout: float = 15.0):
self._timeout = timeout
async def get_schedule(self, week: int) -> list[AnimeInfo]:
"""요일별 편성표 조회 (week: 0=일 ~ 6=토, 7=기타)."""
async with httpx.AsyncClient(timeout=self._timeout) as client:
resp = await client.get(f"{BASE_URL}/anime/schedule/{week}")
resp.raise_for_status()
data = resp.json()
if data.get("code") != "ok":
raise RuntimeError(f"Anissia API 오류: {data}")
return [
AnimeInfo(
anime_no=item["animeNo"],
subject=item["subject"],
original_subject=item.get("originalSubject", ""),
genres=item.get("genres", ""),
week=item.get("week", week) if isinstance(item.get("week"), int) else int(item.get("week", week)),
time=item.get("time", ""),
status=item.get("status", ""),
caption_count=item.get("captionCount", 0),
start_date=item.get("startDate", ""),
end_date=item.get("endDate", ""),
website=item.get("website", ""),
twitter=item.get("twitter", ""),
)
for item in data["data"]
]
async def get_all_schedule(self) -> list[AnimeInfo]:
"""전체 요일 편성표 조회 (0~7)."""
all_anime = []
for week in range(8):
try:
schedule = await self.get_schedule(week)
all_anime.extend(schedule)
except Exception as e:
logger.warning(f"편성표 조회 실패 (week={week}): {e}")
return all_anime
async def get_captions(self, anime_no: int) -> list[CaptionInfo]:
"""특정 애니 자막 목록 조회."""
async with httpx.AsyncClient(timeout=self._timeout) as client:
resp = await client.get(f"{BASE_URL}/anime/caption/animeNo/{anime_no}")
resp.raise_for_status()
data = resp.json()
if data.get("code") != "ok":
raise RuntimeError(f"Anissia caption API 오류: {data}")
return [
CaptionInfo(
episode=item.get("episode", ""),
name=item.get("name", ""),
website=item.get("website", ""),
updated=item.get("updDt", ""),
)
for item in data["data"]
]
async def search_anime(self, keyword: str) -> list[AnimeInfo]:
"""키워드로 전체 편성표에서 검색 (한글/일어 제목 매칭)."""
all_anime = await self.get_all_schedule()
keyword_lower = keyword.lower()
return [
a for a in all_anime
if keyword_lower in a.subject.lower()
or keyword_lower in a.original_subject.lower()
]