From 855202e40e09af1cb5fb372d4a2d05a61b3a9bb2 Mon Sep 17 00:00:00 2001 From: Gavin Johnson Date: Mon, 16 Jan 2023 15:40:38 -0800 Subject: [PATCH 001/184] added youtube playlist import; initial commit Signed-off-by: Gavin Johnson --- locales/en-US.json | 1 + src/invidious/user/imports.cr | 85 +++++++++++++++++++++++ src/invidious/views/user/data_control.ecr | 5 ++ 3 files changed, 91 insertions(+) diff --git a/locales/en-US.json b/locales/en-US.json index 12955665..c30a90db 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -33,6 +33,7 @@ "Import": "Import", "Import Invidious data": "Import Invidious JSON data", "Import YouTube subscriptions": "Import YouTube/OPML subscriptions", + "Import YouTube playlist": "Import YouTube playlist (.csv)", "Import FreeTube subscriptions (.db)": "Import FreeTube subscriptions (.db)", "Import NewPipe subscriptions (.json)": "Import NewPipe subscriptions (.json)", "Import NewPipe data (.zip)": "Import NewPipe data (.zip)", diff --git a/src/invidious/user/imports.cr b/src/invidious/user/imports.cr index 20ae0d47..870d083e 100644 --- a/src/invidious/user/imports.cr +++ b/src/invidious/user/imports.cr @@ -30,6 +30,75 @@ struct Invidious::User return subscriptions end + # Parse a youtube CSV playlist file and create the playlist + #NEW - Done + def parse_playlist_export_csv(user : User, csv_content : String) + rows = CSV.new(csv_content, headers: false) + if rows.size >= 2 + title = rows[1][4]?.try &.as_s?.try + descripton = rows[1][5]?.try &.as_s?.try + visibility = rows[1][6]?.try &.as_s?.try + + if visibility.compare("Public", case_insensitive: true) == 0 + privacy = PlaylistPrivacy:Public + else + privacy = PlaylistPrivacy:Private + end + + if title && privacy && user + playlist = create_playlist(title, privacy, user) + end + + if playlist && descripton + Invidious::Database::Playlists.update_description(playlist.id, description) + end + end + + return playlist + end + + # Parse a youtube CSV playlist file and add videos from it to a playlist + #NEW - done + def parse_playlist_videos_export_csv(playlist : Playlist, csv_content : String) + rows = CSV.new(csv_content, headers: false) + if rows.size >= 5 + offset = env.params.query["index"]?.try &.to_i? || 0 + row_counter = 0 + rows.each do |row| + if row_counter >= 4 + video_id = row[0]?.try &.as_s?.try + end + row_counter += 1 + next if !video_id + + begin + video = get_video(video_id) + rescue ex + next + end + + playlist_video = PlaylistVideo.new({ + title: video.title, + id: video.id, + author: video.author, + ucid: video.ucid, + length_seconds: video.length_seconds, + published: video.published, + plid: playlist.id, + live_now: video.live_now, + index: Random::Secure.rand(0_i64..Int64::MAX), + }) + + Invidious::Database::PlaylistVideos.insert(playlist_video) + Invidious::Database::Playlists.update_video_added(playlist.id, playlist_video.index) + end + + videos = get_playlist_videos(playlist, offset: offset) + end + + return videos + end + # ------------------- # Invidious # ------------------- @@ -149,6 +218,22 @@ struct Invidious::User return true end + # Import playlist from Youtube + # Returns success status + #NEW + def from_youtube_pl(user : User, body : String, filename : String, type : String) : Bool + extension = filename.split(".").last + + if extension == "csv" || type == "text/csv" + playlist = parse_playlist_export_csv(user, body) + playlist = parse_playlist_videos_export_csv(playlist, body) + else + return false + end + + return true + end + # ------------------- # Freetube # ------------------- diff --git a/src/invidious/views/user/data_control.ecr b/src/invidious/views/user/data_control.ecr index a451159f..0f8e8dae 100644 --- a/src/invidious/views/user/data_control.ecr +++ b/src/invidious/views/user/data_control.ecr @@ -21,6 +21,11 @@ +
+ + +
+
From 96344f28b4b842e915325aef64bc93fc9fc55387 Mon Sep 17 00:00:00 2001 From: Gavin Johnson Date: Sat, 28 Jan 2023 09:26:16 -0800 Subject: [PATCH 002/184] added youtube playlist import functionality. fixes issue #2114 Signed-off-by: Gavin Johnson --- locales/en-US.json | 2 +- src/invidious/routes/preferences.cr | 10 ++ src/invidious/user/imports.cr | 129 +++++++++++----------- src/invidious/views/feeds/playlists.ecr | 13 ++- src/invidious/views/user/data_control.ecr | 4 +- 5 files changed, 86 insertions(+), 72 deletions(-) diff --git a/locales/en-US.json b/locales/en-US.json index c30a90db..8f1ec58d 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -33,7 +33,7 @@ "Import": "Import", "Import Invidious data": "Import Invidious JSON data", "Import YouTube subscriptions": "Import YouTube/OPML subscriptions", - "Import YouTube playlist": "Import YouTube playlist (.csv)", + "Import YouTube playlist (.csv)": "Import YouTube playlist (.csv)", "Import FreeTube subscriptions (.db)": "Import FreeTube subscriptions (.db)", "Import NewPipe subscriptions (.json)": "Import NewPipe subscriptions (.json)", "Import NewPipe data (.zip)": "Import NewPipe data (.zip)", diff --git a/src/invidious/routes/preferences.cr b/src/invidious/routes/preferences.cr index 570cba69..adac0068 100644 --- a/src/invidious/routes/preferences.cr +++ b/src/invidious/routes/preferences.cr @@ -310,6 +310,16 @@ module Invidious::Routes::PreferencesRoute response: error_template(415, "Invalid subscription file uploaded") ) end + # Gavin Johnson (thtmnisamnstr), 20230127: Call the Youtube playlist import function + when "import_youtube_pl" + filename = part.filename || "" + success = Invidious::User::Import.from_youtube_pl(user, body, filename, type) + + if !success + haltf(env, status_code: 415, + response: error_template(415, "Invalid playlist file uploaded") + ) + end when "import_freetube" Invidious::User::Import.from_freetube(user, body) when "import_newpipe_subscriptions" diff --git a/src/invidious/user/imports.cr b/src/invidious/user/imports.cr index 870d083e..fa1bbe7f 100644 --- a/src/invidious/user/imports.cr +++ b/src/invidious/user/imports.cr @@ -30,75 +30,72 @@ struct Invidious::User return subscriptions end - # Parse a youtube CSV playlist file and create the playlist - #NEW - Done + # Gavin Johnson (thtmnisamnstr), 20230127: Parse a youtube CSV playlist file and create the playlist def parse_playlist_export_csv(user : User, csv_content : String) - rows = CSV.new(csv_content, headers: false) - if rows.size >= 2 - title = rows[1][4]?.try &.as_s?.try - descripton = rows[1][5]?.try &.as_s?.try - visibility = rows[1][6]?.try &.as_s?.try - - if visibility.compare("Public", case_insensitive: true) == 0 - privacy = PlaylistPrivacy:Public - else - privacy = PlaylistPrivacy:Private - end + rows = CSV.new(csv_content, headers: true) + row_counter = 0 + playlist = uninitialized InvidiousPlaylist + title = uninitialized String + description = uninitialized String + visibility = uninitialized String + rows.each do |row| + if row_counter == 0 + title = row[4] + description = row[5] + visibility = row[6] - if title && privacy && user - playlist = create_playlist(title, privacy, user) - end + if visibility.compare("Public", case_insensitive: true) == 0 + privacy = PlaylistPrivacy::Public + else + privacy = PlaylistPrivacy::Private + end + + if title && privacy && user + playlist = create_playlist(title, privacy, user) + end + + if playlist && description + Invidious::Database::Playlists.update_description(playlist.id, description) + end - if playlist && descripton - Invidious::Database::Playlists.update_description(playlist.id, description) + row_counter += 1 + end + if row_counter > 0 && row_counter < 3 + row_counter += 1 + end + if row_counter >= 3 + if playlist + video_id = row[0] + row_counter += 1 + next if !video_id + + begin + video = get_video(video_id) + rescue ex + next + end + + playlist_video = PlaylistVideo.new({ + title: video.title, + id: video.id, + author: video.author, + ucid: video.ucid, + length_seconds: video.length_seconds, + published: video.published, + plid: playlist.id, + live_now: video.live_now, + index: Random::Secure.rand(0_i64..Int64::MAX), + }) + + Invidious::Database::PlaylistVideos.insert(playlist_video) + Invidious::Database::Playlists.update_video_added(playlist.id, playlist_video.index) + end end end return playlist end - # Parse a youtube CSV playlist file and add videos from it to a playlist - #NEW - done - def parse_playlist_videos_export_csv(playlist : Playlist, csv_content : String) - rows = CSV.new(csv_content, headers: false) - if rows.size >= 5 - offset = env.params.query["index"]?.try &.to_i? || 0 - row_counter = 0 - rows.each do |row| - if row_counter >= 4 - video_id = row[0]?.try &.as_s?.try - end - row_counter += 1 - next if !video_id - - begin - video = get_video(video_id) - rescue ex - next - end - - playlist_video = PlaylistVideo.new({ - title: video.title, - id: video.id, - author: video.author, - ucid: video.ucid, - length_seconds: video.length_seconds, - published: video.published, - plid: playlist.id, - live_now: video.live_now, - index: Random::Secure.rand(0_i64..Int64::MAX), - }) - - Invidious::Database::PlaylistVideos.insert(playlist_video) - Invidious::Database::Playlists.update_video_added(playlist.id, playlist_video.index) - end - - videos = get_playlist_videos(playlist, offset: offset) - end - - return videos - end - # ------------------- # Invidious # ------------------- @@ -218,20 +215,20 @@ struct Invidious::User return true end - # Import playlist from Youtube - # Returns success status - #NEW + # Gavin Johnson (thtmnisamnstr), 20230127: Import playlist from Youtube export. Returns success status. def from_youtube_pl(user : User, body : String, filename : String, type : String) : Bool extension = filename.split(".").last if extension == "csv" || type == "text/csv" playlist = parse_playlist_export_csv(user, body) - playlist = parse_playlist_videos_export_csv(playlist, body) + if playlist + return true + else + return false + end else return false end - - return true end # ------------------- diff --git a/src/invidious/views/feeds/playlists.ecr b/src/invidious/views/feeds/playlists.ecr index a59344c4..05a48ce3 100644 --- a/src/invidious/views/feeds/playlists.ecr +++ b/src/invidious/views/feeds/playlists.ecr @@ -5,14 +5,21 @@ <%= rendered "components/feed_menu" %>
-
+

<%= translate(locale, "user_created_playlists", %(#{items_created.size})) %>

-
diff --git a/src/invidious/views/user/data_control.ecr b/src/invidious/views/user/data_control.ecr index 0f8e8dae..27654b40 100644 --- a/src/invidious/views/user/data_control.ecr +++ b/src/invidious/views/user/data_control.ecr @@ -8,7 +8,7 @@ <%= translate(locale, "Import") %>
- +
@@ -22,7 +22,7 @@
- +
From 5c7bda66ae90f3aef559a0269e56156a359814a3 Mon Sep 17 00:00:00 2001 From: Gavin Johnson Date: Sat, 28 Jan 2023 09:55:36 -0800 Subject: [PATCH 003/184] removed comments Signed-off-by: Gavin Johnson --- src/invidious/user/imports.cr | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/invidious/user/imports.cr b/src/invidious/user/imports.cr index fa1bbe7f..77009538 100644 --- a/src/invidious/user/imports.cr +++ b/src/invidious/user/imports.cr @@ -30,7 +30,6 @@ struct Invidious::User return subscriptions end - # Gavin Johnson (thtmnisamnstr), 20230127: Parse a youtube CSV playlist file and create the playlist def parse_playlist_export_csv(user : User, csv_content : String) rows = CSV.new(csv_content, headers: true) row_counter = 0 @@ -215,7 +214,6 @@ struct Invidious::User return true end - # Gavin Johnson (thtmnisamnstr), 20230127: Import playlist from Youtube export. Returns success status. def from_youtube_pl(user : User, body : String, filename : String, type : String) : Bool extension = filename.split(".").last From 72d0c9e40971b5460048f8906914fbef55289236 Mon Sep 17 00:00:00 2001 From: Gavin Johnson Date: Sat, 28 Jan 2023 09:57:28 -0800 Subject: [PATCH 004/184] removed comments Signed-off-by: Gavin Johnson --- src/invidious/routes/preferences.cr | 1 - 1 file changed, 1 deletion(-) diff --git a/src/invidious/routes/preferences.cr b/src/invidious/routes/preferences.cr index adac0068..abe0f34e 100644 --- a/src/invidious/routes/preferences.cr +++ b/src/invidious/routes/preferences.cr @@ -310,7 +310,6 @@ module Invidious::Routes::PreferencesRoute response: error_template(415, "Invalid subscription file uploaded") ) end - # Gavin Johnson (thtmnisamnstr), 20230127: Call the Youtube playlist import function when "import_youtube_pl" filename = part.filename || "" success = Invidious::User::Import.from_youtube_pl(user, body, filename, type) From 6f01d6eacf0719e8569a338e5a44615f159c5120 Mon Sep 17 00:00:00 2001 From: thtmnisamnstr Date: Fri, 10 Feb 2023 12:00:02 -0800 Subject: [PATCH 005/184] ran crystal tool format. it should fix some CI issues Signed-off-by: thtmnisamnstr --- src/invidious/user/imports.cr | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/invidious/user/imports.cr b/src/invidious/user/imports.cr index 77009538..aa87ca99 100644 --- a/src/invidious/user/imports.cr +++ b/src/invidious/user/imports.cr @@ -48,11 +48,11 @@ struct Invidious::User else privacy = PlaylistPrivacy::Private end - + if title && privacy && user - playlist = create_playlist(title, privacy, user) + playlist = create_playlist(title, privacy, user) end - + if playlist && description Invidious::Database::Playlists.update_description(playlist.id, description) end From 8445d3ae120c52eba531183caa1fa63d5701f322 Mon Sep 17 00:00:00 2001 From: Brahim Hadriche Date: Sun, 19 Feb 2023 19:01:28 -0500 Subject: [PATCH 006/184] Fix watch history order --- src/invidious/database/users.cr | 1 + src/invidious/routes/watch.cr | 6 ++---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/invidious/database/users.cr b/src/invidious/database/users.cr index 0a4a4fd8..f8422874 100644 --- a/src/invidious/database/users.cr +++ b/src/invidious/database/users.cr @@ -50,6 +50,7 @@ module Invidious::Database::Users end def mark_watched(user : User, vid : String) + mark_unwatched(user, vid) request = <<-SQL UPDATE users SET watched = array_append(watched, $1) diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr index 5d3845c3..813cb0f4 100644 --- a/src/invidious/routes/watch.cr +++ b/src/invidious/routes/watch.cr @@ -76,7 +76,7 @@ module Invidious::Routes::Watch end env.params.query.delete_all("iv_load_policy") - if watched && preferences.watch_history && !watched.includes? id + if watched && preferences.watch_history Invidious::Database::Users.mark_watched(user.as(User), id) end @@ -259,9 +259,7 @@ module Invidious::Routes::Watch case action when "action_mark_watched" - if !user.watched.includes? id - Invidious::Database::Users.mark_watched(user, id) - end + Invidious::Database::Users.mark_watched(user, id) when "action_mark_unwatched" Invidious::Database::Users.mark_unwatched(user, id) else From 20289a4d014d36c9a7bd50d8b1549bf36f78eb59 Mon Sep 17 00:00:00 2001 From: Brahim Hadriche Date: Mon, 20 Feb 2023 14:56:38 -0500 Subject: [PATCH 007/184] Fix order for import --- src/invidious/user/imports.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/invidious/user/imports.cr b/src/invidious/user/imports.cr index 20ae0d47..aa947456 100644 --- a/src/invidious/user/imports.cr +++ b/src/invidious/user/imports.cr @@ -48,7 +48,7 @@ struct Invidious::User if data["watch_history"]? user.watched += data["watch_history"].as_a.map(&.as_s) - user.watched.uniq! + user.watched.reverse!.uniq!.reverse! Invidious::Database::Users.update_watch_history(user) end From b3eea6ab3ebdb1618916b02041b22e0e238e8a7d Mon Sep 17 00:00:00 2001 From: thtmnisamnstr Date: Thu, 23 Feb 2023 15:55:38 -0800 Subject: [PATCH 008/184] improved import algorithm, fixed a referer issue from the playlists page after deleting a playlist Signed-off-by: thtmnisamnstr --- src/invidious/user/imports.cr | 55 +++++++++++++------------ src/invidious/views/feeds/playlists.ecr | 4 +- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/src/invidious/user/imports.cr b/src/invidious/user/imports.cr index aa87ca99..7fddcc4c 100644 --- a/src/invidious/user/imports.cr +++ b/src/invidious/user/imports.cr @@ -30,43 +30,44 @@ struct Invidious::User return subscriptions end - def parse_playlist_export_csv(user : User, csv_content : String) - rows = CSV.new(csv_content, headers: true) - row_counter = 0 + def parse_playlist_export_csv(user : User, raw_input : String) playlist = uninitialized InvidiousPlaylist title = uninitialized String description = uninitialized String visibility = uninitialized String - rows.each do |row| - if row_counter == 0 - title = row[4] - description = row[5] - visibility = row[6] + privacy = uninitialized PlaylistPrivacy - if visibility.compare("Public", case_insensitive: true) == 0 - privacy = PlaylistPrivacy::Public - else - privacy = PlaylistPrivacy::Private - end + # Split the input into head and body content + raw_head, raw_body = raw_input.split("\n\n", limit: 2, remove_empty: true) - if title && privacy && user - playlist = create_playlist(title, privacy, user) - end + # Create the playlist from the head content + csv_head = CSV.new(raw_head, headers: true) + csv_head.next + title = csv_head[4] + description = csv_head[5] + visibility = csv_head[6] + + if visibility.compare("Public", case_insensitive: true) == 0 + privacy = PlaylistPrivacy::Public + else + privacy = PlaylistPrivacy::Private + end - if playlist && description - Invidious::Database::Playlists.update_description(playlist.id, description) - end + if title && privacy && user + playlist = create_playlist(title, privacy, user) + end - row_counter += 1 - end - if row_counter > 0 && row_counter < 3 - row_counter += 1 - end - if row_counter >= 3 + if playlist && description + Invidious::Database::Playlists.update_description(playlist.id, description) + end + + # Add each video to the playlist from the body content + CSV.each_row(raw_body) do |row| + if row.size >= 1 + video_id = row[0] if playlist - video_id = row[0] - row_counter += 1 next if !video_id + next if video_id == "Video Id" begin video = get_video(video_id) diff --git a/src/invidious/views/feeds/playlists.ecr b/src/invidious/views/feeds/playlists.ecr index 05a48ce3..43173355 100644 --- a/src/invidious/views/feeds/playlists.ecr +++ b/src/invidious/views/feeds/playlists.ecr @@ -10,12 +10,12 @@

- + "> <%= translate(locale, "Import/export") %>

From 025e7555420a88757aa8709419e8f09ba654854d Mon Sep 17 00:00:00 2001 From: Brahim Hadriche Date: Sat, 4 Mar 2023 19:14:28 -0500 Subject: [PATCH 009/184] Use single db call --- src/invidious/database/users.cr | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/invidious/database/users.cr b/src/invidious/database/users.cr index f8422874..d54e6a76 100644 --- a/src/invidious/database/users.cr +++ b/src/invidious/database/users.cr @@ -50,10 +50,9 @@ module Invidious::Database::Users end def mark_watched(user : User, vid : String) - mark_unwatched(user, vid) request = <<-SQL UPDATE users - SET watched = array_append(watched, $1) + SET watched = array_append(array_remove(watched, $1), $1) WHERE email = $2 SQL From 3341929060bb20c086974a282fd1e33029f685f3 Mon Sep 17 00:00:00 2001 From: thtmnisamnstr Date: Tue, 7 Mar 2023 15:46:36 -0800 Subject: [PATCH 010/184] removed unnecessary conditionals and uninitialized variable declarations Signed-off-by: thtmnisamnstr --- src/invidious/user/imports.cr | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/invidious/user/imports.cr b/src/invidious/user/imports.cr index 7fddcc4c..757f5b13 100644 --- a/src/invidious/user/imports.cr +++ b/src/invidious/user/imports.cr @@ -31,12 +31,6 @@ struct Invidious::User end def parse_playlist_export_csv(user : User, raw_input : String) - playlist = uninitialized InvidiousPlaylist - title = uninitialized String - description = uninitialized String - visibility = uninitialized String - privacy = uninitialized PlaylistPrivacy - # Split the input into head and body content raw_head, raw_body = raw_input.split("\n\n", limit: 2, remove_empty: true) @@ -53,13 +47,8 @@ struct Invidious::User privacy = PlaylistPrivacy::Private end - if title && privacy && user - playlist = create_playlist(title, privacy, user) - end - - if playlist && description - Invidious::Database::Playlists.update_description(playlist.id, description) - end + playlist = create_playlist(title, privacy, user) + Invidious::Database::Playlists.update_description(playlist.id, description) # Add each video to the playlist from the body content CSV.each_row(raw_body) do |row| From 49ddf8b6bdb98c7a9678cbec800c45350a54a786 Mon Sep 17 00:00:00 2001 From: techmetx11 Date: Thu, 23 Mar 2023 05:10:21 +0000 Subject: [PATCH 011/184] Added attributed description support --- src/invidious/videos/description.cr | 81 +++++++++++++++++++++++++++++ src/invidious/videos/parser.cr | 6 ++- 2 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 src/invidious/videos/description.cr diff --git a/src/invidious/videos/description.cr b/src/invidious/videos/description.cr new file mode 100644 index 00000000..d4c60a84 --- /dev/null +++ b/src/invidious/videos/description.cr @@ -0,0 +1,81 @@ +require "json" +require "uri" + +def parse_command(command : JSON::Any?, string : String) : String? + on_tap = command.dig?("onTap", "innertubeCommand") + + # 3rd party URL, extract original URL from YouTube tracking URL + if url_endpoint = on_tap.try &.["urlEndpoint"]? + youtube_url = URI.parse url_endpoint["url"].as_s + + original_url = youtube_url.query_params["q"]? + if original_url.nil? + return "" + else + return "#{original_url}" + end + # 1st party watch URL + elsif watch_endpoint = on_tap.try &.["watchEndpoint"]? + video_id = watch_endpoint["videoId"].as_s + time = watch_endpoint["startTimeSeconds"].as_i + + url = "/watch?v=#{video_id}&t=#{time}s" + + # if text is a timestamp, use the string instead + if /(?:\d{2}:){1,2}\d{2}/ =~ string + return "#{string}" + else + return "#{url}" + end + # hashtag/other browse URLs + elsif browse_endpoint = on_tap.try &.dig?("commandMetadata", "webCommandMetadata") + url = browse_endpoint["url"].try &.as_s + + # remove unnecessary character in a channel name + if browse_endpoint["webPageType"]?.try &.as_s == "WEB_PAGE_TYPE_CHANNEL" + name = string.match(/@[\w\d]+/) + if name.try &.[0]? + return "#{name.try &.[0]}" + end + end + + return "#{string}" + end + + return "(unknown YouTube desc command)" +end + +def parse_description(desc : JSON::Any?) : String? + if desc.nil? + return "" + end + + content = desc["content"].as_s + if content.empty? + return "" + end + + if commands = desc["commandRuns"]?.try &.as_a + description = String.build do |str| + index = 0 + commands.each do |command| + start_index = command["startIndex"].as_i + length = command["length"].as_i + + if start_index > 0 && start_index - index > 0 + str << content[index..(start_index - 1)] + index += start_index - index + end + + str << parse_command(command, content[start_index, length]) + index += length + end + if index < content.size + str << content[index..content.size] + end + end + return description + end + + return content +end diff --git a/src/invidious/videos/parser.cr b/src/invidious/videos/parser.cr index 608ae99d..3a342a95 100644 --- a/src/invidious/videos/parser.cr +++ b/src/invidious/videos/parser.cr @@ -284,8 +284,10 @@ def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any description = microformat.dig?("description", "simpleText").try &.as_s || "" short_description = player_response.dig?("videoDetails", "shortDescription") - description_html = video_secondary_renderer.try &.dig?("description", "runs") - .try &.as_a.try { |t| content_to_comment_html(t, video_id) } + # description_html = video_secondary_renderer.try &.dig?("description", "runs") + # .try &.as_a.try { |t| content_to_comment_html(t, video_id) } + + description_html = parse_description(video_secondary_renderer.try &.dig?("attributedDescription")) # Video metadata From 7755ed4ac8812377da04cff951324ab31d2e621c Mon Sep 17 00:00:00 2001 From: techmetx11 Date: Thu, 23 Mar 2023 20:12:54 +0000 Subject: [PATCH 012/184] Fix regexs --- src/invidious/videos/description.cr | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/invidious/videos/description.cr b/src/invidious/videos/description.cr index d4c60a84..3d25197b 100644 --- a/src/invidious/videos/description.cr +++ b/src/invidious/videos/description.cr @@ -21,8 +21,9 @@ def parse_command(command : JSON::Any?, string : String) : String? url = "/watch?v=#{video_id}&t=#{time}s" - # if text is a timestamp, use the string instead - if /(?:\d{2}:){1,2}\d{2}/ =~ string + # if string is a timestamp, use the string instead + # this is a lazy regex for validating timestamps + if /(?:\d{1,2}:){1,2}\d{2}/ =~ string return "#{string}" else return "#{url}" @@ -33,7 +34,7 @@ def parse_command(command : JSON::Any?, string : String) : String? # remove unnecessary character in a channel name if browse_endpoint["webPageType"]?.try &.as_s == "WEB_PAGE_TYPE_CHANNEL" - name = string.match(/@[\w\d]+/) + name = string.match(/@[\w\d.-]+/) if name.try &.[0]? return "#{name.try &.[0]}" end From a3da03bee91eab5c602882c4b43b959362ee441d Mon Sep 17 00:00:00 2001 From: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com> Date: Thu, 23 Mar 2023 18:10:53 -0400 Subject: [PATCH 013/184] improve accessibility --- assets/css/default.css | 29 ++++++++++++++----- assets/css/embed.css | 3 +- assets/js/_helpers.js | 8 +++-- assets/js/handlers.js | 2 +- assets/js/player.js | 2 +- src/invidious/comments.cr | 6 ++-- src/invidious/mixes.cr | 2 +- src/invidious/playlists.cr | 2 +- .../views/components/channel_info.ecr | 4 +-- src/invidious/views/components/item.ecr | 10 +++---- src/invidious/views/feeds/history.ecr | 2 +- src/invidious/views/watch.ecr | 4 +-- 12 files changed, 45 insertions(+), 29 deletions(-) diff --git a/assets/css/default.css b/assets/css/default.css index f8b1c9f7..65d03be1 100644 --- a/assets/css/default.css +++ b/assets/css/default.css @@ -119,13 +119,16 @@ body a.pure-button { button.pure-button-primary, body a.pure-button-primary, -.channel-owner:hover { +.channel-owner:hover, +.channel-owner:focus { background-color: #a0a0a0; color: rgba(35, 35, 35, 1); } button.pure-button-primary:hover, -body a.pure-button-primary:hover { +body a.pure-button-primary:hover, +button.pure-button-primary:focus, +body a.pure-button-primary:focus { background-color: rgba(0, 182, 240, 1); color: #fff; } @@ -227,6 +230,7 @@ div.watched-indicator { border-radius: 0; box-shadow: none; + appearance: none; -webkit-appearance: none; } @@ -365,11 +369,14 @@ span > select { .light-theme a:hover, .light-theme a:active, -.light-theme summary:hover { +.light-theme summary:hover, +.light-theme a:focus, +.light-theme summary:focus { color: #075A9E !important; } -.light-theme a.pure-button-primary:hover { +.light-theme a.pure-button-primary:hover, +.light-theme a.pure-button-primary:focus { color: #fff !important; } @@ -392,11 +399,14 @@ span > select { @media (prefers-color-scheme: light) { .no-theme a:hover, .no-theme a:active, - .no-theme summary:hover { + .no-theme summary:hover, + .no-theme a:focus, + .no-theme summary:focus { color: #075A9E !important; } - .no-theme a.pure-button-primary:hover { + .no-theme a.pure-button-primary:hover, + .no-theme a.pure-button-primary:focus { color: #fff !important; } @@ -423,7 +433,9 @@ span > select { .dark-theme a:hover, .dark-theme a:active, -.dark-theme summary:hover { +.dark-theme summary:hover, +.dark-theme a:focus, +.dark-theme summary:focus { color: rgb(0, 182, 240); } @@ -462,7 +474,8 @@ body.dark-theme { @media (prefers-color-scheme: dark) { .no-theme a:hover, - .no-theme a:active { + .no-theme a:active, + .no-theme a:focus { color: rgb(0, 182, 240); } diff --git a/assets/css/embed.css b/assets/css/embed.css index 466a284a..cbafcfea 100644 --- a/assets/css/embed.css +++ b/assets/css/embed.css @@ -21,6 +21,7 @@ color: white; } -.watch-on-invidious > a:hover { +.watch-on-invidious > a:hover, +.watch-on-invidious > a:focus { color: rgba(0, 182, 240, 1);; } diff --git a/assets/js/_helpers.js b/assets/js/_helpers.js index 7c50670e..3960cf2c 100644 --- a/assets/js/_helpers.js +++ b/assets/js/_helpers.js @@ -6,6 +6,7 @@ Array.prototype.find = Array.prototype.find || function (condition) { return this.filter(condition)[0]; }; + Array.from = Array.from || function (source) { return Array.prototype.slice.call(source); }; @@ -201,15 +202,16 @@ window.helpers = window.helpers || { if (localStorageIsUsable) { return { get: function (key) { - if (!localStorage[key]) return; + let storageItem = localStorage.getItem(key) + if (!storageItem) return; try { - return JSON.parse(decodeURIComponent(localStorage[key])); + return JSON.parse(decodeURIComponent(storageItem)); } catch(e) { // Erase non parsable value helpers.storage.remove(key); } }, - set: function (key, value) { localStorage[key] = encodeURIComponent(JSON.stringify(value)); }, + set: function (key, value) { localStorage.setItem(key, encodeURIComponent(JSON.stringify(value))); }, remove: function (key) { localStorage.removeItem(key); } }; } diff --git a/assets/js/handlers.js b/assets/js/handlers.js index 29810e72..539974fb 100644 --- a/assets/js/handlers.js +++ b/assets/js/handlers.js @@ -137,7 +137,7 @@ if (focused_tag === 'textarea') return; if (focused_tag === 'input') { let focused_type = document.activeElement.type.toLowerCase(); - if (!focused_type.match(allowed)) return; + if (!allowed.test(focused_type)) return; } // Focus search bar on '/' diff --git a/assets/js/player.js b/assets/js/player.js index ee678663..bb53ac24 100644 --- a/assets/js/player.js +++ b/assets/js/player.js @@ -261,7 +261,7 @@ function updateCookie(newVolume, newSpeed) { var date = new Date(); date.setFullYear(date.getFullYear() + 2); - var ipRegex = /^((\d+\.){3}\d+|[A-Fa-f0-9]*:[A-Fa-f0-9:]*:[A-Fa-f0-9:]+)$/; + var ipRegex = /^((\d+\.){3}\d+|[\dA-Fa-f]*:[\d:A-Fa-f]*:[\d:A-Fa-f]+)$/; var domainUsed = location.hostname; // Fix for a bug in FF where the leading dot in the FQDN is not ignored diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr index b15d63d4..2d62580d 100644 --- a/src/invidious/comments.cr +++ b/src/invidious/comments.cr @@ -346,7 +346,7 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false) html << <<-END_HTML
- +

@@ -367,7 +367,7 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false) html << <<-END_HTML

- +
END_HTML @@ -428,7 +428,7 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false) html << <<-END_HTML
- +
diff --git a/src/invidious/mixes.cr b/src/invidious/mixes.cr index 3f342b92..defbbc84 100644 --- a/src/invidious/mixes.cr +++ b/src/invidious/mixes.cr @@ -97,7 +97,7 @@ def template_mix(mix)
  • - +

    #{recode_length_seconds(video["lengthSeconds"].as_i)}

    #{video["title"]}

    diff --git a/src/invidious/playlists.cr b/src/invidious/playlists.cr index 57f1f53e..40bb244b 100644 --- a/src/invidious/playlists.cr +++ b/src/invidious/playlists.cr @@ -507,7 +507,7 @@ def template_playlist(playlist)
  • - +

    #{recode_length_seconds(video["lengthSeconds"].as_i)}

    #{video["title"]}

    diff --git a/src/invidious/views/components/channel_info.ecr b/src/invidious/views/components/channel_info.ecr index f216359f..d94ecdad 100644 --- a/src/invidious/views/components/channel_info.ecr +++ b/src/invidious/views/components/channel_info.ecr @@ -1,6 +1,6 @@ <% if channel.banner %>
    - "> + " alt="">
    @@ -11,7 +11,7 @@
    - + <%= author %><% if !channel.verified.nil? && channel.verified %> <% end %>
    diff --git a/src/invidious/views/components/item.ecr b/src/invidious/views/components/item.ecr index fa12374f..36e9d45b 100644 --- a/src/invidious/views/components/item.ecr +++ b/src/invidious/views/components/item.ecr @@ -7,7 +7,7 @@
    <% if !env.get("preferences").as(Preferences).thin_mode %>
    - "/> + " alt=""/>
    <% end %>

    <%= HTML.escape(item.author) %><% if !item.author_verified.nil? && item.author_verified %> <% end %>

    @@ -25,7 +25,7 @@
    <% if !env.get("preferences").as(Preferences).thin_mode %>
    - "/> + " alt="" />

    <%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %>

    <% end %> @@ -38,7 +38,7 @@
    <% if !env.get("preferences").as(Preferences).thin_mode %>
    - + <% if item.length_seconds != 0 %>

    <%= recode_length_seconds(item.length_seconds) %>

    <% end %> @@ -58,7 +58,7 @@
    <% if !env.get("preferences").as(Preferences).thin_mode %>
    - + <% if plid_form = env.get?("remove_playlist_items") %>
    " method="post"> @@ -112,7 +112,7 @@ <% if !env.get("preferences").as(Preferences).thin_mode %>
    - + <% if env.get? "show_watched" %> " method="post"> "> diff --git a/src/invidious/views/feeds/history.ecr b/src/invidious/views/feeds/history.ecr index 471d21db..be1b521d 100644 --- a/src/invidious/views/feeds/history.ecr +++ b/src/invidious/views/feeds/history.ecr @@ -34,7 +34,7 @@ <% if !env.get("preferences").as(Preferences).thin_mode %>
    - + " method="post"> ">

    diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr index a3ec94e8..d2082557 100644 --- a/src/invidious/views/watch.ecr +++ b/src/invidious/views/watch.ecr @@ -208,7 +208,7 @@ we're going to need to do it here in order to allow for translations.

    @@ -298,7 +298,7 @@ we're going to need to do it here in order to allow for translations. &listen=<%= params.listen %>"> <% if !env.get("preferences").as(Preferences).thin_mode %>
    - /mqdefault.jpg"> + /mqdefault.jpg" alt="">

    <%= recode_length_seconds(rv["length_seconds"]?.try &.to_i? || 0) %>

    <% end %> From 73d2ed6f77308dd300e68f3ea059c6aa2c10b1ce Mon Sep 17 00:00:00 2001 From: techmetx11 Date: Wed, 29 Mar 2023 23:33:23 +0000 Subject: [PATCH 014/184] Optimize some redundant stuff --- src/invidious/videos/description.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/invidious/videos/description.cr b/src/invidious/videos/description.cr index 3d25197b..b1d851d3 100644 --- a/src/invidious/videos/description.cr +++ b/src/invidious/videos/description.cr @@ -64,8 +64,8 @@ def parse_description(desc : JSON::Any?) : String? length = command["length"].as_i if start_index > 0 && start_index - index > 0 - str << content[index..(start_index - 1)] - index += start_index - index + str << content[index...start_index] + index = start_index end str << parse_command(command, content[start_index, length]) From 1da00bade3d370711c670afb38dcd0f97e9dd965 Mon Sep 17 00:00:00 2001 From: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com> Date: Sun, 2 Apr 2023 16:31:59 -0400 Subject: [PATCH 015/184] implement code suggestions Co-Authored-By: Samantaz Fox --- assets/js/_helpers.js | 5 ++++- src/invidious/comments.cr | 8 ++++---- src/invidious/mixes.cr | 2 +- src/invidious/playlists.cr | 2 +- src/invidious/views/components/channel_info.ecr | 4 ++-- src/invidious/views/components/item.ecr | 8 ++++---- src/invidious/views/feeds/history.ecr | 2 +- src/invidious/views/watch.ecr | 4 ++-- 8 files changed, 19 insertions(+), 16 deletions(-) diff --git a/assets/js/_helpers.js b/assets/js/_helpers.js index 3960cf2c..8e18169e 100644 --- a/assets/js/_helpers.js +++ b/assets/js/_helpers.js @@ -211,7 +211,10 @@ window.helpers = window.helpers || { helpers.storage.remove(key); } }, - set: function (key, value) { localStorage.setItem(key, encodeURIComponent(JSON.stringify(value))); }, + set: function (key, value) { + let encoded_value = encodeURIComponent(JSON.stringify(value)) + localStorage.setItem(key, encoded_value); + }, remove: function (key) { localStorage.removeItem(key); } }; } diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr index 2d62580d..fd2be73d 100644 --- a/src/invidious/comments.cr +++ b/src/invidious/comments.cr @@ -346,7 +346,7 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false) html << <<-END_HTML
    - +

    @@ -367,7 +367,7 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false) html << <<-END_HTML

    - +
    END_HTML @@ -428,7 +428,7 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false) html << <<-END_HTML
    - +
    @@ -702,7 +702,7 @@ def content_to_comment_html(content, video_id : String? = "") str << %(title=") << emojiAlt << "\" " str << %(width=") << emojiThumb["width"] << "\" " str << %(height=") << emojiThumb["height"] << "\" " - str << %(class="channel-emoji"/>) + str << %(class="channel-emoji" />) end else # Hide deleted channel emoji diff --git a/src/invidious/mixes.cr b/src/invidious/mixes.cr index defbbc84..823ca85b 100644 --- a/src/invidious/mixes.cr +++ b/src/invidious/mixes.cr @@ -97,7 +97,7 @@ def template_mix(mix)
  • - +

    #{recode_length_seconds(video["lengthSeconds"].as_i)}

    #{video["title"]}

    diff --git a/src/invidious/playlists.cr b/src/invidious/playlists.cr index 40bb244b..013be268 100644 --- a/src/invidious/playlists.cr +++ b/src/invidious/playlists.cr @@ -507,7 +507,7 @@ def template_playlist(playlist)
  • - +

    #{recode_length_seconds(video["lengthSeconds"].as_i)}

    #{video["title"]}

    diff --git a/src/invidious/views/components/channel_info.ecr b/src/invidious/views/components/channel_info.ecr index d94ecdad..59888760 100644 --- a/src/invidious/views/components/channel_info.ecr +++ b/src/invidious/views/components/channel_info.ecr @@ -1,6 +1,6 @@ <% if channel.banner %>
    - " alt=""> + " alt="" />
    @@ -11,7 +11,7 @@
    - + <%= author %><% if !channel.verified.nil? && channel.verified %> <% end %>
    diff --git a/src/invidious/views/components/item.ecr b/src/invidious/views/components/item.ecr index 36e9d45b..7cfd38db 100644 --- a/src/invidious/views/components/item.ecr +++ b/src/invidious/views/components/item.ecr @@ -7,7 +7,7 @@
    <% if !env.get("preferences").as(Preferences).thin_mode %>
    - " alt=""/> + " alt="" />
    <% end %>

    <%= HTML.escape(item.author) %><% if !item.author_verified.nil? && item.author_verified %> <% end %>

    @@ -38,7 +38,7 @@
    <% if !env.get("preferences").as(Preferences).thin_mode %>
    - + <% if item.length_seconds != 0 %>

    <%= recode_length_seconds(item.length_seconds) %>

    <% end %> @@ -58,7 +58,7 @@
    <% if !env.get("preferences").as(Preferences).thin_mode %>
    - + <% if plid_form = env.get?("remove_playlist_items") %> " method="post"> @@ -112,7 +112,7 @@ <% if !env.get("preferences").as(Preferences).thin_mode %>
    - + <% if env.get? "show_watched" %> " method="post"> "> diff --git a/src/invidious/views/feeds/history.ecr b/src/invidious/views/feeds/history.ecr index be1b521d..2234b297 100644 --- a/src/invidious/views/feeds/history.ecr +++ b/src/invidious/views/feeds/history.ecr @@ -34,7 +34,7 @@ <% if !env.get("preferences").as(Preferences).thin_mode %>
    - + " method="post"> ">

    diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr index d2082557..5b3190f3 100644 --- a/src/invidious/views/watch.ecr +++ b/src/invidious/views/watch.ecr @@ -208,7 +208,7 @@ we're going to need to do it here in order to allow for translations.

    @@ -298,7 +298,7 @@ we're going to need to do it here in order to allow for translations. &listen=<%= params.listen %>"> <% if !env.get("preferences").as(Preferences).thin_mode %>
    - /mqdefault.jpg" alt=""> + /mqdefault.jpg" alt="" />

    <%= recode_length_seconds(rv["length_seconds"]?.try &.to_i? || 0) %>

    <% end %> From 600da635b78f3cabee327361866f1ff0c78c0438 Mon Sep 17 00:00:00 2001 From: raphj Date: Sun, 2 Apr 2023 23:36:06 +0200 Subject: [PATCH 016/184] Allow browser suggestions for search (#3704) --- src/invidious/views/components/search_box.ecr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/invidious/views/components/search_box.ecr b/src/invidious/views/components/search_box.ecr index 1240e5bd..a03785d1 100644 --- a/src/invidious/views/components/search_box.ecr +++ b/src/invidious/views/components/search_box.ecr @@ -1,6 +1,6 @@
    - autofocus<% end %> name="q" placeholder="<%= translate(locale, "search") %>" title="<%= translate(locale, "search") %>" From fffdaa1410db7f9b5c67b1b47d401a2744e7b220 Mon Sep 17 00:00:00 2001 From: thtmnisamnstr Date: Mon, 3 Apr 2023 17:07:58 -0700 Subject: [PATCH 017/184] Updated csv reading as per feedback and ran Signed-off-by: thtmnisamnstr --- src/invidious/user/imports.cr | 53 +++++++++++++++++------------------ 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/src/invidious/user/imports.cr b/src/invidious/user/imports.cr index 757f5b13..673991f7 100644 --- a/src/invidious/user/imports.cr +++ b/src/invidious/user/imports.cr @@ -40,7 +40,7 @@ struct Invidious::User title = csv_head[4] description = csv_head[5] visibility = csv_head[6] - + if visibility.compare("Public", case_insensitive: true) == 0 privacy = PlaylistPrivacy::Public else @@ -51,34 +51,33 @@ struct Invidious::User Invidious::Database::Playlists.update_description(playlist.id, description) # Add each video to the playlist from the body content - CSV.each_row(raw_body) do |row| - if row.size >= 1 - video_id = row[0] - if playlist - next if !video_id - next if video_id == "Video Id" + csv_body = CSV.new(raw_body, headers: true) + csv_body.each do |row| + video_id = row[0] + if playlist + next if !video_id + next if video_id == "Video Id" - begin - video = get_video(video_id) - rescue ex - next - end - - playlist_video = PlaylistVideo.new({ - title: video.title, - id: video.id, - author: video.author, - ucid: video.ucid, - length_seconds: video.length_seconds, - published: video.published, - plid: playlist.id, - live_now: video.live_now, - index: Random::Secure.rand(0_i64..Int64::MAX), - }) - - Invidious::Database::PlaylistVideos.insert(playlist_video) - Invidious::Database::Playlists.update_video_added(playlist.id, playlist_video.index) + begin + video = get_video(video_id) + rescue ex + next end + + playlist_video = PlaylistVideo.new({ + title: video.title, + id: video.id, + author: video.author, + ucid: video.ucid, + length_seconds: video.length_seconds, + published: video.published, + plid: playlist.id, + live_now: video.live_now, + index: Random::Secure.rand(0_i64..Int64::MAX), + }) + + Invidious::Database::PlaylistVideos.insert(playlist_video) + Invidious::Database::Playlists.update_video_added(playlist.id, playlist_video.index) end end From b3c0afef02ee13c7f291fd26a5d64b4aee059906 Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Wed, 5 Apr 2023 23:43:41 +0200 Subject: [PATCH 018/184] Videos: fix description text offset when emojis are present --- src/invidious/videos/description.cr | 71 +++++++++++++++++++---------- 1 file changed, 47 insertions(+), 24 deletions(-) diff --git a/src/invidious/videos/description.cr b/src/invidious/videos/description.cr index b1d851d3..2017955d 100644 --- a/src/invidious/videos/description.cr +++ b/src/invidious/videos/description.cr @@ -46,37 +46,60 @@ def parse_command(command : JSON::Any?, string : String) : String? return "(unknown YouTube desc command)" end -def parse_description(desc : JSON::Any?) : String? - if desc.nil? - return "" +private def copy_string(str : String::Builder, iter : Iterator, count : Int) : Int + copied = 0 + while copied < count + cp = iter.next + break if cp.is_a?(Iterator::Stop) + + str << cp.chr + + # A codepoint from the SMP counts twice + copied += 1 if cp > 0xFFFF + copied += 1 end + return copied +end + +def parse_description(desc : JSON::Any?) : String? + return "" if desc.nil? + content = desc["content"].as_s - if content.empty? - return "" - end + return "" if content.empty? - if commands = desc["commandRuns"]?.try &.as_a - description = String.build do |str| - index = 0 - commands.each do |command| - start_index = command["startIndex"].as_i - length = command["length"].as_i + commands = desc["commandRuns"]?.try &.as_a + return content if commands.nil? - if start_index > 0 && start_index - index > 0 - str << content[index...start_index] - index = start_index - end + # Not everything is stored in UTF-8 on youtube's side. The SMP codepoints + # (0x10000 and above) are encoded as UTF-16 surrogate pairs, which are + # automatically decoded by the JSON parser. It means that we need to count + # copied byte in a special manner, preventing the use of regular string copy. + iter = content.each_codepoint - str << parse_command(command, content[start_index, length]) - index += length - end - if index < content.size - str << content[index..content.size] + index = 0 + + return String.build do |str| + commands.each do |command| + cmd_start = command["startIndex"].as_i + cmd_length = command["length"].as_i + + # Copy the text chunk between this command and the previous if needed. + length = cmd_start - index + index += copy_string(str, iter, length) + + # We need to copy the command's text using the iterator + # and the special function defined above. + cmd_content = String.build(cmd_length) do |str2| + copy_string(str2, iter, cmd_length) end + + str << parse_command(command, cmd_content) + index += cmd_length end - return description - end - return content + # Copy the end of the string (past the last command). + remaining_length = content.size - index + copy_string(str, iter, remaining_length) if remaining_length > 0 + end end From 9a765418d1410ceda3a27ebcd2febd9fe4319edc Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Mon, 10 Apr 2023 16:59:13 +0200 Subject: [PATCH 019/184] Update specs --- mocks | 2 +- .../videos/regular_videos_extract_spec.cr | 34 +++++++++---------- .../videos/scheduled_live_extract_spec.cr | 7 ++-- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/mocks b/mocks index cb16e034..11ec372f 160000 --- a/mocks +++ b/mocks @@ -1 +1 @@ -Subproject commit cb16e0343c8f94182615610bfe3c503db89717a7 +Subproject commit 11ec372f72747c09d48ffef04843f72be67d5b54 diff --git a/spec/invidious/videos/regular_videos_extract_spec.cr b/spec/invidious/videos/regular_videos_extract_spec.cr index cbe80010..a6a3e60a 100644 --- a/spec/invidious/videos/regular_videos_extract_spec.cr +++ b/spec/invidious/videos/regular_videos_extract_spec.cr @@ -17,8 +17,8 @@ Spectator.describe "parse_video_info" do # Basic video infos expect(info["title"].as_s).to eq("I Gave My 100,000,000th Subscriber An Island") - expect(info["views"].as_i).to eq(115_784_415) - expect(info["likes"].as_i).to eq(4_932_790) + expect(info["views"].as_i).to eq(126_573_823) + expect(info["likes"].as_i).to eq(5_157_654) # For some reason the video length from VideoDetails and the # one from microformat differs by 1s... @@ -48,12 +48,12 @@ Spectator.describe "parse_video_info" do expect(info["relatedVideos"].as_a.size).to eq(20) - expect(info["relatedVideos"][0]["id"]).to eq("iogcY_4xGjo") - expect(info["relatedVideos"][0]["title"]).to eq("$1 vs $1,000,000 Hotel Room!") + expect(info["relatedVideos"][0]["id"]).to eq("Hwybp38GnZw") + expect(info["relatedVideos"][0]["title"]).to eq("I Built Willy Wonka's Chocolate Factory!") expect(info["relatedVideos"][0]["author"]).to eq("MrBeast") expect(info["relatedVideos"][0]["ucid"]).to eq("UCX6OQ3DkcsbYNE6H8uQQuVA") - expect(info["relatedVideos"][0]["view_count"]).to eq("172972109") - expect(info["relatedVideos"][0]["short_view_count"]).to eq("172M") + expect(info["relatedVideos"][0]["view_count"]).to eq("179877630") + expect(info["relatedVideos"][0]["short_view_count"]).to eq("179M") expect(info["relatedVideos"][0]["author_verified"]).to eq("true") # Description @@ -76,11 +76,11 @@ Spectator.describe "parse_video_info" do expect(info["ucid"].as_s).to eq("UCX6OQ3DkcsbYNE6H8uQQuVA") expect(info["authorThumbnail"].as_s).to eq( - "https://yt3.ggpht.com/ytc/AL5GRJUfhQdJS6n-YJtsAf-ouS2myDavDOq_zXBfebal3Q=s48-c-k-c0x00ffffff-no-rj" + "https://yt3.ggpht.com/ytc/AL5GRJVuqw82ERvHzsmBxL7avr1dpBtsVIXcEzBPZaloFg=s48-c-k-c0x00ffffff-no-rj" ) expect(info["authorVerified"].as_bool).to be_true - expect(info["subCountText"].as_s).to eq("135M") + expect(info["subCountText"].as_s).to eq("143M") end it "parses a regular video with no descrition/comments" do @@ -99,7 +99,7 @@ Spectator.describe "parse_video_info" do # Basic video infos expect(info["title"].as_s).to eq("Chris Rea - Auberge") - expect(info["views"].as_i).to eq(10_698_554) + expect(info["views"].as_i).to eq(10_943_126) expect(info["likes"].as_i).to eq(0) expect(info["lengthSeconds"].as_i).to eq(283_i64) expect(info["published"].as_s).to eq("2012-05-21T00:00:00Z") @@ -132,21 +132,21 @@ Spectator.describe "parse_video_info" do # Related videos - expect(info["relatedVideos"].as_a.size).to eq(18) + expect(info["relatedVideos"].as_a.size).to eq(19) - expect(info["relatedVideos"][0]["id"]).to eq("rfyZrJUmzxU") - expect(info["relatedVideos"][0]["title"]).to eq("cheb mami - bekatni") - expect(info["relatedVideos"][0]["author"]).to eq("pelitovic") - expect(info["relatedVideos"][0]["ucid"]).to eq("UCsp6vFyJeGoLxgn-AsHp1tw") - expect(info["relatedVideos"][0]["view_count"]).to eq("13863619") - expect(info["relatedVideos"][0]["short_view_count"]).to eq("13M") + expect(info["relatedVideos"][0]["id"]).to eq("Ww3KeZ2_Yv4") + expect(info["relatedVideos"][0]["title"]).to eq("Chris Rea") + expect(info["relatedVideos"][0]["author"]).to eq("PanMusic") + expect(info["relatedVideos"][0]["ucid"]).to eq("UCsKAPSuh1iNbLWUga_igPyA") + expect(info["relatedVideos"][0]["view_count"]).to eq("31581") + expect(info["relatedVideos"][0]["short_view_count"]).to eq("31K") expect(info["relatedVideos"][0]["author_verified"]).to eq("false") # Description expect(info["description"].as_s).to eq(" ") expect(info["shortDescription"].as_s).to be_empty - expect(info["descriptionHtml"].as_s).to eq("

    ") + expect(info["descriptionHtml"].as_s).to eq("") # Video metadata diff --git a/spec/invidious/videos/scheduled_live_extract_spec.cr b/spec/invidious/videos/scheduled_live_extract_spec.cr index 9dd22b97..25e08c51 100644 --- a/spec/invidious/videos/scheduled_live_extract_spec.cr +++ b/spec/invidious/videos/scheduled_live_extract_spec.cr @@ -86,9 +86,10 @@ Spectator.describe "parse_video_info" do expect(info["description"].as_s).to start_with(description_start_text) expect(info["shortDescription"].as_s).to start_with(description_start_text) - expect(info["descriptionHtml"].as_s).to start_with( - "PBD Podcast Episode 241. The home team is ready and at it again with the latest news, interesting topics and trending conversations on topics that matter. Try our sponsor Aura for 14 days free -
    aura.com/pbd" - ) + # TODO: Update mocks right before the start of PDB podcast, either on friday or saturday (time unknown) + # expect(info["descriptionHtml"].as_s).to start_with( + # "PBD Podcast Episode 241. The home team is ready and at it again with the latest news, interesting topics and trending conversations on topics that matter. Try our sponsor Aura for 14 days free - aura.com/pbd" + # ) # Video metadata From 5517a4eadb980ae06d4dde08afd10fec8c83f9b4 Mon Sep 17 00:00:00 2001 From: chunky programmer <78101139+ChunkyProgrammer@users.noreply.github.com> Date: Wed, 19 Apr 2023 23:29:50 -0400 Subject: [PATCH 020/184] fix fetching community continuations --- src/invidious/channels/community.cr | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/invidious/channels/community.cr b/src/invidious/channels/community.cr index ce34ff82..ad786f3a 100644 --- a/src/invidious/channels/community.cr +++ b/src/invidious/channels/community.cr @@ -31,18 +31,16 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode) session_token: session_token, } - response = YT_POOL.client &.post("/comment_service_ajax?action_get_comments=1&ctoken=#{continuation}&continuation=#{continuation}&hl=en&gl=US", headers, form: post_req) - body = JSON.parse(response.body) + body = YoutubeAPI.browse(continuation) - body = body["response"]["continuationContents"]["itemSectionContinuation"]? || - body["response"]["continuationContents"]["backstageCommentsContinuation"]? + body = body.dig?("continuationContents", "itemSectionContinuation") || + body.dig?("continuationContents", "backstageCommentsContinuation") if !body raise InfoException.new("Could not extract continuation.") end end - continuation = body["continuations"]?.try &.[0]["nextContinuationData"]["continuation"].as_s posts = body["contents"].as_a if message = posts[0]["messageRenderer"]? @@ -270,10 +268,8 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode) end end end - - if body["continuations"]? - continuation = body["continuations"][0]["nextContinuationData"]["continuation"].as_s - json.field "continuation", extract_channel_community_cursor(continuation) + if cont = posts.dig?(-1, "continuationItemRenderer", "continuationEndpoint", "continuationCommand", "token") + json.field "continuation", extract_channel_community_cursor(cont.as_s) end end end From d1b51e57a2aa2fd29cf0d1ebed71dcce7ba4ac1c Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Tue, 25 Apr 2023 22:51:21 +0200 Subject: [PATCH 021/184] CI: Add crystal 1.7.3 and 1.8.1 --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4aa334c9..565cf8cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,6 +42,8 @@ jobs: - 1.4.1 - 1.5.1 - 1.6.2 + - 1.7.3 + - 1.8.1 include: - crystal: nightly stable: false From e24feab1f7eddbb912b2ea874800c061eebd8dcc Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Tue, 25 Apr 2023 22:51:56 +0200 Subject: [PATCH 022/184] CI: Remove crystal 1.3.2 --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 565cf8cd..96903fc7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,6 @@ jobs: matrix: stable: [true] crystal: - - 1.3.2 - 1.4.1 - 1.5.1 - 1.6.2 From 0107b774f29b0f4cc0a7fabe546db347390337ec Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Fri, 28 Apr 2023 17:23:40 +0200 Subject: [PATCH 023/184] Trending: Don't extract items from categories --- src/invidious/trending.cr | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/invidious/trending.cr b/src/invidious/trending.cr index 134eb437..74bab1bd 100644 --- a/src/invidious/trending.cr +++ b/src/invidious/trending.cr @@ -17,7 +17,9 @@ def fetch_trending(trending_type, region, locale) client_config = YoutubeAPI::ClientConfig.new(region: region) initial_data = YoutubeAPI.browse("FEtrending", params: params, client_config: client_config) - trending = extract_videos(initial_data) - return {trending, plid} + items, _ = extract_items(initial_data) + + # Return items, but ignore categories (e.g featured content) + return items.reject!(Category), plid end From 7afa03d821365673e955468eff58009b5fb5c4c8 Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Fri, 28 Apr 2023 17:27:06 +0200 Subject: [PATCH 024/184] Search: Don't extract items from categories too --- src/invidious/search/processors.cr | 4 ++-- src/invidious/search/query.cr | 23 +---------------------- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/src/invidious/search/processors.cr b/src/invidious/search/processors.cr index 7e909590..25edb936 100644 --- a/src/invidious/search/processors.cr +++ b/src/invidious/search/processors.cr @@ -10,7 +10,7 @@ module Invidious::Search initial_data = YoutubeAPI.search(query.text, search_params, client_config: client_config) items, _ = extract_items(initial_data) - return items + return items.reject!(Category) end # Search a youtube channel @@ -32,7 +32,7 @@ module Invidious::Search response_json = YoutubeAPI.browse(continuation) items, _ = extract_items(response_json, "", ucid) - return items + return items.reject!(Category) end # Search inside of user subscriptions diff --git a/src/invidious/search/query.cr b/src/invidious/search/query.cr index 24e79609..e38845d9 100644 --- a/src/invidious/search/query.cr +++ b/src/invidious/search/query.cr @@ -113,7 +113,7 @@ module Invidious::Search case @type when .regular?, .playlist? - items = unnest_items(Processors.regular(self)) + items = Processors.regular(self) # when .channel? items = Processors.channel(self) @@ -136,26 +136,5 @@ module Invidious::Search return params end - - # TODO: clean code - private def unnest_items(all_items) : Array(SearchItem) - items = [] of SearchItem - - # Light processing to flatten search results out of Categories. - # They should ideally be supported in the future. - all_items.each do |i| - if i.is_a? Category - i.contents.each do |nest_i| - if !nest_i.is_a? Video - items << nest_i - end - end - else - items << i - end - end - - return items - end end end From 3cfbc19ccc031ec4640f5e06568d2a52ebf90627 Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Fri, 28 Apr 2023 17:30:01 +0200 Subject: [PATCH 025/184] Extractors: Add utility function to extract items from categories --- src/invidious/yt_backend/extractors_utils.cr | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/invidious/yt_backend/extractors_utils.cr b/src/invidious/yt_backend/extractors_utils.cr index 0cb3c079..b247dca8 100644 --- a/src/invidious/yt_backend/extractors_utils.cr +++ b/src/invidious/yt_backend/extractors_utils.cr @@ -68,19 +68,16 @@ rescue ex return false end -def extract_videos(initial_data : Hash(String, JSON::Any), author_fallback : String? = nil, author_id_fallback : String? = nil) : Array(SearchVideo) - extracted, _ = extract_items(initial_data, author_fallback, author_id_fallback) +# This function extracts the SearchItems from a Category. +# Categories are commonly returned in search results and trending pages. +def extract_category(category : Category) : Array(SearchVideo) + items = [] of SearchItem - target = [] of (SearchItem | Continuation) - extracted.each do |i| - if i.is_a?(Category) - i.contents.each { |cate_i| target << cate_i if !cate_i.is_a? Video } - else - target << i - end + category.contents.each do |item| + target << cate_i if item.is_a?(SearchItem) end - return target.select(SearchVideo) + return items end def extract_selected_tab(tabs) From 67859113fdaac70f4524c44fd24913824889b691 Mon Sep 17 00:00:00 2001 From: AHOHNMYC Date: Wed, 22 Mar 2023 06:57:05 +0000 Subject: [PATCH 026/184] Update Russian translation --- locales/ru.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/locales/ru.json b/locales/ru.json index 7ca5cf1f..d2d7c86d 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -495,5 +495,8 @@ "channel_tab_shorts_label": "Shorts", "Music in this video": "Музыка в этом видео", "Artist: ": "Исполнитель: ", - "Album: ": "Альбом: " + "Album: ": "Альбом: ", + "Song: ": "Композиция: ", + "Standard YouTube license": "Стандартная лицензия YouTube", + "Channel Sponsor": "Спонсор канала" } From 17ecdbaf7db2843e9b8d0977228cc5c8c18ecf39 Mon Sep 17 00:00:00 2001 From: Ashirg-ch Date: Wed, 22 Mar 2023 09:19:19 +0000 Subject: [PATCH 027/184] Update German translation --- locales/de.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index c2941d6d..0df86663 100644 --- a/locales/de.json +++ b/locales/de.json @@ -479,5 +479,8 @@ "Artist: ": "Künstler: ", "Album: ": "Album: ", "channel_tab_playlists_label": "Wiedergabelisten", - "channel_tab_channels_label": "Kanäle" + "channel_tab_channels_label": "Kanäle", + "Channel Sponsor": "Kanalsponsor", + "Standard YouTube license": "Standard YouTube-Lizenz", + "Song: ": "Musik: " } From 155f5fef97c75ad242f623c0b97b1422ee832ec5 Mon Sep 17 00:00:00 2001 From: Matthaiks Date: Mon, 20 Mar 2023 19:10:20 +0000 Subject: [PATCH 028/184] Update Polish translation --- locales/pl.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/pl.json b/locales/pl.json index 3c713e70..2b6768d9 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -498,5 +498,6 @@ "Artist: ": "Wykonawca: ", "Album: ": "Album: ", "Song: ": "Piosenka: ", - "Channel Sponsor": "Sponsor kanału" + "Channel Sponsor": "Sponsor kanału", + "Standard YouTube license": "Standardowa licencja YouTube" } From d1393343765268f8131fab13cc8e95520f4f99ac Mon Sep 17 00:00:00 2001 From: Rex_sa Date: Mon, 20 Mar 2023 22:14:09 +0000 Subject: [PATCH 029/184] Update Arabic translation --- locales/ar.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/ar.json b/locales/ar.json index 3ce34c2d..fb9e7564 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -545,5 +545,6 @@ "Album: ": "الألبوم: ", "Artist: ": "الفنان: ", "Song: ": "أغنية: ", - "Channel Sponsor": "راعي القناة" + "Channel Sponsor": "راعي القناة", + "Standard YouTube license": "ترخيص YouTube القياسي" } From b97b5b5859ea0e0755745f7d2138e4d21ecaec0c Mon Sep 17 00:00:00 2001 From: gallegonovato Date: Mon, 20 Mar 2023 20:03:55 +0000 Subject: [PATCH 030/184] Update Spanish translation --- locales/es.json | 75 +++++++++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 30 deletions(-) diff --git a/locales/es.json b/locales/es.json index bb082c06..aa03f124 100644 --- a/locales/es.json +++ b/locales/es.json @@ -398,37 +398,51 @@ "search_filters_features_option_three_sixty": "360°", "videoinfo_watch_on_youTube": "Ver en YouTube", "preferences_save_player_pos_label": "Guardar posición de reproducción: ", - "generic_views_count": "{{count}} visualización", - "generic_views_count_plural": "{{count}} visualizaciones", - "generic_subscribers_count": "{{count}} suscriptor", - "generic_subscribers_count_plural": "{{count}} suscriptores", - "generic_subscriptions_count": "{{count}} suscripción", - "generic_subscriptions_count_plural": "{{count}} suscripciones", - "subscriptions_unseen_notifs_count": "{{count}} notificación no vista", - "subscriptions_unseen_notifs_count_plural": "{{count}} notificaciones no vistas", - "generic_count_days": "{{count}} día", - "generic_count_days_plural": "{{count}} días", - "comments_view_x_replies": "Ver {{count}} respuesta", - "comments_view_x_replies_plural": "Ver {{count}} respuestas", - "generic_count_weeks": "{{count}} semana", - "generic_count_weeks_plural": "{{count}} semanas", - "generic_playlists_count": "{{count}} lista de reproducción", - "generic_playlists_count_plural": "{{count}} listas de reproducción", + "generic_views_count_0": "{{count}} visualización", + "generic_views_count_1": "{{count}} visualizaciones", + "generic_views_count_2": "{{count}} visualizaciones", + "generic_subscribers_count_0": "{{count}} suscriptor", + "generic_subscribers_count_1": "{{count}} suscriptores", + "generic_subscribers_count_2": "{{count}} suscriptores", + "generic_subscriptions_count_0": "{{count}} suscripción", + "generic_subscriptions_count_1": "{{count}} suscripciones", + "generic_subscriptions_count_2": "{{count}} suscripciones", + "subscriptions_unseen_notifs_count_0": "{{count}} notificación no vista", + "subscriptions_unseen_notifs_count_1": "{{count}} notificaciones no vistas", + "subscriptions_unseen_notifs_count_2": "{{count}} notificaciones no vistas", + "generic_count_days_0": "{{count}} día", + "generic_count_days_1": "{{count}} días", + "generic_count_days_2": "{{count}} días", + "comments_view_x_replies_0": "Ver {{count}} respuesta", + "comments_view_x_replies_1": "Ver {{count}} respuestas", + "comments_view_x_replies_2": "Ver {{count}} respuestas", + "generic_count_weeks_0": "{{count}} semana", + "generic_count_weeks_1": "{{count}} semanas", + "generic_count_weeks_2": "{{count}} semanas", + "generic_playlists_count_0": "{{count}} lista de reproducción", + "generic_playlists_count_1": "{{count}} listas de reproducciones", + "generic_playlists_count_2": "{{count}} listas de reproducciones", "generic_videos_count_0": "{{count}} video", "generic_videos_count_1": "{{count}} videos", "generic_videos_count_2": "{{count}} videos", - "generic_count_months": "{{count}} mes", - "generic_count_months_plural": "{{count}} meses", - "comments_points_count": "{{count}} punto", - "comments_points_count_plural": "{{count}} puntos", - "generic_count_years": "{{count}} año", - "generic_count_years_plural": "{{count}} años", - "generic_count_hours": "{{count}} hora", - "generic_count_hours_plural": "{{count}} horas", - "generic_count_minutes": "{{count}} minuto", - "generic_count_minutes_plural": "{{count}} minutos", - "generic_count_seconds": "{{count}} segundo", - "generic_count_seconds_plural": "{{count}} segundos", + "generic_count_months_0": "{{count}} mes", + "generic_count_months_1": "{{count}} meses", + "generic_count_months_2": "{{count}} meses", + "comments_points_count_0": "{{count}} punto", + "comments_points_count_1": "{{count}} puntos", + "comments_points_count_2": "{{count}} puntos", + "generic_count_years_0": "{{count}} año", + "generic_count_years_1": "{{count}} años", + "generic_count_years_2": "{{count}} años", + "generic_count_hours_0": "{{count}} hora", + "generic_count_hours_1": "{{count}} horas", + "generic_count_hours_2": "{{count}} horas", + "generic_count_minutes_0": "{{count}} minuto", + "generic_count_minutes_1": "{{count}} minutos", + "generic_count_minutes_2": "{{count}} minutos", + "generic_count_seconds_0": "{{count}} segundo", + "generic_count_seconds_1": "{{count}} segundos", + "generic_count_seconds_2": "{{count}} segundos", "crash_page_before_reporting": "Antes de notificar un error asegúrate de que has:", "crash_page_switch_instance": "probado a usar otra instancia", "crash_page_read_the_faq": "leído las Preguntas Frecuentes", @@ -469,8 +483,9 @@ "search_filters_duration_option_none": "Cualquier duración", "search_filters_features_option_vr180": "VR180", "search_filters_apply_button": "Aplicar filtros", - "tokens_count": "{{count}} ficha", - "tokens_count_plural": "{{count}} fichas", + "tokens_count_0": "{{count}} ficha", + "tokens_count_1": "{{count}} fichas", + "tokens_count_2": "{{count}} fichas", "search_message_use_another_instance": " También puede buscar en otra instancia.", "Popular enabled: ": "¿Habilitar la sección popular? ", "error_video_not_in_playlist": "El video que solicitaste no existe en esta lista de reproducción. Haz clic aquí para acceder a la página de inicio de la lista de reproducción.", From 5c24bf1322f470ef46c1db34a24d4167f04ca987 Mon Sep 17 00:00:00 2001 From: Jorge Maldonado Ventura Date: Mon, 20 Mar 2023 18:46:59 +0000 Subject: [PATCH 031/184] Update Spanish translation --- locales/es.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/es.json b/locales/es.json index aa03f124..4f9250d4 100644 --- a/locales/es.json +++ b/locales/es.json @@ -497,5 +497,6 @@ "Artist: ": "Artista: ", "Album: ": "Álbum: ", "Song: ": "Canción: ", - "Channel Sponsor": "Patrocinador del canal" + "Channel Sponsor": "Patrocinador del canal", + "Standard YouTube license": "Licencia de YouTube estándar" } From 9eafbbdcbbcbdef86b5645219793f4e6cfe86a83 Mon Sep 17 00:00:00 2001 From: Jorge Maldonado Ventura Date: Mon, 20 Mar 2023 18:47:21 +0000 Subject: [PATCH 032/184] Update Esperanto translation --- locales/eo.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/locales/eo.json b/locales/eo.json index 9f37c7cb..a70b71d3 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -479,5 +479,8 @@ "channel_tab_shorts_label": "Mallongaj", "Music in this video": "Muziko en ĉi tiu video", "Artist: ": "Artisto: ", - "Album: ": "Albumo: " + "Album: ": "Albumo: ", + "Channel Sponsor": "Kanala sponsoro", + "Song: ": "Muzikaĵo: ", + "Standard YouTube license": "Implicita YouTube-licenco" } From ec1d6ee851953c52a0128b2ce303e3a9decf2b87 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Mon, 20 Mar 2023 19:35:31 +0000 Subject: [PATCH 033/184] Update Ukrainian translation --- locales/uk.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/uk.json b/locales/uk.json index 4d748e7f..e9c90287 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -497,5 +497,6 @@ "Artist: ": "Виконавець: ", "Album: ": "Альбом: ", "Song: ": "Пісня: ", - "Channel Sponsor": "Спонсор каналу" + "Channel Sponsor": "Спонсор каналу", + "Standard YouTube license": "Стандартна ліцензія YouTube" } From f46cc98654dc26ac58f61f856e6f4516f25031a7 Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 21 Mar 2023 03:48:53 +0000 Subject: [PATCH 034/184] Update Chinese (Simplified) translation --- locales/zh-CN.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/zh-CN.json b/locales/zh-CN.json index f202cf88..634175cb 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -465,5 +465,6 @@ "channel_tab_shorts_label": "短视频", "channel_tab_channels_label": "频道", "Song: ": "歌曲: ", - "Channel Sponsor": "频道赞助者" + "Channel Sponsor": "频道赞助者", + "Standard YouTube license": "标准 YouTube 许可证" } From 4aa2c406ff6f7f704d4243c7d7a79eaec0e4ec7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Mon, 20 Mar 2023 19:07:59 +0000 Subject: [PATCH 035/184] Update Turkish translation --- locales/tr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/tr.json b/locales/tr.json index 6e0bc175..c5381f4c 100644 --- a/locales/tr.json +++ b/locales/tr.json @@ -481,5 +481,6 @@ "Music in this video": "Bu videodaki müzik", "Artist: ": "Sanatçı: ", "Channel Sponsor": "Kanal Sponsoru", - "Song: ": "Şarkı: " + "Song: ": "Şarkı: ", + "Standard YouTube license": "Standart YouTube lisansı" } From a9fcfcf7c9b15a7550349c76621b4006ce261a8c Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Wed, 22 Mar 2023 02:36:12 +0000 Subject: [PATCH 036/184] Update Chinese (Traditional) translation --- locales/zh-TW.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/zh-TW.json b/locales/zh-TW.json index 54090d3d..4100931c 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -465,5 +465,6 @@ "Album: ": "專輯: ", "Music in this video": "此影片中的音樂", "Channel Sponsor": "頻道贊助者", - "Song: ": "歌曲: " + "Song: ": "歌曲: ", + "Standard YouTube license": "標準 YouTube 授權條款" } From 4078fc5818d27c0d4365f335e1ccbe0fd27c9f2f Mon Sep 17 00:00:00 2001 From: Parsa Date: Thu, 23 Mar 2023 07:24:05 +0000 Subject: [PATCH 037/184] Update Persian translation --- locales/fa.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/locales/fa.json b/locales/fa.json index 56685f64..29a0c527 100644 --- a/locales/fa.json +++ b/locales/fa.json @@ -450,5 +450,8 @@ "Music in this video": "آهنگ در این ویدیو", "Artist: ": "هنرمند: ", "Album: ": "آلبوم: ", - "Song: ": "آهنگ: " + "Song: ": "آهنگ: ", + "Channel Sponsor": "اسپانسر کانال", + "Standard YouTube license": "پروانه استاندارد YouTube", + "search_message_use_another_instance": " شما همچنین می‌توانید در نمونه دیگر هم جستجو کنید." } From a3e587657fdb62738115efda75d36bc078c93b77 Mon Sep 17 00:00:00 2001 From: Milo Ivir Date: Thu, 23 Mar 2023 19:43:36 +0000 Subject: [PATCH 038/184] Update Croatian translation --- locales/hr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/hr.json b/locales/hr.json index ade732ad..bbd899c9 100644 --- a/locales/hr.json +++ b/locales/hr.json @@ -497,5 +497,6 @@ "Album: ": "Album: ", "Artist: ": "Izvođač: ", "Channel Sponsor": "Sponzor kanala", - "Song: ": "Pjesma: " + "Song: ": "Pjesma: ", + "Standard YouTube license": "Standardna YouTube licenca" } From 1825b8edb3917000efaaeffd527c56e343c659c1 Mon Sep 17 00:00:00 2001 From: Fjuro Date: Tue, 21 Mar 2023 14:04:39 +0000 Subject: [PATCH 039/184] Update Czech translation --- locales/cs.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/cs.json b/locales/cs.json index 4611c4fd..38c3a8d2 100644 --- a/locales/cs.json +++ b/locales/cs.json @@ -497,5 +497,6 @@ "Artist: ": "Umělec: ", "Album: ": "Album: ", "Channel Sponsor": "Sponzor kanálu", - "Song: ": "Skladba: " + "Song: ": "Skladba: ", + "Standard YouTube license": "Standardní licence YouTube" } From fe1648e72eca3e23fa7c4a7d811aee13ac230ba2 Mon Sep 17 00:00:00 2001 From: SC Date: Sat, 25 Mar 2023 11:38:22 +0000 Subject: [PATCH 040/184] Update Portuguese translation --- locales/pt.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/pt.json b/locales/pt.json index 310381ae..79d8f354 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -481,5 +481,6 @@ "Artist: ": "Artista: ", "Album: ": "Álbum: ", "Song: ": "Canção: ", - "Channel Sponsor": "Patrocinador do canal" + "Channel Sponsor": "Patrocinador do canal", + "Standard YouTube license": "Licença padrão do YouTube" } From 778edf63cb79ee7ff1d7fc1c20cef3d6f17a184e Mon Sep 17 00:00:00 2001 From: victor dargallo Date: Tue, 21 Mar 2023 20:27:50 +0000 Subject: [PATCH 041/184] Update Catalan translation --- locales/ca.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index 54a0b177..59396c11 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -66,7 +66,7 @@ "Malay": "Malai", "Persian": "Persa", "Slovak": "Eslovac", - "Search": "Busca", + "Search": "Cerca", "Show annotations": "Mostra anotacions", "preferences_region_label": "País del contingut: ", "preferences_sort_label": "Ordena vídeos per: ", @@ -481,5 +481,6 @@ "Top": "Millors", "preferences_max_results_label": "Nombre de vídeos mostrats al feed: ", "Engagement: ": "Atracció: ", - "Redirect homepage to feed: ": "Redirigeix la pàgina d'inici al feed: " + "Redirect homepage to feed: ": "Redirigeix la pàgina d'inici al feed: ", + "Standard YouTube license": "Llicència estàndard de YouTube" } From 7b4e3639cf034abab14cc984f0cce30f698baacb Mon Sep 17 00:00:00 2001 From: Damjan Gerl Date: Tue, 21 Mar 2023 16:42:49 +0000 Subject: [PATCH 042/184] Update Slovenian translation --- locales/sl.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/locales/sl.json b/locales/sl.json index 47f295e0..ec1decaf 100644 --- a/locales/sl.json +++ b/locales/sl.json @@ -511,5 +511,8 @@ "channel_tab_streams_label": "Prenosi v živo", "Artist: ": "Umetnik/ca: ", "Music in this video": "Glasba v tem videoposnetku", - "Album: ": "Album: " + "Album: ": "Album: ", + "Song: ": "Pesem: ", + "Standard YouTube license": "Standardna licenca YouTube", + "Channel Sponsor": "Sponzor kanala" } From 231fb3481efda5a2e9b394dbe0cb35f3f9ae5793 Mon Sep 17 00:00:00 2001 From: maboroshin Date: Wed, 29 Mar 2023 01:13:39 +0000 Subject: [PATCH 043/184] Update Japanese translation --- locales/ja.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/ja.json b/locales/ja.json index 8a4537d4..d1813bcd 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -465,5 +465,6 @@ "Artist: ": "アーティスト: ", "Album: ": "アルバム: ", "Song: ": "曲: ", - "Channel Sponsor": "チャンネルのスポンサー" + "Channel Sponsor": "チャンネルのスポンサー", + "Standard YouTube license": "標準 Youtube ライセンス" } From d5a516d76cae1ec0b9bb89cb55b0fb884437b8a4 Mon Sep 17 00:00:00 2001 From: victor dargallo Date: Wed, 29 Mar 2023 20:35:14 +0000 Subject: [PATCH 044/184] Update Catalan translation --- locales/ca.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/ca.json b/locales/ca.json index 59396c11..efc8cc8a 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -75,7 +75,7 @@ "Title": "Títol", "Belarusian": "Bielorús", "Enable web notifications": "Activa notificacions web", - "search": "cerca", + "search": "Cerca", "Catalan": "Català", "Croatian": "Croat", "preferences_category_admin": "Preferències d'administrador", From 66e671237f74da17d251f4951e902fd211a484aa Mon Sep 17 00:00:00 2001 From: gallegonovato Date: Sat, 1 Apr 2023 10:56:05 +0000 Subject: [PATCH 045/184] Update Spanish translation --- locales/es.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/es.json b/locales/es.json index 4f9250d4..af82b2a3 100644 --- a/locales/es.json +++ b/locales/es.json @@ -498,5 +498,6 @@ "Album: ": "Álbum: ", "Song: ": "Canción: ", "Channel Sponsor": "Patrocinador del canal", - "Standard YouTube license": "Licencia de YouTube estándar" + "Standard YouTube license": "Licencia de YouTube estándar", + "Download is disabled": "La descarga está deshabilitada" } From d8337252a86dd0ad508a4d10cd6d68a9468f7c99 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Fri, 31 Mar 2023 21:24:47 +0000 Subject: [PATCH 046/184] Update Ukrainian translation --- locales/uk.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/uk.json b/locales/uk.json index e9c90287..61bf3d31 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -498,5 +498,6 @@ "Album: ": "Альбом: ", "Song: ": "Пісня: ", "Channel Sponsor": "Спонсор каналу", - "Standard YouTube license": "Стандартна ліцензія YouTube" + "Standard YouTube license": "Стандартна ліцензія YouTube", + "Download is disabled": "Завантаження вимкнено" } From 9d52ddbf8decbec7cd2a34c75917ba08b46786d8 Mon Sep 17 00:00:00 2001 From: Eric Date: Sat, 1 Apr 2023 04:06:41 +0000 Subject: [PATCH 047/184] Update Chinese (Simplified) translation --- locales/zh-CN.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/zh-CN.json b/locales/zh-CN.json index 634175cb..df31812a 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -466,5 +466,6 @@ "channel_tab_channels_label": "频道", "Song: ": "歌曲: ", "Channel Sponsor": "频道赞助者", - "Standard YouTube license": "标准 YouTube 许可证" + "Standard YouTube license": "标准 YouTube 许可证", + "Download is disabled": "已禁用下载" } From 657486c19ade93dd2ce45ea6b9fd68e3a5445cf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Fri, 31 Mar 2023 20:56:20 +0000 Subject: [PATCH 048/184] Update Turkish translation --- locales/tr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/tr.json b/locales/tr.json index c5381f4c..a2fdd573 100644 --- a/locales/tr.json +++ b/locales/tr.json @@ -482,5 +482,6 @@ "Artist: ": "Sanatçı: ", "Channel Sponsor": "Kanal Sponsoru", "Song: ": "Şarkı: ", - "Standard YouTube license": "Standart YouTube lisansı" + "Standard YouTube license": "Standart YouTube lisansı", + "Download is disabled": "İndirme devre dışı" } From d857ee5a7ca2082ab15be24130a14575f2e7485f Mon Sep 17 00:00:00 2001 From: Rex_sa Date: Sun, 2 Apr 2023 01:02:29 +0000 Subject: [PATCH 049/184] Update Arabic translation --- locales/ar.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/ar.json b/locales/ar.json index fb9e7564..7303915b 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -546,5 +546,6 @@ "Artist: ": "الفنان: ", "Song: ": "أغنية: ", "Channel Sponsor": "راعي القناة", - "Standard YouTube license": "ترخيص YouTube القياسي" + "Standard YouTube license": "ترخيص YouTube القياسي", + "Download is disabled": "تم تعطيل التحميلات" } From c60c14851b5f950d49958fe5bf5fad6262662a91 Mon Sep 17 00:00:00 2001 From: Jorge Maldonado Ventura Date: Sun, 2 Apr 2023 19:53:43 +0000 Subject: [PATCH 050/184] Update Esperanto translation --- locales/eo.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/eo.json b/locales/eo.json index a70b71d3..464d16ca 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -482,5 +482,6 @@ "Album: ": "Albumo: ", "Channel Sponsor": "Kanala sponsoro", "Song: ": "Muzikaĵo: ", - "Standard YouTube license": "Implicita YouTube-licenco" + "Standard YouTube license": "Implicita YouTube-licenco", + "Download is disabled": "Elŝuto estas malebligita" } From 4c541489dd4dae5e5cb0761d3c993d6c40a24357 Mon Sep 17 00:00:00 2001 From: abyan akhtar Date: Sun, 2 Apr 2023 04:31:13 +0000 Subject: [PATCH 051/184] Update Indonesian translation --- locales/id.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/id.json b/locales/id.json index 51d6d55c..f0adfdb1 100644 --- a/locales/id.json +++ b/locales/id.json @@ -453,5 +453,6 @@ "crash_page_switch_instance": "mencoba untuk menggunakan peladen lainnya", "crash_page_read_the_faq": "baca Soal Sering Ditanya (SSD/FAQ)", "crash_page_search_issue": "mencari isu yang ada di GitHub", - "crash_page_report_issue": "Jika yang di atas tidak membantu, buka isu baru di GitHub (sebaiknya dalam bahasa Inggris) dan sertakan teks berikut dalam pesan Anda (JANGAN terjemahkan teks tersebut):" + "crash_page_report_issue": "Jika yang di atas tidak membantu, buka isu baru di GitHub (sebaiknya dalam bahasa Inggris) dan sertakan teks berikut dalam pesan Anda (JANGAN terjemahkan teks tersebut):", + "Popular enabled: ": "Populer diaktifkan: " } From f81bc96da08494263c187e37ed1a3bb39b570d95 Mon Sep 17 00:00:00 2001 From: Milo Ivir Date: Mon, 3 Apr 2023 16:37:54 +0000 Subject: [PATCH 052/184] Update Croatian translation --- locales/hr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/hr.json b/locales/hr.json index bbd899c9..b87a7729 100644 --- a/locales/hr.json +++ b/locales/hr.json @@ -498,5 +498,6 @@ "Artist: ": "Izvođač: ", "Channel Sponsor": "Sponzor kanala", "Song: ": "Pjesma: ", - "Standard YouTube license": "Standardna YouTube licenca" + "Standard YouTube license": "Standardna YouTube licenca", + "Download is disabled": "Preuzimanje je deaktivirano" } From e6ba3e3dab4ddf3034597ce03fc9f7cf421f3674 Mon Sep 17 00:00:00 2001 From: Fjuro Date: Mon, 3 Apr 2023 20:35:59 +0000 Subject: [PATCH 053/184] Update Czech translation --- locales/cs.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/cs.json b/locales/cs.json index 38c3a8d2..0e8610bf 100644 --- a/locales/cs.json +++ b/locales/cs.json @@ -498,5 +498,6 @@ "Album: ": "Album: ", "Channel Sponsor": "Sponzor kanálu", "Song: ": "Skladba: ", - "Standard YouTube license": "Standardní licence YouTube" + "Standard YouTube license": "Standardní licence YouTube", + "Download is disabled": "Stahování je zakázáno" } From cb0e837a5ec7615e521ba86866f5e313583aa6a3 Mon Sep 17 00:00:00 2001 From: victor dargallo Date: Wed, 5 Apr 2023 13:55:57 +0000 Subject: [PATCH 054/184] Update Catalan translation --- locales/ca.json | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/locales/ca.json b/locales/ca.json index efc8cc8a..901249ac 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -122,8 +122,8 @@ "search_filters_features_option_location": "Ubicació", "search_filters_apply_button": "Aplica els filtres seleccionats", "videoinfo_started_streaming_x_ago": "Ha començat el directe fa `x`", - "next_steps_error_message_go_to_youtube": "Anar a YouTube", - "footer_donate_page": "Donar", + "next_steps_error_message_go_to_youtube": "Vés a YouTube", + "footer_donate_page": "Feu un donatiu", "footer_original_source_code": "Codi font original", "videoinfo_watch_on_youTube": "Veure a YouTube", "user_saved_playlists": "`x` llistes de reproducció guardades", @@ -164,7 +164,7 @@ "crash_page_report_issue": "Si cap de les anteriors no ha ajudat, obre un nou issue a GitHub (preferiblement en anglès) i inclou el text següent al missatge (NO tradueixis aquest text):", "generic_subscriptions_count": "{{count}} subscripció", "generic_subscriptions_count_plural": "{{count}} subscripcions", - "error_video_not_in_playlist": "El vídeo sol·licitat no existeix en aquesta llista de reproducció. Fes clic aquí per a la pàgina d'inici de la llista de reproducció.", + "error_video_not_in_playlist": "El vídeo sol·licitat no existeix en aquesta llista de reproducció. Feu clic aquí per a la pàgina d'inici de la llista de reproducció.", "comments_points_count": "{{count}} punt", "comments_points_count_plural": "{{count}} punts", "%A %B %-d, %Y": "%A %B %-d, %Y", @@ -175,7 +175,7 @@ "preferences_unseen_only_label": "Mostra només no vistos: ", "preferences_listen_label": "Escolta per defecte: ", "Import": "Importar", - "Token": "Senyal", + "Token": "Testimoni", "Wilson score: ": "Puntuació de Wilson: ", "search_filters_date_label": "Data de càrrega", "search_filters_features_option_three_sixty": "360°", @@ -184,10 +184,10 @@ "preferences_comments_label": "Comentaris per defecte: ", "`x` uploaded a video": "`x` ha penjat un vídeo", "Released under the AGPLv3 on Github.": "Publicat sota l'AGPLv3 a GitHub.", - "Token manager": "Gestor de tokens", + "Token manager": "Gestor de testimonis", "Watch history": "Historial de reproduccions", "Cannot change password for Google accounts": "No es pot canviar la contrasenya dels comptes de Google", - "Authorize token?": "Autoritzar senyal?", + "Authorize token?": "Autoritzar testimoni?", "Source available here.": "Font disponible aquí.", "Export subscriptions as OPML (for NewPipe & FreeTube)": "Exporta subscripcions com a OPML (per a NewPipe i FreeTube)", "Log in": "Inicia sessió", @@ -197,7 +197,7 @@ "Public": "Públic", "View all playlists": "Veure totes les llistes de reproducció", "reddit": "Reddit", - "Manage tokens": "Gestiona senyals", + "Manage tokens": "Gestiona testimonis", "Not a playlist.": "No és una llista de reproducció.", "preferences_local_label": "Vídeos de Proxy: ", "View channel on YouTube": "Veure canal a Youtube", @@ -272,7 +272,7 @@ "Khmer": "Khmer", "This channel does not exist.": "Aquest canal no existeix.", "Song: ": "Cançó: ", - "Login failed. This may be because two-factor authentication is not turned on for your account.": "Error a l'iniciar sessió. Això pot ser perquè l'autenticació de dos factors no està activada per al vostre compte.", + "Login failed. This may be because two-factor authentication is not turned on for your account.": "S'ha produït un error en iniciar sessió. Això pot ser perquè l'autenticació de dos factors no està activada per al vostre compte.", "channel:`x`": "canal: `x`", "Deleted or invalid channel": "Canal suprimit o no vàlid", "Could not get channel info.": "No s'ha pogut obtenir la informació del canal.", @@ -298,10 +298,10 @@ "generic_views_count_plural": "{{count}} visualitzacions", "generic_videos_count": "{{count}} vídeo", "generic_videos_count_plural": "{{count}} vídeos", - "Token is expired, please try again": "La senyal ha caducat, torna-ho a provar", + "Token is expired, please try again": "El testimoni ha caducat, torna-ho a provar", "English": "Anglès", "Kannada": "Kanarès", - "Erroneous token": "Senyal errònia", + "Erroneous token": "Testimoni erroni", "`x` ago": "fa `x`", "Empty playlist": "Llista de reproducció buida", "Playlist does not exist.": "La llista de reproducció no existeix.", @@ -376,7 +376,7 @@ "Clear watch history": "Neteja l'historial de reproduccions", "Mongolian": "Mongol", "preferences_quality_dash_option_best": "Millor", - "Authorize token for `x`?": "Autoritzar senyal per a `x`?", + "Authorize token for `x`?": "Autoritzar testimoni per a `x`?", "Report statistics: ": "Estadístiques de l'informe: ", "Switch Invidious Instance": "Canvia la instància d'Invidious", "History": "Historial", @@ -410,7 +410,7 @@ "Export": "Exportar", "preferences_quality_dash_option_4320p": "4320p", "JavaScript license information": "Informació de la llicència de JavaScript", - "Hidden field \"token\" is a required field": "El camp ocult \"senyal\" és un camp obligatori", + "Hidden field \"token\" is a required field": "El camp ocult \"testimoni\" és un camp obligatori", "Shona": "Xona", "Family friendly? ": "Apte per a tots els públics? ", "preferences_quality_dash_label": "Qualitat de vídeo DASH preferida: ", @@ -443,7 +443,7 @@ "unsubscribe": "cancel·la la subscripció", "View playlist on YouTube": "Veure llista de reproducció a YouTube", "Import NewPipe subscriptions (.json)": "Importar subscripcions de NewPipe (.json)", - "crash_page_you_found_a_bug": "Sembla que has trobat un error a Invidious!", + "crash_page_you_found_a_bug": "Heu trobat un error a Invidious!", "Subscribe": "Subscriu-me", "Quota exceeded, try again in a few hours": "S'ha superat la quota, torna-ho a provar d'aquí a unes hores", "generic_count_days": "{{count}} dia", @@ -468,8 +468,8 @@ "revoke": "revocar", "English (United Kingdom)": "Anglès (Regne Unit)", "preferences_quality_option_hd720": "HD720", - "tokens_count": "{{count}} senyal", - "tokens_count_plural": "{{count}} senyals", + "tokens_count": "{{count}} testimoni", + "tokens_count_plural": "{{count}} testimonis", "subscriptions_unseen_notifs_count": "{{count}} notificació no vista", "subscriptions_unseen_notifs_count_plural": "{{count}} notificacions no vistes", "generic_subscribers_count": "{{count}} subscriptor", @@ -482,5 +482,6 @@ "preferences_max_results_label": "Nombre de vídeos mostrats al feed: ", "Engagement: ": "Atracció: ", "Redirect homepage to feed: ": "Redirigeix la pàgina d'inici al feed: ", - "Standard YouTube license": "Llicència estàndard de YouTube" + "Standard YouTube license": "Llicència estàndard de YouTube", + "Download is disabled": "Les baixades s'han inhabilitat" } From 6667bdcd92c998abc6aa594a79e6a4f164da98fd Mon Sep 17 00:00:00 2001 From: Damjan Gerl Date: Tue, 4 Apr 2023 12:16:45 +0000 Subject: [PATCH 055/184] Update Slovenian translation --- locales/sl.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/sl.json b/locales/sl.json index ec1decaf..410b432c 100644 --- a/locales/sl.json +++ b/locales/sl.json @@ -514,5 +514,6 @@ "Album: ": "Album: ", "Song: ": "Pesem: ", "Standard YouTube license": "Standardna licenca YouTube", - "Channel Sponsor": "Sponzor kanala" + "Channel Sponsor": "Sponzor kanala", + "Download is disabled": "Prenos je onemogočen" } From 919997e41c753ffec015783851abf6170d29dd9b Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Fri, 7 Apr 2023 02:25:36 +0000 Subject: [PATCH 056/184] Update Chinese (Traditional) translation --- locales/zh-TW.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/zh-TW.json b/locales/zh-TW.json index 4100931c..daa22493 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -466,5 +466,6 @@ "Music in this video": "此影片中的音樂", "Channel Sponsor": "頻道贊助者", "Song: ": "歌曲: ", - "Standard YouTube license": "標準 YouTube 授權條款" + "Standard YouTube license": "標準 YouTube 授權條款", + "Download is disabled": "已停用下載" } From 72f83d4aa2004f3e43947622b2b5ac01601c9859 Mon Sep 17 00:00:00 2001 From: Andrey Date: Sat, 8 Apr 2023 08:18:06 +0000 Subject: [PATCH 057/184] Update Russian translation --- locales/ru.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/ru.json b/locales/ru.json index d2d7c86d..33a7931e 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -498,5 +498,6 @@ "Album: ": "Альбом: ", "Song: ": "Композиция: ", "Standard YouTube license": "Стандартная лицензия YouTube", - "Channel Sponsor": "Спонсор канала" + "Channel Sponsor": "Спонсор канала", + "Download is disabled": "Загрузка отключена" } From b9932b113bc5dedb924afdb3558151c6bd5a6347 Mon Sep 17 00:00:00 2001 From: atilluF Date: Sat, 8 Apr 2023 13:15:43 +0000 Subject: [PATCH 058/184] Update Italian translation --- locales/it.json | 86 ++++++++++++++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 33 deletions(-) diff --git a/locales/it.json b/locales/it.json index c60f760b..5991304e 100644 --- a/locales/it.json +++ b/locales/it.json @@ -1,10 +1,13 @@ { - "generic_subscribers_count": "{{count}} iscritto", - "generic_subscribers_count_plural": "{{count}} iscritti", - "generic_videos_count": "{{count}} video", - "generic_videos_count_plural": "{{count}} video", - "generic_playlists_count": "{{count}} playlist", - "generic_playlists_count_plural": "{{count}} playlist", + "generic_subscribers_count_0": "{{count}} iscritto", + "generic_subscribers_count_1": "{{count}} iscritti", + "generic_subscribers_count_2": "{{count}} iscritti", + "generic_videos_count_0": "{{count}} video", + "generic_videos_count_1": "{{count}} video", + "generic_videos_count_2": "{{count}} video", + "generic_playlists_count_0": "{{count}} playlist", + "generic_playlists_count_1": "{{count}} playlist", + "generic_playlists_count_2": "{{count}} playlist", "LIVE": "IN DIRETTA", "Shared `x` ago": "Condiviso `x` fa", "Unsubscribe": "Disiscriviti", @@ -116,16 +119,19 @@ "Subscription manager": "Gestione delle iscrizioni", "Token manager": "Gestione dei gettoni", "Token": "Gettone", - "generic_subscriptions_count": "{{count}} iscrizione", - "generic_subscriptions_count_plural": "{{count}} iscrizioni", - "tokens_count": "{{count}} gettone", - "tokens_count_plural": "{{count}} gettoni", + "generic_subscriptions_count_0": "{{count}} iscrizione", + "generic_subscriptions_count_1": "{{count}} iscrizioni", + "generic_subscriptions_count_2": "{{count}} iscrizioni", + "tokens_count_0": "{{count}} gettone", + "tokens_count_1": "{{count}} gettoni", + "tokens_count_2": "{{count}} gettoni", "Import/export": "Importa/esporta", "unsubscribe": "disiscriviti", "revoke": "revoca", "Subscriptions": "Iscrizioni", - "subscriptions_unseen_notifs_count": "{{count}} notifica non visualizzata", - "subscriptions_unseen_notifs_count_plural": "{{count}} notifiche non visualizzate", + "subscriptions_unseen_notifs_count_0": "{{count}} notifica non visualizzata", + "subscriptions_unseen_notifs_count_1": "{{count}} notifiche non visualizzate", + "subscriptions_unseen_notifs_count_2": "{{count}} notifiche non visualizzate", "search": "Cerca", "Log out": "Esci", "Source available here.": "Codice sorgente.", @@ -154,8 +160,9 @@ "Whitelisted regions: ": "Regioni in lista bianca: ", "Blacklisted regions: ": "Regioni in lista nera: ", "Shared `x`": "Condiviso `x`", - "generic_views_count": "{{count}} visualizzazione", - "generic_views_count_plural": "{{count}} visualizzazioni", + "generic_views_count_0": "{{count}} visualizzazione", + "generic_views_count_1": "{{count}} visualizzazioni", + "generic_views_count_2": "{{count}} visualizzazioni", "Premieres in `x`": "In anteprima in `x`", "Premieres `x`": "In anteprima `x`", "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Ciao, Sembra che tu abbia disattivato JavaScript. Clicca qui per visualizzare i commenti, ma considera che il caricamento potrebbe richiedere più tempo.", @@ -308,20 +315,27 @@ "Yiddish": "Yiddish", "Yoruba": "Yoruba", "Zulu": "Zulu", - "generic_count_years": "{{count}} anno", - "generic_count_years_plural": "{{count}} anni", - "generic_count_months": "{{count}} mese", - "generic_count_months_plural": "{{count}} mesi", - "generic_count_weeks": "{{count}} settimana", - "generic_count_weeks_plural": "{{count}} settimane", - "generic_count_days": "{{count}} giorno", - "generic_count_days_plural": "{{count}} giorni", - "generic_count_hours": "{{count}} ora", - "generic_count_hours_plural": "{{count}} ore", - "generic_count_minutes": "{{count}} minuto", - "generic_count_minutes_plural": "{{count}} minuti", - "generic_count_seconds": "{{count}} secondo", - "generic_count_seconds_plural": "{{count}} secondi", + "generic_count_years_0": "{{count}} anno", + "generic_count_years_1": "{{count}} anni", + "generic_count_years_2": "{{count}} anni", + "generic_count_months_0": "{{count}} mese", + "generic_count_months_1": "{{count}} mesi", + "generic_count_months_2": "{{count}} mesi", + "generic_count_weeks_0": "{{count}} settimana", + "generic_count_weeks_1": "{{count}} settimane", + "generic_count_weeks_2": "{{count}} settimane", + "generic_count_days_0": "{{count}} giorno", + "generic_count_days_1": "{{count}} giorni", + "generic_count_days_2": "{{count}} giorni", + "generic_count_hours_0": "{{count}} ora", + "generic_count_hours_1": "{{count}} ore", + "generic_count_hours_2": "{{count}} ore", + "generic_count_minutes_0": "{{count}} minuto", + "generic_count_minutes_1": "{{count}} minuti", + "generic_count_minutes_2": "{{count}} minuti", + "generic_count_seconds_0": "{{count}} secondo", + "generic_count_seconds_1": "{{count}} secondi", + "generic_count_seconds_2": "{{count}} secondi", "Fallback comments: ": "Commenti alternativi: ", "Popular": "Popolare", "Search": "Cerca", @@ -425,10 +439,12 @@ "search_filters_duration_option_short": "Corto (< 4 minuti)", "search_filters_duration_option_long": "Lungo (> 20 minuti)", "search_filters_features_option_purchased": "Acquistato", - "comments_view_x_replies": "Vedi {{count}} risposta", - "comments_view_x_replies_plural": "Vedi {{count}} risposte", - "comments_points_count": "{{count}} punto", - "comments_points_count_plural": "{{count}} punti", + "comments_view_x_replies_0": "Vedi {{count}} risposta", + "comments_view_x_replies_1": "Vedi {{count}} risposte", + "comments_view_x_replies_2": "Vedi {{count}} risposte", + "comments_points_count_0": "{{count}} punto", + "comments_points_count_1": "{{count}} punti", + "comments_points_count_2": "{{count}} punti", "Portuguese (auto-generated)": "Portoghese (generati automaticamente)", "crash_page_you_found_a_bug": "Sembra che tu abbia trovato un bug in Invidious!", "crash_page_switch_instance": "provato a usare un'altra istanza", @@ -479,5 +495,9 @@ "channel_tab_community_label": "Comunità", "Music in this video": "Musica in questo video", "Artist: ": "Artista: ", - "Album: ": "Album: " + "Album: ": "Album: ", + "Download is disabled": "Il download è disabilitato", + "Song: ": "Canzone: ", + "Standard YouTube license": "Licenza standard di YouTube", + "Channel Sponsor": "Sponsor del canale" } From 7d48b961733714b55f2d8d25a0bd254665176089 Mon Sep 17 00:00:00 2001 From: Ernestas Date: Sun, 9 Apr 2023 21:33:21 +0000 Subject: [PATCH 059/184] Update Lithuanian translation --- locales/lt.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/lt.json b/locales/lt.json index 9bfcfdba..91c7febe 100644 --- a/locales/lt.json +++ b/locales/lt.json @@ -488,5 +488,6 @@ "preferences_save_player_pos_label": "Išsaugoti atkūrimo padėtį: ", "videoinfo_youTube_embed_link": "Įterpti", "videoinfo_invidious_embed_link": "Įterpti nuorodą", - "crash_page_refresh": "pabandėte atnaujinti puslapį" + "crash_page_refresh": "pabandėte atnaujinti puslapį", + "Album: ": "Albumas " } From 346f32855a704efc23e46013376435cecc5f0462 Mon Sep 17 00:00:00 2001 From: SC Date: Mon, 10 Apr 2023 14:45:36 +0000 Subject: [PATCH 060/184] Update Portuguese translation --- locales/pt.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/pt.json b/locales/pt.json index 79d8f354..cbce0e5a 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -482,5 +482,6 @@ "Album: ": "Álbum: ", "Song: ": "Canção: ", "Channel Sponsor": "Patrocinador do canal", - "Standard YouTube license": "Licença padrão do YouTube" + "Standard YouTube license": "Licença padrão do YouTube", + "Download is disabled": "A descarga está desativada" } From 14053821ac7ac48f4ff276fbd12e3d29c4a2a917 Mon Sep 17 00:00:00 2001 From: AHOHNMYC Date: Sun, 16 Apr 2023 03:54:12 +0000 Subject: [PATCH 061/184] Update Russian translation --- locales/ru.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/locales/ru.json b/locales/ru.json index 33a7931e..4ce82bff 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -69,11 +69,11 @@ "preferences_vr_mode_label": "Интерактивные 360-градусные видео (необходим WebGL): ", "preferences_category_visual": "Настройки сайта", "preferences_player_style_label": "Стиль проигрывателя: ", - "Dark mode: ": "Темное оформление: ", + "Dark mode: ": "Тёмное оформление: ", "preferences_dark_mode_label": "Тема: ", - "dark": "темная", + "dark": "тёмная", "light": "светлая", - "preferences_thin_mode_label": "Облегченное оформление: ", + "preferences_thin_mode_label": "Облегчённое оформление: ", "preferences_category_misc": "Прочие настройки", "preferences_automatic_instance_redirect_label": "Автоматическая смена зеркала (переход на redirect.invidious.io): ", "preferences_category_subscription": "Настройки подписок", @@ -147,13 +147,13 @@ "License: ": "Лицензия: ", "Family friendly? ": "Семейный просмотр: ", "Wilson score: ": "Оценка Уилсона: ", - "Engagement: ": "Вовлеченность: ", + "Engagement: ": "Вовлечённость: ", "Whitelisted regions: ": "Доступно в регионах: ", "Blacklisted regions: ": "Недоступно в регионах: ", "Shared `x`": "Опубликовано `x`", "Premieres in `x`": "Премьера через `x`", "Premieres `x`": "Премьера `x`", - "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Похоже, у вас отключен JavaScript. Нажмите сюда, чтобы увидеть комментарии. Но учтите: они могут загружаться немного медленнее.", + "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Похоже, у вас отключён JavaScript. Нажмите сюда, чтобы увидеть комментарии. Но учтите: они могут загружаться немного медленнее.", "View YouTube comments": "Показать комментарии с YouTube", "View more comments on Reddit": "Посмотреть больше комментариев на Reddit", "View `x` comments": { @@ -180,23 +180,23 @@ "Please log in": "Пожалуйста, войдите", "Invidious Private Feed for `x`": "Приватная лента Invidious для `x`", "channel:`x`": "канал: `x`", - "Deleted or invalid channel": "Канал удален или не найден", + "Deleted or invalid channel": "Канал удалён или не найден", "This channel does not exist.": "Такого канала не существует.", - "Could not get channel info.": "Не удается получить информацию об этом канале.", - "Could not fetch comments": "Не удается загрузить комментарии", + "Could not get channel info.": "Не удаётся получить информацию об этом канале.", + "Could not fetch comments": "Не удаётся загрузить комментарии", "`x` ago": "`x` назад", - "Load more": "Загрузить еще", + "Load more": "Загрузить ещё", "Could not create mix.": "Не удалось создать микс.", "Empty playlist": "Плейлист пуст", "Not a playlist.": "Это не плейлист.", "Playlist does not exist.": "Плейлист не существует.", - "Could not pull trending pages.": "Не удается загрузить страницы «в тренде».", + "Could not pull trending pages.": "Не удаётся загрузить страницы «в тренде».", "Hidden field \"challenge\" is a required field": "Необходимо заполнить скрытое поле «challenge»", "Hidden field \"token\" is a required field": "Необходимо заполнить скрытое поле «токен»", "Erroneous challenge": "Неправильный ответ в «challenge»", "Erroneous token": "Неправильный токен", "No such user": "Пользователь не найден", - "Token is expired, please try again": "Срок действия токена истек, попробуйте позже", + "Token is expired, please try again": "Срок действия токена истёк, попробуйте позже", "English": "Английский", "English (auto-generated)": "Английский (созданы автоматически)", "Afrikaans": "Африкаанс", @@ -379,7 +379,7 @@ "Turkish (auto-generated)": "Турецкий (созданы автоматически)", "Vietnamese (auto-generated)": "Вьетнамский (созданы автоматически)", "footer_documentation": "Документация", - "adminprefs_modified_source_code_url_label": "URL-адрес репозитория измененного исходного кода", + "adminprefs_modified_source_code_url_label": "Ссылка на репозиторий с измененными исходными кодами", "none": "ничего", "videoinfo_watch_on_youTube": "Смотреть на YouTube", "videoinfo_youTube_embed_link": "Версия для встраивания", @@ -453,8 +453,8 @@ "Portuguese (Brazil)": "Португальский (Бразилия)", "footer_source_code": "Исходный код", "footer_original_source_code": "Оригинальный исходный код", - "footer_modfied_source_code": "Измененный исходный код", - "user_saved_playlists": "`x` сохраненных плейлистов", + "footer_modfied_source_code": "Изменённый исходный код", + "user_saved_playlists": "`x` сохранённых плейлистов", "crash_page_search_issue": "поискали похожую проблему на GitHub", "comments_points_count_0": "{{count}} плюс", "comments_points_count_1": "{{count}} плюса", From 732fb7c499e3b3a9b5fcbd759d7d6edcc67d7772 Mon Sep 17 00:00:00 2001 From: John Donne Date: Sun, 16 Apr 2023 18:31:55 +0000 Subject: [PATCH 062/184] Update French translation --- locales/fr.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index 9d3e117f..d1093868 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -476,5 +476,7 @@ "channel_tab_shorts_label": "Clips", "channel_tab_streams_label": "En direct", "channel_tab_playlists_label": "Listes de lecture", - "channel_tab_channels_label": "Chaînes" + "channel_tab_channels_label": "Chaînes", + "Song: ": "Chanson : ", + "Artist: ": "Artiste : " } From 1f12323ee6ee35c90a9c9b145195b26c35b6321c Mon Sep 17 00:00:00 2001 From: Nicolas Dommanget-Muller Date: Sun, 16 Apr 2023 18:48:36 +0000 Subject: [PATCH 063/184] Update French translation --- locales/fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/fr.json b/locales/fr.json index d1093868..908ff5eb 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -478,5 +478,6 @@ "channel_tab_playlists_label": "Listes de lecture", "channel_tab_channels_label": "Chaînes", "Song: ": "Chanson : ", - "Artist: ": "Artiste : " + "Artist: ": "Artiste : ", + "Album: ": "Album: " } From 49e04192c04de8c0501c336d6bf6ef331ca193a4 Mon Sep 17 00:00:00 2001 From: John Donne Date: Sun, 16 Apr 2023 18:51:37 +0000 Subject: [PATCH 064/184] Update French translation --- locales/fr.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/locales/fr.json b/locales/fr.json index 908ff5eb..bb40916b 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -474,10 +474,14 @@ "search_filters_duration_option_none": "Toutes les durées", "error_video_not_in_playlist": "La vidéo demandée n'existe pas dans cette liste de lecture. Cliquez ici pour retourner à la liste de lecture.", "channel_tab_shorts_label": "Clips", - "channel_tab_streams_label": "En direct", + "channel_tab_streams_label": "Vidéos en direct", "channel_tab_playlists_label": "Listes de lecture", "channel_tab_channels_label": "Chaînes", "Song: ": "Chanson : ", "Artist: ": "Artiste : ", - "Album: ": "Album: " + "Album: ": "Album : ", + "Standard YouTube license": "Licence YouTube Standard", + "Music in this video": "Musique dans cette vidéo", + "Channel Sponsor": "Soutien de la chaîne", + "Download is disabled": "Le téléchargement est désactivé" } From e6471feadc35674b8e987e645d3f52296c779a9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9A=D0=BE=D1=82=D0=BB?= =?UTF-8?q?=D1=83=D0=B1=D0=B0=D0=B9?= Date: Sun, 23 Apr 2023 09:02:34 +0000 Subject: [PATCH 065/184] Update Russian translation --- locales/ru.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/locales/ru.json b/locales/ru.json index 4ce82bff..1bece168 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -14,7 +14,7 @@ "Clear watch history?": "Очистить историю просмотров?", "New password": "Новый пароль", "New passwords must match": "Новые пароли не совпадают", - "Cannot change password for Google accounts": "Изменить пароль аккаунта Google невозможно", + "Cannot change password for Google accounts": "Изменить пароль учётной записи Google невозможно", "Authorize token?": "Авторизовать токен?", "Authorize token for `x`?": "Авторизовать токен для `x`?", "Yes": "Да", @@ -30,7 +30,7 @@ "Export subscriptions as OPML": "Экспортировать подписки в формате OPML", "Export subscriptions as OPML (for NewPipe & FreeTube)": "Экспортировать подписки в формате OPML (для NewPipe и FreeTube)", "Export data as JSON": "Экспортировать данные Invidious в формате JSON", - "Delete account?": "Удалить аккаунт?", + "Delete account?": "Удалить учётку?", "History": "История", "An alternative front-end to YouTube": "Альтернативный фронтенд для YouTube", "JavaScript license information": "Информация о лицензиях JavaScript", @@ -38,14 +38,14 @@ "Log in": "Войти", "Log in/register": "Войти или зарегистрироваться", "Log in with Google": "Войти через Google", - "User ID": "ID пользователя", + "User ID": "ИД пользователя", "Password": "Пароль", "Time (h:mm:ss):": "Время (ч:мм:сс):", "Text CAPTCHA": "Текстовая капча (англ.)", "Image CAPTCHA": "Капча-картинка", "Sign In": "Войти", "Register": "Зарегистрироваться", - "E-mail": "Электронная почта", + "E-mail": "Эл. почта", "Google verification code": "Код подтверждения Google", "Preferences": "Настройки", "preferences_category_player": "Настройки проигрывателя", @@ -171,7 +171,7 @@ "Wrong answer": "Неправильный ответ", "Erroneous CAPTCHA": "Неправильная капча", "CAPTCHA is a required field": "Необходимо решить капчу", - "User ID is a required field": "Необходимо ввести ID пользователя", + "User ID is a required field": "Необходимо ввести идентификатор пользователя", "Password is a required field": "Необходимо ввести пароль", "Wrong username or password": "Неправильный логин или пароль", "Please sign in using 'Log in with Google'": "Пожалуйста, нажмите «Войти через Google»", @@ -213,7 +213,7 @@ "Burmese": "Бирманский", "Catalan": "Каталонский", "Cebuano": "Себуанский", - "Chinese (Simplified)": "Китайский (упрощенный)", + "Chinese (Simplified)": "Китайский (упрощённый)", "Chinese (Traditional)": "Китайский (традиционный)", "Corsican": "Корсиканский", "Croatian": "Хорватский", From 70a79f343dd771be0842b9aef97edd357750a6fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D1=91=D0=BC=20=D0=9A=D0=BE=D1=82=D0=BB?= =?UTF-8?q?=D1=83=D0=B1=D0=B0=D0=B9?= Date: Mon, 24 Apr 2023 16:34:03 +0000 Subject: [PATCH 066/184] Update Russian translation --- locales/ru.json | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/locales/ru.json b/locales/ru.json index 1bece168..0031f79a 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -4,7 +4,7 @@ "Unsubscribe": "Отписаться", "Subscribe": "Подписаться", "View channel on YouTube": "Смотреть канал на YouTube", - "View playlist on YouTube": "Посмотреть плейлист на YouTube", + "View playlist on YouTube": "Просмотреть подборку на ютубе", "newest": "сначала новые", "oldest": "сначала старые", "popular": "популярные", @@ -129,14 +129,14 @@ "Public": "Публичный", "Unlisted": "Нет в списке", "Private": "Приватный", - "View all playlists": "Посмотреть все плейлисты", + "View all playlists": "Просмотреть все подборки", "Updated `x` ago": "Обновлено `x` назад", - "Delete playlist `x`?": "Удалить плейлист `x`?", - "Delete playlist": "Удалить плейлист", - "Create playlist": "Создать плейлист", + "Delete playlist `x`?": "Удалить подборку `x`?", + "Delete playlist": "Удалить подборку", + "Create playlist": "Создать подборку", "Title": "Заголовок", - "Playlist privacy": "Видимость плейлиста", - "Editing playlist `x`": "Редактирование плейлиста `x`", + "Playlist privacy": "Видимость подборки", + "Editing playlist `x`": "Изменение подборки `x`", "Show more": "Развернуть", "Show less": "Свернуть", "Watch on YouTube": "Смотреть на YouTube", @@ -187,9 +187,9 @@ "`x` ago": "`x` назад", "Load more": "Загрузить ещё", "Could not create mix.": "Не удалось создать микс.", - "Empty playlist": "Плейлист пуст", - "Not a playlist.": "Это не плейлист.", - "Playlist does not exist.": "Плейлист не существует.", + "Empty playlist": "Подборка пуста", + "Not a playlist.": "Это не подборка.", + "Playlist does not exist.": "Подборка не существует.", "Could not pull trending pages.": "Не удаётся загрузить страницы «в тренде».", "Hidden field \"challenge\" is a required field": "Необходимо заполнить скрытое поле «challenge»", "Hidden field \"token\" is a required field": "Необходимо заполнить скрытое поле «токен»", @@ -310,7 +310,7 @@ "About": "О сайте", "Rating: ": "Рейтинг: ", "preferences_locale_label": "Язык: ", - "View as playlist": "Смотреть как плейлист", + "View as playlist": "Смотреть как подборку", "Default": "По умолчанию", "Music": "Музыка", "Gaming": "Игры", @@ -326,7 +326,7 @@ "Audio mode": "Аудио режим", "Video mode": "Видео режим", "channel_tab_videos_label": "Видео", - "Playlists": "Плейлисты", + "Playlists": "Подборки", "channel_tab_community_label": "Сообщество", "search_filters_sort_option_relevance": "по актуальности", "search_filters_sort_option_rating": "по рейтингу", @@ -343,7 +343,7 @@ "search_filters_date_option_year": "Этот год", "search_filters_type_option_video": "Видео", "search_filters_type_option_channel": "Канал", - "search_filters_type_option_playlist": "Плейлист", + "search_filters_type_option_playlist": "Подборка", "search_filters_type_option_movie": "Фильм", "search_filters_type_option_show": "Сериал", "search_filters_features_option_hd": "HD", @@ -385,7 +385,7 @@ "videoinfo_youTube_embed_link": "Версия для встраивания", "videoinfo_invidious_embed_link": "Ссылка для встраивания", "download_subtitles": "Субтитры - `x` (.vtt)", - "user_created_playlists": "`x` созданных плейлистов", + "user_created_playlists": "`x` созданных подборок", "crash_page_you_found_a_bug": "Похоже, вы нашли ошибку в Invidious!", "crash_page_before_reporting": "Прежде чем сообщать об ошибке, убедитесь, что вы:", "crash_page_refresh": "пробовали перезагрузить страницу", @@ -393,9 +393,9 @@ "generic_videos_count_0": "{{count}} видео", "generic_videos_count_1": "{{count}} видео", "generic_videos_count_2": "{{count}} видео", - "generic_playlists_count_0": "{{count}} плейлист", - "generic_playlists_count_1": "{{count}} плейлиста", - "generic_playlists_count_2": "{{count}} плейлистов", + "generic_playlists_count_0": "{{count}} подборка", + "generic_playlists_count_1": "{{count}} подборки", + "generic_playlists_count_2": "{{count}} подборок", "tokens_count_0": "{{count}} токен", "tokens_count_1": "{{count}} токена", "tokens_count_2": "{{count}} токенов", @@ -454,7 +454,7 @@ "footer_source_code": "Исходный код", "footer_original_source_code": "Оригинальный исходный код", "footer_modfied_source_code": "Изменённый исходный код", - "user_saved_playlists": "`x` сохранённых плейлистов", + "user_saved_playlists": "`x` сохранённых подборок", "crash_page_search_issue": "поискали похожую проблему на GitHub", "comments_points_count_0": "{{count}} плюс", "comments_points_count_1": "{{count}} плюса", @@ -488,8 +488,8 @@ "search_filters_duration_option_medium": "Средние (4 - 20 минут)", "search_filters_apply_button": "Применить фильтры", "Popular enabled: ": "Популярное включено: ", - "error_video_not_in_playlist": "Запрошенного видео нет в этом плейлисте. Нажмите тут, чтобы вернуться к странице плейлиста.", - "channel_tab_playlists_label": "Плейлисты", + "error_video_not_in_playlist": "Запрошенного видео нет в этой подборке. Нажмите тут, чтобы вернуться к странице подборки.", + "channel_tab_playlists_label": "Подборки", "channel_tab_channels_label": "Каналы", "channel_tab_streams_label": "Живое вещание", "channel_tab_shorts_label": "Shorts", From deed4d10f28574500891bc5c5892799a09cded6c Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Sun, 30 Apr 2023 19:31:59 +0200 Subject: [PATCH 067/184] Fix broken Spanish/Italian locales (i18next v3->v4 mixup) --- locales/es.json | 75 +++++++++++++++++++--------------------------- locales/it.json | 80 ++++++++++++++++++++----------------------------- 2 files changed, 62 insertions(+), 93 deletions(-) diff --git a/locales/es.json b/locales/es.json index af82b2a3..09f510a7 100644 --- a/locales/es.json +++ b/locales/es.json @@ -398,51 +398,37 @@ "search_filters_features_option_three_sixty": "360°", "videoinfo_watch_on_youTube": "Ver en YouTube", "preferences_save_player_pos_label": "Guardar posición de reproducción: ", - "generic_views_count_0": "{{count}} visualización", - "generic_views_count_1": "{{count}} visualizaciones", - "generic_views_count_2": "{{count}} visualizaciones", - "generic_subscribers_count_0": "{{count}} suscriptor", - "generic_subscribers_count_1": "{{count}} suscriptores", - "generic_subscribers_count_2": "{{count}} suscriptores", - "generic_subscriptions_count_0": "{{count}} suscripción", - "generic_subscriptions_count_1": "{{count}} suscripciones", - "generic_subscriptions_count_2": "{{count}} suscripciones", - "subscriptions_unseen_notifs_count_0": "{{count}} notificación no vista", - "subscriptions_unseen_notifs_count_1": "{{count}} notificaciones no vistas", - "subscriptions_unseen_notifs_count_2": "{{count}} notificaciones no vistas", - "generic_count_days_0": "{{count}} día", - "generic_count_days_1": "{{count}} días", - "generic_count_days_2": "{{count}} días", - "comments_view_x_replies_0": "Ver {{count}} respuesta", - "comments_view_x_replies_1": "Ver {{count}} respuestas", - "comments_view_x_replies_2": "Ver {{count}} respuestas", - "generic_count_weeks_0": "{{count}} semana", - "generic_count_weeks_1": "{{count}} semanas", - "generic_count_weeks_2": "{{count}} semanas", - "generic_playlists_count_0": "{{count}} lista de reproducción", - "generic_playlists_count_1": "{{count}} listas de reproducciones", - "generic_playlists_count_2": "{{count}} listas de reproducciones", + "generic_views_count": "{{count}} visualización", + "generic_views_count_plural": "{{count}} visualizaciones", + "generic_subscribers_count": "{{count}} suscriptor", + "generic_subscribers_count_plural": "{{count}} suscriptores", + "generic_subscriptions_count": "{{count}} suscripción", + "generic_subscriptions_count_plural": "{{count}} suscripciones", + "subscriptions_unseen_notifs_count": "{{count}} notificación no vista", + "subscriptions_unseen_notifs_count_plural": "{{count}} notificaciones no vistas", + "generic_count_days": "{{count}} día", + "generic_count_days_plural": "{{count}} días", + "comments_view_x_replies": "Ver {{count}} respuesta", + "comments_view_x_replies_plural": "Ver {{count}} respuestas", + "generic_count_weeks": "{{count}} semana", + "generic_count_weeks_plural": "{{count}} semanas", + "generic_playlists_count": "{{count}} lista de reproducción", + "generic_playlists_count_plural": "{{count}} listas de reproducción", "generic_videos_count_0": "{{count}} video", "generic_videos_count_1": "{{count}} videos", "generic_videos_count_2": "{{count}} videos", - "generic_count_months_0": "{{count}} mes", - "generic_count_months_1": "{{count}} meses", - "generic_count_months_2": "{{count}} meses", - "comments_points_count_0": "{{count}} punto", - "comments_points_count_1": "{{count}} puntos", - "comments_points_count_2": "{{count}} puntos", - "generic_count_years_0": "{{count}} año", - "generic_count_years_1": "{{count}} años", - "generic_count_years_2": "{{count}} años", - "generic_count_hours_0": "{{count}} hora", - "generic_count_hours_1": "{{count}} horas", - "generic_count_hours_2": "{{count}} horas", - "generic_count_minutes_0": "{{count}} minuto", - "generic_count_minutes_1": "{{count}} minutos", - "generic_count_minutes_2": "{{count}} minutos", - "generic_count_seconds_0": "{{count}} segundo", - "generic_count_seconds_1": "{{count}} segundos", - "generic_count_seconds_2": "{{count}} segundos", + "generic_count_months": "{{count}} mes", + "generic_count_months_plural": "{{count}} meses", + "comments_points_count": "{{count}} punto", + "comments_points_count_plural": "{{count}} puntos", + "generic_count_years": "{{count}} año", + "generic_count_years_plural": "{{count}} años", + "generic_count_hours": "{{count}} hora", + "generic_count_hours_plural": "{{count}} horas", + "generic_count_minutes": "{{count}} minuto", + "generic_count_minutes_plural": "{{count}} minutos", + "generic_count_seconds": "{{count}} segundo", + "generic_count_seconds_plural": "{{count}} segundos", "crash_page_before_reporting": "Antes de notificar un error asegúrate de que has:", "crash_page_switch_instance": "probado a usar otra instancia", "crash_page_read_the_faq": "leído las Preguntas Frecuentes", @@ -483,9 +469,8 @@ "search_filters_duration_option_none": "Cualquier duración", "search_filters_features_option_vr180": "VR180", "search_filters_apply_button": "Aplicar filtros", - "tokens_count_0": "{{count}} ficha", - "tokens_count_1": "{{count}} fichas", - "tokens_count_2": "{{count}} fichas", + "tokens_count": "{{count}} ficha", + "tokens_count_plural": "{{count}} fichas", "search_message_use_another_instance": " También puede buscar en otra instancia.", "Popular enabled: ": "¿Habilitar la sección popular? ", "error_video_not_in_playlist": "El video que solicitaste no existe en esta lista de reproducción. Haz clic aquí para acceder a la página de inicio de la lista de reproducción.", diff --git a/locales/it.json b/locales/it.json index 5991304e..0797b387 100644 --- a/locales/it.json +++ b/locales/it.json @@ -1,13 +1,10 @@ { - "generic_subscribers_count_0": "{{count}} iscritto", - "generic_subscribers_count_1": "{{count}} iscritti", - "generic_subscribers_count_2": "{{count}} iscritti", - "generic_videos_count_0": "{{count}} video", - "generic_videos_count_1": "{{count}} video", - "generic_videos_count_2": "{{count}} video", - "generic_playlists_count_0": "{{count}} playlist", - "generic_playlists_count_1": "{{count}} playlist", - "generic_playlists_count_2": "{{count}} playlist", + "generic_subscribers_count": "{{count}} iscritto", + "generic_subscribers_count_plural": "{{count}} iscritti", + "generic_videos_count": "{{count}} video", + "generic_videos_count_plural": "{{count}} video", + "generic_playlists_count": "{{count}} playlist", + "generic_playlists_count_plural": "{{count}} playlist", "LIVE": "IN DIRETTA", "Shared `x` ago": "Condiviso `x` fa", "Unsubscribe": "Disiscriviti", @@ -119,19 +116,16 @@ "Subscription manager": "Gestione delle iscrizioni", "Token manager": "Gestione dei gettoni", "Token": "Gettone", - "generic_subscriptions_count_0": "{{count}} iscrizione", - "generic_subscriptions_count_1": "{{count}} iscrizioni", - "generic_subscriptions_count_2": "{{count}} iscrizioni", - "tokens_count_0": "{{count}} gettone", - "tokens_count_1": "{{count}} gettoni", - "tokens_count_2": "{{count}} gettoni", + "generic_subscriptions_count": "{{count}} iscrizione", + "generic_subscriptions_count_plural": "{{count}} iscrizioni", + "tokens_count": "{{count}} gettone", + "tokens_count_plural": "{{count}} gettoni", "Import/export": "Importa/esporta", "unsubscribe": "disiscriviti", "revoke": "revoca", "Subscriptions": "Iscrizioni", - "subscriptions_unseen_notifs_count_0": "{{count}} notifica non visualizzata", - "subscriptions_unseen_notifs_count_1": "{{count}} notifiche non visualizzate", - "subscriptions_unseen_notifs_count_2": "{{count}} notifiche non visualizzate", + "subscriptions_unseen_notifs_count": "{{count}} notifica non visualizzata", + "subscriptions_unseen_notifs_count_plural": "{{count}} notifiche non visualizzate", "search": "Cerca", "Log out": "Esci", "Source available here.": "Codice sorgente.", @@ -160,9 +154,8 @@ "Whitelisted regions: ": "Regioni in lista bianca: ", "Blacklisted regions: ": "Regioni in lista nera: ", "Shared `x`": "Condiviso `x`", - "generic_views_count_0": "{{count}} visualizzazione", - "generic_views_count_1": "{{count}} visualizzazioni", - "generic_views_count_2": "{{count}} visualizzazioni", + "generic_views_count": "{{count}} visualizzazione", + "generic_views_count_plural": "{{count}} visualizzazioni", "Premieres in `x`": "In anteprima in `x`", "Premieres `x`": "In anteprima `x`", "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Ciao, Sembra che tu abbia disattivato JavaScript. Clicca qui per visualizzare i commenti, ma considera che il caricamento potrebbe richiedere più tempo.", @@ -315,27 +308,20 @@ "Yiddish": "Yiddish", "Yoruba": "Yoruba", "Zulu": "Zulu", - "generic_count_years_0": "{{count}} anno", - "generic_count_years_1": "{{count}} anni", - "generic_count_years_2": "{{count}} anni", - "generic_count_months_0": "{{count}} mese", - "generic_count_months_1": "{{count}} mesi", - "generic_count_months_2": "{{count}} mesi", - "generic_count_weeks_0": "{{count}} settimana", - "generic_count_weeks_1": "{{count}} settimane", - "generic_count_weeks_2": "{{count}} settimane", - "generic_count_days_0": "{{count}} giorno", - "generic_count_days_1": "{{count}} giorni", - "generic_count_days_2": "{{count}} giorni", - "generic_count_hours_0": "{{count}} ora", - "generic_count_hours_1": "{{count}} ore", - "generic_count_hours_2": "{{count}} ore", - "generic_count_minutes_0": "{{count}} minuto", - "generic_count_minutes_1": "{{count}} minuti", - "generic_count_minutes_2": "{{count}} minuti", - "generic_count_seconds_0": "{{count}} secondo", - "generic_count_seconds_1": "{{count}} secondi", - "generic_count_seconds_2": "{{count}} secondi", + "generic_count_years": "{{count}} anno", + "generic_count_years_plural": "{{count}} anni", + "generic_count_months": "{{count}} mese", + "generic_count_months_plural": "{{count}} mesi", + "generic_count_weeks": "{{count}} settimana", + "generic_count_weeks_plural": "{{count}} settimane", + "generic_count_days": "{{count}} giorno", + "generic_count_days_plural": "{{count}} giorni", + "generic_count_hours": "{{count}} ora", + "generic_count_hours_plural": "{{count}} ore", + "generic_count_minutes": "{{count}} minuto", + "generic_count_minutes_plural": "{{count}} minuti", + "generic_count_seconds": "{{count}} secondo", + "generic_count_seconds_plural": "{{count}} secondi", "Fallback comments: ": "Commenti alternativi: ", "Popular": "Popolare", "Search": "Cerca", @@ -439,12 +425,10 @@ "search_filters_duration_option_short": "Corto (< 4 minuti)", "search_filters_duration_option_long": "Lungo (> 20 minuti)", "search_filters_features_option_purchased": "Acquistato", - "comments_view_x_replies_0": "Vedi {{count}} risposta", - "comments_view_x_replies_1": "Vedi {{count}} risposte", - "comments_view_x_replies_2": "Vedi {{count}} risposte", - "comments_points_count_0": "{{count}} punto", - "comments_points_count_1": "{{count}} punti", - "comments_points_count_2": "{{count}} punti", + "comments_view_x_replies": "Vedi {{count}} risposta", + "comments_view_x_replies_plural": "Vedi {{count}} risposte", + "comments_points_count": "{{count}} punto", + "comments_points_count_plural": "{{count}} punti", "Portuguese (auto-generated)": "Portoghese (generati automaticamente)", "crash_page_you_found_a_bug": "Sembra che tu abbia trovato un bug in Invidious!", "crash_page_switch_instance": "provato a usare un'altra istanza", From f298e225a114578e0551e04d5e68f2bfcbe84e72 Mon Sep 17 00:00:00 2001 From: chunky programmer <78101139+ChunkyProgrammer@users.noreply.github.com> Date: Mon, 24 Apr 2023 19:29:34 -0400 Subject: [PATCH 068/184] fix live video attachments, parse playlists --- src/invidious/channels/community.cr | 68 ++++++--------------- src/invidious/helpers/serialized_yt_data.cr | 1 + src/invidious/yt_backend/extractors.cr | 2 +- 3 files changed, 22 insertions(+), 49 deletions(-) diff --git a/src/invidious/channels/community.cr b/src/invidious/channels/community.cr index ad786f3a..87430305 100644 --- a/src/invidious/channels/community.cr +++ b/src/invidious/channels/community.cr @@ -123,49 +123,13 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode) if attachment = post["backstageAttachment"]? json.field "attachment" do - json.object do - case attachment.as_h - when .has_key?("videoRenderer") - attachment = attachment["videoRenderer"] - json.field "type", "video" - - if !attachment["videoId"]? - error_message = (attachment["title"]["simpleText"]? || - attachment["title"]["runs"]?.try &.[0]?.try &.["text"]?) - - json.field "error", error_message - else - video_id = attachment["videoId"].as_s - - video_title = attachment["title"]["simpleText"]? || attachment["title"]["runs"]?.try &.[0]?.try &.["text"]? - json.field "title", video_title - json.field "videoId", video_id - json.field "videoThumbnails" do - Invidious::JSONify::APIv1.thumbnails(json, video_id) - end - - json.field "lengthSeconds", decode_length_seconds(attachment["lengthText"]["simpleText"].as_s) - - author_info = attachment["ownerText"]["runs"][0].as_h - - json.field "author", author_info["text"].as_s - json.field "authorId", author_info["navigationEndpoint"]["browseEndpoint"]["browseId"] - json.field "authorUrl", author_info["navigationEndpoint"]["commandMetadata"]["webCommandMetadata"]["url"] - - # TODO: json.field "authorThumbnails", "channelThumbnailSupportedRenderers" - # TODO: json.field "authorVerified", "ownerBadges" - - published = decode_date(attachment["publishedTimeText"]["simpleText"].as_s) - - json.field "published", published.to_unix - json.field "publishedText", translate(locale, "`x` ago", recode_date(published, locale)) - - view_count = attachment["viewCountText"]?.try &.["simpleText"].as_s.gsub(/\D/, "").to_i64? || 0_i64 - - json.field "viewCount", view_count - json.field "viewCountText", translate_count(locale, "generic_views_count", view_count, NumberFormatting::Short) - end - when .has_key?("backstageImageRenderer") + case attachment.as_h + when .has_key?("videoRenderer") + parse_item(attachment) + .as(SearchVideo) + .to_json(locale, json) + when .has_key?("backstageImageRenderer") + json.object do attachment = attachment["backstageImageRenderer"] json.field "type", "image" @@ -186,7 +150,9 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode) end end end - when .has_key?("pollRenderer") + end + when .has_key?("pollRenderer") + json.object do attachment = attachment["pollRenderer"] json.field "type", "poll" json.field "totalVotes", short_text_to_number(attachment["totalVotes"]["simpleText"].as_s.split(" ")[0]) @@ -219,7 +185,9 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode) end end end - when .has_key?("postMultiImageRenderer") + end + when .has_key?("postMultiImageRenderer") + json.object do attachment = attachment["postMultiImageRenderer"] json.field "type", "multiImage" json.field "images" do @@ -243,10 +211,14 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode) end end end - else - json.field "type", "unknown" - json.field "error", "Unrecognized attachment type." end + when .has_key?("playlistRenderer") + parse_item(attachment) + .as(SearchPlaylist) + .to_json(locale, json) + else + json.field "type", "unknown" + json.field "error", "Unrecognized attachment type." end end end diff --git a/src/invidious/helpers/serialized_yt_data.cr b/src/invidious/helpers/serialized_yt_data.cr index c1874780..7c12ad0e 100644 --- a/src/invidious/helpers/serialized_yt_data.cr +++ b/src/invidious/helpers/serialized_yt_data.cr @@ -84,6 +84,7 @@ struct SearchVideo json.field "descriptionHtml", self.description_html json.field "viewCount", self.views + json.field "viewCountText", translate_count(locale, "generic_views_count", self.views, NumberFormatting::Short) json.field "published", self.published.to_unix json.field "publishedText", translate(locale, "`x` ago", recode_date(self.published, locale)) json.field "lengthSeconds", self.length_seconds diff --git a/src/invidious/yt_backend/extractors.cr b/src/invidious/yt_backend/extractors.cr index 9c041361..8ff4c1f9 100644 --- a/src/invidious/yt_backend/extractors.cr +++ b/src/invidious/yt_backend/extractors.cr @@ -268,7 +268,7 @@ private module Parsers end private def self.parse(item_contents, author_fallback) - title = item_contents["title"]["simpleText"]?.try &.as_s || "" + title = extract_text(item_contents["title"]) || "" plid = item_contents["playlistId"]?.try &.as_s || "" video_count = HelperExtractors.get_video_count(item_contents) From d420741cc15dce656da641f5143120ec88e59bc8 Mon Sep 17 00:00:00 2001 From: chunky programmer <78101139+ChunkyProgrammer@users.noreply.github.com> Date: Wed, 19 Apr 2023 20:59:06 -0400 Subject: [PATCH 069/184] Allow channel urls to be displayed in YT description --- src/invidious/videos/description.cr | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/invidious/videos/description.cr b/src/invidious/videos/description.cr index 2017955d..0a9d84f8 100644 --- a/src/invidious/videos/description.cr +++ b/src/invidious/videos/description.cr @@ -6,13 +6,19 @@ def parse_command(command : JSON::Any?, string : String) : String? # 3rd party URL, extract original URL from YouTube tracking URL if url_endpoint = on_tap.try &.["urlEndpoint"]? - youtube_url = URI.parse url_endpoint["url"].as_s - - original_url = youtube_url.query_params["q"]? - if original_url.nil? - return "" + if url_endpoint["url"].as_s.includes? "youtube.com/redirect" + youtube_url = URI.parse url_endpoint["url"].as_s + original_url = youtube_url.query_params["q"]? + if original_url.nil? + return "" + else + return "#{original_url}" + end else - return "#{original_url}" + # not a redirect url, some first party url + # see https://github.com/iv-org/invidious/issues/3751 + first_party_url = url_endpoint["url"].as_s + return "#{first_party_url}" end # 1st party watch URL elsif watch_endpoint = on_tap.try &.["watchEndpoint"]? From 1b10446e5ecfb50d84fae88b6b8953ed19bfe1fb Mon Sep 17 00:00:00 2001 From: chunky programmer <78101139+ChunkyProgrammer@users.noreply.github.com> Date: Mon, 24 Apr 2023 17:40:58 -0400 Subject: [PATCH 070/184] move url parsing to utils method --- src/invidious/comments.cr | 51 +------------------------ src/invidious/helpers/utils.cr | 53 ++++++++++++++++++++++++++ src/invidious/videos/description.cr | 59 +++-------------------------- src/invidious/videos/parser.cr | 2 +- 4 files changed, 62 insertions(+), 103 deletions(-) diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr index fd2be73d..0c863977 100644 --- a/src/invidious/comments.cr +++ b/src/invidious/comments.cr @@ -635,55 +635,8 @@ def content_to_comment_html(content, video_id : String? = "") text = HTML.escape(run["text"].as_s) - if run["navigationEndpoint"]? - if url = run["navigationEndpoint"]["urlEndpoint"]?.try &.["url"].as_s - url = URI.parse(url) - displayed_url = text - - if url.host == "youtu.be" - url = "/watch?v=#{url.request_target.lstrip('/')}" - elsif url.host.nil? || url.host.not_nil!.ends_with?("youtube.com") - if url.path == "/redirect" - # Sometimes, links can be corrupted (why?) so make sure to fallback - # nicely. See https://github.com/iv-org/invidious/issues/2682 - url = url.query_params["q"]? || "" - displayed_url = url - else - url = url.request_target - displayed_url = "youtube.com#{url}" - end - end - - text = %(#{reduce_uri(displayed_url)}) - elsif watch_endpoint = run["navigationEndpoint"]["watchEndpoint"]? - start_time = watch_endpoint["startTimeSeconds"]?.try &.as_i - link_video_id = watch_endpoint["videoId"].as_s - - url = "/watch?v=#{link_video_id}" - url += "&t=#{start_time}" if !start_time.nil? - - # If the current video ID (passed through from the caller function) - # is the same as the video ID in the link, add HTML attributes for - # the JS handler function that bypasses page reload. - # - # See: https://github.com/iv-org/invidious/issues/3063 - if link_video_id == video_id - start_time ||= 0 - text = %(#{reduce_uri(text)}) - else - text = %(#{text}) - end - elsif url = run.dig?("navigationEndpoint", "commandMetadata", "webCommandMetadata", "url").try &.as_s - if text.starts_with?(/\s?[@#]/) - # Handle "pings" in comments and hasthags differently - # See: - # - https://github.com/iv-org/invidious/issues/3038 - # - https://github.com/iv-org/invidious/issues/3062 - text = %(#{text}) - else - text = %(#{reduce_uri(url)}) - end - end + if navigationEndpoint = run.dig?("navigationEndpoint") + text = parse_link_endpoint(navigationEndpoint, text, video_id) end text = "#{text}" if run["bold"]? diff --git a/src/invidious/helpers/utils.cr b/src/invidious/helpers/utils.cr index 500a2582..bcf7c963 100644 --- a/src/invidious/helpers/utils.cr +++ b/src/invidious/helpers/utils.cr @@ -389,3 +389,56 @@ def reduce_uri(uri : URI | String, max_length : Int32 = 50, suffix : String = " end return str end + +# Get the html link from a NavigationEndpoint or an innertubeCommand +def parse_link_endpoint(endpoint : JSON::Any, text : String, video_id : String) + if url = endpoint.dig?("urlEndpoint", "url").try &.as_s + url = URI.parse(url) + displayed_url = text + + if url.host == "youtu.be" + url = "/watch?v=#{url.request_target.lstrip('/')}" + elsif url.host.nil? || url.host.not_nil!.ends_with?("youtube.com") + if url.path == "/redirect" + # Sometimes, links can be corrupted (why?) so make sure to fallback + # nicely. See https://github.com/iv-org/invidious/issues/2682 + url = url.query_params["q"]? || "" + displayed_url = url + else + url = url.request_target + displayed_url = "youtube.com#{url}" + end + end + + text = %(#{reduce_uri(displayed_url)}) + elsif watch_endpoint = endpoint.dig?("watchEndpoint") + start_time = watch_endpoint["startTimeSeconds"]?.try &.as_i + link_video_id = watch_endpoint["videoId"].as_s + + url = "/watch?v=#{link_video_id}" + url += "&t=#{start_time}" if !start_time.nil? + + # If the current video ID (passed through from the caller function) + # is the same as the video ID in the link, add HTML attributes for + # the JS handler function that bypasses page reload. + # + # See: https://github.com/iv-org/invidious/issues/3063 + if link_video_id == video_id + start_time ||= 0 + text = %(#{reduce_uri(text)}) + else + text = %(#{text}) + end + elsif url = endpoint.dig?("commandMetadata", "webCommandMetadata", "url").try &.as_s + if text.starts_with?(/\s?[@#]/) + # Handle "pings" in comments and hasthags differently + # See: + # - https://github.com/iv-org/invidious/issues/3038 + # - https://github.com/iv-org/invidious/issues/3062 + text = %(#{text}) + else + text = %(#{reduce_uri(url)}) + end + end + return text +end diff --git a/src/invidious/videos/description.cr b/src/invidious/videos/description.cr index 0a9d84f8..542cb416 100644 --- a/src/invidious/videos/description.cr +++ b/src/invidious/videos/description.cr @@ -1,57 +1,6 @@ require "json" require "uri" -def parse_command(command : JSON::Any?, string : String) : String? - on_tap = command.dig?("onTap", "innertubeCommand") - - # 3rd party URL, extract original URL from YouTube tracking URL - if url_endpoint = on_tap.try &.["urlEndpoint"]? - if url_endpoint["url"].as_s.includes? "youtube.com/redirect" - youtube_url = URI.parse url_endpoint["url"].as_s - original_url = youtube_url.query_params["q"]? - if original_url.nil? - return "" - else - return "#{original_url}" - end - else - # not a redirect url, some first party url - # see https://github.com/iv-org/invidious/issues/3751 - first_party_url = url_endpoint["url"].as_s - return "#{first_party_url}" - end - # 1st party watch URL - elsif watch_endpoint = on_tap.try &.["watchEndpoint"]? - video_id = watch_endpoint["videoId"].as_s - time = watch_endpoint["startTimeSeconds"].as_i - - url = "/watch?v=#{video_id}&t=#{time}s" - - # if string is a timestamp, use the string instead - # this is a lazy regex for validating timestamps - if /(?:\d{1,2}:){1,2}\d{2}/ =~ string - return "#{string}" - else - return "#{url}" - end - # hashtag/other browse URLs - elsif browse_endpoint = on_tap.try &.dig?("commandMetadata", "webCommandMetadata") - url = browse_endpoint["url"].try &.as_s - - # remove unnecessary character in a channel name - if browse_endpoint["webPageType"]?.try &.as_s == "WEB_PAGE_TYPE_CHANNEL" - name = string.match(/@[\w\d.-]+/) - if name.try &.[0]? - return "#{name.try &.[0]}" - end - end - - return "#{string}" - end - - return "(unknown YouTube desc command)" -end - private def copy_string(str : String::Builder, iter : Iterator, count : Int) : Int copied = 0 while copied < count @@ -68,7 +17,7 @@ private def copy_string(str : String::Builder, iter : Iterator, count : Int) : I return copied end -def parse_description(desc : JSON::Any?) : String? +def parse_description(desc, video_id : String) : String? return "" if desc.nil? content = desc["content"].as_s @@ -100,7 +49,11 @@ def parse_description(desc : JSON::Any?) : String? copy_string(str2, iter, cmd_length) end - str << parse_command(command, cmd_content) + link = cmd_content + if on_tap = command.dig?("onTap", "innertubeCommand") + link = parse_link_endpoint(on_tap, cmd_content, video_id) + end + str << link index += cmd_length end diff --git a/src/invidious/videos/parser.cr b/src/invidious/videos/parser.cr index 1c6d118d..2e8eecc3 100644 --- a/src/invidious/videos/parser.cr +++ b/src/invidious/videos/parser.cr @@ -287,7 +287,7 @@ def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any # description_html = video_secondary_renderer.try &.dig?("description", "runs") # .try &.as_a.try { |t| content_to_comment_html(t, video_id) } - description_html = parse_description(video_secondary_renderer.try &.dig?("attributedDescription")) + description_html = parse_description(video_secondary_renderer.try &.dig?("attributedDescription"), video_id) # Video metadata From 28584f22c52b243da740061eeb834e300f36b7c1 Mon Sep 17 00:00:00 2001 From: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com> Date: Tue, 11 Apr 2023 20:50:23 -0400 Subject: [PATCH 071/184] Fix index out of bounds error --- src/invidious/comments.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr index fd2be73d..b5815bb4 100644 --- a/src/invidious/comments.cr +++ b/src/invidious/comments.cr @@ -604,7 +604,7 @@ def text_to_parsed_content(text : String) : JSON::Any currentNode = {"text" => urlMatch[0], "navigationEndpoint" => {"urlEndpoint" => {"url" => urlMatch[0]}}} currentNodes << (JSON.parse(currentNode.to_json)) # If text remain after match create new simple node with text after match - afterNode = {"text" => splittedLastNode.size > 0 ? splittedLastNode[1] : ""} + afterNode = {"text" => splittedLastNode.size > 1 ? splittedLastNode[1] : ""} currentNodes << (JSON.parse(afterNode.to_json)) end From 384a8e200c953ed5be3ba6a01762e933fd566e45 Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Tue, 2 May 2023 23:18:40 +0200 Subject: [PATCH 072/184] Trending: fix mistakes from #3773 --- src/invidious/trending.cr | 18 ++++++++++++++++-- src/invidious/yt_backend/extractors_utils.cr | 13 +++++++------ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/invidious/trending.cr b/src/invidious/trending.cr index 74bab1bd..fcaf60d1 100644 --- a/src/invidious/trending.cr +++ b/src/invidious/trending.cr @@ -20,6 +20,20 @@ def fetch_trending(trending_type, region, locale) items, _ = extract_items(initial_data) - # Return items, but ignore categories (e.g featured content) - return items.reject!(Category), plid + extracted = [] of SearchItem + + items.each do |itm| + if itm.is_a?(Category) + # Ignore the smaller categories, as they generally contain a sponsored + # channel, which brings a lot of noise on the trending page. + # See: https://github.com/iv-org/invidious/issues/2989 + next if itm.contents.size < 24 + + extracted.concat extract_category(itm) + else + extracted << itm + end + end + + return extracted, plid end diff --git a/src/invidious/yt_backend/extractors_utils.cr b/src/invidious/yt_backend/extractors_utils.cr index b247dca8..11d95958 100644 --- a/src/invidious/yt_backend/extractors_utils.cr +++ b/src/invidious/yt_backend/extractors_utils.cr @@ -68,16 +68,17 @@ rescue ex return false end -# This function extracts the SearchItems from a Category. +# This function extracts SearchVideo items from a Category. # Categories are commonly returned in search results and trending pages. def extract_category(category : Category) : Array(SearchVideo) - items = [] of SearchItem + return category.contents.select(SearchVideo) +end - category.contents.each do |item| - target << cate_i if item.is_a?(SearchItem) +# :ditto: +def extract_category(category : Category, &) + category.contents.select(SearchVideo).each do |item| + yield item end - - return items end def extract_selected_tab(tabs) From 90914343ec1a4c89e8bb873fdefa0a8e8ac656df Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Wed, 3 May 2023 00:02:38 +0200 Subject: [PATCH 073/184] Trending: de-duplicate results --- src/invidious/trending.cr | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/invidious/trending.cr b/src/invidious/trending.cr index fcaf60d1..2d9f8a83 100644 --- a/src/invidious/trending.cr +++ b/src/invidious/trending.cr @@ -35,5 +35,6 @@ def fetch_trending(trending_type, region, locale) end end - return extracted, plid + # Deduplicate items before returning results + return extracted.select(SearchVideo).uniq!(&.id), plid end From 2d5145614be46c0b59a87c26cecac0c4b69e3437 Mon Sep 17 00:00:00 2001 From: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com> Date: Tue, 2 May 2023 21:10:57 -0400 Subject: [PATCH 074/184] Fix unknown type attachment Co-authored-by: Samantaz Fox --- src/invidious/channels/community.cr | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/invidious/channels/community.cr b/src/invidious/channels/community.cr index 87430305..2c7b9fec 100644 --- a/src/invidious/channels/community.cr +++ b/src/invidious/channels/community.cr @@ -217,8 +217,10 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode) .as(SearchPlaylist) .to_json(locale, json) else - json.field "type", "unknown" - json.field "error", "Unrecognized attachment type." + json.object do + json.field "type", "unknown" + json.field "error", "Unrecognized attachment type." + end end end end From 7aac401407627fef167b2d0f5bb3dd2324de6a1c Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Sat, 6 May 2023 19:23:55 +0200 Subject: [PATCH 075/184] CSS: limit width of the comments in community tab --- assets/css/default.css | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/assets/css/default.css b/assets/css/default.css index 42f6958f..4d06b77f 100644 --- a/assets/css/default.css +++ b/assets/css/default.css @@ -321,6 +321,16 @@ p.channel-name { margin: 0; } p.video-data { margin: 0; font-weight: bold; font-size: 80%; } +/* + * Comments & community posts + */ + +#comments { + max-width: 800px; + margin: auto; +} + + /* * Footer */ From ce2649420fb868596bd926393fb1073d2671a4f5 Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Sat, 6 May 2023 19:36:52 +0200 Subject: [PATCH 076/184] CSS: Fix iframe attachment size in community posts --- assets/css/default.css | 14 ++++++++++++++ src/invidious/comments.cr | 18 +++++------------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/assets/css/default.css b/assets/css/default.css index 4d06b77f..23649f8f 100644 --- a/assets/css/default.css +++ b/assets/css/default.css @@ -330,6 +330,20 @@ p.video-data { margin: 0; font-weight: bold; font-size: 80%; } margin: auto; } +.video-iframe-wrapper { + position: relative; + height: 0; + padding-bottom: 56.25%; +} + +.video-iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border: none; +} /* * Footer diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr index ec4449f0..f43e39c6 100644 --- a/src/invidious/comments.cr +++ b/src/invidious/comments.cr @@ -372,27 +372,19 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false)
    END_HTML when "video" - html << <<-END_HTML -
    -
    -
    - END_HTML - if attachment["error"]? html << <<-END_HTML +

    #{attachment["error"]}

    +
    END_HTML else html << <<-END_HTML - +
    + +
    END_HTML end - - html << <<-END_HTML -
    -
    -
    - END_HTML else nil # Ignore end end From 720789b6221518fd1614debfcee794a422df9466 Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Sat, 6 May 2023 19:41:07 +0200 Subject: [PATCH 077/184] HTML: wrap comments metadata in a paragraph --- src/invidious/comments.cr | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr index f43e39c6..01556099 100644 --- a/src/invidious/comments.cr +++ b/src/invidious/comments.cr @@ -390,6 +390,7 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false) end html << <<-END_HTML +

    #{translate(locale, "`x` ago", recode_date(Time.unix(child["published"].as_i64), locale))} #{child["isEdited"] == true ? translate(locale, "(edited)") : ""} | END_HTML @@ -408,6 +409,7 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false) html << <<-END_HTML #{number_with_separator(child["likeCount"])} +

    END_HTML if child["creatorHeart"]? From 36f7c99cfb96dd743df237a09c390e11cedae420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milien=20Devos=20=28perso=29?= Date: Sun, 7 May 2023 17:49:43 +0200 Subject: [PATCH 078/184] Update config.example.yml Document save playback position in the config.example.yml --- config/config.example.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/config/config.example.yml b/config/config.example.yml index 8abe1b9e..7ea80017 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -817,6 +817,16 @@ default_user_preferences: ## Default: true ## #vr_mode: true + + ## + ## Save the playback position + ## Allow to continue watching at the previous position when + ## watching the same video. + ## + ## Accepted values: true, false + ## Default: false + ## + #save_player_pos: false # ----------------------------- # Subscription feed From f3d9db10a2be1c8ef3f9b919343dde6a6f36fcb0 Mon Sep 17 00:00:00 2001 From: Fjuro Date: Mon, 1 May 2023 12:59:27 +0000 Subject: [PATCH 079/184] Update Czech translation --- locales/cs.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/cs.json b/locales/cs.json index 0e8610bf..d9e5b4d5 100644 --- a/locales/cs.json +++ b/locales/cs.json @@ -13,7 +13,7 @@ "Previous page": "Předchozí strana", "Clear watch history?": "Smazat historii?", "New password": "Nové heslo", - "New passwords must match": "Hesla se musí schodovat", + "New passwords must match": "Hesla se musí shodovat", "Cannot change password for Google accounts": "Nelze změnit heslo pro účty Google", "Authorize token?": "Autorizovat token?", "Authorize token for `x`?": "Autorizovat token pro `x`?", From cca8bcf2a85fe7c2e241cb26ec94ee51dd8f37e7 Mon Sep 17 00:00:00 2001 From: xrfmkrh Date: Mon, 1 May 2023 05:57:30 +0000 Subject: [PATCH 080/184] Update Korean translation --- locales/ko.json | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/locales/ko.json b/locales/ko.json index d4f3a711..2b454add 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -46,7 +46,7 @@ "Log in/register": "로그인/회원가입", "Log in": "로그인", "source": "출처", - "JavaScript license information": "자바스크립트 라이센스 정보", + "JavaScript license information": "자바스크립트 라이선스 정보", "An alternative front-end to YouTube": "유튜브의 프론트엔드 대안", "History": "역사", "Delete account?": "계정을 삭제 하시겠습니까?", @@ -116,7 +116,7 @@ "Show replies": "댓글 보기", "Hide replies": "댓글 숨기기", "Incorrect password": "잘못된 비밀번호", - "License: ": "라이센스: ", + "License: ": "라이선스: ", "Genre: ": "장르: ", "Editing playlist `x`": "재생목록 `x` 수정하기", "Playlist privacy": "재생목록 공개 범위", @@ -135,7 +135,7 @@ "Unlisted": "목록에 없음", "Public": "공개", "View privacy policy.": "개인정보 처리방침 보기.", - "View JavaScript license information.": "자바스크립트 라이센스 정보 보기.", + "View JavaScript license information.": "자바스크립트 라이선스 정보 보기.", "Source available here.": "소스는 여기에서 사용할 수 있습니다.", "Log out": "로그아웃", "search": "검색", @@ -460,5 +460,12 @@ "channel_tab_shorts_label": "쇼츠", "channel_tab_streams_label": "실시간 스트리밍", "channel_tab_channels_label": "채널", - "channel_tab_playlists_label": "재생목록" + "channel_tab_playlists_label": "재생목록", + "Standard YouTube license": "표준 유튜브 라이선스", + "Song: ": "제목: ", + "Channel Sponsor": "채널 스폰서", + "Album: ": "앨범: ", + "Music in this video": "동영상 속 음악", + "Artist: ": "아티스트: ", + "Download is disabled": "다운로드가 비활성화 되어있음" } From 56ebb477caff6189da5db40291d68c6e895fc2d8 Mon Sep 17 00:00:00 2001 From: gallegonovato Date: Wed, 3 May 2023 12:25:54 +0000 Subject: [PATCH 081/184] Update Spanish translation --- locales/es.json | 75 +++++++++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 30 deletions(-) diff --git a/locales/es.json b/locales/es.json index 09f510a7..63079d9e 100644 --- a/locales/es.json +++ b/locales/es.json @@ -398,37 +398,51 @@ "search_filters_features_option_three_sixty": "360°", "videoinfo_watch_on_youTube": "Ver en YouTube", "preferences_save_player_pos_label": "Guardar posición de reproducción: ", - "generic_views_count": "{{count}} visualización", - "generic_views_count_plural": "{{count}} visualizaciones", - "generic_subscribers_count": "{{count}} suscriptor", - "generic_subscribers_count_plural": "{{count}} suscriptores", - "generic_subscriptions_count": "{{count}} suscripción", - "generic_subscriptions_count_plural": "{{count}} suscripciones", - "subscriptions_unseen_notifs_count": "{{count}} notificación no vista", - "subscriptions_unseen_notifs_count_plural": "{{count}} notificaciones no vistas", - "generic_count_days": "{{count}} día", - "generic_count_days_plural": "{{count}} días", - "comments_view_x_replies": "Ver {{count}} respuesta", - "comments_view_x_replies_plural": "Ver {{count}} respuestas", - "generic_count_weeks": "{{count}} semana", - "generic_count_weeks_plural": "{{count}} semanas", - "generic_playlists_count": "{{count}} lista de reproducción", - "generic_playlists_count_plural": "{{count}} listas de reproducción", + "generic_views_count_0": "{{count}} vista", + "generic_views_count_1": "{{count}} vistas", + "generic_views_count_2": "{{count}} vistas", + "generic_subscribers_count_0": "{{count}} suscriptor", + "generic_subscribers_count_1": "{{count}} suscriptores", + "generic_subscribers_count_2": "{{count}} suscriptores", + "generic_subscriptions_count_0": "{{count}} suscripción", + "generic_subscriptions_count_1": "{{count}} suscripciones", + "generic_subscriptions_count_2": "{{count}} suscripciones", + "subscriptions_unseen_notifs_count_0": "{{count}} notificación sin ver", + "subscriptions_unseen_notifs_count_1": "{{count}} notificaciones sin ver", + "subscriptions_unseen_notifs_count_2": "{{count}} notificaciones sin ver", + "generic_count_days_0": "{{count}} día", + "generic_count_days_1": "{{count}} días", + "generic_count_days_2": "{{count}} días", + "comments_view_x_replies_0": "Ver {{count}} respuesta", + "comments_view_x_replies_1": "Ver {{count}} respuestas", + "comments_view_x_replies_2": "Ver {{count}} respuestas", + "generic_count_weeks_0": "{{count}} semana", + "generic_count_weeks_1": "{{count}} semanas", + "generic_count_weeks_2": "{{count}} semanas", + "generic_playlists_count_0": "{{count}} reproducción", + "generic_playlists_count_1": "{{count}} reproducciones", + "generic_playlists_count_2": "{{count}} reproducciones", "generic_videos_count_0": "{{count}} video", "generic_videos_count_1": "{{count}} videos", "generic_videos_count_2": "{{count}} videos", - "generic_count_months": "{{count}} mes", - "generic_count_months_plural": "{{count}} meses", - "comments_points_count": "{{count}} punto", - "comments_points_count_plural": "{{count}} puntos", - "generic_count_years": "{{count}} año", - "generic_count_years_plural": "{{count}} años", - "generic_count_hours": "{{count}} hora", - "generic_count_hours_plural": "{{count}} horas", - "generic_count_minutes": "{{count}} minuto", - "generic_count_minutes_plural": "{{count}} minutos", - "generic_count_seconds": "{{count}} segundo", - "generic_count_seconds_plural": "{{count}} segundos", + "generic_count_months_0": "{{count}} mes", + "generic_count_months_1": "{{count}} meses", + "generic_count_months_2": "{{count}} meses", + "comments_points_count_0": "{{count}} punto", + "comments_points_count_1": "{{count}} puntos", + "comments_points_count_2": "{{count}} puntos", + "generic_count_years_0": "{{count}} año", + "generic_count_years_1": "{{count}} años", + "generic_count_years_2": "{{count}} años", + "generic_count_hours_0": "{{count}} hora", + "generic_count_hours_1": "{{count}} horas", + "generic_count_hours_2": "{{count}} horas", + "generic_count_minutes_0": "{{count}} minuto", + "generic_count_minutes_1": "{{count}} minutos", + "generic_count_minutes_2": "{{count}} minutos", + "generic_count_seconds_0": "{{count}} segundo", + "generic_count_seconds_1": "{{count}} segundos", + "generic_count_seconds_2": "{{count}} segundos", "crash_page_before_reporting": "Antes de notificar un error asegúrate de que has:", "crash_page_switch_instance": "probado a usar otra instancia", "crash_page_read_the_faq": "leído las Preguntas Frecuentes", @@ -469,8 +483,9 @@ "search_filters_duration_option_none": "Cualquier duración", "search_filters_features_option_vr180": "VR180", "search_filters_apply_button": "Aplicar filtros", - "tokens_count": "{{count}} ficha", - "tokens_count_plural": "{{count}} fichas", + "tokens_count_0": "{{count}} token", + "tokens_count_1": "{{count}} tokens", + "tokens_count_2": "{{count}} tokens", "search_message_use_another_instance": " También puede buscar en otra instancia.", "Popular enabled: ": "¿Habilitar la sección popular? ", "error_video_not_in_playlist": "El video que solicitaste no existe en esta lista de reproducción. Haz clic aquí para acceder a la página de inicio de la lista de reproducción.", From ce1fb8d08c86f747ee638289c8bcfeb208702445 Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Mon, 8 May 2023 00:53:08 +0200 Subject: [PATCH 082/184] Use XML.parse instead of XML.parse_html Due to recent changes to libxml2 (between 2.9.14 and 2.10.4, See https://gitlab.gnome.org/GNOME/libxml2/-/issues/508), the HTML parser doesn't take into account the namespaces (xmlns). Because HTML shouldn't contain namespaces anyway, there is no reason for use to keep using it. But switching to the XML parser means that we have to pass the namespaces to every single 'xpath_node(s)' method for it to be able to properly navigate the XML structure. --- src/invidious/channels/channels.cr | 36 +++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/invidious/channels/channels.cr b/src/invidious/channels/channels.cr index 63dd2194..b09d93b1 100644 --- a/src/invidious/channels/channels.cr +++ b/src/invidious/channels/channels.cr @@ -159,12 +159,18 @@ def fetch_channel(ucid, pull_all_videos : Bool) LOGGER.debug("fetch_channel: #{ucid}") LOGGER.trace("fetch_channel: #{ucid} : pull_all_videos = #{pull_all_videos}") + namespaces = { + "yt" => "http://www.youtube.com/xml/schemas/2015", + "media" => "http://search.yahoo.com/mrss/", + "default" => "http://www.w3.org/2005/Atom", + } + LOGGER.trace("fetch_channel: #{ucid} : Downloading RSS feed") rss = YT_POOL.client &.get("/feeds/videos.xml?channel_id=#{ucid}").body LOGGER.trace("fetch_channel: #{ucid} : Parsing RSS feed") - rss = XML.parse_html(rss) + rss = XML.parse(rss) - author = rss.xpath_node(%q(//feed/title)) + author = rss.xpath_node("//default:feed/default:title", namespaces) if !author raise InfoException.new("Deleted or invalid channel") end @@ -192,15 +198,23 @@ def fetch_channel(ucid, pull_all_videos : Bool) videos, continuation = IV::Channel::Tabs.get_videos(channel) LOGGER.trace("fetch_channel: #{ucid} : Extracting videos from channel RSS feed") - rss.xpath_nodes("//feed/entry").each do |entry| - video_id = entry.xpath_node("videoid").not_nil!.content - title = entry.xpath_node("title").not_nil!.content - published = Time.parse_rfc3339(entry.xpath_node("published").not_nil!.content) - updated = Time.parse_rfc3339(entry.xpath_node("updated").not_nil!.content) - author = entry.xpath_node("author/name").not_nil!.content - ucid = entry.xpath_node("channelid").not_nil!.content - views = entry.xpath_node("group/community/statistics").try &.["views"]?.try &.to_i64? - views ||= 0_i64 + rss.xpath_nodes("//default:feed/default:entry", namespaces).each do |entry| + video_id = entry.xpath_node("yt:videoid", namespaces).not_nil!.content + title = entry.xpath_node("default:title", namespaces).not_nil!.content + + published = Time.parse_rfc3339( + entry.xpath_node("default:published", namespaces).not_nil!.content + ) + updated = Time.parse_rfc3339( + entry.xpath_node("default:updated", namespaces).not_nil!.content + ) + + author = entry.xpath_node("default:author/default:name", namespaces).not_nil!.content + ucid = entry.xpath_node("yt:channelid", namespaces).not_nil!.content + + views = entry + .xpath_node("media:group/media:community/media:statistics", namespaces) + .try &.["views"]?.try &.to_i64? || 0_i64 channel_video = videos .select(SearchVideo) From c385a944e642ce9e060c2dcf2082ecf0bb10b45a Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Mon, 8 May 2023 13:10:18 +0200 Subject: [PATCH 083/184] Subscriptions: Fix casing of XML tag names --- src/invidious/channels/channels.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/invidious/channels/channels.cr b/src/invidious/channels/channels.cr index b09d93b1..c3d6124f 100644 --- a/src/invidious/channels/channels.cr +++ b/src/invidious/channels/channels.cr @@ -199,7 +199,7 @@ def fetch_channel(ucid, pull_all_videos : Bool) LOGGER.trace("fetch_channel: #{ucid} : Extracting videos from channel RSS feed") rss.xpath_nodes("//default:feed/default:entry", namespaces).each do |entry| - video_id = entry.xpath_node("yt:videoid", namespaces).not_nil!.content + video_id = entry.xpath_node("yt:videoId", namespaces).not_nil!.content title = entry.xpath_node("default:title", namespaces).not_nil!.content published = Time.parse_rfc3339( @@ -210,7 +210,7 @@ def fetch_channel(ucid, pull_all_videos : Bool) ) author = entry.xpath_node("default:author/default:name", namespaces).not_nil!.content - ucid = entry.xpath_node("yt:channelid", namespaces).not_nil!.content + ucid = entry.xpath_node("yt:channelId", namespaces).not_nil!.content views = entry .xpath_node("media:group/media:community/media:statistics", namespaces) From 544fc9f92e9f3a55e362282680542b6ecbebfdc3 Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Mon, 8 May 2023 15:33:23 +0200 Subject: [PATCH 084/184] Fix broken Spanish locale (i18next v3->v4 mixup) --- locales/es.json | 80 ++++++++++++++++++++----------------------------- 1 file changed, 32 insertions(+), 48 deletions(-) diff --git a/locales/es.json b/locales/es.json index 63079d9e..68ff0170 100644 --- a/locales/es.json +++ b/locales/es.json @@ -398,51 +398,36 @@ "search_filters_features_option_three_sixty": "360°", "videoinfo_watch_on_youTube": "Ver en YouTube", "preferences_save_player_pos_label": "Guardar posición de reproducción: ", - "generic_views_count_0": "{{count}} vista", - "generic_views_count_1": "{{count}} vistas", - "generic_views_count_2": "{{count}} vistas", - "generic_subscribers_count_0": "{{count}} suscriptor", - "generic_subscribers_count_1": "{{count}} suscriptores", - "generic_subscribers_count_2": "{{count}} suscriptores", - "generic_subscriptions_count_0": "{{count}} suscripción", - "generic_subscriptions_count_1": "{{count}} suscripciones", - "generic_subscriptions_count_2": "{{count}} suscripciones", - "subscriptions_unseen_notifs_count_0": "{{count}} notificación sin ver", - "subscriptions_unseen_notifs_count_1": "{{count}} notificaciones sin ver", - "subscriptions_unseen_notifs_count_2": "{{count}} notificaciones sin ver", - "generic_count_days_0": "{{count}} día", - "generic_count_days_1": "{{count}} días", - "generic_count_days_2": "{{count}} días", - "comments_view_x_replies_0": "Ver {{count}} respuesta", - "comments_view_x_replies_1": "Ver {{count}} respuestas", - "comments_view_x_replies_2": "Ver {{count}} respuestas", - "generic_count_weeks_0": "{{count}} semana", - "generic_count_weeks_1": "{{count}} semanas", - "generic_count_weeks_2": "{{count}} semanas", - "generic_playlists_count_0": "{{count}} reproducción", - "generic_playlists_count_1": "{{count}} reproducciones", - "generic_playlists_count_2": "{{count}} reproducciones", - "generic_videos_count_0": "{{count}} video", - "generic_videos_count_1": "{{count}} videos", - "generic_videos_count_2": "{{count}} videos", - "generic_count_months_0": "{{count}} mes", - "generic_count_months_1": "{{count}} meses", - "generic_count_months_2": "{{count}} meses", - "comments_points_count_0": "{{count}} punto", - "comments_points_count_1": "{{count}} puntos", - "comments_points_count_2": "{{count}} puntos", - "generic_count_years_0": "{{count}} año", - "generic_count_years_1": "{{count}} años", - "generic_count_years_2": "{{count}} años", - "generic_count_hours_0": "{{count}} hora", - "generic_count_hours_1": "{{count}} horas", - "generic_count_hours_2": "{{count}} horas", - "generic_count_minutes_0": "{{count}} minuto", - "generic_count_minutes_1": "{{count}} minutos", - "generic_count_minutes_2": "{{count}} minutos", - "generic_count_seconds_0": "{{count}} segundo", - "generic_count_seconds_1": "{{count}} segundos", - "generic_count_seconds_2": "{{count}} segundos", + "generic_views_count": "{{count}} vista", + "generic_views_count_plural": "{{count}} vistas", + "generic_subscribers_count": "{{count}} suscriptor", + "generic_subscribers_count_plural": "{{count}} suscriptores", + "generic_subscriptions_count": "{{count}} suscripción", + "generic_subscriptions_count_plural": "{{count}} suscripciones", + "subscriptions_unseen_notifs_count": "{{count}} notificación sin ver", + "subscriptions_unseen_notifs_count_plural": "{{count}} notificaciones sin ver", + "generic_count_days": "{{count}} día", + "generic_count_days_plural": "{{count}} días", + "comments_view_x_replies": "Ver {{count}} respuesta", + "comments_view_x_replies_plural": "Ver {{count}} respuestas", + "generic_count_weeks": "{{count}} semana", + "generic_count_weeks_plural": "{{count}} semanas", + "generic_playlists_count": "{{count}} reproducción", + "generic_playlists_count_plural": "{{count}} reproducciones", + "generic_videos_count": "{{count}} video", + "generic_videos_count_plural": "{{count}} videos", + "generic_count_months": "{{count}} mes", + "generic_count_months_plural": "{{count}} meses", + "comments_points_count": "{{count}} punto", + "comments_points_count_plural": "{{count}} puntos", + "generic_count_years": "{{count}} año", + "generic_count_years_plural": "{{count}} años", + "generic_count_hours": "{{count}} hora", + "generic_count_hours_plural": "{{count}} horas", + "generic_count_minutes": "{{count}} minuto", + "generic_count_minutes_plural": "{{count}} minutos", + "generic_count_seconds": "{{count}} segundo", + "generic_count_seconds_plural": "{{count}} segundos", "crash_page_before_reporting": "Antes de notificar un error asegúrate de que has:", "crash_page_switch_instance": "probado a usar otra instancia", "crash_page_read_the_faq": "leído las Preguntas Frecuentes", @@ -483,9 +468,8 @@ "search_filters_duration_option_none": "Cualquier duración", "search_filters_features_option_vr180": "VR180", "search_filters_apply_button": "Aplicar filtros", - "tokens_count_0": "{{count}} token", - "tokens_count_1": "{{count}} tokens", - "tokens_count_2": "{{count}} tokens", + "tokens_count": "{{count}} token", + "tokens_count_plural": "{{count}} tokens", "search_message_use_another_instance": " También puede buscar en otra instancia.", "Popular enabled: ": "¿Habilitar la sección popular? ", "error_video_not_in_playlist": "El video que solicitaste no existe en esta lista de reproducción. Haz clic aquí para acceder a la página de inicio de la lista de reproducción.", From 6755e31b726fa857e75ede988af216a52eab8cc7 Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Sun, 14 May 2023 20:10:56 +0200 Subject: [PATCH 085/184] Fix hashtag continuation token --- src/invidious/hashtag.cr | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/invidious/hashtag.cr b/src/invidious/hashtag.cr index bc329205..d9d584c9 100644 --- a/src/invidious/hashtag.cr +++ b/src/invidious/hashtag.cr @@ -17,21 +17,18 @@ module Invidious::Hashtag "80226972:embedded" => { "2:string" => "FEhashtag", "3:base64" => { - "1:varint" => cursor.to_i64, - }, - "7:base64" => { - "325477796:embedded" => { - "1:embedded" => { - "2:0:embedded" => { - "2:string" => '#' + hashtag, - "4:varint" => 0_i64, - "11:string" => "", - }, - "4:string" => "browse-feedFEhashtag", - }, - "2:string" => hashtag, + "1:varint" => 60_i64, # result count + "15:base64" => { + "1:varint" => cursor.to_i64, + "2:varint" => 0_i64, + }, + "93:2:embedded" => { + "1:string" => hashtag, + "2:varint" => 0_i64, + "3:varint" => 1_i64, }, }, + "35:string" => "browse-feedFEhashtag", }, } From d6fb5c03b72b40bf7bd71f8023c71c76ea41f53d Mon Sep 17 00:00:00 2001 From: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com> Date: Thu, 16 Mar 2023 11:03:07 -0400 Subject: [PATCH 086/184] add hashtag endpoint --- src/invidious/routes/api/v1/search.cr | 30 +++++++++++++++++++++++++++ src/invidious/routing.cr | 1 + 2 files changed, 31 insertions(+) diff --git a/src/invidious/routes/api/v1/search.cr b/src/invidious/routes/api/v1/search.cr index 21451d33..0bf74bc3 100644 --- a/src/invidious/routes/api/v1/search.cr +++ b/src/invidious/routes/api/v1/search.cr @@ -55,4 +55,34 @@ module Invidious::Routes::API::V1::Search return error_json(500, ex) end end + + def self.hashtag(env) + hashtag = env.params.url["hashtag"] + + # page does not change anything. + # page = env.params.query["page"]?.try &.to_i?|| 1 + + page = 1 + locale = env.get("preferences").as(Preferences).locale + region = env.params.query["region"]? + env.response.content_type = "application/json" + + begin + results = Invidious::Hashtag.fetch(hashtag, page, region) + rescue ex + return error_json(400, ex) + end + + JSON.build do |json| + json.object do + json.field "results" do + json.array do + results.each do |item| + item.to_json(locale, json) + end + end + end + end + end + end end diff --git a/src/invidious/routing.cr b/src/invidious/routing.cr index 9e2ade3d..72ee9194 100644 --- a/src/invidious/routing.cr +++ b/src/invidious/routing.cr @@ -243,6 +243,7 @@ module Invidious::Routing # Search get "/api/v1/search", {{namespace}}::Search, :search get "/api/v1/search/suggestions", {{namespace}}::Search, :search_suggestions + get "/api/v1/hashtag/:hashtag", {{namespace}}::Search, :hashtag # Authenticated From d7285992517c98a276e325f83e1b7584dac3c498 Mon Sep 17 00:00:00 2001 From: chunky programmer <78101139+ChunkyProgrammer@users.noreply.github.com> Date: Sun, 14 May 2023 15:20:59 -0400 Subject: [PATCH 087/184] add page parameter --- src/invidious/routes/api/v1/search.cr | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/invidious/routes/api/v1/search.cr b/src/invidious/routes/api/v1/search.cr index 0bf74bc3..9fb283c2 100644 --- a/src/invidious/routes/api/v1/search.cr +++ b/src/invidious/routes/api/v1/search.cr @@ -59,10 +59,8 @@ module Invidious::Routes::API::V1::Search def self.hashtag(env) hashtag = env.params.url["hashtag"] - # page does not change anything. - # page = env.params.query["page"]?.try &.to_i?|| 1 + page = env.params.query["page"]?.try &.to_i? || 1 - page = 1 locale = env.get("preferences").as(Preferences).locale region = env.params.query["region"]? env.response.content_type = "application/json" From b2a0e6f1ffe448f8c3f6f943b34c673537210794 Mon Sep 17 00:00:00 2001 From: chunky programmer <78101139+ChunkyProgrammer@users.noreply.github.com> Date: Sun, 14 May 2023 16:49:49 -0400 Subject: [PATCH 088/184] Parse playlists when searching a channel --- src/invidious/yt_backend/extractors.cr | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/invidious/yt_backend/extractors.cr b/src/invidious/yt_backend/extractors.cr index 8ff4c1f9..6686e6e7 100644 --- a/src/invidious/yt_backend/extractors.cr +++ b/src/invidious/yt_backend/extractors.cr @@ -381,7 +381,7 @@ private module Parsers # Parses an InnerTube itemSectionRenderer into a SearchVideo. # Returns nil when the given object isn't a ItemSectionRenderer # - # A itemSectionRenderer seems to be a simple wrapper for a videoRenderer, used + # A itemSectionRenderer seems to be a simple wrapper for a videoRenderer or a playlistRenderer, used # by the result page for channel searches. It is located inside a continuationItems # container.It is very similar to RichItemRendererParser # @@ -394,6 +394,8 @@ private module Parsers private def self.parse(item_contents, author_fallback) child = VideoRendererParser.process(item_contents, author_fallback) + child ||= PlaylistRendererParser.process(item_contents, author_fallback) + return child end From 12b4dd9191307c2b3387a4c73c2fc06be5da7703 Mon Sep 17 00:00:00 2001 From: chunky programmer <78101139+ChunkyProgrammer@users.noreply.github.com> Date: Sun, 14 May 2023 17:25:32 -0400 Subject: [PATCH 089/184] Populate search bar with ChannelId --- src/invidious/routes/channels.cr | 1 + src/invidious/routes/search.cr | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/invidious/routes/channels.cr b/src/invidious/routes/channels.cr index d3969d29..740f3096 100644 --- a/src/invidious/routes/channels.cr +++ b/src/invidious/routes/channels.cr @@ -278,6 +278,7 @@ module Invidious::Routes::Channels return error_template(500, ex) end + env.set "search", "channel:" + ucid + " " return {locale, user, subscriptions, continuation, ucid, channel} end end diff --git a/src/invidious/routes/search.cr b/src/invidious/routes/search.cr index 2a9705cf..7f17124e 100644 --- a/src/invidious/routes/search.cr +++ b/src/invidious/routes/search.cr @@ -65,7 +65,11 @@ module Invidious::Routes::Search redirect_url = Invidious::Frontend::Misc.redirect_url(env) - env.set "search", query.text + if query.type == Invidious::Search::Query::Type::Channel + env.set "search", "channel:" + query.channel + " " + query.text + else + env.set "search", query.text + end templated "search" end end From c713c32cebda5d0199b5c0dd553744f8d61707da Mon Sep 17 00:00:00 2001 From: chunky programmer <78101139+ChunkyProgrammer@users.noreply.github.com> Date: Sun, 14 May 2023 22:35:51 -0400 Subject: [PATCH 090/184] Fix issue where playlists will refetch the same videos --- src/invidious/routes/playlists.cr | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/invidious/routes/playlists.cr b/src/invidious/routes/playlists.cr index 0d242ee6..8675fa45 100644 --- a/src/invidious/routes/playlists.cr +++ b/src/invidious/routes/playlists.cr @@ -410,8 +410,8 @@ module Invidious::Routes::Playlists return error_template(500, ex) end - page_count = (playlist.video_count / 100).to_i - page_count += 1 if (playlist.video_count % 100) > 0 + page_count = (playlist.video_count / 200).to_i + page_count += 1 if (playlist.video_count % 200) > 0 if page > page_count return env.redirect "/playlist?list=#{plid}&page=#{page_count}" @@ -422,7 +422,7 @@ module Invidious::Routes::Playlists end begin - videos = get_playlist_videos(playlist, offset: (page - 1) * 100) + videos = get_playlist_videos(playlist, offset: (page - 1) * 200) rescue ex return error_template(500, "Error encountered while retrieving playlist videos.
    #{ex.message}") end From 8bd2e60abc42f51e6cdd246e883ab953cabd78ae Mon Sep 17 00:00:00 2001 From: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com> Date: Mon, 22 May 2023 09:19:32 -0400 Subject: [PATCH 091/184] Use string interpolation instead of concatenation Co-authored-by: Samantaz Fox --- src/invidious/routes/channels.cr | 2 +- src/invidious/routes/search.cr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/invidious/routes/channels.cr b/src/invidious/routes/channels.cr index 740f3096..16621994 100644 --- a/src/invidious/routes/channels.cr +++ b/src/invidious/routes/channels.cr @@ -278,7 +278,7 @@ module Invidious::Routes::Channels return error_template(500, ex) end - env.set "search", "channel:" + ucid + " " + env.set "search", "channel:#{ucid} " return {locale, user, subscriptions, continuation, ucid, channel} end end diff --git a/src/invidious/routes/search.cr b/src/invidious/routes/search.cr index 7f17124e..6c3088de 100644 --- a/src/invidious/routes/search.cr +++ b/src/invidious/routes/search.cr @@ -66,7 +66,7 @@ module Invidious::Routes::Search redirect_url = Invidious::Frontend::Misc.redirect_url(env) if query.type == Invidious::Search::Query::Type::Channel - env.set "search", "channel:" + query.channel + " " + query.text + env.set "search", "channel:#{query.channel} #{query.text}" else env.set "search", query.text end From 6440ae0b5c15355dd87959412ea609396a198215 Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Tue, 9 May 2023 23:37:49 +0200 Subject: [PATCH 092/184] Community: Fix position of the "creator heart" (broken by #3783) --- assets/css/default.css | 2 ++ src/invidious/comments.cr | 12 ++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/assets/css/default.css b/assets/css/default.css index 23649f8f..431a0427 100644 --- a/assets/css/default.css +++ b/assets/css/default.css @@ -46,6 +46,7 @@ body a.channel-owner { } .creator-heart { + display: inline-block; position: relative; width: 16px; height: 16px; @@ -66,6 +67,7 @@ body a.channel-owner { } .creator-heart-small-container { + display: block; position: relative; width: 13px; height: 13px; diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr index 01556099..466c9fe5 100644 --- a/src/invidious/comments.cr +++ b/src/invidious/comments.cr @@ -409,7 +409,6 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false) html << <<-END_HTML #{number_with_separator(child["likeCount"])} -

    END_HTML if child["creatorHeart"]? @@ -420,13 +419,14 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false) end html << <<-END_HTML +   -
    + -
    -
    -
    -
    + + + +
    END_HTML end From ef4ff4e4b25855379bae367f96e40a267b227f83 Mon Sep 17 00:00:00 2001 From: gallegonovato Date: Tue, 9 May 2023 10:20:27 +0000 Subject: [PATCH 093/184] Update Spanish translation --- locales/es.json | 80 +++++++++++++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 32 deletions(-) diff --git a/locales/es.json b/locales/es.json index 68ff0170..74f80a64 100644 --- a/locales/es.json +++ b/locales/es.json @@ -398,36 +398,51 @@ "search_filters_features_option_three_sixty": "360°", "videoinfo_watch_on_youTube": "Ver en YouTube", "preferences_save_player_pos_label": "Guardar posición de reproducción: ", - "generic_views_count": "{{count}} vista", - "generic_views_count_plural": "{{count}} vistas", - "generic_subscribers_count": "{{count}} suscriptor", - "generic_subscribers_count_plural": "{{count}} suscriptores", - "generic_subscriptions_count": "{{count}} suscripción", - "generic_subscriptions_count_plural": "{{count}} suscripciones", - "subscriptions_unseen_notifs_count": "{{count}} notificación sin ver", - "subscriptions_unseen_notifs_count_plural": "{{count}} notificaciones sin ver", - "generic_count_days": "{{count}} día", - "generic_count_days_plural": "{{count}} días", - "comments_view_x_replies": "Ver {{count}} respuesta", - "comments_view_x_replies_plural": "Ver {{count}} respuestas", - "generic_count_weeks": "{{count}} semana", - "generic_count_weeks_plural": "{{count}} semanas", - "generic_playlists_count": "{{count}} reproducción", - "generic_playlists_count_plural": "{{count}} reproducciones", - "generic_videos_count": "{{count}} video", - "generic_videos_count_plural": "{{count}} videos", - "generic_count_months": "{{count}} mes", - "generic_count_months_plural": "{{count}} meses", - "comments_points_count": "{{count}} punto", - "comments_points_count_plural": "{{count}} puntos", - "generic_count_years": "{{count}} año", - "generic_count_years_plural": "{{count}} años", - "generic_count_hours": "{{count}} hora", - "generic_count_hours_plural": "{{count}} horas", - "generic_count_minutes": "{{count}} minuto", - "generic_count_minutes_plural": "{{count}} minutos", - "generic_count_seconds": "{{count}} segundo", - "generic_count_seconds_plural": "{{count}} segundos", + "generic_views_count_0": "{{count}} vista", + "generic_views_count_1": "{{count}} vistas", + "generic_views_count_2": "{{count}} vistas", + "generic_subscribers_count_0": "{{count}} suscriptor", + "generic_subscribers_count_1": "{{count}} suscriptores", + "generic_subscribers_count_2": "{{count}} suscriptores", + "generic_subscriptions_count_0": "{{count}} suscripción", + "generic_subscriptions_count_1": "{{count}} suscripciones", + "generic_subscriptions_count_2": "{{count}} suscripciones", + "subscriptions_unseen_notifs_count_0": "{{count}} notificación no vista", + "subscriptions_unseen_notifs_count_1": "{{count}} notificaciones no vistas", + "subscriptions_unseen_notifs_count_2": "{{count}} notificaciones no vistas", + "generic_count_days_0": "{{count}} día", + "generic_count_days_1": "{{count}} días", + "generic_count_days_2": "{{count}} días", + "comments_view_x_replies_0": "Ver {{count}} respuesta", + "comments_view_x_replies_1": "Ver las {{count}} respuestas", + "comments_view_x_replies_2": "Ver las {{count}} respuestas", + "generic_count_weeks_0": "{{count}} semana", + "generic_count_weeks_1": "{{count}} semanas", + "generic_count_weeks_2": "{{count}} semanas", + "generic_playlists_count_0": "{{count}} lista de reproducción", + "generic_playlists_count_1": "{{count}} listas de reproducciones", + "generic_playlists_count_2": "{{count}} listas de reproducciones", + "generic_videos_count_0": "{{count}} video", + "generic_videos_count_1": "{{count}} videos", + "generic_videos_count_2": "{{count}} videos", + "generic_count_months_0": "{{count}} mes", + "generic_count_months_1": "{{count}} meses", + "generic_count_months_2": "{{count}} meses", + "comments_points_count_0": "{{count}} punto", + "comments_points_count_1": "{{count}} puntos", + "comments_points_count_2": "{{count}} puntos", + "generic_count_years_0": "{{count}} año", + "generic_count_years_1": "{{count}} años", + "generic_count_years_2": "{{count}} años", + "generic_count_hours_0": "{{count}} hora", + "generic_count_hours_1": "{{count}} horas", + "generic_count_hours_2": "{{count}} horas", + "generic_count_minutes_0": "{{count}} minuto", + "generic_count_minutes_1": "{{count}} minutos", + "generic_count_minutes_2": "{{count}} minutos", + "generic_count_seconds_0": "{{count}} segundo", + "generic_count_seconds_1": "{{count}} segundos", + "generic_count_seconds_2": "{{count}} segundos", "crash_page_before_reporting": "Antes de notificar un error asegúrate de que has:", "crash_page_switch_instance": "probado a usar otra instancia", "crash_page_read_the_faq": "leído las Preguntas Frecuentes", @@ -468,8 +483,9 @@ "search_filters_duration_option_none": "Cualquier duración", "search_filters_features_option_vr180": "VR180", "search_filters_apply_button": "Aplicar filtros", - "tokens_count": "{{count}} token", - "tokens_count_plural": "{{count}} tokens", + "tokens_count_0": "{{count}} token", + "tokens_count_1": "{{count}} tokens", + "tokens_count_2": "{{count}} tokens", "search_message_use_another_instance": " También puede buscar en otra instancia.", "Popular enabled: ": "¿Habilitar la sección popular? ", "error_video_not_in_playlist": "El video que solicitaste no existe en esta lista de reproducción. Haz clic aquí para acceder a la página de inicio de la lista de reproducción.", From a79b7ef170f8806c25f43b0fa5deaaa7e27eb53d Mon Sep 17 00:00:00 2001 From: maboroshin Date: Wed, 10 May 2023 11:40:49 +0000 Subject: [PATCH 094/184] Update Japanese translation --- locales/ja.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/locales/ja.json b/locales/ja.json index d1813bcd..49763cc8 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -314,7 +314,7 @@ "Zulu": "ズール語", "generic_count_years_0": "{{count}}年", "generic_count_months_0": "{{count}}か月", - "generic_count_weeks_0": "{{count}}週", + "generic_count_weeks_0": "{{count}}週間", "generic_count_days_0": "{{count}}日", "generic_count_hours_0": "{{count}}時間", "generic_count_minutes_0": "{{count}}分", @@ -442,7 +442,7 @@ "crash_page_switch_instance": "別のインスタンスを使用を試す", "crash_page_read_the_faq": "よくある質問 (FAQ) を読む", "Popular enabled: ": "人気動画を有効化 ", - "search_message_use_another_instance": " 別のインスタンス上でも検索できます。", + "search_message_use_another_instance": " 別のインスタンス上での検索も可能です。", "search_filters_apply_button": "選択したフィルターを適用", "user_saved_playlists": "`x` 個の保存した再生リスト", "crash_page_you_found_a_bug": "Invidious のバグのようです!", @@ -466,5 +466,6 @@ "Album: ": "アルバム: ", "Song: ": "曲: ", "Channel Sponsor": "チャンネルのスポンサー", - "Standard YouTube license": "標準 Youtube ライセンス" + "Standard YouTube license": "標準 Youtube ライセンス", + "Download is disabled": "ダウンロード: このインスタンスでは未対応" } From e65671454287e0aabf1bdb4619f9a352d56d12e0 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 17 May 2023 16:34:16 +0000 Subject: [PATCH 095/184] Update German translation --- locales/de.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 0df86663..1a6f2cea 100644 --- a/locales/de.json +++ b/locales/de.json @@ -482,5 +482,6 @@ "channel_tab_channels_label": "Kanäle", "Channel Sponsor": "Kanalsponsor", "Standard YouTube license": "Standard YouTube-Lizenz", - "Song: ": "Musik: " + "Song: ": "Musik: ", + "Download is disabled": "Herunterladen ist deaktiviert" } From f2cc97b2902dc647c0deb97e0a554e41ef2c2cce Mon Sep 17 00:00:00 2001 From: joaooliva Date: Sat, 20 May 2023 14:27:29 +0000 Subject: [PATCH 096/184] Update Portuguese (Brazil) translation --- locales/pt-BR.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/locales/pt-BR.json b/locales/pt-BR.json index ec00d46e..759aec94 100644 --- a/locales/pt-BR.json +++ b/locales/pt-BR.json @@ -479,5 +479,9 @@ "channel_tab_streams_label": "Ao Vivo", "Music in this video": "Música neste vídeo", "Artist: ": "Artista: ", - "Album: ": "Álbum: " + "Album: ": "Álbum: ", + "Standard YouTube license": "Licença padrão do YouTube", + "Song: ": "Música: ", + "Channel Sponsor": "Patrocinador do Canal", + "Download is disabled": "Download está desativado" } From 11d45adcdcd20e1a0c0f2c403e59e2d2ff801388 Mon Sep 17 00:00:00 2001 From: Ashirg-ch Date: Tue, 23 May 2023 08:03:47 +0000 Subject: [PATCH 097/184] Update German translation --- locales/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/de.json b/locales/de.json index 1a6f2cea..3c1120c0 100644 --- a/locales/de.json +++ b/locales/de.json @@ -433,7 +433,7 @@ "comments_points_count_plural": "{{count}} Punkte", "crash_page_you_found_a_bug": "Anscheinend haben Sie einen Fehler in Invidious gefunden!", "generic_count_months": "{{count}} Monat", - "generic_count_months_plural": "{{count}} Monate", + "generic_count_months_plural": "{{count}} Monaten", "Cantonese (Hong Kong)": "Kantonesisch (Hong Kong)", "Chinese (Hong Kong)": "Chinesisch (Hong Kong)", "generic_playlists_count": "{{count}} Wiedergabeliste", From 67a79faaeb0f87b93f1247f8c87ff9aba5018b22 Mon Sep 17 00:00:00 2001 From: Matthaiks Date: Tue, 23 May 2023 20:15:19 +0000 Subject: [PATCH 098/184] Update Polish translation --- locales/pl.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/pl.json b/locales/pl.json index 2b6768d9..ca80757c 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -499,5 +499,6 @@ "Album: ": "Album: ", "Song: ": "Piosenka: ", "Channel Sponsor": "Sponsor kanału", - "Standard YouTube license": "Standardowa licencja YouTube" + "Standard YouTube license": "Standardowa licencja YouTube", + "Import YouTube playlist (.csv)": "Importuj playlistę YouTube (.csv)" } From 7e3c685cd619cc65ae6b7e34c22de8471dc1bd00 Mon Sep 17 00:00:00 2001 From: Rex_sa Date: Thu, 25 May 2023 06:12:01 +0000 Subject: [PATCH 099/184] Update Arabic translation --- locales/ar.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/ar.json b/locales/ar.json index 7303915b..6fe5b8bf 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -547,5 +547,6 @@ "Song: ": "أغنية: ", "Channel Sponsor": "راعي القناة", "Standard YouTube license": "ترخيص YouTube القياسي", - "Download is disabled": "تم تعطيل التحميلات" + "Download is disabled": "تم تعطيل التحميلات", + "Import YouTube playlist (.csv)": "استيراد قائمة تشغيل YouTube (.csv)" } From f0120bece165f3d325b758e3b1d837b084f0dd62 Mon Sep 17 00:00:00 2001 From: atilluF <110931720+atilluF@users.noreply.github.com> Date: Thu, 25 May 2023 15:26:00 +0000 Subject: [PATCH 100/184] Update Italian translation --- locales/it.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/it.json b/locales/it.json index 0797b387..9299add7 100644 --- a/locales/it.json +++ b/locales/it.json @@ -483,5 +483,6 @@ "Download is disabled": "Il download è disabilitato", "Song: ": "Canzone: ", "Standard YouTube license": "Licenza standard di YouTube", - "Channel Sponsor": "Sponsor del canale" + "Channel Sponsor": "Sponsor del canale", + "Import YouTube playlist (.csv)": "Importa playlist di YouTube (.csv)" } From 184bd3204fe0122ab18296962de9ba976fb89ff2 Mon Sep 17 00:00:00 2001 From: Jorge Maldonado Ventura Date: Tue, 23 May 2023 20:16:45 +0000 Subject: [PATCH 101/184] Update Spanish translation --- locales/es.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/es.json b/locales/es.json index 74f80a64..4940dd90 100644 --- a/locales/es.json +++ b/locales/es.json @@ -499,5 +499,6 @@ "Song: ": "Canción: ", "Channel Sponsor": "Patrocinador del canal", "Standard YouTube license": "Licencia de YouTube estándar", - "Download is disabled": "La descarga está deshabilitada" + "Download is disabled": "La descarga está deshabilitada", + "Import YouTube playlist (.csv)": "Importar lista de reproducción de YouTube (.csv)" } From ea6db9c58ab74c248cda7dfa8ec2e68f1868b49f Mon Sep 17 00:00:00 2001 From: Jorge Maldonado Ventura Date: Tue, 23 May 2023 20:16:16 +0000 Subject: [PATCH 102/184] Update Esperanto translation --- locales/eo.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/eo.json b/locales/eo.json index 464d16ca..4e789390 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -483,5 +483,6 @@ "Channel Sponsor": "Kanala sponsoro", "Song: ": "Muzikaĵo: ", "Standard YouTube license": "Implicita YouTube-licenco", - "Download is disabled": "Elŝuto estas malebligita" + "Download is disabled": "Elŝuto estas malebligita", + "Import YouTube playlist (.csv)": "Importi YouTube-ludliston (.csv)" } From fd06656d86a68d226458e2648cded2603fa07bbf Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Tue, 23 May 2023 21:50:59 +0000 Subject: [PATCH 103/184] Update Ukrainian translation --- locales/uk.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/uk.json b/locales/uk.json index 61bf3d31..863916f7 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -499,5 +499,6 @@ "Song: ": "Пісня: ", "Channel Sponsor": "Спонсор каналу", "Standard YouTube license": "Стандартна ліцензія YouTube", - "Download is disabled": "Завантаження вимкнено" + "Download is disabled": "Завантаження вимкнено", + "Import YouTube playlist (.csv)": "Імпорт списку відтворення YouTube (.csv)" } From e8df08e41eedfd26364110562f134735e307d7b6 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 25 May 2023 07:58:41 +0000 Subject: [PATCH 104/184] Update Chinese (Simplified) translation --- locales/zh-CN.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/zh-CN.json b/locales/zh-CN.json index df31812a..fdd940c3 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -467,5 +467,6 @@ "Song: ": "歌曲: ", "Channel Sponsor": "频道赞助者", "Standard YouTube license": "标准 YouTube 许可证", - "Download is disabled": "已禁用下载" + "Download is disabled": "已禁用下载", + "Import YouTube playlist (.csv)": "导入 YouTube 播放列表(.csv)" } From f0f6cb0d83545d852dddac94b9d7ce7bf666db60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Wed, 24 May 2023 17:45:42 +0000 Subject: [PATCH 105/184] Update Turkish translation --- locales/tr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/tr.json b/locales/tr.json index a2fdd573..ca74ef23 100644 --- a/locales/tr.json +++ b/locales/tr.json @@ -483,5 +483,6 @@ "Channel Sponsor": "Kanal Sponsoru", "Song: ": "Şarkı: ", "Standard YouTube license": "Standart YouTube lisansı", - "Download is disabled": "İndirme devre dışı" + "Download is disabled": "İndirme devre dışı", + "Import YouTube playlist (.csv)": "YouTube Oynatma Listesini İçe Aktar (.csv)" } From a727bb037f4c75f6c30e0e52f050a6e6c8d4325f Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Thu, 25 May 2023 02:00:55 +0000 Subject: [PATCH 106/184] Update Chinese (Traditional) translation --- locales/zh-TW.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/zh-TW.json b/locales/zh-TW.json index daa22493..593a946a 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -467,5 +467,6 @@ "Channel Sponsor": "頻道贊助者", "Song: ": "歌曲: ", "Standard YouTube license": "標準 YouTube 授權條款", - "Download is disabled": "已停用下載" + "Download is disabled": "已停用下載", + "Import YouTube playlist (.csv)": "匯入 YouTube 播放清單 (.csv)" } From ed2d16c91d2b7a2de51e556bc7e360e18a58a854 Mon Sep 17 00:00:00 2001 From: maboroshin Date: Wed, 24 May 2023 13:27:34 +0000 Subject: [PATCH 107/184] Update Japanese translation --- locales/ja.json | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/locales/ja.json b/locales/ja.json index 49763cc8..d9207d3f 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -8,8 +8,8 @@ "Shared `x` ago": "`x`前に公開", "Unsubscribe": "登録解除", "Subscribe": "登録", - "View channel on YouTube": "YouTube でチャンネルを見る", - "View playlist on YouTube": "YouTube で再生リストを見る", + "View channel on YouTube": "YouTube でチャンネルを表示", + "View playlist on YouTube": "YouTube で再生リストを表示", "newest": "新しい順", "oldest": "古い順", "popular": "人気順", @@ -69,7 +69,7 @@ "preferences_captions_label": "優先する字幕: ", "Fallback captions: ": "フォールバック時の字幕: ", "preferences_related_videos_label": "関連動画を表示: ", - "preferences_annotations_label": "デフォルトでアノテーションを表示: ", + "preferences_annotations_label": "最初からアノテーションを表示: ", "preferences_extend_desc_label": "動画の説明文を自動的に拡張: ", "preferences_vr_mode_label": "対話的な360°動画 (WebGL が必要): ", "preferences_category_visual": "外観設定", @@ -82,7 +82,7 @@ "preferences_category_misc": "ほかの設定", "preferences_automatic_instance_redirect_label": "インスタンスの自動転送 (redirect.invidious.ioにフォールバック): ", "preferences_category_subscription": "登録チャンネル設定", - "preferences_annotations_subscribed_label": "デフォルトで登録チャンネルのアノテーションを表示しますか? ", + "preferences_annotations_subscribed_label": "最初から登録チャンネルのアノテーションを表示 ", "Redirect homepage to feed: ": "ホームからフィードにリダイレクト: ", "preferences_max_results_label": "フィードに表示する動画の量: ", "preferences_sort_label": "動画を並び替え: ", @@ -110,7 +110,7 @@ "preferences_category_admin": "管理者設定", "preferences_default_home_label": "ホームに表示するページ: ", "preferences_feed_menu_label": "フィードメニュー: ", - "preferences_show_nick_label": "ニックネームを一番上に表示する: ", + "preferences_show_nick_label": "ログイン名を上部に表示: ", "Top enabled: ": "トップページを有効化: ", "CAPTCHA enabled: ": "CAPTCHA を有効化: ", "Login enabled: ": "ログインを有効化: ", @@ -131,7 +131,7 @@ "Released under the AGPLv3 on Github.": "GitHub 上で AGPLv3 の元で公開", "Source available here.": "ソースはここで閲覧可能です。", "View JavaScript license information.": "JavaScript ライセンス情報", - "View privacy policy.": "プライバシーポリシー", + "View privacy policy.": "個人情報保護方針", "Trending": "急上昇", "Public": "公開", "Unlisted": "限定公開", @@ -142,11 +142,11 @@ "Delete playlist": "再生リストを削除", "Create playlist": "再生リストを作成", "Title": "タイトル", - "Playlist privacy": "再生リストの公開設定", + "Playlist privacy": "再生リストの公開状態", "Editing playlist `x`": "再生リスト `x` を編集中", "Show more": "もっと見る", "Show less": "表示を少なく", - "Watch on YouTube": "YouTube で視聴", + "Watch on YouTube": "YouTubeで視聴", "Switch Invidious Instance": "Invidious インスタンスの変更", "Hide annotations": "アノテーションを隠す", "Show annotations": "アノテーションを表示", @@ -161,13 +161,13 @@ "Premieres in `x`": "`x`後にプレミア公開", "Premieres `x`": "`x`にプレミア公開", "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "やあ!君は JavaScript を無効にしているのかな?ここをクリックしてコメントを見れるけど、読み込みには少し時間がかかることがあるのを覚えておいてね。", - "View YouTube comments": "YouTube のコメントを見る", + "View YouTube comments": "YouTube のコメントを表示", "View more comments on Reddit": "Reddit でコメントをもっと見る", "View `x` comments": { - "([^.,0-9]|^)1([^.,0-9]|$)": "`x` 件のコメントを見る", - "": "`x` 件のコメントを見る" + "([^.,0-9]|^)1([^.,0-9]|$)": "`x` 件のコメントを表示", + "": "`x` 件のコメントを表示" }, - "View Reddit comments": "Reddit のコメントを見る", + "View Reddit comments": "Reddit のコメントを表示", "Hide replies": "返信を非表示", "Show replies": "返信を表示", "Incorrect password": "パスワードが間違っています", @@ -326,8 +326,8 @@ "About": "このサービスについて", "Rating: ": "評価: ", "preferences_locale_label": "言語: ", - "View as playlist": "再生リストで見る", - "Default": "デフォルト", + "View as playlist": "再生リストとして閲覧", + "Default": "標準", "Music": "音楽", "Gaming": "ゲーム", "News": "ニュース", @@ -375,7 +375,7 @@ "next_steps_error_message_refresh": "再読込", "next_steps_error_message_go_to_youtube": "YouTubeへ", "search_filters_duration_option_short": "4 分未満", - "footer_documentation": "文書", + "footer_documentation": "説明書", "footer_source_code": "ソースコード", "footer_original_source_code": "元のソースコード", "footer_modfied_source_code": "改変して使用", @@ -407,7 +407,7 @@ "preferences_quality_dash_option_worst": "最悪", "preferences_quality_dash_option_best": "最高", "videoinfo_started_streaming_x_ago": "`x`前に配信を開始", - "videoinfo_watch_on_youTube": "YouTube上で見る", + "videoinfo_watch_on_youTube": "YouTubeで視聴", "user_created_playlists": "`x`個の作成した再生リスト", "Video unavailable": "動画は利用できません", "Chinese": "中国語", @@ -467,5 +467,6 @@ "Song: ": "曲: ", "Channel Sponsor": "チャンネルのスポンサー", "Standard YouTube license": "標準 Youtube ライセンス", - "Download is disabled": "ダウンロード: このインスタンスでは未対応" + "Download is disabled": "ダウンロード: このインスタンスでは未対応", + "Import YouTube playlist (.csv)": "YouTube 再生リストをインポート (.csv)" } From fe97b3d76185080cd63245b32f067ed5dad94a4c Mon Sep 17 00:00:00 2001 From: Milo Ivir Date: Tue, 23 May 2023 21:16:15 +0000 Subject: [PATCH 108/184] Update Croatian translation --- locales/hr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/hr.json b/locales/hr.json index b87a7729..46e07b83 100644 --- a/locales/hr.json +++ b/locales/hr.json @@ -499,5 +499,6 @@ "Channel Sponsor": "Sponzor kanala", "Song: ": "Pjesma: ", "Standard YouTube license": "Standardna YouTube licenca", - "Download is disabled": "Preuzimanje je deaktivirano" + "Download is disabled": "Preuzimanje je deaktivirano", + "Import YouTube playlist (.csv)": "Uvezi YouTube zbirku (.csv)" } From c9eafb250f108505b6aa4559f708ae45d3845df7 Mon Sep 17 00:00:00 2001 From: Fjuro Date: Wed, 24 May 2023 18:35:19 +0000 Subject: [PATCH 109/184] Update Czech translation --- locales/cs.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/cs.json b/locales/cs.json index d9e5b4d5..8e656827 100644 --- a/locales/cs.json +++ b/locales/cs.json @@ -499,5 +499,6 @@ "Channel Sponsor": "Sponzor kanálu", "Song: ": "Skladba: ", "Standard YouTube license": "Standardní licence YouTube", - "Download is disabled": "Stahování je zakázáno" + "Download is disabled": "Stahování je zakázáno", + "Import YouTube playlist (.csv)": "Importovat YouTube playlist (.csv)" } From 4b29f8254a412fc7a0f278a1677844627499e455 Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Thu, 25 May 2023 22:44:08 +0200 Subject: [PATCH 110/184] Fix broken Spanish locale (i18next v3->v4 mixup) --- locales/es.json | 80 ++++++++++++++++++++----------------------------- 1 file changed, 32 insertions(+), 48 deletions(-) diff --git a/locales/es.json b/locales/es.json index 4940dd90..0425ed68 100644 --- a/locales/es.json +++ b/locales/es.json @@ -398,51 +398,36 @@ "search_filters_features_option_three_sixty": "360°", "videoinfo_watch_on_youTube": "Ver en YouTube", "preferences_save_player_pos_label": "Guardar posición de reproducción: ", - "generic_views_count_0": "{{count}} vista", - "generic_views_count_1": "{{count}} vistas", - "generic_views_count_2": "{{count}} vistas", - "generic_subscribers_count_0": "{{count}} suscriptor", - "generic_subscribers_count_1": "{{count}} suscriptores", - "generic_subscribers_count_2": "{{count}} suscriptores", - "generic_subscriptions_count_0": "{{count}} suscripción", - "generic_subscriptions_count_1": "{{count}} suscripciones", - "generic_subscriptions_count_2": "{{count}} suscripciones", - "subscriptions_unseen_notifs_count_0": "{{count}} notificación no vista", - "subscriptions_unseen_notifs_count_1": "{{count}} notificaciones no vistas", - "subscriptions_unseen_notifs_count_2": "{{count}} notificaciones no vistas", - "generic_count_days_0": "{{count}} día", - "generic_count_days_1": "{{count}} días", - "generic_count_days_2": "{{count}} días", - "comments_view_x_replies_0": "Ver {{count}} respuesta", - "comments_view_x_replies_1": "Ver las {{count}} respuestas", - "comments_view_x_replies_2": "Ver las {{count}} respuestas", - "generic_count_weeks_0": "{{count}} semana", - "generic_count_weeks_1": "{{count}} semanas", - "generic_count_weeks_2": "{{count}} semanas", - "generic_playlists_count_0": "{{count}} lista de reproducción", - "generic_playlists_count_1": "{{count}} listas de reproducciones", - "generic_playlists_count_2": "{{count}} listas de reproducciones", - "generic_videos_count_0": "{{count}} video", - "generic_videos_count_1": "{{count}} videos", - "generic_videos_count_2": "{{count}} videos", - "generic_count_months_0": "{{count}} mes", - "generic_count_months_1": "{{count}} meses", - "generic_count_months_2": "{{count}} meses", - "comments_points_count_0": "{{count}} punto", - "comments_points_count_1": "{{count}} puntos", - "comments_points_count_2": "{{count}} puntos", - "generic_count_years_0": "{{count}} año", - "generic_count_years_1": "{{count}} años", - "generic_count_years_2": "{{count}} años", - "generic_count_hours_0": "{{count}} hora", - "generic_count_hours_1": "{{count}} horas", - "generic_count_hours_2": "{{count}} horas", - "generic_count_minutes_0": "{{count}} minuto", - "generic_count_minutes_1": "{{count}} minutos", - "generic_count_minutes_2": "{{count}} minutos", - "generic_count_seconds_0": "{{count}} segundo", - "generic_count_seconds_1": "{{count}} segundos", - "generic_count_seconds_2": "{{count}} segundos", + "generic_views_count": "{{count}} vista", + "generic_views_count_plural": "{{count}} vistas", + "generic_subscribers_count": "{{count}} suscriptor", + "generic_subscribers_count_plural": "{{count}} suscriptores", + "generic_subscriptions_count": "{{count}} suscripción", + "generic_subscriptions_count_plural": "{{count}} suscripciones", + "subscriptions_unseen_notifs_count": "{{count}} notificación no vista", + "subscriptions_unseen_notifs_count_plural": "{{count}} notificaciones no vistas", + "generic_count_days": "{{count}} día", + "generic_count_days_plural": "{{count}} días", + "comments_view_x_replies": "Ver {{count}} respuesta", + "comments_view_x_replies_plural": "Ver {{count}} respuestas", + "generic_count_weeks": "{{count}} semana", + "generic_count_weeks_plural": "{{count}} semanas", + "generic_playlists_count": "{{count}} lista de reproducción", + "generic_playlists_count_plural": "{{count}} listas de reproducciones", + "generic_videos_count": "{{count}} video", + "generic_videos_count_plural": "{{count}} videos", + "generic_count_months": "{{count}} mes", + "generic_count_months_plural": "{{count}} meses", + "comments_points_count": "{{count}} punto", + "comments_points_count_plural": "{{count}} puntos", + "generic_count_years": "{{count}} año", + "generic_count_years_plural": "{{count}} años", + "generic_count_hours": "{{count}} hora", + "generic_count_hours_plural": "{{count}} horas", + "generic_count_minutes": "{{count}} minuto", + "generic_count_minutes_plural": "{{count}} minutos", + "generic_count_seconds": "{{count}} segundo", + "generic_count_seconds_plural": "{{count}} segundos", "crash_page_before_reporting": "Antes de notificar un error asegúrate de que has:", "crash_page_switch_instance": "probado a usar otra instancia", "crash_page_read_the_faq": "leído las Preguntas Frecuentes", @@ -483,9 +468,8 @@ "search_filters_duration_option_none": "Cualquier duración", "search_filters_features_option_vr180": "VR180", "search_filters_apply_button": "Aplicar filtros", - "tokens_count_0": "{{count}} token", - "tokens_count_1": "{{count}} tokens", - "tokens_count_2": "{{count}} tokens", + "tokens_count": "{{count}} token", + "tokens_count_plural": "{{count}} tokens", "search_message_use_another_instance": " También puede buscar en otra instancia.", "Popular enabled: ": "¿Habilitar la sección popular? ", "error_video_not_in_playlist": "El video que solicitaste no existe en esta lista de reproducción. Haz clic aquí para acceder a la página de inicio de la lista de reproducción.", From c7876d564f09995244186f57d61cedfeb63038b6 Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Sat, 6 May 2023 19:50:35 +0200 Subject: [PATCH 111/184] Comments: add 'require' statement for a dedicated folder --- src/invidious.cr | 1 + 1 file changed, 1 insertion(+) diff --git a/src/invidious.cr b/src/invidious.cr index d4f8e0fb..b5abd5c7 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -43,6 +43,7 @@ require "./invidious/videos/*" require "./invidious/jsonify/**" require "./invidious/*" +require "./invidious/comments/*" require "./invidious/channels/*" require "./invidious/user/*" require "./invidious/search/*" From 8dd18248692726e8db05138c4ce2b01f39ad62f6 Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Sat, 6 May 2023 19:51:49 +0200 Subject: [PATCH 112/184] Comments: Move reddit type definitions to their own file --- src/invidious/comments.cr | 58 -------------------------- src/invidious/comments/reddit_types.cr | 57 +++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 58 deletions(-) create mode 100644 src/invidious/comments/reddit_types.cr diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr index 466c9fe5..00e8d399 100644 --- a/src/invidious/comments.cr +++ b/src/invidious/comments.cr @@ -1,61 +1,3 @@ -class RedditThing - include JSON::Serializable - - property kind : String - property data : RedditComment | RedditLink | RedditMore | RedditListing -end - -class RedditComment - include JSON::Serializable - - property author : String - property body_html : String - property replies : RedditThing | String - property score : Int32 - property depth : Int32 - property permalink : String - - @[JSON::Field(converter: RedditComment::TimeConverter)] - property created_utc : Time - - module TimeConverter - def self.from_json(value : JSON::PullParser) : Time - Time.unix(value.read_float.to_i) - end - - def self.to_json(value : Time, json : JSON::Builder) - json.number(value.to_unix) - end - end -end - -struct RedditLink - include JSON::Serializable - - property author : String - property score : Int32 - property subreddit : String - property num_comments : Int32 - property id : String - property permalink : String - property title : String -end - -struct RedditMore - include JSON::Serializable - - property children : Array(String) - property count : Int32 - property depth : Int32 -end - -class RedditListing - include JSON::Serializable - - property children : Array(RedditThing) - property modhash : String -end - def fetch_youtube_comments(id, cursor, format, locale, thin_mode, region, sort_by = "top") case cursor when nil, "" diff --git a/src/invidious/comments/reddit_types.cr b/src/invidious/comments/reddit_types.cr new file mode 100644 index 00000000..796a1183 --- /dev/null +++ b/src/invidious/comments/reddit_types.cr @@ -0,0 +1,57 @@ +class RedditThing + include JSON::Serializable + + property kind : String + property data : RedditComment | RedditLink | RedditMore | RedditListing +end + +class RedditComment + include JSON::Serializable + + property author : String + property body_html : String + property replies : RedditThing | String + property score : Int32 + property depth : Int32 + property permalink : String + + @[JSON::Field(converter: RedditComment::TimeConverter)] + property created_utc : Time + + module TimeConverter + def self.from_json(value : JSON::PullParser) : Time + Time.unix(value.read_float.to_i) + end + + def self.to_json(value : Time, json : JSON::Builder) + json.number(value.to_unix) + end + end +end + +struct RedditLink + include JSON::Serializable + + property author : String + property score : Int32 + property subreddit : String + property num_comments : Int32 + property id : String + property permalink : String + property title : String +end + +struct RedditMore + include JSON::Serializable + + property children : Array(String) + property count : Int32 + property depth : Int32 +end + +class RedditListing + include JSON::Serializable + + property children : Array(RedditThing) + property modhash : String +end From 1b25737b013d0589f396fa938ba2747e9a76af93 Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Sat, 6 May 2023 19:56:30 +0200 Subject: [PATCH 113/184] Comments: Move 'fetch_youtube' function to own file + module --- src/invidious/comments.cr | 203 ------------------------- src/invidious/comments/youtube.cr | 206 ++++++++++++++++++++++++++ src/invidious/routes/api/v1/videos.cr | 2 +- src/invidious/routes/watch.cr | 6 +- 4 files changed, 210 insertions(+), 207 deletions(-) create mode 100644 src/invidious/comments/youtube.cr diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr index 00e8d399..07579cf3 100644 --- a/src/invidious/comments.cr +++ b/src/invidious/comments.cr @@ -1,206 +1,3 @@ -def fetch_youtube_comments(id, cursor, format, locale, thin_mode, region, sort_by = "top") - case cursor - when nil, "" - ctoken = produce_comment_continuation(id, cursor: "", sort_by: sort_by) - when .starts_with? "ADSJ" - ctoken = produce_comment_continuation(id, cursor: cursor, sort_by: sort_by) - else - ctoken = cursor - end - - client_config = YoutubeAPI::ClientConfig.new(region: region) - response = YoutubeAPI.next(continuation: ctoken, client_config: client_config) - contents = nil - - if on_response_received_endpoints = response["onResponseReceivedEndpoints"]? - header = nil - on_response_received_endpoints.as_a.each do |item| - if item["reloadContinuationItemsCommand"]? - case item["reloadContinuationItemsCommand"]["slot"] - when "RELOAD_CONTINUATION_SLOT_HEADER" - header = item["reloadContinuationItemsCommand"]["continuationItems"][0] - when "RELOAD_CONTINUATION_SLOT_BODY" - # continuationItems is nil when video has no comments - contents = item["reloadContinuationItemsCommand"]["continuationItems"]? - end - elsif item["appendContinuationItemsAction"]? - contents = item["appendContinuationItemsAction"]["continuationItems"] - end - end - elsif response["continuationContents"]? - response = response["continuationContents"] - if response["commentRepliesContinuation"]? - body = response["commentRepliesContinuation"] - else - body = response["itemSectionContinuation"] - end - contents = body["contents"]? - header = body["header"]? - else - raise NotFoundException.new("Comments not found.") - end - - if !contents - if format == "json" - return {"comments" => [] of String}.to_json - else - return {"contentHtml" => "", "commentCount" => 0}.to_json - end - end - - continuation_item_renderer = nil - contents.as_a.reject! do |item| - if item["continuationItemRenderer"]? - continuation_item_renderer = item["continuationItemRenderer"] - true - end - end - - response = JSON.build do |json| - json.object do - if header - count_text = header["commentsHeaderRenderer"]["countText"] - comment_count = (count_text["simpleText"]? || count_text["runs"]?.try &.[0]?.try &.["text"]?) - .try &.as_s.gsub(/\D/, "").to_i? || 0 - json.field "commentCount", comment_count - end - - json.field "videoId", id - - json.field "comments" do - json.array do - contents.as_a.each do |node| - json.object do - if node["commentThreadRenderer"]? - node = node["commentThreadRenderer"] - end - - if node["replies"]? - node_replies = node["replies"]["commentRepliesRenderer"] - end - - if node["comment"]? - node_comment = node["comment"]["commentRenderer"] - else - node_comment = node["commentRenderer"] - end - - content_html = node_comment["contentText"]?.try { |t| parse_content(t, id) } || "" - author = node_comment["authorText"]?.try &.["simpleText"]? || "" - - json.field "verified", (node_comment["authorCommentBadge"]? != nil) - - json.field "author", author - json.field "authorThumbnails" do - json.array do - node_comment["authorThumbnail"]["thumbnails"].as_a.each do |thumbnail| - json.object do - json.field "url", thumbnail["url"] - json.field "width", thumbnail["width"] - json.field "height", thumbnail["height"] - end - end - end - end - - if node_comment["authorEndpoint"]? - json.field "authorId", node_comment["authorEndpoint"]["browseEndpoint"]["browseId"] - json.field "authorUrl", node_comment["authorEndpoint"]["browseEndpoint"]["canonicalBaseUrl"] - else - json.field "authorId", "" - json.field "authorUrl", "" - end - - published_text = node_comment["publishedTimeText"]["runs"][0]["text"].as_s - published = decode_date(published_text.rchop(" (edited)")) - - if published_text.includes?(" (edited)") - json.field "isEdited", true - else - json.field "isEdited", false - end - - json.field "content", html_to_content(content_html) - json.field "contentHtml", content_html - - json.field "isPinned", (node_comment["pinnedCommentBadge"]? != nil) - json.field "isSponsor", (node_comment["sponsorCommentBadge"]? != nil) - if node_comment["sponsorCommentBadge"]? - # Sponsor icon thumbnails always have one object and there's only ever the url property in it - json.field "sponsorIconUrl", node_comment.dig("sponsorCommentBadge", "sponsorCommentBadgeRenderer", "customBadge", "thumbnails", 0, "url").to_s - end - json.field "published", published.to_unix - json.field "publishedText", translate(locale, "`x` ago", recode_date(published, locale)) - - comment_action_buttons_renderer = node_comment["actionButtons"]["commentActionButtonsRenderer"] - - json.field "likeCount", comment_action_buttons_renderer["likeButton"]["toggleButtonRenderer"]["accessibilityData"]["accessibilityData"]["label"].as_s.scan(/\d/).map(&.[0]).join.to_i - json.field "commentId", node_comment["commentId"] - json.field "authorIsChannelOwner", node_comment["authorIsChannelOwner"] - - if comment_action_buttons_renderer["creatorHeart"]? - hearth_data = comment_action_buttons_renderer["creatorHeart"]["creatorHeartRenderer"]["creatorThumbnail"] - json.field "creatorHeart" do - json.object do - json.field "creatorThumbnail", hearth_data["thumbnails"][-1]["url"] - json.field "creatorName", hearth_data["accessibility"]["accessibilityData"]["label"] - end - end - end - - if node_replies && !response["commentRepliesContinuation"]? - if node_replies["continuations"]? - continuation = node_replies["continuations"]?.try &.as_a[0]["nextContinuationData"]["continuation"].as_s - elsif node_replies["contents"]? - continuation = node_replies["contents"]?.try &.as_a[0]["continuationItemRenderer"]["continuationEndpoint"]["continuationCommand"]["token"].as_s - end - continuation ||= "" - - json.field "replies" do - json.object do - json.field "replyCount", node_comment["replyCount"]? || 1 - json.field "continuation", continuation - end - end - end - end - end - end - end - - if continuation_item_renderer - if continuation_item_renderer["continuationEndpoint"]? - continuation_endpoint = continuation_item_renderer["continuationEndpoint"] - elsif continuation_item_renderer["button"]? - continuation_endpoint = continuation_item_renderer["button"]["buttonRenderer"]["command"] - end - if continuation_endpoint - json.field "continuation", continuation_endpoint["continuationCommand"]["token"].as_s - end - end - end - end - - if format == "html" - response = JSON.parse(response) - content_html = template_youtube_comments(response, locale, thin_mode) - - response = JSON.build do |json| - json.object do - json.field "contentHtml", content_html - - if response["commentCount"]? - json.field "commentCount", response["commentCount"] - else - json.field "commentCount", 0 - end - end - end - end - - return response -end - def fetch_reddit_comments(id, sort_by = "confidence") client = make_client(REDDIT_URL) headers = HTTP::Headers{"User-Agent" => "web:invidious:v#{CURRENT_VERSION} (by github.com/iv-org/invidious)"} diff --git a/src/invidious/comments/youtube.cr b/src/invidious/comments/youtube.cr new file mode 100644 index 00000000..7e0c8d24 --- /dev/null +++ b/src/invidious/comments/youtube.cr @@ -0,0 +1,206 @@ +module Invidious::Comments + extend self + + def fetch_youtube(id, cursor, format, locale, thin_mode, region, sort_by = "top") + case cursor + when nil, "" + ctoken = produce_comment_continuation(id, cursor: "", sort_by: sort_by) + when .starts_with? "ADSJ" + ctoken = produce_comment_continuation(id, cursor: cursor, sort_by: sort_by) + else + ctoken = cursor + end + + client_config = YoutubeAPI::ClientConfig.new(region: region) + response = YoutubeAPI.next(continuation: ctoken, client_config: client_config) + contents = nil + + if on_response_received_endpoints = response["onResponseReceivedEndpoints"]? + header = nil + on_response_received_endpoints.as_a.each do |item| + if item["reloadContinuationItemsCommand"]? + case item["reloadContinuationItemsCommand"]["slot"] + when "RELOAD_CONTINUATION_SLOT_HEADER" + header = item["reloadContinuationItemsCommand"]["continuationItems"][0] + when "RELOAD_CONTINUATION_SLOT_BODY" + # continuationItems is nil when video has no comments + contents = item["reloadContinuationItemsCommand"]["continuationItems"]? + end + elsif item["appendContinuationItemsAction"]? + contents = item["appendContinuationItemsAction"]["continuationItems"] + end + end + elsif response["continuationContents"]? + response = response["continuationContents"] + if response["commentRepliesContinuation"]? + body = response["commentRepliesContinuation"] + else + body = response["itemSectionContinuation"] + end + contents = body["contents"]? + header = body["header"]? + else + raise NotFoundException.new("Comments not found.") + end + + if !contents + if format == "json" + return {"comments" => [] of String}.to_json + else + return {"contentHtml" => "", "commentCount" => 0}.to_json + end + end + + continuation_item_renderer = nil + contents.as_a.reject! do |item| + if item["continuationItemRenderer"]? + continuation_item_renderer = item["continuationItemRenderer"] + true + end + end + + response = JSON.build do |json| + json.object do + if header + count_text = header["commentsHeaderRenderer"]["countText"] + comment_count = (count_text["simpleText"]? || count_text["runs"]?.try &.[0]?.try &.["text"]?) + .try &.as_s.gsub(/\D/, "").to_i? || 0 + json.field "commentCount", comment_count + end + + json.field "videoId", id + + json.field "comments" do + json.array do + contents.as_a.each do |node| + json.object do + if node["commentThreadRenderer"]? + node = node["commentThreadRenderer"] + end + + if node["replies"]? + node_replies = node["replies"]["commentRepliesRenderer"] + end + + if node["comment"]? + node_comment = node["comment"]["commentRenderer"] + else + node_comment = node["commentRenderer"] + end + + content_html = node_comment["contentText"]?.try { |t| parse_content(t, id) } || "" + author = node_comment["authorText"]?.try &.["simpleText"]? || "" + + json.field "verified", (node_comment["authorCommentBadge"]? != nil) + + json.field "author", author + json.field "authorThumbnails" do + json.array do + node_comment["authorThumbnail"]["thumbnails"].as_a.each do |thumbnail| + json.object do + json.field "url", thumbnail["url"] + json.field "width", thumbnail["width"] + json.field "height", thumbnail["height"] + end + end + end + end + + if node_comment["authorEndpoint"]? + json.field "authorId", node_comment["authorEndpoint"]["browseEndpoint"]["browseId"] + json.field "authorUrl", node_comment["authorEndpoint"]["browseEndpoint"]["canonicalBaseUrl"] + else + json.field "authorId", "" + json.field "authorUrl", "" + end + + published_text = node_comment["publishedTimeText"]["runs"][0]["text"].as_s + published = decode_date(published_text.rchop(" (edited)")) + + if published_text.includes?(" (edited)") + json.field "isEdited", true + else + json.field "isEdited", false + end + + json.field "content", html_to_content(content_html) + json.field "contentHtml", content_html + + json.field "isPinned", (node_comment["pinnedCommentBadge"]? != nil) + json.field "isSponsor", (node_comment["sponsorCommentBadge"]? != nil) + if node_comment["sponsorCommentBadge"]? + # Sponsor icon thumbnails always have one object and there's only ever the url property in it + json.field "sponsorIconUrl", node_comment.dig("sponsorCommentBadge", "sponsorCommentBadgeRenderer", "customBadge", "thumbnails", 0, "url").to_s + end + json.field "published", published.to_unix + json.field "publishedText", translate(locale, "`x` ago", recode_date(published, locale)) + + comment_action_buttons_renderer = node_comment["actionButtons"]["commentActionButtonsRenderer"] + + json.field "likeCount", comment_action_buttons_renderer["likeButton"]["toggleButtonRenderer"]["accessibilityData"]["accessibilityData"]["label"].as_s.scan(/\d/).map(&.[0]).join.to_i + json.field "commentId", node_comment["commentId"] + json.field "authorIsChannelOwner", node_comment["authorIsChannelOwner"] + + if comment_action_buttons_renderer["creatorHeart"]? + hearth_data = comment_action_buttons_renderer["creatorHeart"]["creatorHeartRenderer"]["creatorThumbnail"] + json.field "creatorHeart" do + json.object do + json.field "creatorThumbnail", hearth_data["thumbnails"][-1]["url"] + json.field "creatorName", hearth_data["accessibility"]["accessibilityData"]["label"] + end + end + end + + if node_replies && !response["commentRepliesContinuation"]? + if node_replies["continuations"]? + continuation = node_replies["continuations"]?.try &.as_a[0]["nextContinuationData"]["continuation"].as_s + elsif node_replies["contents"]? + continuation = node_replies["contents"]?.try &.as_a[0]["continuationItemRenderer"]["continuationEndpoint"]["continuationCommand"]["token"].as_s + end + continuation ||= "" + + json.field "replies" do + json.object do + json.field "replyCount", node_comment["replyCount"]? || 1 + json.field "continuation", continuation + end + end + end + end + end + end + end + + if continuation_item_renderer + if continuation_item_renderer["continuationEndpoint"]? + continuation_endpoint = continuation_item_renderer["continuationEndpoint"] + elsif continuation_item_renderer["button"]? + continuation_endpoint = continuation_item_renderer["button"]["buttonRenderer"]["command"] + end + if continuation_endpoint + json.field "continuation", continuation_endpoint["continuationCommand"]["token"].as_s + end + end + end + end + + if format == "html" + response = JSON.parse(response) + content_html = template_youtube_comments(response, locale, thin_mode) + + response = JSON.build do |json| + json.object do + json.field "contentHtml", content_html + + if response["commentCount"]? + json.field "commentCount", response["commentCount"] + else + json.field "commentCount", 0 + end + end + end + end + + return response + end +end diff --git a/src/invidious/routes/api/v1/videos.cr b/src/invidious/routes/api/v1/videos.cr index f312211e..ce3e96d2 100644 --- a/src/invidious/routes/api/v1/videos.cr +++ b/src/invidious/routes/api/v1/videos.cr @@ -333,7 +333,7 @@ module Invidious::Routes::API::V1::Videos sort_by ||= "top" begin - comments = fetch_youtube_comments(id, continuation, format, locale, thin_mode, region, sort_by: sort_by) + comments = Comments.fetch_youtube(id, continuation, format, locale, thin_mode, region, sort_by: sort_by) rescue ex : NotFoundException return error_json(404, ex) rescue ex diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr index 813cb0f4..861b25c2 100644 --- a/src/invidious/routes/watch.cr +++ b/src/invidious/routes/watch.cr @@ -95,7 +95,7 @@ module Invidious::Routes::Watch if source == "youtube" begin - comment_html = JSON.parse(fetch_youtube_comments(id, nil, "html", locale, preferences.thin_mode, region))["contentHtml"] + comment_html = JSON.parse(Comments.fetch_youtube(id, nil, "html", locale, preferences.thin_mode, region))["contentHtml"] rescue ex if preferences.comments[1] == "reddit" comments, reddit_thread = fetch_reddit_comments(id) @@ -114,12 +114,12 @@ module Invidious::Routes::Watch comment_html = replace_links(comment_html) rescue ex if preferences.comments[1] == "youtube" - comment_html = JSON.parse(fetch_youtube_comments(id, nil, "html", locale, preferences.thin_mode, region))["contentHtml"] + comment_html = JSON.parse(Comments.fetch_youtube(id, nil, "html", locale, preferences.thin_mode, region))["contentHtml"] end end end else - comment_html = JSON.parse(fetch_youtube_comments(id, nil, "html", locale, preferences.thin_mode, region))["contentHtml"] + comment_html = JSON.parse(Comments.fetch_youtube(id, nil, "html", locale, preferences.thin_mode, region))["contentHtml"] end comment_html ||= "" From 634e913da9381f5212a1017e2f4a37e7d7075204 Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Sat, 6 May 2023 20:02:42 +0200 Subject: [PATCH 114/184] Comments: Move 'fetch_reddit' function to own file + module --- src/invidious/comments.cr | 38 ------------------------- src/invidious/comments/reddit.cr | 41 +++++++++++++++++++++++++++ src/invidious/routes/api/v1/videos.cr | 2 +- src/invidious/routes/watch.cr | 4 +-- 4 files changed, 44 insertions(+), 41 deletions(-) create mode 100644 src/invidious/comments/reddit.cr diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr index 07579cf3..07b92786 100644 --- a/src/invidious/comments.cr +++ b/src/invidious/comments.cr @@ -1,41 +1,3 @@ -def fetch_reddit_comments(id, sort_by = "confidence") - client = make_client(REDDIT_URL) - headers = HTTP::Headers{"User-Agent" => "web:invidious:v#{CURRENT_VERSION} (by github.com/iv-org/invidious)"} - - # TODO: Use something like #479 for a static list of instances to use here - query = URI::Params.encode({q: "(url:3D#{id} OR url:#{id}) AND (site:invidio.us OR site:youtube.com OR site:youtu.be)"}) - search_results = client.get("/search.json?#{query}", headers) - - if search_results.status_code == 200 - search_results = RedditThing.from_json(search_results.body) - - # For videos that have more than one thread, choose the one with the highest score - threads = search_results.data.as(RedditListing).children - thread = threads.max_by?(&.data.as(RedditLink).score).try(&.data.as(RedditLink)) - result = thread.try do |t| - body = client.get("/r/#{t.subreddit}/comments/#{t.id}.json?limit=100&sort=#{sort_by}", headers).body - Array(RedditThing).from_json(body) - end - result ||= [] of RedditThing - elsif search_results.status_code == 302 - # Previously, if there was only one result then the API would redirect to that result. - # Now, it appears it will still return a listing so this section is likely unnecessary. - - result = client.get(search_results.headers["Location"], headers).body - result = Array(RedditThing).from_json(result) - - thread = result[0].data.as(RedditListing).children[0].data.as(RedditLink) - else - raise NotFoundException.new("Comments not found.") - end - - client.close - - comments = result[1]?.try(&.data.as(RedditListing).children) - comments ||= [] of RedditThing - return comments, thread -end - def template_youtube_comments(comments, locale, thin_mode, is_replies = false) String.build do |html| root = comments["comments"].as_a diff --git a/src/invidious/comments/reddit.cr b/src/invidious/comments/reddit.cr new file mode 100644 index 00000000..ba9c19f1 --- /dev/null +++ b/src/invidious/comments/reddit.cr @@ -0,0 +1,41 @@ +module Invidious::Comments + extend self + + def fetch_reddit(id, sort_by = "confidence") + client = make_client(REDDIT_URL) + headers = HTTP::Headers{"User-Agent" => "web:invidious:v#{CURRENT_VERSION} (by github.com/iv-org/invidious)"} + + # TODO: Use something like #479 for a static list of instances to use here + query = URI::Params.encode({q: "(url:3D#{id} OR url:#{id}) AND (site:invidio.us OR site:youtube.com OR site:youtu.be)"}) + search_results = client.get("/search.json?#{query}", headers) + + if search_results.status_code == 200 + search_results = RedditThing.from_json(search_results.body) + + # For videos that have more than one thread, choose the one with the highest score + threads = search_results.data.as(RedditListing).children + thread = threads.max_by?(&.data.as(RedditLink).score).try(&.data.as(RedditLink)) + result = thread.try do |t| + body = client.get("/r/#{t.subreddit}/comments/#{t.id}.json?limit=100&sort=#{sort_by}", headers).body + Array(RedditThing).from_json(body) + end + result ||= [] of RedditThing + elsif search_results.status_code == 302 + # Previously, if there was only one result then the API would redirect to that result. + # Now, it appears it will still return a listing so this section is likely unnecessary. + + result = client.get(search_results.headers["Location"], headers).body + result = Array(RedditThing).from_json(result) + + thread = result[0].data.as(RedditListing).children[0].data.as(RedditLink) + else + raise NotFoundException.new("Comments not found.") + end + + client.close + + comments = result[1]?.try(&.data.as(RedditListing).children) + comments ||= [] of RedditThing + return comments, thread + end +end diff --git a/src/invidious/routes/api/v1/videos.cr b/src/invidious/routes/api/v1/videos.cr index ce3e96d2..cb1008ac 100644 --- a/src/invidious/routes/api/v1/videos.cr +++ b/src/invidious/routes/api/v1/videos.cr @@ -345,7 +345,7 @@ module Invidious::Routes::API::V1::Videos sort_by ||= "confidence" begin - comments, reddit_thread = fetch_reddit_comments(id, sort_by: sort_by) + comments, reddit_thread = Comments.fetch_reddit(id, sort_by: sort_by) rescue ex comments = nil reddit_thread = nil diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr index 861b25c2..b08e6fbe 100644 --- a/src/invidious/routes/watch.cr +++ b/src/invidious/routes/watch.cr @@ -98,7 +98,7 @@ module Invidious::Routes::Watch comment_html = JSON.parse(Comments.fetch_youtube(id, nil, "html", locale, preferences.thin_mode, region))["contentHtml"] rescue ex if preferences.comments[1] == "reddit" - comments, reddit_thread = fetch_reddit_comments(id) + comments, reddit_thread = Comments.fetch_reddit(id) comment_html = template_reddit_comments(comments, locale) comment_html = fill_links(comment_html, "https", "www.reddit.com") @@ -107,7 +107,7 @@ module Invidious::Routes::Watch end elsif source == "reddit" begin - comments, reddit_thread = fetch_reddit_comments(id) + comments, reddit_thread = Comments.fetch_reddit(id) comment_html = template_reddit_comments(comments, locale) comment_html = fill_links(comment_html, "https", "www.reddit.com") From e10f6b6626bfe462861980184b09b7350499c889 Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Sat, 6 May 2023 20:07:13 +0200 Subject: [PATCH 115/184] Comments: Move 'template_youtube' function to own file + module --- src/invidious/channels/community.cr | 2 +- src/invidious/comments.cr | 157 -------------------- src/invidious/comments/youtube.cr | 2 +- src/invidious/frontend/comments_youtube.cr | 160 +++++++++++++++++++++ src/invidious/views/community.ecr | 2 +- 5 files changed, 163 insertions(+), 160 deletions(-) create mode 100644 src/invidious/frontend/comments_youtube.cr diff --git a/src/invidious/channels/community.cr b/src/invidious/channels/community.cr index 2c7b9fec..aac4bc8a 100644 --- a/src/invidious/channels/community.cr +++ b/src/invidious/channels/community.cr @@ -250,7 +250,7 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode) if format == "html" response = JSON.parse(response) - content_html = template_youtube_comments(response, locale, thin_mode) + content_html = IV::Frontend::Comments.template_youtube(response, locale, thin_mode) response = JSON.build do |json| json.object do diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr index 07b92786..8943b1da 100644 --- a/src/invidious/comments.cr +++ b/src/invidious/comments.cr @@ -1,160 +1,3 @@ -def template_youtube_comments(comments, locale, thin_mode, is_replies = false) - String.build do |html| - root = comments["comments"].as_a - root.each do |child| - if child["replies"]? - replies_count_text = translate_count(locale, - "comments_view_x_replies", - child["replies"]["replyCount"].as_i64 || 0, - NumberFormatting::Separator - ) - - replies_html = <<-END_HTML -
    -
    - -
    - END_HTML - end - - if !thin_mode - author_thumbnail = "/ggpht#{URI.parse(child["authorThumbnails"][-1]["url"].as_s).request_target}" - else - author_thumbnail = "" - end - - author_name = HTML.escape(child["author"].as_s) - sponsor_icon = "" - if child["verified"]?.try &.as_bool && child["authorIsChannelOwner"]?.try &.as_bool - author_name += " " - elsif child["verified"]?.try &.as_bool - author_name += " " - end - - if child["isSponsor"]?.try &.as_bool - sponsor_icon = String.build do |str| - str << %() - end - end - html << <<-END_HTML -
    -
    - -
    -
    -

    - - #{author_name} - - #{sponsor_icon} -

    #{child["contentHtml"]}

    - END_HTML - - if child["attachment"]? - attachment = child["attachment"] - - case attachment["type"] - when "image" - attachment = attachment["imageThumbnails"][1] - - html << <<-END_HTML -
    -
    - -
    -
    - END_HTML - when "video" - if attachment["error"]? - html << <<-END_HTML -
    -

    #{attachment["error"]}

    -
    - END_HTML - else - html << <<-END_HTML -
    - -
    - END_HTML - end - else nil # Ignore - end - end - - html << <<-END_HTML -

    - #{translate(locale, "`x` ago", recode_date(Time.unix(child["published"].as_i64), locale))} #{child["isEdited"] == true ? translate(locale, "(edited)") : ""} - | - END_HTML - - if comments["videoId"]? - html << <<-END_HTML - [YT] - | - END_HTML - elsif comments["authorId"]? - html << <<-END_HTML - [YT] - | - END_HTML - end - - html << <<-END_HTML - #{number_with_separator(child["likeCount"])} - END_HTML - - if child["creatorHeart"]? - if !thin_mode - creator_thumbnail = "/ggpht#{URI.parse(child["creatorHeart"]["creatorThumbnail"].as_s).request_target}" - else - creator_thumbnail = "" - end - - html << <<-END_HTML -   - - - - - - - - - END_HTML - end - - html << <<-END_HTML -

    - #{replies_html} -
    -
    - END_HTML - end - - if comments["continuation"]? - html << <<-END_HTML - - END_HTML - end - end -end - def template_reddit_comments(root, locale) String.build do |html| root.each do |child| diff --git a/src/invidious/comments/youtube.cr b/src/invidious/comments/youtube.cr index 7e0c8d24..c262876e 100644 --- a/src/invidious/comments/youtube.cr +++ b/src/invidious/comments/youtube.cr @@ -186,7 +186,7 @@ module Invidious::Comments if format == "html" response = JSON.parse(response) - content_html = template_youtube_comments(response, locale, thin_mode) + content_html = Frontend::Comments.template_youtube(response, locale, thin_mode) response = JSON.build do |json| json.object do diff --git a/src/invidious/frontend/comments_youtube.cr b/src/invidious/frontend/comments_youtube.cr new file mode 100644 index 00000000..41f43f04 --- /dev/null +++ b/src/invidious/frontend/comments_youtube.cr @@ -0,0 +1,160 @@ +module Invidious::Frontend::Comments + extend self + + def template_youtube(comments, locale, thin_mode, is_replies = false) + String.build do |html| + root = comments["comments"].as_a + root.each do |child| + if child["replies"]? + replies_count_text = translate_count(locale, + "comments_view_x_replies", + child["replies"]["replyCount"].as_i64 || 0, + NumberFormatting::Separator + ) + + replies_html = <<-END_HTML +
    +
    + +
    + END_HTML + end + + if !thin_mode + author_thumbnail = "/ggpht#{URI.parse(child["authorThumbnails"][-1]["url"].as_s).request_target}" + else + author_thumbnail = "" + end + + author_name = HTML.escape(child["author"].as_s) + sponsor_icon = "" + if child["verified"]?.try &.as_bool && child["authorIsChannelOwner"]?.try &.as_bool + author_name += " " + elsif child["verified"]?.try &.as_bool + author_name += " " + end + + if child["isSponsor"]?.try &.as_bool + sponsor_icon = String.build do |str| + str << %() + end + end + html << <<-END_HTML +
    +
    + +
    +
    +

    + + #{author_name} + + #{sponsor_icon} +

    #{child["contentHtml"]}

    + END_HTML + + if child["attachment"]? + attachment = child["attachment"] + + case attachment["type"] + when "image" + attachment = attachment["imageThumbnails"][1] + + html << <<-END_HTML +
    +
    + +
    +
    + END_HTML + when "video" + if attachment["error"]? + html << <<-END_HTML +
    +

    #{attachment["error"]}

    +
    + END_HTML + else + html << <<-END_HTML +
    + +
    + END_HTML + end + else nil # Ignore + end + end + + html << <<-END_HTML +

    + #{translate(locale, "`x` ago", recode_date(Time.unix(child["published"].as_i64), locale))} #{child["isEdited"] == true ? translate(locale, "(edited)") : ""} + | + END_HTML + + if comments["videoId"]? + html << <<-END_HTML + [YT] + | + END_HTML + elsif comments["authorId"]? + html << <<-END_HTML + [YT] + | + END_HTML + end + + html << <<-END_HTML + #{number_with_separator(child["likeCount"])} + END_HTML + + if child["creatorHeart"]? + if !thin_mode + creator_thumbnail = "/ggpht#{URI.parse(child["creatorHeart"]["creatorThumbnail"].as_s).request_target}" + else + creator_thumbnail = "" + end + + html << <<-END_HTML +   + + + + + + + + + END_HTML + end + + html << <<-END_HTML +

    + #{replies_html} +
    +
    + END_HTML + end + + if comments["continuation"]? + html << <<-END_HTML + + END_HTML + end + end + end +end diff --git a/src/invidious/views/community.ecr b/src/invidious/views/community.ecr index 9e11d562..24efc34e 100644 --- a/src/invidious/views/community.ecr +++ b/src/invidious/views/community.ecr @@ -27,7 +27,7 @@
    <% else %>
    - <%= template_youtube_comments(items.not_nil!, locale, thin_mode) %> + <%= IV::Frontend::Comments.template_youtube(items.not_nil!, locale, thin_mode) %>
    <% end %> From de78848039c2e5e8dea25b6013f3e24797a0b1ce Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Sat, 6 May 2023 20:12:02 +0200 Subject: [PATCH 116/184] Comments: Move 'template_reddit' function to own file + module --- src/invidious/comments.cr | 47 --------------------- src/invidious/frontend/comments_reddit.cr | 50 +++++++++++++++++++++++ src/invidious/routes/api/v1/videos.cr | 2 +- src/invidious/routes/watch.cr | 4 +- 4 files changed, 53 insertions(+), 50 deletions(-) create mode 100644 src/invidious/frontend/comments_reddit.cr diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr index 8943b1da..6a3aa4c2 100644 --- a/src/invidious/comments.cr +++ b/src/invidious/comments.cr @@ -1,50 +1,3 @@ -def template_reddit_comments(root, locale) - String.build do |html| - root.each do |child| - if child.data.is_a?(RedditComment) - child = child.data.as(RedditComment) - body_html = HTML.unescape(child.body_html) - - replies_html = "" - if child.replies.is_a?(RedditThing) - replies = child.replies.as(RedditThing) - replies_html = template_reddit_comments(replies.data.as(RedditListing).children, locale) - end - - if child.depth > 0 - html << <<-END_HTML -
    -
    -
    -
    - END_HTML - else - html << <<-END_HTML -
    -
    - END_HTML - end - - html << <<-END_HTML -

    - [ − ] - #{child.author} - #{translate_count(locale, "comments_points_count", child.score, NumberFormatting::Separator)} - #{translate(locale, "`x` ago", recode_date(child.created_utc, locale))} - #{translate(locale, "permalink")} -

    -
    - #{body_html} - #{replies_html} -
    -
    -
    - END_HTML - end - end - end -end - def replace_links(html) # Check if the document is empty # Prevents edge-case bug with Reddit comments, see issue #3115 diff --git a/src/invidious/frontend/comments_reddit.cr b/src/invidious/frontend/comments_reddit.cr new file mode 100644 index 00000000..b5647bae --- /dev/null +++ b/src/invidious/frontend/comments_reddit.cr @@ -0,0 +1,50 @@ +module Invidious::Frontend::Comments + extend self + + def template_reddit(root, locale) + String.build do |html| + root.each do |child| + if child.data.is_a?(RedditComment) + child = child.data.as(RedditComment) + body_html = HTML.unescape(child.body_html) + + replies_html = "" + if child.replies.is_a?(RedditThing) + replies = child.replies.as(RedditThing) + replies_html = self.template_reddit(replies.data.as(RedditListing).children, locale) + end + + if child.depth > 0 + html << <<-END_HTML +
    +
    +
    +
    + END_HTML + else + html << <<-END_HTML +
    +
    + END_HTML + end + + html << <<-END_HTML +

    + [ − ] + #{child.author} + #{translate_count(locale, "comments_points_count", child.score, NumberFormatting::Separator)} + #{translate(locale, "`x` ago", recode_date(child.created_utc, locale))} + #{translate(locale, "permalink")} +

    +
    + #{body_html} + #{replies_html} +
    +
    +
    + END_HTML + end + end + end + end +end diff --git a/src/invidious/routes/api/v1/videos.cr b/src/invidious/routes/api/v1/videos.cr index cb1008ac..6feaaef4 100644 --- a/src/invidious/routes/api/v1/videos.cr +++ b/src/invidious/routes/api/v1/videos.cr @@ -361,7 +361,7 @@ module Invidious::Routes::API::V1::Videos return reddit_thread.to_json else - content_html = template_reddit_comments(comments, locale) + content_html = Frontend::Comments.template_reddit(comments, locale) content_html = fill_links(content_html, "https", "www.reddit.com") content_html = replace_links(content_html) response = { diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr index b08e6fbe..6b441a48 100644 --- a/src/invidious/routes/watch.cr +++ b/src/invidious/routes/watch.cr @@ -99,7 +99,7 @@ module Invidious::Routes::Watch rescue ex if preferences.comments[1] == "reddit" comments, reddit_thread = Comments.fetch_reddit(id) - comment_html = template_reddit_comments(comments, locale) + comment_html = Frontend::Comments.template_reddit(comments, locale) comment_html = fill_links(comment_html, "https", "www.reddit.com") comment_html = replace_links(comment_html) @@ -108,7 +108,7 @@ module Invidious::Routes::Watch elsif source == "reddit" begin comments, reddit_thread = Comments.fetch_reddit(id) - comment_html = template_reddit_comments(comments, locale) + comment_html = Frontend::Comments.template_reddit(comments, locale) comment_html = fill_links(comment_html, "https", "www.reddit.com") comment_html = replace_links(comment_html) From df8526545383f4def3605fb61551edbd851c18c7 Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Sat, 6 May 2023 20:20:27 +0200 Subject: [PATCH 117/184] Comments: Move link utility functions to own file + module --- src/invidious/comments.cr | 73 ------------------------- src/invidious/comments/links_util.cr | 76 +++++++++++++++++++++++++++ src/invidious/routes/api/v1/videos.cr | 4 +- src/invidious/routes/watch.cr | 8 +-- 4 files changed, 82 insertions(+), 79 deletions(-) create mode 100644 src/invidious/comments/links_util.cr diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr index 6a3aa4c2..3c7e2bb4 100644 --- a/src/invidious/comments.cr +++ b/src/invidious/comments.cr @@ -1,76 +1,3 @@ -def replace_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") - if url.host.try &.ends_with? "youtu.be" - url = "/watch?v=#{url.path.lstrip('/')}#{url.query_params}" - else - if url.path == "/redirect" - params = HTTP::Params.parse(url.query.not_nil!) - anchor["href"] = params["q"]? - else - anchor["href"] = url.request_target - end - end - elsif url.to_s == "#" - begin - length_seconds = decode_length_seconds(anchor.content) - rescue ex - length_seconds = decode_time(anchor.content) - end - - if length_seconds > 0 - anchor["href"] = "javascript:void(0)" - anchor["onclick"] = "player.currentTime(#{length_seconds})" - else - anchor["href"] = url.request_target - end - 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 - -def fill_links(html, scheme, host) - # 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("//a").each do |match| - url = URI.parse(match["href"]) - # Reddit links don't have host - if !url.host && !match["href"].starts_with?("javascript") && !url.to_s.ends_with? "#" - url.scheme = scheme - url.host = host - match["href"] = url - end - end - - if host == "www.youtube.com" - html = html.xpath_node(%q(//body/p)).not_nil! - end - - return html.to_xml(options: XML::SaveOptions::NO_DECL) -end - def text_to_parsed_content(text : String) : JSON::Any nodes = [] of JSON::Any # For each line convert line to array of nodes diff --git a/src/invidious/comments/links_util.cr b/src/invidious/comments/links_util.cr new file mode 100644 index 00000000..f89b86d3 --- /dev/null +++ b/src/invidious/comments/links_util.cr @@ -0,0 +1,76 @@ +module Invidious::Comments + extend self + + def replace_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") + if url.host.try &.ends_with? "youtu.be" + url = "/watch?v=#{url.path.lstrip('/')}#{url.query_params}" + else + if url.path == "/redirect" + params = HTTP::Params.parse(url.query.not_nil!) + anchor["href"] = params["q"]? + else + anchor["href"] = url.request_target + end + end + elsif url.to_s == "#" + begin + length_seconds = decode_length_seconds(anchor.content) + rescue ex + length_seconds = decode_time(anchor.content) + end + + if length_seconds > 0 + anchor["href"] = "javascript:void(0)" + anchor["onclick"] = "player.currentTime(#{length_seconds})" + else + anchor["href"] = url.request_target + end + 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 + + def fill_links(html, scheme, host) + # 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("//a").each do |match| + url = URI.parse(match["href"]) + # Reddit links don't have host + if !url.host && !match["href"].starts_with?("javascript") && !url.to_s.ends_with? "#" + url.scheme = scheme + url.host = host + match["href"] = url + end + end + + if host == "www.youtube.com" + html = html.xpath_node(%q(//body/p)).not_nil! + end + + return html.to_xml(options: XML::SaveOptions::NO_DECL) + end +end diff --git a/src/invidious/routes/api/v1/videos.cr b/src/invidious/routes/api/v1/videos.cr index 6feaaef4..af4fc806 100644 --- a/src/invidious/routes/api/v1/videos.cr +++ b/src/invidious/routes/api/v1/videos.cr @@ -362,8 +362,8 @@ module Invidious::Routes::API::V1::Videos return reddit_thread.to_json else content_html = Frontend::Comments.template_reddit(comments, locale) - content_html = fill_links(content_html, "https", "www.reddit.com") - content_html = replace_links(content_html) + content_html = Comments.fill_links(content_html, "https", "www.reddit.com") + content_html = Comments.replace_links(content_html) response = { "title" => reddit_thread.title, "permalink" => reddit_thread.permalink, diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr index 6b441a48..e5cf3716 100644 --- a/src/invidious/routes/watch.cr +++ b/src/invidious/routes/watch.cr @@ -101,8 +101,8 @@ module Invidious::Routes::Watch comments, reddit_thread = Comments.fetch_reddit(id) comment_html = Frontend::Comments.template_reddit(comments, locale) - comment_html = fill_links(comment_html, "https", "www.reddit.com") - comment_html = replace_links(comment_html) + comment_html = Comments.fill_links(comment_html, "https", "www.reddit.com") + comment_html = Comments.replace_links(comment_html) end end elsif source == "reddit" @@ -110,8 +110,8 @@ module Invidious::Routes::Watch comments, reddit_thread = Comments.fetch_reddit(id) comment_html = Frontend::Comments.template_reddit(comments, locale) - comment_html = fill_links(comment_html, "https", "www.reddit.com") - comment_html = replace_links(comment_html) + comment_html = Comments.fill_links(comment_html, "https", "www.reddit.com") + comment_html = Comments.replace_links(comment_html) rescue ex if preferences.comments[1] == "youtube" comment_html = JSON.parse(Comments.fetch_youtube(id, nil, "html", locale, preferences.thin_mode, region))["contentHtml"] From 4379a3d873540460859ec30845dfba66a33d0aea Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Sat, 6 May 2023 20:23:47 +0200 Subject: [PATCH 118/184] Comments: Move ctoken functions to youtube.cr --- spec/invidious/helpers_spec.cr | 12 -------- src/invidious/comments.cr | 44 ---------------------------- src/invidious/comments/youtube.cr | 48 +++++++++++++++++++++++++++++-- 3 files changed, 46 insertions(+), 58 deletions(-) diff --git a/spec/invidious/helpers_spec.cr b/spec/invidious/helpers_spec.cr index f81cd29a..142e1653 100644 --- a/spec/invidious/helpers_spec.cr +++ b/spec/invidious/helpers_spec.cr @@ -23,18 +23,6 @@ Spectator.describe "Helper" do end end - describe "#produce_comment_continuation" do - it "correctly produces a continuation token for comments" do - expect(produce_comment_continuation("_cE8xSu6swE", "ADSJ_i2qvJeFtL0htmS5_K5Ctj3eGFVBMWL9Wd42o3kmUL6_mAzdLp85-liQZL0mYr_16BhaggUqX652Sv9JqV6VXinShSP-ZT6rL4NolPBaPXVtJsO5_rA_qE3GubAuLFw9uzIIXU2-HnpXbdgPLWTFavfX206hqWmmpHwUOrmxQV_OX6tYkM3ux3rPAKCDrT8eWL7MU3bLiNcnbgkW8o0h8KYLL_8BPa8LcHbTv8pAoNkjerlX1x7K4pqxaXPoyz89qNlnh6rRx6AXgAzzoHH1dmcyQ8CIBeOHg-m4i8ZxdX4dP88XWrIFg-jJGhpGP8JUMDgZgavxVx225hUEYZMyrLGler5em4FgbG62YWC51moLDLeYEA")).to eq("EkMSC19jRTh4U3U2c3dFyAEA4AEBogINKP___________wFAAMICHQgEGhdodHRwczovL3d3dy55b3V0dWJlLmNvbSIAGAYyjAMK9gJBRFNKX2kycXZKZUZ0TDBodG1TNV9LNUN0ajNlR0ZWQk1XTDlXZDQybzNrbVVMNl9tQXpkTHA4NS1saVFaTDBtWXJfMTZCaGFnZ1VxWDY1MlN2OUpxVjZWWGluU2hTUC1aVDZyTDROb2xQQmFQWFZ0SnNPNV9yQV9xRTNHdWJBdUxGdzl1eklJWFUyLUhucFhiZGdQTFdURmF2ZlgyMDZocVdtbXBId1VPcm14UVZfT1g2dFlrTTN1eDNyUEFLQ0RyVDhlV0w3TVUzYkxpTmNuYmdrVzhvMGg4S1lMTF84QlBhOExjSGJUdjhwQW9Oa2plcmxYMXg3SzRwcXhhWFBveXo4OXFObG5oNnJSeDZBWGdBenpvSEgxZG1jeVE4Q0lCZU9IZy1tNGk4WnhkWDRkUDg4WFdySUZnLWpKR2hwR1A4SlVNRGdaZ2F2eFZ4MjI1aFVFWVpNeXJMR2xlcjVlbTRGZ2JHNjJZV0M1MW1vTERMZVlFQSIPIgtfY0U4eFN1NnN3RTAAKBQ%3D") - - expect(produce_comment_continuation("_cE8xSu6swE", "ADSJ_i1yz21HI4xrtsYXVC-2_kfZ6kx1yjYQumXAAxqH3CAd7ZxKxfLdZS1__fqhCtOASRbbpSBGH_tH1J96Dxux-Qfjk-lUbupMqv08Q3aHzGu7p70VoUMHhI2-GoJpnbpmcOxkGzeIuenRS_ym2Y8fkDowhqLPFgsS0n4djnZ2UmC17F3Ch3N1S1UYf1ZVOc991qOC1iW9kJDzyvRQTWCPsJUPneSaAKW-Rr97pdesOkR4i8cNvHZRnQKe2HEfsvlJOb2C3lF1dJBfJeNfnQYeh5hv6_fZN7bt3-JL1Xk3Qc9NXNxmmbDpwAC_yFR8dthFfUJdyIO9Nu1D79MLYeR-H5HxqUJokkJiGIz4lTE_CXXbhAI")).to eq("EkMSC19jRTh4U3U2c3dFyAEA4AEBogINKP___________wFAAMICHQgEGhdodHRwczovL3d3dy55b3V0dWJlLmNvbSIAGAYyiQMK8wJBRFNKX2kxeXoyMUhJNHhydHNZWFZDLTJfa2ZaNmt4MXlqWVF1bVhBQXhxSDNDQWQ3WnhLeGZMZFpTMV9fZnFoQ3RPQVNSYmJwU0JHSF90SDFKOTZEeHV4LVFmamstbFVidXBNcXYwOFEzYUh6R3U3cDcwVm9VTUhoSTItR29KcG5icG1jT3hrR3plSXVlblJTX3ltMlk4ZmtEb3docUxQRmdzUzBuNGRqbloyVW1DMTdGM0NoM04xUzFVWWYxWlZPYzk5MXFPQzFpVzlrSkR6eXZSUVRXQ1BzSlVQbmVTYUFLVy1Scjk3cGRlc09rUjRpOGNOdkhaUm5RS2UySEVmc3ZsSk9iMkMzbEYxZEpCZkplTmZuUVllaDVodjZfZlpON2J0My1KTDFYazNRYzlOWE54bW1iRHB3QUNfeUZSOGR0aEZmVUpkeUlPOU51MUQ3OU1MWWVSLUg1SHhxVUpva2tKaUdJejRsVEVfQ1hYYmhBSSIPIgtfY0U4eFN1NnN3RTAAKBQ%3D") - - expect(produce_comment_continuation("29-q7YnyUmY", "")).to eq("EkMSCzI5LXE3WW55VW1ZyAEA4AEBogINKP___________wFAAMICHQgEGhdodHRwczovL3d3dy55b3V0dWJlLmNvbSIAGAYyFQoAIg8iCzI5LXE3WW55VW1ZMAAoFA%3D%3D") - - expect(produce_comment_continuation("CvFH_6DNRCY", "")).to eq("EkMSC0N2RkhfNkROUkNZyAEA4AEBogINKP___________wFAAMICHQgEGhdodHRwczovL3d3dy55b3V0dWJlLmNvbSIAGAYyFQoAIg8iC0N2RkhfNkROUkNZMAAoFA%3D%3D") - end - end - describe "#produce_channel_community_continuation" do it "correctly produces a continuation token for a channel community" do expect(produce_channel_community_continuation("UCCj956IF62FbT7Gouszaj9w", "Egljb21tdW5pdHm4")).to eq("4qmFsgIsEhhVQ0NqOTU2SUY2MkZiVDdHb3VzemFqOXcaEEVnbGpiMjF0ZFc1cGRIbTQ%3D") diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr index 3c7e2bb4..c8cdc2df 100644 --- a/src/invidious/comments.cr +++ b/src/invidious/comments.cr @@ -87,47 +87,3 @@ def content_to_comment_html(content, video_id : String? = "") return html_array.join("").delete('\ufeff') end - -def produce_comment_continuation(video_id, cursor = "", sort_by = "top") - object = { - "2:embedded" => { - "2:string" => video_id, - "25:varint" => 0_i64, - "28:varint" => 1_i64, - "36:embedded" => { - "5:varint" => -1_i64, - "8:varint" => 0_i64, - }, - "40:embedded" => { - "1:varint" => 4_i64, - "3:string" => "https://www.youtube.com", - "4:string" => "", - }, - }, - "3:varint" => 6_i64, - "6:embedded" => { - "1:string" => cursor, - "4:embedded" => { - "4:string" => video_id, - "6:varint" => 0_i64, - }, - "5:varint" => 20_i64, - }, - } - - case sort_by - when "top" - object["6:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 0_i64 - when "new", "newest" - object["6:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 1_i64 - else # top - object["6:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 0_i64 - end - - continuation = object.try { |i| Protodec::Any.cast_json(i) } - .try { |i| Protodec::Any.from_json(i) } - .try { |i| Base64.urlsafe_encode(i) } - .try { |i| URI.encode_www_form(i) } - - return continuation -end diff --git a/src/invidious/comments/youtube.cr b/src/invidious/comments/youtube.cr index c262876e..1ba1b534 100644 --- a/src/invidious/comments/youtube.cr +++ b/src/invidious/comments/youtube.cr @@ -4,9 +4,9 @@ module Invidious::Comments def fetch_youtube(id, cursor, format, locale, thin_mode, region, sort_by = "top") case cursor when nil, "" - ctoken = produce_comment_continuation(id, cursor: "", sort_by: sort_by) + ctoken = Comments.produce_continuation(id, cursor: "", sort_by: sort_by) when .starts_with? "ADSJ" - ctoken = produce_comment_continuation(id, cursor: cursor, sort_by: sort_by) + ctoken = Comments.produce_continuation(id, cursor: cursor, sort_by: sort_by) else ctoken = cursor end @@ -203,4 +203,48 @@ module Invidious::Comments return response end + + def produce_continuation(video_id, cursor = "", sort_by = "top") + object = { + "2:embedded" => { + "2:string" => video_id, + "25:varint" => 0_i64, + "28:varint" => 1_i64, + "36:embedded" => { + "5:varint" => -1_i64, + "8:varint" => 0_i64, + }, + "40:embedded" => { + "1:varint" => 4_i64, + "3:string" => "https://www.youtube.com", + "4:string" => "", + }, + }, + "3:varint" => 6_i64, + "6:embedded" => { + "1:string" => cursor, + "4:embedded" => { + "4:string" => video_id, + "6:varint" => 0_i64, + }, + "5:varint" => 20_i64, + }, + } + + case sort_by + when "top" + object["6:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 0_i64 + when "new", "newest" + object["6:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 1_i64 + else # top + object["6:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 0_i64 + end + + continuation = object.try { |i| Protodec::Any.cast_json(i) } + .try { |i| Protodec::Any.from_json(i) } + .try { |i| Base64.urlsafe_encode(i) } + .try { |i| URI.encode_www_form(i) } + + return continuation + end end From f0c8477905e6aae5c3979a64dab964dc4b353fe0 Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Sat, 6 May 2023 20:27:02 +0200 Subject: [PATCH 119/184] Comments: Move content-related functions to their own file --- src/invidious/{comments.cr => comments/content.cr} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/invidious/{comments.cr => comments/content.cr} (100%) diff --git a/src/invidious/comments.cr b/src/invidious/comments/content.cr similarity index 100% rename from src/invidious/comments.cr rename to src/invidious/comments/content.cr From 193c510c65cfc6c56f4409180b798c9eb8ef3efd Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Sat, 6 May 2023 20:53:39 +0200 Subject: [PATCH 120/184] Spec: Update require to point to new files --- spec/parsers_helper.cr | 2 +- spec/spec_helper.cr | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/parsers_helper.cr b/spec/parsers_helper.cr index bf05f9ec..6589acad 100644 --- a/spec/parsers_helper.cr +++ b/spec/parsers_helper.cr @@ -13,7 +13,7 @@ require "../src/invidious/helpers/utils" require "../src/invidious/videos" require "../src/invidious/videos/*" -require "../src/invidious/comments" +require "../src/invidious/comments/content" require "../src/invidious/helpers/serialized_yt_data" require "../src/invidious/yt_backend/extractors" diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index f8bfa718..b3060acf 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -7,7 +7,6 @@ require "../src/invidious/helpers/*" require "../src/invidious/channels/*" require "../src/invidious/videos/caption" require "../src/invidious/videos" -require "../src/invidious/comments" require "../src/invidious/playlists" require "../src/invidious/search/ctoken" require "../src/invidious/trending" From 898066407d85a2844c87fa6fc0e8179977cabb9c Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Mon, 29 May 2023 12:41:53 +0200 Subject: [PATCH 121/184] Utils: Update 'decode_date' to take into account short "x ago" forms --- src/invidious/helpers/utils.cr | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/invidious/helpers/utils.cr b/src/invidious/helpers/utils.cr index bcf7c963..48bf769f 100644 --- a/src/invidious/helpers/utils.cr +++ b/src/invidious/helpers/utils.cr @@ -111,24 +111,27 @@ def decode_date(string : String) else nil # Continue end - # String matches format "20 hours ago", "4 months ago"... - date = string.split(" ")[-3, 3] - delta = date[0].to_i + # String matches format "20 hours ago", "4 months ago", "20s ago", "15min ago"... + match = string.match(/(?\d+) ?(?[smhdwy]\w*) ago/) - case date[1] - when .includes? "second" + raise "Could not parse #{string}" if match.nil? + + delta = match["count"].to_i + + case match["span"] + when .starts_with? "s" # second(s) delta = delta.seconds - when .includes? "minute" + when .starts_with? "mi" # minute(s) delta = delta.minutes - when .includes? "hour" + when .starts_with? "h" # hour(s) delta = delta.hours - when .includes? "day" + when .starts_with? "d" # day(s) delta = delta.days - when .includes? "week" + when .starts_with? "w" # week(s) delta = delta.weeks - when .includes? "month" + when .starts_with? "mo" # month(s) delta = delta.months - when .includes? "year" + when .starts_with? "y" # year(s) delta = delta.years else raise "Could not parse #{string}" From 4414c9df70580008c8817ace026b765e83c052aa Mon Sep 17 00:00:00 2001 From: Samantaz Fox Date: Mon, 29 May 2023 12:42:19 +0200 Subject: [PATCH 122/184] specc: Add tests for 'decode_date' --- spec/invidious/utils_spec.cr | 46 ++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 spec/invidious/utils_spec.cr diff --git a/spec/invidious/utils_spec.cr b/spec/invidious/utils_spec.cr new file mode 100644 index 00000000..7c2c2711 --- /dev/null +++ b/spec/invidious/utils_spec.cr @@ -0,0 +1,46 @@ +require "../spec_helper" + +Spectator.describe "Utils" do + describe "decode_date" do + it "parses short dates (en-US)" do + expect(decode_date("1s ago")).to be_close(Time.utc - 1.second, 500.milliseconds) + expect(decode_date("2min ago")).to be_close(Time.utc - 2.minutes, 500.milliseconds) + expect(decode_date("3h ago")).to be_close(Time.utc - 3.hours, 500.milliseconds) + expect(decode_date("4d ago")).to be_close(Time.utc - 4.days, 500.milliseconds) + expect(decode_date("5w ago")).to be_close(Time.utc - 5.weeks, 500.milliseconds) + expect(decode_date("6mo ago")).to be_close(Time.utc - 6.months, 500.milliseconds) + expect(decode_date("7y ago")).to be_close(Time.utc - 7.years, 500.milliseconds) + end + + it "parses short dates (en-GB)" do + expect(decode_date("55s ago")).to be_close(Time.utc - 55.seconds, 500.milliseconds) + expect(decode_date("44min ago")).to be_close(Time.utc - 44.minutes, 500.milliseconds) + expect(decode_date("22hr ago")).to be_close(Time.utc - 22.hours, 500.milliseconds) + expect(decode_date("1day ago")).to be_close(Time.utc - 1.day, 500.milliseconds) + expect(decode_date("2days ago")).to be_close(Time.utc - 2.days, 500.milliseconds) + expect(decode_date("3wk ago")).to be_close(Time.utc - 3.weeks, 500.milliseconds) + expect(decode_date("11mo ago")).to be_close(Time.utc - 11.months, 500.milliseconds) + expect(decode_date("11yr ago")).to be_close(Time.utc - 11.years, 500.milliseconds) + end + + it "parses long forms (singular)" do + expect(decode_date("1 second ago")).to be_close(Time.utc - 1.second, 500.milliseconds) + expect(decode_date("1 minute ago")).to be_close(Time.utc - 1.minute, 500.milliseconds) + expect(decode_date("1 hour ago")).to be_close(Time.utc - 1.hour, 500.milliseconds) + expect(decode_date("1 day ago")).to be_close(Time.utc - 1.day, 500.milliseconds) + expect(decode_date("1 week ago")).to be_close(Time.utc - 1.week, 500.milliseconds) + expect(decode_date("1 month ago")).to be_close(Time.utc - 1.month, 500.milliseconds) + expect(decode_date("1 year ago")).to be_close(Time.utc - 1.year, 500.milliseconds) + end + + it "parses long forms (plural)" do + expect(decode_date("5 seconds ago")).to be_close(Time.utc - 5.seconds, 500.milliseconds) + expect(decode_date("17 minutes ago")).to be_close(Time.utc - 17.minutes, 500.milliseconds) + expect(decode_date("23 hours ago")).to be_close(Time.utc - 23.hours, 500.milliseconds) + expect(decode_date("3 days ago")).to be_close(Time.utc - 3.days, 500.milliseconds) + expect(decode_date("2 weeks ago")).to be_close(Time.utc - 2.weeks, 500.milliseconds) + expect(decode_date("9 months ago")).to be_close(Time.utc - 9.months, 500.milliseconds) + expect(decode_date("8 years ago")).to be_close(Time.utc - 8.years, 500.milliseconds) + end + end +end From 042ad1f2662503c123ba1dd415e5ed3d9ddc3cc0 Mon Sep 17 00:00:00 2001 From: Emilien Devos Date: Sat, 3 Jun 2023 13:06:48 +0200 Subject: [PATCH 123/184] auto close duplicated issues --- .github/workflows/auto-close-duplicate.yaml | 35 +++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/auto-close-duplicate.yaml diff --git a/.github/workflows/auto-close-duplicate.yaml b/.github/workflows/auto-close-duplicate.yaml new file mode 100644 index 00000000..3e977a84 --- /dev/null +++ b/.github/workflows/auto-close-duplicate.yaml @@ -0,0 +1,35 @@ +name: Close duplicates +on: + issues: + types: [opened] +jobs: + run: + runs-on: ubuntu-latest + permissions: write-all + steps: + - uses: iv-org/close-potential-duplicates@v1 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Issue title filter work with anymatch https://www.npmjs.com/package/anymatch. + # Any matched issue will stop detection immediately. + # You can specify multi filters in each line. + filter: '' + # Exclude keywords in title before detecting. + exclude: '' + # Label to set, when potential duplicates are detected. + label: duplicate + # Get issues with state to compare. Supported state: 'all', 'closed', 'open'. + state: open + # If similarity is higher than this threshold([0,1]), issue will be marked as duplicate. + threshold: 0.6 + # Reactions to be add to comment when potential duplicates are detected. + # Available reactions: "-1", "+1", "confused", "laugh", "heart", "hooray", "rocket", "eyes" + reactions: '' + close: true + # Comment to post when potential duplicates are detected. + comment: > + Hello, your issue is a duplicate of this/these issue(s): {{#issues}} + - #{{ number }} [accuracy: ({{ accuracy }}%)] + {{/issues}} + If this is a mistake please explain why and ping @\unixfox, @\SamantazFox and @\TheFrenchGhosty. + Please refrain from opening new issues, it won't help in solving your problem. \ No newline at end of file From 7ea6ec1f52ae02cbc35401ad272433d7073d8866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milien=20Devos=20=28perso=29?= Date: Sat, 3 Jun 2023 18:57:42 +0200 Subject: [PATCH 124/184] add one return line for the reply message --- .github/workflows/auto-close-duplicate.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/auto-close-duplicate.yaml b/.github/workflows/auto-close-duplicate.yaml index 3e977a84..4495c22e 100644 --- a/.github/workflows/auto-close-duplicate.yaml +++ b/.github/workflows/auto-close-duplicate.yaml @@ -32,4 +32,5 @@ jobs: - #{{ number }} [accuracy: ({{ accuracy }}%)] {{/issues}} If this is a mistake please explain why and ping @\unixfox, @\SamantazFox and @\TheFrenchGhosty. + Please refrain from opening new issues, it won't help in solving your problem. \ No newline at end of file From bc06c2fc27a9f1fb4edb8a2af570d67c0af5ba0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milien=20Devos=20=28perso=29?= Date: Sat, 3 Jun 2023 17:27:24 +0000 Subject: [PATCH 125/184] Better message for auto close --- .github/workflows/auto-close-duplicate.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/auto-close-duplicate.yaml b/.github/workflows/auto-close-duplicate.yaml index 4495c22e..aa6457ed 100644 --- a/.github/workflows/auto-close-duplicate.yaml +++ b/.github/workflows/auto-close-duplicate.yaml @@ -27,10 +27,11 @@ jobs: reactions: '' close: true # Comment to post when potential duplicates are detected. - comment: > + comment: | Hello, your issue is a duplicate of this/these issue(s): {{#issues}} - - #{{ number }} [accuracy: ({{ accuracy }}%)] + - #{{ number }} [accuracy: {{ accuracy }}%] {{/issues}} + If this is a mistake please explain why and ping @\unixfox, @\SamantazFox and @\TheFrenchGhosty. - Please refrain from opening new issues, it won't help in solving your problem. \ No newline at end of file + Please refrain from opening new issues, it won't help in solving your problem. From 372192eabc9a23373023d0ed9209059138bb4e66 Mon Sep 17 00:00:00 2001 From: Emilien Devos Date: Sun, 4 Jun 2023 17:13:48 +0200 Subject: [PATCH 126/184] warn about hmac key deadline --- src/invidious.cr | 9 +++++++-- src/invidious/views/template.ecr | 8 ++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/invidious.cr b/src/invidious.cr index b5abd5c7..27c4775e 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -57,8 +57,9 @@ end # Simple alias to make code easier to read alias IV = Invidious -CONFIG = Config.load -HMAC_KEY = CONFIG.hmac_key || Random::Secure.hex(32) +CONFIG = Config.load +HMAC_KEY_CONFIGURED = CONFIG.hmac_key != nil +HMAC_KEY = CONFIG.hmac_key || Random::Secure.hex(32) PG_DB = DB.open CONFIG.database_url ARCHIVE_URL = URI.parse("https://archive.org") @@ -230,6 +231,10 @@ Kemal.config.host_binding = Kemal.config.host_binding != "0.0.0.0" ? Kemal.confi Kemal.config.port = Kemal.config.port != 3000 ? Kemal.config.port : CONFIG.port Kemal.config.app_name = "Invidious" +if !HMAC_KEY_CONFIGURED + LOGGER.warn("Please configure hmac_key by July 1st, see more here: https://github.com/iv-org/invidious/issues/3854") +end + # Use in kemal's production mode. # Users can also set the KEMAL_ENV environmental variable for this to be set automatically. {% if flag?(:release) || flag?(:production) %} diff --git a/src/invidious/views/template.ecr b/src/invidious/views/template.ecr index 77265679..aa0fc15f 100644 --- a/src/invidious/views/template.ecr +++ b/src/invidious/views/template.ecr @@ -111,6 +111,14 @@
    <% end %> + <% if env.get? "user" %> + <% if !HMAC_KEY_CONFIGURED && CONFIG.admins.includes? env.get("user").as(Invidious::User).email %> +
    +

    Message for admin: please configure hmac_key, see more here.

    +
    + <% end %> + <% end %> + <%= content %>