answering polls in stories

This commit is contained in:
Austin Huang 2020-07-30 18:19:44 -04:00
parent e1c07c23a7
commit 71402cd164
No known key found for this signature in database
GPG Key ID: 84C23AA04587A91F
13 changed files with 207 additions and 31 deletions

View File

@ -34,6 +34,7 @@ Not compatible with pre-16.6 versions (including alpha).
Remember to read the [wiki](https://github.com/austinhuang0131/instagrabber/wiki) for more info!
[![Open Source Love svg3](https://badges.frapsoft.com/os/v3/open-source.svg?v=103)](https://github.com/ellerbrock/open-source-badges/)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com)
[![GPLv3 license](https://img.shields.io/badge/License-GPLv3-blue.svg)](./LICENSE)
[![Snyk Vulnerabilities](https://img.shields.io/snyk/vulnerabilities/github/austinhuang0131/instagrabber)](https://snyk.io/test/github/austinhuang0131/instagrabber)
[![LGTM Alerts](https://img.shields.io/lgtm/alerts/github/austinhuang0131/instagrabber)](https://lgtm.com/projects/g/austinhuang0131/instagrabber)

View File

@ -15,10 +15,12 @@ import android.util.Pair;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.view.GestureDetectorCompat;
@ -36,8 +38,11 @@ import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
@ -47,6 +52,7 @@ import awais.instagrabber.customviews.helpers.SwipeGestureListener;
import awais.instagrabber.databinding.ActivityStoryViewerBinding;
import awais.instagrabber.interfaces.SwipeEvent;
import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.models.PollModel;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.MediaItemType;
@ -79,6 +85,7 @@ public final class StoryViewer extends BaseLanguageActivity {
private SimpleExoPlayer player;
private SwipeEvent swipeEvent;
private MenuItem menuDownload;
private PollModel poll;
private StoryModel currentStory;
private String url, username;
private int slidePos = 0, lastSlidePos = 0;
@ -121,8 +128,6 @@ public final class StoryViewer extends BaseLanguageActivity {
@Override
public void onSwipe(final boolean isRightSwipe) {
Log.d("austin_debug", "swipe: "+(isRightSwipe ? "backward " : "forward ") + slidePos + "/" + storiesLen + " "
+ (slidePos == storiesLen - 1 && isRightSwipe == false) + " " + intent.hasExtra(Constants.FEED));
if (storyModels != null && storiesLen > 0) {
if (((slidePos + 1 >= storiesLen && isRightSwipe == false) || (slidePos == 0 && isRightSwipe == true))
&& intent.hasExtra(Constants.FEED)) {
@ -195,6 +200,30 @@ public final class StoryViewer extends BaseLanguageActivity {
.putExtra(Constants.EXTRAS_POST, new PostModel(tag.toString())));
});
storyViewerBinding.interactStory.setOnClickListener(v -> {
final Object tag = v.getTag();
if (tag instanceof PollModel) {
poll = (PollModel) tag;
if (poll.getMyChoice() > -1)
new AlertDialog.Builder(this).setTitle(R.string.voted_story_poll)
.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new String[]{
(poll.getMyChoice() == 0 ? "" : "") + poll.getLeftChoice() + " (" + poll.getLeftCount() + ")",
(poll.getMyChoice() == 1 ? "" : "") + poll.getRightChoice() + " (" + poll.getRightCount() + ")"
}), null)
.setPositiveButton(R.string.ok, null)
.show();
else new AlertDialog.Builder(this).setTitle(poll.getQuestion())
.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new String[]{
poll.getLeftChoice() + " (" + poll.getLeftCount() + ")",
poll.getRightChoice() + " (" + poll.getRightCount() + ")"
}), (d, w) -> {
new VoteAction().execute(w);
})
.setPositiveButton(R.string.cancel, null)
.show();
}
});
storiesAdapter.setData(storyModels);
if (storyModels.length > 1) storyViewerBinding.storiesList.setVisibility(View.VISIBLE);
@ -368,9 +397,14 @@ public final class StoryViewer extends BaseLanguageActivity {
storyViewerBinding.viewStoryPost.setVisibility(shortCode != null ? View.VISIBLE : View.GONE);
storyViewerBinding.viewStoryPost.setTag(shortCode);
final PollModel poll = currentStory.getPoll();
storyViewerBinding.interactStory.setVisibility(poll != null ? View.VISIBLE : View.GONE);
storyViewerBinding.interactStory.setText(R.string.vote_story_poll);
storyViewerBinding.interactStory.setTag(poll);
releasePlayer();
final Intent intent = getIntent();
if (intent.hasExtra(Constants.EXTRAS_HASHTAG)) {
if (intent.getBooleanExtra(Constants.EXTRAS_HASHTAG, false)) {
storyViewerBinding.toolbar.toolbar.setTitle(currentStory.getUsername() + " (" + intent.getStringExtra(Constants.EXTRAS_USERNAME) + ")");
storyViewerBinding.toolbar.toolbar.setOnClickListener(v -> {
searchUsername(currentStory.getUsername());
@ -408,4 +442,46 @@ public final class StoryViewer extends BaseLanguageActivity {
}
return returnvalue;
}
class VoteAction extends AsyncTask<Integer, Void, Void> {
int ok = -1;
String action;
protected Void doInBackground(Integer... rawchoice) {
int choice = rawchoice[0];
final String url = "https://www.instagram.com/media/"+currentStory.getStoryMediaId()+"/"+poll.getId()+"/story_poll_vote/";
try {
final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
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("Content-Type", "application/x-www-form-urlencoded");
urlConnection.setRequestProperty("Content-Length", "6");
urlConnection.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream());
wr.writeBytes("vote="+choice);
wr.flush();
wr.close();
urlConnection.connect();
if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
ok = choice;
}
else Toast.makeText(getApplicationContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
urlConnection.disconnect();
} catch (Throwable ex) {
Log.e("austin_debug", "vote: " + ex);
}
return null;
}
@Override
protected void onPostExecute(Void result) {
if (ok > -1) {
poll.setMyChoice(ok);
Toast.makeText(getApplicationContext(), R.string.votef_story_poll, Toast.LENGTH_SHORT).show();
}
}
}
}

View File

@ -425,8 +425,6 @@ public final class FeedAdapter extends RecyclerView.Adapter<FeedItemViewHolder>
if (btnMute != null) btnMute.setVisibility(View.VISIBLE);
final PlayerView playerView = new PlayerView(context);
Log.d("austin_debug","1");
player = new SimpleExoPlayer.Builder(context).build();
playerView.setPlayer(player);

View File

@ -177,7 +177,7 @@ public final class DiscoverFetcher extends AsyncTask<Void, Void, DiscoverItemMod
Utils.getThumbnailUrl(media, mediaType));
Utils.checkExistence(downloadDir, customDir, username,
mediaType == MediaItemType.MEDIA_TYPE_SLIDER, -1, model);
mediaType == MediaItemType.MEDIA_TYPE_SLIDER, model);
return model;
}

View File

@ -26,7 +26,6 @@ public final class LocationFetcher extends AsyncTask<Void, Void, LocationModel>
private final String idSlug;
public LocationFetcher(String idSlug, FetchListener<LocationModel> fetchListener) {
Log.d("austin_debug", idSlug);
// idSlug = id + "/" + slug
this.idSlug = idSlug;
this.fetchListener = fetchListener;

View File

@ -95,7 +95,7 @@ public final class PostFetcher extends AsyncTask<Void, Void, ViewerPostModel[]>
postModel.setCommentsCount(commentsCount);
postModel.setCommentsEndCursor(endCursor);
Utils.checkExistence(downloadDir, customDir, username, false, -1, postModel);
Utils.checkExistence(downloadDir, customDir, username, false, postModel);
result = new ViewerPostModel[]{postModel};
@ -119,7 +119,7 @@ public final class PostFetcher extends AsyncTask<Void, Void, ViewerPostModel[]>
media.optJSONObject("location"));
postModels[i].setSliderDisplayUrl(node.getString("display_url"));
Utils.checkExistence(downloadDir, customDir, username, true, i, postModels[i]);
Utils.checkExistence(downloadDir, customDir, username, true, postModels[i]);
}
postModels[0].setCommentsCount(commentsCount);

View File

@ -115,7 +115,7 @@ public final class PostsFetcher extends AsyncTask<Void, Void, PostModel[]> {
mediaNode.getLong("taken_at_timestamp"), mediaNode.optBoolean("viewer_has_liked"),
mediaNode.optBoolean("viewer_has_saved"), mediaNode.getJSONObject("edge_liked_by").getLong("count"));
Utils.checkExistence(downloadDir, customDir, username, isSlider, -1, models[i]);
Utils.checkExistence(downloadDir, customDir, username, isSlider, models[i]);
}
if (models[models.length - 1] != null)

View File

@ -12,6 +12,7 @@ import java.net.URL;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.models.PollModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
@ -34,8 +35,8 @@ public final class StoryStatusFetcher extends AsyncTask<Void, Void, StoryModel[]
@Override
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,"
final String url = "https://www.instagram.com/graphql/query/?query_hash=90709b530ea0969f002c86a89b4f2b8d&variables=" +
"{\"precomposed_overlay\":false,\"show_story_viewer_list\":false,\"stories_video_dash_manifest\":false,"
+(!Utils.isEmpty(hashtag) ? ("\"tag_names\":\""+hashtag+"\"}") : (
location ? "\"location_ids\":[\""+id.split("/")[0]+"\"]}" : "\"reel_ids\":[\"" + id + "\"]}"));
@ -76,9 +77,19 @@ public final class StoryStatusFetcher extends AsyncTask<Void, Void, StoryModel[]
for (int j = 0; j < tappableLength; ++j) {
JSONObject tappableObject = tappableObjects.getJSONObject(j);
if (tappableObject.optString("__typename").equals("GraphTappableFeedMedia")) {
tappableObject = tappableObject.getJSONObject("media");
models[i].setTappableShortCode(tappableObject.getString(Constants.EXTRAS_SHORTCODE));
break;
models[i].setTappableShortCode(tappableObject.getJSONObject("media").getString(Constants.EXTRAS_SHORTCODE));
}
else if (tappableObject.optString("__typename").equals("GraphTappableStoryPoll")) {
Log.d("austin_debug", "poll: "+url+" "+tappableObject);
models[i].setPoll(new PollModel(
tappableObject.getString("id"),
tappableObject.getString("question"),
tappableObject.getJSONArray("tallies").getJSONObject(0).getString("text"),
tappableObject.getJSONArray("tallies").getJSONObject(0).getInt("count"),
tappableObject.getJSONArray("tallies").getJSONObject(1).getString("text"),
tappableObject.getJSONArray("tallies").getJSONObject(1).getInt("count"),
tappableObject.optInt("viewer_vote", -1)
));
}
}
}

View File

@ -0,0 +1,52 @@
package awais.instagrabber.models;
import java.io.Serializable;
public final class PollModel implements Serializable {
private int leftcount, rightcount, mychoice;
private final String id, question, leftchoice, rightchoice;
public PollModel(final String id, final String question, final String leftchoice, final int leftcount,
final String rightchoice, final int rightcount, final int mychoice) {
this.id = id; // only the poll id
this.question = question;
this.leftchoice = leftchoice;
this.leftcount = leftcount;
this.rightchoice = rightchoice;
this.rightcount = rightcount;
this.mychoice = mychoice;
}
public String getId() {
return id;
}
public String getQuestion() {
return question;
}
public String getLeftChoice() {
return leftchoice;
}
public int getLeftCount() {
return leftcount;
}
public String getRightChoice() {
return rightchoice;
}
public int getRightCount() {
return rightcount;
}
public int getMyChoice() { return mychoice; }
public int setMyChoice(final int choice) {
this.mychoice = choice;
if (choice == 0) this.leftcount += 1;
else if (choice == 1) this.rightcount += 1;
return choice;
}
}

View File

@ -9,6 +9,7 @@ public final class StoryModel implements Serializable {
private final MediaItemType itemType;
private final long timestamp;
private String videoUrl, tappableShortCode;
private PollModel poll;
private int position;
private boolean isCurrentSlide = false;
@ -44,6 +45,10 @@ public final class StoryModel implements Serializable {
return tappableShortCode;
}
public PollModel getPoll() {
return poll;
}
public int getPosition() {
return position;
}
@ -56,6 +61,10 @@ public final class StoryModel implements Serializable {
this.tappableShortCode = tappableShortCode;
}
public void setPoll(final PollModel poll) {
this.poll = poll;
}
public void setPosition(final int position) {
this.position = position;
}

View File

@ -70,6 +70,7 @@ import awais.instagrabber.models.BasePostModel;
import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.models.HighlightModel;
import awais.instagrabber.models.IntentModel;
import awais.instagrabber.models.PollModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.direct_messages.DirectItemModel;
@ -972,17 +973,17 @@ public final class Utils {
}
public static void checkExistence(final File downloadDir, final File customDir, final String username, final boolean isSlider,
final int sliderIndex, @NonNull final BasePostModel model) {
@NonNull final BasePostModel model) {
boolean exists = false;
try {
final String displayUrl = model.getDisplayUrl();
final int index = displayUrl.indexOf('?');
final String fileName = model.getPostId() + '_' + model.getPosition();
final String fileName = model.getPostId() + '_';
final String extension = displayUrl.substring(index - 4, index);
final String fileWithoutPrefix = fileName + extension;
final String fileWithoutPrefix = fileName + '0' + extension;
exists = new File(downloadDir, fileWithoutPrefix).exists();
if (!exists) {
if (customDir != null) exists = new File(customDir, fileWithoutPrefix).exists();
@ -993,8 +994,8 @@ public final class Utils {
exists = new File(new File(customDir, username), fileWithoutPrefix).exists();
}
if (!exists && isSlider && sliderIndex != -1) {
final String fileWithPrefix = fileName + "_slide_[\\d]+" + extension;
if (!exists && isSlider) {
final String fileWithPrefix = fileName + "[\\d]+_slide_[\\d]+" + extension;
final FilenameFilter filenameFilter = (dir, name) -> Pattern.matches(fileWithPrefix, name);
File[] files = downloadDir.listFiles(filenameFilter);
@ -1007,7 +1008,6 @@ public final class Utils {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.UTILS, "checkExistence",
new Pair<>("isSlider", isSlider),
new Pair<>("sliderIndex", sliderIndex),
new Pair<>("model", model));
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
@ -1167,9 +1167,18 @@ public final class Utils {
for (int k = 0; k < tappableLength; ++k) {
JSONObject jsonObject = tappableObjects.getJSONObject(k);
if (jsonObject.getString("__typename").equals("GraphTappableFeedMedia") && jsonObject.has("media")) {
jsonObject = jsonObject.getJSONObject("media");
storyModels[j].setTappableShortCode(jsonObject.getString(Constants.EXTRAS_SHORTCODE));
break;
storyModels[j].setTappableShortCode(jsonObject.getJSONObject("media").getString(Constants.EXTRAS_SHORTCODE));
}
else if (jsonObject.optString("__typename").equals("GraphTappableStoryPoll")) {
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)
));
}
}
}

View File

@ -38,15 +38,33 @@
android:layout_gravity="center"
android:visibility="gone" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/viewStoryPost"
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/postActions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:text="@string/view_story_post"
android:textColor="@color/btn_green_text_color"
android:visibility="gone"
app:backgroundTint="@color/btn_green_background" />
android:layout_weight="0.3"
android:background="#0000"
android:weightSum="2"
android:layout_gravity="bottom">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/viewStoryPost"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/view_story_post"
android:textColor="@color/btn_green_text_color"
android:visibility="gone"
app:backgroundTint="@color/btn_green_background" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/interactStory"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/view_story_post"
android:textColor="@color/btn_blue_text_color"
android:visibility="gone"
app:backgroundTint="@color/btn_blue_background" />
</androidx.appcompat.widget.LinearLayoutCompat>
</FrameLayout>
<androidx.recyclerview.widget.RecyclerView

View File

@ -60,6 +60,9 @@
<string name="show_stories">Show stories</string>
<string name="no_more_stories">No more stories!</string>
<string name="view_story_post">View Story Post</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>
<string name="priv_acc">This Account is Private</string>
<string name="empty_acc">This Account has No Posts</string>
<string name="curr_version">Current version: v%s</string>