convert StoryModel to StoryMedia

close #1151, close #1208
This commit is contained in:
Austin Huang 2021-06-29 16:56:12 -04:00
parent 53b0301385
commit 4d9494cbcf
No known key found for this signature in database
GPG Key ID: 84C23AA04587A91F
25 changed files with 292 additions and 603 deletions

View File

@ -10,20 +10,21 @@ import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.databinding.ItemStoryBinding;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.repositories.responses.stories.StoryMedia;
import awais.instagrabber.utils.ResponseBodyUtils;
public final class StoriesAdapter extends ListAdapter<StoryModel, StoriesAdapter.StoryViewHolder> {
public final class StoriesAdapter extends ListAdapter<StoryMedia, StoriesAdapter.StoryViewHolder> {
private final OnItemClickListener onItemClickListener;
private static final DiffUtil.ItemCallback<StoryModel> diffCallback = new DiffUtil.ItemCallback<StoryModel>() {
private static final DiffUtil.ItemCallback<StoryMedia> diffCallback = new DiffUtil.ItemCallback<StoryMedia>() {
@Override
public boolean areItemsTheSame(@NonNull final StoryModel oldItem, @NonNull final StoryModel newItem) {
return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId());
public boolean areItemsTheSame(@NonNull final StoryMedia oldItem, @NonNull final StoryMedia newItem) {
return oldItem.getId().equals(newItem.getId());
}
@Override
public boolean areContentsTheSame(@NonNull final StoryModel oldItem, @NonNull final StoryModel newItem) {
return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId());
public boolean areContentsTheSame(@NonNull final StoryMedia oldItem, @NonNull final StoryMedia newItem) {
return oldItem.getId().equals(newItem.getId());
}
};
@ -42,8 +43,8 @@ public final class StoriesAdapter extends ListAdapter<StoryModel, StoriesAdapter
@Override
public void onBindViewHolder(@NonNull final StoryViewHolder holder, final int position) {
final StoryModel storyModel = getItem(position);
holder.bind(storyModel, position, onItemClickListener);
final StoryMedia storyMedia = getItem(position);
holder.bind(storyMedia, position, onItemClickListener);
}
public final static class StoryViewHolder extends RecyclerView.ViewHolder {
@ -54,7 +55,7 @@ public final class StoriesAdapter extends ListAdapter<StoryModel, StoriesAdapter
this.binding = binding;
}
public void bind(final StoryModel model,
public void bind(final StoryMedia model,
final int position,
final OnItemClickListener clickListener) {
if (model == null) return;
@ -67,14 +68,11 @@ public final class StoriesAdapter extends ListAdapter<StoryModel, StoriesAdapter
});
binding.selectedView.setVisibility(model.isCurrentSlide() ? View.VISIBLE : View.GONE);
binding.icon.setImageURI(model.getStoryUrl());
// Glide.with(itemView).load(model.getStoryUrl())
// .apply(new RequestOptions().override(width, height))
// .into(holder.icon);
binding.icon.setImageURI(ResponseBodyUtils.getThumbUrl(model));
}
}
public interface OnItemClickListener {
void onItemClick(StoryModel storyModel, int position);
void onItemClick(StoryMedia storyModel, int position);
}
}

View File

@ -548,7 +548,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
// private void fetchStories() {
// if (!isLoggedIn) return;
// storiesFetching = true;
// storiesRepository.getUserStory(
// storiesRepository.getStories(
// StoryViewerOptions.forHashtag(hashtagModel.getName()),
// CoroutineUtilsKt.getContinuation((storyModels, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
// if (throwable != null) {

View File

@ -552,7 +552,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
// private void fetchStories() {
// if (isLoggedIn) {
// storiesFetching = true;
// storiesRepository.getUserStory(
// storiesRepository.getStories(
// StoryViewerOptions.forLocation(locationId, locationModel.getName()),
// CoroutineUtilsKt.getContinuation((storyModels, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
// if (throwable != null) {

View File

@ -58,8 +58,10 @@ import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import java.io.IOException;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.stream.Collectors;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -72,23 +74,17 @@ import awais.instagrabber.databinding.FragmentStoryViewerBinding;
import awais.instagrabber.fragments.main.ProfileFragmentDirections;
import awais.instagrabber.fragments.settings.PreferenceKeys;
import awais.instagrabber.interfaces.SwipeEvent;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.models.stickers.PollModel;
import awais.instagrabber.models.stickers.QuestionModel;
import awais.instagrabber.models.stickers.QuizModel;
import awais.instagrabber.models.stickers.SliderModel;
import awais.instagrabber.models.stickers.SwipeUpModel;
import awais.instagrabber.repositories.requests.StoryViewerOptions;
import awais.instagrabber.repositories.requests.StoryViewerOptions.Type;
import awais.instagrabber.repositories.requests.directmessages.ThreadIdsOrUserIds;
import awais.instagrabber.repositories.responses.stories.Broadcast;
import awais.instagrabber.repositories.responses.stories.Story;
import awais.instagrabber.repositories.responses.stories.*;
import awais.instagrabber.utils.AppExecutors;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.CoroutineUtilsKt;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.ArchivesViewModel;
@ -121,16 +117,16 @@ public class StoryViewerFragment extends Fragment {
private GestureDetectorCompat gestureDetector;
private StoriesRepository storiesRepository;
private MediaRepository mediaRepository;
private StoryModel currentStory;
private StoryMedia currentStory;
private Broadcast live;
private int slidePos;
private int lastSlidePos;
private String url;
private PollModel poll;
private QuestionModel question;
private String[] mentions;
private QuizModel quiz;
private SliderModel slider;
private PollSticker poll;
private QuestionSticker question;
private List<String> mentions = new ArrayList<String>();
private QuizSticker quiz;
private SliderSticker slider;
private MenuItem menuDownload, menuDm, menuProfile;
private SimpleExoPlayer player;
// private boolean isHashtag;
@ -220,10 +216,10 @@ public class StoryViewerFragment extends Fragment {
csrfToken,
userId,
deviceId,
ThreadIdsOrUserIds.Companion.ofOneUser(String.valueOf(currentStory.getUserId())),
ThreadIdsOrUserIds.Companion.ofOneUser(String.valueOf(currentStory.getUser().getPk())),
input.getText().toString(),
currentStory.getStoryMediaId(),
String.valueOf(currentStory.getUserId()),
currentStory.getId(),
String.valueOf(currentStory.getUser().getPk()),
CoroutineUtilsKt.getContinuation(
(directThreadBroadcastResponse, throwable1) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
if (throwable1 != null) {
@ -253,7 +249,7 @@ public class StoryViewerFragment extends Fragment {
return true;
}
if (itemId == R.id.action_profile) {
openProfile("@" + currentStory.getUsername());
openProfile("@" + currentStory.getUser().getPk());
}
return false;
}
@ -357,7 +353,7 @@ public class StoryViewerFragment extends Fragment {
final Context context = getContext();
if (context == null) return;
swipeEvent = isRightSwipe -> {
final List<StoryModel> storyModels = storiesViewModel.getList().getValue();
final List<StoryMedia> storyModels = storiesViewModel.getList().getValue();
final int storiesLen = storyModels == null ? 0 : storyModels.size();
if (sticking) {
Toast.makeText(context, R.string.follower_wait_to_load, Toast.LENGTH_SHORT).show();
@ -373,12 +369,14 @@ public class StoryViewerFragment extends Fragment {
Toast.makeText(context, R.string.no_more_stories, Toast.LENGTH_SHORT).show();
return;
}
removeStickers();
final Object feedStoryModel = isRightSwipe
? finalModels.get(index - 1)
: finalModels.size() == index + 1 ? null : finalModels.get(index + 1);
paginateStories(feedStoryModel, finalModels.get(index), context, isRightSwipe, currentFeedStoryIndex == finalModels.size() - 2);
return;
}
removeStickers();
if (isRightSwipe) {
if (--slidePos <= 0) {
slidePos = 0;
@ -471,35 +469,31 @@ public class StoryViewerFragment extends Fragment {
});
final View.OnClickListener storyActionListener = v -> {
final Object tag = v.getTag();
if (tag instanceof PollModel) {
poll = (PollModel) tag;
if (poll.getMyChoice() > -1) {
if (tag instanceof PollSticker) {
poll = (PollSticker) tag;
final List<Tally> tallies = poll.getTallies();
final String[] choices = tallies.stream()
.map(t -> (poll.getViewerVote() == tallies.indexOf(t) ? "" : "")
+ t.getText() + " (" + t.getCount() + ")" )
.toArray(String[]::new);
final ArrayAdapter adapter = new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, choices);
if (poll.getViewerVote() > -1) {
new AlertDialog.Builder(context)
.setTitle(R.string.voted_story_poll)
.setAdapter(new ArrayAdapter<>(
context,
android.R.layout.simple_list_item_1,
new String[]{
(poll.getMyChoice() == 0 ? "" : "") + poll.getLeftChoice() + " (" + poll.getLeftCount() + ")",
(poll.getMyChoice() == 1 ? "" : "") + poll.getRightChoice() + " (" + poll.getRightCount() + ")"
}),
null)
.setAdapter(adapter, null)
.setPositiveButton(R.string.ok, null)
.show();
} else {
new AlertDialog.Builder(context)
.setTitle(poll.getQuestion())
.setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, new String[]{
poll.getLeftChoice() + " (" + poll.getLeftCount() + ")",
poll.getRightChoice() + " (" + poll.getRightCount() + ")"
}), (d, w) -> {
.setAdapter(adapter, (d, w) -> {
sticking = true;
storiesRepository.respondToPoll(
csrfToken,
userId,
deviceId,
currentStory.getStoryMediaId().split("_")[0],
poll.getId(),
currentStory.getId().split("_")[0],
poll.getPollId(),
w,
CoroutineUtilsKt.getContinuation(
(storyStickerResponse, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
@ -513,7 +507,7 @@ public class StoryViewerFragment extends Fragment {
}
sticking = false;
try {
poll.setMyChoice(w);
poll.setViewerVote(w);
Toast.makeText(context, R.string.votef_story_poll, Toast.LENGTH_SHORT).show();
} catch (Exception ignored) {}
}),
@ -524,8 +518,8 @@ public class StoryViewerFragment extends Fragment {
.setPositiveButton(R.string.cancel, null)
.show();
}
} else if (tag instanceof QuestionModel) {
question = (QuestionModel) tag;
} else if (tag instanceof QuestionSticker) {
question = (QuestionSticker) tag;
final EditText input = new EditText(context);
input.setHint(R.string.answer_hint);
final AlertDialog ad = new AlertDialog.Builder(context)
@ -537,8 +531,8 @@ public class StoryViewerFragment extends Fragment {
csrfToken,
userId,
deviceId,
currentStory.getStoryMediaId().split("_")[0],
question.getId(),
currentStory.getId().split("_")[0],
question.getQuestionId(),
input.getText().toString(),
CoroutineUtilsKt.getContinuation(
(storyStickerResponse, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
@ -575,28 +569,31 @@ public class StoryViewerFragment extends Fragment {
public void afterTextChanged(final Editable s) {}
});
} else if (tag instanceof String[]) {
mentions = (String[]) tag;
final String[] rawMentions = (String[]) tag;
mentions = new ArrayList<String>(Arrays.asList(rawMentions));
new AlertDialog.Builder(context)
.setTitle(R.string.story_mentions)
.setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, mentions), (d, w) -> openProfile(mentions[w]))
.setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, rawMentions), (d, w) -> openProfile(mentions.get(w)))
.setPositiveButton(R.string.cancel, null)
.show();
} else if (tag instanceof QuizModel) {
String[] choices = new String[quiz.getChoices().length];
for (int q = 0; q < choices.length; ++q) {
choices[q] = (quiz.getMyChoice() == q ? "" : "") + quiz.getChoices()[q] + " (" + quiz.getCounts()[q] + ")";
}
} else if (tag instanceof QuizSticker) {
final List<Tally> tallies = quiz.getTallies();
final String[] choices = tallies.stream().map(
t -> (quiz.getViewerAnswer() == tallies.indexOf(t) ? "" : "") +
(quiz.getCorrectAnswer() == tallies.indexOf(t) ? "*** " : "") +
t.getText() + " (" + t.getCount() + ")"
).toArray(String[]::new);
new AlertDialog.Builder(context)
.setTitle(quiz.getMyChoice() > -1 ? getString(R.string.story_quizzed) : quiz.getQuestion())
.setTitle(quiz.getViewerAnswer() > -1 ? getString(R.string.story_quizzed) : quiz.getQuestion())
.setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, choices), (d, w) -> {
if (quiz.getMyChoice() == -1) {
if (quiz.getViewerAnswer() == -1) {
sticking = true;
storiesRepository.respondToQuiz(
csrfToken,
userId,
deviceId,
currentStory.getStoryMediaId().split("_")[0],
quiz.getId(),
currentStory.getId().split("_")[0],
quiz.getQuizId(),
w,
CoroutineUtilsKt.getContinuation(
(storyStickerResponse, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
@ -610,7 +607,7 @@ public class StoryViewerFragment extends Fragment {
}
sticking = false;
try {
quiz.setMyChoice(w);
quiz.setViewerAnswer(w);
Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show();
} catch (Exception ignored) {}
}),
@ -621,8 +618,8 @@ public class StoryViewerFragment extends Fragment {
})
.setPositiveButton(R.string.cancel, null)
.show();
} else if (tag instanceof SliderModel) {
slider = (SliderModel) tag;
} else if (tag instanceof SliderSticker) {
slider = (SliderSticker) tag;
NumberFormat percentage = NumberFormat.getPercentInstance();
percentage.setMaximumFractionDigits(2);
LinearLayout sliderView = new LinearLayout(context);
@ -633,11 +630,11 @@ public class StoryViewerFragment extends Fragment {
TextView tv = new TextView(context);
tv.setGravity(Gravity.CENTER_HORIZONTAL);
final SeekBar input = new SeekBar(context);
double avg = slider.getAverage() * 100;
double avg = slider.getSliderVoteAverage() * 100;
input.setProgress((int) avg);
sliderView.addView(input);
sliderView.addView(tv);
if (slider.getMyChoice().isNaN() && slider.canVote()) {
if (slider.getViewerVote().isNaN() && slider.getViewerCanVote()) {
input.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
@ -656,9 +653,9 @@ public class StoryViewerFragment extends Fragment {
new AlertDialog.Builder(context)
.setTitle(TextUtils.isEmpty(slider.getQuestion()) ? slider.getEmoji() : slider.getQuestion())
.setMessage(getResources().getQuantityString(R.plurals.slider_info,
slider.getVoteCount(),
slider.getVoteCount(),
percentage.format(slider.getAverage())))
slider.getSliderVoteCount(),
slider.getSliderVoteCount(),
percentage.format(slider.getSliderVoteAverage())))
.setView(sliderView)
.setPositiveButton(R.string.confirm, (d, w) -> {
sticking = true;
@ -666,8 +663,8 @@ public class StoryViewerFragment extends Fragment {
csrfToken,
userId,
deviceId,
currentStory.getStoryMediaId().split("_")[0],
slider.getId(),
currentStory.getId().split("_")[0],
slider.getSliderId(),
sliderValue,
CoroutineUtilsKt.getContinuation(
(storyStickerResponse, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
@ -681,7 +678,7 @@ public class StoryViewerFragment extends Fragment {
}
sticking = false;
try {
slider.setMyChoice(sliderValue);
slider.setViewerVote(sliderValue);
Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show();
} catch (Exception ignored) {}
}), Dispatchers.getIO()
@ -692,13 +689,13 @@ public class StoryViewerFragment extends Fragment {
.show();
} else {
input.setEnabled(false);
tv.setText(getString(R.string.slider_answer, percentage.format(slider.getMyChoice())));
tv.setText(getString(R.string.slider_answer, percentage.format(slider.getViewerVote())));
new AlertDialog.Builder(context)
.setTitle(TextUtils.isEmpty(slider.getQuestion()) ? slider.getEmoji() : slider.getQuestion())
.setMessage(getResources().getQuantityString(R.plurals.slider_info,
slider.getVoteCount(),
slider.getVoteCount(),
percentage.format(slider.getAverage())))
slider.getSliderVoteCount(),
slider.getSliderVoteCount(),
percentage.format(slider.getSliderVoteAverage())))
.setView(sliderView)
.setPositiveButton(R.string.ok, null)
.show();
@ -746,11 +743,12 @@ public class StoryViewerFragment extends Fragment {
case FEED_STORY_POSITION: {
final FeedStoriesViewModel feedStoriesViewModel = (FeedStoriesViewModel) viewModel;
final List<Story> models = feedStoriesViewModel.getList().getValue();
if (models == null || currentFeedStoryIndex >= models.size() || currentFeedStoryIndex < 0) return;
if (models == null || currentFeedStoryIndex >= models.size() || currentFeedStoryIndex < 0)
return;
final Story model = models.get(currentFeedStoryIndex);
currentStoryMediaId = model.getId();
currentStoryMediaId = String.valueOf(model.getUser().getPk());
currentStoryUsername = model.getUser().getUsername();
fetchOptions = StoryViewerOptions.forUser(Long.parseLong(currentStoryMediaId), currentStoryUsername);
fetchOptions = StoryViewerOptions.forUser(model.getUser().getPk(), currentStoryUsername);
live = model.getBroadcast();
break;
}
@ -767,11 +765,12 @@ public class StoryViewerFragment extends Fragment {
fetchOptions = StoryViewerOptions.forStoryArchive(model.getId());
break;
}
}
if (type == Type.USER) {
currentStoryMediaId = String.valueOf(options.getId());
currentStoryUsername = options.getName();
fetchOptions = StoryViewerOptions.forUser(options.getId(), currentStoryUsername);
case USER: {
currentStoryMediaId = String.valueOf(options.getId());
currentStoryUsername = options.getName();
fetchOptions = StoryViewerOptions.forUser(options.getId(), currentStoryUsername);
break;
}
}
setTitle(type);
storiesViewModel.getList().setValue(Collections.emptyList());
@ -804,9 +803,9 @@ public class StoryViewerFragment extends Fragment {
refreshLive();
return;
}
final ServiceCallback<List<StoryModel>> storyCallback = new ServiceCallback<List<StoryModel>>() {
final ServiceCallback<List<StoryMedia>> storyCallback = new ServiceCallback<List<StoryMedia>>() {
@Override
public void onSuccess(final List<StoryModel> storyModels) {
public void onSuccess(final List<StoryMedia> storyModels) {
fetching = false;
if (storyModels == null || storyModels.isEmpty()) {
storiesViewModel.getList().setValue(Collections.emptyList());
@ -826,11 +825,10 @@ public class StoryViewerFragment extends Fragment {
@Override
public void onFailure(final Throwable t) {
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
Log.e(TAG, "Error", t);
}
};
storiesRepository.getUserStory(
storiesRepository.getStories(
fetchOptions,
CoroutineUtilsKt.getContinuation((storyModels, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
if (throwable != null) {
@ -838,7 +836,7 @@ public class StoryViewerFragment extends Fragment {
return;
}
//noinspection unchecked
storyCallback.onSuccess((List<StoryModel>) storyModels);
storyCallback.onSuccess((List<StoryMedia>) storyModels);
}), Dispatchers.getIO())
);
}
@ -887,9 +885,9 @@ public class StoryViewerFragment extends Fragment {
private synchronized void refreshStory() {
if (binding.storiesList.getVisibility() == View.VISIBLE) {
final List<StoryModel> storyModels = storiesViewModel.getList().getValue();
final List<StoryMedia> storyModels = storiesViewModel.getList().getValue();
if (storyModels != null && storyModels.size() > 0) {
StoryModel item = storyModels.get(lastSlidePos);
StoryMedia item = storyModels.get(lastSlidePos);
if (item != null) {
item.setCurrentSlide(false);
storiesAdapter.notifyItemChanged(lastSlidePos, item);
@ -903,59 +901,96 @@ public class StoryViewerFragment extends Fragment {
}
lastSlidePos = slidePos;
final MediaItemType itemType = currentStory.getItemType();
final MediaItemType itemType = currentStory.getMediaType();
url = itemType == MediaItemType.MEDIA_TYPE_IMAGE ? currentStory.getStoryUrl() : currentStory.getVideoUrl();
url = itemType == MediaItemType.MEDIA_TYPE_IMAGE
? ResponseBodyUtils.getImageUrl(currentStory)
: ResponseBodyUtils.getVideoUrl(currentStory);
final String shortCode = currentStory.getTappableShortCode();
binding.viewStoryPost.setVisibility(shortCode != null ? View.VISIBLE : View.GONE);
binding.viewStoryPost.setTag(shortCode);
if (currentStory.getStoryFeedMedia() != null) {
final String shortCode = currentStory.getStoryFeedMedia().get(0).getMediaId();
binding.viewStoryPost.setVisibility(View.VISIBLE);
binding.viewStoryPost.setTag(shortCode);
}
final String spotify = currentStory.getSpotify();
binding.spotify.setVisibility(spotify != null ? View.VISIBLE : View.GONE);
binding.spotify.setTag(spotify);
final StoryAppAttribution spotify = currentStory.getStoryAppAttribution();
if (spotify != null) {
binding.spotify.setVisibility(View.VISIBLE);
binding.spotify.setText(spotify.getName());
binding.spotify.setTag(spotify.getContentUrl().split("?")[0]);
}
poll = currentStory.getPoll();
binding.poll.setVisibility(poll != null ? View.VISIBLE : View.GONE);
binding.poll.setTag(poll);
if (currentStory.getStoryPolls() != null) {
poll = currentStory.getStoryPolls().get(0).getPollSticker();
binding.poll.setVisibility(View.VISIBLE);
binding.poll.setTag(poll);
}
question = currentStory.getQuestion();
binding.answer.setVisibility((question != null) ? View.VISIBLE : View.GONE);
binding.answer.setTag(question);
if (currentStory.getStoryQuestions() != null) {
question = currentStory.getStoryQuestions().get(0).getQuestionSticker();
binding.answer.setVisibility(View.VISIBLE);
binding.answer.setTag(question);
}
mentions = currentStory.getMentions();
binding.mention.setVisibility((mentions != null && mentions.length > 0) ? View.VISIBLE : View.GONE);
binding.mention.setTag(mentions);
mentions.clear();
if (currentStory.getReelMentions() != null) {
mentions.addAll(currentStory.getReelMentions().stream().map(
s -> s.getUser().getUsername()
).distinct().collect(Collectors.toList()));
}
if (currentStory.getStoryHashtags() != null) {
mentions.addAll(currentStory.getStoryHashtags().stream().map(
s -> s.getHashtag().getName()
).distinct().collect(Collectors.toList()));
}
if (currentStory.getStoryLocations() != null) {
mentions.addAll(currentStory.getStoryLocations().stream().map(
s -> s.getLocation().getShortName() + " (" + s.getLocation().getPk() + ")"
).distinct().collect(Collectors.toList()));
}
if (mentions.size() > 0) {
binding.mention.setVisibility(View.VISIBLE);
binding.mention.setTag(mentions.stream().toArray(String[]::new));
}
quiz = currentStory.getQuiz();
binding.quiz.setVisibility(quiz != null ? View.VISIBLE : View.GONE);
binding.quiz.setTag(quiz);
if (currentStory.getStoryQuizs() != null) {
quiz = currentStory.getStoryQuizs().get(0).getQuizSticker();
binding.quiz.setVisibility(View.VISIBLE);
binding.quiz.setTag(quiz);
}
slider = currentStory.getSlider();
binding.slider.setVisibility(slider != null ? View.VISIBLE : View.GONE);
binding.slider.setTag(slider);
if (currentStory.getStorySliders() != null) {
slider = currentStory.getStorySliders().get(0).getSliderSticker();
binding.slider.setVisibility(View.VISIBLE);
binding.slider.setTag(slider);
}
final SwipeUpModel swipeUp = currentStory.getSwipeUp();
if (swipeUp != null) {
if (currentStory.getStoryCta() != null) {
final StoryCta swipeUp = currentStory.getStoryCta().get(0).getLinks();
binding.swipeUp.setVisibility(View.VISIBLE);
binding.swipeUp.setText(swipeUp.getText());
binding.swipeUp.setTag(swipeUp.getUrl());
} else binding.swipeUp.setVisibility(View.GONE);
binding.swipeUp.setText(currentStory.getLinkText());
final String swipeUpUrl = swipeUp.getWebUri();
final String actualLink = swipeUpUrl.startsWith("https://l.instagram.com/")
? Uri.parse(swipeUpUrl).getQueryParameter("u")
: null;
binding.swipeUp.setTag(actualLink == null && actualLink.startsWith("http")
? swipeUpUrl : actualLink);
}
releasePlayer();
final Type type = options.getType();
if (type == Type.HASHTAG || type == Type.LOCATION) {
final ActionBar actionBar = fragmentActivity.getSupportActionBar();
if (actionBar != null) {
actionBarTitle = currentStory.getUsername();
actionBar.setTitle(currentStory.getUsername());
actionBarTitle = currentStory.getUser().getUsername();
actionBar.setTitle(currentStory.getUser().getUsername());
}
}
if (itemType == MediaItemType.MEDIA_TYPE_VIDEO) setupVideo();
else setupImage();
final ActionBar actionBar = fragmentActivity.getSupportActionBar();
actionBarSubtitle = TextUtils.epochSecondToString(currentStory.getTimestamp());
actionBarSubtitle = TextUtils.epochSecondToString(currentStory.getTakenAt());
if (actionBar != null) {
try {
actionBar.setSubtitle(actionBarSubtitle);
@ -969,13 +1004,23 @@ public class StoryViewerFragment extends Fragment {
csrfToken,
userId,
deviceId,
currentStory.getStoryMediaId(),
currentStory.getTimestamp(),
currentStory.getId(),
currentStory.getTakenAt(),
System.currentTimeMillis() / 1000,
CoroutineUtilsKt.getContinuation((s, throwable) -> {}, Dispatchers.getIO())
);
}
private void removeStickers() {
binding.swipeUp.setVisibility(View.GONE);
binding.quiz.setVisibility(View.GONE);
binding.spotify.setVisibility(View.GONE);
binding.mention.setVisibility(View.GONE);
binding.viewStoryPost.setVisibility(View.GONE);
binding.answer.setVisibility(View.GONE);
binding.slider.setVisibility(View.GONE);
}
private void downloadStory() {
final Context context = getContext();
if (context == null) return;
@ -1016,7 +1061,7 @@ public class StoryViewerFragment extends Fragment {
dmVisible = true;
menuDm.setVisible(true);
}
if (!TextUtils.isEmpty(currentStory.getUsername())) {
if (!TextUtils.isEmpty(currentStory.getUser().getUsername())) {
profileVisible = true;
menuProfile.setVisible(true);
}
@ -1057,7 +1102,7 @@ public class StoryViewerFragment extends Fragment {
dmVisible = true;
menuDm.setVisible(true);
}
if (!TextUtils.isEmpty(currentStory.getUsername()) && menuProfile != null) {
if (!TextUtils.isEmpty(currentStory.getUser().getUsername()) && menuProfile != null) {
profileVisible = true;
menuProfile.setVisible(true);
}
@ -1077,7 +1122,7 @@ public class StoryViewerFragment extends Fragment {
dmVisible = true;
menuDm.setVisible(true);
}
if (!TextUtils.isEmpty(currentStory.getUsername()) && menuProfile != null) {
if (!TextUtils.isEmpty(currentStory.getUser().getUsername()) && menuProfile != null) {
profileVisible = true;
menuProfile.setVisible(true);
}

View File

@ -1,29 +0,0 @@
package awais.instagrabber.models
import awais.instagrabber.models.enums.MediaItemType
import awais.instagrabber.models.stickers.*
import java.io.Serializable
data class StoryModel(
val storyMediaId: String? = null,
val storyUrl: String? = null,
var thumbnail: String? = null,
val itemType: MediaItemType? = null,
val timestamp: Long = 0,
val username: String? = null,
val userId: Long = 0,
val canReply: Boolean = false,
) : Serializable {
var videoUrl: String? = null
var tappableShortCode: String? = null
val tappableId: String? = null
var spotify: String? = null
var poll: PollModel? = null
var question: QuestionModel? = null
var slider: SliderModel? = null
var quiz: QuizModel? = null
var swipeUp: SwipeUpModel? = null
var mentions: Array<String>? = null
var position = 0
var isCurrentSlide = false
}

View File

@ -1,52 +0,0 @@
package awais.instagrabber.models.stickers;
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

@ -1,20 +0,0 @@
package awais.instagrabber.models.stickers;
import java.io.Serializable;
public final class QuestionModel implements Serializable {
private final String id, question;
public QuestionModel(final String id, final String question) {
this.id = id; // only the poll id
this.question = question;
}
public String getId() {
return id;
}
public String getQuestion() {
return question;
}
}

View File

@ -1,37 +0,0 @@
package awais.instagrabber.models.stickers;
import java.io.Serializable;
public final class QuizModel implements Serializable {
private final String id, question;
private final String[] choices;
private Long[] counts;
private int mychoice;
public QuizModel(final String id, final String question, final String[] choices, final Long[] counts, final int mychoice) {
this.id = id; // only the poll id
this.question = question;
this.choices = choices;
this.counts = counts;
this.mychoice = mychoice;
}
public String getId() {
return id;
}
public String getQuestion() {
return question;
}
public String[] getChoices() { return choices;}
public Long[] getCounts() { return counts;}
public int getMyChoice() { return mychoice; }
public void setMyChoice(final int choice) {
this.mychoice = choice;
counts[choice] += 1L;
}
}

View File

@ -1,53 +0,0 @@
package awais.instagrabber.models.stickers;
import java.io.Serializable;
public final class SliderModel implements Serializable {
private final int voteCount;
private final Double average;
private Double myChoice;
private final boolean canVote;
private final String id, question, emoji;
public SliderModel(final String id, final String question, final String emoji, final boolean canVote,
final Double average, final int voteCount, final Double myChoice) {
this.id = id;
this.question = question;
this.emoji = emoji;
this.canVote = canVote;
this.average = average;
this.voteCount = voteCount;
this.myChoice = myChoice;
}
public String getId() {
return id;
}
public String getQuestion() {
return question;
}
public String getEmoji() {
return emoji;
}
public boolean canVote() {
return canVote;
}
public int getVoteCount() {
return voteCount;
}
public Double getAverage() {
return average;
}
public Double getMyChoice() { return myChoice; }
public Double setMyChoice(final Double choice) {
this.myChoice = choice;
return choice;
}
}

View File

@ -1,20 +0,0 @@
package awais.instagrabber.models.stickers;
import java.io.Serializable;
public final class SwipeUpModel implements Serializable {
private final String url, text;
public SwipeUpModel(final String url, final String text) {
this.url = url;
this.text = text;
}
public String getUrl() {
return url;
}
public String getText() {
return text;
}
}

View File

@ -1,14 +1,17 @@
package awais.instagrabber.repositories
import awais.instagrabber.repositories.responses.stories.ArchiveResponse
import awais.instagrabber.repositories.responses.stories.ReelsMediaResponse
import awais.instagrabber.repositories.responses.stories.ReelsResponse
import awais.instagrabber.repositories.responses.stories.ReelsTrayResponse
import awais.instagrabber.repositories.responses.stories.StoryMediaResponse
import awais.instagrabber.repositories.responses.stories.StoryStickerResponse
import retrofit2.http.*
interface StoriesService {
// this one is the same as MediaRepository.fetch BUT you need to make sure it's a story
@GET("/api/v1/media/{mediaId}/info/")
suspend fun fetch(@Path("mediaId") mediaId: Long): String
suspend fun fetch(@Path("mediaId") mediaId: Long): StoryMediaResponse
@GET("/api/v1/feed/reels_tray/")
suspend fun getFeedStories(): ReelsTrayResponse?
@ -19,14 +22,20 @@ interface StoriesService {
@GET("/api/v1/archive/reel/day_shells/")
suspend fun fetchArchive(@QueryMap queryParams: Map<String, String>): ArchiveResponse?
@GET
suspend fun getUserStory(@Url url: String): String
@GET("/api/v1/feed/reels_media/")
suspend fun getReelsMedia(@Query("user_ids") id: String): ReelsMediaResponse
@GET("/api/v1/{type}/{id}/story/")
suspend fun getStories(@Path("type") type: String, @Path("id") id: String): ReelsResponse
@GET("/api/v1/feed/user/{id}/story/")
suspend fun getUserStories(@Path("id") id: String): ReelsResponse
@FormUrlEncoded
@POST("/api/v1/media/{storyId}/{stickerId}/{action}/")
suspend fun respondToSticker(
@Path("storyId") storyId: String,
@Path("stickerId") stickerId: String,
@Path("stickerId") stickerId: Long,
@Path("action") action: String, // story_poll_vote, story_question_response, story_slider_vote, story_quiz_answer
@FieldMap form: Map<String, String>,
): StoryStickerResponse

View File

@ -42,7 +42,7 @@ public class StoryViewerOptions implements Serializable {
}
public static StoryViewerOptions forUser(final long id, final String name) {
return new StoryViewerOptions(id, name,Type.USER);
return new StoryViewerOptions(id, name, Type.USER);
}
public static StoryViewerOptions forHighlight(final String highlight) {

View File

@ -9,5 +9,5 @@ data class PollSticker(
val pollId: Long?,
val question: String?,
val tallies: List<Tally>?,
val viewerVote: Int?
var viewerVote: Int = -1
) : Serializable

View File

@ -9,6 +9,6 @@ data class QuizSticker(
val quizId: Long?,
val question: String?,
val tallies: List<Tally>?,
val viewerAnswer: Int?,
var viewerAnswer: Int? = -1,
val correctAnswer: Int?
) : Serializable

View File

@ -0,0 +1,8 @@
package awais.instagrabber.repositories.responses.stories
import java.io.Serializable
data class ReelsMediaResponse(
val status: String?,
val reels: Map<String, Story?>?
) : Serializable

View File

@ -4,6 +4,7 @@ import java.io.Serializable
data class ReelsResponse(
val status: String?,
val reel: Story?,
val reel: Story?, // users
val story: Story?, // hashtag and locations (unused)
val broadcast: Broadcast?
) : Serializable

View File

@ -10,7 +10,7 @@ data class SliderSticker(
val question: String?,
val emoji: String?,
val viewerCanVote: Boolean?,
val viewerVote: Double?,
var viewerVote: Double?,
val sliderVoteAverage: Double?,
val sliderVoteCount: Int?,
) : Serializable

View File

@ -41,6 +41,8 @@ data class StoryMedia(
val storyAppAttribution: StoryAppAttribution? = null
) : Serializable {
private var dateString: String? = null
var position = 0
var isCurrentSlide = false
// TODO use extension once all usages are converted to kotlin
// val date: String by lazy {

View File

@ -0,0 +1,14 @@
package awais.instagrabber.repositories.responses.stories
import awais.instagrabber.models.enums.MediaItemType
import awais.instagrabber.repositories.responses.ImageVersions2
import awais.instagrabber.repositories.responses.MediaCandidate
import awais.instagrabber.repositories.responses.User
import awais.instagrabber.utils.TextUtils
import java.io.Serializable
data class StoryMediaResponse(
val items: List<StoryMedia?>?, // length 1
val status: String?
// ignoring pagination properties
) : Serializable

View File

@ -13,9 +13,9 @@ import androidx.documentfile.provider.DocumentFile
import androidx.work.*
import awais.instagrabber.R
import awais.instagrabber.fragments.settings.PreferenceKeys
import awais.instagrabber.models.StoryModel
import awais.instagrabber.models.enums.MediaItemType
import awais.instagrabber.repositories.responses.Media
import awais.instagrabber.repositories.responses.stories.StoryMedia
import awais.instagrabber.utils.TextUtils.isEmpty
import awais.instagrabber.workers.DownloadWorker
import com.google.gson.Gson
@ -392,18 +392,19 @@ object DownloadUtils {
@JvmStatic
fun download(
context: Context,
storyModel: StoryModel
storyModel: StoryMedia
) {
val downloadDir = getDownloadDir(context, storyModel.username) ?: return
val downloadDir = getDownloadDir(context, storyModel.user?.username) ?: return
val url =
if (storyModel.itemType == MediaItemType.MEDIA_TYPE_VIDEO) storyModel.videoUrl else storyModel.storyUrl
if (storyModel.mediaType == MediaItemType.MEDIA_TYPE_VIDEO) ResponseBodyUtils.getVideoUrl(storyModel)
else ResponseBodyUtils.getImageUrl(storyModel)
val extension = getFileExtensionFromUrl(url)
val baseFileName = (storyModel.storyMediaId + "_"
+ storyModel.timestamp + extension)
val baseFileName = (storyModel.id + "_"
+ storyModel.takenAt + extension)
val usernamePrepend =
if (Utils.settingsHelper.getBoolean(PreferenceKeys.DOWNLOAD_PREPEND_USER_NAME)
&& storyModel.username != null
) storyModel.username + "_" else ""
&& storyModel.user?.username != null
) storyModel.user.username + "_" else ""
val fileName = usernamePrepend + baseFileName
var saveFile = downloadDir.findFile(fileName)
if (saveFile == null) {

View File

@ -14,13 +14,7 @@ import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.models.stickers.PollModel;
import awais.instagrabber.models.stickers.QuestionModel;
import awais.instagrabber.models.stickers.QuizModel;
import awais.instagrabber.models.stickers.SliderModel;
import awais.instagrabber.models.stickers.SwipeUpModel;
import awais.instagrabber.repositories.responses.Caption;
import awais.instagrabber.repositories.responses.FriendshipStatus;
import awais.instagrabber.repositories.responses.ImageVersions2;
@ -276,136 +270,6 @@ public final class ResponseBodyUtils {
);
}
public static StoryModel parseStoryItem(final JSONObject data,
final boolean isLocOrHashtag,
final String username) throws JSONException {
final boolean isVideo = data.has("video_duration");
final StoryModel model = new StoryModel(data.getString("id"),
data.getJSONObject("image_versions2").getJSONArray("candidates").getJSONObject(0)
.getString("url"), null,
isVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE,
data.optLong("taken_at", 0),
isLocOrHashtag ? data.getJSONObject("user").getString("username") : username,
data.getJSONObject("user").getLong("pk"),
data.optBoolean("can_reply"));
if (data.getJSONObject("image_versions2").getJSONArray("candidates").length() > 1) {
model.setThumbnail(data.getJSONObject("image_versions2").getJSONArray("candidates").getJSONObject(1)
.getString("url"));
}
final JSONArray videoResources = data.optJSONArray("video_versions");
if (isVideo && videoResources != null)
model.setVideoUrl(ResponseBodyUtils.getHighQualityPost(videoResources, true, true, false));
if (data.has("story_feed_media")) {
model.setTappableShortCode(data.getJSONArray("story_feed_media").getJSONObject(0).optString("media_id"));
}
// TODO: this may not be limited to spotify
if (!data.isNull("story_app_attribution"))
model.setSpotify(data.getJSONObject("story_app_attribution").optString("content_url").split("\\?")[0]);
if (data.has("story_polls")) {
final JSONArray storyPolls = data.optJSONArray("story_polls");
JSONObject tappableObject = null;
if (storyPolls != null) {
tappableObject = storyPolls.getJSONObject(0).optJSONObject("poll_sticker");
}
if (tappableObject != null) model.setPoll(new PollModel(
String.valueOf(tappableObject.getLong("poll_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)
));
}
if (data.has("story_questions")) {
final JSONObject tappableObject = data.getJSONArray("story_questions").getJSONObject(0)
.optJSONObject("question_sticker");
if (tappableObject != null && !tappableObject.getString("question_type").equals("music"))
model.setQuestion(new QuestionModel(
String.valueOf(tappableObject.getLong("question_id")),
tappableObject.getString("question")
));
}
if (data.has("story_quizs")) {
JSONObject tappableObject = data.getJSONArray("story_quizs").getJSONObject(0).optJSONObject("quiz_sticker");
if (tappableObject != null) {
String[] choices = new String[tappableObject.getJSONArray("tallies").length()];
Long[] counts = new Long[choices.length];
for (int q = 0; q < choices.length; ++q) {
JSONObject tempchoice = tappableObject.getJSONArray("tallies").getJSONObject(q);
choices[q] = (q == tappableObject.getInt("correct_answer") ? "*** " : "")
+ tempchoice.getString("text");
counts[q] = tempchoice.getLong("count");
}
model.setQuiz(new QuizModel(
String.valueOf(tappableObject.getLong("quiz_id")),
tappableObject.getString("question"),
choices,
counts,
tappableObject.optInt("viewer_answer", -1)
));
}
}
if (data.has("story_cta") && data.has("link_text")) {
JSONObject tappableObject = data.getJSONArray("story_cta").getJSONObject(0).getJSONArray("links").getJSONObject(0);
String swipeUpUrl = tappableObject.optString("webUri");
final String backupSwipeUpUrl = swipeUpUrl;
if (swipeUpUrl != null && swipeUpUrl.startsWith("https://l.instagram.com/")) {
swipeUpUrl = Uri.parse(swipeUpUrl).getQueryParameter("u");
}
if (swipeUpUrl != null && swipeUpUrl.startsWith("http"))
model.setSwipeUp(new SwipeUpModel(swipeUpUrl, data.getString("link_text")));
else if (backupSwipeUpUrl != null && backupSwipeUpUrl.startsWith("http"))
model.setSwipeUp(new SwipeUpModel(backupSwipeUpUrl, data.getString("link_text")));
}
if (data.has("story_sliders")) {
final JSONObject tappableObject = data.getJSONArray("story_sliders").getJSONObject(0)
.optJSONObject("slider_sticker");
if (tappableObject != null)
model.setSlider(new SliderModel(
String.valueOf(tappableObject.getLong("slider_id")),
tappableObject.getString("question"),
tappableObject.getString("emoji"),
tappableObject.getBoolean("viewer_can_vote"),
tappableObject.optDouble("slider_vote_average"),
tappableObject.getInt("slider_vote_count"),
tappableObject.optDouble("viewer_vote")
));
}
JSONArray hashtags = data.optJSONArray("story_hashtags");
JSONArray locations = data.optJSONArray("story_locations");
JSONArray atmarks = data.optJSONArray("reel_mentions");
String[] mentions = new String[(hashtags == null ? 0 : hashtags.length())
+ (atmarks == null ? 0 : atmarks.length())
+ (locations == null ? 0 : locations.length())];
if (hashtags != null) {
for (int h = 0; h < hashtags.length(); ++h) {
mentions[h] = "#" + hashtags.getJSONObject(h).getJSONObject("hashtag").getString("name");
}
}
if (atmarks != null) {
for (int h = 0; h < atmarks.length(); ++h) {
mentions[h + (hashtags == null ? 0 : hashtags.length())] =
"@" + atmarks.getJSONObject(h).getJSONObject("user").getString("username");
}
}
if (locations != null) {
for (int h = 0; h < locations.length(); ++h) {
mentions[h + (hashtags == null ? 0 : hashtags.length()) + (atmarks == null ? 0 : atmarks.length())] =
locations.getJSONObject(h).getJSONObject("location").getString("short_name")
+ " (" + locations.getJSONObject(h).getJSONObject("location").getLong("pk") + ")";
}
}
if (mentions.length != 0) model.setMentions(mentions);
return model;
}
public static String getThumbUrl(final Object media) {
return getImageCandidate(media, CandidateType.THUMBNAIL);
}
@ -415,6 +279,7 @@ public final class ResponseBodyUtils {
}
private static String getImageCandidate(final Object rawMedia, final CandidateType type) {
if (rawMedia == null) return null;
final ImageVersions2 imageVersions2;
final int originalWidth, originalHeight;
if (rawMedia instanceof StoryMedia) {
@ -453,22 +318,34 @@ public final class ResponseBodyUtils {
return getVideoCandidate(media, CandidateType.VIDEO_THUMBNAIL);
}
public static String getVideoUrl(final Media media) {
public static String getVideoUrl(final Object media) {
return getVideoCandidate(media, CandidateType.DOWNLOAD);
}
// TODO: merge with getImageCandidate when Kotlin
private static String getVideoCandidate(final Media media, final CandidateType type) {
if (media == null) return null;
final List<MediaCandidate> candidates = media.getVideoVersions();
private static String getVideoCandidate(final Object rawMedia, final CandidateType type) {
if (rawMedia == null) return null;
final List<MediaCandidate> candidates;
final int originalWidth, originalHeight;
if (rawMedia instanceof StoryMedia) {
candidates = ((StoryMedia) rawMedia).getVideoVersions();
originalWidth = ((StoryMedia) rawMedia).getOriginalWidth();
originalHeight = ((StoryMedia) rawMedia).getOriginalHeight();
}
else if (rawMedia instanceof Media) {
candidates = ((Media) rawMedia).getVideoVersions();
originalWidth = ((Media) rawMedia).getOriginalWidth();
originalHeight = ((Media) rawMedia).getOriginalHeight();
}
else return null;
if (candidates == null || candidates.isEmpty()) return null;
final boolean isSquare = Integer.compare(media.getOriginalWidth(), media.getOriginalHeight()) == 0;
final boolean isSquare = Integer.compare(originalWidth, originalHeight) == 0;
final List<MediaCandidate> sortedCandidates = candidates.stream()
.sorted((c1, c2) -> Integer.compare(c2.getWidth(), c1.getWidth()))
.collect(Collectors.toList());
final List<MediaCandidate> filteredCandidates = sortedCandidates.stream()
.filter(c ->
c.getWidth() <= media.getOriginalWidth()
c.getWidth() <= originalWidth
&& c.getWidth() <= type.getValue()
&& (isSquare || Integer
.compare(c.getWidth(), c.getHeight()) != 0)
@ -480,19 +357,6 @@ public final class ResponseBodyUtils {
return candidate.getUrl();
}
public static StoryModel parseBroadcastItem(final JSONObject data) throws JSONException {
final StoryModel model = new StoryModel(data.getString("id"),
data.getString("cover_frame_url"),
data.getString("cover_frame_url"),
MediaItemType.MEDIA_TYPE_LIVE,
data.optLong("published_time", 0),
data.getJSONObject("broadcast_owner").getString("username"),
data.getJSONObject("broadcast_owner").getLong("pk"),
false);
model.setVideoUrl(data.getString("dash_playback_url"));
return model;
}
private enum CandidateType {
VIDEO_THUMBNAIL(700),
THUMBNAIL(1000),

View File

@ -8,7 +8,6 @@ import awais.instagrabber.db.entities.Favorite
import awais.instagrabber.db.repositories.FavoriteRepository
import awais.instagrabber.managers.DirectMessagesManager
import awais.instagrabber.models.Resource
import awais.instagrabber.models.StoryModel
import awais.instagrabber.models.enums.BroadcastItemType
import awais.instagrabber.models.enums.FavoriteType
import awais.instagrabber.repositories.requests.StoryViewerOptions
@ -17,6 +16,7 @@ import awais.instagrabber.repositories.responses.User
import awais.instagrabber.repositories.responses.UserProfileContextLink
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient
import awais.instagrabber.repositories.responses.stories.Story
import awais.instagrabber.repositories.responses.stories.StoryMedia
import awais.instagrabber.utils.ControlledRunner
import awais.instagrabber.utils.Event
import awais.instagrabber.utils.SingleRunner
@ -153,9 +153,9 @@ class ProfileFragmentViewModel(
}
}
private val storyFetchControlledRunner = ControlledRunner<List<StoryModel>?>()
val userStories: LiveData<Resource<List<StoryModel>?>> = currentUserProfileActionLiveData.switchMap { currentUserAndProfilePair ->
liveData<Resource<List<StoryModel>?>>(context = viewModelScope.coroutineContext + ioDispatcher) {
private val storyFetchControlledRunner = ControlledRunner<List<StoryMedia>?>()
val userStories: LiveData<Resource<List<StoryMedia>?>> = currentUserProfileActionLiveData.switchMap { currentUserAndProfilePair ->
liveData<Resource<List<StoryMedia>?>>(context = viewModelScope.coroutineContext + ioDispatcher) {
val (currentUserResource, profileResource, action) = currentUserAndProfilePair
if (action != INIT && action != REFRESH) {
return@liveData
@ -231,7 +231,7 @@ class ProfileFragmentViewModel(
return graphQLRepository.fetchUser(stateUsername)
}
private suspend fun fetchUserStory(fetchedUser: User): List<StoryModel> = storiesRepository.getUserStory(
private suspend fun fetchUserStory(fetchedUser: User): List<StoryMedia> = storiesRepository.getStories(
StoryViewerOptions.forUser(fetchedUser.pk, fetchedUser.fullName)
)

View File

@ -5,12 +5,12 @@ import androidx.lifecycle.ViewModel;
import java.util.List;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.repositories.responses.stories.StoryMedia;
public class StoriesViewModel extends ViewModel {
private MutableLiveData<List<StoryModel>> list;
private MutableLiveData<List<StoryMedia>> list;
public MutableLiveData<List<StoryModel>> getList() {
public MutableLiveData<List<StoryMedia>> getList() {
if (list == null) {
list = new MutableLiveData<>();
}

View File

@ -1,26 +1,22 @@
package awais.instagrabber.webservices
import awais.instagrabber.fragments.settings.PreferenceKeys
import awais.instagrabber.models.StoryModel
import awais.instagrabber.repositories.StoriesService
import awais.instagrabber.repositories.requests.StoryViewerOptions
import awais.instagrabber.repositories.responses.stories.ArchiveResponse
import awais.instagrabber.repositories.responses.stories.Story
import awais.instagrabber.repositories.responses.stories.StoryMedia
import awais.instagrabber.repositories.responses.stories.StoryStickerResponse
import awais.instagrabber.utils.ResponseBodyUtils
import awais.instagrabber.utils.TextUtils.isEmpty
import awais.instagrabber.utils.Utils
import awais.instagrabber.webservices.RetrofitFactory.retrofit
import org.json.JSONArray
import org.json.JSONObject
import java.util.*
import java.util.UUID
open class StoriesRepository(private val service: StoriesService) {
suspend fun fetch(mediaId: Long): StoryModel {
suspend fun fetch(mediaId: Long): StoryMedia? {
val response = service.fetch(mediaId)
val itemJson = JSONObject(response).getJSONArray("items").getJSONObject(0)
return ResponseBodyUtils.parseStoryItem(itemJson, false, null)
return response.items?.get(0)
}
suspend fun getFeedStories(): List<Story> {
@ -70,31 +66,30 @@ open class StoriesRepository(private val service: StoriesService) {
return service.fetchArchive(form)
}
open suspend fun getUserStory(options: StoryViewerOptions): List<StoryModel> {
val url = buildUrl(options) ?: return emptyList()
val response = service.getUserStory(url)
val isLocOrHashtag = options.type == StoryViewerOptions.Type.LOCATION || options.type == StoryViewerOptions.Type.HASHTAG
val isHighlight = options.type == StoryViewerOptions.Type.HIGHLIGHT || options.type == StoryViewerOptions.Type.STORY_ARCHIVE
var data: JSONObject? = JSONObject(response)
data = if (!isHighlight) {
data?.optJSONObject(if (isLocOrHashtag) "story" else "reel")
} else {
data?.getJSONObject("reels")?.optJSONObject(options.name)
}
var username: String? = null
if (data != null && !isLocOrHashtag) {
username = data.getJSONObject("user").getString("username")
}
val media: JSONArray? = data?.optJSONArray("items")
return if (media?.length() ?: 0 > 0 && media?.optJSONObject(0) != null) {
val mediaLen = media.length()
val models: MutableList<StoryModel> = ArrayList()
for (i in 0 until mediaLen) {
data = media.getJSONObject(i)
models.add(ResponseBodyUtils.parseStoryItem(data, isLocOrHashtag, username))
open suspend fun getStories(options: StoryViewerOptions): List<StoryMedia> {
return when (options.type) {
StoryViewerOptions.Type.HIGHLIGHT,
StoryViewerOptions.Type.STORY_ARCHIVE
-> {
val response = service.getReelsMedia(options.name)
val story: Story? = response.reels?.get(options.name)
story?.items ?: emptyList()
}
models
} else emptyList()
StoryViewerOptions.Type.USER -> {
val response = service.getUserStories(options.id.toString())
response.reel?.items ?: emptyList()
}
// should not reach beyond this point
StoryViewerOptions.Type.LOCATION -> {
val response = service.getStories("locations", options.id.toString())
response.story?.items ?: emptyList()
}
StoryViewerOptions.Type.HASHTAG -> {
val response = service.getStories("tags", options.name)
response.story?.items ?: emptyList()
}
else -> emptyList()
}
}
private suspend fun respondToSticker(
@ -102,7 +97,7 @@ open class StoriesRepository(private val service: StoriesService) {
userId: Long,
deviceUuid: String,
storyId: String,
stickerId: String,
stickerId: Long,
action: String,
arg1: String,
arg2: String,
@ -125,7 +120,7 @@ open class StoriesRepository(private val service: StoriesService) {
userId: Long,
deviceUuid: String,
storyId: String,
stickerId: String,
stickerId: Long,
answer: String,
): StoryStickerResponse = respondToSticker(csrfToken, userId, deviceUuid, storyId, stickerId, "story_question_response", "response", answer)
@ -134,7 +129,7 @@ open class StoriesRepository(private val service: StoriesService) {
userId: Long,
deviceUuid: String,
storyId: String,
stickerId: String,
stickerId: Long,
answer: Int,
): StoryStickerResponse {
return respondToSticker(csrfToken, userId, deviceUuid, storyId, stickerId, "story_quiz_answer", "answer", answer.toString())
@ -145,7 +140,7 @@ open class StoriesRepository(private val service: StoriesService) {
userId: Long,
deviceUuid: String,
storyId: String,
stickerId: String,
stickerId: Long,
answer: Int,
): StoryStickerResponse = respondToSticker(csrfToken, userId, deviceUuid, storyId, stickerId, "story_poll_vote", "vote", answer.toString())
@ -154,7 +149,7 @@ open class StoriesRepository(private val service: StoriesService) {
userId: Long,
deviceUuid: String,
storyId: String,
stickerId: String,
stickerId: Long,
answer: Double,
): StoryStickerResponse = respondToSticker(csrfToken, userId, deviceUuid, storyId, stickerId, "story_slider_vote", "vote", answer.toString())
@ -182,43 +177,6 @@ open class StoriesRepository(private val service: StoriesService) {
return service.seen(queryMap, signedForm)
}
private fun buildUrl(options: StoryViewerOptions): String? {
val builder = StringBuilder()
builder.append("https://i.instagram.com/api/v1/")
val type = options.type
var id: String? = null
when (type) {
StoryViewerOptions.Type.HASHTAG -> {
builder.append("tags/")
id = options.name
}
StoryViewerOptions.Type.LOCATION -> {
builder.append("locations/")
id = options.id.toString()
}
StoryViewerOptions.Type.USER -> {
builder.append("feed/user/")
id = options.id.toString()
}
StoryViewerOptions.Type.HIGHLIGHT, StoryViewerOptions.Type.STORY_ARCHIVE -> {
builder.append("feed/reels_media/?user_ids=")
id = options.name
}
StoryViewerOptions.Type.STORY -> {
}
else -> {
}
}
if (id == null) {
return null
}
builder.append(id)
if (type != StoryViewerOptions.Type.HIGHLIGHT && type != StoryViewerOptions.Type.STORY_ARCHIVE) {
builder.append("/story/")
}
return builder.toString()
}
private fun sort(list: List<Story>): List<Story> {
val listCopy = ArrayList(list)
listCopy.sortWith { o1, o2 ->

View File

@ -10,12 +10,12 @@ import awais.instagrabber.db.entities.Favorite
import awais.instagrabber.db.repositories.FavoriteRepository
import awais.instagrabber.getOrAwaitValue
import awais.instagrabber.models.Resource
import awais.instagrabber.models.StoryModel
import awais.instagrabber.models.enums.FavoriteType
import awais.instagrabber.repositories.requests.StoryViewerOptions
import awais.instagrabber.repositories.responses.FriendshipStatus
import awais.instagrabber.repositories.responses.User
import awais.instagrabber.repositories.responses.stories.Story
import awais.instagrabber.repositories.responses.stories.StoryMedia
import awais.instagrabber.webservices.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.json.JSONException
@ -320,13 +320,13 @@ internal class ProfileFragmentViewModelTest {
"username" to testPublicUser.username
)
)
val testUserStories = listOf(StoryModel())
val testUserStories = listOf(StoryMedia())
val testUserHighlights = listOf(Story())
val userRepository = object : UserRepository(UserServiceAdapter()) {
override suspend fun getUsernameInfo(username: String): User = testPublicUser
}
val storiesRepository = object : StoriesRepository(StoriesServiceAdapter()) {
override suspend fun getUserStory(options: StoryViewerOptions): List<StoryModel> = testUserStories
override suspend fun getStories(options: StoryViewerOptions): List<StoryMedia> = testUserStories
override suspend fun fetchHighlights(profileId: Long): List<Story> = testUserHighlights
}
val viewModel = ProfileFragmentViewModel(