Extract channel routes (#2227)

* Extract primary channel routes from invidious.cr

Also removes timedtext_video stub since all it does is redirect to the
homepage. However, Invidious's 404 handler already does this.

--
As the template for the channel about page doesn't exist yet, the
behavior for the /channel/:ucid/about endpoint has been changed to be
the same as what's currently present on Invidious

(cherry picked from commit 8fad19d805)

* Manually extract brand_redirect from 1b569bbc99

This commit manually extracts the brand_redirect function from the
commit mentioned.

However, the redirect to the  `.../about` endpoint is removed due to the
fact that it doesn't exist yet.

This commit is also mainly just a bridge for the next few cherry picks from
\#2215

* Update brand_redirect to use youtubei resolve_url

(cherry picked from commit 53335fe7cf)

* Add additional channel endpoints to brand_redirect

(cherry picked from commit 8fc6f3add6)

* Add separate handler for /profile endpoint

* Add /channel/:ucid/home route

* Document all channel brand_urls
This commit is contained in:
syeopite
2021-08-03 14:46:15 -07:00
committed by GitHub
parent 4b46313e19
commit 1321c90920
2 changed files with 190 additions and 211 deletions

View File

@@ -309,6 +309,24 @@ Invidious::Routing.get "/", Invidious::Routes::Misc, :home
Invidious::Routing.get "/privacy", Invidious::Routes::Misc, :privacy
Invidious::Routing.get "/licenses", Invidious::Routes::Misc, :licenses
Invidious::Routing.get "/channel/:ucid", Invidious::Routes::Channels, :home
Invidious::Routing.get "/channel/:ucid/home", Invidious::Routes::Channels, :home
Invidious::Routing.get "/channel/:ucid/videos", Invidious::Routes::Channels, :videos
Invidious::Routing.get "/channel/:ucid/playlists", Invidious::Routes::Channels, :playlists
Invidious::Routing.get "/channel/:ucid/community", Invidious::Routes::Channels, :community
Invidious::Routing.get "/channel/:ucid/about", Invidious::Routes::Channels, :about
["", "/videos", "/playlists", "/community", "/about"].each do |path|
# /c/LinusTechTips
Invidious::Routing.get "/c/:user#{path}", Invidious::Routes::Channels, :brand_redirect
# /user/linustechtips | Not always the same as /c/
Invidious::Routing.get "/user/:user#{path}", Invidious::Routes::Channels, :brand_redirect
# /attribution_link?a=anything&u=/channel/UCZYTClx2T1of7BRZ86-8fow
Invidious::Routing.get "/attribution_link#{path}", Invidious::Routes::Channels, :brand_redirect
# /profile?user=linustechtips
Invidious::Routing.get "/profile/#{path}", Invidious::Routes::Channels, :profile
end
Invidious::Routing.get "/watch", Invidious::Routes::Watch, :handle
Invidious::Routing.get "/watch/:id", Invidious::Routes::Watch, :redirect
Invidious::Routing.get "/shorts/:id", Invidious::Routes::Watch, :redirect
@@ -1618,217 +1636,6 @@ end
end
end
# YouTube appears to let users set a "brand" URL that
# is different from their username, so we convert that here
get "/c/:user" do |env|
locale = LOCALES[env.get("preferences").as(Preferences).locale]?
user = env.params.url["user"]
response = YT_POOL.client &.get("/c/#{user}")
html = XML.parse_html(response.body)
ucid = html.xpath_node(%q(//link[@rel="canonical"])).try &.["href"].split("/")[-1]
next env.redirect "/" if !ucid
env.redirect "/channel/#{ucid}"
end
# Legacy endpoint for /user/:username
get "/profile" do |env|
user = env.params.query["user"]?
if !user
env.redirect "/"
else
env.redirect "/user/#{user}"
end
end
get "/attribution_link" do |env|
if query = env.params.query["u"]?
url = URI.parse(query).request_target
else
url = "/"
end
env.redirect url
end
# Page used by YouTube to provide captioning widget, since we
# don't support it we redirect to '/'
get "/timedtext_video" do |env|
env.redirect "/"
end
get "/user/:user" do |env|
user = env.params.url["user"]
env.redirect "/channel/#{user}"
end
get "/user/:user/videos" do |env|
user = env.params.url["user"]
env.redirect "/channel/#{user}/videos"
end
get "/user/:user/about" do |env|
user = env.params.url["user"]
env.redirect "/channel/#{user}"
end
get "/channel/:ucid/about" do |env|
ucid = env.params.url["ucid"]
env.redirect "/channel/#{ucid}"
end
get "/channel/:ucid" do |env|
locale = LOCALES[env.get("preferences").as(Preferences).locale]?
user = env.get? "user"
if user
user = user.as(User)
subscriptions = user.subscriptions
end
subscriptions ||= [] of String
ucid = env.params.url["ucid"]
page = env.params.query["page"]?.try &.to_i?
page ||= 1
continuation = env.params.query["continuation"]?
sort_by = env.params.query["sort_by"]?.try &.downcase
begin
channel = get_about_info(ucid, locale)
rescue ex : ChannelRedirect
next env.redirect env.request.resource.gsub(ucid, ex.channel_id)
rescue ex
next error_template(500, ex)
end
if channel.auto_generated
sort_options = {"last", "oldest", "newest"}
sort_by ||= "last"
items, continuation = fetch_channel_playlists(channel.ucid, channel.author, continuation, sort_by)
items.uniq! do |item|
if item.responds_to?(:title)
item.title
elsif item.responds_to?(:author)
item.author
end
end
items = items.select(&.is_a?(SearchPlaylist)).map(&.as(SearchPlaylist))
items.each { |item| item.author = "" }
else
sort_options = {"newest", "oldest", "popular"}
sort_by ||= "newest"
count, items = get_60_videos(channel.ucid, channel.author, page, channel.auto_generated, sort_by)
items.reject! &.paid
env.set "search", "channel:#{channel.ucid} "
end
templated "channel"
end
get "/channel/:ucid/videos" do |env|
locale = LOCALES[env.get("preferences").as(Preferences).locale]?
ucid = env.params.url["ucid"]
params = env.request.query
if !params || params.empty?
params = ""
else
params = "?#{params}"
end
env.redirect "/channel/#{ucid}#{params}"
end
get "/channel/:ucid/playlists" do |env|
locale = LOCALES[env.get("preferences").as(Preferences).locale]?
user = env.get? "user"
if user
user = user.as(User)
subscriptions = user.subscriptions
end
subscriptions ||= [] of String
ucid = env.params.url["ucid"]
continuation = env.params.query["continuation"]?
sort_by = env.params.query["sort_by"]?.try &.downcase
sort_by ||= "last"
begin
channel = get_about_info(ucid, locale)
rescue ex : ChannelRedirect
next env.redirect env.request.resource.gsub(ucid, ex.channel_id)
rescue ex
next error_template(500, ex)
end
if channel.auto_generated
next env.redirect "/channel/#{channel.ucid}"
end
items, continuation = fetch_channel_playlists(channel.ucid, channel.author, continuation, sort_by)
items = items.select { |item| item.is_a?(SearchPlaylist) }.map { |item| item.as(SearchPlaylist) }
items.each { |item| item.author = "" }
env.set "search", "channel:#{channel.ucid} "
templated "playlists"
end
get "/channel/:ucid/community" do |env|
locale = LOCALES[env.get("preferences").as(Preferences).locale]?
user = env.get? "user"
if user
user = user.as(User)
subscriptions = user.subscriptions
end
subscriptions ||= [] of String
ucid = env.params.url["ucid"]
thin_mode = env.params.query["thin_mode"]? || env.get("preferences").as(Preferences).thin_mode
thin_mode = thin_mode == "true"
continuation = env.params.query["continuation"]?
# sort_by = env.params.query["sort_by"]?.try &.downcase
begin
channel = get_about_info(ucid, locale)
rescue ex : ChannelRedirect
next env.redirect env.request.resource.gsub(ucid, ex.channel_id)
rescue ex
next error_template(500, ex)
end
if !channel.tabs.includes? "community"
next env.redirect "/channel/#{channel.ucid}"
end
begin
items = JSON.parse(fetch_channel_community(ucid, continuation, locale, "json", thin_mode))
rescue ex : InfoException
env.response.status_code = 500
error_message = ex.message
rescue ex
next error_template(500, ex)
end
env.set "search", "channel:#{channel.ucid} "
templated "community"
end
# API Endpoints
get "/api/v1/stats" do |env|