from http import HTTPMethod from common.utils import http_request YT = "https://www.googleapis.com/youtube/v3" class YouTubeClient: def __init__(self, api_key: str): self.api_key = api_key async def _resolve_channel_id(self, url: str) -> str: print("input yt url : ", url) # /channel/UCxxxxx → 채널 ID 직접 반환 if "/channel/" in url: return url.split("/channel/")[1].split("/")[0] if "/@" in url: val = "@" + url.split("/@")[1].split("/")[0] elif "/c/" in url: val = url.split("/c/")[1].split("/")[0] elif "/user/" in url: val = url.split("/user/")[1].split("/")[0] elif url.startswith("UC") and len(url) == 24: return url else: val = url print("val : ", val) for param in ("forHandle", "forUsername"): resp = await http_request( HTTPMethod.GET, url=f"{YT}/channels", params={"part": "id", param: val, "key": self.api_key}, label="yt-resolve", ) if resp and resp.is_success and (items := resp.json().get("items", [])): print("items : ", items) return items[0]["id"] print("YT NOT FOUND") return "" async def fetch_channel(self, url: str) -> dict | None: channel_id = await self._resolve_channel_id(url) if not channel_id: return None resp = await http_request( HTTPMethod.GET, url=f"{YT}/channels", params={"part": "snippet,statistics", "id": channel_id, "key": self.api_key}, label="yt-channel", ) if not resp or not resp.is_success: return None items = resp.json().get("items", []) if not items: return None channel = items[0] video_ids: list[str] = [] resp = await http_request( HTTPMethod.GET, url=f"{YT}/search", params={"part": "snippet", "channelId": channel_id, "order": "viewCount", "type": "video", "maxResults": 10, "key": self.api_key}, label="yt-search", ) if resp and resp.is_success: video_ids = [i["id"]["videoId"] for i in resp.json().get("items", []) if i.get("id", {}).get("videoId")] videos: list[dict] = [] if video_ids: resp = await http_request( HTTPMethod.GET, url=f"{YT}/videos", params={"part": "snippet,statistics,contentDetails", "id": ",".join(video_ids), "key": self.api_key}, label="yt-videos", ) if resp and resp.is_success: videos = resp.json().get("items", [])[:10] return {"channelId": channel_id, "channel": channel, "videos": videos} async def get_channel(self, url: str) -> dict | None: raw = await self.fetch_channel(url) if not raw: return None ch = raw["channel"] stats = ch.get("statistics", {}) snippet = ch.get("snippet", {}) return { "channelId": raw["channelId"], "channelName": snippet.get("title"), "handle": snippet.get("customUrl"), "description": snippet.get("description", ""), "publishedAt": snippet.get("publishedAt"), "subscribers": int(stats.get("subscriberCount", 0)), "totalViews": int(stats.get("viewCount", 0)), "totalVideos": int(stats.get("videoCount", 0)), "videos": [ { "title": v.get("snippet", {}).get("title"), "views": int(v.get("statistics", {}).get("viewCount", 0)), "likes": int(v.get("statistics", {}).get("likeCount", 0)), "comments": int(v.get("statistics", {}).get("commentCount", 0)), "date": v.get("snippet", {}).get("publishedAt"), "duration": v.get("contentDetails", {}).get("duration"), "url": f"https://www.youtube.com/watch?v={v['id']}", } for v in raw["videos"] ], } async def search_channels(self, query: str, max_results: int = 3) -> list[str]: resp = await http_request( HTTPMethod.GET, url=f"{YT}/search", params={"part": "snippet", "type": "channel", "q": query, "maxResults": max_results, "key": self.api_key}, label="yt-search-channels", ) if not resp or not resp.is_success: return [] return [ i.get("snippet", {}).get("channelId") or i.get("id", {}).get("channelId") for i in resp.json().get("items", []) if i.get("snippet", {}).get("channelId") or i.get("id", {}).get("channelId") ]