summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/invidious.cr1
-rw-r--r--src/invidious/helpers/errors.cr35
-rw-r--r--src/invidious/helpers/helpers.cr1
-rw-r--r--src/invidious/helpers/utils.cr62
-rw-r--r--src/invidious/routes/misc.cr11
-rw-r--r--src/invidious/routes/preferences.cr61
-rw-r--r--src/invidious/users.cr1
-rw-r--r--src/invidious/views/channel.ecr3
-rw-r--r--src/invidious/views/community.ecr3
-rw-r--r--src/invidious/views/components/item.ecr21
-rw-r--r--src/invidious/views/error.ecr1
-rw-r--r--src/invidious/views/playlist.ecr5
-rw-r--r--src/invidious/views/playlists.ecr5
-rw-r--r--src/invidious/views/preferences.ecr7
-rw-r--r--src/invidious/views/search.ecr183
-rw-r--r--src/invidious/views/watch.ecr3
16 files changed, 281 insertions, 122 deletions
diff --git a/src/invidious.cr b/src/invidious.cr
index 7037ecfe..b1ee1525 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -314,6 +314,7 @@ Invidious::Routing.get "/shorts/:id", Invidious::Routes::Watch, :redirect
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.get "/embed/", Invidious::Routes::Embed, :redirect
Invidious::Routing.get "/embed/:id", Invidious::Routes::Embed, :show
diff --git a/src/invidious/helpers/errors.cr b/src/invidious/helpers/errors.cr
index 68ced430..e1d02563 100644
--- a/src/invidious/helpers/errors.cr
+++ b/src/invidious/helpers/errors.cr
@@ -40,6 +40,9 @@ def error_template_helper(env : HTTP::Server::Context, locale : Hash(String, JSO
and include the following text in your message:
<pre style="padding: 20px; background: rgba(0, 0, 0, 0.12345);">#{issue_template}</pre>
END_HTML
+
+ next_steps = error_redirect_helper(env, locale)
+
return templated "error"
end
@@ -47,6 +50,7 @@ def error_template_helper(env : HTTP::Server::Context, locale : Hash(String, JSO
env.response.content_type = "text/html"
env.response.status_code = status_code
error_message = translate(locale, message)
+ next_steps = error_redirect_helper(env, locale)
return templated "error"
end
@@ -103,3 +107,34 @@ end
def error_json_helper(env : HTTP::Server::Context, locale : Hash(String, JSON::Any) | Nil, status_code : Int32, message : String)
error_json_helper(env, locale, status_code, message, nil)
end
+
+def error_redirect_helper(env : HTTP::Server::Context, locale : Hash(String, JSON::Any) | Nil)
+ request_path = env.request.path
+
+ if request_path.starts_with?("/search") || request_path.starts_with?("/watch") ||
+ request_path.starts_with?("/channel") || request_path.starts_with?("/playlist?list=PL")
+ next_steps_text = translate(locale, "next_steps_error_message")
+ refresh = translate(locale, "next_steps_error_message_refresh")
+ go_to_youtube = translate(locale, "next_steps_error_message_go_to_youtube")
+ switch_instance = translate(locale, "Switch Invidious Instance")
+
+ return <<-END_HTML
+ <p style="margin-bottom: 4px;">#{next_steps_text}</p>
+ <ul>
+ <li>
+ <a href="#{env.request.resource}">#{refresh}</a>
+ </li>
+ <li>
+ <a href="/redirect?referer=#{env.get("current_page")}">#{switch_instance}</a>
+ </li>
+ <li>
+ <a href="https://youtube.com#{env.request.resource}">#{go_to_youtube}</a>
+ </li>
+ </ul>
+ END_HTML
+
+ return next_step_html
+ else
+ return ""
+ end
+end
diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr
index 1f92c4ce..0c70cb02 100644
--- a/src/invidious/helpers/helpers.cr
+++ b/src/invidious/helpers/helpers.cr
@@ -44,6 +44,7 @@ struct ConfigPreferences
property quality_dash : String = "auto"
property default_home : String? = "Popular"
property feed_menu : Array(String) = ["Popular", "Trending", "Subscriptions", "Playlists"]
+ property automatic_instance_redirect : Bool = false
property related_videos : Bool = true
property sort : String = "published"
property speed : Float32 = 1.0_f32
diff --git a/src/invidious/helpers/utils.cr b/src/invidious/helpers/utils.cr
index 66ad6961..6ee07d7a 100644
--- a/src/invidious/helpers/utils.cr
+++ b/src/invidious/helpers/utils.cr
@@ -409,3 +409,65 @@ def convert_theme(theme)
theme
end
end
+
+def fetch_random_instance
+ begin
+ instance_api_client = HTTP::Client.new(URI.parse("https://api.invidious.io"))
+
+ # Timeouts
+ instance_api_client.connect_timeout = 10.seconds
+ instance_api_client.dns_timeout = 10.seconds
+
+ instance_list = JSON.parse(instance_api_client.get("/instances.json").body).as_a
+ instance_api_client.close
+ rescue Socket::ConnectError | IO::TimeoutError | JSON::ParseException
+ instance_list = [] of JSON::Any
+ end
+
+ filtered_instance_list = [] of String
+
+ instance_list.each do |data|
+ # TODO Check if current URL is onion instance and use .onion types if so.
+ if data[1]["type"] == "https"
+ # Instances can have statisitics disabled, which is an requirement of version validation.
+ # as_nil? doesn't exist. Thus we'll have to handle the error rasied if as_nil fails.
+ begin
+ data[1]["stats"].as_nil
+ next
+ rescue TypeCastError
+ end
+
+ # stats endpoint could also lack the software dict.
+ next if data[1]["stats"]["software"]?.nil?
+
+ # Makes sure the instance isn't too outdated.
+ if remote_version = data[1]["stats"]?.try &.["software"]?.try &.["version"]
+ remote_commit_date = remote_version.as_s.match(/\d{4}\.\d{2}\.\d{2}/)
+ next if !remote_commit_date
+
+ remote_commit_date = Time.parse(remote_commit_date[0], "%Y.%m.%d", Time::Location::UTC)
+ local_commit_date = Time.parse(CURRENT_VERSION, "%Y.%m.%d", Time::Location::UTC)
+
+ next if (remote_commit_date - local_commit_date).abs.days > 30
+
+ begin
+ data[1]["monitor"].as_nil
+ health = data[1]["monitor"].as_h["dailyRatios"][0].as_h["ratio"]
+ filtered_instance_list << data[0].as_s if health.to_s.to_f > 90
+ rescue TypeCastError
+ # We can't check the health if the monitoring is broken. Thus we'll just add it to the list
+ # and move on. Ideally we'll ignore any instance that has broken health monitoring but due to the fact that
+ # it's an error that often occurs with all the instances at the same time, we have to just skip the check.
+ filtered_instance_list << data[0].as_s
+ end
+ end
+ end
+ end
+
+ # If for some reason no instances managed to get fetched successfully then we'll just redirect to redirect.invidious.io
+ if filtered_instance_list.size == 0
+ return "redirect.invidious.io"
+ end
+
+ return filtered_instance_list.sample(1)[0]
+end
diff --git a/src/invidious/routes/misc.cr b/src/invidious/routes/misc.cr
index d32ba892..336f7e33 100644
--- a/src/invidious/routes/misc.cr
+++ b/src/invidious/routes/misc.cr
@@ -35,4 +35,15 @@ class Invidious::Routes::Misc < Invidious::Routes::BaseRoute
locale = LOCALES[env.get("preferences").as(Preferences).locale]?
rendered "licenses"
end
+
+ def cross_instance_redirect(env)
+ referer = get_referer(env)
+
+ if !env.get("preferences").as(Preferences).automatic_instance_redirect
+ return env.redirect("https://redirect.invidious.io#{referer}")
+ end
+
+ instance_url = fetch_random_instance
+ env.redirect "https://#{instance_url}#{referer}"
+ end
end
diff --git a/src/invidious/routes/preferences.cr b/src/invidious/routes/preferences.cr
index f98c7a5e..d6002ffd 100644
--- a/src/invidious/routes/preferences.cr
+++ b/src/invidious/routes/preferences.cr
@@ -92,6 +92,10 @@ class Invidious::Routes::PreferencesRoute < Invidious::Routes::BaseRoute
end
end
+ automatic_instance_redirect = env.params.body["automatic_instance_redirect"]?.try &.as(String)
+ automatic_instance_redirect ||= "off"
+ automatic_instance_redirect = automatic_instance_redirect == "on"
+
locale = env.params.body["locale"]?.try &.as(String)
locale ||= CONFIG.default_user_preferences.locale
@@ -122,34 +126,35 @@ class Invidious::Routes::PreferencesRoute < Invidious::Routes::BaseRoute
# Convert to JSON and back again to take advantage of converters used for compatability
preferences = Preferences.from_json({
- annotations: annotations,
- annotations_subscribed: annotations_subscribed,
- autoplay: autoplay,
- captions: captions,
- comments: comments,
- continue: continue,
- continue_autoplay: continue_autoplay,
- dark_mode: dark_mode,
- latest_only: latest_only,
- listen: listen,
- local: local,
- locale: locale,
- max_results: max_results,
- notifications_only: notifications_only,
- player_style: player_style,
- quality: quality,
- quality_dash: quality_dash,
- default_home: default_home,
- feed_menu: feed_menu,
- related_videos: related_videos,
- sort: sort,
- speed: speed,
- thin_mode: thin_mode,
- unseen_only: unseen_only,
- video_loop: video_loop,
- volume: volume,
- extend_desc: extend_desc,
- vr_mode: vr_mode,
+ annotations: annotations,
+ annotations_subscribed: annotations_subscribed,
+ autoplay: autoplay,
+ captions: captions,
+ comments: comments,
+ continue: continue,
+ continue_autoplay: continue_autoplay,
+ dark_mode: dark_mode,
+ latest_only: latest_only,
+ listen: listen,
+ local: local,
+ locale: locale,
+ max_results: max_results,
+ notifications_only: notifications_only,
+ player_style: player_style,
+ quality: quality,
+ quality_dash: quality_dash,
+ default_home: default_home,
+ feed_menu: feed_menu,
+ automatic_instance_redirect: automatic_instance_redirect,
+ related_videos: related_videos,
+ sort: sort,
+ speed: speed,
+ thin_mode: thin_mode,
+ unseen_only: unseen_only,
+ video_loop: video_loop,
+ volume: volume,
+ extend_desc: extend_desc,
+ vr_mode: vr_mode,
}.to_json).to_json
if user = env.get? "user"
diff --git a/src/invidious/users.cr b/src/invidious/users.cr
index d774ee12..98ef8792 100644
--- a/src/invidious/users.cr
+++ b/src/invidious/users.cr
@@ -36,6 +36,7 @@ struct Preferences
property annotations : Bool = CONFIG.default_user_preferences.annotations
property annotations_subscribed : Bool = CONFIG.default_user_preferences.annotations_subscribed
property autoplay : Bool = CONFIG.default_user_preferences.autoplay
+ property automatic_instance_redirect : Bool = CONFIG.default_user_preferences.automatic_instance_redirect
@[JSON::Field(converter: Preferences::StringToArray)]
@[YAML::Field(converter: Preferences::StringToArray)]
diff --git a/src/invidious/views/channel.ecr b/src/invidious/views/channel.ecr
index 061d7eec..21038394 100644
--- a/src/invidious/views/channel.ecr
+++ b/src/invidious/views/channel.ecr
@@ -41,6 +41,9 @@
<div class="pure-g h-box">
<div class="pure-u-1-3">
<a href="https://www.youtube.com/channel/<%= channel.ucid %>"><%= translate(locale, "View channel on YouTube") %></a>
+ <div class="pure-u-1 pure-md-1-3">
+ <a href="/redirect?referer=<%= env.get?("current_page") %>"><%= translate(locale, "Switch Invidious Instance") %></a>
+ </div>
<% if !channel.auto_generated %>
<div class="pure-u-1 pure-md-1-3">
<b><%= translate(locale, "Videos") %></b>
diff --git a/src/invidious/views/community.ecr b/src/invidious/views/community.ecr
index 3c4eaabb..b0092e5f 100644
--- a/src/invidious/views/community.ecr
+++ b/src/invidious/views/community.ecr
@@ -40,6 +40,9 @@
<div class="pure-g h-box">
<div class="pure-u-1-3">
<a href="https://www.youtube.com/channel/<%= channel.ucid %>/community"><%= translate(locale, "View channel on YouTube") %></a>
+ <div class="pure-u-1 pure-md-1-3">
+ <a href="/redirect?referer=<%= env.get?("current_page") %>"><%= translate(locale, "Switch Invidious Instance") %></a>
+ </div>
<% if !channel.auto_generated %>
<div class="pure-u-1 pure-md-1-3">
<a href="/channel/<%= channel.ucid %>"><%= translate(locale, "Videos") %></a>
diff --git a/src/invidious/views/components/item.ecr b/src/invidious/views/components/item.ecr
index 9dfa047e..6f027bee 100644
--- a/src/invidious/views/components/item.ecr
+++ b/src/invidious/views/components/item.ecr
@@ -137,17 +137,22 @@
</a>
<% end %>
<p><a href="/watch?v=<%= item.id %>"><%= HTML.escape(item.title) %></a></p>
- <p style="display: flex;">
+ <div style="display: flex">
<b style="flex: 1;">
<a style="width:100%" href="/channel/<%= item.ucid %>"><%= item.author %></a>
</b>
- <a title="<%=translate(locale, "Watch on YouTube")%>" href="https://www.youtube.com/watch?v=<%= item.id %>" style="margin-right: 5px;">
- <i class="icon ion-logo-youtube"></i>
- </a>
- <a title="<%=translate(locale, "Audio mode")%>" href="/watch?v=<%= item.id %>&amp;listen=1">
- <i class="icon ion-md-headset"></i>
- </a>
- </p>
+ <div class="icon-buttons">
+ <a title="<%=translate(locale, "Watch on YouTube")%>" href="https://www.youtube.com/watch?v=<%= item.id %>">
+ <i class="icon ion-logo-youtube"></i>
+ </a>
+ <a title="<%=translate(locale, "Audio mode")%>" href="/watch?v=<%= item.id %>&amp;listen=1">
+ <i class="icon ion-md-headset"></i>
+ </a>
+ <a title="<%=translate(locale, "Switch Invidious Instance")%>" href="/redirect?referer=<%=HTML.escape("watch?v=#{item.id}")%>">
+ <i class="icon ion-md-jet"></i>
+ </a>
+ </div>
+ </div>
<h5 class="pure-g">
<% if item.responds_to?(:premiere_timestamp) && item.premiere_timestamp.try &.> Time.utc %>
diff --git a/src/invidious/views/error.ecr b/src/invidious/views/error.ecr
index d0752e5b..04eb74d5 100644
--- a/src/invidious/views/error.ecr
+++ b/src/invidious/views/error.ecr
@@ -4,4 +4,5 @@
<div class="h-box">
<%= error_message %>
+ <%= next_steps %>
</div>
diff --git a/src/invidious/views/playlist.ecr b/src/invidious/views/playlist.ecr
index 91156028..a19dd182 100644
--- a/src/invidious/views/playlist.ecr
+++ b/src/invidious/views/playlist.ecr
@@ -36,6 +36,11 @@
<a href="https://www.youtube.com/playlist?list=<%= playlist.id %>">
<%= translate(locale, "View playlist on YouTube") %>
</a>
+ <span> | </span>
+ <a href="/redirect?referer=<%= env.get?("current_page") %>">
+ <%= translate(locale, "Switch Invidious Instance") %>
+ </a>
+
</div>
<% end %>
</div>
diff --git a/src/invidious/views/playlists.ecr b/src/invidious/views/playlists.ecr
index 44bdb94d..975ccd6c 100644
--- a/src/invidious/views/playlists.ecr
+++ b/src/invidious/views/playlists.ecr
@@ -42,6 +42,11 @@
<div class="pure-u-1 pure-md-1-3">
<a href="https://www.youtube.com/channel/<%= channel.ucid %>/playlists"><%= translate(locale, "View channel on YouTube") %></a>
</div>
+
+ <div class="pure-u-1 pure-md-1-3">
+ <a href="/redirect?referer=<%= env.get?("current_page") %>"><%= translate(locale, "Switch Invidious Instance") %></a>
+ </div>
+
<div class="pure-u-1 pure-md-1-3">
<a href="/channel/<%= channel.ucid %>"><%= translate(locale, "Videos") %></a>
</div>
diff --git a/src/invidious/views/preferences.ecr b/src/invidious/views/preferences.ecr
index 1e1e8cae..c5b64ad6 100644
--- a/src/invidious/views/preferences.ecr
+++ b/src/invidious/views/preferences.ecr
@@ -176,6 +176,13 @@
<% end %>
</div>
+ <legend><%= translate(locale, "Miscellaneous preferences") %></legend>
+
+ <div class="pure-control-group">
+ <label for="automatic_instance_redirect"><%= translate(locale, "Automaticatic instance redirection (fallback to redirect.invidious.io): ") %></label>
+ <input name="automatic_instance_redirect" id="automatic_instance_redirect" type="checkbox" <% if preferences.automatic_instance_redirect %>checked<% end %>>
+ </div>
+
<% if env.get? "user" %>
<legend><%= translate(locale, "Subscription preferences") %></legend>
diff --git a/src/invidious/views/search.ecr b/src/invidious/views/search.ecr
index fefc9fbb..15389dce 100644
--- a/src/invidious/views/search.ecr
+++ b/src/invidious/views/search.ecr
@@ -2,94 +2,105 @@
<title><%= search_query.not_nil!.size > 30 ? HTML.escape(query.not_nil![0,30].rstrip(".") + "...") : HTML.escape(query.not_nil!) %> - Invidious</title>
<% end %>
-<details id="filters">
- <summary>
- <h3 style="display:inline"> <%= translate(locale, "filter") %> </h3>
- </summary>
- <div id="filters" class="pure-g h-box">
- <div class="pure-u-1-3 pure-u-md-1-5">
- <b><%= translate(locale, "date") %></b>
- <hr/>
- <% ["hour", "today", "week", "month", "year"].each do |date| %>
- <div class="pure-u-1 pure-md-1-5">
- <% if operator_hash.fetch("date", "all") == date %>
- <b><%= translate(locale, date) %></b>
- <% else %>
- <a href="/search?q=<%= HTML.escape(query.not_nil!.gsub(/ ?date:[a-z]+/, "") + " date:" + date) %>&page=<%= page %>">
- <%= translate(locale, date) %>
- </a>
- <% end %>
- </div>
- <% end %>
+<!-- Search redirection and filtering UI -->
+<% if count == 0 %>
+ <h3 style="text-align: center">
+ <a href="/redirect?referer=<%= env.get?("current_page") %>"><%= translate(locale, "Broken? Try another Invidious Instance!") %></a>
+ </h3>
+<% else %>
+ <details id="filters">
+ <summary>
+ <h3 style="display:inline"> <%= translate(locale, "filter") %> </h3>
+ </summary>
+ <div id="filters" class="pure-g h-box">
+ <div class="pure-u-1-3 pure-u-md-1-5">
+ <b><%= translate(locale, "date") %></b>
+ <hr/>
+ <% ["hour", "today", "week", "month", "year"].each do |date| %>
+ <div class="pure-u-1 pure-md-1-5">
+ <% if operator_hash.fetch("date", "all") == date %>
+ <b><%= translate(locale, date) %></b>
+ <% else %>
+ <a href="/search?q=<%= HTML.escape(query.not_nil!.gsub(/ ?date:[a-z]+/, "") + " date:" + date) %>&page=<%= page %>">
+ <%= translate(locale, date) %>
+ </a>
+ <% end %>
+ </div>
+ <% end %>
+ </div>
+ <div class="pure-u-1-3 pure-u-md-1-5">
+ <b><%= translate(locale, "content_type") %></b>
+ <hr/>
+ <% ["video", "channel", "playlist", "movie", "show"].each do |content_type| %>
+ <div class="pure-u-1 pure-md-1-5">
+ <% if operator_hash.fetch("content_type", "all") == content_type %>
+ <b><%= translate(locale, content_type) %></b>
+ <% else %>
+ <a href="/search?q=<%= HTML.escape(query.not_nil!.gsub(/ ?content_type:[a-z]+/, "") + " content_type:" + content_type) %>&page=<%= page %>">
+ <%= translate(locale, content_type) %>
+ </a>
+ <% end %>
+ </div>
+ <% end %>
+ </div>
+ <div class="pure-u-1-3 pure-u-md-1-5">
+ <b><%= translate(locale, "duration") %></b>
+ <hr/>
+ <% ["short", "long"].each do |duration| %>
+ <div class="pure-u-1 pure-md-1-5">
+ <% if operator_hash.fetch("duration", "all") == duration %>
+ <b><%= translate(locale, duration) %></b>
+ <% else %>
+ <a href="/search?q=<%= HTML.escape(query.not_nil!.gsub(/ ?duration:[a-z]+/, "") + " duration:" + duration) %>&page=<%= page %>">
+ <%= translate(locale, duration) %>
+ </a>
+ <% end %>
+ </div>
+ <% end %>
+ </div>
+ <div class="pure-u-1-3 pure-u-md-1-5">
+ <b><%= translate(locale, "features") %></b>
+ <hr/>
+ <% ["hd", "subtitles", "creative_commons", "3d", "live", "purchased", "4k", "360", "location", "hdr"].each do |feature| %>
+ <div class="pure-u-1 pure-md-1-5">
+ <% if operator_hash.fetch("features", "all").includes?(feature) %>
+ <b><%= translate(locale, feature) %></b>
+ <% elsif operator_hash.has_key?("features") %>
+ <a href="/search?q=<%= HTML.escape(query.not_nil!.gsub(/features:/, "features:" + feature + ",")) %>&page=<%= page %>">
+ <%= translate(locale, feature) %>
+ </a>
+ <% else %>
+ <a href="/search?q=<%= HTML.escape(query.not_nil! + " features:" + feature) %>&page=<%= page %>">
+ <%= translate(locale, feature) %>
+ </a>
+ <% end %>
+ </div>
+ <% end %>
+ </div>
+ <div class="pure-u-1-3 pure-u-md-1-5">
+ <b><%= translate(locale, "sort") %></b>
+ <hr/>
+ <% ["relevance", "rating", "date", "views"].each do |sort| %>
+ <div class="pure-u-1 pure-md-1-5">
+ <% if operator_hash.fetch("sort", "relevance") == sort %>
+ <b><%= translate(locale, sort) %></b>
+ <% else %>
+ <a href="/search?q=<%= HTML.escape(query.not_nil!.gsub(/ ?sort:[a-z]+/, "") + " sort:" + sort) %>&page=<%= page %>">
+ <%= translate(locale, sort) %>
+ </a>
+ <% end %>
+ </div>
+ <% end %>
+ </div>
</div>
- <div class="pure-u-1-3 pure-u-md-1-5">
- <b><%= translate(locale, "content_type") %></b>
- <hr/>
- <% ["video", "channel", "playlist", "movie", "show"].each do |content_type| %>
- <div class="pure-u-1 pure-md-1-5">
- <% if operator_hash.fetch("content_type", "all") == content_type %>
- <b><%= translate(locale, content_type) %></b>
- <% else %>
- <a href="/search?q=<%= HTML.escape(query.not_nil!.gsub(/ ?content_type:[a-z]+/, "") + " content_type:" + content_type) %>&page=<%= page %>">
- <%= translate(locale, content_type) %>
- </a>
- <% end %>
- </div>
- <% end %>
- </div>
- <div class="pure-u-1-3 pure-u-md-1-5">
- <b><%= translate(locale, "duration") %></b>
- <hr/>
- <% ["short", "long"].each do |duration| %>
- <div class="pure-u-1 pure-md-1-5">
- <% if operator_hash.fetch("duration", "all") == duration %>
- <b><%= translate(locale, duration) %></b>
- <% else %>
- <a href="/search?q=<%= HTML.escape(query.not_nil!.gsub(/ ?duration:[a-z]+/, "") + " duration:" + duration) %>&page=<%= page %>">
- <%= translate(locale, duration) %>
- </a>
- <% end %>
- </div>
- <% end %>
- </div>
- <div class="pure-u-1-3 pure-u-md-1-5">
- <b><%= translate(locale, "features") %></b>
- <hr/>
- <% ["hd", "subtitles", "creative_commons", "3d", "live", "purchased", "4k", "360", "location", "hdr"].each do |feature| %>
- <div class="pure-u-1 pure-md-1-5">
- <% if operator_hash.fetch("features", "all").includes?(feature) %>
- <b><%= translate(locale, feature) %></b>
- <% elsif operator_hash.has_key?("features") %>
- <a href="/search?q=<%= HTML.escape(query.not_nil!.gsub(/features:/, "features:" + feature + ",")) %>&page=<%= page %>">
- <%= translate(locale, feature) %>
- </a>
- <% else %>
- <a href="/search?q=<%= HTML.escape(query.not_nil! + " features:" + feature) %>&page=<%= page %>">
- <%= translate(locale, feature) %>
- </a>
- <% end %>
- </div>
- <% end %>
- </div>
- <div class="pure-u-1-3 pure-u-md-1-5">
- <b><%= translate(locale, "sort") %></b>
- <hr/>
- <% ["relevance", "rating", "date", "views"].each do |sort| %>
- <div class="pure-u-1 pure-md-1-5">
- <% if operator_hash.fetch("sort", "relevance") == sort %>
- <b><%= translate(locale, sort) %></b>
- <% else %>
- <a href="/search?q=<%= HTML.escape(query.not_nil!.gsub(/ ?sort:[a-z]+/, "") + " sort:" + sort) %>&page=<%= page %>">
- <%= translate(locale, sort) %>
- </a>
- <% end %>
- </div>
- <% end %>
- </div>
- </div>
-</details>
+ </details>
+<% end %>
-<hr/>
+<% if count == 0 %>
+ <hr style="margin: 0;"/>
+<% else %>
+ <hr/>
+<% end %>
<div class="pure-g h-box v-box">
<div class="pure-u-1 pure-u-lg-1-5">
diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr
index baffa08b..91e03725 100644
--- a/src/invidious/views/watch.ecr
+++ b/src/invidious/views/watch.ecr
@@ -109,6 +109,9 @@ we're going to need to do it here in order to allow for translations.
<a href="https://www.youtube.com/watch?v=<%= video.id %>"><%= translate(locale, "Watch on YouTube") %></a>
(<a href="https://www.youtube.com/embed/<%= video.id %>"><%= translate(locale, "Embed") %></a>)
</span>
+ <p id="watch-on-another-invidious-instance">
+ <a href="/redirect?referer=<%= env.get?("current_page") %>"><%= translate(locale, "Switch Invidious Instance") %></a>
+ </p>
<p id="embed-link">
<a href="<%= embed_link %>"><%= translate(locale, "Embed Link") %></a>
</p>