notification backend revamp

This commit is contained in:
Austin Huang 2021-03-04 15:27:46 -05:00
parent 5421bb4592
commit 7acd5faefd
No known key found for this signature in database
GPG Key ID: 84C23AA04587A91F
19 changed files with 452 additions and 461 deletions

View File

@ -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<NotificationModel, NotificationViewHolder> {
public final class NotificationsAdapter extends ListAdapter<Notification, NotificationViewHolder> {
private final OnNotificationClickListener notificationClickListener;
private static final DiffUtil.ItemCallback<NotificationModel> DIFF_CALLBACK = new DiffUtil.ItemCallback<NotificationModel>() {
private static final DiffUtil.ItemCallback<Notification> DIFF_CALLBACK = new DiffUtil.ItemCallback<Notification>() {
@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<NotificationModel, N
@Override
public void onBindViewHolder(@NonNull final NotificationViewHolder holder, final int position) {
final NotificationModel notificationModel = getItem(position);
holder.bind(notificationModel, notificationClickListener);
final Notification Notification = getItem(position);
holder.bind(Notification, notificationClickListener);
}
@Override
public void submitList(@Nullable final List<NotificationModel> list, @Nullable final Runnable commitCallback) {
public void submitList(@Nullable final List<Notification> list, @Nullable final Runnable commitCallback) {
if (list == null) {
super.submitList(null, commitCallback);
return;
@ -61,7 +62,7 @@ public final class NotificationsAdapter extends ListAdapter<NotificationModel, N
}
@Override
public void submitList(@Nullable final List<NotificationModel> list) {
public void submitList(@Nullable final List<Notification> list) {
if (list == null) {
super.submitList(null);
return;
@ -69,8 +70,10 @@ public final class NotificationsAdapter extends ListAdapter<NotificationModel, N
super.submitList(sort(list));
}
private List<NotificationModel> sort(final List<NotificationModel> list) {
final List<NotificationModel> listCopy = new ArrayList<>(list);
private List<Notification> sort(final List<Notification> list) {
final List<Notification> 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<NotificationModel, N
else if (o1.getType() == NotificationType.REQUEST) return -1;
else if (o2.getType() == NotificationType.REQUEST) return 1;
// timestamp
return Long.compare(o2.getTimestamp(), o1.getTimestamp());
return Double.compare(o2.getArgs().getTimestamp(), o1.getArgs().getTimestamp());
});
return listCopy;
}
public interface OnNotificationClickListener {
void onNotificationClick(final NotificationModel model);
void onNotificationClick(final Notification model);
void onProfileClick(final String username);
void onPreviewClick(final NotificationModel model);
void onPreviewClick(final Notification model);
}
}

View File

@ -8,8 +8,9 @@ import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.R;
import awais.instagrabber.adapters.NotificationsAdapter.OnNotificationClickListener;
import awais.instagrabber.databinding.ItemNotificationBinding;
import awais.instagrabber.models.NotificationModel;
import awais.instagrabber.models.enums.NotificationType;
import awais.instagrabber.repositories.responses.Notification;
import awais.instagrabber.repositories.responses.NotificationArgs;
public final class NotificationViewHolder extends RecyclerView.ViewHolder {
private final ItemNotificationBinding binding;
@ -19,22 +20,23 @@ public final class NotificationViewHolder extends RecyclerView.ViewHolder {
this.binding = binding;
}
public void bind(final NotificationModel model,
public void bind(final Notification model,
final OnNotificationClickListener notificationClickListener) {
if (model == null) return;
int text = -1;
CharSequence subtext = null;
final NotificationArgs args = model.getArgs();
switch (model.getType()) {
case LIKE:
text = R.string.liked_notif;
break;
case COMMENT:
text = R.string.comment_notif;
subtext = model.getText();
subtext = args.getText();
break;
case COMMENT_MENTION:
text = R.string.mention_notif;
subtext = model.getText();
subtext = args.getText();
break;
case TAGGED:
text = R.string.tagged_notif;
@ -44,18 +46,18 @@ public final class NotificationViewHolder extends RecyclerView.ViewHolder {
break;
case REQUEST:
text = R.string.request_notif;
subtext = model.getText();
subtext = args.getText();
break;
case COMMENT_LIKE:
case TAGGED_COMMENT:
case RESPONDED_STORY:
subtext = model.getText();
subtext = args.getText();
break;
case AYML:
subtext = model.getPreviewPic();
subtext = args.getFullName();
break;
}
binding.tvSubComment.setText(model.getType() == NotificationType.AYML ? model.getText() : subtext);
binding.tvSubComment.setText(model.getType() == NotificationType.AYML ? args.getText() : subtext);
if (text == -1 && subtext != null) {
binding.tvComment.setText(subtext);
binding.tvComment.setVisibility(TextUtils.isEmpty(subtext) ? View.GONE : View.VISIBLE);
@ -65,24 +67,25 @@ public final class NotificationViewHolder extends RecyclerView.ViewHolder {
binding.tvSubComment.setVisibility(subtext == null ? View.GONE : View.VISIBLE);
}
binding.tvDate.setVisibility(model.getType() == NotificationType.AYML ? View.GONE : View.VISIBLE);
if (model.getType() != NotificationType.REQUEST && model.getType() != NotificationType.AYML) {
binding.tvDate.setText(model.getDateTime());
binding.tvDate.setText(args.getDateTime());
}
binding.tvUsername.setText(model.getUsername());
binding.ivProfilePic.setImageURI(model.getProfilePic());
binding.tvUsername.setText(args.getUsername());
binding.ivProfilePic.setImageURI(args.getProfilePic());
binding.ivProfilePic.setOnClickListener(v -> {
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);

View File

@ -1,139 +0,0 @@
package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import android.util.Log;
import androidx.annotation.NonNull;
import org.json.JSONObject;
import java.net.HttpURLConnection;
import java.net.URL;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.NetworkUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
public class GetActivityAsyncTask extends AsyncTask<String, Void, GetActivityAsyncTask.NotificationCounts> {
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);
}
}

View File

@ -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<Void, Void, List<NotificationModel>> {
public final class NotificationsFetcher extends AsyncTask<Void, Void, List<Notification>> {
private static final String TAG = "NotificationsFetcher";
private final FetchListener<List<NotificationModel>> fetchListener;
private final FetchListener<List<Notification>> fetchListener;
private final NewsService newsService;
private final boolean markAsSeen;
private boolean fetchedWeb = false;
public NotificationsFetcher(final boolean markAsSeen,
final FetchListener<List<NotificationModel>> fetchListener) {
final FetchListener<List<Notification>> fetchListener) {
this.markAsSeen = markAsSeen;
this.fetchListener = fetchListener;
newsService = NewsService.getInstance();
}
@Override
protected List<NotificationModel> doInBackground(final Void... voids) {
List<NotificationModel> notificationModels = new ArrayList<>();
protected List<Notification> doInBackground(final Void... voids) {
List<Notification> notificationModels = new ArrayList<>();
newsService.fetchAppInbox(markAsSeen, new ServiceCallback<List<NotificationModel>>() {
newsService.fetchAppInbox(markAsSeen, new ServiceCallback<List<Notification>>() {
@Override
public void onSuccess(final List<NotificationModel> result) {
public void onSuccess(final List<Notification> result) {
if (result == null) return;
notificationModels.addAll(result);
if (fetchedWeb) {
@ -44,7 +44,7 @@ public final class NotificationsFetcher extends AsyncTask<Void, Void, List<Notif
}
else {
fetchedWeb = true;
newsService.fetchWebInbox(markAsSeen, this);
newsService.fetchWebInbox(this);
}
}

View File

@ -33,11 +33,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;
@ -70,14 +72,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<Media>() {
mediaService.fetch(mediaId, new ServiceCallback<Media>() {
@Override
public void onSuccess(final Media feedModel) {
final PostViewV2Fragment fragment = PostViewV2Fragment
@ -95,13 +101,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 +117,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 +138,7 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
break;
case 1:
if (model.getType() == NotificationType.REQUEST) {
friendshipService.approve(model.getUserId(), new ServiceCallback<FriendshipChangeResponse>() {
friendshipService.approve(args.getUserId(), new ServiceCallback<FriendshipChangeResponse>() {
@Override
public void onSuccess(final FriendshipChangeResponse result) {
onRefresh();
@ -148,7 +155,7 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
clickListener.onPreviewClick(model);
break;
case 2:
friendshipService.ignore(model.getUserId(), new ServiceCallback<FriendshipChangeResponse>() {
friendshipService.ignore(args.getUserId(), new ServiceCallback<FriendshipChangeResponse>() {
@Override
public void onSuccess(final FriendshipChangeResponse result) {
onRefresh();
@ -226,9 +233,9 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
binding.swipeRefreshLayout.setRefreshing(true);
switch (type) {
case "notif":
new NotificationsFetcher(true, new FetchListener<List<NotificationModel>>() {
new NotificationsFetcher(true, new FetchListener<List<Notification>>() {
@Override
public void onResult(final List<NotificationModel> notificationModels) {
public void onResult(final List<Notification> notificationModels) {
binding.swipeRefreshLayout.setRefreshing(false);
notificationViewModel.getList().postValue(notificationModels);
}
@ -245,9 +252,9 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
break;
case "ayml":
final NewsService newsService = NewsService.getInstance();
newsService.fetchSuggestions(csrfToken, new ServiceCallback<List<NotificationModel>>() {
newsService.fetchSuggestions(csrfToken, new ServiceCallback<List<Notification>>() {
@Override
public void onSuccess(final List<NotificationModel> notificationModels) {
public void onSuccess(final List<Notification> notificationModels) {
binding.swipeRefreshLayout.setRefreshing(false);
notificationViewModel.getList().postValue(notificationModels);
}

View File

@ -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; }
}

View File

@ -2,6 +2,8 @@ package awais.instagrabber.repositories;
import java.util.Map;
import awais.instagrabber.repositories.responses.AymlResponse;
import awais.instagrabber.repositories.responses.NewsInboxResponse;
import retrofit2.Call;
import retrofit2.http.FieldMap;
import retrofit2.http.FormUrlEncoded;
@ -16,9 +18,9 @@ public interface NewsRepository {
Call<String> webInbox(@Header("User-Agent") String userAgent);
@GET("/api/v1/news/inbox/")
Call<String> appInbox(@Header("User-Agent") String userAgent, @Query(value = "mark_as_seen", encoded = true) boolean markAsSeen);
Call<NewsInboxResponse> appInbox(@Header("User-Agent") String userAgent, @Query(value = "mark_as_seen", encoded = true) boolean markAsSeen);
@FormUrlEncoded
@POST("/api/v1/discover/ayml/")
Call<String> getAyml(@Header("User-Agent") String userAgent, @FieldMap final Map<String, String> form);
Call<AymlResponse> getAyml(@Header("User-Agent") String userAgent, @FieldMap final Map<String, String> form);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,15 @@
package awais.instagrabber.repositories.responses;
import java.util.List;
public class AymlUserList {
private final List<AymlUser> suggestions;
public AymlUserList(final List<AymlUser> suggestions) {
this.suggestions = suggestions;
}
public List<AymlUser> getSuggestions() {
return suggestions;
}
}

View File

@ -0,0 +1,29 @@
package awais.instagrabber.repositories.responses;
import java.util.List;
public class NewsInboxResponse {
private final NotificationCounts counts;
private final List<Notification> newStories;
private final List<Notification> oldStories;
public NewsInboxResponse(final NotificationCounts counts,
final List<Notification> newStories,
final List<Notification> oldStories) {
this.counts = counts;
this.newStories = newStories;
this.oldStories = oldStories;
}
public NotificationCounts getCounts() {
return counts;
}
public List<Notification> getNewStories() {
return newStories;
}
public List<Notification> getOldStories() {
return oldStories;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,86 @@
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<NotificationImage> media;
private final double timestamp;
private final String profileName;
private final String fullName; // for AYML, not naturally generated
public NotificationArgs(final String text,
final String richText, // for AYML, this is the algorithm
final long profileId,
final String profileImage,
final List<NotificationImage> media,
final double timestamp,
final String profileName,
final String fullName) {
this.text = text;
this.richText = richText;
this.profileId = profileId;
this.profileImage = profileImage;
this.media = media;
this.timestamp = timestamp;
this.profileName = profileName;
this.fullName = fullName;
}
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<NotificationImage> getMedia() {
return media;
}
public double getTimestamp() {
return timestamp;
}
@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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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 + '\'' +
'}';
}
}

View File

@ -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<String> 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)

View File

@ -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<NotificationModel>> list;
private MutableLiveData<List<Notification>> list;
public MutableLiveData<List<NotificationModel>> getList() {
public MutableLiveData<List<Notification>> getList() {
if (list == null) {
list = new MutableLiveData<>();
}

View File

@ -9,17 +9,23 @@ 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.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 +58,31 @@ public class NewsService extends BaseService {
}
public void fetchAppInbox(final boolean markAsSeen,
final ServiceCallback<List<NotificationModel>> callback) {
final List<NotificationModel> result = new ArrayList<>();
final Call<String> request = repository.appInbox(appUa, markAsSeen);
request.enqueue(new Callback<String>() {
final ServiceCallback<List<Notification>> callback) {
final Call<NewsInboxResponse> request = repository.appInbox(appUa, markAsSeen);
request.enqueue(new Callback<NewsInboxResponse>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String body = response.body();
public void onResponse(@NonNull final Call<NewsInboxResponse> call, @NonNull final Response<NewsInboxResponse> 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<Notification> result = new ArrayList<>();
result.addAll(body.getNewStories());
result.addAll(body.getOldStories());
callback.onSuccess(result);
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
public void onFailure(@NonNull final Call<NewsInboxResponse> call, @NonNull final Throwable t) {
callback.onFailure(t);
// Log.e(TAG, "onFailure: ", t);
}
});
}
public void fetchWebInbox(final boolean markAsSeen,
final ServiceCallback<List<NotificationModel>> callback) {
public void fetchWebInbox(final ServiceCallback<List<Notification>> callback) {
final Call<String> request = repository.webInbox(browserUa);
request.enqueue(new Callback<String>() {
@Override
@ -104,7 +93,7 @@ public class NewsService extends BaseService {
return;
}
try {
final List<NotificationModel> result = new ArrayList<>();
final List<Notification> result = new ArrayList<>();
final JSONObject page = new JSONObject(body)
.getJSONObject("graphql")
.getJSONObject("user");
@ -124,16 +113,24 @@ 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
),
type,
data.getString(Constants.EXTRAS_ID)
));
}
}
@ -144,15 +141,20 @@ 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")
),
"REQUEST",
data.getString(Constants.EXTRAS_ID)
));
}
}
callback.onSuccess(result);
@ -169,40 +171,8 @@ 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<List<NotificationModel>> callback) {
final ServiceCallback<List<Notification>> callback) {
final Map<String, String> form = new HashMap<>();
form.put("_uuid", UUID.randomUUID().toString());
form.put("_csrftoken", csrfToken);
@ -210,57 +180,46 @@ public class NewsService extends BaseService {
form.put("device_id", UUID.randomUUID().toString());
form.put("module", "discover_people");
form.put("paginate", "false");
final Call<String> request = repository.getAyml(appUa, form);
request.enqueue(new Callback<String>() {
final Call<AymlResponse> request = repository.getAyml(appUa, form);
request.enqueue(new Callback<AymlResponse>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String body = response.body();
public void onResponse(@NonNull final Call<AymlResponse> call, @NonNull final Response<AymlResponse> response) {
final AymlResponse body = response.body();
if (body == null) {
callback.onSuccess(null);
return;
}
try {
final List<NotificationModel> 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<AymlUser> 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<Notification> 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()
),
"AYML",
i.getUuid()
);
})
.collect(Collectors.toList());
callback.onSuccess(newsItems);
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
public void onFailure(@NonNull final Call<AymlResponse> 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);
}
}