This commit is contained in:
Austin Huang 2020-07-25 17:10:26 -04:00
parent 51c4da8dce
commit 1827ed85ee
No known key found for this signature in database
GPG Key ID: 84C23AA04587A91F
41 changed files with 428 additions and 93 deletions

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.

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

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

View File

@ -26,7 +26,6 @@
<string name="title_favorites">Favoritos</string>
<string name="title_discover">Explorar</string>
<string name="title_comments">Comentarios</string>
<string name="title_hashtag_prefix">Hashtag: </string>
<string name="title_highlight">Resaltado: %s</string>
<string name="title_user_story">Historia de usuario</string>
<string name="title_changelog">Lista de cambios</string>

View File

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

View File

@ -26,7 +26,6 @@
<string name="title_favorites">Favoris</string>
<string name="title_discover">Découvrir</string>
<string name="title_comments">Commentaires</string>
<string name="title_hashtag_prefix">Hashtag: </string>
<string name="title_highlight">Highlight: %s</string>
<string name="title_user_story">Stories de l\'utilisateur</string>
<string name="title_changelog">Journal des changements</string>

View File

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

View File

@ -26,7 +26,6 @@
<string name="title_favorites">Favorit</string>
<string name="title_discover">Temukan</string>
<string name="title_comments">Komentar</string>
<string name="title_hashtag_prefix">Hashtag: </string>
<string name="title_highlight">Sorotan: %s</string>
<string name="title_user_story">Story</string>
<string name="title_changelog">Catatan Perubahan</string>

View File

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

View File

@ -26,7 +26,6 @@
<string name="title_favorites">Favoriti</string>
<string name="title_discover">Scopri</string>
<string name="title_comments">Commenti</string>
<string name="title_hashtag_prefix">Hashtag: </string>
<string name="title_highlight">Contenuto in evidenza: %s</string>
<string name="title_user_story">Storia Utente</string>
<string name="title_changelog">Changelog</string>

View File

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

View File

@ -26,7 +26,6 @@
<string name="title_favorites">最爱</string>
<string name="title_discover">发现</string>
<string name="title_comments">留言</string>
<string name="title_hashtag_prefix">标签: </string>
<string name="title_highlight">精彩:%s</string>
<string name="title_user_story">用户快拍</string>
<string name="title_changelog">日志</string>
@ -106,7 +105,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">选择头像服务</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: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 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!