diff --git a/src/invidious.cr b/src/invidious.cr
index 070b4d18..0601d5b2 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -178,305 +178,19 @@ def popular_videos
Invidious::Jobs::PullPopularVideosJob::POPULAR_VIDEOS.get
end
+# Routing
+
before_all do |env|
- preferences = Preferences.from_json("{}")
-
- begin
- if prefs_cookie = env.request.cookies["PREFS"]?
- preferences = Preferences.from_json(URI.decode_www_form(prefs_cookie.value))
- else
- if language_header = env.request.headers["Accept-Language"]?
- if language = ANG.language_negotiator.best(language_header, LOCALES.keys)
- preferences.locale = language.header
- end
- end
- end
- rescue
- preferences = Preferences.from_json("{}")
- end
-
- env.set "preferences", preferences
- env.response.headers["X-XSS-Protection"] = "1; mode=block"
- env.response.headers["X-Content-Type-Options"] = "nosniff"
-
- # Allow media resources to be loaded from google servers
- # TODO: check if *.youtube.com can be removed
- if CONFIG.disabled?("local") || !preferences.local
- extra_media_csp = " https://*.googlevideo.com:443 https://*.youtube.com:443"
- else
- extra_media_csp = ""
- end
-
- # Only allow the pages at /embed/* to be embedded
- if env.request.resource.starts_with?("/embed")
- frame_ancestors = "'self' http: https:"
- else
- frame_ancestors = "'none'"
- end
-
- # TODO: Remove style-src's 'unsafe-inline', requires to remove all
- # inline styles (, style=" [..] ")
- env.response.headers["Content-Security-Policy"] = {
- "default-src 'none'",
- "script-src 'self'",
- "style-src 'self' 'unsafe-inline'",
- "img-src 'self' data:",
- "font-src 'self' data:",
- "connect-src 'self'",
- "manifest-src 'self'",
- "media-src 'self' blob:" + extra_media_csp,
- "child-src 'self' blob:",
- "frame-src 'self'",
- "frame-ancestors " + frame_ancestors,
- }.join("; ")
-
- env.response.headers["Referrer-Policy"] = "same-origin"
-
- # Ask the chrom*-based browsers to disable FLoC
- # See: https://blog.runcloud.io/google-floc/
- env.response.headers["Permissions-Policy"] = "interest-cohort=()"
-
- if (Kemal.config.ssl || CONFIG.https_only) && CONFIG.hsts
- env.response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains; preload"
- end
-
- next if {
- "/sb/",
- "/vi/",
- "/s_p/",
- "/yts/",
- "/ggpht/",
- "/api/manifest/",
- "/videoplayback",
- "/latest_version",
- "/download",
- }.any? { |r| env.request.resource.starts_with? r }
-
- if env.request.cookies.has_key? "SID"
- sid = env.request.cookies["SID"].value
-
- if sid.starts_with? "v1:"
- raise "Cannot use token as SID"
- end
-
- # Invidious users only have SID
- if !env.request.cookies.has_key? "SSID"
- if email = Invidious::Database::SessionIDs.select_email(sid)
- user = Invidious::Database::Users.select!(email: email)
- csrf_token = generate_response(sid, {
- ":authorize_token",
- ":playlist_ajax",
- ":signout",
- ":subscription_ajax",
- ":token_ajax",
- ":watch_ajax",
- }, HMAC_KEY, 1.week)
-
- preferences = user.preferences
- env.set "preferences", preferences
-
- env.set "sid", sid
- env.set "csrf_token", csrf_token
- env.set "user", user
- end
- else
- headers = HTTP::Headers.new
- headers["Cookie"] = env.request.headers["Cookie"]
-
- begin
- user, sid = get_user(sid, headers, false)
- csrf_token = generate_response(sid, {
- ":authorize_token",
- ":playlist_ajax",
- ":signout",
- ":subscription_ajax",
- ":token_ajax",
- ":watch_ajax",
- }, HMAC_KEY, 1.week)
-
- preferences = user.preferences
- env.set "preferences", preferences
-
- env.set "sid", sid
- env.set "csrf_token", csrf_token
- env.set "user", user
- rescue ex
- end
- end
- end
-
- dark_mode = convert_theme(env.params.query["dark_mode"]?) || preferences.dark_mode.to_s
- thin_mode = env.params.query["thin_mode"]? || preferences.thin_mode.to_s
- thin_mode = thin_mode == "true"
- locale = env.params.query["hl"]? || preferences.locale
-
- preferences.dark_mode = dark_mode
- preferences.thin_mode = thin_mode
- preferences.locale = locale
- env.set "preferences", preferences
-
- current_page = env.request.path
- if env.request.query
- query = HTTP::Params.parse(env.request.query.not_nil!)
-
- if query["referer"]?
- query["referer"] = get_referer(env, "/")
- end
-
- current_page += "?#{query}"
- end
-
- env.set "current_page", URI.encode_www_form(current_page)
+ Invidious::Routes::BeforeAll.handle(env)
end
-{% unless flag?(:api_only) %}
- 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
- Invidious::Routing.get "/channel/:ucid/live", Invidious::Routes::Channels, :live
- Invidious::Routing.get "/user/:user/live", Invidious::Routes::Channels, :live
- Invidious::Routing.get "/c/:user/live", Invidious::Routes::Channels, :live
-
- ["", "/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.post "/watch_ajax", Invidious::Routes::Watch, :mark_watched
- Invidious::Routing.get "/watch/:id", Invidious::Routes::Watch, :redirect
- Invidious::Routing.get "/shorts/:id", Invidious::Routes::Watch, :redirect
- Invidious::Routing.get "/clip/:clip", Invidious::Routes::Watch, :clip
- Invidious::Routing.get "/w/:id", Invidious::Routes::Watch, :redirect
- Invidious::Routing.get "/v/:id", Invidious::Routes::Watch, :redirect
- Invidious::Routing.get "/e/:id", Invidious::Routes::Watch, :redirect
- Invidious::Routing.get "/redirect", Invidious::Routes::Misc, :cross_instance_redirect
-
- Invidious::Routing.post "/download", Invidious::Routes::Watch, :download
-
- Invidious::Routing.get "/embed/", Invidious::Routes::Embed, :redirect
- Invidious::Routing.get "/embed/:id", Invidious::Routes::Embed, :show
-
- Invidious::Routing.get "/create_playlist", Invidious::Routes::Playlists, :new
- Invidious::Routing.post "/create_playlist", Invidious::Routes::Playlists, :create
- Invidious::Routing.get "/subscribe_playlist", Invidious::Routes::Playlists, :subscribe
- Invidious::Routing.get "/delete_playlist", Invidious::Routes::Playlists, :delete_page
- Invidious::Routing.post "/delete_playlist", Invidious::Routes::Playlists, :delete
- Invidious::Routing.get "/edit_playlist", Invidious::Routes::Playlists, :edit
- Invidious::Routing.post "/edit_playlist", Invidious::Routes::Playlists, :update
- Invidious::Routing.get "/add_playlist_items", Invidious::Routes::Playlists, :add_playlist_items_page
- Invidious::Routing.post "/playlist_ajax", Invidious::Routes::Playlists, :playlist_ajax
- Invidious::Routing.get "/playlist", Invidious::Routes::Playlists, :show
- Invidious::Routing.get "/mix", Invidious::Routes::Playlists, :mix
- Invidious::Routing.get "/watch_videos", Invidious::Routes::Playlists, :watch_videos
-
- Invidious::Routing.get "/opensearch.xml", Invidious::Routes::Search, :opensearch
- Invidious::Routing.get "/results", Invidious::Routes::Search, :results
- Invidious::Routing.get "/search", Invidious::Routes::Search, :search
- Invidious::Routing.get "/hashtag/:hashtag", Invidious::Routes::Search, :hashtag
-
- # User routes
- define_user_routes()
-
- # Feeds
- Invidious::Routing.get "/view_all_playlists", Invidious::Routes::Feeds, :view_all_playlists_redirect
- Invidious::Routing.get "/feed/playlists", Invidious::Routes::Feeds, :playlists
- Invidious::Routing.get "/feed/popular", Invidious::Routes::Feeds, :popular
- Invidious::Routing.get "/feed/trending", Invidious::Routes::Feeds, :trending
- Invidious::Routing.get "/feed/subscriptions", Invidious::Routes::Feeds, :subscriptions
- Invidious::Routing.get "/feed/history", Invidious::Routes::Feeds, :history
-
- # RSS Feeds
- Invidious::Routing.get "/feed/channel/:ucid", Invidious::Routes::Feeds, :rss_channel
- Invidious::Routing.get "/feed/private", Invidious::Routes::Feeds, :rss_private
- Invidious::Routing.get "/feed/playlist/:plid", Invidious::Routes::Feeds, :rss_playlist
- Invidious::Routing.get "/feeds/videos.xml", Invidious::Routes::Feeds, :rss_videos
-
- # Support push notifications via PubSubHubbub
- Invidious::Routing.get "/feed/webhook/:token", Invidious::Routes::Feeds, :push_notifications_get
- Invidious::Routing.post "/feed/webhook/:token", Invidious::Routes::Feeds, :push_notifications_post
-
- Invidious::Routing.get "/modify_notifications", Invidious::Routes::Notifications, :modify
-
- Invidious::Routing.post "/subscription_ajax", Invidious::Routes::Subscriptions, :toggle_subscription
- Invidious::Routing.get "/subscription_manager", Invidious::Routes::Subscriptions, :subscription_manager
-{% end %}
-
-Invidious::Routing.get "/ggpht/*", Invidious::Routes::Images, :ggpht
-Invidious::Routing.options "/sb/:authority/:id/:storyboard/:index", Invidious::Routes::Images, :options_storyboard
-Invidious::Routing.get "/sb/:authority/:id/:storyboard/:index", Invidious::Routes::Images, :get_storyboard
-Invidious::Routing.get "/s_p/:id/:name", Invidious::Routes::Images, :s_p_image
-Invidious::Routing.get "/yts/img/:name", Invidious::Routes::Images, :yts_image
-Invidious::Routing.get "/vi/:id/:name", Invidious::Routes::Images, :thumbnails
-
-# API routes (macro)
-define_v1_api_routes()
-
-# Video playback (macros)
-define_api_manifest_routes()
-define_video_playback_routes()
+Invidious::Routing.register_all
error 404 do |env|
- if md = env.request.path.match(/^\/(?([a-zA-Z0-9_-]{11})|(\w+))$/)
- item = md["id"]
-
- # Check if item is branding URL e.g. https://youtube.com/gaming
- response = YT_POOL.client &.get("/#{item}")
-
- if response.status_code == 301
- response = YT_POOL.client &.get(URI.parse(response.headers["Location"]).request_target)
- end
-
- if response.body.empty?
- env.response.headers["Location"] = "/"
- halt env, status_code: 302
- end
-
- html = XML.parse_html(response.body)
- ucid = html.xpath_node(%q(//link[@rel="canonical"])).try &.["href"].split("/")[-1]
-
- if ucid
- env.response.headers["Location"] = "/channel/#{ucid}"
- halt env, status_code: 302
- end
-
- params = [] of String
- env.params.query.each do |k, v|
- params << "#{k}=#{v}"
- end
- params = params.join("&")
-
- url = "/watch?v=#{item}"
- if !params.empty?
- url += "{params}"
- end
-
- # Check if item is video ID
- if item.match(/^[a-zA-Z0-9_-]{11}$/) && YT_POOL.client &.head("/watch?v=#{item}").status_code != 404
- env.response.headers["Location"] = url
- halt env, status_code: 302
- end
- end
-
- env.response.headers["Location"] = "/"
- halt env, status_code: 302
+ Invidious::Routes::ErrorRoutes.error_404(env)
end
error 500 do |env, ex|
- locale = env.get("preferences").as(Preferences).locale
error_template(500, ex)
end
@@ -484,6 +198,8 @@ static_headers do |response|
response.headers.add("Cache-Control", "max-age=2629800")
end
+# Init Kemal
+
public_folder "assets"
Kemal.config.powered_by_header = false
diff --git a/src/invidious/routes/before_all.cr b/src/invidious/routes/before_all.cr
new file mode 100644
index 00000000..8e2a253f
--- /dev/null
+++ b/src/invidious/routes/before_all.cr
@@ -0,0 +1,152 @@
+module Invidious::Routes::BeforeAll
+ def self.handle(env)
+ preferences = Preferences.from_json("{}")
+
+ begin
+ if prefs_cookie = env.request.cookies["PREFS"]?
+ preferences = Preferences.from_json(URI.decode_www_form(prefs_cookie.value))
+ else
+ if language_header = env.request.headers["Accept-Language"]?
+ if language = ANG.language_negotiator.best(language_header, LOCALES.keys)
+ preferences.locale = language.header
+ end
+ end
+ end
+ rescue
+ preferences = Preferences.from_json("{}")
+ end
+
+ env.set "preferences", preferences
+ env.response.headers["X-XSS-Protection"] = "1; mode=block"
+ env.response.headers["X-Content-Type-Options"] = "nosniff"
+
+ # Allow media resources to be loaded from google servers
+ # TODO: check if *.youtube.com can be removed
+ if CONFIG.disabled?("local") || !preferences.local
+ extra_media_csp = " https://*.googlevideo.com:443 https://*.youtube.com:443"
+ else
+ extra_media_csp = ""
+ end
+
+ # Only allow the pages at /embed/* to be embedded
+ if env.request.resource.starts_with?("/embed")
+ frame_ancestors = "'self' http: https:"
+ else
+ frame_ancestors = "'none'"
+ end
+
+ # TODO: Remove style-src's 'unsafe-inline', requires to remove all
+ # inline styles (, style=" [..] ")
+ env.response.headers["Content-Security-Policy"] = {
+ "default-src 'none'",
+ "script-src 'self'",
+ "style-src 'self' 'unsafe-inline'",
+ "img-src 'self' data:",
+ "font-src 'self' data:",
+ "connect-src 'self'",
+ "manifest-src 'self'",
+ "media-src 'self' blob:" + extra_media_csp,
+ "child-src 'self' blob:",
+ "frame-src 'self'",
+ "frame-ancestors " + frame_ancestors,
+ }.join("; ")
+
+ env.response.headers["Referrer-Policy"] = "same-origin"
+
+ # Ask the chrom*-based browsers to disable FLoC
+ # See: https://blog.runcloud.io/google-floc/
+ env.response.headers["Permissions-Policy"] = "interest-cohort=()"
+
+ if (Kemal.config.ssl || CONFIG.https_only) && CONFIG.hsts
+ env.response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains; preload"
+ end
+
+ return if {
+ "/sb/",
+ "/vi/",
+ "/s_p/",
+ "/yts/",
+ "/ggpht/",
+ "/api/manifest/",
+ "/videoplayback",
+ "/latest_version",
+ "/download",
+ }.any? { |r| env.request.resource.starts_with? r }
+
+ if env.request.cookies.has_key? "SID"
+ sid = env.request.cookies["SID"].value
+
+ if sid.starts_with? "v1:"
+ raise "Cannot use token as SID"
+ end
+
+ # Invidious users only have SID
+ if !env.request.cookies.has_key? "SSID"
+ if email = Invidious::Database::SessionIDs.select_email(sid)
+ user = Invidious::Database::Users.select!(email: email)
+ csrf_token = generate_response(sid, {
+ ":authorize_token",
+ ":playlist_ajax",
+ ":signout",
+ ":subscription_ajax",
+ ":token_ajax",
+ ":watch_ajax",
+ }, HMAC_KEY, 1.week)
+
+ preferences = user.preferences
+ env.set "preferences", preferences
+
+ env.set "sid", sid
+ env.set "csrf_token", csrf_token
+ env.set "user", user
+ end
+ else
+ headers = HTTP::Headers.new
+ headers["Cookie"] = env.request.headers["Cookie"]
+
+ begin
+ user, sid = get_user(sid, headers, false)
+ csrf_token = generate_response(sid, {
+ ":authorize_token",
+ ":playlist_ajax",
+ ":signout",
+ ":subscription_ajax",
+ ":token_ajax",
+ ":watch_ajax",
+ }, HMAC_KEY, 1.week)
+
+ preferences = user.preferences
+ env.set "preferences", preferences
+
+ env.set "sid", sid
+ env.set "csrf_token", csrf_token
+ env.set "user", user
+ rescue ex
+ end
+ end
+ end
+
+ dark_mode = convert_theme(env.params.query["dark_mode"]?) || preferences.dark_mode.to_s
+ thin_mode = env.params.query["thin_mode"]? || preferences.thin_mode.to_s
+ thin_mode = thin_mode == "true"
+ locale = env.params.query["hl"]? || preferences.locale
+
+ preferences.dark_mode = dark_mode
+ preferences.thin_mode = thin_mode
+ preferences.locale = locale
+ env.set "preferences", preferences
+
+ current_page = env.request.path
+ if env.request.query
+ query = HTTP::Params.parse(env.request.query.not_nil!)
+
+ if query["referer"]?
+ query["referer"] = get_referer(env, "/")
+ end
+
+ current_page += "?#{query}"
+ end
+
+ env.set "current_page", URI.encode_www_form(current_page)
+ end
+end
diff --git a/src/invidious/routes/errors.cr b/src/invidious/routes/errors.cr
new file mode 100644
index 00000000..b138b562
--- /dev/null
+++ b/src/invidious/routes/errors.cr
@@ -0,0 +1,47 @@
+module Invidious::Routes::ErrorRoutes
+ def self.error_404(env)
+ if md = env.request.path.match(/^\/(?([a-zA-Z0-9_-]{11})|(\w+))$/)
+ item = md["id"]
+
+ # Check if item is branding URL e.g. https://youtube.com/gaming
+ response = YT_POOL.client &.get("/#{item}")
+
+ if response.status_code == 301
+ response = YT_POOL.client &.get(URI.parse(response.headers["Location"]).request_target)
+ end
+
+ if response.body.empty?
+ env.response.headers["Location"] = "/"
+ haltf env, status_code: 302
+ end
+
+ html = XML.parse_html(response.body)
+ ucid = html.xpath_node(%q(//link[@rel="canonical"])).try &.["href"].split("/")[-1]
+
+ if ucid
+ env.response.headers["Location"] = "/channel/#{ucid}"
+ haltf env, status_code: 302
+ end
+
+ params = [] of String
+ env.params.query.each do |k, v|
+ params << "#{k}=#{v}"
+ end
+ params = params.join("&")
+
+ url = "/watch?v=#{item}"
+ if !params.empty?
+ url += "{params}"
+ end
+
+ # Check if item is video ID
+ if item.match(/^[a-zA-Z0-9_-]{11}$/) && YT_POOL.client &.head("/watch?v=#{item}").status_code != 404
+ env.response.headers["Location"] = url
+ haltf env, status_code: 302
+ end
+ end
+
+ env.response.headers["Location"] = "/"
+ haltf env, status_code: 302
+ end
+end
diff --git a/src/invidious/routing.cr b/src/invidious/routing.cr
index bd72c577..b1cef086 100644
--- a/src/invidious/routing.cr
+++ b/src/invidious/routing.cr
@@ -1,130 +1,273 @@
module Invidious::Routing
- {% for http_method in {"get", "post", "delete", "options", "patch", "put", "head"} %}
+ extend self
+
+ {% for http_method in {"get", "post", "delete", "options", "patch", "put"} %}
macro {{http_method.id}}(path, controller, method = :handle)
- {{http_method.id}} \{{ path }} do |env|
+ unless !Kemal::Utils.path_starts_with_slash?(\{{path}})
+ raise Kemal::Exceptions::InvalidPathStartException.new({{http_method}}, \{{path}})
+ end
+
+ Kemal::RouteHandler::INSTANCE.add_route({{http_method.upcase}}, \{{path}}) do |env|
\{{ controller }}.\{{ method.id }}(env)
end
end
{% end %}
-end
-
-macro define_user_routes
- # User login/out
- Invidious::Routing.get "/login", Invidious::Routes::Login, :login_page
- Invidious::Routing.post "/login", Invidious::Routes::Login, :login
- Invidious::Routing.post "/signout", Invidious::Routes::Login, :signout
- Invidious::Routing.get "/Captcha", Invidious::Routes::Login, :captcha
-
- # User preferences
- Invidious::Routing.get "/preferences", Invidious::Routes::PreferencesRoute, :show
- Invidious::Routing.post "/preferences", Invidious::Routes::PreferencesRoute, :update
- Invidious::Routing.get "/toggle_theme", Invidious::Routes::PreferencesRoute, :toggle_theme
- Invidious::Routing.get "/data_control", Invidious::Routes::PreferencesRoute, :data_control
- Invidious::Routing.post "/data_control", Invidious::Routes::PreferencesRoute, :update_data_control
-
- # User account management
- Invidious::Routing.get "/change_password", Invidious::Routes::Account, :get_change_password
- Invidious::Routing.post "/change_password", Invidious::Routes::Account, :post_change_password
- Invidious::Routing.get "/delete_account", Invidious::Routes::Account, :get_delete
- Invidious::Routing.post "/delete_account", Invidious::Routes::Account, :post_delete
- Invidious::Routing.get "/clear_watch_history", Invidious::Routes::Account, :get_clear_history
- Invidious::Routing.post "/clear_watch_history", Invidious::Routes::Account, :post_clear_history
- Invidious::Routing.get "/authorize_token", Invidious::Routes::Account, :get_authorize_token
- Invidious::Routing.post "/authorize_token", Invidious::Routes::Account, :post_authorize_token
- Invidious::Routing.get "/token_manager", Invidious::Routes::Account, :token_manager
- Invidious::Routing.post "/token_ajax", Invidious::Routes::Account, :token_ajax
-end
-
-macro define_v1_api_routes
- {{namespace = Invidious::Routes::API::V1}}
- # Videos
- Invidious::Routing.get "/api/v1/videos/:id", {{namespace}}::Videos, :videos
- Invidious::Routing.get "/api/v1/storyboards/:id", {{namespace}}::Videos, :storyboards
- Invidious::Routing.get "/api/v1/captions/:id", {{namespace}}::Videos, :captions
- Invidious::Routing.get "/api/v1/annotations/:id", {{namespace}}::Videos, :annotations
- Invidious::Routing.get "/api/v1/comments/:id", {{namespace}}::Videos, :comments
-
- # Feeds
- Invidious::Routing.get "/api/v1/trending", {{namespace}}::Feeds, :trending
- Invidious::Routing.get "/api/v1/popular", {{namespace}}::Feeds, :popular
-
- # Channels
- Invidious::Routing.get "/api/v1/channels/:ucid", {{namespace}}::Channels, :home
- {% for route in {"videos", "latest", "playlists", "community", "search"} %}
- Invidious::Routing.get "/api/v1/channels/#{{{route}}}/:ucid", {{namespace}}::Channels, :{{route}}
- Invidious::Routing.get "/api/v1/channels/:ucid/#{{{route}}}", {{namespace}}::Channels, :{{route}}
- {% end %}
-
- # 301 redirects to new /api/v1/channels/community/:ucid and /:ucid/community
- Invidious::Routing.get "/api/v1/channels/comments/:ucid", {{namespace}}::Channels, :channel_comments_redirect
- Invidious::Routing.get "/api/v1/channels/:ucid/comments", {{namespace}}::Channels, :channel_comments_redirect
-
-
- # Search
- Invidious::Routing.get "/api/v1/search", {{namespace}}::Search, :search
- Invidious::Routing.get "/api/v1/search/suggestions", {{namespace}}::Search, :search_suggestions
-
- # 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
-
- Invidious::Routing.get "/api/v1/auth/preferences", {{namespace}}::Authenticated, :get_preferences
- Invidious::Routing.post "/api/v1/auth/preferences", {{namespace}}::Authenticated, :set_preferences
-
- Invidious::Routing.get "/api/v1/auth/feed", {{namespace}}::Authenticated, :feed
-
- Invidious::Routing.get "/api/v1/auth/subscriptions", {{namespace}}::Authenticated, :get_subscriptions
- Invidious::Routing.post "/api/v1/auth/subscriptions/:ucid", {{namespace}}::Authenticated, :subscribe_channel
- Invidious::Routing.delete "/api/v1/auth/subscriptions/:ucid", {{namespace}}::Authenticated, :unsubscribe_channel
-
-
- Invidious::Routing.get "/api/v1/auth/playlists", {{namespace}}::Authenticated, :list_playlists
- Invidious::Routing.post "/api/v1/auth/playlists", {{namespace}}::Authenticated, :create_playlist
- Invidious::Routing.patch "/api/v1/auth/playlists/:plid",{{namespace}}:: Authenticated, :update_playlist_attribute
- Invidious::Routing.delete "/api/v1/auth/playlists/:plid", {{namespace}}::Authenticated, :delete_playlist
-
-
- Invidious::Routing.post "/api/v1/auth/playlists/:plid/videos", {{namespace}}::Authenticated, :insert_video_into_playlist
- Invidious::Routing.delete "/api/v1/auth/playlists/:plid/videos/:index", {{namespace}}::Authenticated, :delete_video_in_playlist
-
- Invidious::Routing.get "/api/v1/auth/tokens", {{namespace}}::Authenticated, :get_tokens
- Invidious::Routing.post "/api/v1/auth/tokens/register", {{namespace}}::Authenticated, :register_token
- Invidious::Routing.post "/api/v1/auth/tokens/unregister", {{namespace}}::Authenticated, :unregister_token
-
- Invidious::Routing.get "/api/v1/auth/notifications", {{namespace}}::Authenticated, :notifications
- Invidious::Routing.post "/api/v1/auth/notifications", {{namespace}}::Authenticated, :notifications
-
- # Misc
- Invidious::Routing.get "/api/v1/stats", {{namespace}}::Misc, :stats
- Invidious::Routing.get "/api/v1/playlists/:plid", {{namespace}}::Misc, :get_playlist
- Invidious::Routing.get "/api/v1/auth/playlists/:plid", {{namespace}}::Misc, :get_playlist
- Invidious::Routing.get "/api/v1/mixes/:rdid", {{namespace}}::Misc, :mixes
-end
-
-macro define_api_manifest_routes
- Invidious::Routing.get "/api/manifest/dash/id/:id", Invidious::Routes::API::Manifest, :get_dash_video_id
-
- Invidious::Routing.get "/api/manifest/dash/id/videoplayback", Invidious::Routes::API::Manifest, :get_dash_video_playback
- Invidious::Routing.get "/api/manifest/dash/id/videoplayback/*", Invidious::Routes::API::Manifest, :get_dash_video_playback_greedy
-
- Invidious::Routing.options "/api/manifest/dash/id/videoplayback", Invidious::Routes::API::Manifest, :options_dash_video_playback
- Invidious::Routing.options "/api/manifest/dash/id/videoplayback/*", Invidious::Routes::API::Manifest, :options_dash_video_playback
-
- Invidious::Routing.get "/api/manifest/hls_playlist/*", Invidious::Routes::API::Manifest, :get_hls_playlist
- Invidious::Routing.get "/api/manifest/hls_variant/*", Invidious::Routes::API::Manifest, :get_hls_variant
-end
-
-macro define_video_playback_routes
- Invidious::Routing.get "/videoplayback", Invidious::Routes::VideoPlayback, :get_video_playback
- Invidious::Routing.get "/videoplayback/*", Invidious::Routes::VideoPlayback, :get_video_playback_greedy
-
- Invidious::Routing.options "/videoplayback", Invidious::Routes::VideoPlayback, :options_video_playback
- Invidious::Routing.options "/videoplayback/*", Invidious::Routes::VideoPlayback, :options_video_playback
-
- Invidious::Routing.get "/latest_version", Invidious::Routes::VideoPlayback, :latest_version
+
+ def register_all
+ {% unless flag?(:api_only) %}
+ get "/", Routes::Misc, :home
+ get "/privacy", Routes::Misc, :privacy
+ get "/licenses", Routes::Misc, :licenses
+ get "/redirect", Routes::Misc, :cross_instance_redirect
+
+ self.register_channel_routes
+ self.register_watch_routes
+
+ self.register_iv_playlist_routes
+ self.register_yt_playlist_routes
+
+ self.register_search_routes
+
+ self.register_user_routes
+ self.register_feed_routes
+
+ # Support push notifications via PubSubHubbub
+ get "/feed/webhook/:token", Routes::Feeds, :push_notifications_get
+ post "/feed/webhook/:token", Routes::Feeds, :push_notifications_post
+
+ get "/modify_notifications", Routes::Notifications, :modify
+ {% end %}
+
+ self.register_image_routes
+ self.register_api_v1_routes
+ self.register_api_manifest_routes
+ self.register_video_playback_routes
+ end
+
+ # -------------------
+ # Invidious routes
+ # -------------------
+
+ def register_user_routes
+ # User login/out
+ get "/login", Routes::Login, :login_page
+ post "/login", Routes::Login, :login
+ post "/signout", Routes::Login, :signout
+ get "/Captcha", Routes::Login, :captcha
+
+ # User preferences
+ get "/preferences", Routes::PreferencesRoute, :show
+ post "/preferences", Routes::PreferencesRoute, :update
+ get "/toggle_theme", Routes::PreferencesRoute, :toggle_theme
+ get "/data_control", Routes::PreferencesRoute, :data_control
+ post "/data_control", Routes::PreferencesRoute, :update_data_control
+
+ # User account management
+ get "/change_password", Routes::Account, :get_change_password
+ post "/change_password", Routes::Account, :post_change_password
+ get "/delete_account", Routes::Account, :get_delete
+ post "/delete_account", Routes::Account, :post_delete
+ get "/clear_watch_history", Routes::Account, :get_clear_history
+ post "/clear_watch_history", Routes::Account, :post_clear_history
+ get "/authorize_token", Routes::Account, :get_authorize_token
+ post "/authorize_token", Routes::Account, :post_authorize_token
+ get "/token_manager", Routes::Account, :token_manager
+ post "/token_ajax", Routes::Account, :token_ajax
+ post "/subscription_ajax", Routes::Subscriptions, :toggle_subscription
+ get "/subscription_manager", Routes::Subscriptions, :subscription_manager
+ end
+
+ def register_iv_playlist_routes
+ get "/create_playlist", Routes::Playlists, :new
+ post "/create_playlist", Routes::Playlists, :create
+ get "/subscribe_playlist", Routes::Playlists, :subscribe
+ get "/delete_playlist", Routes::Playlists, :delete_page
+ post "/delete_playlist", Routes::Playlists, :delete
+ get "/edit_playlist", Routes::Playlists, :edit
+ post "/edit_playlist", Routes::Playlists, :update
+ get "/add_playlist_items", Routes::Playlists, :add_playlist_items_page
+ post "/playlist_ajax", Routes::Playlists, :playlist_ajax
+ end
+
+ def register_feed_routes
+ # Feeds
+ get "/view_all_playlists", Routes::Feeds, :view_all_playlists_redirect
+ get "/feed/playlists", Routes::Feeds, :playlists
+ get "/feed/popular", Routes::Feeds, :popular
+ get "/feed/trending", Routes::Feeds, :trending
+ get "/feed/subscriptions", Routes::Feeds, :subscriptions
+ get "/feed/history", Routes::Feeds, :history
+
+ # RSS Feeds
+ get "/feed/channel/:ucid", Routes::Feeds, :rss_channel
+ get "/feed/private", Routes::Feeds, :rss_private
+ get "/feed/playlist/:plid", Routes::Feeds, :rss_playlist
+ get "/feeds/videos.xml", Routes::Feeds, :rss_videos
+ end
+
+ # -------------------
+ # Youtube routes
+ # -------------------
+
+ def register_channel_routes
+ get "/channel/:ucid", Routes::Channels, :home
+ get "/channel/:ucid/home", Routes::Channels, :home
+ get "/channel/:ucid/videos", Routes::Channels, :videos
+ get "/channel/:ucid/playlists", Routes::Channels, :playlists
+ get "/channel/:ucid/community", Routes::Channels, :community
+ get "/channel/:ucid/about", Routes::Channels, :about
+ get "/channel/:ucid/live", Routes::Channels, :live
+ get "/user/:user/live", Routes::Channels, :live
+ get "/c/:user/live", Routes::Channels, :live
+
+ ["", "/videos", "/playlists", "/community", "/about"].each do |path|
+ # /c/LinusTechTips
+ get "/c/:user#{path}", Routes::Channels, :brand_redirect
+ # /user/linustechtips | Not always the same as /c/
+ get "/user/:user#{path}", Routes::Channels, :brand_redirect
+ # /attribution_link?a=anything&u=/channel/UCZYTClx2T1of7BRZ86-8fow
+ get "/attribution_link#{path}", Routes::Channels, :brand_redirect
+ # /profile?user=linustechtips
+ get "/profile/#{path}", Routes::Channels, :profile
+ end
+ end
+
+ def register_watch_routes
+ get "/watch", Routes::Watch, :handle
+ post "/watch_ajax", Routes::Watch, :mark_watched
+ get "/watch/:id", Routes::Watch, :redirect
+ get "/shorts/:id", Routes::Watch, :redirect
+ get "/clip/:clip", Routes::Watch, :clip
+ get "/w/:id", Routes::Watch, :redirect
+ get "/v/:id", Routes::Watch, :redirect
+ get "/e/:id", Routes::Watch, :redirect
+
+ post "/download", Routes::Watch, :download
+
+ get "/embed/", Routes::Embed, :redirect
+ get "/embed/:id", Routes::Embed, :show
+ end
+
+ def register_yt_playlist_routes
+ get "/playlist", Routes::Playlists, :show
+ get "/mix", Routes::Playlists, :mix
+ get "/watch_videos", Routes::Playlists, :watch_videos
+ end
+
+ def register_search_routes
+ get "/opensearch.xml", Routes::Search, :opensearch
+ get "/results", Routes::Search, :results
+ get "/search", Routes::Search, :search
+ get "/hashtag/:hashtag", Routes::Search, :hashtag
+ end
+
+ # -------------------
+ # Media proxy routes
+ # -------------------
+
+ def register_api_manifest_routes
+ get "/api/manifest/dash/id/:id", Routes::API::Manifest, :get_dash_video_id
+
+ get "/api/manifest/dash/id/videoplayback", Routes::API::Manifest, :get_dash_video_playback
+ get "/api/manifest/dash/id/videoplayback/*", Routes::API::Manifest, :get_dash_video_playback_greedy
+
+ options "/api/manifest/dash/id/videoplayback", Routes::API::Manifest, :options_dash_video_playback
+ options "/api/manifest/dash/id/videoplayback/*", Routes::API::Manifest, :options_dash_video_playback
+
+ get "/api/manifest/hls_playlist/*", Routes::API::Manifest, :get_hls_playlist
+ get "/api/manifest/hls_variant/*", Routes::API::Manifest, :get_hls_variant
+ end
+
+ def register_video_playback_routes
+ get "/videoplayback", Routes::VideoPlayback, :get_video_playback
+ get "/videoplayback/*", Routes::VideoPlayback, :get_video_playback_greedy
+
+ options "/videoplayback", Routes::VideoPlayback, :options_video_playback
+ options "/videoplayback/*", Routes::VideoPlayback, :options_video_playback
+
+ get "/latest_version", Routes::VideoPlayback, :latest_version
+ end
+
+ def register_image_routes
+ get "/ggpht/*", Routes::Images, :ggpht
+ options "/sb/:authority/:id/:storyboard/:index", Routes::Images, :options_storyboard
+ get "/sb/:authority/:id/:storyboard/:index", Routes::Images, :get_storyboard
+ get "/s_p/:id/:name", Routes::Images, :s_p_image
+ get "/yts/img/:name", Routes::Images, :yts_image
+ get "/vi/:id/:name", Routes::Images, :thumbnails
+ end
+
+ # -------------------
+ # API routes
+ # -------------------
+
+ def register_api_v1_routes
+ {% begin %}
+ {{namespace = Routes::API::V1}}
+
+ # Videos
+ get "/api/v1/videos/:id", {{namespace}}::Videos, :videos
+ get "/api/v1/storyboards/:id", {{namespace}}::Videos, :storyboards
+ get "/api/v1/captions/:id", {{namespace}}::Videos, :captions
+ get "/api/v1/annotations/:id", {{namespace}}::Videos, :annotations
+ get "/api/v1/comments/:id", {{namespace}}::Videos, :comments
+
+ # Feeds
+ get "/api/v1/trending", {{namespace}}::Feeds, :trending
+ get "/api/v1/popular", {{namespace}}::Feeds, :popular
+
+ # Channels
+ get "/api/v1/channels/:ucid", {{namespace}}::Channels, :home
+ {% 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 %}
+
+ # 301 redirects to new /api/v1/channels/community/:ucid and /:ucid/community
+ get "/api/v1/channels/comments/:ucid", {{namespace}}::Channels, :channel_comments_redirect
+ get "/api/v1/channels/:ucid/comments", {{namespace}}::Channels, :channel_comments_redirect
+
+ # Search
+ get "/api/v1/search", {{namespace}}::Search, :search
+ get "/api/v1/search/suggestions", {{namespace}}::Search, :search_suggestions
+
+ # 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
+ post "/api/v1/auth/preferences", {{namespace}}::Authenticated, :set_preferences
+
+ get "/api/v1/auth/feed", {{namespace}}::Authenticated, :feed
+
+ get "/api/v1/auth/subscriptions", {{namespace}}::Authenticated, :get_subscriptions
+ post "/api/v1/auth/subscriptions/:ucid", {{namespace}}::Authenticated, :subscribe_channel
+ delete "/api/v1/auth/subscriptions/:ucid", {{namespace}}::Authenticated, :unsubscribe_channel
+
+ get "/api/v1/auth/playlists", {{namespace}}::Authenticated, :list_playlists
+ post "/api/v1/auth/playlists", {{namespace}}::Authenticated, :create_playlist
+ patch "/api/v1/auth/playlists/:plid",{{namespace}}:: Authenticated, :update_playlist_attribute
+ delete "/api/v1/auth/playlists/:plid", {{namespace}}::Authenticated, :delete_playlist
+ post "/api/v1/auth/playlists/:plid/videos", {{namespace}}::Authenticated, :insert_video_into_playlist
+ delete "/api/v1/auth/playlists/:plid/videos/:index", {{namespace}}::Authenticated, :delete_video_in_playlist
+
+ get "/api/v1/auth/tokens", {{namespace}}::Authenticated, :get_tokens
+ post "/api/v1/auth/tokens/register", {{namespace}}::Authenticated, :register_token
+ post "/api/v1/auth/tokens/unregister", {{namespace}}::Authenticated, :unregister_token
+
+ get "/api/v1/auth/notifications", {{namespace}}::Authenticated, :notifications
+ post "/api/v1/auth/notifications", {{namespace}}::Authenticated, :notifications
+
+ # Misc
+ get "/api/v1/stats", {{namespace}}::Misc, :stats
+ get "/api/v1/playlists/:plid", {{namespace}}::Misc, :get_playlist
+ get "/api/v1/auth/playlists/:plid", {{namespace}}::Misc, :get_playlist
+ get "/api/v1/mixes/:rdid", {{namespace}}::Misc, :mixes
+ {% end %}
+ end
end