-
-
Notifications
You must be signed in to change notification settings - Fork 544
Feature/live chat comments #1481
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
943a41f
00b933f
b012ade
cb82024
08f6570
b782992
8a7cc39
05a8d36
620b756
40c54a7
81b39fa
fc533e4
70c2734
9443929
3169692
05f6a5e
2430540
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,6 +29,7 @@ | |
| import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; | ||
|
|
||
| public class YoutubeCommentsExtractor extends CommentsExtractor { | ||
| private static final String TAG = YoutubeCommentsExtractor.class.getSimpleName(); | ||
|
|
||
| private static final String COMMENT_VIEW_MODEL_KEY = "commentViewModel"; | ||
| private static final String COMMENT_RENDERER_KEY = "commentRenderer"; | ||
|
|
@@ -43,6 +44,16 @@ | |
| */ | ||
| private JsonObject ajaxJson; | ||
|
|
||
| /** | ||
| * Live chat continuation token, used when regular comments are disabled. | ||
| */ | ||
| private String liveChatContinuation; | ||
|
|
||
| /** | ||
| * Whether this video is / was a live stream. | ||
| */ | ||
| private boolean isLiveStream; | ||
|
|
||
| public YoutubeCommentsExtractor( | ||
| final StreamingService service, | ||
| final ListLinkHandler uiHandler) { | ||
|
|
@@ -54,6 +65,10 @@ | |
| public InfoItemsPage<CommentsInfoItem> getInitialPage() | ||
| throws IOException, ExtractionException { | ||
|
|
||
| if (liveChatContinuation != null) { | ||
| return fetchLiveChat(liveChatContinuation); | ||
| } | ||
|
|
||
| if (commentsDisabled) { | ||
| return getInfoItemsPageForDisabledComments(); | ||
| } | ||
|
|
@@ -194,6 +209,11 @@ | |
| public InfoItemsPage<CommentsInfoItem> getPage(final Page page) | ||
| throws IOException, ExtractionException { | ||
|
|
||
| if ("live_chat".equals(page.getUrl()) || liveChatContinuation != null) { | ||
| isLiveStream = true; | ||
| return fetchLiveChat(page.getId()); | ||
| } | ||
|
|
||
| if (commentsDisabled) { | ||
| return getInfoItemsPageForDisabledComments(); | ||
| } | ||
|
|
@@ -206,7 +226,7 @@ | |
| // @formatter:off | ||
| final byte[] body = JsonWriter.string( | ||
| prepareDesktopJsonBuilder(localization, getExtractorContentCountry()) | ||
| .value("continuation", page.getId()) | ||
|
Check failure on line 229 in extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeCommentsExtractor.java
|
||
| .done()) | ||
| .getBytes(StandardCharsets.UTF_8); | ||
| // @formatter:on | ||
|
|
@@ -351,8 +371,8 @@ | |
| .getBytes(StandardCharsets.UTF_8); | ||
| // @formatter:on | ||
|
|
||
| final String initialToken = | ||
| findInitialCommentsToken(getJsonPostResponse("next", body, localization)); | ||
| final JsonObject nextResponse = getJsonPostResponse("next", body, localization); | ||
| final String initialToken = findInitialCommentsToken(nextResponse); | ||
|
|
||
| if (initialToken == null) { | ||
| return; | ||
|
|
@@ -369,10 +389,110 @@ | |
| ajaxJson = getJsonPostResponse("next", ajaxBody, localization); | ||
| } | ||
|
|
||
| /** | ||
| * Configures this extractor to fetch live chat messages. | ||
| */ | ||
| @Override | ||
| public void setLiveChatContinuation(final String continuation) { | ||
| this.liveChatContinuation = continuation; | ||
| } | ||
|
|
||
| /** | ||
| * Fetches live chat messages and converts them to CommentsInfoItem. | ||
| */ | ||
| private InfoItemsPage<CommentsInfoItem> fetchLiveChat(final String chatContinuation) | ||
| throws IOException, ExtractionException { | ||
| isLiveStream = true; | ||
| final Localization localization = getExtractorLocalization(); | ||
| final byte[] json = JsonWriter.string( | ||
| prepareDesktopJsonBuilder(localization, getExtractorContentCountry()) | ||
| .value("continuation", chatContinuation) | ||
| .object("currentPlayerState") | ||
| .value("playerOffsetMs", "0") | ||
| .end() | ||
| .done()) | ||
| .getBytes(StandardCharsets.UTF_8); | ||
|
|
||
| final String endpoint = "live_chat/" | ||
| + (isLiveStream ? "get_live_chat" : "get_live_chat_replay"); | ||
|
Comment on lines
+403
to
+417
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| final JsonObject result = getJsonPostResponse(endpoint, json, localization); | ||
|
|
||
| return extractLiveChatComments(result); | ||
| } | ||
|
|
||
| /** | ||
| * Extracts live chat actions into CommentsInfoItem objects. | ||
| */ | ||
| private InfoItemsPage<CommentsInfoItem> extractLiveChatComments( | ||
|
Check failure on line 426 in extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeCommentsExtractor.java
|
||
| final JsonObject result) throws ExtractionException { | ||
| final CommentsInfoItemsCollector collector = new CommentsInfoItemsCollector( | ||
| getServiceId()); | ||
|
|
||
| try { | ||
| final JsonObject chatContinuation = result | ||
| .getObject("continuationContents") | ||
| .getObject("liveChatContinuation"); | ||
| final JsonArray actions = chatContinuation.getArray("actions"); | ||
|
|
||
| for (int i = 0; i < actions.size(); i++) { | ||
| final JsonObject action = actions.getObject(i); | ||
| final JsonObject item; | ||
| if (action.has("addChatItemAction")) { | ||
|
Check failure on line 440 in extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeCommentsExtractor.java
|
||
| item = action.getObject("addChatItemAction") | ||
| .getObject("item"); | ||
| } else if (action.has("replayChatItemAction")) { | ||
| item = action.getObject("replayChatItemAction") | ||
| .getArray("actions").getObject(0) | ||
| .getObject("addChatItemAction") | ||
| .getObject("item"); | ||
| } else { | ||
| continue; | ||
| } | ||
|
|
||
| if (item.has("liveChatTextMessageRenderer")) { | ||
| collector.commit(new YoutubeLiveChatInfoItemExtractor( | ||
| item.getObject("liveChatTextMessageRenderer"))); | ||
| } | ||
| } | ||
|
|
||
| // Extract next continuation | ||
| final JsonArray continuations = chatContinuation | ||
| .getArray("continuations"); | ||
| final Page nextPage; | ||
| if (!continuations.isEmpty()) { | ||
| final JsonObject contObj = continuations.getObject( | ||
| continuations.size() - 1); | ||
| String nextCont = null; | ||
| if (contObj.has("timedContinuationData")) { | ||
| nextCont = contObj.getObject("timedContinuationData") | ||
| .getString("continuation"); | ||
| } else if (contObj.has("invalidationContinuationData")) { | ||
| nextCont = contObj.getObject("invalidationContinuationData") | ||
| .getString("continuation"); | ||
| } else if (contObj.has("liveChatReplayContinuationData")) { | ||
| nextCont = contObj.getObject("liveChatReplayContinuationData") | ||
| .getString("continuation"); | ||
| } | ||
| nextPage = nextCont != null ? new Page("live_chat", nextCont) : null; | ||
| } else { | ||
| nextPage = null; | ||
| } | ||
|
|
||
| return new InfoItemsPage<>(collector, nextPage); | ||
| } catch (final Exception e) { | ||
| return getInfoItemsPageForDisabledComments(); | ||
| } | ||
| } | ||
|
|
||
|
|
||
| @Override | ||
| public boolean isCommentsDisabled() { | ||
| return commentsDisabled; | ||
| return commentsDisabled && !isLiveChat(); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isLiveChat() { | ||
| return liveChatContinuation != null; | ||
| } | ||
|
|
||
| @Override | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not understand why there are two ways to determine whether this extractor handles a live stream/chat. While
isLiveStreamis used and set individually for internal purposes,isLiveChat()relies onliveChatContinuation. I thinkisLiveChatcan be removed.Please explain why two different ways are needed. As far as I understand YouTube how handles this, a video can either have comments or a live chat, but not both at the same time. Is this correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isLiveChat() cannot be removed because it represents the extractor's configuration mode set externally via setLiveChatContinuation() whereas isLiveStream represents the video's metadata. A live-stream video may or may not have live chat configured on this extractor instance.
However, you are correct that the ternary inside fetchLiveChat() is dead code because isLiveStream is overwriten to true at the top of the method. I will either remove the ternary or pass the replay/live flag as a parameter instead.