implement app feed endpoint instead of browser
This commit is contained in:
parent
71264bef96
commit
22fc894c9d
@ -1,34 +1,55 @@
|
||||
package awais.instagrabber.asyncs;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import awais.instagrabber.customviews.helpers.PostFetcher;
|
||||
import awais.instagrabber.interfaces.FetchListener;
|
||||
import awais.instagrabber.models.FeedModel;
|
||||
import awais.instagrabber.repositories.responses.PostsFetchResponse;
|
||||
import awais.instagrabber.webservices.GraphQLService;
|
||||
import awais.instagrabber.utils.Constants;
|
||||
import awais.instagrabber.utils.CookieUtils;
|
||||
import awais.instagrabber.webservices.FeedService;
|
||||
import awais.instagrabber.webservices.ServiceCallback;
|
||||
|
||||
import static awais.instagrabber.utils.Utils.settingsHelper;
|
||||
|
||||
public class FeedPostFetchService implements PostFetcher.PostFetchService {
|
||||
private static final String TAG = "FeedPostFetchService";
|
||||
private final GraphQLService graphQLService;
|
||||
private final FeedService feedService;
|
||||
private String nextCursor;
|
||||
private boolean hasNextPage;
|
||||
|
||||
public FeedPostFetchService() {
|
||||
graphQLService = GraphQLService.getInstance();
|
||||
feedService = FeedService.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetch(final FetchListener<List<FeedModel>> fetchListener) {
|
||||
graphQLService.fetchFeed(25, nextCursor, new ServiceCallback<PostsFetchResponse>() {
|
||||
final List<FeedModel> feedModels = new ArrayList<>();
|
||||
final String cookie = settingsHelper.getString(Constants.COOKIE);
|
||||
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
|
||||
feedModels.clear();
|
||||
feedService.fetch(csrfToken, nextCursor, new ServiceCallback<PostsFetchResponse>() {
|
||||
@Override
|
||||
public void onSuccess(final PostsFetchResponse result) {
|
||||
if (result == null) return;
|
||||
if (result == null && feedModels.size() > 0) {
|
||||
fetchListener.onResult(feedModels);
|
||||
return;
|
||||
}
|
||||
else if (result == null) return;
|
||||
nextCursor = result.getNextCursor();
|
||||
hasNextPage = result.hasNextPage();
|
||||
feedModels.addAll(result.getFeedModels());
|
||||
if (fetchListener != null) {
|
||||
fetchListener.onResult(result.getFeedModels());
|
||||
if (feedModels.size() < 15 && hasNextPage) {
|
||||
feedService.fetch(csrfToken, nextCursor, this);
|
||||
}
|
||||
else {
|
||||
fetchListener.onResult(feedModels);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,14 @@
|
||||
package awais.instagrabber.repositories;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.FieldMap;
|
||||
import retrofit2.http.FormUrlEncoded;
|
||||
import retrofit2.http.POST;
|
||||
|
||||
public interface FeedRepository {
|
||||
@FormUrlEncoded
|
||||
@POST("/api/v1/feed/timeline/")
|
||||
Call<String> fetch(@FieldMap final Map<String, String> signedForm);
|
||||
}
|
@ -0,0 +1,137 @@
|
||||
package awais.instagrabber.webservices;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
import java.util.UUID;
|
||||
|
||||
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.FeedRepository;
|
||||
import awais.instagrabber.repositories.responses.PostsFetchResponse;
|
||||
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 FeedService extends BaseService {
|
||||
private static final String TAG = "FeedService";
|
||||
|
||||
private final FeedRepository repository;
|
||||
|
||||
private static FeedService instance;
|
||||
|
||||
private FeedService() {
|
||||
final Retrofit retrofit = getRetrofitBuilder()
|
||||
.baseUrl("https://i.instagram.com")
|
||||
.build();
|
||||
repository = retrofit.create(FeedRepository.class);
|
||||
}
|
||||
|
||||
public static FeedService getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new FeedService();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void fetch(final String csrfToken,
|
||||
final String cursor,
|
||||
final ServiceCallback<PostsFetchResponse> callback) {
|
||||
final Map<String, String> form = new HashMap<>();
|
||||
form.put("_uuid", UUID.randomUUID().toString());
|
||||
form.put("_csrftoken", csrfToken);
|
||||
form.put("phone_id", UUID.randomUUID().toString());
|
||||
form.put("device_id", UUID.randomUUID().toString());
|
||||
form.put("client_session_id", UUID.randomUUID().toString());
|
||||
form.put("is_prefetch", "0");
|
||||
form.put("timezone_offset", String.valueOf(TimeZone.getDefault().getRawOffset() / 1000));
|
||||
if (!TextUtils.isEmpty(cursor)) {
|
||||
form.put("max_id", cursor);
|
||||
form.put("reason", "pagination");
|
||||
}
|
||||
else {
|
||||
form.put("is_pull_to_refresh", "1");
|
||||
form.put("reason", "pull_to_refresh");
|
||||
}
|
||||
final Call<String> request = repository.fetch(form);
|
||||
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 = 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 onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
|
||||
if (callback != null) {
|
||||
callback.onFailure(t);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private PostsFetchResponse parseResponse(@NonNull 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(response.body());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private PostsFetchResponse parseResponseBody(@NonNull final String body)
|
||||
throws JSONException {
|
||||
final JSONObject root = new JSONObject(body);
|
||||
final boolean moreAvailable = root.optBoolean("more_available");
|
||||
final String nextMaxId = root.optString("next_max_id");
|
||||
final JSONArray feedItems = root.optJSONArray("items");
|
||||
final List<FeedModel> feedModels = new ArrayList<>();
|
||||
for (int i = 0; i < feedItems.length(); ++i) {
|
||||
final JSONObject itemJson = feedItems.optJSONObject(i);
|
||||
if (itemJson == null || itemJson.has("injected")
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
final FeedModel feedModel = ResponseBodyUtils.parseItem(itemJson);
|
||||
if (feedModel != null) {
|
||||
feedModels.add(feedModel);
|
||||
}
|
||||
}
|
||||
return new PostsFetchResponse(feedModels, moreAvailable, nextMaxId);
|
||||
}
|
||||
}
|
@ -88,42 +88,6 @@ public class GraphQLService extends BaseService {
|
||||
});
|
||||
}
|
||||
|
||||
public void fetchFeed(final int maxItemsToLoad,
|
||||
final String cursor,
|
||||
final ServiceCallback<PostsFetchResponse> callback) {
|
||||
if (loadFromMock) {
|
||||
final Handler handler = new Handler();
|
||||
handler.postDelayed(() -> {
|
||||
final ClassLoader classLoader = getClass().getClassLoader();
|
||||
if (classLoader == null) {
|
||||
Log.e(TAG, "fetch: classLoader is null!");
|
||||
return;
|
||||
}
|
||||
try (InputStream resourceAsStream = classLoader.getResourceAsStream("feed_response.json");
|
||||
Reader in = new InputStreamReader(resourceAsStream, StandardCharsets.UTF_8)) {
|
||||
final int bufferSize = 1024;
|
||||
final char[] buffer = new char[bufferSize];
|
||||
final StringBuilder out = new StringBuilder();
|
||||
int charsRead;
|
||||
while ((charsRead = in.read(buffer, 0, buffer.length)) > 0) {
|
||||
out.append(buffer, 0, charsRead);
|
||||
}
|
||||
callback.onSuccess(parseResponseBody(out.toString(), Constants.EXTRAS_USER, "edge_web_feed_timeline"));
|
||||
} catch (IOException | JSONException e) {
|
||||
Log.e(TAG, "fetch: ", e);
|
||||
}
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
fetch("c699b185975935ae2a457f24075de8c7",
|
||||
"{\"fetch_media_item_count\":" + maxItemsToLoad + "," +
|
||||
"\"fetch_like\":3,\"has_stories\":false,\"has_stories\":false,\"has_threaded_comments\":true," +
|
||||
"\"fetch_media_item_cursor\":\"" + (cursor == null ? "" : cursor) + "\"}",
|
||||
Constants.EXTRAS_USER,
|
||||
"edge_web_feed_timeline",
|
||||
callback);
|
||||
}
|
||||
|
||||
public void fetchLocationPosts(@NonNull final String locationId,
|
||||
final String maxId,
|
||||
final ServiceCallback<PostsFetchResponse> callback) {
|
||||
|
@ -89,8 +89,6 @@ public class LocationService extends BaseService {
|
||||
final JSONObject root = new JSONObject(body);
|
||||
final boolean moreAvailable = root.optBoolean("more_available");
|
||||
final String nextMaxId = root.optString("next_max_id");
|
||||
final int numResults = root.optInt("num_results");
|
||||
final String status = root.optString("status");
|
||||
final JSONArray itemsJson = root.optJSONArray("items");
|
||||
final List<FeedModel> items = parseItems(itemsJson);
|
||||
return new PostsFetchResponse(
|
||||
|
@ -228,8 +228,6 @@ public class ProfileService extends BaseService {
|
||||
final JSONObject root = new JSONObject(body);
|
||||
final boolean moreAvailable = root.optBoolean("more_available");
|
||||
final String nextMaxId = root.optString("next_max_id");
|
||||
final int numResults = root.optInt("num_results");
|
||||
final String status = root.optString("status");
|
||||
final JSONArray itemsJson = root.optJSONArray("items");
|
||||
final List<FeedModel> items = parseItems(itemsJson, isInMedia);
|
||||
return new PostsFetchResponse(
|
||||
|
@ -158,8 +158,6 @@ public class TagsService extends BaseService {
|
||||
final JSONObject root = new JSONObject(body);
|
||||
final boolean moreAvailable = root.optBoolean("more_available");
|
||||
final String nextMaxId = root.optString("next_max_id");
|
||||
final int numResults = root.optInt("num_results");
|
||||
final String status = root.optString("status");
|
||||
final JSONArray itemsJson = root.optJSONArray("items");
|
||||
final List<FeedModel> items = parseItems(itemsJson);
|
||||
return new PostsFetchResponse(
|
||||
|
Loading…
Reference in New Issue
Block a user