Browse Source

Merge branch 'master' into l10n_master

legacy
Austin Huang 4 years ago
committed by GitHub
parent
commit
bc6e3837f7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      .bettercodehub.yml
  2. 24
      README.md
  3. 9
      SECURITY.md
  4. 133
      app/src/main/java/awais/instagrabber/MainHelper.java
  5. 4
      app/src/main/java/awais/instagrabber/activities/DirectMessagesUserInbox.java
  6. 19
      app/src/main/java/awais/instagrabber/activities/Main.java
  7. 13
      app/src/main/java/awais/instagrabber/activities/PostViewer.java
  8. 25
      app/src/main/java/awais/instagrabber/activities/ProfileViewer.java
  9. 7
      app/src/main/java/awais/instagrabber/activities/StoryViewer.java
  10. 72
      app/src/main/java/awais/instagrabber/asyncs/HashtagFetcher.java
  11. 2
      app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java
  12. 20
      app/src/main/java/awais/instagrabber/asyncs/ProfilePictureFetcher.java
  13. 11
      app/src/main/java/awais/instagrabber/asyncs/StoryStatusFetcher.java
  14. 2
      app/src/main/java/awais/instagrabber/customviews/helpers/GridAutofitLayoutManager.java
  15. 33
      app/src/main/java/awais/instagrabber/models/HashtagModel.java
  16. 9
      app/src/main/java/awais/instagrabber/models/StoryModel.java
  17. 2
      app/src/main/java/awais/instagrabber/models/enums/ProfilePictureFetchMode.java
  18. 1
      app/src/main/java/awais/instagrabber/utils/Constants.java
  19. 16
      app/src/main/java/awais/instagrabber/utils/DataBox.java
  20. 25
      app/src/main/java/awais/instagrabber/utils/FlavorTown.java
  21. 14
      app/src/main/java/awais/instagrabber/utils/UpdateChecker.java
  22. 2
      app/src/main/java/awais/instagrabber/utils/Utils.java
  23. 1
      app/src/main/java/awaisomereport/LogCollector.java
  24. 57
      app/src/main/res/layout/activity_main.xml
  25. 10
      app/src/main/res/layout/activity_viewer.xml
  26. 2
      app/src/main/res/values-zh/strings.xml
  27. 1
      app/src/main/res/values/arrays.xml
  28. 10
      app/src/main/res/values/strings.xml
  29. 2
      fastlane/metadata/android/changelogs/32.txt
  30. 9
      fastlane/metadata/android/changelogs/33.txt
  31. 3
      fastlane/metadata/android/full_description.txt
  32. BIN
      fastlane/metadata/android/images/phoneScreenshots/1.jpg
  33. BIN
      fastlane/metadata/android/images/phoneScreenshots/4.jpg
  34. 2
      fastlane/metadata/android/short_description.txt

3
.bettercodehub.yml

@ -0,0 +1,3 @@
component_depth: 7
languages:
- java

24
README.md

@ -6,8 +6,8 @@ InstaGrabber is an app that allows...
* Viewing **and downloading** Instagram posts, stories (including highlights)\*, DM\*, and profile pictures, **without** letting people know you viewed it<sup>1</sup>! Works for followed private accounts\*!
* Like/bookmark posts\*!
* Follow/restrict/block people\*!
* Downloading multiple posts at once (hold & select)!
* (Un)follow/restrict/block people\*, and (un)follow hashtags\*! (Or you can add shortcuts to them, without logging in!)
* **Copy** post captions, comments, DM messages\*, and profile bios.
* **Compare** follower/following list<sup>2</sup>!
* Searching usernames and hashtags.
@ -16,9 +16,10 @@ InstaGrabber is an app that allows...
It can be used as a drop-in replacement for read functionalities of the official Instagram app, with unnecessary components stripped.
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/images/phoneScreenshots/1.jpg"><img src="./fastlane/metadata/android/images/phoneScreenshots/1.jpg" alt="Profile" width="30%"/></a>
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/images/phoneScreenshots/2.jpg"><img src="./fastlane/metadata/android/images/phoneScreenshots/2.jpg" alt="Post" width="30%"/></a>
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/images/phoneScreenshots/3.jpg"><img src="./fastlane/metadata/android/images/phoneScreenshots/3.jpg" alt="Story (Highlight shown)" width="30%"/></a>
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/images/phoneScreenshots/1.jpg"><img src="./fastlane/metadata/android/images/phoneScreenshots/1.jpg" alt="Profile" width="23%"/></a>
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/images/phoneScreenshots/2.jpg"><img src="./fastlane/metadata/android/images/phoneScreenshots/2.jpg" alt="Post" width="23%"/></a>
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/images/phoneScreenshots/3.jpg"><img src="./fastlane/metadata/android/images/phoneScreenshots/3.jpg" alt="Story (Highlight shown)" width="23%"/></a>
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/images/phoneScreenshots/4.jpg"><img src="./fastlane/metadata/android/images/phoneScreenshots/3.jpg" alt="Hashtag" width="23%"/></a>
This app is originally made by [@AwaisKing](https://github.com/AwaisKing) who posted on [GitLab](https://gitlab.com/AwaisKing/instagrabber) but subsequently abandoned it. I decided to continue the app cuz why not, ~~even though it might not be that *cash money*.~~ (Also I need to learn Java.)
@ -30,9 +31,15 @@ Not compatible with pre-16.6 versions (including alpha).
[F-droid pending.](https://gitlab.com/fdroid/rfp/-/issues/1432)
Remember to read the [wiki](https://github.com/austinhuang0131/instagrabber/wiki) for more info!
[![Open Source Love svg3](https://badges.frapsoft.com/os/v3/open-source.svg?v=103)](https://github.com/ellerbrock/open-source-badges/)
[![GPLv3 license](https://img.shields.io/badge/License-GPLv3-blue.svg)](./LICENSE)
[![Snyk Vulnerabilities](https://img.shields.io/snyk/vulnerabilities/github/austinhuang0131/instagrabber)](https://snyk.io/test/github/austinhuang0131/instagrabber)
[![LGTM Alerts](https://img.shields.io/lgtm/alerts/github/austinhuang0131/instagrabber)](https://lgtm.com/projects/g/austinhuang0131/instagrabber)
[![LGTM Grade](https://img.shields.io/lgtm/grade/java/github/austinhuang0131/instagrabber)](https://lgtm.com/projects/g/austinhuang0131/instagrabber)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/f87cac1fbf674888b00bd91bc5eccce0)](https://app.codacy.com/manual/austinhuang0131/instagrabber)
[![Crowdin](https://badges.crowdin.net/instagrabber/localized.svg)](https://github.com/austinhuang0131/instagrabber/wiki/Contribute)
[![GitHub stars](https://img.shields.io/github/stars/austinhuang0131/instagrabber.svg?style=social&label=Star&maxAge=2592000)](https://GitHub.com/austinhuang0131/instagrabber/stargazers/)
### How to log in
@ -54,15 +61,8 @@ The relevant source code is [here](https://github.com/austinhuang0131/instagrabb
* Matrix: [#InstaGrabber:matrix.org](https://matrix.to/#/#instagrabber:matrix.org)
* Telegram: [@Grabber_App](https://t.me/grabber_app)
### Legal
* We do not collect any data, other than crash log, which is only stored locally. No funky stuff.
* You can voluntarily provide us with the crash log (3 dots => Settings => "Send logs"). In that case, it is your sole responsibility to remove any sensitive information. As well, you agree to the privacy policy of the platform that you send it on.
* While the best effort is made in this app, nobody (me or AWAiS) is liable for damages that have arisen due to the usage of this app, including but not limited to account bans and broken friendship. (The former wouldn't happen so easily as this app does imitate actual Instagram clients, the latter depends on who is using it and who their friends are, neither of which I have any control of.)
* Please use downloaded content legally and responsibly. [Don't pull a Newsweek!](https://arstechnica.com/tech-policy/2020/06/instagram-just-threw-users-of-its-embedding-api-under-the-bus/)
* This app is licensed under GPLv3.
[![forthebadge](https://forthebadge.com/images/badges/made-with-java.svg)](https://forthebadge.com)[![forthebadge](https://forthebadge.com/images/badges/built-for-android.svg)](https://forthebadge.com)
[![Crowdin | Agile localization for tech companies](https://badges.crowdin.net/badge/dark/crowdin-on-light.png)](https://crowdin.com/?utm_source=badge&utm_medium=referral&utm_campaign=badge-add-on)
<sub>Previous owner left a lot of swearings in the code, I will remove them when I get to that file.</sub>

9
SECURITY.md

@ -0,0 +1,9 @@
# Security Policy
## Supported Versions
Any versions that are made in this repository (i.e. >= 16.6) are supported.
## Reporting a Vulnerability
GitHub issues, or email `im [at] austinhuang [dot] me` if confidential. Use [this PGP key](https://github.com/austinhuang0131/austinhuang0131.github.io/blob/master/assets/key.asc) if you know how to.

133
app/src/main/java/awais/instagrabber/MainHelper.java

@ -54,6 +54,7 @@ import awais.instagrabber.adapters.PostsAdapter;
import awais.instagrabber.asyncs.DiscoverFetcher;
import awais.instagrabber.asyncs.FeedFetcher;
import awais.instagrabber.asyncs.FeedStoriesFetcher;
import awais.instagrabber.asyncs.HashtagFetcher;
import awais.instagrabber.asyncs.HighlightsFetcher;
import awais.instagrabber.asyncs.PostsFetcher;
import awais.instagrabber.asyncs.ProfileFetcher;
@ -70,12 +71,14 @@ import awais.instagrabber.models.BasePostModel;
import awais.instagrabber.models.DiscoverItemModel;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.models.HashtagModel;
import awais.instagrabber.models.IntentModel;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.IntentModelType;
import awais.instagrabber.models.enums.ItemGetType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.Utils;
import awaisomereport.LogCollector;
@ -115,7 +118,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
}
if (isHashtag)
main.mainBinding.toolbar.toolbar.setTitle(main.getString(R.string.title_hashtag_prefix) + main.userQuery);
main.mainBinding.toolbar.toolbar.setTitle(main.userQuery);
else main.mainBinding.toolbar.toolbar.setTitle(username + " (" + main.allItems.size() + postFix);
final PostModel model = result[result.length - 1];
@ -138,8 +141,10 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
if ((autoloadPosts && hasNextPage) && !isHashtag)
currentlyExecuting = new PostsFetcher(main.profileModel.getId(), endCursor, this)
.setUsername(main.profileModel.getUsername()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else
else {
main.mainBinding.swipeRefreshLayout.setRefreshing(false);
main.mainBinding.tagToolbar.setVisibility(View.VISIBLE);
}
model.setPageCursor(false, null);
}
}
@ -253,6 +258,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
private RecyclerLazyLoader feedLazyLoader, discoverLazyLoader;
private DiscoverAdapter discoverAdapter;
public SimpleExoPlayer currentFeedPlayer; // hack for remix drawer layout
final boolean isLoggedIn = !Utils.isEmpty(Utils.settingsHelper.getString(Constants.COOKIE));
public MainHelper(@NonNull final Main main) {
stopCurrentExecutor();
@ -264,8 +270,6 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
main.mainBinding.swipeRefreshLayout.setOnRefreshListener(this);
main.mainBinding.mainUrl.setMovementMethod(new LinkMovementMethod());
final boolean isLoggedIn = !Utils.isEmpty(Utils.settingsHelper.getString(Constants.COOKIE));
final LinearLayout iconSlider = main.findViewById(R.id.iconSlider);
final ImageView iconFeed = (ImageView) iconSlider.getChildAt(0);
final ImageView iconProfile = (ImageView) iconSlider.getChildAt(1);
@ -426,7 +430,8 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
linearLayout.addView(main.mainBinding.toolbar.toolbar, linearLayout.getChildCount());
}
final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(main, Utils.convertDpToPx(130));
// change the next number to adjust grid
final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(main, Utils.convertDpToPx(110));
main.mainBinding.mainPosts.setLayoutManager(layoutManager);
main.mainBinding.mainPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4)));
main.mainBinding.mainPosts.setAdapter(postsAdapter = new PostsAdapter(main.allItems, v -> {
@ -617,6 +622,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
main.mainBinding.appBarLayout.setExpanded(true, true);
main.mainBinding.privatePage.setVisibility(View.GONE);
main.mainBinding.mainProfileImage.setImageBitmap(null);
main.mainBinding.mainHashtagImage.setImageBitmap(null);
main.mainBinding.mainUrl.setText(null);
main.mainBinding.mainFullName.setText(null);
main.mainBinding.mainPostCount.setText(null);
@ -625,8 +631,10 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
main.mainBinding.mainBiography.setText(null);
main.mainBinding.mainBiography.setEnabled(false);
main.mainBinding.mainProfileImage.setEnabled(false);
main.mainBinding.mainHashtagImage.setEnabled(false);
main.mainBinding.mainBiography.setMentionClickListener(null);
main.mainBinding.mainUrl.setVisibility(View.GONE);
main.mainBinding.mainTagPostCount.setVisibility(View.GONE);
main.mainBinding.isVerified.setVisibility(View.GONE);
main.mainBinding.btnFollow.setVisibility(View.GONE);
main.mainBinding.btnRestrict.setVisibility(View.GONE);
@ -647,12 +655,77 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
collapsingToolbar.setVisibility(isHashtag ? View.GONE : View.VISIBLE);
if (isHashtag) {
main.mainBinding.toolbar.toolbar.setTitle(resources.getString(R.string.title_hashtag_prefix) + main.userQuery);
main.profileModel = null;
main.mainBinding.toolbar.toolbar.setTitle(main.userQuery);
main.mainBinding.infoContainer.setVisibility(View.GONE);
main.mainBinding.tagInfoContainer.setVisibility(View.VISIBLE);
main.mainBinding.btnFollowTag.setVisibility(View.GONE);
currentlyExecuting = new HashtagFetcher(main.userQuery.substring(1), hashtagModel -> {
main.hashtagModel = hashtagModel;
if (hashtagModel == null) {
main.mainBinding.swipeRefreshLayout.setRefreshing(false);
Toast.makeText(main, R.string.error_loading_profile, Toast.LENGTH_SHORT).show();
main.mainBinding.toolbar.toolbar.setTitle(R.string.app_name);
return;
}
currentlyExecuting = new PostsFetcher(main.userQuery, postsFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
final String profileId = hashtagModel.getId();
final String cookie = Utils.settingsHelper.getString(Constants.COOKIE);
final boolean isLoggedIn = !Utils.isEmpty(cookie);
currentlyExecuting = new PostsFetcher(main.userQuery, postsFetchListener)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
main.mainBinding.btnFollowTag.setVisibility(View.VISIBLE);
main.mainBinding.btnFollowTag.setOnClickListener(profileActionListener);
if (isLoggedIn) {
new StoryStatusFetcher(profileId, hashtagModel.getName(), result -> {
main.storyModels = result;
if (result != null && result.length > 0) main.mainBinding.mainHashtagImage.setStoriesBorder();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
if (hashtagModel.getFollowing() == true) {
main.mainBinding.btnFollowTag.setText(R.string.unfollow);
main.mainBinding.btnFollowTag.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
R.color.btn_purple_background, null)));
}
else {
main.mainBinding.btnFollowTag.setText(R.string.follow);
main.mainBinding.btnFollowTag.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
R.color.btn_pink_background, null)));
}
} else {
if (Utils.dataBox.getFavorite(main.userQuery) != null) {
main.mainBinding.btnFollowTag.setText(R.string.unfavorite_short);
main.mainBinding.btnFollowTag.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
R.color.btn_purple_background, null)));
}
else {
main.mainBinding.btnFollowTag.setText(R.string.favorite_short);
main.mainBinding.btnFollowTag.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
R.color.btn_pink_background, null)));
}
}
main.mainBinding.mainHashtagImage.setEnabled(false);
new MyTask().execute();
main.mainBinding.mainHashtagImage.setEnabled(true);
final String postCount = String.valueOf(hashtagModel.getPostCount());
SpannableStringBuilder span = new SpannableStringBuilder(resources.getString(R.string.main_posts_count, postCount));
span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0);
span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0);
main.mainBinding.mainTagPostCount.setText(span);
main.mainBinding.mainTagPostCount.setVisibility(View.VISIBLE);
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
main.hashtagModel = null;
main.mainBinding.tagInfoContainer.setVisibility(View.GONE);
main.mainBinding.toolbar.toolbar.setTitle(main.userQuery);
main.mainBinding.infoContainer.setVisibility(View.VISIBLE);
@ -672,7 +745,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
final String cookie = Utils.settingsHelper.getString(Constants.COOKIE);
final boolean isLoggedIn = !Utils.isEmpty(cookie);
if (isLoggedIn) {
new StoryStatusFetcher(profileId, result -> {
new StoryStatusFetcher(profileId, "", result -> {
main.storyModels = result;
if (result != null && result.length > 0) main.mainBinding.mainProfileImage.setStoriesBorder();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
@ -682,6 +755,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
main.mainBinding.highlightsList.setVisibility(View.VISIBLE);
main.highlightsAdapter.setData(result);
}
else main.mainBinding.highlightsList.setVisibility(View.GONE);
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
final String myId = Utils.getUserIdFromCookie(Utils.settingsHelper.getString(Constants.COOKIE));
@ -728,6 +802,19 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
R.color.btn_red_background, null)));
}
}
} else {
if (Utils.dataBox.getFavorite(main.userQuery) != null) {
main.mainBinding.btnFollow.setText(R.string.unfavorite);
main.mainBinding.btnFollow.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
R.color.btn_purple_background, null)));
}
else {
main.mainBinding.btnFollow.setText(R.string.favorite);
main.mainBinding.btnFollow.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
R.color.btn_pink_background, null)));
}
main.mainBinding.btnFollow.setOnClickListener(profileActionListener);
main.mainBinding.btnFollow.setVisibility(View.VISIBLE);
}
main.mainBinding.mainProfileImage.setEnabled(false);
@ -932,7 +1019,9 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
protected Void doInBackground(Void... voids) {
try {
mIcon_val = BitmapFactory.decodeStream((InputStream) new URL(main.profileModel.getSdProfilePic()).getContent());
mIcon_val = BitmapFactory.decodeStream((InputStream) new URL(
(main.hashtagModel != null) ? main.hashtagModel.getSdProfilePic() : main.profileModel.getSdProfilePic()
).getContent());
} catch (Throwable ex) {
Log.e("austin_debug", "bitmap: " + ex);
}
@ -941,19 +1030,29 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
@Override
protected void onPostExecute(Void result) {
main.mainBinding.mainProfileImage.setImageBitmap(mIcon_val);
if (main.hashtagModel != null) main.mainBinding.mainHashtagImage.setImageBitmap(mIcon_val);
else main.mainBinding.mainProfileImage.setImageBitmap(mIcon_val);
}
}
private final View.OnClickListener profileActionListener = new View.OnClickListener() {
@Override
public void onClick(final View v) {
if (v == main.mainBinding.btnFollow) {
if (!isLoggedIn && Utils.dataBox.getFavorite(main.userQuery) != null) {
Utils.dataBox.delFavorite(new DataBox.FavoriteModel(main.userQuery,
Long.parseLong(Utils.dataBox.getFavorite(main.userQuery).split("/")[1])));
onRefresh();
} else if (!isLoggedIn) {
Utils.dataBox.addFavorite(new DataBox.FavoriteModel(main.userQuery, System.currentTimeMillis()));
onRefresh();
} else if (v == main.mainBinding.btnFollow) {
new ProfileAction().execute("follow");
} else if (v == main.mainBinding.btnRestrict) {
new ProfileAction().execute("restrict");
} else if (v == main.mainBinding.btnBlock) {
new ProfileAction().execute("block");
} else if (v == main.mainBinding.btnFollowTag) {
new ProfileAction().execute("followtag");
}
}
};
@ -965,15 +1064,16 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
protected Void doInBackground(String... rawAction) {
action = rawAction[0];
final String url = "https://www.instagram.com/web/"+
(action == "restrict" ? "restrict_action" : ("friendships/"+main.profileModel.getId()))+"/"+
(action == "follow" ?
((action == "followtag" && main.hashtagModel != null) ? ("tags/"+
(main.hashtagModel.getFollowing() == true ? "unfollow/" : "follow/")+main.hashtagModel.getName()+"/") : (
((action == "restrict" && main.profileModel != null) ? "restrict_action" : ("friendships/"+main.profileModel.getId()))+"/"+
((action == "follow" && main.profileModel != null) ?
((main.profileModel.getFollowing() == true ||
(main.profileModel.getFollowing() == false && main.profileModel.getRequested() == true))
? "unfollow/" : "follow/") :
(action == "restrict" ?
((action == "restrict" && main.profileModel != null) ?
(main.profileModel.getRestricted() == true ? "unrestrict/" : "restrict/") :
(main.profileModel.getBlocked() == true ? "unblock/" : "block/")));
final String urlParameters = "target_user_id="+main.profileModel.getId();
(main.profileModel.getBlocked() == true ? "unblock/" : "block/")))));
try {
final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
urlConnection.setRequestMethod("POST");
@ -982,6 +1082,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
urlConnection.setRequestProperty("x-csrftoken",
Utils.settingsHelper.getString(Constants.COOKIE).split("csrftoken=")[1].split(";")[0]);
if (action == "restrict") {
final String urlParameters = "target_user_id="+main.profileModel.getId();
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
urlConnection.setRequestProperty("Content-Length", "" +
Integer.toString(urlParameters.getBytes().length));

4
app/src/main/java/awais/instagrabber/activities/DirectMessagesUserInbox.java

@ -133,7 +133,9 @@ public final class DirectMessagesUserInbox extends AppCompatActivity {
directItemModel.getReelShare().getReelId(),
directItemModel.getReelShare().getMedia().getVideoUrl(),
directItemModel.getReelShare().getMedia().getMediaType(),
directItemModel.getTimestamp()
directItemModel.getTimestamp(),
directItemModel.getReelShare().getReelOwnerName()
);
sm.setVideoUrl(directItemModel.getReelShare().getMedia().getVideoUrl());
StoryModel[] sms = {sm};

19
app/src/main/java/awais/instagrabber/activities/Main.java

@ -41,6 +41,7 @@ import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.interfaces.ItemGetter;
import awais.instagrabber.models.DiscoverItemModel;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.HashtagModel;
import awais.instagrabber.models.HighlightModel;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.ProfileModel;
@ -89,6 +90,7 @@ public final class Main extends BaseLanguageActivity {
public String userQuery = null;
public MainHelper mainHelper;
public ProfileModel profileModel;
public HashtagModel hashtagModel;
private AutoCompleteTextView searchAutoComplete;
private ArrayAdapter<String> profileDialogAdapter;
private DialogInterface.OnClickListener profileDialogListener;
@ -112,6 +114,9 @@ public final class Main extends BaseLanguageActivity {
final String uid = Utils.getUserIdFromCookie(cookie);
Utils.setupCookies(cookie);
if (settingsHelper.getInteger(Constants.PROFILE_FETCH_MODE) == 2)
settingsHelper.putInteger(Constants.PROFILE_FETCH_MODE, 1);
MainHelper.stopCurrentExecutor();
mainHelper = new MainHelper(this);
if (bundle == null) {
@ -185,17 +190,21 @@ public final class Main extends BaseLanguageActivity {
new String[]{resources.getString(R.string.view_pfp), resources.getString(R.string.show_stories)});
profileDialogListener = (dialog, which) -> {
final Intent intent;
if (which == 0 || storyModels == null || storyModels.length < 1)
intent = new Intent(this, ProfileViewer.class).putExtra(Constants.EXTRAS_PROFILE, profileModel);
if (which == 0 || storyModels == null || storyModels.length < 1) {
intent = new Intent(this, ProfileViewer.class).putExtra(
((hashtagModel != null) ? Constants.EXTRAS_HASHTAG : Constants.EXTRAS_PROFILE),
((hashtagModel != null) ? hashtagModel : profileModel));
}
else intent = new Intent(this, StoryViewer.class).putExtra(Constants.EXTRAS_USERNAME, userQuery)
.putExtra(Constants.EXTRAS_STORIES, storyModels);
.putExtra(Constants.EXTRAS_STORIES, storyModels)
.putExtra(Constants.EXTRAS_HASHTAG, (hashtagModel != null));
startActivity(intent);
};
final View.OnClickListener onClickListener = v -> {
if (v == mainBinding.mainBiography) {
Utils.copyText(this, mainBinding.mainBiography.getText().toString());
} else if (v == mainBinding.mainProfileImage) {
} else if (v == mainBinding.mainProfileImage || v == mainBinding.mainHashtagImage) {
if (storyModels == null || storyModels.length <= 0) {
profileDialogListener.onClick(null, 0);
} else {
@ -208,9 +217,11 @@ public final class Main extends BaseLanguageActivity {
mainBinding.mainBiography.setOnClickListener(onClickListener);
mainBinding.mainProfileImage.setOnClickListener(onClickListener);
mainBinding.mainHashtagImage.setOnClickListener(onClickListener);
mainBinding.mainBiography.setEnabled(false);
mainBinding.mainProfileImage.setEnabled(false);
mainBinding.mainHashtagImage.setEnabled(false);
final boolean isQueryNull = userQuery == null;
if (isQueryNull) allItems.clear();

13
app/src/main/java/awais/instagrabber/activities/PostViewer.java

@ -134,13 +134,13 @@ public final class PostViewer extends BaseLanguageActivity {
final LinearLayout topPanelRoot = viewerBinding.topPanel.getRoot();
final int iconRes;
if (containerLayoutParams.height == 0) {
containerLayoutParams.height = LinearLayout.LayoutParams.MATCH_PARENT;
if (containerLayoutParams.weight != 3.3f) {
containerLayoutParams.weight = 3.3f;
iconRes = R.drawable.ic_fullscreen_exit;
topPanelRoot.setVisibility(View.GONE);
viewerBinding.btnDownload.setVisibility(View.VISIBLE);
} else {
containerLayoutParams.height = 0;
containerLayoutParams.weight = (viewerBinding.mediaList.getVisibility() == View.VISIBLE) ? 1.35f : 1.9f;
iconRes = R.drawable.ic_fullscreen;
topPanelRoot.setVisibility(View.VISIBLE);
viewerBinding.btnDownload.setVisibility(View.GONE);
@ -324,6 +324,11 @@ public final class PostViewer extends BaseLanguageActivity {
mediaAdapter.setData(result);
if (result.length > 1) {
viewerBinding.mediaList.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, 0, 0.55f
));
containerLayoutParams.weight = 1.35f;
viewerBinding.container.setLayoutParams(containerLayoutParams);
viewerBinding.mediaList.setVisibility(View.VISIBLE);
}
@ -604,7 +609,7 @@ public final class PostViewer extends BaseLanguageActivity {
url = viewerPostModel.getDisplayUrl();
releasePlayer();
viewerBinding.btnDownload.setVisibility(containerLayoutParams.height == 0 ? View.GONE : View.VISIBLE);
viewerBinding.btnDownload.setVisibility(containerLayoutParams.weight == 3.3f ? View.GONE : View.VISIBLE);
if (viewerPostModel.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) setupVideo();
else setupImage();
}

25
app/src/main/java/awais/instagrabber/activities/ProfileViewer.java

@ -31,6 +31,7 @@ import awais.instagrabber.asyncs.ProfilePictureFetcher;
import awais.instagrabber.databinding.ActivityProfileBinding;
import awais.instagrabber.dialogs.ProfileSettingsDialog;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.HashtagModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.enums.ProfilePictureFetchMode;
import awais.instagrabber.utils.Constants;
@ -41,11 +42,11 @@ import static awais.instagrabber.utils.Constants.PROFILE_FETCH_MODE;
public final class ProfileViewer extends BaseLanguageActivity {
private final ProfilePictureFetchMode[] fetchModes = {
ProfilePictureFetchMode.INSTADP,
ProfilePictureFetchMode.INSTA_STALKER,
ProfilePictureFetchMode.INSTAFULLSIZE,
ProfilePictureFetchMode.INSTAFULLSIZE
};
private ActivityProfileBinding profileBinding;
private ProfileModel profileModel;
private HashtagModel hashtagModel;
private MenuItem menuItemDownload;
private String profilePicUrl;
private FragmentManager fragmentManager;
@ -63,16 +64,17 @@ public final class ProfileViewer extends BaseLanguageActivity {
setSupportActionBar(profileBinding.toolbar.toolbar);
final Intent intent = getIntent();
if (intent == null || !intent.hasExtra(Constants.EXTRAS_PROFILE)
|| (profileModel = (ProfileModel) intent.getSerializableExtra(Constants.EXTRAS_PROFILE)) == null) {
if (intent == null || (!intent.hasExtra(Constants.EXTRAS_PROFILE) && !intent.hasExtra(Constants.EXTRAS_HASHTAG))
|| ((profileModel = (ProfileModel) intent.getSerializableExtra(Constants.EXTRAS_PROFILE)) == null
&& (hashtagModel = (HashtagModel) intent.getSerializableExtra(Constants.EXTRAS_HASHTAG)) == null)) {
Utils.errorFinish(this);
return;
}
fragmentManager = getSupportFragmentManager();
final String id = profileModel.getId();
final String username = profileModel.getUsername();
final String id = hashtagModel != null ? hashtagModel.getId() : profileModel.getId();
final String username = hashtagModel != null ? hashtagModel.getName() : profileModel.getUsername();
profileBinding.toolbar.toolbar.setTitle(username);
@ -91,12 +93,12 @@ public final class ProfileViewer extends BaseLanguageActivity {
if (!fallbackToProfile && Utils.isEmpty(profilePicUrl)) {
fallbackToProfile = true;
new ProfilePictureFetcher(username, id, fetchListener, fetchMode).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new ProfilePictureFetcher(username, id, fetchListener, fetchMode, profilePicUrl, hashtagModel != null).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
return;
}
if (errorHandled && fallbackToProfile || Utils.isEmpty(profilePicUrl))
profilePicUrl = profileModel.getHdProfilePic();
profilePicUrl = hashtagModel != null ? hashtagModel.getSdProfilePic() : profileModel.getHdProfilePic();
if (destroyed == true) return;
@ -108,10 +110,10 @@ public final class ProfileViewer extends BaseLanguageActivity {
fallbackToProfile = true;
if (!errorHandled) {
errorHandled = true;
new ProfilePictureFetcher(username, id, fetchListener, fetchModes[Math.min(2, Math.max(0, fetchIndex + 1))])
new ProfilePictureFetcher(username, id, fetchListener, fetchModes[Math.min(2, Math.max(0, fetchIndex + 1))], profilePicUrl, hashtagModel != null)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
glideRequestManager.load(profileModel.getHdProfilePic()).into(profileBinding.imageViewer);
glideRequestManager.load(profilePicUrl).into(profileBinding.imageViewer);
showImageInfo();
}
profileBinding.progressView.setVisibility(View.GONE);
@ -163,7 +165,8 @@ public final class ProfileViewer extends BaseLanguageActivity {
}).into(profileBinding.imageViewer);
};
new ProfilePictureFetcher(username, id, fetchListener, fetchMode).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new ProfilePictureFetcher(username, id, fetchListener, fetchMode, profilePicUrl, hashtagModel != null)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void downloadProfilePicture() {

7
app/src/main/java/awais/instagrabber/activities/StoryViewer.java

@ -368,6 +368,13 @@ public final class StoryViewer extends BaseLanguageActivity {
storyViewerBinding.viewStoryPost.setTag(shortCode);
releasePlayer();
final Intent intent = getIntent();
if (intent.hasExtra(Constants.EXTRAS_HASHTAG)) {
storyViewerBinding.toolbar.toolbar.setTitle(currentStory.getUsername() + " (" + intent.getStringExtra(Constants.EXTRAS_USERNAME) + ")");
storyViewerBinding.toolbar.toolbar.setOnClickListener(v -> {
searchUsername(currentStory.getUsername());
});
}
if (itemType == MediaItemType.MEDIA_TYPE_VIDEO) setupVideo();
else setupImage();
}

72
app/src/main/java/awais/instagrabber/asyncs/HashtagFetcher.java

@ -0,0 +1,72 @@
package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import android.util.Log;
import androidx.annotation.Nullable;
import org.json.JSONArray;
import org.json.JSONObject;
import java.net.HttpURLConnection;
import java.net.URL;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.HashtagModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
public final class HashtagFetcher extends AsyncTask<Void, Void, HashtagModel> {
private final FetchListener<HashtagModel> fetchListener;
private final String hashtag;
public HashtagFetcher(String hashtag, FetchListener<HashtagModel> fetchListener) {
this.hashtag = hashtag;
this.fetchListener = fetchListener;
}
@Nullable
@Override
protected HashtagModel doInBackground(final Void... voids) {
HashtagModel result = null;
try {
final HttpURLConnection conn = (HttpURLConnection) new URL("https://www.instagram.com/explore/tags/" + hashtag + "/?__a=1").openConnection();
conn.setUseCaches(true);
conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
final JSONObject user = new JSONObject(Utils.readFromConnection(conn)).getJSONObject("graphql").getJSONObject(Constants.EXTRAS_HASHTAG);
final JSONObject timelineMedia = user.getJSONObject("edge_hashtag_to_media");
if (timelineMedia.has("edges")) {
final JSONArray edges = timelineMedia.getJSONArray("edges");
}
result = new HashtagModel(
user.getString(Constants.EXTRAS_ID),
user.getString("name"),
user.getString("profile_pic_url"),
timelineMedia.getLong("count"),
user.optBoolean("is_following"));
}
conn.disconnect();
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_HASHTAG_FETCHER, "doInBackground");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
return result;
}
@Override
protected void onPostExecute(final HashtagModel result) {
if (fetchListener != null) fetchListener.onResult(result);
}
}

2
app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java

@ -53,7 +53,7 @@ public final class PostsFetcher extends AsyncTask<Void, Void, PostModel[]> {
final String url;
if (isHashTag)
url = "https://www.instagram.com/graphql/query/?query_hash=ded47faa9a1aaded10161a2ff32abb6b&variables=" +
"{\"tag_name\":\"" + id.substring(1) + "\",\"first\":150,\"after\":\"" + endCursor + "\"}";
"{\"tag_name\":\"" + id.substring(1).toLowerCase() + "\",\"first\":150,\"after\":\"" + endCursor + "\"}";
else
url = "https://www.instagram.com/graphql/query/?query_id=17880160963012870&id=" + id + "&first=50&after=" + endCursor;

20
app/src/main/java/awais/instagrabber/asyncs/ProfilePictureFetcher.java

@ -22,27 +22,29 @@ import static awais.instagrabber.utils.Utils.logCollector;
public final class ProfilePictureFetcher extends AsyncTask<Void, Void, String> {
private final FetchListener<String> fetchListener;
private final String userName, userId;
private final ProfilePictureFetchMode fetchMode;
private final String userName, userId, picUrl;
private final boolean isHashtag;
private ProfilePictureFetchMode fetchMode;
public ProfilePictureFetcher(final String userName, final String userId, final FetchListener<String> fetchListener,
final ProfilePictureFetchMode fetchMode) {
final ProfilePictureFetchMode fetchMode, final String picUrl, final boolean isHashtag) {
this.fetchListener = fetchListener;
this.fetchMode = fetchMode;
this.userName = userName;
this.userId = userId;
this.picUrl = picUrl;
this.isHashtag = isHashtag;
}
@Override
protected String doInBackground(final Void... voids) {
String out = null;
try {
String out = picUrl;
if (!isHashtag) try {
if (fetchMode == ProfilePictureFetchMode.INSTA_STALKER) fetchMode = ProfilePictureFetchMode.INSTADP;
final String url;
if (fetchMode == ProfilePictureFetchMode.INSTADP)
url = "https://instadp.com/fullsize/" + userName;
else if (fetchMode == ProfilePictureFetchMode.INSTA_STALKER)
url = "https://insta-stalker.co/instadp_fullsize/?id=" + userName;
else // select from s1, s2, s3 but s1 works fine
url = "https://instafullsize.com/ifsapi/ig/photo/s1/" + userName + "?igid=" + userId;
@ -84,10 +86,6 @@ public final class ProfilePictureFetcher extends AsyncTask<Void, Void, String> {
fallback = true;
}
} else {
final Elements elements = doc.select("img[data-src]");
if (elements.size() > 0) out = elements.get(0).attr("data-src");
else fallback = true;
}
if (fallback) {

11
app/src/main/java/awais/instagrabber/asyncs/StoryStatusFetcher.java

@ -20,11 +20,12 @@ import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
public final class StoryStatusFetcher extends AsyncTask<Void, Void, StoryModel[]> {
private final String id;
private final String id, hashtag;
private final FetchListener<StoryModel[]> fetchListener;
public StoryStatusFetcher(final String id, final FetchListener<StoryModel[]> fetchListener) {
public StoryStatusFetcher(final String id, final String hashtag, final FetchListener<StoryModel[]> fetchListener) {
this.id = id;
this.hashtag = hashtag;
this.fetchListener = fetchListener;
}
@ -32,7 +33,8 @@ public final class StoryStatusFetcher extends AsyncTask<Void, Void, StoryModel[]
protected StoryModel[] doInBackground(final Void... voids) {
StoryModel[] result = null;
final String url = "https://www.instagram.com/graphql/query/?query_hash=52a36e788a02a3c612742ed5146f1676&variables=" +
"{\"precomposed_overlay\":false,\"reel_ids\":[\"" + id + "\"]}";
"{\"precomposed_overlay\":false,\"reel_ids\":[\"" + id + "\"]"
+(!Utils.isEmpty(hashtag) ? (",\"tag_names\":\""+hashtag+"\"") : "")+"}";
try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
@ -61,7 +63,8 @@ public final class StoryStatusFetcher extends AsyncTask<Void, Void, StoryModel[]
models[i] = new StoryModel(data.getString(Constants.EXTRAS_ID),
data.getString("display_url"),
isVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE,
data.optLong("taken_at_timestamp", 0));
data.optLong("taken_at_timestamp", 0),
data.getJSONObject("owner").getString("username"));
final JSONArray videoResources = data.optJSONArray("video_resources");
if (isVideo && videoResources != null)

2
app/src/main/java/awais/instagrabber/customviews/helpers/GridAutofitLayoutManager.java

@ -28,7 +28,7 @@ public class GridAutofitLayoutManager extends GridLayoutManager {
final int totalSpace = getOrientation() == VERTICAL ? width - getPaddingRight() - getPaddingLeft()
: height - getPaddingTop() - getPaddingBottom();
setSpanCount(Math.max(1, totalSpace / mColumnWidth));
setSpanCount(Math.max(1, Math.min(totalSpace / mColumnWidth, 3)));
mColumnWidthChanged = false;
}

33
app/src/main/java/awais/instagrabber/models/HashtagModel.java

@ -0,0 +1,33 @@
package awais.instagrabber.models;
import java.io.Serializable;
public final class HashtagModel implements Serializable {
private final boolean following;
private final long postCount;
private final String id, name, sdProfilePic;
public HashtagModel(final String id, final String name, final String sdProfilePic, final long postCount, final boolean following) {
this.id = id;
this.name = name;
this.sdProfilePic = sdProfilePic;
this.postCount = postCount;
this.following = following;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public String getSdProfilePic() {
return sdProfilePic;
}
public long getPostCount() { return postCount; }
public boolean getFollowing() { return following; }
}

9
app/src/main/java/awais/instagrabber/models/StoryModel.java

@ -5,18 +5,19 @@ import java.io.Serializable;
import awais.instagrabber.models.enums.MediaItemType;
public final class StoryModel implements Serializable {
private final String storyMediaId, storyUrl;
private final String storyMediaId, storyUrl, username;
private final MediaItemType itemType;
private final long timestamp;
private String videoUrl, tappableShortCode;
private int position;
private boolean isCurrentSlide = false;
public StoryModel(final String storyMediaId, final String storyUrl, final MediaItemType itemType, final long timestamp) {
public StoryModel(final String storyMediaId, final String storyUrl, final MediaItemType itemType, final long timestamp, final String username) {
this.storyMediaId = storyMediaId;
this.storyUrl = storyUrl;
this.itemType = itemType;
this.timestamp = timestamp;
this.username = username;
}
public String getStoryUrl() {
@ -66,4 +67,8 @@ public final class StoryModel implements Serializable {
public boolean isCurrentSlide() {
return isCurrentSlide;
}
public String getUsername() {
return username;
}
}

2
app/src/main/java/awais/instagrabber/models/enums/ProfilePictureFetchMode.java

@ -2,6 +2,6 @@ package awais.instagrabber.models.enums;
public enum ProfilePictureFetchMode {
INSTADP,
INSTA_STALKER,
INSTAFULLSIZE,
INSTA_STALKER,
}

1
app/src/main/java/awais/instagrabber/utils/Constants.java

@ -25,6 +25,7 @@ public final class Constants {
public static final String SHOW_QUICK_ACCESS_DIALOG = "show_quick_dlg";
//////////////////////// EXTRAS ////////////////////////
public static final String EXTRAS_USER = "user";
public static final String EXTRAS_HASHTAG = "hashtag";
public static final String EXTRAS_USERNAME = "username";
public static final String EXTRAS_ID = "id";
public static final String EXTRAS_POST = "post";

16
app/src/main/java/awais/instagrabber/utils/DataBox.java

@ -46,7 +46,7 @@ public final class DataBox extends SQLiteOpenHelper {
@Override
public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) { }
///////////////////////////////////////// YOUR WEIRD FETIS-FAVORITES! HERE /////////////////////////////////////////
///////////////////////////////////////// YOUR FAVORITES! HERE /////////////////////////////////////////
public final void addFavorite(@NonNull final FavoriteModel favoriteModel) {
final String query = favoriteModel.getQuery();
if (!Utils.isEmpty(query)) {
@ -114,6 +114,20 @@ public final class DataBox extends SQLiteOpenHelper {
return favorites;
}
public final String getFavorite(@NonNull final String query) {
ArrayList<FavoriteModel> favorites = null;
try (final SQLiteDatabase db = getReadableDatabase();
final Cursor cursor = db.rawQuery("SELECT query_text, date_added FROM favorites WHERE "
+KEY_QUERY_TEXT+"='"+query+"' ORDER BY date_added DESC", null)) {
if (cursor != null && cursor.moveToFirst()) {
return cursor.getString(0) + "/" + String.valueOf(cursor.getLong(1));
}
}
return null;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////// YOUR COOKIES FOR COOKIE MONSTER ARE HERE /////////////////////////////////////

25
app/src/main/java/awais/instagrabber/utils/FlavorTown.java

@ -17,6 +17,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentManager;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
@ -26,15 +27,29 @@ import static awais.instagrabber.utils.Utils.settingsHelper;
public final class FlavorTown {
public static void updateCheck(@NonNull final Context context) {
new UpdateChecker(versionUrl -> {
new AlertDialog.Builder(context).setTitle(R.string.update_available).setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.action_download, (dialog, which) -> {
Resources res = context.getResources();
new UpdateChecker(version -> {
new AlertDialog.Builder(context)
.setTitle(res.getString(R.string.update_available) + " (" + version + ")")
.setMessage(R.string.update_notice)
.setNeutralButton(R.string.cancel, null)
.setNegativeButton(R.string.action_github, (dialog, which) -> {
try {
context.startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse(versionUrl)));
context.startActivity(new Intent(Intent.ACTION_VIEW).setData(
Uri.parse("https://github.com/austinhuang0131/instagrabber/releases/tag/" + version)));
} catch (final ActivityNotFoundException e) {
// do nothing
}
}).show();
})
.setPositiveButton(R.string.action_fdroid, (dialog, which) -> {
try {
context.startActivity(new Intent(Intent.ACTION_VIEW).setData(
Uri.parse("https://f-droid.org/packages/me.austinhuang.instagrabber/")));
} catch (final ActivityNotFoundException e) {
// do nothing
}
})
.show();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}

14
app/src/main/java/awais/instagrabber/utils/UpdateChecker.java

@ -13,7 +13,7 @@ import awais.instagrabber.interfaces.FetchListener;
public final class UpdateChecker extends AsyncTask<Void, Void, Boolean> {
private final FetchListener<String> fetchListener;
private String versionUrl;
private String version;
public UpdateChecker(final FetchListener<String> fetchListener) {
this.fetchListener = fetchListener;
@ -22,17 +22,17 @@ public final class UpdateChecker extends AsyncTask<Void, Void, Boolean> {
@NonNull
@Override
protected Boolean doInBackground(final Void... voids) {
final String UPDATE_BASE_URL = "https://github.com/austinhuang0131/instagrabber/releases/tag/v";
final String UPDATE_BASE_URL = "https://github.com/austinhuang0131/instagrabber/releases/tag/";
final String versionName = BuildConfig.VERSION_NAME;
final int index = versionName.indexOf('.');
try {
final int verMajor = Integer.parseInt(versionName.substring(0, index));
versionUrl = UPDATE_BASE_URL + (verMajor + 1) + ".0";
version = "v" + (verMajor + 1) + ".0";
// check major version first
HttpURLConnection conn = (HttpURLConnection) new URL(versionUrl).openConnection();
HttpURLConnection conn = (HttpURLConnection) new URL(UPDATE_BASE_URL + version).openConnection();
conn.setUseCaches(false);
conn.setRequestMethod("HEAD");
conn.connect();
@ -46,10 +46,10 @@ public final class UpdateChecker extends AsyncTask<Void, Void, Boolean> {
final int verMinor = Integer.parseInt(substring) + 1;
for (int i = verMinor; i < 10; ++i) {
versionUrl = UPDATE_BASE_URL + verMajor + '.' + i;
version = "v" + verMajor + '.' + i;
conn.disconnect();
conn = (HttpURLConnection) new URL(versionUrl).openConnection();
conn = (HttpURLConnection) new URL(UPDATE_BASE_URL + version).openConnection();
conn.setUseCaches(false);
conn.setRequestMethod("HEAD");
conn.connect();
@ -70,6 +70,6 @@ public final class UpdateChecker extends AsyncTask<Void, Void, Boolean> {
@Override
protected void onPostExecute(final Boolean result) {
if (result != null && result && fetchListener != null)
fetchListener.onResult(versionUrl);
fetchListener.onResult(version);
}
}

2
app/src/main/java/awais/instagrabber/utils/Utils.java

@ -1155,7 +1155,7 @@ public final class Utils {
storyModels[j] = new StoryModel(data.getString(Constants.EXTRAS_ID), data.getString("display_url"),
isVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE,
data.getLong("taken_at_timestamp"));
data.getLong("taken_at_timestamp"), data.getJSONObject("owner").getString("username"));
if (isVideo && data.has("video_resources"))
storyModels[j].setVideoUrl(Utils.getHighQualityPost(data.getJSONArray("video_resources"), true));

1
app/src/main/java/awaisomereport/LogCollector.java

@ -106,6 +106,7 @@ public final class LogCollector {
ASYNC_MAIN_POSTS_FETCHER("async-main-posts-fetcher.txt"),
ASYNC_POST_FETCHER("async-single-post-fetcher.txt"),
ASYNC_FEED_FETCHER("async-feed-fetcher.txt"),
ASYNC_HASHTAG_FETCHER("async-hashtag-fetcher.txt"),
ASYNC_PROFILE_FETCHER("async-profile-fetcher.txt"),
ASYNC_PROFILE_PICTURE_FETCHER("async-pfp-fetcher.txt"),
ASYNC_STORY_STATUS_FETCHER("async-story-status-fetcher.txt"),

57
app/src/main/res/layout/activity_main.xml

@ -227,6 +227,63 @@
</LinearLayout>
</RelativeLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout>
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/tagToolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
android:visibility="gone">
<RelativeLayout
android:id="@+id/tagInfoContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:background="@null"
android:orientation="vertical"
android:paddingBottom="5dp">
<LinearLayout
android:id="@+id/hashtagInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="@dimen/profile_info_container_bottom_space">
<awais.instagrabber.customviews.CircularImageView
android:id="@+id/mainHashtagImage"
android:layout_width="@dimen/profile_picture_size"
android:layout_height="@dimen/profile_picture_size"
android:adjustViewBounds="true"
android:background="?selectableItemBackgroundBorderless" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/mainTagPostCount"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="12dp"
android:layout_marginLeft="12dp"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:layout_weight="1"
android:gravity="center"
android:textAppearance="@style/TextAppearance.AppCompat"
android:textSize="15sp"
tools:text="35\nPosts" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnFollowTag"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="2"
android:text="@string/follow"
android:textColor="@color/btn_pink_text_color"
android:textSize="20sp"
app:backgroundTint="@color/btn_pink_background" />
</LinearLayout>
</RelativeLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout

10
app/src/main/res/layout/activity_viewer.xml

@ -4,6 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:weightSum="3.2"
android:animateLayoutChanges="true"
android:orientation="vertical"
tools:context=".activities.PostViewer">
@ -27,7 +28,7 @@
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.6">
android:layout_weight="1.9">
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/playerView"
@ -81,7 +82,7 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/mediaList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="0dp"
android:clipToPadding="false"
android:orientation="horizontal"
android:paddingStart="5dp"
@ -95,12 +96,12 @@
layout="@layout/item_feed_bottom"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.4" />
android:layout_weight="1"/>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.26"
android:layout_weight="0.3"
android:background="#0000"
android:weightSum="2"
android:layout_alignParentBottom="true">
@ -119,6 +120,7 @@
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnBookmark"
android:layout_alignParentBottom="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"

2
app/src/main/res/values-zh/strings.xml

@ -109,7 +109,7 @@
<string name="quick_access_confirm_delete">你真的要删除 %s?</string>
<string name="profile_viewer_imageinfo">宽: %d\n高: %d</string>
<string name="profile_viewer_colordepth_prefix">\n色深:</string>
<string name="profile_endpoint">Select profile picture endpoint\n(Does not affect hashtags)</string>
<string name="profile_endpoint">选择头像服务\n(不影响#标签)</string>
<string name="open_profile">打开主页</string>
<string name="view_pfp">查看头像</string>
<string name="direct_messages_you"></string>

1
app/src/main/res/values/arrays.xml

@ -28,7 +28,6 @@
<string-array name="profile_fetch_modes">
<item>Instadp</item>
<item>Insta-Stalker</item>
<item>Instafullsize</item>
</string-array>

10
app/src/main/res/values/strings.xml

@ -9,6 +9,8 @@
<string name="action_setting">Settings (v%s)</string>
<string name="action_settings">Settings</string>
<string name="action_download">Download</string>
<string name="action_github" translatable="false">GitHub</string>
<string name="action_fdroid" translatable="false">F-Droid</string>
<string name="action_search">Search username…</string>
<string name="action_compare">Compare</string>
@ -31,7 +33,6 @@
<string name="title_favorites">Favorites</string>
<string name="title_discover">Discover</string>
<string name="title_comments">Comments</string>
<string name="title_hashtag_prefix">Hashtag: </string>
<string name="title_highlight">Highlight: %s</string>
<string name="title_user_story">User Story</string>
<string name="title_changelog">Changelog</string>
@ -88,6 +89,10 @@
<string name="follow">Follow</string>
<string name="unfollow">Unfollow</string>
<string name="favorite">Add to Favorites</string>
<string name="unfavorite">Remove from Favorites</string>
<string name="favorite_short">Favorite</string>
<string name="unfavorite_short">Unfavorite</string>
<string name="block">Block</string>
<string name="unblock">Unblock</string>
<string name="restrict">Restrict</string>
@ -124,7 +129,7 @@
<string name="profile_viewer_imageinfo">Width: %d\nHeight: %d</string>
<string name="profile_viewer_colordepth_prefix">\nColor depth:</string>
<string name="profile_endpoint">Select profile picture endpoint</string>
<string name="profile_endpoint">Select profile picture endpoint\n(Does not affect hashtags)</string>
<string name="open_profile">Open profile</string>
<string name="view_pfp">View profile picture</string>
@ -182,6 +187,7 @@
<string name="login_success_loading_cookies">Successfully loaded cookies!\nIf you still can\'t open private pages/posts, re-login!</string>
<string name="update_available">An update is available!</string>
<string name="update_notice">Reminder: If you downloaded from F-Droid, you must update from it! Same applies for GitHub.</string>
<string name="updated">Thank you for updating InstaGrabber!</string>
<string name="crash_title">App crashed</string>
<string name="crash_descr">Oops.. the app crashed, but don\'t worry you can send error report to the developer to help him fix the issue. (:</string>

2
fastlane/metadata/android/changelogs/32.txt

@ -1,5 +1,3 @@
v16.7 build 32:
* You can now (un)follow/restrict/block people
* For this reason, "Open in Instagram" for following/follower list is removed
* Link in bio is now under bio text (like the actual Instagram app)

9
fastlane/metadata/android/changelogs/33.txt

@ -0,0 +1,9 @@
* Full support on hashtags, including stories, "profile picture" (SD only), post count, and (un)following. Removed "Hashtag:" prefix.
* Non-logged-in users now have an "add/remove from favorites" button on the profile/hashtag page, alongside Quick Access.
* Update checker will now have a F-Droid button
* Updated Italian and Simplified Chinese translations
* Adjusted grid size threshold at popular request
* Adjusted post viewer component sizes (to prevent the buttons being squished downwards, but the exact outcome depends on device)
* Fixed a bug where highlights of the viewed user gets carried to other users
* Fixed a bug where mentions in feeds were parsed incorrectly
* Removed Insta-Stalker (defunct) as an HD profile picture provider, existing users are moved to Instafullsize upon first run.

3
fastlane/metadata/android/full_description.txt

@ -3,6 +3,7 @@ InstaGrabber is an app that allows...
* Viewing **and downloading** Instagram posts, stories (including highlights)\*, DM\*, and profile pictures, **without** letting people know you viewed it! Works for followed private accounts\*!
* Like/bookmark posts\*!
* Downloading multiple posts at once (hold & select)!
* (Un)follow/restrict/block people\*, and (un)follow hashtags\*! (Or you can add shortcuts to them, without logging in!)
* **Copy** post captions, comments, DM messages\*, and profile bios.
* **Compare** follower/following list!
* Searching usernames and hashtags.
@ -10,3 +11,5 @@ InstaGrabber is an app that allows...
<sub>* Requires [login](https://github.com/austinhuang0131/instagrabber/blob/master/README.md#how-to-log-in). You must be a current follower of the desired private accounts, this app cannot hack people (which I have to state despite the obvious)!</sub>
It can be used as a drop-in replacement for read functionalities of the official Instagram app, with unnecessary components stripped.
Remember to read the [wiki](https://github.com/austinhuang0131/instagrabber/wiki) for more info!

BIN
fastlane/metadata/android/images/phoneScreenshots/1.jpg

Before

Width: 1080  |  Height: 1920  |  Size: 161 KiB

After

Width: 720  |  Height: 1280  |  Size: 137 KiB

BIN
fastlane/metadata/android/images/phoneScreenshots/4.jpg

After

Width: 720  |  Height: 1280  |  Size: 152 KiB

2
fastlane/metadata/android/short_description.txt

@ -1 +1 @@
A simple yet advanced viewer/downloader app for Instagram (+login support).
A simple yet advanced client for Instagram, with login support!
Loading…
Cancel
Save