comments redo (logged-in endpoint)

This commit is contained in:
Austin Huang 2021-05-29 10:46:23 -04:00
parent 59750b1026
commit 890139a287
No known key found for this signature in database
GPG Key ID: 84C23AA04587A91F
13 changed files with 590 additions and 363 deletions

View File

@ -17,7 +17,7 @@ public final class CommentsAdapter extends ListAdapter<Comment, CommentViewHolde
private static final DiffUtil.ItemCallback<Comment> DIFF_CALLBACK = new DiffUtil.ItemCallback<Comment>() { private static final DiffUtil.ItemCallback<Comment> DIFF_CALLBACK = new DiffUtil.ItemCallback<Comment>() {
@Override @Override
public boolean areItemsTheSame(@NonNull final Comment oldItem, @NonNull final Comment newItem) { public boolean areItemsTheSame(@NonNull final Comment oldItem, @NonNull final Comment newItem) {
return Objects.equals(oldItem.getId(), newItem.getId()); return Objects.equals(oldItem.getPk(), newItem.getPk());
} }
@Override @Override

View File

@ -123,7 +123,7 @@ public final class CommentViewHolder extends RecyclerView.ViewHolder {
private void setLikes(@NonNull final Comment comment, final boolean isReply) { private void setLikes(@NonNull final Comment comment, final boolean isReply) {
// final String likesString = itemView.getResources().getQuantityString(R.plurals.likes_count, likes, likes); // final String likesString = itemView.getResources().getQuantityString(R.plurals.likes_count, likes, likes);
binding.likes.setText(String.valueOf(comment.getLikes())); binding.likes.setText(String.valueOf(comment.getCommentLikeCount()));
binding.likes.setOnLongClickListener(v -> { binding.likes.setOnLongClickListener(v -> {
if (commentCallback == null) return false; if (commentCallback == null) return false;
commentCallback.onViewLikes(comment); commentCallback.onViewLikes(comment);
@ -147,7 +147,7 @@ public final class CommentViewHolder extends RecyclerView.ViewHolder {
} }
private void setReplies(@NonNull final Comment comment, final boolean isReply) { private void setReplies(@NonNull final Comment comment, final boolean isReply) {
final int replies = comment.getReplyCount(); final int replies = comment.getChildCommentCount();
binding.replies.setVisibility(View.VISIBLE); binding.replies.setVisibility(View.VISIBLE);
final String text = isReply ? "" : String.valueOf(replies); final String text = isReply ? "" : String.valueOf(replies);
// final String string = itemView.getResources().getQuantityString(R.plurals.replies_count, replies, replies); // final String string = itemView.getResources().getQuantityString(R.plurals.replies_count, replies, replies);

View File

@ -419,7 +419,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
final long locationId = locationModel.getPk(); final long locationId = locationModel.getPk();
// binding.swipeRefreshLayout.setRefreshing(true); // binding.swipeRefreshLayout.setRefreshing(true);
locationDetailsBinding.mainLocationImage.setImageURI("res:/" + R.drawable.ic_location); locationDetailsBinding.mainLocationImage.setImageURI("res:/" + R.drawable.ic_location);
// final String postCount = String.valueOf(locationModel.getCount()); // final String postCount = String.valueOf(locationModel.getChildCommentCount());
// final SpannableStringBuilder span = new SpannableStringBuilder(getResources().getQuantityString(R.plurals.main_posts_count_inline, // final SpannableStringBuilder span = new SpannableStringBuilder(getResources().getQuantityString(R.plurals.main_posts_count_inline,
// locationModel.getPostCount() > 2000000000L // locationModel.getPostCount() > 2000000000L
// ? 2000000000 // ? 2000000000

View File

@ -126,7 +126,7 @@ public final class Helper {
if (navController == null) return; if (navController == null) return;
try { try {
final Bundle bundle = new Bundle(); final Bundle bundle = new Bundle();
bundle.putString("postId", comment.getId()); bundle.putString("postId", comment.getPk());
bundle.putBoolean("isComment", true); bundle.putBoolean("isComment", true);
navController.navigate(R.id.action_global_likesViewerFragment, bundle); navController.navigate(R.id.action_global_likesViewerFragment, bundle);
} catch (Exception e) { } catch (Exception e) {

View File

@ -6,25 +6,24 @@ import java.io.Serializable
import java.util.* import java.util.*
class Comment( class Comment(
val id: String, val pk: String,
val text: String, val text: String,
val timestamp: Long, val createdAt: Long,
var likes: Long, var commentLikeCount: Long,
private var liked: Boolean, private var hasLikedComment: Boolean,
val user: User, val user: User,
val replyCount: Int, val childCommentCount: Int
val isChild: Boolean,
) : Serializable, Cloneable { ) : Serializable, Cloneable {
val dateTime: String val dateTime: String
get() = TextUtils.epochSecondToString(timestamp) get() = TextUtils.epochSecondToString(createdAt)
fun getLiked(): Boolean { fun getLiked(): Boolean {
return liked return hasLikedComment
} }
fun setLiked(liked: Boolean) { fun setLiked(liked: Boolean) {
likes = if (liked) likes + 1 else likes - 1 commentLikeCount = if (hasLikedComment) commentLikeCount + 1 else commentLikeCount - 1
this.liked = liked this.hasLikedComment = hasLikedComment
} }
@Throws(CloneNotSupportedException::class) @Throws(CloneNotSupportedException::class)
@ -39,31 +38,29 @@ class Comment(
other as Comment other as Comment
if (id != other.id) return false if (pk != other.pk) return false
if (text != other.text) return false if (text != other.text) return false
if (timestamp != other.timestamp) return false if (createdAt != other.createdAt) return false
if (likes != other.likes) return false if (commentLikeCount != other.commentLikeCount) return false
if (liked != other.liked) return false if (hasLikedComment != other.hasLikedComment) return false
if (user != other.user) return false if (user != other.user) return false
if (replyCount != other.replyCount) return false if (childCommentCount != other.childCommentCount) return false
if (isChild != other.isChild) return false
return true return true
} }
override fun hashCode(): Int { override fun hashCode(): Int {
var result = id.hashCode() var result = pk.hashCode()
result = 31 * result + text.hashCode() result = 31 * result + text.hashCode()
result = 31 * result + timestamp.hashCode() result = 31 * result + createdAt.hashCode()
result = 31 * result + likes.hashCode() result = 31 * result + commentLikeCount.hashCode()
result = 31 * result + liked.hashCode() result = 31 * result + hasLikedComment.hashCode()
result = 31 * result + user.hashCode() result = 31 * result + user.hashCode()
result = 31 * result + replyCount result = 31 * result + childCommentCount
result = 31 * result + isChild.hashCode()
return result return result
} }
override fun toString(): String { override fun toString(): String {
return "Comment(id='$id', text='$text', timestamp=$timestamp, likes=$likes, liked=$liked, user=$user, replyCount=$replyCount, isChild=$isChild)" return "Comment(pk='$pk', text='$text', createdAt=$createdAt, commentLikeCount=$commentLikeCount, hasLikedComment=$hasLikedComment, user=$user, childCommentCount=$childCommentCount)"
} }
} }

View File

@ -0,0 +1,49 @@
package awais.instagrabber.repositories;
import java.util.Map;
import awais.instagrabber.repositories.responses.CommentsFetchResponse;
import awais.instagrabber.repositories.responses.ChildCommentsFetchResponse;
import retrofit2.Call;
import retrofit2.http.FieldMap;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.POST;
import retrofit2.http.Path;
import retrofit2.http.Query;
import retrofit2.http.QueryMap;
public interface CommentRepository {
@GET("/api/v1/media/{mediaId}/comments/")
Call<CommentsFetchResponse> fetchComments(@Path("mediaId") final String mediaId,
@QueryMap final Map<String, String> queryMap);
@GET("/api/v1/media/{mediaId}/comments/{commentId}/inline_child_comments/")
Call<ChildCommentsFetchResponse> fetchChildComments(@Path("mediaId") final String mediaId,
@Path("commentId") final String commentId,
@QueryMap final Map<String, String> queryMap);
@FormUrlEncoded
@POST("/api/v1/media/{mediaId}/comment/")
Call<String> comment(@Path("mediaId") final String mediaId,
@FieldMap final Map<String, String> signedForm);
@FormUrlEncoded
@POST("/api/v1/media/{mediaId}/comment/bulk_delete/")
Call<String> commentsBulkDelete(@Path("mediaId") final String mediaId,
@FieldMap final Map<String, String> signedForm);
@FormUrlEncoded
@POST("/api/v1/media/{commentId}/comment_like/")
Call<String> commentLike(@Path("commentId") final String commentId,
@FieldMap final Map<String, String> signedForm);
@FormUrlEncoded
@POST("/api/v1/media/{commentId}/comment_unlike/")
Call<String> commentUnlike(@Path("commentId") final String commentId,
@FieldMap final Map<String, String> signedForm);
@GET("/api/v1/language/translate/")
Call<String> translate(@QueryMap final Map<String, String> form);
}

View File

@ -28,26 +28,6 @@ public interface MediaRepository {
@Path("mediaId") final String mediaId, @Path("mediaId") final String mediaId,
@FieldMap final Map<String, String> signedForm); @FieldMap final Map<String, String> signedForm);
@FormUrlEncoded
@POST("/api/v1/media/{mediaId}/comment/")
Call<String> comment(@Path("mediaId") final String mediaId,
@FieldMap final Map<String, String> signedForm);
@FormUrlEncoded
@POST("/api/v1/media/{mediaId}/comment/bulk_delete/")
Call<String> commentsBulkDelete(@Path("mediaId") final String mediaId,
@FieldMap final Map<String, String> signedForm);
@FormUrlEncoded
@POST("/api/v1/media/{commentId}/comment_like/")
Call<String> commentLike(@Path("commentId") final String commentId,
@FieldMap final Map<String, String> signedForm);
@FormUrlEncoded
@POST("/api/v1/media/{commentId}/comment_unlike/")
Call<String> commentUnlike(@Path("commentId") final String commentId,
@FieldMap final Map<String, String> signedForm);
@FormUrlEncoded @FormUrlEncoded
@POST("/api/v1/media/{mediaId}/edit_media/") @POST("/api/v1/media/{mediaId}/edit_media/")
Call<String> editCaption(@Path("mediaId") final String mediaId, Call<String> editCaption(@Path("mediaId") final String mediaId,

View File

@ -0,0 +1,47 @@
package awais.instagrabber.repositories.responses;
import androidx.annotation.NonNull;
import java.util.List;
import awais.instagrabber.models.Comment;
public class ChildCommentsFetchResponse {
private final int childCommentCount;
private final String nextMinId;
private final List<Comment> childComments;
public ChildCommentsFetchResponse(final int childCommentCount,
final String nextMinId, // unconfirmed
final List<Comment> childComments) {
this.childCommentCount = childCommentCount;
this.nextMinId = nextMinId;
this.childComments = childComments;
}
public int getChildCommentCount() {
return childCommentCount;
}
public String getNextMinId() {
return nextMinId;
}
public boolean hasNext() {
return nextMinId != null;
}
public List<Comment> getChildComments() {
return childComments;
}
@NonNull
@Override
public String toString() {
return "CommentsFetchResponse{" +
"childCommentCount=" + childCommentCount +
", nextMinId='" + nextMinId + '\'' +
", childComments=" + childComments +
'}';
}
}

View File

@ -0,0 +1,47 @@
package awais.instagrabber.repositories.responses;
import androidx.annotation.NonNull;
import java.util.List;
import awais.instagrabber.models.Comment;
public class CommentsFetchResponse {
private final int commentCount;
private final String nextMinId;
private final List<Comment> comments;
public CommentsFetchResponse(final int commentCount,
final String nextMinId,
final List<Comment> comments) {
this.commentCount = commentCount;
this.nextMinId = nextMinId;
this.comments = comments;
}
public int getCommentCount() {
return commentCount;
}
public String getNextMinId() {
return nextMinId;
}
public boolean hasNext() {
return nextMinId != null;
}
public List<Comment> getComments() {
return comments;
}
@NonNull
@Override
public String toString() {
return "CommentsFetchResponse{" +
"commentCount=" + commentCount +
", nextMinId='" + nextMinId + '\'' +
", comments=" + comments +
'}';
}
}

View File

@ -1,51 +0,0 @@
package awais.instagrabber.repositories.responses;
import androidx.annotation.NonNull;
import java.util.List;
import awais.instagrabber.models.Comment;
public class GraphQLCommentsFetchResponse {
private final int count;
private final String cursor;
private final boolean hasNext;
private final List<Comment> comments;
public GraphQLCommentsFetchResponse(final int count,
final String cursor,
final boolean hasNext,
final List<Comment> comments) {
this.count = count;
this.cursor = cursor;
this.hasNext = hasNext;
this.comments = comments;
}
public int getCount() {
return count;
}
public String getCursor() {
return cursor;
}
public boolean hasNext() {
return hasNext;
}
public List<Comment> getComments() {
return comments;
}
@NonNull
@Override
public String toString() {
return "GraphQLCommentsFetchResponse{" +
"count=" + count +
", cursor='" + cursor + '\'' +
", hasNext=" + hasNext +
", comments=" + comments +
'}';
}
}

View File

@ -25,14 +25,14 @@ import java.util.stream.IntStream;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.models.Comment; import awais.instagrabber.models.Comment;
import awais.instagrabber.models.Resource; import awais.instagrabber.models.Resource;
import awais.instagrabber.repositories.responses.FriendshipStatus; import awais.instagrabber.repositories.responses.ChildCommentsFetchResponse;
import awais.instagrabber.repositories.responses.GraphQLCommentsFetchResponse; import awais.instagrabber.repositories.responses.CommentsFetchResponse;
import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
import awais.instagrabber.webservices.CommentService;
import awais.instagrabber.webservices.GraphQLService; import awais.instagrabber.webservices.GraphQLService;
import awais.instagrabber.webservices.MediaService;
import awais.instagrabber.webservices.ServiceCallback; import awais.instagrabber.webservices.ServiceCallback;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.Callback; import retrofit2.Callback;
@ -48,7 +48,7 @@ public class CommentsViewerViewModel extends ViewModel {
private final MutableLiveData<Resource<List<Comment>>> rootList = new MutableLiveData<>(); private final MutableLiveData<Resource<List<Comment>>> rootList = new MutableLiveData<>();
private final MutableLiveData<Integer> rootCount = new MutableLiveData<>(0); private final MutableLiveData<Integer> rootCount = new MutableLiveData<>(0);
private final MutableLiveData<Resource<List<Comment>>> replyList = new MutableLiveData<>(); private final MutableLiveData<Resource<List<Comment>>> replyList = new MutableLiveData<>();
private final GraphQLService service; private final GraphQLService graphQLService;
private String shortCode; private String shortCode;
private String postId; private String postId;
@ -57,18 +57,68 @@ public class CommentsViewerViewModel extends ViewModel {
private Comment repliesParent; private Comment repliesParent;
private String repliesCursor; private String repliesCursor;
private boolean repliesHasNext = true; private boolean repliesHasNext = true;
private final MediaService mediaService; private final CommentService commentService;
private List<Comment> prevReplies; private List<Comment> prevReplies;
private String prevRepliesCursor; private String prevRepliesCursor;
private boolean prevRepliesHasNext = true; private boolean prevRepliesHasNext = true;
private final ServiceCallback<CommentsFetchResponse> ccb = new ServiceCallback<CommentsFetchResponse>() {
@Override
public void onSuccess(final CommentsFetchResponse result) {
// Log.d(TAG, "onSuccess: " + result);
List<Comment> comments = result.getComments();
if (rootCursor == null) {
rootCount.postValue(result.getCommentCount());
}
if (rootCursor != null) {
comments = mergeList(rootList, comments);
}
rootCursor = result.getNextMinId();
rootHasNext = result.hasNext();
rootList.postValue(Resource.success(comments));
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "onFailure: ", t);
rootList.postValue(Resource.error(t.getMessage(), getPrevList(rootList)));
}
};
private final ServiceCallback<ChildCommentsFetchResponse> rcb = new ServiceCallback<ChildCommentsFetchResponse>() {
@Override
public void onSuccess(final ChildCommentsFetchResponse result) {
// Log.d(TAG, "onSuccess: " + result);
List<Comment> comments = result.getChildComments();
// Replies
if (repliesCursor == null) {
// add parent to top of replies
comments = ImmutableList.<Comment>builder()
.add(repliesParent)
.addAll(comments)
.build();
}
if (repliesCursor != null) {
comments = mergeList(replyList, comments);
}
repliesCursor = result.getNextMinId();
repliesHasNext = result.hasNext();
replyList.postValue(Resource.success(comments));
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "onFailure: ", t);
replyList.postValue(Resource.error(t.getMessage(), getPrevList(replyList)));
}
};
public CommentsViewerViewModel() { public CommentsViewerViewModel() {
service = GraphQLService.getInstance(); graphQLService = GraphQLService.getInstance();
final String cookie = settingsHelper.getString(Constants.COOKIE); final String cookie = settingsHelper.getString(Constants.COOKIE);
final String deviceUuid = Utils.settingsHelper.getString(Constants.DEVICE_UUID); final String deviceUuid = Utils.settingsHelper.getString(Constants.DEVICE_UUID);
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
final long userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); final long userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie);
mediaService = MediaService.getInstance(deviceUuid, csrfToken, userIdFromCookie); commentService = CommentService.getInstance(deviceUuid, csrfToken, userIdFromCookie);
} }
public void setCurrentUser(final User currentUser) { public void setCurrentUser(final User currentUser) {
@ -108,87 +158,45 @@ public class CommentsViewerViewModel extends ViewModel {
} }
public void fetchComments() { public void fetchComments() {
if (shortCode == null) return; if (shortCode == null || postId == null) return;
fetchComments(shortCode, true); if (!rootHasNext) return;
rootList.postValue(Resource.loading(getPrevList(rootList)));
if (isLoggedIn.getValue()) {
commentService.fetchComments(postId, rootCursor, ccb);
return;
}
final Call<String> request = graphQLService.fetchComments(shortCode, true, rootCursor);
enqueueRequest(request, true, shortCode, ccb);
} }
public void fetchReplies() { public void fetchReplies() {
if (repliesParent == null) return; if (repliesParent == null) return;
fetchReplies(repliesParent.getId()); fetchReplies(repliesParent.getPk());
} }
public void fetchReplies(@NonNull final String commentId) { public void fetchReplies(@NonNull final String commentId) {
fetchComments(commentId, false); if (!repliesHasNext) return;
} final List<Comment> list;
if (repliesParent != null && !Objects.equals(repliesParent.getPk(), commentId)) {
public void fetchComments(@NonNull final String shortCodeOrCommentId, repliesCursor = null;
final boolean root) { repliesHasNext = false;
if (root) { list = Collections.emptyList();
if (!rootHasNext) return;
rootList.postValue(Resource.loading(getPrevList(rootList)));
} else { } else {
if (!repliesHasNext) return; list = getPrevList(replyList);
final List<Comment> list;
if (repliesParent != null && !Objects.equals(repliesParent.getId(), shortCodeOrCommentId)) {
repliesCursor = null;
repliesHasNext = false;
list = Collections.emptyList();
} else {
list = getPrevList(replyList);
}
replyList.postValue(Resource.loading(list));
} }
final Call<String> request = service.fetchComments(shortCodeOrCommentId, root, root ? rootCursor : repliesCursor); replyList.postValue(Resource.loading(list));
enqueueRequest(request, root, shortCodeOrCommentId, new ServiceCallback<GraphQLCommentsFetchResponse>() { if (isLoggedIn.getValue()) {
@Override commentService.fetchChildComments(postId, commentId, rootCursor, rcb);
public void onSuccess(final GraphQLCommentsFetchResponse result) { return;
// Log.d(TAG, "onSuccess: " + result); }
List<Comment> comments = result.getComments(); final Call<String> request = graphQLService.fetchComments(commentId, false, repliesCursor);
if (root) { enqueueRequest(request, false, commentId, rcb);
if (rootCursor == null) {
rootCount.postValue(result.getCount());
}
if (rootCursor != null) {
comments = mergeList(rootList, comments);
}
rootCursor = result.getCursor();
rootHasNext = result.hasNext();
rootList.postValue(Resource.success(comments));
return;
}
// Replies
if (repliesCursor == null) {
// add parent to top of replies
comments = ImmutableList.<Comment>builder()
.add(repliesParent)
.addAll(comments)
.build();
}
if (repliesCursor != null) {
comments = mergeList(replyList, comments);
}
repliesCursor = result.getCursor();
repliesHasNext = result.hasNext();
replyList.postValue(Resource.success(comments));
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "onFailure: ", t);
if (root) {
rootList.postValue(Resource.error(t.getMessage(), getPrevList(rootList)));
return;
}
replyList.postValue(Resource.error(t.getMessage(), getPrevList(replyList)));
}
});
} }
private void enqueueRequest(@NonNull final Call<String> request, private void enqueueRequest(@NonNull final Call<String> request,
final boolean root, final boolean root,
final String shortCodeOrCommentId, final String shortCodeOrCommentId,
final ServiceCallback<GraphQLCommentsFetchResponse> callback) { final ServiceCallback callback) {
request.enqueue(new Callback<String>() { request.enqueue(new Callback<String>() {
@Override @Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) { public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
@ -208,14 +216,16 @@ public class CommentsViewerViewModel extends ViewModel {
final int count = body.optInt("count"); final int count = body.optInt("count");
final JSONObject pageInfo = body.getJSONObject("page_info"); final JSONObject pageInfo = body.getJSONObject("page_info");
final boolean hasNextPage = pageInfo.getBoolean("has_next_page"); final boolean hasNextPage = pageInfo.getBoolean("has_next_page");
final String endCursor = pageInfo.isNull("end_cursor") ? null : pageInfo.optString("end_cursor"); final String endCursor = pageInfo.isNull("end_cursor") || !hasNextPage ? null : pageInfo.optString("end_cursor");
final JSONArray commentsJsonArray = body.getJSONArray("edges"); final JSONArray commentsJsonArray = body.getJSONArray("edges");
final ImmutableList.Builder<Comment> builder = ImmutableList.builder(); final ImmutableList.Builder<Comment> builder = ImmutableList.builder();
for (int i = 0; i < commentsJsonArray.length(); i++) { for (int i = 0; i < commentsJsonArray.length(); i++) {
final Comment commentModel = getComment(commentsJsonArray.getJSONObject(i).getJSONObject("node"), root); final Comment commentModel = getComment(commentsJsonArray.getJSONObject(i).getJSONObject("node"), root);
builder.add(commentModel); builder.add(commentModel);
} }
callback.onSuccess(new GraphQLCommentsFetchResponse(count, endCursor, hasNextPage, builder.build())); callback.onSuccess(root ?
new CommentsFetchResponse(count, endCursor, builder.build()) :
new ChildCommentsFetchResponse(count, endCursor, builder.build()));
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "onResponse", e); Log.e(TAG, "onResponse", e);
callback.onFailure(e); callback.onFailure(e);
@ -252,8 +262,7 @@ public class CommentsViewerViewModel extends ViewModel {
likedBy != null ? likedBy.optLong("count", 0) : 0, likedBy != null ? likedBy.optLong("count", 0) : 0,
commentJsonObject.getBoolean("viewer_has_liked"), commentJsonObject.getBoolean("viewer_has_liked"),
user, user,
replyCount, replyCount);
!root);
} }
@NonNull @NonNull
@ -278,12 +287,12 @@ public class CommentsViewerViewModel extends ViewModel {
public void showReplies(final Comment comment) { public void showReplies(final Comment comment) {
if (comment == null) return; if (comment == null) return;
if (repliesParent == null || !Objects.equals(repliesParent.getId(), comment.getId())) { if (repliesParent == null || !Objects.equals(repliesParent.getPk(), comment.getPk())) {
repliesParent = comment; repliesParent = comment;
prevReplies = null; prevReplies = null;
prevRepliesCursor = null; prevRepliesCursor = null;
prevRepliesHasNext = true; prevRepliesHasNext = true;
fetchReplies(comment.getId()); fetchReplies(comment.getPk());
return; return;
} }
if (prevReplies != null && !prevReplies.isEmpty()) { if (prevReplies != null && !prevReplies.isEmpty()) {
@ -296,7 +305,7 @@ public class CommentsViewerViewModel extends ViewModel {
// prev list was null or empty, fetch // prev list was null or empty, fetch
prevRepliesCursor = null; prevRepliesCursor = null;
prevRepliesHasNext = true; prevRepliesHasNext = true;
fetchReplies(comment.getId()); fetchReplies(comment.getPk());
} }
public LiveData<Resource<Object>> likeComment(@NonNull final Comment comment, final boolean liked, final boolean isReply) { public LiveData<Resource<Object>> likeComment(@NonNull final Comment comment, final boolean liked, final boolean isReply) {
@ -319,9 +328,9 @@ public class CommentsViewerViewModel extends ViewModel {
} }
}; };
if (liked) { if (liked) {
mediaService.commentLike(comment.getId(), callback); commentService.commentLike(comment.getPk(), callback);
} else { } else {
mediaService.commentUnlike(comment.getId(), callback); commentService.commentUnlike(comment.getPk(), callback);
} }
return data; return data;
} }
@ -333,7 +342,7 @@ public class CommentsViewerViewModel extends ViewModel {
if (list == null) return; if (list == null) return;
final List<Comment> copy = new ArrayList<>(list); final List<Comment> copy = new ArrayList<>(list);
OptionalInt indexOpt = IntStream.range(0, copy.size()) OptionalInt indexOpt = IntStream.range(0, copy.size())
.filter(i -> copy.get(i) != null && Objects.equals(copy.get(i).getId(), comment.getId())) .filter(i -> copy.get(i) != null && Objects.equals(copy.get(i).getPk(), comment.getPk()))
.findFirst(); .findFirst();
if (!indexOpt.isPresent()) return; if (!indexOpt.isPresent()) return;
try { try {
@ -352,13 +361,13 @@ public class CommentsViewerViewModel extends ViewModel {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(Resource.loading(null)); final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(Resource.loading(null));
String replyToId = null; String replyToId = null;
if (isReply && repliesParent != null) { if (isReply && repliesParent != null) {
replyToId = repliesParent.getId(); replyToId = repliesParent.getPk();
} }
if (isReply && replyToId == null) { if (isReply && replyToId == null) {
data.postValue(Resource.error(null, null)); data.postValue(Resource.error(null, null));
return data; return data;
} }
mediaService.comment(postId, text, replyToId, new ServiceCallback<Comment>() { commentService.comment(postId, text, replyToId, new ServiceCallback<Comment>() {
@Override @Override
public void onSuccess(final Comment result) { public void onSuccess(final Comment result) {
if (result == null) { if (result == null) {
@ -396,12 +405,12 @@ public class CommentsViewerViewModel extends ViewModel {
public void translate(@NonNull final Comment comment, public void translate(@NonNull final Comment comment,
@NonNull final ServiceCallback<String> callback) { @NonNull final ServiceCallback<String> callback) {
mediaService.translate(comment.getId(), "2", callback); commentService.translate(comment.getPk(), callback);
} }
public LiveData<Resource<Object>> deleteComment(@NonNull final Comment comment, final boolean isReply) { public LiveData<Resource<Object>> deleteComment(@NonNull final Comment comment, final boolean isReply) {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(Resource.loading(null)); final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(Resource.loading(null));
mediaService.deleteComment(postId, comment.getId(), new ServiceCallback<Boolean>() { commentService.deleteComment(postId, comment.getPk(), new ServiceCallback<Boolean>() {
@Override @Override
public void onSuccess(final Boolean result) { public void onSuccess(final Boolean result) {
if (result == null || !result) { if (result == null || !result) {
@ -425,7 +434,7 @@ public class CommentsViewerViewModel extends ViewModel {
final List<Comment> list = getPrevList(isReply ? replyList : rootList); final List<Comment> list = getPrevList(isReply ? replyList : rootList);
final List<Comment> updated = list.stream() final List<Comment> updated = list.stream()
.filter(Objects::nonNull) .filter(Objects::nonNull)
.filter(c -> !Objects.equals(c.getId(), comment.getId())) .filter(c -> !Objects.equals(c.getPk(), comment.getPk()))
.collect(Collectors.toList()); .collect(Collectors.toList());
final MutableLiveData<Resource<List<Comment>>> liveData = isReply ? replyList : rootList; final MutableLiveData<Resource<List<Comment>>> liveData = isReply ? replyList : rootList;
liveData.postValue(Resource.success(updated)); liveData.postValue(Resource.success(updated));

View File

@ -0,0 +1,324 @@
package awais.instagrabber.webservices;
import android.util.Log;
import androidx.annotation.NonNull;
import com.google.gson.Gson;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import awais.instagrabber.models.Comment;
import awais.instagrabber.repositories.CommentRepository;
import awais.instagrabber.repositories.responses.ChildCommentsFetchResponse;
import awais.instagrabber.repositories.responses.CommentsFetchResponse;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class CommentService extends BaseService {
private static final String TAG = "CommentService";
private final CommentRepository repository;
private final String deviceUuid, csrfToken;
private final long userId;
private static CommentService instance;
private CommentService(final String deviceUuid,
final String csrfToken,
final long userId) {
this.deviceUuid = deviceUuid;
this.csrfToken = csrfToken;
this.userId = userId;
repository = RetrofitFactory.INSTANCE
.getRetrofit()
.create(CommentRepository.class);
}
public String getCsrfToken() {
return csrfToken;
}
public String getDeviceUuid() {
return deviceUuid;
}
public long getUserId() {
return userId;
}
public static CommentService getInstance(final String deviceUuid, final String csrfToken, final long userId) {
if (instance == null
|| !Objects.equals(instance.getCsrfToken(), csrfToken)
|| !Objects.equals(instance.getDeviceUuid(), deviceUuid)
|| !Objects.equals(instance.getUserId(), userId)) {
instance = new CommentService(deviceUuid, csrfToken, userId);
}
return instance;
}
public void fetchComments(@NonNull final String mediaId,
final String maxId,
@NonNull final ServiceCallback<CommentsFetchResponse> callback) {
final Map<String, String> form = new HashMap<>();
form.put("can_support_threading", "true");
if (maxId != null) form.put("max_id", maxId);
final Call<CommentsFetchResponse> request = repository.fetchComments(mediaId, form);
request.enqueue(new Callback<CommentsFetchResponse>() {
@Override
public void onResponse(@NonNull final Call<CommentsFetchResponse> call, @NonNull final Response<CommentsFetchResponse> response) {
if (callback == null) return;
final CommentsFetchResponse cfr = response.body();
if (cfr == null) callback.onFailure(new Exception("response is empty"));
callback.onSuccess(cfr);
}
@Override
public void onFailure(@NonNull final Call<CommentsFetchResponse> call, @NonNull final Throwable t) {
callback.onFailure(t);
}
});
}
public void fetchChildComments(@NonNull final String mediaId,
@NonNull final String commentId,
final String maxId,
@NonNull final ServiceCallback<ChildCommentsFetchResponse> callback) {
final Map<String, String> form = new HashMap<>();
if (maxId != null) form.put("max_id", maxId);
final Call<ChildCommentsFetchResponse> request = repository.fetchChildComments(mediaId, commentId, form);
request.enqueue(new Callback<ChildCommentsFetchResponse>() {
@Override
public void onResponse(@NonNull final Call<ChildCommentsFetchResponse> call, @NonNull final Response<ChildCommentsFetchResponse> response) {
if (callback == null) return;
final ChildCommentsFetchResponse cfr = response.body();
if (cfr == null) callback.onFailure(new Exception("response is empty"));
callback.onSuccess(cfr);
}
@Override
public void onFailure(@NonNull final Call<ChildCommentsFetchResponse> call, @NonNull final Throwable t) {
callback.onFailure(t);
}
});
}
public void comment(@NonNull final String mediaId,
@NonNull final String comment,
final String replyToCommentId,
@NonNull final ServiceCallback<Comment> callback) {
final String module = "self_comments_v2";
final Map<String, Object> form = new HashMap<>();
// form.put("user_breadcrumb", userBreadcrumb(comment.length()));
form.put("idempotence_token", UUID.randomUUID().toString());
form.put("_csrftoken", csrfToken);
form.put("_uid", userId);
form.put("_uuid", deviceUuid);
form.put("comment_text", comment);
form.put("containermodule", module);
if (!TextUtils.isEmpty(replyToCommentId)) {
form.put("replied_to_comment_id", replyToCommentId);
}
final Map<String, String> signedForm = Utils.sign(form);
final Call<String> commentRequest = repository.comment(mediaId, signedForm);
commentRequest.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String body = response.body();
if (body == null) {
Log.e(TAG, "Error occurred while creating comment");
callback.onSuccess(null);
return;
}
try {
final JSONObject jsonObject = new JSONObject(body);
// final String status = jsonObject.optString("status");
final JSONObject commentJsonObject = jsonObject.optJSONObject("comment");
Comment comment = null;
if (commentJsonObject != null) {
final JSONObject userJsonObject = commentJsonObject.optJSONObject("user");
if (userJsonObject != null) {
final Gson gson = new Gson();
final User user = gson.fromJson(userJsonObject.toString(), User.class);
comment = new Comment(
commentJsonObject.optString("pk"),
commentJsonObject.optString("text"),
commentJsonObject.optLong("created_at"),
0L,
false,
user,
0
);
}
}
callback.onSuccess(comment);
} catch (Exception e) {
callback.onFailure(e);
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
callback.onFailure(t);
}
});
}
public void deleteComment(final String mediaId,
final String commentId,
@NonNull final ServiceCallback<Boolean> callback) {
deleteComments(mediaId, Collections.singletonList(commentId), callback);
}
public void deleteComments(final String mediaId,
final List<String> commentIds,
@NonNull final ServiceCallback<Boolean> callback) {
final Map<String, Object> form = new HashMap<>();
form.put("comment_ids_to_delete", android.text.TextUtils.join(",", commentIds));
form.put("_csrftoken", csrfToken);
form.put("_uid", userId);
form.put("_uuid", deviceUuid);
final Map<String, String> signedForm = Utils.sign(form);
final Call<String> bulkDeleteRequest = repository.commentsBulkDelete(mediaId, signedForm);
bulkDeleteRequest.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String body = response.body();
if (body == null) {
Log.e(TAG, "Error occurred while deleting comments");
callback.onSuccess(false);
return;
}
try {
final JSONObject jsonObject = new JSONObject(body);
final String status = jsonObject.optString("status");
callback.onSuccess(status.equals("ok"));
} catch (JSONException e) {
// Log.e(TAG, "Error parsing body", e);
callback.onFailure(e);
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
// Log.e(TAG, "Error deleting comments", t);
callback.onFailure(t);
}
});
}
public void commentLike(@NonNull final String commentId,
@NonNull final ServiceCallback<Boolean> callback) {
final Map<String, Object> form = new HashMap<>();
form.put("_csrftoken", csrfToken);
// form.put("_uid", userId);
// form.put("_uuid", deviceUuid);
final Map<String, String> signedForm = Utils.sign(form);
final Call<String> commentLikeRequest = repository.commentLike(commentId, signedForm);
commentLikeRequest.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String body = response.body();
if (body == null) {
Log.e(TAG, "Error occurred while liking comment");
callback.onSuccess(false);
return;
}
try {
final JSONObject jsonObject = new JSONObject(body);
final String status = jsonObject.optString("status");
callback.onSuccess(status.equals("ok"));
} catch (JSONException e) {
// Log.e(TAG, "Error parsing body", e);
callback.onFailure(e);
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
Log.e(TAG, "Error liking comment", t);
callback.onFailure(t);
}
});
}
public void commentUnlike(final String commentId,
@NonNull final ServiceCallback<Boolean> callback) {
final Map<String, Object> form = new HashMap<>();
form.put("_csrftoken", csrfToken);
// form.put("_uid", userId);
// form.put("_uuid", deviceUuid);
final Map<String, String> signedForm = Utils.sign(form);
final Call<String> commentUnlikeRequest = repository.commentUnlike(commentId, signedForm);
commentUnlikeRequest.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String body = response.body();
if (body == null) {
Log.e(TAG, "Error occurred while unliking comment");
callback.onSuccess(false);
return;
}
try {
final JSONObject jsonObject = new JSONObject(body);
final String status = jsonObject.optString("status");
callback.onSuccess(status.equals("ok"));
} catch (JSONException e) {
// Log.e(TAG, "Error parsing body", e);
callback.onFailure(e);
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
Log.e(TAG, "Error unliking comment", t);
callback.onFailure(t);
}
});
}
public void translate(final String id,
@NonNull final ServiceCallback<String> callback) {
final Map<String, String> form = new HashMap<>();
form.put("id", String.valueOf(id));
form.put("type", "2");
final Call<String> request = repository.translate(form);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String body = response.body();
if (body == null) {
Log.e(TAG, "Error occurred while translating");
callback.onSuccess(null);
return;
}
try {
final JSONObject jsonObject = new JSONObject(body);
final String translation = jsonObject.optString("translation");
callback.onSuccess(translation);
} catch (JSONException e) {
// Log.e(TAG, "Error parsing body", e);
callback.onFailure(e);
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
Log.e(TAG, "Error translating", t);
callback.onFailure(t);
}
});
}
}

View File

@ -171,181 +171,6 @@ public class MediaService extends BaseService {
}); });
} }
public void comment(@NonNull final String mediaId,
@NonNull final String comment,
final String replyToCommentId,
@NonNull final ServiceCallback<Comment> callback) {
final String module = "self_comments_v2";
final Map<String, Object> form = new HashMap<>();
// form.put("user_breadcrumb", userBreadcrumb(comment.length()));
form.put("idempotence_token", UUID.randomUUID().toString());
form.put("_csrftoken", csrfToken);
form.put("_uid", userId);
form.put("_uuid", deviceUuid);
form.put("comment_text", comment);
form.put("containermodule", module);
if (!TextUtils.isEmpty(replyToCommentId)) {
form.put("replied_to_comment_id", replyToCommentId);
}
final Map<String, String> signedForm = Utils.sign(form);
final Call<String> commentRequest = repository.comment(mediaId, signedForm);
commentRequest.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String body = response.body();
if (body == null) {
Log.e(TAG, "Error occurred while creating comment");
callback.onSuccess(null);
return;
}
try {
final JSONObject jsonObject = new JSONObject(body);
// final String status = jsonObject.optString("status");
final JSONObject commentJsonObject = jsonObject.optJSONObject("comment");
Comment comment = null;
if (commentJsonObject != null) {
final JSONObject userJsonObject = commentJsonObject.optJSONObject("user");
if (userJsonObject != null) {
final Gson gson = new Gson();
final User user = gson.fromJson(userJsonObject.toString(), User.class);
comment = new Comment(
commentJsonObject.optString("pk"),
commentJsonObject.optString("text"),
commentJsonObject.optLong("created_at"),
0,
false,
user,
0,
!TextUtils.isEmpty(replyToCommentId)
);
}
}
callback.onSuccess(comment);
} catch (Exception e) {
callback.onFailure(e);
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
callback.onFailure(t);
}
});
}
public void deleteComment(final String mediaId,
final String commentId,
@NonNull final ServiceCallback<Boolean> callback) {
deleteComments(mediaId, Collections.singletonList(commentId), callback);
}
public void deleteComments(final String mediaId,
final List<String> commentIds,
@NonNull final ServiceCallback<Boolean> callback) {
final Map<String, Object> form = new HashMap<>();
form.put("comment_ids_to_delete", android.text.TextUtils.join(",", commentIds));
form.put("_csrftoken", csrfToken);
form.put("_uid", userId);
form.put("_uuid", deviceUuid);
final Map<String, String> signedForm = Utils.sign(form);
final Call<String> bulkDeleteRequest = repository.commentsBulkDelete(mediaId, signedForm);
bulkDeleteRequest.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String body = response.body();
if (body == null) {
Log.e(TAG, "Error occurred while deleting comments");
callback.onSuccess(false);
return;
}
try {
final JSONObject jsonObject = new JSONObject(body);
final String status = jsonObject.optString("status");
callback.onSuccess(status.equals("ok"));
} catch (JSONException e) {
// Log.e(TAG, "Error parsing body", e);
callback.onFailure(e);
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
// Log.e(TAG, "Error deleting comments", t);
callback.onFailure(t);
}
});
}
public void commentLike(@NonNull final String commentId,
@NonNull final ServiceCallback<Boolean> callback) {
final Map<String, Object> form = new HashMap<>();
form.put("_csrftoken", csrfToken);
// form.put("_uid", userId);
// form.put("_uuid", deviceUuid);
final Map<String, String> signedForm = Utils.sign(form);
final Call<String> commentLikeRequest = repository.commentLike(commentId, signedForm);
commentLikeRequest.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String body = response.body();
if (body == null) {
Log.e(TAG, "Error occurred while liking comment");
callback.onSuccess(false);
return;
}
try {
final JSONObject jsonObject = new JSONObject(body);
final String status = jsonObject.optString("status");
callback.onSuccess(status.equals("ok"));
} catch (JSONException e) {
// Log.e(TAG, "Error parsing body", e);
callback.onFailure(e);
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
Log.e(TAG, "Error liking comment", t);
callback.onFailure(t);
}
});
}
public void commentUnlike(final String commentId,
@NonNull final ServiceCallback<Boolean> callback) {
final Map<String, Object> form = new HashMap<>();
form.put("_csrftoken", csrfToken);
// form.put("_uid", userId);
// form.put("_uuid", deviceUuid);
final Map<String, String> signedForm = Utils.sign(form);
final Call<String> commentUnlikeRequest = repository.commentUnlike(commentId, signedForm);
commentUnlikeRequest.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String body = response.body();
if (body == null) {
Log.e(TAG, "Error occurred while unliking comment");
callback.onSuccess(false);
return;
}
try {
final JSONObject jsonObject = new JSONObject(body);
final String status = jsonObject.optString("status");
callback.onSuccess(status.equals("ok"));
} catch (JSONException e) {
// Log.e(TAG, "Error parsing body", e);
callback.onFailure(e);
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
Log.e(TAG, "Error unliking comment", t);
callback.onFailure(t);
}
});
}
public void editCaption(final String postId, public void editCaption(final String postId,
final String newCaption, final String newCaption,
@NonNull final ServiceCallback<Boolean> callback) { @NonNull final ServiceCallback<Boolean> callback) {