Skip to content

Commit 3782d9a

Browse files
committed
[YouTube] Support new A/B tested like data and avoid like count conversion from integer to long
Also make minor improvements to current like data extraction and remove previous like count data support, as it is not returned anymore.
1 parent b71ce11 commit 3782d9a

1 file changed

Lines changed: 79 additions & 58 deletions

File tree

extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java

Lines changed: 79 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -388,57 +388,43 @@ public long getLikeCount() throws ParsingException {
388388

389389
// If ratings are not allowed, there is no like count available
390390
if (!playerResponse.getObject("videoDetails").getBoolean("allowRatings")) {
391-
return -1;
391+
return -1L;
392392
}
393393

394-
String likesString = "";
394+
final JsonArray topLevelButtons = getVideoPrimaryInfoRenderer()
395+
.getObject("videoActions")
396+
.getObject("menuRenderer")
397+
.getArray("topLevelButtons");
395398

396399
try {
397-
final JsonArray topLevelButtons = getVideoPrimaryInfoRenderer()
398-
.getObject("videoActions")
399-
.getObject("menuRenderer")
400-
.getArray("topLevelButtons");
400+
return parseLikeCountFromLikeButtonViewModel(topLevelButtons);
401+
} catch (final ParsingException ignored) {
402+
// A segmentedLikeDislikeButtonRenderer could be returned instead of a
403+
// segmentedLikeDislikeButtonViewModel, so ignore extraction errors relative to
404+
// segmentedLikeDislikeButtonViewModel object
405+
}
401406

402-
// Try first with the new video actions buttons data structure
403-
JsonObject likeToggleButtonRenderer = topLevelButtons.stream()
404-
.filter(JsonObject.class::isInstance)
405-
.map(JsonObject.class::cast)
406-
.map(button -> button.getObject("segmentedLikeDislikeButtonRenderer")
407-
.getObject("likeButton")
408-
.getObject("toggleButtonRenderer"))
409-
.filter(toggleButtonRenderer -> !isNullOrEmpty(toggleButtonRenderer))
410-
.findFirst()
411-
.orElse(null);
412-
413-
// Use the old video actions buttons data structure if the new one isn't returned
414-
if (likeToggleButtonRenderer == null) {
415-
/*
416-
In the old video actions buttons data structure, there are 3 ways to detect whether
417-
a button is the like button, using its toggleButtonRenderer:
418-
- checking whether toggleButtonRenderer.targetId is equal to watch-like;
419-
- checking whether toggleButtonRenderer.defaultIcon.iconType is equal to LIKE;
420-
- checking whether
421-
toggleButtonRenderer.toggleButtonSupportedData.toggleButtonIdData.id
422-
is equal to TOGGLE_BUTTON_ID_TYPE_LIKE.
423-
*/
424-
likeToggleButtonRenderer = topLevelButtons.stream()
425-
.filter(JsonObject.class::isInstance)
426-
.map(JsonObject.class::cast)
427-
.map(topLevelButton -> topLevelButton.getObject("toggleButtonRenderer"))
428-
.filter(toggleButtonRenderer -> toggleButtonRenderer.getString("targetId")
429-
.equalsIgnoreCase("watch-like")
430-
|| toggleButtonRenderer.getObject("defaultIcon")
431-
.getString("iconType")
432-
.equalsIgnoreCase("LIKE")
433-
|| toggleButtonRenderer.getObject("toggleButtonSupportedData")
434-
.getObject("toggleButtonIdData")
435-
.getString("id")
436-
.equalsIgnoreCase("TOGGLE_BUTTON_ID_TYPE_LIKE"))
437-
.findFirst()
438-
.orElseThrow(() -> new ParsingException(
439-
"The like button is missing even though ratings are enabled"));
440-
}
407+
try {
408+
return parseLikeCountFromLikeButtonRenderer(topLevelButtons);
409+
} catch (final ParsingException e) {
410+
throw new ParsingException("Could not get like count", e);
411+
}
412+
}
441413

414+
private static long parseLikeCountFromLikeButtonRenderer(
415+
@Nonnull final JsonArray topLevelButtons) throws ParsingException {
416+
String likesString = null;
417+
final JsonObject likeToggleButtonRenderer = topLevelButtons.stream()
418+
.filter(JsonObject.class::isInstance)
419+
.map(JsonObject.class::cast)
420+
.map(button -> button.getObject("segmentedLikeDislikeButtonRenderer")
421+
.getObject("likeButton")
422+
.getObject("toggleButtonRenderer"))
423+
.filter(toggleButtonRenderer -> !isNullOrEmpty(toggleButtonRenderer))
424+
.findFirst()
425+
.orElse(null);
426+
427+
if (likeToggleButtonRenderer != null) {
442428
// Use one of the accessibility strings available (this one has the same path as the
443429
// one used for comments' like count extraction)
444430
likesString = likeToggleButtonRenderer.getObject("accessibilityData")
@@ -460,23 +446,58 @@ public long getLikeCount() throws ParsingException {
460446
.getString("label");
461447
}
462448

463-
// If ratings are allowed and the likes string is null, it means that we couldn't
464-
// extract the (real) like count from accessibility data
465-
if (likesString == null) {
466-
throw new ParsingException("Could not get like count from accessibility data");
467-
}
468-
469449
// This check only works with English localizations!
470-
if (likesString.toLowerCase().contains("no likes")) {
450+
if (likesString != null && likesString.toLowerCase().contains("no likes")) {
471451
return 0;
472452
}
453+
}
473454

474-
return Integer.parseInt(Utils.removeNonDigitCharacters(likesString));
475-
} catch (final NumberFormatException nfe) {
476-
throw new ParsingException("Could not parse \"" + likesString + "\" as an Integer",
477-
nfe);
478-
} catch (final Exception e) {
479-
throw new ParsingException("Could not get like count", e);
455+
// If ratings are allowed and the likes string is null, it means that we couldn't extract
456+
// the full like count from accessibility data
457+
if (likesString == null) {
458+
throw new ParsingException("Could not get like count from accessibility data");
459+
}
460+
461+
try {
462+
return Long.parseLong(Utils.removeNonDigitCharacters(likesString));
463+
} catch (final NumberFormatException e) {
464+
throw new ParsingException("Could not parse \"" + likesString + "\" as a long", e);
465+
}
466+
}
467+
468+
private static long parseLikeCountFromLikeButtonViewModel(
469+
@Nonnull final JsonArray topLevelButtons) throws ParsingException {
470+
// Try first with the current video actions buttons data structure
471+
final JsonObject likeToggleButtonViewModel = topLevelButtons.stream()
472+
.filter(JsonObject.class::isInstance)
473+
.map(JsonObject.class::cast)
474+
.map(button -> button.getObject("segmentedLikeDislikeButtonViewModel")
475+
.getObject("likeButtonViewModel")
476+
.getObject("likeButtonViewModel")
477+
.getObject("toggleButtonViewModel")
478+
.getObject("toggleButtonViewModel")
479+
.getObject("defaultButtonViewModel")
480+
.getObject("buttonViewModel"))
481+
.filter(buttonViewModel -> !isNullOrEmpty(buttonViewModel))
482+
.findFirst()
483+
.orElse(null);
484+
485+
if (likeToggleButtonViewModel == null) {
486+
throw new ParsingException("Could not find buttonViewModel object");
487+
}
488+
489+
final String accessibilityText = likeToggleButtonViewModel.getString("accessibilityText");
490+
if (accessibilityText == null) {
491+
throw new ParsingException("Could not find buttonViewModel's accessibilityText string");
492+
}
493+
494+
// The like count is always returned as a number in this element, even for videos with no
495+
// likes
496+
try {
497+
return Long.parseLong(Utils.removeNonDigitCharacters(accessibilityText));
498+
} catch (final NumberFormatException e) {
499+
throw new ParsingException(
500+
"Could not parse \"" + accessibilityText + "\" as a long", e);
480501
}
481502
}
482503

0 commit comments

Comments
 (0)