Skip to content

Commit 884da40

Browse files
authored
Merge pull request #926 from AudricV/fix-yt-like-count-extraction
[YouTube] Fix extraction of like count with the new data model
2 parents 58b09cf + 119b912 commit 884da40

1 file changed

Lines changed: 73 additions & 16 deletions

File tree

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

Lines changed: 73 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -417,28 +417,88 @@ public long getViewCount() throws ParsingException {
417417
@Override
418418
public long getLikeCount() throws ParsingException {
419419
assertPageFetched();
420+
421+
// If ratings are not allowed, there is no like count available
422+
if (!playerResponse.getObject("videoDetails").getBoolean("allowRatings")) {
423+
return -1;
424+
}
425+
420426
String likesString = "";
427+
421428
try {
422-
likesString = getVideoPrimaryInfoRenderer()
429+
final JsonArray topLevelButtons = getVideoPrimaryInfoRenderer()
423430
.getObject("videoActions")
424431
.getObject("menuRenderer")
425-
.getArray("topLevelButtons")
426-
.getObject(0)
427-
.getObject("toggleButtonRenderer")
428-
.getObject("defaultText")
429-
.getObject("accessibility")
432+
.getArray("topLevelButtons");
433+
434+
// Try first with the new video actions buttons data structure
435+
JsonObject likeToggleButtonRenderer = topLevelButtons.stream()
436+
.filter(JsonObject.class::isInstance)
437+
.map(JsonObject.class::cast)
438+
.map(button -> button.getObject("segmentedLikeDislikeButtonRenderer")
439+
.getObject("likeButton")
440+
.getObject("toggleButtonRenderer"))
441+
.filter(toggleButtonRenderer -> !isNullOrEmpty(toggleButtonRenderer))
442+
.findFirst()
443+
.orElse(null);
444+
445+
// Use the old video actions buttons data structure if the new one isn't returned
446+
if (likeToggleButtonRenderer == null) {
447+
/*
448+
In the old video actions buttons data structure, there are 3 ways to detect whether
449+
a button is the like button, using its toggleButtonRenderer:
450+
- checking whether toggleButtonRenderer.targetId is equal to watch-like;
451+
- checking whether toggleButtonRenderer.defaultIcon.iconType is equal to LIKE;
452+
- checking whether
453+
toggleButtonRenderer.toggleButtonSupportedData.toggleButtonIdData.id
454+
is equal to TOGGLE_BUTTON_ID_TYPE_LIKE.
455+
*/
456+
likeToggleButtonRenderer = topLevelButtons.stream()
457+
.filter(JsonObject.class::isInstance)
458+
.map(JsonObject.class::cast)
459+
.map(topLevelButton -> topLevelButton.getObject("toggleButtonRenderer"))
460+
.filter(toggleButtonRenderer -> toggleButtonRenderer.getString("targetId")
461+
.equalsIgnoreCase("watch-like")
462+
|| toggleButtonRenderer.getObject("defaultIcon")
463+
.getString("iconType")
464+
.equalsIgnoreCase("LIKE")
465+
|| toggleButtonRenderer.getObject("toggleButtonSupportedData")
466+
.getObject("toggleButtonIdData")
467+
.getString("id")
468+
.equalsIgnoreCase("TOGGLE_BUTTON_ID_TYPE_LIKE"))
469+
.findFirst()
470+
.orElseThrow(() -> new ParsingException(
471+
"The like button is missing even though ratings are enabled"));
472+
}
473+
474+
// Use one of the accessibility strings available (this one has the same path as the
475+
// one used for comments' like count extraction)
476+
likesString = likeToggleButtonRenderer.getObject("accessibilityData")
430477
.getObject("accessibilityData")
431478
.getString("label");
432479

480+
// Use the other accessibility string available which contains the exact like count
433481
if (likesString == null) {
434-
// If this kicks in our button has no content and therefore ratings must be disabled
435-
if (playerResponse.getObject("videoDetails").getBoolean("allowRatings")) {
436-
throw new ParsingException(
437-
"Ratings are enabled even though the like button is missing");
438-
}
439-
return -1;
482+
likesString = likeToggleButtonRenderer.getObject("accessibility")
483+
.getString("label");
484+
}
485+
486+
// Last method: use the defaultText's accessibility data, which contains the exact like
487+
// count too, except when it is equal to 0, where a localized string is returned instead
488+
if (likesString == null) {
489+
likesString = likeToggleButtonRenderer.getObject("defaultText")
490+
.getObject("accessibility")
491+
.getObject("accessibilityData")
492+
.getString("label");
440493
}
441494

495+
// If ratings are allowed and the likes string is null, it means that we couldn't
496+
// extract the (real) like count from accessibility data
497+
if (likesString == null) {
498+
throw new ParsingException("Could not get like count from accessibility data");
499+
}
500+
501+
// This check only works with English localizations!
442502
if (likesString.toLowerCase().contains("no likes")) {
443503
return 0;
444504
}
@@ -448,10 +508,7 @@ public long getLikeCount() throws ParsingException {
448508
throw new ParsingException("Could not parse \"" + likesString + "\" as an Integer",
449509
nfe);
450510
} catch (final Exception e) {
451-
if (getAgeLimit() == NO_AGE_LIMIT) {
452-
throw new ParsingException("Could not get like count", e);
453-
}
454-
return -1;
511+
throw new ParsingException("Could not get like count", e);
455512
}
456513
}
457514

0 commit comments

Comments
 (0)