add suggestion chaining

This commit is contained in:
Austin Huang 2021-03-05 14:06:27 -05:00
parent 508a80be53
commit 8105462acb
No known key found for this signature in database
GPG Key ID: 84C23AA04587A91F
21 changed files with 194 additions and 52 deletions

View File

@ -59,8 +59,10 @@ public final class NotificationViewHolder extends RecyclerView.ViewHolder {
}
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);
binding.tvComment.setText(args.getText());
binding.tvComment.setVisibility(TextUtils.isEmpty(args.getText()) || args.getText().equals(args.getFullName())
? View.GONE : View.VISIBLE);
binding.tvSubComment.setText(subtext);
binding.tvSubComment.setVisibility(model.getType() == NotificationType.AYML ? View.VISIBLE : View.GONE);
} else if (text != -1) {
binding.tvComment.setText(text);
@ -72,6 +74,8 @@ public final class NotificationViewHolder extends RecyclerView.ViewHolder {
binding.tvDate.setText(args.getDateTime());
}
binding.isVerified.setVisibility(args.isVerified() ? View.VISIBLE : View.GONE);
binding.tvUsername.setText(args.getUsername());
binding.ivProfilePic.setImageURI(args.getProfilePic());
binding.ivProfilePic.setOnClickListener(v -> {

View File

@ -115,7 +115,8 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod
owner.getString("profile_pic_url"),
null,
new FriendshipStatus(false, false, false, false, false, false, false, false, false, false),
false, false, false, false, false, null, null, 0, 0, 0, 0, null, null, 0, null, null, null, null);
false, false, false, false, false, null, null, 0, 0, 0, 0, null, null, 0, null, null, null,
null, null);
final JSONObject likedBy = childComment.optJSONObject("edge_liked_by");
commentModels.add(new CommentModel(childComment.getString(Constants.EXTRAS_ID),
childComment.getString("text"),
@ -193,7 +194,8 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod
null,
new FriendshipStatus(false, false, false, false, false, false, false, false, false, false),
owner.optBoolean("is_verified"),
false, false, false, false, null, null, 0, 0, 0, 0, null, null, 0, null, null, null, null);
false, false, false, false, null, null, 0, 0, 0, 0, null, null, 0, null, null, null, null,
null);
final JSONObject likedBy = comment.optJSONObject("edge_liked_by");
final String commentId = comment.getString(Constants.EXTRAS_ID);
final CommentModel commentModel = new CommentModel(commentId,
@ -235,7 +237,7 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod
null,
new FriendshipStatus(false, false, false, false, false, false, false, false, false, false),
tempJsonObject.optBoolean("is_verified"), false, false, false, false, null, null, 0, 0, 0, 0, null, null, 0,
null, null, null, null);
null, null, null, null, null);
tempJsonObject = childComment.optJSONObject("edge_liked_by");
childCommentModels.add(new CommentModel(childComment.getString(Constants.EXTRAS_ID),

View File

@ -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;
@ -55,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<List<Notification>> cb = new ServiceCallback<List<Notification>>() {
@Override
public void onSuccess(final List<Notification> 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) {
@ -181,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);
@ -190,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
@ -217,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);
@ -231,8 +256,10 @@ 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":
if (actionBar != null) actionBar.setTitle(R.string.action_notif);
new NotificationsFetcher(true, new FetchListener<List<Notification>>() {
@Override
public void onResult(final List<Notification> notificationModels) {
@ -251,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<List<Notification>>() {
@Override
public void onSuccess(final List<Notification> 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;
}
}

View File

@ -56,7 +56,6 @@ import awais.instagrabber.R;
import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.adapters.HighlightsAdapter;
import awais.instagrabber.asyncs.CreateThreadAction;
import awais.instagrabber.asyncs.ProfileFetcher;
import awais.instagrabber.asyncs.ProfilePostFetchService;
import awais.instagrabber.asyncs.UsernameFetcher;
@ -125,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<Media> selectedFeedModels;
@ -364,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);
}
}
}
@ -433,6 +453,12 @@ 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);
}
@ -884,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;
}
}

View File

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

View File

@ -4,6 +4,7 @@ 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;
@ -23,4 +24,7 @@ public interface NewsRepository {
@FormUrlEncoded
@POST("/api/v1/discover/ayml/")
Call<AymlResponse> getAyml(@Header("User-Agent") String userAgent, @FieldMap final Map<String, String> form);
@GET("/api/v1/discover/chaining/")
Call<UserSearchResponse> getChaining(@Header("User-Agent") String userAgent, @Query(value = "target_id") long targetId);
}

View File

@ -20,6 +20,7 @@ public class NotificationArgs {
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
@ -28,7 +29,8 @@ public class NotificationArgs {
final List<NotificationImage> media,
final double timestamp,
final String profileName,
final String fullName) {
final String fullName,
final boolean isVerified) {
this.text = text;
this.richText = richText;
this.profileId = profileId;
@ -37,6 +39,7 @@ public class NotificationArgs {
this.timestamp = timestamp;
this.profileName = profileName;
this.fullName = fullName;
this.isVerified = isVerified;
}
public String getText() {
@ -67,6 +70,10 @@ public class NotificationArgs {
return timestamp;
}
public boolean isVerified() {
return isVerified;
}
@NonNull
public String getDateTime() {
return Utils.datetimeParser.format(new Date(Math.round(timestamp * 1000)));

View File

@ -30,6 +30,7 @@ public class User implements Serializable {
private final HdProfilePicUrlInfo hdProfilePicUrlInfo;
private final String profileContext;
private final List<UserProfileContextLink> profileContextLinksWithUserIds;
private final String socialContext;
public User(final long pk,
final String username,
@ -55,7 +56,8 @@ public class User implements Serializable {
final String publicEmail,
final HdProfilePicUrlInfo hdProfilePicUrlInfo,
final String profileContext,
final List<UserProfileContextLink> profileContextLinksWithUserIds) {
final List<UserProfileContextLink> profileContextLinksWithUserIds,
final String socialContext) {
this.pk = pk;
this.username = username;
this.fullName = fullName;
@ -81,6 +83,7 @@ public class User implements Serializable {
this.hdProfilePicUrlInfo = hdProfilePicUrlInfo;
this.profileContext = profileContext;
this.profileContextLinksWithUserIds = profileContextLinksWithUserIds;
this.socialContext = socialContext;
}
public long getPk() {
@ -183,6 +186,10 @@ public class User implements Serializable {
return profileContext;
}
public String getSocialContext() {
return socialContext;
}
public List<UserProfileContextLink> getProfileContextLinks() {
return profileContextLinksWithUserIds;
}

View File

@ -782,7 +782,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, 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(

View File

@ -245,6 +245,7 @@ public class GraphQLService extends BaseService {
null,
null,
null,
null,
null
));
// userModels.add(new ProfileModel(userObject.optBoolean("is_private"),
@ -338,6 +339,7 @@ public class GraphQLService extends BaseService {
null,
null,
null,
null,
null));
} catch (JSONException e) {
Log.e(TAG, "onResponse", e);

View File

@ -21,6 +21,7 @@ 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;
@ -126,7 +127,8 @@ public class NewsService extends BaseService {
)),
data.getLong("timestamp"),
user.getString("username"),
null
null,
false
),
type,
data.getString(Constants.EXTRAS_ID)
@ -150,7 +152,8 @@ public class NewsService extends BaseService {
null,
0L,
data.getString("username"),
data.optString("full_name")
data.optString("full_name"),
data.optBoolean("is_verified")
),
"REQUEST",
data.getString(Constants.EXTRAS_ID)
@ -172,6 +175,7 @@ public class NewsService extends BaseService {
}
public void fetchSuggestions(final String csrfToken,
final String deviceUuid,
final ServiceCallback<List<Notification>> callback) {
final Map<String, String> form = new HashMap<>();
form.put("_uuid", UUID.randomUUID().toString());
@ -205,7 +209,8 @@ public class NewsService extends BaseService {
null,
0L,
u.getUsername(),
u.getFullName()
u.getFullName(),
u.isVerified()
),
"AYML",
i.getUuid()
@ -222,4 +227,45 @@ public class NewsService extends BaseService {
}
});
}
public void fetchChaining(final long targetId, final ServiceCallback<List<Notification>> callback) {
final Call<UserSearchResponse> request = repository.getChaining(appUa, targetId);
request.enqueue(new Callback<UserSearchResponse>() {
@Override
public void onResponse(@NonNull final Call<UserSearchResponse> call, @NonNull final Response<UserSearchResponse> response) {
final UserSearchResponse body = response.body();
if (body == null) {
callback.onSuccess(null);
return;
}
final List<Notification> 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<UserSearchResponse> call, @NonNull final Throwable t) {
callback.onFailure(t);
// Log.e(TAG, "onFailure: ", t);
}
});
}
}

View File

@ -174,6 +174,7 @@ public class StoriesService extends BaseService {
null,
null,
null,
null,
null
);
final String id = node.getString("id");
@ -235,6 +236,7 @@ public class StoriesService extends BaseService {
null,
null,
null,
null,
null
);
final String id = node.getString("id");

View File

@ -45,6 +45,21 @@
app:layout_constraintTop_toTopOf="parent"
tools:text="username" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/isVerified"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:paddingEnd="16dp"
android:paddingRight="16dp"
android:adjustViewBounds="true"
android:scaleType="fitStart"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/tvComment"
app:layout_constraintEnd_toStartOf="@id/ivPreviewPic"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/verified"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvComment"
android:layout_width="0dp"

View File

@ -20,4 +20,10 @@
android:title="@string/restrict"
android:visible="false"
app:showAsAction="never" />
<item
android:id="@+id/chaining"
android:title="@string/action_ayml"
android:visible="false"
app:showAsAction="never" />
</menu>

View File

@ -41,6 +41,9 @@
android:name="type"
app:argType="string"
app:nullable="false" />
<argument
android:name="targetId"
app:argType="long" />
</action>
<include app:graph="@navigation/comments_nav_graph" />

View File

@ -89,6 +89,9 @@
android:name="type"
app:argType="string"
app:nullable="false" />
<argument
android:name="targetId"
app:argType="long" />
</action>
<fragment

View File

@ -89,6 +89,9 @@
android:name="type"
app:argType="string"
app:nullable="false" />
<argument
android:name="targetId"
app:argType="long" />
</action>
<include app:graph="@navigation/story_list_nav_graph" />

View File

@ -54,6 +54,9 @@
android:name="type"
app:argType="string"
app:nullable="false" />
<argument
android:name="targetId"
app:argType="long" />
</action>
<fragment

View File

@ -14,6 +14,9 @@
android:name="type"
app:argType="string"
app:nullable="false" />
<argument
android:name="targetId"
app:argType="long" />
<action
android:id="@+id/action_notificationsViewerFragment_to_storyViewerFragment"
app:destination="@id/storyViewerFragment" />

View File

@ -80,6 +80,9 @@
android:name="type"
app:argType="string"
app:nullable="false" />
<argument
android:name="targetId"
app:argType="long" />
</action>
<include app:graph="@navigation/saved_nav_graph" />

View File

@ -64,15 +64,6 @@
app:nullable="false" />
</action>
<action
android:id="@+id/action_global_notificationsViewerFragment"
app:destination="@id/notification_viewer_nav_graph">
<argument
android:name="type"
app:argType="string"
app:nullable="false" />
</action>
<fragment
android:id="@+id/savedCollectionsFragment"
android:name="awais.instagrabber.fragments.SavedCollectionsFragment"