From f67d3a023c197e92c67423dc19f10008c7ed1d52 Mon Sep 17 00:00:00 2001 From: Austin Huang Date: Fri, 13 Nov 2020 14:19:26 -0500 Subject: [PATCH] version up, close #266, address half of #259 instagram is not returning most data in graphql anymore so mitigation has been implemented, but again they need to stop trampling on the rights of anonymous users --- app/build.gradle | 4 +- .../viewholder/FeedGridItemViewHolder.java | 4 +- .../asyncs/HashtagPostFetchService.java | 26 ++- .../fragments/HashTagFragment.java | 2 +- .../fragments/StoryViewerFragment.java | 6 +- .../fragments/settings/AboutFragment.java | 18 +- .../repositories/TagsRepository.java | 3 + .../instagrabber/utils/ResponseBodyUtils.java | 156 ++++++++++++++++++ .../instagrabber/webservices/FeedService.java | 150 +---------------- .../instagrabber/webservices/TagsService.java | 78 +++++++++ 10 files changed, 280 insertions(+), 167 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 269c200d..60b01708 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { minSdkVersion 21 targetSdkVersion 29 - versionCode 52 - versionName '19.0.0' + versionCode 53 + versionName '19.0.1' multiDexEnabled true diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/FeedGridItemViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/FeedGridItemViewHolder.java index 53a372b8..bc15f8c3 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/FeedGridItemViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/FeedGridItemViewHolder.java @@ -64,7 +64,7 @@ public class FeedGridItemViewHolder extends RecyclerView.ViewHolder { binding.postImage.setAspectRatio(1); } if (layoutPreferences.isAvatarVisible()) { - binding.profilePic.setVisibility(View.VISIBLE); + binding.profilePic.setVisibility(TextUtils.isEmpty(feedModel.getProfileModel().getSdProfilePic()) ? View.GONE : View.VISIBLE); binding.profilePic.setImageURI(feedModel.getProfileModel().getSdProfilePic()); final ViewGroup.LayoutParams layoutParams = binding.profilePic.getLayoutParams(); @DimenRes final int dimenRes; @@ -88,7 +88,7 @@ public class FeedGridItemViewHolder extends RecyclerView.ViewHolder { binding.profilePic.setVisibility(View.GONE); } if (layoutPreferences.isNameVisible()) { - binding.name.setVisibility(View.VISIBLE); + binding.name.setVisibility(TextUtils.isEmpty(feedModel.getProfileModel().getUsername()) ? View.GONE : View.VISIBLE); binding.name.setText(feedModel.getProfileModel().getUsername()); } else { binding.name.setVisibility(View.GONE); diff --git a/app/src/main/java/awais/instagrabber/asyncs/HashtagPostFetchService.java b/app/src/main/java/awais/instagrabber/asyncs/HashtagPostFetchService.java index b341eb4a..9e83939a 100644 --- a/app/src/main/java/awais/instagrabber/asyncs/HashtagPostFetchService.java +++ b/app/src/main/java/awais/instagrabber/asyncs/HashtagPostFetchService.java @@ -14,16 +14,36 @@ public class HashtagPostFetchService implements PostFetcher.PostFetchService { private final TagsService tagsService; private final HashtagModel hashtagModel; private String nextMaxId; - private boolean moreAvailable; + private boolean moreAvailable, isLoggedIn; - public HashtagPostFetchService(final HashtagModel hashtagModel) { + public HashtagPostFetchService(final HashtagModel hashtagModel, final boolean isLoggedIn) { this.hashtagModel = hashtagModel; + this.isLoggedIn = isLoggedIn; tagsService = TagsService.getInstance(); } @Override public void fetch(final FetchListener> fetchListener) { - tagsService.fetchPosts(hashtagModel.getName().toLowerCase(), nextMaxId, new ServiceCallback() { + if (isLoggedIn) tagsService.fetchPosts(hashtagModel.getName().toLowerCase(), nextMaxId, new ServiceCallback() { + @Override + public void onSuccess(final TagPostsFetchResponse result) { + if (result == null) return; + nextMaxId = result.getNextMaxId(); + moreAvailable = result.isMoreAvailable(); + if (fetchListener != null) { + fetchListener.onResult(result.getItems()); + } + } + + @Override + public void onFailure(final Throwable t) { + // Log.e(TAG, "onFailure: ", t); + if (fetchListener != null) { + fetchListener.onFailure(t); + } + } + }); + else tagsService.fetchGraphQLPosts(hashtagModel.getName().toLowerCase(), nextMaxId, new ServiceCallback() { @Override public void onSuccess(final TagPostsFetchResponse result) { if (result == null) return; diff --git a/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java b/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java index 721940df..7ed7dbd7 100644 --- a/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java @@ -352,7 +352,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe private void setupPosts() { binding.posts.setViewModelStoreOwner(this) .setLifeCycleOwner(this) - .setPostFetchService(new HashtagPostFetchService(hashtagModel)) + .setPostFetchService(new HashtagPostFetchService(hashtagModel, isLoggedIn)) .setLayoutPreferences(PostsLayoutPreferences.fromJson(settingsHelper.getString(Constants.PREF_HASHTAG_POSTS_LAYOUT))) .addFetchStatusChangeListener(fetching -> updateSwipeRefreshState()) .setFeedItemCallback(feedItemCallback) diff --git a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java index 60897ec1..5501f5b5 100644 --- a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java @@ -112,7 +112,7 @@ public class StoryViewerFragment extends Fragment { private StoryModel currentStory; private int slidePos; private int lastSlidePos; - private String url, username; + private String url; private PollModel poll; private QuestionModel question; private String[] mentions; @@ -498,7 +498,7 @@ public class StoryViewerFragment extends Fragment { } } else if (!TextUtils.isEmpty(fragmentArgs.getProfileId()) && !TextUtils.isEmpty(fragmentArgs.getUsername())) { currentStoryMediaId = fragmentArgs.getProfileId(); - username = fragmentArgs.getUsername(); + currentStoryUsername = fragmentArgs.getUsername(); } isHashtag = fragmentArgs.getIsHashtag(); isLoc = fragmentArgs.getIsLoc(); @@ -534,7 +534,7 @@ public class StoryViewerFragment extends Fragment { } }; storiesService.getUserStory(currentStoryMediaId, - username, + currentStoryUsername, isLoc, isHashtag, isHighlight, diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/AboutFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/AboutFragment.java index bdca380f..294e9fc2 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/AboutFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/AboutFragment.java @@ -39,10 +39,10 @@ public class AboutFragment extends BasePreferencesFragment { //thirdPartyCategory.setSummary(R.string.about_category_3pt_summary); thirdPartyCategory.setIconSpaceReserved(false); // alphabetical order!!! + thirdPartyCategory.addPreference(getACIPreference()); thirdPartyCategory.addPreference(getAutolinkPreference()); thirdPartyCategory.addPreference(getExoPlayerPreference()); thirdPartyCategory.addPreference(getFrescoPreference()); - thirdPartyCategory.addPreference(getIcafePreference()); thirdPartyCategory.addPreference(getJsoupPreference()); thirdPartyCategory.addPreference(getMDIPreference()); thirdPartyCategory.addPreference(getRetrofitPreference()); @@ -101,7 +101,7 @@ public class AboutFragment extends BasePreferencesFragment { if (context == null) return null; final Preference preference = new Preference(context); preference.setTitle("Retrofit"); - preference.setSummary("Copyright 2013 Square, Inc. Apache Version 2.0."); + preference.setSummary("Copyright 2013 Square, Inc. Apache 2.0."); preference.setIconSpaceReserved(false); preference.setOnPreferenceClickListener(p -> { final Intent intent = new Intent(Intent.ACTION_VIEW); @@ -149,7 +149,7 @@ public class AboutFragment extends BasePreferencesFragment { if (context == null) return null; final Preference preference = new Preference(context); preference.setTitle("ExoPlayer"); - preference.setSummary("Copyright (C) 2016 The Android Open Source Project. Apache Version 2.0."); + preference.setSummary("Copyright (C) 2016 The Android Open Source Project. Apache 2.0."); preference.setIconSpaceReserved(false); preference.setOnPreferenceClickListener(p -> { final Intent intent = new Intent(Intent.ACTION_VIEW); @@ -165,7 +165,7 @@ public class AboutFragment extends BasePreferencesFragment { if (context == null) return null; final Preference preference = new Preference(context); preference.setTitle("Material Design Icons"); - preference.setSummary("Copyright (C) 2014 Austin Andrews & Google LLC. Apache Version 2.0."); + preference.setSummary("Copyright (C) 2014 Austin Andrews & Google LLC. Apache 2.0."); preference.setIconSpaceReserved(false); preference.setOnPreferenceClickListener(p -> { final Intent intent = new Intent(Intent.ACTION_VIEW); @@ -181,7 +181,7 @@ public class AboutFragment extends BasePreferencesFragment { if (context == null) return null; final Preference preference = new Preference(context); preference.setTitle("AutoLinkTextViewV2"); - preference.setSummary("Copyright (C) 2019 Arman Chatikyan. Apache Version 2.0."); + preference.setSummary("Copyright (C) 2019 Arman Chatikyan. Apache 2.0."); preference.setIconSpaceReserved(false); preference.setOnPreferenceClickListener(p -> { final Intent intent = new Intent(Intent.ACTION_VIEW); @@ -192,16 +192,16 @@ public class AboutFragment extends BasePreferencesFragment { return preference; } - private Preference getIcafePreference() { + private Preference getACIPreference() { final Context context = getContext(); if (context == null) return null; final Preference preference = new Preference(context); - preference.setTitle("ICAFE"); - preference.setSummary("Copyright (C) 2014-2019 Wen Yu. Eclipse Version 2.0."); + preference.setTitle("Apache Commons Imaging"); + preference.setSummary("Copyright 2007-2020 The Apache Software Foundation. Apache 2.0."); preference.setIconSpaceReserved(false); preference.setOnPreferenceClickListener(p -> { final Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse("https://github.com/dragon66/icafe")); + intent.setData(Uri.parse("https://commons.apache.org/proper/commons-imaging/")); startActivity(intent); return true; }); diff --git a/app/src/main/java/awais/instagrabber/repositories/TagsRepository.java b/app/src/main/java/awais/instagrabber/repositories/TagsRepository.java index 4db3efbd..b646a8a3 100644 --- a/app/src/main/java/awais/instagrabber/repositories/TagsRepository.java +++ b/app/src/main/java/awais/instagrabber/repositories/TagsRepository.java @@ -24,4 +24,7 @@ public interface TagsRepository { @GET("/api/v1/feed/tag/{tag}/") Call fetchPosts(@Path("tag") final String tag, @QueryMap Map queryParams); + + @GET("/graphql/query/") + Call fetchGraphQLPosts(@QueryMap(encoded = true) Map queryParams); } diff --git a/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java b/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java index 26172184..d14c34a5 100644 --- a/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java @@ -665,6 +665,124 @@ public final class ResponseBodyUtils { return feedModelBuilder.build(); } + public static FeedModel parseGraphQLItem(final JSONObject itemJson) throws JSONException { + if (itemJson == null) { + return null; + } + final JSONObject feedItem = itemJson.getJSONObject("node"); + final String mediaType = feedItem.optString("__typename"); + if (mediaType.isEmpty() || "GraphSuggestedUserFeedUnit".equals(mediaType)) + return null; + + final boolean isVideo = feedItem.optBoolean("is_video"); + final long videoViews = feedItem.optLong("video_view_count", 0); + + final String displayUrl = feedItem.optString("display_url"); + if (TextUtils.isEmpty(displayUrl)) return null; + final String resourceUrl; + if (isVideo && feedItem.has("video_url")) { + resourceUrl = feedItem.getString("video_url"); + } else { + resourceUrl = feedItem.has("display_resources") ? ResponseBodyUtils.getHighQualityImage(feedItem) : displayUrl; + } + + ProfileModel profileModel = null; + if (feedItem.has("owner")) { + final JSONObject owner = feedItem.getJSONObject("owner"); + profileModel = new ProfileModel( + owner.optBoolean("is_private"), + false, // if you can see it then you def follow + owner.optBoolean("is_verified"), + owner.getString(Constants.EXTRAS_ID), + owner.optString(Constants.EXTRAS_USERNAME), + owner.optString("full_name"), + null, + null, + owner.optString("profile_pic_url"), + null, + 0, + 0, + 0, + false, + false, + false, + false); + } + JSONObject tempJsonObject = feedItem.optJSONObject("edge_media_preview_comment"); + final long commentsCount = tempJsonObject != null ? tempJsonObject.optLong("count") : 0; + tempJsonObject = feedItem.optJSONObject("edge_media_preview_like"); + final long likesCount = tempJsonObject != null ? tempJsonObject.optLong("count") : 0; + tempJsonObject = feedItem.optJSONObject("edge_media_to_caption"); + final JSONArray captions = tempJsonObject != null ? tempJsonObject.getJSONArray("edges") : null; + String captionText = null; + if (captions != null && captions.length() > 0) { + if ((tempJsonObject = captions.optJSONObject(0)) != null && + (tempJsonObject = tempJsonObject.optJSONObject("node")) != null) { + captionText = tempJsonObject.getString("text"); + } + } + final JSONObject location = feedItem.optJSONObject("location"); + // Log.d(TAG, "location: " + (location == null ? null : location.toString())); + String locationId = null; + String locationName = null; + if (location != null) { + locationName = location.optString("name"); + if (location.has("id")) { + locationId = location.getString("id"); + } else if (location.has("pk")) { + locationId = location.getString("pk"); + } + // Log.d(TAG, "locationId: " + locationId); + } + int height = 0; + int width = 0; + final JSONObject dimensions = feedItem.optJSONObject("dimensions"); + if (dimensions != null) { + height = dimensions.optInt("height"); + width = dimensions.optInt("width"); + } + String thumbnailUrl = null; + try { + thumbnailUrl = feedItem.getJSONArray("display_resources") + .getJSONObject(0) + .getString("src"); + } catch (JSONException ignored) {} + final FeedModel.Builder feedModelBuilder = new FeedModel.Builder() + .setProfileModel(profileModel) + .setItemType(isVideo ? MediaItemType.MEDIA_TYPE_VIDEO + : MediaItemType.MEDIA_TYPE_IMAGE) + .setViewCount(videoViews) + .setPostId(feedItem.getString(Constants.EXTRAS_ID)) + .setDisplayUrl(resourceUrl) + .setThumbnailUrl(thumbnailUrl != null ? thumbnailUrl : displayUrl) + .setShortCode(feedItem.getString(Constants.EXTRAS_SHORTCODE)) + .setPostCaption(captionText) + .setCommentsCount(commentsCount) + .setTimestamp(feedItem.optLong("taken_at_timestamp", -1)) + .setLiked(feedItem.optBoolean("viewer_has_liked")) + .setBookmarked(feedItem.optBoolean("viewer_has_saved")) + .setLikesCount(likesCount) + .setLocationName(locationName) + .setLocationId(locationId) + .setImageHeight(height) + .setImageWidth(width); + + final boolean isSlider = "GraphSidecar".equals(mediaType) && feedItem.has("edge_sidecar_to_children"); + + if (isSlider) { + feedModelBuilder.setItemType(MediaItemType.MEDIA_TYPE_SLIDER); + final JSONObject sidecar = feedItem.optJSONObject("edge_sidecar_to_children"); + if (sidecar != null) { + final JSONArray children = sidecar.optJSONArray("edges"); + if (children != null) { + final List sliderItems = getSliderItems(children); + feedModelBuilder.setSliderItems(sliderItems); + } + } + } + return feedModelBuilder.build(); + } + private static List getChildPosts(final JSONObject mediaJson) throws JSONException { if (mediaJson == null) { return Collections.emptyList(); @@ -708,4 +826,42 @@ public final class ResponseBodyUtils { .setWidth(childJson.optInt("original_width")) .build(); } + + // this is for graphql + @NonNull + private static List getSliderItems(final JSONArray children) throws JSONException { + final List sliderItems = new ArrayList<>(); + for (int j = 0; j < children.length(); ++j) { + final JSONObject childNode = children.optJSONObject(j).getJSONObject("node"); + final boolean isChildVideo = childNode.optBoolean("is_video"); + int height = 0; + int width = 0; + final JSONObject dimensions = childNode.optJSONObject("dimensions"); + if (dimensions != null) { + height = dimensions.optInt("height"); + width = dimensions.optInt("width"); + } + String thumbnailUrl = null; + try { + thumbnailUrl = childNode.getJSONArray("display_resources") + .getJSONObject(0) + .getString("src"); + } catch (JSONException ignored) {} + final PostChild sliderItem = new PostChild.Builder() + .setItemType(isChildVideo ? MediaItemType.MEDIA_TYPE_VIDEO + : MediaItemType.MEDIA_TYPE_IMAGE) + .setPostId(childNode.getString(Constants.EXTRAS_ID)) + .setDisplayUrl(isChildVideo ? childNode.getString("video_url") + : childNode.getString("display_url")) + .setThumbnailUrl(thumbnailUrl != null ? thumbnailUrl + : childNode.getString("display_url")) + .setVideoViews(childNode.optLong("video_view_count", 0)) + .setHeight(height) + .setWidth(width) + .build(); + // Log.d(TAG, "getSliderItems: sliderItem: " + sliderItem); + sliderItems.add(sliderItem); + } + return sliderItems; + } } diff --git a/app/src/main/java/awais/instagrabber/webservices/FeedService.java b/app/src/main/java/awais/instagrabber/webservices/FeedService.java index 84b567a8..cfdb1ab4 100644 --- a/app/src/main/java/awais/instagrabber/webservices/FeedService.java +++ b/app/src/main/java/awais/instagrabber/webservices/FeedService.java @@ -150,157 +150,13 @@ public class FeedService extends BaseService { final JSONArray feedItems = timelineFeed.getJSONArray("edges"); for (int i = 0; i < feedItems.length(); ++i) { - final JSONObject feedItem = feedItems.getJSONObject(i).getJSONObject("node"); - final String mediaType = feedItem.optString("__typename"); - if (mediaType.isEmpty() || "GraphSuggestedUserFeedUnit".equals(mediaType)) + final JSONObject itemJson = feedItems.optJSONObject(i); + if (itemJson == null) { continue; - - final boolean isVideo = feedItem.optBoolean("is_video"); - final long videoViews = feedItem.optLong("video_view_count", 0); - - final String displayUrl = feedItem.optString("display_url"); - if (TextUtils.isEmpty(displayUrl)) continue; - final String resourceUrl; - if (isVideo) { - resourceUrl = feedItem.getString("video_url"); - } else { - resourceUrl = feedItem.has("display_resources") ? ResponseBodyUtils.getHighQualityImage(feedItem) : displayUrl; - } - - ProfileModel profileModel = null; - if (feedItem.has("owner")) { - final JSONObject owner = feedItem.getJSONObject("owner"); - profileModel = new ProfileModel( - owner.optBoolean("is_private"), - false, // if you can see it then you def follow - owner.optBoolean("is_verified"), - owner.getString(Constants.EXTRAS_ID), - owner.getString(Constants.EXTRAS_USERNAME), - owner.optString("full_name"), - null, - null, - owner.getString("profile_pic_url"), - null, - 0, - 0, - 0, - false, - false, - false, - false); - } - JSONObject tempJsonObject = feedItem.optJSONObject("edge_media_preview_comment"); - final long commentsCount = tempJsonObject != null ? tempJsonObject.optLong("count") : 0; - tempJsonObject = feedItem.optJSONObject("edge_media_preview_like"); - final long likesCount = tempJsonObject != null ? tempJsonObject.optLong("count") : 0; - tempJsonObject = feedItem.optJSONObject("edge_media_to_caption"); - final JSONArray captions = tempJsonObject != null ? tempJsonObject.getJSONArray("edges") : null; - String captionText = null; - if (captions != null && captions.length() > 0) { - if ((tempJsonObject = captions.optJSONObject(0)) != null && - (tempJsonObject = tempJsonObject.optJSONObject("node")) != null) { - captionText = tempJsonObject.getString("text"); - } - } - final JSONObject location = feedItem.optJSONObject("location"); - // Log.d(TAG, "location: " + (location == null ? null : location.toString())); - String locationId = null; - String locationName = null; - if (location != null) { - locationName = location.optString("name"); - if (location.has("id")) { - locationId = location.getString("id"); - } else if (location.has("pk")) { - locationId = location.getString("pk"); - } - // Log.d(TAG, "locationId: " + locationId); } - int height = 0; - int width = 0; - final JSONObject dimensions = feedItem.optJSONObject("dimensions"); - if (dimensions != null) { - height = dimensions.optInt("height"); - width = dimensions.optInt("width"); - } - String thumbnailUrl = null; - try { - thumbnailUrl = feedItem.getJSONArray("display_resources") - .getJSONObject(0) - .getString("src"); - } catch (JSONException ignored) {} - final FeedModel.Builder feedModelBuilder = new FeedModel.Builder() - .setProfileModel(profileModel) - .setItemType(isVideo ? MediaItemType.MEDIA_TYPE_VIDEO - : MediaItemType.MEDIA_TYPE_IMAGE) - .setViewCount(videoViews) - .setPostId(feedItem.getString(Constants.EXTRAS_ID)) - .setDisplayUrl(resourceUrl) - .setThumbnailUrl(thumbnailUrl != null ? thumbnailUrl : displayUrl) - .setShortCode(feedItem.getString(Constants.EXTRAS_SHORTCODE)) - .setPostCaption(captionText) - .setCommentsCount(commentsCount) - .setTimestamp(feedItem.optLong("taken_at_timestamp", -1)) - .setLiked(feedItem.getBoolean("viewer_has_liked")) - .setBookmarked(feedItem.getBoolean("viewer_has_saved")) - .setLikesCount(likesCount) - .setLocationName(locationName) - .setLocationId(locationId) - .setImageHeight(height) - .setImageWidth(width); - - final boolean isSlider = "GraphSidecar".equals(mediaType) && feedItem.has("edge_sidecar_to_children"); - - if (isSlider) { - feedModelBuilder.setItemType(MediaItemType.MEDIA_TYPE_SLIDER); - final JSONObject sidecar = feedItem.optJSONObject("edge_sidecar_to_children"); - if (sidecar != null) { - final JSONArray children = sidecar.optJSONArray("edges"); - if (children != null) { - final List sliderItems = getSliderItems(children); - feedModelBuilder.setSliderItems(sliderItems); - } - } - } - final FeedModel feedModel = feedModelBuilder.build(); + final FeedModel feedModel = ResponseBodyUtils.parseItem(itemJson); feedModels.add(feedModel); } return new PostsFetchResponse(feedModels, hasNextPage, endCursor); } - - @NonNull - private List getSliderItems(final JSONArray children) throws JSONException { - final List sliderItems = new ArrayList<>(); - for (int j = 0; j < children.length(); ++j) { - final JSONObject childNode = children.optJSONObject(j).getJSONObject("node"); - final boolean isChildVideo = childNode.optBoolean("is_video"); - int height = 0; - int width = 0; - final JSONObject dimensions = childNode.optJSONObject("dimensions"); - if (dimensions != null) { - height = dimensions.optInt("height"); - width = dimensions.optInt("width"); - } - String thumbnailUrl = null; - try { - thumbnailUrl = childNode.getJSONArray("display_resources") - .getJSONObject(0) - .getString("src"); - } catch (JSONException ignored) {} - final PostChild sliderItem = new PostChild.Builder() - .setItemType(isChildVideo ? MediaItemType.MEDIA_TYPE_VIDEO - : MediaItemType.MEDIA_TYPE_IMAGE) - .setPostId(childNode.getString(Constants.EXTRAS_ID)) - .setDisplayUrl(isChildVideo ? childNode.getString("video_url") - : childNode.getString("display_url")) - .setThumbnailUrl(thumbnailUrl != null ? thumbnailUrl - : childNode.getString("display_url")) - .setVideoViews(childNode.optLong("video_view_count", 0)) - .setHeight(height) - .setWidth(width) - .build(); - // Log.d(TAG, "getSliderItems: sliderItem: " + sliderItem); - sliderItems.add(sliderItem); - } - return sliderItems; - } } diff --git a/app/src/main/java/awais/instagrabber/webservices/TagsService.java b/app/src/main/java/awais/instagrabber/webservices/TagsService.java index 0ef6862e..5aabaee1 100644 --- a/app/src/main/java/awais/instagrabber/webservices/TagsService.java +++ b/app/src/main/java/awais/instagrabber/webservices/TagsService.java @@ -12,7 +12,9 @@ import org.json.JSONObject; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import awais.instagrabber.models.FeedModel; @@ -186,6 +188,82 @@ public class TagsService extends BaseService { return feedModels; } + public void fetchGraphQLPosts(@NonNull final String tag, + final String maxId, + final ServiceCallback callback) { + final Map queryMap = new HashMap<>(); + queryMap.put("query_hash", "9b498c08113f1e09617a1703c22b2f32"); + queryMap.put("variables", "{" + + "\"tag_name\":\"" + tag + "\"," + + "\"first\":25," + + "\"after\":\"" + (maxId == null ? "" : maxId) + "\"" + + "}"); + final Call request = webRepository.fetchGraphQLPosts(queryMap); + request.enqueue(new Callback() { + @Override + public void onResponse(@NonNull final Call call, @NonNull final Response 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 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 items = parseGraphQLItems(itemsJson); + return new TagPostsFetchResponse( + moreAvailable, + nextMaxId, + numResults, + status, + items + ); + } + + private List parseGraphQLItems(final JSONArray items) throws JSONException { + if (items == null) { + return Collections.emptyList(); + } + final List 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;