Compare commits

...

20 Commits

Author SHA1 Message Date
Samantaz Fox
3ac8978e96
VideoProxy: Handle 302 redirects in chunked section 2024-11-10 18:15:24 +01:00
Samantaz Fox
e7a93fcc18
API: Replace any URL in HLS manifests 2024-11-10 18:13:30 +01:00
Samantaz Fox
aa33d9b7ec
Videos: Fix missing host parameter on playback URLs when local=true 2024-11-10 18:13:30 +01:00
Samantaz Fox
2150264d84
Update CHANGELOG.md 2024-11-10 18:00:26 +01:00
Samantaz Fox
d42561d74a
API: Add "sort_by" parameter to channels/shorts endpoint (#5071)
Small follow up to PR 5059

No related issue
2024-11-10 17:50:00 +01:00
Samantaz Fox
7092bb8855
Docker: Install tzdata in Dockerfile (#5070)
Should close 5067
2024-11-10 17:48:18 +01:00
Samantaz Fox
d7c35e6e3d
Videos: Stop using TVHTML5_SIMPLY_EMBEDDED_PLAYER (#5063)
The age restriction bypass does not work anymore with this client.
See: https://github.com/iv-org/invidious/issues/2189#issuecomment-2437740627

Related to 2189
2024-11-10 17:45:58 +01:00
Samantaz Fox
bc86fb8a82
Routing: Deprecate old channel API routes (#5045)
Deprecate the following routes:

* /api/v1/channels/videos/:ucid
* /api/v1/channels/latest/:ucid
* /api/v1/channels/playlists/:ucid
* /api/v1/channels/community/:ucid
* /api/v1/channels/search/:ucid

in favor of:

* /api/v1/channels/:ucid/videos
* /api/v1/channels/:ucid/latest
* /api/v1/channels/:ucid/playlists
* /api/v1/channels/:ucid/community
* /api/v1/channels/:ucid/search

No related issue
2024-11-10 17:44:45 +01:00
Samantaz Fox
ec82c2f539
Videos: use WEB client instead of WEB CREATOR (#4984)
Use the WEB client when a potoken is configured, otherwise try with Android
test suite if there is no potoken configured.

This PR reverts some of the changes made in 4928

Related to 4734
2024-11-10 17:41:54 +01:00
Samantaz Fox
4b363e32fa
Parsers: Fix parsing live_now and premiere_timestamp (#4934)
This pull request fixes the parsing for the 'live_now' and 'premiere_timestamp'
variables so that they work without the 'microformat' data being present.

Related to 4929
2024-11-10 17:36:49 +01:00
Brahim Hadriche
d2123b4682 Sort channel shorts API 2024-11-09 17:49:06 -05:00
Émilien (perso)
0f8f32bca8 remove explicit usage of WEB 2024-11-09 22:21:09 +01:00
Emilien
f3e93ca83d revert back to www.youtube.com when client_config.screen embed 2024-11-09 22:21:09 +01:00
Emilien
82b1506ccc remove usage of WebEmbeddedPlayer 2024-11-09 22:21:09 +01:00
Emilien
b9ad9bd723 use WEB when po_token + android test suite when no po_token 2024-11-09 22:21:09 +01:00
Samantaz Fox
1a49e798c8
Docker: Install tzdata in Dockerfile 2024-11-09 21:52:06 +01:00
Émilien (perso)
09ccea1d31
remove usage of TVHTML5_SIMPLY_EMBEDDED_PLAYER 2024-11-08 22:01:23 +01:00
Samantaz Fox
6da18ddc41
Routing: Also remove outdated comment about notification routes 2024-10-31 11:52:09 +01:00
Samantaz Fox
cdf93b29e6
Routing: Remove deprecated /api/v1/channels/.../:ucid routes 2024-10-31 11:51:33 +01:00
absidue
b2a83991d1 Fix parsing live_now and premiere_timestamp 2024-09-20 18:46:00 +02:00
11 changed files with 66 additions and 68 deletions

View File

@ -5,6 +5,12 @@
### Full list of pull requests merged since the last release (newest first) ### Full list of pull requests merged since the last release (newest first)
* API: Add "sort_by" parameter to channels/shorts endpoint ([#5071], thanks @iBicha)
* Docker: Install tzdata in Dockerfile ([#5070], by @SamantazFox)
* Videos: Stop using TVHTML5_SIMPLY_EMBEDDED_PLAYER ([#5063], thanks @unixfox)
* Routing: Deprecate old channel API routes ([#5045], by @SamantazFox)
* Videos: use WEB client instead of WEB CREATOR ([#4984], thanks @unixfox)
* Parsers: Fix parsing live_now and premiere_timestamp ([#4934], thanks @absidue)
* Stale bot updates ([#5060], thanks @syeopite) * Stale bot updates ([#5060], thanks @syeopite)
* Channels: Fix "Youtube API returned error 400" ([#5059], by @SamantazFox) * Channels: Fix "Youtube API returned error 400" ([#5059], by @SamantazFox)
* Channels: Fix for live videos ([#5027], thanks @iBicha) * Channels: Fix for live videos ([#5027], thanks @iBicha)
@ -52,15 +58,21 @@
[#4928]: https://github.com/iv-org/invidious/pull/4928 [#4928]: https://github.com/iv-org/invidious/pull/4928
[#4930]: https://github.com/iv-org/invidious/pull/4930 [#4930]: https://github.com/iv-org/invidious/pull/4930
[#4931]: https://github.com/iv-org/invidious/pull/4931 [#4931]: https://github.com/iv-org/invidious/pull/4931
[#4934]: https://github.com/iv-org/invidious/pull/4934
[#4942]: https://github.com/iv-org/invidious/pull/4942 [#4942]: https://github.com/iv-org/invidious/pull/4942
[#4984]: https://github.com/iv-org/invidious/pull/4984
[#4991]: https://github.com/iv-org/invidious/pull/4991 [#4991]: https://github.com/iv-org/invidious/pull/4991
[#4993]: https://github.com/iv-org/invidious/pull/4993 [#4993]: https://github.com/iv-org/invidious/pull/4993
[#4995]: https://github.com/iv-org/invidious/pull/4995 [#4995]: https://github.com/iv-org/invidious/pull/4995
[#5027]: https://github.com/iv-org/invidious/pull/5027 [#5027]: https://github.com/iv-org/invidious/pull/5027
[#5034]: https://github.com/iv-org/invidious/pull/5034 [#5034]: https://github.com/iv-org/invidious/pull/5034
[#5045]: https://github.com/iv-org/invidious/pull/5045
[#5046]: https://github.com/iv-org/invidious/pull/5046 [#5046]: https://github.com/iv-org/invidious/pull/5046
[#5059]: https://github.com/iv-org/invidious/pull/5059 [#5059]: https://github.com/iv-org/invidious/pull/5059
[#5060]: https://github.com/iv-org/invidious/pull/5060 [#5060]: https://github.com/iv-org/invidious/pull/5060
[#5063]: https://github.com/iv-org/invidious/pull/5063
[#5070]: https://github.com/iv-org/invidious/pull/5070
[#5071]: https://github.com/iv-org/invidious/pull/5071
## v2.20240825.2 (2024-08-26) ## v2.20240825.2 (2024-08-26)

View File

@ -1,4 +1,4 @@
FROM crystallang/crystal:1.12.1-alpine AS builder FROM crystallang/crystal:1.12.2-alpine AS builder
RUN apk add --no-cache sqlite-static yaml-static RUN apk add --no-cache sqlite-static yaml-static
@ -32,8 +32,8 @@ RUN if [[ "${release}" == 1 ]] ; then \
--link-flags "-lxml2 -llzma"; \ --link-flags "-lxml2 -llzma"; \
fi fi
FROM alpine:3.18 FROM alpine:3.20
RUN apk add --no-cache rsvg-convert ttf-opensans tini RUN apk add --no-cache rsvg-convert ttf-opensans tini tzdata
WORKDIR /invidious WORKDIR /invidious
RUN addgroup -g 1000 -S invidious && \ RUN addgroup -g 1000 -S invidious && \
adduser -u 1000 -S invidious -G invidious adduser -u 1000 -S invidious -G invidious

View File

@ -1,5 +1,6 @@
FROM alpine:3.19 AS builder FROM alpine:3.20 AS builder
RUN apk add --no-cache 'crystal=1.10.1-r0' shards sqlite-static yaml-static yaml-dev libxml2-static zlib-static openssl-libs-static openssl-dev musl-dev xz-static RUN apk add --no-cache 'crystal=1.12.2-r0' shards sqlite-static yaml-static yaml-dev libxml2-static \
zlib-static openssl-libs-static openssl-dev musl-dev xz-static
ARG release ARG release
@ -32,8 +33,8 @@ RUN if [[ "${release}" == 1 ]] ; then \
--link-flags "-lxml2 -llzma"; \ --link-flags "-lxml2 -llzma"; \
fi fi
FROM alpine:3.18 FROM alpine:3.20
RUN apk add --no-cache rsvg-convert ttf-opensans tini RUN apk add --no-cache rsvg-convert ttf-opensans tini tzdata
WORKDIR /invidious WORKDIR /invidious
RUN addgroup -g 1000 -S invidious && \ RUN addgroup -g 1000 -S invidious && \
adduser -u 1000 -S invidious -G invidious adduser -u 1000 -S invidious -G invidious

View File

@ -27,28 +27,21 @@ module Invidious::Routes::API::Manifest
haltf env, status_code: response.status_code haltf env, status_code: response.status_code
end end
manifest = response.body # Proxy URLs for video playback on invidious.
# Other API clients can get the original URLs by omiting `local=true`.
manifest = manifest.gsub(/<BaseURL>[^<]+<\/BaseURL>/) do |baseurl| manifest = response.body.gsub(/<BaseURL>[^<]+<\/BaseURL>/) do |baseurl|
url = baseurl.lchop("<BaseURL>") url = baseurl.lchop("<BaseURL>").rchop("</BaseURL>")
url = url.rchop("</BaseURL>") url = HttpServer::Utils.proxy_video_url(url, absolute: true) if local
if local
uri = URI.parse(url)
url = "#{HOST_URL}#{uri.request_target}host/#{uri.host}/"
end
"<BaseURL>#{url}</BaseURL>" "<BaseURL>#{url}</BaseURL>"
end end
return manifest return manifest
end end
adaptive_fmts = video.adaptive_fmts # Ditto, only proxify URLs if `local=true` is used
if local if local
adaptive_fmts.each do |fmt| video.adaptive_fmts.each do |fmt|
fmt["url"] = JSON::Any.new("#{HOST_URL}#{URI.parse(fmt["url"].as_s).request_target}") fmt["url"] = JSON::Any.new(HttpServer::Utils.proxy_video_url(fmt["url"].as_s, absolute: true))
end end
end end
@ -177,8 +170,9 @@ module Invidious::Routes::API::Manifest
manifest = response.body manifest = response.body
if local if local
manifest = manifest.gsub(/^https:\/\/\w+---.{11}\.c\.youtube\.com[^\n]*/m) do |match| manifest = manifest.gsub(/https:\/\/[^\n"]*/m) do |match|
path = URI.parse(match).path uri = URI.parse(match)
path = uri.path
path = path.lchop("/videoplayback/") path = path.lchop("/videoplayback/")
path = path.rchop("/") path = path.rchop("/")
@ -207,7 +201,7 @@ module Invidious::Routes::API::Manifest
raw_params["fvip"] = fvip["fvip"] raw_params["fvip"] = fvip["fvip"]
end end
raw_params["local"] = "true" raw_params["host"] = uri.host.not_nil!
"#{HOST_URL}/videoplayback?#{raw_params}" "#{HOST_URL}/videoplayback?#{raw_params}"
end end

View File

@ -197,6 +197,7 @@ module Invidious::Routes::API::V1::Channels
get_channel() get_channel()
# Retrieve continuation from URL parameters # Retrieve continuation from URL parameters
sort_by = env.params.query["sort_by"]?.try &.downcase || "newest"
continuation = env.params.query["continuation"]? continuation = env.params.query["continuation"]?
if channel.is_age_gated if channel.is_age_gated
@ -211,7 +212,7 @@ module Invidious::Routes::API::V1::Channels
else else
begin begin
videos, next_continuation = Channel::Tabs.get_shorts( videos, next_continuation = Channel::Tabs.get_shorts(
channel, continuation: continuation channel, continuation: continuation, sort_by: sort_by
) )
rescue ex rescue ex
return error_json(500, ex) return error_json(500, ex)

View File

@ -157,10 +157,12 @@ module Invidious::Routes::Embed
adaptive_fmts = video.adaptive_fmts adaptive_fmts = video.adaptive_fmts
if params.local if params.local
fmt_stream.each { |fmt| fmt["url"] = JSON::Any.new(URI.parse(fmt["url"].as_s).request_target) } fmt_stream.each { |fmt| fmt["url"] = JSON::Any.new(HttpServer::Utils.proxy_video_url(fmt["url"].as_s)) }
adaptive_fmts.each { |fmt| fmt["url"] = JSON::Any.new(URI.parse(fmt["url"].as_s).request_target) }
end end
# Always proxy DASH streams, otherwise youtube CORS headers will prevent playback
adaptive_fmts.each { |fmt| fmt["url"] = JSON::Any.new(HttpServer::Utils.proxy_video_url(fmt["url"].as_s)) }
video_streams = video.video_streams video_streams = video.video_streams
audio_streams = video.audio_streams audio_streams = video.audio_streams

View File

@ -164,10 +164,13 @@ module Invidious::Routes::VideoPlayback
env.response.headers["Access-Control-Allow-Origin"] = "*" env.response.headers["Access-Control-Allow-Origin"] = "*"
if location = resp.headers["Location"]? if location = resp.headers["Location"]?
location = URI.parse(location) url = Invidious::HttpServer::Utils.proxy_video_url(location, region: region)
location = "#{location.request_target}&host=#{location.host}#{region ? "&region=#{region}" : ""}"
env.redirect location if title = query_params["title"]?
url = "#{url}&title=#{URI.encode_www_form(title)}"
end
env.redirect url
break break
end end

View File

@ -121,10 +121,12 @@ module Invidious::Routes::Watch
adaptive_fmts = video.adaptive_fmts adaptive_fmts = video.adaptive_fmts
if params.local if params.local
fmt_stream.each { |fmt| fmt["url"] = JSON::Any.new(URI.parse(fmt["url"].as_s).request_target) } fmt_stream.each { |fmt| fmt["url"] = JSON::Any.new(HttpServer::Utils.proxy_video_url(fmt["url"].as_s)) }
adaptive_fmts.each { |fmt| fmt["url"] = JSON::Any.new(URI.parse(fmt["url"].as_s).request_target) }
end end
# Always proxy DASH streams, otherwise youtube CORS headers will prevent playback
adaptive_fmts.each { |fmt| fmt["url"] = JSON::Any.new(HttpServer::Utils.proxy_video_url(fmt["url"].as_s)) }
video_streams = video.video_streams video_streams = video.video_streams
audio_streams = video.audio_streams audio_streams = video.audio_streams

View File

@ -243,17 +243,16 @@ module Invidious::Routing
# Channels # Channels
get "/api/v1/channels/:ucid", {{namespace}}::Channels, :home get "/api/v1/channels/:ucid", {{namespace}}::Channels, :home
get "/api/v1/channels/:ucid/latest", {{namespace}}::Channels, :latest
get "/api/v1/channels/:ucid/videos", {{namespace}}::Channels, :videos
get "/api/v1/channels/:ucid/shorts", {{namespace}}::Channels, :shorts get "/api/v1/channels/:ucid/shorts", {{namespace}}::Channels, :shorts
get "/api/v1/channels/:ucid/streams", {{namespace}}::Channels, :streams get "/api/v1/channels/:ucid/streams", {{namespace}}::Channels, :streams
get "/api/v1/channels/:ucid/podcasts", {{namespace}}::Channels, :podcasts get "/api/v1/channels/:ucid/podcasts", {{namespace}}::Channels, :podcasts
get "/api/v1/channels/:ucid/releases", {{namespace}}::Channels, :releases get "/api/v1/channels/:ucid/releases", {{namespace}}::Channels, :releases
get "/api/v1/channels/:ucid/playlists", {{namespace}}::Channels, :playlists
get "/api/v1/channels/:ucid/community", {{namespace}}::Channels, :community
get "/api/v1/channels/:ucid/channels", {{namespace}}::Channels, :channels get "/api/v1/channels/:ucid/channels", {{namespace}}::Channels, :channels
get "/api/v1/channels/:ucid/search", {{namespace}}::Channels, :search
{% for route in {"videos", "latest", "playlists", "community", "search"} %}
get "/api/v1/channels/#{{{route}}}/:ucid", {{namespace}}::Channels, :{{route}}
get "/api/v1/channels/:ucid/#{{{route}}}", {{namespace}}::Channels, :{{route}}
{% end %}
# Posts # Posts
get "/api/v1/post/:id", {{namespace}}::Channels, :post get "/api/v1/post/:id", {{namespace}}::Channels, :post
@ -271,11 +270,6 @@ module Invidious::Routing
# Authenticated # Authenticated
# The notification APIs cannot be extracted yet! They require the *local* notifications constant defined in invidious.cr
#
# Invidious::Routing.get "/api/v1/auth/notifications", {{namespace}}::Authenticated, :notifications
# Invidious::Routing.post "/api/v1/auth/notifications", {{namespace}}::Authenticated, :notifications
get "/api/v1/auth/preferences", {{namespace}}::Authenticated, :get_preferences get "/api/v1/auth/preferences", {{namespace}}::Authenticated, :get_preferences
post "/api/v1/auth/preferences", {{namespace}}::Authenticated, :set_preferences post "/api/v1/auth/preferences", {{namespace}}::Authenticated, :set_preferences

View File

@ -53,10 +53,6 @@ end
def extract_video_info(video_id : String) def extract_video_info(video_id : String)
# Init client config for the API # Init client config for the API
client_config = YoutubeAPI::ClientConfig.new client_config = YoutubeAPI::ClientConfig.new
# Use the WEB_CREATOR when po_token is configured because it fully only works on this client
if CONFIG.po_token
client_config.client_type = YoutubeAPI::ClientType::WebCreator
end
# Fetch data from the player endpoint # Fetch data from the player endpoint
player_response = YoutubeAPI.player(video_id: video_id, params: "2AMB", client_config: client_config) player_response = YoutubeAPI.player(video_id: video_id, params: "2AMB", client_config: client_config)
@ -106,15 +102,8 @@ def extract_video_info(video_id : String)
new_player_response = nil new_player_response = nil
# Second try in case WEB_CREATOR doesn't work with po_token. # Don't use Android test suite client if po_token is passed because po_token doesn't
# Only trigger if reason found and po_token configured. # work for Android test suite client.
if reason && CONFIG.po_token
client_config.client_type = YoutubeAPI::ClientType::WebEmbeddedPlayer
new_player_response = try_fetch_streaming_data(video_id, client_config)
end
# Don't use Android client if po_token is passed because po_token doesn't
# work for Android client.
if reason.nil? && CONFIG.po_token.nil? if reason.nil? && CONFIG.po_token.nil?
# Fetch the video streams using an Android client in order to get the # Fetch the video streams using an Android client in order to get the
# decrypted URLs and maybe fix throttling issues (#2194). See the # decrypted URLs and maybe fix throttling issues (#2194). See the
@ -124,14 +113,6 @@ def extract_video_info(video_id : String)
new_player_response = try_fetch_streaming_data(video_id, client_config) new_player_response = try_fetch_streaming_data(video_id, client_config)
end end
# Last hope
# Only trigger if reason found or didn't work wth Android client.
# TvHtml5ScreenEmbed now requires sig helper for it to work but doesn't work with po_token.
if reason && CONFIG.po_token.nil?
client_config.client_type = YoutubeAPI::ClientType::TvHtml5ScreenEmbed
new_player_response = try_fetch_streaming_data(video_id, client_config)
end
# Replace player response and reset reason # Replace player response and reset reason
if !new_player_response.nil? if !new_player_response.nil?
# Preserve captions & storyboard data before replacement # Preserve captions & storyboard data before replacement
@ -235,8 +216,17 @@ def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any
premiere_timestamp = microformat.dig?("liveBroadcastDetails", "startTimestamp") premiere_timestamp = microformat.dig?("liveBroadcastDetails", "startTimestamp")
.try { |t| Time.parse_rfc3339(t.as_s) } .try { |t| Time.parse_rfc3339(t.as_s) }
premiere_timestamp ||= player_response.dig?(
"playabilityStatus", "liveStreamability",
"liveStreamabilityRenderer", "offlineSlate",
"liveStreamOfflineSlateRenderer", "scheduledStartTime"
)
.try &.as_s.to_i64
.try { |t| Time.unix(t) }
live_now = microformat.dig?("liveBroadcastDetails", "isLiveNow") live_now = microformat.dig?("liveBroadcastDetails", "isLiveNow")
.try &.as_bool || false .try &.as_bool
live_now ||= video_details.dig?("isLive").try &.as_bool || false
post_live_dvr = video_details.dig?("isPostLiveDvr") post_live_dvr = video_details.dig?("isPostLiveDvr")
.try &.as_bool || false .try &.as_bool || false

View File

@ -300,9 +300,8 @@ module YoutubeAPI
end end
if client_config.screen == "EMBED" if client_config.screen == "EMBED"
# embedUrl https://www.google.com allow loading almost all video that are configured not embeddable
client_context["thirdParty"] = { client_context["thirdParty"] = {
"embedUrl" => "https://www.google.com/", "embedUrl" => "https://www.youtube.com/embed/#{video_id}",
} of String => String | Int64 } of String => String | Int64
end end