implement app feed endpoint instead of browser

This commit is contained in:
Austin Huang 2020-12-22 11:54:52 -05:00
parent 71264bef96
commit 22fc894c9d
No known key found for this signature in database
GPG Key ID: 84C23AA04587A91F
7 changed files with 178 additions and 48 deletions

View File

@ -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);
}
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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) {

View File

@ -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(

View File

@ -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(

View File

@ -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(