From 234466374c0f0b7a8657b0bde42b0a02de8ea7c1 Mon Sep 17 00:00:00 2001 From: litetex <40789489+litetex@users.noreply.github.com> Date: Thu, 30 Nov 2023 17:05:47 +0100 Subject: [PATCH] [YT] Fix "Could not get like count" 1. YT uses a different data structure for likes (ViewModels instead of Renderers) 2. Fixed NPE in old code due to incorrect usage of string comparison 3. Unified JSONObject filtering and mapping --- .../extractors/YoutubeStreamExtractor.java | 136 ++++++++++-------- 1 file changed, 79 insertions(+), 57 deletions(-) diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java index be008fa23..b9c5bf318 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java @@ -391,73 +391,34 @@ public class YoutubeStreamExtractor extends StreamExtractor { return -1; } - String likesString = ""; + String likesString = null; try { - final JsonArray topLevelButtons = getVideoPrimaryInfoRenderer() + final List topLevelButtons = getVideoPrimaryInfoRenderer() .getObject("videoActions") .getObject("menuRenderer") - .getArray("topLevelButtons"); - - // Try first with the new video actions buttons data structure - JsonObject likeToggleButtonRenderer = topLevelButtons.stream() + .getArray("topLevelButtons") + .stream() .filter(JsonObject.class::isInstance) .map(JsonObject.class::cast) - .map(button -> button.getObject("segmentedLikeDislikeButtonRenderer") - .getObject("likeButton") - .getObject("toggleButtonRenderer")) - .filter(toggleButtonRenderer -> !isNullOrEmpty(toggleButtonRenderer)) + .collect(Collectors.toList()); + + likesString = topLevelButtons.stream() + .map(btn -> btn.getObject("segmentedLikeDislikeButtonViewModel") + .getObject("likeButtonViewModel") + .getObject("likeButtonViewModel") + .getObject("toggleButtonViewModel") + .getObject("toggleButtonViewModel") + .getObject("defaultButtonViewModel") + .getObject("buttonViewModel") + .getString("accessibilityText")) + .filter(Objects::nonNull) .findFirst() .orElse(null); - // Use the old video actions buttons data structure if the new one isn't returned - if (likeToggleButtonRenderer == null) { - /* - In the old video actions buttons data structure, there are 3 ways to detect whether - a button is the like button, using its toggleButtonRenderer: - - checking whether toggleButtonRenderer.targetId is equal to watch-like; - - checking whether toggleButtonRenderer.defaultIcon.iconType is equal to LIKE; - - checking whether - toggleButtonRenderer.toggleButtonSupportedData.toggleButtonIdData.id - is equal to TOGGLE_BUTTON_ID_TYPE_LIKE. - */ - likeToggleButtonRenderer = topLevelButtons.stream() - .filter(JsonObject.class::isInstance) - .map(JsonObject.class::cast) - .map(topLevelButton -> topLevelButton.getObject("toggleButtonRenderer")) - .filter(toggleButtonRenderer -> toggleButtonRenderer.getString("targetId") - .equalsIgnoreCase("watch-like") - || toggleButtonRenderer.getObject("defaultIcon") - .getString("iconType") - .equalsIgnoreCase("LIKE") - || toggleButtonRenderer.getObject("toggleButtonSupportedData") - .getObject("toggleButtonIdData") - .getString("id") - .equalsIgnoreCase("TOGGLE_BUTTON_ID_TYPE_LIKE")) - .findFirst() - .orElseThrow(() -> new ParsingException( - "The like button is missing even though ratings are enabled")); - } - - // Use one of the accessibility strings available (this one has the same path as the - // one used for comments' like count extraction) - likesString = likeToggleButtonRenderer.getObject("accessibilityData") - .getObject("accessibilityData") - .getString("label"); - - // Use the other accessibility string available which contains the exact like count + // Old - pre Dec 2023 way if (likesString == null) { - likesString = likeToggleButtonRenderer.getObject("accessibility") - .getString("label"); - } - - // Last method: use the defaultText's accessibility data, which contains the exact like - // count too, except when it is equal to 0, where a localized string is returned instead - if (likesString == null) { - likesString = likeToggleButtonRenderer.getObject("defaultText") - .getObject("accessibility") - .getObject("accessibilityData") - .getString("label"); + likesString = getPreDec2023LikeString(topLevelButtons); } // If ratings are allowed and the likes string is null, it means that we couldn't @@ -480,6 +441,67 @@ public class YoutubeStreamExtractor extends StreamExtractor { } } + protected String getPreDec2023LikeString(final List topLevelButtons) + throws ParsingException { + // Try first with the new video actions buttons data structure + JsonObject likeToggleButtonRenderer = topLevelButtons.stream() + .map(button -> button.getObject("segmentedLikeDislikeButtonRenderer") + .getObject("likeButton") + .getObject("toggleButtonRenderer")) + .filter(toggleButtonRenderer -> !isNullOrEmpty(toggleButtonRenderer)) + .findFirst() + .orElse(null); + + // Use the old video actions buttons data structure if the new one isn't returned + if (likeToggleButtonRenderer == null) { + /* + In the old video actions buttons data structure, there are 3 ways to detect whether + a button is the like button, using its toggleButtonRenderer: + - checking whether toggleButtonRenderer.targetId is equal to watch-like; + - checking whether toggleButtonRenderer.defaultIcon.iconType is equal to LIKE; + - checking whether + toggleButtonRenderer.toggleButtonSupportedData.toggleButtonIdData.id + is equal to TOGGLE_BUTTON_ID_TYPE_LIKE. + */ + likeToggleButtonRenderer = topLevelButtons.stream() + .map(topLevelButton -> topLevelButton.getObject("toggleButtonRenderer")) + .filter(toggleButtonRenderer -> "watch-like".equalsIgnoreCase( + toggleButtonRenderer.getString("targetId")) + || "LIKE".equalsIgnoreCase( + toggleButtonRenderer.getObject("defaultIcon") + .getString("iconType")) + || "TOGGLE_BUTTON_ID_TYPE_LIKE".equalsIgnoreCase( + toggleButtonRenderer.getObject("toggleButtonSupportedData") + .getObject("toggleButtonIdData") + .getString("id"))) + .findFirst() + .orElseThrow(() -> new ParsingException( + "The like button is missing even though ratings are enabled")); + } + + // Use one of the accessibility strings available (this one has the same path as the + // one used for comments' like count extraction) + String likesString = likeToggleButtonRenderer.getObject("accessibilityData") + .getObject("accessibilityData") + .getString("label"); + + // Use the other accessibility string available which contains the exact like count + if (likesString == null) { + likesString = likeToggleButtonRenderer.getObject("accessibility") + .getString("label"); + } + + // Last method: use the defaultText's accessibility data, which contains the exact like + // count too, except when it is equal to 0, where a localized string is returned instead + if (likesString == null) { + likesString = likeToggleButtonRenderer.getObject("defaultText") + .getObject("accessibility") + .getObject("accessibilityData") + .getString("label"); + } + return likesString; + } + @Nonnull @Override public String getUploaderUrl() throws ParsingException {