breadtube-bot/breadtube_bot/youtube_objects.py
2025-10-30 00:00:09 +09:00

177 lines
4.2 KiB
Python

from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import datetime
from types import get_original_bases
from typing import Generic, Self, TypeVar, get_args
T = TypeVar('T')
class _Api(Generic[T], ABC):
@staticmethod
@abstractmethod
def from_dict(info: dict) -> T:
raise NotImplementedError
T_api = TypeVar('T_api', bound=_Api)
# Generic Objects
@dataclass
class PageInfo(_Api):
totalResults: int
resultsPerPage: int
@staticmethod
def from_dict(info: dict) -> PageInfo:
return PageInfo(totalResults=info['totalResults'], resultsPerPage=info['resultsPerPage'])
@dataclass
class ThumbnailInfo(_Api):
url: str
width: int
height: int
@staticmethod
def from_dict(info: dict) -> ThumbnailInfo:
return ThumbnailInfo(url=info['url'], width=info['width'], height=info['height'])
@dataclass
class Thumbnails(_Api):
default: ThumbnailInfo
medium: ThumbnailInfo
high: ThumbnailInfo
@staticmethod
def from_dict(info: dict) -> Thumbnails:
return Thumbnails(
default=ThumbnailInfo.from_dict(info['default']),
medium=ThumbnailInfo.from_dict(info['medium']),
high=ThumbnailInfo.from_dict(info['high']))
@dataclass
class Result(Generic[T_api]):
kind: str
etag: str
nextPageToken: str | None
pageInfo: PageInfo
items: list[T_api]
@classmethod
def from_dict(cls, info: dict) -> Self:
item_type = get_args(get_original_bases(cls)[0])[0]
return cls(
kind=info['kind'],
etag=info['etag'],
nextPageToken=info.get('nextPageToken'),
pageInfo=PageInfo.from_dict(info['pageInfo']),
items=[item_type.from_dict(i) for i in info['items']])
# Channel Objects
@dataclass
class ChannelSnippet(_Api):
title: str
description: str
customUrl: str
publishedAt: datetime
thumbnails: Thumbnails
country: str | None
@staticmethod
def from_dict(info: dict) -> ChannelSnippet:
return ChannelSnippet(
title=info['title'],
description=info['description'],
customUrl=info['customUrl'],
publishedAt=datetime.fromisoformat(info['publishedAt']),
thumbnails=Thumbnails.from_dict(info['thumbnails']),
country=info.get('country'))
@dataclass
class ChannelResultItem(_Api):
kind: str
etag: str
id: str
snippet: ChannelSnippet
@staticmethod
def from_dict(info: dict) -> ChannelResultItem:
return ChannelResultItem(
kind=info['kind'],
etag=info['etag'],
id=info['id'],
snippet=ChannelSnippet.from_dict(info['snippet']))
class ChannelResult(Result[ChannelResultItem]):
pass
# Search Objects
@dataclass
class SearchResultId(_Api):
kind: str
videoId: str
@staticmethod
def from_dict(info: dict) -> SearchResultId:
return SearchResultId(kind=info['kind'], videoId=info['videoId'])
@dataclass
class SearchSnippet(_Api):
publishedAt: datetime
channelId: str
title: str
description: str
thumbnails: Thumbnails
channelTitle: str
liveBroadcastContent: str
publishTime: datetime
@staticmethod
def from_dict(info: dict) -> SearchSnippet:
return SearchSnippet(
publishedAt=datetime.fromisoformat(info['publishedAt']),
channelId=info['channelId'],
title=info['title'],
description=info['description'],
thumbnails=Thumbnails.from_dict(info['thumbnails']),
channelTitle=info['channelTitle'],
liveBroadcastContent=info['liveBroadcastContent'],
publishTime=datetime.fromisoformat(info['publishTime']))
@dataclass
class SearchResultItem(_Api):
kind: str
etag: str
id: SearchResultId
snippet: SearchSnippet
@staticmethod
def from_dict(info: dict) -> SearchResultItem:
return SearchResultItem(
kind=info['kind'],
etag=info['etag'],
id=SearchResultId.from_dict(info['id']),
snippet=SearchSnippet.from_dict(info['snippet']))
class SearchResult(Result[SearchResultItem]):
pass