Skip to content

Commit b012ade

Browse files
committed
Add live chat fallback when comments are disabled
1 parent 00b933f commit b012ade

1 file changed

Lines changed: 125 additions & 3 deletions

File tree

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

Lines changed: 125 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,16 @@ public class YoutubeCommentsExtractor extends CommentsExtractor {
4343
*/
4444
private JsonObject ajaxJson;
4545

46+
/**
47+
* Live chat continuation token, used when regular comments are disabled.
48+
*/
49+
private String liveChatContinuation;
50+
51+
/**
52+
* Whether this video is / was a live stream.
53+
*/
54+
private boolean isLiveStream;
55+
4656
public YoutubeCommentsExtractor(
4757
final StreamingService service,
4858
final ListLinkHandler uiHandler) {
@@ -54,6 +64,10 @@ public YoutubeCommentsExtractor(
5464
public InfoItemsPage<CommentsInfoItem> getInitialPage()
5565
throws IOException, ExtractionException {
5666

67+
if (commentsDisabled && liveChatContinuation != null) {
68+
return fetchLiveChat(liveChatContinuation);
69+
}
70+
5771
if (commentsDisabled) {
5872
return getInfoItemsPageForDisabledComments();
5973
}
@@ -351,10 +365,12 @@ public void onFetchPage(@Nonnull final Downloader downloader)
351365
.getBytes(StandardCharsets.UTF_8);
352366
// @formatter:on
353367

354-
final String initialToken =
355-
findInitialCommentsToken(getJsonPostResponse("next", body, localization));
368+
final JsonObject nextResponse = getJsonPostResponse("next", body, localization);
369+
final String initialToken = findInitialCommentsToken(nextResponse);
356370

357371
if (initialToken == null) {
372+
// Try to extract live chat continuation for live streams
373+
findLiveChatContinuation(nextResponse);
358374
return;
359375
}
360376

@@ -369,10 +385,116 @@ public void onFetchPage(@Nonnull final Downloader downloader)
369385
ajaxJson = getJsonPostResponse("next", ajaxBody, localization);
370386
}
371387

388+
/**
389+
* Tries to extract a live chat continuation token from the next response.
390+
* This is used when regular comments are disabled on a live stream.
391+
*/
392+
private void findLiveChatContinuation(final JsonObject nextResponse) {
393+
try {
394+
final JsonObject liveChatRenderer = nextResponse
395+
.getObject("contents")
396+
.getObject("twoColumnWatchNextResults")
397+
.getObject("conversationBar")
398+
.getObject("liveChatRenderer");
399+
liveChatContinuation = liveChatRenderer
400+
.getArray("continuations")
401+
.getObject(0)
402+
.getObject("reloadContinuationData")
403+
.getString("continuation");
404+
} catch (final Exception e) {
405+
liveChatContinuation = null;
406+
}
407+
}
408+
409+
/**
410+
* Fetches live chat messages and converts them to CommentsInfoItem.
411+
*/
412+
private InfoItemsPage<CommentsInfoItem> fetchLiveChat(final String chatContinuation)
413+
throws IOException, ExtractionException {
414+
final Localization localization = getExtractorLocalization();
415+
final byte[] json = JsonWriter.string(
416+
prepareDesktopJsonBuilder(localization, getExtractorContentCountry())
417+
.value("continuation", chatContinuation)
418+
.object("currentPlayerState")
419+
.value("playerOffsetMs", "0")
420+
.end()
421+
.done())
422+
.getBytes(StandardCharsets.UTF_8);
423+
424+
final String endpoint = "live_chat/" + (isLiveStream ? "get_live_chat" : "get_live_chat_replay");
425+
final JsonObject result = getJsonPostResponse(endpoint, json, localization);
426+
427+
return extractLiveChatComments(result);
428+
}
429+
430+
/**
431+
* Extracts live chat actions into CommentsInfoItem objects.
432+
*/
433+
private InfoItemsPage<CommentsInfoItem> extractLiveChatComments(
434+
final JsonObject result) throws ExtractionException {
435+
final CommentsInfoItemsCollector collector = new CommentsInfoItemsCollector(
436+
getServiceId());
437+
438+
try {
439+
final JsonObject chatContinuation = result
440+
.getObject("continuationContents")
441+
.getObject("liveChatContinuation");
442+
final JsonArray actions = chatContinuation.getArray("actions");
443+
444+
for (int i = 0; i < actions.size(); i++) {
445+
final JsonObject action = actions.getObject(i);
446+
final JsonObject item;
447+
if (action.has("addChatItemAction")) {
448+
item = action.getObject("addChatItemAction")
449+
.getObject("item");
450+
} else if (action.has("replayChatItemAction")) {
451+
item = action.getObject("replayChatItemAction")
452+
.getArray("actions").getObject(0)
453+
.getObject("addChatItemAction")
454+
.getObject("item");
455+
} else {
456+
continue;
457+
}
458+
459+
if (item.has("liveChatTextMessageRenderer")) {
460+
collector.commit(new YoutubeLiveChatInfoItemExtractor(
461+
item.getObject("liveChatTextMessageRenderer")));
462+
}
463+
}
464+
465+
// Extract next continuation
466+
final JsonArray continuations = chatContinuation
467+
.getArray("continuations");
468+
final Page nextPage;
469+
if (!continuations.isEmpty()) {
470+
final JsonObject contObj = continuations.getObject(
471+
continuations.size() - 1);
472+
String nextCont = null;
473+
if (contObj.has("timedContinuationData")) {
474+
nextCont = contObj.getObject("timedContinuationData")
475+
.getString("continuation");
476+
} else if (contObj.has("invalidationContinuationData")) {
477+
nextCont = contObj.getObject("invalidationContinuationData")
478+
.getString("continuation");
479+
} else if (contObj.has("liveChatReplayContinuationData")) {
480+
nextCont = contObj.getObject("liveChatReplayContinuationData")
481+
.getString("continuation");
482+
}
483+
nextPage = nextCont != null ? new Page(getUrl(), nextCont) : null;
484+
} else {
485+
nextPage = null;
486+
}
487+
488+
return new InfoItemsPage<>(collector, nextPage);
489+
} catch (final Exception e) {
490+
return getInfoItemsPageForDisabledComments();
491+
}
492+
}
493+
372494

373495
@Override
374496
public boolean isCommentsDisabled() {
375-
return commentsDisabled;
497+
return commentsDisabled && liveChatContinuation == null;
376498
}
377499

378500
@Override

0 commit comments

Comments
 (0)