Skip to content

Commit 9ae1be7

Browse files
committed
Fix displaying replies which do not start with the mention of another user
1 parent 3aa74f0 commit 9ae1be7

File tree

3 files changed

+82
-63
lines changed

3 files changed

+82
-63
lines changed

extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudParsingHelper.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -326,14 +326,8 @@ public static String getUploaderName(final JsonObject object) {
326326

327327
public static boolean isReplyTo(@Nonnull final JsonObject originalComment,
328328
@Nonnull final JsonObject otherComment) {
329-
final String mention = "@" + originalComment.getObject("user").getString("permalink");
330-
return otherComment.getString("body").startsWith(mention)
331-
&& originalComment.getInt("timestamp") == otherComment.getInt("timestamp");
329+
return originalComment.getInt("timestamp") == otherComment.getInt("timestamp");
332330

333331
}
334332

335-
public static boolean isReply(@Nonnull final JsonObject comment) {
336-
return comment.getString("body").startsWith("@");
337-
}
338-
339333
}

extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/extractors/SoundcloudCommentsExtractor.java

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.schabi.newpipe.extractor.services.soundcloud.extractors;
22

3+
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
4+
35
import com.grack.nanojson.JsonArray;
46
import com.grack.nanojson.JsonObject;
57
import com.grack.nanojson.JsonParser;
@@ -22,10 +24,9 @@
2224

2325
import javax.annotation.Nonnull;
2426

25-
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
26-
2727
public class SoundcloudCommentsExtractor extends CommentsExtractor {
2828
public static final String COLLECTION = "collection";
29+
public static final String NEXT_HREF = "next_href";
2930

3031
public SoundcloudCommentsExtractor(final StreamingService service,
3132
final ListLinkHandler uiHandler) {
@@ -49,9 +50,9 @@ public InfoItemsPage<CommentsInfoItem> getInitialPage() throws ExtractionExcepti
4950
final CommentsInfoItemsCollector collector = new CommentsInfoItemsCollector(
5051
getServiceId());
5152

52-
collectStreamsFrom(collector, json);
53+
collectCommentsFrom(collector, json);
5354

54-
return new InfoItemsPage<>(collector, new Page(json.getString("next_href")));
55+
return new InfoItemsPage<>(collector, new Page(json.getString(NEXT_HREF)));
5556
}
5657

5758
@Override
@@ -83,15 +84,15 @@ public InfoItemsPage<CommentsInfoItem> getPage(final Page page) throws Extractio
8384

8485
try {
8586
json = JsonParser.object().from(response.responseBody());
86-
hasNextPage = json.has("next_href");
87+
hasNextPage = json.has(NEXT_HREF);
8788
} catch (final JsonParserException e) {
8889
throw new ParsingException("Could not parse json", e);
8990
}
90-
collectStreamsFrom(collector, json);
91+
collectCommentsFrom(collector, json);
9192
}
9293

9394
if (hasNextPage) {
94-
return new InfoItemsPage<>(collector, new Page(json.getString("next_href")));
95+
return new InfoItemsPage<>(collector, new Page(json.getString(NEXT_HREF)));
9596
} else {
9697
return new InfoItemsPage<>(collector, null);
9798
}
@@ -100,25 +101,27 @@ public InfoItemsPage<CommentsInfoItem> getPage(final Page page) throws Extractio
100101
@Override
101102
public void onFetchPage(@Nonnull final Downloader downloader) { }
102103

103-
private void collectStreamsFrom(final CommentsInfoItemsCollector collector,
104-
final JsonObject json) throws ParsingException {
104+
private void collectCommentsFrom(final CommentsInfoItemsCollector collector,
105+
final JsonObject json) throws ParsingException {
105106
final String url = getUrl();
106107
final JsonArray entries = json.getArray(COLLECTION);
108+
JsonObject lastTopComment = null;
107109
for (int i = 0; i < entries.size(); i++) {
108110
final JsonObject entry = entries.getObject(i);
109111
if (i == 0
110-
|| (!SoundcloudParsingHelper.isReply(entry)
111-
&& !SoundcloudParsingHelper.isReplyTo(entries.getObject(i - 1), entry))) {
112+
|| (!SoundcloudParsingHelper.isReplyTo(entries.getObject(i - 1), entry)
113+
&& !SoundcloudParsingHelper.isReplyTo(lastTopComment, entry))) {
114+
lastTopComment = entry;
112115
collector.commit(new SoundcloudCommentsInfoItemExtractor(
113-
json, i, entries.getObject(i), url));
116+
json, i, entry, url));
114117
}
115118
}
116119
}
117120

118121
private boolean collectRepliesFrom(final CommentsInfoItemsCollector collector,
119122
final JsonObject json,
120123
final int id,
121-
final String url) throws ParsingException {
124+
final String url) {
122125
JsonObject originalComment = null;
123126
final JsonArray entries = json.getArray(COLLECTION);
124127
boolean moreReplies = false;
@@ -134,7 +137,7 @@ private boolean collectRepliesFrom(final CommentsInfoItemsCollector collector,
134137
json, i, entries.getObject(i), url, originalComment));
135138
// There might be more replies to the originalComment,
136139
// especially if the original comment is at the end of the list.
137-
if (i == entries.size() - 1 && json.has("next_href")) {
140+
if (i == entries.size() - 1 && json.has(NEXT_HREF)) {
138141
moreReplies = true;
139142
}
140143
}

extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/extractors/SoundcloudCommentsInfoItemExtractor.java

Lines changed: 64 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package org.schabi.newpipe.extractor.services.soundcloud.extractors;
22

3+
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
4+
35
import com.grack.nanojson.JsonArray;
46
import com.grack.nanojson.JsonObject;
7+
58
import org.schabi.newpipe.extractor.Page;
69
import org.schabi.newpipe.extractor.ServiceList;
710
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
@@ -12,20 +15,21 @@
1215
import org.schabi.newpipe.extractor.services.soundcloud.SoundcloudParsingHelper;
1316
import org.schabi.newpipe.extractor.stream.Description;
1417

15-
import javax.annotation.Nullable;
16-
import java.util.ArrayList;
17-
import java.util.List;
1818
import java.util.Objects;
1919

20+
import javax.annotation.Nullable;
21+
2022
public class SoundcloudCommentsInfoItemExtractor implements CommentsInfoItemExtractor {
21-
public static final String USER = "user";
2223
public static final String BODY = "body";
2324
public static final String USER_PERMALINK = "permalink";
25+
public static final String USER_FULL_NAME = "full_name";
26+
public static final String USER_USERNAME = "username";
2427

2528
private final JsonObject json;
2629
private final int index;
2730
private final JsonObject item;
2831
private final String url;
32+
private final JsonObject user;
2933
private final JsonObject superComment;
3034

3135
private int replyCount = CommentsInfoItem.UNKNOWN_REPLY_COUNT;
@@ -39,6 +43,7 @@ public SoundcloudCommentsInfoItemExtractor(final JsonObject json, final int inde
3943
this.item = item;
4044
this.url = url;
4145
this.superComment = superComment;
46+
this.user = item.getObject("user");
4247
}
4348

4449
public SoundcloudCommentsInfoItemExtractor(final JsonObject json, final int index,
@@ -50,7 +55,6 @@ public SoundcloudCommentsInfoItemExtractor(final JsonObject json, final int inde
5055
public String getCommentId() {
5156
return Objects.toString(item.getLong("id"), null);
5257
}
53-
5458
@Override
5559
public Description getCommentText() {
5660
String commentContent = item.getString(BODY);
@@ -61,32 +65,49 @@ public Description getCommentText() {
6165
// Therefore, the comment starts with the mention of the original comment's author.
6266
// The account is automatically linked by the SoundCloud web UI.
6367
// We need to do this manually.
64-
final JsonObject user = superComment.getObject("user");
65-
final String link = "<a href=\"" + user.getString("permalink_url") + "\">"
66-
+ "@" + user.getString("full_name") + "</a>";
67-
commentContent = commentContent
68-
.replace("@" + user.getString(USER_PERMALINK), link)
69-
.replace("@" + superComment.getInt("user_id"), link);
68+
if (commentContent.startsWith("@")) {
69+
final String authorName = commentContent.split(" ", 2)[0].replace("@", "");
70+
final JsonArray comments = json.getArray(SoundcloudCommentsExtractor.COLLECTION);
71+
JsonObject author = null;
72+
for (int i = index - 1; i >= 0 && author == null; i--) {
73+
final JsonObject commentsAuthor = comments.getObject(i).getObject("user");
74+
// use startsWith because sometimes the mention of the user
75+
// is followed by a punctuation character.
76+
if (authorName.startsWith(commentsAuthor.getString(USER_PERMALINK))) {
77+
author = commentsAuthor;
78+
}
79+
}
80+
if (author == null) {
81+
author = superComment.getObject("user");
82+
}
83+
final String name = isNullOrEmpty(author.getString(USER_FULL_NAME))
84+
? author.getString(USER_USERNAME) : author.getString(USER_FULL_NAME);
85+
final String link = "<a href=\"" + author.getString("permalink_url") + "\">"
86+
+ "@" + name + "</a>";
87+
commentContent = commentContent
88+
.replace("@" + author.getString(USER_PERMALINK), link)
89+
.replace("@" + author.getInt("user_id"), link);
90+
}
7091

7192
return new Description(commentContent, Description.HTML);
7293
}
7394

7495
@Override
7596
public String getUploaderName() {
76-
if (isNullOrEmpty(user.getString("full_name"))) {
77-
return user.getString("username");
97+
if (isNullOrEmpty(user.getString(USER_FULL_NAME))) {
98+
return user.getString(USER_USERNAME);
7899
}
79-
return user.getString("full_name");
100+
return user.getString(USER_FULL_NAME);
80101
}
81102

82103
@Override
83104
public String getUploaderAvatarUrl() {
84-
return item.getObject(USER).getString("avatar_url");
105+
return user.getString("avatar_url");
85106
}
86107

87108
@Override
88109
public boolean isUploaderVerified() throws ParsingException {
89-
return item.getObject(USER).getBoolean("verified");
110+
return user.getBoolean("verified");
90111
}
91112

92113
@Override
@@ -96,7 +117,7 @@ public int getStreamPosition() throws ParsingException {
96117

97118
@Override
98119
public String getUploaderUrl() {
99-
return item.getObject(USER).getString("permalink_url");
120+
return user.getString("permalink_url");
100121
}
101122

102123
@Override
@@ -112,7 +133,7 @@ public DateWrapper getUploadDate() throws ParsingException {
112133

113134
@Override
114135
public String getName() throws ParsingException {
115-
return item.getObject(USER).getString("permalink");
136+
return user.getString(USER_PERMALINK);
116137
}
117138

118139
@Override
@@ -122,38 +143,39 @@ public String getUrl() {
122143

123144
@Override
124145
public String getThumbnailUrl() {
125-
return item.getObject(USER).getString("avatar_url");
146+
return user.getString("avatar_url");
126147
}
127148

128149
@Override
129150
public Page getReplies() {
130151
if (replyCount == CommentsInfoItem.UNKNOWN_REPLY_COUNT) {
131-
final List<JsonObject> replies = new ArrayList<>();
152+
final JsonArray replies = new JsonArray();
132153
final CommentsInfoItemsCollector collector = new CommentsInfoItemsCollector(
133154
ServiceList.SoundCloud.getServiceId());
134-
final JsonArray jsonArray = new JsonArray();
135-
// Replies start with the mention of the user who created the original comment.
136-
final String mention = "@" + item.getObject(USER).getString(USER_PERMALINK);
137-
// Loop through all comments which come after the original comment to find its replies.
138-
final JsonArray allItems = json.getArray(SoundcloudCommentsExtractor.COLLECTION);
139-
for (int i = index + 1; i < allItems.size(); i++) {
140-
final JsonObject comment = allItems.getObject(i);
141-
final String commentContent = comment.getString("body");
142-
if (commentContent.startsWith(mention)) {
143-
replies.add(comment);
144-
jsonArray.add(comment);
145-
collector.commit(new SoundcloudCommentsInfoItemExtractor(
146-
json, i, comment, url, item));
147-
} else if (!commentContent.startsWith("@") || replies.isEmpty()) {
148-
// Only the comments directly after the original comment
149-
// starting with the mention of the comment's creator
150-
// are replies to the original comment.
151-
// The first comment not starting with these letters
152-
// is the next top-level comment.
153-
break;
155+
// SoundCloud has only comments and top level replies, but not nested replies.
156+
// Therefore, replies cannot have further replies.
157+
if (superComment == null) {
158+
// Loop through all comments which come after the original comment
159+
// to find its replies.
160+
final JsonArray allItems = json.getArray(SoundcloudCommentsExtractor.COLLECTION);
161+
boolean foundReply = false;
162+
for (int i = index + 1; i < allItems.size(); i++) {
163+
final JsonObject comment = allItems.getObject(i);
164+
if (SoundcloudParsingHelper.isReplyTo(item, comment)) {
165+
replies.add(comment);
166+
collector.commit(new SoundcloudCommentsInfoItemExtractor(
167+
json, i, comment, url, item));
168+
foundReply = true;
169+
} else if (foundReply) {
170+
// Only the comments directly after the original comment
171+
// having the same timestamp are replies to the original comment.
172+
// The first comment not having the same timestamp
173+
// is the next top-level comment.
174+
break;
175+
}
154176
}
155177
}
156-
replyCount = jsonArray.size();
178+
replyCount = replies.size();
157179
if (collector.getItems().isEmpty()) {
158180
return null;
159181
}
@@ -165,7 +187,7 @@ public Page getReplies() {
165187
}
166188

167189
@Override
168-
public int getReplyCount() throws ParsingException {
190+
public int getReplyCount() {
169191
if (replyCount == CommentsInfoItem.UNKNOWN_REPLY_COUNT) {
170192
getReplies();
171193
}

0 commit comments

Comments
 (0)