1
0
mirror of https://github.com/KokaKiwi/BarInsta synced 2024-11-15 19:27:31 +00:00

Convert GraphQLRepository and GraphQLService to kotlin

This commit is contained in:
Ammar Githam 2021-06-06 13:14:29 +09:00
parent dd3562116b
commit 143a0ce259
12 changed files with 449 additions and 600 deletions

View File

@ -49,7 +49,6 @@ import awais.instagrabber.models.IntentModel
import awais.instagrabber.models.Resource
import awais.instagrabber.models.Tab
import awais.instagrabber.models.enums.IntentModelType
import awais.instagrabber.repositories.responses.Media
import awais.instagrabber.services.ActivityCheckerService
import awais.instagrabber.services.DMSyncAlarmReceiver
import awais.instagrabber.utils.*
@ -61,7 +60,6 @@ import awais.instagrabber.viewmodels.AppStateViewModel
import awais.instagrabber.viewmodels.DirectInboxViewModel
import awais.instagrabber.webservices.GraphQLService
import awais.instagrabber.webservices.MediaService
import awais.instagrabber.webservices.ServiceCallback
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.AppBarLayout.ScrollingViewBehavior
import com.google.android.material.appbar.CollapsingToolbarLayout
@ -71,6 +69,7 @@ import com.google.common.collect.ImmutableList
import com.google.common.collect.Iterators
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.*
import java.util.stream.Collectors
@ -92,7 +91,6 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL
var currentTabs: List<Tab> = emptyList()
private set
private var showBottomViewDestinations: List<Int> = emptyList()
private var graphQLService: GraphQLService? = null
private val serviceConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
@ -633,39 +631,32 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL
.setCancelable(false)
.setView(R.layout.dialog_opening_post)
.create()
if (graphQLService == null) graphQLService = GraphQLService.getInstance()
val postCb: ServiceCallback<Media> = object : ServiceCallback<Media> {
override fun onSuccess(feedModel: Media?) {
if (feedModel != null) {
val currentNavControllerLiveData = currentNavControllerLiveData ?: return
alertDialog.show()
lifecycleScope.launch(Dispatchers.IO) {
try {
val media = if (isLoggedIn) MediaService.fetch(shortcodeToId(shortCode)) else GraphQLService.fetchPost(shortCode)
withContext(Dispatchers.Main) {
if (media == null) {
Toast.makeText(applicationContext, R.string.post_not_found, Toast.LENGTH_SHORT).show()
return@withContext
}
val currentNavControllerLiveData = currentNavControllerLiveData ?: return@withContext
val navController = currentNavControllerLiveData.value
val bundle = Bundle()
bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, feedModel)
bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, media)
try {
navController?.navigate(R.id.action_global_post_view, bundle)
} catch (e: Exception) {
Log.e(TAG, "showPostView: ", e)
}
} else Toast.makeText(applicationContext, R.string.post_not_found, Toast.LENGTH_SHORT).show()
alertDialog.dismiss()
}
override fun onFailure(t: Throwable) {
alertDialog.dismiss()
}
}
alertDialog.show()
if (isLoggedIn) {
lifecycleScope.launch(Dispatchers.IO) {
try {
val media = MediaService.fetch(shortcodeToId(shortCode))
postCb.onSuccess(media)
} catch (e: Exception) {
postCb.onFailure(e)
Log.e(TAG, "showPostView: ", e)
} finally {
withContext(Dispatchers.Main) {
alertDialog.dismiss()
}
}
} else {
graphQLService?.fetchPost(shortCode, postCb)
}
}

View File

@ -7,9 +7,11 @@ import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.repositories.responses.Hashtag;
import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.utils.CoroutineUtilsKt;
import awais.instagrabber.webservices.GraphQLService;
import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.TagsService;
import kotlinx.coroutines.Dispatchers;
public class HashtagPostFetchService implements PostFetcher.PostFetchService {
private final TagsService tagsService;
@ -23,7 +25,7 @@ public class HashtagPostFetchService implements PostFetcher.PostFetchService {
this.hashtagModel = hashtagModel;
this.isLoggedIn = isLoggedIn;
tagsService = isLoggedIn ? TagsService.getInstance() : null;
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
graphQLService = isLoggedIn ? null : GraphQLService.INSTANCE;
}
@Override
@ -48,7 +50,17 @@ public class HashtagPostFetchService implements PostFetcher.PostFetchService {
}
};
if (isLoggedIn) tagsService.fetchPosts(hashtagModel.getName().toLowerCase(), nextMaxId, cb);
else graphQLService.fetchHashtagPosts(hashtagModel.getName().toLowerCase(), nextMaxId, cb);
else graphQLService.fetchHashtagPosts(
hashtagModel.getName().toLowerCase(),
nextMaxId,
CoroutineUtilsKt.getContinuation((postsFetchResponse, throwable) -> {
if (throwable != null) {
cb.onFailure(throwable);
return;
}
cb.onSuccess(postsFetchResponse);
}, Dispatchers.getIO())
);
}
@Override

View File

@ -7,9 +7,11 @@ import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.repositories.responses.Location;
import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.utils.CoroutineUtilsKt;
import awais.instagrabber.webservices.GraphQLService;
import awais.instagrabber.webservices.LocationService;
import awais.instagrabber.webservices.ServiceCallback;
import kotlinx.coroutines.Dispatchers;
public class LocationPostFetchService implements PostFetcher.PostFetchService {
private final LocationService locationService;
@ -23,7 +25,7 @@ public class LocationPostFetchService implements PostFetcher.PostFetchService {
this.locationModel = locationModel;
this.isLoggedIn = isLoggedIn;
locationService = isLoggedIn ? LocationService.getInstance() : null;
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
graphQLService = isLoggedIn ? null : GraphQLService.INSTANCE;
}
@Override
@ -48,7 +50,17 @@ public class LocationPostFetchService implements PostFetcher.PostFetchService {
}
};
if (isLoggedIn) locationService.fetchPosts(locationModel.getPk(), nextMaxId, cb);
else graphQLService.fetchLocationPosts(locationModel.getPk(), nextMaxId, cb);
else graphQLService.fetchLocationPosts(
locationModel.getPk(),
nextMaxId,
CoroutineUtilsKt.getContinuation((postsFetchResponse, throwable) -> {
if (throwable != null) {
cb.onFailure(throwable);
return;
}
cb.onSuccess(postsFetchResponse);
}, Dispatchers.getIO())
);
}
@Override

View File

@ -7,9 +7,11 @@ import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.utils.CoroutineUtilsKt;
import awais.instagrabber.webservices.GraphQLService;
import awais.instagrabber.webservices.ProfileService;
import awais.instagrabber.webservices.ServiceCallback;
import kotlinx.coroutines.Dispatchers;
public class ProfilePostFetchService implements PostFetcher.PostFetchService {
private static final String TAG = "ProfilePostFetchService";
@ -23,7 +25,7 @@ public class ProfilePostFetchService implements PostFetcher.PostFetchService {
public ProfilePostFetchService(final User profileModel, final boolean isLoggedIn) {
this.profileModel = profileModel;
this.isLoggedIn = isLoggedIn;
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
graphQLService = isLoggedIn ? null : GraphQLService.INSTANCE;
profileService = isLoggedIn ? ProfileService.getInstance() : null;
}
@ -49,7 +51,19 @@ public class ProfilePostFetchService implements PostFetcher.PostFetchService {
}
};
if (isLoggedIn) profileService.fetchPosts(profileModel.getPk(), nextMaxId, cb);
else graphQLService.fetchProfilePosts(profileModel.getPk(), 30, nextMaxId, profileModel, cb);
else graphQLService.fetchProfilePosts(
profileModel.getPk(),
30,
nextMaxId,
profileModel,
CoroutineUtilsKt.getContinuation((postsFetchResponse, throwable) -> {
if (throwable != null) {
cb.onFailure(throwable);
return;
}
cb.onSuccess(postsFetchResponse);
}, Dispatchers.getIO())
);
}
@Override

View File

@ -7,9 +7,11 @@ import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.utils.CoroutineUtilsKt;
import awais.instagrabber.webservices.GraphQLService;
import awais.instagrabber.webservices.ProfileService;
import awais.instagrabber.webservices.ServiceCallback;
import kotlinx.coroutines.Dispatchers;
public class SavedPostFetchService implements PostFetcher.PostFetchService {
private final ProfileService profileService;
@ -27,7 +29,7 @@ public class SavedPostFetchService implements PostFetcher.PostFetchService {
this.type = type;
this.isLoggedIn = isLoggedIn;
this.collectionId = collectionId;
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
graphQLService = isLoggedIn ? null : GraphQLService.INSTANCE;
profileService = isLoggedIn ? ProfileService.getInstance() : null;
}
@ -58,7 +60,18 @@ public class SavedPostFetchService implements PostFetcher.PostFetchService {
break;
case TAGGED:
if (isLoggedIn) profileService.fetchTagged(profileId, nextMaxId, callback);
else graphQLService.fetchTaggedPosts(profileId, 30, nextMaxId, callback);
else graphQLService.fetchTaggedPosts(
profileId,
30,
nextMaxId,
CoroutineUtilsKt.getContinuation((postsFetchResponse, throwable) -> {
if (throwable != null) {
callback.onFailure(throwable);
return;
}
callback.onSuccess(postsFetchResponse);
}, Dispatchers.getIO())
);
break;
case COLLECTION:
case SAVED:

View File

@ -63,8 +63,10 @@ import awais.instagrabber.repositories.responses.Hashtag;
import awais.instagrabber.repositories.responses.Location;
import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.utils.AppExecutors;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.CoroutineUtilsKt;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
@ -72,6 +74,7 @@ import awais.instagrabber.webservices.GraphQLService;
import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.StoriesService;
import awais.instagrabber.webservices.TagsService;
import kotlinx.coroutines.Dispatchers;
import static androidx.core.content.PermissionChecker.checkSelfPermission;
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
@ -218,20 +221,15 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
if (TextUtils.isEmpty(user.getUsername())) {
// this only happens for anons
opening = true;
graphQLService.fetchPost(feedModel.getCode(), new ServiceCallback<Media>() {
@Override
public void onSuccess(final Media newFeedModel) {
graphQLService.fetchPost(feedModel.getCode(), CoroutineUtilsKt.getContinuation((media, throwable) -> {
opening = false;
if (newFeedModel == null) return;
openPostDialog(newFeedModel, profilePicView, mainPostImage, position);
if (throwable != null) {
Log.e(TAG, "Error", throwable);
return;
}
@Override
public void onFailure(final Throwable t) {
opening = false;
Log.e(TAG, "Error", t);
}
});
if (media == null) return;
AppExecutors.INSTANCE.getMainThread().execute(() -> openPostDialog(media, profilePicView, mainPostImage, position));
}, Dispatchers.getIO()));
return;
}
opening = true;
@ -304,7 +302,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) > 0;
tagsService = isLoggedIn ? TagsService.getInstance() : null;
storiesService = isLoggedIn ? StoriesService.getInstance(null, 0L, null) : null;
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
graphQLService = isLoggedIn ? null : GraphQLService.INSTANCE;
setHasOptionsMenu(true);
}
@ -385,7 +383,13 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
private void fetchHashtagModel() {
binding.swipeRefreshLayout.setRefreshing(true);
if (isLoggedIn) tagsService.fetch(hashtag, cb);
else graphQLService.fetchTag(hashtag, cb);
else graphQLService.fetchTag(hashtag, CoroutineUtilsKt.getContinuation((hashtag1, throwable) -> {
if (throwable != null) {
cb.onFailure(throwable);
return;
}
AppExecutors.INSTANCE.getMainThread().execute(() -> cb.onSuccess(hashtag1));
}, Dispatchers.getIO()));
}
private void setupPosts() {

View File

@ -113,7 +113,7 @@ public final class LikesViewerFragment extends BottomSheetDialogFragment impleme
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
if (csrfToken == null) return;
mediaService = isLoggedIn ? MediaService.INSTANCE : null;
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
graphQLService = isLoggedIn ? null : GraphQLService.INSTANCE;
// setHasOptionsMenu(true);
}
@ -135,7 +135,17 @@ public final class LikesViewerFragment extends BottomSheetDialogFragment impleme
public void onRefresh() {
if (isComment && !isLoggedIn) {
lazyLoader.resetState();
graphQLService.fetchCommentLikers(postId, null, anonCb);
graphQLService.fetchCommentLikers(
postId,
null,
CoroutineUtilsKt.getContinuation((response, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
if (throwable != null) {
anonCb.onFailure(throwable);
return;
}
anonCb.onSuccess(response);
}), Dispatchers.getIO())
);
} else {
mediaService.fetchLikes(
postId,
@ -164,8 +174,19 @@ public final class LikesViewerFragment extends BottomSheetDialogFragment impleme
binding.rvLikes.setLayoutManager(layoutManager);
binding.rvLikes.addItemDecoration(new DividerItemDecoration(getContext(), DividerItemDecoration.HORIZONTAL));
lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (!TextUtils.isEmpty(endCursor))
graphQLService.fetchCommentLikers(postId, endCursor, anonCb);
if (!TextUtils.isEmpty(endCursor)) {
graphQLService.fetchCommentLikers(
postId,
endCursor,
CoroutineUtilsKt.getContinuation((response, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
if (throwable != null) {
anonCb.onFailure(throwable);
return;
}
anonCb.onSuccess(response);
}), Dispatchers.getIO())
);
}
endCursor = null;
});
binding.rvLikes.addOnScrollListener(lazyLoader);

View File

@ -59,8 +59,10 @@ import awais.instagrabber.repositories.requests.StoryViewerOptions;
import awais.instagrabber.repositories.responses.Location;
import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.utils.AppExecutors;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.CoroutineUtilsKt;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
@ -208,20 +210,18 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
if (user == null) return;
if (TextUtils.isEmpty(user.getUsername())) {
opening = true;
graphQLService.fetchPost(feedModel.getCode(), new ServiceCallback<Media>() {
@Override
public void onSuccess(final Media newFeedModel) {
graphQLService.fetchPost(
feedModel.getCode(),
CoroutineUtilsKt.getContinuation((media, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
opening = false;
if (newFeedModel == null) return;
openPostDialog(newFeedModel, profilePicView, mainPostImage, position);
if (throwable != null) {
Log.e(TAG, "Error", throwable);
return;
}
@Override
public void onFailure(final Throwable t) {
opening = false;
Log.e(TAG, "Error", t);
}
});
if (media == null) return;
openPostDialog(media, profilePicView, mainPostImage, position);
}))
);
return;
}
opening = true;
@ -294,7 +294,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) > 0;
locationService = isLoggedIn ? LocationService.getInstance() : null;
storiesService = StoriesService.getInstance(null, 0L, null);
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
graphQLService = isLoggedIn ? null : GraphQLService.INSTANCE;
setHasOptionsMenu(true);
}
@ -402,7 +402,16 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
private void fetchLocationModel() {
binding.swipeRefreshLayout.setRefreshing(true);
if (isLoggedIn) locationService.fetch(locationId, cb);
else graphQLService.fetchLocation(locationId, cb);
else graphQLService.fetchLocation(
locationId,
CoroutineUtilsKt.getContinuation((location, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
if (throwable != null) {
cb.onFailure(throwable);
return;
}
cb.onSuccess(location);
}))
);
}
private void setupLocationDetails() {

View File

@ -339,7 +339,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
storiesService = isLoggedIn ? StoriesService.getInstance(null, 0L, null) : null;
mediaService = isLoggedIn ? MediaService.INSTANCE : null;
userService = isLoggedIn ? UserService.INSTANCE : null;
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
graphQLService = isLoggedIn ? null : GraphQLService.INSTANCE;
final Context context = getContext();
if (context == null) return;
accountRepository = AccountRepository.getInstance(AccountDataSource.getInstance(context));
@ -618,25 +618,19 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
);
return;
}
graphQLService.fetchUser(usernameTemp, new ServiceCallback<User>() {
@Override
public void onSuccess(final User user) {
graphQLService.fetchUser(
usernameTemp,
CoroutineUtilsKt.getContinuation((user, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
if (throwable != null) {
Log.e(TAG, "Error fetching profile", throwable);
final Context context = getContext();
if (context == null) return;
Toast.makeText(context, throwable.getMessage(), Toast.LENGTH_SHORT).show();
}
profileModel = user;
setProfileDetails();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error fetching profile", t);
final Context context = getContext();
try {
if (t == null)
Toast.makeText(context, R.string.error_loading_profile, Toast.LENGTH_LONG).show();
else Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
} catch (final Throwable ignored) {
}
}
});
}))
);
}
private void setProfileDetails() {

View File

@ -1,25 +1,22 @@
package awais.instagrabber.repositories;
package awais.instagrabber.repositories
import java.util.Map;
import retrofit2.http.GET
import retrofit2.http.Path
import retrofit2.http.QueryMap
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Path;
import retrofit2.http.QueryMap;
public interface GraphQLRepository {
interface GraphQLRepository {
@GET("/graphql/query/")
Call<String> fetch(@QueryMap(encoded = true) Map<String, String> queryParams);
suspend fun fetch(@QueryMap(encoded = true) queryParams: Map<String, String>): String
@GET("/{username}/?__a=1")
Call<String> getUser(@Path("username") String username);
suspend fun getUser(@Path("username") username: String): String
@GET("/p/{shortcode}/?__a=1")
Call<String> getPost(@Path("shortcode") String shortcode);
suspend fun getPost(@Path("shortcode") shortcode: String): String
@GET("/explore/tags/{tag}/?__a=1")
Call<String> getTag(@Path("tag") String tag);
suspend fun getTag(@Path("tag") tag: String): String
@GET("/explore/locations/{locationId}/?__a=1")
Call<String> getLocation(@Path("locationId") long locationId);
suspend fun getLocation(@Path("locationId") locationId: Long): String
}

View File

@ -30,13 +30,13 @@ import awais.instagrabber.repositories.responses.CommentsFetchResponse;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.CoroutineUtilsKt;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.webservices.CommentService;
import awais.instagrabber.webservices.GraphQLService;
import awais.instagrabber.webservices.ServiceCallback;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import kotlin.coroutines.Continuation;
import kotlinx.coroutines.Dispatchers;
import static awais.instagrabber.utils.Utils.settingsHelper;
@ -113,7 +113,7 @@ public class CommentsViewerViewModel extends ViewModel {
};
public CommentsViewerViewModel() {
graphQLService = GraphQLService.getInstance();
graphQLService = GraphQLService.INSTANCE;
final String cookie = settingsHelper.getString(Constants.COOKIE);
final String deviceUuid = Utils.settingsHelper.getString(Constants.DEVICE_UUID);
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
@ -165,8 +165,12 @@ public class CommentsViewerViewModel extends ViewModel {
commentService.fetchComments(postId, rootCursor, ccb);
return;
}
final Call<String> request = graphQLService.fetchComments(shortCode, true, rootCursor);
enqueueRequest(request, true, shortCode, ccb);
graphQLService.fetchComments(
shortCode,
true,
rootCursor,
enqueueRequest(true, shortCode, ccb)
);
}
public void fetchReplies() {
@ -190,28 +194,28 @@ public class CommentsViewerViewModel extends ViewModel {
commentService.fetchChildComments(postId, commentId, repliesCursor, rcb);
return;
}
final Call<String> request = graphQLService.fetchComments(commentId, false, repliesCursor);
enqueueRequest(request, false, commentId, rcb);
graphQLService.fetchComments(commentId, false, repliesCursor, enqueueRequest(false, commentId, rcb));
}
private void enqueueRequest(@NonNull final Call<String> request,
final boolean root,
private Continuation<String> enqueueRequest(final boolean root,
final String shortCodeOrCommentId,
final ServiceCallback callback) {
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String rawBody = response.body();
if (rawBody == null) {
@SuppressWarnings("rawtypes") final ServiceCallback callback) {
return CoroutineUtilsKt.getContinuation((response, throwable) -> {
if (throwable != null) {
callback.onFailure(throwable);
return;
}
if (response == null) {
Log.e(TAG, "Error occurred while fetching gql comments of " + shortCodeOrCommentId);
//noinspection unchecked
callback.onSuccess(null);
return;
}
try {
final JSONObject body = root ? new JSONObject(rawBody).getJSONObject("data")
final JSONObject body = root ? new JSONObject(response).getJSONObject("data")
.getJSONObject("shortcode_media")
.getJSONObject("edge_media_to_parent_comment")
: new JSONObject(rawBody).getJSONObject("data")
: new JSONObject(response).getJSONObject("data")
.getJSONObject("comment")
.getJSONObject("edge_threaded_comments");
final int count = body.optInt("count");
@ -224,20 +228,15 @@ public class CommentsViewerViewModel extends ViewModel {
final Comment commentModel = getComment(commentsJsonArray.getJSONObject(i).getJSONObject("node"), root);
builder.add(commentModel);
}
callback.onSuccess(root ?
new CommentsFetchResponse(count, endCursor, builder.build()) :
new ChildCommentsFetchResponse(count, endCursor, builder.build()));
final Object result = root ? new CommentsFetchResponse(count, endCursor, builder.build())
: new ChildCommentsFetchResponse(count, endCursor, builder.build());
//noinspection unchecked
callback.onSuccess(result);
} catch (Exception e) {
Log.e(TAG, "onResponse", e);
callback.onFailure(e);
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
callback.onFailure(t);
}
});
}, Dispatchers.getIO());
}
@NonNull

View File

@ -1,317 +1,202 @@
package awais.instagrabber.webservices;
package awais.instagrabber.webservices
import android.util.Log;
import android.util.Log
import awais.instagrabber.models.enums.FollowingType
import awais.instagrabber.repositories.GraphQLRepository
import awais.instagrabber.repositories.responses.*
import awais.instagrabber.utils.Constants
import awais.instagrabber.utils.ResponseBodyUtils
import awais.instagrabber.utils.extensions.TAG
import awais.instagrabber.webservices.RetrofitFactory.retrofitWeb
import org.json.JSONException
import org.json.JSONObject
import java.util.*
import androidx.annotation.NonNull;
import com.google.common.collect.ImmutableMap;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import awais.instagrabber.models.enums.FollowingType;
import awais.instagrabber.repositories.GraphQLRepository;
import awais.instagrabber.repositories.responses.FriendshipStatus;
import awais.instagrabber.repositories.responses.GraphQLUserListFetchResponse;
import awais.instagrabber.repositories.responses.Hashtag;
import awais.instagrabber.repositories.responses.Location;
import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.TextUtils;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class GraphQLService extends BaseService {
private static final String TAG = "GraphQLService";
private final GraphQLRepository repository;
private static GraphQLService instance;
private GraphQLService() {
repository = RetrofitFactory.INSTANCE
.getRetrofitWeb()
.create(GraphQLRepository.class);
}
public static GraphQLService getInstance() {
if (instance == null) {
instance = new GraphQLService();
}
return instance;
}
object GraphQLService : BaseService() {
private val repository: GraphQLRepository = retrofitWeb.create(GraphQLRepository::class.java)
// TODO convert string response to a response class
private void fetch(final String queryHash,
final String variables,
final String arg1,
final String arg2,
final User backup,
final ServiceCallback<PostsFetchResponse> callback) {
final Map<String, String> queryMap = new HashMap<>();
queryMap.put("query_hash", queryHash);
queryMap.put("variables", variables);
final Call<String> request = repository.fetch(queryMap);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
try {
// Log.d(TAG, "onResponse: body: " + response.body());
final PostsFetchResponse postsFetchResponse = parsePostResponse(response, arg1, arg2, backup);
if (callback != null) {
callback.onSuccess(postsFetchResponse);
}
} catch (JSONException e) {
Log.e(TAG, "onResponse", e);
if (callback != null) {
callback.onFailure(e);
}
}
private suspend fun fetch(
queryHash: String,
variables: String,
arg1: String,
arg2: String,
backup: User?,
): PostsFetchResponse {
val queryMap = mapOf(
"query_hash" to queryHash,
"variables" to variables,
)
val response = repository.fetch(queryMap)
return parsePostResponse(response, arg1, arg2, backup)
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
if (callback != null) {
callback.onFailure(t);
}
}
});
}
public void fetchLocationPosts(final long locationId,
final String maxId,
final ServiceCallback<PostsFetchResponse> callback) {
fetch("36bd0f2bf5911908de389b8ceaa3be6d",
"{\"id\":\"" + locationId + "\"," +
"\"first\":25," +
"\"after\":\"" + (maxId == null ? "" : maxId) + "\"}",
suspend fun fetchLocationPosts(
locationId: Long,
maxId: String?,
): PostsFetchResponse = fetch(
"36bd0f2bf5911908de389b8ceaa3be6d",
"{\"id\":\"" + locationId + "\"," + "\"first\":25," + "\"after\":\"" + (maxId ?: "") + "\"}",
Constants.EXTRAS_LOCATION,
"edge_location_to_media",
null,
callback);
}
null
)
public void fetchHashtagPosts(@NonNull final String tag,
final String maxId,
final ServiceCallback<PostsFetchResponse> callback) {
fetch("9b498c08113f1e09617a1703c22b2f32",
"{\"tag_name\":\"" + tag + "\"," +
"\"first\":25," +
"\"after\":\"" + (maxId == null ? "" : maxId) + "\"}",
suspend fun fetchHashtagPosts(
tag: String,
maxId: String?,
): PostsFetchResponse = fetch(
"9b498c08113f1e09617a1703c22b2f32",
"{\"tag_name\":\"" + tag + "\"," + "\"first\":25," + "\"after\":\"" + (maxId ?: "") + "\"}",
Constants.EXTRAS_HASHTAG,
"edge_hashtag_to_media",
null,
callback);
}
)
public void fetchProfilePosts(final long profileId,
final int postsPerPage,
final String maxId,
final User backup,
final ServiceCallback<PostsFetchResponse> callback) {
fetch("02e14f6a7812a876f7d133c9555b1151",
"{\"id\":\"" + profileId + "\"," +
"\"first\":" + postsPerPage + "," +
"\"after\":\"" + (maxId == null ? "" : maxId) + "\"}",
suspend fun fetchProfilePosts(
profileId: Long,
postsPerPage: Int,
maxId: String?,
backup: User?,
): PostsFetchResponse = fetch(
"02e14f6a7812a876f7d133c9555b1151",
"{\"id\":\"" + profileId + "\"," + "\"first\":" + postsPerPage + "," + "\"after\":\"" + (maxId ?: "") + "\"}",
Constants.EXTRAS_USER,
"edge_owner_to_timeline_media",
backup,
callback);
}
)
public void fetchTaggedPosts(final long profileId,
final int postsPerPage,
final String maxId,
final ServiceCallback<PostsFetchResponse> callback) {
fetch("31fe64d9463cbbe58319dced405c6206",
"{\"id\":\"" + profileId + "\"," +
"\"first\":" + postsPerPage + "," +
"\"after\":\"" + (maxId == null ? "" : maxId) + "\"}",
suspend fun fetchTaggedPosts(
profileId: Long,
postsPerPage: Int,
maxId: String?,
): PostsFetchResponse = fetch(
"31fe64d9463cbbe58319dced405c6206",
"{\"id\":\"" + profileId + "\"," + "\"first\":" + postsPerPage + "," + "\"after\":\"" + (maxId ?: "") + "\"}",
Constants.EXTRAS_USER,
"edge_user_to_photos_of_you",
null,
callback);
)
@Throws(JSONException::class)
private fun parsePostResponse(
response: String,
arg1: String,
arg2: String,
backup: User?,
): PostsFetchResponse {
if (response.isBlank()) {
Log.e(TAG, "parseResponse: feed response body is empty")
return PostsFetchResponse(emptyList(), false, null)
}
return parseResponseBody(response, arg1, arg2, backup)
}
@NonNull
private PostsFetchResponse parsePostResponse(@NonNull final Response<String> response,
@NonNull final String arg1,
@NonNull final String arg2,
final User backup)
throws JSONException {
if (TextUtils.isEmpty(response.body())) {
Log.e(TAG, "parseResponse: feed response body is empty with status code: " + response.code());
return new PostsFetchResponse(Collections.emptyList(), false, null);
}
return parseResponseBody(response.body(), arg1, arg2, backup);
}
@NonNull
private PostsFetchResponse parseResponseBody(@NonNull final String body,
@NonNull final String arg1,
@NonNull final String arg2,
final User backup)
throws JSONException {
final List<Media> items = new ArrayList<>();
final JSONObject timelineFeed = new JSONObject(body)
@Throws(JSONException::class)
private fun parseResponseBody(
body: String,
arg1: String,
arg2: String,
backup: User?,
): PostsFetchResponse {
val items: MutableList<Media> = ArrayList()
val timelineFeed = JSONObject(body)
.getJSONObject("data")
.getJSONObject(arg1)
.getJSONObject(arg2);
final String endCursor;
final boolean hasNextPage;
final JSONObject pageInfo = timelineFeed.getJSONObject("page_info");
.getJSONObject(arg2)
val endCursor: String?
val hasNextPage: Boolean
val pageInfo = timelineFeed.getJSONObject("page_info")
if (pageInfo.has("has_next_page")) {
hasNextPage = pageInfo.getBoolean("has_next_page");
endCursor = hasNextPage ? pageInfo.getString("end_cursor") : null;
hasNextPage = pageInfo.getBoolean("has_next_page")
endCursor = if (hasNextPage) pageInfo.getString("end_cursor") else null
} else {
hasNextPage = false;
endCursor = null;
hasNextPage = false
endCursor = null
}
final JSONArray feedItems = timelineFeed.getJSONArray("edges");
for (int i = 0; i < feedItems.length(); ++i) {
final JSONObject itemJson = feedItems.optJSONObject(i);
if (itemJson == null) {
continue;
}
final Media media = ResponseBodyUtils.parseGraphQLItem(itemJson, backup);
val feedItems = timelineFeed.getJSONArray("edges")
for (i in 0 until feedItems.length()) {
val itemJson = feedItems.optJSONObject(i) ?: continue
val media = ResponseBodyUtils.parseGraphQLItem(itemJson, backup)
if (media != null) {
items.add(media);
items.add(media)
}
}
return new PostsFetchResponse(items, hasNextPage, endCursor);
return PostsFetchResponse(items, hasNextPage, endCursor)
}
// TODO convert string response to a response class
public void fetchCommentLikers(final String commentId,
final String endCursor,
final ServiceCallback<GraphQLUserListFetchResponse> callback) {
final Map<String, String> queryMap = new HashMap<>();
queryMap.put("query_hash", "5f0b1f6281e72053cbc07909c8d154ae");
queryMap.put("variables", "{\"comment_id\":\"" + commentId + "\"," +
"\"first\":30," +
"\"after\":\"" + (endCursor == null ? "" : endCursor) + "\"}");
final Call<String> request = repository.fetch(queryMap);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String rawBody = response.body();
if (rawBody == null) {
Log.e(TAG, "Error occurred while fetching gql comment likes of " + commentId);
callback.onSuccess(null);
return;
}
try {
final JSONObject body = new JSONObject(rawBody);
final String status = body.getString("status");
final JSONObject data = body.getJSONObject("data").getJSONObject("comment").getJSONObject("edge_liked_by");
final JSONObject pageInfo = data.getJSONObject("page_info");
final String endCursor = pageInfo.getBoolean("has_next_page") ? pageInfo.getString("end_cursor") : null;
final JSONArray users = data.getJSONArray("edges");
final int usersLen = users.length();
final List<User> userModels = new ArrayList<>();
for (int j = 0; j < usersLen; ++j) {
final JSONObject userObject = users.getJSONObject(j).getJSONObject("node");
userModels.add(new User(
suspend fun fetchCommentLikers(
commentId: String,
endCursor: String?,
): GraphQLUserListFetchResponse {
val queryMap = mapOf(
"query_hash" to "5f0b1f6281e72053cbc07909c8d154ae",
"variables" to "{\"comment_id\":\"" + commentId + "\"," + "\"first\":30," + "\"after\":\"" + (endCursor ?: "") + "\"}"
)
val response = repository.fetch(queryMap)
val body = JSONObject(response)
val status = body.getString("status")
val data = body.getJSONObject("data").getJSONObject("comment").getJSONObject("edge_liked_by")
val pageInfo = data.getJSONObject("page_info")
val newEndCursor = if (pageInfo.getBoolean("has_next_page")) pageInfo.getString("end_cursor") else null
val users = data.getJSONArray("edges")
val usersLen = users.length()
val userModels: MutableList<User> = ArrayList()
for (j in 0 until usersLen) {
val userObject = users.getJSONObject(j).getJSONObject("node")
userModels.add(User(
userObject.getLong("id"),
userObject.getString("username"),
userObject.optString("full_name"),
userObject.optBoolean("is_private"),
userObject.getString("profile_pic_url"),
userObject.optBoolean("is_verified")
));
// userModels.add(new ProfileModel(userObject.optBoolean("is_private"),
// false,
// userObject.optBoolean("is_verified"),
// userObject.getString("id"),
// userObject.getString("username"),
// userObject.optString("full_name"),
// null, null,
// userObject.getString("profile_pic_url"),
// null, 0, 0, 0, false, false, false, false, false));
}
callback.onSuccess(new GraphQLUserListFetchResponse(endCursor, status, userModels));
} catch (JSONException e) {
Log.e(TAG, "onResponse", e);
if (callback != null) {
callback.onFailure(e);
}
))
}
return GraphQLUserListFetchResponse(newEndCursor, status, userModels)
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
if (callback != null) {
callback.onFailure(t);
}
}
});
}
public Call<String> fetchComments(final String shortCodeOrCommentId,
final boolean root,
final String cursor) {
final Map<String, String> queryMap = new HashMap<>();
queryMap.put("query_hash", root ? "bc3296d1ce80a24b1b6e40b1e72903f5" : "51fdd02b67508306ad4484ff574a0b62");
final Map<String, Object> variables = ImmutableMap.of(
root ? "shortcode" : "comment_id", shortCodeOrCommentId,
"first", 50,
"after", cursor == null ? "" : cursor
);
queryMap.put("variables", new JSONObject(variables).toString());
return repository.fetch(queryMap);
suspend fun fetchComments(
shortCodeOrCommentId: String?,
root: Boolean,
cursor: String?,
): String {
val variables = mapOf(
(if (root) "shortcode" else "comment_id") to shortCodeOrCommentId,
"first" to 50,
"after" to (cursor ?: "")
)
val queryMap = mapOf(
"query_hash" to if (root) "bc3296d1ce80a24b1b6e40b1e72903f5" else "51fdd02b67508306ad4484ff574a0b62",
"variables" to JSONObject(variables).toString()
)
return repository.fetch(queryMap)
}
// TODO convert string response to a response class
public void fetchUser(final String username,
final ServiceCallback<User> callback) {
final Call<String> request = repository.getUser(username);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String rawBody = response.body();
if (rawBody == null) {
Log.e(TAG, "Error occurred while fetching gql user of " + username);
callback.onSuccess(null);
return;
}
try {
final JSONObject body = new JSONObject(rawBody);
final JSONObject userJson = body.getJSONObject("graphql")
.getJSONObject(Constants.EXTRAS_USER);
boolean isPrivate = userJson.getBoolean("is_private");
final long id = userJson.optLong(Constants.EXTRAS_ID, 0);
final JSONObject timelineMedia = userJson.getJSONObject("edge_owner_to_timeline_media");
suspend fun fetchUser(
username: String,
): User {
val response = repository.getUser(username)
val body = JSONObject(response)
val userJson = body.getJSONObject("graphql").getJSONObject(Constants.EXTRAS_USER)
val isPrivate = userJson.getBoolean("is_private")
val id = userJson.optLong(Constants.EXTRAS_ID, 0)
val timelineMedia = userJson.getJSONObject("edge_owner_to_timeline_media")
// if (timelineMedia.has("edges")) {
// final JSONArray edges = timelineMedia.getJSONArray("edges");
// }
String url = userJson.optString("external_url");
if (TextUtils.isEmpty(url)) url = null;
callback.onSuccess(new User(
var url: String? = userJson.optString("external_url")
if (url.isNullOrBlank()) url = null
return User(
id,
username,
userJson.getString("full_name"),
isPrivate,
userJson.getString("profile_pic_url_hd"),
userJson.getBoolean("is_verified"),
null,
new FriendshipStatus(
friendshipStatus = FriendshipStatus(
userJson.optBoolean("followed_by_viewer"),
userJson.optBoolean("follows_viewer"),
userJson.optBoolean("blocked_by_viewer"),
@ -323,161 +208,59 @@ public class GraphQLService extends BaseService {
userJson.optBoolean("restricted_by_viewer"),
false
),
false,
false,
false,
false,
false,
null,
null,
timelineMedia.getLong("count"),
userJson.getJSONObject("edge_followed_by").getLong("count"),
userJson.getJSONObject("edge_follow").getLong("count"),
0,
userJson.getString("biography"),
url,
0,
null,
null,
null,
null,
null,
null));
} catch (JSONException e) {
Log.e(TAG, "onResponse", e);
if (callback != null) {
callback.onFailure(e);
}
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
if (callback != null) {
callback.onFailure(t);
}
}
});
mediaCount = timelineMedia.getLong("count"),
followerCount = userJson.getJSONObject("edge_followed_by").getLong("count"),
followingCount = userJson.getJSONObject("edge_follow").getLong("count"),
biography = userJson.getString("biography"),
externalUrl = url,
)
}
// TODO convert string response to a response class
public void fetchPost(final String shortcode,
final ServiceCallback<Media> callback) {
final Call<String> request = repository.getPost(shortcode);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String rawBody = response.body();
if (rawBody == null) {
Log.e(TAG, "Error occurred while fetching gql post of " + shortcode);
callback.onSuccess(null);
return;
}
try {
final JSONObject body = new JSONObject(rawBody);
final JSONObject media = body.getJSONObject("graphql")
.getJSONObject("shortcode_media");
callback.onSuccess(ResponseBodyUtils.parseGraphQLItem(media, null));
} catch (JSONException e) {
Log.e(TAG, "onResponse", e);
if (callback != null) {
callback.onFailure(e);
}
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
if (callback != null) {
callback.onFailure(t);
}
}
});
suspend fun fetchPost(
shortcode: String,
): Media {
val response = repository.getPost(shortcode)
val body = JSONObject(response)
val media = body.getJSONObject("graphql").getJSONObject("shortcode_media")
return ResponseBodyUtils.parseGraphQLItem(media, null)
}
// TODO convert string response to a response class
public void fetchTag(final String tag,
final ServiceCallback<Hashtag> callback) {
final Call<String> request = repository.getTag(tag);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String rawBody = response.body();
if (rawBody == null) {
Log.e(TAG, "Error occurred while fetching gql tag of " + tag);
callback.onSuccess(null);
return;
}
try {
final JSONObject body = new JSONObject(rawBody)
suspend fun fetchTag(
tag: String,
): Hashtag {
val response = repository.getTag(tag)
val body = JSONObject(response)
.getJSONObject("graphql")
.getJSONObject(Constants.EXTRAS_HASHTAG);
final JSONObject timelineMedia = body.getJSONObject("edge_hashtag_to_media");
callback.onSuccess(new Hashtag(
.getJSONObject(Constants.EXTRAS_HASHTAG)
val timelineMedia = body.getJSONObject("edge_hashtag_to_media")
return Hashtag(
body.getString(Constants.EXTRAS_ID),
body.getString("name"),
timelineMedia.getLong("count"),
body.optBoolean("is_following") ? FollowingType.FOLLOWING : FollowingType.NOT_FOLLOWING,
null));
} catch (JSONException e) {
Log.e(TAG, "onResponse", e);
if (callback != null) {
callback.onFailure(e);
}
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
if (callback != null) {
callback.onFailure(t);
}
}
});
if (body.optBoolean("is_following")) FollowingType.FOLLOWING else FollowingType.NOT_FOLLOWING,
null)
}
// TODO convert string response to a response class
public void fetchLocation(final long locationId,
final ServiceCallback<Location> callback) {
final Call<String> request = repository.getLocation(locationId);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String rawBody = response.body();
if (rawBody == null) {
Log.e(TAG, "Error occurred while fetching gql location of " + locationId);
callback.onSuccess(null);
return;
}
try {
final JSONObject body = new JSONObject(rawBody)
suspend fun fetchLocation(
locationId: Long,
): Location {
val response = repository.getLocation(locationId)
val body = JSONObject(response)
.getJSONObject("graphql")
.getJSONObject(Constants.EXTRAS_LOCATION);
final JSONObject timelineMedia = body.getJSONObject("edge_location_to_media");
final JSONObject address = new JSONObject(body.getString("address_json"));
callback.onSuccess(new Location(
.getJSONObject(Constants.EXTRAS_LOCATION)
// val timelineMedia = body.getJSONObject("edge_location_to_media")
val address = JSONObject(body.getString("address_json"))
return Location(
body.getLong(Constants.EXTRAS_ID),
body.getString("slug"),
body.getString("name"),
address.optString("street_address"),
address.optString("city_name"),
body.optDouble("lng", 0d),
body.optDouble("lat", 0d)
));
} catch (JSONException e) {
Log.e(TAG, "onResponse", e);
if (callback != null) {
callback.onFailure(e);
}
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
if (callback != null) {
callback.onFailure(t);
}
}
});
body.optDouble("lng", 0.0),
body.optDouble("lat", 0.0)
)
}
}