diff --git a/twitchdl/commands/download.py b/twitchdl/commands/download.py index acd98af..4b6f378 100644 --- a/twitchdl/commands/download.py +++ b/twitchdl/commands/download.py @@ -20,6 +20,7 @@ from twitchdl.entities import Data, DownloadOptions from twitchdl.exceptions import ConsoleError from twitchdl.http import download_all from twitchdl.output import blue, bold, dim, green, print_log, yellow +from twitchdl.twitch import Video def download(ids: list[str], args: DownloadOptions): @@ -115,7 +116,7 @@ def _concat_vods(vod_paths: list[str], target: str): raise ConsoleError(f"Joining files failed: {result.stderr}") -def get_video_placeholders(video: Data, format: str) -> Data: +def get_video_placeholders(video: Video, format: str) -> Data: date, time = video['publishedAt'].split("T") game = video["game"]["name"] if video["game"] else "Unknown" @@ -134,7 +135,7 @@ def get_video_placeholders(video: Data, format: str) -> Data: } -def _video_target_filename(video: Data, args: DownloadOptions): +def _video_target_filename(video: Video, args: DownloadOptions): subs = get_video_placeholders(video, args.format) try: diff --git a/twitchdl/commands/info.py b/twitchdl/commands/info.py index 6e8bc87..c23c1c8 100644 --- a/twitchdl/commands/info.py +++ b/twitchdl/commands/info.py @@ -47,7 +47,7 @@ def info(id: str, *, json: bool = False): raise ConsoleError(f"Invalid input: {id}") -def video_info(video, playlists, chapters): +def video_info(video: Video, playlists, chapters): click.echo() print_video(video) diff --git a/twitchdl/output.py b/twitchdl/output.py index 0e94a68..2873fcb 100644 --- a/twitchdl/output.py +++ b/twitchdl/output.py @@ -6,6 +6,7 @@ from twitchdl import utils from typing import Any, Callable, Generator, TypeVar from twitchdl.entities import Data +from twitchdl.twitch import Video T = TypeVar("T") @@ -78,7 +79,7 @@ def print_paged( -def print_video(video: Data): +def print_video(video: Video): published_at = video["publishedAt"].replace("T", " @ ").replace("Z", "") length = utils.format_duration(video["lengthSeconds"]) @@ -89,7 +90,7 @@ def print_video(video: Data): url = f"https://www.twitch.tv/videos/{video['id']}" click.secho(f"Video {video['id']}", bold=True) - click.secho(f"{video['title']}", fg="green") + click.secho(video["title"], fg="green") if channel or playing: click.echo(" ".join([channel, playing])) @@ -102,7 +103,7 @@ def print_video(video: Data): click.echo() -def print_video_compact(video: Data): +def print_video_compact(video: Video): id = video["id"] date = video["publishedAt"][:10] game = video["game"]["name"] if video["game"] else "" diff --git a/twitchdl/twitch.py b/twitchdl/twitch.py index 4054466..3457655 100644 --- a/twitchdl/twitch.py +++ b/twitchdl/twitch.py @@ -6,7 +6,7 @@ import httpx import json import click -from typing import Dict, Generator, Literal +from typing import Dict, Generator, Literal, TypedDict from twitchdl import CLIENT_ID from twitchdl.entities import Data from twitchdl.exceptions import ConsoleError @@ -16,6 +16,26 @@ ClipsPeriod = Literal["last_day", "last_week", "last_month", "all_time"] VideosSort = Literal["views", "time"] VideosType = Literal["archive", "highlight", "upload"] +class User(TypedDict): + login: str + displayName: str + + +class Game(TypedDict): + id: str + name: str + + +class Video(TypedDict): + id: str + title: str + description: str + publishedAt: str + broadcastType: str + lengthSeconds: int + game: Game + creator: User + class GQLError(click.ClickException): def __init__(self, errors: list[str]): @@ -67,6 +87,7 @@ VIDEO_FIELDS = """ broadcastType lengthSeconds game { + id name } creator { @@ -100,7 +121,7 @@ CLIP_FIELDS = """ """ -def get_video(video_id: str): +def get_video(video_id: str) -> Video | None: query = f""" {{ video(id: "{video_id}") {{ @@ -274,10 +295,10 @@ def channel_videos_generator( sort: VideosSort, type: VideosType, game_ids: list[str] | None = None -) -> tuple[int, Generator[Data, None, None]]: +) -> tuple[int, Generator[Video, None, None]]: game_ids = game_ids or [] - def _generator(videos: Data, max_videos: int) -> Generator[Data, None, None]: + def _generator(videos: Data, max_videos: int) -> Generator[Video, None, None]: for video in videos["edges"]: if max_videos < 1: return @@ -298,7 +319,7 @@ def channel_videos_generator( return videos["totalCount"], _generator(videos, max_videos) -def get_access_token(video_id, auth_token=None): +def get_access_token(video_id: str, auth_token: str | None = None): query = f""" {{ videoPlaybackAccessToken( @@ -315,7 +336,7 @@ def get_access_token(video_id, auth_token=None): }} """ - headers = {} + headers: dict[str, str] = {} if auth_token is not None: headers['authorization'] = f'OAuth {auth_token}'