-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
416 additions
and
159 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,74 @@ | ||
from innertube import InnerTube | ||
from innertube.raw_models import ResponseContext | ||
from typing import Any, Mapping, Optional, Sequence, Set, Union | ||
from typing_extensions import TypeAlias | ||
import rich | ||
from pydantic import TypeAdapter | ||
from innertube import InnerTube, utils | ||
from innertube.renderers import parse_renderable | ||
from innertube.raw_models import Response, ResponseContext | ||
from innertube.renderers import RENDERERS | ||
|
||
IGNORE_FIELDS: Set[str] = {"responseContext", "trackingParams"} | ||
|
||
# response_context: ResponseContext = ResponseContext.model_validate( | ||
# data["responseContext"] | ||
# ) | ||
# tracking_params: str = TypeAdapter(str).validate_python( | ||
# data.get("trackingParams") | ||
# ) | ||
|
||
""" | ||
{ | ||
"responseContext": { | ||
"visitorData": "...", | ||
"...": "...", | ||
}, | ||
"items": [ | ||
{ | ||
"SomeRenderer": { | ||
"...": "...", | ||
}, | ||
"SomeOtherRenderer": { | ||
"...": "...", | ||
}, | ||
} | ||
], | ||
"trackingParams": "CAAQumkiEwiYnZL5osmDAxVOBwYAHQe9Czw=" | ||
} | ||
""" | ||
|
||
|
||
def parse_response(response: dict, /): | ||
parsed = {} | ||
|
||
key: str | ||
value: Mapping[str, Any] | ||
for key, value in response.items(): | ||
if key in IGNORE_FIELDS: | ||
rich.print(f"{key} -> [IGNORE]") | ||
# parsed[key] = value | ||
continue | ||
|
||
parsed[key] = parse_renderable(value, parent=key) | ||
|
||
return parsed | ||
|
||
|
||
# Clients | ||
WEB = InnerTube("WEB", "2.20230728.00.00") | ||
WEB_REMIX = InnerTube("WEB_REMIX", "1.20220607.03.01") | ||
IOS = InnerTube("IOS", "17.14.2") | ||
IOS_MUSIC = InnerTube("IOS_MUSIC", "4.16.1") | ||
|
||
channel_id: str = "UC8Yu1_yfN5qPh601Y4btsYw" # Arctic Monkeys | ||
# Arctic Monkeys | ||
channel_id: str = "UC8Yu1_yfN5qPh601Y4btsYw" | ||
|
||
# data_browse_channel = WEB_REMIX.adaptor.dispatch( | ||
# "browse", body={"browseId": channel_id} | ||
# ) | ||
response = WEB_REMIX.adaptor.dispatch("guide") | ||
|
||
d = data = WEB_REMIX.adaptor.dispatch("browse", body={"browseId": channel_id}) | ||
# rc_1 = ResponseContext.model_validate(data_browse_channel["responseContext"]) | ||
# rc_2 = ResponseContext.model_validate(data_guide["responseContext"]) | ||
|
||
rc = ResponseContext.parse_obj(data["responseContext"]) | ||
# d = parse(data_browse_channel) | ||
d = parse_response(response) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,27 @@ | ||
from enum import Enum, auto | ||
|
||
|
||
class StrEnum(str, Enum): | ||
pass | ||
|
||
|
||
class ButtonStyle(StrEnum): | ||
STYLE_DEFAULT: str = "STYLE_DEFAULT" | ||
|
||
|
||
# A "service" as listed under "responseContext.serviceTrackingParams" | ||
class Service(StrEnum): | ||
CSI: str = "CSI" | ||
GFEEDBACK: str = "GFEEDBACK" | ||
ECATCHER: str = "ECATCHER" | ||
|
||
|
||
# E.g., {"icon": {"iconType": "TAB_HOME"}} | ||
class IconType(StrEnum): | ||
LIBRARY_MUSIC = "LIBRARY_MUSIC" | ||
TAB_EXPLORE = "TAB_EXPLORE" | ||
TAB_HOME = "TAB_HOME" | ||
|
||
|
||
class SharePanelType(Enum): | ||
SHARE_PANEL_TYPE_UNIFIED_SHARE_PANEL = auto() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
from typing import Any, Mapping, MutableMapping, Optional, Sequence, TypeVar, Union | ||
from typing_extensions import Annotated, TypeAlias | ||
|
||
import humps | ||
import pydantic | ||
import rich | ||
|
||
from .raw_enums import ButtonStyle, IconType | ||
|
||
from . import utils | ||
|
||
|
||
def build_abs_key(field: Union[str, int], parent: Optional[str]) -> str: | ||
sep: str = "." if isinstance(field, str) else "" | ||
|
||
if isinstance(field, int): | ||
field = f"[{field}]" | ||
|
||
if parent is None: | ||
return field | ||
|
||
return f"{parent}{sep}{field}" | ||
|
||
|
||
DataMap: TypeAlias = Mapping[str, Mapping[str, Any]] | ||
DataSeq: TypeAlias = Sequence[DataMap] | ||
Data: TypeAlias = Union[DataMap, DataSeq] | ||
|
||
|
||
def parse_renderable(data: Data, /, *, parent: Optional[str] = None): | ||
assert utils.is_renderable(data) | ||
|
||
if isinstance(data, Sequence): | ||
return [ | ||
parse_renderable(item, parent=build_abs_key(index, parent)) | ||
for index, item in enumerate(data) | ||
] | ||
|
||
key = next(iter(data)) | ||
value = data[key] | ||
|
||
abs_key: str = build_abs_key(key, parent) | ||
|
||
if key not in RENDERERS: | ||
raise Exception(f"No renderer available for {key!r} ({abs_key})") | ||
|
||
render_cls: type = RENDERERS[key] | ||
|
||
rich.print(f"{abs_key} -> render({key!r}, {set(value.keys())})") | ||
|
||
return render_cls.model_validate(value) | ||
|
||
|
||
Renderable = Annotated[Any, pydantic.BeforeValidator(parse_renderable)] | ||
|
||
|
||
C = TypeVar("C", bound=type) | ||
|
||
RENDERERS: MutableMapping[str, type] = {} | ||
|
||
|
||
def renderer(cls: C) -> C: | ||
renderer_id: str = humps.camelize(cls.__name__) + "Renderer" | ||
|
||
RENDERERS[renderer_id] = cls | ||
|
||
return cls | ||
|
||
|
||
class BaseModel(pydantic.BaseModel): | ||
model_config = pydantic.ConfigDict( | ||
extra="forbid", | ||
alias_generator=humps.camelize, | ||
) | ||
|
||
|
||
class ComplexTextRun(BaseModel): | ||
text: str | ||
|
||
|
||
class ComplexText(BaseModel): | ||
runs: Sequence[ComplexTextRun] | ||
|
||
# def join(self) -> str: | ||
# return "".join(run for run in self.runs) | ||
|
||
|
||
class SimpleText(BaseModel): | ||
simple_text: str | ||
|
||
|
||
Text: TypeAlias = Union[ComplexText, SimpleText] | ||
|
||
|
||
class AccessibilityData(BaseModel): | ||
label: str | ||
|
||
|
||
class Accessibility(BaseModel): | ||
accessibility_data: AccessibilityData | ||
|
||
|
||
class BrowseEndpoint(BaseModel): | ||
browse_id: str | ||
|
||
|
||
class Icon(BaseModel): | ||
icon_type: IconType | ||
|
||
|
||
class SignInEndpoint(BaseModel): | ||
hack: bool | ||
|
||
|
||
class NavigationEndpoint(BaseModel): | ||
click_tracking_params: str | ||
browse_endpoint: Optional[BrowseEndpoint] = None | ||
sign_in_endpoint: Optional[SignInEndpoint] = None | ||
|
||
|
||
@renderer | ||
class Button(BaseModel): | ||
style: ButtonStyle | ||
is_disabled: bool | ||
text: SimpleText | ||
navigation_endpoint: NavigationEndpoint | ||
tracking_params: str | ||
|
||
|
||
@renderer | ||
class SingleColumnBrowseResultsRenderer: | ||
pass | ||
|
||
|
||
@renderer | ||
class SingleColumnBrowseResultsRenderer: | ||
pass | ||
|
||
|
||
@renderer | ||
class MusicImmersiveHeaderRenderer: | ||
pass | ||
|
||
|
||
@renderer | ||
class GuideEntry(BaseModel): | ||
navigation_endpoint: NavigationEndpoint | ||
icon: Icon | ||
tracking_params: str | ||
formatted_title: ComplexText | ||
accessibility: Accessibility | ||
is_primary: bool | ||
|
||
|
||
@renderer | ||
class GuideSection(BaseModel): | ||
tracking_params: str | ||
items: Sequence[Renderable] | ||
|
||
|
||
@renderer | ||
class GuideSigninPromo(BaseModel): | ||
action_text: ComplexText | ||
descriptiveText: ComplexText | ||
signInButton: Renderable |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,39 @@ | ||
import re | ||
from typing import Any, Mapping, Sequence | ||
|
||
|
||
def filter(dictionary: dict, /) -> dict: | ||
return {key: value for key, value in dictionary.items() if value is not None} | ||
|
||
def is_renderable(data: Any, /) -> bool: | ||
""" | ||
>>> is_renderable({"FooRenderer": {}}) | ||
True | ||
>>> is_renderable({}) | ||
False | ||
""" | ||
|
||
# print("is_renderable", data) | ||
|
||
if isinstance(data, Sequence): | ||
return all(map(is_renderable, data)) | ||
|
||
if not isinstance(data, Mapping): | ||
return False | ||
|
||
if len(data) != 1: | ||
return False | ||
|
||
key: Any = next(iter(data)) | ||
value: Any = data[key] | ||
|
||
if not isinstance(key, str): | ||
return False | ||
|
||
if not re.match(r"(.+)Renderer", key): | ||
return False | ||
|
||
if not isinstance(value, Mapping): | ||
return False | ||
|
||
return True |
Oops, something went wrong.