merge discover and feed api's and convert them to kotlin

This commit is contained in:
Austin Huang 2021-07-25 17:39:57 -04:00
parent 2099aa8676
commit 32fe0edb55
No known key found for this signature in database
GPG Key ID: 84C23AA04587A91F
9 changed files with 166 additions and 283 deletions

View File

@ -10,52 +10,49 @@ import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.discover.TopicalExploreFeedResponse;
import awais.instagrabber.repositories.responses.WrappedMedia;
import awais.instagrabber.webservices.DiscoverService;
import awais.instagrabber.utils.CoroutineUtilsKt;
import awais.instagrabber.webservices.FeedRepository;
import awais.instagrabber.webservices.ServiceCallback;
public class DiscoverPostFetchService implements PostFetcher.PostFetchService {
private static final String TAG = "DiscoverPostFetchService";
private final DiscoverService discoverService;
private final FeedRepository feedRepository;
private String maxId;
private boolean moreAvailable = false;
public DiscoverPostFetchService() {
discoverService = DiscoverService.getInstance();
feedRepository = FeedRepository.Companion.getInstance();
}
@Override
public void fetch(final FetchListener<List<Media>> fetchListener) {
discoverService.topicalExplore(maxId, new ServiceCallback<TopicalExploreFeedResponse>() {
@Override
public void onSuccess(final TopicalExploreFeedResponse result) {
if (result == null) {
onFailure(new RuntimeException("result is null"));
return;
}
moreAvailable = result.getMoreAvailable();
maxId = result.getNextMaxId();
final List<WrappedMedia> items = result.getItems();
final List<Media> posts;
if (items == null) {
posts = Collections.emptyList();
} else {
posts = items.stream()
.map(WrappedMedia::getMedia)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
if (fetchListener != null) {
fetchListener.onResult(posts);
}
}
@Override
public void onFailure(final Throwable t) {
feedRepository.topicalExplore(maxId, CoroutineUtilsKt.getContinuation((result, t) -> {
if (t != null) {
if (fetchListener != null) {
fetchListener.onFailure(t);
}
return;
}
});
if (result == null) {
fetchListener.onFailure(new RuntimeException("result is null"));
return;
}
moreAvailable = result.getMoreAvailable();
maxId = result.getNextMaxId();
final List<WrappedMedia> items = result.getItems();
final List<Media> posts;
if (items == null) {
posts = Collections.emptyList();
} else {
posts = items.stream()
.map(WrappedMedia::getMedia)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
if (fetchListener != null) {
fetchListener.onResult(posts);
}
}));
}
@Override

View File

@ -9,19 +9,20 @@ import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.webservices.FeedService;
import awais.instagrabber.utils.CoroutineUtilsKt;
import awais.instagrabber.webservices.FeedRepository;
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 FeedService feedService;
private final FeedRepository feedRepository;
private String nextCursor;
private boolean hasNextPage;
public FeedPostFetchService() {
feedService = FeedService.getInstance();
feedRepository = FeedRepository.Companion.getInstance();
}
@Override
@ -31,35 +32,27 @@ public class FeedPostFetchService implements PostFetcher.PostFetchService {
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
final String deviceUuid = settingsHelper.getString(Constants.DEVICE_UUID);
feedModels.clear();
feedService.fetch(csrfToken, deviceUuid, nextCursor, new ServiceCallback<PostsFetchResponse>() {
@Override
public void onSuccess(final PostsFetchResponse result) {
if (result == null && feedModels.size() > 0) {
fetchListener.onResult(feedModels);
return;
} else if (result == null) return;
nextCursor = result.getNextCursor();
hasNextPage = result.getHasNextPage();
final List<Media> mediaResults = result.getFeedModels();
feedModels.addAll(mediaResults);
if (fetchListener != null) {
// if (feedModels.size() < 15 && hasNextPage) {
// feedService.fetch(csrfToken, nextCursor, this);
// } else {
fetchListener.onResult(feedModels);
// }
}
}
@Override
public void onFailure(final Throwable t) {
feedRepository.fetchFeed(csrfToken, deviceUuid, nextCursor, CoroutineUtilsKt.getContinuation((result, t) -> {
if (t != null) {
if (fetchListener != null) {
fetchListener.onFailure(t);
}
return;
}
});
if (result == null && feedModels.size() > 0) {
fetchListener.onResult(feedModels);
return;
} else if (result == null) return;
nextCursor = result.getNextCursor();
hasNextPage = result.getHasNextPage();
final List<Media> mediaResults = result.getFeedModels();
feedModels.addAll(mediaResults);
if (fetchListener != null) {
fetchListener.onResult(feedModels);
}
}));
}
@Override

View File

@ -16,7 +16,6 @@ import androidx.activity.OnBackPressedCallback;
import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
@ -34,7 +33,6 @@ import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.databinding.FragmentDiscoverBinding;
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.repositories.responses.Location;
import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.User;
@ -44,7 +42,6 @@ import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.webservices.DiscoverService;
import static awais.instagrabber.utils.Utils.settingsHelper;

View File

@ -1,15 +0,0 @@
package awais.instagrabber.repositories;
import java.util.Map;
import awais.instagrabber.repositories.responses.feed.FeedFetchResponse;
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<FeedFetchResponse> fetch(@FieldMap final Map<String, String> signedForm);
}

View File

@ -1,13 +1,15 @@
package awais.instagrabber.repositories;
package awais.instagrabber.repositories
import java.util.Map;
import awais.instagrabber.repositories.responses.discover.TopicalExploreFeedResponse
import awais.instagrabber.repositories.responses.feed.FeedFetchResponse
import retrofit2.Call
import retrofit2.http.*
import awais.instagrabber.repositories.responses.discover.TopicalExploreFeedResponse;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.QueryMap;
public interface DiscoverRepository {
interface FeedService {
@GET("/api/v1/discover/topical_explore/")
Call<TopicalExploreFeedResponse> topicalExplore(@QueryMap Map<String, String> queryParams);
}
suspend fun topicalExplore(@QueryMap queryParams: Map<String?, String?>?): TopicalExploreFeedResponse?
@FormUrlEncoded
@POST("/api/v1/feed/timeline/")
suspend fun fetchFeed(@FieldMap signedForm: Map<String?, String?>?): FeedFetchResponse
}

View File

@ -0,0 +1,22 @@
package awais.instagrabber.webservices
import awais.instagrabber.repositories.responses.discover.TopicalExploreFeedResponse
import awais.instagrabber.utils.TextUtils.isEmpty
import awais.instagrabber.repositories.FeedService
import com.google.common.collect.ImmutableMap
open class DiscoverRepository(private val repository: FeedService) {
companion object {
@Volatile
private var INSTANCE: DiscoverRepository? = null
fun getInstance(): DiscoverRepository {
return INSTANCE ?: synchronized(this) {
val service = RetrofitFactory.retrofit.create(FeedService::class.java)
DiscoverRepository(service).also { INSTANCE = it }
}
}
}
}

View File

@ -1,64 +0,0 @@
package awais.instagrabber.webservices;
import androidx.annotation.NonNull;
import com.google.common.collect.ImmutableMap;
import java.util.Objects;
import awais.instagrabber.repositories.DiscoverRepository;
import awais.instagrabber.repositories.responses.discover.TopicalExploreFeedResponse;
import awais.instagrabber.utils.TextUtils;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class DiscoverService {
private static final String TAG = "DiscoverService";
private final DiscoverRepository repository;
private static DiscoverService instance;
private DiscoverService() {
repository = RetrofitFactory.INSTANCE
.getRetrofit()
.create(DiscoverRepository.class);
}
public static DiscoverService getInstance() {
if (instance == null) {
instance = new DiscoverService();
}
return instance;
}
public void topicalExplore(final String maxId,
final ServiceCallback<TopicalExploreFeedResponse> callback) {
final ImmutableMap.Builder<String, String> builder = ImmutableMap.<String, String>builder()
.put("module", "explore_popular");
if (!TextUtils.isEmpty(maxId)) {
builder.put("max_id", maxId);
}
final Call<TopicalExploreFeedResponse> req = repository.topicalExplore(builder.build());
req.enqueue(new Callback<TopicalExploreFeedResponse>() {
@Override
public void onResponse(@NonNull final Call<TopicalExploreFeedResponse> call,
@NonNull final Response<TopicalExploreFeedResponse> response) {
if (callback == null) return;
final TopicalExploreFeedResponse feedResponse = response.body();
if (feedResponse == null) {
callback.onSuccess(null);
return;
}
callback.onSuccess(feedResponse);
}
@Override
public void onFailure(@NonNull final Call<TopicalExploreFeedResponse> call, @NonNull final Throwable t) {
callback.onFailure(t);
}
});
}
}

View File

@ -0,0 +1,84 @@
package awais.instagrabber.webservices
import android.util.Log
import awais.instagrabber.repositories.FeedService
import awais.instagrabber.repositories.responses.Media
import awais.instagrabber.repositories.responses.PostsFetchResponse
import awais.instagrabber.repositories.responses.discover.TopicalExploreFeedResponse
import awais.instagrabber.repositories.responses.feed.FeedFetchResponse
import awais.instagrabber.utils.TextUtils.isEmpty
import com.google.common.collect.ImmutableMap
import java.util.*
open class FeedRepository(private val repository: FeedService) {
suspend fun fetchFeed(
csrfToken: String,
deviceUuid: String,
cursor: String
): PostsFetchResponse {
val form: MutableMap<String, String> = HashMap()
form["_uuid"] = deviceUuid
form["_csrftoken"] = csrfToken
form["phone_id"] = UUID.randomUUID().toString()
form["device_id"] = UUID.randomUUID().toString()
form["client_session_id"] = UUID.randomUUID().toString()
form["is_prefetch"] = "0"
if (!isEmpty(cursor)) {
form["max_id"] = cursor
form["reason"] = "pagination"
} else {
form["is_pull_to_refresh"] = "1"
form["reason"] = "pull_to_refresh"
}
return parseResponse(repository.fetchFeed(form.toMap()))
}
suspend fun topicalExplore(maxId: String): TopicalExploreFeedResponse? {
val builder = ImmutableMap.builder<String, String>().put("module", "explore_popular")
if (!isEmpty(maxId)) {
builder.put("max_id", maxId)
}
return repository.topicalExplore(builder.build())
}
private fun parseResponse(feedFetchResponse: FeedFetchResponse): PostsFetchResponse {
val moreAvailable = feedFetchResponse.isMoreAvailable
var nextMaxId = feedFetchResponse.nextMaxId
val needNewMaxId = nextMaxId == "feed_recs_head_load"
val allPosts: MutableList<Media> = ArrayList()
val items = feedFetchResponse.items
for (media in items) {
if (needNewMaxId && media!!.endOfFeedDemarcator != null) {
val endOfFeedDemarcator = media.endOfFeedDemarcator
val groupSet = endOfFeedDemarcator!!.groupSet ?: continue
val groups = groupSet.groups ?: continue
for (group in groups) {
val id = group.id
if (id == null || id != "past_posts") continue
nextMaxId = group.nextMaxId
val feedItems = group.feedItems
for (feedItem in feedItems) {
if (feedItem == null || feedItem.isInjected() || feedItem.type == null) continue
allPosts.add(feedItem)
}
}
continue
}
if (media == null || media.isInjected() || media.type == null) continue
allPosts.add(media)
}
return PostsFetchResponse(allPosts, moreAvailable, nextMaxId)
}
companion object {
@Volatile
private var INSTANCE: FeedRepository? = null
fun getInstance(): FeedRepository {
return INSTANCE ?: synchronized(this) {
val service = RetrofitFactory.retrofit.create(FeedService::class.java)
FeedRepository(service).also { INSTANCE = it }
}
}
}
}

View File

@ -1,133 +0,0 @@
package awais.instagrabber.webservices;
import android.util.Log;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import awais.instagrabber.repositories.FeedRepository;
import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.repositories.responses.feed.EndOfFeedDemarcator;
import awais.instagrabber.repositories.responses.feed.EndOfFeedGroup;
import awais.instagrabber.repositories.responses.feed.EndOfFeedGroupSet;
import awais.instagrabber.repositories.responses.feed.FeedFetchResponse;
import awais.instagrabber.utils.TextUtils;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class FeedService {
private static final String TAG = "FeedService";
private final FeedRepository repository;
private static FeedService instance;
private FeedService() {
repository = RetrofitFactory.INSTANCE
.getRetrofit()
.create(FeedRepository.class);
}
public static FeedService getInstance() {
if (instance == null) {
instance = new FeedService();
}
return instance;
}
public void fetch(final String csrfToken,
final String deviceUuid,
final String cursor,
final ServiceCallback<PostsFetchResponse> callback) {
final Map<String, String> form = new HashMap<>();
form.put("_uuid", deviceUuid);
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");
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<FeedFetchResponse> request = repository.fetch(form);
request.enqueue(new Callback<FeedFetchResponse>() {
@Override
public void onResponse(@NonNull final Call<FeedFetchResponse> call, @NonNull final Response<FeedFetchResponse> response) {
try {
// Log.d(TAG, "onResponse: body: " + response.body());
final PostsFetchResponse postsFetchResponse = parseResponse(response);
if (callback != null) {
callback.onSuccess(postsFetchResponse);
}
} catch (Exception e) {
Log.e(TAG, "onResponse", e);
if (callback != null) {
callback.onFailure(e);
}
}
}
@Override
public void onFailure(@NonNull final Call<FeedFetchResponse> call, @NonNull final Throwable t) {
if (callback != null) {
callback.onFailure(t);
}
}
});
}
@NonNull
private PostsFetchResponse parseResponse(@NonNull final Response<FeedFetchResponse> response) {
final FeedFetchResponse feedFetchResponse = response.body();
if (feedFetchResponse == null) {
Log.e(TAG, "parseResponse: feed response body is empty with status code: " + response.code());
return new PostsFetchResponse(Collections.emptyList(), false, null);
}
return parseResponseBody(feedFetchResponse);
}
@NonNull
private PostsFetchResponse parseResponseBody(@NonNull final FeedFetchResponse feedFetchResponse) {
final boolean moreAvailable = feedFetchResponse.isMoreAvailable();
String nextMaxId = feedFetchResponse.getNextMaxId();
final boolean needNewMaxId = nextMaxId.equals("feed_recs_head_load");
final List<Media> allPosts = new ArrayList<>();
final List<Media> items = feedFetchResponse.getItems();
for (final Media media : items) {
if (needNewMaxId && media.getEndOfFeedDemarcator() != null) {
final EndOfFeedDemarcator endOfFeedDemarcator = media.getEndOfFeedDemarcator();
final EndOfFeedGroupSet groupSet = endOfFeedDemarcator.getGroupSet();
if (groupSet == null) continue;
final List<EndOfFeedGroup> groups = groupSet.getGroups();
if (groups == null) continue;
for (final EndOfFeedGroup group : groups) {
final String id = group.getId();
if (id == null || !id.equals("past_posts")) continue;
nextMaxId = group.getNextMaxId();
final List<Media> feedItems = group.getFeedItems();
for (final Media feedItem : feedItems) {
if (feedItem == null || feedItem.isInjected() || feedItem.getType() == null) continue;
allPosts.add(feedItem);
}
}
continue;
}
if (media == null || media.isInjected() || media.getType() == null) continue;
allPosts.add(media);
}
return new PostsFetchResponse(allPosts, moreAvailable, nextMaxId);
}
}