diff --git a/.all-contributorsrc b/.all-contributorsrc index ac44348d..edf8dd87 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -42,6 +42,15 @@ "bug" ] }, + { + "login": "Zopieux", + "name": "Alexandre Macabies", + "avatar_url": "https://avatars.githubusercontent.com/u/81353?v=4", + "profile": "https://github.com/Zopieux", + "contributions": [ + "code" + ] + }, { "login": "MeLlamoPablo", "name": "Pablo RodrΓ­guez", diff --git a/README.md b/README.md index d3671d60..8480e67d 100755 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## THERE ARE CURRENTLY NO OFFICIAL GOOGLE PLAY RELEASES. PLEASE REPORT ANY OCCURRENCES TO US. +### THERE ARE CURRENTLY NO OFFICIAL GOOGLE PLAY RELEASES. PLEASE REPORT ANY OCCURRENCES TO US. Barinsta logo @@ -9,7 +9,7 @@ [![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) [![GitHub stars](https://img.shields.io/github/stars/austinhuang0131/instagrabber.svg?style=social&label=Star)](https://GitHub.com/austinhuang0131/barinsta/stargazers/) -[![All Contributors](https://img.shields.io/badge/all_contributors-38-orange.svg)](#contributors) +[![All Contributors](https://img.shields.io/badge/all_contributors-39-orange.svg)](#contributors) Instagram client; previously known as InstaGrabber. @@ -57,51 +57,52 @@ Prominent contributors are listed here in the [all-contributors](https://allcont
Austin Huang

πŸ’» πŸ“– πŸ’¬ 🌍 πŸ€”
Ammar Githam

πŸ’» 🎨 πŸ€” 🚧 πŸ’¬
Anderson Mesquita

πŸ’» πŸ› +
Alexandre Macabies

πŸ’»
Pablo RodrΓ­guez

πŸ’»
AWAiS

πŸ’» -
Stefan Najdovski

🎨 🌍 +
Stefan Najdovski

🎨 🌍
CrazyMarvin

πŸ’΅
Kevin Thomas

πŸ’΅
Shadowspear123

πŸ“ πŸ› πŸ€” πŸ’¬
Ricardo

πŸ› 🌍
Airikr

πŸ€” πŸ’¬ -
Akrai

πŸ€” 🌍 +
Akrai

πŸ€” 🌍
avtkal

🌍
CΓ©zar Augusto

🌍
Dimitris T

🌍
farzadx

🌍
Fatih AydΔ±n

🌍 -
fouze555

🌍 +
fouze555

🌍
Galang23

🌍
Initdebugs

🌍
Jakub Janek

🌍
GenosseFlosse

🌍
kernoeb

🌍 -
MoaufmKlo

🌍 +
MoaufmKlo

🌍
nalinalini

🌍
peterge1998

🌍
PierreM0

🌍
RAMAR-RAR

🌍
rohang02

🌍 -
retiolus

🌍 +
retiolus

🌍
rikishi0071

🌍
Alexey Peschany

🌍
Still Hsu

🌍
Ten_Lego

🌍
wagnim

🌍 -
wokija

🌍 +
wokija

🌍
ysakamoto

🌍
ZDVokoun

🌍 diff --git a/app/src/main/java/awais/instagrabber/adapters/NotificationsAdapter.java b/app/src/main/java/awais/instagrabber/adapters/NotificationsAdapter.java index 394c2374..8a5c3a4b 100644 --- a/app/src/main/java/awais/instagrabber/adapters/NotificationsAdapter.java +++ b/app/src/main/java/awais/instagrabber/adapters/NotificationsAdapter.java @@ -11,24 +11,25 @@ import androidx.recyclerview.widget.ListAdapter; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import awais.instagrabber.adapters.viewholder.NotificationViewHolder; import awais.instagrabber.databinding.ItemNotificationBinding; -import awais.instagrabber.models.NotificationModel; import awais.instagrabber.models.enums.NotificationType; +import awais.instagrabber.repositories.responses.Notification; -public final class NotificationsAdapter extends ListAdapter { +public final class NotificationsAdapter extends ListAdapter { private final OnNotificationClickListener notificationClickListener; - private static final DiffUtil.ItemCallback DIFF_CALLBACK = new DiffUtil.ItemCallback() { + private static final DiffUtil.ItemCallback DIFF_CALLBACK = new DiffUtil.ItemCallback() { @Override - public boolean areItemsTheSame(@NonNull final NotificationModel oldItem, @NonNull final NotificationModel newItem) { - return oldItem.getId().equals(newItem.getId()); + public boolean areItemsTheSame(@NonNull final Notification oldItem, @NonNull final Notification newItem) { + return oldItem.getPk().equals(newItem.getPk()); } @Override - public boolean areContentsTheSame(@NonNull final NotificationModel oldItem, @NonNull final NotificationModel newItem) { - return oldItem.getId().equals(newItem.getId()); + public boolean areContentsTheSame(@NonNull final Notification oldItem, @NonNull final Notification newItem) { + return oldItem.getPk().equals(newItem.getPk()); } }; @@ -47,12 +48,12 @@ public final class NotificationsAdapter extends ListAdapter list, @Nullable final Runnable commitCallback) { + public void submitList(@Nullable final List list, @Nullable final Runnable commitCallback) { if (list == null) { super.submitList(null, commitCallback); return; @@ -61,7 +62,7 @@ public final class NotificationsAdapter extends ListAdapter list) { + public void submitList(@Nullable final List list) { if (list == null) { super.submitList(null); return; @@ -69,8 +70,10 @@ public final class NotificationsAdapter extends ListAdapter sort(final List list) { - final List listCopy = new ArrayList<>(list); + private List sort(final List list) { + final List listCopy = new ArrayList<>(list).stream() + .filter(i -> i.getType() != null) + .collect(Collectors.toList()); Collections.sort(listCopy, (o1, o2) -> { // keep requests at top if (o1.getType() == o2.getType() @@ -79,16 +82,16 @@ public final class NotificationsAdapter extends ListAdapter { if (notificationClickListener == null) return; - notificationClickListener.onProfileClick(model.getUsername()); + notificationClickListener.onProfileClick(args.getUsername()); }); if (model.getType() == NotificationType.AYML) { binding.ivPreviewPic.setVisibility(View.GONE); - } else if (TextUtils.isEmpty(model.getPreviewPic())) { + } else if (args.getMedia() == null) { binding.ivPreviewPic.setVisibility(View.INVISIBLE); } else { binding.ivPreviewPic.setVisibility(View.VISIBLE); - binding.ivPreviewPic.setImageURI(model.getPreviewPic()); + binding.ivPreviewPic.setImageURI(args.getMedia().get(0).getImage()); binding.ivPreviewPic.setOnClickListener(v -> { if (notificationClickListener == null) return; notificationClickListener.onPreviewClick(model); diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectInboxItemViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectInboxItemViewHolder.java index 41465b38..f6b0f679 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectInboxItemViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectInboxItemViewHolder.java @@ -144,7 +144,7 @@ public final class DirectInboxItemViewHolder extends RecyclerView.ViewHolder { private void setReadState(@NonNull final DirectThread thread) { final boolean read = DMUtils.isRead(thread); binding.unread.setVisibility(read ? View.GONE : View.VISIBLE); - binding.threadTitle.setTypeface(binding.threadTitle.getTypeface(), read ? Typeface.NORMAL : Typeface.BOLD); - binding.subtitle.setTypeface(binding.subtitle.getTypeface(), read ? Typeface.NORMAL : Typeface.BOLD); + binding.threadTitle.setTypeface(null, read ? Typeface.NORMAL : Typeface.BOLD); + binding.subtitle.setTypeface(null, read ? Typeface.NORMAL : Typeface.BOLD); } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/asyncs/CommentsFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/CommentsFetcher.java index 6b9929c5..48dcc401 100755 --- a/app/src/main/java/awais/instagrabber/asyncs/CommentsFetcher.java +++ b/app/src/main/java/awais/instagrabber/asyncs/CommentsFetcher.java @@ -115,7 +115,8 @@ public final class CommentsFetcher extends AsyncTask { - private static final String TAG = "GetActivityAsyncTask"; - - private final OnTaskCompleteListener onTaskCompleteListener; - - public GetActivityAsyncTask(final OnTaskCompleteListener onTaskCompleteListener) { - this.onTaskCompleteListener = onTaskCompleteListener; - } - - /* - This needs to be redone to fetch i inbox instead - Within inbox, data is (body JSON => counts) - Then we have these counts: - new_posts, activity_feed_dot_badge, relationships, campaign_notification - usertags, likes, comment_likes, shopping_notification, comments - photos_of_you (not sure about difference to usertags), requests - */ - - protected NotificationCounts doInBackground(final String... cookiesArray) { - if (cookiesArray == null) return null; - final String cookie = cookiesArray[0]; - if (TextUtils.isEmpty(cookie)) return null; - final long uid = CookieUtils.getUserIdFromCookie(cookie); - final String url = "https://www.instagram.com/graphql/query/?query_hash=0f318e8cfff9cc9ef09f88479ff571fb" - + "&variables={\"id\":\"" + uid + "\"}"; - HttpURLConnection urlConnection = null; - try { - urlConnection = (HttpURLConnection) new URL(url).openConnection(); - urlConnection.setUseCaches(false); - urlConnection.setRequestProperty("User-Agent", Utils.settingsHelper.getString(Constants.BROWSER_UA)); - urlConnection.setRequestProperty("x-csrftoken", cookie.split("csrftoken=")[1].split(";")[0]); - urlConnection.connect(); - if (urlConnection.getResponseCode() != HttpURLConnection.HTTP_OK) { - return null; - } - final JSONObject data = new JSONObject(NetworkUtils.readFromConnection(urlConnection)) - .getJSONObject("data") - .getJSONObject("user") - .getJSONObject("edge_activity_count") - .getJSONArray("edges") - .getJSONObject(0) - .getJSONObject("node"); - return new NotificationCounts( - data.getInt("relationships"), - data.getInt("usertags"), - data.getInt("comments"), - data.getInt("comment_likes"), - data.getInt("likes") - ); - } catch (Throwable ex) { - Log.e(TAG, "Error", ex); - } finally { - if (urlConnection != null) { - urlConnection.disconnect(); - } - } - return null; - } - - @Override - protected void onPostExecute(final NotificationCounts result) { - if (onTaskCompleteListener == null) return; - onTaskCompleteListener.onTaskComplete(result); - } - - public static class NotificationCounts { - private final int relationshipsCount; - private final int userTagsCount; - private final int commentsCount; - private final int commentLikesCount; - private final int likesCount; - - public NotificationCounts(final int relationshipsCount, - final int userTagsCount, - final int commentsCount, - final int commentLikesCount, - final int likesCount) { - this.relationshipsCount = relationshipsCount; - this.userTagsCount = userTagsCount; - this.commentsCount = commentsCount; - this.commentLikesCount = commentLikesCount; - this.likesCount = likesCount; - } - - public int getRelationshipsCount() { - return relationshipsCount; - } - - public int getUserTagsCount() { - return userTagsCount; - } - - public int getCommentsCount() { - return commentsCount; - } - - public int getCommentLikesCount() { - return commentLikesCount; - } - - public int getLikesCount() { - return likesCount; - } - - @NonNull - @Override - public String toString() { - return "NotificationCounts{" + - "relationshipsCount=" + relationshipsCount + - ", userTagsCount=" + userTagsCount + - ", commentsCount=" + commentsCount + - ", commentLikesCount=" + commentLikesCount + - ", likesCount=" + likesCount + - '}'; - } - } - - public interface OnTaskCompleteListener { - void onTaskComplete(final NotificationCounts result); - } -} diff --git a/app/src/main/java/awais/instagrabber/asyncs/NotificationsFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/NotificationsFetcher.java index 714a2793..89729909 100644 --- a/app/src/main/java/awais/instagrabber/asyncs/NotificationsFetcher.java +++ b/app/src/main/java/awais/instagrabber/asyncs/NotificationsFetcher.java @@ -8,35 +8,35 @@ import java.util.List; import awais.instagrabber.BuildConfig; import awais.instagrabber.interfaces.FetchListener; -import awais.instagrabber.models.NotificationModel; +import awais.instagrabber.repositories.responses.Notification; import awais.instagrabber.webservices.NewsService; import awais.instagrabber.webservices.ServiceCallback; import awaisomereport.LogCollector; import static awais.instagrabber.utils.Utils.logCollector; -public final class NotificationsFetcher extends AsyncTask> { +public final class NotificationsFetcher extends AsyncTask> { private static final String TAG = "NotificationsFetcher"; - private final FetchListener> fetchListener; + private final FetchListener> fetchListener; private final NewsService newsService; private final boolean markAsSeen; private boolean fetchedWeb = false; public NotificationsFetcher(final boolean markAsSeen, - final FetchListener> fetchListener) { + final FetchListener> fetchListener) { this.markAsSeen = markAsSeen; this.fetchListener = fetchListener; newsService = NewsService.getInstance(); } @Override - protected List doInBackground(final Void... voids) { - List notificationModels = new ArrayList<>(); + protected List doInBackground(final Void... voids) { + List notificationModels = new ArrayList<>(); - newsService.fetchAppInbox(markAsSeen, new ServiceCallback>() { + newsService.fetchAppInbox(markAsSeen, new ServiceCallback>() { @Override - public void onSuccess(final List result) { + public void onSuccess(final List result) { if (result == null) return; notificationModels.addAll(result); if (fetchedWeb) { @@ -44,7 +44,7 @@ public final class NotificationsFetcher extends AsyncTask { - private static final String TAG = "SeenAction"; - - private final String cookie; - private final StoryModel storyModel; - - public SeenAction(final String cookie, final StoryModel storyModel) { - this.cookie = cookie; - this.storyModel = storyModel; - } - - protected Void doInBackground(Void... voids) { - final String url = "https://www.instagram.com/stories/reel/seen"; - try { - final String urlParameters = "reelMediaId=" + storyModel.getStoryMediaId().split("_")[0] - + "&reelMediaOwnerId=" + storyModel.getUserId() - + "&reelId=" + storyModel.getUserId() - + "&reelMediaTakenAt=" + storyModel.getTimestamp() - + "&viewSeenAt=" + storyModel.getTimestamp(); - final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection(); - urlConnection.setRequestMethod("POST"); - urlConnection.setUseCaches(false); - 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); - DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream()); - wr.writeBytes(urlParameters); - wr.flush(); - wr.close(); - urlConnection.connect(); - Log.d(TAG, urlConnection.getResponseCode() + " " + NetworkUtils.readFromConnection(urlConnection)); - urlConnection.disconnect(); - } catch (Throwable ex) { - Log.e(TAG, "Error", ex); - } - return null; - } -} diff --git a/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java b/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java index 12b9b413..550d43e1 100644 --- a/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java @@ -276,7 +276,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe super.onCreate(savedInstanceState); fragmentActivity = (MainActivity) requireActivity(); tagsService = TagsService.getInstance(); - storiesService = StoriesService.getInstance(); + storiesService = StoriesService.getInstance(null, 0L, null); setHasOptionsMenu(true); } diff --git a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java index 49e23159..4c1c9618 100644 --- a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java @@ -270,7 +270,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); fragmentActivity = (MainActivity) requireActivity(); - storiesService = StoriesService.getInstance(); + storiesService = StoriesService.getInstance(null, 0L, null); setHasOptionsMenu(true); } diff --git a/app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java index e272cb8f..e3f22ebc 100644 --- a/app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java @@ -15,7 +15,9 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.NotificationManagerCompat; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; @@ -33,11 +35,13 @@ import awais.instagrabber.asyncs.NotificationsFetcher; import awais.instagrabber.databinding.FragmentNotificationsViewerBinding; import awais.instagrabber.fragments.settings.MorePreferencesFragmentDirections; import awais.instagrabber.interfaces.FetchListener; -import awais.instagrabber.models.NotificationModel; import awais.instagrabber.models.enums.NotificationType; import awais.instagrabber.repositories.requests.StoryViewerOptions; import awais.instagrabber.repositories.responses.FriendshipChangeResponse; import awais.instagrabber.repositories.responses.Media; +import awais.instagrabber.repositories.responses.Notification; +import awais.instagrabber.repositories.responses.NotificationArgs; +import awais.instagrabber.repositories.responses.NotificationImage; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.TextUtils; @@ -53,16 +57,36 @@ import static awais.instagrabber.utils.Utils.settingsHelper; public final class NotificationsViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { private static final String TAG = "NotificationsViewer"; + private AppCompatActivity fragmentActivity; private FragmentNotificationsViewerBinding binding; private SwipeRefreshLayout root; private boolean shouldRefresh = true; private NotificationViewModel notificationViewModel; private FriendshipService friendshipService; private MediaService mediaService; - private String csrfToken; + private NewsService newsService; + private String csrfToken, deviceUuid; private String type; + private long targetId; private Context context; + private final ServiceCallback> cb = new ServiceCallback>() { + @Override + public void onSuccess(final List notificationModels) { + binding.swipeRefreshLayout.setRefreshing(false); + notificationViewModel.getList().postValue(notificationModels); + } + + @Override + public void onFailure(final Throwable t) { + try { + binding.swipeRefreshLayout.setRefreshing(false); + Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); + } + catch(Throwable e) {} + } + }; + private final OnNotificationClickListener clickListener = new OnNotificationClickListener() { @Override public void onProfileClick(final String username) { @@ -70,14 +94,18 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe } @Override - public void onPreviewClick(final NotificationModel model) { + public void onPreviewClick(final Notification model) { + final NotificationImage notificationImage = model.getArgs().getMedia().get(0); + final long mediaId = Long.valueOf(notificationImage.getId().split("_")[0]); if (model.getType() == NotificationType.RESPONDED_STORY) { final NavDirections action = NotificationsViewerFragmentDirections - .actionNotificationsViewerFragmentToStoryViewerFragment(StoryViewerOptions.forStory(model.getPostId(), - model.getUsername())); + .actionNotificationsViewerFragmentToStoryViewerFragment( + StoryViewerOptions.forStory( + mediaId, + model.getArgs().getUsername())); NavHostFragment.findNavController(NotificationsViewerFragment.this).navigate(action); } else { - mediaService.fetch(model.getPostId(), new ServiceCallback() { + mediaService.fetch(mediaId, new ServiceCallback() { @Override public void onSuccess(final Media feedModel) { final PostViewV2Fragment fragment = PostViewV2Fragment @@ -95,13 +123,14 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe } @Override - public void onNotificationClick(final NotificationModel model) { + public void onNotificationClick(final Notification model) { if (model == null) return; - final String username = model.getUsername(); + final NotificationArgs args = model.getArgs(); + final String username = args.getUsername(); if (model.getType() == NotificationType.FOLLOW || model.getType() == NotificationType.AYML) { openProfile(username); } else { - final SpannableString title = new SpannableString(username + (TextUtils.isEmpty(model.getText()) ? "" : (":\n" + model.getText()))); + final SpannableString title = new SpannableString(username + (TextUtils.isEmpty(args.getText()) ? "" : (":\n" + args.getText()))); title.setSpan(new RelativeSizeSpan(1.23f), 0, username.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); String[] commentDialogList; @@ -110,7 +139,7 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe getString(R.string.open_profile), getString(R.string.view_story) }; - } else if (model.getPostId() > 0) { + } else if (args.getMedia() != null) { commentDialogList = new String[]{ getString(R.string.open_profile), getString(R.string.view_post) @@ -131,7 +160,7 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe break; case 1: if (model.getType() == NotificationType.REQUEST) { - friendshipService.approve(model.getUserId(), new ServiceCallback() { + friendshipService.approve(args.getUserId(), new ServiceCallback() { @Override public void onSuccess(final FriendshipChangeResponse result) { onRefresh(); @@ -148,7 +177,7 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe clickListener.onPreviewClick(model); break; case 2: - friendshipService.ignore(model.getUserId(), new ServiceCallback() { + friendshipService.ignore(args.getUserId(), new ServiceCallback() { @Override public void onSuccess(final FriendshipChangeResponse result) { onRefresh(); @@ -174,6 +203,7 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe @Override public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); + fragmentActivity = (AppCompatActivity) requireActivity(); context = getContext(); if (context == null) return; NotificationManagerCompat.from(context.getApplicationContext()).cancel(Constants.ACTIVITY_NOTIFICATION_ID); @@ -183,9 +213,10 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe } mediaService = MediaService.getInstance(null, null, 0); final long userId = CookieUtils.getUserIdFromCookie(cookie); - final String deviceUuid = Utils.settingsHelper.getString(Constants.DEVICE_UUID); + deviceUuid = Utils.settingsHelper.getString(Constants.DEVICE_UUID); csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); friendshipService = FriendshipService.getInstance(deviceUuid, csrfToken, userId); + newsService = NewsService.getInstance(); } @NonNull @@ -210,6 +241,7 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe private void init() { final NotificationsViewerFragmentArgs fragmentArgs = NotificationsViewerFragmentArgs.fromBundle(getArguments()); type = fragmentArgs.getType(); + targetId = fragmentArgs.getTargetId(); final Context context = getContext(); CookieUtils.setupCookies(settingsHelper.getString(Constants.COOKIE)); binding.swipeRefreshLayout.setOnRefreshListener(this); @@ -224,11 +256,13 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe @Override public void onRefresh() { binding.swipeRefreshLayout.setRefreshing(true); + final ActionBar actionBar = fragmentActivity.getSupportActionBar(); switch (type) { case "notif": - new NotificationsFetcher(true, new FetchListener>() { + if (actionBar != null) actionBar.setTitle(R.string.action_notif); + new NotificationsFetcher(true, new FetchListener>() { @Override - public void onResult(final List notificationModels) { + public void onResult(final List notificationModels) { binding.swipeRefreshLayout.setRefreshing(false); notificationViewModel.getList().postValue(notificationModels); } @@ -244,23 +278,12 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); break; case "ayml": - final NewsService newsService = NewsService.getInstance(); - newsService.fetchSuggestions(csrfToken, new ServiceCallback>() { - @Override - public void onSuccess(final List notificationModels) { - binding.swipeRefreshLayout.setRefreshing(false); - notificationViewModel.getList().postValue(notificationModels); - } - - @Override - public void onFailure(final Throwable t) { - try { - binding.swipeRefreshLayout.setRefreshing(false); - Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); - } - catch(Throwable e) {} - } - }); + if (actionBar != null) actionBar.setTitle(R.string.action_ayml); + newsService.fetchSuggestions(csrfToken, deviceUuid, cb); + break; + case "chaining": + if (actionBar != null) actionBar.setTitle(R.string.action_ayml); + newsService.fetchChaining(targetId, cb); break; } } diff --git a/app/src/main/java/awais/instagrabber/fragments/StoryListViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/StoryListViewerFragment.java index 880af8f5..d71a7cfb 100644 --- a/app/src/main/java/awais/instagrabber/fragments/StoryListViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/StoryListViewerFragment.java @@ -119,7 +119,7 @@ public final class StoryListViewerFragment extends Fragment implements SwipeRefr fragmentActivity = (AppCompatActivity) requireActivity(); context = getContext(); if (context == null) return; - storiesService = StoriesService.getInstance(); + storiesService = StoriesService.getInstance(null, 0L, null); } @NonNull diff --git a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java index ea38abe8..4a14927a 100644 --- a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java @@ -71,7 +71,6 @@ import awais.instagrabber.R; import awais.instagrabber.adapters.StoriesAdapter; import awais.instagrabber.asyncs.CreateThreadAction; import awais.instagrabber.asyncs.PostFetcher; -import awais.instagrabber.asyncs.SeenAction; import awais.instagrabber.customviews.helpers.SwipeGestureListener; import awais.instagrabber.databinding.FragmentStoryViewerBinding; import awais.instagrabber.fragments.main.ProfileFragmentDirections; @@ -151,17 +150,16 @@ public class StoryViewerFragment extends Fragment { private DirectMessagesService directMessagesService; private final String cookie = settingsHelper.getString(Constants.COOKIE); - private final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); - private final long userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); - private final String deviceId = settingsHelper.getString(Constants.DEVICE_UUID); private StoryViewerOptions options; @Override public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); + final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); + final long userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); + final String deviceId = settingsHelper.getString(Constants.DEVICE_UUID); fragmentActivity = (AppCompatActivity) requireActivity(); - storiesService = StoriesService.getInstance(); - if (csrfToken == null) return; + storiesService = StoriesService.getInstance(csrfToken, userIdFromCookie, deviceId); directMessagesService = DirectMessagesService.getInstance(csrfToken, userIdFromCookie, deviceId); setHasOptionsMenu(true); } @@ -478,36 +476,32 @@ public class StoryViewerFragment extends Fragment { poll.getLeftChoice() + " (" + poll.getLeftCount() + ")", poll.getRightChoice() + " (" + poll.getRightCount() + ")" }), (d, w) -> { - if (!TextUtils.isEmpty(cookie)) { - sticking = true; - storiesService.respondToPoll( - currentStory.getStoryMediaId().split("_")[0], - poll.getId(), - w, - userIdFromCookie, - csrfToken, - new ServiceCallback() { - @Override - public void onSuccess(final StoryStickerResponse result) { - sticking = false; - try { - poll.setMyChoice(w); - Toast.makeText(context, R.string.votef_story_poll, Toast.LENGTH_SHORT).show(); - } - catch (Exception ignored) {} + sticking = true; + storiesService.respondToPoll( + currentStory.getStoryMediaId().split("_")[0], + poll.getId(), + w, + new ServiceCallback() { + @Override + public void onSuccess(final StoryStickerResponse result) { + sticking = false; + try { + poll.setMyChoice(w); + Toast.makeText(context, R.string.votef_story_poll, Toast.LENGTH_SHORT).show(); } + catch (Exception ignored) {} + } - @Override - public void onFailure(final Throwable t) { - sticking = false; - Log.e(TAG, "Error responding", t); - try { - Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); - } - catch (Exception ignored) {} + @Override + public void onFailure(final Throwable t) { + sticking = false; + Log.e(TAG, "Error responding", t); + try { + Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); } - }); - } + catch (Exception ignored) {} + } + }); }) .setPositiveButton(R.string.cancel, null) .show(); @@ -525,8 +519,6 @@ public class StoryViewerFragment extends Fragment { currentStory.getStoryMediaId().split("_")[0], question.getId(), input.getText().toString(), - userIdFromCookie, - csrfToken, new ServiceCallback() { @Override public void onSuccess(final StoryStickerResponse result) { @@ -565,14 +557,12 @@ public class StoryViewerFragment extends Fragment { new AlertDialog.Builder(context) .setTitle(quiz.getMyChoice() > -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 && !TextUtils.isEmpty(cookie)) { + if (quiz.getMyChoice() == -1) { sticking = true; storiesService.respondToQuiz( currentStory.getStoryMediaId().split("_")[0], quiz.getId(), w, - userIdFromCookie, - csrfToken, new ServiceCallback() { @Override public void onSuccess(final StoryStickerResponse result) { @@ -643,8 +633,6 @@ public class StoryViewerFragment extends Fragment { currentStory.getStoryMediaId().split("_")[0], slider.getId(), sliderValue, - userIdFromCookie, - csrfToken, new ServiceCallback() { @Override public void onSuccess(final StoryStickerResponse result) { @@ -868,7 +856,7 @@ public class StoryViewerFragment extends Fragment { binding.poll.setTag(poll); question = currentStory.getQuestion(); - binding.answer.setVisibility((question != null && !TextUtils.isEmpty(cookie)) ? View.VISIBLE : View.GONE); + binding.answer.setVisibility((question != null) ? View.VISIBLE : View.GONE); binding.answer.setTag(question); mentions = currentStory.getMentions(); @@ -909,7 +897,11 @@ public class StoryViewerFragment extends Fragment { actionBar.setSubtitle(Utils.datetimeParser.format(new Date(currentStory.getTimestamp() * 1000L))); } - if (settingsHelper.getBoolean(MARK_AS_SEEN)) new SeenAction(cookie, currentStory).execute(); + if (settingsHelper.getBoolean(MARK_AS_SEEN)) + storiesService.seen(currentStory.getStoryMediaId(), + currentStory.getTimestamp(), + System.currentTimeMillis() / 1000, + null); } private void downloadStory() { @@ -947,7 +939,7 @@ public class StoryViewerFragment extends Fragment { if (menuDownload != null) { menuDownload.setVisible(true); } - if (currentStory.canReply() && menuDm != null && !TextUtils.isEmpty(cookie)) { + if (currentStory.canReply() && menuDm != null) { menuDm.setVisible(true); } binding.progressView.setVisibility(View.GONE); @@ -980,7 +972,7 @@ public class StoryViewerFragment extends Fragment { @NonNull final LoadEventInfo loadEventInfo, @NonNull final MediaLoadData mediaLoadData) { if (menuDownload != null) menuDownload.setVisible(true); - if (currentStory.canReply() && menuDm != null && !TextUtils.isEmpty(cookie)) + if (currentStory.canReply() && menuDm != null) menuDm.setVisible(true); binding.progressView.setVisibility(View.GONE); } @@ -991,7 +983,7 @@ public class StoryViewerFragment extends Fragment { @NonNull final LoadEventInfo loadEventInfo, @NonNull final MediaLoadData mediaLoadData) { if (menuDownload != null) menuDownload.setVisible(true); - if (currentStory.canReply() && menuDm != null && !TextUtils.isEmpty(cookie)) + if (currentStory.canReply() && menuDm != null) menuDm.setVisible(true); binding.progressView.setVisibility(View.VISIBLE); } diff --git a/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java b/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java index 54f6ea25..f4b8d47e 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java @@ -259,7 +259,7 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); fragmentActivity = (MainActivity) requireActivity(); - storiesService = StoriesService.getInstance(); + storiesService = StoriesService.getInstance(null, 0L, null); setHasOptionsMenu(true); } diff --git a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java index f045dcd4..a90bac90 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java @@ -85,6 +85,7 @@ import awais.instagrabber.repositories.responses.FriendshipRestrictResponse; import awais.instagrabber.repositories.responses.FriendshipStatus; import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.User; +import awais.instagrabber.repositories.responses.UserProfileContextLink; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.DownloadUtils; @@ -123,6 +124,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe private HighlightsViewModel highlightsViewModel; private MenuItem blockMenuItem; private MenuItem restrictMenuItem; + private MenuItem chainingMenuItem; private boolean highlightsFetching; private boolean postsSetupDone = false; private Set selectedFeedModels; @@ -306,7 +308,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); fragmentActivity = (MainActivity) requireActivity(); friendshipService = isLoggedIn ? FriendshipService.getInstance(deviceUuid, csrfToken, userId) : null; - storiesService = isLoggedIn ? StoriesService.getInstance() : null; + storiesService = isLoggedIn ? StoriesService.getInstance(null, 0L, null) : null; mediaService = isLoggedIn ? MediaService.getInstance(null, null, 0) : null; accountRepository = AccountRepository.getInstance(AccountDataSource.getInstance(getContext())); favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(getContext())); @@ -362,11 +364,31 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe inflater.inflate(R.menu.profile_menu, menu); blockMenuItem = menu.findItem(R.id.block); if (blockMenuItem != null) { - blockMenuItem.setVisible(false); + if (profileModel != null) { + blockMenuItem.setVisible(!Objects.equals(profileModel.getPk(), CookieUtils.getUserIdFromCookie(cookie))); + blockMenuItem.setTitle(profileModel.getFriendshipStatus().isBlocking() ? R.string.unblock : R.string.block); + } else { + blockMenuItem.setVisible(false); + } } restrictMenuItem = menu.findItem(R.id.restrict); if (restrictMenuItem != null) { - restrictMenuItem.setVisible(false); + if (profileModel != null) { + restrictMenuItem.setVisible(!Objects.equals(profileModel.getPk(), CookieUtils.getUserIdFromCookie(cookie))); + restrictMenuItem.setTitle(profileModel.getFriendshipStatus().isRestricted() ? R.string.unrestrict : R.string.restrict); + } + else { + restrictMenuItem.setVisible(false); + } + } + chainingMenuItem = menu.findItem(R.id.chaining); + if (chainingMenuItem != null) { + if (profileModel != null) { + chainingMenuItem.setVisible(!Objects.equals(profileModel.getPk(), CookieUtils.getUserIdFromCookie(cookie))); + } + else { + chainingMenuItem.setVisible(false); + } } } @@ -431,11 +453,18 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe }); return true; } + if (item.getItemId() == R.id.chaining) { + if (!isLoggedIn) return false; + final NavDirections navDirections = ProfileFragmentDirections.actionGlobalNotificationsViewerFragment("chaining", profileModel.getPk()); + NavHostFragment.findNavController(this).navigate(navDirections); + return true; + } return super.onOptionsItemSelected(item); } @Override public void onRefresh() { + profileDetailsBinding.countsBarrier.setVisibility(View.GONE); profileDetailsBinding.mainProfileImage.setVisibility(View.INVISIBLE); fetchProfileDetails(); } @@ -653,6 +682,8 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe profileDetailsBinding.mainProfileImage.setImageURI(profileModel.getProfilePicUrl()); profileDetailsBinding.mainProfileImage.setVisibility(View.VISIBLE); + profileDetailsBinding.countsBarrier.setVisibility(View.VISIBLE); + final long followersCount = profileModel.getFollowerCount(); final long followingCount = profileModel.getFollowingCount(); @@ -691,7 +722,11 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe : profileModel.getFullName()); final String biography = profileModel.getBiography(); - if (!TextUtils.isEmpty(biography)) { + if (TextUtils.isEmpty(biography)) { + profileDetailsBinding.mainBiography.setVisibility(View.GONE); + } + else { + profileDetailsBinding.mainBiography.setVisibility(View.VISIBLE); profileDetailsBinding.mainBiography.setText(biography); profileDetailsBinding.mainBiography.addOnHashtagListener(autoLinkItem -> { final NavController navController = NavHostFragment.findNavController(this); @@ -756,6 +791,27 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe return true; }); } + + String profileContext = profileModel.getProfileContext(); + if (TextUtils.isEmpty(profileContext)) { + profileDetailsBinding.profileContext.setVisibility(View.GONE); + } + else { + profileDetailsBinding.profileContext.setVisibility(View.VISIBLE); + final List userProfileContextLinks = profileModel.getProfileContextLinks(); + for (int i = 0; i < userProfileContextLinks.size(); i++) { + final UserProfileContextLink link = userProfileContextLinks.get(i); + if (link.getUsername() != null) + profileContext = profileContext.substring(0, link.getStart() + i) + + "@" + profileContext.substring(link.getStart() + i); + } + profileDetailsBinding.profileContext.setText(profileContext); + profileDetailsBinding.profileContext.addOnMentionClickListener(autoLinkItem -> { + final String originalText = autoLinkItem.getOriginalText().trim(); + navigateToProfile(originalText); + }); + } + final String url = profileModel.getExternalUrl(); if (TextUtils.isEmpty(url)) { profileDetailsBinding.mainUrl.setVisibility(View.GONE); @@ -830,13 +886,13 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe } if (profileModel.getFriendshipStatus().isFollowing()) { profileDetailsBinding.btnFollow.setText(R.string.unfollow); - profileDetailsBinding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24); + profileDetailsBinding.btnFollow.setChipIconResource(R.drawable.ic_outline_person_add_disabled_24); } else if (profileModel.getFriendshipStatus().isOutgoingRequest()) { profileDetailsBinding.btnFollow.setText(R.string.cancel); - profileDetailsBinding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24); + profileDetailsBinding.btnFollow.setChipIconResource(R.drawable.ic_outline_person_add_disabled_24); } else { profileDetailsBinding.btnFollow.setText(R.string.follow); - profileDetailsBinding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_24); + profileDetailsBinding.btnFollow.setChipIconResource(R.drawable.ic_outline_person_add_24); } if (restrictMenuItem != null) { restrictMenuItem.setVisible(true); @@ -854,15 +910,10 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe blockMenuItem.setTitle(R.string.block); } } - return; - } - if (!isReallyPrivate() && restrictMenuItem != null) { - restrictMenuItem.setVisible(true); - if (profileModel.getFriendshipStatus().isRestricted()) { - restrictMenuItem.setTitle(R.string.unrestrict); - } else { - restrictMenuItem.setTitle(R.string.restrict); + if (chainingMenuItem != null && !Objects.equals(profileId, myId)) { + chainingMenuItem.setVisible(true); } + return; } } diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java index 55088c8b..fce752e1 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java @@ -134,12 +134,12 @@ public class MorePreferencesFragment extends BasePreferencesFragment { screen.addPreference(getDivider(context)); if (isLoggedIn) { screen.addPreference(getPreference(R.string.action_notif, R.drawable.ic_not_liked, preference -> { - final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalNotificationsViewerFragment("notif"); + final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalNotificationsViewerFragment("notif", 0l); NavHostFragment.findNavController(this).navigate(navDirections); return true; })); screen.addPreference(getPreference(R.string.action_ayml, R.drawable.ic_suggested_users, preference -> { - final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalNotificationsViewerFragment("ayml"); + final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalNotificationsViewerFragment("ayml", 0l); NavHostFragment.findNavController(this).navigate(navDirections); return true; })); diff --git a/app/src/main/java/awais/instagrabber/models/NotificationModel.java b/app/src/main/java/awais/instagrabber/models/NotificationModel.java deleted file mode 100755 index 4ca34b2d..00000000 --- a/app/src/main/java/awais/instagrabber/models/NotificationModel.java +++ /dev/null @@ -1,79 +0,0 @@ -package awais.instagrabber.models; - -import androidx.annotation.NonNull; - -import java.util.Date; - -import awais.instagrabber.models.enums.NotificationType; -import awais.instagrabber.utils.Utils; - -public final class NotificationModel { - private final String id; - private final long userId; - private final String username; - private final String profilePicUrl; - private final long postId; - private final String previewUrl; - private final NotificationType type; - private final CharSequence text; - private final long timestamp; - - public NotificationModel(final String id, - final String text, - final long timestamp, - final long userId, - final String username, - final String profilePicUrl, - final long postId, - final String previewUrl, - final NotificationType type) { - this.id = id; - this.text = text; - this.timestamp = timestamp; - this.userId = userId; - this.username = username; - this.profilePicUrl = profilePicUrl; - this.postId = postId; - this.previewUrl = previewUrl; - this.type = type; - } - - public String getId() { - return id; - } - - public CharSequence getText() { - return text; - } - - public long getTimestamp() { - return timestamp; - } - - @NonNull - public String getDateTime() { - return Utils.datetimeParser.format(new Date(timestamp * 1000L)); - } - - public long getUserId() { - return userId; - } - - public String getUsername() { - return username; - } - - public String getProfilePic() { - return profilePicUrl; - } - - public long getPostId() { - return postId; - } - - public String getPreviewPic() { - return previewUrl; - } - - public NotificationType getType() { return type; } -} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/repositories/NewsRepository.java b/app/src/main/java/awais/instagrabber/repositories/NewsRepository.java index edc1e149..0cc89739 100644 --- a/app/src/main/java/awais/instagrabber/repositories/NewsRepository.java +++ b/app/src/main/java/awais/instagrabber/repositories/NewsRepository.java @@ -2,6 +2,9 @@ package awais.instagrabber.repositories; import java.util.Map; +import awais.instagrabber.repositories.responses.AymlResponse; +import awais.instagrabber.repositories.responses.NewsInboxResponse; +import awais.instagrabber.repositories.responses.UserSearchResponse; import retrofit2.Call; import retrofit2.http.FieldMap; import retrofit2.http.FormUrlEncoded; @@ -16,9 +19,12 @@ public interface NewsRepository { Call webInbox(@Header("User-Agent") String userAgent); @GET("/api/v1/news/inbox/") - Call appInbox(@Header("User-Agent") String userAgent, @Query(value = "mark_as_seen", encoded = true) boolean markAsSeen); + Call appInbox(@Header("User-Agent") String userAgent, @Query(value = "mark_as_seen", encoded = true) boolean markAsSeen); @FormUrlEncoded @POST("/api/v1/discover/ayml/") - Call getAyml(@Header("User-Agent") String userAgent, @FieldMap final Map form); + Call getAyml(@Header("User-Agent") String userAgent, @FieldMap final Map form); + + @GET("/api/v1/discover/chaining/") + Call getChaining(@Header("User-Agent") String userAgent, @Query(value = "target_id") long targetId); } diff --git a/app/src/main/java/awais/instagrabber/repositories/StoriesRepository.java b/app/src/main/java/awais/instagrabber/repositories/StoriesRepository.java index 6eaa83ba..766649c4 100644 --- a/app/src/main/java/awais/instagrabber/repositories/StoriesRepository.java +++ b/app/src/main/java/awais/instagrabber/repositories/StoriesRepository.java @@ -36,4 +36,8 @@ public interface StoriesRepository { @Path("action") String action, // story_poll_vote, story_question_response, story_slider_vote, story_quiz_answer @FieldMap Map form); + + @FormUrlEncoded + @POST("/api/v2/media/seen/") + Call seen(@QueryMap Map queryParams, @FieldMap Map form); } diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/AymlResponse.java b/app/src/main/java/awais/instagrabber/repositories/responses/AymlResponse.java new file mode 100644 index 00000000..5762e170 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/repositories/responses/AymlResponse.java @@ -0,0 +1,22 @@ +package awais.instagrabber.repositories.responses; + +import java.util.List; + +public class AymlResponse { + private final AymlUserList newSuggestedUsers; + private final AymlUserList suggestedUsers; + + public AymlResponse(final AymlUserList newSuggestedUsers, + final AymlUserList suggestedUsers) { + this.newSuggestedUsers = newSuggestedUsers; + this.suggestedUsers = suggestedUsers; + } + + public AymlUserList getNewSuggestedUsers() { + return newSuggestedUsers; + } + + public AymlUserList getSuggestedUsers() { + return suggestedUsers; + } +} diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/AymlUser.java b/app/src/main/java/awais/instagrabber/repositories/responses/AymlUser.java new file mode 100644 index 00000000..7f5b2632 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/repositories/responses/AymlUser.java @@ -0,0 +1,34 @@ +package awais.instagrabber.repositories.responses; + +public class AymlUser { + private final User user; + private final String algorithm; + private final String socialContext; + private final String uuid; + + public AymlUser(final User user, + final String algorithm, + final String socialContext, + final String uuid) { + this.user = user; + this.algorithm = algorithm; + this.socialContext = socialContext; + this.uuid = uuid; + } + + public User getUser() { + return user; + } + + public String getAlgorithm() { + return algorithm; + } + + public String getSocialContext() { + return socialContext; + } + + public String getUuid() { + return uuid; + } +} diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/AymlUserList.java b/app/src/main/java/awais/instagrabber/repositories/responses/AymlUserList.java new file mode 100644 index 00000000..47b2c112 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/repositories/responses/AymlUserList.java @@ -0,0 +1,15 @@ +package awais.instagrabber.repositories.responses; + +import java.util.List; + +public class AymlUserList { + private final List suggestions; + + public AymlUserList(final List suggestions) { + this.suggestions = suggestions; + } + + public List getSuggestions() { + return suggestions; + } +} diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/NewsInboxResponse.java b/app/src/main/java/awais/instagrabber/repositories/responses/NewsInboxResponse.java new file mode 100644 index 00000000..5dcdb487 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/repositories/responses/NewsInboxResponse.java @@ -0,0 +1,29 @@ +package awais.instagrabber.repositories.responses; + +import java.util.List; + +public class NewsInboxResponse { + private final NotificationCounts counts; + private final List newStories; + private final List oldStories; + + public NewsInboxResponse(final NotificationCounts counts, + final List newStories, + final List oldStories) { + this.counts = counts; + this.newStories = newStories; + this.oldStories = oldStories; + } + + public NotificationCounts getCounts() { + return counts; + } + + public List getNewStories() { + return newStories; + } + + public List getOldStories() { + return oldStories; + } +} diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/Notification.java b/app/src/main/java/awais/instagrabber/repositories/responses/Notification.java new file mode 100644 index 00000000..d81bf357 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/repositories/responses/Notification.java @@ -0,0 +1,29 @@ +package awais.instagrabber.repositories.responses; + +import awais.instagrabber.models.enums.NotificationType; + +public class Notification { + private final NotificationArgs args; + private final String storyType; + private final String pk; + + public Notification(final NotificationArgs args, + final String storyType, + final String pk) { + this.args = args; + this.storyType = storyType; + this.pk = pk; + } + + public NotificationArgs getArgs() { + return args; + } + + public NotificationType getType() { + return NotificationType.valueOfType(storyType); + } + + public String getPk() { + return pk; + } +} diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/NotificationArgs.java b/app/src/main/java/awais/instagrabber/repositories/responses/NotificationArgs.java new file mode 100644 index 00000000..87116896 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/repositories/responses/NotificationArgs.java @@ -0,0 +1,93 @@ +package awais.instagrabber.repositories.responses; + +import androidx.annotation.NonNull; + +import java.util.Date; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import android.util.Log; + +import awais.instagrabber.utils.Utils; + +public class NotificationArgs { + private final String text; + private final String richText; + private final long profileId; + private final String profileImage; + private final List media; + private final double timestamp; + private final String profileName; + private final String fullName; // for AYML, not naturally generated + private final boolean isVerified; // mostly for AYML, not sure about notif + + public NotificationArgs(final String text, + final String richText, // for AYML, this is the algorithm + final long profileId, + final String profileImage, + final List media, + final double timestamp, + final String profileName, + final String fullName, + final boolean isVerified) { + this.text = text; + this.richText = richText; + this.profileId = profileId; + this.profileImage = profileImage; + this.media = media; + this.timestamp = timestamp; + this.profileName = profileName; + this.fullName = fullName; + this.isVerified = isVerified; + } + + public String getText() { + return text == null ? cleanRichText(richText) : text; + } + + public long getUserId() { + return profileId; + } + + public String getProfilePic() { + return profileImage; + } + + public String getUsername() { + return profileName; + } + + public String getFullName() { + return fullName; + } + + public List getMedia() { + return media; + } + + public double getTimestamp() { + return timestamp; + } + + public boolean isVerified() { + return isVerified; + } + + @NonNull + public String getDateTime() { + return Utils.datetimeParser.format(new Date(Math.round(timestamp * 1000))); + } + + private String cleanRichText(final String raw) { + if (raw == null) return null; + final Matcher matcher = Pattern.compile("\\{[\\p{L}\\d._]+\\|000000\\|1\\|user\\?id=\\d+\\}").matcher(raw); + String result = raw; + while (matcher.find()) { + final String richObject = raw.substring(matcher.start(), matcher.end()); + final String username = richObject.split("\\|")[0].substring(1); + result = result.replace(richObject, username); + } + return result; + } +} diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/NotificationCounts.java b/app/src/main/java/awais/instagrabber/repositories/responses/NotificationCounts.java new file mode 100644 index 00000000..38fb70f9 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/repositories/responses/NotificationCounts.java @@ -0,0 +1,57 @@ +package awais.instagrabber.repositories.responses; + +import androidx.annotation.NonNull; + +public class NotificationCounts { + private final int commentLikes; + private final int usertags; + private final int likes; + private final int comments; + private final int relationships; + private final int photosOfYou; + private final int requests; + + public NotificationCounts(final int commentLikes, + final int usertags, + final int likes, + final int comments, + final int relationships, + final int photosOfYou, + final int requests) { + this.commentLikes = commentLikes; + this.usertags = usertags; + this.likes = likes; + this.comments = comments; + this.relationships = relationships; + this.photosOfYou = photosOfYou; + this.requests = requests; + } + + public int getRelationshipsCount() { + return relationships; + } + + public int getUserTagsCount() { + return usertags; + } + + public int getCommentsCount() { + return comments; + } + + public int getCommentLikesCount() { + return commentLikes; + } + + public int getLikesCount() { + return likes; + } + + public int getPOYCount() { + return photosOfYou; + } + + public int getRequestsCount() { + return requests; + } +} diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/NotificationImage.java b/app/src/main/java/awais/instagrabber/repositories/responses/NotificationImage.java new file mode 100644 index 00000000..3ba92ce2 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/repositories/responses/NotificationImage.java @@ -0,0 +1,19 @@ +package awais.instagrabber.repositories.responses; + +public class NotificationImage { + private final String id; + private final String image; + + public NotificationImage(final String id, final String image) { + this.id = id; + this.image = image; + } + + public String getId() { + return id; + } + + public String getImage() { + return image; + } +} diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/User.java b/app/src/main/java/awais/instagrabber/repositories/responses/User.java index 2de6407f..1be7b245 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/User.java +++ b/app/src/main/java/awais/instagrabber/repositories/responses/User.java @@ -1,6 +1,7 @@ package awais.instagrabber.repositories.responses; import java.io.Serializable; +import java.util.List; import java.util.Objects; public class User implements Serializable { @@ -27,7 +28,9 @@ public class User implements Serializable { private final long usertagsCount; private final String publicEmail; private final HdProfilePicUrlInfo hdProfilePicUrlInfo; - + private final String profileContext; + private final List profileContextLinksWithUserIds; + private final String socialContext; public User(final long pk, final String username, @@ -51,7 +54,10 @@ public class User implements Serializable { final String externalUrl, final long usertagsCount, final String publicEmail, - final HdProfilePicUrlInfo hdProfilePicUrlInfo) { + final HdProfilePicUrlInfo hdProfilePicUrlInfo, + final String profileContext, + final List profileContextLinksWithUserIds, + final String socialContext) { this.pk = pk; this.username = username; this.fullName = fullName; @@ -75,6 +81,9 @@ public class User implements Serializable { this.usertagsCount = usertagsCount; this.publicEmail = publicEmail; this.hdProfilePicUrlInfo = hdProfilePicUrlInfo; + this.profileContext = profileContext; + this.profileContextLinksWithUserIds = profileContextLinksWithUserIds; + this.socialContext = socialContext; } public long getPk() { @@ -173,46 +182,17 @@ public class User implements Serializable { return publicEmail; } - // public boolean isReallyPrivate() { - // final FriendshipStatus friendshipStatus = getFriendshipStatus(); - // !user.optBoolean("followed_by_viewer") && (id != uid && isPrivate) - // } + public String getProfileContext() { + return profileContext; + } - // public static User fromProfileModel(final ProfileModel profileModel) { - // return new User( - // Long.parseLong(profileModel.getId()), - // profileModel.getUsername(), - // profileModel.getName(), - // profileModel.isPrivate(), - // profileModel.getSdProfilePic(), - // null, - // new FriendshipStatus( - // profileModel.isFollowing(), - // false, - // profileModel.isBlocked(), - // false, - // profileModel.isPrivate(), - // false, - // profileModel.isRequested(), - // false, - // profileModel.isRestricted(), - // false), - // profileModel.isVerified(), - // false, - // false, - // false, - // false, - // null, - // null, - // profileModel.getPostCount(), - // profileModel.getFollowersCount(), - // profileModel.getFollowingCount(), - // 0, - // profileModel.getBiography(), - // profileModel.getUrl(), - // 0, - // null); - // } + public String getSocialContext() { + return socialContext; + } + + public List getProfileContextLinks() { + return profileContextLinksWithUserIds; + } @Override diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/UserInfo.java b/app/src/main/java/awais/instagrabber/repositories/responses/UserInfo.java deleted file mode 100644 index d85f5161..00000000 --- a/app/src/main/java/awais/instagrabber/repositories/responses/UserInfo.java +++ /dev/null @@ -1,49 +0,0 @@ -package awais.instagrabber.repositories.responses; - -public class UserInfo { - private final long pk; - private final String username, fullName, profilePicUrl, hdProfilePicUrl; - - public UserInfo(final long pk, - final String username, - final String fullName, - final String profilePicUrl, - final String hdProfilePicUrl) { - this.pk = pk; - this.username = username; - this.fullName = fullName; - this.profilePicUrl = profilePicUrl; - this.hdProfilePicUrl = hdProfilePicUrl; - } - - public long getPk() { - return pk; - } - - public String getUsername() { - return username; - } - - public String getFullName() { - return fullName; - } - - public String getProfilePicUrl() { - return profilePicUrl; - } - - public String getHDProfilePicUrl() { - return hdProfilePicUrl; - } - - @Override - public String toString() { - return "UserInfo{" + - "uid='" + pk + '\'' + - ", username='" + username + '\'' + - ", fullName='" + fullName + '\'' + - ", profilePicUrl='" + profilePicUrl + '\'' + - ", hdProfilePicUrl='" + hdProfilePicUrl + '\'' + - '}'; - } -} diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/UserProfileContextLink.java b/app/src/main/java/awais/instagrabber/repositories/responses/UserProfileContextLink.java new file mode 100644 index 00000000..7beb954d --- /dev/null +++ b/app/src/main/java/awais/instagrabber/repositories/responses/UserProfileContextLink.java @@ -0,0 +1,21 @@ +package awais.instagrabber.repositories.responses; + +public class UserProfileContextLink { + private final String username; + private final int start; + private final int end; + + public UserProfileContextLink(final String username, final int start, final int end) { + this.username = username; + this.start = start; + this.end = end; + } + + public String getUsername() { + return username; + } + + public int getStart() { + return start; + } +} diff --git a/app/src/main/java/awais/instagrabber/services/ActivityCheckerService.java b/app/src/main/java/awais/instagrabber/services/ActivityCheckerService.java index 2ae44b79..03f2c9b2 100644 --- a/app/src/main/java/awais/instagrabber/services/ActivityCheckerService.java +++ b/app/src/main/java/awais/instagrabber/services/ActivityCheckerService.java @@ -18,8 +18,6 @@ import java.util.List; import awais.instagrabber.R; import awais.instagrabber.activities.MainActivity; -import awais.instagrabber.asyncs.GetActivityAsyncTask.NotificationCounts; -import awais.instagrabber.asyncs.GetActivityAsyncTask.OnTaskCompleteListener; import awais.instagrabber.utils.Constants; import static awais.instagrabber.utils.Utils.settingsHelper; @@ -30,7 +28,7 @@ public class ActivityCheckerService extends Service { private static final int DELAY_MILLIS = 60000; private Handler handler; - private OnTaskCompleteListener onTaskCompleteListener; + // private OnTaskCompleteListener onTaskCompleteListener; private NotificationManagerCompat notificationManager; private final IBinder binder = new LocalBinder(); @@ -50,6 +48,7 @@ public class ActivityCheckerService extends Service { public void onCreate() { notificationManager = NotificationManagerCompat.from(getApplicationContext()); handler = new Handler(); + /* onTaskCompleteListener = result -> { // Log.d(TAG, "onTaskCompleteListener: result: " + result); try { @@ -62,20 +61,12 @@ public class ActivityCheckerService extends Service { handler.postDelayed(runnable, DELAY_MILLIS); } }; + */ } @Override public IBinder onBind(Intent intent) { startChecking(); - // Uncomment to test notifications - // final String notificationString = getNotificationString(new NotificationCounts( - // 1, - // 2, - // 3, - // 4, - // 5 - // )); - // showNotification(notificationString); return binder; } @@ -93,6 +84,7 @@ public class ActivityCheckerService extends Service { handler.removeCallbacks(runnable); } + /* private String getNotificationString(final NotificationCounts result) { final List list = new ArrayList<>(); if (result.getRelationshipsCount() != 0) { @@ -113,6 +105,7 @@ public class ActivityCheckerService extends Service { if (list.isEmpty()) return null; return TextUtils.join(", ", list); } + */ private void showNotification(final String notificationString) { final Notification notification = new NotificationCompat.Builder(this, Constants.ACTIVITY_CHANNEL_ID) diff --git a/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java b/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java index 17aa3684..77cc3502 100644 --- a/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java @@ -778,7 +778,8 @@ public final class ResponseBodyUtils { null, friendshipStatus, owner.optBoolean("is_verified"), - false, false, false, false, null, null, 0, 0, 0, 0, null, null, 0, null, null); + false, false, false, false, null, null, 0, 0, 0, 0, null, null, 0, null, null, + null, null, null); } final String id = feedItem.getString(Constants.EXTRAS_ID); final ImageVersions2 imageVersions2 = new ImageVersions2( diff --git a/app/src/main/java/awais/instagrabber/viewmodels/NotificationViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/NotificationViewModel.java index aa8fc194..59a8bb64 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/NotificationViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/NotificationViewModel.java @@ -5,12 +5,12 @@ import androidx.lifecycle.ViewModel; import java.util.List; -import awais.instagrabber.models.NotificationModel; +import awais.instagrabber.repositories.responses.Notification; public class NotificationViewModel extends ViewModel { - private MutableLiveData> list; + private MutableLiveData> list; - public MutableLiveData> getList() { + public MutableLiveData> getList() { if (list == null) { list = new MutableLiveData<>(); } diff --git a/app/src/main/java/awais/instagrabber/webservices/GraphQLService.java b/app/src/main/java/awais/instagrabber/webservices/GraphQLService.java index 74044d29..2af7738c 100644 --- a/app/src/main/java/awais/instagrabber/webservices/GraphQLService.java +++ b/app/src/main/java/awais/instagrabber/webservices/GraphQLService.java @@ -243,6 +243,9 @@ public class GraphQLService extends BaseService { null, 0, null, + null, + null, + null, null )); // userModels.add(new ProfileModel(userObject.optBoolean("is_private"), @@ -334,6 +337,9 @@ public class GraphQLService extends BaseService { url, 0, null, + null, + null, + null, null)); } catch (JSONException e) { Log.e(TAG, "onResponse", e); diff --git a/app/src/main/java/awais/instagrabber/webservices/NewsService.java b/app/src/main/java/awais/instagrabber/webservices/NewsService.java index 2eca1769..a03ca36d 100644 --- a/app/src/main/java/awais/instagrabber/webservices/NewsService.java +++ b/app/src/main/java/awais/instagrabber/webservices/NewsService.java @@ -9,17 +9,24 @@ import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.util.stream.Collectors; import awais.instagrabber.BuildConfig; -import awais.instagrabber.models.NotificationModel; import awais.instagrabber.models.enums.NotificationType; import awais.instagrabber.repositories.NewsRepository; +import awais.instagrabber.repositories.responses.AymlResponse; +import awais.instagrabber.repositories.responses.AymlUser; +import awais.instagrabber.repositories.responses.UserSearchResponse; +import awais.instagrabber.repositories.responses.NewsInboxResponse; +import awais.instagrabber.repositories.responses.Notification; +import awais.instagrabber.repositories.responses.NotificationArgs; +import awais.instagrabber.repositories.responses.NotificationImage; +import awais.instagrabber.repositories.responses.User; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Utils; import retrofit2.Call; @@ -52,48 +59,31 @@ public class NewsService extends BaseService { } public void fetchAppInbox(final boolean markAsSeen, - final ServiceCallback> callback) { - final List result = new ArrayList<>(); - final Call request = repository.appInbox(appUa, markAsSeen); - request.enqueue(new Callback() { + final ServiceCallback> callback) { + final Call request = repository.appInbox(appUa, markAsSeen); + request.enqueue(new Callback() { @Override - public void onResponse(@NonNull final Call call, @NonNull final Response response) { - final String body = response.body(); + public void onResponse(@NonNull final Call call, @NonNull final Response response) { + final NewsInboxResponse body = response.body(); if (body == null) { callback.onSuccess(null); return; } - try { - final JSONObject jsonObject = new JSONObject(body); - final JSONArray oldStories = jsonObject.getJSONArray("old_stories"), - newStories = jsonObject.getJSONArray("new_stories"); - - for (int j = 0; j < newStories.length(); ++j) { - final NotificationModel newsItem = parseNewsItem(newStories.getJSONObject(j)); - if (newsItem != null) result.add(newsItem); - } - - for (int i = 0; i < oldStories.length(); ++i) { - final NotificationModel newsItem = parseNewsItem(oldStories.getJSONObject(i)); - if (newsItem != null) result.add(newsItem); - } - - callback.onSuccess(result); - } catch (JSONException e) { - callback.onFailure(e); - } + final List result = new ArrayList<>(); + result.addAll(body.getNewStories()); + result.addAll(body.getOldStories()); + callback.onSuccess(result); } @Override - public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { + public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { callback.onFailure(t); // Log.e(TAG, "onFailure: ", t); } }); } - public void fetchWebInbox(final boolean markAsSeen, - final ServiceCallback> callback) { + public void fetchWebInbox(final ServiceCallback> callback) { final Call request = repository.webInbox(browserUa); request.enqueue(new Callback() { @Override @@ -104,7 +94,7 @@ public class NewsService extends BaseService { return; } try { - final List result = new ArrayList<>(); + final List result = new ArrayList<>(); final JSONObject page = new JSONObject(body) .getJSONObject("graphql") .getJSONObject("user"); @@ -124,16 +114,25 @@ public class NewsService extends BaseService { final NotificationType notificationType = NotificationType.valueOfType(type); if (notificationType == null) continue; final JSONObject user = data.getJSONObject("user"); - result.add(new NotificationModel( - data.getString(Constants.EXTRAS_ID), - data.optString("text"), // comments or mentions - data.getLong("timestamp"), - user.getLong("id"), - user.getString("username"), - user.getString("profile_pic_url"), - !data.isNull("media") ? Long.valueOf(data.getJSONObject("media").getString("id").split("_")[0]) : 0, - data.has("media") ? data.getJSONObject("media").getString("thumbnail_src") : null, - notificationType)); + + result.add(new Notification( + new NotificationArgs( + data.optString("text"), + null, + user.getLong(Constants.EXTRAS_ID), + user.getString("profile_pic_url"), + data.isNull("media") ? null : Collections.singletonList(new NotificationImage( + data.getJSONObject("media").getString("id"), + data.getJSONObject("media").getString("thumbnail_src") + )), + data.getLong("timestamp"), + user.getString("username"), + null, + false + ), + type, + data.getString(Constants.EXTRAS_ID) + )); } } @@ -144,15 +143,21 @@ public class NewsService extends BaseService { for (int i = 0; i < media.length(); ++i) { data = media.optJSONObject(i).optJSONObject("node"); if (data == null) continue; - result.add(new NotificationModel( - data.getString(Constants.EXTRAS_ID), - data.optString("full_name"), - 0L, - data.getLong(Constants.EXTRAS_ID), - data.getString("username"), - data.getString("profile_pic_url"), - 0, - null, NotificationType.REQUEST)); + result.add(new Notification( + new NotificationArgs( + null, + null, + data.getLong(Constants.EXTRAS_ID), + data.getString("profile_pic_url"), + null, + 0L, + data.getString("username"), + data.optString("full_name"), + data.optBoolean("is_verified") + ), + "REQUEST", + data.getString(Constants.EXTRAS_ID) + )); } } callback.onSuccess(result); @@ -169,40 +174,9 @@ public class NewsService extends BaseService { }); } - private NotificationModel parseNewsItem(final JSONObject itemJson) throws JSONException { - if (itemJson == null) return null; - final String type = itemJson.getString("story_type"); - final NotificationType notificationType = NotificationType.valueOfType(type); - if (notificationType == null) { - if (BuildConfig.DEBUG) Log.d("austin_debug", "unhandled news type: " + itemJson); - return null; - } - final JSONObject data = itemJson.getJSONObject("args"); - return new NotificationModel( - data.getString("tuuid"), - data.has("text") ? data.getString("text") : cleanRichText(data.optString("rich_text", "")), - data.getLong("timestamp"), - data.getLong("profile_id"), - data.getString("profile_name"), - data.getString("profile_image"), - !data.isNull("media") ? Long.valueOf(data.getJSONArray("media").getJSONObject(0).getString("id").split("_")[0]) : 0, - !data.isNull("media") ? data.getJSONArray("media").getJSONObject(0).getString("image") : null, - notificationType); - } - - private String cleanRichText(final String raw) { - final Matcher matcher = Pattern.compile("\\{[\\p{L}\\d._]+\\|000000\\|1\\|user\\?id=\\d+\\}").matcher(raw); - String result = raw; - while (matcher.find()) { - final String richObject = raw.substring(matcher.start(), matcher.end()); - final String username = richObject.split("\\|")[0].substring(1); - result = result.replace(richObject, username); - } - return result; - } - public void fetchSuggestions(final String csrfToken, - final ServiceCallback> callback) { + final String deviceUuid, + final ServiceCallback> callback) { final Map form = new HashMap<>(); form.put("_uuid", UUID.randomUUID().toString()); form.put("_csrftoken", csrfToken); @@ -210,57 +184,88 @@ public class NewsService extends BaseService { form.put("device_id", UUID.randomUUID().toString()); form.put("module", "discover_people"); form.put("paginate", "false"); - final Call request = repository.getAyml(appUa, form); - request.enqueue(new Callback() { + final Call request = repository.getAyml(appUa, form); + request.enqueue(new Callback() { @Override - public void onResponse(@NonNull final Call call, @NonNull final Response response) { - final String body = response.body(); + public void onResponse(@NonNull final Call call, @NonNull final Response response) { + final AymlResponse body = response.body(); if (body == null) { callback.onSuccess(null); return; } - try { - final List result = new ArrayList<>(); - final JSONObject jsonObject = new JSONObject(body); - final JSONArray oldStories = jsonObject.getJSONObject("suggested_users").getJSONArray("suggestions"), - newStories = jsonObject.getJSONObject("new_suggested_users").getJSONArray("suggestions"); + final List aymlUsers = new ArrayList<>(); + aymlUsers.addAll(body.getNewSuggestedUsers().getSuggestions()); + aymlUsers.addAll(body.getSuggestedUsers().getSuggestions()); - for (int j = 0; j < newStories.length(); ++j) { - final NotificationModel newsItem = parseAymlItem(newStories.getJSONObject(j)); - if (newsItem != null) result.add(newsItem); - } - - for (int i = 0; i < oldStories.length(); ++i) { - final NotificationModel newsItem = parseAymlItem(oldStories.getJSONObject(i)); - if (newsItem != null) result.add(newsItem); - } - - callback.onSuccess(result); - } catch (JSONException e) { - callback.onFailure(e); - } + final List newsItems = aymlUsers.stream() + .map(i -> { + final User u = i.getUser(); + return new Notification( + new NotificationArgs( + i.getSocialContext(), + i.getAlgorithm(), + u.getPk(), + u.getProfilePicUrl(), + null, + 0L, + u.getUsername(), + u.getFullName(), + u.isVerified() + ), + "AYML", + i.getUuid() + ); + }) + .collect(Collectors.toList()); + callback.onSuccess(newsItems); } @Override - public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { + public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { callback.onFailure(t); // Log.e(TAG, "onFailure: ", t); } }); } - private NotificationModel parseAymlItem(final JSONObject itemJson) throws JSONException { - if (itemJson == null) return null; - final JSONObject data = itemJson.getJSONObject("user"); - return new NotificationModel( - itemJson.getString("uuid"), - itemJson.getString("social_context"), - 0L, - data.getLong("pk"), - data.getString("username"), - data.getString("profile_pic_url"), - 0, - data.getString("full_name"), // just borrowing this field - NotificationType.AYML); + public void fetchChaining(final long targetId, final ServiceCallback> callback) { + final Call request = repository.getChaining(appUa, targetId); + request.enqueue(new Callback() { + @Override + public void onResponse(@NonNull final Call call, @NonNull final Response response) { + final UserSearchResponse body = response.body(); + if (body == null) { + callback.onSuccess(null); + return; + } + + final List newsItems = body.getUsers().stream() + .map(u -> { + return new Notification( + new NotificationArgs( + u.getSocialContext(), + null, + u.getPk(), + u.getProfilePicUrl(), + null, + 0L, + u.getUsername(), + u.getFullName(), + u.isVerified() + ), + "AYML", + u.getProfilePicId() // placeholder + ); + }) + .collect(Collectors.toList()); + callback.onSuccess(newsItems); + } + + @Override + public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { + callback.onFailure(t); + // Log.e(TAG, "onFailure: ", t); + } + }); } } diff --git a/app/src/main/java/awais/instagrabber/webservices/StoriesService.java b/app/src/main/java/awais/instagrabber/webservices/StoriesService.java index 0a7d561c..96e6866f 100644 --- a/app/src/main/java/awais/instagrabber/webservices/StoriesService.java +++ b/app/src/main/java/awais/instagrabber/webservices/StoriesService.java @@ -14,6 +14,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.UUID; import awais.instagrabber.models.FeedStoryModel; @@ -36,20 +37,45 @@ import retrofit2.Retrofit; public class StoriesService extends BaseService { private static final String TAG = "StoriesService"; - private final StoriesRepository repository; - private static StoriesService instance; - private StoriesService() { + private final StoriesRepository repository; + private final String csrfToken; + private final long userId; + private final String deviceUuid; + + private StoriesService(@NonNull final String csrfToken, + final long userId, + @NonNull final String deviceUuid) { + this.csrfToken = csrfToken; + this.userId = userId; + this.deviceUuid = deviceUuid; final Retrofit retrofit = getRetrofitBuilder() .baseUrl("https://i.instagram.com") .build(); repository = retrofit.create(StoriesRepository.class); } - public static StoriesService getInstance() { - if (instance == null) { - instance = new StoriesService(); + public String getCsrfToken() { + return csrfToken; + } + + public long getUserId() { + return userId; + } + + public String getDeviceUuid() { + return deviceUuid; + } + + public static StoriesService getInstance(final String csrfToken, + final long userId, + final String deviceUuid) { + if (instance == null + || !Objects.equals(instance.getCsrfToken(), csrfToken) + || !Objects.equals(instance.getUserId(), userId) + || !Objects.equals(instance.getDeviceUuid(), deviceUuid)) { + instance = new StoriesService(csrfToken, userId, deviceUuid); } return instance; } @@ -146,6 +172,9 @@ public class StoriesService extends BaseService { null, 0, null, + null, + null, + null, null ); final String id = node.getString("id"); @@ -205,6 +234,9 @@ public class StoriesService extends BaseService { null, 0, null, + null, + null, + null, null ); final String id = node.getString("id"); @@ -390,13 +422,11 @@ public class StoriesService extends BaseService { final String action, final String arg1, final String arg2, - final long userId, - final String csrfToken, final ServiceCallback callback) { final Map form = new HashMap<>(); form.put("_csrftoken", csrfToken); form.put("_uid", userId); - form.put("_uuid", UUID.randomUUID().toString()); + form.put("_uuid", deviceUuid); form.put("mutation_token", UUID.randomUUID().toString()); form.put("client_context", UUID.randomUUID().toString()); form.put("radio_type", "wifi-none"); @@ -427,39 +457,67 @@ public class StoriesService extends BaseService { public void respondToQuestion(final String storyId, final String stickerId, final String answer, - final long userId, - final String csrfToken, final ServiceCallback callback) { - respondToSticker(storyId, stickerId, "story_question_response", "response", answer, userId, csrfToken, callback); + respondToSticker(storyId, stickerId, "story_question_response", "response", answer, callback); } // QuizAction.java public void respondToQuiz(final String storyId, final String stickerId, final int answer, - final long userId, - final String csrfToken, final ServiceCallback callback) { - respondToSticker(storyId, stickerId, "story_quiz_answer", "answer", String.valueOf(answer), userId, csrfToken, callback); + respondToSticker(storyId, stickerId, "story_quiz_answer", "answer", String.valueOf(answer), callback); } // VoteAction.java public void respondToPoll(final String storyId, final String stickerId, final int answer, - final long userId, - final String csrfToken, final ServiceCallback callback) { - respondToSticker(storyId, stickerId, "story_poll_vote", "vote", String.valueOf(answer), userId, csrfToken, callback); + respondToSticker(storyId, stickerId, "story_poll_vote", "vote", String.valueOf(answer), callback); } public void respondToSlider(final String storyId, final String stickerId, final double answer, - final long userId, - final String csrfToken, final ServiceCallback callback) { - respondToSticker(storyId, stickerId, "story_slider_vote", "vote", String.valueOf(answer), userId, csrfToken, callback); + respondToSticker(storyId, stickerId, "story_slider_vote", "vote", String.valueOf(answer), callback); + } + + public void seen(final String storyMediaId, + final long takenAt, + final long seenAt, + final ServiceCallback callback) { + final Map form = new HashMap<>(); + form.put("_csrftoken", csrfToken); + form.put("_uid", userId); + form.put("_uuid", deviceUuid); + form.put("container_module", "feed_timeline"); + final Map reelsForm = new HashMap<>(); + reelsForm.put(storyMediaId, Collections.singletonList(takenAt + "_" + seenAt)); + form.put("reels", reelsForm); + final Map signedForm = Utils.sign(form); + final Map queryMap = new HashMap<>(); + queryMap.put("reel", "1"); + queryMap.put("live_vod", "0"); + final Call request = repository.seen(queryMap, signedForm); + request.enqueue(new Callback() { + @Override + public void onResponse(@NonNull final Call call, + @NonNull final Response response) { + if (callback != null) { + callback.onSuccess(response.body()); + } + } + + @Override + public void onFailure(@NonNull final Call call, + @NonNull final Throwable t) { + if (callback != null) { + callback.onFailure(t); + } + } + }); } @Nullable diff --git a/app/src/main/res/layout/item_notification.xml b/app/src/main/res/layout/item_notification.xml index f17f2c6c..d12c365b 100644 --- a/app/src/main/res/layout/item_notification.xml +++ b/app/src/main/res/layout/item_notification.xml @@ -45,6 +45,21 @@ app:layout_constraintTop_toTopOf="parent" tools:text="username" /> + + + app:layout_constraintBottom_toBottomOf="@id/btnTagged" + tools:visibility="visible" + tools:foreground="@mipmap/ic_launcher" /> + - - - - + + + + + @@ -104,24 +109,40 @@ app:chipBackgroundColor="@null" app:chipIcon="@drawable/ic_outline_person_pin_24" app:chipIconTint="@color/deep_orange_800" - app:layout_constraintStart_toEndOf="@id/fav_chip" - app:layout_constraintTop_toTopOf="@id/fav_chip" + app:layout_constraintStart_toEndOf="@id/mainProfileImage" + app:layout_constraintTop_toBottomOf="@id/fav_chip" app:rippleColor="@color/deep_orange_400" tools:visibility="visible" /> + + @@ -165,14 +187,17 @@ android:id="@+id/mainBiography" android:layout_width="0dp" android:layout_height="wrap_content" - android:padding="8dp" android:background="?android:selectableItemBackground" + android:paddingStart="8dp" + android:paddingEnd="8dp" + android:paddingTop="4dp" + android:paddingBottom="4dp" android:textAppearance="@style/TextAppearance.AppCompat.Body1" app:layout_constraintBottom_toTopOf="@id/mainUrl" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/mainFullName" - tools:text="THE GLORIOUS (step)OWNER OF THIS APP. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec id justo lorem. In malesuada feugiat ornare. Suspendisse et mauris imperdiet, luctus augue eget, tempus eros. Cras vitae molestie ipsum. " /> + tools:text="One of THE GLORIOUS OWNERS OF THIS APP. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec id justo lorem. In malesuada feugiat ornare. Suspendisse et mauris imperdiet, luctus augue eget, tempus eros. Cras vitae molestie ipsum. " /> - - - + android:paddingStart="8dp" + android:paddingEnd="8dp" + android:paddingTop="4dp" + android:paddingBottom="8dp" + android:gravity="center" + android:textAppearance="@style/TextAppearance.AppCompat" + app:layout_constraintBottom_toBottomOf="@id/highlights_barrier" + app:layout_constraintEnd_toStartOf="@id/mainFollowers" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/counts_barrier" + tools:text="35\nPosts" /> - + + + app:layout_constraintStart_toEndOf="@id/mainFollowers" + app:layout_constraintTop_toBottomOf="@id/counts_barrier" + tools:text="64\nFollowing" /> + app:barrierDirection="bottom" /> + + \ No newline at end of file diff --git a/app/src/main/res/navigation/direct_messages_nav_graph.xml b/app/src/main/res/navigation/direct_messages_nav_graph.xml index d616f202..a324ff1a 100644 --- a/app/src/main/res/navigation/direct_messages_nav_graph.xml +++ b/app/src/main/res/navigation/direct_messages_nav_graph.xml @@ -41,6 +41,9 @@ android:name="type" app:argType="string" app:nullable="false" /> + diff --git a/app/src/main/res/navigation/discover_nav_graph.xml b/app/src/main/res/navigation/discover_nav_graph.xml index 7b8b83e3..3a2f8483 100644 --- a/app/src/main/res/navigation/discover_nav_graph.xml +++ b/app/src/main/res/navigation/discover_nav_graph.xml @@ -89,6 +89,9 @@ android:name="type" app:argType="string" app:nullable="false" /> + + diff --git a/app/src/main/res/navigation/more_nav_graph.xml b/app/src/main/res/navigation/more_nav_graph.xml index 6d35b9b7..a5d12d2b 100644 --- a/app/src/main/res/navigation/more_nav_graph.xml +++ b/app/src/main/res/navigation/more_nav_graph.xml @@ -54,6 +54,9 @@ android:name="type" app:argType="string" app:nullable="false" /> + + diff --git a/app/src/main/res/navigation/profile_nav_graph.xml b/app/src/main/res/navigation/profile_nav_graph.xml index e26b966e..f84af5f5 100644 --- a/app/src/main/res/navigation/profile_nav_graph.xml +++ b/app/src/main/res/navigation/profile_nav_graph.xml @@ -80,6 +80,9 @@ android:name="type" app:argType="string" app:nullable="false" /> + diff --git a/app/src/main/res/navigation/saved_nav_graph.xml b/app/src/main/res/navigation/saved_nav_graph.xml index 6813d98c..805a06e5 100644 --- a/app/src/main/res/navigation/saved_nav_graph.xml +++ b/app/src/main/res/navigation/saved_nav_graph.xml @@ -64,15 +64,6 @@ app:nullable="false" /> - - - - Import/Export Language - %s Post - %s Posts + %s\nPost + %s\nPosts - %s Follower - %s Followers + %s\nFollower + %s\nFollowers - %s Following + %s\nFollowing Autoplay videos Always mute videos Always show post captions