summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSamantaz Fox <coding@samantaz.fr>2023-01-08 13:50:52 +0100
committerSamantaz Fox <coding@samantaz.fr>2023-01-08 13:50:52 +0100
commita37522a03dc12f61386fc0529a9136ad296b1228 (patch)
tree7ca15ec1c80ac960877399c45d1823214c982f95 /src
parentf9eb839c7ae2c29e641495c4a2affd384445bf97 (diff)
downloadinvidious-a37522a03dc12f61386fc0529a9136ad296b1228.tar.gz
invidious-a37522a03dc12f61386fc0529a9136ad296b1228.tar.bz2
invidious-a37522a03dc12f61386fc0529a9136ad296b1228.zip
Implement workaround for broken shorts objects
Diffstat (limited to 'src')
-rw-r--r--src/invidious/channels/videos.cr30
-rw-r--r--src/invidious/exceptions.cr5
-rw-r--r--src/invidious/yt_backend/extractors.cr26
3 files changed, 46 insertions, 15 deletions
diff --git a/src/invidious/channels/videos.cr b/src/invidious/channels/videos.cr
index bea406c1..befec03d 100644
--- a/src/invidious/channels/videos.cr
+++ b/src/invidious/channels/videos.cr
@@ -127,16 +127,38 @@ module Invidious::Channel::Tabs
# Shorts
# -------------------
- def get_shorts(channel : AboutChannel, continuation : String? = nil)
+ private def fetch_shorts_data(ucid : String, continuation : String? = nil)
if continuation.nil?
# EgZzaG9ydHPyBgUKA5oBAA%3D%3D is the protobuf object to load "shorts"
# TODO: try to extract the continuation tokens that allows other sorting options
- initial_data = YoutubeAPI.browse(channel.ucid, params: "EgZzaG9ydHPyBgUKA5oBAA%3D%3D")
+ return YoutubeAPI.browse(ucid, params: "EgZzaG9ydHPyBgUKA5oBAA%3D%3D")
else
- initial_data = YoutubeAPI.browse(continuation: continuation)
+ return YoutubeAPI.browse(continuation: continuation)
end
+ end
- return extract_items(initial_data, channel.author, channel.ucid)
+ def get_shorts(channel : AboutChannel, continuation : String? = nil)
+ initial_data = self.fetch_shorts_data(channel.ucid, continuation)
+
+ begin
+ # Try to parse the initial data fetched above
+ return extract_items(initial_data, channel.author, channel.ucid)
+ rescue ex : RetryOnceException
+ # Sometimes, for a completely unknown reason, the "reelItemRenderer"
+ # object is missing some critical information (it happens once in about
+ # 20 subsequent requests). Refreshing the page is required to properly
+ # show the "shorts" tab.
+ #
+ # In order to make the experience smoother for the user, we simulate
+ # said page refresh by fetching again the JSON. If that still doesn't
+ # work, we raise a BrokenTubeException, as something is really broken.
+ begin
+ initial_data = self.fetch_shorts_data(channel.ucid, continuation)
+ return extract_items(initial_data, channel.author, channel.ucid)
+ rescue ex : RetryOnceException
+ raise BrokenTubeException.new "reelPlayerHeaderSupportedRenderers"
+ end
+ end
end
# -------------------
diff --git a/src/invidious/exceptions.cr b/src/invidious/exceptions.cr
index 425c08da..690db907 100644
--- a/src/invidious/exceptions.cr
+++ b/src/invidious/exceptions.cr
@@ -33,3 +33,8 @@ end
class VideoNotAvailableException < Exception
end
+
+# Exception used to indicate that the JSON response from YT is missing
+# some important informations, and that the query should be sent again.
+class RetryOnceException < Exception
+end
diff --git a/src/invidious/yt_backend/extractors.cr b/src/invidious/yt_backend/extractors.cr
index bca0dcbd..65d107b2 100644
--- a/src/invidious/yt_backend/extractors.cr
+++ b/src/invidious/yt_backend/extractors.cr
@@ -408,19 +408,23 @@ private module Parsers
private def self.parse(item_contents, author_fallback)
video_id = item_contents["videoId"].as_s
- begin
- video_details_container = item_contents.dig(
- "navigationEndpoint", "reelWatchEndpoint",
- "overlay", "reelPlayerOverlayRenderer",
- "reelPlayerHeaderSupportedRenderers",
- "reelPlayerHeaderRenderer"
- )
- rescue ex : KeyError
- # Extract key name from original message
- key = /"([^"]+)"/.match(ex.message || "").try &.[1]?
- raise BrokenTubeException.new(key || "reelPlayerOverlayRenderer")
+ reel_player_overlay = item_contents.dig(
+ "navigationEndpoint", "reelWatchEndpoint",
+ "overlay", "reelPlayerOverlayRenderer"
+ )
+
+ # Sometimes, the "reelPlayerOverlayRenderer" object is missing the
+ # important part of the response. We use this exception to tell
+ # the calling function to fetch the content again.
+ if !reel_player_overlay.as_h.has_key?("reelPlayerHeaderSupportedRenderers")
+ raise RetryOnceException.new
end
+ video_details_container = reel_player_overlay.dig(
+ "reelPlayerHeaderSupportedRenderers",
+ "reelPlayerHeaderRenderer"
+ )
+
# Author infos
author = video_details_container