summaryrefslogtreecommitdiff
path: root/src/invidious/channels/playlists.cr
blob: d5628f6ab4788007fd13eed0cb32f453f95a211c (plain)
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
def fetch_channel_playlists(ucid, author, continuation, sort_by)
  if continuation
    response_json = YoutubeAPI.browse(continuation)
    continuation_items = response_json["onResponseReceivedActions"]?
      .try &.[0]["appendContinuationItemsAction"]["continuationItems"]

    return [] of SearchItem, nil if !continuation_items

    items = [] of SearchItem
    continuation_items.as_a.select(&.as_h.has_key?("gridPlaylistRenderer")).each { |item|
      extract_item(item, author, ucid).try { |t| items << t }
    }

    continuation = continuation_items.as_a.last["continuationItemRenderer"]?
      .try &.["continuationEndpoint"]["continuationCommand"]["token"].as_s
  else
    url = "/channel/#{ucid}/playlists?flow=list&view=1"

    case sort_by
    when "last", "last_added"
      #
    when "oldest", "oldest_created"
      url += "&sort=da"
    when "newest", "newest_created"
      url += "&sort=dd"
    else nil # Ignore
    end

    response = YT_POOL.client &.get(url)
    initial_data = extract_initial_data(response.body)
    return [] of SearchItem, nil if !initial_data

    items = extract_items(initial_data, author, ucid)
    continuation = response.body.match(/"token":"(?<continuation>[^"]+)"/).try &.["continuation"]?
  end

  return items, continuation
end

# ## NOTE: DEPRECATED
# Reason -> Unstable
# The Protobuf object must be provided with an id of the last playlist from the current "page"
# in order to fetch the next one accurately
# (if the id isn't included, entries shift around erratically between pages,
# leading to repetitions and skip overs)
#
# Since it's impossible to produce the appropriate Protobuf without an id being provided by the user,
# it's better to stick to continuation tokens provided by the first request and onward
def produce_channel_playlists_url(ucid, cursor, sort = "newest", auto_generated = false)
  object = {
    "80226972:embedded" => {
      "2:string" => ucid,
      "3:base64" => {
        "2:string"  => "playlists",
        "6:varint"  => 2_i64,
        "7:varint"  => 1_i64,
        "12:varint" => 1_i64,
        "13:string" => "",
        "23:varint" => 0_i64,
      },
    },
  }

  if cursor
    cursor = Base64.urlsafe_encode(cursor, false) if !auto_generated
    object["80226972:embedded"]["3:base64"].as(Hash)["15:string"] = cursor
  end

  if auto_generated
    object["80226972:embedded"]["3:base64"].as(Hash)["4:varint"] = 0x32_i64
  else
    object["80226972:embedded"]["3:base64"].as(Hash)["4:varint"] = 1_i64
    case sort
    when "oldest", "oldest_created"
      object["80226972:embedded"]["3:base64"].as(Hash)["3:varint"] = 2_i64
    when "newest", "newest_created"
      object["80226972:embedded"]["3:base64"].as(Hash)["3:varint"] = 3_i64
    when "last", "last_added"
      object["80226972:embedded"]["3:base64"].as(Hash)["3:varint"] = 4_i64
    else nil # Ignore
    end
  end

  object["80226972:embedded"]["3:string"] = Base64.urlsafe_encode(Protodec::Any.from_json(Protodec::Any.cast_json(object["80226972:embedded"]["3:base64"])))
  object["80226972:embedded"].delete("3:base64")

  continuation = object.try { |i| Protodec::Any.cast_json(i) }
    .try { |i| Protodec::Any.from_json(i) }
    .try { |i| Base64.urlsafe_encode(i) }
    .try { |i| URI.encode_www_form(i) }

  return "/browse_ajax?continuation=#{continuation}&gl=US&hl=en"
end