diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index bc80c75c..7e10be8a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -41,6 +41,7 @@ jobs:
- 1.2.2
- 1.3.2
- 1.4.0
+ - 1.5.0
include:
- crystal: nightly
stable: false
diff --git a/.github/workflows/container-release.yml b/.github/workflows/container-release.yml
index 212487c8..7e427e6e 100644
--- a/.github/workflows/container-release.yml
+++ b/.github/workflows/container-release.yml
@@ -27,7 +27,7 @@ jobs:
- name: Install Crystal
uses: crystal-lang/install-crystal@v1.6.0
with:
- crystal: 1.2.2
+ crystal: 1.5.0
- name: Run lint
run: |
diff --git a/README.md b/README.md
index 9ed68a4b..6068a66b 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@
-
+
@@ -28,17 +28,17 @@
<%= HTML.escape(item.author) %><% if !item.author_verified.nil? && item.author_verified %> <% end %>
@@ -23,7 +23,7 @@ <% if !env.get("preferences").as(Preferences).thin_mode %><%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %>
<%= recode_length_seconds(item.length_seconds) %>
<% end %> @@ -51,16 +51,13 @@ <% if !env.get("preferences").as(Preferences).thin_mode %> <% if env.get("preferences").as(Preferences).show_nick %><%= number_with_separator(video.views) %>
<%= number_with_separator(video.likes) %>
- +<%= translate(locale, "Genre: ") %> <% if !video.genre_url %> <%= video.genre %> @@ -185,9 +185,9 @@ we're going to need to do it here in order to allow for translations.
<%= translate(locale, "License: ") %><%= video.license %>
<% end %><%= translate(locale, "Family friendly? ") %><%= translate_bool(locale, video.is_family_friendly) %>
-<%= translate(locale, "Wilson score: ") %><%= video.wilson_score %>
- -<%= translate(locale, "Engagement: ") %><%= video.engagement %>%
+ + + <% if video.allowed_regions.size != REGIONS.size %><% if video.allowed_regions.size < REGIONS.size // 2 %> diff --git a/src/invidious/yt_backend/extractors.cr b/src/invidious/yt_backend/extractors.cr index c4326cab..dc65cc52 100644 --- a/src/invidious/yt_backend/extractors.cr +++ b/src/invidious/yt_backend/extractors.cr @@ -417,7 +417,7 @@ private module Extractors # {"tabRenderer": { # "endpoint": {...} # "title": "Playlists", - # "selected": true, + # "selected": true, # Is nil unless tab is selected # "content": {...}, # ... # }} @@ -435,20 +435,22 @@ private module Extractors raw_items = [] of JSON::Any content = extract_selected_tab(target["tabs"])["content"] - content["sectionListRenderer"]["contents"].as_a.each do |renderer_container| - renderer_container_contents = renderer_container["itemSectionRenderer"]["contents"][0] + if section_list_contents = content.dig?("sectionListRenderer", "contents") + section_list_contents.as_a.each do |renderer_container| + renderer_container_contents = renderer_container["itemSectionRenderer"]["contents"][0] - # Category extraction - if items_container = renderer_container_contents["shelfRenderer"]? - raw_items << renderer_container_contents - next - elsif items_container = renderer_container_contents["gridRenderer"]? - else - items_container = renderer_container_contents - end + # Category extraction + if items_container = renderer_container_contents["shelfRenderer"]? + raw_items << renderer_container_contents + next + elsif items_container = renderer_container_contents["gridRenderer"]? + else + items_container = renderer_container_contents + end - items_container["items"]?.try &.as_a.each do |item| - raw_items << item + items_container["items"]?.try &.as_a.each do |item| + raw_items << item + end end end diff --git a/src/invidious/yt_backend/extractors_utils.cr b/src/invidious/yt_backend/extractors_utils.cr index 3d5e5787..f8245160 100644 --- a/src/invidious/yt_backend/extractors_utils.cr +++ b/src/invidious/yt_backend/extractors_utils.cr @@ -84,7 +84,7 @@ end def extract_selected_tab(tabs) # Extract the selected tab from the array of tabs Youtube returns - return selected_target = tabs.as_a.select(&.["tabRenderer"]?.try &.["selected"].as_bool)[0]["tabRenderer"] + return selected_target = tabs.as_a.select(&.["tabRenderer"]?.try &.["selected"]?.try &.as_bool)[0]["tabRenderer"] end def fetch_continuation_token(items : Array(JSON::Any)) diff --git a/src/invidious/yt_backend/youtube_api.cr b/src/invidious/yt_backend/youtube_api.cr index 2678ac6c..30d7613b 100644 --- a/src/invidious/yt_backend/youtube_api.cr +++ b/src/invidious/yt_backend/youtube_api.cr @@ -5,15 +5,28 @@ module YoutubeAPI extend self + private DEFAULT_API_KEY = "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8" + + private ANDROID_APP_VERSION = "17.29.35" + private ANDROID_SDK_VERSION = 30_i64 + private IOS_APP_VERSION = "17.30.1" + # Enumerate used to select one of the clients supported by the API enum ClientType Web WebEmbeddedPlayer WebMobile WebScreenEmbed + Android AndroidEmbeddedPlayer AndroidScreenEmbed + + IOS + IOSEmbedded + IOSMusic + + TvHtml5 TvHtml5ScreenEmbed end @@ -21,50 +34,78 @@ module YoutubeAPI HARDCODED_CLIENTS = { ClientType::Web => { name: "WEB", - version: "2.20210721.00.00", - api_key: "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8", + version: "2.20220804.07.00", + api_key: DEFAULT_API_KEY, screen: "WATCH_FULL_SCREEN", }, ClientType::WebEmbeddedPlayer => { name: "WEB_EMBEDDED_PLAYER", # 56 - version: "1.20210721.1.0", - api_key: "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8", + version: "1.20220803.01.00", + api_key: DEFAULT_API_KEY, screen: "EMBED", }, ClientType::WebMobile => { name: "MWEB", - version: "2.20210726.08.00", - api_key: "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8", - screen: "", # None + version: "2.20220805.01.00", + api_key: DEFAULT_API_KEY, }, ClientType::WebScreenEmbed => { name: "WEB", - version: "2.20210721.00.00", - api_key: "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8", + version: "2.20220804.00.00", + api_key: DEFAULT_API_KEY, screen: "EMBED", }, + + # Android + ClientType::Android => { - name: "ANDROID", - version: "16.20", - api_key: "AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w", - screen: "", # ?? + name: "ANDROID", + version: ANDROID_APP_VERSION, + api_key: "AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w", + android_sdk_version: ANDROID_SDK_VERSION, }, ClientType::AndroidEmbeddedPlayer => { name: "ANDROID_EMBEDDED_PLAYER", # 55 - version: "16.20", - api_key: "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8", - screen: "", # None? + version: ANDROID_APP_VERSION, + api_key: DEFAULT_API_KEY, }, ClientType::AndroidScreenEmbed => { - name: "ANDROID", # 3 - version: "16.20", - api_key: "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8", - screen: "EMBED", + name: "ANDROID", # 3 + version: ANDROID_APP_VERSION, + api_key: DEFAULT_API_KEY, + screen: "EMBED", + android_sdk_version: ANDROID_SDK_VERSION, + }, + + # IOS + + ClientType::IOS => { + name: "IOS", # 5 + version: IOS_APP_VERSION, + api_key: "AIzaSyB-63vPrdThhKuerbB2N_l7Kwwcxj6yUAc", + }, + ClientType::IOSEmbedded => { + name: "IOS_MESSAGES_EXTENSION", # 66 + version: IOS_APP_VERSION, + api_key: DEFAULT_API_KEY, + }, + ClientType::IOSMusic => { + name: "IOS_MUSIC", # 26 + version: "4.32", + api_key: "AIzaSyBAETezhkwP0ZWA02RsqT1zu78Fpt0bC_s", + }, + + # TV app + + ClientType::TvHtml5 => { + name: "TVHTML5", # 7 + version: "7.20220325", + api_key: DEFAULT_API_KEY, }, ClientType::TvHtml5ScreenEmbed => { - name: "TVHTML5_SIMPLY_EMBEDDED_PLAYER", + name: "TVHTML5_SIMPLY_EMBEDDED_PLAYER", # 85 version: "2.0", - api_key: "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8", + api_key: DEFAULT_API_KEY, screen: "EMBED", }, } @@ -131,7 +172,11 @@ module YoutubeAPI # :ditto: def screen : String - HARDCODED_CLIENTS[@client_type][:screen] + HARDCODED_CLIENTS[@client_type][:screen]? || "" + end + + def android_sdk_version : Int64? + HARDCODED_CLIENTS[@client_type][:android_sdk_version]? end # Convert to string, for logging purposes @@ -163,7 +208,7 @@ module YoutubeAPI "gl" => client_config.region || "US", # Can't be empty! "clientName" => client_config.name, "clientVersion" => client_config.version, - }, + } of String => String | Int64, } # Add some more context if it exists in the client definitions @@ -174,7 +219,11 @@ module YoutubeAPI if client_config.screen == "EMBED" client_context["thirdParty"] = { "embedUrl" => "https://www.youtube.com/embed/dQw4w9WgXcQ", - } + } of String => String | Int64 + end + + if android_sdk_version = client_config.android_sdk_version + client_context["client"]["androidSdkVersion"] = android_sdk_version end return client_context