This commit is contained in:
Austin Huang 2020-08-06 22:26:23 -04:00
parent 45bf250657
commit d48bc32a15
No known key found for this signature in database
GPG Key ID: 84C23AA04587A91F
15 changed files with 238 additions and 219 deletions

View File

@ -9,8 +9,8 @@ android {
minSdkVersion 16
targetSdkVersion 29
versionCode 40
versionName '17.4'
versionCode 41
versionName '17.5'
multiDexEnabled true

View File

@ -240,7 +240,7 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
if (tag instanceof FeedStoryModel) {
final FeedStoryModel feedStoryModel = (FeedStoryModel) tag;
final int index = indexOfIntArray(stories, feedStoryModel);
new iStoryStatusFetcher(feedStoryModel.getStoryMediaId(), null, false, false, result -> {
new iStoryStatusFetcher(feedStoryModel.getStoryMediaId(), null, false, false, false, false, result -> {
if (result != null && result.length > 0)
main.startActivity(new Intent(main, StoryViewer.class)
.putExtra(Constants.EXTRAS_STORIES, result)
@ -733,31 +733,31 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
main.mainBinding.btnFollowTag.setVisibility(View.VISIBLE);
if (isLoggedIn) {
new iStoryStatusFetcher(hashtagModel.getName(), null, false, true, result -> {
new iStoryStatusFetcher(hashtagModel.getName(), null, false, true, false, false, 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)));
main.mainBinding.btnFollowTag.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
main, R.color.btn_purple_background)));
}
else {
main.mainBinding.btnFollowTag.setText(R.string.follow);
main.mainBinding.btnFollowTag.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
R.color.btn_pink_background, null)));
main.mainBinding.btnFollowTag.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
main, R.color.btn_pink_background)));
}
} 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)));
main.mainBinding.btnFollowTag.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
main, R.color.btn_purple_background)));
}
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.btnFollowTag.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
main, R.color.btn_pink_background)));
}
}
@ -795,20 +795,22 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
final String cookie = Utils.settingsHelper.getString(Constants.COOKIE);
final boolean isLoggedIn = !Utils.isEmpty(cookie);
new iStoryStatusFetcher(profileId, profileModel.getUsername(), false, false,
(!isLoggedIn && Utils.settingsHelper.getBoolean(Constants.STORIESIG)), false,
result -> {
main.storyModels = result;
if (result != null && result.length > 0) main.mainBinding.mainProfileImage.setStoriesBorder();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new HighlightsFetcher(profileId, (!isLoggedIn && Utils.settingsHelper.getBoolean(Constants.STORIESIG)), result -> {
if (result != null && result.length > 0) {
main.mainBinding.highlightsList.setVisibility(View.VISIBLE);
main.highlightsAdapter.setData(result);
}
else main.mainBinding.highlightsList.setVisibility(View.GONE);
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
if (isLoggedIn) {
new iStoryStatusFetcher(profileId, profileModel.getUsername(), false, false, result -> {
main.storyModels = result;
if (result != null && result.length > 0) main.mainBinding.mainProfileImage.setStoriesBorder();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new HighlightsFetcher(profileId, result -> {
if (result != null && result.length > 0) {
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));
if (!profileId.equals(myId)) {
main.mainBinding.btnTagged.setVisibility(View.GONE);
@ -817,41 +819,41 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
main.mainBinding.btnFollow.setVisibility(View.VISIBLE);
if (profileModel.getFollowing() == true) {
main.mainBinding.btnFollow.setText(R.string.unfollow);
main.mainBinding.btnFollow.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
R.color.btn_purple_background, null)));
main.mainBinding.btnFollow.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
main, R.color.btn_purple_background)));
}
else if (profileModel.getRequested() == true) {
main.mainBinding.btnFollow.setText(R.string.cancel);
main.mainBinding.btnFollow.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
R.color.btn_purple_background, null)));
main.mainBinding.btnFollow.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
main, R.color.btn_purple_background)));
}
else {
main.mainBinding.btnFollow.setText(R.string.follow);
main.mainBinding.btnFollow.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
R.color.btn_pink_background, null)));
main.mainBinding.btnFollow.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
main, R.color.btn_pink_background)));
}
main.mainBinding.btnRestrict.setVisibility(View.VISIBLE);
if (profileModel.getRestricted() == true) {
main.mainBinding.btnRestrict.setText(R.string.unrestrict);
main.mainBinding.btnRestrict.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
R.color.btn_green_background, null)));
main.mainBinding.btnRestrict.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
main, R.color.btn_green_background)));
}
else {
main.mainBinding.btnRestrict.setText(R.string.restrict);
main.mainBinding.btnRestrict.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
R.color.btn_orange_background, null)));
main.mainBinding.btnRestrict.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
main, R.color.btn_orange_background)));
}
if (profileModel.isReallyPrivate()) {
main.mainBinding.btnBlock.setVisibility(View.VISIBLE);
main.mainBinding.btnTagged.setVisibility(View.GONE);
if (profileModel.getBlocked() == true) {
main.mainBinding.btnBlock.setText(R.string.unblock);
main.mainBinding.btnBlock.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
R.color.btn_green_background, null)));
main.mainBinding.btnBlock.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
main, R.color.btn_green_background)));
} else {
main.mainBinding.btnBlock.setText(R.string.block);
main.mainBinding.btnBlock.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
R.color.btn_red_background, null)));
main.mainBinding.btnBlock.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
main, R.color.btn_red_background)));
}
} else {
main.mainBinding.btnBlock.setVisibility(View.GONE);
@ -859,12 +861,12 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
main.mainBinding.btnTagged.setVisibility(View.VISIBLE);
if (profileModel.getBlocked() == true) {
main.mainBinding.btnSaved.setText(R.string.unblock);
main.mainBinding.btnSaved.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
R.color.btn_green_background, null)));
main.mainBinding.btnSaved.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
main, R.color.btn_green_background)));
} else {
main.mainBinding.btnSaved.setText(R.string.block);
main.mainBinding.btnSaved.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
R.color.btn_red_background, null)));
main.mainBinding.btnSaved.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
main, R.color.btn_red_background)));
}
}
}
@ -873,26 +875,26 @@ public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener {
main.mainBinding.btnSaved.setVisibility(View.VISIBLE);
main.mainBinding.btnLiked.setVisibility(View.VISIBLE);
main.mainBinding.btnSaved.setText(R.string.saved);
main.mainBinding.btnSaved.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
R.color.btn_orange_background, null)));
main.mainBinding.btnSaved.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
main, R.color.btn_orange_background)));
}
} else {
if (Utils.dataBox.getFavorite(main.userQuery) != null) {
main.mainBinding.btnFollow.setText(R.string.unfavorite_short);
main.mainBinding.btnFollow.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
R.color.btn_purple_background, null)));
main.mainBinding.btnFollow.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
main, R.color.btn_purple_background)));
}
else {
main.mainBinding.btnFollow.setText(R.string.favorite_short);
main.mainBinding.btnFollow.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
R.color.btn_pink_background, null)));
main.mainBinding.btnFollow.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
main, R.color.btn_pink_background)));
}
main.mainBinding.btnFollow.setVisibility(View.VISIBLE);
if (!profileModel.isReallyPrivate()) {
main.mainBinding.btnRestrict.setVisibility(View.VISIBLE);
main.mainBinding.btnRestrict.setText(R.string.tagged);
main.mainBinding.btnRestrict.setBackgroundTintList(ColorStateList.valueOf(resources.getColor(
R.color.btn_blue_background, null)));
main.mainBinding.btnRestrict.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(
main, R.color.btn_blue_background)));
}
}
@ -1010,7 +1012,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 iStoryStatusFetcher(profileId.split("/")[0], null, true, false, result -> {
new iStoryStatusFetcher(profileId.split("/")[0], null, true, false, false, false, result -> {
main.storyModels = result;
if (result != null && result.length > 0) main.mainBinding.mainLocationImage.setStoriesBorder();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);

View File

@ -15,6 +15,7 @@ import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -33,6 +34,7 @@ import awais.instagrabber.adapters.HighlightsAdapter;
import awais.instagrabber.adapters.SuggestionsAdapter;
import awais.instagrabber.asyncs.SuggestionsFetcher;
import awais.instagrabber.asyncs.UsernameFetcher;
import awais.instagrabber.asyncs.i.iStoryStatusFetcher;
import awais.instagrabber.customviews.MouseDrawer;
import awais.instagrabber.databinding.ActivityMainBinding;
import awais.instagrabber.dialogs.AboutDialog;
@ -76,10 +78,16 @@ public final class Main extends BaseLanguageActivity {
final Object tag = v.getTag();
if (tag instanceof HighlightModel) {
final HighlightModel highlightModel = (HighlightModel) tag;
startActivity(new Intent(Main.this, StoryViewer.class)
.putExtra(Constants.EXTRAS_USERNAME, userQuery)
.putExtra(Constants.EXTRAS_HIGHLIGHT, highlightModel.getTitle())
.putExtra(Constants.EXTRAS_STORIES, highlightModel.getStoryModels()));
new iStoryStatusFetcher(highlightModel.getId(), null, false, false,
(!mainHelper.isLoggedIn && Utils.settingsHelper.getBoolean(Constants.STORIESIG)), true, result -> {
if (result != null && result.length > 0)
startActivity(new Intent(Main.this, StoryViewer.class)
.putExtra(Constants.EXTRAS_USERNAME, userQuery)
.putExtra(Constants.EXTRAS_HIGHLIGHT, highlightModel.getTitle())
.putExtra(Constants.EXTRAS_STORIES, result)
);
else Toast.makeText(Main.this, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
});
@ -349,7 +357,7 @@ public final class Main extends BaseLanguageActivity {
public boolean onMenuItemActionCollapse(MenuItem item) {
menu.findItem(R.id.action_about).setVisible(true);
menu.findItem(R.id.action_settings).setVisible(true);
menu.findItem(R.id.action_dms).setVisible(true);
menu.findItem(R.id.action_dms).setVisible(!Utils.isEmpty(Utils.settingsHelper.getString(Constants.COOKIE)));
menu.findItem(R.id.action_quickaccess).setVisible(true);
menu.findItem(R.id.action_notif).setVisible(true);
return true;

View File

@ -104,6 +104,7 @@ public final class StoryViewer extends BaseLanguageActivity {
private StoryModel currentStory;
private String url, username;
private int slidePos = 0, lastSlidePos = 0;
private final String cookie = settingsHelper.getString(Constants.COOKIE);
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
@ -160,7 +161,7 @@ public final class StoryViewer extends BaseLanguageActivity {
Toast.makeText(getApplicationContext(), R.string.be_patient, Toast.LENGTH_SHORT).show();
} else {
fetching = true;
new iStoryStatusFetcher(feedStoryModel.getStoryMediaId(), null, false, false, result -> {
new iStoryStatusFetcher(feedStoryModel.getStoryMediaId(), null, false, false, false, false, result -> {
if (result != null && result.length > 0) {
final Intent newIntent = new Intent(getApplicationContext(), StoryViewer.class)
.putExtra(Constants.EXTRAS_STORIES, result)
@ -251,7 +252,7 @@ public final class StoryViewer extends BaseLanguageActivity {
poll.getLeftChoice() + " (" + poll.getLeftCount() + ")",
poll.getRightChoice() + " (" + poll.getRightCount() + ")"
}), (d, w) -> {
new VoteAction().execute(w);
if (!Utils.isEmpty(cookie)) new VoteAction().execute(w);
})
.setPositiveButton(R.string.cancel, null)
.show();
@ -285,7 +286,7 @@ public final class StoryViewer extends BaseLanguageActivity {
}
new AlertDialog.Builder(this).setTitle(quiz.getMyChoice() > -1 ? getString(R.string.story_quizzed) : quiz.getQuestion())
.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, choices), (d,w) -> {
if (quiz.getMyChoice() == -1) new QuizAction().execute(w);
if (quiz.getMyChoice() == -1 && !Utils.isEmpty(cookie)) new QuizAction().execute(w);
})
.setPositiveButton(R.string.cancel, null)
.show();
@ -320,14 +321,14 @@ public final class StoryViewer extends BaseLanguageActivity {
@Override
public void onLoadCompleted(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData) {
if (menuDownload != null) menuDownload.setVisible(true);
if (currentStory.canReply() && menuDm != null) menuDm.setVisible(true);
if (currentStory.canReply() && menuDm != null && !Utils.isEmpty(cookie)) menuDm.setVisible(true);
storyViewerBinding.progressView.setVisibility(View.GONE);
}
@Override
public void onLoadStarted(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData) {
if (menuDownload != null) menuDownload.setVisible(true);
if (currentStory.canReply() && menuDm != null) menuDm.setVisible(true);
if (currentStory.canReply() && menuDm != null && !Utils.isEmpty(cookie)) menuDm.setVisible(true);
storyViewerBinding.progressView.setVisibility(View.VISIBLE);
}
@ -373,7 +374,7 @@ public final class StoryViewer extends BaseLanguageActivity {
@Override
public boolean onResourceReady(final Drawable resource, final Object model, final Target<Drawable> target, final DataSource dataSource, final boolean isFirstResource) {
if (menuDownload != null) menuDownload.setVisible(true);
if (currentStory.canReply() && menuDm != null) menuDm.setVisible(true);
if (currentStory.canReply() && menuDm != null && !Utils.isEmpty(cookie)) menuDm.setVisible(true);
storyViewerBinding.progressView.setVisibility(View.GONE);
return false;
}
@ -497,7 +498,7 @@ public final class StoryViewer extends BaseLanguageActivity {
storyViewerBinding.poll.setTag(poll);
question = currentStory.getQuestion();
storyViewerBinding.answer.setVisibility(question != null ? View.VISIBLE : View.GONE);
storyViewerBinding.answer.setVisibility((question != null && !Utils.isEmpty(cookie)) ? View.VISIBLE : View.GONE);
storyViewerBinding.answer.setTag(question);
mentions = currentStory.getMentions();
@ -508,8 +509,6 @@ public final class StoryViewer extends BaseLanguageActivity {
storyViewerBinding.quiz.setVisibility(quiz != null ? View.VISIBLE : View.GONE);
storyViewerBinding.quiz.setTag(quiz);
storyViewerBinding.toolbar.toolbar.setSubtitle(Utils.datetimeParser.format(new Date(currentStory.getTimestamp() * 1000L)));
releasePlayer();
final Intent intent = getIntent();
if (intent.getBooleanExtra(Constants.EXTRAS_HASHTAG, false)) {
@ -521,6 +520,9 @@ public final class StoryViewer extends BaseLanguageActivity {
if (itemType == MediaItemType.MEDIA_TYPE_VIDEO) setupVideo();
else setupImage();
if (!intent.hasExtra(Constants.EXTRAS_HIGHLIGHT))
storyViewerBinding.toolbar.toolbar.setSubtitle(Utils.datetimeParser.format(new Date(currentStory.getTimestamp() * 1000L)));
if (settingsHelper.getBoolean(MARK_AS_SEEN)) new SeenAction().execute();
}
@ -565,8 +567,7 @@ public final class StoryViewer extends BaseLanguageActivity {
urlConnection.setRequestMethod("POST");
urlConnection.setUseCaches(false);
urlConnection.setRequestProperty("User-Agent", Constants.USER_AGENT);
urlConnection.setRequestProperty("x-csrftoken",
settingsHelper.getString(Constants.COOKIE).split("csrftoken=")[1].split(";")[0]);
urlConnection.setRequestProperty("x-csrftoken", cookie.split("csrftoken=")[1].split(";")[0]);
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
urlConnection.setRequestProperty("Content-Length", "6");
urlConnection.setDoOutput(true);
@ -601,8 +602,7 @@ public final class StoryViewer extends BaseLanguageActivity {
protected Void doInBackground(Integer... rawchoice) {
int choice = rawchoice[0];
final String cookie = settingsHelper.getString(Constants.COOKIE);
final String url = "https://i.instagram.com/api/v1/media/"+currentStory.getStoryMediaId().split("_")[0]+"/"+quiz.getId()+"/story_quiz_answer/";
final String url = "https://i.instagram.com/api/v1/media/"+currentStory.getStoryMediaId().split("_")[0]+"/"+quiz.getId()+"/story_quiz_answer/";
try {
JSONObject ogbody = new JSONObject("{\"client_context\":\"" + UUID.randomUUID().toString()
+"\",\"mutation_token\":\"" + UUID.randomUUID().toString()
@ -650,7 +650,6 @@ public final class StoryViewer extends BaseLanguageActivity {
String action;
protected Void doInBackground(String... rawchoice) {
final String cookie = settingsHelper.getString(Constants.COOKIE);
final String url = "https://i.instagram.com/api/v1/media/"
+currentStory.getStoryMediaId().split("_")[0]+"/"+question.getId()+"/story_question_response/";
try {
@ -696,8 +695,7 @@ public final class StoryViewer extends BaseLanguageActivity {
class SeenAction extends AsyncTask<Void, Void, Void> {
protected Void doInBackground(Void... lmao) {
final String cookie = settingsHelper.getString(Constants.COOKIE);
final String url = "https://www.instagram.com/stories/reel/seen";
final String url = "https://www.instagram.com/stories/reel/seen";
try {
String urlParameters = "reelMediaId="+currentStory.getStoryMediaId().split("_")[0]
+"&reelMediaOwnerId="+currentStory.getUserId()
@ -707,8 +705,7 @@ public final class StoryViewer extends BaseLanguageActivity {
final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setUseCaches(false);
urlConnection.setRequestProperty("x-csrftoken",
settingsHelper.getString(Constants.COOKIE).split("csrftoken=")[1].split(";")[0]);
urlConnection.setRequestProperty("x-csrftoken", cookie.split("csrftoken=")[1].split(";")[0]);
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
urlConnection.setRequestProperty("Content-Length", Integer.toString(urlParameters.getBytes().length));
urlConnection.setDoOutput(true);
@ -731,8 +728,7 @@ public final class StoryViewer extends BaseLanguageActivity {
protected Void doInBackground(String... rawAction) {
final String action = rawAction[0];
final String url = "https://i.instagram.com/api/v1/direct_v2/create_group_thread/";
final String cookie = settingsHelper.getString(Constants.COOKIE);
try {
try {
final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setRequestProperty("User-Agent", Constants.I_USER_AGENT);
@ -780,7 +776,6 @@ public final class StoryViewer extends BaseLanguageActivity {
wr2.flush();
wr2.close();
urlConnection2.connect();
Log.d("austin_debug", urlConnection2.getResponseCode() + " " + urlParameters2 + " " + cookie);
if (urlConnection2.getResponseCode() == HttpURLConnection.HTTP_OK) {
ok = true;
}

View File

@ -17,58 +17,43 @@ import awais.instagrabber.utils.Utils;
public final class HighlightsFetcher extends AsyncTask<Void, Void, HighlightModel[]> {
private final String id;
private final boolean storiesig;
private final FetchListener<HighlightModel[]> fetchListener;
public HighlightsFetcher(final String id, final FetchListener<HighlightModel[]> fetchListener) {
public HighlightsFetcher(final String id, final boolean storiesig, final FetchListener<HighlightModel[]> fetchListener) {
this.id = id;
this.storiesig = storiesig;
this.fetchListener = fetchListener;
}
@Override
protected HighlightModel[] doInBackground(final Void... voids) {
HighlightModel[] result = null;
String url = "https://www.instagram.com/graphql/query/?query_hash=7c16654f22c819fb63d1183034a5162f&variables=" +
"{\"user_id\":\"" + id + "\",\"include_chaining\":false,\"include_reel\":true,\"include_suggested_users\":false," +
"\"include_logged_out_extras\":false,\"include_highlight_reels\":true}";
String url = "https://" + (storiesig ? "storiesig" : "i.instagram") + ".com/api/v1/highlights/" + id + "/highlights_tray/";
try {
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setInstanceFollowRedirects(false);
conn.setUseCaches(false);
conn.setRequestProperty("User-Agent", storiesig ? Constants.A_USER_AGENT : Constants.I_USER_AGENT);
conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
final JSONArray highlightsReel = new JSONObject(Utils.readFromConnection(conn)).getJSONObject("data")
.getJSONObject(Constants.EXTRAS_USER).getJSONObject("edge_highlight_reels").getJSONArray("edges");
final JSONArray highlightsReel = new JSONObject(Utils.readFromConnection(conn)).getJSONArray("tray");
final int length = highlightsReel.length();
final HighlightModel[] highlightModels = new HighlightModel[length];
final String[] highlightIds = new String[length];
for (int i = 0; i < length; ++i) {
final JSONObject highlightNode = highlightsReel.getJSONObject(i).getJSONObject("node");
final String id = highlightNode.getString(Constants.EXTRAS_ID);
highlightIds[i] = id;
final JSONObject highlightNode = highlightsReel.getJSONObject(i);
highlightModels[i] = new HighlightModel(
highlightNode.getString("title"),
highlightNode.getJSONObject("cover_media").getString("thumbnail_src")
highlightNode.getString(Constants.EXTRAS_ID),
highlightNode.getJSONObject("cover_media").getJSONObject("cropped_image_version").getString("url")
);
}
conn.disconnect();
// a22a50ce4582220909e302d6eb84d259
// 45246d3fe16ccc6577e0bd297a5db1ab
url = "https://www.instagram.com/graphql/query/?query_hash=a22a50ce4582220909e302d6eb84d259&variables=" +
"{\"highlight_reel_ids\":" + Utils.highlightIdsMerger(highlightIds) + ",\"reel_ids\":[],\"location_ids\":[],\"precomposed_overlay\":false}";
conn = (HttpURLConnection) new URL(url).openConnection();
conn.setInstanceFollowRedirects(false);
conn.setUseCaches(false);
conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
Utils.putHighlightModels(conn, highlightModels);
}
result = highlightModels;
}

View File

@ -55,11 +55,12 @@ public final class ProfilePictureFetcher extends AsyncTask<Void, Void, String> {
out = data.getJSONObject("hd_profile_pic_url_info").optString("url");
}
if (Utils.isEmpty(out)) {
if (Utils.isEmpty(out) && Utils.settingsHelper.getBoolean(Constants.INSTADP)) {
final HttpURLConnection backup =
(HttpURLConnection) new URL("https://instadp.com/fullsize/" + userName).openConnection();
backup.setUseCaches(false);
backup.setRequestMethod("GET");
backup.setRequestProperty("User-Agent", Constants.A_USER_AGENT);
final String instadp = backup.getResponseCode() == HttpURLConnection.HTTP_OK ? Utils.readFromConnection(backup) : null;
backup.disconnect();
@ -85,7 +86,6 @@ public final class ProfilePictureFetcher extends AsyncTask<Void, Void, String> {
}
}
}
if (out == null) out = picUrl;
}
} catch (final Exception e) {
if (logCollector != null)

View File

@ -23,37 +23,46 @@ import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
public final class iStoryStatusFetcher extends AsyncTask<Void, Void, StoryModel[]> {
private final String id, username;
private final boolean isLoc, isHashtag;
private final String id;
private String username;
private final boolean isLoc, isHashtag, storiesig, highlight;
private final FetchListener<StoryModel[]> fetchListener;
public iStoryStatusFetcher(final String id, final String username, final boolean isLoc,
final boolean isHashtag, final FetchListener<StoryModel[]> fetchListener) {
final boolean isHashtag, final boolean storiesig, final boolean highlight,
final FetchListener<StoryModel[]> fetchListener) {
this.id = id;
this.username = username;
this.isLoc = isLoc;
this.isHashtag = isHashtag;
this.storiesig = storiesig;
this.highlight = highlight;
this.fetchListener = fetchListener;
}
@Override
protected StoryModel[] doInBackground(final Void... voids) {
StoryModel[] result = null;
final String url = "https://i.instagram.com/api/v1/" + (isLoc ? "locations/" : (isHashtag ? "tags/" : "feed/user/"))
+ id + "/story/";
final String url = "https://" + (storiesig ? "storiesig" : "i.instagram") + ".com/api/v1/"
+ (isLoc ? "locations/" : (isHashtag ? "tags/" : (highlight ? "feed/reels_media?user_ids=" : "feed/user/")))
+ id + (highlight ? "" : (storiesig ? "/reel_media/" : "/story/"));
try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setInstanceFollowRedirects(false);
conn.setUseCaches(false);
conn.setRequestProperty("User-Agent", Constants.USER_AGENT);
conn.setRequestProperty("User-Agent", storiesig ? Constants.A_USER_AGENT : Constants.I_USER_AGENT);
conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
JSONObject data = new JSONObject(Utils.readFromConnection(conn)).getJSONObject((isLoc || isHashtag) ? "story" : "reel");
JSONObject data = new JSONObject(Utils.readFromConnection(conn));
if (!storiesig && !highlight) data = data.optJSONObject((isLoc || isHashtag) ? "story" : "reel");
else if (highlight) data = data.getJSONObject("reels").optJSONObject(id);
if (username == null && !isLoc && !isHashtag) username = data.getJSONObject("user").getString("username");
JSONArray media;
if ((media = data.optJSONArray("items")) != null && media.length() > 0 &&
(data = media.optJSONObject(0)) != null) {
if (data != null && (media = data.optJSONArray("items")) != null
&& media.length() > 0 && (data = media.optJSONObject(0)) != null) {
final int mediaLen = media.length();

View File

@ -6,6 +6,7 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
@ -40,16 +41,18 @@ import static awais.instagrabber.utils.Constants.BOTTOM_TOOLBAR;
import static awais.instagrabber.utils.Constants.DOWNLOAD_USER_FOLDER;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
import static awais.instagrabber.utils.Constants.INSTADP;
import static awais.instagrabber.utils.Constants.MARK_AS_SEEN;
import static awais.instagrabber.utils.Constants.MUTED_VIDEOS;
import static awais.instagrabber.utils.Constants.SHOW_FEED;
import static awais.instagrabber.utils.Constants.STORIESIG;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class SettingsDialog extends BottomSheetDialogFragment implements View.OnClickListener, AdapterView.OnItemSelectedListener,
CompoundButton.OnCheckedChangeListener {
private Activity activity;
private FragmentManager fragmentManager;
private View btnSaveTo, btnImportExport, btnLogin, btnLogout, btnTimeSettings, btnReport;
private View btnSaveTo, btnImportExport, btnLogin, btnLogout, btnTimeSettings, btnReport, btnPrivacy;
private AppCompatTextView settingTitle;
private Spinner spAppTheme, spLanguage;
private boolean somethingChanged = false;
@ -94,6 +97,7 @@ public final class SettingsDialog extends BottomSheetDialogFragment implements V
btnImportExport = contentView.findViewById(R.id.importExport);
btnTimeSettings = contentView.findViewById(R.id.btnTimeSettings);
btnReport = contentView.findViewById(R.id.btnReport);
btnPrivacy = contentView.findViewById(R.id.btnPrivacy);
Utils.setTooltipText(btnImportExport, R.string.import_export);
@ -103,6 +107,7 @@ public final class SettingsDialog extends BottomSheetDialogFragment implements V
btnSaveTo.setOnClickListener(this);
btnImportExport.setOnClickListener(this);
btnTimeSettings.setOnClickListener(this);
btnPrivacy.setOnClickListener(this);
if (Utils.isEmpty(settingsHelper.getString(Constants.COOKIE))) btnLogout.setEnabled(false);
@ -123,12 +128,16 @@ public final class SettingsDialog extends BottomSheetDialogFragment implements V
final AppCompatCheckBox cbAutoplayVideos = contentView.findViewById(R.id.cbAutoplayVideos);
final AppCompatCheckBox cbDownloadUsername = contentView.findViewById(R.id.cbDownloadUsername);
final AppCompatCheckBox cbMarkAsSeen = contentView.findViewById(R.id.cbMarkAsSeen);
final AppCompatCheckBox cbInstadp = contentView.findViewById(R.id.cbInstadp);
final AppCompatCheckBox cbStoriesig = contentView.findViewById(R.id.cbStoriesig);
cbSaveTo.setChecked(settingsHelper.getBoolean(FOLDER_SAVE_TO));
cbMuteVideos.setChecked(settingsHelper.getBoolean(MUTED_VIDEOS));
cbBottomToolbar.setChecked(settingsHelper.getBoolean(BOTTOM_TOOLBAR));
cbAutoplayVideos.setChecked(settingsHelper.getBoolean(AUTOPLAY_VIDEOS));
cbMarkAsSeen.setChecked(settingsHelper.getBoolean(MARK_AS_SEEN));
cbInstadp.setChecked(settingsHelper.getBoolean(INSTADP));
cbStoriesig.setChecked(settingsHelper.getBoolean(STORIESIG));
cbAutoloadPosts.setChecked(settingsHelper.getBoolean(AUTOLOAD_POSTS));
cbDownloadUsername.setChecked(settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER));
@ -140,6 +149,8 @@ public final class SettingsDialog extends BottomSheetDialogFragment implements V
setupListener(cbAutoplayVideos);
setupListener(cbDownloadUsername);
setupListener(cbMarkAsSeen);
setupListener(cbInstadp);
setupListener(cbStoriesig);
btnSaveTo.setEnabled(cbSaveTo.isChecked());
@ -183,18 +194,18 @@ public final class SettingsDialog extends BottomSheetDialogFragment implements V
if (ContextCompat.checkSelfPermission(activity, Utils.PERMS[0]) == PackageManager.PERMISSION_DENIED)
requestPermissions(Utils.PERMS, 6007);
else Utils.showImportExportDialog(activity);
} else if (v == btnTimeSettings) {
new TimeSettingsDialog().show(fragmentManager, null);
} else if (v == btnReport) {
CrashReporter.get(activity.getApplication()).zipLogs().startCrashEmailIntent(activity, true);
} else if (v == btnSaveTo) {
if (ContextCompat.checkSelfPermission(activity, Utils.PERMS[0]) == PackageManager.PERMISSION_DENIED)
requestPermissions(Utils.PERMS, 6200);
else showDirectoryChooser();
} else if (v == btnPrivacy) {
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://instagrabber.austinhuang.me/disclosure#for-anonymous-users"));
startActivity(intent);
} else if (v instanceof ViewGroup)
((ViewGroup) v).getChildAt(0).performClick();
}
@ -208,6 +219,8 @@ public final class SettingsDialog extends BottomSheetDialogFragment implements V
else if (id == R.id.cbMuteVideos) settingsHelper.putBoolean(MUTED_VIDEOS, checked);
else if (id == R.id.cbAutoloadPosts) settingsHelper.putBoolean(AUTOLOAD_POSTS, checked);
else if (id == R.id.cbMarkAsSeen) settingsHelper.putBoolean(MARK_AS_SEEN, checked);
else if (id == R.id.cbInstadp) settingsHelper.putBoolean(INSTADP, checked);
else if (id == R.id.cbStoriesig) settingsHelper.putBoolean(STORIESIG, checked);
else if (id == R.id.cbSaveTo) {
settingsHelper.putBoolean(FOLDER_SAVE_TO, checked);
btnSaveTo.setEnabled(checked);

View File

@ -1,11 +1,11 @@
package awais.instagrabber.models;
public final class HighlightModel {
private final String title, thumbnailUrl;
private StoryModel[] storyModels;
private final String title, id, thumbnailUrl;
public HighlightModel(final String title, final String thumbnailUrl) {
public HighlightModel(final String title, final String id, final String thumbnailUrl) {
this.title = title;
this.id = id;
this.thumbnailUrl = thumbnailUrl;
}
@ -13,15 +13,11 @@ public final class HighlightModel {
return title;
}
public String getId() {
return id;
}
public String getThumbnailUrl() {
return thumbnailUrl;
}
public StoryModel[] getStoryModels() {
return storyModels;
}
public void setStoryModels(final StoryModel[] storyModels) {
this.storyModels = storyModels;
}
}

View File

@ -20,6 +20,8 @@ public final class Constants {
public static final String SHOW_FEED = "show_feed";
public static final String CUSTOM_DATE_TIME_FORMAT_ENABLED = "data_time_custom_enabled";
public static final String MARK_AS_SEEN = "mark_as_seen";
public static final String INSTADP = "instadp";
public static final String STORIESIG = "storiesig";
// never Export
public static final String COOKIE = "cookie";
public static final String SHOW_QUICK_ACCESS_DIALOG = "show_quick_dlg";
@ -49,6 +51,7 @@ public final class Constants {
"Instagram 152.0.0.25.117 Android (27/8.1.0; 320dpi; 720x1362; motorola; motorola one; deen_sprout; qcom; pt_BR; 234847224)";
public static final String I_USER_AGENT =
"Instagram 152.0.0.25.117 Android (27/8.1.0; 320dpi; 720x1362; motorola; motorola one; deen_sprout; qcom; pt_BR; 234847224)";
public static final String A_USER_AGENT = "InstaGrabber.AustinHuang.me / InstaGrabber@AustinHuang.me";
// see https://github.com/dilame/instagram-private-api/blob/master/src/core/constants.ts
public static final String SUPPORTED_CAPABILITIES = "[ { \"name\": \"SUPPORTED_SDK_VERSIONS\", \"value\":" +
" \"13.0,14.0,15.0,16.0,17.0,18.0,19.0,20.0,21.0,22.0,23.0,24.0,25.0,26.0,27.0,28.0,29.0,30.0,31.0," +

View File

@ -22,10 +22,12 @@ import static awais.instagrabber.utils.Constants.DEVICE_UUID;
import static awais.instagrabber.utils.Constants.DOWNLOAD_USER_FOLDER;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
import static awais.instagrabber.utils.Constants.INSTADP;
import static awais.instagrabber.utils.Constants.MARK_AS_SEEN;
import static awais.instagrabber.utils.Constants.MUTED_VIDEOS;
import static awais.instagrabber.utils.Constants.PREV_INSTALL_VERSION;
import static awais.instagrabber.utils.Constants.SHOW_QUICK_ACCESS_DIALOG;
import static awais.instagrabber.utils.Constants.STORIESIG;
public final class SettingsHelper {
private final SharedPreferences sharedPreferences;
@ -107,7 +109,7 @@ public final class SettingsHelper {
public @interface StringSettings {}
@StringDef({DOWNLOAD_USER_FOLDER, BOTTOM_TOOLBAR, FOLDER_SAVE_TO, AUTOPLAY_VIDEOS, SHOW_QUICK_ACCESS_DIALOG, MUTED_VIDEOS,
AUTOLOAD_POSTS, CUSTOM_DATE_TIME_FORMAT_ENABLED, MARK_AS_SEEN})
AUTOLOAD_POSTS, CUSTOM_DATE_TIME_FORMAT_ENABLED, MARK_AS_SEEN, INSTADP, STORIESIG})
public @interface BooleanSettings {}
@StringDef({APP_THEME, APP_LANGUAGE, PREV_INSTALL_VERSION})

View File

@ -1173,95 +1173,6 @@ public final class Utils {
dialog[0] = new AlertDialog.Builder(context).setView(importExportBinding.getRoot()).show();
}
// taken from Arrays.toString()
@NonNull
public static String highlightIdsMerger(final String... strings) {
if (strings != null) {
int iMax = strings.length - 1;
if (iMax != -1) {
final StringBuilder builder = new StringBuilder();
builder.append('[');
for (int i = 0; ; i++) {
builder.append('"').append(strings[i]).append('"');
if (i == iMax) return builder.append(']').toString();
builder.append(',');
}
}
}
return "[]";
}
public static void putHighlightModels(final HttpURLConnection conn, final Object[] model) throws Exception {
final boolean isHighlightModel = model instanceof HighlightModel[];
final boolean isFeedStoryModel = model instanceof FeedStoryModel[];
if (isHighlightModel || isFeedStoryModel) {
final JSONArray highlightsMediaReel = new JSONObject(Utils.readFromConnection(conn)).getJSONObject("data").getJSONArray("reels_media");
final int mediaLength = highlightsMediaReel.length();
for (int i = 0; i < mediaLength; ++i) {
final JSONArray items = highlightsMediaReel.getJSONObject(i).getJSONArray("items");
final int itemsLen = items.length();
final StoryModel[] storyModels = new StoryModel[itemsLen];
for (int j = 0; j < itemsLen; ++j) {
final JSONObject data = items.getJSONObject(j);
final boolean isVideo = data.getBoolean("is_video");
boolean hasTappableObjecs = data.has("tappable_objects");
final JSONArray tappableObjects;
final int tappableLength;
if (hasTappableObjecs) {
tappableObjects = data.getJSONArray("tappable_objects");
tappableLength = tappableObjects.length();
hasTappableObjecs = tappableLength > 0;
} else {
tappableLength = 0;
tappableObjects = null;
}
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.getJSONObject("owner").getString("username"),
data.getJSONObject("owner").getString("id"), data.getBoolean("can_reply"));
if (isVideo && data.has("video_resources"))
storyModels[j].setVideoUrl(Utils.getHighQualityPost(data.getJSONArray("video_resources"), true, false, false));
if (!data.isNull("story_app_attribution"))
storyModels[j].setSpotify(data.getJSONObject("story_app_attribution").optString("content_url").split("\\?")[0]);
if (hasTappableObjecs) {
for (int k = 0; k < tappableLength; ++k) {
JSONObject jsonObject = tappableObjects.getJSONObject(k);
if (jsonObject.getString("__typename").equals("GraphTappableFeedMedia") && jsonObject.has("media")) {
storyModels[j].setTappableShortCode(jsonObject.getJSONObject("media").getString(Constants.EXTRAS_SHORTCODE));
}
else if (jsonObject.optString("__typename").equals("GraphTappableStoryPoll") && !jsonObject.isNull("id")) {
storyModels[j].setPoll(new PollModel(
jsonObject.getString("id"),
jsonObject.getString("question"),
jsonObject.getJSONArray("tallies").getJSONObject(0).getString("text"),
jsonObject.getJSONArray("tallies").getJSONObject(0).getInt("count"),
jsonObject.getJSONArray("tallies").getJSONObject(1).getString("text"),
jsonObject.getJSONArray("tallies").getJSONObject(1).getInt("count"),
jsonObject.optInt("viewer_vote", -1)
));
}
}
}
}
if (isHighlightModel)
((HighlightModel[]) model)[i].setStoryModels(storyModels);
else
((FeedStoryModel[]) model)[i].setStoryModels(storyModels);
}
}
}
public static String sign(final String message) {
try {
Mac hasher = Mac.getInstance("HmacSHA256");

View File

@ -259,6 +259,17 @@
android:textSize="16sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:padding="5dp"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:text="@string/login_settings"
android:textColor="?android:textColorPrimary"
android:textSize="16sp" />
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -284,6 +295,80 @@
android:textSize="16sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:background="?android:selectableItemBackground"
android:orientation="horizontal"
android:padding="5dp">
<androidx.appcompat.widget.AppCompatTextView
android:gravity="center_vertical"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="@string/anonymous_settings"
android:textColor="?android:textColorPrimary"
android:textSize="16sp" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnPrivacy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/privacy_warning" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:background="?android:selectableItemBackground"
android:orientation="horizontal"
android:padding="5dp">
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbInstadp"
android:layout_width="30dp"
android:layout_height="30dp"
android:contentDescription="@string/instadp_settings" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:padding="5dp"
android:text="@string/instadp_settings"
android:textColor="?android:textColorPrimary"
android:textSize="16sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:background="?android:selectableItemBackground"
android:orientation="horizontal"
android:padding="5dp">
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cbStoriesig"
android:layout_width="30dp"
android:layout_height="30dp"
android:contentDescription="@string/storiesig_settings" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:padding="5dp"
android:text="@string/storiesig_settings"
android:textColor="?android:textColorPrimary"
android:textSize="16sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@ -45,6 +45,11 @@
<string name="save_to_folder">Save to custom folder</string>
<string name="select_folder">Select folder</string>
<string name="theme_settings">Theme Settings</string>
<string name="login_settings">Only affects logged-in users:</string>
<string name="anonymous_settings">Only affects anonymous users:</string>
<string name="privacy_warning">Privacy</string>
<string name="instadp_settings">Use Instadp for high definition profile pictures</string>
<string name="storiesig_settings">Use storiesig for stories and highlights from public users</string>
<string name="import_export">Import/Export</string>
<string name="select_language">Select language</string>
<string name="what_to_do_dialog">What to do?</string>
@ -67,6 +72,7 @@
<string name="view_story_post">View Post</string>
<string name="view_post">View Post</string>
<string name="spotify">Spotify</string>
<string name="stories_notloggedin">Can\'t interact if y\'ain\'t logged in!</string>
<string name="vote_story_poll">Vote</string>
<string name="votef_story_poll">Vote successful!</string>
<string name="voted_story_poll">You have already voted!</string>

View File

@ -0,0 +1,4 @@
* You can now view public stories and highlights WITHOUT logging in, but only if you enable it in Settings
* Originally, viewing HD avatars without logging in automatically uses Instadp, now you have to enable it in Settings as well
* Consent is important! A button now tells you
* Highlights now support showing sticker infos