Implement YouTube manager
This commit is contained in:
parent
486cb82773
commit
b958f4fe55
8 changed files with 253 additions and 38 deletions
71
breadtube_bot/youtube_api.py
Normal file
71
breadtube_bot/youtube_api.py
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import json
|
||||
import time
|
||||
from typing import TYPE_CHECKING
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
from breadtube_bot.yt_objects import SearchResult
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import logging
|
||||
from .objects import HTTPHeaders
|
||||
|
||||
|
||||
class YoutubeManager:
|
||||
DEFAULT_DAILY_REQUESTS = 10_000
|
||||
|
||||
@dataclass
|
||||
class RateLimit:
|
||||
remaining: int
|
||||
next_reset: float
|
||||
|
||||
def __init__(self, api_key: str, logger: logging.Logger):
|
||||
self._api_key = api_key
|
||||
self._logger = logger
|
||||
self.rate_limit = self.RateLimit(remaining=self.DEFAULT_DAILY_REQUESTS, next_reset=time.time() + 24 * 3600)
|
||||
|
||||
def _request(self, url: str, request_timeout: float, expected_status: int = 200) -> tuple[HTTPHeaders, dict]:
|
||||
if time.time() >= self.rate_limit.next_reset:
|
||||
self.rate_limit.next_reset = time.time() + 24 * 3600
|
||||
self.rate_limit.remaining = self.DEFAULT_DAILY_REQUESTS
|
||||
elif self.rate_limit.remaining <= 0:
|
||||
sleep_time = time.time() - self.rate_limit.next_reset
|
||||
self._logger.debug('No more remaining in Youtube RateLimit : sleeping for %.03fs', sleep_time)
|
||||
time.sleep(sleep_time)
|
||||
self.rate_limit.remaining -= 1
|
||||
|
||||
request = urllib.request.Request(url)
|
||||
request.add_header('Accept', 'application/json')
|
||||
try:
|
||||
with urllib.request.urlopen(request, timeout=request_timeout) as response:
|
||||
if response.status != expected_status:
|
||||
raise RuntimeError(
|
||||
f'Unexpected YT status {response.status} (expected: {expected_status})'
|
||||
f' -> {response.read().decode()}')
|
||||
return dict(response.getheaders()), json.loads(response.read().decode())
|
||||
except urllib.error.HTTPError as error:
|
||||
raise RuntimeError(
|
||||
f'HTTP error calling API ({url}): {error}:\n'
|
||||
f'Headers:\n{error.headers}Body:\n{error.read()}') from error
|
||||
except urllib.error.URLError as error:
|
||||
raise RuntimeError(f'URL error calling YT API ({url}): {error}') from error
|
||||
except TimeoutError as error:
|
||||
raise RuntimeError(f'Timeout calling YT API ({url}): {error}') from error
|
||||
|
||||
def request_channel_info(self, channel_id: str, request_timeout: float) -> tuple[
|
||||
HTTPHeaders, dict]:
|
||||
url = ('https://www.googleapis.com/youtube/v3/channels?part=snippet'
|
||||
f'&id={channel_id}&key={self._api_key}')
|
||||
headers, info = self._request(url=url, request_timeout=request_timeout)
|
||||
return headers, info
|
||||
|
||||
def request_channel_videos(self, channel_id: str, max_results: int, request_timeout: float) -> tuple[
|
||||
HTTPHeaders, SearchResult]:
|
||||
url = ('https://www.googleapis.com/youtube/v3/search?part=snippet'
|
||||
f'&channelId={channel_id}&maxResults={max_results}&order=date&key={self._api_key}')
|
||||
headers, info = self._request(url=url, request_timeout=request_timeout)
|
||||
return headers, SearchResult.from_dict(info)
|
||||
Loading…
Add table
Add a link
Reference in a new issue