diff --git a/.bettercodehub.yml b/.bettercodehub.yml new file mode 100644 index 00000000..745eb11f --- /dev/null +++ b/.bettercodehub.yml @@ -0,0 +1,3 @@ +component_depth: 7 +languages: +- java diff --git a/README.md b/README.md index 83f54b8e..5d793f41 100755 --- a/README.md +++ b/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 it1! 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 list2! * 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. -Profile -Post -Story (Highlight shown) +Profile +Post +Story (Highlight shown) +Hashtag 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) Previous owner left a lot of swearings in the code, I will remove them when I get to that file. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..6cc395c5 --- /dev/null +++ b/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. diff --git a/app/src/main/java/awais/instagrabber/MainHelper.java b/app/src/main/java/awais/instagrabber/MainHelper.java index f18e180b..d280c03a 100755 --- a/app/src/main/java/awais/instagrabber/MainHelper.java +++ b/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 PostsFetcher(main.userQuery, postsFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + 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; + } + + 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)); diff --git a/app/src/main/java/awais/instagrabber/activities/DirectMessagesUserInbox.java b/app/src/main/java/awais/instagrabber/activities/DirectMessagesUserInbox.java index 56222dfc..49459862 100755 --- a/app/src/main/java/awais/instagrabber/activities/DirectMessagesUserInbox.java +++ b/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}; diff --git a/app/src/main/java/awais/instagrabber/activities/Main.java b/app/src/main/java/awais/instagrabber/activities/Main.java index 18b4e226..0bd94955 100755 --- a/app/src/main/java/awais/instagrabber/activities/Main.java +++ b/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 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(); diff --git a/app/src/main/java/awais/instagrabber/activities/PostViewer.java b/app/src/main/java/awais/instagrabber/activities/PostViewer.java index f60d20fc..9c082955 100755 --- a/app/src/main/java/awais/instagrabber/activities/PostViewer.java +++ b/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(); } diff --git a/app/src/main/java/awais/instagrabber/activities/ProfileViewer.java b/app/src/main/java/awais/instagrabber/activities/ProfileViewer.java index bd5d3398..20f8bccd 100755 --- a/app/src/main/java/awais/instagrabber/activities/ProfileViewer.java +++ b/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() { diff --git a/app/src/main/java/awais/instagrabber/activities/StoryViewer.java b/app/src/main/java/awais/instagrabber/activities/StoryViewer.java index ef237794..e4de59ff 100755 --- a/app/src/main/java/awais/instagrabber/activities/StoryViewer.java +++ b/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(); } diff --git a/app/src/main/java/awais/instagrabber/asyncs/HashtagFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/HashtagFetcher.java new file mode 100644 index 00000000..5f815e68 --- /dev/null +++ b/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 { + private final FetchListener fetchListener; + private final String hashtag; + + public HashtagFetcher(String hashtag, FetchListener 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); + } +} diff --git a/app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java index 25085fd5..f5372b55 100755 --- a/app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java +++ b/app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java @@ -53,7 +53,7 @@ public final class PostsFetcher extends AsyncTask { 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; diff --git a/app/src/main/java/awais/instagrabber/asyncs/ProfilePictureFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/ProfilePictureFetcher.java index a41583b1..4607b761 100755 --- a/app/src/main/java/awais/instagrabber/asyncs/ProfilePictureFetcher.java +++ b/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 { private final FetchListener 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 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 { 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) { diff --git a/app/src/main/java/awais/instagrabber/asyncs/StoryStatusFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/StoryStatusFetcher.java index fbf9030d..586fd086 100755 --- a/app/src/main/java/awais/instagrabber/asyncs/StoryStatusFetcher.java +++ b/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 { - private final String id; + private final String id, hashtag; private final FetchListener fetchListener; - public StoryStatusFetcher(final String id, final FetchListener fetchListener) { + public StoryStatusFetcher(final String id, final String hashtag, final FetchListener fetchListener) { this.id = id; + this.hashtag = hashtag; this.fetchListener = fetchListener; } @@ -32,7 +33,8 @@ public final class StoryStatusFetcher extends AsyncTask 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 ///////////////////////////////////// diff --git a/app/src/main/java/awais/instagrabber/utils/FlavorTown.java b/app/src/main/java/awais/instagrabber/utils/FlavorTown.java index d48b7a9e..a37748e6 100755 --- a/app/src/main/java/awais/instagrabber/utils/FlavorTown.java +++ b/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); } diff --git a/app/src/main/java/awais/instagrabber/utils/UpdateChecker.java b/app/src/main/java/awais/instagrabber/utils/UpdateChecker.java index 7e48d45a..ceb28a31 100755 --- a/app/src/main/java/awais/instagrabber/utils/UpdateChecker.java +++ b/app/src/main/java/awais/instagrabber/utils/UpdateChecker.java @@ -13,7 +13,7 @@ import awais.instagrabber.interfaces.FetchListener; public final class UpdateChecker extends AsyncTask { private final FetchListener fetchListener; - private String versionUrl; + private String version; public UpdateChecker(final FetchListener fetchListener) { this.fetchListener = fetchListener; @@ -22,17 +22,17 @@ public final class UpdateChecker extends AsyncTask { @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 { 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 { @Override protected void onPostExecute(final Boolean result) { if (result != null && result && fetchListener != null) - fetchListener.onResult(versionUrl); + fetchListener.onResult(version); } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/utils/Utils.java b/app/src/main/java/awais/instagrabber/utils/Utils.java index 21b8f1cb..0b634fa7 100755 --- a/app/src/main/java/awais/instagrabber/utils/Utils.java +++ b/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)); diff --git a/app/src/main/java/awaisomereport/LogCollector.java b/app/src/main/java/awaisomereport/LogCollector.java index 43ca9596..0ca77381 100755 --- a/app/src/main/java/awaisomereport/LogCollector.java +++ b/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"), diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 01fd4f12..4a5b7c4b 100755 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -227,6 +227,63 @@ + + + + + + + + + + + + + + @@ -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"> + android:layout_weight="1"/> @@ -119,6 +120,7 @@ 你真的要删除 %s? 宽: %d\n高: %d \n色深: - Select profile picture endpoint\n(Does not affect hashtags) + 选择头像服务\n(不影响#标签) 打开主页 查看头像 diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 2cdc8166..afee762f 100755 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -28,7 +28,6 @@ Instadp - Insta-Stalker Instafullsize diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c6946455..6df8e214 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -9,6 +9,8 @@ Settings (v%s) Settings Download + GitHub + F-Droid Search username… Compare @@ -31,7 +33,6 @@ Favorites Discover Comments - Hashtag: Highlight: %s User Story Changelog @@ -88,6 +89,10 @@ Follow Unfollow + Add to Favorites + Remove from Favorites + Favorite + Unfavorite Block Unblock Restrict @@ -124,7 +129,7 @@ Width: %d\nHeight: %d \nColor depth: - Select profile picture endpoint + Select profile picture endpoint\n(Does not affect hashtags) Open profile View profile picture @@ -182,6 +187,7 @@ Successfully loaded cookies!\nIf you still can\'t open private pages/posts, re-login! An update is available! + Reminder: If you downloaded from F-Droid, you must update from it! Same applies for GitHub. Thank you for updating InstaGrabber! App crashed Oops.. the app crashed, but don\'t worry you can send error report to the developer to help him fix the issue. (: diff --git a/fastlane/metadata/android/changelogs/32.txt b/fastlane/metadata/android/changelogs/32.txt index 4220740f..d7e2f3c6 100755 --- a/fastlane/metadata/android/changelogs/32.txt +++ b/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) diff --git a/fastlane/metadata/android/changelogs/33.txt b/fastlane/metadata/android/changelogs/33.txt new file mode 100644 index 00000000..47f18749 --- /dev/null +++ b/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. \ No newline at end of file diff --git a/fastlane/metadata/android/full_description.txt b/fastlane/metadata/android/full_description.txt index 48ec98af..d4cd88f1 100755 --- a/fastlane/metadata/android/full_description.txt +++ b/fastlane/metadata/android/full_description.txt @@ -3,10 +3,13 @@ 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. * 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)! -It can be used as a drop-in replacement for read functionalities of the official Instagram app, with unnecessary components stripped. \ No newline at end of file +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! \ No newline at end of file diff --git a/fastlane/metadata/android/images/phoneScreenshots/1.jpg b/fastlane/metadata/android/images/phoneScreenshots/1.jpg index 504a6cca..5796bf47 100644 Binary files a/fastlane/metadata/android/images/phoneScreenshots/1.jpg and b/fastlane/metadata/android/images/phoneScreenshots/1.jpg differ diff --git a/fastlane/metadata/android/images/phoneScreenshots/4.jpg b/fastlane/metadata/android/images/phoneScreenshots/4.jpg new file mode 100644 index 00000000..b48d64a4 Binary files /dev/null and b/fastlane/metadata/android/images/phoneScreenshots/4.jpg differ diff --git a/fastlane/metadata/android/short_description.txt b/fastlane/metadata/android/short_description.txt index 5e5c5e9e..ecb0dd4d 100755 --- a/fastlane/metadata/android/short_description.txt +++ b/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!