diff --git a/locales/en-US.json b/locales/en-US.json index 3987f796e..8801cb0aa 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -496,5 +496,6 @@ "toggle_theme": "Toggle Theme", "carousel_slide": "Slide {{current}} of {{total}}", "carousel_skip": "Skip the Carousel", - "carousel_go_to": "Go to slide `x`" + "carousel_go_to": "Go to slide `x`", + "confirm_dialog_external_link": "You are leaving Invidious. Continue to external link?" } diff --git a/src/invidious/channels/about.cr b/src/invidious/channels/about.cr index 139095279..a64953831 100644 --- a/src/invidious/channels/about.cr +++ b/src/invidious/channels/about.cr @@ -159,6 +159,9 @@ def get_about_info(ucid, locale) : AboutChannel end end + # use comments link_utils to replace external links with the confirmation page + description_html = Invidious::Comments.replace_external_links(description_html) + sub_count = 0 if (metadata_rows = initdata.dig?("header", "pageHeaderRenderer", "content", "pageHeaderViewModel", "metadata", "contentMetadataViewModel", "metadataRows").try &.as_a) diff --git a/src/invidious/comments/links_util.cr b/src/invidious/comments/links_util.cr index f89b86d32..a28db7404 100644 --- a/src/invidious/comments/links_util.cr +++ b/src/invidious/comments/links_util.cr @@ -73,4 +73,30 @@ module Invidious::Comments return html.to_xml(options: XML::SaveOptions::NO_DECL) end + + def replace_external_links(html) + # Check if the document is empty + # Prevents edge-case bug with Reddit comments, see issue #3115 + if html.nil? || html.empty? + return html + end + + html = XML.parse_html(html) + + html.xpath_nodes(%q(//a)).each do |anchor| + url = URI.parse(anchor["href"]) + + if !url.host.nil? && !url.host.not_nil!.ends_with?("youtube.com") && !url.host.not_nil!.ends_with?("youtu.be") + confirm_leave = "/confirm_leave?link=#{URI.encode_path(url.to_s)}" + anchor["href"] = confirm_leave + end + end + + html = html.xpath_node(%q(//body)).not_nil! + if node = html.xpath_node(%q(./p)) + html = node + end + + return html.to_xml(options: XML::SaveOptions::NO_DECL) + end end diff --git a/src/invidious/comments/youtube.cr b/src/invidious/comments/youtube.cr index 0716fcde6..148f6cbee 100644 --- a/src/invidious/comments/youtube.cr +++ b/src/invidious/comments/youtube.cr @@ -303,6 +303,7 @@ module Invidious::Comments if format == "html" response = JSON.parse(response) content_html = Frontend::Comments.template_youtube(response, locale, thin_mode) + content_html = Comments.replace_external_links(content_html) response = JSON.build do |json| json.object do json.field "contentHtml", content_html diff --git a/src/invidious/frontend/comments_youtube.cr b/src/invidious/frontend/comments_youtube.cr index a0e1d783d..5e9faecee 100644 --- a/src/invidious/frontend/comments_youtube.cr +++ b/src/invidious/frontend/comments_youtube.cr @@ -1,3 +1,5 @@ +require "uri" + module Invidious::Frontend::Comments extend self @@ -148,13 +150,17 @@ module Invidious::Frontend::Comments END_HTML if comments["videoId"]? + permalink = "https://www.youtube.com/watch?v=#{comments["videoId"]}&lc=#{child["commentId"]}" + permalink_confirm = "/confirm_leave?link=#{URI.encode_path(permalink)}" html << <<-END_HTML - [YT] + [YT] | END_HTML elsif comments["authorId"]? + permalink = "https://www.youtube.com/channel/#{comments["authorId"]}/community?lb=#{child["commentId"]}" + permalink_confirm = "/confirm_leave?link=#{URI.encode_path(permalink)}" html << <<-END_HTML - [YT] + [YT] | END_HTML end diff --git a/src/invidious/helpers/errors.cr b/src/invidious/helpers/errors.cr index b2df682d5..c8e360dcf 100644 --- a/src/invidious/helpers/errors.cr +++ b/src/invidious/helpers/errors.cr @@ -180,6 +180,9 @@ def error_redirect_helper(env : HTTP::Server::Context) go_to_youtube = translate(locale, "next_steps_error_message_go_to_youtube") switch_instance = translate(locale, "Switch Invidious Instance") + youtube_link = "https://youtube.com#{env.request.resource}" + youtube_link_confirm = "/confirm_leave?link=#{URI.encode_path(youtube_link)}" + return <<-END_HTML

#{next_steps_text}

END_HTML diff --git a/src/invidious/routes/misc.cr b/src/invidious/routes/misc.cr index d6bd95711..ff863f58d 100644 --- a/src/invidious/routes/misc.cr +++ b/src/invidious/routes/misc.cr @@ -43,4 +43,17 @@ module Invidious::Routes::Misc instance_url = fetch_random_instance env.redirect "https://#{instance_url}#{referer}" end + + def self.confirm_leave(env) + locale = env.get("preferences").as(Preferences).locale + referer = get_referer(env) + + if env.params.query["link"]? && !env.params.query["link"].empty? + link = HTML.escape(env.params.query["link"].to_s) + + templated "confirm_leave" + else + env.redirect "#{referer}" + end + end end diff --git a/src/invidious/routing.cr b/src/invidious/routing.cr index ba05da193..1504e7eab 100644 --- a/src/invidious/routing.cr +++ b/src/invidious/routing.cr @@ -21,6 +21,7 @@ module Invidious::Routing get "/privacy", Routes::Misc, :privacy get "/licenses", Routes::Misc, :licenses get "/redirect", Routes::Misc, :cross_instance_redirect + get "/confirm_leave", Routes::Misc, :confirm_leave self.register_channel_routes self.register_watch_routes diff --git a/src/invidious/videos/parser.cr b/src/invidious/videos/parser.cr index 95fa3d799..cd11995fe 100644 --- a/src/invidious/videos/parser.cr +++ b/src/invidious/videos/parser.cr @@ -316,6 +316,9 @@ def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any description_html = parse_description(video_secondary_renderer.try &.dig?("attributedDescription"), video_id) + # use comments link_utils to replace external links with the confirmation page + description_html = Invidious::Comments.replace_external_links(description_html) + # Video metadata metadata = video_secondary_renderer diff --git a/src/invidious/views/components/channel_info.ecr b/src/invidious/views/components/channel_info.ecr index f4164f31b..0d9820c5b 100644 --- a/src/invidious/views/components/channel_info.ecr +++ b/src/invidious/views/components/channel_info.ecr @@ -37,7 +37,10 @@
- <%= translate(locale, "View channel on YouTube") %> + <%- + confirm_view_yt = "/confirm_leave?link=#{URI.encode_path(youtube_url)}" + -%> + <%= translate(locale, "View channel on YouTube") %>
<%= translate(locale, "Switch Invidious Instance") %> diff --git a/src/invidious/views/components/video-context-buttons.ecr b/src/invidious/views/components/video-context-buttons.ecr index 22458a030..a8078da8e 100644 --- a/src/invidious/views/components/video-context-buttons.ecr +++ b/src/invidious/views/components/video-context-buttons.ecr @@ -1,6 +1,10 @@
- " rel="noreferrer noopener" href="https://www.youtube.com/watch<%=endpoint_params%>"> + <%- + watch_yt = "https://www.youtube.com/watch#{endpoint_params}" + confirm_watch_yt = "/confirm_leave?link=#{URI.encode_path(watch_yt)}" + -%> + " href="<%= confirm_watch_yt %>"> " href="/watch<%=endpoint_params%>&listen=1"> diff --git a/src/invidious/views/confirm_leave.ecr b/src/invidious/views/confirm_leave.ecr new file mode 100644 index 000000000..122c64bdb --- /dev/null +++ b/src/invidious/views/confirm_leave.ecr @@ -0,0 +1,22 @@ +<% content_for "header" do %> +<%= translate(locale, "Leave") %> - Invidious +<% end %> + + diff --git a/src/invidious/views/playlist.ecr b/src/invidious/views/playlist.ecr index c27ddba60..031a03aed 100644 --- a/src/invidious/views/playlist.ecr +++ b/src/invidious/views/playlist.ecr @@ -83,7 +83,11 @@ <% if !playlist.is_a? InvidiousPlaylist %>
- + <%- + view_yt = "https://www.youtube.com/playlist?list=#{playlist.id}" + confirm_view_yt = "/confirm_leave?link=#{URI.encode_path(view_yt)}" + -%> + <%= translate(locale, "View playlist on YouTube") %> | diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr index 45c58a162..1753a3316 100644 --- a/src/invidious/views/watch.ecr +++ b/src/invidious/views/watch.ecr @@ -122,9 +122,12 @@ we're going to need to do it here in order to allow for translations. link_yt_watch = IV::HttpServer::Utils.add_params_to_url(link_yt_watch, link_yt_param) link_yt_embed = IV::HttpServer::Utils.add_params_to_url(link_yt_embed, link_yt_param) end + + confirm_yt_watch = "/confirm_leave?link=#{URI.encode_path(link_yt_watch.to_s)}" + confirm_yt_embed = "/confirm_leave?link=#{URI.encode_path(link_yt_embed.to_s)}" -%> - <%= translate(locale, "videoinfo_watch_on_youTube") %> - (<%= translate(locale, "videoinfo_youTube_embed_link") %>) + <%= translate(locale, "videoinfo_watch_on_youTube") %> + (<%= translate(locale, "videoinfo_youTube_embed_link") %>)