summaryrefslogtreecommitdiff
path: root/src/invidious/routes/api/v1/misc.cr
blob: 844fedb8751e150ebbf69b8e4f1760f2e2f72ef8 (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
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
module Invidious::Routes::API::V1::Misc
  # Stats API endpoint for Invidious
  def self.stats(env)
    env.response.content_type = "application/json"

    if !CONFIG.statistics_enabled
      return {"software" => SOFTWARE}.to_json
    else
      return Invidious::Jobs::StatisticsRefreshJob::STATISTICS.to_json
    end
  end

  # APIv1 currently uses the same logic for both
  # user playlists and Invidious playlists. This means that we can't
  # reasonably split them yet. This should be addressed in APIv2
  def self.get_playlist(env : HTTP::Server::Context)
    env.response.content_type = "application/json"
    plid = env.params.url["plid"]

    offset = env.params.query["index"]?.try &.to_i?
    offset ||= env.params.query["page"]?.try &.to_i?.try { |page| (page - 1) * 100 }
    offset ||= 0

    video_id = env.params.query["continuation"]?

    format = env.params.query["format"]?
    format ||= "json"

    if plid.starts_with? "RD"
      return env.redirect "/api/v1/mixes/#{plid}"
    end

    begin
      playlist = get_playlist(plid)
    rescue ex : InfoException
      return error_json(404, ex)
    rescue ex
      return error_json(404, "Playlist does not exist.")
    end

    user = env.get?("user").try &.as(User)
    if !playlist || playlist.privacy.private? && playlist.author != user.try &.email
      return error_json(404, "Playlist does not exist.")
    end

    # includes into the playlist a maximum of 20 videos, before the offset
    if offset > 0
      lookback = offset < 50 ? offset : 50
      response = playlist.to_json(offset - lookback)
      json_response = JSON.parse(response)
    else
      #  Unless the continuation is really the offset 0, it becomes expensive.
      #  It happens when the offset is not set.
      #  First we find the actual offset, and then we lookback
      #  it shouldn't happen often though

      lookback = 0
      response = playlist.to_json(offset, video_id: video_id)
      json_response = JSON.parse(response)

      if json_response["videos"].as_a[0]["index"] != offset
        offset = json_response["videos"].as_a[0]["index"].as_i
        lookback = offset < 50 ? offset : 50
        response = playlist.to_json(offset - lookback)
        json_response = JSON.parse(response)
      end
    end

    if format == "html"
      playlist_html = template_playlist(json_response)
      index, next_video = json_response["videos"].as_a.skip(1 + lookback).select { |video| !video["author"].as_s.empty? }[0]?.try { |v| {v["index"], v["videoId"]} } || {nil, nil}

      response = {
        "playlistHtml" => playlist_html,
        "index"        => index,
        "nextVideo"    => next_video,
      }.to_json
    end

    response
  end

  def self.mixes(env)
    locale = env.get("preferences").as(Preferences).locale

    env.response.content_type = "application/json"

    rdid = env.params.url["rdid"]

    continuation = env.params.query["continuation"]?
    continuation ||= rdid.lchop("RD")[0, 11]

    format = env.params.query["format"]?
    format ||= "json"

    begin
      mix = fetch_mix(rdid, continuation, locale: locale)

      if !rdid.ends_with? continuation
        mix = fetch_mix(rdid, mix.videos[1].id)
        index = mix.videos.index(mix.videos.select { |video| video.id == continuation }[0]?)
      end

      mix.videos = mix.videos[index..-1]
    rescue ex
      return error_json(500, ex)
    end

    response = JSON.build do |json|
      json.object do
        json.field "title", mix.title
        json.field "mixId", mix.id

        json.field "videos" do
          json.array do
            mix.videos.each do |video|
              json.object do
                json.field "title", video.title
                json.field "videoId", video.id
                json.field "author", video.author

                json.field "authorId", video.ucid
                json.field "authorUrl", "/channel/#{video.ucid}"

                json.field "videoThumbnails" do
                  json.array do
                    generate_thumbnails(json, video.id)
                  end
                end

                json.field "index", video.index
                json.field "lengthSeconds", video.length_seconds
              end
            end
          end
        end
      end
    end

    if format == "html"
      response = JSON.parse(response)
      playlist_html = template_mix(response)
      next_video = response["videos"].as_a.select { |video| !video["author"].as_s.empty? }[0]?.try &.["videoId"]

      response = {
        "playlistHtml" => playlist_html,
        "nextVideo"    => next_video,
      }.to_json
    end

    response
  end
end