package awais.instagrabber.webservices; import android.util.Log; import androidx.annotation.NonNull; 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.repositories.GraphQLRepository; import awais.instagrabber.repositories.responses.FriendshipStatus; import awais.instagrabber.repositories.responses.GraphQLUserListFetchResponse; 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; import retrofit2.Retrofit; public class GraphQLService extends BaseService { private static final String TAG = "GraphQLService"; // private static final boolean loadFromMock = false; private final GraphQLRepository repository; private static GraphQLService instance; private GraphQLService() { final Retrofit retrofit = getRetrofitBuilder() .baseUrl("https://www.instagram.com") .build(); repository = retrofit.create(GraphQLRepository.class); } public static GraphQLService getInstance() { if (instance == null) { instance = new GraphQLService(); } return instance; } private void fetch(final String queryHash, final String variables, final String arg1, final String arg2, final ServiceCallback callback) { final Map queryMap = new HashMap<>(); queryMap.put("query_hash", queryHash); queryMap.put("variables", variables); final Call request = repository.fetch(queryMap); request.enqueue(new Callback() { @Override public void onResponse(@NonNull final Call call, @NonNull final Response response) { try { // Log.d(TAG, "onResponse: body: " + response.body()); final PostsFetchResponse postsFetchResponse = parsePostResponse(response, arg1, arg2); if (callback != null) { callback.onSuccess(postsFetchResponse); } } catch (JSONException e) { Log.e(TAG, "onResponse", e); if (callback != null) { callback.onFailure(e); } } } @Override public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { if (callback != null) { callback.onFailure(t); } } }); } public void fetchLocationPosts(final long locationId, final String maxId, final ServiceCallback callback) { fetch("36bd0f2bf5911908de389b8ceaa3be6d", "{\"id\":\"" + locationId + "\"," + "\"first\":25," + "\"after\":\"" + (maxId == null ? "" : maxId) + "\"}", Constants.EXTRAS_LOCATION, "edge_location_to_media", callback); } public void fetchHashtagPosts(@NonNull final String tag, final String maxId, final ServiceCallback callback) { fetch("9b498c08113f1e09617a1703c22b2f32", "{\"tag_name\":\"" + tag + "\"," + "\"first\":25," + "\"after\":\"" + (maxId == null ? "" : maxId) + "\"}", Constants.EXTRAS_HASHTAG, "edge_hashtag_to_media", callback); } public void fetchProfilePosts(final long profileId, final int postsPerPage, final String maxId, final ServiceCallback callback) { fetch("18a7b935ab438c4514b1f742d8fa07a7", "{\"id\":\"" + profileId + "\"," + "\"first\":" + postsPerPage + "," + "\"after\":\"" + (maxId == null ? "" : maxId) + "\"}", Constants.EXTRAS_USER, "edge_owner_to_timeline_media", callback); } public void fetchTaggedPosts(final long profileId, final int postsPerPage, final String maxId, final ServiceCallback callback) { fetch("31fe64d9463cbbe58319dced405c6206", "{\"id\":\"" + profileId + "\"," + "\"first\":" + postsPerPage + "," + "\"after\":\"" + (maxId == null ? "" : maxId) + "\"}", Constants.EXTRAS_USER, "edge_user_to_photos_of_you", callback); } @NonNull private PostsFetchResponse parsePostResponse(@NonNull final Response response, @NonNull final String arg1, @NonNull final String arg2) 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); } @NonNull private PostsFetchResponse parseResponseBody(@NonNull final String body, @NonNull final String arg1, @NonNull final String arg2) throws JSONException { final List items = new ArrayList<>(); final JSONObject timelineFeed = new JSONObject(body) .getJSONObject("data") .getJSONObject(arg1) .getJSONObject(arg2); final String endCursor; final boolean hasNextPage; final JSONObject pageInfo = timelineFeed.getJSONObject("page_info"); if (pageInfo.has("has_next_page")) { hasNextPage = pageInfo.getBoolean("has_next_page"); endCursor = hasNextPage ? pageInfo.getString("end_cursor") : null; } else { 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); if (media != null) { items.add(media); } } return new PostsFetchResponse(items, hasNextPage, endCursor); } public void fetchCommentLikers(final String commentId, final String endCursor, final ServiceCallback callback) { final Map queryMap = new HashMap<>(); queryMap.put("query_hash", "5f0b1f6281e72053cbc07909c8d154ae"); queryMap.put("variables", "{\"comment_id\":\"" + commentId + "\"," + "\"first\":30," + "\"after\":\"" + (endCursor == null ? "" : endCursor) + "\"}"); final Call request = repository.fetch(queryMap); request.enqueue(new Callback() { @Override public void onResponse(@NonNull final Call call, @NonNull final Response 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 userModels = new ArrayList<>(); for (int j = 0; j < usersLen; ++j) { final JSONObject userObject = users.getJSONObject(j).getJSONObject("node"); userModels.add(new User( userObject.getLong("id"), userObject.getString("username"), userObject.optString("full_name"), userObject.optBoolean("is_private"), userObject.getString("profile_pic_url"), null, new FriendshipStatus( false, false, false, false, false, false, false, false, false, false ), userObject.optBoolean("is_verified"), false, false, false, false, null, null, 0, 0, 0, 0, null, null, 0, null, null, null, null, null, null )); // 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); } } } @Override public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { if (callback != null) { callback.onFailure(t); } } }); } public void fetchUser(final String username, final ServiceCallback callback) { final Call request = repository.getUser(username); request.enqueue(new Callback() { @Override public void onResponse(@NonNull final Call call, @NonNull final Response 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"); // 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( id, username, userJson.getString("full_name"), isPrivate, userJson.getString("profile_pic_url_hd"), null, new FriendshipStatus( userJson.optBoolean("followed_by_viewer"), userJson.optBoolean("follows_viewer"), userJson.optBoolean("blocked_by_viewer"), false, isPrivate, userJson.optBoolean("has_requested_viewer"), userJson.optBoolean("requested_by_viewer"), false, userJson.optBoolean("restricted_by_viewer"), false ), userJson.getBoolean("is_verified"), 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 call, @NonNull final Throwable t) { if (callback != null) { callback.onFailure(t); } } }); } }