Merge branch 'master' into l10n_master

This commit is contained in:
Austin Huang 2020-07-25 17:22:01 -04:00 committed by GitHub
commit bc6e3837f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 451 additions and 94 deletions

3
.bettercodehub.yml Normal file
View File

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

View File

@ -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 Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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";

View File

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

View File

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

View File

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

View File

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

View File

@ -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"),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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.
<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.
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!

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

View File

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