1
0
mirror of https://github.com/KokaKiwi/BarInsta synced 2024-11-22 22:57:29 +00:00

api refactor

This commit is contained in:
Austin Huang 2020-12-20 13:35:16 -05:00
parent 4d6ac5d293
commit 49f41f4654
No known key found for this signature in database
GPG Key ID: 84C23AA04587A91F
18 changed files with 320 additions and 775 deletions

View File

@ -6,22 +6,22 @@ import awais.instagrabber.customviews.helpers.PostFetcher;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel; import awais.instagrabber.models.FeedModel;
import awais.instagrabber.repositories.responses.PostsFetchResponse; import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.webservices.FeedService; import awais.instagrabber.webservices.GraphQLService;
import awais.instagrabber.webservices.ServiceCallback; import awais.instagrabber.webservices.ServiceCallback;
public class FeedPostFetchService implements PostFetcher.PostFetchService { public class FeedPostFetchService implements PostFetcher.PostFetchService {
private static final String TAG = "FeedPostFetchService"; private static final String TAG = "FeedPostFetchService";
private final FeedService feedService; private final GraphQLService graphQLService;
private String nextCursor; private String nextCursor;
private boolean hasNextPage; private boolean hasNextPage;
public FeedPostFetchService() { public FeedPostFetchService() {
feedService = FeedService.getInstance(); graphQLService = GraphQLService.getInstance();
} }
@Override @Override
public void fetch(final FetchListener<List<FeedModel>> fetchListener) { public void fetch(final FetchListener<List<FeedModel>> fetchListener) {
feedService.fetch(25, nextCursor, new ServiceCallback<PostsFetchResponse>() { graphQLService.fetchFeed(25, nextCursor, new ServiceCallback<PostsFetchResponse>() {
@Override @Override
public void onSuccess(final PostsFetchResponse result) { public void onSuccess(final PostsFetchResponse result) {
if (result == null) return; if (result == null) return;

View File

@ -6,12 +6,14 @@ import awais.instagrabber.customviews.helpers.PostFetcher;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel; import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.HashtagModel; import awais.instagrabber.models.HashtagModel;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.webservices.GraphQLService;
import awais.instagrabber.webservices.ServiceCallback; import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.TagsService; import awais.instagrabber.webservices.TagsService;
import awais.instagrabber.webservices.TagsService.TagPostsFetchResponse;
public class HashtagPostFetchService implements PostFetcher.PostFetchService { public class HashtagPostFetchService implements PostFetcher.PostFetchService {
private final TagsService tagsService; private final TagsService tagsService;
private final GraphQLService graphQLService;
private final HashtagModel hashtagModel; private final HashtagModel hashtagModel;
private String nextMaxId; private String nextMaxId;
private boolean moreAvailable; private boolean moreAvailable;
@ -20,19 +22,20 @@ public class HashtagPostFetchService implements PostFetcher.PostFetchService {
public HashtagPostFetchService(final HashtagModel hashtagModel, final boolean isLoggedIn) { public HashtagPostFetchService(final HashtagModel hashtagModel, final boolean isLoggedIn) {
this.hashtagModel = hashtagModel; this.hashtagModel = hashtagModel;
this.isLoggedIn = isLoggedIn; this.isLoggedIn = isLoggedIn;
tagsService = TagsService.getInstance(); tagsService = isLoggedIn ? TagsService.getInstance() : null;
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
} }
@Override @Override
public void fetch(final FetchListener<List<FeedModel>> fetchListener) { public void fetch(final FetchListener<List<FeedModel>> fetchListener) {
final ServiceCallback cb = new ServiceCallback<TagPostsFetchResponse>() { final ServiceCallback cb = new ServiceCallback<PostsFetchResponse>() {
@Override @Override
public void onSuccess(final TagPostsFetchResponse result) { public void onSuccess(final PostsFetchResponse result) {
if (result == null) return; if (result == null) return;
nextMaxId = result.getNextMaxId(); nextMaxId = result.getNextCursor();
moreAvailable = result.isMoreAvailable(); moreAvailable = result.hasNextPage();
if (fetchListener != null) { if (fetchListener != null) {
fetchListener.onResult(result.getItems()); fetchListener.onResult(result.getFeedModels());
} }
} }
@ -45,7 +48,7 @@ public class HashtagPostFetchService implements PostFetcher.PostFetchService {
} }
}; };
if (isLoggedIn) tagsService.fetchPosts(hashtagModel.getName().toLowerCase(), nextMaxId, cb); if (isLoggedIn) tagsService.fetchPosts(hashtagModel.getName().toLowerCase(), nextMaxId, cb);
else tagsService.fetchGraphQLPosts(hashtagModel.getName().toLowerCase(), nextMaxId, cb); else graphQLService.fetchHashtagPosts(hashtagModel.getName().toLowerCase(), nextMaxId, cb);
} }
@Override @Override

View File

@ -1,73 +0,0 @@
package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONObject;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.HighlightModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.NetworkUtils;
public final class HighlightsFetcher extends AsyncTask<Void, Void, List<HighlightModel>> {
private final String id;
private final FetchListener<List<HighlightModel>> fetchListener;
public HighlightsFetcher(final String id, final FetchListener<List<HighlightModel>> fetchListener) {
this.id = id;
this.fetchListener = fetchListener;
}
@Override
protected List<HighlightModel> doInBackground(final Void... voids) {
List<HighlightModel> result = null;
String url = "https://i.instagram.com/api/v1/highlights/" + id + "/highlights_tray/";
try {
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setInstanceFollowRedirects(false);
conn.setUseCaches(false);
conn.setRequestProperty("User-Agent", Constants.I_USER_AGENT);
conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
final JSONArray highlightsReel = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONArray("tray");
final int length = highlightsReel.length();
final List<HighlightModel> highlightModels = new ArrayList<>();
// final String[] highlightIds = new String[length];
for (int i = 0; i < length; ++i) {
final JSONObject highlightNode = highlightsReel.getJSONObject(i);
highlightModels.add(new HighlightModel(
highlightNode.getString("title"),
highlightNode.getString(Constants.EXTRAS_ID),
highlightNode.getJSONObject("cover_media")
.getJSONObject("cropped_image_version")
.getString("url")
));
}
conn.disconnect();
result = highlightModels;
}
conn.disconnect();
} catch (Exception e) {
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
return result;
}
@Override
protected void onPostExecute(final List<HighlightModel> result) {
if (fetchListener != null) fetchListener.onResult(result);
}
}

View File

@ -6,12 +6,14 @@ import awais.instagrabber.customviews.helpers.PostFetcher;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel; import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.LocationModel; import awais.instagrabber.models.LocationModel;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.webservices.GraphQLService;
import awais.instagrabber.webservices.LocationService; import awais.instagrabber.webservices.LocationService;
import awais.instagrabber.webservices.LocationService.LocationPostsFetchResponse;
import awais.instagrabber.webservices.ServiceCallback; import awais.instagrabber.webservices.ServiceCallback;
public class LocationPostFetchService implements PostFetcher.PostFetchService { public class LocationPostFetchService implements PostFetcher.PostFetchService {
private final LocationService locationService; private final LocationService locationService;
private final GraphQLService graphQLService;
private final LocationModel locationModel; private final LocationModel locationModel;
private String nextMaxId; private String nextMaxId;
private boolean moreAvailable; private boolean moreAvailable;
@ -20,19 +22,20 @@ public class LocationPostFetchService implements PostFetcher.PostFetchService {
public LocationPostFetchService(final LocationModel locationModel, final boolean isLoggedIn) { public LocationPostFetchService(final LocationModel locationModel, final boolean isLoggedIn) {
this.locationModel = locationModel; this.locationModel = locationModel;
this.isLoggedIn = isLoggedIn; this.isLoggedIn = isLoggedIn;
locationService = LocationService.getInstance(); locationService = isLoggedIn ? LocationService.getInstance() : null;
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
} }
@Override @Override
public void fetch(final FetchListener<List<FeedModel>> fetchListener) { public void fetch(final FetchListener<List<FeedModel>> fetchListener) {
final ServiceCallback cb = new ServiceCallback<LocationPostsFetchResponse>() { final ServiceCallback cb = new ServiceCallback<PostsFetchResponse>() {
@Override @Override
public void onSuccess(final LocationPostsFetchResponse result) { public void onSuccess(final PostsFetchResponse result) {
if (result == null) return; if (result == null) return;
nextMaxId = result.getNextMaxId(); nextMaxId = result.getNextCursor();
moreAvailable = result.isMoreAvailable(); moreAvailable = result.hasNextPage();
if (fetchListener != null) { if (fetchListener != null) {
fetchListener.onResult(result.getItems()); fetchListener.onResult(result.getFeedModels());
} }
} }
@ -45,7 +48,7 @@ public class LocationPostFetchService implements PostFetcher.PostFetchService {
} }
}; };
if (isLoggedIn) locationService.fetchPosts(locationModel.getId(), nextMaxId, cb); if (isLoggedIn) locationService.fetchPosts(locationModel.getId(), nextMaxId, cb);
else locationService.fetchGraphQLPosts(locationModel.getId(), nextMaxId, cb); else graphQLService.fetchLocationPosts(locationModel.getId(), nextMaxId, cb);
} }
@Override @Override

View File

@ -7,29 +7,34 @@ import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel; import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.ProfileModel; import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.repositories.responses.PostsFetchResponse; import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.webservices.GraphQLService;
import awais.instagrabber.webservices.ProfileService; import awais.instagrabber.webservices.ProfileService;
import awais.instagrabber.webservices.ServiceCallback; import awais.instagrabber.webservices.ServiceCallback;
public class ProfilePostFetchService implements PostFetcher.PostFetchService { public class ProfilePostFetchService implements PostFetcher.PostFetchService {
private static final String TAG = "ProfilePostFetchService"; private static final String TAG = "ProfilePostFetchService";
private final ProfileService profileService; private final ProfileService profileService;
private final GraphQLService graphQLService;
private final ProfileModel profileModel; private final ProfileModel profileModel;
private String nextCursor; private final boolean isLoggedIn;
private boolean hasNextPage; private String nextMaxId;
private boolean moreAvailable;
public ProfilePostFetchService(final ProfileModel profileModel) { public ProfilePostFetchService(final ProfileModel profileModel, final boolean isLoggedIn) {
this.profileModel = profileModel; this.profileModel = profileModel;
profileService = ProfileService.getInstance(); this.isLoggedIn = isLoggedIn;
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
profileService = isLoggedIn ? ProfileService.getInstance() : null;
} }
@Override @Override
public void fetch(final FetchListener<List<FeedModel>> fetchListener) { public void fetch(final FetchListener<List<FeedModel>> fetchListener) {
profileService.fetchPosts(profileModel, 30, nextCursor, new ServiceCallback<PostsFetchResponse>() { final ServiceCallback cb = new ServiceCallback<PostsFetchResponse>() {
@Override @Override
public void onSuccess(final PostsFetchResponse result) { public void onSuccess(final PostsFetchResponse result) {
if (result == null) return; if (result == null) return;
nextCursor = result.getNextCursor(); nextMaxId = result.getNextCursor();
hasNextPage = result.hasNextPage(); moreAvailable = result.hasNextPage();
if (fetchListener != null) { if (fetchListener != null) {
fetchListener.onResult(result.getFeedModels()); fetchListener.onResult(result.getFeedModels());
} }
@ -42,16 +47,18 @@ public class ProfilePostFetchService implements PostFetcher.PostFetchService {
fetchListener.onFailure(t); fetchListener.onFailure(t);
} }
} }
}); };
if (isLoggedIn) profileService.fetchPosts(profileModel.getId(), nextMaxId, cb);
else graphQLService.fetchProfilePosts(profileModel.getId(), 30, nextMaxId, cb);
} }
@Override @Override
public void reset() { public void reset() {
nextCursor = null; nextMaxId = null;
} }
@Override @Override
public boolean hasNextPage() { public boolean hasNextPage() {
return hasNextPage; return moreAvailable;
} }
} }

View File

@ -6,34 +6,39 @@ import awais.instagrabber.customviews.helpers.PostFetcher;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel; import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.enums.PostItemType; import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.webservices.GraphQLService;
import awais.instagrabber.webservices.ProfileService; import awais.instagrabber.webservices.ProfileService;
import awais.instagrabber.webservices.ProfileService.SavedPostsFetchResponse;
import awais.instagrabber.webservices.ServiceCallback; import awais.instagrabber.webservices.ServiceCallback;
public class SavedPostFetchService implements PostFetcher.PostFetchService { public class SavedPostFetchService implements PostFetcher.PostFetchService {
private final ProfileService profileService; private final ProfileService profileService;
private final GraphQLService graphQLService;
private final String profileId; private final String profileId;
private final PostItemType type; private final PostItemType type;
private final boolean isLoggedIn;
private String nextMaxId; private String nextMaxId;
private boolean moreAvailable; private boolean moreAvailable;
public SavedPostFetchService(final String profileId, final PostItemType type) { public SavedPostFetchService(final String profileId, final PostItemType type, final boolean isLoggedIn) {
this.profileId = profileId; this.profileId = profileId;
this.type = type; this.type = type;
profileService = ProfileService.getInstance(); this.isLoggedIn = isLoggedIn;
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
profileService = isLoggedIn ? ProfileService.getInstance() : null;
} }
@Override @Override
public void fetch(final FetchListener<List<FeedModel>> fetchListener) { public void fetch(final FetchListener<List<FeedModel>> fetchListener) {
final ServiceCallback<SavedPostsFetchResponse> callback = new ServiceCallback<SavedPostsFetchResponse>() { final ServiceCallback<PostsFetchResponse> callback = new ServiceCallback<PostsFetchResponse>() {
@Override @Override
public void onSuccess(final SavedPostsFetchResponse result) { public void onSuccess(final PostsFetchResponse result) {
if (result == null) return; if (result == null) return;
nextMaxId = result.getNextMaxId(); nextMaxId = result.getNextCursor();
moreAvailable = result.isMoreAvailable(); moreAvailable = result.hasNextPage();
if (fetchListener != null) { if (fetchListener != null) {
fetchListener.onResult(result.getItems()); fetchListener.onResult(result.getFeedModels());
} }
} }
@ -50,7 +55,8 @@ public class SavedPostFetchService implements PostFetcher.PostFetchService {
profileService.fetchLiked(nextMaxId, callback); profileService.fetchLiked(nextMaxId, callback);
break; break;
case TAGGED: case TAGGED:
profileService.fetchTagged(profileId, 30, nextMaxId, callback); if (isLoggedIn) profileService.fetchTagged(profileId, nextMaxId, callback);
else graphQLService.fetchTaggedPosts(profileId, 30, nextMaxId, callback);
break; break;
case SAVED: case SAVED:
default: default:

View File

@ -40,24 +40,26 @@ import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.models.enums.PostItemType; import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
import static androidx.core.content.PermissionChecker.checkSelfPermission; import static androidx.core.content.PermissionChecker.checkSelfPermission;
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class SavedViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { public final class SavedViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final int STORAGE_PERM_REQUEST_CODE = 8020; private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030; private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030;
private FragmentSavedBinding binding; private FragmentSavedBinding binding;
private String username; private String username, cookie, profileId;
private ActionMode actionMode; private ActionMode actionMode;
private SwipeRefreshLayout root; private SwipeRefreshLayout root;
private AppCompatActivity fragmentActivity; private AppCompatActivity fragmentActivity;
private boolean shouldRefresh = true; private boolean isLoggedIn, shouldRefresh = true;
private PostItemType type; private PostItemType type;
private String profileId;
private Set<FeedModel> selectedFeedModels; private Set<FeedModel> selectedFeedModels;
private FeedModel downloadFeedModel; private FeedModel downloadFeedModel;
private int downloadChildPosition = -1; private int downloadChildPosition = -1;
@ -225,6 +227,8 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
@Override @Override
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) {
cookie = settingsHelper.getString(Constants.COOKIE);
isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null;
if (root != null) { if (root != null) {
shouldRefresh = false; shouldRefresh = false;
return root; return root;
@ -281,7 +285,7 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
private void setupPosts() { private void setupPosts() {
binding.posts.setViewModelStoreOwner(this) binding.posts.setViewModelStoreOwner(this)
.setLifeCycleOwner(this) .setLifeCycleOwner(this)
.setPostFetchService(new SavedPostFetchService(profileId, type)) .setPostFetchService(new SavedPostFetchService(profileId, type, isLoggedIn))
.setLayoutPreferences(layoutPreferences) .setLayoutPreferences(layoutPreferences)
.addFetchStatusChangeListener(fetching -> updateSwipeRefreshState()) .addFetchStatusChangeListener(fetching -> updateSwipeRefreshState())
.setFeedItemCallback(feedItemCallback) .setFeedItemCallback(feedItemCallback)

View File

@ -54,7 +54,6 @@ import awais.instagrabber.R;
import awais.instagrabber.activities.MainActivity; import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.FeedAdapterV2; import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.adapters.HighlightsAdapter; import awais.instagrabber.adapters.HighlightsAdapter;
import awais.instagrabber.asyncs.HighlightsFetcher;
import awais.instagrabber.asyncs.ProfileFetcher; import awais.instagrabber.asyncs.ProfileFetcher;
import awais.instagrabber.asyncs.ProfilePostFetchService; import awais.instagrabber.asyncs.ProfilePostFetchService;
import awais.instagrabber.asyncs.UsernameFetcher; import awais.instagrabber.asyncs.UsernameFetcher;
@ -75,6 +74,7 @@ import awais.instagrabber.dialogs.ProfilePicDialogFragment;
import awais.instagrabber.fragments.PostViewV2Fragment; import awais.instagrabber.fragments.PostViewV2Fragment;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel; import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.HighlightModel;
import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.models.ProfileModel; import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.StoryModel; import awais.instagrabber.models.StoryModel;
@ -729,6 +729,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
} }
private void setupButtons(final String profileId, final String myId) { private void setupButtons(final String profileId, final String myId) {
profileDetailsBinding.btnTagged.setVisibility(profileModel.isReallyPrivate() ? View.GONE : View.VISIBLE);
if (isLoggedIn) { if (isLoggedIn) {
if (profileId.equals(myId)) { if (profileId.equals(myId)) {
profileDetailsBinding.btnTagged.setVisibility(View.VISIBLE); profileDetailsBinding.btnTagged.setVisibility(View.VISIBLE);
@ -738,7 +739,6 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
profileDetailsBinding.btnSaved.setText(R.string.saved); profileDetailsBinding.btnSaved.setText(R.string.saved);
return; return;
} }
profileDetailsBinding.btnTagged.setVisibility(View.GONE);
profileDetailsBinding.btnSaved.setVisibility(View.GONE); profileDetailsBinding.btnSaved.setVisibility(View.GONE);
profileDetailsBinding.btnLiked.setVisibility(View.GONE); profileDetailsBinding.btnLiked.setVisibility(View.GONE);
profileDetailsBinding.btnDM.setVisibility(View.VISIBLE); // maybe there is a judgment mechanism? profileDetailsBinding.btnDM.setVisibility(View.VISIBLE); // maybe there is a judgment mechanism?
@ -776,7 +776,6 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
restrictMenuItem.setTitle(R.string.restrict); restrictMenuItem.setTitle(R.string.restrict);
} }
} }
profileDetailsBinding.btnTagged.setVisibility(profileModel.isReallyPrivate() ? View.GONE : View.VISIBLE);
if (blockMenuItem != null) { if (blockMenuItem != null) {
blockMenuItem.setVisible(true); blockMenuItem.setVisible(true);
if (profileModel.isBlocked()) { if (profileModel.isBlocked()) {
@ -817,14 +816,24 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
Log.e(TAG, "Error", t); Log.e(TAG, "Error", t);
} }
}); });
new HighlightsFetcher(profileId, storiesService.fetchHighlights(profileId,
result -> { new ServiceCallback<List<HighlightModel>>() {
@Override
public void onSuccess(final List<HighlightModel> result) {
highlightsFetching = false; highlightsFetching = false;
if (result != null) { if (result != null) {
profileDetailsBinding.highlightsList.setVisibility(View.VISIBLE); profileDetailsBinding.highlightsList.setVisibility(View.VISIBLE);
highlightsViewModel.getList().postValue(result); highlightsViewModel.getList().postValue(result);
} else profileDetailsBinding.highlightsList.setVisibility(View.GONE); }
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); else profileDetailsBinding.highlightsList.setVisibility(View.GONE);
}
@Override
public void onFailure(final Throwable t) {
profileDetailsBinding.highlightsList.setVisibility(View.GONE);
Log.e(TAG, "Error", t);
}
});
} }
private void setupCommonListeners() { private void setupCommonListeners() {
@ -979,7 +988,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
private void setupPosts() { private void setupPosts() {
binding.postsRecyclerView.setViewModelStoreOwner(this) binding.postsRecyclerView.setViewModelStoreOwner(this)
.setLifeCycleOwner(this) .setLifeCycleOwner(this)
.setPostFetchService(new ProfilePostFetchService(profileModel)) .setPostFetchService(new ProfilePostFetchService(profileModel, isLoggedIn))
.setLayoutPreferences(layoutPreferences) .setLayoutPreferences(layoutPreferences)
.addFetchStatusChangeListener(fetching -> updateSwipeRefreshState()) .addFetchStatusChangeListener(fetching -> updateSwipeRefreshState())
.setFeedItemCallback(feedItemCallback) .setFeedItemCallback(feedItemCallback)

View File

@ -6,7 +6,7 @@ import retrofit2.Call;
import retrofit2.http.GET; import retrofit2.http.GET;
import retrofit2.http.QueryMap; import retrofit2.http.QueryMap;
public interface FeedRepository { public interface GraphQLRepository {
@GET("/graphql/query/") @GET("/graphql/query/")
Call<String> fetch(@QueryMap(encoded = true) Map<String, String> queryParams); Call<String> fetch(@QueryMap(encoded = true) Map<String, String> queryParams);
} }

View File

@ -12,7 +12,4 @@ public interface LocationRepository {
@GET("/api/v1/feed/location/{location}/") @GET("/api/v1/feed/location/{location}/")
Call<String> fetchPosts(@Path("location") final String locationId, Call<String> fetchPosts(@Path("location") final String locationId,
@QueryMap Map<String, String> queryParams); @QueryMap Map<String, String> queryParams);
@GET("/graphql/query/")
Call<String> fetchGraphQLPosts(@QueryMap(encoded = true) Map<String, String> queryParams);
} }

View File

@ -9,15 +9,18 @@ import retrofit2.http.QueryMap;
public interface ProfileRepository { public interface ProfileRepository {
@GET("api/v1/users/{uid}/info/") @GET("/api/v1/users/{uid}/info/")
Call<String> getUserInfo(@Path("uid") final String uid); Call<String> getUserInfo(@Path("uid") final String uid);
@GET("/graphql/query/") @GET("/api/v1/feed/user/{uid}/")
Call<String> fetch(@QueryMap Map<String, String> queryMap); Call<String> fetch(@Path("uid") final String uid, @QueryMap Map<String, String> queryParams);
@GET("/api/v1/feed/saved/") @GET("/api/v1/feed/saved/")
Call<String> fetchSaved(@QueryMap Map<String, String> queryParams); Call<String> fetchSaved(@QueryMap Map<String, String> queryParams);
@GET("/api/v1/feed/liked/") @GET("/api/v1/feed/liked/")
Call<String> fetchLiked(@QueryMap Map<String, String> queryParams); Call<String> fetchLiked(@QueryMap Map<String, String> queryParams);
@GET("/api/v1/usertags/{profileId}/feed/")
Call<String> fetchTagged(@Path("profileId") final String profileId, @QueryMap Map<String, String> queryParams);
} }

View File

@ -17,9 +17,12 @@ public interface StoriesRepository {
@FormUrlEncoded @FormUrlEncoded
@POST("/api/v1/feed/reels_tray/") @POST("/api/v1/feed/reels_tray/")
Call<String> getStories(@Header("User-Agent") String userAgent, Call<String> getFeedStories(@Header("User-Agent") String userAgent,
@FieldMap Map<String, String> form); @FieldMap Map<String, String> form);
@GET("/api/v1/highlights/{uid}/highlights_tray/")
Call<String> fetchHighlights(@Path("uid") final String uid);
@GET @GET
Call<String> getUserStory(@Header("User-Agent") String userAgent, @Url String url); Call<String> getUserStory(@Header("User-Agent") String userAgent, @Url String url);

View File

@ -24,7 +24,4 @@ public interface TagsRepository {
@GET("/api/v1/feed/tag/{tag}/") @GET("/api/v1/feed/tag/{tag}/")
Call<String> fetchPosts(@Path("tag") final String tag, Call<String> fetchPosts(@Path("tag") final String tag,
@QueryMap Map<String, String> queryParams); @QueryMap Map<String, String> queryParams);
@GET("/graphql/query/")
Call<String> fetchGraphQLPosts(@QueryMap(encoded = true) Map<String, String> queryParams);
} }

View File

@ -21,10 +21,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import awais.instagrabber.models.FeedModel; import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostChild; import awais.instagrabber.repositories.GraphQLRepository;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.repositories.FeedRepository;
import awais.instagrabber.repositories.responses.PostsFetchResponse; import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.ResponseBodyUtils; import awais.instagrabber.utils.ResponseBodyUtils;
@ -34,29 +31,64 @@ import retrofit2.Callback;
import retrofit2.Response; import retrofit2.Response;
import retrofit2.Retrofit; import retrofit2.Retrofit;
public class FeedService extends BaseService { public class GraphQLService extends BaseService {
private static final String TAG = "FeedService"; private static final String TAG = "GraphQLService";
private static final boolean loadFromMock = false; private static final boolean loadFromMock = false;
private final FeedRepository repository; private final GraphQLRepository repository;
private static FeedService instance; private static GraphQLService instance;
private FeedService() { private GraphQLService() {
final Retrofit retrofit = getRetrofitBuilder() final Retrofit retrofit = getRetrofitBuilder()
.baseUrl("https://www.instagram.com") .baseUrl("https://www.instagram.com")
.build(); .build();
repository = retrofit.create(FeedRepository.class); repository = retrofit.create(GraphQLRepository.class);
} }
public static FeedService getInstance() { public static GraphQLService getInstance() {
if (instance == null) { if (instance == null) {
instance = new FeedService(); instance = new GraphQLService();
} }
return instance; return instance;
} }
public void fetch(final int maxItemsToLoad, private void fetch(final String queryHash,
final String variables,
final String arg1,
final String arg2,
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);
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<String> call, @NonNull final Throwable t) {
if (callback != null) {
callback.onFailure(t);
}
}
});
}
public void fetchFeed(final int maxItemsToLoad,
final String cursor, final String cursor,
final ServiceCallback<PostsFetchResponse> callback) { final ServiceCallback<PostsFetchResponse> callback) {
if (loadFromMock) { if (loadFromMock) {
@ -76,65 +108,89 @@ public class FeedService extends BaseService {
while ((charsRead = in.read(buffer, 0, buffer.length)) > 0) { while ((charsRead = in.read(buffer, 0, buffer.length)) > 0) {
out.append(buffer, 0, charsRead); out.append(buffer, 0, charsRead);
} }
callback.onSuccess(parseResponseBody(out.toString())); callback.onSuccess(parseResponseBody(out.toString(), Constants.EXTRAS_USER, "edge_web_feed_timeline"));
} catch (IOException | JSONException e) { } catch (IOException | JSONException e) {
Log.e(TAG, "fetch: ", e); Log.e(TAG, "fetch: ", e);
} }
}, 1000); }, 1000);
return; return;
} }
final Map<String, String> queryMap = new HashMap<>(); fetch("c699b185975935ae2a457f24075de8c7",
queryMap.put("query_hash", "c699b185975935ae2a457f24075de8c7"); "{\"fetch_media_item_count\":" + maxItemsToLoad + "," +
queryMap.put("variables", "{" +
"\"fetch_media_item_count\":" + maxItemsToLoad + "," +
"\"fetch_like\":3,\"has_stories\":false,\"has_stories\":false,\"has_threaded_comments\":true," + "\"fetch_like\":3,\"has_stories\":false,\"has_stories\":false,\"has_threaded_comments\":true," +
"\"fetch_media_item_cursor\":\"" + (cursor == null ? "" : cursor) + "\"" + "\"fetch_media_item_cursor\":\"" + (cursor == null ? "" : cursor) + "\"}",
"}"); Constants.EXTRAS_USER,
final Call<String> request = repository.fetch(queryMap); "edge_web_feed_timeline",
request.enqueue(new Callback<String>() { callback);
@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 = parseResponse(response);
if (callback != null) {
callback.onSuccess(postsFetchResponse);
}
} catch (JSONException e) {
Log.e(TAG, "onResponse", e);
if (callback != null) {
callback.onFailure(e);
}
}
} }
@Override public void fetchLocationPosts(@NonNull final String locationId,
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) { final String maxId,
if (callback != null) { final ServiceCallback<PostsFetchResponse> callback) {
callback.onFailure(t); 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<PostsFetchResponse> callback) {
fetch("9b498c08113f1e09617a1703c22b2f32",
"{\"tag_name\":\"" + tag + "\"," +
"\"first\":25," +
"\"after\":\"" + (maxId == null ? "" : maxId) + "\"}",
Constants.EXTRAS_HASHTAG,
"edge_hashtag_to_media",
callback);
}
public void fetchProfilePosts(@NonNull final String profileId,
final int postsPerPage,
final String maxId,
final ServiceCallback<PostsFetchResponse> callback) {
fetch("18a7b935ab438c4514b1f742d8fa07a7",
"{\"id\":\"" + profileId + "\"," +
"\"first\":" + postsPerPage + "," +
"\"after\":\"" + (maxId == null ? "" : maxId) + "\"}",
Constants.EXTRAS_USER,
"edge_owner_to_timeline_media",
callback);
}
public void fetchTaggedPosts(@NonNull final String profileId,
final int postsPerPage,
final String maxId,
final ServiceCallback<PostsFetchResponse> callback) {
fetch("31fe64d9463cbbe58319dced405c6206",
"{\"id\":\"" + profileId + "\"," +
"\"first\":" + postsPerPage + "," +
"\"after\":\"" + (maxId == null ? "" : maxId) + "\"}",
Constants.EXTRAS_USER,
"edge_user_to_photos_of_you",
callback);
} }
@NonNull @NonNull
private PostsFetchResponse parseResponse(@NonNull final Response<String> response) throws JSONException { private PostsFetchResponse parsePostResponse(@NonNull final Response<String> response, @NonNull final String arg1, @NonNull final String arg2) throws JSONException {
if (TextUtils.isEmpty(response.body())) { if (TextUtils.isEmpty(response.body())) {
Log.e(TAG, "parseResponse: feed response body is empty with status code: " + response.code()); Log.e(TAG, "parseResponse: feed response body is empty with status code: " + response.code());
return new PostsFetchResponse(Collections.emptyList(), false, null); return new PostsFetchResponse(Collections.emptyList(), false, null);
} }
return parseResponseBody(response.body()); return parseResponseBody(response.body(), arg1, arg2);
} }
@NonNull @NonNull
private PostsFetchResponse parseResponseBody(@NonNull final String body) private PostsFetchResponse parseResponseBody(@NonNull final String body, @NonNull final String arg1, @NonNull final String arg2)
throws JSONException { throws JSONException {
final List<FeedModel> feedModels = new ArrayList<>(); final List<FeedModel> feedModels = new ArrayList<>();
final JSONObject timelineFeed = new JSONObject(body) final JSONObject timelineFeed = new JSONObject(body)
.getJSONObject("data") .getJSONObject("data")
.getJSONObject(Constants.EXTRAS_USER) .getJSONObject(arg1)
.getJSONObject("edge_web_feed_timeline"); .getJSONObject(arg2);
final String endCursor; final String endCursor;
final boolean hasNextPage; final boolean hasNextPage;

View File

@ -19,6 +19,7 @@ import java.util.Objects;
import awais.instagrabber.models.FeedModel; import awais.instagrabber.models.FeedModel;
import awais.instagrabber.repositories.LocationRepository; import awais.instagrabber.repositories.LocationRepository;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.utils.ResponseBodyUtils; import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
import retrofit2.Call; import retrofit2.Call;
@ -29,7 +30,7 @@ import retrofit2.Retrofit;
public class LocationService extends BaseService { public class LocationService extends BaseService {
private static final String TAG = "LocationService"; private static final String TAG = "LocationService";
private final LocationRepository repository, webRepository; private final LocationRepository repository;
private static LocationService instance; private static LocationService instance;
@ -38,10 +39,6 @@ public class LocationService extends BaseService {
.baseUrl("https://i.instagram.com") .baseUrl("https://i.instagram.com")
.build(); .build();
repository = retrofit.create(LocationRepository.class); repository = retrofit.create(LocationRepository.class);
final Retrofit webRetrofit = getRetrofitBuilder()
.baseUrl("https://www.instagram.com")
.build();
webRepository = webRetrofit.create(LocationRepository.class);
} }
public static LocationService getInstance() { public static LocationService getInstance() {
@ -53,7 +50,7 @@ public class LocationService extends BaseService {
public void fetchPosts(@NonNull final String locationId, public void fetchPosts(@NonNull final String locationId,
final String maxId, final String maxId,
final ServiceCallback<LocationPostsFetchResponse> callback) { final ServiceCallback<PostsFetchResponse> callback) {
final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
if (!TextUtils.isEmpty(maxId)) { if (!TextUtils.isEmpty(maxId)) {
builder.put("max_id", maxId); builder.put("max_id", maxId);
@ -71,7 +68,7 @@ public class LocationService extends BaseService {
callback.onSuccess(null); callback.onSuccess(null);
return; return;
} }
final LocationPostsFetchResponse tagPostsFetchResponse = parseResponse(body); final PostsFetchResponse tagPostsFetchResponse = parseResponse(body);
callback.onSuccess(tagPostsFetchResponse); callback.onSuccess(tagPostsFetchResponse);
} catch (JSONException e) { } catch (JSONException e) {
Log.e(TAG, "onResponse", e); Log.e(TAG, "onResponse", e);
@ -88,7 +85,7 @@ public class LocationService extends BaseService {
}); });
} }
private LocationPostsFetchResponse parseResponse(@NonNull final String body) throws JSONException { private PostsFetchResponse parseResponse(@NonNull final String body) throws JSONException {
final JSONObject root = new JSONObject(body); final JSONObject root = new JSONObject(body);
final boolean moreAvailable = root.optBoolean("more_available"); final boolean moreAvailable = root.optBoolean("more_available");
final String nextMaxId = root.optString("next_max_id"); final String nextMaxId = root.optString("next_max_id");
@ -96,12 +93,10 @@ public class LocationService extends BaseService {
final String status = root.optString("status"); final String status = root.optString("status");
final JSONArray itemsJson = root.optJSONArray("items"); final JSONArray itemsJson = root.optJSONArray("items");
final List<FeedModel> items = parseItems(itemsJson); final List<FeedModel> items = parseItems(itemsJson);
return new LocationPostsFetchResponse( return new PostsFetchResponse(
items,
moreAvailable, moreAvailable,
nextMaxId, nextMaxId
numResults,
status,
items
); );
} }
@ -122,174 +117,4 @@ public class LocationService extends BaseService {
} }
return feedModels; return feedModels;
} }
public void fetchGraphQLPosts(@NonNull final String locationId,
final String maxId,
final ServiceCallback<LocationPostsFetchResponse> callback) {
final Map<String, String> queryMap = new HashMap<>();
queryMap.put("query_hash", "36bd0f2bf5911908de389b8ceaa3be6d");
queryMap.put("variables", "{" +
"\"id\":\"" + locationId + "\"," +
"\"first\":25," +
"\"after\":\"" + (maxId == null ? "" : maxId) + "\"" +
"}");
final Call<String> request = webRepository.fetchGraphQLPosts(queryMap);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
try {
if (callback == null) {
return;
}
final String body = response.body();
if (TextUtils.isEmpty(body)) {
callback.onSuccess(null);
return;
}
final LocationPostsFetchResponse tagPostsFetchResponse = parseGraphQLResponse(body);
callback.onSuccess(tagPostsFetchResponse);
} catch (JSONException e) {
Log.e(TAG, "onResponse", e);
callback.onFailure(e);
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
if (callback != null) {
callback.onFailure(t);
}
}
});
}
private LocationPostsFetchResponse parseGraphQLResponse(@NonNull final String body) throws JSONException {
final JSONObject rootroot = new JSONObject(body);
final JSONObject root = rootroot.getJSONObject("data").getJSONObject("location").getJSONObject("edge_location_to_media");
final boolean moreAvailable = root.getJSONObject("page_info").optBoolean("has_next_page");
final String nextMaxId = root.getJSONObject("page_info").optString("end_cursor");
final int numResults = root.optInt("count");
final String status = rootroot.optString("status");
final JSONArray itemsJson = root.optJSONArray("edges");
final List<FeedModel> items = parseGraphQLItems(itemsJson);
return new LocationPostsFetchResponse(
moreAvailable,
nextMaxId,
numResults,
status,
items
);
}
private List<FeedModel> parseGraphQLItems(final JSONArray items) throws JSONException {
if (items == null) {
return Collections.emptyList();
}
final List<FeedModel> feedModels = new ArrayList<>();
for (int i = 0; i < items.length(); i++) {
final JSONObject itemJson = items.optJSONObject(i);
if (itemJson == null) {
continue;
}
final FeedModel feedModel = ResponseBodyUtils.parseGraphQLItem(itemJson);
if (feedModel != null) {
feedModels.add(feedModel);
}
}
return feedModels;
}
public static class LocationPostsFetchResponse {
private boolean moreAvailable;
private String nextMaxId;
private int numResults;
private String status;
private List<FeedModel> items;
public LocationPostsFetchResponse(final boolean moreAvailable,
final String nextMaxId,
final int numResults,
final String status,
final List<FeedModel> items) {
this.moreAvailable = moreAvailable;
this.nextMaxId = nextMaxId;
this.numResults = numResults;
this.status = status;
this.items = items;
}
public boolean isMoreAvailable() {
return moreAvailable;
}
public LocationPostsFetchResponse setMoreAvailable(final boolean moreAvailable) {
this.moreAvailable = moreAvailable;
return this;
}
public String getNextMaxId() {
return nextMaxId;
}
public LocationPostsFetchResponse setNextMaxId(final String nextMaxId) {
this.nextMaxId = nextMaxId;
return this;
}
public int getNumResults() {
return numResults;
}
public LocationPostsFetchResponse setNumResults(final int numResults) {
this.numResults = numResults;
return this;
}
public String getStatus() {
return status;
}
public LocationPostsFetchResponse setStatus(final String status) {
this.status = status;
return this;
}
public List<FeedModel> getItems() {
return items;
}
public LocationPostsFetchResponse setItems(final List<FeedModel> items) {
this.items = items;
return this;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final LocationPostsFetchResponse that = (LocationPostsFetchResponse) o;
return moreAvailable == that.moreAvailable &&
numResults == that.numResults &&
Objects.equals(nextMaxId, that.nextMaxId) &&
Objects.equals(status, that.status) &&
Objects.equals(items, that.items);
}
@Override
public int hashCode() {
return Objects.hash(moreAvailable, nextMaxId, numResults, status, items);
}
@NonNull
@Override
public String toString() {
return "LocationPostsFetchResponse{" +
"moreAvailable=" + moreAvailable +
", nextMaxId='" + nextMaxId + '\'' +
", numResults=" + numResults +
", status='" + status + '\'' +
", items=" + items +
'}';
}
}
} }

View File

@ -12,15 +12,9 @@ import org.json.JSONObject;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects;
import awais.instagrabber.models.FeedModel; import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostChild;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.repositories.ProfileRepository; import awais.instagrabber.repositories.ProfileRepository;
import awais.instagrabber.repositories.responses.PostsFetchResponse; import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.repositories.responses.UserInfo; import awais.instagrabber.repositories.responses.UserInfo;
@ -36,7 +30,6 @@ public class ProfileService extends BaseService {
private static final String TAG = "ProfileService"; private static final String TAG = "ProfileService";
private final ProfileRepository repository; private final ProfileRepository repository;
private final ProfileRepository wwwRepository;
private static ProfileService instance; private static ProfileService instance;
@ -44,11 +37,7 @@ public class ProfileService extends BaseService {
final Retrofit retrofit = getRetrofitBuilder() final Retrofit retrofit = getRetrofitBuilder()
.baseUrl("https://i.instagram.com") .baseUrl("https://i.instagram.com")
.build(); .build();
final Retrofit wwwRetrofit = getRetrofitBuilder()
.baseUrl("https://www.instagram.com")
.build();
repository = retrofit.create(ProfileRepository.class); repository = retrofit.create(ProfileRepository.class);
wwwRepository = wwwRetrofit.create(ProfileRepository.class);
} }
public static ProfileService getInstance() { public static ProfileService getInstance() {
@ -89,34 +78,33 @@ public class ProfileService extends BaseService {
}); });
} }
public void fetchPosts(final ProfileModel profileModel, public void fetchPosts(final String userId,
final int postsPerPage, final String maxId,
final String cursor,
final ServiceCallback<PostsFetchResponse> callback) { final ServiceCallback<PostsFetchResponse> callback) {
final Map<String, String> queryMap = new HashMap<>(); final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
queryMap.put("query_hash", "18a7b935ab438c4514b1f742d8fa07a7"); if (!TextUtils.isEmpty(maxId)) {
queryMap.put("variables", "{" + builder.put("max_id", maxId);
"\"id\":\"" + profileModel.getId() + "\"," + }
"\"first\":" + postsPerPage + "," + final Call<String> request = repository.fetch(userId, builder.build());
"\"after\":\"" + (cursor == null ? "" : cursor) + "\"" +
"}");
final Call<String> request = wwwRepository.fetch(queryMap);
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) {
try { try {
// Log.d(TAG, "onResponse: body: " + response.body()); if (callback == null) {
final PostsFetchResponse postsFetchResponse = parseResponse(profileModel, response); return;
if (callback != null) {
callback.onSuccess(postsFetchResponse);
} }
final String body = response.body();
if (TextUtils.isEmpty(body)) {
callback.onSuccess(null);
return;
}
final PostsFetchResponse postsFetchResponse = parseProfilePostsResponse(body, false);
callback.onSuccess(postsFetchResponse);
} catch (JSONException e) { } catch (JSONException e) {
Log.e(TAG, "onResponse", e); Log.e(TAG, "onResponse", e);
if (callback != null) {
callback.onFailure(e); callback.onFailure(e);
} }
} }
}
@Override @Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) { public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
@ -127,48 +115,8 @@ public class ProfileService extends BaseService {
}); });
} }
private PostsFetchResponse parseResponse(final ProfileModel profileModel, final Response<String> response) 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(profileModel, response.body());
}
private PostsFetchResponse parseResponseBody(final ProfileModel profileModel, final String body) throws JSONException {
// Log.d(TAG, "parseResponseBody: body: " + body);
final List<FeedModel> feedModels = new ArrayList<>();
// return new FeedFetchResponse(feedModels, false, null);
final JSONObject mediaPosts = new JSONObject(body)
.getJSONObject("data")
.getJSONObject(Constants.EXTRAS_USER)
.getJSONObject("edge_owner_to_timeline_media");
final String endCursor;
final boolean hasNextPage;
final JSONObject pageInfo = mediaPosts.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 edges = mediaPosts.getJSONArray("edges");
for (int i = 0; i < edges.length(); ++i) {
final JSONObject itemJson = edges.optJSONObject(i);
if (itemJson == null) {
continue;
}
final FeedModel feedModel = ResponseBodyUtils.parseGraphQLItem(itemJson);
if (feedModel != null) {
feedModels.add(feedModel);
}
}
return new PostsFetchResponse(feedModels, hasNextPage, endCursor);
}
public void fetchSaved(final String maxId, public void fetchSaved(final String maxId,
final ServiceCallback<SavedPostsFetchResponse> callback) { final ServiceCallback<PostsFetchResponse> callback) {
final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
if (!TextUtils.isEmpty(maxId)) { if (!TextUtils.isEmpty(maxId)) {
builder.put("max_id", maxId); builder.put("max_id", maxId);
@ -186,8 +134,8 @@ public class ProfileService extends BaseService {
callback.onSuccess(null); callback.onSuccess(null);
return; return;
} }
final SavedPostsFetchResponse savedPostsFetchResponse = parseSavedPostsResponse(body, true); final PostsFetchResponse PostsFetchResponse = parseSavedPostsResponse(body, true);
callback.onSuccess(savedPostsFetchResponse); callback.onSuccess(PostsFetchResponse);
} catch (JSONException e) { } catch (JSONException e) {
Log.e(TAG, "onResponse", e); Log.e(TAG, "onResponse", e);
callback.onFailure(e); callback.onFailure(e);
@ -204,7 +152,7 @@ public class ProfileService extends BaseService {
} }
public void fetchLiked(final String maxId, public void fetchLiked(final String maxId,
final ServiceCallback<SavedPostsFetchResponse> callback) { final ServiceCallback<PostsFetchResponse> callback) {
final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
if (!TextUtils.isEmpty(maxId)) { if (!TextUtils.isEmpty(maxId)) {
builder.put("max_id", maxId); builder.put("max_id", maxId);
@ -222,8 +170,8 @@ public class ProfileService extends BaseService {
callback.onSuccess(null); callback.onSuccess(null);
return; return;
} }
final SavedPostsFetchResponse savedPostsFetchResponse = parseSavedPostsResponse(body, false); final PostsFetchResponse PostsFetchResponse = parseSavedPostsResponse(body, false);
callback.onSuccess(savedPostsFetchResponse); callback.onSuccess(PostsFetchResponse);
} catch (JSONException e) { } catch (JSONException e) {
Log.e(TAG, "onResponse", e); Log.e(TAG, "onResponse", e);
callback.onFailure(e); callback.onFailure(e);
@ -240,17 +188,13 @@ public class ProfileService extends BaseService {
} }
public void fetchTagged(final String profileId, public void fetchTagged(final String profileId,
final int postsPerPage, final String maxId,
final String cursor, final ServiceCallback<PostsFetchResponse> callback) {
final ServiceCallback<SavedPostsFetchResponse> callback) { final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
final Map<String, String> queryMap = new HashMap<>(); if (!TextUtils.isEmpty(maxId)) {
queryMap.put("query_hash", "31fe64d9463cbbe58319dced405c6206"); builder.put("max_id", maxId);
queryMap.put("variables", "{" + }
"\"id\":\"" + profileId + "\"," + final Call<String> request = repository.fetchTagged(profileId, builder.build());
"\"first\":" + postsPerPage + "," +
"\"after\":\"" + (cursor == null ? "" : cursor) + "\"" +
"}");
final Call<String> request = wwwRepository.fetch(queryMap);
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) {
@ -263,8 +207,8 @@ public class ProfileService extends BaseService {
callback.onSuccess(null); callback.onSuccess(null);
return; return;
} }
final SavedPostsFetchResponse savedPostsFetchResponse = parseTaggedPostsResponse(body); final PostsFetchResponse PostsFetchResponse = parseSavedPostsResponse(body, false);
callback.onSuccess(savedPostsFetchResponse); callback.onSuccess(PostsFetchResponse);
} catch (JSONException e) { } catch (JSONException e) {
Log.e(TAG, "onResponse", e); Log.e(TAG, "onResponse", e);
callback.onFailure(e); callback.onFailure(e);
@ -280,7 +224,7 @@ public class ProfileService extends BaseService {
}); });
} }
private SavedPostsFetchResponse parseSavedPostsResponse(final String body, final boolean isInMedia) throws JSONException { private PostsFetchResponse parseProfilePostsResponse(final String body, final boolean isInMedia) throws JSONException {
final JSONObject root = new JSONObject(body); final JSONObject root = new JSONObject(body);
final boolean moreAvailable = root.optBoolean("more_available"); final boolean moreAvailable = root.optBoolean("more_available");
final String nextMaxId = root.optString("next_max_id"); final String nextMaxId = root.optString("next_max_id");
@ -288,48 +232,26 @@ public class ProfileService extends BaseService {
final String status = root.optString("status"); final String status = root.optString("status");
final JSONArray itemsJson = root.optJSONArray("items"); final JSONArray itemsJson = root.optJSONArray("items");
final List<FeedModel> items = parseItems(itemsJson, isInMedia); final List<FeedModel> items = parseItems(itemsJson, isInMedia);
return new SavedPostsFetchResponse( return new PostsFetchResponse(
items,
moreAvailable, moreAvailable,
nextMaxId, nextMaxId
numResults,
status,
items
); );
} }
@NonNull private PostsFetchResponse parseSavedPostsResponse(final String body, final boolean isInMedia) throws JSONException {
private SavedPostsFetchResponse parseTaggedPostsResponse(@NonNull final String body) final JSONObject root = new JSONObject(body);
throws JSONException { final boolean moreAvailable = root.optBoolean("more_available");
final List<FeedModel> feedModels = new ArrayList<>(); final String nextMaxId = root.optString("next_max_id");
final JSONObject timelineFeed = new JSONObject(body) final int numResults = root.optInt("num_results");
.getJSONObject("data") final String status = root.optString("status");
.getJSONObject(Constants.EXTRAS_USER) final JSONArray itemsJson = root.optJSONArray("items");
.getJSONObject("edge_user_to_photos_of_you"); final List<FeedModel> items = parseItems(itemsJson, isInMedia);
final String endCursor; return new PostsFetchResponse(
final boolean hasNextPage; items,
moreAvailable,
final JSONObject pageInfo = timelineFeed.getJSONObject("page_info"); nextMaxId
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 FeedModel feedModel = ResponseBodyUtils.parseGraphQLItem(itemJson);
if (feedModel != null) {
feedModels.add(feedModel);
}
}
return new SavedPostsFetchResponse(hasNextPage, endCursor, timelineFeed.getInt("count"), "ok", feedModels);
} }
private List<FeedModel> parseItems(final JSONArray items, final boolean isInMedia) throws JSONException { private List<FeedModel> parseItems(final JSONArray items, final boolean isInMedia) throws JSONException {
@ -349,98 +271,4 @@ public class ProfileService extends BaseService {
} }
return feedModels; return feedModels;
} }
public static class SavedPostsFetchResponse {
private boolean moreAvailable;
private String nextMaxId;
private int numResults;
private String status;
private List<FeedModel> items;
public SavedPostsFetchResponse(final boolean moreAvailable,
final String nextMaxId,
final int numResults,
final String status,
final List<FeedModel> items) {
this.moreAvailable = moreAvailable;
this.nextMaxId = nextMaxId;
this.numResults = numResults;
this.status = status;
this.items = items;
}
public boolean isMoreAvailable() {
return moreAvailable;
}
public SavedPostsFetchResponse setMoreAvailable(final boolean moreAvailable) {
this.moreAvailable = moreAvailable;
return this;
}
public String getNextMaxId() {
return nextMaxId;
}
public SavedPostsFetchResponse setNextMaxId(final String nextMaxId) {
this.nextMaxId = nextMaxId;
return this;
}
public int getNumResults() {
return numResults;
}
public SavedPostsFetchResponse setNumResults(final int numResults) {
this.numResults = numResults;
return this;
}
public String getStatus() {
return status;
}
public SavedPostsFetchResponse setStatus(final String status) {
this.status = status;
return this;
}
public List<FeedModel> getItems() {
return items;
}
public SavedPostsFetchResponse setItems(final List<FeedModel> items) {
this.items = items;
return this;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final SavedPostsFetchResponse that = (SavedPostsFetchResponse) o;
return moreAvailable == that.moreAvailable &&
numResults == that.numResults &&
Objects.equals(nextMaxId, that.nextMaxId) &&
Objects.equals(status, that.status) &&
Objects.equals(items, that.items);
}
@Override
public int hashCode() {
return Objects.hash(moreAvailable, nextMaxId, numResults, status, items);
}
@NonNull
@Override
public String toString() {
return "SavedPostsFetchResponse{" +
"moreAvailable=" + moreAvailable +
", nextMaxId='" + nextMaxId + '\'' +
", numResults=" + numResults +
", status='" + status + '\'' +
", items=" + items +
'}';
}
}
} }

View File

@ -21,6 +21,7 @@ import java.util.Map;
import java.util.UUID; import java.util.UUID;
import awais.instagrabber.models.FeedStoryModel; import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.models.HighlightModel;
import awais.instagrabber.models.ProfileModel; import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.StoryModel; import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.MediaItemType; import awais.instagrabber.models.enums.MediaItemType;
@ -33,6 +34,7 @@ import awais.instagrabber.repositories.StoriesRepository;
import awais.instagrabber.repositories.responses.StoryStickerResponse; import awais.instagrabber.repositories.responses.StoryStickerResponse;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.ResponseBodyUtils; import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.Callback; import retrofit2.Callback;
@ -92,7 +94,7 @@ public class StoriesService extends BaseService {
form.put("_uuid", UUID.randomUUID().toString()); form.put("_uuid", UUID.randomUUID().toString());
form.put("supported_capabilities_new", Constants.SUPPORTED_CAPABILITIES); form.put("supported_capabilities_new", Constants.SUPPORTED_CAPABILITIES);
final Map<String, String> signedForm = Utils.sign(form); final Map<String, String> signedForm = Utils.sign(form);
final Call<String> response = repository.getStories(Constants.I_USER_AGENT, signedForm); final Call<String> response = repository.getFeedStories(Constants.I_USER_AGENT, signedForm);
response.enqueue(new Callback<String>() { response.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) {
@ -134,6 +136,52 @@ public class StoriesService extends BaseService {
} }
} }
public void fetchHighlights(final String profileId,
final ServiceCallback<List<HighlightModel>> callback) {
final Call<String> request = repository.fetchHighlights(profileId);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
try {
if (callback == null) {
return;
}
final String body = response.body();
if (TextUtils.isEmpty(body)) {
callback.onSuccess(null);
return;
}
final JSONArray highlightsReel = new JSONObject(body).getJSONArray("tray");
final int length = highlightsReel.length();
final List<HighlightModel> highlightModels = new ArrayList<>();
// final String[] highlightIds = new String[length];
for (int i = 0; i < length; ++i) {
final JSONObject highlightNode = highlightsReel.getJSONObject(i);
highlightModels.add(new HighlightModel(
highlightNode.getString("title"),
highlightNode.getString(Constants.EXTRAS_ID),
highlightNode.getJSONObject("cover_media")
.getJSONObject("cropped_image_version")
.getString("url")
));
}
callback.onSuccess(highlightModels);
} catch (JSONException e) {
Log.e(TAG, "onResponse", e);
callback.onFailure(e);
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
if (callback != null) {
callback.onFailure(t);
}
}
});
}
public void getUserStory(final String id, public void getUserStory(final String id,
final String username, final String username,
final boolean isLoc, final boolean isLoc,

View File

@ -19,6 +19,7 @@ import java.util.Objects;
import awais.instagrabber.models.FeedModel; import awais.instagrabber.models.FeedModel;
import awais.instagrabber.repositories.TagsRepository; import awais.instagrabber.repositories.TagsRepository;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.ResponseBodyUtils; import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
@ -118,8 +119,8 @@ public class TagsService extends BaseService {
public void fetchPosts(@NonNull final String tag, public void fetchPosts(@NonNull final String tag,
final String maxId, final String maxId,
final ServiceCallback<TagPostsFetchResponse> callback) { final ServiceCallback<PostsFetchResponse> callback) {
final ImmutableMap.Builder<String, String> builder = ImmutableMap.<String, String>builder(); final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
if (!TextUtils.isEmpty(maxId)) { if (!TextUtils.isEmpty(maxId)) {
builder.put("max_id", maxId); builder.put("max_id", maxId);
} }
@ -136,7 +137,7 @@ public class TagsService extends BaseService {
callback.onSuccess(null); callback.onSuccess(null);
return; return;
} }
final TagPostsFetchResponse tagPostsFetchResponse = parseResponse(body); final PostsFetchResponse tagPostsFetchResponse = parseResponse(body);
callback.onSuccess(tagPostsFetchResponse); callback.onSuccess(tagPostsFetchResponse);
} catch (JSONException e) { } catch (JSONException e) {
Log.e(TAG, "onResponse", e); Log.e(TAG, "onResponse", e);
@ -153,7 +154,7 @@ public class TagsService extends BaseService {
}); });
} }
private TagPostsFetchResponse parseResponse(@NonNull final String body) throws JSONException { private PostsFetchResponse parseResponse(@NonNull final String body) throws JSONException {
final JSONObject root = new JSONObject(body); final JSONObject root = new JSONObject(body);
final boolean moreAvailable = root.optBoolean("more_available"); final boolean moreAvailable = root.optBoolean("more_available");
final String nextMaxId = root.optString("next_max_id"); final String nextMaxId = root.optString("next_max_id");
@ -161,12 +162,10 @@ public class TagsService extends BaseService {
final String status = root.optString("status"); final String status = root.optString("status");
final JSONArray itemsJson = root.optJSONArray("items"); final JSONArray itemsJson = root.optJSONArray("items");
final List<FeedModel> items = parseItems(itemsJson); final List<FeedModel> items = parseItems(itemsJson);
return new TagPostsFetchResponse( return new PostsFetchResponse(
items,
moreAvailable, moreAvailable,
nextMaxId, nextMaxId
numResults,
status,
items
); );
} }
@ -187,174 +186,4 @@ public class TagsService extends BaseService {
} }
return feedModels; return feedModels;
} }
public void fetchGraphQLPosts(@NonNull final String tag,
final String maxId,
final ServiceCallback<TagPostsFetchResponse> callback) {
final Map<String, String> queryMap = new HashMap<>();
queryMap.put("query_hash", "9b498c08113f1e09617a1703c22b2f32");
queryMap.put("variables", "{" +
"\"tag_name\":\"" + tag + "\"," +
"\"first\":25," +
"\"after\":\"" + (maxId == null ? "" : maxId) + "\"" +
"}");
final Call<String> request = webRepository.fetchGraphQLPosts(queryMap);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
try {
if (callback == null) {
return;
}
final String body = response.body();
if (TextUtils.isEmpty(body)) {
callback.onSuccess(null);
return;
}
final TagPostsFetchResponse tagPostsFetchResponse = parseGraphQLResponse(body);
callback.onSuccess(tagPostsFetchResponse);
} catch (JSONException e) {
Log.e(TAG, "onResponse", e);
callback.onFailure(e);
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
if (callback != null) {
callback.onFailure(t);
}
}
});
}
private TagPostsFetchResponse parseGraphQLResponse(@NonNull final String body) throws JSONException {
final JSONObject rootroot = new JSONObject(body);
final JSONObject root = rootroot.getJSONObject("data").getJSONObject("hashtag").getJSONObject("edge_hashtag_to_media");
final boolean moreAvailable = root.getJSONObject("page_info").optBoolean("has_next_page");
final String nextMaxId = root.getJSONObject("page_info").optString("end_cursor");
final int numResults = root.optInt("count");
final String status = rootroot.optString("status");
final JSONArray itemsJson = root.optJSONArray("edges");
final List<FeedModel> items = parseGraphQLItems(itemsJson);
return new TagPostsFetchResponse(
moreAvailable,
nextMaxId,
numResults,
status,
items
);
}
private List<FeedModel> parseGraphQLItems(final JSONArray items) throws JSONException {
if (items == null) {
return Collections.emptyList();
}
final List<FeedModel> feedModels = new ArrayList<>();
for (int i = 0; i < items.length(); i++) {
final JSONObject itemJson = items.optJSONObject(i);
if (itemJson == null) {
continue;
}
final FeedModel feedModel = ResponseBodyUtils.parseGraphQLItem(itemJson);
if (feedModel != null) {
feedModels.add(feedModel);
}
}
return feedModels;
}
public static class TagPostsFetchResponse {
private boolean moreAvailable;
private String nextMaxId;
private int numResults;
private String status;
private List<FeedModel> items;
public TagPostsFetchResponse(final boolean moreAvailable,
final String nextMaxId,
final int numResults,
final String status,
final List<FeedModel> items) {
this.moreAvailable = moreAvailable;
this.nextMaxId = nextMaxId;
this.numResults = numResults;
this.status = status;
this.items = items;
}
public boolean isMoreAvailable() {
return moreAvailable;
}
public TagPostsFetchResponse setMoreAvailable(final boolean moreAvailable) {
this.moreAvailable = moreAvailable;
return this;
}
public String getNextMaxId() {
return nextMaxId;
}
public TagPostsFetchResponse setNextMaxId(final String nextMaxId) {
this.nextMaxId = nextMaxId;
return this;
}
public int getNumResults() {
return numResults;
}
public TagPostsFetchResponse setNumResults(final int numResults) {
this.numResults = numResults;
return this;
}
public String getStatus() {
return status;
}
public TagPostsFetchResponse setStatus(final String status) {
this.status = status;
return this;
}
public List<FeedModel> getItems() {
return items;
}
public TagPostsFetchResponse setItems(final List<FeedModel> items) {
this.items = items;
return this;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final TagPostsFetchResponse that = (TagPostsFetchResponse) o;
return moreAvailable == that.moreAvailable &&
numResults == that.numResults &&
Objects.equals(nextMaxId, that.nextMaxId) &&
Objects.equals(status, that.status) &&
Objects.equals(items, that.items);
}
@Override
public int hashCode() {
return Objects.hash(moreAvailable, nextMaxId, numResults, status, items);
}
@NonNull
@Override
public String toString() {
return "TagPostsFetchResponse{" +
"moreAvailable=" + moreAvailable +
", nextMaxId='" + nextMaxId + '\'' +
", numResults=" + numResults +
", status='" + status + '\'' +
", items=" + items +
'}';
}
}
} }