File: continuations.py

package info (click to toggle)
python-ytmusicapi 1.10.2-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 3,412 kB
  • sloc: python: 4,324; sh: 14; makefile: 12
file content (122 lines) | stat: -rw-r--r-- 4,427 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
from typing import Any, Optional

from ytmusicapi.navigation import nav

CONTINUATION_TOKEN = ["continuationItemRenderer", "continuationEndpoint", "continuationCommand", "token"]
CONTINUATION_ITEMS = ["onResponseReceivedActions", 0, "appendContinuationItemsAction", "continuationItems"]


def get_continuation_token(results: list[dict[str, Any]]) -> Optional[str]:
    return nav(results[-1], CONTINUATION_TOKEN, True)


def get_continuations_2025(results, limit, request_func, parse_func):
    items = []
    continuation_token = get_continuation_token(results["contents"])
    while continuation_token and (limit is None or len(items) < limit):
        response = request_func({"continuation": continuation_token})
        continuation_items = nav(response, CONTINUATION_ITEMS, True)
        if not continuation_items:
            break

        contents = parse_func(continuation_items)
        if len(contents) == 0:
            break
        items.extend(contents)
        continuation_token = get_continuation_token(continuation_items)

    return items


def get_continuations(
    results, continuation_type, limit, request_func, parse_func, ctoken_path="", reloadable=False
):
    items = []
    while "continuations" in results and (limit is None or len(items) < limit):
        additionalParams = (
            get_reloadable_continuation_params(results)
            if reloadable
            else get_continuation_params(results, ctoken_path)
        )
        response = request_func(additionalParams)
        if "continuationContents" in response:
            results = response["continuationContents"][continuation_type]
        else:
            break
        contents = get_continuation_contents(results, parse_func)
        if len(contents) == 0:
            break
        items.extend(contents)

    return items


def get_validated_continuations(
    results, continuation_type, limit, per_page, request_func, parse_func, ctoken_path=""
):
    items = []
    while "continuations" in results and len(items) < limit:
        additionalParams = get_continuation_params(results, ctoken_path)
        wrapped_parse_func = lambda raw_response: get_parsed_continuation_items(
            raw_response, parse_func, continuation_type
        )
        validate_func = lambda parsed: validate_response(parsed, per_page, limit, len(items))

        response = resend_request_until_parsed_response_is_valid(
            request_func, additionalParams, wrapped_parse_func, validate_func, 3
        )
        results = response["results"]
        items.extend(response["parsed"])

    return items


def get_parsed_continuation_items(response, parse_func, continuation_type):
    results = response["continuationContents"][continuation_type]
    return {"results": results, "parsed": get_continuation_contents(results, parse_func)}


def get_continuation_params(results, ctoken_path=""):
    ctoken = nav(results, ["continuations", 0, "next" + ctoken_path + "ContinuationData", "continuation"])
    return get_continuation_string(ctoken)


def get_reloadable_continuation_params(results):
    ctoken = nav(results, ["continuations", 0, "reloadContinuationData", "continuation"])
    return get_continuation_string(ctoken)


def get_continuation_string(ctoken):
    return "&ctoken=" + ctoken + "&continuation=" + ctoken


def get_continuation_contents(continuation, parse_func):
    for term in ["contents", "items"]:
        if term in continuation:
            return parse_func(continuation[term])

    return []


def resend_request_until_parsed_response_is_valid(
    request_func, request_additional_params, parse_func, validate_func, max_retries
):
    response = request_func(request_additional_params)
    parsed_object = parse_func(response)
    retry_counter = 0
    while not validate_func(parsed_object) and retry_counter < max_retries:
        response = request_func(request_additional_params)
        attempt = parse_func(response)
        if len(attempt["parsed"]) > len(parsed_object["parsed"]):
            parsed_object = attempt
        retry_counter += 1

    return parsed_object


def validate_response(response, per_page, limit, current_count):
    remaining_items_count = limit - current_count
    expected_items_count = min(per_page, remaining_items_count)

    # response is invalid, if it has less items then minimal expected count
    return len(response["parsed"]) >= expected_items_count